From f5914e8c10ab864b45756e8cb5687376581191d5 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 30 Oct 2018 16:19:59 +1100 Subject: [PATCH 01/71] Engine changes --- Dockerfile | 2 +- {tools => cmd}/config/config.go | 0 {tools => cmd}/config/config_test.go | 0 cmd/config_builder/builder.go | 51 + .../common_templates/common_readme.tmpl | 0 .../communications_templates/base.tmpl | 0 .../communications_templates/comms.tmpl | 0 .../communications_templates/slack.tmpl | 0 .../communications_templates/smsglobal.tmpl | 0 .../communications_templates/smtp.tmpl | 0 .../communications_templates/telegram.tmpl | 0 .../config_templates/config_readme.tmpl | 0 .../currency_pair_readme.tmpl | 0 .../currency_templates/currency_readme.tmpl | 0 .../currency_symbol_readme.tmpl | 0 .../currency_translation_readme.tmpl | 0 .../documentation/currency_templates/fx.tmpl | 0 .../currency_templates/fx_base.tmpl | 0 .../fx_currencyconverterapi.tmpl | 0 .../currency_templates/fx_currencylayer.tmpl | 0 .../currency_templates/fx_fixer.tmpl | 0 .../fx_openexchangerates.tmpl | 0 {tools => cmd}/documentation/documentation.go | 0 .../events_templates/events_readme.tmpl | 0 .../exchanges_templates/alphapoint.tmpl | 0 .../exchanges_templates/anx.tmpl | 10 +- .../exchanges_templates/binance.tmpl | 10 +- .../exchanges_templates/bitfinex.tmpl | 10 +- .../exchanges_templates/bitflyer.tmpl | 10 +- .../exchanges_templates/bithumb.tmpl | 10 +- .../exchanges_templates/bitmex.tmpl | 10 +- .../exchanges_templates/bitstamp.tmpl | 10 +- .../exchanges_templates/bittrex.tmpl | 10 +- .../exchanges_templates/btcc.tmpl | 10 +- .../exchanges_templates/btcmarkets.tmpl | 10 +- .../exchanges_templates/coinbasepro.tmpl | 10 +- .../exchanges_templates/coinut.tmpl | 10 +- .../exchanges_orderbook_readme.tmpl | 2 +- .../exchanges_templates/exchanges_readme.tmpl | 0 .../exchanges_stats_readme.tmpl | 0 .../exchanges_ticker_readme.tmpl | 2 +- .../exchanges_templates/exmo.tmpl | 10 +- .../exchanges_templates/gateio.tmpl | 10 +- .../exchanges_templates/gemini.tmpl | 10 +- .../exchanges_templates/hitbtc.tmpl | 10 +- .../exchanges_templates/huobi.tmpl | 10 +- .../exchanges_templates/huobihadax.tmpl | 10 +- .../exchanges_templates/itbit.tmpl | 10 +- .../exchanges_templates/kraken.tmpl | 10 +- .../exchanges_templates/lakebtc.tmpl | 10 +- .../exchanges_templates/localbitcoins.tmpl | 10 +- .../exchanges_templates/nonce.tmpl | 0 .../exchanges_templates/okcoin.tmpl | 10 +- .../exchanges_templates/okex.tmpl | 10 +- .../exchanges_templates/orders.tmpl | 0 .../exchanges_templates/poloniex.tmpl | 10 +- .../exchanges_templates/request.tmpl | 0 .../exchanges_templates/yobit.tmpl | 10 +- .../documentation/exchanges_templates/zb.tmpl | 10 +- .../portfolio_templates/portfolio_readme.tmpl | 0 .../documentation/root_templates/CONTRIBUTORS | 0 .../documentation/root_templates/LICENSE | 0 .../root_templates/root_readme.tmpl | 0 .../sub_templates/contributions.tmpl | 0 .../sub_templates/contributors.tmpl | 0 .../sub_templates/donations.tmpl | 0 .../documentation/sub_templates/header.tmpl | 0 .../documentation/sub_templates/status.tmpl | 0 .../testdata_templates/testdata_readme.tmpl | 0 .../tools_templates/config_tool.tmpl | 0 .../tools_templates/documentation_tool.tmpl | 0 .../tools_templates/exchange_tool.tmpl | 0 .../tools_templates/huobi_auth_tool.tmpl | 0 .../tools_templates/portfolio_tool.tmpl | 0 .../documentation/tools_templates/tools.tmpl | 0 .../websocket_client_tool.tmpl | 0 .../web_templates/web_readme.tmpl | 0 .../exchange_template/exchange_template.go | 15 +- .../exchange_template/main_file.tmpl | 22 +- .../exchange_template/readme_file.tmpl | 0 .../exchange_template/test_file.tmpl | 6 +- .../exchange_template/type_file.tmpl | 0 .../exchange_template/wrapper_file.tmpl | 16 +- cmd/exchange_wrapper_coverage/main.go | 174 + cmd/gctcli/commands.go | 1502 +++++ cmd/gctcli/main.go | 115 + cmd/gen_cert/main.go | 105 + {tools => cmd}/huobi_auth/main.go | 11 +- cmd/otp_gen/otp_gen.go | 57 + {tools => cmd}/portfolio/portfolio.go | 6 +- {tools => cmd}/portfolio/portfolio_test.go | 0 {tools => cmd}/websocket_client/main.go | 16 +- common/common.go | 159 +- common/common_test.go | 253 - common/crypto/crypto.go | 113 + common/crypto/crypto_test.go | 180 + common/math/math.go | 51 + common/math/math_test.go | 81 + communications/base/base.go | 5 +- communications/base/base_interface.go | 13 +- communications/slack/README.md | 32 + config/config.go | 853 +-- config/config_encryption.go | 5 +- config/config_test.go | 175 +- config/config_types.go | 346 ++ core/banner.go | 11 + version.go => core/version.go | 23 +- currency/coinmarketcap/coinmarketcap.go | 11 +- currency/manager.go | 107 + currency/manager_test.go | 140 + currency/manager_types.go | 34 + docker-compose.yml | 3 +- engine/engine.go | 456 ++ engine/engine_types.go | 58 + {events => engine/events}/event_test.go | 34 +- {events => engine/events}/events.go | 202 +- exchange.go => engine/exchange.go | 103 +- exchange_test.go => engine/exchange_test.go | 15 +- engine/helpers.go | 745 +++ helpers_test.go => engine/helpers_test.go | 75 +- engine/orders.go | 47 + engine/restful_router.go | 107 + engine/restful_server.go | 129 + .../restful_server_test.go | 9 +- engine/restful_types.go | 46 + routines.go => engine/routines.go | 188 +- engine/rpcserver.go | 678 +++ engine/syncer.go | 577 ++ engine/syncer_test.go | 46 + engine/syncer_types.go | 61 + websocket.go => engine/websocket.go | 79 +- engine/websocket_types.go | 49 + events/README.md | 45 - exchanges/alphapoint/alphapoint.go | 35 +- exchanges/alphapoint/alphapoint_test.go | 40 +- exchanges/alphapoint/alphapoint_websocket.go | 2 +- exchanges/alphapoint/alphapoint_wrapper.go | 81 +- exchanges/anx/README.md | 10 +- exchanges/anx/anx.go | 95 +- exchanges/anx/anx_test.go | 71 +- exchanges/anx/anx_wrapper.go | 203 +- exchanges/assets/assets.go | 114 + exchanges/assets/assets_test.go | 81 + exchanges/binance/README.md | 10 +- exchanges/binance/binance.go | 202 +- exchanges/binance/binance_test.go | 42 +- exchanges/binance/binance_types.go | 13 +- exchanges/binance/binance_websocket.go | 28 +- exchanges/binance/binance_wrapper.go | 251 +- exchanges/bitfinex/README.md | 10 +- exchanges/bitfinex/bitfinex.go | 141 +- exchanges/bitfinex/bitfinex_test.go | 126 +- exchanges/bitfinex/bitfinex_websocket.go | 33 +- exchanges/bitfinex/bitfinex_wrapper.go | 172 +- exchanges/bitflyer/README.md | 10 +- exchanges/bitflyer/bitflyer.go | 97 +- exchanges/bitflyer/bitflyer_test.go | 41 +- exchanges/bitflyer/bitflyer_wrapper.go | 175 +- exchanges/bithumb/README.md | 10 +- exchanges/bithumb/bithumb.go | 93 +- exchanges/bithumb/bithumb_test.go | 41 +- exchanges/bithumb/bithumb_wrapper.go | 165 +- exchanges/bitmex/README.md | 10 +- exchanges/bitmex/bitmex.go | 97 +- exchanges/bitmex/bitmex_test.go | 38 +- exchanges/bitmex/bitmex_websocket.go | 21 +- exchanges/bitmex/bitmex_wrapper.go | 256 +- exchanges/bitstamp/README.md | 10 +- exchanges/bitstamp/bitstamp.go | 121 +- exchanges/bitstamp/bitstamp_test.go | 76 +- exchanges/bitstamp/bitstamp_websocket.go | 16 +- exchanges/bitstamp/bitstamp_wrapper.go | 195 +- exchanges/bittrex/README.md | 10 +- exchanges/bittrex/bittrex.go | 117 +- exchanges/bittrex/bittrex_test.go | 37 +- exchanges/bittrex/bittrex_wrapper.go | 210 +- exchanges/btcc/README.md | 10 +- exchanges/btcc/btcc.go | 82 - exchanges/btcc/btcc_test.go | 32 +- exchanges/btcc/btcc_websocket.go | 20 +- exchanges/btcc/btcc_wrapper.go | 150 +- exchanges/btcmarkets/README.md | 10 +- exchanges/btcmarkets/btcmarkets.go | 95 +- exchanges/btcmarkets/btcmarkets_test.go | 42 +- exchanges/btcmarkets/btcmarkets_types.go | 2 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 203 +- exchanges/btse/btse.go | 93 +- exchanges/btse/btse_test.go | 14 +- exchanges/btse/btse_websocket.go | 10 +- exchanges/btse/btse_wrapper.go | 188 +- exchanges/coinbasepro/README.md | 10 +- exchanges/coinbasepro/coinbasepro.go | 130 +- exchanges/coinbasepro/coinbasepro_test.go | 53 +- .../coinbasepro/coinbasepro_websocket.go | 22 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 199 +- exchanges/coinut/README.md | 10 +- exchanges/coinut/coinut.go | 102 +- exchanges/coinut/coinut_test.go | 39 +- exchanges/coinut/coinut_websocket.go | 22 +- exchanges/coinut/coinut_wrapper.go | 191 +- exchanges/exchange.go | 1251 ++--- exchanges/exchange_test.go | 741 +-- exchanges/exchange_types.go | 643 +++ exchanges/exmo/README.md | 10 +- exchanges/exmo/exmo.go | 94 +- exchanges/exmo/exmo_test.go | 38 +- exchanges/exmo/exmo_wrapper.go | 180 +- exchanges/gateio/README.md | 10 +- exchanges/gateio/gateio.go | 118 +- exchanges/gateio/gateio_test.go | 37 +- exchanges/gateio/gateio_types.go | 19 +- exchanges/gateio/gateio_websocket.go | 27 +- exchanges/gateio/gateio_wrapper.go | 198 +- exchanges/gemini/README.md | 10 +- exchanges/gemini/gemini.go | 121 +- exchanges/gemini/gemini_test.go | 51 +- exchanges/gemini/gemini_websocket.go | 16 +- exchanges/gemini/gemini_wrapper.go | 176 +- exchanges/hitbtc/README.md | 10 +- exchanges/hitbtc/hitbtc.go | 121 +- exchanges/hitbtc/hitbtc_test.go | 35 +- exchanges/hitbtc/hitbtc_websocket.go | 14 +- exchanges/hitbtc/hitbtc_wrapper.go | 223 +- exchanges/huobi/README.md | 10 +- exchanges/huobi/huobi.go | 127 +- exchanges/huobi/huobi_test.go | 89 +- exchanges/huobi/huobi_websocket.go | 9 +- exchanges/huobi/huobi_wrapper.go | 270 +- exchanges/huobihadax/README.md | 10 +- exchanges/huobihadax/huobihadax.go | 126 +- exchanges/huobihadax/huobihadax_test.go | 127 +- exchanges/huobihadax/huobihadax_websocket.go | 9 +- exchanges/huobihadax/huobihadax_wrapper.go | 200 +- exchanges/interfaces.go | 64 + exchanges/itbit/README.md | 10 +- exchanges/itbit/itbit.go | 97 +- exchanges/itbit/itbit_test.go | 38 +- exchanges/itbit/itbit_wrapper.go | 134 +- exchanges/kraken/README.md | 10 +- exchanges/kraken/kraken.go | 148 +- exchanges/kraken/kraken_test.go | 66 +- exchanges/kraken/kraken_websocket.go | 3 +- exchanges/kraken/kraken_wrapper.go | 255 +- exchanges/lakebtc/README.md | 10 +- exchanges/lakebtc/lakebtc.go | 106 +- exchanges/lakebtc/lakebtc_test.go | 51 +- exchanges/lakebtc/lakebtc_wrapper.go | 165 +- exchanges/localbitcoins/README.md | 10 +- exchanges/localbitcoins/localbitcoins.go | 101 +- exchanges/localbitcoins/localbitcoins_test.go | 40 +- .../localbitcoins/localbitcoins_wrapper.go | 148 +- exchanges/okcoin/README.md | 10 +- exchanges/okcoin/okcoin.go | 40 - exchanges/okcoin/okcoin_test.go | 93 +- exchanges/okcoin/okcoin_wrapper.go | 154 + exchanges/okex/README.md | 10 +- exchanges/okex/okex.go | 41 +- exchanges/okex/okex_test.go | 101 +- exchanges/okex/okex_wrapper.go | 189 + exchanges/okgroup/okgroup.go | 85 +- exchanges/okgroup/okgroup_websocket.go | 18 +- exchanges/okgroup/okgroup_wrapper.go | 96 +- exchanges/orderbook/README.md | 2 +- exchanges/orderbook/orderbook.go | 33 +- exchanges/orderbook/orderbook_test.go | 31 +- exchanges/poloniex/README.md | 10 +- exchanges/poloniex/poloniex.go | 137 +- exchanges/poloniex/poloniex_test.go | 34 +- exchanges/poloniex/poloniex_websocket.go | 16 +- exchanges/poloniex/poloniex_wrapper.go | 202 +- exchanges/request/request.go | 26 +- exchanges/stats/stats.go | 13 +- exchanges/stats/stats_test.go | 33 +- exchanges/support.go | 33 + exchanges/ticker/README.md | 2 +- exchanges/ticker/ticker.go | 37 +- exchanges/ticker/ticker_test.go | 71 +- .../{exchange_websocket.go => websocket.go} | 5 +- ...ge_websocket_test.go => websocket_test.go} | 9 +- ..._websocket_types.go => websocket_types.go} | 11 +- exchanges/yobit/README.md | 10 +- exchanges/yobit/yobit.go | 98 +- exchanges/yobit/yobit_test.go | 45 +- exchanges/yobit/yobit_wrapper.go | 178 +- exchanges/zb/README.md | 10 +- exchanges/zb/zb.go | 133 +- exchanges/zb/zb_test.go | 44 +- exchanges/zb/zb_websocket.go | 11 +- exchanges/zb/zb_wrapper.go | 210 +- gctrpc/auth/auth.go | 26 + gctrpc/gen_pb_linux.sh | 4 + gctrpc/gen_pb_win.bat | 5 + gctrpc/rpc.pb.go | 4987 +++++++++++++++++ gctrpc/rpc.pb.gw.go | 1191 ++++ gctrpc/rpc.proto | 581 ++ gctrpc/rpc.swagger.json | 1628 ++++++ go.mod | 9 + go.sum | 56 +- helpers.go | 422 -- main.go | 315 +- portfolio/portfolio.go | 4 +- portfolio/portfolio_test.go | 24 +- portfolio/portfolio_types.go | 2 +- restful_router.go | 141 - restful_server.go | 299 - testdata/configtest.json | 8 +- tools/README.md | 51 - utils/utils.go | 36 + utils/utils_test.go | 22 + .../pages/settings/settings.component.html | 6 +- 310 files changed, 23570 insertions(+), 9155 deletions(-) rename {tools => cmd}/config/config.go (100%) rename {tools => cmd}/config/config_test.go (100%) create mode 100644 cmd/config_builder/builder.go rename {tools => cmd}/documentation/common_templates/common_readme.tmpl (100%) rename {tools => cmd}/documentation/communications_templates/base.tmpl (100%) rename {tools => cmd}/documentation/communications_templates/comms.tmpl (100%) rename {tools => cmd}/documentation/communications_templates/slack.tmpl (100%) rename {tools => cmd}/documentation/communications_templates/smsglobal.tmpl (100%) rename {tools => cmd}/documentation/communications_templates/smtp.tmpl (100%) rename {tools => cmd}/documentation/communications_templates/telegram.tmpl (100%) rename {tools => cmd}/documentation/config_templates/config_readme.tmpl (100%) rename {tools => cmd}/documentation/currency_templates/currency_pair_readme.tmpl (100%) rename {tools => cmd}/documentation/currency_templates/currency_readme.tmpl (100%) rename {tools => cmd}/documentation/currency_templates/currency_symbol_readme.tmpl (100%) rename {tools => cmd}/documentation/currency_templates/currency_translation_readme.tmpl (100%) rename {tools => cmd}/documentation/currency_templates/fx.tmpl (100%) rename {tools => cmd}/documentation/currency_templates/fx_base.tmpl (100%) rename {tools => cmd}/documentation/currency_templates/fx_currencyconverterapi.tmpl (100%) rename {tools => cmd}/documentation/currency_templates/fx_currencylayer.tmpl (100%) rename {tools => cmd}/documentation/currency_templates/fx_fixer.tmpl (100%) rename {tools => cmd}/documentation/currency_templates/fx_openexchangerates.tmpl (100%) rename {tools => cmd}/documentation/documentation.go (100%) rename {tools => cmd}/documentation/events_templates/events_readme.tmpl (100%) rename {tools => cmd}/documentation/exchanges_templates/alphapoint.tmpl (100%) rename {tools => cmd}/documentation/exchanges_templates/anx.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/binance.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/bitfinex.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/bitflyer.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/bithumb.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/bitmex.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/bitstamp.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/bittrex.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/btcc.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/btcmarkets.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/coinbasepro.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/coinut.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/exchanges_orderbook_readme.tmpl (95%) rename {tools => cmd}/documentation/exchanges_templates/exchanges_readme.tmpl (100%) rename {tools => cmd}/documentation/exchanges_templates/exchanges_stats_readme.tmpl (100%) rename {tools => cmd}/documentation/exchanges_templates/exchanges_ticker_readme.tmpl (95%) rename {tools => cmd}/documentation/exchanges_templates/exmo.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/gateio.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/gemini.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/hitbtc.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/huobi.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/huobihadax.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/itbit.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/kraken.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/lakebtc.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/localbitcoins.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/nonce.tmpl (100%) rename {tools => cmd}/documentation/exchanges_templates/okcoin.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/okex.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/orders.tmpl (100%) rename {tools => cmd}/documentation/exchanges_templates/poloniex.tmpl (93%) rename {tools => cmd}/documentation/exchanges_templates/request.tmpl (100%) rename {tools => cmd}/documentation/exchanges_templates/yobit.tmpl (92%) rename {tools => cmd}/documentation/exchanges_templates/zb.tmpl (93%) rename {tools => cmd}/documentation/portfolio_templates/portfolio_readme.tmpl (100%) rename {tools => cmd}/documentation/root_templates/CONTRIBUTORS (100%) rename {tools => cmd}/documentation/root_templates/LICENSE (100%) rename {tools => cmd}/documentation/root_templates/root_readme.tmpl (100%) rename {tools => cmd}/documentation/sub_templates/contributions.tmpl (100%) rename {tools => cmd}/documentation/sub_templates/contributors.tmpl (100%) rename {tools => cmd}/documentation/sub_templates/donations.tmpl (100%) rename {tools => cmd}/documentation/sub_templates/header.tmpl (100%) rename {tools => cmd}/documentation/sub_templates/status.tmpl (100%) rename {tools => cmd}/documentation/testdata_templates/testdata_readme.tmpl (100%) rename {tools => cmd}/documentation/tools_templates/config_tool.tmpl (100%) rename {tools => cmd}/documentation/tools_templates/documentation_tool.tmpl (100%) rename {tools => cmd}/documentation/tools_templates/exchange_tool.tmpl (100%) rename {tools => cmd}/documentation/tools_templates/huobi_auth_tool.tmpl (100%) rename {tools => cmd}/documentation/tools_templates/portfolio_tool.tmpl (100%) rename {tools => cmd}/documentation/tools_templates/tools.tmpl (100%) rename {tools => cmd}/documentation/tools_templates/websocket_client_tool.tmpl (100%) rename {tools => cmd}/documentation/web_templates/web_readme.tmpl (100%) rename {tools => cmd}/exchange_template/exchange_template.go (95%) rename {tools => cmd}/exchange_template/main_file.tmpl (81%) rename {tools => cmd}/exchange_template/readme_file.tmpl (100%) rename {tools => cmd}/exchange_template/test_file.tmpl (80%) rename {tools => cmd}/exchange_template/type_file.tmpl (100%) rename {tools => cmd}/exchange_template/wrapper_file.tmpl (92%) create mode 100644 cmd/exchange_wrapper_coverage/main.go create mode 100644 cmd/gctcli/commands.go create mode 100644 cmd/gctcli/main.go create mode 100644 cmd/gen_cert/main.go rename {tools => cmd}/huobi_auth/main.go (90%) create mode 100644 cmd/otp_gen/otp_gen.go rename {tools => cmd}/portfolio/portfolio.go (97%) rename {tools => cmd}/portfolio/portfolio_test.go (100%) rename {tools => cmd}/websocket_client/main.go (89%) create mode 100644 common/crypto/crypto.go create mode 100644 common/crypto/crypto_test.go create mode 100644 common/math/math.go create mode 100644 common/math/math_test.go create mode 100644 config/config_types.go create mode 100644 core/banner.go rename version.go => core/version.go (72%) create mode 100644 currency/manager.go create mode 100644 currency/manager_test.go create mode 100644 currency/manager_types.go create mode 100644 engine/engine.go create mode 100644 engine/engine_types.go rename {events => engine/events}/event_test.go (86%) rename {events => engine/events}/events.go (50%) rename exchange.go => engine/exchange.go (71%) rename exchange_test.go => engine/exchange_test.go (93%) create mode 100644 engine/helpers.go rename helpers_test.go => engine/helpers_test.go (84%) create mode 100644 engine/orders.go create mode 100644 engine/restful_router.go create mode 100644 engine/restful_server.go rename restful_server_test.go => engine/restful_server_test.go (91%) create mode 100644 engine/restful_types.go rename routines.go => engine/routines.go (67%) create mode 100644 engine/rpcserver.go create mode 100644 engine/syncer.go create mode 100644 engine/syncer_test.go create mode 100644 engine/syncer_types.go rename websocket.go => engine/websocket.go (82%) create mode 100644 engine/websocket_types.go delete mode 100644 events/README.md create mode 100644 exchanges/assets/assets.go create mode 100644 exchanges/assets/assets_test.go create mode 100644 exchanges/exchange_types.go create mode 100644 exchanges/interfaces.go create mode 100644 exchanges/okcoin/okcoin_wrapper.go create mode 100644 exchanges/okex/okex_wrapper.go create mode 100644 exchanges/support.go rename exchanges/{exchange_websocket.go => websocket.go} (99%) rename exchanges/{exchange_websocket_test.go => websocket_test.go} (98%) rename exchanges/{exchange_websocket_types.go => websocket_types.go} (96%) create mode 100644 gctrpc/auth/auth.go create mode 100644 gctrpc/gen_pb_linux.sh create mode 100644 gctrpc/gen_pb_win.bat create mode 100644 gctrpc/rpc.pb.go create mode 100644 gctrpc/rpc.pb.gw.go create mode 100644 gctrpc/rpc.proto create mode 100644 gctrpc/rpc.swagger.json delete mode 100644 helpers.go delete mode 100644 restful_router.go delete mode 100644 restful_server.go delete mode 100644 tools/README.md create mode 100644 utils/utils.go create mode 100644 utils/utils_test.go diff --git a/Dockerfile b/Dockerfile index f08d9031..167bcd1d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,5 +10,5 @@ FROM alpine:latest RUN apk update && apk add --no-cache ca-certificates COPY --from=build /go/bin/gocryptotrader /app/ COPY --from=build /go/src/github.com/thrasher-/gocryptotrader/config.json /app/ -EXPOSE 9050 +EXPOSE 9050-9053 CMD ["/app/gocryptotrader"] diff --git a/tools/config/config.go b/cmd/config/config.go similarity index 100% rename from tools/config/config.go rename to cmd/config/config.go diff --git a/tools/config/config_test.go b/cmd/config/config_test.go similarity index 100% rename from tools/config/config_test.go rename to cmd/config/config_test.go diff --git a/cmd/config_builder/builder.go b/cmd/config_builder/builder.go new file mode 100644 index 00000000..71c384e8 --- /dev/null +++ b/cmd/config_builder/builder.go @@ -0,0 +1,51 @@ +package main + +import ( + "encoding/json" + "log" + "sync" + + "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/engine" + exchange "github.com/thrasher-/gocryptotrader/exchanges" +) + +func main() { + var err error + engine.Bot, err = engine.New() + if err != nil { + log.Fatalf("Failed to initialise engine. Err: %s", err) + } + + log.Printf("Loading exchanges..") + var wg sync.WaitGroup + for x := range exchange.Exchanges { + name := exchange.Exchanges[x] + err = engine.LoadExchange(name, true, &wg) + if err != nil { + log.Printf("Failed to load exchange %s. Err: %s", name, err) + continue + } + } + wg.Wait() + log.Println("Done.") + + var cfgs []config.ExchangeConfig + for x := range engine.Bot.Exchanges { + var cfg *config.ExchangeConfig + cfg, err = engine.Bot.Exchanges[x].GetDefaultConfig() + if err != nil { + log.Printf("Failed to get exchanges default config. Err: %s", err) + continue + } + log.Printf("Adding %s", engine.Bot.Exchanges[x].GetName()) + cfgs = append(cfgs, *cfg) + } + + data, err := json.MarshalIndent(cfgs, "", " ") + if err != nil { + log.Fatalf("Unable to marshal cfgs. Err: %s", err) + } + + log.Println(string(data)) +} diff --git a/tools/documentation/common_templates/common_readme.tmpl b/cmd/documentation/common_templates/common_readme.tmpl similarity index 100% rename from tools/documentation/common_templates/common_readme.tmpl rename to cmd/documentation/common_templates/common_readme.tmpl diff --git a/tools/documentation/communications_templates/base.tmpl b/cmd/documentation/communications_templates/base.tmpl similarity index 100% rename from tools/documentation/communications_templates/base.tmpl rename to cmd/documentation/communications_templates/base.tmpl diff --git a/tools/documentation/communications_templates/comms.tmpl b/cmd/documentation/communications_templates/comms.tmpl similarity index 100% rename from tools/documentation/communications_templates/comms.tmpl rename to cmd/documentation/communications_templates/comms.tmpl diff --git a/tools/documentation/communications_templates/slack.tmpl b/cmd/documentation/communications_templates/slack.tmpl similarity index 100% rename from tools/documentation/communications_templates/slack.tmpl rename to cmd/documentation/communications_templates/slack.tmpl diff --git a/tools/documentation/communications_templates/smsglobal.tmpl b/cmd/documentation/communications_templates/smsglobal.tmpl similarity index 100% rename from tools/documentation/communications_templates/smsglobal.tmpl rename to cmd/documentation/communications_templates/smsglobal.tmpl diff --git a/tools/documentation/communications_templates/smtp.tmpl b/cmd/documentation/communications_templates/smtp.tmpl similarity index 100% rename from tools/documentation/communications_templates/smtp.tmpl rename to cmd/documentation/communications_templates/smtp.tmpl diff --git a/tools/documentation/communications_templates/telegram.tmpl b/cmd/documentation/communications_templates/telegram.tmpl similarity index 100% rename from tools/documentation/communications_templates/telegram.tmpl rename to cmd/documentation/communications_templates/telegram.tmpl diff --git a/tools/documentation/config_templates/config_readme.tmpl b/cmd/documentation/config_templates/config_readme.tmpl similarity index 100% rename from tools/documentation/config_templates/config_readme.tmpl rename to cmd/documentation/config_templates/config_readme.tmpl diff --git a/tools/documentation/currency_templates/currency_pair_readme.tmpl b/cmd/documentation/currency_templates/currency_pair_readme.tmpl similarity index 100% rename from tools/documentation/currency_templates/currency_pair_readme.tmpl rename to cmd/documentation/currency_templates/currency_pair_readme.tmpl diff --git a/tools/documentation/currency_templates/currency_readme.tmpl b/cmd/documentation/currency_templates/currency_readme.tmpl similarity index 100% rename from tools/documentation/currency_templates/currency_readme.tmpl rename to cmd/documentation/currency_templates/currency_readme.tmpl diff --git a/tools/documentation/currency_templates/currency_symbol_readme.tmpl b/cmd/documentation/currency_templates/currency_symbol_readme.tmpl similarity index 100% rename from tools/documentation/currency_templates/currency_symbol_readme.tmpl rename to cmd/documentation/currency_templates/currency_symbol_readme.tmpl diff --git a/tools/documentation/currency_templates/currency_translation_readme.tmpl b/cmd/documentation/currency_templates/currency_translation_readme.tmpl similarity index 100% rename from tools/documentation/currency_templates/currency_translation_readme.tmpl rename to cmd/documentation/currency_templates/currency_translation_readme.tmpl diff --git a/tools/documentation/currency_templates/fx.tmpl b/cmd/documentation/currency_templates/fx.tmpl similarity index 100% rename from tools/documentation/currency_templates/fx.tmpl rename to cmd/documentation/currency_templates/fx.tmpl diff --git a/tools/documentation/currency_templates/fx_base.tmpl b/cmd/documentation/currency_templates/fx_base.tmpl similarity index 100% rename from tools/documentation/currency_templates/fx_base.tmpl rename to cmd/documentation/currency_templates/fx_base.tmpl diff --git a/tools/documentation/currency_templates/fx_currencyconverterapi.tmpl b/cmd/documentation/currency_templates/fx_currencyconverterapi.tmpl similarity index 100% rename from tools/documentation/currency_templates/fx_currencyconverterapi.tmpl rename to cmd/documentation/currency_templates/fx_currencyconverterapi.tmpl diff --git a/tools/documentation/currency_templates/fx_currencylayer.tmpl b/cmd/documentation/currency_templates/fx_currencylayer.tmpl similarity index 100% rename from tools/documentation/currency_templates/fx_currencylayer.tmpl rename to cmd/documentation/currency_templates/fx_currencylayer.tmpl diff --git a/tools/documentation/currency_templates/fx_fixer.tmpl b/cmd/documentation/currency_templates/fx_fixer.tmpl similarity index 100% rename from tools/documentation/currency_templates/fx_fixer.tmpl rename to cmd/documentation/currency_templates/fx_fixer.tmpl diff --git a/tools/documentation/currency_templates/fx_openexchangerates.tmpl b/cmd/documentation/currency_templates/fx_openexchangerates.tmpl similarity index 100% rename from tools/documentation/currency_templates/fx_openexchangerates.tmpl rename to cmd/documentation/currency_templates/fx_openexchangerates.tmpl diff --git a/tools/documentation/documentation.go b/cmd/documentation/documentation.go similarity index 100% rename from tools/documentation/documentation.go rename to cmd/documentation/documentation.go diff --git a/tools/documentation/events_templates/events_readme.tmpl b/cmd/documentation/events_templates/events_readme.tmpl similarity index 100% rename from tools/documentation/events_templates/events_readme.tmpl rename to cmd/documentation/events_templates/events_readme.tmpl diff --git a/tools/documentation/exchanges_templates/alphapoint.tmpl b/cmd/documentation/exchanges_templates/alphapoint.tmpl similarity index 100% rename from tools/documentation/exchanges_templates/alphapoint.tmpl rename to cmd/documentation/exchanges_templates/alphapoint.tmpl diff --git a/tools/documentation/exchanges_templates/anx.tmpl b/cmd/documentation/exchanges_templates/anx.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/anx.tmpl rename to cmd/documentation/exchanges_templates/anx.tmpl index e94a37e2..97dae83d 100644 --- a/tools/documentation/exchanges_templates/anx.tmpl +++ b/cmd/documentation/exchanges_templates/anx.tmpl @@ -33,22 +33,22 @@ main.go ```go var a exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "ANX" { - a = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "ANX" { + a = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := a.GetTickerPrice() +tick, err := a.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := a.GetOrderbookEx() +ob, err := a.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/binance.tmpl b/cmd/documentation/exchanges_templates/binance.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/binance.tmpl rename to cmd/documentation/exchanges_templates/binance.tmpl index a836b302..314e8b1f 100644 --- a/tools/documentation/exchanges_templates/binance.tmpl +++ b/cmd/documentation/exchanges_templates/binance.tmpl @@ -30,22 +30,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Binance" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Binance" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/bitfinex.tmpl b/cmd/documentation/exchanges_templates/bitfinex.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/bitfinex.tmpl rename to cmd/documentation/exchanges_templates/bitfinex.tmpl index bdf756aa..c2143617 100644 --- a/tools/documentation/exchanges_templates/bitfinex.tmpl +++ b/cmd/documentation/exchanges_templates/bitfinex.tmpl @@ -30,22 +30,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bitfinex" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bitfinex" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/bitflyer.tmpl b/cmd/documentation/exchanges_templates/bitflyer.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/bitflyer.tmpl rename to cmd/documentation/exchanges_templates/bitflyer.tmpl index 11525706..3ecb74a4 100644 --- a/tools/documentation/exchanges_templates/bitflyer.tmpl +++ b/cmd/documentation/exchanges_templates/bitflyer.tmpl @@ -29,22 +29,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bitflyer" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bitflyer" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/bithumb.tmpl b/cmd/documentation/exchanges_templates/bithumb.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/bithumb.tmpl rename to cmd/documentation/exchanges_templates/bithumb.tmpl index 4e630f83..77409b4d 100644 --- a/tools/documentation/exchanges_templates/bithumb.tmpl +++ b/cmd/documentation/exchanges_templates/bithumb.tmpl @@ -29,22 +29,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bithumb" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bithumb" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/bitmex.tmpl b/cmd/documentation/exchanges_templates/bitmex.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/bitmex.tmpl rename to cmd/documentation/exchanges_templates/bitmex.tmpl index fb65ba93..0aae7b0e 100644 --- a/tools/documentation/exchanges_templates/bitmex.tmpl +++ b/cmd/documentation/exchanges_templates/bitmex.tmpl @@ -29,22 +29,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bitmex" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bitmex" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/bitstamp.tmpl b/cmd/documentation/exchanges_templates/bitstamp.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/bitstamp.tmpl rename to cmd/documentation/exchanges_templates/bitstamp.tmpl index 1d288aa4..4bdc703c 100644 --- a/tools/documentation/exchanges_templates/bitstamp.tmpl +++ b/cmd/documentation/exchanges_templates/bitstamp.tmpl @@ -30,22 +30,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bitstamp" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bitstamp" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/bittrex.tmpl b/cmd/documentation/exchanges_templates/bittrex.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/bittrex.tmpl rename to cmd/documentation/exchanges_templates/bittrex.tmpl index e5e4f011..28180991 100644 --- a/tools/documentation/exchanges_templates/bittrex.tmpl +++ b/cmd/documentation/exchanges_templates/bittrex.tmpl @@ -29,22 +29,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bittrex" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bittrex" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/btcc.tmpl b/cmd/documentation/exchanges_templates/btcc.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/btcc.tmpl rename to cmd/documentation/exchanges_templates/btcc.tmpl index 095a9fea..c3193304 100644 --- a/tools/documentation/exchanges_templates/btcc.tmpl +++ b/cmd/documentation/exchanges_templates/btcc.tmpl @@ -30,22 +30,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "BTCC" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "BTCC" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/btcmarkets.tmpl b/cmd/documentation/exchanges_templates/btcmarkets.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/btcmarkets.tmpl rename to cmd/documentation/exchanges_templates/btcmarkets.tmpl index 6ca25f3d..e97ebb6f 100644 --- a/tools/documentation/exchanges_templates/btcmarkets.tmpl +++ b/cmd/documentation/exchanges_templates/btcmarkets.tmpl @@ -29,22 +29,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "BTCMarkets" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "BTCMarkets" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/coinbasepro.tmpl b/cmd/documentation/exchanges_templates/coinbasepro.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/coinbasepro.tmpl rename to cmd/documentation/exchanges_templates/coinbasepro.tmpl index 6843d0e8..9d7b2c8a 100644 --- a/tools/documentation/exchanges_templates/coinbasepro.tmpl +++ b/cmd/documentation/exchanges_templates/coinbasepro.tmpl @@ -30,22 +30,22 @@ main.go ```go var c exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "CoinbasePro" { - c = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "CoinbasePro" { + c = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := c.GetTickerPrice() +tick, err := c.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := c.GetOrderbookEx() +ob, err := c.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/coinut.tmpl b/cmd/documentation/exchanges_templates/coinut.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/coinut.tmpl rename to cmd/documentation/exchanges_templates/coinut.tmpl index 1a7960ec..cdbdce12 100644 --- a/tools/documentation/exchanges_templates/coinut.tmpl +++ b/cmd/documentation/exchanges_templates/coinut.tmpl @@ -30,22 +30,22 @@ main.go ```go var c exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Coinut" { - c = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Coinut" { + c = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := c.GetTickerPrice() +tick, err := c.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := c.GetOrderbookEx() +ob, err := c.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/exchanges_orderbook_readme.tmpl b/cmd/documentation/exchanges_templates/exchanges_orderbook_readme.tmpl similarity index 95% rename from tools/documentation/exchanges_templates/exchanges_orderbook_readme.tmpl rename to cmd/documentation/exchanges_templates/exchanges_orderbook_readme.tmpl index 78de8a20..047d7c92 100644 --- a/tools/documentation/exchanges_templates/exchanges_orderbook_readme.tmpl +++ b/cmd/documentation/exchanges_templates/exchanges_orderbook_readme.tmpl @@ -16,7 +16,7 @@ exchange interface system set by exchange wrapper orderbook functions in Examples below: ```go -ob, err := yobitExchange.GetOrderbookEx() +ob, err := yobitExchange.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/exchanges_readme.tmpl b/cmd/documentation/exchanges_templates/exchanges_readme.tmpl similarity index 100% rename from tools/documentation/exchanges_templates/exchanges_readme.tmpl rename to cmd/documentation/exchanges_templates/exchanges_readme.tmpl diff --git a/tools/documentation/exchanges_templates/exchanges_stats_readme.tmpl b/cmd/documentation/exchanges_templates/exchanges_stats_readme.tmpl similarity index 100% rename from tools/documentation/exchanges_templates/exchanges_stats_readme.tmpl rename to cmd/documentation/exchanges_templates/exchanges_stats_readme.tmpl diff --git a/tools/documentation/exchanges_templates/exchanges_ticker_readme.tmpl b/cmd/documentation/exchanges_templates/exchanges_ticker_readme.tmpl similarity index 95% rename from tools/documentation/exchanges_templates/exchanges_ticker_readme.tmpl rename to cmd/documentation/exchanges_templates/exchanges_ticker_readme.tmpl index 0f47ef6d..afd210aa 100644 --- a/tools/documentation/exchanges_templates/exchanges_ticker_readme.tmpl +++ b/cmd/documentation/exchanges_templates/exchanges_ticker_readme.tmpl @@ -17,7 +17,7 @@ exchange interface system set by exchange wrapper orderbook functions in Examples below: ```go -tick, err := yobitExchange.GetTickerPrice() +tick, err := yobitExchange.FetchTicker() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/exmo.tmpl b/cmd/documentation/exchanges_templates/exmo.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/exmo.tmpl rename to cmd/documentation/exchanges_templates/exmo.tmpl index d65ec2b7..044f58da 100644 --- a/tools/documentation/exchanges_templates/exmo.tmpl +++ b/cmd/documentation/exchanges_templates/exmo.tmpl @@ -29,22 +29,22 @@ main.go ```go var e exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Exmo" { - e = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Exmo" { + e = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := e.GetTickerPrice() +tick, err := e.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := e.GetOrderbookEx() +ob, err := e.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/gateio.tmpl b/cmd/documentation/exchanges_templates/gateio.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/gateio.tmpl rename to cmd/documentation/exchanges_templates/gateio.tmpl index ef80257b..c0e6c2da 100644 --- a/tools/documentation/exchanges_templates/gateio.tmpl +++ b/cmd/documentation/exchanges_templates/gateio.tmpl @@ -29,22 +29,22 @@ main.go ```go var g exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "GateIO" { - g = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "GateIO" { + g = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := g.GetTickerPrice() +tick, err := g.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := g.GetOrderbookEx() +ob, err := g.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/gemini.tmpl b/cmd/documentation/exchanges_templates/gemini.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/gemini.tmpl rename to cmd/documentation/exchanges_templates/gemini.tmpl index 18e2170d..7a5a742c 100644 --- a/tools/documentation/exchanges_templates/gemini.tmpl +++ b/cmd/documentation/exchanges_templates/gemini.tmpl @@ -29,22 +29,22 @@ main.go ```go var g exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Gemini" { - g = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Gemini" { + g = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := g.GetTickerPrice() +tick, err := g.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := g.GetOrderbookEx() +ob, err := g.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/hitbtc.tmpl b/cmd/documentation/exchanges_templates/hitbtc.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/hitbtc.tmpl rename to cmd/documentation/exchanges_templates/hitbtc.tmpl index bc484858..411a3e57 100644 --- a/tools/documentation/exchanges_templates/hitbtc.tmpl +++ b/cmd/documentation/exchanges_templates/hitbtc.tmpl @@ -30,22 +30,22 @@ main.go ```go var h exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "HitBTC" { - h = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "HitBTC" { + h = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := h.GetTickerPrice() +tick, err := h.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := h.GetOrderbookEx() +ob, err := h.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/huobi.tmpl b/cmd/documentation/exchanges_templates/huobi.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/huobi.tmpl rename to cmd/documentation/exchanges_templates/huobi.tmpl index 0917fb77..d7ce36cd 100644 --- a/tools/documentation/exchanges_templates/huobi.tmpl +++ b/cmd/documentation/exchanges_templates/huobi.tmpl @@ -29,22 +29,22 @@ main.go ```go var h exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Huobi" { - h = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Huobi" { + h = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := h.GetTickerPrice() +tick, err := h.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := h.GetOrderbookEx() +ob, err := h.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/huobihadax.tmpl b/cmd/documentation/exchanges_templates/huobihadax.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/huobihadax.tmpl rename to cmd/documentation/exchanges_templates/huobihadax.tmpl index 6534d838..331af4f0 100644 --- a/tools/documentation/exchanges_templates/huobihadax.tmpl +++ b/cmd/documentation/exchanges_templates/huobihadax.tmpl @@ -29,22 +29,22 @@ main.go ```go var h exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "HuobiHadax" { - h = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "HuobiHadax" { + h = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := h.GetTickerPrice() +tick, err := h.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := h.GetOrderbookEx() +ob, err := h.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/itbit.tmpl b/cmd/documentation/exchanges_templates/itbit.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/itbit.tmpl rename to cmd/documentation/exchanges_templates/itbit.tmpl index 2112c376..b09ff0ff 100644 --- a/tools/documentation/exchanges_templates/itbit.tmpl +++ b/cmd/documentation/exchanges_templates/itbit.tmpl @@ -29,22 +29,22 @@ main.go ```go var i exchange.IBotExchange -for x := range bot.exchanges { - if bot.exchanges[x].GetName() == "Itbit" { - i = bot.exchanges[x] +for x := range bot.Exchanges { + if bot.Exchanges[x].GetName() == "Itbit" { + i = bot.Exchanges[x] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := i.GetTickerPrice() +tick, err := i.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := i.GetOrderbookEx() +ob, err := i.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/kraken.tmpl b/cmd/documentation/exchanges_templates/kraken.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/kraken.tmpl rename to cmd/documentation/exchanges_templates/kraken.tmpl index c433b000..db6ccee7 100644 --- a/tools/documentation/exchanges_templates/kraken.tmpl +++ b/cmd/documentation/exchanges_templates/kraken.tmpl @@ -29,22 +29,22 @@ main.go ```go var k exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Kraken" { - k = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Kraken" { + k = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := k.GetTickerPrice() +tick, err := k.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := k.GetOrderbookEx() +ob, err := k.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/lakebtc.tmpl b/cmd/documentation/exchanges_templates/lakebtc.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/lakebtc.tmpl rename to cmd/documentation/exchanges_templates/lakebtc.tmpl index 0b28111a..ece6a5a1 100644 --- a/tools/documentation/exchanges_templates/lakebtc.tmpl +++ b/cmd/documentation/exchanges_templates/lakebtc.tmpl @@ -29,22 +29,22 @@ main.go ```go var l exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "LakeBTC" { - l = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "LakeBTC" { + l = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := l.GetTickerPrice() +tick, err := l.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := l.GetOrderbookEx() +ob, err := l.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/localbitcoins.tmpl b/cmd/documentation/exchanges_templates/localbitcoins.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/localbitcoins.tmpl rename to cmd/documentation/exchanges_templates/localbitcoins.tmpl index b3654258..957420fe 100644 --- a/tools/documentation/exchanges_templates/localbitcoins.tmpl +++ b/cmd/documentation/exchanges_templates/localbitcoins.tmpl @@ -29,22 +29,22 @@ main.go ```go var l exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "LocalBitcoins" { - l = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "LocalBitcoins" { + l = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := l.GetTickerPrice() +tick, err := l.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := l.GetOrderbookEx() +ob, err := l.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/nonce.tmpl b/cmd/documentation/exchanges_templates/nonce.tmpl similarity index 100% rename from tools/documentation/exchanges_templates/nonce.tmpl rename to cmd/documentation/exchanges_templates/nonce.tmpl diff --git a/tools/documentation/exchanges_templates/okcoin.tmpl b/cmd/documentation/exchanges_templates/okcoin.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/okcoin.tmpl rename to cmd/documentation/exchanges_templates/okcoin.tmpl index 9657a62a..130cea25 100644 --- a/tools/documentation/exchanges_templates/okcoin.tmpl +++ b/cmd/documentation/exchanges_templates/okcoin.tmpl @@ -30,22 +30,22 @@ main.go ```go var o exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "OKCoin" { - y = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "OKCoin" { + y = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := o.GetTickerPrice() +tick, err := o.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := o.GetOrderbookEx() +ob, err := o.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/okex.tmpl b/cmd/documentation/exchanges_templates/okex.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/okex.tmpl rename to cmd/documentation/exchanges_templates/okex.tmpl index 1b5d091e..7dc46ae5 100644 --- a/tools/documentation/exchanges_templates/okex.tmpl +++ b/cmd/documentation/exchanges_templates/okex.tmpl @@ -29,22 +29,22 @@ main.go ```go var o exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "OKex" { - y = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "OKex" { + y = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := o.GetTickerPrice() +tick, err := o.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := o.GetOrderbookEx() +ob, err := o.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/orders.tmpl b/cmd/documentation/exchanges_templates/orders.tmpl similarity index 100% rename from tools/documentation/exchanges_templates/orders.tmpl rename to cmd/documentation/exchanges_templates/orders.tmpl diff --git a/tools/documentation/exchanges_templates/poloniex.tmpl b/cmd/documentation/exchanges_templates/poloniex.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/poloniex.tmpl rename to cmd/documentation/exchanges_templates/poloniex.tmpl index 512f010e..b8a2dd6f 100644 --- a/tools/documentation/exchanges_templates/poloniex.tmpl +++ b/cmd/documentation/exchanges_templates/poloniex.tmpl @@ -30,22 +30,22 @@ main.go ```go var p exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Poloniex" { - y = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Poloniex" { + y = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := p.GetTickerPrice() +tick, err := p.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := p.GetOrderbookEx() +ob, err := p.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/request.tmpl b/cmd/documentation/exchanges_templates/request.tmpl similarity index 100% rename from tools/documentation/exchanges_templates/request.tmpl rename to cmd/documentation/exchanges_templates/request.tmpl diff --git a/tools/documentation/exchanges_templates/yobit.tmpl b/cmd/documentation/exchanges_templates/yobit.tmpl similarity index 92% rename from tools/documentation/exchanges_templates/yobit.tmpl rename to cmd/documentation/exchanges_templates/yobit.tmpl index 3e51bc05..fb815ff8 100644 --- a/tools/documentation/exchanges_templates/yobit.tmpl +++ b/cmd/documentation/exchanges_templates/yobit.tmpl @@ -29,22 +29,22 @@ main.go ```go var y exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Yobit" { - y = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Yobit" { + y = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := y.GetTickerPrice() +tick, err := y.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := y.GetOrderbookEx() +ob, err := y.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/exchanges_templates/zb.tmpl b/cmd/documentation/exchanges_templates/zb.tmpl similarity index 93% rename from tools/documentation/exchanges_templates/zb.tmpl rename to cmd/documentation/exchanges_templates/zb.tmpl index f3ed9124..70e33670 100644 --- a/tools/documentation/exchanges_templates/zb.tmpl +++ b/cmd/documentation/exchanges_templates/zb.tmpl @@ -29,22 +29,22 @@ main.go ```go var z exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "ZB" { - z = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "ZB" { + z = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := z.GetTickerPrice() +tick, err := z.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := z.GetOrderbookEx() +ob, err := z.FetchOrderbook() if err != nil { // Handle error } diff --git a/tools/documentation/portfolio_templates/portfolio_readme.tmpl b/cmd/documentation/portfolio_templates/portfolio_readme.tmpl similarity index 100% rename from tools/documentation/portfolio_templates/portfolio_readme.tmpl rename to cmd/documentation/portfolio_templates/portfolio_readme.tmpl diff --git a/tools/documentation/root_templates/CONTRIBUTORS b/cmd/documentation/root_templates/CONTRIBUTORS similarity index 100% rename from tools/documentation/root_templates/CONTRIBUTORS rename to cmd/documentation/root_templates/CONTRIBUTORS diff --git a/tools/documentation/root_templates/LICENSE b/cmd/documentation/root_templates/LICENSE similarity index 100% rename from tools/documentation/root_templates/LICENSE rename to cmd/documentation/root_templates/LICENSE diff --git a/tools/documentation/root_templates/root_readme.tmpl b/cmd/documentation/root_templates/root_readme.tmpl similarity index 100% rename from tools/documentation/root_templates/root_readme.tmpl rename to cmd/documentation/root_templates/root_readme.tmpl diff --git a/tools/documentation/sub_templates/contributions.tmpl b/cmd/documentation/sub_templates/contributions.tmpl similarity index 100% rename from tools/documentation/sub_templates/contributions.tmpl rename to cmd/documentation/sub_templates/contributions.tmpl diff --git a/tools/documentation/sub_templates/contributors.tmpl b/cmd/documentation/sub_templates/contributors.tmpl similarity index 100% rename from tools/documentation/sub_templates/contributors.tmpl rename to cmd/documentation/sub_templates/contributors.tmpl diff --git a/tools/documentation/sub_templates/donations.tmpl b/cmd/documentation/sub_templates/donations.tmpl similarity index 100% rename from tools/documentation/sub_templates/donations.tmpl rename to cmd/documentation/sub_templates/donations.tmpl diff --git a/tools/documentation/sub_templates/header.tmpl b/cmd/documentation/sub_templates/header.tmpl similarity index 100% rename from tools/documentation/sub_templates/header.tmpl rename to cmd/documentation/sub_templates/header.tmpl diff --git a/tools/documentation/sub_templates/status.tmpl b/cmd/documentation/sub_templates/status.tmpl similarity index 100% rename from tools/documentation/sub_templates/status.tmpl rename to cmd/documentation/sub_templates/status.tmpl diff --git a/tools/documentation/testdata_templates/testdata_readme.tmpl b/cmd/documentation/testdata_templates/testdata_readme.tmpl similarity index 100% rename from tools/documentation/testdata_templates/testdata_readme.tmpl rename to cmd/documentation/testdata_templates/testdata_readme.tmpl diff --git a/tools/documentation/tools_templates/config_tool.tmpl b/cmd/documentation/tools_templates/config_tool.tmpl similarity index 100% rename from tools/documentation/tools_templates/config_tool.tmpl rename to cmd/documentation/tools_templates/config_tool.tmpl diff --git a/tools/documentation/tools_templates/documentation_tool.tmpl b/cmd/documentation/tools_templates/documentation_tool.tmpl similarity index 100% rename from tools/documentation/tools_templates/documentation_tool.tmpl rename to cmd/documentation/tools_templates/documentation_tool.tmpl diff --git a/tools/documentation/tools_templates/exchange_tool.tmpl b/cmd/documentation/tools_templates/exchange_tool.tmpl similarity index 100% rename from tools/documentation/tools_templates/exchange_tool.tmpl rename to cmd/documentation/tools_templates/exchange_tool.tmpl diff --git a/tools/documentation/tools_templates/huobi_auth_tool.tmpl b/cmd/documentation/tools_templates/huobi_auth_tool.tmpl similarity index 100% rename from tools/documentation/tools_templates/huobi_auth_tool.tmpl rename to cmd/documentation/tools_templates/huobi_auth_tool.tmpl diff --git a/tools/documentation/tools_templates/portfolio_tool.tmpl b/cmd/documentation/tools_templates/portfolio_tool.tmpl similarity index 100% rename from tools/documentation/tools_templates/portfolio_tool.tmpl rename to cmd/documentation/tools_templates/portfolio_tool.tmpl diff --git a/tools/documentation/tools_templates/tools.tmpl b/cmd/documentation/tools_templates/tools.tmpl similarity index 100% rename from tools/documentation/tools_templates/tools.tmpl rename to cmd/documentation/tools_templates/tools.tmpl diff --git a/tools/documentation/tools_templates/websocket_client_tool.tmpl b/cmd/documentation/tools_templates/websocket_client_tool.tmpl similarity index 100% rename from tools/documentation/tools_templates/websocket_client_tool.tmpl rename to cmd/documentation/tools_templates/websocket_client_tool.tmpl diff --git a/tools/documentation/web_templates/web_readme.tmpl b/cmd/documentation/web_templates/web_readme.tmpl similarity index 100% rename from tools/documentation/web_templates/web_readme.tmpl rename to cmd/documentation/web_templates/web_readme.tmpl diff --git a/tools/exchange_template/exchange_template.go b/cmd/exchange_template/exchange_template.go similarity index 95% rename from tools/exchange_template/exchange_template.go rename to cmd/exchange_template/exchange_template.go index c607254d..bce03308 100644 --- a/tools/exchange_template/exchange_template.go +++ b/cmd/exchange_template/exchange_template.go @@ -8,6 +8,9 @@ import ( "os" "os/exec" + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" ) @@ -117,10 +120,14 @@ func main() { newExchConfig := config.ExchangeConfig{} newExchConfig.Name = capName newExchConfig.Enabled = true - newExchConfig.RESTPollingDelay = 10 - newExchConfig.APIKey = "Key" - newExchConfig.APISecret = "Secret" - newExchConfig.AssetTypes = "SPOT" + newExchConfig.API.Credentials.Key = "Key" + newExchConfig.API.Credentials.Secret = "Secret" + + newExchConfig.CurrencyPairs = ¤cy.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + } configTestFile.Exchanges = append(configTestFile.Exchanges, newExchConfig) // TODO sorting function so exchanges are in alphabetical order - low priority diff --git a/tools/exchange_template/main_file.tmpl b/cmd/exchange_template/main_file.tmpl similarity index 81% rename from tools/exchange_template/main_file.tmpl rename to cmd/exchange_template/main_file.tmpl index 029cc56e..024711dc 100644 --- a/tools/exchange_template/main_file.tmpl +++ b/cmd/exchange_template/main_file.tmpl @@ -32,36 +32,34 @@ func ({{.Variable}} *{{.CapitalName}}) SetDefaults() { {{.Variable}}.Name = "{{.CapitalName}}" {{.Variable}}.Enabled = false {{.Variable}}.Verbose = false - {{.Variable}}.RESTPollingDelay = 10 {{.Variable}}.RequestCurrencyPairFormat.Delimiter = "" {{.Variable}}.RequestCurrencyPairFormat.Uppercase = true {{.Variable}}.ConfigCurrencyPairFormat.Delimiter = "" {{.Variable}}.ConfigCurrencyPairFormat.Uppercase = true - {{.Variable}}.AssetTypes = []string{ticker.Spot} + {{.Variable}}.AssetTypes = assets.AssetTypes{assets.AssetTypeSpot} {{.Variable}}.SupportsAutoPairUpdating = false {{.Variable}}.SupportsRESTTickerBatching = false {{.Variable}}.Requester = request.New({{.Variable}}.Name, request.NewRateLimit(time.Second, 0), request.NewRateLimit(time.Second, 0), common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - {{.Variable}}.APIUrlDefault = {{.Name}}APIURL - {{.Variable}}.APIUrl = {{.Variable}}.APIUrlDefault + {{.Variable}}.API.Endpoints.URLDefault = {{.Name}}APIURL + {{.Variable}}.API.Endpoints.URL = {{.Variable}}.API.Endpoints.URLDefault {{.Variable}}.WebsocketInit() } // Setup takes in the supplied exchange configuration details and sets params -func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) { +func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) error { if !exch.Enabled { {{.Variable}}.SetEnabled(false) } else { {{.Variable}}.Enabled = true - {{.Variable}}.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - {{.Variable}}.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) + {{.Variable}}.API.AuthenticatedSupport = exch.API.AuthenticatedSupport + {{.Variable}}.SetAPIKeys(exch.API.Credentials.Key, exch.API.Credentials.Secret, "", false) {{.Variable}}.SetHTTPClientTimeout(exch.HTTPTimeout) {{.Variable}}.SetHTTPClientUserAgent(exch.HTTPUserAgent) - {{.Variable}}.RESTPollingDelay = exch.RESTPollingDelay {{.Variable}}.Verbose = exch.Verbose - {{.Variable}}.Websocket.SetWsStatusAndConnection(exch.Websocket) + {{.Variable}}.Websocket.SetWsStatusAndConnection(exch.Features.Enabled.Websocket) {{.Variable}}.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") {{.Variable}}.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") {{.Variable}}.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") @@ -73,7 +71,7 @@ func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) { if err != nil { log.Fatal(err) } - err = {{.Variable}}.SetAutoPairDefaults() + err = {{.Variable}}.SetFeatureDefaults() if err != nil { log.Fatal(err) } @@ -89,9 +87,9 @@ func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) { // If the exchange supports websocket, update the below block // err = {{.Variable}}.WebsocketSetup({{.Variable}}.WsConnect, // exch.Name, - // exch.Websocket, + // exch.Features.Enabled.Websocket, // {{.Name}}Websocket, - // exch.WebsocketURL) + // exch.Features.Enabled.WebsocketURL) // if err != nil { // log.Fatal(err) // } diff --git a/tools/exchange_template/readme_file.tmpl b/cmd/exchange_template/readme_file.tmpl similarity index 100% rename from tools/exchange_template/readme_file.tmpl rename to cmd/exchange_template/readme_file.tmpl diff --git a/tools/exchange_template/test_file.tmpl b/cmd/exchange_template/test_file.tmpl similarity index 80% rename from tools/exchange_template/test_file.tmpl rename to cmd/exchange_template/test_file.tmpl index 4717d500..43c0e70b 100644 --- a/tools/exchange_template/test_file.tmpl +++ b/cmd/exchange_template/test_file.tmpl @@ -27,9 +27,9 @@ func TestSetup(t *testing.T) { t.Error("Test Failed - {{.CapitalName}} Setup() init error") } - {{.Name}}Config.AuthenticatedAPISupport = true - {{.Name}}Config.APIKey = testAPIKey - {{.Name}}Config.APISecret = testAPISecret + {{.Name}}Config.API.AuthenticatedSupport = true + {{.Name}}Config.API.Credentials.Key = testAPIKey + {{.Name}}Config.API.Credentials.Secret = testAPISecret {{.Variable}}.Setup({{.Name}}Config) } diff --git a/tools/exchange_template/type_file.tmpl b/cmd/exchange_template/type_file.tmpl similarity index 100% rename from tools/exchange_template/type_file.tmpl rename to cmd/exchange_template/type_file.tmpl diff --git a/tools/exchange_template/wrapper_file.tmpl b/cmd/exchange_template/wrapper_file.tmpl similarity index 92% rename from tools/exchange_template/wrapper_file.tmpl rename to cmd/exchange_template/wrapper_file.tmpl index d2aaec0e..2f1f0726 100644 --- a/tools/exchange_template/wrapper_file.tmpl +++ b/cmd/exchange_template/wrapper_file.tmpl @@ -31,7 +31,7 @@ func ({{.Variable}} *{{.CapitalName}}) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price // NOTE EXAMPLE FOR GETTING TICKER PRICE //tick, err := {{.Variable}}.GetTickers() @@ -39,7 +39,7 @@ func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(p currency.Pair, assetType s // return tickerPrice, err //} - //for _, x := range {{.Variable}}.GetEnabledCurrencies() { + //for _, x := range {{.Variable}}.GetEnabledPairs(assetType) { //curr := exchange.FormatExchangeCurrency({{.Variable}}.Name, x) //for y := range tick { // if tick[y].Symbol == curr.String() { @@ -58,8 +58,8 @@ func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(p currency.Pair, assetType s return tickerPrice, nil // NOTE DO NOT USE AS RETURN } -// GetTickerPrice returns the ticker for a currency pair -func ({{.Variable}} *{{.CapitalName}}) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func ({{.Variable}} *{{.CapitalName}}) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker({{.Variable}}.GetName(), p, assetType) if err != nil { return {{.Variable}}.UpdateTicker(p, assetType) @@ -67,8 +67,8 @@ func ({{.Variable}} *{{.CapitalName}}) GetTickerPrice(p currency.Pair, assetType return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func ({{.Variable}} *{{.CapitalName}}) GetOrderbookEx(currency currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func ({{.Variable}} *{{.CapitalName}}) FetchOrderbook(currency currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get({{.Variable}}.GetName(), currency, assetType) if err != nil { return {{.Variable}}.UpdateOrderbook(currency, assetType) @@ -77,7 +77,7 @@ func ({{.Variable}} *{{.CapitalName}}) GetOrderbookEx(currency currency.Pair, as } // UpdateOrderbook updates and returns the orderbook for a currency pair -func ({{.Variable}} *{{.CapitalName}}) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func ({{.Variable}} *{{.CapitalName}}) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base //NOTE UPDATE ORDERBOOK EXAMPLE //orderbookNew, err := {{.Variable}}.GetOrderBook(exchange.FormatExchangeCurrency({{.Variable}}.Name, p).String(), 1000) @@ -111,7 +111,7 @@ func ({{.Variable}} *{{.CapitalName}}) GetFundingHistory() ([]exchange.FundHisto } // GetExchangeHistory returns historic trade data since exchange opening. -func ({{.Variable}} *{{.CapitalName}}) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { +func ({{.Variable}} *{{.CapitalName}}) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } diff --git a/cmd/exchange_wrapper_coverage/main.go b/cmd/exchange_wrapper_coverage/main.go new file mode 100644 index 00000000..f786ac6d --- /dev/null +++ b/cmd/exchange_wrapper_coverage/main.go @@ -0,0 +1,174 @@ +package main + +import ( + "log" + "sync" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/engine" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" +) + +const ( + totalWrappers = 20 +) + +func main() { + var err error + engine.Bot, err = engine.New() + if err != nil { + log.Fatalf("Failed to initialise engine. Err: %s", err) + } + + engine.Bot.Settings = engine.Settings{ + DisableExchangeAutoPairUpdates: true, + } + + log.Printf("Loading exchanges..") + var wg sync.WaitGroup + for x := range exchange.Exchanges { + name := exchange.Exchanges[x] + err := engine.LoadExchange(name, true, &wg) + if err != nil { + log.Printf("Failed to load exchange %s. Err: %s", name, err) + continue + } + } + wg.Wait() + log.Println("Done.") + + log.Printf("Testing exchange wrappers..") + results := make(map[string][]string) + wg = sync.WaitGroup{} + for x := range engine.Bot.Exchanges { + wg.Add(1) + go func(num int) { + name := engine.Bot.Exchanges[num].GetName() + results[name] = testWrappers(engine.Bot.Exchanges[num]) + wg.Done() + }(x) + } + wg.Wait() + log.Println("Done.") + + log.Println() + for name, funcs := range results { + pct := float64(totalWrappers-len(funcs)) / float64(totalWrappers) * 100 + log.Printf("Exchange %s wrapper coverage [%d/%d - %.2f%%] | Total missing: %d", name, totalWrappers-len(funcs), totalWrappers, pct, len(funcs)) + log.Printf("\t Wrappers not implemented:") + + for x := range funcs { + log.Printf("\t - %s", funcs[x]) + } + log.Println() + } +} + +func testWrappers(e exchange.IBotExchange) []string { + p := currency.NewPair(currency.BTC, currency.USD) + assetType := assets.AssetTypeSpot + var funcs []string + + _, err := e.FetchTicker(p, assetType) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "FetchTicker") + } + + _, err = e.UpdateTicker(p, assetType) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "UpdateTicker") + } + + _, err = e.FetchOrderbook(p, assetType) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "FetchOrderbook") + } + + _, err = e.UpdateOrderbook(p, assetType) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "UpdateOrderbook") + } + + _, err = e.FetchTradablePairs(assets.AssetTypeSpot) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "FetchTradablePairs") + } + + err = e.UpdateTradablePairs(false) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "UpdateTradablePairs") + } + + _, err = e.GetAccountInfo() + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "GetAccountInfo") + } + + _, err = e.GetExchangeHistory(p, assetType) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "GetExchangeHistory") + } + + _, err = e.GetFundingHistory() + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "GetFundingHistory") + } + + _, err = e.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1000000, 10000000000, "meow") + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "SubmitOrder") + } + + _, err = e.ModifyOrder(&exchange.ModifyOrder{}) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "ModifyOrder") + } + + err = e.CancelOrder(&exchange.OrderCancellation{}) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "CancelOrder") + } + + _, err = e.CancelAllOrders(&exchange.OrderCancellation{}) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "CancelAllOrders") + } + + _, err = e.GetOrderInfo("1") + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "GetOrderInfo") + } + + _, err = e.GetOrderHistory(&exchange.GetOrdersRequest{}) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "GetOrderHistory") + } + + _, err = e.GetActiveOrders(&exchange.GetOrdersRequest{}) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "GetActiveOrders") + } + + _, err = e.GetDepositAddress(currency.BTC, "") + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "GetDepositAddress") + } + + _, err = e.WithdrawCryptocurrencyFunds(&exchange.CryptoWithdrawRequest{}) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "WithdrawCryptocurrencyFunds") + } + + _, err = e.WithdrawFiatFunds(&exchange.FiatWithdrawRequest{}) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "WithdrawFiatFunds") + } + _, err = e.WithdrawFiatFundsToInternationalBank(&exchange.FiatWithdrawRequest{}) + if err == common.ErrNotYetImplemented { + funcs = append(funcs, "WithdrawFiatFundsToInternationalBank") + } + + return funcs +} diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go new file mode 100644 index 00000000..20438447 --- /dev/null +++ b/cmd/gctcli/commands.go @@ -0,0 +1,1502 @@ +package main + +import ( + "context" + "fmt" + "strconv" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/gctrpc" + "github.com/urfave/cli" +) + +var getInfoCommand = cli.Command{ + Name: "getinfo", + Usage: "gets GoCryptoTrader info", + Action: getInfo, +} + +func getInfo(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetInfo(context.Background(), + &gctrpc.GetInfoRequest{}, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getExchangesCommand = cli.Command{ + Name: "getexchanges", + Usage: "gets a list of enabled or available exchanges", + ArgsUsage: "", + Action: getExchanges, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "enabled", + Usage: "whether to list enabled exchanges or not", + }, + }, +} + +func getExchanges(c *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var enabledOnly bool + if c.IsSet("enabled") { + enabledOnly = c.Bool("enabled") + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetExchanges(context.Background(), + &gctrpc.GetExchangesRequest{ + Enabled: enabledOnly, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var enableExchangeCommand = cli.Command{ + Name: "enableexchange", + Usage: "enables an exchange", + ArgsUsage: "", + Action: enableExchange, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to enable", + }, + }, +} + +func enableExchange(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "enableexchange") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.EnableExchange(context.Background(), + &gctrpc.GenericExchangeNameRequest{ + Exchange: exchangeName, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var disableExchangeCommand = cli.Command{ + Name: "disableexchange", + Usage: "disables an exchange", + ArgsUsage: "", + Action: disableExchange, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to disable", + }, + }, +} + +func disableExchange(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "disableexchange") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.DisableExchange(context.Background(), + &gctrpc.GenericExchangeNameRequest{ + Exchange: exchangeName, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getExchangeInfoCommand = cli.Command{ + Name: "getexchangeinfo", + Usage: "gets a specific exchanges info", + ArgsUsage: "", + Action: getExchangeInfo, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the info for", + }, + }, +} + +func getExchangeInfo(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getexchangeinfo") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetExchangeInfo(context.Background(), + &gctrpc.GenericExchangeNameRequest{ + Exchange: exchangeName, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getTickerCommand = cli.Command{ + Name: "getticker", + Usage: "gets the ticker for a specific currency pair and exchange", + ArgsUsage: " ", + Action: getTicker, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the ticker for", + }, + cli.StringFlag{ + Name: "pair", + Usage: "the currency pair to get the ticker for", + }, + cli.StringFlag{ + Name: "asset", + Usage: "the asset type of the currency pair to get the ticker for", + }, + }, +} + +func getTicker(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getticker") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + var currencyPair string + var assetType string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if c.IsSet("pair") { + currencyPair = c.String("pair") + } else { + currencyPair = c.Args().Get(1) + } + + if c.IsSet("asset") { + assetType = c.String("asset") + } else { + assetType = c.Args().Get(2) + } + + p := currency.NewPairFromString(currencyPair) + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetTicker(context.Background(), + &gctrpc.GetTickerRequest{ + Exchange: exchangeName, + Pair: &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }, + AssetType: assetType, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getTickersCommand = cli.Command{ + Name: "gettickers", + Usage: "gets all tickers for all enabled exchanes and currency pairs", + Action: getTickers, +} + +func getTickers(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetTickers(context.Background(), &gctrpc.GetTickersRequest{}) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getOrderbookCommand = cli.Command{ + Name: "getorderbook", + Usage: "gets the orderbook for a specific currency pair and exchange", + ArgsUsage: " ", + Action: getOrderbook, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the orderbook for", + }, + cli.StringFlag{ + Name: "pair", + Usage: "the currency pair to get the orderbook for", + }, + cli.StringFlag{ + Name: "asset", + Usage: "the asset type of the currency pair to get the orderbook for", + }, + }, +} + +func getOrderbook(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getorderbook") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + var currencyPair string + var assetType string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if c.IsSet("pair") { + currencyPair = c.String("pair") + } else { + currencyPair = c.Args().Get(1) + } + + if c.IsSet("asset") { + assetType = c.String("asset") + } else { + assetType = c.Args().Get(2) + } + + p := currency.NewPairFromString(currencyPair) + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetOrderbook(context.Background(), + &gctrpc.GetOrderbookRequest{ + Exchange: exchangeName, + Pair: &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }, + AssetType: assetType, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getOrderbooksCommand = cli.Command{ + Name: "getorderbooks", + Usage: "gets all orderbooks for all enabled exchanes and currency pairs", + Action: getOrderbooks, +} + +func getOrderbooks(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetOrderbooks(context.Background(), &gctrpc.GetOrderbooksRequest{}) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getAccountInfoCommand = cli.Command{ + Name: "getaccountinfo", + Usage: "gets the exchange account balance info", + ArgsUsage: "", + Action: getAccountInfo, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the account info for", + }, + }, +} + +func getAccountInfo(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getaccountinfo") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchange string + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetAccountInfo(context.Background(), + &gctrpc.GetAccountInfoRequest{ + Exchange: exchange, + }, + ) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getConfigCommand = cli.Command{ + Name: "getconfig", + Usage: "gets the config", + Action: getConfig, +} + +func getConfig(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetConfig(context.Background(), &gctrpc.GetConfigRequest{}) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getPortfolioCommand = cli.Command{ + Name: "getportfolio", + Usage: "gets the portfolio", + Action: getPortfolio, +} + +func getPortfolio(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetPortfolio(context.Background(), &gctrpc.GetPortfolioRequest{}) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getPortfolioSummaryCommand = cli.Command{ + Name: "getportfoliosummary", + Usage: "gets the portfolio summary", + Action: getPortfolioSummary, +} + +func getPortfolioSummary(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetPortfolioSummary(context.Background(), &gctrpc.GetPortfolioSummaryRequest{}) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var addPortfolioAddressCommand = cli.Command{ + Name: "addportfolioaddress", + Usage: "adds an address to the portfolio", + ArgsUsage: "
", + Action: addPortfolioAddress, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "address", + Usage: "the address to add to the portfolio", + }, + cli.StringFlag{ + Name: "coin_type", + Usage: "the coin type e.g ('BTC')", + }, + cli.StringFlag{ + Name: "description", + Usage: "description of the address", + }, + cli.Float64Flag{ + Name: "balance", + Usage: "balance of the address", + }, + }, +} + +func addPortfolioAddress(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "addportfolioaddress") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var address string + var coinType string + var description string + var balance float64 + + if c.IsSet("address") { + address = c.String("address") + } else { + address = c.Args().First() + } + + if c.IsSet("coin_type") { + coinType = c.String("coin_type") + } else { + coinType = c.Args().Get(1) + } + + if c.IsSet("description") { + description = c.String("asset") + } else { + description = c.Args().Get(2) + } + + if c.IsSet("balance") { + balance = c.Float64("balance") + } else { + balance, _ = strconv.ParseFloat(c.Args().Get(3), 64) + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.AddPortfolioAddress(context.Background(), + &gctrpc.AddPortfolioAddressRequest{ + Address: address, + CoinType: coinType, + Description: description, + Balance: balance, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var removePortfolioAddressCommand = cli.Command{ + Name: "removeportfolioaddress", + Usage: "removes an address from the portfolio", + ArgsUsage: "
", + Action: removePortfolioAddress, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "address", + Usage: "the address to add to the portfolio", + }, + cli.StringFlag{ + Name: "coin_type", + Usage: "the coin type e.g ('BTC')", + }, + cli.StringFlag{ + Name: "description", + Usage: "description of the address", + }, + }, +} + +func removePortfolioAddress(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "removeportfolioaddress") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var address string + var coinType string + var description string + + if c.IsSet("address") { + address = c.String("address") + } else { + address = c.Args().First() + } + + if c.IsSet("coin_type") { + coinType = c.String("coin_type") + } else { + coinType = c.Args().Get(1) + } + + if c.IsSet("description") { + description = c.String("asset") + } else { + description = c.Args().Get(2) + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.RemovePortfolioAddress(context.Background(), + &gctrpc.RemovePortfolioAddressRequest{ + Address: address, + CoinType: coinType, + Description: description, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getForexProvidersCommand = cli.Command{ + Name: "getforexproviders", + Usage: "gets the available forex providers", + Action: getForexProviders, +} + +func getForexProviders(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetForexProviders(context.Background(), &gctrpc.GetForexProvidersRequest{}) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getForexRatesCommand = cli.Command{ + Name: "getforexrates", + Usage: "gets forex rates", + Action: getForexRates, +} + +func getForexRates(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetForexRates(context.Background(), &gctrpc.GetForexRatesRequest{}) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getOrdersCommand = cli.Command{ + Name: "getorders", + Usage: "gets the open orders", + ArgsUsage: " ", + Action: getOrders, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get orders for", + }, + cli.StringFlag{ + Name: "asset_type", + Usage: "the asset type to get orders for", + }, + cli.StringFlag{ + Name: "pair", + Usage: "the currency pair to get orders for", + }, + }, +} + +func getOrders(c *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + var assetType string + var currencyPair string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if c.IsSet("asset_type") { + assetType = c.String("asset_type") + } else { + assetType = c.Args().Get(1) + } + + if c.IsSet("pair") { + currencyPair = c.String("pair") + } else { + currencyPair = c.Args().Get(2) + } + + p := currency.NewPairFromString(currencyPair) + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetOrders(context.Background(), &gctrpc.GetOrdersRequest{ + Exchange: exchangeName, + AssetType: assetType, + Pair: &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }, + }) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getOrderCommand = cli.Command{ + Name: "getorder", + Usage: "gets the specified order info", + ArgsUsage: " ", + Action: getOrder, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the order for", + }, + cli.StringFlag{ + Name: "order_id", + Usage: "the order id to retrieve", + }, + }, +} + +func getOrder(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getorder") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + var orderID string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if c.IsSet("order_id") { + orderID = c.String("order_id") + } else { + orderID = c.Args().Get(1) + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetOrder(context.Background(), &gctrpc.GetOrderRequest{ + Exchange: exchangeName, + OrderId: orderID, + }) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var submitOrderCommand = cli.Command{ + Name: "submitorder", + Usage: "submit order submits an exchange order", + ArgsUsage: " ", + Action: submitOrder, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to submit the order for", + }, + cli.StringFlag{ + Name: "currency_pair", + Usage: "the currency pair", + }, + cli.StringFlag{ + Name: "side", + Usage: "the order side to use (BUY OR SELL)", + }, + cli.StringFlag{ + Name: "order_type", + Usage: "the order type (MARKET OR LIMIT)", + }, + cli.Float64Flag{ + Name: "amount", + Usage: "the amount for the order", + }, + cli.Float64Flag{ + Name: "price", + Usage: "the price for the order", + }, + cli.StringFlag{ + Name: "client_id", + Usage: "the optional client order ID", + }, + }, +} + +func submitOrder(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "submitorder") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + var currencyPair string + var orderSide string + var orderType string + var amount float64 + var price float64 + var clientID string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if c.IsSet("currency_pair") { + currencyPair = c.String("currency_pair") + } else { + currencyPair = c.Args().Get(1) + } + + if c.IsSet("side") { + orderSide = c.String("side") + } else { + orderSide = c.Args().Get(2) + } + + if c.IsSet("order_type") { + orderType = c.String("order_type") + } else { + orderType = c.Args().Get(3) + } + + if c.IsSet("amount") { + amount = c.Float64("amount") + } else { + amount, _ = strconv.ParseFloat(c.Args().Get(4), 64) + } + + if c.IsSet("price") { + price = c.Float64("price") + } else { + price, _ = strconv.ParseFloat(c.Args().Get(5), 64) + } + + if c.IsSet("client_id") { + clientID = c.String("client_id") + } else { + clientID = c.Args().Get(6) + } + + p := currency.NewPairFromString(currencyPair) + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.SubmitOrder(context.Background(), &gctrpc.SubmitOrderRequest{ + Exchange: exchangeName, + Pair: &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }, + Side: orderSide, + OrderType: orderType, + Amount: amount, + Price: price, + ClientId: clientID, + }) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var cancelOrderCommand = cli.Command{ + Name: "cancelorder", + Usage: "cancel order cancels an exchange order", + ArgsUsage: " ", + Action: cancelOrder, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to cancel the order for", + }, + cli.StringFlag{ + Name: "account_id", + Usage: "the account id", + }, + cli.StringFlag{ + Name: "order_id", + Usage: "the order id", + }, + cli.StringFlag{ + Name: "currency_pair", + Usage: "the currency pair to cancel the order for", + }, + cli.StringFlag{ + Name: "asset_type", + Usage: "the asset type", + }, + cli.Float64Flag{ + Name: "wallet_address", + Usage: "the wallet address", + }, + cli.StringFlag{ + Name: "side", + Usage: "the order side", + }, + }, +} + +func cancelOrder(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "cancelorder") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + var accountID string + var orderID string + var currencyPair string + var assetType string + var walletAddress string + var orderSide string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if c.IsSet("order_id") { + orderID = c.String("order_id") + } else { + orderID = c.Args().Get(2) + } + + if c.IsSet("account_id") { + accountID = c.String("account_id") + } + + if c.IsSet("currency_pair") { + currencyPair = c.String("currency_pair") + } + + if c.IsSet("asset_type") { + assetType = c.String("asset_type") + } + + if c.IsSet("wallet_address") { + walletAddress = c.String("wallet_address") + } + + if c.IsSet("order_side") { + orderSide = c.String("order_side") + } + + var p currency.Pair + if len(currencyPair) > 0 { + p = currency.NewPairFromString(currencyPair) + } + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.CancelOrder(context.Background(), &gctrpc.CancelOrderRequest{ + Exchange: exchangeName, + AccountId: accountID, + OrderId: orderID, + Pair: &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }, + AssetType: assetType, + WalletAddress: walletAddress, + Side: orderSide, + }) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var cancelAllOrdersCommand = cli.Command{ + Name: "cancelallorders", + Usage: "cancels all orders (all or by exchange name)", + ArgsUsage: "", + Action: cancelAllOrders, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to cancel all orders on", + }, + }, +} + +func cancelAllOrders(c *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.CancelAllOrders(context.Background(), &gctrpc.CancelAllOrdersRequest{ + Exchange: exchangeName, + }) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getEventsCommand = cli.Command{ + Name: "getevents", + Usage: "gets all events", + Action: getEvents, +} + +func getEvents(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetEvents(context.Background(), &gctrpc.GetEventsRequest{}) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var addEventCommand = cli.Command{ + Name: "addevent", + Usage: "adds an event", + ArgsUsage: " ", + Action: addEvent, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to add an event for", + }, + cli.StringFlag{ + Name: "item", + Usage: "the item to trigger the event", + }, + cli.StringFlag{ + Name: "condition", + Usage: "the condition for the event", + }, + cli.Float64Flag{ + Name: "price", + Usage: "the price to trigger the event", + }, + cli.BoolFlag{ + Name: "check_bids", + Usage: "whether to check the bids (if false, asks will be used)", + }, + cli.BoolFlag{ + Name: "check_bids_and_asks", + Usage: "the wallet address", + }, + cli.Float64Flag{ + Name: "orderbook_amount", + Usage: "the orderbook amount to trigger the event", + }, + cli.StringFlag{ + Name: "currency_pair", + Usage: "the currency pair", + }, + cli.StringFlag{ + Name: "asset_type", + Usage: "the asset type", + }, + cli.StringFlag{ + Name: "action", + Usage: "the action for the event to perform upon trigger", + }, + }, +} + +func addEvent(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "addevent") + return nil + } + + var exchangeName string + var item string + var condition string + var price float64 + var checkBids bool + var checkBidsAndAsks bool + var orderbookAmount float64 + var currencyPair string + var assetType string + var action string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + return fmt.Errorf("exchange name is required") + } + + if c.IsSet("item") { + item = c.String("item") + } else { + return fmt.Errorf("item is required") + } + + if c.IsSet("condition") { + condition = c.String("condition") + } else { + return fmt.Errorf("condition is required") + } + + if c.IsSet("price") { + price = c.Float64("price") + } + + if c.IsSet("check_bids") { + checkBids = c.Bool("check_bids") + } + + if c.IsSet("check_bids_and_asks") { + checkBids = c.Bool("check_bids_and_asks") + } + + if c.IsSet("orderbook_amount") { + orderbookAmount = c.Float64("orderbook_amount") + } + + if c.IsSet("currency_pair") { + currencyPair = c.String("currency_pair") + } else { + return fmt.Errorf("currency pair is required") + } + + if c.IsSet("asset_type") { + assetType = c.String("asset_type") + } + + if c.IsSet("action") { + action = c.String("action") + } else { + return fmt.Errorf("action is required") + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + p := currency.NewPairFromString(currencyPair) + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.AddEvent(context.Background(), &gctrpc.AddEventRequest{ + Exchange: exchangeName, + Item: item, + ConditionParams: &gctrpc.ConditionParams{ + Condition: condition, + Price: price, + CheckBids: checkBids, + CheckBidsAndAsks: checkBidsAndAsks, + OrderbookAmount: orderbookAmount, + }, + Pair: &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }, + AssetType: assetType, + Action: action, + }) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var removeEventCommand = cli.Command{ + Name: "removeevent", + Usage: "removes an event", + ArgsUsage: "", + Action: removeEvent, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "event_id", + Usage: "the event id to remove", + }, + }, +} + +func removeEvent(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "removeevent") + return nil + } + + var eventID int64 + if c.IsSet("event_id") { + eventID = c.Int64("event_id") + } else { + evtID, err := strconv.Atoi(c.Args().Get(0)) + if err != nil { + return fmt.Errorf("unable to strconv input to int. Err: %s", err) + } + eventID = int64(evtID) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.RemoveEvent(context.Background(), + &gctrpc.RemoveEventRequest{Id: eventID}) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getCryptocurrencyDepositAddressesCommand = cli.Command{ + Name: "getcryptocurrencydepositaddresses", + Usage: "gets the cryptocurrency deposit addresses for an exchange", + ArgsUsage: "", + Action: getCryptocurrencyDepositAddresses, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the cryptocurrency deposit addresses for", + }, + }, +} + +func getCryptocurrencyDepositAddresses(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getcryptocurrencydepositaddresses") + return nil + } + + var exchangeName string + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetCryptocurrencyDepositAddresses(context.Background(), + &gctrpc.GetCryptocurrencyDepositAddressesRequest{Exchange: exchangeName}) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getCryptocurrencyDepositAddressCommand = cli.Command{ + Name: "getcryptocurrencydepositaddress", + Usage: "gets the cryptocurrency deposit address for an exchange and cryptocurrency", + ArgsUsage: " ", + Action: getCryptocurrencyDepositAddress, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the cryptocurrency deposit address for", + }, + cli.StringFlag{ + Name: "cryptocurrency", + Usage: "the cryptocurrency to get the deposit address for", + }, + }, +} + +func getCryptocurrencyDepositAddress(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getcryptocurrencydepositaddresses") + return nil + } + + var exchangeName string + var cryptocurrency string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if c.IsSet("cryptocurrency") { + cryptocurrency = c.String("cryptocurrency") + } else { + cryptocurrency = c.Args().Get(1) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetCryptocurrencyDepositAddress(context.Background(), + &gctrpc.GetCryptocurrencyDepositAddressRequest{ + Exchange: exchangeName, + Cryptocurrency: cryptocurrency, + }, + ) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var withdrawCryptocurrencyFundsCommand = cli.Command{ + Name: "withdrawcryptocurrencyfunds", + Usage: "withdraws cryptocurrency funds from the desired exchange", + ArgsUsage: " ", + Action: withdrawCryptocurrencyFunds, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to withdraw from", + }, + cli.StringFlag{ + Name: "cryptocurrency", + Usage: "the cryptocurrency to withdraw funds from", + }, + }, +} + +func withdrawCryptocurrencyFunds(_ *cli.Context) error { + return common.ErrNotYetImplemented +} + +var withdrawFiatFundsCommand = cli.Command{ + Name: "withdrawfiatfunds", + Usage: "withdraws fiat funds from the desired exchange", + ArgsUsage: " ", + Action: withdrawFiatFunds, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to withdraw from", + }, + cli.StringFlag{ + Name: "fiat_currency", + Usage: "the fiat currency to withdraw funds from", + }, + }, +} + +func withdrawFiatFunds(_ *cli.Context) error { + return common.ErrNotYetImplemented +} diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go new file mode 100644 index 00000000..e3d758d7 --- /dev/null +++ b/cmd/gctcli/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "runtime" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/core" + "github.com/thrasher-/gocryptotrader/gctrpc/auth" + "github.com/urfave/cli" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +var ( + host string + username string + password string +) + +func jsonOutput(in interface{}) { + j, err := json.MarshalIndent(in, "", " ") + if err != nil { + return + } + fmt.Print(string(j)) +} + +func setupClient() (*grpc.ClientConn, error) { + targetPath := filepath.Join(common.GetDefaultDataDir(runtime.GOOS), "tls", "cert.pem") + creds, err := credentials.NewClientTLSFromFile(targetPath, "") + if err != nil { + return nil, err + } + + opts := []grpc.DialOption{grpc.WithTransportCredentials(creds), + grpc.WithPerRPCCredentials(auth.BasicAuth{ + Username: username, + Password: password, + }), + } + conn, err := grpc.Dial(host, opts...) + if err != nil { + return nil, err + } + + return conn, err +} + +func main() { + app := cli.NewApp() + app.Name = "gctcli" + app.Version = core.Version(true) + app.Usage = "command line interface for managing the gocryptotrader daemon" + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "rpchost", + Value: "localhost:9052", + Usage: "the gRPC host to connect to", + Destination: &host, + }, + cli.StringFlag{ + Name: "rpcuser", + Value: "admin", + Usage: "the gRPC username", + Destination: &username, + }, + cli.StringFlag{ + Name: "rpcpassword", + Value: "Password", + Usage: "the gRPC password", + Destination: &password, + }, + } + app.Commands = []cli.Command{ + getInfoCommand, + getExchangesCommand, + enableExchangeCommand, + disableExchangeCommand, + getExchangeInfoCommand, + getTickerCommand, + getTickersCommand, + getOrderbookCommand, + getOrderbooksCommand, + getAccountInfoCommand, + getConfigCommand, + getPortfolioCommand, + getPortfolioSummaryCommand, + addPortfolioAddressCommand, + removePortfolioAddressCommand, + getForexProvidersCommand, + getForexRatesCommand, + getOrdersCommand, + getOrderCommand, + submitOrderCommand, + cancelOrderCommand, + cancelAllOrdersCommand, + getEventsCommand, + addEventCommand, + removeEventCommand, + getCryptocurrencyDepositAddressesCommand, + getCryptocurrencyDepositAddressCommand, + withdrawCryptocurrencyFundsCommand, + withdrawFiatFundsCommand, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} diff --git a/cmd/gen_cert/main.go b/cmd/gen_cert/main.go new file mode 100644 index 00000000..20161d6e --- /dev/null +++ b/cmd/gen_cert/main.go @@ -0,0 +1,105 @@ +package main + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "log" + "math/big" + "net" + "os" + "time" + + "github.com/thrasher-/gocryptotrader/common" +) + +func main() { + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + log.Fatalf("failed to generate private key: %s", err) + } + + notBefore := time.Now() + notAfter := notBefore.Add(time.Hour * 24 * 365) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Fatalf("failed to generate serial number: %s", err) + } + + host, err := os.Hostname() + if err != nil { + log.Fatalf("failed to get hostname: %s", err) + } + + dnsNames := []string{host} + if host != "localhost" { + dnsNames = append(dnsNames, "localhost") + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"gocryptotrader"}, + CommonName: host, + }, + NotBefore: notBefore, + NotAfter: notAfter, + IsCA: true, + BasicConstraintsValid: true, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + + IPAddresses: []net.IP{ + net.ParseIP("127.0.0.1"), + net.ParseIP("::1"), + }, + DNSNames: dnsNames, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey) + if err != nil { + log.Fatalf("Failed to create certificate: %s", err) + } + + certData := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + if certData == nil { + log.Fatalf("cert data is nil") + } + + b, err := x509.MarshalECPrivateKey(privKey) + if err != nil { + log.Printf("failed to marshal ECDSA private key: %s", err) + } + + keyData := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}) + if keyData == nil { + log.Fatalf("key pem data is nil") + } + + err = common.WriteFile("key.pem", keyData) + if err != nil { + log.Fatalf("failed to write key.pem file %s", err) + } + log.Printf("wrote key.pem file") + + err = common.WriteFile("cert.pem", certData) + if err != nil { + log.Fatalf("failed to write cert.pem file %s", err) + } + log.Printf("wrote cert.pem file") + + log.Printf("testing tls.LoadX509Keypair..") + _, err = tls.LoadX509KeyPair("cert.pem", "key.pem") + if err != nil { + log.Fatal(err) + } + + log.Printf("ok!") +} diff --git a/tools/huobi_auth/main.go b/cmd/huobi_auth/main.go similarity index 90% rename from tools/huobi_auth/main.go rename to cmd/huobi_auth/main.go index e174c6bd..d0a82a87 100644 --- a/tools/huobi_auth/main.go +++ b/cmd/huobi_auth/main.go @@ -6,6 +6,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/x509" + "encoding/json" "encoding/pem" "errors" "fmt" @@ -129,19 +130,21 @@ func main() { fmt.Println(string(pubKey)) type JSONGeneration struct { - APIAuthPEMKey string + PEMKey string + PEMKeySupport bool } r := JSONGeneration{ - APIAuthPEMKey: string(privKey), + PEMKey: string(privKey), + PEMKeySupport: true, } - resultk, err := common.JSONEncode(r) + resultk, err := json.MarshalIndent(r, "", " ") if err != nil { log.Fatal(err) } log.Println("Please visit https://github.com/huobiapi/API_Docs_en/wiki/Signing_API_Requests and follow from step 2 onwards.") - log.Printf("After completing the above instructions, please copy and paste the below key (including the following ',') into your Huobi exchange config file:\n\n") + log.Printf("After completing the above instructions, please copy and paste the below key in the API section (including the following ',') into your Huobi exchange config file:\n\n") fmt.Println(string(resultk[1:len(resultk)-1]) + ",") } diff --git a/cmd/otp_gen/otp_gen.go b/cmd/otp_gen/otp_gen.go new file mode 100644 index 00000000..7c1c5081 --- /dev/null +++ b/cmd/otp_gen/otp_gen.go @@ -0,0 +1,57 @@ +package main + +import ( + "flag" + "log" + "time" + + "github.com/pquerna/otp/totp" + "github.com/thrasher-/gocryptotrader/config" +) + +func containsOTP(cfg *config.Config) bool { + for x := range cfg.Exchanges { + if cfg.Exchanges[x].API.Credentials.OTPSecret != "" { + return true + } + } + return false +} + +func main() { + var inFile string + defaultCfg, err := config.GetFilePath("") + if err != nil { + log.Fatal(err) + } + + flag.StringVar(&inFile, "infile", defaultCfg, "The config input file to process.") + flag.Parse() + + log.Println("GoCryptoTrader: OTP code generator tool.") + + var cfg config.Config + err = cfg.LoadConfig(inFile) + if err != nil { + log.Fatal(err) + } + log.Println("Loaded config file.") + + if !containsOTP(&cfg) { + log.Println("No exchanges with OTP code stored. Exiting.") + } + + for { + for x := range cfg.Exchanges { + if cfg.Exchanges[x].API.Credentials.OTPSecret != "" { + code, err := totp.GenerateCode(cfg.Exchanges[x].API.Credentials.OTPSecret, time.Now()) + if err != nil { + log.Printf("Exchange %s: Failed to generate OTP code. Err: %s", cfg.Exchanges[x].Name, err) + continue + } + log.Printf("%s: %s", cfg.Exchanges[x].Name, code) + } + time.Sleep(time.Second) + } + } +} diff --git a/tools/portfolio/portfolio.go b/cmd/portfolio/portfolio.go similarity index 97% rename from tools/portfolio/portfolio.go rename to cmd/portfolio/portfolio.go index e775ef6f..23e8e3b7 100644 --- a/tools/portfolio/portfolio.go +++ b/cmd/portfolio/portfolio.go @@ -80,9 +80,9 @@ func main() { } log.Println("Loaded config file.") - displayCurrency = cfg.FiatDisplayCurrency + displayCurrency = cfg.Currency.FiatDisplayCurrency port := portfolio.Base{} - port.SeedPortfolio(cfg.Portfolio) + port.Seed(cfg.Portfolio) result := port.GetPortfolioSummary() log.Println("Fetched portfolio data.") @@ -132,6 +132,8 @@ func main() { } } else { bf := bitfinex.Bitfinex{} + bf.SetDefaults() + bf.Verbose = false ticker, errf := bf.GetTicker(y.Coin.String() + currency.USD.String()) if errf != nil { log.Println(errf) diff --git a/tools/portfolio/portfolio_test.go b/cmd/portfolio/portfolio_test.go similarity index 100% rename from tools/portfolio/portfolio_test.go rename to cmd/portfolio/portfolio_test.go diff --git a/tools/websocket_client/main.go b/cmd/websocket_client/main.go similarity index 89% rename from tools/websocket_client/main.go rename to cmd/websocket_client/main.go index 70c980c3..8115dea4 100644 --- a/tools/websocket_client/main.go +++ b/cmd/websocket_client/main.go @@ -8,7 +8,9 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) // Vars for the websocket client @@ -40,9 +42,9 @@ type WebsocketEventResponse 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"` + Exchange string `json:"exchangeName"` + Currency string `json:"currency"` + AssetType assets.AssetType `json:"assetType"` } // SendWebsocketEvent sends a websocket event message @@ -79,7 +81,7 @@ func main() { log.Fatalf("Failed to load config file: %s", err) } - listenAddr := cfg.Webserver.ListenAddress + listenAddr := cfg.RemoteControl.WebsocketRPC.ListenAddress wsHost := fmt.Sprintf("ws://%s:%d/ws", common.ExtractHost(listenAddr), common.ExtractPort(listenAddr)) log.Printf("Connecting to websocket host: %s", wsHost) @@ -95,8 +97,8 @@ func main() { log.Println("Authenticating..") var wsResp WebsocketEventResponse reqData := WebsocketAuth{ - Username: cfg.Webserver.AdminUsername, - Password: common.HexEncodeToString(common.GetSHA256([]byte(cfg.Webserver.AdminPassword))), + Username: cfg.RemoteControl.Username, + Password: crypto.HexEncodeToString(crypto.GetSHA256([]byte(cfg.RemoteControl.Password))), } err = SendWebsocketEvent("auth", reqData, &wsResp) if err != nil { @@ -155,7 +157,7 @@ func main() { dataReq := WebsocketOrderbookTickerRequest{ Exchange: "Bitfinex", Currency: "BTCUSD", - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, } err = SendWebsocketEvent("GetTicker", dataReq, &wsResp) diff --git a/common/common.go b/common/common.go index 8b7aa37b..426317d6 100644 --- a/common/common.go +++ b/common/common.go @@ -1,22 +1,12 @@ package common import ( - "crypto/hmac" - "crypto/md5" // nolint:gosec - "crypto/rand" - "crypto/sha1" // nolint:gosec - "crypto/sha256" - "crypto/sha512" - "encoding/base64" "encoding/csv" - "encoding/hex" "encoding/json" "errors" "fmt" - "hash" "io" "io/ioutil" - "math" "net/http" "net/url" "os" @@ -34,7 +24,8 @@ import ( // Vars for common.go operations var ( - HTTPClient *http.Client + HTTPClient *http.Client + HTTPUserAgent string // ErrNotYetImplemented defines a common error across the code base that // alerts of a function that has not been completed or tied into main code @@ -47,11 +38,6 @@ var ( // Const declarations for common.go operations const ( - HashSHA1 = iota - HashSHA256 - HashSHA512 - HashSHA512_384 - HashMD5 SatoshisPerBTC = 100000000 SatoshisPerLTC = 100000000 WeiPerEther = 1000000000000000000 @@ -71,95 +57,6 @@ func NewHTTPClientWithTimeout(t time.Duration) *http.Client { return h } -// GetRandomSalt returns a random salt -func GetRandomSalt(input []byte, saltLen int) ([]byte, error) { - if saltLen <= 0 { - return nil, errors.New("salt length is too small") - } - salt := make([]byte, saltLen) - if _, err := io.ReadFull(rand.Reader, salt); err != nil { - return nil, err - } - - var result []byte - if input != nil { - result = input - } - result = append(result, salt...) - return result, nil -} - -// GetMD5 returns a MD5 hash of a byte array -func GetMD5(input []byte) []byte { - m := md5.New() // nolint:gosec - m.Write(input) - return m.Sum(nil) -} - -// GetSHA512 returns a SHA512 hash of a byte array -func GetSHA512(input []byte) []byte { - sha := sha512.New() - sha.Write(input) - return sha.Sum(nil) -} - -// GetSHA256 returns a SHA256 hash of a byte array -func GetSHA256(input []byte) []byte { - sha := sha256.New() - sha.Write(input) - return sha.Sum(nil) -} - -// GetHMAC returns a keyed-hash message authentication code using the desired -// hashtype -func GetHMAC(hashType int, input, key []byte) []byte { - var hasher func() hash.Hash - - switch hashType { - case HashSHA1: - hasher = sha1.New - case HashSHA256: - hasher = sha256.New - case HashSHA512: - hasher = sha512.New - case HashSHA512_384: - hasher = sha512.New384 - case HashMD5: - hasher = md5.New - } - - h := hmac.New(hasher, key) - h.Write(input) - return h.Sum(nil) -} - -// Sha1ToHex takes a string, sha1 hashes it and return a hex string of the -// result -func Sha1ToHex(data string) string { - h := sha1.New() // nolint:gosec - h.Write([]byte(data)) - return hex.EncodeToString(h.Sum(nil)) -} - -// HexEncodeToString takes in a hexadecimal byte array and returns a string -func HexEncodeToString(input []byte) string { - return hex.EncodeToString(input) -} - -// Base64Decode takes in a Base64 string and returns a byte array and an error -func Base64Decode(input string) ([]byte, error) { - result, err := base64.StdEncoding.DecodeString(input) - if err != nil { - return nil, err - } - return result, nil -} - -// Base64Encode takes in a byte array then returns an encoded base64 string -func Base64Encode(input []byte) string { - return base64.StdEncoding.EncodeToString(input) -} - // StringSliceDifference concatenates slices together based on its index and // returns an individual string array func StringSliceDifference(slice1, slice2 []string) []string { @@ -260,27 +157,6 @@ func StringToLower(input string) string { return strings.ToLower(input) } -// RoundFloat rounds your floating point number to the desired decimal place -func RoundFloat(x float64, prec int) float64 { - var rounder float64 - pow := math.Pow(10, float64(prec)) - intermed := x * pow - _, frac := math.Modf(intermed) - intermed += .5 - x = .5 - if frac < 0.0 { - x = -.5 - intermed-- - } - if frac >= x { - rounder = math.Ceil(intermed) - } else { - rounder = math.Floor(intermed) - } - - return rounder / pow -} - // IsEnabled takes in a boolean param and returns a string if it is enabled // or disabled func IsEnabled(isEnabled bool) string { @@ -314,33 +190,6 @@ func YesOrNo(input string) bool { return false } -// CalculateAmountWithFee returns a calculated fee included amount on fee -func CalculateAmountWithFee(amount, fee float64) float64 { - return amount + CalculateFee(amount, fee) -} - -// CalculateFee returns a simple fee on amount -func CalculateFee(amount, fee float64) float64 { - return amount * (fee / 100) -} - -// CalculatePercentageGainOrLoss returns the percentage rise over a certain -// period -func CalculatePercentageGainOrLoss(priceNow, priceThen float64) float64 { - return (priceNow - priceThen) / priceThen * 100 -} - -// CalculatePercentageDifference returns the percentage of difference between -// multiple time periods -func CalculatePercentageDifference(amount, secondAmount float64) float64 { - return (amount - secondAmount) / ((amount + secondAmount) / 2) * 100 -} - -// CalculateNetProfit returns net profit -func CalculateNetProfit(amount, priceThen, priceNow, costs float64) float64 { - return (priceNow * amount) - (priceThen * amount) - costs -} - // SendHTTPRequest sends a request using the http package and returns a response // as a string and an error func SendHTTPRequest(method, urlPath string, headers map[string]string, body io.Reader) (string, error) { @@ -361,6 +210,10 @@ func SendHTTPRequest(method, urlPath string, headers map[string]string, body io. req.Header.Add(k, v) } + if HTTPUserAgent != "" && req.Header.Get("User-Agent") == "" { + req.Header.Add("User-Agent", HTTPUserAgent) + } + resp, err := HTTPClient.Do(req) if err != nil { return "", err diff --git a/common/common_test.go b/common/common_test.go index 8905bb6f..16d73227 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -1,7 +1,6 @@ package common import ( - "bytes" "net/url" "os" "os/user" @@ -74,142 +73,6 @@ func TestIsValidCryptoAddress(t *testing.T) { } } -func TestGetRandomSalt(t *testing.T) { - t.Parallel() - - _, err := GetRandomSalt(nil, -1) - if err == nil { - t.Fatal("Test failed. Expected err on negative salt length") - } - - salt, err := GetRandomSalt(nil, 10) - if err != nil { - t.Fatal(err) - } - - if len(salt) != 10 { - t.Fatal("Test failed. Expected salt of len=10") - } - - salt, err = GetRandomSalt([]byte("RAWR"), 12) - if err != nil { - t.Fatal(err) - } - - if len(salt) != 16 { - t.Fatal("Test failed. Expected salt of len=16") - } -} - -func TestGetMD5(t *testing.T) { - t.Parallel() - var originalString = []byte("I am testing the MD5 function in common!") - var expectedOutput = []byte("18fddf4a41ba90a7352765e62e7a8744") - actualOutput := GetMD5(originalString) - actualStr := HexEncodeToString(actualOutput) - if !bytes.Equal(expectedOutput, []byte(actualStr)) { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", - expectedOutput, []byte(actualStr)) - } - -} - -func TestGetSHA512(t *testing.T) { - t.Parallel() - var originalString = []byte("I am testing the GetSHA512 function in common!") - var expectedOutput = []byte( - `a2273f492ea73fddc4f25c267b34b3b74998bd8a6301149e1e1c835678e3c0b90859fce22e4e7af33bde1711cbb924809aedf5d759d648d61774b7185c5dc02b`, - ) - actualOutput := GetSHA512(originalString) - actualStr := HexEncodeToString(actualOutput) - if !bytes.Equal(expectedOutput, []byte(actualStr)) { - t.Errorf("Test failed. Expected '%x'. Actual '%x'", - expectedOutput, []byte(actualStr)) - } -} - -func TestGetSHA256(t *testing.T) { - t.Parallel() - var originalString = []byte("I am testing the GetSHA256 function in common!") - var expectedOutput = []byte( - "0962813d7a9f739cdcb7f0c0be0c2a13bd630167e6e54468266e4af6b1ad9303", - ) - actualOutput := GetSHA256(originalString) - actualStr := HexEncodeToString(actualOutput) - if !bytes.Equal(expectedOutput, []byte(actualStr)) { - t.Errorf("Test failed. Expected '%x'. Actual '%x'", expectedOutput, - []byte(actualStr)) - } -} - -func TestGetHMAC(t *testing.T) { - t.Parallel() - expectedSha1 := []byte{ - 74, 253, 245, 154, 87, 168, 110, 182, 172, 101, 177, 49, 142, 2, 253, 165, - 100, 66, 86, 246, - } - expectedsha256 := []byte{ - 54, 68, 6, 12, 32, 158, 80, 22, 142, 8, 131, 111, 248, 145, 17, 202, 224, - 59, 135, 206, 11, 170, 154, 197, 183, 28, 150, 79, 168, 105, 62, 102, - } - expectedsha512 := []byte{ - 249, 212, 31, 38, 23, 3, 93, 220, 81, 209, 214, 112, 92, 75, 126, 40, 109, - 95, 247, 182, 210, 54, 217, 224, 199, 252, 129, 226, 97, 201, 245, 220, 37, - 201, 240, 15, 137, 236, 75, 6, 97, 12, 190, 31, 53, 153, 223, 17, 214, 11, - 153, 203, 49, 29, 158, 217, 204, 93, 179, 109, 140, 216, 202, 71, - } - expectedsha512384 := []byte{ - 121, 203, 109, 105, 178, 68, 179, 57, 21, 217, 76, 82, 94, 100, 210, 1, 55, - 201, 8, 232, 194, 168, 165, 58, 192, 26, 193, 167, 254, 183, 172, 4, 189, - 158, 158, 150, 173, 33, 119, 125, 94, 13, 125, 89, 241, 184, 166, 128, - } - expectedmd5 := []byte{ - 113, 64, 132, 129, 213, 68, 231, 99, 252, 15, 175, 109, 198, 132, 139, 39, - } - - sha1 := GetHMAC(HashSHA1, []byte("Hello,World"), []byte("1234")) - if string(sha1) != string(expectedSha1) { - t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", - expectedSha1, sha1, - ) - } - sha256 := GetHMAC(HashSHA256, []byte("Hello,World"), []byte("1234")) - if string(sha256) != string(expectedsha256) { - t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", - expectedsha256, sha256, - ) - } - sha512 := GetHMAC(HashSHA512, []byte("Hello,World"), []byte("1234")) - if string(sha512) != string(expectedsha512) { - t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", - expectedsha512, sha512, - ) - } - sha512384 := GetHMAC(HashSHA512_384, []byte("Hello,World"), []byte("1234")) - if string(sha512384) != string(expectedsha512384) { - t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", - expectedsha512384, sha512384, - ) - } - md5 := GetHMAC(HashMD5, []byte("Hello World"), []byte("1234")) - if string(md5) != string(expectedmd5) { - t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", - expectedmd5, md5, - ) - } - -} - -func TestSha1Tohex(t *testing.T) { - t.Parallel() - expectedResult := "fcfbfcd7d31d994ef660f6972399ab5d7a890149" - actualResult := Sha1ToHex("Testing Sha1ToHex") - if actualResult != expectedResult { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", - expectedResult, actualResult) - } -} - func TestStringToLower(t *testing.T) { t.Parallel() upperCaseString := "HEY MAN" @@ -232,44 +95,6 @@ func TestStringToUpper(t *testing.T) { } } -func TestHexEncodeToString(t *testing.T) { - t.Parallel() - originalInput := []byte("string") - expectedOutput := "737472696e67" - actualResult := HexEncodeToString(originalInput) - if actualResult != expectedOutput { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", - expectedOutput, actualResult) - } -} - -func TestBase64Decode(t *testing.T) { - t.Parallel() - originalInput := "aGVsbG8=" - expectedOutput := []byte("hello") - actualResult, err := Base64Decode(originalInput) - if !bytes.Equal(actualResult, expectedOutput) { - t.Errorf("Test failed. Expected '%s'. Actual '%s'. Error: %s", - expectedOutput, actualResult, err) - } - - _, err = Base64Decode("-") - if err == nil { - t.Error("Test failed. Bad base64 string failed returned nil error") - } -} - -func TestBase64Encode(t *testing.T) { - t.Parallel() - originalInput := []byte("hello") - expectedOutput := "aGVsbG8=" - actualResult := Base64Encode(originalInput) - if actualResult != expectedOutput { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", - expectedOutput, actualResult) - } -} - func TestStringSliceDifference(t *testing.T) { t.Parallel() originalInputOne := []string{"hello"} @@ -429,22 +254,6 @@ func TestReplaceString(t *testing.T) { } } -func TestRoundFloat(t *testing.T) { - t.Parallel() - // 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.Errorf("Test failed. RoundFloat Expected '%f'. Actual '%f'.", - expectedOutput, actualOutput) - } - } -} - func TestYesOrNo(t *testing.T) { t.Parallel() if !YesOrNo("y") { @@ -458,68 +267,6 @@ func TestYesOrNo(t *testing.T) { } } -func TestCalculateFee(t *testing.T) { - t.Parallel() - originalInput := float64(1) - fee := float64(1) - expectedOutput := float64(0.01) - actualResult := CalculateFee(originalInput, fee) - if expectedOutput != actualResult { - t.Errorf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) - } -} - -func TestCalculateAmountWithFee(t *testing.T) { - t.Parallel() - originalInput := float64(1) - fee := float64(1) - expectedOutput := float64(1.01) - actualResult := CalculateAmountWithFee(originalInput, fee) - if expectedOutput != actualResult { - t.Errorf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) - } -} - -func TestCalculatePercentageGainOrLoss(t *testing.T) { - t.Parallel() - originalInput := float64(9300) - secondInput := float64(9000) - expectedOutput := 3.3333333333333335 - actualResult := CalculatePercentageGainOrLoss(originalInput, secondInput) - if expectedOutput != actualResult { - t.Errorf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) - } -} - -func TestCalculatePercentageDifference(t *testing.T) { - t.Parallel() - originalInput := float64(10) - secondAmount := float64(5) - expectedOutput := 66.66666666666666 - actualResult := CalculatePercentageDifference(originalInput, secondAmount) - if expectedOutput != actualResult { - t.Errorf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) - } -} - -func TestCalculateNetProfit(t *testing.T) { - t.Parallel() - amount := float64(5) - priceThen := float64(1) - priceNow := float64(10) - costs := float64(1) - expectedOutput := float64(44) - actualResult := CalculateNetProfit(amount, priceThen, priceNow, costs) - if expectedOutput != actualResult { - t.Errorf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) - } -} - func TestSendHTTPRequest(t *testing.T) { methodPost := "pOst" methodGet := "GeT" diff --git a/common/crypto/crypto.go b/common/crypto/crypto.go new file mode 100644 index 00000000..75ae6d9e --- /dev/null +++ b/common/crypto/crypto.go @@ -0,0 +1,113 @@ +package crypto + +import ( + "crypto/hmac" + "crypto/md5" // nolint:gosec + "crypto/rand" + "crypto/sha1" // nolint:gosec + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "encoding/hex" + "errors" + "hash" + "io" +) + +// Const declarations for common.go operations +const ( + HashSHA1 = iota + HashSHA256 + HashSHA512 + HashSHA512_384 + HashMD5 +) + +// HexEncodeToString takes in a hexadecimal byte array and returns a string +func HexEncodeToString(input []byte) string { + return hex.EncodeToString(input) +} + +// Base64Decode takes in a Base64 string and returns a byte array and an error +func Base64Decode(input string) ([]byte, error) { + result, err := base64.StdEncoding.DecodeString(input) + if err != nil { + return nil, err + } + return result, nil +} + +// Base64Encode takes in a byte array then returns an encoded base64 string +func Base64Encode(input []byte) string { + return base64.StdEncoding.EncodeToString(input) +} + +// GetRandomSalt returns a random salt +func GetRandomSalt(input []byte, saltLen int) ([]byte, error) { + if saltLen <= 0 { + return nil, errors.New("salt length is too small") + } + salt := make([]byte, saltLen) + if _, err := io.ReadFull(rand.Reader, salt); err != nil { + return nil, err + } + + var result []byte + if input != nil { + result = input + } + result = append(result, salt...) + return result, nil +} + +// GetMD5 returns a MD5 hash of a byte array +func GetMD5(input []byte) []byte { + m := md5.New() // nolint:gosec + m.Write(input) + return m.Sum(nil) +} + +// GetSHA512 returns a SHA512 hash of a byte array +func GetSHA512(input []byte) []byte { + sha := sha512.New() + sha.Write(input) + return sha.Sum(nil) +} + +// GetSHA256 returns a SHA256 hash of a byte array +func GetSHA256(input []byte) []byte { + sha := sha256.New() + sha.Write(input) + return sha.Sum(nil) +} + +// GetHMAC returns a keyed-hash message authentication code using the desired +// hashtype +func GetHMAC(hashType int, input, key []byte) []byte { + var hasher func() hash.Hash + + switch hashType { + case HashSHA1: + hasher = sha1.New + case HashSHA256: + hasher = sha256.New + case HashSHA512: + hasher = sha512.New + case HashSHA512_384: + hasher = sha512.New384 + case HashMD5: + hasher = md5.New + } + + h := hmac.New(hasher, key) + h.Write(input) + return h.Sum(nil) +} + +// Sha1ToHex takes a string, sha1 hashes it and return a hex string of the +// result +func Sha1ToHex(data string) string { + h := sha1.New() // nolint:gosec + h.Write([]byte(data)) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/common/crypto/crypto_test.go b/common/crypto/crypto_test.go new file mode 100644 index 00000000..38f929e7 --- /dev/null +++ b/common/crypto/crypto_test.go @@ -0,0 +1,180 @@ +package crypto + +import ( + "bytes" + "testing" +) + +func TestHexEncodeToString(t *testing.T) { + t.Parallel() + originalInput := []byte("string") + expectedOutput := "737472696e67" + actualResult := HexEncodeToString(originalInput) + if actualResult != expectedOutput { + t.Errorf("Test failed. Expected '%s'. Actual '%s'", + expectedOutput, actualResult) + } +} + +func TestBase64Decode(t *testing.T) { + t.Parallel() + originalInput := "aGVsbG8=" + expectedOutput := []byte("hello") + actualResult, err := Base64Decode(originalInput) + if !bytes.Equal(actualResult, expectedOutput) { + t.Errorf("Test failed. Expected '%s'. Actual '%s'. Error: %s", + expectedOutput, actualResult, err) + } + + _, err = Base64Decode("-") + if err == nil { + t.Error("Test failed. Bad base64 string failed returned nil error") + } +} + +func TestBase64Encode(t *testing.T) { + t.Parallel() + originalInput := []byte("hello") + expectedOutput := "aGVsbG8=" + actualResult := Base64Encode(originalInput) + if actualResult != expectedOutput { + t.Errorf("Test failed. Expected '%s'. Actual '%s'", + expectedOutput, actualResult) + } +} + +func TestGetRandomSalt(t *testing.T) { + t.Parallel() + + _, err := GetRandomSalt(nil, -1) + if err == nil { + t.Fatal("Test failed. Expected err on negative salt length") + } + + salt, err := GetRandomSalt(nil, 10) + if err != nil { + t.Fatal(err) + } + + if len(salt) != 10 { + t.Fatal("Test failed. Expected salt of len=10") + } + + salt, err = GetRandomSalt([]byte("RAWR"), 12) + if err != nil { + t.Fatal(err) + } + + if len(salt) != 16 { + t.Fatal("Test failed. Expected salt of len=16") + } +} + +func TestGetMD5(t *testing.T) { + t.Parallel() + var originalString = []byte("I am testing the MD5 function in common!") + var expectedOutput = []byte("18fddf4a41ba90a7352765e62e7a8744") + actualOutput := GetMD5(originalString) + actualStr := HexEncodeToString(actualOutput) + if !bytes.Equal(expectedOutput, []byte(actualStr)) { + t.Errorf("Test failed. Expected '%s'. Actual '%s'", + expectedOutput, []byte(actualStr)) + } + +} + +func TestGetSHA512(t *testing.T) { + t.Parallel() + var originalString = []byte("I am testing the GetSHA512 function in common!") + var expectedOutput = []byte( + `a2273f492ea73fddc4f25c267b34b3b74998bd8a6301149e1e1c835678e3c0b90859fce22e4e7af33bde1711cbb924809aedf5d759d648d61774b7185c5dc02b`, + ) + actualOutput := GetSHA512(originalString) + actualStr := HexEncodeToString(actualOutput) + if !bytes.Equal(expectedOutput, []byte(actualStr)) { + t.Errorf("Test failed. Expected '%x'. Actual '%x'", + expectedOutput, []byte(actualStr)) + } +} + +func TestGetSHA256(t *testing.T) { + t.Parallel() + var originalString = []byte("I am testing the GetSHA256 function in common!") + var expectedOutput = []byte( + "0962813d7a9f739cdcb7f0c0be0c2a13bd630167e6e54468266e4af6b1ad9303", + ) + actualOutput := GetSHA256(originalString) + actualStr := HexEncodeToString(actualOutput) + if !bytes.Equal(expectedOutput, []byte(actualStr)) { + t.Errorf("Test failed. Expected '%x'. Actual '%x'", expectedOutput, + []byte(actualStr)) + } +} + +func TestGetHMAC(t *testing.T) { + t.Parallel() + expectedSha1 := []byte{ + 74, 253, 245, 154, 87, 168, 110, 182, 172, 101, 177, 49, 142, 2, 253, 165, + 100, 66, 86, 246, + } + expectedsha256 := []byte{ + 54, 68, 6, 12, 32, 158, 80, 22, 142, 8, 131, 111, 248, 145, 17, 202, 224, + 59, 135, 206, 11, 170, 154, 197, 183, 28, 150, 79, 168, 105, 62, 102, + } + expectedsha512 := []byte{ + 249, 212, 31, 38, 23, 3, 93, 220, 81, 209, 214, 112, 92, 75, 126, 40, 109, + 95, 247, 182, 210, 54, 217, 224, 199, 252, 129, 226, 97, 201, 245, 220, 37, + 201, 240, 15, 137, 236, 75, 6, 97, 12, 190, 31, 53, 153, 223, 17, 214, 11, + 153, 203, 49, 29, 158, 217, 204, 93, 179, 109, 140, 216, 202, 71, + } + expectedsha512384 := []byte{ + 121, 203, 109, 105, 178, 68, 179, 57, 21, 217, 76, 82, 94, 100, 210, 1, 55, + 201, 8, 232, 194, 168, 165, 58, 192, 26, 193, 167, 254, 183, 172, 4, 189, + 158, 158, 150, 173, 33, 119, 125, 94, 13, 125, 89, 241, 184, 166, 128, + } + expectedmd5 := []byte{ + 113, 64, 132, 129, 213, 68, 231, 99, 252, 15, 175, 109, 198, 132, 139, 39, + } + + sha1 := GetHMAC(HashSHA1, []byte("Hello,World"), []byte("1234")) + if string(sha1) != string(expectedSha1) { + t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", + expectedSha1, sha1, + ) + } + sha256 := GetHMAC(HashSHA256, []byte("Hello,World"), []byte("1234")) + if string(sha256) != string(expectedsha256) { + t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", + expectedsha256, sha256, + ) + } + sha512 := GetHMAC(HashSHA512, []byte("Hello,World"), []byte("1234")) + if string(sha512) != string(expectedsha512) { + t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", + expectedsha512, sha512, + ) + } + sha512384 := GetHMAC(HashSHA512_384, []byte("Hello,World"), []byte("1234")) + if string(sha512384) != string(expectedsha512384) { + t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", + expectedsha512384, sha512384, + ) + } + md5 := GetHMAC(HashMD5, []byte("Hello World"), []byte("1234")) + if string(md5) != string(expectedmd5) { + t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", + expectedmd5, md5, + ) + } + +} + +func TestSha1Tohex(t *testing.T) { + t.Parallel() + expectedResult := "fcfbfcd7d31d994ef660f6972399ab5d7a890149" + actualResult := Sha1ToHex("Testing Sha1ToHex") + if actualResult != expectedResult { + t.Errorf("Test failed. Expected '%s'. Actual '%s'", + expectedResult, actualResult) + } +} diff --git a/common/math/math.go b/common/math/math.go new file mode 100644 index 00000000..fccd6e72 --- /dev/null +++ b/common/math/math.go @@ -0,0 +1,51 @@ +package math + +import "math" + +// CalculateAmountWithFee returns a calculated fee included amount on fee +func CalculateAmountWithFee(amount, fee float64) float64 { + return amount + CalculateFee(amount, fee) +} + +// CalculateFee returns a simple fee on amount +func CalculateFee(amount, fee float64) float64 { + return amount * (fee / 100) +} + +// CalculatePercentageGainOrLoss returns the percentage rise over a certain +// period +func CalculatePercentageGainOrLoss(priceNow, priceThen float64) float64 { + return (priceNow - priceThen) / priceThen * 100 +} + +// CalculatePercentageDifference returns the percentage of difference between +// multiple time periods +func CalculatePercentageDifference(amount, secondAmount float64) float64 { + return (amount - secondAmount) / ((amount + secondAmount) / 2) * 100 +} + +// CalculateNetProfit returns net profit +func CalculateNetProfit(amount, priceThen, priceNow, costs float64) float64 { + return (priceNow * amount) - (priceThen * amount) - costs +} + +// RoundFloat rounds your floating point number to the desired decimal place +func RoundFloat(x float64, prec int) float64 { + var rounder float64 + pow := math.Pow(10, float64(prec)) + intermed := x * pow + _, frac := math.Modf(intermed) + intermed += .5 + x = .5 + if frac < 0.0 { + x = -.5 + intermed-- + } + if frac >= x { + rounder = math.Ceil(intermed) + } else { + rounder = math.Floor(intermed) + } + + return rounder / pow +} diff --git a/common/math/math_test.go b/common/math/math_test.go new file mode 100644 index 00000000..93c43a21 --- /dev/null +++ b/common/math/math_test.go @@ -0,0 +1,81 @@ +package math + +import "testing" + +func TestCalculateFee(t *testing.T) { + t.Parallel() + originalInput := float64(1) + fee := float64(1) + expectedOutput := float64(0.01) + actualResult := CalculateFee(originalInput, fee) + if expectedOutput != actualResult { + t.Errorf( + "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) + } +} + +func TestCalculateAmountWithFee(t *testing.T) { + t.Parallel() + originalInput := float64(1) + fee := float64(1) + expectedOutput := float64(1.01) + actualResult := CalculateAmountWithFee(originalInput, fee) + if expectedOutput != actualResult { + t.Errorf( + "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) + } +} + +func TestCalculatePercentageGainOrLoss(t *testing.T) { + t.Parallel() + originalInput := float64(9300) + secondInput := float64(9000) + expectedOutput := 3.3333333333333335 + actualResult := CalculatePercentageGainOrLoss(originalInput, secondInput) + if expectedOutput != actualResult { + t.Errorf( + "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) + } +} + +func TestCalculatePercentageDifference(t *testing.T) { + t.Parallel() + originalInput := float64(10) + secondAmount := float64(5) + expectedOutput := 66.66666666666666 + actualResult := CalculatePercentageDifference(originalInput, secondAmount) + if expectedOutput != actualResult { + t.Errorf( + "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) + } +} + +func TestCalculateNetProfit(t *testing.T) { + t.Parallel() + amount := float64(5) + priceThen := float64(1) + priceNow := float64(10) + costs := float64(1) + expectedOutput := float64(44) + actualResult := CalculateNetProfit(amount, priceThen, priceNow, costs) + if expectedOutput != actualResult { + t.Errorf( + "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) + } +} + +func TestRoundFloat(t *testing.T) { + t.Parallel() + // 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.Errorf("Test failed. RoundFloat Expected '%f'. Actual '%f'.", + expectedOutput, actualOutput) + } + } +} diff --git a/communications/base/base.go b/communications/base/base.go index accfb369..5a6701fd 100644 --- a/communications/base/base.go +++ b/communications/base/base.go @@ -6,14 +6,15 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) // global vars contain staged update data that will be sent to the communication // mediums var ( - TickerStaged map[string]map[string]map[string]ticker.Price - OrderbookStaged map[string]map[string]map[string]Orderbook + TickerStaged map[string]map[assets.AssetType]map[string]ticker.Price + OrderbookStaged map[string]map[assets.AssetType]map[string]Orderbook PortfolioStaged Portfolio SettingsStaged Settings ServiceStarted time.Time diff --git a/communications/base/base_interface.go b/communications/base/base_interface.go index ae8f0ab1..e961b2fc 100644 --- a/communications/base/base_interface.go +++ b/communications/base/base_interface.go @@ -4,6 +4,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" @@ -25,8 +26,8 @@ type ICommunicate interface { // Setup sets up communication variables and intiates a connection to the // communication mediums func (c IComm) Setup() { - TickerStaged = make(map[string]map[string]map[string]ticker.Price) - OrderbookStaged = make(map[string]map[string]map[string]Orderbook) + TickerStaged = make(map[string]map[assets.AssetType]map[string]ticker.Price) + OrderbookStaged = make(map[string]map[assets.AssetType]map[string]Orderbook) ServiceStarted = time.Now() for i := range c { @@ -68,12 +69,12 @@ func (c IComm) GetEnabledCommunicationMediums() { } // StageTickerData stages updated ticker data for the communications package -func (c IComm) StageTickerData(exchangeName, assetType string, tickerPrice *ticker.Price) { +func (c IComm) StageTickerData(exchangeName string, assetType assets.AssetType, tickerPrice *ticker.Price) { m.Lock() defer m.Unlock() if _, ok := TickerStaged[exchangeName]; !ok { - TickerStaged[exchangeName] = make(map[string]map[string]ticker.Price) + TickerStaged[exchangeName] = make(map[assets.AssetType]map[string]ticker.Price) } if _, ok := TickerStaged[exchangeName][assetType]; !ok { @@ -85,12 +86,12 @@ func (c IComm) StageTickerData(exchangeName, assetType string, tickerPrice *tick // StageOrderbookData stages updated orderbook data for the communications // package -func (c IComm) StageOrderbookData(exchangeName, assetType string, ob *orderbook.Base) { +func (c IComm) StageOrderbookData(exchangeName string, assetType assets.AssetType, ob *orderbook.Base) { m.Lock() defer m.Unlock() if _, ok := OrderbookStaged[exchangeName]; !ok { - OrderbookStaged[exchangeName] = make(map[string]map[string]Orderbook) + OrderbookStaged[exchangeName] = make(map[assets.AssetType]map[string]Orderbook) } if _, ok := OrderbookStaged[exchangeName][assetType]; !ok { diff --git a/communications/slack/README.md b/communications/slack/README.md index b56e0617..96639baf 100644 --- a/communications/slack/README.md +++ b/communications/slack/README.md @@ -63,6 +63,38 @@ err := s.Connect Once the bot has started you can interact with the bot using these commands via Slack: +main.go +```go +var b exchange.IBotExchange + +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bitfinex" { + b = bot.Exchanges[i] + } +} + +// Public calls - wrapper functions + +// Fetches current ticker information +tick, err := b.FetchTicker() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := b.FetchOrderbook() +if err != nil { + // Handle error +} + +// Private calls - wrapper functions - make sure your APIKEY and APISECRET are +// set and AuthenticatedAPISupport is set to true + +// Fetches current account information +accountInfo, err := b.GetAccountInfo() +if err != nil { + // Handle error +} ``` !status - Displays current working status of bot !help - Displays help text diff --git a/config/config.go b/config/config.go index 9aa9e622..dc6f7f23 100644 --- a/config/config.go +++ b/config/config.go @@ -20,8 +20,8 @@ import ( "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/forexprovider" "github.com/thrasher-/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-/gocryptotrader/exchanges/assets" log "github.com/thrasher-/gocryptotrader/logger" - "github.com/thrasher-/gocryptotrader/portfolio" ) // Constants declared here are filename strings and test strings @@ -35,9 +35,13 @@ const ( configFileEncryptionDisabled = -1 configPairsLastUpdatedWarningThreshold = 30 // 30 days configDefaultHTTPTimeout = time.Second * 15 - configMaxAuthFailres = 3 defaultNTPAllowedDifference = 50000000 defaultNTPAllowedNegativeDifference = 50000000 + configMaxAuthFailures = 3 + + DefaultAPIKey = "Key" + DefaultAPISecret = "Secret" + DefaultAPIClientID = "ClientID" ) // Constants here hold some messages @@ -77,207 +81,6 @@ var ( m sync.Mutex ) -// WebserverConfig struct holds the prestart variables for the webserver. -type WebserverConfig struct { - Enabled bool `json:"enabled"` - AdminUsername string `json:"adminUsername"` - AdminPassword string `json:"adminPassword"` - ListenAddress string `json:"listenAddress"` - WebsocketConnectionLimit int `json:"websocketConnectionLimit"` - WebsocketMaxAuthFailures int `json:"websocketMaxAuthFailures"` - WebsocketAllowInsecureOrigin bool `json:"websocketAllowInsecureOrigin"` -} - -// Post holds the bot configuration data -type Post struct { - Data Config `json:"data"` -} - -// CurrencyPairFormatConfig stores the users preferred currency pair display -type CurrencyPairFormatConfig struct { - Uppercase bool `json:"uppercase"` - Delimiter string `json:"delimiter,omitempty"` - Separator string `json:"separator,omitempty"` - Index string `json:"index,omitempty"` -} - -// Config is the overarching object that holds all the information for -// prestart management of Portfolio, Communications, Webserver and Enabled -// Exchanges -type Config struct { - Name string `json:"name"` - EncryptConfig int `json:"encryptConfig"` - GlobalHTTPTimeout time.Duration `json:"globalHTTPTimeout"` - Logging log.Logging `json:"logging"` - Profiler ProfilerConfig `json:"profiler"` - NTPClient NTPClientConfig `json:"ntpclient"` - Currency CurrencyConfig `json:"currencyConfig"` - Communications CommunicationsConfig `json:"communications"` - Portfolio portfolio.Base `json:"portfolioAddresses"` - Webserver WebserverConfig `json:"webserver"` - Exchanges []ExchangeConfig `json:"exchanges"` - BankAccounts []BankAccount `json:"bankAccounts"` - ConnectionMonitor ConnectionMonitorConfig `json:"connectionMonitor"` - - // Deprecated config settings, will be removed at a future date - CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat,omitempty"` - FiatDisplayCurrency currency.Code `json:"fiatDispayCurrency,omitempty"` - Cryptocurrencies currency.Currencies `json:"cryptocurrencies,omitempty"` - SMS *SMSGlobalConfig `json:"smsGlobal,omitempty"` -} - -// ConnectionMonitorConfig defines the connection monitor variables to ensure -// that there is internet connectivity -type ConnectionMonitorConfig struct { - DNSList []string `json:"preferredDNSList"` - PublicDomainList []string `json:"preferredDomainList"` - CheckInterval time.Duration `json:"checkInterval"` -} - -// ProfilerConfig defines the profiler configuration to enable pprof -type ProfilerConfig struct { - Enabled bool `json:"enabled"` -} - -// NTPClientConfig defines a network time protocol configuration to allow for -// positive and negative differences -type NTPClientConfig struct { - Level int `json:"enabled"` - Pool []string `json:"pool"` - AllowedDifference *time.Duration `json:"allowedDifference"` - AllowedNegativeDifference *time.Duration `json:"allowedNegativeDifference"` -} - -// ExchangeConfig holds all the information needed for each enabled Exchange. -type ExchangeConfig struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - Verbose bool `json:"verbose"` - Websocket bool `json:"websocket"` - UseSandbox bool `json:"useSandbox"` - RESTPollingDelay time.Duration `json:"restPollingDelay"` - HTTPTimeout time.Duration `json:"httpTimeout"` - HTTPUserAgent string `json:"httpUserAgent"` - HTTPDebugging bool `json:"httpDebugging"` - AuthenticatedAPISupport bool `json:"authenticatedApiSupport"` - APIKey string `json:"apiKey"` - APISecret string `json:"apiSecret"` - APIAuthPEMKeySupport bool `json:"apiAuthPemKeySupport,omitempty"` - APIAuthPEMKey string `json:"apiAuthPemKey,omitempty"` - APIURL string `json:"apiUrl"` - APIURLSecondary string `json:"apiUrlSecondary"` - ProxyAddress string `json:"proxyAddress"` - WebsocketURL string `json:"websocketUrl"` - ClientID string `json:"clientId,omitempty"` - AvailablePairs currency.Pairs `json:"availablePairs"` - EnabledPairs currency.Pairs `json:"enabledPairs"` - BaseCurrencies currency.Currencies `json:"baseCurrencies"` - AssetTypes string `json:"assetTypes"` - SupportsAutoPairUpdates bool `json:"supportsAutoPairUpdates"` - PairsLastUpdated int64 `json:"pairsLastUpdated,omitempty"` - ConfigCurrencyPairFormat *CurrencyPairFormatConfig `json:"configCurrencyPairFormat"` - RequestCurrencyPairFormat *CurrencyPairFormatConfig `json:"requestCurrencyPairFormat"` - BankAccounts []BankAccount `json:"bankAccounts"` -} - -// BankAccount holds differing bank account details by supported funding -// currency -type BankAccount struct { - Enabled bool `json:"enabled,omitempty"` - BankName string `json:"bankName"` - BankAddress string `json:"bankAddress"` - AccountName string `json:"accountName"` - AccountNumber string `json:"accountNumber"` - SWIFTCode string `json:"swiftCode"` - IBAN string `json:"iban"` - BSBNumber string `json:"bsbNumber,omitempty"` - SupportedCurrencies string `json:"supportedCurrencies"` - SupportedExchanges string `json:"supportedExchanges,omitempty"` -} - -// BankTransaction defines a related banking transaction -type BankTransaction struct { - ReferenceNumber string `json:"referenceNumber"` - TransactionNumber string `json:"transactionNumber"` - PaymentInstructions string `json:"paymentInstructions"` -} - -// CurrencyConfig holds all the information needed for currency related manipulation -type CurrencyConfig struct { - ForexProviders []base.Settings `json:"forexProviders"` - CryptocurrencyProvider CryptocurrencyProvider `json:"cryptocurrencyProvider"` - Cryptocurrencies currency.Currencies `json:"cryptocurrencies"` - CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat"` - FiatDisplayCurrency currency.Code `json:"fiatDisplayCurrency"` - CurrencyFileUpdateDuration time.Duration `json:"currencyFileUpdateDuration"` - ForeignExchangeUpdateDuration time.Duration `json:"foreignExchangeUpdateDuration"` -} - -// CryptocurrencyProvider defines coinmarketcap tools -type CryptocurrencyProvider struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - Verbose bool `json:"verbose"` - APIkey string `json:"apiKey"` - AccountPlan string `json:"accountPlan"` -} - -// CommunicationsConfig holds all the information needed for each -// enabled communication package -type CommunicationsConfig struct { - SlackConfig SlackConfig `json:"slack"` - SMSGlobalConfig SMSGlobalConfig `json:"smsGlobal"` - SMTPConfig SMTPConfig `json:"smtp"` - TelegramConfig TelegramConfig `json:"telegram"` -} - -// SlackConfig holds all variables to start and run the Slack package -type SlackConfig struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - Verbose bool `json:"verbose"` - TargetChannel string `json:"targetChannel"` - VerificationToken string `json:"verificationToken"` -} - -// SMSContact stores the SMS contact info -type SMSContact struct { - Name string `json:"name"` - Number string `json:"number"` - Enabled bool `json:"enabled"` -} - -// SMSGlobalConfig structure holds all the variables you need for instant -// messaging and broadcast used by SMSGlobal -type SMSGlobalConfig struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - Verbose bool `json:"verbose"` - Username string `json:"username"` - Password string `json:"password"` - Contacts []SMSContact `json:"contacts"` -} - -// SMTPConfig holds all variables to start and run the SMTP package -type SMTPConfig struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - Verbose bool `json:"verbose"` - Host string `json:"host"` - Port string `json:"port"` - AccountName string `json:"accountName"` - AccountPassword string `json:"accountPassword"` - RecipientList string `json:"recipientList"` -} - -// TelegramConfig holds all variables to start and run the Telegram package -type TelegramConfig struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - Verbose bool `json:"verbose"` - VerificationToken string `json:"verificationToken"` -} - // GetCurrencyConfig returns currency configurations func (c *Config) GetCurrencyConfig() CurrencyConfig { return c.Currency @@ -400,6 +203,33 @@ func (c *Config) CheckClientBankAccounts() error { return nil } +// PurgeExchangeAPICredentials purges the stored API credentials +func (c *Config) PurgeExchangeAPICredentials() { + m.Lock() + defer m.Unlock() + for x := range c.Exchanges { + if !c.Exchanges[x].API.AuthenticatedSupport { + continue + } + c.Exchanges[x].API.AuthenticatedSupport = false + + if c.Exchanges[x].API.CredentialsValidator.RequiresKey { + c.Exchanges[x].API.Credentials.Key = DefaultAPIKey + } + + if c.Exchanges[x].API.CredentialsValidator.RequiresSecret { + c.Exchanges[x].API.Credentials.Secret = DefaultAPISecret + } + + if c.Exchanges[x].API.CredentialsValidator.RequiresClientID { + c.Exchanges[x].API.Credentials.ClientID = DefaultAPIClientID + } + + c.Exchanges[x].API.Credentials.PEMKey = "" + c.Exchanges[x].API.Credentials.OTPSecret = "" + } +} + // GetCommunicationsConfig returns the communications configuration func (c *Config) GetCommunicationsConfig() CommunicationsConfig { m.Lock() @@ -554,92 +384,305 @@ func (c *Config) CheckCommunicationsConfig() { } } -// CheckPairConsistency checks to see if the enabled pair exists in the -// available pairs list -func (c *Config) CheckPairConsistency(exchName string) error { - enabledPairs, err := c.GetEnabledPairs(exchName) +// GetExchangeAssetTypes returns the exchanges supported asset types +func (c *Config) GetExchangeAssetTypes(exchName string) (assets.AssetTypes, error) { + exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { - return err + return assets.AssetTypes{}, err } - availPairs, err := c.GetAvailablePairs(exchName) - if err != nil { - return err + if exchCfg.CurrencyPairs == nil { + return assets.AssetTypes{}, fmt.Errorf("exchange %s currency pairs is nil", exchName) } - var pairs, pairsRemoved currency.Pairs - update := false - for x := range enabledPairs { - if !availPairs.Contains(enabledPairs[x], true) { - update = true - pairsRemoved = append(pairsRemoved, enabledPairs[x]) - continue + return exchCfg.CurrencyPairs.AssetTypes, nil +} + +// SupportsExchangeAssetType returns whether or not the exchange supports the supplied asset type +func (c *Config) SupportsExchangeAssetType(exchName string, assetType assets.AssetType) (bool, error) { + exchCfg, err := c.GetExchangeConfig(exchName) + if err != nil { + return false, err + } + + if exchCfg.CurrencyPairs == nil { + return false, fmt.Errorf("exchange %s currency pairs is nil", exchName) + } + + if !assets.IsValid(assetType) { + return false, fmt.Errorf("exchange %s invalid asset types", exchName) + } + + return exchCfg.CurrencyPairs.AssetTypes.Contains(assetType), nil +} + +// CheckExchangeAssetsConsistency checks the exchanges supported assets compared to the stored +// entries and removes any non supported +func (c *Config) CheckExchangeAssetsConsistency(exchName string) { + exchCfg, err := c.GetExchangeConfig(exchName) + if err != nil { + return + } + + if exchCfg.CurrencyPairs == nil { + return + } + + exchangeAssetTypes, err := c.GetExchangeAssetTypes(exchName) + if err != nil { + return + } + + storedAssetTypes := exchCfg.CurrencyPairs.GetAssetTypes() + for x := range storedAssetTypes { + if !exchangeAssetTypes.Contains(storedAssetTypes[x]) { + log.Warnf("%s has non-needed stored asset type %v. Removing..", exchName, storedAssetTypes[x]) + exchCfg.CurrencyPairs.Delete(storedAssetTypes[x]) } - pairs = append(pairs, enabledPairs[x]) - } - - if !update { - return nil } +} +// SetPairs sets the exchanges currency pairs +func (c *Config) SetPairs(exchName string, assetType assets.AssetType, enabled bool, pairs currency.Pairs) error { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return err } - if len(pairs) == 0 { - exchCfg.EnabledPairs = append(exchCfg.EnabledPairs, - availPairs.GetRandomPair()) - log.Debugf("Exchange %s: No enabled pairs found in available pairs, randomly added %v\n", - exchName, - exchCfg.EnabledPairs) - } else { - exchCfg.EnabledPairs = pairs - } - - err = c.UpdateExchangeConfig(&exchCfg) + supports, err := c.SupportsExchangeAssetType(exchName, assetType) if err != nil { return err } - log.Debugf("Exchange %s: Removing enabled pair(s) %v from enabled pairs as it isn't an available pair", - exchName, - pairsRemoved.Strings()) + if !supports { + return fmt.Errorf("exchange %s does not support asset type %v", exchName, assetType) + } + + exchCfg.CurrencyPairs.StorePairs(assetType, pairs, enabled) + return nil +} + +// GetCurrencyPairConfig returns currency pair config for the desired exchange and asset type +func (c *Config) GetCurrencyPairConfig(exchName string, assetType assets.AssetType) (*currency.PairStore, error) { + exchCfg, err := c.GetExchangeConfig(exchName) + if err != nil { + return nil, err + } + + supports, err := c.SupportsExchangeAssetType(exchName, assetType) + if err != nil { + return nil, err + } + + if !supports { + return nil, fmt.Errorf("exchange %s does not support asset type %v", exchName, assetType) + } + + return exchCfg.CurrencyPairs.Get(assetType), nil +} + +// CheckPairConfigFormats checks to see if the pair config format is valid +func (c *Config) CheckPairConfigFormats(exchName string) error { + assetTypes, err := c.GetExchangeAssetTypes(exchName) + if err != nil { + return err + } + + for x := range assetTypes { + assetType := assetTypes[x] + pairFmt, err := c.GetPairFormat(exchName, assetType) + if err != nil { + return err + } + + pairs, err := c.GetCurrencyPairConfig(exchName, assetType) + if err != nil { + return err + } + + if pairs == nil { + continue + } + + if len(pairs.Available) == 0 || len(pairs.Enabled) == 0 { + continue + } + + checker := func(enabled bool) error { + pairsType := "enabled" + loadedPairs := pairs.Enabled + if !enabled { + pairsType = "available" + loadedPairs = pairs.Available + } + + for y := range loadedPairs { + if pairFmt.Delimiter != "" { + if !common.StringContains(loadedPairs[y].String(), pairFmt.Delimiter) { + return fmt.Errorf("exchange %s %s %v pairs does not contain delimiter", exchName, pairsType, assetType) + } + } + + if pairFmt.Index != "" { + if !common.StringContains(loadedPairs[y].String(), pairFmt.Index) { + return fmt.Errorf("exchange %s %s %v pairs does not contain an index", exchName, pairsType, assetType) + } + } + } + return nil + } + + err = checker(true) + if err != nil { + return err + } + + err = checker(false) + if err != nil { + return err + } + } + + return nil +} + +// CheckPairConsistency checks to see if the enabled pair exists in the +// available pairs list +func (c *Config) CheckPairConsistency(exchName string) error { + assetTypes, err := c.GetExchangeAssetTypes(exchName) + if err != nil { + return err + } + + err = c.CheckPairConfigFormats(exchName) + if err != nil { + return err + } + + for x := range assetTypes { + enabledPairs, err := c.GetEnabledPairs(exchName, assetTypes[x]) + if err != nil { + return err + } + + availPairs, err := c.GetAvailablePairs(exchName, assetTypes[x]) + if err != nil { + return err + } + + if len(availPairs) == 0 { + continue + } + + var pairs, pairsRemoved currency.Pairs + update := false + + if len(enabledPairs) > 0 { + for x := range enabledPairs { + if !availPairs.Contains(enabledPairs[x], true) { + update = true + pairsRemoved = append(pairsRemoved, enabledPairs[x]) + continue + } + pairs = append(pairs, enabledPairs[x]) + } + } else { + update = true + } + + if !update { + continue + } + + if len(pairs) == 0 || len(enabledPairs) == 0 { + newPair := availPairs.GetRandomPair() + err = c.SetPairs(exchName, assetTypes[x], true, + currency.Pairs{newPair}, + ) + if err != nil { + return fmt.Errorf("exchange %s failed to set pairs: %v", exchName, err) + } + log.Warnf("Exchange %s: No enabled pairs found in available pairs, randomly added %v pair.\n", exchName, newPair) + continue + } else { + err = c.SetPairs(exchName, assetTypes[x], true, pairs) + if err != nil { + return fmt.Errorf("exchange %s failed to set pairs: %v", exchName, err) + } + } + log.Warnf("Exchange %s: Removing enabled pair(s) %v from enabled pairs as it isn't an available pair.", exchName, pairsRemoved.Strings()) + } return nil } // SupportsPair returns true or not whether the exchange supports the supplied // pair -func (c *Config) SupportsPair(exchName string, p currency.Pair) (bool, error) { - pairs, err := c.GetAvailablePairs(exchName) +func (c *Config) SupportsPair(exchName string, p currency.Pair, assetType assets.AssetType) (bool, error) { + pairs, err := c.GetAvailablePairs(exchName, assetType) if err != nil { return false, err } return pairs.Contains(p, false), nil } +// GetPairFormat returns the exchanges pair config storage format +func (c *Config) GetPairFormat(exchName string, assetType assets.AssetType) (currency.PairFormat, error) { + exchCfg, err := c.GetExchangeConfig(exchName) + if err != nil { + return currency.PairFormat{}, err + } + + if exchCfg.CurrencyPairs == nil { + return currency.PairFormat{}, errors.New("exchange currency pairs type is nil") + } + + if exchCfg.CurrencyPairs.UseGlobalFormat { + return *exchCfg.CurrencyPairs.ConfigFormat, nil + } + + return *exchCfg.CurrencyPairs.Get(assetType).ConfigFormat, nil +} + // GetAvailablePairs returns a list of currency pairs for a specifc exchange -func (c *Config) GetAvailablePairs(exchName string) (currency.Pairs, error) { +func (c *Config) GetAvailablePairs(exchName string, assetType assets.AssetType) (currency.Pairs, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err } - return exchCfg.AvailablePairs.Format(exchCfg.ConfigCurrencyPairFormat.Delimiter, - exchCfg.ConfigCurrencyPairFormat.Index, - exchCfg.ConfigCurrencyPairFormat.Uppercase), nil + pairFormat, err := c.GetPairFormat(exchName, assetType) + if err != nil { + return nil, err + } + + pairs := exchCfg.CurrencyPairs.Get(assetType) + if pairs == nil { + return nil, nil + } + + return pairs.Available.Format(pairFormat.Delimiter, pairFormat.Index, + pairFormat.Uppercase), nil } // GetEnabledPairs returns a list of currency pairs for a specifc exchange -func (c *Config) GetEnabledPairs(exchName string) (currency.Pairs, error) { +func (c *Config) GetEnabledPairs(exchName string, assetType assets.AssetType) ([]currency.Pair, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err } - return exchCfg.EnabledPairs.Format(exchCfg.ConfigCurrencyPairFormat.Delimiter, - exchCfg.ConfigCurrencyPairFormat.Index, - exchCfg.ConfigCurrencyPairFormat.Uppercase), nil + pairFormat, err := c.GetPairFormat(exchName, assetType) + if err != nil { + return nil, err + } + + pairs := exchCfg.CurrencyPairs.Get(assetType) + if pairs == nil { + return nil, nil + } + + return pairs.Enabled.Format(pairFormat.Delimiter, pairFormat.Index, + pairFormat.Uppercase), nil } // GetEnabledExchanges returns a list of enabled exchanges @@ -677,7 +720,7 @@ func (c *Config) CountEnabledExchanges() int { // GetConfigCurrencyPairFormat returns the config currency pair format // for a specific exchange -func (c *Config) GetConfigCurrencyPairFormat(exchName string) (*CurrencyPairFormatConfig, error) { +func (c *Config) GetConfigCurrencyPairFormat(exchName string) (*currency.PairFormat, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err @@ -687,7 +730,7 @@ func (c *Config) GetConfigCurrencyPairFormat(exchName string) (*CurrencyPairForm // GetRequestCurrencyPairFormat returns the request currency pair format // for a specific exchange -func (c *Config) GetRequestCurrencyPairFormat(exchName string) (*CurrencyPairFormatConfig, error) { +func (c *Config) GetRequestCurrencyPairFormat(exchName string) (*currency.PairFormat, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err @@ -708,15 +751,15 @@ func (c *Config) GetAllExchangeConfigs() []ExchangeConfig { } // GetExchangeConfig returns exchange configurations by its indivdual name -func (c *Config) GetExchangeConfig(name string) (ExchangeConfig, error) { +func (c *Config) GetExchangeConfig(name string) (*ExchangeConfig, error) { m.Lock() defer m.Unlock() for i := range c.Exchanges { if strings.EqualFold(c.Exchanges[i].Name, name) { - return c.Exchanges[i], nil + return &c.Exchanges[i], nil } } - return ExchangeConfig{}, fmt.Errorf(ErrExchangeNotFound, name) + return nil, fmt.Errorf(ErrExchangeNotFound, name) } // GetForexProviderConfig returns a forex provider configuration by its name @@ -731,6 +774,13 @@ func (c *Config) GetForexProviderConfig(name string) (base.Settings, error) { return base.Settings{}, errors.New("provider not found") } +// GetForexProvidersConfig returns a list of available forex providers +func (c *Config) GetForexProvidersConfig() []base.Settings { + m.Lock() + defer m.Unlock() + return c.Currency.ForexProviders +} + // GetPrimaryForexProvider returns the primary forex provider func (c *Config) GetPrimaryForexProvider() string { m.Lock() @@ -765,106 +815,206 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].Name = "CoinbasePro" } - if c.Exchanges[i].WebsocketURL != WebsocketURLNonDefaultMessage { - if c.Exchanges[i].WebsocketURL == "" { - c.Exchanges[i].WebsocketURL = WebsocketURLNonDefaultMessage + // Check to see if the old API storage format is used + if c.Exchanges[i].APIKey != nil { + // It is, migrate settings to new format + c.Exchanges[i].API.AuthenticatedSupport = *c.Exchanges[i].AuthenticatedAPISupport + c.Exchanges[i].API.Credentials.Key = *c.Exchanges[i].APIKey + c.Exchanges[i].API.Credentials.Secret = *c.Exchanges[i].APISecret + + if c.Exchanges[i].APIAuthPEMKey != nil { + c.Exchanges[i].API.Credentials.PEMKey = *c.Exchanges[i].APIAuthPEMKey + } + + if c.Exchanges[i].APIAuthPEMKeySupport != nil { + c.Exchanges[i].API.PEMKeySupport = *c.Exchanges[i].APIAuthPEMKeySupport + } + + if c.Exchanges[i].ClientID != nil { + c.Exchanges[i].API.Credentials.ClientID = *c.Exchanges[i].ClientID + } + + if c.Exchanges[i].WebsocketURL != nil { + c.Exchanges[i].API.Endpoints.WebsocketURL = *c.Exchanges[i].WebsocketURL + } + + c.Exchanges[i].API.Endpoints.URL = *c.Exchanges[i].APIURL + c.Exchanges[i].API.Endpoints.URLSecondary = *c.Exchanges[i].APIURLSecondary + + // Flush settings + c.Exchanges[i].AuthenticatedAPISupport = nil + c.Exchanges[i].APIKey = nil + c.Exchanges[i].APIAuthPEMKey = nil + c.Exchanges[i].APISecret = nil + c.Exchanges[i].APIURL = nil + c.Exchanges[i].APIURLSecondary = nil + c.Exchanges[i].WebsocketURL = nil + c.Exchanges[i].ClientID = nil + } + + if c.Exchanges[i].Features == nil { + c.Exchanges[i].Features = &FeaturesConfig{} + } + + if c.Exchanges[i].SupportsAutoPairUpdates != nil { + c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates = *c.Exchanges[i].SupportsAutoPairUpdates + c.Exchanges[i].Features.Enabled.AutoPairUpdates = *c.Exchanges[i].SupportsAutoPairUpdates + c.Exchanges[i].SupportsAutoPairUpdates = nil + } + + if c.Exchanges[i].Websocket != nil { + c.Exchanges[i].Features.Enabled.Websocket = *c.Exchanges[i].Websocket + c.Exchanges[i].Websocket = nil + } + + if c.Exchanges[i].API.Endpoints.URL != APIURLNonDefaultMessage { + if c.Exchanges[i].API.Endpoints.URL == "" { + // Set default if nothing set + c.Exchanges[i].API.Endpoints.URL = APIURLNonDefaultMessage } } - if c.Exchanges[i].APIURL != APIURLNonDefaultMessage { - if c.Exchanges[i].APIURL == "" { + if c.Exchanges[i].API.Endpoints.URLSecondary != APIURLNonDefaultMessage { + if c.Exchanges[i].API.Endpoints.URLSecondary == "" { // Set default if nothing set - c.Exchanges[i].APIURL = APIURLNonDefaultMessage + c.Exchanges[i].API.Endpoints.URLSecondary = APIURLNonDefaultMessage } } - if c.Exchanges[i].APIURLSecondary != APIURLNonDefaultMessage { - if c.Exchanges[i].APIURLSecondary == "" { - // Set default if nothing set - c.Exchanges[i].APIURLSecondary = APIURLNonDefaultMessage + if c.Exchanges[i].API.Endpoints.WebsocketURL != WebsocketURLNonDefaultMessage { + if c.Exchanges[i].API.Endpoints.WebsocketURL == "" { + c.Exchanges[i].API.Endpoints.WebsocketURL = WebsocketURLNonDefaultMessage } } + // Check if see if the new currency pairs format is empty and flesh it out if so + if c.Exchanges[i].CurrencyPairs == nil { + c.Exchanges[i].CurrencyPairs = new(currency.PairsManager) + c.Exchanges[i].CurrencyPairs.Pairs = make(map[assets.AssetType]*currency.PairStore) + + if c.Exchanges[i].PairsLastUpdated != nil { + c.Exchanges[i].CurrencyPairs.LastUpdated = *c.Exchanges[i].PairsLastUpdated + } + + c.Exchanges[i].CurrencyPairs.ConfigFormat = c.Exchanges[i].ConfigCurrencyPairFormat + c.Exchanges[i].CurrencyPairs.RequestFormat = c.Exchanges[i].RequestCurrencyPairFormat + c.Exchanges[i].CurrencyPairs.AssetTypes = assets.New(strings.ToLower(*c.Exchanges[i].AssetTypes)) + c.Exchanges[i].CurrencyPairs.UseGlobalFormat = true + c.Exchanges[i].CurrencyPairs.Store(assets.AssetTypeSpot, + currency.PairStore{ + Available: *c.Exchanges[i].AvailablePairs, + Enabled: *c.Exchanges[i].EnabledPairs, + }, + ) + + // flush old values + c.Exchanges[i].PairsLastUpdated = nil + c.Exchanges[i].ConfigCurrencyPairFormat = nil + c.Exchanges[i].RequestCurrencyPairFormat = nil + c.Exchanges[i].AssetTypes = nil + c.Exchanges[i].AvailablePairs = nil + c.Exchanges[i].EnabledPairs = nil + } + if c.Exchanges[i].Enabled { if c.Exchanges[i].Name == "" { - return fmt.Errorf(ErrExchangeNameEmpty, i) + log.Error(ErrExchangeNameEmpty, i) + c.Exchanges[i].Enabled = false + continue } - if len(c.Exchanges[i].AvailablePairs) == 0 { - return fmt.Errorf(ErrExchangeAvailablePairsEmpty, c.Exchanges[i].Name) - } - if len(c.Exchanges[i].EnabledPairs) == 0 { - return fmt.Errorf(ErrExchangeEnabledPairsEmpty, c.Exchanges[i].Name) - } - if len(c.Exchanges[i].BaseCurrencies) == 0 { - return fmt.Errorf(ErrExchangeBaseCurrenciesEmpty, c.Exchanges[i].Name) - } - if c.Exchanges[i].AuthenticatedAPISupport { // non-fatal error - if c.Exchanges[i].APIKey == "" || c.Exchanges[i].APIKey == DefaultUnsetAPIKey { - c.Exchanges[i].AuthenticatedAPISupport = false + if c.Exchanges[i].API.AuthenticatedSupport { + if c.Exchanges[i].API.CredentialsValidator.RequiresKey && (c.Exchanges[i].API.Credentials.Key == "" || c.Exchanges[i].API.Credentials.Key == DefaultAPIKey) { + c.Exchanges[i].API.AuthenticatedSupport = false } - if (c.Exchanges[i].APISecret == "" || c.Exchanges[i].APISecret == DefaultUnsetAPISecret) && - c.Exchanges[i].Name != "COINUT" { - c.Exchanges[i].AuthenticatedAPISupport = false + if c.Exchanges[i].API.CredentialsValidator.RequiresSecret && (c.Exchanges[i].API.Credentials.Secret == "" || c.Exchanges[i].API.Credentials.Secret == DefaultAPISecret) { + c.Exchanges[i].API.AuthenticatedSupport = false } - if (c.Exchanges[i].ClientID == "ClientID" || c.Exchanges[i].ClientID == "") && - (c.Exchanges[i].Name == "ITBIT" || c.Exchanges[i].Name == "Bitstamp" || c.Exchanges[i].Name == "COINUT" || c.Exchanges[i].Name == "CoinbasePro") { - c.Exchanges[i].AuthenticatedAPISupport = false + if c.Exchanges[i].API.CredentialsValidator.RequiresClientID && (c.Exchanges[i].API.Credentials.ClientID == DefaultAPIClientID || c.Exchanges[i].API.Credentials.ClientID == "") { + c.Exchanges[i].API.AuthenticatedSupport = false } - if !c.Exchanges[i].AuthenticatedAPISupport { + if !c.Exchanges[i].API.AuthenticatedSupport { log.Warnf(WarningExchangeAuthAPIDefaultOrEmptyValues, c.Exchanges[i].Name) } } - if !c.Exchanges[i].SupportsAutoPairUpdates { - lastUpdated := common.UnixTimestampToTime(c.Exchanges[i].PairsLastUpdated) + if !c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates && !c.Exchanges[i].Features.Supports.WebsocketCapabilities.AutoPairUpdates { + lastUpdated := common.UnixTimestampToTime(c.Exchanges[i].CurrencyPairs.LastUpdated) lastUpdated = lastUpdated.AddDate(0, 0, configPairsLastUpdatedWarningThreshold) if lastUpdated.Unix() <= time.Now().Unix() { log.Warnf(WarningPairsLastUpdatedThresholdExceeded, c.Exchanges[i].Name, configPairsLastUpdatedWarningThreshold) } } - if c.Exchanges[i].HTTPTimeout <= 0 { log.Warnf("Exchange %s HTTP Timeout value not set, defaulting to %v.", c.Exchanges[i].Name, configDefaultHTTPTimeout) c.Exchanges[i].HTTPTimeout = configDefaultHTTPTimeout } + if c.Exchanges[i].HTTPRateLimiter != nil { + if c.Exchanges[i].HTTPRateLimiter.Authenticated.Duration < 0 { + log.Warnf("Exchange %s HTTP Rate Limiter authenticated duration set to negative value, defaulting to 0", c.Exchanges[i].Name) + c.Exchanges[i].HTTPRateLimiter.Authenticated.Duration = 0 + } + + if c.Exchanges[i].HTTPRateLimiter.Authenticated.Rate < 0 { + log.Warnf("Exchange %s HTTP Rate Limiter authenticated rate set to negative value, defaulting to 0", c.Exchanges[i].Name) + c.Exchanges[i].HTTPRateLimiter.Authenticated.Rate = 0 + } + + if c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Duration < 0 { + log.Warnf("Exchange %s HTTP Rate Limiter unauthenticated duration set to negative value, defaulting to 0", c.Exchanges[i].Name) + c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Duration = 0 + } + + if c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Rate < 0 { + log.Warnf("Exchange %s HTTP Rate Limiter unauthenticated rate set to negative value, defaulting to 0", c.Exchanges[i].Name) + c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Rate = 0 + } + } + err := c.CheckPairConsistency(c.Exchanges[i].Name) if err != nil { log.Errorf("Exchange %s: CheckPairConsistency error: %s", c.Exchanges[i].Name, err) + c.Exchanges[i].Enabled = false + continue } - if len(c.Exchanges[i].BankAccounts) == 0 { - c.Exchanges[i].BankAccounts = append(c.Exchanges[i].BankAccounts, BankAccount{}) - } else { - for y := range c.Exchanges[i].BankAccounts { - bankAccount := &c.Exchanges[i].BankAccounts[y] - if bankAccount.Enabled { - if bankAccount.BankName == "" || bankAccount.BankAddress == "" { - log.Warnf("banking details for %s is enabled but variables not set", - c.Exchanges[i].Name) - bankAccount.Enabled = false - } + c.CheckExchangeAssetsConsistency(c.Exchanges[i].Name) - if bankAccount.AccountName == "" || bankAccount.AccountNumber == "" { - log.Warnf("banking account details for %s variables not set", - c.Exchanges[i].Name) - bankAccount.Enabled = false - } + if len(c.Exchanges[i].BankAccounts) > 0 { + for x := range c.Exchanges[i].BankAccounts { + if !c.Exchanges[i].BankAccounts[x].Enabled { + continue + } + bankError := false + if c.Exchanges[i].BankAccounts[x].BankName == "" || c.Exchanges[i].BankAccounts[x].BankAddress == "" { + log.Warnf("banking details for %s is enabled but variables not set", + c.Exchanges[i].Name) + bankError = true + } - if bankAccount.SupportedCurrencies == "" { - log.Warnf("banking account details for %s acceptable funding currencies not set", - c.Exchanges[i].Name) - bankAccount.Enabled = false - } + if c.Exchanges[i].BankAccounts[x].AccountName == "" || c.Exchanges[i].BankAccounts[x].AccountNumber == "" { + log.Warnf("banking account details for %s variables not set", + c.Exchanges[i].Name) + bankError = true + } - if bankAccount.BSBNumber == "" && bankAccount.IBAN == "" && - bankAccount.SWIFTCode == "" { - log.Warnf("banking account details for %s critical banking numbers not set", - c.Exchanges[i].Name) - bankAccount.Enabled = false - } + if c.Exchanges[i].BankAccounts[x].SupportedCurrencies == "" { + log.Warnf("banking account details for %s acceptable funding currencies not set", + c.Exchanges[i].Name) + bankError = true + } + + if c.Exchanges[i].BankAccounts[x].BSBNumber == "" && c.Exchanges[i].BankAccounts[x].IBAN == "" && + c.Exchanges[i].BankAccounts[x].SWIFTCode == "" { + log.Warnf("banking account details for %s critical banking numbers not set", + c.Exchanges[i].Name) + bankError = true + } + + if bankError { + c.Exchanges[i].BankAccounts[x].Enabled = false } } } @@ -877,38 +1027,6 @@ func (c *Config) CheckExchangeConfigValues() error { return nil } -// CheckWebserverConfigValues checks information before webserver starts and -// returns an error if values are incorrect. -func (c *Config) CheckWebserverConfigValues() error { - if c.Webserver.AdminUsername == "" || c.Webserver.AdminPassword == "" { - return errors.New(WarningWebserverCredentialValuesEmpty) - } - - if !common.StringContains(c.Webserver.ListenAddress, ":") { - return errors.New(WarningWebserverListenAddressInvalid) - } - - portStr := common.SplitStrings(c.Webserver.ListenAddress, ":")[1] - port, err := strconv.Atoi(portStr) - if err != nil { - return errors.New(WarningWebserverListenAddressInvalid) - } - - if port < 1 || port > 65355 { - return errors.New(WarningWebserverListenAddressInvalid) - } - - if c.Webserver.WebsocketConnectionLimit <= 0 { - c.Webserver.WebsocketConnectionLimit = 1 - } - - if c.Webserver.WebsocketMaxAuthFailures <= 0 { - c.Webserver.WebsocketMaxAuthFailures = 3 - } - - return nil -} - // CheckCurrencyConfigValues checks to see if the currency config values are correct or not func (c *Config) CheckCurrencyConfigValues() error { fxProviders := forexprovider.GetAvailableForexProviders() @@ -1054,9 +1172,9 @@ func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool) error { var pairs []currency.Pair var err error if !c.Exchanges[x].Enabled && enabledOnly { - pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name) + pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name, assets.AssetTypeSpot) } else { - pairs, err = c.GetAvailablePairs(c.Exchanges[x].Name) + pairs, err = c.GetAvailablePairs(c.Exchanges[x].Name, assets.AssetTypeSpot) } if err != nil { @@ -1095,7 +1213,6 @@ func (c *Config) CheckLoggerConfig() error { log.Warn("Missing or invalid config settings using safe defaults") // Set logger to safe defaults - c.Logging = log.Logging{ Enabled: t, Level: "DEBUG|INFO|WARN|ERROR|FATAL", @@ -1314,7 +1431,7 @@ func (c *Config) ReadConfig(configPath string) error { } else { errCounter := 0 for { - if errCounter >= configMaxAuthFailres { + if errCounter >= configMaxAuthFailures { return errors.New("failed to decrypt config after 3 attempts") } key, err := PromptForConfigKey(IsInitialSetup) @@ -1335,7 +1452,7 @@ func (c *Config) ReadConfig(configPath string) error { err = ConfirmConfigJSON(data, &c) if err != nil { - if errCounter < configMaxAuthFailres { + if errCounter < configMaxAuthFailures { log.Errorf("Invalid password.") } errCounter++ @@ -1388,12 +1505,40 @@ func (c *Config) CheckConfig() error { c.CheckConnectionMonitorConfig() c.CheckCommunicationsConfig() - if c.Webserver.Enabled { - err = c.CheckWebserverConfigValues() - if err != nil { - log.Warnf(ErrCheckingConfigValues, err) - c.Webserver.Enabled = false + if c.Webserver != nil { + port := common.ExtractPort(c.Webserver.ListenAddress) + host := common.ExtractHost(c.Webserver.ListenAddress) + + c.RemoteControl = RemoteControlConfig{ + Username: c.Webserver.AdminUsername, + Password: c.Webserver.AdminPassword, + + DeprecatedRPC: DepcrecatedRPCConfig{ + Enabled: c.Webserver.Enabled, + ListenAddress: host + ":" + strconv.Itoa(port), + }, } + + port++ + c.RemoteControl.WebsocketRPC = WebsocketRPCConfig{ + Enabled: c.Webserver.Enabled, + ListenAddress: host + ":" + strconv.Itoa(port), + ConnectionLimit: c.Webserver.WebsocketConnectionLimit, + MaxAuthFailures: c.Webserver.WebsocketMaxAuthFailures, + AllowInsecureOrigin: c.Webserver.WebsocketAllowInsecureOrigin, + } + + port++ + gRPCProxyPort := port + 1 + c.RemoteControl.GRPC = GRPCConfig{ + Enabled: c.Webserver.Enabled, + ListenAddress: host + ":" + strconv.Itoa(port), + GRPCProxyEnabled: c.Webserver.Enabled, + GRPCProxyListenAddress: host + ":" + strconv.Itoa(gRPCProxyPort), + } + + // Then flush the old webserver settings + c.Webserver = nil } err = c.CheckCurrencyConfigValues() diff --git a/config/config_encryption.go b/config/config_encryption.go index a94dc6fb..b0ac465f 100644 --- a/config/config_encryption.go +++ b/config/config_encryption.go @@ -10,6 +10,7 @@ import ( "io" "github.com/thrasher-/gocryptotrader/common" + gctcrypto "github.com/thrasher-/gocryptotrader/common/crypto" log "github.com/thrasher-/gocryptotrader/logger" "golang.org/x/crypto/scrypt" ) @@ -33,7 +34,7 @@ var ( // PromptForConfigEncryption asks for encryption key func (c *Config) PromptForConfigEncryption() bool { - log.Println("Would you like to encrypt your config file (y/n)?") + log.Infof("Would you like to encrypt your config file (y/n)?") input := "" _, err := fmt.Scanln(&input) @@ -191,7 +192,7 @@ func getScryptDK(key, salt []byte) ([]byte, error) { func makeNewSessionDK(key []byte) ([]byte, error) { var err error - storedSalt, err = common.GetRandomSalt([]byte(SaltPrefix), SaltRandomLength) + storedSalt, err = gctcrypto.GetRandomSalt([]byte(SaltPrefix), SaltRandomLength) if err != nil { return nil, err } diff --git a/config/config_test.go b/config/config_test.go index 73a0507a..b5028938 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -6,6 +6,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" log "github.com/thrasher-/gocryptotrader/logger" "github.com/thrasher-/gocryptotrader/ntpclient" ) @@ -303,16 +304,24 @@ func TestCheckPairConsistency(t *testing.T) { t.Error("Test failed. CheckPairConsistency. Non-existent exchange returned nil error") } - cfg.Exchanges = append(cfg.Exchanges, ExchangeConfig{ - Name: "TestExchange", - Enabled: true, - AvailablePairs: currency.NewPairsFromStrings([]string{"DOGE_USD,DOGE_AUD"}), - EnabledPairs: currency.NewPairsFromStrings([]string{"DOGE_USD,DOGE_AUD,DOGE_BTC"}), - ConfigCurrencyPairFormat: &CurrencyPairFormatConfig{ - Uppercase: true, + pairsMan := currency.PairsManager{ + UseGlobalFormat: true, + ConfigFormat: ¤cy.PairFormat{ Delimiter: "_", + Uppercase: true, }, + } + pairsMan.Store(assets.AssetTypeSpot, currency.PairStore{ + Available: currency.NewPairsFromStrings([]string{"DOGE_USD,DOGE_AUD"}), + Enabled: currency.NewPairsFromStrings([]string{"DOGE_USD,DOGE_AUD,DOGE_BTC"}), }) + + cfg.Exchanges = append(cfg.Exchanges, ExchangeConfig{ + Name: "TestExchange", + Enabled: true, + CurrencyPairs: &pairsMan, + }) + tec, err := cfg.GetExchangeConfig("TestExchange") if err != nil { t.Error("Test failed. CheckPairConsistency GetExchangeConfig error", err) @@ -328,8 +337,8 @@ func TestCheckPairConsistency(t *testing.T) { t.Error("Test failed. CheckPairConsistency error:", err) } - tec.EnabledPairs = currency.NewPairsFromStrings([]string{"DOGE_LTC,BTC_LTC"}) - err = cfg.UpdateExchangeConfig(&tec) + tec.CurrencyPairs.StorePairs(assets.AssetTypeSpot, currency.NewPairsFromStrings([]string{"DOGE_LTC,BTC_LTC"}), false) + err = cfg.UpdateExchangeConfig(tec) if err != nil { t.Error("Test failed. CheckPairConsistency Update config failed, error:", err) } @@ -349,8 +358,9 @@ func TestSupportsPair(t *testing.T) { ) } + assetType := assets.AssetTypeSpot _, err = cfg.SupportsPair("asdf", - currency.NewPair(currency.BTC, currency.USD)) + currency.NewPair(currency.BTC, currency.USD), assetType) if err == nil { t.Error( "Test failed. TestSupportsPair. Non-existent exchange returned nil error", @@ -358,7 +368,7 @@ func TestSupportsPair(t *testing.T) { } _, err = cfg.SupportsPair("Bitfinex", - currency.NewPair(currency.BTC, currency.USD)) + currency.NewPair(currency.BTC, currency.USD), assetType) if err != nil { t.Errorf( "Test failed. TestSupportsPair. Incorrect values. Err: %s", err, @@ -374,13 +384,14 @@ func TestGetAvailablePairs(t *testing.T) { "Test failed. TestGetAvailablePairs. LoadConfig Error: %s", err.Error()) } - _, err = cfg.GetAvailablePairs("asdf") + assetType := assets.AssetTypeSpot + _, err = cfg.GetAvailablePairs("asdf", assetType) if err == nil { t.Error( "Test failed. TestGetAvailablePairs. Non-existent exchange returned nil error") } - _, err = cfg.GetAvailablePairs("Bitfinex") + _, err = cfg.GetAvailablePairs("Bitfinex", assetType) if err != nil { t.Errorf( "Test failed. TestGetAvailablePairs. Incorrect values. Err: %s", err) @@ -395,13 +406,14 @@ func TestGetEnabledPairs(t *testing.T) { "Test failed. TestGetEnabledPairs. LoadConfig Error: %s", err.Error()) } - _, err = cfg.GetEnabledPairs("asdf") + assetType := assets.AssetTypeSpot + _, err = cfg.GetEnabledPairs("asdf", assetType) if err == nil { t.Error( "Test failed. TestGetEnabledPairs. Non-existent exchange returned nil error") } - _, err = cfg.GetEnabledPairs("Bitfinex") + _, err = cfg.GetEnabledPairs("Bitfinex", assetType) if err != nil { t.Errorf( "Test failed. TestGetEnabledPairs. Incorrect values. Err: %s", err) @@ -455,7 +467,7 @@ func TestGetDisabledExchanges(t *testing.T) { } exchCfg.Enabled = false - err = cfg.UpdateExchangeConfig(&exchCfg) + err = cfg.UpdateExchangeConfig(exchCfg) if err != nil { t.Errorf( "Test failed. TestGetDisabledExchanges. UpdateExchangeConfig Error: %s", err.Error(), @@ -633,18 +645,13 @@ func TestUpdateExchangeConfig(t *testing.T) { "Test failed. UpdateExchangeConfig.GetExchangeConfig: %s", err.Error(), ) } - e.APIKey = "test1234" - err3 := UpdateExchangeConfig.UpdateExchangeConfig(&e) + e.API.Credentials.Key = "test1234" + err3 := UpdateExchangeConfig.UpdateExchangeConfig(e) if err3 != nil { t.Errorf( "Test failed. UpdateExchangeConfig.UpdateExchangeConfig: %s", err.Error(), ) } - e.Name = "testyTest" - err = UpdateExchangeConfig.UpdateExchangeConfig(&e) - if err == nil { - t.Error("Test failed. UpdateExchangeConfig.UpdateExchangeConfig Error") - } } func TestCheckExchangeConfigValues(t *testing.T) { @@ -670,9 +677,9 @@ func TestCheckExchangeConfigValues(t *testing.T) { t.Fatalf("Test failed. Expected exchange %s to have updated HTTPTimeout value", checkExchangeConfigValues.Exchanges[0].Name) } - checkExchangeConfigValues.Exchanges[0].APIKey = "Key" - checkExchangeConfigValues.Exchanges[0].APISecret = "Secret" - checkExchangeConfigValues.Exchanges[0].AuthenticatedAPISupport = true + checkExchangeConfigValues.Exchanges[0].API.Credentials.Key = "Key" + checkExchangeConfigValues.Exchanges[0].API.Credentials.Secret = "Secret" + checkExchangeConfigValues.Exchanges[0].API.AuthenticatedSupport = true err = checkExchangeConfigValues.CheckExchangeConfigValues() if err != nil { t.Errorf( @@ -680,9 +687,9 @@ func TestCheckExchangeConfigValues(t *testing.T) { ) } - checkExchangeConfigValues.Exchanges[0].AuthenticatedAPISupport = true - checkExchangeConfigValues.Exchanges[0].APIKey = "TESTYTEST" - checkExchangeConfigValues.Exchanges[0].APISecret = "TESTYTEST" + checkExchangeConfigValues.Exchanges[0].API.AuthenticatedSupport = true + checkExchangeConfigValues.Exchanges[0].API.Credentials.Key = "TESTYTEST" + checkExchangeConfigValues.Exchanges[0].API.Credentials.Secret = "TESTYTEST" checkExchangeConfigValues.Exchanges[0].Name = "ITBIT" err = checkExchangeConfigValues.CheckExchangeConfigValues() if err != nil { @@ -691,41 +698,10 @@ func TestCheckExchangeConfigValues(t *testing.T) { ) } - checkExchangeConfigValues.Exchanges[0].BaseCurrencies = currency.NewCurrenciesFromStringArray([]string{""}) - err = checkExchangeConfigValues.CheckExchangeConfigValues() - if err == nil { - t.Errorf( - "Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error", - ) - } - - checkExchangeConfigValues.Exchanges[0].EnabledPairs = currency.NewPairsFromStrings([]string{""}) - err = checkExchangeConfigValues.CheckExchangeConfigValues() - if err == nil { - t.Errorf( - "Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error", - ) - } - - checkExchangeConfigValues.Exchanges[0].AvailablePairs = currency.NewPairsFromStrings([]string{""}) - err = checkExchangeConfigValues.CheckExchangeConfigValues() - if err == nil { - t.Errorf( - "Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error", - ) - } - + checkExchangeConfigValues.Exchanges[0].Enabled = true checkExchangeConfigValues.Exchanges[0].Name = "" - err = checkExchangeConfigValues.CheckExchangeConfigValues() - if err == nil { - t.Errorf( - "Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error", - ) - } - - checkExchangeConfigValues.Cryptocurrencies = currency.NewCurrenciesFromStringArray([]string{""}) - err = checkExchangeConfigValues.CheckExchangeConfigValues() - if err == nil { + checkExchangeConfigValues.CheckExchangeConfigValues() + if checkExchangeConfigValues.Exchanges[0].Enabled { t.Errorf( "Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error", ) @@ -741,79 +717,6 @@ func TestCheckExchangeConfigValues(t *testing.T) { } } -func TestCheckWebserverConfigValues(t *testing.T) { - checkWebserverConfigValues := GetConfig() - err := checkWebserverConfigValues.LoadConfig(ConfigTestFile) - if err != nil { - t.Errorf( - "Test failed. checkWebserverConfigValues.LoadConfig: %s", err.Error(), - ) - } - - err = checkWebserverConfigValues.CheckWebserverConfigValues() - if err != nil { - t.Errorf( - "Test failed. checkWebserverConfigValues.CheckWebserverConfigValues: %s", - err.Error(), - ) - } - - checkWebserverConfigValues.Webserver.WebsocketConnectionLimit = -1 - err = checkWebserverConfigValues.CheckWebserverConfigValues() - if err != nil { - t.Errorf( - "Test failed. checkWebserverConfigValues.CheckWebserverConfigValues: %s", - err.Error(), - ) - } - - if checkWebserverConfigValues.Webserver.WebsocketConnectionLimit != 1 { - t.Error( - "Test failed. checkWebserverConfigValues.CheckWebserverConfigValues error", - ) - } - - checkWebserverConfigValues.Webserver.WebsocketMaxAuthFailures = -1 - checkWebserverConfigValues.CheckWebserverConfigValues() - if checkWebserverConfigValues.Webserver.WebsocketMaxAuthFailures != 3 { - t.Error( - "Test failed. checkWebserverConfigValues.CheckWebserverConfigValues error", - ) - } - - checkWebserverConfigValues.Webserver.ListenAddress = ":0" - err = checkWebserverConfigValues.CheckWebserverConfigValues() - if err == nil { - t.Error( - "Test failed. checkWebserverConfigValues.CheckWebserverConfigValues error", - ) - } - - checkWebserverConfigValues.Webserver.ListenAddress = ":LOLOLOL" - err = checkWebserverConfigValues.CheckWebserverConfigValues() - if err == nil { - t.Error( - "Test failed. checkWebserverConfigValues.CheckWebserverConfigValues error", - ) - } - - checkWebserverConfigValues.Webserver.ListenAddress = "LOLOLOL" - err = checkWebserverConfigValues.CheckWebserverConfigValues() - if err == nil { - t.Error( - "Test failed. checkWebserverConfigValues.CheckWebserverConfigValues error", - ) - } - - checkWebserverConfigValues.Webserver.AdminUsername = "" - err = checkWebserverConfigValues.CheckWebserverConfigValues() - if err == nil { - t.Error( - "Test failed. checkWebserverConfigValues.CheckWebserverConfigValues error", - ) - } -} - func TestRetrieveConfigCurrencyPairs(t *testing.T) { cfg := GetConfig() err := cfg.LoadConfig(ConfigTestFile) diff --git a/config/config_types.go b/config/config_types.go new file mode 100644 index 00000000..7b6aed41 --- /dev/null +++ b/config/config_types.go @@ -0,0 +1,346 @@ +package config + +import ( + "time" + + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/currency/forexprovider/base" + log "github.com/thrasher-/gocryptotrader/logger" + "github.com/thrasher-/gocryptotrader/portfolio" +) + +// Config is the overarching object that holds all the information for +// prestart management of Portfolio, Communications, Webserver and Enabled +// Exchanges +type Config struct { + Name string `json:"name"` + EncryptConfig int `json:"encryptConfig"` + GlobalHTTPTimeout time.Duration `json:"globalHTTPTimeout"` + Logging log.Logging `json:"logging"` + ConnectionMonitor ConnectionMonitorConfig `json:"connectionMonitor"` + Profiler ProfilerConfig `json:"profiler"` + NTPClient NTPClientConfig `json:"ntpclient"` + Currency CurrencyConfig `json:"currencyConfig"` + Communications CommunicationsConfig `json:"communications"` + RemoteControl RemoteControlConfig `json:"remoteControl"` + Portfolio portfolio.Base `json:"portfolioAddresses"` + Exchanges []ExchangeConfig `json:"exchanges"` + BankAccounts []BankAccount `json:"bankAccounts"` + + // Deprecated config settings, will be removed at a future date + Webserver *WebserverConfig `json:"webserver,omitempty"` + CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat,omitempty"` + FiatDisplayCurrency currency.Code `json:"fiatDispayCurrency,omitempty"` + Cryptocurrencies currency.Currencies `json:"cryptocurrencies,omitempty"` + SMS *SMSGlobalConfig `json:"smsGlobal,omitempty"` +} + +// ConnectionMonitorConfig defines the connection monitor variables to ensure +// that there is internet connectivity +type ConnectionMonitorConfig struct { + DNSList []string `json:"preferredDNSList"` + PublicDomainList []string `json:"preferredDomainList"` + CheckInterval time.Duration `json:"checkInterval"` +} + +// ExchangeConfig holds all the information needed for each enabled Exchange. +type ExchangeConfig struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + UseSandbox bool `json:"useSandbox,omitempty"` + HTTPTimeout time.Duration `json:"httpTimeout"` + HTTPUserAgent string `json:"httpUserAgent,omitempty"` + HTTPDebugging bool `json:"httpDebugging,omitempty"` + HTTPRateLimiter *HTTPRateLimitConfig `json:"httpRateLimiter,omitempty"` + ProxyAddress string `json:"proxyAddress,omitempty"` + BaseCurrencies currency.Currencies `json:"baseCurrencies"` + CurrencyPairs *currency.PairsManager `json:"currencyPairs"` + API APIConfig `json:"api"` + Features *FeaturesConfig `json:"features"` + BankAccounts []BankAccount `json:"bankAccounts,omitempty"` + + // Deprecated settings which will be removed in a future update + AvailablePairs *currency.Pairs `json:"availablePairs,omitempty"` + EnabledPairs *currency.Pairs `json:"enabledPairs,omitempty"` + AssetTypes *string `json:"assetTypes,omitempty"` + PairsLastUpdated *int64 `json:"pairsLastUpdated,omitempty"` + ConfigCurrencyPairFormat *currency.PairFormat `json:"configCurrencyPairFormat,omitempty"` + RequestCurrencyPairFormat *currency.PairFormat `json:"requestCurrencyPairFormat,omitempty"` + AuthenticatedAPISupport *bool `json:"authenticatedApiSupport,omitempty"` + APIKey *string `json:"apiKey,omitempty"` + APISecret *string `json:"apiSecret,omitempty"` + APIAuthPEMKeySupport *bool `json:"apiAuthPemKeySupport,omitempty"` + APIAuthPEMKey *string `json:"apiAuthPemKey,omitempty"` + APIURL *string `json:"apiUrl,omitempty"` + APIURLSecondary *string `json:"apiUrlSecondary,omitempty"` + ClientID *string `json:"clientId,omitempty"` + SupportsAutoPairUpdates *bool `json:"supportsAutoPairUpdates,omitempty"` + Websocket *bool `json:"websocket,omitempty"` + WebsocketURL *string `json:"websocketUrl,omitempty"` +} + +// ProfilerConfig defines the profiler configuration to enable pprof +type ProfilerConfig struct { + Enabled bool `json:"enabled"` +} + +// NTPClientConfig defines a network time protocol configuration to allow for +// positive and negative differences +type NTPClientConfig struct { + Level int `json:"enabled"` + Pool []string `json:"pool"` + AllowedDifference *time.Duration `json:"allowedDifference"` + AllowedNegativeDifference *time.Duration `json:"allowedNegativeDifference"` +} + +// GRPCConfig stores the gRPC settings +type GRPCConfig struct { + Enabled bool `json:"enabled"` + ListenAddress string `json:"listenAddress"` + GRPCProxyEnabled bool `json:"grpcProxyEnabled"` + GRPCProxyListenAddress string `json:"grpcProxyListenAddress"` +} + +// DepcrecatedRPCConfig stores the deprecatedRPCConfig settings +type DepcrecatedRPCConfig struct { + Enabled bool `json:"enabled"` + ListenAddress string `json:"listenAddress"` +} + +// WebsocketRPCConfig stores the websocket config info +type WebsocketRPCConfig struct { + Enabled bool `json:"enabled"` + ListenAddress string `json:"listenAddress"` + ConnectionLimit int `json:"connectionLimit"` + MaxAuthFailures int `json:"maxAuthFailures"` + AllowInsecureOrigin bool `json:"allowInsecureOrigin"` +} + +// RemoteControlConfig stores the RPC services config +type RemoteControlConfig struct { + Username string `json:"username"` + Password string `json:"password"` + + GRPC GRPCConfig `json:"gRPC"` + DeprecatedRPC DepcrecatedRPCConfig `json:"deprecatedRPC"` + WebsocketRPC WebsocketRPCConfig `json:"websocketRPC"` +} + +// WebserverConfig stores the old webserver config +type WebserverConfig struct { + Enabled bool `json:"enabled"` + AdminUsername string `json:"adminUsername"` + AdminPassword string `json:"adminPassword"` + ListenAddress string `json:"listenAddress"` + WebsocketConnectionLimit int `json:"websocketConnectionLimit"` + WebsocketMaxAuthFailures int `json:"websocketMaxAuthFailures"` + WebsocketAllowInsecureOrigin bool `json:"websocketAllowInsecureOrigin"` +} + +// Post holds the bot configuration data +type Post struct { + Data Config `json:"data"` +} + +// CurrencyPairFormatConfig stores the users preferred currency pair display +type CurrencyPairFormatConfig struct { + Uppercase bool `json:"uppercase"` + Delimiter string `json:"delimiter,omitempty"` + Separator string `json:"separator,omitempty"` + Index string `json:"index,omitempty"` +} + +// BankAccount holds differing bank account details by supported funding +// currency +type BankAccount struct { + Enabled bool `json:"enabled"` + BankName string `json:"bankName"` + BankAddress string `json:"bankAddress"` + AccountName string `json:"accountName"` + AccountNumber string `json:"accountNumber"` + SWIFTCode string `json:"swiftCode"` + IBAN string `json:"iban"` + BSBNumber string `json:"bsbNumber,omitempty"` + SupportedCurrencies string `json:"supportedCurrencies"` + SupportedExchanges string `json:"supportedExchanges,omitempty"` +} + +// BankTransaction defines a related banking transaction +type BankTransaction struct { + ReferenceNumber string `json:"referenceNumber"` + TransactionNumber string `json:"transactionNumber"` + PaymentInstructions string `json:"paymentInstructions"` +} + +// CurrencyConfig holds all the information needed for currency related manipulation +type CurrencyConfig struct { + ForexProviders []base.Settings `json:"forexProviders"` + CryptocurrencyProvider CryptocurrencyProvider `json:"cryptocurrencyProvider"` + Cryptocurrencies currency.Currencies `json:"cryptocurrencies"` + CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat"` + FiatDisplayCurrency currency.Code `json:"fiatDisplayCurrency"` + CurrencyFileUpdateDuration time.Duration `json:"currencyFileUpdateDuration"` + ForeignExchangeUpdateDuration time.Duration `json:"foreignExchangeUpdateDuration"` +} + +// CryptocurrencyProvider defines coinmarketcap tools +type CryptocurrencyProvider struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + APIkey string `json:"apiKey"` + AccountPlan string `json:"accountPlan"` +} + +// CommunicationsConfig holds all the information needed for each +// enabled communication package +type CommunicationsConfig struct { + SlackConfig SlackConfig `json:"slack"` + SMSGlobalConfig SMSGlobalConfig `json:"smsGlobal"` + SMTPConfig SMTPConfig `json:"smtp"` + TelegramConfig TelegramConfig `json:"telegram"` +} + +// SlackConfig holds all variables to start and run the Slack package +type SlackConfig struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + TargetChannel string `json:"targetChannel"` + VerificationToken string `json:"verificationToken"` +} + +// SMSContact stores the SMS contact info +type SMSContact struct { + Name string `json:"name"` + Number string `json:"number"` + Enabled bool `json:"enabled"` +} + +// SMSGlobalConfig structure holds all the variables you need for instant +// messaging and broadcast used by SMSGlobal +type SMSGlobalConfig struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + Username string `json:"username"` + Password string `json:"password"` + Contacts []SMSContact `json:"contacts"` +} + +// SMTPConfig holds all variables to start and run the SMTP package +type SMTPConfig struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + Host string `json:"host"` + Port string `json:"port"` + AccountName string `json:"accountName"` + AccountPassword string `json:"accountPassword"` + RecipientList string `json:"recipientList"` +} + +// TelegramConfig holds all variables to start and run the Telegram package +type TelegramConfig struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + VerificationToken string `json:"verificationToken"` +} + +// ProtocolFeaturesConfig holds all variables for the exchanges supported features +// for a protocol (e.g REST or Websocket) +type ProtocolFeaturesConfig struct { + TickerBatching bool `json:"tickerBatching,omitempty"` + AutoPairUpdates bool `json:"autoPairUpdates,omitempty"` + AccountBalance bool `json:"accountBalance,omitempty"` + CryptoDeposit bool `json:"cryptoDeposit,omitempty"` + CryptoWithdrawal uint32 `json:"cryptoWithdrawal,omitempty"` + FiatWithdraw bool `json:"fiatWithdraw,omitempty"` + GetOrder bool `json:"getOrder,omitempty"` + GetOrders bool `json:"getOrders,omitempty"` + CancelOrders bool `json:"cancelOrders,omitempty"` + CancelOrder bool `json:"cancelOrder,omitempty"` + SubmitOrder bool `json:"submitOrder,omitempty"` + SubmitOrders bool `json:"submitOrders,omitempty"` + ModifyOrder bool `json:"modifyOrder,omitempty"` + DepositHistory bool `json:"depositHistory,omitempty"` + WithdrawalHistory bool `json:"withdrawalHistory,omitempty"` + TradeHistory bool `json:"tradeHistory,omitempty"` + UserTradeHistory bool `json:"userTradeHistory,omitempty"` + TradeFee bool `json:"tradeFee,omitempty"` + FiatDepositFee bool `json:"fiatDepositFee,omitempty"` + FiatWithdrawalFee bool `json:"fiatWithdrawalFee,omitempty"` + CryptoDepositFee bool `json:"cryptoDepositFee,omitempty"` + CryptoWithdrawalFee bool `json:"cryptoWithdrawalFee,omitempty"` +} + +// FeaturesSupportedConfig stores the exchanges supported features +type FeaturesSupportedConfig struct { + REST bool `json:"restAPI"` + RESTCapabilities ProtocolFeaturesConfig `json:"restCapabilities,omitempty"` + Websocket bool `json:"websocketAPI"` + WebsocketCapabilities ProtocolFeaturesConfig `json:"websocketCapabilities,omitempty"` +} + +// FeaturesEnabledConfig stores the exchanges enabled features +type FeaturesEnabledConfig struct { + AutoPairUpdates bool `json:"autoPairUpdates"` + Websocket bool `json:"websocketAPI"` +} + +// FeaturesConfig stores the exchanges supported and enabled features +type FeaturesConfig struct { + Supports FeaturesSupportedConfig `json:"supports"` + Enabled FeaturesEnabledConfig `json:"enabled"` +} + +// APIEndpointsConfig stores the API endpoint addresses +type APIEndpointsConfig struct { + URL string `json:"url"` + URLSecondary string `json:"urlSecondary"` + WebsocketURL string `json:"websocketURL"` +} + +// APICredentialsConfig stores the API credentials +type APICredentialsConfig struct { + Key string `json:"key,omitempty"` + Secret string `json:"secret,omitempty"` + ClientID string `json:"clientID,omitempty"` + PEMKey string `json:"pemKey,omitempty"` + OTPSecret string `json:"otpSecret,omitempty"` +} + +// APICredentialsValidatorConfig stores the API credentials validator settings +type APICredentialsValidatorConfig struct { + // For Huobi (optional) + RequiresPEM bool `json:"requiresPEM,omitempty"` + + RequiresKey bool `json:"requiresKey,omitempty"` + RequiresSecret bool `json:"requiresSecret,omitempty"` + RequiresClientID bool `json:"requiresClientID,omitempty"` + RequiresBase64DecodeSecret bool `json:"requiresBase64DecodeSecret,omitempty"` +} + +// APIConfig stores the exchange API config +type APIConfig struct { + AuthenticatedSupport bool `json:"authenticatedSupport"` + PEMKeySupport bool `json:"pemKeySupport,omitempty"` + + Endpoints APIEndpointsConfig `json:"endpoints"` + Credentials APICredentialsConfig `json:"credentials"` + CredentialsValidator APICredentialsValidatorConfig `json:"credentialsValidator"` +} + +// HTTPRateConfig stores the exchanges HTTP rate limiter config +type HTTPRateConfig struct { + Duration time.Duration `json:"duration"` + Rate int `json:"rate"` +} + +// HTTPRateLimitConfig stores the rate limit config +type HTTPRateLimitConfig struct { + Unauthenticated HTTPRateConfig `json:"unauthenticated"` + Authenticated HTTPRateConfig `json:"authenticated"` +} diff --git a/core/banner.go b/core/banner.go new file mode 100644 index 00000000..d22f59f2 --- /dev/null +++ b/core/banner.go @@ -0,0 +1,11 @@ +package core + +// Banner features the GoCryptoTrader banner +const Banner = ` + ______ ______ __ ______ __ + / ____/____ / ____/_____ __ __ ____ / /_ ____ /_ __/_____ ______ ____/ /___ _____ + / / __ / __ \ / / / ___// / / // __ \ / __// __ \ / / / ___// __ // __ // _ \ / ___/ +/ /_/ // /_/ // /___ / / / /_/ // /_/ // /_ / /_/ // / / / / /_/ // /_/ // __// / +\____/ \____/ \____//_/ \__, // .___/ \__/ \____//_/ /_/ \__,_/ \__,_/ \___//_/ + /____//_/ +` diff --git a/version.go b/core/version.go similarity index 72% rename from version.go rename to core/version.go index 04e63f8e..4f0ae9ef 100644 --- a/version.go +++ b/core/version.go @@ -1,6 +1,10 @@ -package main +package core -import "fmt" +import ( + "fmt" + "runtime" + "time" +) // const vars related to the app version const ( @@ -9,17 +13,22 @@ const ( PrereleaseBlurb = "This version is pre-release and is not inteded to be used as a production ready trading framework or bot - use at your own risk." IsRelease = false - Copyright = "Copyright (c) 2019 The GoCryptoTrader Developers." GitHub = "GitHub: https://github.com/thrasher-/gocryptotrader" Trello = "Trello: https://trello.com/b/ZAhMhpOy/gocryptotrader" Slack = "Slack: https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY" Issues = "Issues: https://github.com/thrasher-/gocryptotrader/issues" ) -// BuildVersion returns the version string -func BuildVersion(short bool) string { - versionStr := fmt.Sprintf("GoCryptoTrader v%s.%s", - MajorVersion, MinorVersion) +// vars related to the app version +var ( + Copyright = fmt.Sprintf("Copyright (c) 2014-%d The GoCryptoTrader Developers.", + time.Now().Year()) +) + +// Version returns the version string +func Version(short bool) string { + versionStr := fmt.Sprintf("GoCryptoTrader v%s.%s %s %s", + MajorVersion, MinorVersion, runtime.GOARCH, runtime.Version()) if !IsRelease { versionStr += " pre-release.\n" if !short { diff --git a/currency/coinmarketcap/coinmarketcap.go b/currency/coinmarketcap/coinmarketcap.go index f6cd6349..d57a68af 100644 --- a/currency/coinmarketcap/coinmarketcap.go +++ b/currency/coinmarketcap/coinmarketcap.go @@ -8,7 +8,6 @@ package coinmarketcap import ( "errors" "fmt" - "log" "net/http" "net/url" "strconv" @@ -17,6 +16,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/exchanges/request" + log "github.com/thrasher-/gocryptotrader/logger" ) // Coinmarketcap account plan bitmasks, url and enpoint consts @@ -84,13 +84,14 @@ func (c *Coinmarketcap) Setup(conf Settings) { if !conf.Enabled { c.Enabled = false } else { + err := c.SetAccountPlan(conf.AccountPlan) + if err != nil { + log.Errorf("CoinMarketCap enabled but SetAccountPlan failed. Err: %s", err) + return + } c.Enabled = true c.Verbose = conf.Verbose c.APIkey = conf.APIkey - err := c.SetAccountPlan(conf.AccountPlan) - if err != nil { - log.Fatal(err) - } } } diff --git a/currency/manager.go b/currency/manager.go new file mode 100644 index 00000000..e6570efe --- /dev/null +++ b/currency/manager.go @@ -0,0 +1,107 @@ +package currency + +import ( + "github.com/thrasher-/gocryptotrader/exchanges/assets" +) + +// GetAssetTypes returns a list of stored asset types +func (p *PairsManager) GetAssetTypes() assets.AssetTypes { + p.m.Lock() + defer p.m.Unlock() + var assetTypes assets.AssetTypes + for k := range p.Pairs { + assetTypes = append(assetTypes, k) + } + return assetTypes +} + +// Get gets the currency pair config based on the asset type +func (p *PairsManager) Get(a assets.AssetType) *PairStore { + p.m.Lock() + defer p.m.Unlock() + c, ok := p.Pairs[a] + if !ok { + return nil + } + return c +} + +// Store stores a new currency pair config based on its asset type +func (p *PairsManager) Store(a assets.AssetType, ps PairStore) { + p.m.Lock() + + if p.Pairs == nil { + p.Pairs = make(map[assets.AssetType]*PairStore) + } + + if !p.AssetTypes.Contains(a) { + p.AssetTypes = append(p.AssetTypes, a) + } + + p.Pairs[a] = &ps + p.m.Unlock() +} + +// Delete deletes a map entry based on the supplied asset type +func (p *PairsManager) Delete(a assets.AssetType) { + p.m.Lock() + defer p.m.Unlock() + if p.Pairs == nil { + return + } + + _, ok := p.Pairs[a] + if !ok { + return + } + + delete(p.Pairs, a) +} + +// GetPairs gets a list of stored pairs based on the asset type and whether +// they're enabled or not +func (p *PairsManager) GetPairs(a assets.AssetType, enabled bool) Pairs { + p.m.Lock() + defer p.m.Unlock() + if p.Pairs == nil { + return nil + } + + c, ok := p.Pairs[a] + if !ok { + return nil + } + + var pairs Pairs + if enabled { + pairs = c.Enabled + } else { + pairs = c.Available + } + + return pairs +} + +// StorePairs stores a list of pairs based on the asset type and whether +// they're enabled or not +func (p *PairsManager) StorePairs(a assets.AssetType, pairs Pairs, enabled bool) { + p.m.Lock() + defer p.m.Unlock() + + if p.Pairs == nil { + p.Pairs = make(map[assets.AssetType]*PairStore) + } + + c, ok := p.Pairs[a] + if !ok { + c = new(PairStore) + } + + if enabled { + c.Enabled = pairs + } else { + c.Available = pairs + } + + p.Pairs[a] = c +} diff --git a/currency/manager_test.go b/currency/manager_test.go new file mode 100644 index 00000000..7994ddf4 --- /dev/null +++ b/currency/manager_test.go @@ -0,0 +1,140 @@ +package currency + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/exchanges/assets" +) + +var p PairsManager + +func initTest() { + p.Store(assets.AssetTypeSpot, + PairStore{ + Available: NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}), + Enabled: NewPairsFromStrings([]string{"BTC-USD"}), + RequestFormat: &PairFormat{ + Uppercase: true, + }, + ConfigFormat: &PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + }, + ) +} + +func TestGetAssetTypes(t *testing.T) { + initTest() + + a := p.GetAssetTypes() + if len(a) == 0 { + t.Errorf("Test failed. GetAssetTypes shouldn't be nil") + } + + if !a.Contains(assets.AssetTypeSpot) { + t.Errorf("Test failed. AssetTypeSpot should be in the assets list") + } +} + +func TestGet(t *testing.T) { + initTest() + + if p.Get(assets.AssetTypeSpot) == nil { + t.Error("Test failed. Spot assets shouldn't be nil") + } + + if p.Get(assets.AssetTypeFutures) != nil { + t.Error("Test Failed. Futures should be nil") + } +} + +func TestStore(t *testing.T) { + p.Store(assets.AssetTypeFutures, + PairStore{ + Available: NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}), + Enabled: NewPairsFromStrings([]string{"BTC-USD"}), + RequestFormat: &PairFormat{ + Uppercase: true, + }, + ConfigFormat: &PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + }, + ) + + if p.Get(assets.AssetTypeFutures) == nil { + t.Error("Test failed. Futures assets shouldn't be nil") + } +} + +func TestDelete(t *testing.T) { + p.Pairs = nil + p.Delete(assets.AssetTypeSpot) + + p.Store(assets.AssetTypeSpot, + PairStore{ + Available: NewPairsFromStrings([]string{"BTC-USD"}), + }, + ) + p.Delete(assets.AssetTypeUpsideProfitContract) + if p.Get(assets.AssetTypeSpot) == nil { + t.Error("Test failed. AssetTypeSpot should exist") + } + + p.Delete(assets.AssetTypeSpot) + if p.Get(assets.AssetTypeSpot) != nil { + t.Error("Test failed. Delete should have deleted AssetTypeSpot") + } + +} + +func TestGetPairs(t *testing.T) { + p.Pairs = nil + pairs := p.GetPairs(assets.AssetTypeSpot, true) + if pairs != nil { + t.Fatal("pairs shouldn't be populated") + } + + initTest() + pairs = p.GetPairs(assets.AssetTypeSpot, true) + if pairs == nil { + t.Fatal("pairs should be populated") + } + + pairs = p.GetPairs("blah", true) + if pairs != nil { + t.Fatal("pairs shouldn't be populated") + } +} + +func TestStorePairs(t *testing.T) { + p.Pairs = nil + p.StorePairs(assets.AssetTypeSpot, NewPairsFromStrings([]string{"ETH-USD"}), false) + pairs := p.GetPairs(assets.AssetTypeSpot, false) + if !pairs.Contains(NewPairFromString("ETH-USD"), true) { + t.Errorf("TestStorePairs failed, unexpected result") + } + + initTest() + p.StorePairs(assets.AssetTypeSpot, NewPairsFromStrings([]string{"ETH-USD"}), false) + pairs = p.GetPairs(assets.AssetTypeSpot, false) + if pairs == nil { + t.Errorf("pairs should be populated") + } + + if !pairs.Contains(NewPairFromString("ETH-USD"), true) { + t.Errorf("TestStorePairs failed, unexpected result") + } + + p.StorePairs(assets.AssetTypeFutures, NewPairsFromStrings([]string{"ETH-KRW"}), true) + pairs = p.GetPairs(assets.AssetTypeFutures, true) + if pairs == nil { + t.Errorf("pairs futures should be populated") + } + + if !pairs.Contains(NewPairFromString("ETH-KRW"), true) { + t.Errorf("TestStorePairs failed, unexpected result") + } +} diff --git a/currency/manager_types.go b/currency/manager_types.go new file mode 100644 index 00000000..374d1179 --- /dev/null +++ b/currency/manager_types.go @@ -0,0 +1,34 @@ +package currency + +import ( + "sync" + + "github.com/thrasher-/gocryptotrader/exchanges/assets" +) + +// PairsManager manages asset pairs +type PairsManager struct { + RequestFormat *PairFormat `json:"requestFormat,omitempty"` + ConfigFormat *PairFormat `json:"configFormat,omitempty"` + UseGlobalFormat bool `json:"useGlobalFormat,omitempty"` + LastUpdated int64 `json:"lastUpdated,omitempty"` + AssetTypes assets.AssetTypes `json:"assetTypes"` + Pairs map[assets.AssetType]*PairStore `json:"pairs"` + m sync.Mutex +} + +// PairStore stores a currency pair store +type PairStore struct { + Enabled Pairs `json:"enabled,omitempty"` + Available Pairs `json:"available,omitempty"` + RequestFormat *PairFormat `json:"requestFormat,omitempty"` + ConfigFormat *PairFormat `json:"configFormat,omitempty"` +} + +// PairFormat returns the pair format +type PairFormat struct { + Uppercase bool `json:"uppercase"` + Delimiter string `json:"delimiter,omitempty"` + Separator string `json:"separator,omitempty"` + Index string `json:"index,omitempty"` +} diff --git a/docker-compose.yml b/docker-compose.yml index 65aac43b..3bbf0297 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,9 +7,10 @@ services: depends_on: - daemon ports: - - "9051:80" + - "9052:80" daemon: build: . ports: + - "9051:9051" - "9050:9050" diff --git a/engine/engine.go b/engine/engine.go new file mode 100644 index 00000000..412a74d0 --- /dev/null +++ b/engine/engine.go @@ -0,0 +1,456 @@ +package engine + +import ( + "errors" + "flag" + "fmt" + "os" + "os/signal" + "path" + "syscall" + "time" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/communications" + "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/connchecker" + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/currency/coinmarketcap" + "github.com/thrasher-/gocryptotrader/engine/events" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/request" + log "github.com/thrasher-/gocryptotrader/logger" + "github.com/thrasher-/gocryptotrader/ntpclient" + "github.com/thrasher-/gocryptotrader/portfolio" + "github.com/thrasher-/gocryptotrader/utils" +) + +// Engine contains configuration, portfolio, exchange & ticker data and is the +// overarching type across this code base. +type Engine struct { + Config *config.Config + Portfolio *portfolio.Base + Exchanges []exchange.IBotExchange + ExchangeCurrencyPairManager *ExchangeCurrencyPairSyncer + OrderManager *OrderManager + CommsRelayer *communications.Communications + Connectivity *connchecker.Checker + Shutdown chan bool + Settings Settings + CryptocurrencyDepositAddresses map[string]map[string]string + Uptime time.Time +} + +// Vars for engine +var ( + Bot *Engine +) + +func init() { + if Bot == nil { + return + } +} + +// New starts a new engine +func New() (*Engine, error) { + var b Engine + b.Config = &config.Cfg + + err := b.Config.LoadConfig("") + if err != nil { + return nil, fmt.Errorf("failed to load config. Err: %s", err) + } + + b.CryptocurrencyDepositAddresses = make(map[string]map[string]string) + + return &b, nil +} + +// NewFromSettings starts a new engine based on supplied settings +func NewFromSettings(settings *Settings) (*Engine, error) { + if settings == nil { + return nil, errors.New("engine: settings is nil") + } + + var b Engine + b.Config = &config.Cfg + log.Debugf("Loading config file %s..\n", settings.ConfigFile) + err := b.Config.LoadConfig(settings.ConfigFile) + if err != nil { + return nil, fmt.Errorf("failed to load config. Err: %s", err) + } + + err = common.CreateDir(settings.DataDir) + if err != nil { + return nil, fmt.Errorf("failed to open/create data directory: %s. Err: %s", settings.DataDir, err) + } + + err = b.Config.CheckLoggerConfig() + if err != nil { + log.Errorf("Failed to configure logger. Err: %s", err) + } + + err = log.SetupLogger() + if err != nil { + log.Errorf("Failed to setup logger. Err: %s", err) + } + + b.Settings.ConfigFile = settings.ConfigFile + b.Settings.DataDir = settings.DataDir + b.Settings.LogFile = path.Join(log.LogPath, log.Logger.File) + b.CryptocurrencyDepositAddresses = make(map[string]map[string]string) + + err = utils.AdjustGoMaxProcs(settings.GoMaxProcs) + if err != nil { + return nil, fmt.Errorf("unable to adjust runtime GOMAXPROCS value. Err: %s", err) + } + + b.handleInterrupt() + + ValidateSettings(&b, settings) + + return &b, nil +} + +// ValidateSettings validates and sets all bot settings +func ValidateSettings(b *Engine, s *Settings) { + b.Settings.Verbose = s.Verbose + b.Settings.EnableDryRun = s.EnableDryRun + b.Settings.EnableAllExchanges = s.EnableAllExchanges + b.Settings.EnableAllPairs = s.EnableAllPairs + b.Settings.EnablePortfolioWatcher = s.EnablePortfolioWatcher + b.Settings.EnableCoinmarketcapAnalysis = s.EnableCoinmarketcapAnalysis + + // TO-DO: FIXME + if flag.Lookup("grpc") != nil { + b.Settings.EnableGRPC = s.EnableGRPC + } else { + b.Settings.EnableGRPC = b.Config.RemoteControl.GRPC.Enabled + } + + if flag.Lookup("grpcproxy") != nil { + b.Settings.EnableGRPCProxy = s.EnableGRPCProxy + } else { + b.Settings.EnableGRPCProxy = b.Config.RemoteControl.GRPC.GRPCProxyEnabled + } + + if flag.Lookup("websocketrpc") != nil { + b.Settings.EnableWebsocketRPC = s.EnableWebsocketRPC + } else { + b.Settings.EnableWebsocketRPC = b.Config.RemoteControl.WebsocketRPC.Enabled + } + + if flag.Lookup("deprecatedrpc") != nil { + b.Settings.EnableDeprecatedRPC = s.EnableDeprecatedRPC + } else { + b.Settings.EnableDeprecatedRPC = b.Config.RemoteControl.DeprecatedRPC.Enabled + } + + b.Settings.EnableCommsRelayer = s.EnableCommsRelayer + b.Settings.EnableEventManager = s.EnableEventManager + + if b.Settings.EnableEventManager { + events.Verbose = b.Settings.Verbose + if b.Settings.EventManagerDelay != time.Duration(0) && s.EventManagerDelay > 0 { + b.Settings.EventManagerDelay = s.EventManagerDelay + } else { + b.Settings.EventManagerDelay = events.SleepDelay + } + } + + b.Settings.EnableNTPClient = s.EnableNTPClient + b.Settings.EnableTickerRoutine = s.EnableTickerRoutine + b.Settings.EnableOrderbookRoutine = s.EnableOrderbookRoutine + b.Settings.EnableWebsocketRoutine = s.EnableWebsocketRoutine + b.Settings.EnableExchangeAutoPairUpdates = s.EnableExchangeAutoPairUpdates + b.Settings.EnableExchangeWebsocketSupport = s.EnableExchangeWebsocketSupport + b.Settings.EnableExchangeRESTSupport = s.EnableExchangeRESTSupport + b.Settings.EnableExchangeVerbose = s.EnableExchangeVerbose + b.Settings.EnableExchangeHTTPRateLimiter = s.EnableExchangeHTTPDebugging + b.Settings.EnableExchangeHTTPDebugging = s.EnableExchangeHTTPDebugging + b.Settings.DisableExchangeAutoPairUpdates = s.DisableExchangeAutoPairUpdates + b.Settings.ExchangePurgeCredentials = s.ExchangePurgeCredentials + + if !b.Settings.EnableExchangeHTTPRateLimiter { + request.DisableRateLimiter = true + } + + // Checks if the flag values are different from the defaults + b.Settings.MaxHTTPRequestJobsLimit = s.MaxHTTPRequestJobsLimit + if b.Settings.MaxHTTPRequestJobsLimit != request.DefaultMaxRequestJobs && s.MaxHTTPRequestJobsLimit > 0 { + request.MaxRequestJobs = b.Settings.MaxHTTPRequestJobsLimit + } + + b.Settings.RequestTimeoutRetryAttempts = s.RequestTimeoutRetryAttempts + if b.Settings.RequestTimeoutRetryAttempts != request.DefaultTimeoutRetryAttempts && s.RequestTimeoutRetryAttempts > 0 { + request.TimeoutRetryAttempts = b.Settings.RequestTimeoutRetryAttempts + } + + b.Settings.ExchangeHTTPTimeout = s.ExchangeHTTPTimeout + if s.ExchangeHTTPTimeout != time.Duration(0) && s.ExchangeHTTPTimeout > 0 { + b.Settings.ExchangeHTTPTimeout = s.ExchangeHTTPTimeout + } else { + b.Settings.ExchangeHTTPTimeout = b.Config.GlobalHTTPTimeout + } + + b.Settings.ExchangeHTTPUserAgent = s.ExchangeHTTPUserAgent + b.Settings.ExchangeHTTPProxy = s.ExchangeHTTPProxy + + if s.GlobalHTTPTimeout != time.Duration(0) && s.GlobalHTTPTimeout > 0 { + b.Settings.GlobalHTTPTimeout = s.GlobalHTTPTimeout + } else { + b.Settings.GlobalHTTPTimeout = b.Config.GlobalHTTPTimeout + } + common.HTTPClient = common.NewHTTPClientWithTimeout(b.Settings.GlobalHTTPTimeout) + + b.Settings.GlobalHTTPUserAgent = s.GlobalHTTPUserAgent + if b.Settings.GlobalHTTPUserAgent != "" { + common.HTTPUserAgent = b.Settings.GlobalHTTPUserAgent + } + + b.Settings.GlobalHTTPProxy = s.GlobalHTTPProxy +} + +// PrintSettings returns the engine settings +func PrintSettings(s *Settings) { + log.Debugln() + log.Debugf("ENGINE SETTINGS") + log.Debugf("- CORE SETTINGS:") + log.Debugf("\t Verbose mode: %v", s.Verbose) + log.Debugf("\t Enable dry run mode: %v", s.EnableDryRun) + log.Debugf("\t Enable all exchanges: %v", s.EnableAllExchanges) + log.Debugf("\t Enable all pairs: %v", s.EnableAllPairs) + log.Debugf("\t Enable coinmarketcap analaysis: %v", s.EnableCoinmarketcapAnalysis) + log.Debugf("\t Enable portfolio watcher: %v", s.EnablePortfolioWatcher) + log.Debugf("\t Enable gPRC: %v", s.EnableGRPC) + log.Debugf("\t Enable gRPC Proxy: %v", s.EnableGRPCProxy) + log.Debugf("\t Enable websocket RPC: %v", s.EnableWebsocketRPC) + log.Debugf("\t Enable deprecated RPC: %v", s.EnableDeprecatedRPC) + log.Debugf("\t Enable comms relayer: %v", s.EnableCommsRelayer) + log.Debugf("\t Enable event manager: %v", s.EnableEventManager) + log.Debugf("\t Event manager sleep delay: %v", s.EventManagerDelay) + log.Debugf("\t Enable ticker routine: %v", s.EnableTickerRoutine) + log.Debugf("\t Enable orderbook routine: %v", s.EnableOrderbookRoutine) + log.Debugf("\t Enable websocket routine: %v\n", s.EnableWebsocketRoutine) + log.Debugf("\t Enable NTP client: %v", s.EnableNTPClient) + log.Debugf("- FOREX SETTINGS:") + log.Debugf("\t Enable currency conveter: %v", s.EnableCurrencyConverter) + log.Debugf("\t Enable currency layer: %v", s.EnableCurrencyLayer) + log.Debugf("\t Enable fixer: %v", s.EnableFixer) + log.Debugf("\t Enable OpenExchangeRates: %v", s.EnableOpenExchangeRates) + log.Debugf("- EXCHANGE SETTINGS:") + log.Debugf("\t Enable exchange auto pair updates: %v", s.EnableExchangeAutoPairUpdates) + log.Debugf("\t Disable all exchange auto pair updates: %v", s.DisableExchangeAutoPairUpdates) + log.Debugf("\t Enable exchange websocket support: %v", s.EnableExchangeWebsocketSupport) + log.Debugf("\t Enable exchange verbose mode: %v", s.EnableExchangeVerbose) + log.Debugf("\t Enable exchange HTTP rate limiter: %v", s.EnableExchangeHTTPRateLimiter) + log.Debugf("\t Enable exchange HTTP debugging: %v", s.EnableExchangeHTTPDebugging) + log.Debugf("\t Exchange max HTTP request jobs: %v", s.MaxHTTPRequestJobsLimit) + log.Debugf("\t Exchange HTTP request timeout retry amount: %v", s.RequestTimeoutRetryAttempts) + log.Debugf("\t Exchange HTTP timeout: %v", s.ExchangeHTTPTimeout) + log.Debugf("\t Exchange HTTP user agent: %v", s.ExchangeHTTPUserAgent) + log.Debugf("\t Exchange HTTP proxy: %v\n", s.ExchangeHTTPProxy) + log.Debugf("- COMMON SETTINGS:") + log.Debugf("\t Global HTTP timeout: %v", s.GlobalHTTPTimeout) + log.Debugf("\t Global HTTP user agent: %v", s.GlobalHTTPUserAgent) + log.Debugf("\t Global HTTP proxy: %v", s.ExchangeHTTPProxy) + log.Debugln() +} + +// Start starts the engine +func (e *Engine) Start() { + if e == nil { + log.Fatal("Engine instance is nil") + } + + // Sets up internet connectivity monitor + var err error + e.Connectivity, err = connchecker.New(e.Config.ConnectionMonitor.DNSList, + e.Config.ConnectionMonitor.PublicDomainList, + e.Config.ConnectionMonitor.CheckInterval) + if err != nil { + log.Fatalf("Connectivity checker failure: %s", err) + } + + if e.Settings.EnableNTPClient { + if e.Config.NTPClient.Level != -1 { + e.Config.CheckNTPConfig() + NTPTime, errNTP := ntpclient.NTPClient(e.Config.NTPClient.Pool) + currentTime := time.Now() + if errNTP != nil { + log.Warnf("NTPClient failed to create: %v", errNTP) + } else { + NTPcurrentTimeDifference := NTPTime.Sub(currentTime) + configNTPTime := *e.Config.NTPClient.AllowedDifference + configNTPNegativeTime := (*e.Config.NTPClient.AllowedNegativeDifference - (*e.Config.NTPClient.AllowedNegativeDifference * 2)) + if NTPcurrentTimeDifference > configNTPTime || NTPcurrentTimeDifference < configNTPNegativeTime { + log.Warnf("Time out of sync (NTP): %v | (time.Now()): %v | (Difference): %v | (Allowed): +%v / %v", NTPTime, currentTime, NTPcurrentTimeDifference, configNTPTime, configNTPNegativeTime) + if e.Config.NTPClient.Level == 0 { + disable, errNTP := e.Config.DisableNTPCheck(os.Stdin) + if errNTP != nil { + log.Errorf("failed to disable ntp time check reason: %v", errNTP) + } else { + log.Info(disable) + } + } + } + } + } + } + + e.Uptime = time.Now() + log.Debugf("Bot '%s' started.\n", e.Config.Name) + + enabledExchanges := e.Config.CountEnabledExchanges() + if e.Settings.EnableAllExchanges { + enabledExchanges = len(e.Config.Exchanges) + } + + log.Debugln() + log.Debugln("EXCHANGE COVERAGE") + log.Debugf("\t Available Exchanges: %d. Enabled Exchanges: %d.\n", + len(e.Config.Exchanges), enabledExchanges) + + if e.Settings.ExchangePurgeCredentials { + log.Debugln("Purging exchange API credentials.") + e.Config.PurgeExchangeAPICredentials() + } + + log.Debugln("Setting up exchanges..") + SetupExchanges() + if len(e.Exchanges) == 0 { + log.Fatalf("No exchanges were able to be loaded. Exiting") + } + + if e.Settings.EnableCommsRelayer { + log.Debugln("Starting communication mediums..") + commsCfg := e.Config.GetCommunicationsConfig() + e.CommsRelayer = communications.NewComm(&commsCfg) + e.CommsRelayer.GetEnabledCommunicationMediums() + } + + var newFxSettings []currency.FXSettings + for _, d := range e.Config.Currency.ForexProviders { + newFxSettings = append(newFxSettings, currency.FXSettings(d)) + } + + err = currency.RunStorageUpdater(currency.BotOverrides{ + Coinmarketcap: e.Settings.EnableCoinmarketcapAnalysis, + FxCurrencyConverter: e.Settings.EnableCurrencyConverter, + FxCurrencyLayer: e.Settings.EnableCurrencyLayer, + FxFixer: e.Settings.EnableFixer, + FxOpenExchangeRates: e.Settings.EnableOpenExchangeRates, + }, + ¤cy.MainConfiguration{ + ForexProviders: newFxSettings, + CryptocurrencyProvider: coinmarketcap.Settings(e.Config.Currency.CryptocurrencyProvider), + Cryptocurrencies: e.Config.Currency.Cryptocurrencies, + FiatDisplayCurrency: e.Config.Currency.FiatDisplayCurrency, + CurrencyDelay: e.Config.Currency.CurrencyFileUpdateDuration, + FxRateDelay: e.Config.Currency.ForeignExchangeUpdateDuration, + }, + e.Settings.DataDir, + e.Settings.Verbose) + if err != nil { + log.Warn("currency updater system failed to start", err) + } + + e.Portfolio = &portfolio.Portfolio + e.Portfolio.Seed(e.Config.Portfolio) + SeedExchangeAccountInfo(GetAllEnabledExchangeAccountInfo().Data) + + e.CryptocurrencyDepositAddresses = GetExchangeCryptocurrencyDepositAddresses() + + if e.Settings.EnableGRPC { + go StartRPCServer() + } + + if e.Settings.EnableDeprecatedRPC { + go StartRESTServer() + } + + if e.Settings.EnableWebsocketRPC { + go StartWebsocketServer() + StartWebsocketHandler() + } + + if e.Settings.EnablePortfolioWatcher { + go portfolio.StartPortfolioWatcher() + } + + /* + exchangeSyncCfg := CurrencyPairSyncerConfig{ + SyncTicker: true, + SyncOrderbook: true, + SyncContinuously: true, + NumWorkers: 15, + } + + + e.ExchangeCurrencyPairManager, err = NewCurrencyPairSyncer(exchangeSyncCfg) + if err != nil { + log.Warnf("Unable to initialise exchange currency pair syncer. Err: %s", err) + } else { + e.ExchangeCurrencyPairManager.Start() + } + */ + + go StartOrderManagerRoutine() + + if e.Settings.EnableTickerRoutine { + go TickerUpdaterRoutine() + } + /* + + if e.Settings.EnableOrderbookRoutine { + go OrderbookUpdaterRoutine() + } + + if e.Settings.EnableWebsocketRoutine { + go WebsocketRoutine() + } + */ + + if e.Settings.EnableEventManager { + go events.EventManger() + } + + <-e.Shutdown + e.Stop() +} + +// Stop correctly shuts down engine saving configuration files +func (e *Engine) Stop() { + log.Debugln("Engine shutting down..") + + if len(portfolio.Portfolio.Addresses) != 0 { + e.Config.Portfolio = portfolio.Portfolio + } + + if !e.Settings.EnableDryRun { + err := e.Config.SaveConfig(e.Settings.ConfigFile) + + if err != nil { + log.Error("Unable to save config.") + } else { + log.Debugln("Config file saved successfully.") + } + } + log.Debugln("Exiting.") + log.CloseLogFile() + os.Exit(0) +} + +// handleInterrupt monitors and captures the SIGTERM in a new goroutine then +// shuts down the engine instance +func (e *Engine) handleInterrupt() { + c := make(chan os.Signal, 1) + e.Shutdown = make(chan bool) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + sig := <-c + log.Debugf("Captured %v, shutdown requested.", sig) + e.Shutdown <- true + }() +} diff --git a/engine/engine_types.go b/engine/engine_types.go new file mode 100644 index 00000000..ca80ba22 --- /dev/null +++ b/engine/engine_types.go @@ -0,0 +1,58 @@ +package engine + +import "time" + +// Settings stores engine params +type Settings struct { + ConfigFile string + DataDir string + LogFile string + GoMaxProcs int + + // Core Settings + EnableDryRun bool + EnableAllExchanges bool + EnableAllPairs bool + EnableCoinmarketcapAnalysis bool + EnablePortfolioWatcher bool + EnableGRPC bool + EnableGRPCProxy bool + EnableWebsocketRPC bool + EnableDeprecatedRPC bool + EnableTickerRoutine bool + EnableOrderbookRoutine bool + EnableWebsocketRoutine bool + EnableCommsRelayer bool + EnableEventManager bool + EnableNTPClient bool + EventManagerDelay time.Duration + Verbose bool + + // Forex settings + EnableCurrencyConverter bool + EnableCurrencyLayer bool + EnableFixer bool + EnableOpenExchangeRates bool + + // Exchange tuning settings + EnableExchangeHTTPRateLimiter bool + EnableExchangeHTTPDebugging bool + EnableExchangeVerbose bool + ExchangePurgeCredentials bool + EnableExchangeAutoPairUpdates bool + DisableExchangeAutoPairUpdates bool + EnableExchangeRESTSupport bool + EnableExchangeWebsocketSupport bool + MaxHTTPRequestJobsLimit int + RequestTimeoutRetryAttempts int + + // Global HTTP related settings + GlobalHTTPTimeout time.Duration + GlobalHTTPUserAgent string + GlobalHTTPProxy string + + // Exchange HTTP related settings + ExchangeHTTPTimeout time.Duration + ExchangeHTTPUserAgent string + ExchangeHTTPProxy string +} diff --git a/events/event_test.go b/engine/events/event_test.go similarity index 86% rename from events/event_test.go rename to engine/events/event_test.go index 10ca70c7..4cb1149b 100644 --- a/events/event_test.go +++ b/engine/events/event_test.go @@ -30,23 +30,23 @@ package events // testSetup(t) // // pair := currency.NewPairFromStrings("BTC", "USD") -// eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) +// eventID, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) // if err != nil && eventID != 0 { // t.Errorf("Test Failed. AddEvent: Error, %s", err) // } -// eventID, err = AddEvent("ANXX", "price", ">,==", pair, "SPOT", actionTest) +// eventID, err = AddEvent("ANXX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) // if err == nil && eventID == 0 { // t.Error("Test Failed. AddEvent: Error, error not captured in Exchange") // } -// eventID, err = AddEvent("ANX", "prices", ">,==", pair, "SPOT", actionTest) +// eventID, err = AddEvent("ANX", "prices", ">,==", pair, assets.AssetTypeSpot, actionTest) // if err == nil && eventID == 0 { // t.Error("Test Failed. AddEvent: Error, error not captured in Item") // } -// eventID, err = AddEvent("ANX", "price", "3===D", pair, "SPOT", actionTest) +// eventID, err = AddEvent("ANX", "price", "3===D", pair, assets.AssetTypeSpot, actionTest) // if err == nil && eventID == 0 { // t.Error("Test Failed. AddEvent: Error, error not captured in Condition") // } -// eventID, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", "console_prints") +// eventID, err = AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, "console_prints") // if err == nil && eventID == 0 { // t.Error("Test Failed. AddEvent: Error, error not captured in Action") // } @@ -60,7 +60,7 @@ package events // testSetup(t) // // pair := currency.NewPairFromStrings("BTC", "USD") -// eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) +// eventID, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) // if err != nil && eventID != 0 { // t.Errorf("Test Failed. RemoveEvent: Error, %s", err) // } @@ -76,15 +76,15 @@ package events // testSetup(t) // // pair := currency.NewPairFromStrings("BTC", "USD") -// one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) +// one, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) // if err != nil { // t.Errorf("Test Failed. GetEventCounter: Error, %s", err) // } -// two, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) +// two, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) // if err != nil { // t.Errorf("Test Failed. GetEventCounter: Error, %s", err) // } -// three, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) +// three, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) // if err != nil { // t.Errorf("Test Failed. GetEventCounter: Error, %s", err) // } @@ -115,7 +115,7 @@ package events // testSetup(t) // // pair := currency.NewPairFromStrings("BTC", "USD") -// one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) +// one, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) // if err != nil { // t.Fatalf("Test Failed. ExecuteAction: Error, %s", err) // } @@ -128,7 +128,7 @@ package events // } // // action := actionSMSNotify + "," + "ALL" -// one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action) +// one, err = AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, action) // if err != nil { // t.Fatalf("Test Failed. ExecuteAction: Error, %s", err) // } @@ -142,7 +142,7 @@ package events // } // // action = actionSMSNotify + "," + "StyleGherkin" -// one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action) +// one, err = AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, action) // if err != nil { // t.Fatalf("Test Failed. ExecuteAction: Error, %s", err) // } @@ -161,7 +161,7 @@ package events // testSetup(t) // // pair := currency.NewPairFromStrings("BTC", "USD") -// one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) +// one, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) // if err != nil { // t.Errorf("Test Failed. EventToString: Error, %s", err) // } @@ -181,7 +181,7 @@ package events // // // Test invalid currency pair // newPair := currency.NewPairFromStrings("A", "B") -// one, err := AddEvent("ANX", "price", ">=,10", newPair, "SPOT", actionTest) +// one, err := AddEvent("ANX", "price", ">=,10", newPair, assets.AssetTypeSpot, actionTest) // if err != nil { // t.Errorf("Test Failed. CheckCondition: Error, %s", err) // } @@ -194,7 +194,7 @@ package events // var tickerNew ticker.Price // tickerNew.Last = 0 // newPair = currency.NewPairFromStrings("BTC", "USD") -// ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot) +// ticker.ProcessTicker("ANX", newPair, tickerNew, exchange.AssetTypeSpot) // Events[one].Pair = newPair // conditionBool = Events[one].CheckCondition() // if conditionBool { @@ -203,7 +203,7 @@ package events // // // Test last pricce > 0 and conditional logic // tickerNew.Last = 11 -// ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot) +// ticker.ProcessTicker("ANX", newPair, tickerNew, exchange.AssetTypeSpot) // Events[one].Condition = ">,10" // conditionBool = Events[one].CheckCondition() // if !conditionBool { @@ -288,7 +288,7 @@ package events // testSetup(t) // // pair := currency.NewPairFromStrings("BTC", "USD") -// _, err := AddEvent("ANX", "price", ">=,10", pair, "SPOT", actionTest) +// _, err := AddEvent("ANX", "price", ">=,10", pair, assets.AssetTypeSpot, actionTest) // if err != nil { // t.Fatal("Test failed. TestChcheckEvents add event") // } diff --git a/events/events.go b/engine/events/events.go similarity index 50% rename from events/events.go rename to engine/events/events.go index b6332d5b..12807b9e 100644 --- a/events/events.go +++ b/engine/events/events.go @@ -3,47 +3,70 @@ package events import ( "errors" "fmt" - "strconv" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/communications" "github.com/thrasher-/gocryptotrader/communications/base" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// Event const vars const ( - itemPrice = "PRICE" - greaterThan = ">" - greaterThanOrEqual = ">=" - lessThan = "<" - lessThanOrEqual = "<=" - isEqual = "==" - actionSMSNotify = "SMS" - actionConsolePrint = "CONSOLE_PRINT" - actionTest = "ACTION_TEST" + ItemPrice = "PRICE" + ItemOrderbook = "ORDERBOOK" + + ConditionGreaterThan = ">" + ConditionGreaterThanOrEqual = ">=" + ConditionLessThan = "<" + ConditionLessThanOrEqual = "<=" + ConditionIsEqual = "==" + + ActionSMSNotify = "SMS" + ActionConsolePrint = "CONSOLE_PRINT" + ActionTest = "ACTION_TEST" + + defaultSleepDelay = time.Millisecond * 500 + defaultVerbose = true ) +// vars related to events package var ( errInvalidItem = errors.New("invalid item") errInvalidCondition = errors.New("invalid conditional option") errInvalidAction = errors.New("invalid action") errExchangeDisabled = errors.New("desired exchange is disabled") + SleepDelay = defaultSleepDelay + Verbose = defaultVerbose + // NOTE comms is an interim implementation comms *communications.Communications ) +// ConditionParams holds the event condition variables +type ConditionParams struct { + Condition string + Price float64 + + CheckBids bool + CheckBidsAndAsks bool + OrderbookAmount float64 +} + // Event struct holds the event variables type Event struct { - ID int + ID int64 Exchange string Item string - Condition string + Condition ConditionParams Pair currency.Pair - Asset string + Asset assets.AssetType Action string Executed bool } @@ -58,9 +81,9 @@ func SetComms(commsP *communications.Communications) { comms = commsP } -// AddEvent adds an event to the Events chain and returns an index/eventID +// Add adds an event to the Events chain and returns an index/eventID // and an error -func AddEvent(exchange, item, condition string, currencyPair currency.Pair, asset, action string) (int, error) { +func Add(exchange, item string, condition ConditionParams, currencyPair currency.Pair, asset assets.AssetType, action string) (int64, error) { err := IsValidEvent(exchange, item, condition, action) if err != nil { return 0, err @@ -71,7 +94,7 @@ func AddEvent(exchange, item, condition string, currencyPair currency.Pair, asse if len(Events) == 0 { evt.ID = 0 } else { - evt.ID = len(Events) + 1 + evt.ID = int64(len(Events) + 1) } evt.Exchange = exchange @@ -85,8 +108,8 @@ func AddEvent(exchange, item, condition string, currencyPair currency.Pair, asse return evt.ID, nil } -// RemoveEvent deletes and event by its ID -func RemoveEvent(eventID int) bool { +// Remove deletes and event by its ID +func Remove(eventID int64) bool { for i, x := range Events { if x.ID == eventID { Events = append(Events[:i], Events[i+1:]...) @@ -113,7 +136,7 @@ func GetEventCounter() (total, executed int) { func (e *Event) ExecuteAction() bool { if common.StringContains(e.Action, ",") { action := common.SplitStrings(e.Action, ",") - if action[0] == actionSMSNotify { + if action[0] == ActionSMSNotify { message := fmt.Sprintf("Event triggered: %s", e.String()) if action[1] == "ALL" { comms.PushEvent(base.Event{TradeDetails: message}) @@ -127,62 +150,107 @@ func (e *Event) ExecuteAction() bool { // String turns the structure event into a 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.Pair.Base.String(), - e.Pair.Quote.String(), - e.Asset, - e.Item, - e.Exchange, - condition[0]+" "+condition[1], - e.Action, + "If the %s%s [%s] %s on %s meets the following %v then %s.", e.Pair.Base.String(), + e.Pair.Quote.String(), e.Asset, e.Item, e.Exchange, e.Condition, e.Action, ) } -// CheckCondition will check the event structure to see if there is a condition -// met -func (e *Event) CheckCondition() bool { - condition := common.SplitStrings(e.Condition, ",") - targetPrice, _ := strconv.ParseFloat(condition[1], 64) +func (e *Event) processTicker() bool { + targetPrice := e.Condition.Price t, err := ticker.GetTicker(e.Exchange, e.Pair, e.Asset) if err != nil { + if Verbose { + log.Debugf("Events: failed to get ticker. Err: %s", err) + } return false } lastPrice := t.Last if lastPrice == 0 { + if Verbose { + log.Debugln("Events: ticker last price is 0") + } return false } - switch condition[0] { - case greaterThan: - if lastPrice > targetPrice { + return e.processCondition(lastPrice, targetPrice) +} + +func (e *Event) processCondition(actual, threshold float64) bool { + switch e.Condition.Condition { + case ConditionGreaterThan: + if actual > threshold { return e.ExecuteAction() } - case greaterThanOrEqual: - if lastPrice >= targetPrice { + case ConditionGreaterThanOrEqual: + if actual >= threshold { return e.ExecuteAction() } - case lessThan: - if lastPrice < targetPrice { + case ConditionLessThan: + if actual < threshold { return e.ExecuteAction() } - case lessThanOrEqual: - if lastPrice <= targetPrice { + case ConditionLessThanOrEqual: + if actual <= threshold { return e.ExecuteAction() } - case isEqual: - if lastPrice == targetPrice { + case ConditionIsEqual: + if actual == threshold { return e.ExecuteAction() } } return false } +func (e *Event) processOrderbook() bool { + ob, err := orderbook.Get(e.Exchange, e.Pair, e.Asset) + if err != nil { + if Verbose { + log.Debugf("Events: Failed to get orderbook. Err: %s", err) + } + return false + } + + success := false + if e.Condition.CheckBids || e.Condition.CheckBidsAndAsks { + for x := range ob.Bids { + subtotal := ob.Bids[x].Amount * ob.Bids[x].Price + result := e.processCondition(subtotal, e.Condition.OrderbookAmount) + if result { + success = true + log.Debugf("Events: Bid Amount: %f Price: %v Subtotal: %v", ob.Bids[x].Amount, ob.Bids[x].Price, subtotal) + } + } + } + + if !e.Condition.CheckBids || e.Condition.CheckBidsAndAsks { + for x := range ob.Asks { + subtotal := ob.Asks[x].Amount * ob.Asks[x].Price + result := e.processCondition(subtotal, e.Condition.OrderbookAmount) + if result { + success = true + log.Debugf("Events: Ask Amount: %f Price: %v Subtotal: %v", ob.Asks[x].Amount, ob.Asks[x].Price, subtotal) + } + } + } + return success +} + +// CheckCondition will check the event structure to see if there is a condition +// met +func (e *Event) CheckCondition() bool { + if e.Item == ItemPrice { + return e.processTicker() + } + + return e.processOrderbook() +} + // IsValidEvent checks the actions to be taken and returns an error if incorrect -func IsValidEvent(exchange, item, condition, action string) error { +func IsValidEvent(exchange, item string, condition ConditionParams, action string) error { exchange = common.StringToUpper(exchange) item = common.StringToUpper(item) action = common.StringToUpper(action) @@ -195,45 +263,56 @@ func IsValidEvent(exchange, item, condition, action string) error { return errInvalidItem } - if !common.StringContains(condition, ",") { + if !IsValidCondition(condition.Condition) { return errInvalidCondition } - c := common.SplitStrings(condition, ",") + if item == ItemPrice { + if condition.Price == 0 { + return errInvalidCondition + } + } - if !IsValidCondition(c[0]) || c[1] == "" { - return errInvalidCondition + if item == ItemOrderbook { + if condition.OrderbookAmount == 0 { + return errInvalidAction + } } if common.StringContains(action, ",") { a := common.SplitStrings(action, ",") - if a[0] != actionSMSNotify { + if a[0] != ActionSMSNotify { return errInvalidAction } if a[1] != "ALL" { comms.PushEvent(base.Event{Type: a[1]}) } - } else if action != actionConsolePrint && action != actionTest { + } else if action != ActionConsolePrint && action != ActionTest { return errInvalidAction } return nil } -// CheckEvents is the overarching routine that will iterate through the Events +// EventManger is the overarching routine that will iterate through the Events // chain -func CheckEvents() { +func EventManger() { + log.Debugf("EventManager started. SleepDelay: %v", SleepDelay.String()) + for { total, executed := GetEventCounter() if total > 0 && executed != total { for _, event := range Events { if !event.Executed { + if Verbose { + log.Debugf("Events: Processing event %s.", event.String()) + } success := event.CheckCondition() if success { log.Debugf( - "Event %d triggered on %s successfully.\n", event.ID, + "Events: ID: %d triggered on %s successfully.\n", event.ID, event.Exchange, ) event.Executed = true @@ -241,15 +320,16 @@ func CheckEvents() { } } } + time.Sleep(SleepDelay) } } // IsValidExchange validates the exchange -func IsValidExchange(exchange string) bool { - exchange = common.StringToUpper(exchange) +func IsValidExchange(exchangeName string) bool { + exchangeName = common.StringToLower(exchangeName) cfg := config.GetConfig() for x := range cfg.Exchanges { - if cfg.Exchanges[x].Name == exchange && cfg.Exchanges[x].Enabled { + if common.StringToLower(cfg.Exchanges[x].Name) == exchangeName && cfg.Exchanges[x].Enabled { return true } } @@ -259,7 +339,7 @@ func IsValidExchange(exchange string) bool { // IsValidCondition validates passed in condition func IsValidCondition(condition string) bool { switch condition { - case greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, isEqual: + case ConditionGreaterThan, ConditionGreaterThanOrEqual, ConditionLessThan, ConditionLessThanOrEqual, ConditionIsEqual: return true } return false @@ -269,7 +349,7 @@ func IsValidCondition(condition string) bool { func IsValidAction(action string) bool { action = common.StringToUpper(action) switch action { - case actionSMSNotify, actionConsolePrint, actionTest: + case ActionSMSNotify, ActionConsolePrint, ActionTest: return true } return false @@ -278,5 +358,9 @@ func IsValidAction(action string) bool { // IsValidItem validates passed in Item func IsValidItem(item string) bool { item = common.StringToUpper(item) - return (item == itemPrice) + switch item { + case ItemPrice, ItemOrderbook: + return true + } + return false } diff --git a/exchange.go b/engine/exchange.go similarity index 71% rename from exchange.go rename to engine/exchange.go index 1825af82..21e416dd 100644 --- a/exchange.go +++ b/engine/exchange.go @@ -1,4 +1,4 @@ -package main +package engine import ( "errors" @@ -49,8 +49,8 @@ var ( // CheckExchangeExists returns true whether or not an exchange has already // been loaded func CheckExchangeExists(exchName string) bool { - for x := range bot.exchanges { - if strings.EqualFold(bot.exchanges[x].GetName(), exchName) { + for x := range Bot.Exchanges { + if strings.EqualFold(Bot.Exchanges[x].GetName(), exchName) { return true } } @@ -59,9 +59,9 @@ func CheckExchangeExists(exchName string) bool { // GetExchangeByName returns an exchange given an exchange name func GetExchangeByName(exchName string) exchange.IBotExchange { - for x := range bot.exchanges { - if strings.EqualFold(bot.exchanges[x].GetName(), exchName) { - return bot.exchanges[x] + for x := range Bot.Exchanges { + if strings.EqualFold(Bot.Exchanges[x].GetName(), exchName) { + return Bot.Exchanges[x] } } return nil @@ -69,7 +69,7 @@ func GetExchangeByName(exchName string) exchange.IBotExchange { // ReloadExchange loads an exchange config by name func ReloadExchange(name string) error { - if len(bot.exchanges) == 0 { + if len(Bot.Exchanges) == 0 { return ErrNoExchangesLoaded } @@ -77,20 +77,20 @@ func ReloadExchange(name string) error { return ErrExchangeNotFound } - exchCfg, err := bot.config.GetExchangeConfig(name) + exchCfg, err := Bot.Config.GetExchangeConfig(name) if err != nil { return err } e := GetExchangeByName(name) - e.Setup(&exchCfg) + e.Setup(exchCfg) log.Debugf("%s exchange reloaded successfully.\n", name) return nil } // UnloadExchange unloads an exchange by name func UnloadExchange(name string) error { - if len(bot.exchanges) == 0 { + if len(Bot.Exchanges) == 0 { return ErrNoExchangesLoaded } @@ -98,21 +98,21 @@ func UnloadExchange(name string) error { return ErrExchangeNotFound } - exchCfg, err := bot.config.GetExchangeConfig(name) + exchCfg, err := Bot.Config.GetExchangeConfig(name) if err != nil { return err } exchCfg.Enabled = false - err = bot.config.UpdateExchangeConfig(&exchCfg) + err = Bot.Config.UpdateExchangeConfig(exchCfg) if err != nil { return err } - for x := range bot.exchanges { - if strings.EqualFold(bot.exchanges[x].GetName(), name) { - bot.exchanges[x].SetEnabled(false) - bot.exchanges = append(bot.exchanges[:x], bot.exchanges[x+1:]...) + for x := range Bot.Exchanges { + if strings.EqualFold(Bot.Exchanges[x].GetName(), name) { + Bot.Exchanges[x].SetEnabled(false) + Bot.Exchanges = append(Bot.Exchanges[:x], Bot.Exchanges[x+1:]...) return nil } } @@ -125,7 +125,7 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { nameLower := common.StringToLower(name) var exch exchange.IBotExchange - if len(bot.exchanges) > 0 { + if len(Bot.Exchanges) > 0 { if CheckExchangeExists(name) { return ErrExchangeAlreadyLoaded } @@ -197,14 +197,67 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { } exch.SetDefaults() - bot.exchanges = append(bot.exchanges, exch) - exchCfg, err := bot.config.GetExchangeConfig(name) + exchCfg, err := Bot.Config.GetExchangeConfig(name) if err != nil { return err } + if Bot.Settings.EnableAllPairs { + exchCfg.EnabledPairs = exchCfg.AvailablePairs + } + + if Bot.Settings.EnableExchangeVerbose { + exchCfg.Verbose = true + } + + if Bot.Settings.EnableExchangeWebsocketSupport { + if exchCfg.Features != nil { + if exchCfg.Features.Supports.Websocket { + exchCfg.Features.Enabled.Websocket = true + } + } + } + + if Bot.Settings.EnableExchangeAutoPairUpdates { + if exchCfg.Features != nil { + if exchCfg.Features.Supports.RESTCapabilities.AutoPairUpdates { + exchCfg.Features.Enabled.AutoPairUpdates = true + } + } + } + + if Bot.Settings.DisableExchangeAutoPairUpdates { + if exchCfg.Features != nil { + if exchCfg.Features.Supports.RESTCapabilities.AutoPairUpdates { + exchCfg.Features.Enabled.AutoPairUpdates = false + } + + } + } + + if Bot.Settings.ExchangeHTTPUserAgent != "" { + exchCfg.HTTPUserAgent = Bot.Settings.ExchangeHTTPUserAgent + } + + if Bot.Settings.ExchangeHTTPProxy != "" { + exchCfg.ProxyAddress = Bot.Settings.ExchangeHTTPProxy + } + + if Bot.Settings.ExchangeHTTPTimeout != exchange.DefaultHTTPTimeout { + exchCfg.HTTPTimeout = Bot.Settings.ExchangeHTTPTimeout + } + + if Bot.Settings.EnableExchangeHTTPDebugging { + exchCfg.HTTPDebugging = Bot.Settings.EnableExchangeHTTPDebugging + } + exchCfg.Enabled = true - exch.Setup(&exchCfg) + err = exch.Setup(exchCfg) + if err != nil { + return err + } + + Bot.Exchanges = append(Bot.Exchanges, exch) if useWG { exch.Start(wg) @@ -216,11 +269,11 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { return nil } -// SetupExchanges sets up the exchanges used by the bot +// SetupExchanges sets up the exchanges used by the Bot func SetupExchanges() { var wg sync.WaitGroup - for x := range bot.config.Exchanges { - exch := &bot.config.Exchanges[x] + for x := range Bot.Config.Exchanges { + exch := &Bot.Config.Exchanges[x] if CheckExchangeExists(exch.Name) { e := GetExchangeByName(exch.Name) if e == nil { @@ -241,7 +294,7 @@ func SetupExchanges() { return } - if !exch.Enabled { + if !exch.Enabled && !Bot.Settings.EnableAllExchanges { log.Debugf("%s: Exchange support: Disabled", exch.Name) continue } @@ -253,7 +306,7 @@ func SetupExchanges() { log.Debugf( "%s: Exchange support: Enabled (Authenticated API support: %s - Verbose mode: %s).\n", exch.Name, - common.IsEnabled(exch.AuthenticatedAPISupport), + common.IsEnabled(exch.API.AuthenticatedSupport), common.IsEnabled(exch.Verbose), ) } diff --git a/exchange_test.go b/engine/exchange_test.go similarity index 93% rename from exchange_test.go rename to engine/exchange_test.go index 60662da1..ba41103e 100644 --- a/exchange_test.go +++ b/engine/exchange_test.go @@ -1,4 +1,4 @@ -package main +package engine import ( "testing" @@ -10,8 +10,11 @@ var testSetup = false func SetupTest(t *testing.T) { if !testSetup { - bot.config = &config.Cfg - err := bot.config.LoadConfig("./testdata/configtest.json") + if Bot == nil { + Bot = new(Engine) + } + Bot.Config = &config.Cfg + err := Bot.Config.LoadConfig("") if err != nil { t.Fatalf("Test failed. SetupTest: Failed to load config: %s", err) } @@ -130,9 +133,3 @@ func TestUnloadExchange(t *testing.T) { CleanupTest(t) } - -func TestSetupExchanges(t *testing.T) { - SetupTest(t) - SetupExchanges() - CleanupTest(t) -} diff --git a/engine/helpers.go b/engine/helpers.go new file mode 100644 index 00000000..10ef93ff --- /dev/null +++ b/engine/helpers.go @@ -0,0 +1,745 @@ +package engine + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "net" + "os" + "path/filepath" + "time" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/stats" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" + log "github.com/thrasher-/gocryptotrader/logger" + "github.com/thrasher-/gocryptotrader/portfolio" + "github.com/thrasher-/gocryptotrader/utils" +) + +// GetAvailableExchanges returns a list of enabled exchanges +func GetAvailableExchanges() []string { + var enExchanges []string + for x := range Bot.Config.Exchanges { + if Bot.Config.Exchanges[x].Enabled { + enExchanges = append(enExchanges, Bot.Config.Exchanges[x].Name) + } + } + return enExchanges +} + +// GetAllAvailablePairs returns a list of all available pairs on either enabled +// or disabled exchanges +func GetAllAvailablePairs(enabledExchangesOnly bool, assetType assets.AssetType) currency.Pairs { + var pairList currency.Pairs + for x := range Bot.Config.Exchanges { + if enabledExchangesOnly && !Bot.Config.Exchanges[x].Enabled { + continue + } + + exchName := Bot.Config.Exchanges[x].Name + pairs, err := Bot.Config.GetAvailablePairs(exchName, assetType) + if err != nil { + continue + } + + for y := range pairs { + if pairList.Contains(pairs[y], false) { + continue + } + pairList = append(pairList, pairs[y]) + } + } + return pairList +} + +// GetSpecificAvailablePairs returns a list of supported pairs based on specific +// parameters +func GetSpecificAvailablePairs(enabledExchangesOnly, fiatPairs, includeUSDT, cryptoPairs bool, assetType assets.AssetType) currency.Pairs { + var pairList currency.Pairs + supportedPairs := GetAllAvailablePairs(enabledExchangesOnly, assetType) + + for x := range supportedPairs { + if fiatPairs { + if supportedPairs[x].IsCryptoFiatPair() && + !supportedPairs[x].ContainsCurrency(currency.USDT) || + (includeUSDT && + supportedPairs[x].ContainsCurrency(currency.USDT) && + supportedPairs[x].IsCryptoPair()) { + if pairList.Contains(supportedPairs[x], false) { + continue + } + pairList = append(pairList, supportedPairs[x]) + } + } + if cryptoPairs { + if supportedPairs[x].IsCryptoPair() { + if pairList.Contains(supportedPairs[x], false) { + continue + } + pairList = append(pairList, supportedPairs[x]) + } + } + } + return pairList +} + +// IsRelatablePairs checks to see if the two pairs are relatable +func IsRelatablePairs(p1, p2 currency.Pair, includeUSDT bool) bool { + if p1.EqualIncludeReciprocal(p2) { + return true + } + + var relatablePairs = GetRelatableCurrencies(p1, true, includeUSDT) + if p1.IsCryptoFiatPair() { + for x := range relatablePairs { + relatablePairs = append(relatablePairs, + GetRelatableFiatCurrencies(relatablePairs[x])...) + } + } + return relatablePairs.Contains(p2, false) +} + +// MapCurrenciesByExchange returns a list of currency pairs mapped to an +// exchange +func MapCurrenciesByExchange(p currency.Pairs, enabledExchangesOnly bool, assetType assets.AssetType) map[string]currency.Pairs { + currencyExchange := make(map[string]currency.Pairs) + for x := range p { + for y := range Bot.Config.Exchanges { + if enabledExchangesOnly && !Bot.Config.Exchanges[y].Enabled { + continue + } + exchName := Bot.Config.Exchanges[y].Name + success, err := Bot.Config.SupportsPair(exchName, p[x], assetType) + if err != nil || !success { + continue + } + + result, ok := currencyExchange[exchName] + if !ok { + var pairs []currency.Pair + pairs = append(pairs, p[x]) + currencyExchange[exchName] = pairs + } else { + if result.Contains(p[x], false) { + continue + } + result = append(result, p[x]) + currencyExchange[exchName] = result + } + } + } + return currencyExchange +} + +// GetExchangeNamesByCurrency returns a list of exchanges supporting +// a currency pair based on whether the exchange is enabled or not +func GetExchangeNamesByCurrency(p currency.Pair, enabled bool, assetType assets.AssetType) []string { + var exchanges []string + for x := range Bot.Config.Exchanges { + if enabled != Bot.Config.Exchanges[x].Enabled { + continue + } + + exchName := Bot.Config.Exchanges[x].Name + success, err := Bot.Config.SupportsPair(exchName, p, assetType) + if err != nil { + continue + } + + if success { + exchanges = append(exchanges, exchName) + } + } + return exchanges +} + +// GetRelatableCryptocurrencies returns a list of currency pairs if it can find +// any relatable currencies (e.g ETHBTC -> ETHLTC -> ETHUSDT -> ETHREP) +func GetRelatableCryptocurrencies(p currency.Pair) currency.Pairs { + var pairs currency.Pairs + cryptocurrencies := currency.GetCryptocurrencies() + + for x := range cryptocurrencies { + newPair := currency.NewPair(p.Base, cryptocurrencies[x]) + if newPair.IsInvalid() { + continue + } + + if newPair.Base.Upper() == p.Base.Upper() && + newPair.Quote.Upper() == p.Quote.Upper() { + continue + } + + if pairs.Contains(newPair, false) { + continue + } + pairs = append(pairs, newPair) + } + return pairs +} + +// GetRelatableFiatCurrencies returns a list of currency pairs if it can find +// any relatable currencies (e.g ETHUSD -> ETHAUD -> ETHGBP -> ETHJPY) +func GetRelatableFiatCurrencies(p currency.Pair) currency.Pairs { + var pairs currency.Pairs + fiatCurrencies := currency.GetFiatCurrencies() + + for x := range fiatCurrencies { + newPair := currency.NewPair(p.Base, fiatCurrencies[x]) + if newPair.Base.Upper() == newPair.Quote.Upper() { + continue + } + + if newPair.Base.Upper() == p.Base.Upper() && + newPair.Quote.Upper() == p.Quote.Upper() { + continue + } + + if pairs.Contains(newPair, false) { + continue + } + pairs = append(pairs, newPair) + } + return pairs +} + +// GetRelatableCurrencies returns a list of currency pairs if it can find +// any relatable currencies (e.g BTCUSD -> BTC USDT -> XBT USDT -> XBT USD) +// incOrig includes the supplied pair if desired +func GetRelatableCurrencies(p currency.Pair, incOrig, incUSDT bool) currency.Pairs { + var pairs currency.Pairs + + addPair := func(p currency.Pair) { + if pairs.Contains(p, true) { + return + } + pairs = append(pairs, p) + } + + buildPairs := func(p currency.Pair, incOrig bool) { + if incOrig { + addPair(p) + } + + first, ok := currency.GetTranslation(p.Base) + if ok { + addPair(currency.NewPair(first, p.Quote)) + + var second currency.Code + second, ok = currency.GetTranslation(p.Quote) + if ok { + addPair(currency.NewPair(first, second)) + } + } + + second, ok := currency.GetTranslation(p.Quote) + if ok { + addPair(currency.NewPair(p.Base, second)) + } + } + + buildPairs(p, incOrig) + buildPairs(p.Swap(), incOrig) + + if !incUSDT { + pairs = pairs.RemovePairsByFilter(currency.USDT) + } + + return pairs +} + +// GetSpecificOrderbook returns a specific orderbook given the currency, +// exchangeName and assetType +func GetSpecificOrderbook(p currency.Pair, exchangeName string, assetType assets.AssetType) (orderbook.Base, error) { + var specificOrderbook orderbook.Base + var err error + for x := range Bot.Exchanges { + if Bot.Exchanges[x] != nil { + if Bot.Exchanges[x].GetName() == exchangeName { + specificOrderbook, err = Bot.Exchanges[x].FetchOrderbook( + p, + assetType, + ) + break + } + } + } + return specificOrderbook, err +} + +// GetSpecificTicker returns a specific ticker given the currency, +// exchangeName and assetType +func GetSpecificTicker(p currency.Pair, exchangeName string, assetType assets.AssetType) (ticker.Price, error) { + var specificTicker ticker.Price + var err error + for x := range Bot.Exchanges { + if Bot.Exchanges[x] != nil { + if Bot.Exchanges[x].GetName() == exchangeName { + specificTicker, err = Bot.Exchanges[x].FetchTicker( + p, + assetType, + ) + break + } + } + } + return specificTicker, err +} + +// GetCollatedExchangeAccountInfoByCoin collates individual exchange account +// information and turns into into a map string of +// exchange.AccountCurrencyInfo +func GetCollatedExchangeAccountInfoByCoin(exchAccounts []exchange.AccountInfo) map[currency.Code]exchange.AccountCurrencyInfo { + result := make(map[currency.Code]exchange.AccountCurrencyInfo) + for _, accounts := range exchAccounts { + for _, account := range accounts.Accounts { + for _, accountCurrencyInfo := range account.Currencies { + currencyName := accountCurrencyInfo.CurrencyName + avail := accountCurrencyInfo.TotalValue + onHold := accountCurrencyInfo.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].Exchange == exchangeName { + return accounts[i], nil + } + } + return exchange.AccountInfo{}, ErrExchangeNotFound +} + +// GetExchangeHighestPriceByCurrencyPair returns the exchange with the highest +// price for a given currency pair and asset type +func GetExchangeHighestPriceByCurrencyPair(p currency.Pair, assetType assets.AssetType) (string, error) { + result := stats.SortExchangesByPrice(p, assetType, true) + if len(result) == 0 { + 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 currency.Pair, assetType assets.AssetType) (string, error) { + result := stats.SortExchangesByPrice(p, assetType, false) + if len(result) == 0 { + return "", fmt.Errorf("no stats for supplied currency pair and asset type") + } + + return result[0].Exchange, nil +} + +// SeedExchangeAccountInfo seeds account info +func SeedExchangeAccountInfo(data []exchange.AccountInfo) { + if len(data) == 0 { + return + } + + port := portfolio.GetPortfolio() + + for _, exchangeData := range data { + exchangeName := exchangeData.Exchange + + var currencies []exchange.AccountCurrencyInfo + for _, account := range exchangeData.Accounts { + for _, info := range account.Currencies { + + var update bool + for i := range currencies { + if info.CurrencyName == currencies[i].CurrencyName { + currencies[i].Hold += info.Hold + currencies[i].TotalValue += info.TotalValue + update = true + } + } + + if update { + continue + } + + currencies = append(currencies, exchange.AccountCurrencyInfo{ + CurrencyName: info.CurrencyName, + TotalValue: info.TotalValue, + Hold: info.Hold, + }) + } + } + + for _, total := range currencies { + currencyName := total.CurrencyName + total := total.TotalValue + + if !port.ExchangeAddressExists(exchangeName, currencyName) { + if total <= 0 { + continue + } + + log.Debugf("Portfolio: Adding new exchange address: %s, %s, %f, %s\n", + exchangeName, + currencyName, + total, + portfolio.PortfolioAddressExchange) + + port.Addresses = append( + port.Addresses, + portfolio.Address{Address: exchangeName, + CoinType: currencyName, + Balance: total, + Description: portfolio.PortfolioAddressExchange}) + + } else { + if total <= 0 { + log.Debugf("Portfolio: Removing %s %s entry.\n", + exchangeName, + currencyName) + + port.RemoveExchangeAddress(exchangeName, currencyName) + } else { + balance, ok := port.GetAddressBalance(exchangeName, + portfolio.PortfolioAddressExchange, + currencyName) + + if !ok { + continue + } + + if balance != total { + log.Debugf("Portfolio: Updating %s %s entry with balance %f.\n", + exchangeName, + currencyName, + total) + + port.UpdateExchangeAddressBalance(exchangeName, + currencyName, + total) + } + } + } + } + } +} + +// GetCryptocurrenciesByExchange returns a list of cryptocurrencies the exchange supports +func GetCryptocurrenciesByExchange(exchangeName string, enabledExchangesOnly, enabledPairs bool, assetType assets.AssetType) ([]string, error) { + var cryptocurrencies []string + for x := range Bot.Config.Exchanges { + if Bot.Config.Exchanges[x].Name != exchangeName { + continue + } + if enabledExchangesOnly && !Bot.Config.Exchanges[x].Enabled { + continue + } + + exchName := Bot.Config.Exchanges[x].Name + var pairs []currency.Pair + var err error + + if enabledPairs { + pairs, err = Bot.Config.GetEnabledPairs(exchName, assetType) + if err != nil { + return nil, err + } + } else { + pairs, err = Bot.Config.GetAvailablePairs(exchName, assetType) + if err != nil { + return nil, err + } + } + + for y := range pairs { + if pairs[y].Base.IsCryptocurrency() && + !common.StringDataCompareInsensitive(cryptocurrencies, pairs[y].Base.String()) { + cryptocurrencies = append(cryptocurrencies, pairs[y].Base.String()) + } + + if pairs[y].Quote.IsCryptocurrency() && + !common.StringDataCompareInsensitive(cryptocurrencies, pairs[y].Quote.String()) { + cryptocurrencies = append(cryptocurrencies, pairs[y].Quote.String()) + } + } + + } + return cryptocurrencies, nil +} + +// GetExchangeCryptocurrencyDepositAddress returns the cryptocurrency deposit address for a particular +// exchange +func GetExchangeCryptocurrencyDepositAddress(exchName string, item currency.Code) (string, error) { + exch := GetExchangeByName(exchName) + if exch == nil { + return "", ErrExchangeNotFound + } + + return exch.GetDepositAddress(item, "") +} + +// GetExchangeCryptocurrencyDepositAddresses obtains an exchanges deposit cryptocurrency list +func GetExchangeCryptocurrencyDepositAddresses() map[string]map[string]string { + result := make(map[string]map[string]string) + + for x := range Bot.Exchanges { + if !Bot.Exchanges[x].IsEnabled() { + continue + } + exchName := Bot.Exchanges[x].GetName() + + if !Bot.Exchanges[x].GetAuthenticatedAPISupport() { + if Bot.Settings.Verbose { + log.Debugf("GetExchangeCryptocurrencyDepositAddresses: Skippping %s due to disabled authenticated API support.", exchName) + } + continue + } + + cryptoCurrencies, err := GetCryptocurrenciesByExchange(exchName, true, true, assets.AssetTypeSpot) + if err != nil { + log.Debugf("%s failed to get cryptocurrency deposit addresses. Err: %s", exchName, err) + continue + } + + cryptoAddr := make(map[string]string) + for y := range cryptoCurrencies { + cryptocurrency := cryptoCurrencies[y] + depositAddr, err := Bot.Exchanges[x].GetDepositAddress(currency.NewCode(cryptocurrency), "") + if err != nil { + log.Debugf("%s failed to get cryptocurrency deposit addresses. Err: %s", exchName, err) + continue + } + cryptoAddr[cryptocurrency] = depositAddr + } + result[exchName] = cryptoAddr + } + + return result +} + +// GetDepositAddressByExchange returns a deposit address for the specified exchange and cryptocurrency +// if it exists +func GetDepositAddressByExchange(exchName string, currencyItem currency.Code) string { + for x, y := range Bot.CryptocurrencyDepositAddresses { + if exchName == x { + addr, ok := y[currencyItem.String()] + if ok { + return addr + } + } + } + return "" +} + +// GetDepositAddressesByExchange returns a list of cryptocurrency addresses for the specified +// exchange if they exist +func GetDepositAddressesByExchange(exchName string) map[string]string { + for x, y := range Bot.CryptocurrencyDepositAddresses { + if exchName == x { + return y + } + } + return nil +} + +// WithdrawCryptocurrencyFundsByExchange withdraws the desired cryptocurrency and amount to a desired cryptocurrency address +func WithdrawCryptocurrencyFundsByExchange(exchName string) (string, error) { + exch := GetExchangeByName(exchName) + if exch == nil { + return "", ErrExchangeNotFound + } + + // TO-DO: FILL + return exch.WithdrawCryptocurrencyFunds(&exchange.CryptoWithdrawRequest{}) +} + +// FormatCurrency is a method that formats and returns a currency pair +// based on the user currency display preferences +func FormatCurrency(p currency.Pair) currency.Pair { + return p.Format(Bot.Config.Currency.CurrencyPairFormat.Delimiter, + Bot.Config.Currency.CurrencyPairFormat.Uppercase) +} + +// GetExchanges returns a list of loaded exchanges +func GetExchanges(enabled bool) []string { + var exchanges []string + for x := range Bot.Exchanges { + if Bot.Exchanges[x].IsEnabled() && enabled { + exchanges = append(exchanges, Bot.Exchanges[x].GetName()) + continue + } + exchanges = append(exchanges, Bot.Exchanges[x].GetName()) + } + return exchanges +} + +// GetAllActiveTickers returns all enabled exchange tickers +func GetAllActiveTickers() []EnabledExchangeCurrencies { + var tickerData []EnabledExchangeCurrencies + + for _, exch := range Bot.Exchanges { + if !exch.IsEnabled() { + continue + } + + assets := exch.GetAssetTypes() + exchName := exch.GetName() + var exchangeTicker EnabledExchangeCurrencies + exchangeTicker.ExchangeName = exchName + + for y := range assets { + currencies := exch.GetEnabledPairs(assets[y]) + for z := range currencies { + tp, err := exch.FetchTicker(currencies[z], assets[y]) + if err != nil { + log.Debugf("Exchange %s failed to retrieve %s ticker. Err: %s", exchName, + currencies[z].String(), + err) + continue + } + exchangeTicker.ExchangeValues = append(exchangeTicker.ExchangeValues, tp) + } + tickerData = append(tickerData, exchangeTicker) + } + } + return tickerData +} + +// 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() { + if Bot.Settings.Verbose { + log.Debugf("GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.", individualBot.GetName()) + } + continue + } + individualExchange, err := individualBot.GetAccountInfo() + if err != nil { + log.Debugf("Error encountered retrieving exchange account info for %s. Error %s", + individualBot.GetName(), err) + continue + } + response.Data = append(response.Data, individualExchange) + } + } + return response +} + +func checkCerts() error { + targetDir := utils.GetTLSDir(Bot.Settings.DataDir) + _, err := os.Stat(targetDir) + if os.IsNotExist(err) { + err := common.CreateDir(targetDir) + if err != nil { + return err + } + return genCert(targetDir) + } + + log.Debugf("gRPC TLS certs directory already exists, will use them.") + return nil +} + +func genCert(targetDir string) error { + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return fmt.Errorf("failed to generate ecdsa private key: %s", err) + } + + notBefore := time.Now() + notAfter := notBefore.Add(time.Hour * 24 * 365) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return fmt.Errorf("failed to generate serial number: %s", err) + } + + host, err := os.Hostname() + if err != nil { + return fmt.Errorf("failed to get hostname: %s", err) + } + + dnsNames := []string{host} + if host != "localhost" { + dnsNames = append(dnsNames, "localhost") + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"gocryptotrader"}, + CommonName: host, + }, + NotBefore: notBefore, + NotAfter: notAfter, + IsCA: true, + BasicConstraintsValid: true, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + + IPAddresses: []net.IP{ + net.ParseIP("127.0.0.1"), + net.ParseIP("::1"), + }, + DNSNames: dnsNames, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey) + if err != nil { + return fmt.Errorf("failed to create certificate: %s", err) + } + + certData := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + if certData == nil { + return fmt.Errorf("cert data is nil") + } + + b, err := x509.MarshalECPrivateKey(privKey) + if err != nil { + return fmt.Errorf("failed to marshal ECDSA private key: %s", err) + } + + keyData := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}) + if keyData == nil { + return fmt.Errorf("key pem data is nil") + } + + err = common.WriteFile(filepath.Join(targetDir, "key.pem"), keyData) + if err != nil { + return fmt.Errorf("failed to write key.pem file %s", err) + } + + err = common.WriteFile(filepath.Join(targetDir, "cert.pem"), certData) + if err != nil { + return fmt.Errorf("failed to write cert.pem file %s", err) + } + + log.Debugf("TLS key.pem and cert.pem files written to %s", targetDir) + return nil +} diff --git a/helpers_test.go b/engine/helpers_test.go similarity index 84% rename from helpers_test.go rename to engine/helpers_test.go index 45049bc6..c30a3989 100644 --- a/helpers_test.go +++ b/engine/helpers_test.go @@ -1,4 +1,4 @@ -package main +package engine import ( "testing" @@ -7,13 +7,14 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( - TestConfig = "./testdata/configtest.json" + TestConfig = "../testdata/configtest.json" ) var ( @@ -23,14 +24,17 @@ var ( func SetupTestHelpers(t *testing.T) { if !helperTestLoaded { if !testSetup { - bot.config = &config.Cfg - err := bot.config.LoadConfig("./testdata/configtest.json") + if Bot == nil { + Bot = new(Engine) + } + Bot.Config = &config.Cfg + err := Bot.Config.LoadConfig("../testdata/configtest.json") if err != nil { t.Fatalf("Test failed. SetupTest: Failed to load config: %s", err) } testSetup = true } - err := bot.config.RetrieveConfigCurrencyPairs(true) + err := Bot.Config.RetrieveConfigCurrencyPairs(true) if err != nil { t.Fatalf("Failed to retrieve config currency pairs. %s", err) } @@ -40,7 +44,8 @@ func SetupTestHelpers(t *testing.T) { func TestGetSpecificAvailablePairs(t *testing.T) { SetupTestHelpers(t) - result := GetSpecificAvailablePairs(true, true, true, false) + assetType := assets.AssetTypeSpot + result := GetSpecificAvailablePairs(true, true, true, false, assetType) if !result.Contains(currency.NewPairFromStrings("BTC", "USD"), true) { t.Fatal("Unexpected result") @@ -50,13 +55,13 @@ func TestGetSpecificAvailablePairs(t *testing.T) { t.Fatal("Unexpected result") } - result = GetSpecificAvailablePairs(true, true, false, false) + result = GetSpecificAvailablePairs(true, true, false, false, assetType) if result.Contains(currency.NewPairFromStrings("BTC", "USDT"), false) { t.Fatal("Unexpected result") } - result = GetSpecificAvailablePairs(true, false, false, true) + result = GetSpecificAvailablePairs(true, false, false, true, assetType) if !result.Contains(currency.NewPairFromStrings("LTC", "BTC"), false) { t.Fatal("Unexpected result") } @@ -229,7 +234,7 @@ func TestMapCurrenciesByExchange(t *testing.T) { currency.NewPair(currency.BTC, currency.EUR), } - result := MapCurrenciesByExchange(pairs, true) + result := MapCurrenciesByExchange(pairs, true, assets.AssetTypeSpot) pairs, ok := result["Bitstamp"] if !ok { t.Fatal("Unexpected result") @@ -242,18 +247,20 @@ func TestMapCurrenciesByExchange(t *testing.T) { func TestGetExchangeNamesByCurrency(t *testing.T) { SetupTestHelpers(t) + assetType := assets.AssetTypeSpot - result := GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "USD"), true) + result := GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "USD"), true, assetType) if !common.StringDataCompare(result, "Bitstamp") { t.Fatal("Unexpected result") } - result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "JPY"), true) + result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "JPY"), true, assetType) + t.Log(result) if !common.StringDataCompare(result, "Bitflyer") { t.Fatal("Unexpected result") } - result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("blah", "JPY"), true) + result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("blah", "JPY"), true, assetType) if len(result) > 0 { t.Fatal("Unexpected result") } @@ -266,12 +273,13 @@ func TestGetSpecificOrderbook(t *testing.T) { var bids []orderbook.Item bids = append(bids, orderbook.Item{Price: 1000, Amount: 1}) + asset := assets.AssetTypeSpot base := orderbook.Base{ Pair: currency.NewPair(currency.BTC, currency.USD), Bids: bids, ExchangeName: "Bitstamp", - AssetType: orderbook.Spot, + AssetType: asset, } err := base.Process() @@ -279,7 +287,7 @@ func TestGetSpecificOrderbook(t *testing.T) { t.Fatal("Unexpected result", err) } - ob, err := GetSpecificOrderbook("BTCUSD", "Bitstamp", ticker.Spot) + ob, err := GetSpecificOrderbook(currency.NewPairFromString("BTCUSD"), "Bitstamp", assets.AssetTypeSpot) if err != nil { t.Fatal(err) } @@ -288,7 +296,7 @@ func TestGetSpecificOrderbook(t *testing.T) { t.Fatal("Unexpected result") } - ob, err = GetSpecificOrderbook("ETHLTC", "Bitstamp", ticker.Spot) + ob, err = GetSpecificOrderbook(currency.NewPairFromStrings("ETH", "LTC"), "Bitstamp", asset) if err == nil { t.Fatal("Unexpected result") } @@ -301,15 +309,15 @@ func TestGetSpecificTicker(t *testing.T) { LoadExchange("Bitstamp", false, nil) p := currency.NewPairFromStrings("BTC", "USD") - + asset := assets.AssetTypeSpot err := ticker.ProcessTicker("Bitstamp", &ticker.Price{Pair: p, Last: 1000}, - ticker.Spot) + assets.AssetTypeSpot) if err != nil { t.Fatal("Test failed. ProcessTicker error", err) } - tick, err := GetSpecificTicker("BTCUSD", "Bitstamp", ticker.Spot) + tick, err := GetSpecificTicker(currency.NewPairFromStrings("BTC", "USD"), "Bitstamp", asset) if err != nil { t.Fatal(err) } @@ -318,7 +326,7 @@ func TestGetSpecificTicker(t *testing.T) { t.Fatal("Unexpected result") } - tick, err = GetSpecificTicker("ETHLTC", "Bitstamp", ticker.Spot) + tick, err = GetSpecificTicker(currency.NewPairFromStrings("ETH", "LTC"), "Bitstamp", asset) if err == nil { t.Fatal("Unexpected result") } @@ -409,7 +417,7 @@ func TestGetAccountCurrencyInfoByExchangeName(t *testing.T) { } _, err = GetAccountCurrencyInfoByExchangeName(exchangeInfo, "ASDF") - if err.Error() != exchange.ErrExchangeNotFound { + if err != ErrExchangeNotFound { t.Fatal("Unexepcted result") } } @@ -418,9 +426,10 @@ func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) { SetupTestHelpers(t) p := currency.NewPairFromStrings("BTC", "USD") - stats.Add("Bitfinex", p, ticker.Spot, 1000, 10000) - stats.Add("Bitstamp", p, ticker.Spot, 1337, 10000) - exchangeName, err := GetExchangeHighestPriceByCurrencyPair(p, ticker.Spot) + asset := assets.AssetTypeSpot + stats.Add("Bitfinex", p, assets.AssetTypeSpot, 1000, 10000) + stats.Add("Bitstamp", p, assets.AssetTypeSpot, 1337, 10000) + exchangeName, err := GetExchangeHighestPriceByCurrencyPair(p, asset) if err != nil { t.Error(err) } @@ -429,7 +438,7 @@ func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) { t.Error("Unexpected result") } - _, err = GetExchangeHighestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"), ticker.Spot) + _, err = GetExchangeHighestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"), asset) if err == nil { t.Error("Unexpected result") } @@ -439,9 +448,10 @@ func TestGetExchangeLowestPriceByCurrencyPair(t *testing.T) { SetupTestHelpers(t) p := currency.NewPairFromStrings("BTC", "USD") - stats.Add("Bitfinex", p, ticker.Spot, 1000, 10000) - stats.Add("Bitstamp", p, ticker.Spot, 1337, 10000) - exchangeName, err := GetExchangeLowestPriceByCurrencyPair(p, ticker.Spot) + asset := assets.AssetTypeSpot + stats.Add("Bitfinex", p, assets.AssetTypeSpot, 1000, 10000) + stats.Add("Bitstamp", p, assets.AssetTypeSpot, 1337, 10000) + exchangeName, err := GetExchangeLowestPriceByCurrencyPair(p, asset) if err != nil { t.Error(err) } @@ -450,8 +460,17 @@ func TestGetExchangeLowestPriceByCurrencyPair(t *testing.T) { t.Error("Unexpected result") } - _, err = GetExchangeLowestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"), ticker.Spot) + _, err = GetExchangeLowestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"), asset) if err == nil { t.Error("Unexpected reuslt") } } + +func TestGetCryptocurrenciesByExchange(t *testing.T) { + SetupTestHelpers(t) + + _, err := GetCryptocurrenciesByExchange("Bitfinex", false, false, assets.AssetTypeSpot) + if err != nil { + t.Fatalf("Test failed. Err %s", err) + } +} diff --git a/engine/orders.go b/engine/orders.go new file mode 100644 index 00000000..b56e37ad --- /dev/null +++ b/engine/orders.go @@ -0,0 +1,47 @@ +package engine + +import ( + "sync" + "time" + + exchange "github.com/thrasher-/gocryptotrader/exchanges" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// OrderManager manages orders for all enabled exchanges +type OrderManager struct { + m sync.Mutex + Orders map[string][]exchange.OrderDetail +} + +func (o *OrderManager) add() { + o.m.Lock() + defer o.m.Unlock() +} + +// StartOrderManagerRoutine starts the orderbook manage routine +func StartOrderManagerRoutine() { + log.Debugln("Starting order manager routine") + if Bot.OrderManager == nil { + Bot.OrderManager = new(OrderManager) + } + + for { + for x := range Bot.Exchanges { + if !Bot.Exchanges[x].IsEnabled() || !Bot.Exchanges[x].GetAuthenticatedAPISupport() { + continue + } + exchName := Bot.Exchanges[x].GetName() + log.Printf("Getting active orders for %s", exchName) + + orders, err := Bot.Exchanges[x].GetActiveOrders(&exchange.GetOrdersRequest{}) + if err != nil { + log.Printf("Get active orders failed: %s", err) + continue + } + + log.Printf("Orders for exchange %s: %v", exchName, orders) + } + time.Sleep(time.Second * 1) + } +} diff --git a/engine/restful_router.go b/engine/restful_router.go new file mode 100644 index 00000000..4c7048a6 --- /dev/null +++ b/engine/restful_router.go @@ -0,0 +1,107 @@ +package engine + +import ( + "fmt" + "net/http" + _ "net/http/pprof" // blank import required for pprof + "strconv" + "time" + + "github.com/gorilla/mux" + "github.com/thrasher-/gocryptotrader/common" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// 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.Debugf( + "%s\t%s\t%s\t%s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) +} + +// StartRESTServer starts a REST server +func StartRESTServer() { + listenAddr := Bot.Config.RemoteControl.DeprecatedRPC.ListenAddress + log.Debugf("Deprecated RPC server support enabled. Listen URL: http://%s:%d\n", common.ExtractHost(listenAddr), common.ExtractPort(listenAddr)) + err := http.ListenAndServe(listenAddr, newRouter(true)) + if err != nil { + log.Errorf("Failed to start deprecated RPC server. Err: %s", err) + } +} + +// StartWebsocketServer starts a Websocket server +func StartWebsocketServer() { + listenAddr := Bot.Config.RemoteControl.WebsocketRPC.ListenAddress + log.Debugf("Websocket RPC support enabled. Listen URL: ws://%s:%d/ws\n", common.ExtractHost(listenAddr), common.ExtractPort(listenAddr)) + err := http.ListenAndServe(listenAddr, newRouter(false)) + if err != nil { + log.Errorf("Failed to start websocket RPC server. Err: %s", err) + } +} + +// newRouter takes in the exchange interfaces and returns a new multiplexor +// router +func newRouter(isREST bool) *mux.Router { + router := mux.NewRouter().StrictSlash(true) + var routes []Route + var listenAddr string + + if isREST { + listenAddr = Bot.Config.RemoteControl.DeprecatedRPC.ListenAddress + } else { + listenAddr = Bot.Config.RemoteControl.WebsocketRPC.ListenAddress + } + + if common.ExtractPort(listenAddr) == 80 { + listenAddr = common.ExtractHost(listenAddr) + } else { + listenAddr = common.JoinStrings([]string{common.ExtractHost(listenAddr), + strconv.Itoa(common.ExtractPort(listenAddr))}, ":") + } + + if isREST { + routes = []Route{ + {"", http.MethodGet, "/", getIndex}, + {"GetAllSettings", http.MethodGet, "/config/all", RESTGetAllSettings}, + {"SaveAllSettings", http.MethodPost, "/config/all/save", RESTSaveAllSettings}, + {"AllEnabledAccountInfo", http.MethodGet, "/exchanges/enabled/accounts/all", RESTGetAllEnabledAccountInfo}, + {"AllActiveExchangesAndCurrencies", http.MethodGet, "/exchanges/enabled/latest/all", RESTGetAllActiveTickers}, + {"GetPortfolio", http.MethodGet, "/portfolio/all", RESTGetPortfolio}, + {"AllActiveExchangesAndOrderbooks", http.MethodGet, "/exchanges/orderbook/latest/all", RESTGetAllActiveOrderbooks}, + } + + if Bot.Config.Profiler.Enabled { + log.Debugf("HTTP Go performance profiler (pprof) endpoint enabled: http://%s:%d/debug", common.ExtractHost(listenAddr), + common.ExtractPort(listenAddr)) + router.PathPrefix("/debug").Handler(http.DefaultServeMux) + } + } else { + routes = []Route{ + {"ws", http.MethodGet, "/ws", WebsocketClientHandler}, + } + } + + for _, route := range routes { + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(RESTLogger(route.HandlerFunc, route.Name)). + Host(listenAddr) + } + return router +} + +func getIndex(w http.ResponseWriter, _ *http.Request) { + fmt.Fprint(w, "GoCryptoTrader RESTful interface. For the web GUI, please visit the web GUI readme.") + w.WriteHeader(http.StatusOK) +} diff --git a/engine/restful_server.go b/engine/restful_server.go new file mode 100644 index 00000000..cb25f7bb --- /dev/null +++ b/engine/restful_server.go @@ -0,0 +1,129 @@ +package engine + +import ( + "encoding/json" + "net/http" + + "github.com/thrasher-/gocryptotrader/config" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// RESTfulJSONResponse outputs a JSON response of the response interface +func RESTfulJSONResponse(w http.ResponseWriter, response interface{}) error { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + return json.NewEncoder(w).Encode(response) +} + +// RESTfulError prints the REST method and error +func RESTfulError(method string, err error) { + log.Errorf("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, 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.Settings.ConfigFile, &responseData.Data) + if err != nil { + RESTfulError(r.Method, err) + } + + err = RESTfulJSONResponse(w, Bot.Config) + if err != nil { + RESTfulError(r.Method, err) + } + + SetupExchanges() +} + +// GetAllActiveOrderbooks returns all enabled exchanges orderbooks +func GetAllActiveOrderbooks() []EnabledExchangeOrderbooks { + var orderbookData []EnabledExchangeOrderbooks + + for _, exch := range Bot.Exchanges { + if !exch.IsEnabled() { + continue + } + + assets := exch.GetAssetTypes() + exchName := exch.GetName() + var exchangeOB EnabledExchangeOrderbooks + exchangeOB.ExchangeName = exchName + + for y := range assets { + currencies := exch.GetEnabledPairs(assets[y]) + for z := range currencies { + ob, err := exch.FetchOrderbook(currencies[z], assets[y]) + if err != nil { + log.Errorf("Exchange %s failed to retrieve %s orderbook. Err: %s", exchName, + currencies[z].String(), + err) + continue + } + exchangeOB.ExchangeValues = append(exchangeOB.ExchangeValues, ob) + } + orderbookData = append(orderbookData, exchangeOB) + } + orderbookData = append(orderbookData, exchangeOB) + } + 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, 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, result) + if err != nil { + RESTfulError(r.Method, err) + } +} + +// RESTGetAllActiveTickers returns all active tickers +func RESTGetAllActiveTickers(w http.ResponseWriter, r *http.Request) { + var response AllEnabledExchangeCurrencies + response.Data = GetAllActiveTickers() + + err := RESTfulJSONResponse(w, response) + if err != nil { + RESTfulError(r.Method, err) + } +} + +// RESTGetAllEnabledAccountInfo via get request returns JSON response of account +// info +func RESTGetAllEnabledAccountInfo(w http.ResponseWriter, r *http.Request) { + response := GetAllEnabledExchangeAccountInfo() + err := RESTfulJSONResponse(w, response) + if err != nil { + RESTfulError(r.Method, err) + } +} diff --git a/restful_server_test.go b/engine/restful_server_test.go similarity index 91% rename from restful_server_test.go rename to engine/restful_server_test.go index 387ec732..e09c9b67 100644 --- a/restful_server_test.go +++ b/engine/restful_server_test.go @@ -1,4 +1,4 @@ -package main +package engine import ( "encoding/json" @@ -6,7 +6,6 @@ import ( "net/http" "net/http/httptest" "reflect" - "strings" "testing" "github.com/thrasher-/gocryptotrader/config" @@ -14,7 +13,7 @@ import ( func loadConfig(t *testing.T) *config.Config { cfg := config.GetConfig() - err := cfg.LoadConfig(strings.Replace(config.ConfigTestFile, "..", ".", 1)) + err := cfg.LoadConfig("") if err != nil { t.Error("Test failed. GetCurrencyConfig LoadConfig error", err) } @@ -60,7 +59,7 @@ func TestInvalidHostRequest(t *testing.T) { req.Host = "invalidsite.com" resp := httptest.NewRecorder() - NewRouter().ServeHTTP(resp, req) + newRouter(true).ServeHTTP(resp, req) if status := resp.Code; status != http.StatusNotFound { t.Errorf("Test failed. Response returned wrong status code expected %v got %v", http.StatusNotFound, status) @@ -75,7 +74,7 @@ func TestValidHostRequest(t *testing.T) { req.Host = "localhost:9050" resp := httptest.NewRecorder() - NewRouter().ServeHTTP(resp, req) + newRouter(true).ServeHTTP(resp, req) if status := resp.Code; status != http.StatusOK { t.Errorf("Test failed. Response returned wrong status code expected %v got %v", http.StatusOK, status) diff --git a/engine/restful_types.go b/engine/restful_types.go new file mode 100644 index 00000000..99108633 --- /dev/null +++ b/engine/restful_types.go @@ -0,0 +1,46 @@ +package engine + +import ( + "net/http" + + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +// Route is a sub type that holds the request routes +type Route struct { + Name string + Method string + Pattern string + HandlerFunc http.HandlerFunc +} + +// 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"` +} diff --git a/routines.go b/engine/routines.go similarity index 67% rename from routines.go rename to engine/routines.go index 99b5ce93..4c1b4c76 100644 --- a/routines.go +++ b/engine/routines.go @@ -1,4 +1,4 @@ -package main +package engine import ( "errors" @@ -9,6 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -16,7 +17,7 @@ import ( ) func printCurrencyFormat(price float64) string { - displaySymbol, err := currency.GetSymbolByCurrencyName(bot.config.Currency.FiatDisplayCurrency) + displaySymbol, err := currency.GetSymbolByCurrencyName(Bot.Config.Currency.FiatDisplayCurrency) if err != nil { log.Errorf("Failed to get display symbol: %s", err) } @@ -25,7 +26,7 @@ func printCurrencyFormat(price float64) string { } func printConvertCurrencyFormat(origCurrency currency.Code, origPrice float64) string { - displayCurrency := bot.config.Currency.FiatDisplayCurrency + displayCurrency := Bot.Config.Currency.FiatDisplayCurrency conv, err := currency.ConvertCurrency(origPrice, origCurrency, displayCurrency) @@ -55,7 +56,7 @@ func printConvertCurrencyFormat(origCurrency currency.Code, origPrice float64) s ) } -func printTickerSummary(result *ticker.Price, p currency.Pair, assetType, exchangeName string, err error) { +func printTickerSummary(result *ticker.Price, p currency.Pair, assetType assets.AssetType, exchangeName string, err error) { if err != nil { log.Errorf("Failed to get %s %s ticker. Error: %s", p.String(), @@ -66,11 +67,11 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType, exchan stats.Add(exchangeName, p, assetType, result.Last, result.Volume) if p.Quote.IsFiatCurrency() && - p.Quote != bot.config.Currency.FiatDisplayCurrency { + p.Quote != Bot.Config.Currency.FiatDisplayCurrency { origCurrency := p.Quote.Upper() log.Infof("%s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f", exchangeName, - exchange.FormatCurrency(p).String(), + FormatCurrency(p).String(), assetType, printConvertCurrencyFormat(origCurrency, result.Last), printConvertCurrencyFormat(origCurrency, result.Ask), @@ -80,10 +81,10 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType, exchan result.Volume) } else { if p.Quote.IsFiatCurrency() && - p.Quote == bot.config.Currency.FiatDisplayCurrency { + p.Quote == Bot.Config.Currency.FiatDisplayCurrency { log.Infof("%s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f", exchangeName, - exchange.FormatCurrency(p).String(), + FormatCurrency(p).String(), assetType, printCurrencyFormat(result.Last), printCurrencyFormat(result.Ask), @@ -94,7 +95,7 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType, exchan } else { log.Infof("%s %s %s: TICKER: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f", exchangeName, - exchange.FormatCurrency(p).String(), + FormatCurrency(p).String(), assetType, result.Last, result.Ask, @@ -106,7 +107,7 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType, exchan } } -func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType, exchangeName string, err error) { +func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType assets.AssetType, exchangeName string, err error) { if err != nil { log.Errorf("Failed to get %s %s orderbook of type %s. Error: %s", p, @@ -120,11 +121,11 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType, e asksAmount, asksValue := result.TotalAsksAmount() if p.Quote.IsFiatCurrency() && - p.Quote != bot.config.Currency.FiatDisplayCurrency { + p.Quote != Bot.Config.Currency.FiatDisplayCurrency { origCurrency := p.Quote.Upper() log.Infof("%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(), + FormatCurrency(p).String(), assetType, len(result.Bids), bidsAmount, @@ -137,10 +138,10 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType, e ) } else { if p.Quote.IsFiatCurrency() && - p.Quote == bot.config.Currency.FiatDisplayCurrency { + p.Quote == Bot.Config.Currency.FiatDisplayCurrency { log.Infof("%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(), + FormatCurrency(p).String(), assetType, len(result.Bids), bidsAmount, @@ -154,7 +155,7 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType, e } else { log.Infof("%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(), + FormatCurrency(p).String(), assetType, len(result.Bids), bidsAmount, @@ -189,47 +190,44 @@ func TickerUpdaterRoutine() { log.Debugf("Starting ticker updater routine.") var wg sync.WaitGroup for { - wg.Add(len(bot.exchanges)) - for x := range bot.exchanges { + wg.Add(len(Bot.Exchanges)) + for x := range Bot.Exchanges { go func(x int, wg *sync.WaitGroup) { defer wg.Done() - if bot.exchanges[x] == nil { - return - } - exchangeName := bot.exchanges[x].GetName() - enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies() - supportsBatching := bot.exchanges[x].SupportsRESTTickerBatchUpdates() - assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName) - if err != nil { - log.Debugf("failed to get %s exchange asset types. Error: %s", - exchangeName, err) + + if Bot.Exchanges[x] == nil || !Bot.Exchanges[x].SupportsREST() { return } - processTicker := func(exch exchange.IBotExchange, update bool, c currency.Pair, assetType string) { + exchangeName := Bot.Exchanges[x].GetName() + supportsBatching := Bot.Exchanges[x].SupportsRESTTickerBatchUpdates() + assetTypes := Bot.Exchanges[x].GetAssetTypes() + + processTicker := func(exch exchange.IBotExchange, update bool, c currency.Pair, assetType assets.AssetType) { var result ticker.Price var err error if update { result, err = exch.UpdateTicker(c, assetType) } else { - result, err = exch.GetTickerPrice(c, assetType) + result, err = exch.FetchTicker(c, assetType) } printTickerSummary(&result, c, assetType, exchangeName, err) if err == nil { - bot.comms.StageTickerData(exchangeName, assetType, &result) - if bot.config.Webserver.Enabled { - relayWebsocketEvent(result, "ticker_update", assetType, exchangeName) + Bot.CommsRelayer.StageTickerData(exchangeName, assetType, &result) + if Bot.Config.RemoteControl.WebsocketRPC.Enabled { + relayWebsocketEvent(result, "ticker_update", assetType.String(), exchangeName) } } } for y := range assetTypes { + enabledCurrencies := Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) for z := range enabledCurrencies { if supportsBatching && z > 0 { - processTicker(bot.exchanges[x], false, enabledCurrencies[z], assetTypes[y]) + processTicker(Bot.Exchanges[x], false, enabledCurrencies[z], assetTypes[y]) continue } - processTicker(bot.exchanges[x], true, enabledCurrencies[z], assetTypes[y]) + processTicker(Bot.Exchanges[x], true, enabledCurrencies[z], assetTypes[y]) } } }(x, &wg) @@ -246,37 +244,33 @@ func OrderbookUpdaterRoutine() { log.Debugln("Starting orderbook updater routine.") var wg sync.WaitGroup for { - wg.Add(len(bot.exchanges)) - for x := range bot.exchanges { + wg.Add(len(Bot.Exchanges)) + for x := range Bot.Exchanges { go func(x int, wg *sync.WaitGroup) { defer wg.Done() - if bot.exchanges[x] == nil { - return - } - exchangeName := bot.exchanges[x].GetName() - enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies() - assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName) - if err != nil { - log.Errorf("failed to get %s exchange asset types. Error: %s", - exchangeName, err) + if Bot.Exchanges[x] == nil || !Bot.Exchanges[x].SupportsREST() { return } - processOrderbook := func(exch exchange.IBotExchange, c currency.Pair, assetType string) { + exchangeName := Bot.Exchanges[x].GetName() + assetTypes := Bot.Exchanges[x].GetAssetTypes() + + processOrderbook := func(exch exchange.IBotExchange, c currency.Pair, assetType assets.AssetType) { result, err := exch.UpdateOrderbook(c, assetType) printOrderbookSummary(&result, c, assetType, exchangeName, err) if err == nil { - bot.comms.StageOrderbookData(exchangeName, assetType, &result) - if bot.config.Webserver.Enabled { - relayWebsocketEvent(result, "orderbook_update", assetType, exchangeName) + Bot.CommsRelayer.StageOrderbookData(exchangeName, assetType, &result) + if Bot.Config.RemoteControl.WebsocketRPC.Enabled { + relayWebsocketEvent(result, "orderbook_update", assetType.String(), exchangeName) } } } for y := range assetTypes { + enabledCurrencies := Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) for z := range enabledCurrencies { - processOrderbook(bot.exchanges[x], enabledCurrencies[z], assetTypes[y]) + processOrderbook(Bot.Exchanges[x], enabledCurrencies[z], assetTypes[y]) } } }(x, &wg) @@ -288,34 +282,34 @@ func OrderbookUpdaterRoutine() { } // WebsocketRoutine Initial routine management system for websocket -func WebsocketRoutine(verbose bool) { - log.Debugln("Connecting exchange websocket services...") +func WebsocketRoutine() { + if Bot.Settings.Verbose { + log.Debugln("Connecting exchange websocket services...") + } - for i := range bot.exchanges { + for i := range Bot.Exchanges { go func(i int) { - if verbose { - log.Debugf("Establishing websocket connection for %s", - bot.exchanges[i].GetName()) - } - - ws, err := bot.exchanges[i].GetWebsocket() - if err != nil { - log.Debugf("Websocket not enabled for %s", - bot.exchanges[i].GetName()) - return - } - - // Data handler routine - go WebsocketDataHandler(ws, verbose) - - err = ws.Connect() - if err != nil { - switch err.Error() { - case exchange.WebsocketNotEnabled: - log.Warnf("%s - websocket disabled", bot.exchanges[i].GetName()) - default: - log.Error(err) + if Bot.Exchanges[i].SupportsWebsocket() { + if Bot.Settings.Verbose { + log.Debugf("Exchange %s websocket support: Yes Enabled: %v", Bot.Exchanges[i].GetName(), + common.IsEnabled(Bot.Exchanges[i].IsWebsocketEnabled())) } + + if Bot.Exchanges[i].IsWebsocketEnabled() { + ws, err := Bot.Exchanges[i].GetWebsocket() + if err != nil { + return + } + // Data handler routine + go WebsocketDataHandler(ws) + + err = ws.Connect() + if err != nil { + log.Println(err) + } + } + } else if Bot.Settings.Verbose { + log.Debugf("Exchange %s websocket support: No", Bot.Exchanges[i].GetName()) } }(i) } @@ -352,7 +346,7 @@ func Websocketshutdown(ws *exchange.Websocket) error { // streamDiversion is a diversion switch from websocket to REST or other // alternative feed -func streamDiversion(ws *exchange.Websocket, verbose bool) { +func streamDiversion(ws *exchange.Websocket) { wg.Add(1) defer wg.Done() @@ -362,12 +356,12 @@ func streamDiversion(ws *exchange.Websocket, verbose bool) { return case <-ws.Connected: - if verbose { + if Bot.Settings.Verbose { log.Debugf("exchange %s websocket feed connected", ws.GetName()) } case <-ws.Disconnected: - if verbose { + if Bot.Settings.Verbose { log.Debugf("exchange %s websocket feed disconnected, switching to REST functionality", ws.GetName()) } @@ -377,11 +371,11 @@ func streamDiversion(ws *exchange.Websocket, verbose bool) { // WebsocketDataHandler handles websocket data coming from a websocket feed // associated with an exchange -func WebsocketDataHandler(ws *exchange.Websocket, verbose bool) { +func WebsocketDataHandler(ws *exchange.Websocket) { wg.Add(1) defer wg.Done() - go streamDiversion(ws, verbose) + go streamDiversion(ws) for { select { @@ -393,7 +387,7 @@ func WebsocketDataHandler(ws *exchange.Websocket, verbose bool) { case string: switch d { case exchange.WebsocketNotEnabled: - if verbose { + if Bot.Settings.Verbose { log.Warnf("routines.go warning - exchange %s weboscket not enabled", ws.GetName()) } @@ -413,27 +407,39 @@ func WebsocketDataHandler(ws *exchange.Websocket, verbose bool) { case exchange.TradeData: // Trade Data - if verbose { - log.Infoln("Websocket trades Updated: ", d) - } + // if Bot.Settings.Verbose { + // log.Println("Websocket trades Updated: ", data.(exchange.TradeData)) + // } case exchange.TickerData: // Ticker data - if verbose { - log.Infoln("Websocket Ticker Updated: ", d) + // if Bot.Settings.Verbose { + // log.Println("Websocket Ticker Updated: ", data.(exchange.TickerData)) + // } + + tickerNew := ticker.Price{ + Pair: d.Pair, + LastUpdated: d.Timestamp, + Last: d.ClosePrice, + High: d.HighPrice, + Low: d.LowPrice, + Volume: d.Quantity, } + Bot.ExchangeCurrencyPairManager.update(ws.GetName(), d.Pair, d.AssetType, SyncItemTicker, nil) + ticker.ProcessTicker(ws.GetName(), &tickerNew, d.AssetType) + printTickerSummary(&tickerNew, tickerNew.Pair, d.AssetType, ws.GetName(), nil) case exchange.KlineData: // Kline data - if verbose { + if Bot.Settings.Verbose { log.Infoln("Websocket Kline Updated: ", d) } case exchange.WebsocketOrderbookUpdate: // Orderbook data - if verbose { - log.Infoln("Websocket Orderbook Updated:", d) - } + result := data.(exchange.WebsocketOrderbookUpdate) + Bot.ExchangeCurrencyPairManager.update(ws.GetName(), result.Pair, result.Asset, SyncItemOrderbook, nil) + //nolint:gocritic log.Infof("Websocket %s %s orderbook updated", ws.GetName(), result.Pair.Pair().String()) default: - if verbose { + if Bot.Settings.Verbose { log.Warnf("Websocket Unknown type: %s", d) } } diff --git a/engine/rpcserver.go b/engine/rpcserver.go new file mode 100644 index 00000000..216bf673 --- /dev/null +++ b/engine/rpcserver.go @@ -0,0 +1,678 @@ +package engine + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "path/filepath" + "time" + + grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" + grpcruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/crypto" + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/engine/events" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/gctrpc" + "github.com/thrasher-/gocryptotrader/gctrpc/auth" + log "github.com/thrasher-/gocryptotrader/logger" + "github.com/thrasher-/gocryptotrader/portfolio" + "github.com/thrasher-/gocryptotrader/utils" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" +) + +// RPCServer struct +type RPCServer struct{} + +func authenticateClient(ctx context.Context) (context.Context, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return ctx, fmt.Errorf("unable to extract metadata") + } + + authStr, ok := md["authorization"] + if !ok { + return ctx, fmt.Errorf("authorization header missing") + } + + if !common.StringContains(authStr[0], "Basic") { + return ctx, fmt.Errorf("basic not found in authorization header") + } + + decoded, err := crypto.Base64Decode(common.SplitStrings(authStr[0], " ")[1]) + if err != nil { + return ctx, fmt.Errorf("unable to base64 decode authorization header") + } + + username := common.SplitStrings(string(decoded), ":")[0] + password := common.SplitStrings(string(decoded), ":")[1] + + if username != Bot.Config.RemoteControl.Username || password != Bot.Config.RemoteControl.Password { + return ctx, fmt.Errorf("username/password mismatch") + } + + return ctx, nil +} + +// StartRPCServer starts a gRPC server with TLS auth +func StartRPCServer() { + err := checkCerts() + if err != nil { + log.Errorf("gRPC checkCerts failed. err: %s", err) + return + } + + log.Debugf("gRPC server support enabled. Starting gRPC server on https://%v.", Bot.Config.RemoteControl.GRPC.ListenAddress) + lis, err := net.Listen("tcp", Bot.Config.RemoteControl.GRPC.ListenAddress) + if err != nil { + log.Errorf("gRPC server failed to bind to port: %s", err) + return + } + + targetDir := utils.GetTLSDir(Bot.Settings.DataDir) + creds, err := credentials.NewServerTLSFromFile(filepath.Join(targetDir, "cert.pem"), filepath.Join(targetDir, "key.pem")) + if err != nil { + log.Errorf("gRPC server could not load TLS keys: %s", err) + return + } + + opts := []grpc.ServerOption{ + grpc.Creds(creds), + grpc.UnaryInterceptor(grpcauth.UnaryServerInterceptor(authenticateClient)), + } + server := grpc.NewServer(opts...) + s := RPCServer{} + gctrpc.RegisterGoCryptoTraderServer(server, &s) + + go func() { + if err := server.Serve(lis); err != nil { + log.Errorf("gRPC server failed to serve: %s", err) + return + } + }() + + log.Debugf("gRPC server started!") + + if Bot.Settings.EnableGRPCProxy { + StartRPCRESTProxy() + } +} + +// StartRPCRESTProxy starts a gRPC proxy +func StartRPCRESTProxy() { + log.Debugf("gRPC proxy server support enabled. Starting gRPC proxy server on http://%v.", Bot.Config.RemoteControl.GRPC.GRPCProxyListenAddress) + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + targetDir := utils.GetTLSDir(Bot.Settings.DataDir) + creds, err := credentials.NewClientTLSFromFile(filepath.Join(targetDir, "cert.pem"), "") + if err != nil { + log.Errorf("Unabled to start gRPC proxy. Err: %s", err) + return + } + + mux := grpcruntime.NewServeMux() + opts := []grpc.DialOption{grpc.WithTransportCredentials(creds), + grpc.WithPerRPCCredentials(auth.BasicAuth{ + Username: Bot.Config.RemoteControl.Username, + Password: Bot.Config.RemoteControl.Password, + }), + } + err = gctrpc.RegisterGoCryptoTraderHandlerFromEndpoint(ctx, mux, Bot.Config.RemoteControl.GRPC.ListenAddress, opts) + if err != nil { + log.Errorf("Failed to register gRPC proxy. Err: %s", err) + } + + go func() { + if err := http.ListenAndServe(Bot.Config.RemoteControl.GRPC.GRPCProxyListenAddress, mux); err != nil { + log.Errorf("gRPC proxy failed to server: %s", err) + return + } + }() + + log.Debugf("gRPC proxy server started!") + select {} + +} + +// GetInfo returns info about the current GoCryptoTrader session +func (s *RPCServer) GetInfo(ctx context.Context, r *gctrpc.GetInfoRequest) (*gctrpc.GetInfoResponse, error) { + d := time.Since(Bot.Uptime) + resp := gctrpc.GetInfoResponse{ + Uptime: d.String(), + EnabledExchanges: int64(Bot.Config.CountEnabledExchanges()), + AvailableExchanges: int64(len(Bot.Config.Exchanges)), + DefaultFiatCurrency: Bot.Config.Currency.FiatDisplayCurrency.String(), + DefaultForexProvider: Bot.Config.GetPrimaryForexProvider(), + } + + return &resp, nil +} + +// GetExchanges returns a list of exchanges +// Param is whether or not you wish to list enabled exchanges +func (s *RPCServer) GetExchanges(ctx context.Context, r *gctrpc.GetExchangesRequest) (*gctrpc.GetExchangesResponse, error) { + exchanges := common.JoinStrings(GetExchanges(r.Enabled), ",") + return &gctrpc.GetExchangesResponse{Exchanges: exchanges}, nil +} + +// DisableExchange disables an exchange +func (s *RPCServer) DisableExchange(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GenericExchangeNameResponse, error) { + err := UnloadExchange(r.Exchange) + return &gctrpc.GenericExchangeNameResponse{}, err +} + +// EnableExchange enables an exchange +func (s *RPCServer) EnableExchange(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GenericExchangeNameResponse, error) { + err := LoadExchange(r.Exchange, false, nil) + return &gctrpc.GenericExchangeNameResponse{}, err +} + +// GetExchangeInfo gets info for a specific exchange +func (s *RPCServer) GetExchangeInfo(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeInfoResponse, error) { + exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) + if err != nil { + return nil, err + } + + return &gctrpc.GetExchangeInfoResponse{ + Name: exchCfg.Name, + Enabled: exchCfg.Enabled, + Verbose: exchCfg.Verbose, + UsingSandbox: exchCfg.UseSandbox, + HttpTimeout: exchCfg.HTTPTimeout.String(), + HttpUseragent: exchCfg.HTTPUserAgent, + HttpProxy: exchCfg.ProxyAddress, + BaseCurrencies: common.JoinStrings(exchCfg.BaseCurrencies.Strings(), ","), + SupportedAssets: exchCfg.CurrencyPairs.AssetTypes.JoinToString(","), + + // TO-DO fix pairs + //EnabledPairs: common.JoinStrings( + // exchCfg.CurrencyPairs.Pairs.GetPairs().Enabled.Strings(), ","), + //AvailablePairs: common.JoinStrings( + // exchCfg.CurrencyPairs.Spot.Available.Strings(), ","), + }, nil +} + +// GetTicker returns the ticker for a specified exchange, currency pair and +// asset type +func (s *RPCServer) GetTicker(ctx context.Context, r *gctrpc.GetTickerRequest) (*gctrpc.TickerResponse, error) { + t, err := GetSpecificTicker( + currency.Pair{ + Delimiter: r.Pair.Delimiter, + Base: currency.NewCode(r.Pair.Base), + Quote: currency.NewCode(r.Pair.Quote), + }, + r.Exchange, + assets.AssetType(r.AssetType), + ) + if err != nil { + return nil, err + } + + resp := &gctrpc.TickerResponse{ + Pair: r.Pair, + LastUpdated: t.LastUpdated.Unix(), + Last: t.Last, + High: t.High, + Low: t.Low, + Bid: t.Bid, + Ask: t.Ask, + Volume: t.Volume, + PriceAth: t.PriceATH, + } + + return resp, nil +} + +// GetTickers returns a list of tickers for all enabled exchanges and all +// enabled currency pairs +func (s *RPCServer) GetTickers(ctx context.Context, r *gctrpc.GetTickersRequest) (*gctrpc.GetTickersResponse, error) { + activeTickers := GetAllActiveTickers() + var tickers []*gctrpc.Tickers + + for x := range activeTickers { + var ticker gctrpc.Tickers + ticker.Exchange = activeTickers[x].ExchangeName + for y := range activeTickers[x].ExchangeValues { + t := activeTickers[x].ExchangeValues[y] + ticker.Tickers = append(ticker.Tickers, &gctrpc.TickerResponse{ + Pair: &gctrpc.CurrencyPair{ + Delimiter: t.Pair.Delimiter, + Base: t.Pair.Base.String(), + Quote: t.Pair.Quote.String(), + }, + LastUpdated: t.LastUpdated.Unix(), + Last: t.Last, + High: t.High, + Low: t.Low, + Bid: t.Bid, + Ask: t.Ask, + Volume: t.Volume, + PriceAth: t.PriceATH, + }) + } + tickers = append(tickers, &ticker) + } + + return &gctrpc.GetTickersResponse{Tickers: tickers}, nil +} + +// GetOrderbook returns an orderbook for a specific exchange, currency pair +// and asset type +func (s *RPCServer) GetOrderbook(ctx context.Context, r *gctrpc.GetOrderbookRequest) (*gctrpc.OrderbookResponse, error) { + ob, err := GetSpecificOrderbook( + currency.Pair{ + Delimiter: r.Pair.Delimiter, + Base: currency.NewCode(r.Pair.Base), + Quote: currency.NewCode(r.Pair.Quote), + }, + r.Exchange, + assets.AssetType(r.AssetType), + ) + if err != nil { + return nil, err + } + + var bids []*gctrpc.OrderbookItem + for x := range ob.Bids { + bids = append(bids, &gctrpc.OrderbookItem{ + Amount: ob.Bids[x].Amount, + Price: ob.Bids[x].Price, + }) + } + + var asks []*gctrpc.OrderbookItem + for x := range ob.Asks { + asks = append(asks, &gctrpc.OrderbookItem{ + Amount: ob.Asks[x].Amount, + Price: ob.Asks[x].Price, + }) + } + + resp := &gctrpc.OrderbookResponse{ + Pair: r.Pair, + Bids: bids, + Asks: asks, + LastUpdated: ob.LastUpdated.Unix(), + AssetType: r.AssetType, + } + + return resp, nil +} + +// GetOrderbooks returns a list of orderbooks for all enabled exchanges and all +// enabled currency pairs +func (s *RPCServer) GetOrderbooks(ctx context.Context, r *gctrpc.GetOrderbooksRequest) (*gctrpc.GetOrderbooksResponse, error) { + activeOrderbooks := GetAllActiveOrderbooks() + var orderbooks []*gctrpc.Orderbooks + + for x := range activeOrderbooks { + var ob gctrpc.Orderbooks + ob.Exchange = activeOrderbooks[x].ExchangeName + for y := range activeOrderbooks[x].ExchangeValues { + o := activeOrderbooks[x].ExchangeValues[y] + var bids []*gctrpc.OrderbookItem + for z := range o.Bids { + bids = append(bids, &gctrpc.OrderbookItem{ + Amount: o.Bids[z].Amount, + Price: o.Bids[z].Price, + }) + } + + var asks []*gctrpc.OrderbookItem + for z := range o.Asks { + asks = append(asks, &gctrpc.OrderbookItem{ + Amount: o.Asks[z].Amount, + Price: o.Asks[z].Price, + }) + } + + ob.Orderbooks = append(ob.Orderbooks, &gctrpc.OrderbookResponse{ + Pair: &gctrpc.CurrencyPair{ + Delimiter: o.Pair.Delimiter, + Base: o.Pair.Base.String(), + Quote: o.Pair.Quote.String(), + }, + LastUpdated: o.LastUpdated.Unix(), + Bids: bids, + Asks: asks, + }) + } + orderbooks = append(orderbooks, &ob) + } + + return &gctrpc.GetOrderbooksResponse{Orderbooks: orderbooks}, nil +} + +// GetAccountInfo returns an account balance for a specific exchange +func (s *RPCServer) GetAccountInfo(ctx context.Context, r *gctrpc.GetAccountInfoRequest) (*gctrpc.GetAccountInfoResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errors.New("exchange is not loaded/doesn't exist") + } + + resp, err := exch.GetAccountInfo() + if err != nil { + return nil, err + } + + var accounts []*gctrpc.Account + for x := range resp.Accounts { + var a gctrpc.Account + a.Id = resp.Accounts[x].ID + for _, y := range resp.Accounts[x].Currencies { + a.Currencies = append(a.Currencies, &gctrpc.AccountCurrencyInfo{ + Currency: y.CurrencyName.String(), + Hold: y.Hold, + TotalValue: y.TotalValue, + }) + } + accounts = append(accounts, &a) + } + + return &gctrpc.GetAccountInfoResponse{Exchange: r.Exchange, Accounts: accounts}, nil +} + +// GetConfig returns the bots config +func (s *RPCServer) GetConfig(ctx context.Context, r *gctrpc.GetConfigRequest) (*gctrpc.GetConfigResponse, error) { + return &gctrpc.GetConfigResponse{}, common.ErrNotYetImplemented +} + +// GetPortfolio returns the portfolio details +func (s *RPCServer) GetPortfolio(ctx context.Context, r *gctrpc.GetPortfolioRequest) (*gctrpc.GetPortfolioResponse, error) { + var addrs []*gctrpc.PortfolioAddress + botAddrs := Bot.Portfolio.Addresses + + for x := range botAddrs { + addrs = append(addrs, &gctrpc.PortfolioAddress{ + Address: botAddrs[x].Address, + CoinType: botAddrs[x].CoinType.String(), + Description: botAddrs[x].Description, + Balance: botAddrs[x].Balance, + }) + } + + resp := &gctrpc.GetPortfolioResponse{ + Portfolio: addrs, + } + + return resp, nil +} + +// GetPortfolioSummary returns the portfolio summary +func (s *RPCServer) GetPortfolioSummary(ctx context.Context, r *gctrpc.GetPortfolioSummaryRequest) (*gctrpc.GetPortfolioSummaryResponse, error) { + result := Bot.Portfolio.GetPortfolioSummary() + var resp gctrpc.GetPortfolioSummaryResponse + + p := func(coins []portfolio.Coin) []*gctrpc.Coin { + var c []*gctrpc.Coin + for x := range coins { + c = append(c, + &gctrpc.Coin{ + Coin: coins[x].Coin.String(), + Balance: coins[x].Balance, + Address: coins[x].Address, + Percentage: coins[x].Percentage, + }, + ) + } + return c + } + + resp.CoinTotals = p(result.Totals) + resp.CoinsOffline = p(result.Offline) + resp.CoinsOfflineSummary = make(map[string]*gctrpc.OfflineCoins) + for k, v := range result.OfflineSummary { + var o []*gctrpc.OfflineCoinSummary + for x := range v { + o = append(o, + &gctrpc.OfflineCoinSummary{ + Address: v[x].Address, + Balance: v[x].Balance, + Percentage: v[x].Percentage, + }, + ) + } + resp.CoinsOfflineSummary[k.String()] = &gctrpc.OfflineCoins{ + Addresses: o, + } + } + resp.CoinsOnline = p(result.Online) + resp.CoinsOnlineSummary = make(map[string]*gctrpc.OnlineCoins) + for k, v := range result.OnlineSummary { + o := make(map[string]*gctrpc.OnlineCoinSummary) + for x, y := range v { + o[x.String()] = &gctrpc.OnlineCoinSummary{ + Balance: y.Balance, + Percentage: y.Percentage, + } + } + resp.CoinsOnlineSummary[k] = &gctrpc.OnlineCoins{ + Coins: o, + } + } + + return &resp, nil +} + +// AddPortfolioAddress adds an address to the portfolio manager +func (s *RPCServer) AddPortfolioAddress(ctx context.Context, r *gctrpc.AddPortfolioAddressRequest) (*gctrpc.AddPortfolioAddressResponse, error) { + Bot.Portfolio.AddAddress(r.Address, r.Description, currency.NewCode(r.CoinType), r.Balance) + return &gctrpc.AddPortfolioAddressResponse{}, nil +} + +// RemovePortfolioAddress removes an address from the portfolio manager +func (s *RPCServer) RemovePortfolioAddress(ctx context.Context, r *gctrpc.RemovePortfolioAddressRequest) (*gctrpc.RemovePortfolioAddressResponse, error) { + Bot.Portfolio.RemoveAddress(r.Address, r.Description, currency.NewCode(r.CoinType)) + return &gctrpc.RemovePortfolioAddressResponse{}, nil +} + +// GetForexProviders returns a list of available forex providers +func (s *RPCServer) GetForexProviders(ctx context.Context, r *gctrpc.GetForexProvidersRequest) (*gctrpc.GetForexProvidersResponse, error) { + providers := Bot.Config.GetForexProvidersConfig() + if len(providers) == 0 { + return nil, fmt.Errorf("forex providers is empty") + } + + var forexProviders []*gctrpc.ForexProvider + for x := range providers { + forexProviders = append(forexProviders, &gctrpc.ForexProvider{ + Name: providers[x].Name, + Enabled: providers[x].Enabled, + Verbose: providers[x].Verbose, + RestRollingDelay: providers[x].RESTPollingDelay.String(), + ApiKey: providers[x].APIKey, + ApiKeyLevel: int64(providers[x].APIKeyLvl), + PrimaryProvider: providers[x].PrimaryProvider, + }) + } + return &gctrpc.GetForexProvidersResponse{ForexProviders: forexProviders}, nil +} + +// GetForexRates returns a list of forex rates +func (s *RPCServer) GetForexRates(ctx context.Context, r *gctrpc.GetForexRatesRequest) (*gctrpc.GetForexRatesResponse, error) { + rates, err := currency.GetExchangeRates() + if err != nil { + return nil, err + } + + if len(rates) == 0 { + return nil, fmt.Errorf("forex rates is empty") + } + + var forexRates []*gctrpc.ForexRatesConversion + for x := range rates { + rate, err := rates[x].GetRate() + if err != nil { + continue + } + + // TODO + // inverseRate, err := rates[x].GetInversionRate() + // if err != nil { + // continue + // } + + forexRates = append(forexRates, &gctrpc.ForexRatesConversion{ + From: rates[x].From.String(), + To: rates[x].To.String(), + Rate: rate, + InverseRate: 0, + }) + } + return &gctrpc.GetForexRatesResponse{ForexRates: forexRates}, nil +} + +// GetOrders returns all open orders, filtered by exchange, currency pair or +// asset type +func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) (*gctrpc.GetOrdersResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + log.Debugln(exch) + return nil, errors.New("exchange is not loaded/doesn't exist") + } + + resp, err := exch.GetActiveOrders(&exchange.GetOrdersRequest{}) + if err != nil { + return nil, err + } + + var orders []*gctrpc.OrderDetails + for x := range resp { + orders = append(orders, &gctrpc.OrderDetails{ + Exchange: r.Exchange, + Id: resp[x].ID, + BaseCurrency: resp[x].CurrencyPair.Base.String(), + QuoteCurrency: resp[x].CurrencyPair.Quote.String(), + AssetType: assets.AssetTypeSpot.String(), + OrderType: resp[x].OrderType.ToString(), + OrderSide: resp[x].OrderSide.ToString(), + CreationTime: resp[x].OrderDate.Unix(), + Status: resp[x].Status, + Price: resp[x].Price, + Amount: resp[x].Amount, + }) + } + + return &gctrpc.GetOrdersResponse{Orders: orders}, nil +} + +// GetOrder returns order information based on exchange and order ID +func (s *RPCServer) GetOrder(ctx context.Context, r *gctrpc.GetOrderRequest) (*gctrpc.OrderDetails, error) { + return &gctrpc.OrderDetails{}, common.ErrNotYetImplemented +} + +// SubmitOrder submits an order specified by exchange, currency pair and asset +// type +func (s *RPCServer) SubmitOrder(ctx context.Context, r *gctrpc.SubmitOrderRequest) (*gctrpc.SubmitOrderResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errors.New("exchange is not loaded/doesn't exist") + } + + p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + result, err := exch.SubmitOrder(p, exchange.OrderSide(r.Side), + exchange.OrderType(r.OrderType), r.Amount, r.Price, r.ClientId) + + return &gctrpc.SubmitOrderResponse{ + OrderId: result.OrderID, + OrderPlaced: result.IsOrderPlaced, + }, err +} + +// CancelOrder cancels an order specified by exchange, currency pair and asset +// type +func (s *RPCServer) CancelOrder(ctx context.Context, r *gctrpc.CancelOrderRequest) (*gctrpc.CancelOrderResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errors.New("exchange is not loaded/doesn't exist") + } + + err := exch.CancelOrder(&exchange.OrderCancellation{ + AccountID: r.AccountId, + OrderID: r.OrderId, + Side: exchange.OrderSide(r.Side), + WalletAddress: r.WalletAddress, + }) + + return &gctrpc.CancelOrderResponse{}, err +} + +// CancelAllOrders cancels all orders, filterable by exchange +func (s *RPCServer) CancelAllOrders(ctx context.Context, r *gctrpc.CancelAllOrdersRequest) (*gctrpc.CancelAllOrdersResponse, error) { + return &gctrpc.CancelAllOrdersResponse{}, common.ErrNotYetImplemented +} + +// GetEvents returns the stored events list +func (s *RPCServer) GetEvents(ctx context.Context, r *gctrpc.GetEventsRequest) (*gctrpc.GetEventsResponse, error) { + return &gctrpc.GetEventsResponse{}, common.ErrNotYetImplemented +} + +// AddEvent adds an event +func (s *RPCServer) AddEvent(ctx context.Context, r *gctrpc.AddEventRequest) (*gctrpc.AddEventResponse, error) { + evtCondition := events.ConditionParams{ + CheckBids: r.ConditionParams.CheckBids, + CheckBidsAndAsks: r.ConditionParams.CheckBidsAndAsks, + Condition: r.ConditionParams.Condition, + OrderbookAmount: r.ConditionParams.OrderbookAmount, + Price: r.ConditionParams.Price, + } + + p := currency.NewPairWithDelimiter(r.Pair.Base, + r.Pair.Quote, r.Pair.Delimiter) + + id, err := events.Add(r.Exchange, r.Item, evtCondition, p, assets.AssetType(r.AssetType), r.Action) + if err != nil { + return nil, err + } + + return &gctrpc.AddEventResponse{Id: id}, nil +} + +// RemoveEvent removes an event, specified by an event ID +func (s *RPCServer) RemoveEvent(ctx context.Context, r *gctrpc.RemoveEventRequest) (*gctrpc.RemoveEventResponse, error) { + events.Remove(r.Id) + return &gctrpc.RemoveEventResponse{}, nil +} + +// GetCryptocurrencyDepositAddresses returns a list of cryptocurrency deposit +// addresses specified by an exchange +func (s *RPCServer) GetCryptocurrencyDepositAddresses(ctx context.Context, r *gctrpc.GetCryptocurrencyDepositAddressesRequest) (*gctrpc.GetCryptocurrencyDepositAddressesResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errors.New("exchange is not loaded/doesn't exist") + } + + return &gctrpc.GetCryptocurrencyDepositAddressesResponse{}, common.ErrNotYetImplemented +} + +// GetCryptocurrencyDepositAddress returns a cryptocurrency deposit address +// specified by exchange and cryptocurrency +func (s *RPCServer) GetCryptocurrencyDepositAddress(ctx context.Context, r *gctrpc.GetCryptocurrencyDepositAddressRequest) (*gctrpc.GetCryptocurrencyDepositAddressResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errors.New("exchange is not loaded/doesn't exist") + } + + addr, err := exch.GetDepositAddress(currency.NewCode(r.Cryptocurrency), "") + return &gctrpc.GetCryptocurrencyDepositAddressResponse{Address: addr}, err +} + +// WithdrawCryptocurrencyFunds withdraws cryptocurrency funds specified by +// exchange +func (s *RPCServer) WithdrawCryptocurrencyFunds(ctx context.Context, r *gctrpc.WithdrawCurrencyRequest) (*gctrpc.WithdrawResponse, error) { + return &gctrpc.WithdrawResponse{}, common.ErrNotYetImplemented +} + +// WithdrawFiatFunds withdraws fiat funds specified by exchange +func (s *RPCServer) WithdrawFiatFunds(ctx context.Context, r *gctrpc.WithdrawCurrencyRequest) (*gctrpc.WithdrawResponse, error) { + return &gctrpc.WithdrawResponse{}, common.ErrNotYetImplemented +} diff --git a/engine/syncer.go b/engine/syncer.go new file mode 100644 index 00000000..30178d94 --- /dev/null +++ b/engine/syncer.go @@ -0,0 +1,577 @@ +package engine + +import ( + "errors" + "sync/atomic" + "time" + + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// const holds the sync item types +const ( + SyncItemTicker = iota + SyncItemOrderbook + SyncItemTrade + + defaultSyncerWorkers = 30 + defaultSyncerTimeout = time.Second * 15 +) + +var ( + createdCounter = 0 + removedCounter = 0 +) + +// NewCurrencyPairSyncer starts a new CurrencyPairSyncer +func NewCurrencyPairSyncer(c CurrencyPairSyncerConfig) (*ExchangeCurrencyPairSyncer, error) { + if !c.SyncOrderbook && !c.SyncTicker && !c.SyncTrades { + return nil, errors.New("no sync items enabled") + } + + if c.NumWorkers <= 0 { + c.NumWorkers = defaultSyncerWorkers + } + + s := ExchangeCurrencyPairSyncer{ + Cfg: CurrencyPairSyncerConfig{ + SyncTicker: c.SyncTicker, + SyncOrderbook: c.SyncOrderbook, + SyncTrades: c.SyncTrades, + SyncContinuously: c.SyncContinuously, + NumWorkers: c.NumWorkers, + }, + } + + s.tickerBatchLastRequested = make(map[string]time.Time) + + log.Debugf("Exchange currency pair syncer config:") + log.Debugf("SyncContinuously: %v", s.Cfg.SyncContinuously) + log.Debugf("SyncTicker: %v", s.Cfg.SyncTicker) + log.Debugf("SyncOrderbook: %v", s.Cfg.SyncOrderbook) + log.Debugf("SyncTrades: %v", s.Cfg.SyncTrades) + log.Debugf("NumWorkers: %v", s.Cfg.NumWorkers) + + return &s, nil +} + +func (e *ExchangeCurrencyPairSyncer) get(exchangeName string, p currency.Pair, a assets.AssetType) (*CurrencyPairSyncAgent, error) { + e.mux.Lock() + defer e.mux.Unlock() + + for x := range e.CurrencyPairs { + if e.CurrencyPairs[x].Exchange == exchangeName && + e.CurrencyPairs[x].Pair.Equal(p) && + e.CurrencyPairs[x].AssetType == a { + return &e.CurrencyPairs[x], nil + } + } + + return nil, errors.New("exchange currency pair syncer not found") +} + +func (e *ExchangeCurrencyPairSyncer) exists(exchangeName string, p currency.Pair, a assets.AssetType) bool { + e.mux.Lock() + defer e.mux.Unlock() + + for x := range e.CurrencyPairs { + if e.CurrencyPairs[x].Exchange == exchangeName && + e.CurrencyPairs[x].Pair.Equal(p) && + e.CurrencyPairs[x].AssetType == a { + return true + } + } + return false +} + +func (e *ExchangeCurrencyPairSyncer) add(c *CurrencyPairSyncAgent) { + e.mux.Lock() + defer e.mux.Unlock() + + if e.Cfg.SyncTicker { + log.Debugf("%s: Added ticker sync item %v: using websocket: %v using REST: %v", c.Exchange, c.Pair.String(), + c.Ticker.IsUsingWebsocket, c.Ticker.IsUsingREST) + if atomic.LoadInt32(&e.initSyncCompleted) != 1 { + e.initSyncWG.Add(1) + createdCounter++ + } + } + + if e.Cfg.SyncOrderbook { + log.Debugf("%s: Added orderbook sync item %v: using websocket: %v using REST: %v", c.Exchange, c.Pair.String(), + c.Orderbook.IsUsingWebsocket, c.Orderbook.IsUsingREST) + if atomic.LoadInt32(&e.initSyncCompleted) != 1 { + e.initSyncWG.Add(1) + createdCounter++ + } + } + + if e.Cfg.SyncTrades { + log.Debugf("%s: Added trade sync item %v: using websocket: %v using REST: %v", c.Exchange, c.Pair.String(), + c.Trade.IsUsingWebsocket, c.Trade.IsUsingREST) + if atomic.LoadInt32(&e.initSyncCompleted) != 1 { + e.initSyncWG.Add(1) + createdCounter++ + } + } + + c.Created = time.Now() + e.CurrencyPairs = append(e.CurrencyPairs, *c) +} + +func (e *ExchangeCurrencyPairSyncer) remove(c *CurrencyPairSyncAgent) { + e.mux.Lock() + defer e.mux.Unlock() + + for x := range e.CurrencyPairs { + if e.CurrencyPairs[x].Exchange == c.Exchange && + e.CurrencyPairs[x].Pair.Equal(c.Pair) && + e.CurrencyPairs[x].AssetType == c.AssetType { + e.CurrencyPairs = append(e.CurrencyPairs[:x], e.CurrencyPairs[x+1:]...) + return + } + } +} + +func (e *ExchangeCurrencyPairSyncer) isProcessing(exchangeName string, p currency.Pair, a assets.AssetType, syncType int) bool { + e.mux.Lock() + defer e.mux.Unlock() + + for x := range e.CurrencyPairs { + if e.CurrencyPairs[x].Exchange == exchangeName && + e.CurrencyPairs[x].Pair.Equal(p) && + e.CurrencyPairs[x].AssetType == a { + switch syncType { + case SyncItemTicker: + return e.CurrencyPairs[x].Ticker.IsProcessing + case SyncItemOrderbook: + return e.CurrencyPairs[x].Orderbook.IsProcessing + case SyncItemTrade: + return e.CurrencyPairs[x].Trade.IsProcessing + } + } + } + + return false +} + +func (e *ExchangeCurrencyPairSyncer) setProcessing(exchangeName string, p currency.Pair, a assets.AssetType, syncType int, processing bool) { + e.mux.Lock() + defer e.mux.Unlock() + + for x := range e.CurrencyPairs { + if e.CurrencyPairs[x].Exchange == exchangeName && + e.CurrencyPairs[x].Pair.Equal(p) && + e.CurrencyPairs[x].AssetType == a { + switch syncType { + case SyncItemTicker: + e.CurrencyPairs[x].Ticker.IsProcessing = processing + case SyncItemOrderbook: + e.CurrencyPairs[x].Orderbook.IsProcessing = processing + case SyncItemTrade: + e.CurrencyPairs[x].Trade.IsProcessing = processing + } + } + } +} + +func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair, a assets.AssetType, syncType int, err error) { + if atomic.LoadInt32(&e.initSyncStarted) != 1 { + return + } + + switch syncType { + case SyncItemOrderbook, SyncItemTrade, SyncItemTicker: + if !e.Cfg.SyncOrderbook && syncType == SyncItemOrderbook { + return + } + + if !e.Cfg.SyncTicker && syncType == SyncItemTicker { + return + } + + if !e.Cfg.SyncTrades && syncType == SyncItemTrade { + return + } + default: + log.Warnf("ExchangeCurrencyPairSyncer: unkown sync item %v", syncType) + return + } + + e.mux.Lock() + defer e.mux.Unlock() + + for x := range e.CurrencyPairs { + if e.CurrencyPairs[x].Exchange == exchangeName && + e.CurrencyPairs[x].Pair.Equal(p) && + e.CurrencyPairs[x].AssetType == a { + switch syncType { + case SyncItemTicker: + origHadData := e.CurrencyPairs[x].Ticker.HaveData + e.CurrencyPairs[x].Ticker.LastUpdated = time.Now() + if err != nil { + e.CurrencyPairs[x].Ticker.NumErrors++ + } + e.CurrencyPairs[x].Ticker.HaveData = true + e.CurrencyPairs[x].Ticker.IsProcessing = false + if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { + log.Debugf("%s ticker sync complete %v [%d/%d].", exchangeName, p, removedCounter, createdCounter) + removedCounter++ + e.initSyncWG.Done() + } + + case SyncItemOrderbook: + origHadData := e.CurrencyPairs[x].Orderbook.HaveData + e.CurrencyPairs[x].Orderbook.LastUpdated = time.Now() + if err != nil { + e.CurrencyPairs[x].Orderbook.NumErrors++ + } + e.CurrencyPairs[x].Orderbook.HaveData = true + e.CurrencyPairs[x].Orderbook.IsProcessing = false + if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { + log.Debugf("%s orderbook sync complete %v [%d/%d].", exchangeName, p, removedCounter, createdCounter) + removedCounter++ + e.initSyncWG.Done() + } + + case SyncItemTrade: + origHadData := e.CurrencyPairs[x].Trade.HaveData + e.CurrencyPairs[x].Trade.LastUpdated = time.Now() + if err != nil { + e.CurrencyPairs[x].Trade.NumErrors++ + } + e.CurrencyPairs[x].Trade.HaveData = true + e.CurrencyPairs[x].Trade.IsProcessing = false + if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { + log.Debugf("%s trade sync complete %v [%d/%d].", exchangeName, p, removedCounter, createdCounter) + removedCounter++ + e.initSyncWG.Done() + } + } + } + } +} + +func (e *ExchangeCurrencyPairSyncer) worker() { + cleanup := func() { + log.Debugf("Exchange CurrencyPairSyncer worker shutting down.") + } + defer cleanup() + + for atomic.LoadInt32(&e.shutdown) != 1 { + for x := range Bot.Exchanges { + if !Bot.Exchanges[x].IsEnabled() { + continue + } + + if Bot.Exchanges[x].GetName() == "BTCC" { + continue + } + + exchangeName := Bot.Exchanges[x].GetName() + assetTypes := Bot.Exchanges[x].GetAssetTypes() + supportsREST := Bot.Exchanges[x].SupportsREST() + supportsRESTTickerBatching := Bot.Exchanges[x].SupportsRESTTickerBatchUpdates() + var usingREST bool + var usingWebsocket bool + + if Bot.Exchanges[x].SupportsWebsocket() && Bot.Exchanges[x].IsWebsocketEnabled() { + ws, err := Bot.Exchanges[x].GetWebsocket() + if err != nil { + log.Debugf("%s unable to get websocket pointer. Err: %s", exchangeName, err) + usingREST = true + } + + if ws.IsConnected() { + usingWebsocket = true + } else { + usingREST = true + } + } else if supportsREST { + usingREST = true + } + + for y := range assetTypes { + for _, p := range Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) { + if atomic.LoadInt32(&e.shutdown) == 1 { + return + } + + if !e.exists(exchangeName, p, assetTypes[y]) { + c := CurrencyPairSyncAgent{ + AssetType: assetTypes[y], + Exchange: exchangeName, + Pair: p, + } + + if e.Cfg.SyncTicker { + c.Ticker = SyncBase{ + IsUsingREST: usingREST, + IsUsingWebsocket: usingWebsocket, + } + } + + if e.Cfg.SyncOrderbook { + c.Orderbook = SyncBase{ + IsUsingREST: usingREST, + IsUsingWebsocket: usingWebsocket, + } + } + + if e.Cfg.SyncTrades { + c.Trade = SyncBase{ + IsUsingREST: usingREST, + IsUsingWebsocket: usingWebsocket, + } + } + + e.add(&c) + } + + c, err := e.get(exchangeName, p, assetTypes[y]) + if err != nil { + log.Errorf("failed to get item. Err: %s", err) + continue + } + + if e.Cfg.SyncTicker { + if !e.isProcessing(exchangeName, c.Pair, c.AssetType, SyncItemTicker) { + if c.Ticker.LastUpdated.IsZero() || time.Since(c.Ticker.LastUpdated) > defaultSyncerTimeout { + if c.Ticker.IsUsingWebsocket { + if time.Since(c.Created) < defaultSyncerTimeout { + continue + } + + if supportsREST { + e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, true) + c.Ticker.IsUsingWebsocket = false + c.Ticker.IsUsingREST = true + log.Warnf("%s %s: No ticker update after 10 seconds, switching from websocket to rest", + c.Exchange, c.Pair.String()) + e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, false) + } + } + + if c.Ticker.IsUsingREST { + e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, true) + var result ticker.Price + var err error + + if supportsRESTTickerBatching { + e.mux.Lock() + batchLastDone, ok := e.tickerBatchLastRequested[exchangeName] + if !ok { + e.tickerBatchLastRequested[exchangeName] = time.Time{} + } + e.mux.Unlock() + + if batchLastDone.IsZero() || time.Since(batchLastDone) > defaultSyncerTimeout { + e.mux.Lock() + if e.Cfg.Verbose { + log.Debugf("%s Init'ing REST ticker batching", exchangeName) + } + result, err = Bot.Exchanges[x].UpdateTicker(c.Pair, c.AssetType) + e.tickerBatchLastRequested[exchangeName] = time.Now() + e.mux.Unlock() + } else { + if e.Cfg.Verbose { + log.Debugf("%s Using recent batching cache", exchangeName) + } + result, err = Bot.Exchanges[x].FetchTicker(c.Pair, c.AssetType) + } + } else { + result, err = Bot.Exchanges[x].FetchTicker(c.Pair, c.AssetType) + } + printTickerSummary(&result, c.Pair, c.AssetType, exchangeName, err) + if err == nil { + //nolint:gocritic Bot.CommsRelayer.StageTickerData(exchangeName, c.AssetType, result) + if Bot.Config.RemoteControl.WebsocketRPC.Enabled { + relayWebsocketEvent(result, "ticker_update", c.AssetType.String(), exchangeName) + } + } + e.update(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, err) + } + } else { + time.Sleep(time.Millisecond * 50) + } + } + } + + if e.Cfg.SyncOrderbook { + if !e.isProcessing(exchangeName, c.Pair, c.AssetType, SyncItemOrderbook) { + if c.Orderbook.LastUpdated.IsZero() || time.Since(c.Orderbook.LastUpdated) > defaultSyncerTimeout { + if c.Orderbook.IsUsingWebsocket { + if time.Since(c.Created) < defaultSyncerTimeout { + continue + } + if supportsREST { + e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, true) + c.Orderbook.IsUsingWebsocket = false + c.Orderbook.IsUsingREST = true + log.Warnf("%s %s: No orderbook update after 15 seconds, switching from websocket to rest", + c.Exchange, c.Pair.String()) + e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, false) + } + } + + e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, true) + result, err := Bot.Exchanges[x].UpdateOrderbook(c.Pair, c.AssetType) + printOrderbookSummary(&result, c.Pair, c.AssetType, exchangeName, err) + if err == nil { + //nolint:gocritic Bot.CommsRelayer.StageOrderbookData(exchangeName, c.AssetType, result) + if Bot.Config.RemoteControl.WebsocketRPC.Enabled { + relayWebsocketEvent(result, "orderbook_update", c.AssetType.String(), exchangeName) + } + } + e.update(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, err) + } else { + time.Sleep(time.Millisecond * 50) + } + } + if e.Cfg.SyncTrades { + if !e.isProcessing(exchangeName, c.Pair, c.AssetType, SyncItemTrade) { + if c.Trade.LastUpdated.IsZero() || time.Since(c.Trade.LastUpdated) > defaultSyncerTimeout { + e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTrade, true) + e.update(c.Exchange, c.Pair, c.AssetType, SyncItemTrade, nil) + } + } + } + } + } + } + } + } + +} + +// Start starts an exchange currency pair syncer +func (e *ExchangeCurrencyPairSyncer) Start() { + log.Debugf("Exchange CurrencyPairSyncer started.") + + for x := range Bot.Exchanges { + if !Bot.Exchanges[x].IsEnabled() { + continue + } + + if Bot.Exchanges[x].GetName() == "BTCC" { + continue + } + + exchangeName := Bot.Exchanges[x].GetName() + supportsWebsocket := Bot.Exchanges[x].SupportsWebsocket() + assetTypes := Bot.Exchanges[x].GetAssetTypes() + supportsREST := Bot.Exchanges[x].SupportsREST() + + if !supportsREST && !supportsWebsocket { + log.Warnf("Loaded exchange %s does not support REST or Websocket", exchangeName) + continue + } + + var usingWebsocket bool + var usingREST bool + + if supportsWebsocket { + ws, err := Bot.Exchanges[x].GetWebsocket() + if err != nil { + log.Errorf("%s failed to get websocket. Err: %s", exchangeName, err) + usingREST = true + } + + if !ws.IsEnabled() { + usingREST = true + } + + if !ws.IsConnected() { + go WebsocketDataHandler(ws) + + err = ws.Connect() + if err != nil { + log.Errorf("%s websocket failed to connect. Err: %s", exchangeName, err) + usingREST = true + } else { + usingWebsocket = true + } + } + } else if supportsREST { + usingREST = true + } + + for y := range assetTypes { + for _, p := range Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) { + if e.exists(exchangeName, p, assetTypes[y]) { + continue + } + c := CurrencyPairSyncAgent{ + AssetType: assetTypes[y], + Exchange: exchangeName, + Pair: p, + } + + if e.Cfg.SyncTicker { + c.Ticker = SyncBase{ + IsUsingREST: usingREST, + IsUsingWebsocket: usingWebsocket, + } + } + + if e.Cfg.SyncOrderbook { + c.Orderbook = SyncBase{ + IsUsingREST: usingREST, + IsUsingWebsocket: usingWebsocket, + } + } + + if e.Cfg.SyncTrades { + c.Trade = SyncBase{ + IsUsingREST: usingREST, + IsUsingWebsocket: usingWebsocket, + } + } + + e.add(&c) + } + } + } + + if atomic.CompareAndSwapInt32(&e.initSyncStarted, 0, 1) { + log.Debugln("Exchange CurrencyPairSyncer initial sync started.") + e.initSyncStartTime = time.Now() + log.Debugln(createdCounter) + log.Debugln(removedCounter) + } + + go func() { + e.initSyncWG.Wait() + if atomic.CompareAndSwapInt32(&e.initSyncCompleted, 0, 1) { + log.Debugf("Exchange CurrencyPairSyncer initial sync is complete.") + completedTime := time.Now() + log.Debugf("Exchange CurrencyPairSyncer initiial sync took %v [%v sync items].", completedTime.Sub(e.initSyncStartTime), createdCounter) + + if !e.Cfg.SyncContinuously { + log.Debugf("Exchange CurrencyPairSyncer stopping.") + e.Stop() + Bot.Stop() + return + } + } + }() + + if atomic.LoadInt32(&e.initSyncCompleted) == 1 && !e.Cfg.SyncContinuously { + return + } + + for i := 0; i < e.Cfg.NumWorkers; i++ { + go e.worker() + } +} + +// Stop shuts down the exchange currency pair syncer +func (e *ExchangeCurrencyPairSyncer) Stop() { + stopped := atomic.CompareAndSwapInt32(&e.shutdown, 0, 1) + if stopped { + log.Debugf("Exchange CurrencyPairSyncer stopped.") + } +} diff --git a/engine/syncer_test.go b/engine/syncer_test.go new file mode 100644 index 00000000..ad846564 --- /dev/null +++ b/engine/syncer_test.go @@ -0,0 +1,46 @@ +package engine + +import ( + "testing" + "time" + + "github.com/thrasher-/gocryptotrader/config" + log "github.com/thrasher-/gocryptotrader/logger" +) + +func TestNewCurrencyPairSyncer(t *testing.T) { + t.Skip() + + if Bot == nil { + Bot = new(Engine) + } + Bot.Config = &config.Cfg + err := Bot.Config.LoadConfig("") + if err != nil { + t.Fatalf("Test failed. TestNewExchangeSyncer: Failed to load config: %s", err) + } + + Bot.Settings.DisableExchangeAutoPairUpdates = true + Bot.Settings.Verbose = true + Bot.Settings.EnableExchangeWebsocketSupport = true + + SetupExchanges() + + if err != nil { + log.Printf("failed to start exchange syncer") + } + + Bot.ExchangeCurrencyPairManager, err = NewCurrencyPairSyncer(CurrencyPairSyncerConfig{ + SyncTicker: true, + SyncOrderbook: false, + SyncTrades: false, + SyncContinuously: false, + }) + if err != nil { + t.Errorf("NewCurrencyPairSyncer failed: err %s", err) + } + + Bot.ExchangeCurrencyPairManager.Start() + time.Sleep(time.Second * 15) + Bot.ExchangeCurrencyPairManager.Stop() +} diff --git a/engine/syncer_types.go b/engine/syncer_types.go new file mode 100644 index 00000000..cd181447 --- /dev/null +++ b/engine/syncer_types.go @@ -0,0 +1,61 @@ +package engine + +import ( + "sync" + "time" + + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" +) + +// CurrencyPairSyncerConfig stores the currency pair config +type CurrencyPairSyncerConfig struct { + SyncTicker bool + SyncOrderbook bool + SyncTrades bool + SyncContinuously bool + + NumWorkers int + Verbose bool +} + +// ExchangeSyncerConfig stores the exchange syncer config +type ExchangeSyncerConfig struct { + SyncDepositAddresses bool + SyncOrders bool +} + +// ExchangeCurrencyPairSyncer stores the exchange currency pair syncer object +type ExchangeCurrencyPairSyncer struct { + Cfg CurrencyPairSyncerConfig + CurrencyPairs []CurrencyPairSyncAgent + tickerBatchLastRequested map[string]time.Time + mux sync.Mutex + initSyncWG sync.WaitGroup + + initSyncCompleted int32 + initSyncStarted int32 + initSyncStartTime time.Time + shutdown int32 +} + +// SyncBase stores information +type SyncBase struct { + IsUsingWebsocket bool + IsUsingREST bool + IsProcessing bool + LastUpdated time.Time + HaveData bool + NumErrors int +} + +// CurrencyPairSyncAgent stores the sync agent info +type CurrencyPairSyncAgent struct { + Created time.Time + Exchange string + AssetType assets.AssetType + Pair currency.Pair + Ticker SyncBase + Orderbook SyncBase + Trade SyncBase +} diff --git a/websocket.go b/engine/websocket.go similarity index 82% rename from websocket.go rename to engine/websocket.go index a817ce46..6adbbda4 100644 --- a/websocket.go +++ b/engine/websocket.go @@ -1,4 +1,4 @@ -package main +package engine import ( "errors" @@ -6,8 +6,10 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -39,52 +41,6 @@ var wsHandlers = map[string]wsCommandHandler{ "getportfolio": {authRequired: true, handler: wsGetPortfolio}, } -// WebsocketClient stores information related to the websocket client -type WebsocketClient struct { - Hub *WebsocketHub - Conn *websocket.Conn - Authenticated bool - authFailures int - Send chan []byte -} - -// WebsocketHub stores the data for managing websocket clients -type WebsocketHub struct { - Clients map[*WebsocketClient]bool - Broadcast chan []byte - Register chan *WebsocketClient - Unregister chan *WebsocketClient -} - -// WebsocketEvent is the struct used for websocket events -type WebsocketEvent struct { - Exchange string `json:"exchange,omitempty"` - AssetType string `json:"assetType,omitempty"` - Event string - 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"` -} - -// 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"` -} - -// WebsocketAuth is a struct used for -type WebsocketAuth struct { - Username string `json:"username"` - Password string `json:"password"` -} - // NewWebsocketHub Creates a new websocket hub func NewWebsocketHub() *WebsocketHub { return &WebsocketHub{ @@ -256,7 +212,7 @@ func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) { StartWebsocketHandler() } - connectionLimit := bot.config.Webserver.WebsocketConnectionLimit + connectionLimit := Bot.Config.RemoteControl.WebsocketRPC.ConnectionLimit numClients := len(wsHub.Clients) if numClients >= connectionLimit { @@ -273,7 +229,7 @@ func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) { // 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 { + if Bot.Config.RemoteControl.WebsocketRPC.AllowInsecureOrigin { upgrader.CheckOrigin = func(r *http.Request) bool { return true } } @@ -305,9 +261,8 @@ func wsAuth(client *WebsocketClient, data interface{}) error { return err } - hashPW := common.HexEncodeToString(common.GetSHA256([]byte(bot.config.Webserver.AdminPassword))) - - if auth.Username == bot.config.Webserver.AdminUsername && auth.Password == hashPW { + hashPW := crypto.HexEncodeToString(crypto.GetSHA256([]byte(Bot.Config.RemoteControl.Password))) + if auth.Username == Bot.Config.RemoteControl.Username && auth.Password == hashPW { client.Authenticated = true wsResp.Data = WebsocketResponseSuccess log.Debugf("websocket: client authenticated successfully") @@ -317,22 +272,22 @@ func wsAuth(client *WebsocketClient, data interface{}) error { wsResp.Error = "invalid username/password" client.authFailures++ client.SendWebsocketMessage(wsResp) - if client.authFailures >= bot.config.Webserver.WebsocketMaxAuthFailures { + if client.authFailures >= Bot.Config.RemoteControl.WebsocketRPC.MaxAuthFailures { log.Debugf("websocket: disconnecting client, maximum auth failures threshold reached (failures: %d limit: %d)", - client.authFailures, bot.config.Webserver.WebsocketMaxAuthFailures) + client.authFailures, Bot.Config.RemoteControl.WebsocketRPC.MaxAuthFailures) wsHub.Unregister <- client return nil } log.Debugf("websocket: client sent wrong username/password (failures: %d limit: %d)", - client.authFailures, bot.config.Webserver.WebsocketMaxAuthFailures) + client.authFailures, Bot.Config.RemoteControl.WebsocketRPC.MaxAuthFailures) return nil } func wsGetConfig(client *WebsocketClient, data interface{}) error { wsResp := WebsocketEventResponse{ Event: "GetConfig", - Data: bot.config, + Data: Bot.Config, } return client.SendWebsocketMessage(wsResp) } @@ -349,7 +304,7 @@ func wsSaveConfig(client *WebsocketClient, data interface{}) error { return err } - err = bot.config.UpdateConfig(bot.configFile, &cfg) + err = Bot.Config.UpdateConfig(Bot.Settings.ConfigFile, &cfg) if err != nil { wsResp.Error = err.Error() client.SendWebsocketMessage(wsResp) @@ -390,8 +345,8 @@ func wsGetTicker(client *WebsocketClient, data interface{}) error { return err } - result, err := GetSpecificTicker(tickerReq.Currency, - tickerReq.Exchange, tickerReq.AssetType) + result, err := GetSpecificTicker(currency.NewPairFromString(tickerReq.Currency), + tickerReq.Exchange, assets.AssetType(tickerReq.AssetType)) if err != nil { wsResp.Error = err.Error() @@ -422,8 +377,8 @@ func wsGetOrderbook(client *WebsocketClient, data interface{}) error { return err } - result, err := GetSpecificOrderbook(orderbookReq.Currency, - orderbookReq.Exchange, orderbookReq.AssetType) + result, err := GetSpecificOrderbook(currency.NewPairFromString(orderbookReq.Currency), + orderbookReq.Exchange, assets.AssetType(orderbookReq.AssetType)) if err != nil { wsResp.Error = err.Error() @@ -452,6 +407,6 @@ func wsGetPortfolio(client *WebsocketClient, data interface{}) error { wsResp := WebsocketEventResponse{ Event: "GetPortfolio", } - wsResp.Data = bot.portfolio.GetPortfolioSummary() + wsResp.Data = Bot.Portfolio.GetPortfolioSummary() return client.SendWebsocketMessage(wsResp) } diff --git a/engine/websocket_types.go b/engine/websocket_types.go new file mode 100644 index 00000000..c74483fc --- /dev/null +++ b/engine/websocket_types.go @@ -0,0 +1,49 @@ +package engine + +import "github.com/gorilla/websocket" + +// WebsocketClient stores information related to the websocket client +type WebsocketClient struct { + Hub *WebsocketHub + Conn *websocket.Conn + Authenticated bool + authFailures int + Send chan []byte +} + +// WebsocketHub stores the data for managing websocket clients +type WebsocketHub struct { + Clients map[*WebsocketClient]bool + Broadcast chan []byte + Register chan *WebsocketClient + Unregister chan *WebsocketClient +} + +// WebsocketEvent is the struct used for websocket events +type WebsocketEvent struct { + Exchange string `json:"exchange,omitempty"` + AssetType string `json:"assetType,omitempty"` + Event string + 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"` +} + +// 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"` +} + +// WebsocketAuth is a struct used for +type WebsocketAuth struct { + Username string `json:"username"` + Password string `json:"password"` +} diff --git a/events/README.md b/events/README.md deleted file mode 100644 index 040df9c7..00000000 --- a/events/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# GoCryptoTrader package Events - - - - -[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader) -[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader/events) -[![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader) - - -This events package is part of the GoCryptoTrader codebase. - -## This is still in active development - -You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). - -Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY) - -## Current Features for events - -+ The events package handles events from GoCryptoTrader bot. - -### Please click GoDocs chevron above to view current GoDoc information for this package - -## Contribution - -Please feel free to submit any pull requests or suggest any desired features to be added. - -When submitting a PR, please abide by our coding guidelines: - -+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). -+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. -+ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md). -+ Pull requests need to be based on and opened against the `master` branch. - -## Donations - - - -If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: - -***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/alphapoint/alphapoint.go b/exchanges/alphapoint/alphapoint.go index 0d7f5d68..15d7a650 100644 --- a/exchanges/alphapoint/alphapoint.go +++ b/exchanges/alphapoint/alphapoint.go @@ -6,13 +6,11 @@ import ( "fmt" "net/http" "strconv" - "time" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/crypto" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -48,22 +46,6 @@ type Alphapoint struct { WebsocketConn *websocket.Conn } -// SetDefaults sets current default settings -func (a *Alphapoint) SetDefaults() { - a.APIUrl = alphapointDefaultAPIURL - a.WebsocketURL = alphapointDefaultWebsocketURL - a.AssetTypes = []string{ticker.Spot} - a.SupportsAutoPairUpdating = false - a.SupportsRESTTickerBatching = false - a.APIWithdrawPermissions = exchange.WithdrawCryptoWith2FA | - exchange.AutoWithdrawCryptoWithAPIPermission | - exchange.NoFiatWithdrawals - a.Requester = request.New(a.Name, - request.NewRateLimit(time.Minute*10, alphapointAuthRate), - request.NewRateLimit(time.Minute*10, alphapointUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) -} - // GetTicker returns current ticker information from Alphapoint for a selected // currency pair ie "BTCUSD" func (a *Alphapoint) GetTicker(currencyPair string) (Ticker, error) { @@ -527,7 +509,7 @@ func (a *Alphapoint) GetOrderFee(symbol, side string, quantity, price float64) ( func (a *Alphapoint) SendHTTPRequest(method, path string, data map[string]interface{}, result interface{}) error { headers := make(map[string]string) headers["Content-Type"] = "application/json" - path = fmt.Sprintf("%s/ajax/v%s/%s", a.APIUrl, alphapointAPIVersion, path) + path = fmt.Sprintf("%s/ajax/v%s/%s", a.API.Endpoints.URL, alphapointAPIVersion, path) PayloadJSON, err := common.JSONEncode(data) if err != nil { @@ -539,7 +521,7 @@ func (a *Alphapoint) SendHTTPRequest(method, path string, data map[string]interf // SendAuthenticatedHTTPRequest sends an authenticated request func (a *Alphapoint) SendAuthenticatedHTTPRequest(method, path string, data map[string]interface{}, result interface{}) error { - if !a.AuthenticatedAPISupport { + if !a.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, a.Name) } @@ -547,12 +529,13 @@ func (a *Alphapoint) SendAuthenticatedHTTPRequest(method, path string, data map[ headers := make(map[string]string) headers["Content-Type"] = "application/json" - data["apiKey"] = a.APIKey + data["apiKey"] = a.API.Credentials.Key data["apiNonce"] = n - hmac := common.GetHMAC(common.HashSHA256, []byte(n.String()+a.ClientID+a.APIKey), - []byte(a.APISecret)) - data["apiSig"] = common.StringToUpper(common.HexEncodeToString(hmac)) - path = fmt.Sprintf("%s/ajax/v%s/%s", a.APIUrl, alphapointAPIVersion, path) + hmac := crypto.GetHMAC(crypto.HashSHA256, + []byte(n.String()+a.API.Credentials.ClientID+a.API.Credentials.Key), + []byte(a.API.Credentials.Secret)) + data["apiSig"] = common.StringToUpper(crypto.HexEncodeToString(hmac)) + path = fmt.Sprintf("%s/ajax/v%s/%s", a.API.Endpoints.URL, alphapointAPIVersion, path) PayloadJSON, err := common.JSONEncode(data) if err != nil { diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index 04c6d004..66d9b021 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -20,22 +20,22 @@ func TestSetDefaults(t *testing.T) { SetDefaults := Alphapoint{} SetDefaults.SetDefaults() - if SetDefaults.APIUrl != "https://sim3.alphapoint.com:8400" { - t.Error("Test Failed - SetDefaults: String Incorrect -", SetDefaults.APIUrl) + if SetDefaults.API.Endpoints.URL != "https://sim3.alphapoint.com:8400" { + t.Error("Test Failed - SetDefaults: String Incorrect -", SetDefaults.API.Endpoints.URL) } - if SetDefaults.WebsocketURL != "wss://sim3.alphapoint.com:8401/v1/GetTicker/" { - t.Error("Test Failed - SetDefaults: String Incorrect -", SetDefaults.WebsocketURL) + if SetDefaults.API.Endpoints.WebsocketURL != "wss://sim3.alphapoint.com:8401/v1/GetTicker/" { + t.Error("Test Failed - SetDefaults: String Incorrect -", SetDefaults.API.Endpoints.WebsocketURL) } } func testSetAPIKey(a *Alphapoint) { - a.APIKey = apiKey - a.APISecret = apiSecret - a.AuthenticatedAPISupport = true + a.API.Credentials.Key = apiKey + a.API.Credentials.Secret = apiSecret + a.API.AuthenticatedSupport = true } func testIsAPIKeysSet(a *Alphapoint) bool { - if apiKey != "" && apiSecret != "" && a.AuthenticatedAPISupport { + if apiKey != "" && apiSecret != "" && a.API.AuthenticatedSupport { return true } return false @@ -412,7 +412,7 @@ func TestCreateOrder(t *testing.T) { return } - _, err := a.CreateOrder("", "", exchange.MarketOrderType.ToString(), 0.01, 0) + _, err := a.CreateOrder("", "", exchange.LimitOrderType.ToString(), 0.01, 0) if err == nil { t.Error("Test Failed - GetUserInfo() error") } @@ -526,11 +526,7 @@ func TestGetOrderHistory(t *testing.T) { // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet(a *Alphapoint) bool { - if a.APIKey != "" && a.APIKey != "Key" && - a.APISecret != "" && a.APISecret != "Secret" { - return true - } - return false + return a.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -545,7 +541,7 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.USD, } - response, err := a.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") + response, err := a.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if !areTestAPIKeysSet(a) && err == nil { t.Error("Expecting an error when no keys are set") } @@ -628,13 +624,7 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { a := &Alphapoint{} a.SetDefaults() - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", - AddressTag: "0123456789", - } + var withdrawCryptoRequest = exchange.CryptoWithdrawRequest{} _, err := a.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) if err != common.ErrNotYetImplemented { @@ -650,8 +640,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := a.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrNotYetImplemented { t.Errorf("Expected '%v', received: '%v'", common.ErrNotYetImplemented, err) @@ -666,8 +655,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := a.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrNotYetImplemented { t.Errorf("Expected '%v', received: '%v'", common.ErrNotYetImplemented, err) diff --git a/exchanges/alphapoint/alphapoint_websocket.go b/exchanges/alphapoint/alphapoint_websocket.go index 7e4295f2..88b023e5 100644 --- a/exchanges/alphapoint/alphapoint_websocket.go +++ b/exchanges/alphapoint/alphapoint_websocket.go @@ -17,7 +17,7 @@ func (a *Alphapoint) WebsocketClient() { for a.Enabled { var Dialer websocket.Dialer var err error - a.WebsocketConn, _, err = Dialer.Dial(a.WebsocketURL, http.Header{}) + a.WebsocketConn, _, err = Dialer.Dial(a.API.Endpoints.WebsocketURL, http.Header{}) if err != nil { log.Errorf("%s Unable to connect to Websocket. Error: %s\n", a.Name, err) diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 204cf01c..808d089b 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -7,12 +7,71 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// GetDefaultConfig returns a default exchange config for Alphapoint +func (a *Alphapoint) GetDefaultConfig() (*config.ExchangeConfig, error) { + return nil, common.ErrFunctionNotSupported +} + +// SetDefaults sets current default settings +func (a *Alphapoint) SetDefaults() { + a.Name = "Alphapoint" + a.Enabled = true + a.Verbose = true + a.API.Endpoints.URL = alphapointDefaultAPIURL + a.API.Endpoints.WebsocketURL = alphapointDefaultWebsocketURL + a.API.CredentialsValidator.RequiresKey = true + a.API.CredentialsValidator.RequiresSecret = true + + a.CurrencyPairs.AssetTypes = assets.AssetTypes{ + assets.AssetTypeSpot, + } + + a.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AccountInfo: true, + }, + + WebsocketCapabilities: exchange.ProtocolFeatures{ + TickerFetching: true, + }, + + WithdrawPermissions: exchange.WithdrawCryptoWith2FA | + exchange.AutoWithdrawCryptoWithAPIPermission | + exchange.NoFiatWithdrawals, + }, + } + + a.Requester = request.New(a.Name, + request.NewRateLimit(time.Minute*10, alphapointAuthRate), + request.NewRateLimit(time.Minute*10, alphapointUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + a.WebsocketInit() +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (a *Alphapoint) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + return nil, common.ErrFunctionNotSupported +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (a *Alphapoint) UpdateTradablePairs(forceUpdate bool) error { + return common.ErrFunctionNotSupported +} + // GetAccountInfo retrieves balances for all enabled currencies on the // Alphapoint exchange func (a *Alphapoint) GetAccountInfo() (exchange.AccountInfo, error) { @@ -41,7 +100,7 @@ func (a *Alphapoint) GetAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (a *Alphapoint) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (a *Alphapoint) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := a.GetTicker(p.String()) if err != nil { @@ -64,8 +123,8 @@ func (a *Alphapoint) UpdateTicker(p currency.Pair, assetType string) (ticker.Pri return ticker.GetTicker(a.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (a *Alphapoint) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (a *Alphapoint) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tick, err := ticker.GetTicker(a.GetName(), p, assetType) if err != nil { return a.UpdateTicker(p, assetType) @@ -74,7 +133,7 @@ func (a *Alphapoint) GetTickerPrice(p currency.Pair, assetType string) (ticker.P } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (a *Alphapoint) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (a *Alphapoint) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := a.GetOrderbook(p.String()) if err != nil { @@ -105,8 +164,8 @@ func (a *Alphapoint) UpdateOrderbook(p currency.Pair, assetType string) (orderbo return orderbook.Get(a.Name, p, assetType) } -// GetOrderbookEx returns the orderbook for a currency pair -func (a *Alphapoint) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns the orderbook for a currency pair +func (a *Alphapoint) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(a.GetName(), p, assetType) if err != nil { return a.UpdateOrderbook(p, assetType) @@ -123,7 +182,7 @@ func (a *Alphapoint) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (a *Alphapoint) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { +func (a *Alphapoint) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { var resp []exchange.TradeHistory return resp, common.ErrNotYetImplemented @@ -207,18 +266,18 @@ func (a *Alphapoint) GetDepositAddress(cryptocurrency currency.Code, _ string) ( // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (a *Alphapoint) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (a *Alphapoint) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return "", common.ErrNotYetImplemented } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is submitted -func (a *Alphapoint) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (a *Alphapoint) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrNotYetImplemented } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is // submitted -func (a *Alphapoint) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (a *Alphapoint) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrNotYetImplemented } @@ -270,7 +329,6 @@ func (a *Alphapoint) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - return orders, nil } @@ -313,7 +371,6 @@ func (a *Alphapoint) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - return orders, nil } diff --git a/exchanges/anx/README.md b/exchanges/anx/README.md index fd454a76..085c5daf 100644 --- a/exchanges/anx/README.md +++ b/exchanges/anx/README.md @@ -51,22 +51,22 @@ main.go ```go var a exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "ANX" { - a = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "ANX" { + a = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := a.GetTickerPrice() +tick, err := a.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := a.GetOrderbookEx() +ob, err := a.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 573e8791..0fd32314 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -9,11 +9,9 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -45,80 +43,11 @@ type ANX struct { exchange.Base } -// SetDefaults sets current default settings -func (a *ANX) SetDefaults() { - a.Name = "ANX" - a.Enabled = false - a.TakerFee = 0.02 - a.MakerFee = 0.01 - a.Verbose = false - a.RESTPollingDelay = 10 - a.RequestCurrencyPairFormat.Delimiter = "" - a.RequestCurrencyPairFormat.Uppercase = true - a.RequestCurrencyPairFormat.Index = "" - a.ConfigCurrencyPairFormat.Delimiter = "_" - a.ConfigCurrencyPairFormat.Uppercase = true - a.ConfigCurrencyPairFormat.Index = "" - a.APIWithdrawPermissions = exchange.WithdrawCryptoWithEmail | - exchange.AutoWithdrawCryptoWithSetup | - exchange.WithdrawCryptoWith2FA | - exchange.WithdrawFiatViaWebsiteOnly - a.AssetTypes = []string{ticker.Spot} - a.SupportsAutoPairUpdating = true - a.SupportsRESTTickerBatching = false - a.Requester = request.New(a.Name, - request.NewRateLimit(time.Second, anxAuthRate), - request.NewRateLimit(time.Second, anxUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - a.APIUrlDefault = anxAPIURL - a.APIUrl = a.APIUrlDefault - a.WebsocketInit() -} - -// Setup is run on startup to setup exchange with config values -func (a *ANX) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - a.SetEnabled(false) - } else { - a.Enabled = true - a.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - a.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - a.SetHTTPClientTimeout(exch.HTTPTimeout) - a.SetHTTPClientUserAgent(exch.HTTPUserAgent) - a.RESTPollingDelay = exch.RESTPollingDelay - a.Verbose = exch.Verbose - a.HTTPDebugging = exch.HTTPDebugging - a.BaseCurrencies = exch.BaseCurrencies - a.AvailablePairs = exch.AvailablePairs - a.EnabledPairs = exch.EnabledPairs - err := a.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = a.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = a.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = a.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = a.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - } -} - // GetCurrencies returns a list of supported currencies (both fiat // and cryptocurrencies) func (a *ANX) GetCurrencies() (CurrenciesStore, error) { var result CurrenciesStaticResponse - path := fmt.Sprintf("%sapi/3/%s", a.APIUrl, anxCurrencies) + path := fmt.Sprintf("%sapi/3/%s", a.API.Endpoints.URL, anxCurrencies) err := a.SendHTTPRequest(path, &result) if err != nil { @@ -131,14 +60,14 @@ func (a *ANX) GetCurrencies() (CurrenciesStore, error) { // GetTicker returns the current ticker func (a *ANX) GetTicker(currency string) (Ticker, error) { var t Ticker - path := fmt.Sprintf("%sapi/2/%s/%s", a.APIUrl, currency, anxTicker) + path := fmt.Sprintf("%sapi/2/%s/%s", a.API.Endpoints.URL, currency, anxTicker) return t, a.SendHTTPRequest(path, &t) } // GetDepth returns current orderbook depth. func (a *ANX) GetDepth(currency string) (Depth, error) { var depth Depth - path := fmt.Sprintf("%sapi/2/%s/%s", a.APIUrl, currency, anxDepth) + path := fmt.Sprintf("%sapi/2/%s/%s", a.API.Endpoints.URL, currency, anxDepth) return depth, a.SendHTTPRequest(path, &depth) } @@ -408,7 +337,7 @@ func (a *ANX) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends a authenticated HTTP request func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interface{}, result interface{}) error { - if !a.AuthenticatedAPISupport { + if !a.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, a.Name) } @@ -430,13 +359,15 @@ func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interf log.Debugf("Request JSON: %s\n", PayloadJSON) } - hmac := common.GetHMAC(common.HashSHA512, []byte(path+string("\x00")+string(PayloadJSON)), []byte(a.APISecret)) + hmac := crypto.GetHMAC(crypto.HashSHA512, []byte(path+string("\x00")+string(PayloadJSON)), []byte(a.API.Credentials.Secret)) headers := make(map[string]string) - headers["Rest-Key"] = a.APIKey - headers["Rest-Sign"] = common.Base64Encode(hmac) + headers["Rest-Key"] = a.API.Credentials.Key + headers["Rest-Sign"] = crypto.Base64Encode(hmac) headers["Content-Type"] = "application/json" - return a.SendPayload(http.MethodPost, a.APIUrl+path, headers, bytes.NewBuffer(PayloadJSON), result, true, true, a.Verbose, a.HTTPDebugging) + return a.SendPayload(http.MethodPost, a.API.Endpoints.URL+path, headers, + bytes.NewBuffer(PayloadJSON), result, true, true, a.Verbose, + a.HTTPDebugging) } // GetFee returns an estimate of fee based on type of transaction @@ -468,9 +399,9 @@ func (a *ANX) calculateTradingFee(purchasePrice, amount float64, isMaker bool) f var fee float64 if isMaker { - fee = a.MakerFee * amount * purchasePrice + fee = 0.01 * amount * purchasePrice } else { - fee = a.TakerFee * amount * purchasePrice + fee = 0.02 * amount * purchasePrice } return fee diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index 8d11f22c..a56afc1a 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) // Please supply your own keys here for due diligence testing @@ -24,22 +25,10 @@ func TestSetDefaults(t *testing.T) { if a.Name != "ANX" { t.Error("Test Failed - ANX SetDefaults() incorrect values set") } - if a.Enabled { + if !a.Enabled { t.Error("Test Failed - ANX SetDefaults() incorrect values set") } - if a.TakerFee != 0.02 { - t.Error("Test Failed - ANX SetDefaults() incorrect values set") - } - if a.MakerFee != 0.01 { - t.Error("Test Failed - ANX SetDefaults() incorrect values set") - } - if a.Verbose { - t.Error("Test Failed - ANX SetDefaults() incorrect values set") - } - if a.Websocket.IsEnabled() { - t.Error("Test Failed - ANX SetDefaults() incorrect values set") - } - if a.RESTPollingDelay != 10 { + if !a.Verbose { t.Error("Test Failed - ANX SetDefaults() incorrect values set") } } @@ -48,37 +37,24 @@ func TestSetup(t *testing.T) { anxSetupConfig := config.GetConfig() anxSetupConfig.LoadConfig("../../testdata/configtest.json") anxConfig, err := anxSetupConfig.GetExchangeConfig("ANX") - anxConfig.AuthenticatedAPISupport = true - if err != nil { t.Error("Test Failed - ANX Setup() init error") } - a.Setup(&anxConfig) - a.APIKey = apiKey - a.APISecret = apiSecret - a.AuthenticatedAPISupport = true + + a.Setup(anxConfig) + a.API.Credentials.Key = apiKey + a.API.Credentials.Secret = apiSecret + a.API.AuthenticatedSupport = true if !a.Enabled { t.Error("Test Failed - ANX Setup() incorrect values set") } - if a.RESTPollingDelay != 10 { - t.Error("Test Failed - ANX Setup() incorrect values set") - } if a.Verbose { t.Error("Test Failed - ANX Setup() incorrect values set") } - if a.Websocket.IsEnabled() { - t.Error("Test Failed - ANX Setup() incorrect values set") - } if len(a.BaseCurrencies) == 0 { t.Error("Test Failed - ANX Setup() incorrect values set") } - if len(a.AvailablePairs) == 0 { - t.Error("Test Failed - ANX Setup() incorrect values set") - } - if len(a.EnabledPairs) == 0 { - t.Error("Test Failed - ANX Setup() incorrect values set") - } } func TestGetCurrencies(t *testing.T) { @@ -88,8 +64,8 @@ func TestGetCurrencies(t *testing.T) { } } -func TestGetTradablePairs(t *testing.T) { - _, err := a.GetTradablePairs() +func TestFetchTradablePairs(t *testing.T) { + _, err := a.FetchTradablePairs(assets.AssetTypeSpot) if err != nil { t.Fatalf("Test failed. TestGetTradablePairs failed. Err: %s", err) } @@ -273,11 +249,7 @@ func TestGetOrderHistory(t *testing.T) { // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if a.APIKey != "" && a.APIKey != "Key" && - a.APISecret != "" && a.APISecret != "Secret" { - return true - } - return false + return a.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -292,7 +264,8 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.USD, } - response, err := a.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") + response, err := a.SubmitOrder(p, + exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -384,12 +357,14 @@ func TestWithdraw(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", - AddressTag: "0123456789", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", + AddressTag: "0123456789", } _, err := a.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) @@ -408,7 +383,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := a.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { @@ -424,7 +399,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := a.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 6614a7d5..79ee9209 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -8,13 +8,109 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config for Alphapoint +func (a *ANX) GetDefaultConfig() (*config.ExchangeConfig, error) { + a.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = a.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = a.BaseCurrencies + + err := a.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if a.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = a.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets current default settings +func (a *ANX) SetDefaults() { + a.Name = "ANX" + a.Enabled = true + a.Verbose = true + a.BaseCurrencies = currency.Currencies{ + currency.USD, + currency.HKD, + currency.EUR, + currency.CAD, + currency.AUD, + currency.SGD, + currency.JPY, + currency.GBP, + currency.NZD, + } + a.API.CredentialsValidator.RequiresKey = true + a.API.CredentialsValidator.RequiresSecret = true + + a.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: true, + }, + } + + a.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: false, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.WithdrawCryptoWithEmail | + exchange.AutoWithdrawCryptoWithSetup | + exchange.WithdrawCryptoWith2FA | + exchange.WithdrawFiatViaWebsiteOnly, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: false, + }, + } + + a.Requester = request.New(a.Name, + request.NewRateLimit(time.Second, anxAuthRate), + request.NewRateLimit(time.Second, anxUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + a.API.Endpoints.URLDefault = anxAPIURL + a.API.Endpoints.URL = a.API.Endpoints.URLDefault +} + +// Setup is run on startup to setup exchange with config values +func (a *ANX) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + a.SetEnabled(false) + return nil + } + + return a.SetupDefaults(exch) +} + // Start starts the ANX go routine func (a *ANX) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -27,52 +123,46 @@ func (a *ANX) Start(wg *sync.WaitGroup) { // Run implements the ANX wrapper func (a *ANX) Run() { if a.Verbose { - log.Debugf("%s polling delay: %ds.\n", a.GetName(), a.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", a.GetName(), len(a.EnabledPairs), a.EnabledPairs) + a.PrintEnabledPairs() } - tradablePairs, err := a.GetTradablePairs() - if err != nil { - log.Debugf("%s Failed to get available symbols.\n", a.GetName()) - } else { - forceUpgrade := false - if !common.StringDataContains(a.EnabledPairs.Strings(), "_") || - !common.StringDataContains(a.AvailablePairs.Strings(), "_") { - forceUpgrade = true - } + forceUpdate := false + if !common.StringDataContains(a.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "_") || + !common.StringDataContains(a.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "_") { + enabledPairs := currency.NewPairsFromStrings([]string{"BTC_USD,BTC_HKD,BTC_EUR,BTC_CAD,BTC_AUD,BTC_SGD,BTC_JPY,BTC_GBP,BTC_NZD,LTC_BTC,DOG_EBTC,STR_BTC,XRP_BTC"}) + log.Warn("WARNING: Enabled pairs for ANX reset due to config upgrade, please enable the ones you would like again.") - if forceUpgrade { - newPairs := []string{"BTC_USD,BTC_HKD,BTC_EUR,BTC_CAD,BTC_AUD,BTC_SGD,BTC_JPY,BTC_GBP,BTC_NZD,LTC_BTC,DOG_EBTC,STR_BTC,XRP_BTC"} - - var enabledPairs currency.Pairs - for _, p := range newPairs { - enabledPairs = append(enabledPairs, - currency.NewPairDelimiter(p, "_")) - } - - log.Warn("Enabled pairs for ANX reset due to config upgrade, please enable the ones you would like again.") - - err = a.UpdateCurrencies(enabledPairs, true, true) - if err != nil { - log.Errorf("%s Failed to get config.\n", a.GetName()) - } - } - - var exchangeProducts currency.Pairs - for _, p := range tradablePairs { - exchangeProducts = append(exchangeProducts, - currency.NewPairDelimiter(p, "_")) - } - - err = a.UpdateCurrencies(exchangeProducts, false, forceUpgrade) + forceUpdate = true + err := a.UpdatePairs(enabledPairs, assets.AssetTypeSpot, true, true) if err != nil { - log.Errorf("%s Failed to get config.\n", a.GetName()) + log.Errorf("%s failed to update currencies.\n", a.GetName()) + return } } + + if !a.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { + return + } + + err := a.UpdateTradablePairs(forceUpdate) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", a.GetName(), err) + } } -// GetTradablePairs returns a list of available -func (a *ANX) GetTradablePairs() ([]string, error) { +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (a *ANX) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := a.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return a.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (a *ANX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { result, err := a.GetCurrencies() if err != nil { return nil, err @@ -87,9 +177,9 @@ func (a *ANX) GetTradablePairs() ([]string, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (a *ANX) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (a *ANX) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - tick, err := a.GetTicker(exchange.FormatExchangeCurrency(a.GetName(), p).String()) + tick, err := a.GetTicker(a.FormatExchangeCurrency(p, assetType).String()) if err != nil { return tickerPrice, err } @@ -158,8 +248,8 @@ func (a *ANX) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, err return ticker.GetTicker(a.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (a *ANX) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (a *ANX) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(a.GetName(), p, assetType) if err != nil { return a.UpdateTicker(p, assetType) @@ -167,8 +257,8 @@ func (a *ANX) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, e return tickerNew, nil } -// GetOrderbookEx returns the orderbook for a currency pair -func (a *ANX) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns the orderbook for a currency pair +func (a *ANX) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(a.GetName(), p, assetType) if err != nil { return a.UpdateOrderbook(p, assetType) @@ -177,9 +267,9 @@ func (a *ANX) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (a *ANX) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (a *ANX) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := a.GetDepth(exchange.FormatExchangeCurrency(a.GetName(), p).String()) + orderbookNew, err := a.GetDepth(a.FormatExchangeCurrency(p, assetType).String()) if err != nil { return orderBook, err } @@ -244,10 +334,8 @@ func (a *ANX) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (a *ANX) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (a *ANX) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -342,20 +430,20 @@ func (a *ANX) GetDepositAddress(cryptocurrency currency.Code, _ string) (string, // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (a *ANX) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (a *ANX) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return a.Send(withdrawRequest.Currency.String(), withdrawRequest.Address, "", fmt.Sprintf("%v", withdrawRequest.Amount)) } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is // submitted -func (a *ANX) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (a *ANX) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { // Fiat withdrawals available via website return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is // submitted -func (a *ANX) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (a *ANX) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { // Fiat withdrawals available via website return "", common.ErrFunctionNotSupported } @@ -367,7 +455,7 @@ func (a *ANX) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (a *ANX) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (a.APIKey == "" || a.APISecret == "") && // Todo check connection status + if !a.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -389,7 +477,8 @@ func (a *ANX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]ex orderDetail := exchange.OrderDetail{ Amount: resp[i].TradedCurrencyAmount, CurrencyPair: currency.NewPairWithDelimiter(resp[i].TradedCurrency, - resp[i].SettlementCurrency, a.ConfigCurrencyPairFormat.Delimiter), + resp[i].SettlementCurrency, + a.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), OrderDate: orderDate, Exchange: a.Name, ID: resp[i].OrderID, @@ -405,7 +494,6 @@ func (a *ANX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]ex exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } @@ -432,7 +520,7 @@ func (a *ANX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]ex Status: resp[i].OrderStatus, CurrencyPair: currency.NewPairWithDelimiter(resp[i].TradedCurrency, resp[i].SettlementCurrency, - a.ConfigCurrencyPairFormat.Delimiter), + a.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), } orders = append(orders, orderDetail) @@ -442,7 +530,6 @@ func (a *ANX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]ex exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } diff --git a/exchanges/assets/assets.go b/exchanges/assets/assets.go new file mode 100644 index 00000000..ea95eac8 --- /dev/null +++ b/exchanges/assets/assets.go @@ -0,0 +1,114 @@ +package assets + +import ( + "strings" + + "github.com/thrasher-/gocryptotrader/common" +) + +// AssetType stores the asset type +type AssetType string + +// AssetTypes stores a list of assets +type AssetTypes []AssetType + +// Const vars for asset package +const ( + AssetTypeSpot = AssetType("spot") + AssetTypeMargin = AssetType("margin") + AssetTypeIndex = AssetType("index") + AssetTypeBinary = AssetType("binary") + AssetTypePerpetualContract = AssetType("perpetualcontract") + AssetTypePerpetualSwap = AssetType("perpetualswap") + AssetTypeFutures = AssetType("futures") + AssetTypeUpsideProfitContract = AssetType("upsideprofitcontract") + AssetTypeDownsideProfitContract = AssetType("downsideprofitcontract") +) + +// Supported returns a list of supported asset types +func Supported() AssetTypes { + var a AssetTypes + a = append(a, + AssetTypeSpot, + AssetTypeMargin, + AssetTypeIndex, + AssetTypeBinary, + AssetTypePerpetualContract, + AssetTypePerpetualSwap, + AssetTypeFutures, + AssetTypeUpsideProfitContract, + AssetTypeDownsideProfitContract, + ) + return a +} + +// returns an AssetType to string +func (a AssetType) String() string { + return string(a) +} + +// Strings converts an asset type array to a string array +func (a AssetTypes) Strings() []string { + var assets []string + for x := range a { + assets = append(assets, string(a[x])) + } + return assets +} + +// Contains returns whether or not the supplied asset exists +// in the list of AssetTypes +func (a AssetTypes) Contains(asset AssetType) bool { + if !IsValid(asset) { + return false + } + + for x := range a { + if a[x] == asset { + return true + } + } + + return false +} + +// JoinToString joins an asset type array and converts it to a string +// with the supplied separator +func (a AssetTypes) JoinToString(separator string) string { + return strings.Join(a.Strings(), separator) +} + +// IsValid returns whether or not the supplied asset type is valid or +// not +func IsValid(input AssetType) bool { + a := Supported() + for x := range a { + if strings.EqualFold(a[x].String(), input.String()) { + return true + } + } + return false +} + +// New takes an input of asset types as string and returns an AssetTypes +// array +func New(input string) AssetTypes { + if !common.StringContains(input, ",") { + if IsValid(AssetType(input)) { + return AssetTypes{ + AssetType(input), + } + } + return nil + } + + assets := strings.Split(input, ",") + var result AssetTypes + for x := range assets { + if !IsValid(AssetType(assets[x])) { + return nil + } + result = append(result, AssetType(assets[x])) + } + return result +} diff --git a/exchanges/assets/assets_test.go b/exchanges/assets/assets_test.go new file mode 100644 index 00000000..b7963ee5 --- /dev/null +++ b/exchanges/assets/assets_test.go @@ -0,0 +1,81 @@ +package assets + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/common" +) + +func TestString(t *testing.T) { + a := AssetTypeSpot + if a.String() != "spot" { + t.Fatal("Test failed - TestString returned an unexpected result") + } +} + +func TestToStringArray(t *testing.T) { + a := AssetTypes{AssetTypeSpot, AssetTypeFutures} + result := a.Strings() + for x := range a { + if !common.StringDataCompare(result, a[x].String()) { + t.Fatal("Test failed - TestToStringArray returned an unexpected result") + } + } +} + +func TestContains(t *testing.T) { + a := AssetTypes{AssetTypeSpot, AssetTypeFutures} + if a.Contains("meow") { + t.Fatal("Test failed - TestContains returned an unexpected result") + } + + if !a.Contains(AssetTypeSpot) { + t.Fatal("Test failed - TestContains returned an unexpected result") + } + + if a.Contains(AssetTypeBinary) { + t.Fatal("Test failed - TestContains returned an unexpected result") + } +} + +func TestJoinToString(t *testing.T) { + a := AssetTypes{AssetTypeSpot, AssetTypeFutures} + if a.JoinToString(",") != "spot,futures" { + t.Fatal("Test failed - TestJoinToString returned an unexpected result") + } +} + +func TestIsValid(t *testing.T) { + if IsValid("rawr") { + t.Fatal("Test failed - TestIsValid returned an unexpected result") + } + + if !IsValid(AssetTypeSpot) { + t.Fatal("Test failed - TestIsValid returned an unexpected result") + } +} + +func TestNew(t *testing.T) { + a := New("Spota") + if a != nil { + t.Fatal("Test failed - TestNew returned an unexpected result") + } + + a = New("SpOt") + if a == nil { + t.Fatal("Test failed - TestNew returned an unexpected result") + } + + a = New("spot,futures") + if a.JoinToString(",") != "spot,futures" { + t.Fatal("Test failed - TestNew returned an unexpected result") + } + + if a := New("Spot_rawr"); a != nil { + t.Fatal("Test failed - TestNew returned an unexpected result") + } + + if a := New("Spot,Rawr"); a != nil { + t.Fatal("Test failed - TestNew returned an unexpected result") + } +} diff --git a/exchanges/binance/README.md b/exchanges/binance/README.md index a09706fe..ec905c71 100644 --- a/exchanges/binance/README.md +++ b/exchanges/binance/README.md @@ -48,22 +48,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Binance" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Binance" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 74d6d37c..0d5a05e6 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -12,24 +12,13 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" + "github.com/thrasher-/gocryptotrader/exchanges/assets" log "github.com/thrasher-/gocryptotrader/logger" ) -// Binance is the overarching type across the Bithumb package -type Binance struct { - exchange.Base - WebsocketConn *websocket.Conn - - // Valid string list that is required by the exchange - validLimits []int - validIntervals []TimeInterval -} - const ( apiURL = "https://api.binance.com" @@ -71,110 +60,21 @@ const ( binanceUnauthRate = 0 ) -// SetDefaults sets the basic defaults for Binance -func (b *Binance) SetDefaults() { - b.Name = "Binance" - b.Enabled = false - b.Verbose = false - b.RESTPollingDelay = 10 - b.RequestCurrencyPairFormat.Delimiter = "" - b.RequestCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Delimiter = "-" - b.ConfigCurrencyPairFormat.Uppercase = true - b.AssetTypes = []string{ticker.Spot} - b.SupportsAutoPairUpdating = true - b.SupportsRESTTickerBatching = true - b.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.NoFiatWithdrawals - b.SetValues() - b.Requester = request.New(b.Name, - request.NewRateLimit(time.Second, binanceAuthRate), - request.NewRateLimit(time.Second, binanceUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - b.APIUrlDefault = apiURL - b.APIUrl = b.APIUrlDefault - b.WebsocketInit() - b.WebsocketURL = binanceDefaultWebsocketURL - b.Websocket.Functionality = exchange.WebsocketTradeDataSupported | - exchange.WebsocketTickerSupported | - exchange.WebsocketKlineSupported | - exchange.WebsocketOrderbookSupported -} +// Binance is the overarching type across the Bithumb package +type Binance struct { + exchange.Base + WebsocketConn *websocket.Conn -// Setup takes in the supplied exchange configuration details and sets params -func (b *Binance) 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.SetHTTPClientTimeout(exch.HTTPTimeout) - b.SetHTTPClientUserAgent(exch.HTTPUserAgent) - b.RESTPollingDelay = exch.RESTPollingDelay - b.Verbose = exch.Verbose - b.HTTPDebugging = exch.HTTPDebugging - b.Websocket.SetWsStatusAndConnection(exch.Websocket) - b.BaseCurrencies = exch.BaseCurrencies - b.AvailablePairs = exch.AvailablePairs - b.EnabledPairs = exch.EnabledPairs - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = b.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = b.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = b.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = b.WebsocketSetup(b.WSConnect, - nil, - nil, - exch.Name, - exch.Websocket, - exch.Verbose, - binanceDefaultWebsocketURL, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - -// GetExchangeValidCurrencyPairs returns the full pair list from the exchange -// at the moment do not integrate with config currency pairs automatically -func (b *Binance) GetExchangeValidCurrencyPairs() ([]string, error) { - var validCurrencyPairs []string - - info, err := b.GetExchangeInfo() - if err != nil { - return nil, err - } - - for i := range info.Symbols { - if info.Symbols[i].Status == "TRADING" { - validCurrencyPairs = append(validCurrencyPairs, info.Symbols[i].BaseAsset+"-"+info.Symbols[i].QuoteAsset) - } - } - return validCurrencyPairs, nil + // Valid string list that is required by the exchange + validLimits []int + validIntervals []TimeInterval } // GetExchangeInfo returns exchange information. Check binance_types for more // information func (b *Binance) GetExchangeInfo() (ExchangeInfo, error) { var resp ExchangeInfo - path := b.APIUrl + exchangeInfo + path := b.API.Endpoints.URL + exchangeInfo return resp, b.SendHTTPRequest(path, &resp) } @@ -190,15 +90,12 @@ func (b *Binance) GetOrderBook(obd OrderBookDataRequestParams) (OrderBook, error if err := b.CheckLimit(obd.Limit); err != nil { return orderbook, err } - if err := b.CheckSymbol(obd.Symbol); err != nil { - return orderbook, err - } params := url.Values{} params.Set("symbol", common.StringToUpper(obd.Symbol)) params.Set("limit", fmt.Sprintf("%d", obd.Limit)) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, orderBookDepth, params.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, orderBookDepth, params.Encode()) if err := b.SendHTTPRequest(path, &resp); err != nil { return orderbook, err @@ -249,7 +146,7 @@ func (b *Binance) GetRecentTrades(rtr RecentTradeRequestParams) ([]RecentTrade, params.Set("symbol", common.StringToUpper(rtr.Symbol)) params.Set("limit", fmt.Sprintf("%d", rtr.Limit)) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, recentTrades, params.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, recentTrades, params.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -271,7 +168,7 @@ func (b *Binance) GetHistoricalTrades(symbol string, limit int, fromID int64) ([ params.Set("limit", strconv.Itoa(limit)) params.Set("fromid", strconv.FormatInt(fromID, 10)) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, historicalTrades, params.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, historicalTrades, params.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -286,15 +183,12 @@ func (b *Binance) GetAggregatedTrades(symbol string, limit int) ([]AggregatedTra if err := b.CheckLimit(limit); err != nil { return resp, err } - if err := b.CheckSymbol(symbol); err != nil { - return resp, err - } params := url.Values{} params.Set("symbol", common.StringToUpper(symbol)) params.Set("limit", strconv.Itoa(limit)) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, aggregatedTrades, params.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, aggregatedTrades, params.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -324,7 +218,7 @@ func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) { params.Set("endTime", strconv.FormatInt(arg.EndTime, 10)) } - path := fmt.Sprintf("%s%s?%s", b.APIUrl, candleStick, params.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, candleStick, params.Encode()) if err := b.SendHTTPRequest(path, &resp); err != nil { return kline, err @@ -368,15 +262,10 @@ func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) { // symbol: string of currency pair func (b *Binance) GetAveragePrice(symbol string) (AveragePrice, error) { resp := AveragePrice{} - - if err := b.CheckSymbol(symbol); err != nil { - return resp, err - } - params := url.Values{} params.Set("symbol", common.StringToUpper(symbol)) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, averagePrice, params.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, averagePrice, params.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -386,15 +275,10 @@ func (b *Binance) GetAveragePrice(symbol string) (AveragePrice, error) { // symbol: string of currency pair func (b *Binance) GetPriceChangeStats(symbol string) (PriceChangeStats, error) { resp := PriceChangeStats{} - - if err := b.CheckSymbol(symbol); err != nil { - return resp, err - } - params := url.Values{} params.Set("symbol", common.StringToUpper(symbol)) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, priceChange, params.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, priceChange, params.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -402,7 +286,7 @@ func (b *Binance) GetPriceChangeStats(symbol string) (PriceChangeStats, error) { // GetTickers returns the ticker data for the last 24 hrs func (b *Binance) GetTickers() ([]PriceChangeStats, error) { var resp []PriceChangeStats - path := fmt.Sprintf("%s%s", b.APIUrl, priceChange) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, priceChange) return resp, b.SendHTTPRequest(path, &resp) } @@ -411,15 +295,10 @@ func (b *Binance) GetTickers() ([]PriceChangeStats, error) { // symbol: string of currency pair func (b *Binance) GetLatestSpotPrice(symbol string) (SymbolPrice, error) { resp := SymbolPrice{} - - if err := b.CheckSymbol(symbol); err != nil { - return resp, err - } - params := url.Values{} params.Set("symbol", common.StringToUpper(symbol)) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, symbolPrice, params.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, symbolPrice, params.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -429,15 +308,10 @@ func (b *Binance) GetLatestSpotPrice(symbol string) (SymbolPrice, error) { // symbol: string of currency pair func (b *Binance) GetBestPrice(symbol string) (BestPrice, error) { resp := BestPrice{} - - if err := b.CheckSymbol(symbol); err != nil { - return resp, err - } - params := url.Values{} params.Set("symbol", common.StringToUpper(symbol)) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, bestPrice, params.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, bestPrice, params.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -446,14 +320,14 @@ func (b *Binance) GetBestPrice(symbol string) (BestPrice, error) { func (b *Binance) NewOrder(o *NewOrderRequest) (NewOrderResponse, error) { var resp NewOrderResponse - path := fmt.Sprintf("%s%s", b.APIUrl, newOrder) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, newOrder) params := url.Values{} params.Set("symbol", o.Symbol) - params.Set("side", string(o.Side)) + params.Set("side", o.Side) params.Set("type", string(o.TradeType)) params.Set("quantity", strconv.FormatFloat(o.Quantity, 'f', -1, 64)) - if o.TradeType == "LIMIT" { + if o.TradeType == BinanceRequestParamsOrderLimit { params.Set("price", strconv.FormatFloat(o.Price, 'f', -1, 64)) } if o.TimeInForce != "" { @@ -490,7 +364,7 @@ func (b *Binance) NewOrder(o *NewOrderRequest) (NewOrderResponse, error) { func (b *Binance) CancelExistingOrder(symbol string, orderID int64, origClientOrderID string) (CancelOrderResponse, error) { var resp CancelOrderResponse - path := fmt.Sprintf("%s%s", b.APIUrl, cancelOrder) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, cancelOrder) params := url.Values{} params.Set("symbol", symbol) @@ -511,7 +385,9 @@ func (b *Binance) CancelExistingOrder(symbol string, orderID int64, origClientOr // is equal to the number of symbols currently trading on the exchange. func (b *Binance) OpenOrders(symbol string) ([]QueryOrderData, error) { var resp []QueryOrderData - path := fmt.Sprintf("%s%s", b.APIUrl, openOrders) + + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, openOrders) + params := url.Values{} if symbol != "" { @@ -531,7 +407,7 @@ func (b *Binance) OpenOrders(symbol string) ([]QueryOrderData, error) { func (b *Binance) AllOrders(symbol, orderID, limit string) ([]QueryOrderData, error) { var resp []QueryOrderData - path := fmt.Sprintf("%s%s", b.APIUrl, allOrders) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, allOrders) params := url.Values{} params.Set("symbol", common.StringToUpper(symbol)) @@ -552,7 +428,7 @@ func (b *Binance) AllOrders(symbol, orderID, limit string) ([]QueryOrderData, er func (b *Binance) QueryOrder(symbol, origClientOrderID string, orderID int64) (QueryOrderData, error) { var resp QueryOrderData - path := fmt.Sprintf("%s%s", b.APIUrl, queryOrder) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, queryOrder) params := url.Values{} params.Set("symbol", common.StringToUpper(symbol)) @@ -582,7 +458,7 @@ func (b *Binance) GetAccount() (*Account, error) { var resp response - path := fmt.Sprintf("%s%s", b.APIUrl, accountInfo) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, accountInfo) params := url.Values{} if err := b.SendAuthHTTPRequest(http.MethodGet, path, params, &resp); err != nil { @@ -603,7 +479,7 @@ func (b *Binance) SendHTTPRequest(path string, result interface{}) error { // SendAuthHTTPRequest sends an authenticated HTTP request func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, result interface{}) error { - if !b.AuthenticatedAPISupport { + if !b.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } @@ -614,11 +490,11 @@ func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, re params.Set("timestamp", strconv.FormatInt(time.Now().Unix()*1000, 10)) signature := params.Encode() - hmacSigned := common.GetHMAC(common.HashSHA256, []byte(signature), []byte(b.APISecret)) - hmacSignedStr := common.HexEncodeToString(hmacSigned) + hmacSigned := crypto.GetHMAC(crypto.HashSHA256, []byte(signature), []byte(b.API.Credentials.Secret)) + hmacSignedStr := crypto.HexEncodeToString(hmacSigned) headers := make(map[string]string) - headers["X-MBX-APIKEY"] = b.APIKey + headers["X-MBX-APIKEY"] = b.API.Credentials.Key if b.Verbose { log.Debugf("sent path: %s", path) @@ -659,10 +535,10 @@ func (b *Binance) CheckLimit(limit int) error { } // CheckSymbol checks value against a variable list -func (b *Binance) CheckSymbol(symbol string) error { - enPairs := b.GetAvailableCurrencies() +func (b *Binance) CheckSymbol(symbol string, assetType assets.AssetType) error { + enPairs := b.GetAvailablePairs(assetType) for x := range enPairs { - if exchange.FormatExchangeCurrency(b.Name, enPairs[x]).String() == symbol { + if b.FormatExchangeCurrency(enPairs[x], assetType).String() == symbol { return nil } } @@ -756,7 +632,7 @@ func getCryptocurrencyWithdrawalFee(c currency.Code) float64 { // WithdrawCrypto sends cryptocurrency to the address of your choosing func (b *Binance) WithdrawCrypto(asset, address, addressTag, name, amount string) (int64, error) { var resp WithdrawResponse - path := fmt.Sprintf("%s%s", b.APIUrl, withdraw) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, withdraw) params := url.Values{} params.Set("asset", asset) @@ -782,7 +658,7 @@ func (b *Binance) WithdrawCrypto(asset, address, addressTag, name, amount string // GetDepositAddressForCurrency retrieves the wallet address for a given currency func (b *Binance) GetDepositAddressForCurrency(currency string) (string, error) { - path := fmt.Sprintf("%s%s", b.APIUrl, depositAddress) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, depositAddress) resp := struct { Address string `json:"address"` diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 4ffd1559..d0ad8212 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) // Please supply your own keys here for due diligence testing @@ -30,17 +31,17 @@ func TestSetup(t *testing.T) { t.Error("Test Failed - Binance Setup() init error") } - binanceConfig.AuthenticatedAPISupport = true - binanceConfig.APIKey = apiKey - binanceConfig.APISecret = apiSecret - b.Setup(&binanceConfig) + binanceConfig.API.AuthenticatedSupport = true + binanceConfig.API.Credentials.Key = apiKey + binanceConfig.API.Credentials.Secret = apiSecret + b.Setup(binanceConfig) } -func TestGetExchangeValidCurrencyPairs(t *testing.T) { +func TestFetchTradablePairs(t *testing.T) { t.Parallel() - _, err := b.GetExchangeValidCurrencyPairs() + _, err := b.FetchTradablePairs(assets.AssetTypeSpot) if err != nil { - t.Error("Test Failed - Binance GetExchangeValidCurrencyPairs() error", err) + t.Error("Test Failed - Binance FetchTradablePairs(asset asets.AssetType) error", err) } } @@ -145,7 +146,7 @@ func TestNewOrder(t *testing.T) { } _, err := b.NewOrder(&NewOrderRequest{ Symbol: "BTCUSDT", - Side: BinanceRequestParamsSideSell, + Side: exchange.SellOrderSide.ToString(), TradeType: BinanceRequestParamsOrderLimit, TimeInForce: BinanceRequestParamsTimeGTC, Quantity: 0.01, @@ -394,11 +395,7 @@ func TestGetOrderHistory(t *testing.T) { // ----------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if b.APIKey != "" && b.APIKey != "Key" && - b.APISecret != "" && b.APISecret != "Secret" { - return true - } - return false + return b.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -410,7 +407,8 @@ func TestSubmitOrder(t *testing.T) { Base: currency.LTC, Quote: currency.BTC, } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, + exchange.LimitOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -495,11 +493,13 @@ func TestWithdraw(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } _, err := b.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) @@ -519,7 +519,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { @@ -535,7 +535,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index 66adb10e..05cc078a 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -280,7 +280,7 @@ type NewOrderRequest struct { // Symbol (currency pair to trade) Symbol string // Side Buy or Sell - Side RequestParamsSideType + Side string // TradeType (market or limit order) TradeType RequestParamsOrderType // TimeInForce specifies how long the order remains in effect. @@ -366,17 +366,6 @@ type Account struct { Balances []Balance `json:"balances"` } -// RequestParamsSideType trade order side (buy or sell) -type RequestParamsSideType string - -var ( - // BinanceRequestParamsSideBuy buy order type - BinanceRequestParamsSideBuy = RequestParamsSideType("BUY") - - // BinanceRequestParamsSideSell sell order type - BinanceRequestParamsSideSell = RequestParamsSideType("SELL") -) - // RequestParamsTimeForceType Time in force type RequestParamsTimeForceType string diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index b96fd35f..2bb61d0d 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -14,8 +14,8 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -29,7 +29,7 @@ var m sync.Mutex func (b *Binance) SeedLocalCache(p currency.Pair) error { var newOrderBook orderbook.Base - formattedPair := exchange.FormatExchangeCurrency(b.Name, p) + formattedPair := b.FormatExchangeCurrency(p, assets.AssetTypeSpot) orderbookNew, err := b.GetOrderBook( OrderBookDataRequestParams{ @@ -59,8 +59,7 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error { } newOrderBook.Pair = currency.NewPairFromString(formattedPair.String()) - newOrderBook.AssetType = ticker.Spot - + newOrderBook.AssetType = assets.AssetTypeSpot return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false) } @@ -118,7 +117,7 @@ func (b *Binance) UpdateLocalCache(ob *WebsocketDepthStream) error { currencyPair, updatedTime, b.GetName(), - "SPOT") + assets.AssetTypeSpot) } // WSConnect intiates a websocket connection @@ -130,18 +129,19 @@ func (b *Binance) WSConnect() error { var Dialer websocket.Dialer var err error + pairs := b.GetEnabledPairs(assets.AssetTypeSpot).Strings() tick := strings.ToLower( strings.Replace( - strings.Join(b.EnabledPairs.Strings(), "@ticker/"), "-", "", -1)) + "@ticker" + strings.Join(pairs, "@ticker/"), "-", "", -1)) + "@ticker" trade := strings.ToLower( strings.Replace( - strings.Join(b.EnabledPairs.Strings(), "@trade/"), "-", "", -1)) + "@trade" + strings.Join(pairs, "@trade/"), "-", "", -1)) + "@trade" kline := strings.ToLower( strings.Replace( - strings.Join(b.EnabledPairs.Strings(), "@kline_1m/"), "-", "", -1)) + "@kline_1m" + strings.Join(pairs, "@kline_1m/"), "-", "", -1)) + "@kline_1m" depth := strings.ToLower( strings.Replace( - strings.Join(b.EnabledPairs.Strings(), "@depth/"), "-", "", -1)) + "@depth" + strings.Join(pairs, "@depth/"), "-", "", -1)) + "@depth" wsurl := b.Websocket.GetWebsocketURL() + "/stream?streams=" + @@ -164,7 +164,7 @@ func (b *Binance) WSConnect() error { Dialer.Proxy = http.ProxyURL(u) } - for _, ePair := range b.GetEnabledCurrencies() { + for _, ePair := range b.GetEnabledPairs(assets.AssetTypeSpot) { err = b.SeedLocalCache(ePair) if err != nil { return err @@ -254,7 +254,7 @@ func (b *Binance) WsHandleData() { Price: price, Amount: amount, Exchange: b.GetName(), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Side: trade.EventType, } continue @@ -272,7 +272,7 @@ func (b *Binance) WsHandleData() { wsTicker.Timestamp = time.Unix(t.EventTime/1000, 0) wsTicker.Pair = currency.NewPairFromString(t.Symbol) - wsTicker.AssetType = ticker.Spot + wsTicker.AssetType = assets.AssetTypeSpot wsTicker.Exchange = b.GetName() wsTicker.ClosePrice, _ = strconv.ParseFloat(t.CurrDayClose, 64) wsTicker.Quantity, _ = strconv.ParseFloat(t.TotalTradedVolume, 64) @@ -297,7 +297,7 @@ func (b *Binance) WsHandleData() { wsKline.Timestamp = time.Unix(0, kline.EventTime) wsKline.Pair = currency.NewPairFromString(kline.Symbol) - wsKline.AssetType = ticker.Spot + wsKline.AssetType = assets.AssetTypeSpot wsKline.Exchange = b.GetName() wsKline.StartTime = time.Unix(0, kline.Kline.StartTime) wsKline.CloseTime = time.Unix(0, kline.Kline.CloseTime) @@ -331,7 +331,7 @@ func (b *Binance) WsHandleData() { b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: currencyPair, - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Exchange: b.GetName(), } continue diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index d7de79f8..ec0b8e31 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -9,14 +9,117 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) -// Start starts the OKEX go routine +// GetDefaultConfig returns a default exchange config +func (b *Binance) GetDefaultConfig() (*config.ExchangeConfig, error) { + b.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = b.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = b.BaseCurrencies + + err := b.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if b.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = b.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets the basic defaults for Binance +func (b *Binance) SetDefaults() { + b.Name = "Binance" + b.Enabled = true + b.Verbose = true + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + b.SetValues() + + b.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + } + + b.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + b.Requester = request.New(b.Name, + request.NewRateLimit(time.Second, binanceAuthRate), + request.NewRateLimit(time.Second, binanceUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + b.API.Endpoints.URLDefault = apiURL + b.API.Endpoints.URL = b.API.Endpoints.URLDefault + b.WebsocketInit() + b.API.Endpoints.WebsocketURL = binanceDefaultWebsocketURL + b.Websocket.Functionality = exchange.WebsocketTradeDataSupported | + exchange.WebsocketTickerSupported | + exchange.WebsocketKlineSupported | + exchange.WebsocketOrderbookSupported +} + +// Setup takes in the supplied exchange configuration details and sets params +func (b *Binance) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + b.SetEnabled(false) + return nil + } + + err := b.SetupDefaults(exch) + if err != nil { + return err + } + + return b.WebsocketSetup(b.WSConnect, + nil, + nil, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + binanceDefaultWebsocketURL, + exch.API.Endpoints.WebsocketURL) +} + +// Start starts the Binance go routine func (b *Binance) Start(wg *sync.WaitGroup) { wg.Add(1) go func() { @@ -25,68 +128,75 @@ func (b *Binance) Start(wg *sync.WaitGroup) { }() } -// Run implements the OKEX wrapper +// Run implements the Binance wrapper func (b *Binance) Run() { if b.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n%s polling delay: %ds.\n%s %d currencies enabled: %s.\n", - b.GetName(), - common.IsEnabled(b.Websocket.IsEnabled()), - b.Websocket.GetWebsocketURL(), - b.GetName(), - b.RESTPollingDelay, - b.GetName(), - len(b.EnabledPairs), - b.EnabledPairs) + log.Debugf("%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.Websocket.GetWebsocketURL()) + b.PrintEnabledPairs() } - symbols, err := b.GetExchangeValidCurrencyPairs() - if err != nil { - log.Errorf("%s Failed to get exchange info.\n", b.GetName()) - } else { - forceUpgrade := false - if !common.StringDataContains(b.EnabledPairs.Strings(), "-") || - !common.StringDataContains(b.AvailablePairs.Strings(), "-") { - forceUpgrade = true - } + forceUpdate := false + if !common.StringDataContains(b.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "-") || + !common.StringDataContains(b.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "-") { + enabledPairs := currency.NewPairsFromStrings([]string{"BTC-USDT"}) + log.Warn("WARNING: Available pairs for Binance reset due to config upgrade, please enable the ones you would like again") + forceUpdate = true - if forceUpgrade { - enabledPairs := currency.Pairs{currency.Pair{ - Base: currency.BTC, - Quote: currency.USDT, - Delimiter: "-", - }} - - log.Warn("Available pairs for Binance reset due to config upgrade, please enable the ones you would like again") - - err = b.UpdateCurrencies(enabledPairs, true, true) - if err != nil { - log.Errorf("%s Failed to get config.\n", b.GetName()) - } - } - - var newSymbols currency.Pairs - for _, p := range symbols { - newSymbols = append(newSymbols, - currency.NewPairFromString(p)) - } - - err = b.UpdateCurrencies(newSymbols, false, forceUpgrade) + err := b.UpdatePairs(enabledPairs, assets.AssetTypeSpot, true, true) if err != nil { - log.Errorf("%s Failed to get config.\n", b.GetName()) + log.Errorf("%s failed to update currencies. Err: %s\n", b.Name, err) } } + + if !b.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { + return + } + + err := b.UpdateTradablePairs(forceUpdate) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + } +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (b *Binance) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + var validCurrencyPairs []string + + info, err := b.GetExchangeInfo() + if err != nil { + return nil, err + } + + for x := range info.Symbols { + if info.Symbols[x].Status == "TRADING" { + validCurrencyPairs = append(validCurrencyPairs, + info.Symbols[x].BaseAsset+"-"+info.Symbols[x].QuoteAsset) + } + } + return validCurrencyPairs, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (b *Binance) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Binance) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (b *Binance) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := b.GetTickers() if err != nil { return tickerPrice, err } - for _, x := range b.GetEnabledCurrencies() { - curr := exchange.FormatExchangeCurrency(b.Name, x) + for _, x := range b.GetEnabledPairs(assetType) { + curr := b.FormatExchangeCurrency(x, assetType) for y := range tick { if tick[y].Symbol != curr.String() { continue @@ -104,8 +214,8 @@ func (b *Binance) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, return ticker.GetTicker(b.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (b *Binance) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (b *Binance) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -113,19 +223,20 @@ func (b *Binance) GetTickerPrice(p currency.Pair, assetType string) (ticker.Pric return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (b *Binance) GetOrderbookEx(currency currency.Pair, assetType string) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), currency, assetType) +// FetchOrderbook returns orderbook base on the currency pair +func (b *Binance) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { + ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { - return b.UpdateOrderbook(currency, assetType) + return b.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Binance) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (b *Binance) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := b.GetOrderBook(OrderBookDataRequestParams{Symbol: exchange.FormatExchangeCurrency(b.Name, p).String(), Limit: 1000}) + orderbookNew, err := b.GetOrderBook(OrderBookDataRequestParams{Symbol: b.FormatExchangeCurrency(p, + assetType).String(), Limit: 1000}) if err != nil { return orderBook, err } @@ -196,20 +307,19 @@ func (b *Binance) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Binance) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - return resp, common.ErrNotYetImplemented +func (b *Binance) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order func (b *Binance) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - var sideType RequestParamsSideType + var sideType string if side == exchange.BuyOrderSide { - sideType = BinanceRequestParamsSideBuy + sideType = exchange.BuyOrderSide.ToString() } else { - sideType = BinanceRequestParamsSideSell + sideType = exchange.SellOrderSide.ToString() } var requestParamsOrderType RequestParamsOrderType @@ -258,9 +368,8 @@ func (b *Binance) CancelOrder(order *exchange.OrderCancellation) error { return err } - _, err = b.CancelExistingOrder(exchange.FormatExchangeCurrency(b.Name, order.CurrencyPair).String(), - orderIDInt, - order.AccountID) + _, err = b.CancelExistingOrder(b.FormatExchangeCurrency(order.CurrencyPair, + order.AssetType).String(), orderIDInt, order.AccountID) return err } @@ -298,7 +407,7 @@ func (b *Binance) GetDepositAddress(cryptocurrency currency.Code, _ string) (str // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (b *Binance) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Binance) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { amountStr := strconv.FormatFloat(withdrawRequest.Amount, 'f', -1, 64) id, err := b.WithdrawCrypto(withdrawRequest.Currency.String(), withdrawRequest.Address, withdrawRequest.AddressTag, withdrawRequest.Description, amountStr) @@ -307,13 +416,13 @@ func (b *Binance) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.Withdraw // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (b *Binance) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Binance) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (b *Binance) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Binance) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -324,7 +433,7 @@ func (b *Binance) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (b *Binance) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (b.APIKey == "" || b.APISecret == "") && // Todo check connection status + if !b.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -339,7 +448,8 @@ func (b *Binance) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( var orders []exchange.OrderDetail for _, c := range getOrdersRequest.Currencies { - resp, err := b.OpenOrders(exchange.FormatExchangeCurrency(b.Name, c).String()) + resp, err := b.OpenOrders(b.FormatExchangeCurrency(c, + assets.AssetTypeSpot).String()) if err != nil { return nil, err } @@ -366,7 +476,6 @@ func (b *Binance) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - return orders, nil } @@ -379,7 +488,8 @@ func (b *Binance) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( var orders []exchange.OrderDetail for _, c := range getOrdersRequest.Currencies { - resp, err := b.AllOrders(exchange.FormatExchangeCurrency(b.Name, c).String(), "", "1000") + resp, err := b.AllOrders(b.FormatExchangeCurrency(c, + assets.AssetTypeSpot).String(), "", "1000") if err != nil { return nil, err } @@ -410,7 +520,6 @@ func (b *Binance) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - return orders, nil } diff --git a/exchanges/bitfinex/README.md b/exchanges/bitfinex/README.md index f846c4ec..52c75710 100644 --- a/exchanges/bitfinex/README.md +++ b/exchanges/bitfinex/README.md @@ -48,22 +48,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bitfinex" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bitfinex" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 8769217e..e4836dad 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -11,11 +11,9 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -91,91 +89,10 @@ type Bitfinex struct { wsRequestMtx sync.Mutex } -// SetDefaults sets the basic defaults for bitfinex -func (b *Bitfinex) SetDefaults() { - b.Name = "Bitfinex" - b.Enabled = false - b.Verbose = false - b.RESTPollingDelay = 10 - b.WebsocketSubdChannels = make(map[int]WebsocketChanInfo) - b.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithAPIPermission | - exchange.AutoWithdrawFiatWithAPIPermission - b.RequestCurrencyPairFormat.Delimiter = "" - b.RequestCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Uppercase = true - b.AssetTypes = []string{ticker.Spot} - b.SupportsAutoPairUpdating = true - b.SupportsRESTTickerBatching = true - b.Requester = request.New(b.Name, - request.NewRateLimit(time.Second*60, bitfinexAuthRate), - request.NewRateLimit(time.Second*60, bitfinexUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - b.APIUrlDefault = bitfinexAPIURLBase - b.APIUrl = b.APIUrlDefault - b.WebsocketInit() - b.Websocket.Functionality = exchange.WebsocketTickerSupported | - exchange.WebsocketTradeDataSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup takes in the supplied exchange configuration details and sets params -func (b *Bitfinex) 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.SetHTTPClientTimeout(exch.HTTPTimeout) - b.SetHTTPClientUserAgent(exch.HTTPUserAgent) - b.RESTPollingDelay = exch.RESTPollingDelay - b.Verbose = exch.Verbose - b.HTTPDebugging = exch.HTTPDebugging - b.Websocket.SetWsStatusAndConnection(exch.Websocket) - b.BaseCurrencies = exch.BaseCurrencies - b.AvailablePairs = exch.AvailablePairs - b.EnabledPairs = exch.EnabledPairs - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = b.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = b.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = b.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = b.WebsocketSetup(b.WsConnect, - b.Subscribe, - b.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - bitfinexWebsocket, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetPlatformStatus returns the Bifinex platform status func (b *Bitfinex) GetPlatformStatus() (int, error) { var response []interface{} - path := fmt.Sprintf("%s/v%s/%s", b.APIUrl, bitfinexAPIVersion2, + path := fmt.Sprintf("%s/v%s/%s", b.API.Endpoints.URL, bitfinexAPIVersion2, bitfinexPlatformStatus) err := b.SendHTTPRequest(path, &response, b.Verbose) @@ -204,7 +121,7 @@ func (b *Bitfinex) GetLatestSpotPrice(symbol string) (float64, error) { // GetTicker returns ticker information func (b *Bitfinex) GetTicker(symbol string) (Ticker, error) { response := Ticker{} - path := common.EncodeURLValues(b.APIUrl+bitfinexAPIVersion+bitfinexTicker+symbol, + path := common.EncodeURLValues(b.API.Endpoints.URL+bitfinexAPIVersion+bitfinexTicker+symbol, url.Values{}) if err := b.SendHTTPRequest(path, &response, b.Verbose); err != nil { @@ -224,7 +141,7 @@ func (b *Bitfinex) GetTickerV2(symb string) (Tickerv2, error) { var tick Tickerv2 path := fmt.Sprintf("%s/v%s/%s/%s", - b.APIUrl, + b.API.Endpoints.URL, bitfinexAPIVersion2, bitfinexTickerV2, symb) @@ -271,7 +188,7 @@ func (b *Bitfinex) GetTickersV2(symbols string) ([]Tickersv2, error) { v.Set("symbols", symbols) path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s", - b.APIUrl, + b.API.Endpoints.URL, bitfinexAPIVersion2, bitfinexTickersV2), v) @@ -319,7 +236,7 @@ func (b *Bitfinex) GetTickersV2(symbols string) ([]Tickersv2, error) { // GetStats returns various statistics about the requested pair func (b *Bitfinex) GetStats(symbol string) ([]Stat, error) { var response []Stat - path := fmt.Sprint(b.APIUrl + bitfinexAPIVersion + bitfinexStats + symbol) + path := fmt.Sprint(b.API.Endpoints.URL + bitfinexAPIVersion + bitfinexStats + symbol) return response, b.SendHTTPRequest(path, &response, b.Verbose) } @@ -329,7 +246,7 @@ func (b *Bitfinex) GetStats(symbol string) ([]Stat, error) { // symbol - example "USD" func (b *Bitfinex) GetFundingBook(symbol string) (FundingBook, error) { response := FundingBook{} - path := fmt.Sprint(b.APIUrl + bitfinexAPIVersion + bitfinexLendbook + symbol) + path := fmt.Sprint(b.API.Endpoints.URL + bitfinexAPIVersion + bitfinexLendbook + symbol) if err := b.SendHTTPRequest(path, &response, b.Verbose); err != nil { return response, err @@ -350,7 +267,7 @@ func (b *Bitfinex) GetFundingBook(symbol string) (FundingBook, error) { func (b *Bitfinex) GetOrderbook(currencyPair string, values url.Values) (Orderbook, error) { response := Orderbook{} path := common.EncodeURLValues( - b.APIUrl+bitfinexAPIVersion+bitfinexOrderbook+currencyPair, + b.API.Endpoints.URL+bitfinexAPIVersion+bitfinexOrderbook+currencyPair, values, ) return response, b.SendHTTPRequest(path, &response, b.Verbose) @@ -365,7 +282,7 @@ func (b *Bitfinex) GetOrderbook(currencyPair string, values url.Values) (Orderbo func (b *Bitfinex) GetOrderbookV2(symbol, precision string, values url.Values) (OrderbookV2, error) { var response [][]interface{} var book OrderbookV2 - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", b.APIUrl, + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", b.API.Endpoints.URL, bitfinexAPIVersion2, bitfinexOrderbookV2, symbol, precision), values) err := b.SendHTTPRequest(path, &response, b.Verbose) if err != nil { @@ -412,7 +329,7 @@ func (b *Bitfinex) GetOrderbookV2(symbol, precision string, values url.Values) ( func (b *Bitfinex) GetTrades(currencyPair string, values url.Values) ([]TradeStructure, error) { var response []TradeStructure path := common.EncodeURLValues( - b.APIUrl+bitfinexAPIVersion+bitfinexTrades+currencyPair, + b.API.Endpoints.URL+bitfinexAPIVersion+bitfinexTrades+currencyPair, values, ) return response, b.SendHTTPRequest(path, &response, b.Verbose) @@ -447,10 +364,10 @@ func (b *Bitfinex) GetTradesV2(currencyPair string, timestampStart, timestampEnd tempHistory.Amount = data[2].(float64) tempHistory.Price = data[3].(float64) tempHistory.Exchange = b.Name - tempHistory.Type = "BUY" + tempHistory.Type = exchange.BuyOrderSide.ToString() if tempHistory.Amount < 0 { - tempHistory.Type = "SELL" + tempHistory.Type = exchange.SellOrderSide.ToString() tempHistory.Amount *= -1 } @@ -477,9 +394,8 @@ func (b *Bitfinex) GetLendbook(symbol string, values url.Values) (Lendbook, erro if len(symbol) == 6 { symbol = symbol[:3] } - path := common.EncodeURLValues(b.APIUrl+bitfinexAPIVersion+bitfinexLendbook+symbol, + path := common.EncodeURLValues(b.API.Endpoints.URL+bitfinexAPIVersion+bitfinexLendbook+symbol, values) - return response, b.SendHTTPRequest(path, &response, b.Verbose) } @@ -489,16 +405,15 @@ func (b *Bitfinex) GetLendbook(symbol string, values url.Values) (Lendbook, erro // Symbol - example "USD" func (b *Bitfinex) GetLends(symbol string, values url.Values) ([]Lends, error) { var response []Lends - path := common.EncodeURLValues(b.APIUrl+bitfinexAPIVersion+bitfinexLends+symbol, + path := common.EncodeURLValues(b.API.Endpoints.URL+bitfinexAPIVersion+bitfinexLends+symbol, values) - return response, b.SendHTTPRequest(path, &response, b.Verbose) } // GetSymbols returns the available currency pairs on the exchange func (b *Bitfinex) GetSymbols() ([]string, error) { var products []string - path := fmt.Sprint(b.APIUrl + bitfinexAPIVersion + bitfinexSymbols) + path := fmt.Sprint(b.API.Endpoints.URL + bitfinexAPIVersion + bitfinexSymbols) return products, b.SendHTTPRequest(path, &products, b.Verbose) } @@ -506,7 +421,7 @@ func (b *Bitfinex) GetSymbols() ([]string, error) { // GetSymbolsDetails a list of valid symbol IDs and the pair details func (b *Bitfinex) GetSymbolsDetails() ([]SymbolDetails, error) { var response []SymbolDetails - path := fmt.Sprint(b.APIUrl + bitfinexAPIVersion + bitfinexSymbolsDetails) + path := fmt.Sprint(b.API.Endpoints.URL + bitfinexAPIVersion + bitfinexSymbolsDetails) return response, b.SendHTTPRequest(path, &response, b.Verbose) } @@ -625,7 +540,7 @@ func (b *Bitfinex) WithdrawCryptocurrency(withdrawType, wallet, address, payment } // WithdrawFIAT Sends an authenticated request to withdraw FIAT currency -func (b *Bitfinex) WithdrawFIAT(withdrawalType, walletType string, withdrawRequest *exchange.WithdrawRequest) ([]Withdrawal, error) { +func (b *Bitfinex) WithdrawFIAT(withdrawalType, walletType string, withdrawRequest *exchange.FiatWithdrawRequest) ([]Withdrawal, error) { var response []Withdrawal req := make(map[string]interface{}) @@ -669,9 +584,9 @@ func (b *Bitfinex) NewOrder(currencyPair string, amount, price float64, buy bool req["is_hidden"] = hidden if buy { - req["side"] = "buy" + req["side"] = exchange.BuyOrderSide.ToLower().ToString() } else { - req["side"] = "sell" + req["side"] = exchange.SellOrderSide.ToLower().ToString() } return response, @@ -744,9 +659,9 @@ func (b *Bitfinex) ReplaceOrder(orderID int64, symbol string, amount, price floa req["is_hidden"] = hidden if buy { - req["side"] = "buy" + req["side"] = exchange.BuyOrderSide.ToLower().ToString() } else { - req["side"] = "sell" + req["side"] = exchange.SellOrderSide.ToLower().ToString() } return response, @@ -1015,7 +930,7 @@ func (b *Bitfinex) SendHTTPRequest(path string, result interface{}, verbose bool // SendAuthenticatedHTTPRequest sends an autheticated http request and json // unmarshals result to a supplied variable func (b *Bitfinex) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) error { - if !b.AuthenticatedAPISupport { + if !b.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } @@ -1039,16 +954,16 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequest(method, path string, params map[ log.Debugf("Request JSON: %s\n", PayloadJSON) } - PayloadBase64 := common.Base64Encode(PayloadJSON) - hmac := common.GetHMAC(common.HashSHA512_384, []byte(PayloadBase64), - []byte(b.APISecret)) + PayloadBase64 := crypto.Base64Encode(PayloadJSON) + hmac := crypto.GetHMAC(crypto.HashSHA512_384, []byte(PayloadBase64), + []byte(b.API.Credentials.Secret)) headers := make(map[string]string) - headers["X-BFX-APIKEY"] = b.APIKey + headers["X-BFX-APIKEY"] = b.API.Credentials.Key headers["X-BFX-PAYLOAD"] = PayloadBase64 - headers["X-BFX-SIGNATURE"] = common.HexEncodeToString(hmac) + headers["X-BFX-SIGNATURE"] = crypto.HexEncodeToString(hmac) return b.SendPayload(method, - b.APIUrl+bitfinexAPIVersion+path, + b.API.Endpoints.URL+bitfinexAPIVersion+path, headers, nil, result, diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 3f55ba59..cd190774 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -29,15 +29,15 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Bitfinex Setup() init error") } - b.Setup(&bfxConfig) - b.APIKey = apiKey - b.APISecret = apiSecret - if !b.Enabled || b.AuthenticatedAPISupport || b.RESTPollingDelay != time.Duration(10) || - b.Verbose || b.Websocket.IsEnabled() || len(b.BaseCurrencies) < 1 || - len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 { + b.Setup(bfxConfig) + b.API.Credentials.Key = apiKey + b.API.Credentials.Secret = apiSecret + if !b.Enabled || b.API.AuthenticatedSupport || + b.Verbose || b.Websocket.IsEnabled() || len(b.BaseCurrencies) < 1 { t.Error("Test Failed - Bitfinex Setup values not set correctly") } - b.AuthenticatedAPISupport = true + + b.API.AuthenticatedSupport = true // custom rate limit for testing b.Requester.SetRateLimit(true, time.Millisecond*300, 1) b.Requester.SetRateLimit(false, time.Millisecond*300, 1) @@ -251,7 +251,7 @@ func TestGetAccountInfo(t *testing.T) { } func TestGetAccountFees(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -263,7 +263,7 @@ func TestGetAccountFees(t *testing.T) { } func TestGetAccountSummary(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -275,7 +275,7 @@ func TestGetAccountSummary(t *testing.T) { } func TestNewDeposit(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -287,7 +287,7 @@ func TestNewDeposit(t *testing.T) { } func TestGetKeyPermissions(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -299,7 +299,7 @@ func TestGetKeyPermissions(t *testing.T) { } func TestGetMarginInfo(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -311,7 +311,7 @@ func TestGetMarginInfo(t *testing.T) { } func TestGetAccountBalance(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -323,7 +323,7 @@ func TestGetAccountBalance(t *testing.T) { } func TestWalletTransfer(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -335,19 +335,20 @@ func TestWalletTransfer(t *testing.T) { } func TestNewOrder(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() - _, err := b.NewOrder("BTCUSD", 1, 2, true, "market", false) + _, err := b.NewOrder("BTCUSD", 1, 2, true, + exchange.LimitOrderType.ToLower().ToString(), false) if err == nil { t.Error("Test Failed - NewOrder() error") } } func TestNewOrderMulti(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -358,8 +359,8 @@ func TestNewOrderMulti(t *testing.T) { Amount: 1, Price: 1, Exchange: "bitfinex", - Side: "buy", - Type: "market", + Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: exchange.LimitOrderType.ToLower().ToString(), }, } @@ -369,8 +370,8 @@ func TestNewOrderMulti(t *testing.T) { } } -func TestCancelExistingOrder(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { +func TestCancelOrder(t *testing.T) { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -382,7 +383,7 @@ func TestCancelExistingOrder(t *testing.T) { } func TestCancelMultipleOrders(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -393,8 +394,8 @@ func TestCancelMultipleOrders(t *testing.T) { } } -func TestCancelAllExistingOrders(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { +func TestCancelAllOrders(t *testing.T) { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -406,19 +407,20 @@ func TestCancelAllExistingOrders(t *testing.T) { } func TestReplaceOrder(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() - _, err := b.ReplaceOrder(1337, "BTCUSD", 1, 1, true, "market", false) + _, err := b.ReplaceOrder(1337, "BTCUSD", + 1, 1, true, exchange.LimitOrderType.ToLower().ToString(), false) if err == nil { t.Error("Test Failed - ReplaceOrder() error") } } func TestGetOrderStatus(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -430,7 +432,7 @@ func TestGetOrderStatus(t *testing.T) { } func TestGetOpenOrders(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -442,7 +444,7 @@ func TestGetOpenOrders(t *testing.T) { } func TestGetActivePositions(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -454,7 +456,7 @@ func TestGetActivePositions(t *testing.T) { } func TestClaimPosition(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -466,7 +468,7 @@ func TestClaimPosition(t *testing.T) { } func TestGetBalanceHistory(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -478,7 +480,7 @@ func TestGetBalanceHistory(t *testing.T) { } func TestGetMovementHistory(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -490,7 +492,7 @@ func TestGetMovementHistory(t *testing.T) { } func TestGetTradeHistory(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -502,7 +504,7 @@ func TestGetTradeHistory(t *testing.T) { } func TestNewOffer(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -514,7 +516,7 @@ func TestNewOffer(t *testing.T) { } func TestCancelOffer(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -526,7 +528,7 @@ func TestCancelOffer(t *testing.T) { } func TestGetOfferStatus(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -538,7 +540,7 @@ func TestGetOfferStatus(t *testing.T) { } func TestGetActiveCredits(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -550,7 +552,7 @@ func TestGetActiveCredits(t *testing.T) { } func TestGetActiveOffers(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -562,7 +564,7 @@ func TestGetActiveOffers(t *testing.T) { } func TestGetActiveMarginFunding(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -574,7 +576,7 @@ func TestGetActiveMarginFunding(t *testing.T) { } func TestGetUnusedMarginFunds(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -586,7 +588,7 @@ func TestGetUnusedMarginFunds(t *testing.T) { } func TestGetMarginTotalTakenFunds(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -598,7 +600,7 @@ func TestGetMarginTotalTakenFunds(t *testing.T) { } func TestCloseMarginFunding(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !b.ValidateAPICredentials() { t.SkipNow() } t.Parallel() @@ -752,11 +754,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if b.APIKey != "" && b.APIKey != "Key" && - b.APISecret != "" && b.APISecret != "Secret" { - return true - } - return false + return b.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -771,7 +769,7 @@ func TestSubmitOrder(t *testing.T) { Base: currency.LTC, Quote: currency.BTC, } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -850,11 +848,13 @@ func TestWithdraw(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } _, err := b.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) @@ -874,10 +874,12 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Description: "WITHDRAW IT ALL", + var withdrawFiatRequest = exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.USD, + Description: "WITHDRAW IT ALL", + }, BankAccountName: "Satoshi Nakamoto", BankAccountNumber: 12345, BankAddress: "123 Fake St", @@ -907,10 +909,12 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Description: "WITHDRAW IT ALL", + var withdrawFiatRequest = exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, BankAccountName: "Satoshi Nakamoto", BankAccountNumber: 12345, BankAddress: "123 Fake St", diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 09554df2..1ab0a44d 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -11,8 +11,10 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -79,13 +81,13 @@ func (b *Bitfinex) WsSendAuth() error { req := make(map[string]interface{}) payload := "AUTH" + strconv.FormatInt(time.Now().UnixNano(), 10)[:13] req["event"] = "auth" - req["apiKey"] = b.APIKey + req["apiKey"] = b.API.Credentials.Key - req["authSig"] = common.HexEncodeToString( - common.GetHMAC( - common.HashSHA512_384, + req["authSig"] = crypto.HexEncodeToString( + crypto.GetHMAC( + crypto.HashSHA512_384, []byte(payload), - []byte(b.APISecret))) + []byte(b.API.Credentials.Secret))) req["authPayload"] = payload @@ -156,7 +158,7 @@ func (b *Bitfinex) WsConnect() error { } } - if b.AuthenticatedAPISupport { + if b.AllowAuthenticatedRequest() { err = b.WsSendAuth() if err != nil { return err @@ -232,7 +234,7 @@ func (b *Bitfinex) WsDataHandler() { b.Websocket.DataHandler <- fmt.Errorf("bitfinex.go error - Websocket unable to AUTH. Error code: %s", eventData["code"].(string)) - b.AuthenticatedAPISupport = false + b.API.AuthenticatedSupport = false } } @@ -280,7 +282,7 @@ func (b *Bitfinex) WsDataHandler() { if len(newOrderbook) > 1 { err := b.WsInsertSnapshot(currency.NewPairFromString(chanInfo.Pair), - "SPOT", + assets.AssetTypeSpot, newOrderbook) if err != nil { @@ -291,7 +293,7 @@ func (b *Bitfinex) WsDataHandler() { } err := b.WsUpdateOrderbook(currency.NewPairFromString(chanInfo.Pair), - "SPOT", + assets.AssetTypeSpot, newOrderbook[0]) if err != nil { @@ -307,7 +309,7 @@ func (b *Bitfinex) WsDataHandler() { LowPrice: chanData[10].(float64), Pair: currency.NewPairFromString(chanInfo.Pair), Exchange: b.GetName(), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, } case "account": @@ -463,7 +465,7 @@ func (b *Bitfinex) WsDataHandler() { Price: trades[0].Price, Amount: newAmount, Exchange: b.GetName(), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Side: side, } } @@ -476,7 +478,7 @@ func (b *Bitfinex) WsDataHandler() { // WsInsertSnapshot add the initial orderbook snapshot when subscribed to a // channel -func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType string, books []WebsocketBook) error { +func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType assets.AssetType, books []WebsocketBook) error { if len(books) == 0 { return errors.New("bitfinex.go error - no orderbooks submitted") } @@ -513,7 +515,7 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType string, books []W // WsUpdateOrderbook updates the orderbook list, removing and adding to the // orderbook sides -func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType string, book WebsocketBook) error { +func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType assets.AssetType, book WebsocketBook) error { if book.Count > 0 { if book.Amount > 0 { @@ -603,14 +605,15 @@ func (b *Bitfinex) GenerateDefaultSubscriptions() { var channels = []string{"book", "trades", "ticker"} subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { - for j := range b.EnabledPairs { + enabledPairs := b.GetEnabledPairs(assets.AssetTypeSpot) + for j := range enabledPairs { params := make(map[string]interface{}) if channels[i] == "book" { params["prec"] = "P0" } subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{ Channel: channels[i], - Currency: b.EnabledPairs[j], + Currency: enabledPairs[j], Params: params, }) } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index c15c83ce..07d9b81c 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -10,13 +10,114 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (b *Bitfinex) GetDefaultConfig() (*config.ExchangeConfig, error) { + b.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = b.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = b.BaseCurrencies + + err := b.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if b.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = b.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets the basic defaults for bitfinex +func (b *Bitfinex) SetDefaults() { + b.Name = "Bitfinex" + b.Enabled = true + b.Verbose = true + b.WebsocketSubdChannels = make(map[int]WebsocketChanInfo) + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + + b.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + + b.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | + exchange.AutoWithdrawFiatWithAPIPermission, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + b.Requester = request.New(b.Name, + request.NewRateLimit(time.Second*60, bitfinexAuthRate), + request.NewRateLimit(time.Second*60, bitfinexUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + b.API.Endpoints.URLDefault = bitfinexAPIURLBase + b.API.Endpoints.URL = b.API.Endpoints.URLDefault + b.WebsocketInit() + b.Websocket.Functionality = exchange.WebsocketTickerSupported | + exchange.WebsocketTradeDataSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup takes in the supplied exchange configuration details and sets params +func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + b.SetEnabled(false) + return nil + } + + err := b.SetupDefaults(exch) + if err != nil { + return err + } + + return b.WebsocketSetup(b.WsConnect, + b.Subscribe, + b.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + bitfinexWebsocket, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the Bitfinex go routine func (b *Bitfinex) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -30,31 +131,39 @@ func (b *Bitfinex) Start(wg *sync.WaitGroup) { func (b *Bitfinex) Run() { if b.Verbose { log.Debugf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled())) - log.Debugf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + b.PrintEnabledPairs() } - exchangeProducts, err := b.GetSymbols() - if err != nil { - log.Errorf("%s Failed to get available symbols.\n", b.GetName()) - } else { - var newExchangeProducts currency.Pairs - for _, p := range exchangeProducts { - newExchangeProducts = append(newExchangeProducts, - currency.NewPairFromString(p)) - } + if !b.GetEnabledFeatures().AutoPairUpdates { + return + } - err = b.UpdateCurrencies(newExchangeProducts, false, false) - if err != nil { - log.Errorf("%s Failed to update available symbols.\n", b.GetName()) - } + err := b.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (b *Bitfinex) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + return b.GetSymbols() +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (b *Bitfinex) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - enabledPairs := b.GetEnabledCurrencies() + enabledPairs := b.GetEnabledPairs(assetType) var pairs []string for x := range enabledPairs { @@ -87,17 +196,17 @@ func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType string) (ticker.Price return ticker.GetTicker(b.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (b *Bitfinex) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { - tick, err := ticker.GetTicker(b.GetName(), p, ticker.Spot) +// FetchTicker returns the ticker for a currency pair +func (b *Bitfinex) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { + tick, err := ticker.GetTicker(b.GetName(), p, assets.AssetTypeSpot) if err != nil { return b.UpdateTicker(p, assetType) } return tick, nil } -// GetOrderbookEx returns the orderbook for a currency pair -func (b *Bitfinex) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns the orderbook for a currency pair +func (b *Bitfinex) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -106,7 +215,7 @@ func (b *Bitfinex) GetOrderbookEx(p currency.Pair, assetType string) (orderbook. } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base urlVals := url.Values{} urlVals.Set("limit_bids", "100") @@ -181,10 +290,8 @@ func (b *Bitfinex) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -229,7 +336,6 @@ func (b *Bitfinex) CancelOrder(order *exchange.OrderCancellation) error { } _, err = b.CancelExistingOrder(orderIDInt) - return err } @@ -261,7 +367,7 @@ func (b *Bitfinex) GetDepositAddress(cryptocurrency currency.Code, accountID str } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is submitted -func (b *Bitfinex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitfinex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { withdrawalType := b.ConvertSymbolToWithdrawalType(withdrawRequest.Currency) // Bitfinex has support for three types, exchange, margin and deposit // As this is for trading, I've made the wrapper default 'exchange' @@ -285,7 +391,7 @@ func (b *Bitfinex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.Withdra // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is submitted // Returns comma delimited withdrawal IDs -func (b *Bitfinex) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitfinex) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { withdrawalType := "wire" // Bitfinex has support for three types, exchange, margin and deposit // As this is for trading, I've made the wrapper default 'exchange' @@ -318,7 +424,7 @@ func (b *Bitfinex) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is submitted // Returns comma delimited withdrawal IDs -func (b *Bitfinex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitfinex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return b.WithdrawFiatFunds(withdrawRequest) } @@ -329,7 +435,7 @@ func (b *Bitfinex) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (b *Bitfinex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (b.APIKey == "" || b.APISecret == "") && // Todo check connection status + if !b.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -391,7 +497,6 @@ func (b *Bitfinex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } @@ -451,7 +556,6 @@ func (b *Bitfinex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } diff --git a/exchanges/bitflyer/README.md b/exchanges/bitflyer/README.md index 97afdddd..291b2483 100644 --- a/exchanges/bitflyer/README.md +++ b/exchanges/bitflyer/README.md @@ -47,22 +47,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bitflyer" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bitflyer" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/bitflyer/bitflyer.go b/exchanges/bitflyer/bitflyer.go index b4e68583..6685e11f 100644 --- a/exchanges/bitflyer/bitflyer.go +++ b/exchanges/bitflyer/bitflyer.go @@ -6,15 +6,9 @@ import ( "net/http" "net/url" "strconv" - "time" - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -76,77 +70,11 @@ type Bitflyer struct { exchange.Base } -// SetDefaults sets the basic defaults for Bitflyer -func (b *Bitflyer) SetDefaults() { - b.Name = "Bitflyer" - b.Enabled = false - b.Verbose = false - b.RESTPollingDelay = 10 - b.APIWithdrawPermissions = exchange.WithdrawCryptoViaWebsiteOnly | - exchange.AutoWithdrawFiat - b.RequestCurrencyPairFormat.Delimiter = "_" - b.RequestCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Delimiter = "_" - b.ConfigCurrencyPairFormat.Uppercase = true - b.AssetTypes = []string{ticker.Spot} - b.SupportsAutoPairUpdating = false - b.SupportsRESTTickerBatching = false - b.Requester = request.New(b.Name, - request.NewRateLimit(time.Minute, bitflyerAuthRate), - request.NewRateLimit(time.Minute, bitflyerUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - b.APIUrlDefault = japanURL - b.APIUrl = b.APIUrlDefault - b.APIUrlSecondaryDefault = chainAnalysis - b.APIUrlSecondary = b.APIUrlSecondaryDefault - b.WebsocketInit() -} - -// Setup takes in the supplied exchange configuration details and sets params -func (b *Bitflyer) 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.SetHTTPClientTimeout(exch.HTTPTimeout) - b.SetHTTPClientUserAgent(exch.HTTPUserAgent) - b.RESTPollingDelay = exch.RESTPollingDelay - b.Verbose = exch.Verbose - b.HTTPDebugging = exch.HTTPDebugging - b.Websocket.SetWsStatusAndConnection(exch.Websocket) - b.BaseCurrencies = exch.BaseCurrencies - b.AvailablePairs = exch.AvailablePairs - b.EnabledPairs = exch.EnabledPairs - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = b.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = b.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = b.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - } -} - // GetLatestBlockCA returns the latest block information from bitflyer chain // analysis system func (b *Bitflyer) GetLatestBlockCA() (ChainAnalysisBlock, error) { var resp ChainAnalysisBlock - path := fmt.Sprintf("%s%s", b.APIUrlSecondary, latestBlock) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URLSecondary, latestBlock) return resp, b.SendHTTPRequest(path, &resp) } @@ -155,7 +83,7 @@ func (b *Bitflyer) GetLatestBlockCA() (ChainAnalysisBlock, error) { // analysis system func (b *Bitflyer) GetBlockCA(blockhash string) (ChainAnalysisBlock, error) { var resp ChainAnalysisBlock - path := fmt.Sprintf("%s%s%s", b.APIUrlSecondary, blockByBlockHash, blockhash) + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URLSecondary, blockByBlockHash, blockhash) return resp, b.SendHTTPRequest(path, &resp) } @@ -164,7 +92,7 @@ func (b *Bitflyer) GetBlockCA(blockhash string) (ChainAnalysisBlock, error) { // analysis system func (b *Bitflyer) GetBlockbyHeightCA(height int64) (ChainAnalysisBlock, error) { var resp ChainAnalysisBlock - path := fmt.Sprintf("%s%s%s", b.APIUrlSecondary, blockByBlockHeight, strconv.FormatInt(height, 10)) + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URLSecondary, blockByBlockHeight, strconv.FormatInt(height, 10)) return resp, b.SendHTTPRequest(path, &resp) } @@ -173,7 +101,7 @@ func (b *Bitflyer) GetBlockbyHeightCA(height int64) (ChainAnalysisBlock, error) // bitflyer chain analysis system func (b *Bitflyer) GetTransactionByHashCA(txHash string) (ChainAnalysisTransaction, error) { var resp ChainAnalysisTransaction - path := fmt.Sprintf("%s%s%s", b.APIUrlSecondary, transaction, txHash) + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URLSecondary, transaction, txHash) return resp, b.SendHTTPRequest(path, &resp) } @@ -182,7 +110,7 @@ func (b *Bitflyer) GetTransactionByHashCA(txHash string) (ChainAnalysisTransacti // from bitflyer chain analysis system func (b *Bitflyer) GetAddressInfoCA(addressln string) (ChainAnalysisAddress, error) { var resp ChainAnalysisAddress - path := fmt.Sprintf("%s%s%s", b.APIUrlSecondary, address, addressln) + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URLSecondary, address, addressln) return resp, b.SendHTTPRequest(path, &resp) } @@ -190,7 +118,7 @@ func (b *Bitflyer) GetAddressInfoCA(addressln string) (ChainAnalysisAddress, err // GetMarkets returns market information func (b *Bitflyer) GetMarkets() ([]MarketInfo, error) { var resp []MarketInfo - path := fmt.Sprintf("%s%s", b.APIUrl, pubGetMarkets) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, pubGetMarkets) return resp, b.SendHTTPRequest(path, &resp) } @@ -200,7 +128,7 @@ func (b *Bitflyer) GetOrderBook(symbol string) (Orderbook, error) { var resp Orderbook v := url.Values{} v.Set("product_code", symbol) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, pubGetBoard, v.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, pubGetBoard, v.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -210,7 +138,7 @@ func (b *Bitflyer) GetTicker(symbol string) (Ticker, error) { var resp Ticker v := url.Values{} v.Set("product_code", symbol) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, pubGetTicker, v.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, pubGetTicker, v.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -220,7 +148,7 @@ func (b *Bitflyer) GetExecutionHistory(symbol string) ([]ExecutedTrade, error) { var resp []ExecutedTrade v := url.Values{} v.Set("product_code", symbol) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, pubGetExecutionHistory, v.Encode()) + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, pubGetExecutionHistory, v.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -229,7 +157,7 @@ func (b *Bitflyer) GetExecutionHistory(symbol string) ([]ExecutedTrade, error) { func (b *Bitflyer) GetExchangeStatus() (string, error) { resp := make(map[string]string) - path := fmt.Sprintf("%s%s", b.APIUrl, pubGetHealth) + path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, pubGetHealth) err := b.SendHTTPRequest(path, &resp) if err != nil { @@ -256,8 +184,7 @@ func (b *Bitflyer) GetChats(fromDate string) ([]ChatLog, error) { var resp []ChatLog v := url.Values{} v.Set("from_date", fromDate) - path := fmt.Sprintf("%s%s?%s", b.APIUrl, pubGetChats, v.Encode()) - + path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, pubGetChats, v.Encode()) return resp, b.SendHTTPRequest(path, &resp) } @@ -388,7 +315,7 @@ func (b *Bitflyer) SendHTTPRequest(path string, result interface{}) error { // TODO: Fill out this function once API access is obtained func (b *Bitflyer) SendAuthHTTPRequest() { // headers := make(map[string]string) - // headers["ACCESS-KEY"] = b.APIKey + // headers["ACCESS-KEY"] = b.API.Credentials.Key // headers["ACCESS-TIMESTAMP"] = strconv.FormatInt(time.Now().UnixNano(), 10) } diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index ecaf050c..6eac9747 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -31,11 +32,11 @@ func TestSetup(t *testing.T) { t.Error("Test Failed - bitflyer Setup() init error") } - bitflyerConfig.AuthenticatedAPISupport = true - bitflyerConfig.APIKey = apiKey - bitflyerConfig.APISecret = apiSecret + bitflyerConfig.API.AuthenticatedSupport = true + bitflyerConfig.API.Credentials.Key = apiKey + bitflyerConfig.API.Credentials.Secret = apiSecret - b.Setup(&bitflyerConfig) + b.Setup(bitflyerConfig) } func TestGetLatestBlockCA(t *testing.T) { @@ -130,11 +131,11 @@ func TestCheckFXString(t *testing.T) { } } -func TestGetTickerPrice(t *testing.T) { +func TestFetchTicker(t *testing.T) { t.Parallel() var p currency.Pair - currencies := b.GetAvailableCurrencies() + currencies := b.GetAvailablePairs(assets.AssetTypeSpot) for _, pair := range currencies { if pair.String() == "FXBTC_JPY" { p = pair @@ -142,9 +143,9 @@ func TestGetTickerPrice(t *testing.T) { } } - _, err := b.GetTickerPrice(p, b.AssetTypes[0]) + _, err := b.FetchTicker(p, assets.AssetTypeSpot) if err != nil { - t.Error("test failed - Bitflyer - GetTickerPrice() error", err) + t.Error("test failed - Bitflyer - FetchTicker() error", err) } } @@ -291,11 +292,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if b.APIKey != "" && b.APIKey != "Key" && - b.APISecret != "" && b.APISecret != "Secret" { - return true - } - return false + return b.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -311,7 +308,7 @@ func TestSubmitOrder(t *testing.T) { Base: currency.LTC, Quote: currency.BTC, } - _, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") + _, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not Yet Implemented', received %v", err) } @@ -366,11 +363,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestWithdraw(t *testing.T) { b.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -398,7 +397,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrNotYetImplemented { @@ -414,7 +413,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrNotYetImplemented { diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index e277ea2e..21f3be5c 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -3,15 +3,103 @@ package bitflyer import ( "errors" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (b *Bitflyer) GetDefaultConfig() (*config.ExchangeConfig, error) { + b.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = b.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = b.BaseCurrencies + + err := b.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if b.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = b.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets the basic defaults for Bitflyer +func (b *Bitflyer) SetDefaults() { + b.Name = "Bitflyer" + b.Enabled = true + b.Verbose = true + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + + b.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + assets.AssetTypeFutures, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: true, + }, + } + + b.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: false, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.WithdrawCryptoViaWebsiteOnly | + exchange.AutoWithdrawFiat, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + b.Requester = request.New(b.Name, + request.NewRateLimit(time.Minute, bitflyerAuthRate), + request.NewRateLimit(time.Minute, bitflyerUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + b.API.Endpoints.URLDefault = japanURL + b.API.Endpoints.URL = b.API.Endpoints.URLDefault + b.API.Endpoints.URLSecondaryDefault = chainAnalysis + b.API.Endpoints.URLSecondary = b.API.Endpoints.URLSecondaryDefault +} + +// Setup takes in the supplied exchange configuration details and sets params +func (b *Bitflyer) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + b.SetEnabled(false) + return nil + } + + return b.SetupDefaults(exch) +} + // Start starts the Bitflyer go routine func (b *Bitflyer) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -24,32 +112,57 @@ func (b *Bitflyer) Start(wg *sync.WaitGroup) { // Run implements the Bitflyer wrapper func (b *Bitflyer) Run() { if b.Verbose { - log.Debugf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled())) - log.Debugf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + b.PrintEnabledPairs() } - /* - marketInfo, err := b.GetMarkets() - if err != nil { - log.Printf("%s Failed to get available symbols.\n", b.GetName()) - } else { - var exchangeProducts []string + if !b.GetEnabledFeatures().AutoPairUpdates { + return + } - for _, info := range marketInfo { - exchangeProducts = append(exchangeProducts, info.ProductCode) - } + err := b.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + } +} - err = b.UpdateAvailableCurrencies(exchangeProducts, false) - if err != nil { - log.Printf("%s Failed to get config.\n", b.GetName()) - } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (b *Bitflyer) FetchTradablePairs(assetType assets.AssetType) ([]string, error) { + pairs, err := b.GetMarkets() + if err != nil { + return nil, err + } + + var products []string + for _, info := range pairs { + if info.Alias != "" && assetType == assets.AssetTypeFutures { + products = append(products, info.Alias) + } else if info.Alias == "" && assetType == assets.AssetTypeSpot && common.StringContains(info.ProductCode, "_") { + products = append(products, info.ProductCode) } - */ + } + return products, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (b *Bitflyer) UpdateTradablePairs(forceUpdate bool) error { + for x := range b.CurrencyPairs.AssetTypes { + a := b.CurrencyPairs.AssetTypes[x] + pairs, err := b.FetchTradablePairs(a) + if err != nil { + return err + } + + err = b.UpdatePairs(currency.NewPairsFromStrings(pairs), a, false, forceUpdate) + if err != nil { + return err + } + } + return nil } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price p = b.CheckFXString(p) @@ -74,9 +187,9 @@ func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType string) (ticker.Price return ticker.GetTicker(b.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (b *Bitflyer) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { - tick, err := ticker.GetTicker(b.GetName(), p, ticker.Spot) +// FetchTicker returns the ticker for a currency pair +func (b *Bitflyer) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { + tick, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) } @@ -92,8 +205,8 @@ func (b *Bitflyer) CheckFXString(p currency.Pair) currency.Pair { return p } -// GetOrderbookEx returns the orderbook for a currency pair -func (b *Bitflyer) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns the orderbook for a currency pair +func (b *Bitflyer) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -102,7 +215,7 @@ func (b *Bitflyer) GetOrderbookEx(p currency.Pair, assetType string) (orderbook. } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bitflyer) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (b *Bitflyer) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base p = b.CheckFXString(p) @@ -158,10 +271,8 @@ func (b *Bitflyer) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bitflyer) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (b *Bitflyer) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -202,19 +313,19 @@ func (b *Bitflyer) GetDepositAddress(cryptocurrency currency.Code, accountID str // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (b *Bitflyer) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitflyer) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return "", common.ErrNotYetImplemented } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (b *Bitflyer) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitflyer) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrNotYetImplemented } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (b *Bitflyer) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitflyer) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrNotYetImplemented } @@ -236,7 +347,7 @@ func (b *Bitflyer) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) // GetFeeByType returns an estimate of fee based on the type of transaction func (b *Bitflyer) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (b.APIKey == "" || b.APISecret == "") && // Todo check connection status + if !b.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } diff --git a/exchanges/bithumb/README.md b/exchanges/bithumb/README.md index 745a1b5b..23b5ab6a 100644 --- a/exchanges/bithumb/README.md +++ b/exchanges/bithumb/README.md @@ -47,22 +47,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bithumb" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bithumb" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go index f9c87f9d..63d19651 100644 --- a/exchanges/bithumb/bithumb.go +++ b/exchanges/bithumb/bithumb.go @@ -9,15 +9,11 @@ import ( "net/url" "reflect" "strconv" - "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -59,71 +55,6 @@ type Bithumb struct { exchange.Base } -// SetDefaults sets the basic defaults for Bithumb -func (b *Bithumb) SetDefaults() { - b.Name = "Bithumb" - b.Enabled = false - b.Verbose = false - b.RESTPollingDelay = 10 - b.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.AutoWithdrawFiat - b.RequestCurrencyPairFormat.Delimiter = "" - b.RequestCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Index = "KRW" - b.AssetTypes = []string{ticker.Spot} - b.SupportsAutoPairUpdating = true - b.SupportsRESTTickerBatching = true - b.Requester = request.New(b.Name, - request.NewRateLimit(time.Second, bithumbAuthRate), - request.NewRateLimit(time.Second, bithumbUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - b.APIUrlDefault = apiURL - b.APIUrl = b.APIUrlDefault - b.WebsocketInit() -} - -// Setup takes in the supplied exchange configuration details and sets params -func (b *Bithumb) 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.SetHTTPClientTimeout(exch.HTTPTimeout) - b.SetHTTPClientUserAgent(exch.HTTPUserAgent) - b.RESTPollingDelay = exch.RESTPollingDelay - b.Verbose = exch.Verbose - b.HTTPDebugging = exch.HTTPDebugging - b.Websocket.SetWsStatusAndConnection(exch.Websocket) - b.BaseCurrencies = exch.BaseCurrencies - b.AvailablePairs = exch.AvailablePairs - b.EnabledPairs = exch.EnabledPairs - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = b.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = b.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = b.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - } -} - // GetTradablePairs returns a list of tradable currencies func (b *Bithumb) GetTradablePairs() ([]string, error) { result, err := b.GetAllTickers() @@ -143,7 +74,7 @@ func (b *Bithumb) GetTradablePairs() ([]string, error) { // symbol e.g. "btc" func (b *Bithumb) GetTicker(symbol string) (Ticker, error) { response := Ticker{} - path := fmt.Sprintf("%s%s%s", b.APIUrl, publicTicker, common.StringToUpper(symbol)) + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTicker, common.StringToUpper(symbol)) err := b.SendHTTPRequest(path, &response) if err != nil { @@ -165,7 +96,7 @@ func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) { } response := Response{} - path := fmt.Sprintf("%s%s%s", b.APIUrl, publicTicker, "all") + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTicker, "all") err := b.SendHTTPRequest(path, &response) if err != nil { @@ -209,7 +140,7 @@ func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) { // symbol e.g. "btc" func (b *Bithumb) GetOrderBook(symbol string) (Orderbook, error) { response := Orderbook{} - path := fmt.Sprintf("%s%s%s", b.APIUrl, publicOrderBook, common.StringToUpper(symbol)) + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicOrderBook, common.StringToUpper(symbol)) err := b.SendHTTPRequest(path, &response) if err != nil { @@ -228,7 +159,7 @@ func (b *Bithumb) GetOrderBook(symbol string) (Orderbook, error) { // symbol e.g. "btc" func (b *Bithumb) GetTransactionHistory(symbol string) (TransactionHistory, error) { response := TransactionHistory{} - path := fmt.Sprintf("%s%s%s", b.APIUrl, publicTransactionHistory, common.StringToUpper(symbol)) + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTransactionHistory, common.StringToUpper(symbol)) err := b.SendHTTPRequest(path, &response) if err != nil { @@ -549,7 +480,7 @@ func (b *Bithumb) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to bithumb func (b *Bithumb) SendAuthenticatedHTTPRequest(path string, params url.Values, result interface{}) error { - if !b.AuthenticatedAPISupport { + if !b.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } @@ -562,14 +493,14 @@ func (b *Bithumb) SendAuthenticatedHTTPRequest(path string, params url.Values, r params.Set("endpoint", path) payload := params.Encode() hmacPayload := path + string(0) + payload + string(0) + n - hmac := common.GetHMAC(common.HashSHA512, + hmac := crypto.GetHMAC(crypto.HashSHA512, []byte(hmacPayload), - []byte(b.APISecret)) - hmacStr := common.HexEncodeToString(hmac) + []byte(b.API.Credentials.Secret)) + hmacStr := crypto.HexEncodeToString(hmac) headers := make(map[string]string) - headers["Api-Key"] = b.APIKey - headers["Api-Sign"] = common.Base64Encode([]byte(hmacStr)) + headers["Api-Key"] = b.API.Credentials.Key + headers["Api-Sign"] = crypto.Base64Encode([]byte(hmacStr)) headers["Api-Nonce"] = n headers["Content-Type"] = "application/x-www-form-urlencoded" @@ -581,7 +512,7 @@ func (b *Bithumb) SendAuthenticatedHTTPRequest(path string, params url.Values, r }{} err := b.SendPayload(http.MethodPost, - b.APIUrl+path, + b.API.Endpoints.URL+path, headers, bytes.NewBufferString(payload), &intermediary, diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index e8264566..497006a2 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -30,11 +30,11 @@ func TestSetup(t *testing.T) { t.Error("Test Failed - Bithumb Setup() init error") } - bitConfig.AuthenticatedAPISupport = true - bitConfig.APIKey = apiKey - bitConfig.APISecret = apiSecret + bitConfig.API.AuthenticatedSupport = true + bitConfig.API.Credentials.Key = apiKey + bitConfig.API.Credentials.Secret = apiSecret - b.Setup(&bitConfig) + b.Setup(bitConfig) } func TestGetTradablePairs(t *testing.T) { @@ -334,11 +334,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if b.APIKey != "" && b.APIKey != "Key" && - b.APISecret != "" && b.APISecret != "Secret" { - return true - } - return false + return b.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -354,7 +350,7 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.LTC, } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -453,11 +449,13 @@ func TestWithdraw(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } _, err := b.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) @@ -477,10 +475,12 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.KRW, - Description: "WITHDRAW IT ALL", + var withdrawFiatRequest = exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.USD, + Description: "WITHDRAW IT ALL", + }, BankAccountName: "Satoshi Nakamoto", BankAccountNumber: 12345, BankCode: 123, @@ -511,8 +511,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 245022d4..2e7749e8 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -9,14 +9,97 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) -// Start starts the OKEX go routine +// GetDefaultConfig returns a default exchange config +func (b *Bithumb) GetDefaultConfig() (*config.ExchangeConfig, error) { + b.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = b.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = b.BaseCurrencies + + err := b.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if b.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = b.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets the basic defaults for Bithumb +func (b *Bithumb) SetDefaults() { + b.Name = "Bithumb" + b.Enabled = true + b.Verbose = true + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + + b.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Index: "KRW", + }, + } + + b.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.AutoWithdrawFiat, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + b.Requester = request.New(b.Name, + request.NewRateLimit(time.Second, bithumbAuthRate), + request.NewRateLimit(time.Second, bithumbUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + b.API.Endpoints.URLDefault = apiURL + b.API.Endpoints.URL = b.API.Endpoints.URLDefault +} + +// Setup takes in the supplied exchange configuration details and sets params +func (b *Bithumb) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + b.SetEnabled(false) + return nil + } + + return b.SetupDefaults(exch) +} + +// Start starts the Bithumb go routine func (b *Bithumb) Start(wg *sync.WaitGroup) { wg.Add(1) go func() { @@ -25,33 +108,24 @@ func (b *Bithumb) Start(wg *sync.WaitGroup) { }() } -// Run implements the OKEX wrapper +// Run implements the Bithumb wrapper func (b *Bithumb) Run() { if b.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.WebsocketURL) - log.Debugf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + b.PrintEnabledPairs() } - exchangeProducts, err := b.GetTradingPairs() - if err != nil { - log.Errorf("%s Failed to get available symbols.\n", b.GetName()) - } else { - var newExchangeProducts currency.Pairs - for _, p := range exchangeProducts { - newExchangeProducts = append(newExchangeProducts, - currency.NewPairFromString(p)) - } + if !b.GetEnabledFeatures().AutoPairUpdates { + return + } - err = b.UpdateCurrencies(newExchangeProducts, false, false) - if err != nil { - log.Errorf("%s Failed to update available symbols.\n", b.GetName()) - } + err := b.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) } } -// GetTradingPairs gets the available trading currencies -func (b *Bithumb) GetTradingPairs() ([]string, error) { +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (b *Bithumb) FetchTradablePairs(asset assets.AssetType) ([]string, error) { currencies, err := b.GetTradablePairs() if err != nil { return nil, err @@ -64,8 +138,19 @@ func (b *Bithumb) GetTradingPairs() ([]string, error) { return currencies, nil } +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (b *Bithumb) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bithumb) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (b *Bithumb) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price tickers, err := b.GetAllTickers() @@ -73,7 +158,7 @@ func (b *Bithumb) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, return tickerPrice, err } - for _, x := range b.GetEnabledCurrencies() { + for _, x := range b.GetEnabledPairs(assetType) { currency := x.Base.String() var tp ticker.Price tp.Pair = x @@ -92,8 +177,8 @@ func (b *Bithumb) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, return ticker.GetTicker(b.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (b *Bithumb) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (b *Bithumb) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -101,17 +186,17 @@ func (b *Bithumb) GetTickerPrice(p currency.Pair, assetType string) (ticker.Pric return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (b *Bithumb) GetOrderbookEx(currency currency.Pair, assetType string) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), currency, assetType) +// FetchOrderbook returns orderbook base on the currency pair +func (b *Bithumb) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { + ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { - return b.UpdateOrderbook(currency, assetType) + return b.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bithumb) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (b *Bithumb) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base currency := p.Base.String() @@ -180,10 +265,8 @@ func (b *Bithumb) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bithumb) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (b *Bithumb) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -244,7 +327,7 @@ func (b *Bithumb) CancelAllOrders(orderCancellation *exchange.OrderCancellation) } var allOrders []OrderData - for _, currency := range b.GetEnabledCurrencies() { + for _, currency := range b.GetEnabledPairs(assets.AssetTypeSpot) { orders, err := b.GetOrders("", orderCancellation.Side.ToString(), "100", @@ -286,14 +369,14 @@ func (b *Bithumb) GetDepositAddress(cryptocurrency currency.Code, _ string) (str // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (b *Bithumb) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bithumb) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { _, err := b.WithdrawCrypto(withdrawRequest.Address, withdrawRequest.AddressTag, withdrawRequest.Currency.String(), withdrawRequest.Amount) return "", err } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (b *Bithumb) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bithumb) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { if math.Mod(withdrawRequest.Amount, 1) != 0 { return "", errors.New("currency KRW does not support decimal places") } @@ -315,7 +398,7 @@ func (b *Bithumb) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) ( } // WithdrawFiatFundsToInternationalBank is not supported as Bithumb only withdraws KRW to South Korean banks -func (b *Bithumb) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bithumb) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -326,7 +409,7 @@ func (b *Bithumb) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (b *Bithumb) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (b.APIKey == "" || b.APISecret == "") && // Todo check connection status + if !b.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -357,7 +440,7 @@ func (b *Bithumb) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( Status: string(exchange.ActiveOrderStatus), CurrencyPair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency, resp.Data[i].PaymentCurrency, - b.ConfigCurrencyPairFormat.Delimiter), + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), } if resp.Data[i].Type == "bid" { @@ -373,7 +456,6 @@ func (b *Bithumb) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } @@ -401,7 +483,7 @@ func (b *Bithumb) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( RemainingAmount: resp.Data[i].UnitsRemaining, CurrencyPair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency, resp.Data[i].PaymentCurrency, - b.ConfigCurrencyPairFormat.Delimiter), + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), } if resp.Data[i].Type == "bid" { @@ -417,7 +499,6 @@ func (b *Bithumb) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } diff --git a/exchanges/bitmex/README.md b/exchanges/bitmex/README.md index dac385d8..de5716b0 100644 --- a/exchanges/bitmex/README.md +++ b/exchanges/bitmex/README.md @@ -47,22 +47,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bitmex" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bitmex" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index d2d94d3e..87760e09 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -12,12 +12,9 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) // Bitmex is the overarching type across this package @@ -113,84 +110,6 @@ const ( ContractUpsideProfit ) -// SetDefaults sets the basic defaults for Bitmex -func (b *Bitmex) SetDefaults() { - b.Name = "Bitmex" - b.Enabled = false - b.Verbose = false - b.RESTPollingDelay = 10 - b.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithAPIPermission | - exchange.WithdrawCryptoWithEmail | - exchange.WithdrawCryptoWith2FA | - exchange.NoFiatWithdrawals - b.RequestCurrencyPairFormat.Delimiter = "" - b.RequestCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Uppercase = true - b.AssetTypes = []string{ticker.Spot} - b.Requester = request.New(b.Name, - request.NewRateLimit(time.Second, bitmexAuthRate), - request.NewRateLimit(time.Second, bitmexUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - b.APIUrlDefault = bitmexAPIURL - b.APIUrl = b.APIUrlDefault - b.SupportsAutoPairUpdating = true - b.WebsocketInit() - b.Websocket.Functionality = exchange.WebsocketTradeDataSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup takes in the supplied exchange configuration details and sets params -func (b *Bitmex) 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.HTTPDebugging = exch.HTTPDebugging - b.Websocket.SetWsStatusAndConnection(exch.Websocket) - b.BaseCurrencies = exch.BaseCurrencies - b.AvailablePairs = exch.AvailablePairs - b.EnabledPairs = exch.EnabledPairs - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = b.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = b.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = b.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = b.WebsocketSetup(b.WsConnector, - b.Subscribe, - b.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - bitmexWSURL, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetAnnouncement returns the general announcements from Bitmex func (b *Bitmex) GetAnnouncement() ([]Announcement, error) { var announcement []Announcement @@ -851,7 +770,7 @@ func (b *Bitmex) GetWalletSummary(currency string) ([]TransactionInfo, error) { // SendHTTPRequest sends an unauthenticated HTTP request func (b *Bitmex) SendHTTPRequest(path string, params Parameter, result interface{}) error { var respCheck interface{} - path = b.APIUrl + path + path = b.API.Endpoints.URL + path if params != nil { if !params.IsNil() { encodedPath, err := params.ToURLVals(path) @@ -874,7 +793,7 @@ func (b *Bitmex) SendHTTPRequest(path string, params Parameter, result interface // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to bitmex func (b *Bitmex) SendAuthenticatedHTTPRequest(verb, path string, params Parameter, result interface{}) error { - if !b.AuthenticatedAPISupport { + if !b.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } @@ -886,7 +805,7 @@ func (b *Bitmex) SendAuthenticatedHTTPRequest(verb, path string, params Paramete headers := make(map[string]string) headers["Content-Type"] = "application/json" headers["api-expires"] = timestampNew - headers["api-key"] = b.APIKey + headers["api-key"] = b.API.Credentials.Key var payload string if params != nil { @@ -901,16 +820,16 @@ func (b *Bitmex) SendAuthenticatedHTTPRequest(verb, path string, params Paramete payload = string(data) } - hmac := common.GetHMAC(common.HashSHA256, + hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(verb+"/api/v1"+path+timestampNew+payload), - []byte(b.APISecret)) + []byte(b.API.Credentials.Secret)) - headers["api-signature"] = common.HexEncodeToString(hmac) + headers["api-signature"] = crypto.HexEncodeToString(hmac) var respCheck interface{} err := b.SendPayload(verb, - b.APIUrl+path, + b.API.Endpoints.URL+path, headers, bytes.NewBuffer([]byte(payload)), &respCheck, diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index 8a231924..3c303d9e 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -31,11 +31,12 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Bitmex Setup() init error") } - bitmexConfig.AuthenticatedAPISupport = true - bitmexConfig.APIKey = apiKey - bitmexConfig.APISecret = apiSecret - b.Setup(&bitmexConfig) + bitmexConfig.API.AuthenticatedSupport = true + bitmexConfig.API.Credentials.Key = apiKey + bitmexConfig.API.Credentials.Secret = apiSecret + + b.Setup(bitmexConfig) } func TestStart(t *testing.T) { @@ -504,11 +505,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if b.APIKey != "" && b.APIKey != "Key" && - b.APISecret != "" && b.APISecret != "Secret" { - return true - } - return false + return b.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -524,7 +521,7 @@ func TestSubmitOrder(t *testing.T) { Base: currency.XBT, Quote: currency.USD, } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -613,12 +610,15 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { b.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.XBT, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", - OneTimePassword: 000000, + + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + OneTimePassword: 000000, + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -642,8 +642,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -658,8 +657,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index d025583f..6527b38b 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -10,8 +10,10 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -107,7 +109,7 @@ func (b *Bitmex) WsConnector() error { go b.wsHandleIncomingData() b.GenerateDefaultSubscriptions() - if b.AuthenticatedAPISupport { + if b.AllowAuthenticatedRequest() { err := b.websocketSendAuth() if err != nil { return err @@ -286,17 +288,17 @@ func (b *Bitmex) wsHandleIncomingData() { } } -var snapshotloaded = make(map[currency.Pair]map[string]bool) +var snapshotloaded = make(map[currency.Pair]map[assets.AssetType]bool) // ProcessOrderbook processes orderbook updates -func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPair currency.Pair, assetType string) error { // nolint: unparam +func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPair currency.Pair, assetType assets.AssetType) error { // nolint: unparam if len(data) < 1 { return errors.New("bitmex_websocket.go error - no orderbook data") } _, ok := snapshotloaded[currencyPair] if !ok { - snapshotloaded[currencyPair] = make(map[string]bool) + snapshotloaded[currencyPair] = make(map[assets.AssetType]bool) } _, ok = snapshotloaded[currencyPair][assetType] @@ -386,7 +388,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (b *Bitmex) GenerateDefaultSubscriptions() { - contracts := b.GetEnabledCurrencies() + contracts := b.GetEnabledPairs(assets.AssetTypeSpot) channels := []string{bitmexWSOrderbookL2, bitmexWSTrade} subscriptions := []exchange.WebsocketChannelSubscription{ { @@ -426,14 +428,15 @@ func (b *Bitmex) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscri func (b *Bitmex) websocketSendAuth() error { timestamp := time.Now().Add(time.Hour * 1).Unix() newTimestamp := strconv.FormatInt(timestamp, 10) - hmac := common.GetHMAC(common.HashSHA256, + hmac := crypto.GetHMAC(crypto.HashSHA256, []byte("GET/realtime"+newTimestamp), - []byte(b.APISecret)) - signature := common.HexEncodeToString(hmac) + []byte(b.API.Credentials.Secret)) + + signature := crypto.HexEncodeToString(hmac) var sendAuth WebsocketRequest sendAuth.Command = "authKeyExpires" - sendAuth.Arguments = append(sendAuth.Arguments, b.APIKey, timestamp, + sendAuth.Arguments = append(sendAuth.Arguments, b.API.Credentials.Key, timestamp, signature) return b.wsSend(sendAuth) } diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index b7a06a75..399009de 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -6,15 +6,140 @@ import ( "math" "strings" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (b *Bitmex) GetDefaultConfig() (*config.ExchangeConfig, error) { + b.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = b.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = b.BaseCurrencies + + err := b.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if b.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = b.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets the basic defaults for Bitmex +func (b *Bitmex) SetDefaults() { + b.Name = "Bitmex" + b.Enabled = true + b.Verbose = true + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + + b.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypePerpetualContract, + assets.AssetTypeFutures, + assets.AssetTypeDownsideProfitContract, + assets.AssetTypeUpsideProfitContract, + }, + UseGlobalFormat: false, + } + + // Same format used for perpetual contracts and futures + fmt1 := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + b.CurrencyPairs.Store(assets.AssetTypePerpetualContract, fmt1) + b.CurrencyPairs.Store(assets.AssetTypeFutures, fmt1) + + // Upside and Downside profit contracts use the same format + fmt2 := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + } + b.CurrencyPairs.Store(assets.AssetTypeDownsideProfitContract, fmt2) + b.CurrencyPairs.Store(assets.AssetTypeUpsideProfitContract, fmt2) + + b.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | + exchange.WithdrawCryptoWithEmail | + exchange.WithdrawCryptoWith2FA | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + b.Requester = request.New(b.Name, + request.NewRateLimit(time.Second, bitmexAuthRate), + request.NewRateLimit(time.Second, bitmexUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + b.API.Endpoints.URLDefault = bitmexAPIURL + b.API.Endpoints.URL = b.API.Endpoints.URLDefault + b.WebsocketInit() + b.Websocket.Functionality = exchange.WebsocketTradeDataSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup takes in the supplied exchange configuration details and sets params +func (b *Bitmex) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + b.SetEnabled(false) + return nil + } + + err := b.SetupDefaults(exch) + if err != nil { + return err + } + + return b.WebsocketSetup(b.WsConnector, + b.Subscribe, + b.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + bitmexWSURL, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the Bitmex go routine func (b *Bitmex) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -27,38 +152,85 @@ func (b *Bitmex) Start(wg *sync.WaitGroup) { // Run implements the Bitmex wrapper func (b *Bitmex) Run() { if b.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.WebsocketURL) - log.Debugf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + log.Debugf("%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.API.Endpoints.WebsocketURL) + b.PrintEnabledPairs() } - marketInfo, err := b.GetActiveInstruments(&GenericRequestParams{}) + if !b.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := b.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s Failed to get available symbols.\n", b.GetName()) - - } else { - var exchangeProducts []string - for i := range marketInfo { - exchangeProducts = append(exchangeProducts, marketInfo[i].Symbol) - } - - var NewExchangeProducts currency.Pairs - for _, p := range exchangeProducts { - NewExchangeProducts = append(NewExchangeProducts, - currency.NewPairFromString(p)) - } - - err = b.UpdateCurrencies(NewExchangeProducts, false, false) - if err != nil { - log.Errorf("%s Failed to update available currencies.\n", b.GetName()) - } + log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (b *Bitmex) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + marketInfo, err := b.GetActiveInstruments(&GenericRequestParams{}) + if err != nil { + return nil, err + } + + var products []string + for x := range marketInfo { + products = append(products, marketInfo[x].Symbol) + } + + return products, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (b *Bitmex) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + var assetPairs []string + for x := range b.CurrencyPairs.AssetTypes { + switch b.CurrencyPairs.AssetTypes[x] { + case assets.AssetTypePerpetualContract: + for y := range pairs { + if strings.Contains(pairs[y], "USD") { + assetPairs = append(assetPairs, pairs[y]) + } + } + case assets.AssetTypeFutures: + for y := range pairs { + if strings.Contains(pairs[y], "19") { + assetPairs = append(assetPairs, pairs[y]) + } + } + case assets.AssetTypeDownsideProfitContract: + for y := range pairs { + if strings.Contains(pairs[y], "_D") { + assetPairs = append(assetPairs, pairs[y]) + } + } + case assets.AssetTypeUpsideProfitContract: + for y := range pairs { + if strings.Contains(pairs[y], "_U") { + assetPairs = append(assetPairs, pairs[y]) + } + } + } + + err = b.UpdatePairs(currency.NewPairsFromStrings(assetPairs), b.CurrencyPairs.AssetTypes[x], false, false) + if err != nil { + log.Warnf("%s failed to update available pairs. Err: %v", b.Name, err) + } + assetPairs = nil + } + return nil +} + // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bitmex) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (b *Bitmex) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - currency := exchange.FormatExchangeCurrency(b.Name, p) + currency := b.FormatExchangeCurrency(p, assetType) tick, err := b.GetTrade(&GenericRequestParams{ Symbol: currency.String(), @@ -78,8 +250,8 @@ func (b *Bitmex) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, return tickerPrice, ticker.ProcessTicker(b.Name, &tickerPrice, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (b *Bitmex) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (b *Bitmex) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -87,21 +259,21 @@ func (b *Bitmex) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (b *Bitmex) GetOrderbookEx(currency currency.Pair, assetType string) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), currency, assetType) +// FetchOrderbook returns orderbook base on the currency pair +func (b *Bitmex) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { + ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { - return b.UpdateOrderbook(currency, assetType) + return b.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bitmex) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (b *Bitmex) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(OrderBookGetL2Params{ - Symbol: exchange.FormatExchangeCurrency(b.Name, p).String(), + Symbol: b.FormatExchangeCurrency(p, assetType).String(), Depth: 500}) if err != nil { return orderBook, err @@ -166,10 +338,8 @@ func (b *Bitmex) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bitmex) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (b *Bitmex) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -266,7 +436,7 @@ func (b *Bitmex) GetDepositAddress(cryptocurrency currency.Code, _ string) (stri // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (b *Bitmex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitmex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { var request = UserRequestWithdrawalParams{ Address: withdrawRequest.Address, Amount: withdrawRequest.Amount, @@ -287,13 +457,13 @@ func (b *Bitmex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawR // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is // submitted -func (b *Bitmex) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitmex) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is // submitted -func (b *Bitmex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitmex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -304,7 +474,7 @@ func (b *Bitmex) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (b *Bitmex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (b.APIKey == "" || b.APISecret == "") && // Todo check connection status + if !b.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -340,7 +510,7 @@ func (b *Bitmex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ Status: resp[i].OrdStatus, CurrencyPair: currency.NewPairWithDelimiter(resp[i].Symbol, resp[i].SettlCurrency, - b.ConfigCurrencyPairFormat.Delimiter), + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), } orders = append(orders, orderDetail) @@ -351,7 +521,6 @@ func (b *Bitmex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } @@ -383,7 +552,7 @@ func (b *Bitmex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ Status: resp[i].OrdStatus, CurrencyPair: currency.NewPairWithDelimiter(resp[i].Symbol, resp[i].SettlCurrency, - b.ConfigCurrencyPairFormat.Delimiter), + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), } orders = append(orders, orderDetail) @@ -393,7 +562,6 @@ func (b *Bitmex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } diff --git a/exchanges/bitstamp/README.md b/exchanges/bitstamp/README.md index 27921f8e..84659557 100644 --- a/exchanges/bitstamp/README.md +++ b/exchanges/bitstamp/README.md @@ -48,22 +48,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bitstamp" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bitstamp" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 868e16bf..0b5931a4 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -10,15 +10,12 @@ import ( "strconv" "strings" "sync" - "time" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -36,8 +33,6 @@ const ( bitstampAPIOrderStatus = "order_status" bitstampAPICancelOrder = "cancel_order" bitstampAPICancelAllOrders = "cancel_all_orders" - bitstampAPIBuy = "buy" - bitstampAPISell = "sell" bitstampAPIMarket = "market" bitstampAPIWithdrawalRequests = "withdrawal_requests" bitstampAPIOpenWithdrawal = "withdrawal/open" @@ -68,90 +63,6 @@ type Bitstamp struct { wsRequestMtx sync.Mutex } -// SetDefaults sets default for Bitstamp -func (b *Bitstamp) SetDefaults() { - b.Name = "Bitstamp" - b.Enabled = false - b.Verbose = false - b.RESTPollingDelay = 10 - b.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.AutoWithdrawFiat - b.RequestCurrencyPairFormat.Delimiter = "" - b.RequestCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Uppercase = true - b.AssetTypes = []string{ticker.Spot} - b.SupportsAutoPairUpdating = true - b.SupportsRESTTickerBatching = false - b.Requester = request.New(b.Name, - request.NewRateLimit(time.Minute*10, bitstampAuthRate), - request.NewRateLimit(time.Minute*10, bitstampUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - b.APIUrlDefault = bitstampAPIURL - b.APIUrl = b.APIUrlDefault - b.WebsocketInit() - b.Websocket.Functionality = exchange.WebsocketOrderbookSupported | - exchange.WebsocketTradeDataSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup sets configuration values to bitstamp -func (b *Bitstamp) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - b.SetEnabled(false) - } else { - b.Enabled = true - b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - b.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, false) - b.SetHTTPClientTimeout(exch.HTTPTimeout) - b.SetHTTPClientUserAgent(exch.HTTPUserAgent) - b.RESTPollingDelay = exch.RESTPollingDelay - b.Verbose = exch.Verbose - b.HTTPDebugging = exch.HTTPDebugging - b.Websocket.SetWsStatusAndConnection(exch.Websocket) - b.BaseCurrencies = exch.BaseCurrencies - b.AvailablePairs = exch.AvailablePairs - b.EnabledPairs = exch.EnabledPairs - b.APIKey = exch.APIKey - b.APISecret = exch.APISecret - b.SetAPIKeys(exch.APIKey, exch.APISecret, b.ClientID, false) - b.AuthenticatedAPISupport = true - b.WebsocketURL = bitstampWSURL - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = b.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = b.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = b.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = b.WebsocketSetup(b.WsConnect, - b.Subscribe, - b.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - bitstampWSURL, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetFee returns an estimate of fee based on type of transaction func (b *Bitstamp) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) { var fee float64 @@ -243,7 +154,7 @@ func (b *Bitstamp) GetTicker(currency string, hourly bool) (Ticker, error) { path := fmt.Sprintf( "%s/v%s/%s/%s/", - b.APIUrl, + b.API.Endpoints.URL, bitstampAPIVersion, tickerEndpoint, common.StringToLower(currency), @@ -264,7 +175,7 @@ func (b *Bitstamp) GetOrderbook(currency string) (Orderbook, error) { path := fmt.Sprintf( "%s/v%s/%s/%s/", - b.APIUrl, + b.API.Endpoints.URL, bitstampAPIVersion, bitstampAPIOrderbook, common.StringToLower(currency), @@ -315,7 +226,7 @@ func (b *Bitstamp) GetTradingPairs() ([]TradingPair, error) { var result []TradingPair path := fmt.Sprintf("%s/v%s/%s", - b.APIUrl, + b.API.Endpoints.URL, bitstampAPIVersion, bitstampAPITradingPairsInfo) @@ -330,7 +241,7 @@ func (b *Bitstamp) GetTransactions(currencyPair string, values url.Values) ([]Tr path := common.EncodeURLValues( fmt.Sprintf( "%s/v%s/%s/%s/", - b.APIUrl, + b.API.Endpoints.URL, bitstampAPIVersion, bitstampAPITransactions, common.StringToLower(currencyPair), @@ -344,7 +255,7 @@ func (b *Bitstamp) GetTransactions(currencyPair string, values url.Values) ([]Tr // GetEURUSDConversionRate returns the conversion rate between Euro and USD func (b *Bitstamp) GetEURUSDConversionRate() (EURUSDConversionRate, error) { rate := EURUSDConversionRate{} - path := fmt.Sprintf("%s/%s", b.APIUrl, bitstampAPIEURUSD) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bitstampAPIEURUSD) return rate, b.SendHTTPRequest(path, &rate) } @@ -352,7 +263,7 @@ func (b *Bitstamp) GetEURUSDConversionRate() (EURUSDConversionRate, error) { // GetBalance returns full balance of currency held on the exchange func (b *Bitstamp) GetBalance() (Balances, error) { balance := Balances{} - path := fmt.Sprintf("%s/%s", b.APIUrl, bitstampAPIBalance) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bitstampAPIBalance) return balance, b.SendHTTPRequest(path, &balance) } @@ -468,10 +379,10 @@ func (b *Bitstamp) PlaceOrder(currencyPair string, price, amount float64, buy, m req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("price", strconv.FormatFloat(price, 'f', -1, 64)) response := Order{} - orderType := bitstampAPIBuy + orderType := exchange.BuyOrderSide.ToLower().ToString() if !buy { - orderType = bitstampAPISell + orderType = exchange.SellOrderSide.ToLower().ToString() } path := fmt.Sprintf("%s/%s", orderType, common.StringToLower(currencyPair)) @@ -660,7 +571,7 @@ func (b *Bitstamp) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated request func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url.Values, result interface{}) error { - if !b.AuthenticatedAPISupport { + if !b.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } @@ -670,15 +581,17 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url values = url.Values{} } - values.Set("key", b.APIKey) + values.Set("key", b.API.Credentials.Key) values.Set("nonce", n) - hmac := common.GetHMAC(common.HashSHA256, []byte(n+b.ClientID+b.APIKey), []byte(b.APISecret)) - values.Set("signature", common.StringToUpper(common.HexEncodeToString(hmac))) + hmac := crypto.GetHMAC(crypto.HashSHA256, + []byte(n+b.API.Credentials.ClientID+b.API.Credentials.Key), + []byte(b.API.Credentials.Secret)) + values.Set("signature", common.StringToUpper(crypto.HexEncodeToString(hmac))) if v2 { - path = fmt.Sprintf("%s/v%s/%s/", b.APIUrl, bitstampAPIVersion, path) + path = fmt.Sprintf("%s/v%s/%s/", b.API.Endpoints.URL, bitstampAPIVersion, path) } else { - path = fmt.Sprintf("%s/%s/", b.APIUrl, path) + path = fmt.Sprintf("%s/%s/", b.API.Endpoints.URL, path) } if b.Verbose { diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index c2414521..c01438f8 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -3,7 +3,6 @@ package bitstamp import ( "net/url" "testing" - "time" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" @@ -26,18 +25,15 @@ func TestSetDefaults(t *testing.T) { if b.Name != "Bitstamp" { t.Error("Test Failed - SetDefaults() error") } - if b.Enabled { + if !b.Enabled { t.Error("Test Failed - SetDefaults() error") } - if b.Verbose { + if !b.Verbose { t.Error("Test Failed - SetDefaults() error") } if b.Websocket.IsEnabled() { t.Error("Test Failed - SetDefaults() error") } - if b.RESTPollingDelay != 10 { - t.Error("Test Failed - SetDefaults() error") - } } func TestSetup(t *testing.T) { @@ -47,16 +43,14 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Bitstamp Setup() init error") } - bConfig.APIKey = apiKey - bConfig.APISecret = apiSecret - bConfig.ClientID = customerID + bConfig.API.Credentials.Key = apiKey + bConfig.API.Credentials.Secret = apiSecret + bConfig.API.Credentials.ClientID = customerID - b.Setup(&bConfig) - b.ClientID = customerID + b.Setup(bConfig) - if !b.IsEnabled() || b.RESTPollingDelay != time.Duration(10) || - b.Verbose || b.Websocket.IsEnabled() || len(b.BaseCurrencies) < 1 || - len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 { + if !b.IsEnabled() || b.API.AuthenticatedSupport || + b.Verbose || b.Websocket.IsEnabled() || len(b.BaseCurrencies) < 1 { t.Error("Test Failed - Bitstamp Setup values not set correctly") } } @@ -267,8 +261,7 @@ func TestGetOpenOrders(t *testing.T) { func TestGetOrderStatus(t *testing.T) { t.Parallel() - - if !areTestAPIKeysSet() { + if !b.ValidateAPICredentials() { t.Skip() } @@ -298,8 +291,7 @@ func TestCancelAllExistingOrders(t *testing.T) { func TestPlaceOrder(t *testing.T) { t.Parallel() - - if !areTestAPIKeysSet() { + if !b.ValidateAPICredentials() { t.Skip() } @@ -324,8 +316,7 @@ func TestGetWithdrawalRequests(t *testing.T) { func TestCryptoWithdrawal(t *testing.T) { t.Parallel() - - if !areTestAPIKeysSet() { + if !b.ValidateAPICredentials() { t.Skip() } @@ -355,8 +346,7 @@ func TestGetUnconfirmedBitcoinDeposits(t *testing.T) { func TestTransferAccountBalance(t *testing.T) { t.Parallel() - - if !areTestAPIKeysSet() { + if !b.ValidateAPICredentials() { t.Skip() } @@ -417,11 +407,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if b.APIKey != "" && b.APIKey != "Key" && - b.APISecret != "" && b.APISecret != "Secret" { - return true - } - return false + return b.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -436,7 +422,8 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.USD, } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(p, + exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -511,11 +498,14 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { b.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -539,10 +529,12 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.USD, - Description: "WITHDRAW IT ALL", + var withdrawFiatRequest = exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.USD, + Description: "WITHDRAW IT ALL", + }, BankAccountName: "Satoshi Nakamoto", BankAccountNumber: 12345, BankAddress: "123 Fake St", @@ -574,10 +566,12 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.USD, - Description: "WITHDRAW IT ALL", + var withdrawFiatRequest = exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.USD, + Description: "WITHDRAW IT ALL", + }, BankAccountName: "Satoshi Nakamoto", BankAccountNumber: 12345, BankAddress: "123 Fake St", diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 58b4409f..7526907d 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -12,8 +12,8 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -120,7 +120,7 @@ func (b *Bitstamp) WsHandleData() { currencyPair := common.SplitStrings(wsResponse.Channel, "_") p := currency.NewPairFromString(common.StringToUpper(currencyPair[3])) - err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, ticker.Spot) + err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, assets.AssetTypeSpot) if err != nil { b.Websocket.DataHandler <- err continue @@ -143,7 +143,7 @@ func (b *Bitstamp) WsHandleData() { Amount: wsTradeTemp.Data.Amount, CurrencyPair: p, Exchange: b.GetName(), - AssetType: ticker.Spot, + AssetType: assets.AssetTypeSpot, } } } @@ -152,7 +152,7 @@ func (b *Bitstamp) WsHandleData() { func (b *Bitstamp) generateDefaultSubscriptions() { var channels = []string{"live_trades_", "diff_order_book_"} - enabledCurrencies := b.GetEnabledCurrencies() + enabledCurrencies := b.GetEnabledPairs(assets.AssetTypeSpot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { for j := range enabledCurrencies { @@ -192,7 +192,7 @@ func (b *Bitstamp) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubsc return b.WebsocketConn.WriteJSON(req) } -func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, assetType string) error { +func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, assetType assets.AssetType) error { if len(ob.Asks) == 0 && len(ob.Bids) == 0 { return errors.New("bitstamp_websocket.go error - no orderbook data") } @@ -250,7 +250,7 @@ func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, ass } func (b *Bitstamp) seedOrderBook() error { - p := b.GetEnabledCurrencies() + p := b.GetEnabledPairs(assets.AssetTypeSpot) for x := range p { orderbookSeed, err := b.GetOrderbook(p[x].String()) if err != nil { @@ -277,7 +277,7 @@ func (b *Bitstamp) seedOrderBook() error { newOrderBook.Asks = asks newOrderBook.Bids = bids newOrderBook.Pair = p[x] - newOrderBook.AssetType = ticker.Spot + newOrderBook.AssetType = assets.AssetTypeSpot err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false) if err != nil { @@ -286,7 +286,7 @@ func (b *Bitstamp) seedOrderBook() error { b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p[x], - Asset: ticker.Spot, + Asset: assets.AssetTypeSpot, Exchange: b.GetName(), } } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 00183ac3..67a0240d 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -4,18 +4,118 @@ import ( "errors" "fmt" "strconv" - "strings" "sync" "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (b *Bitstamp) GetDefaultConfig() (*config.ExchangeConfig, error) { + b.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = b.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = b.BaseCurrencies + + err := b.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if b.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = b.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets default for Bitstamp +func (b *Bitstamp) SetDefaults() { + b.Name = "Bitstamp" + b.Enabled = true + b.Verbose = true + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + b.API.CredentialsValidator.RequiresClientID = true + + b.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + + b.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.AutoWithdrawFiat, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + b.Requester = request.New(b.Name, + request.NewRateLimit(time.Minute*10, bitstampAuthRate), + request.NewRateLimit(time.Minute*10, bitstampUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + b.API.Endpoints.URLDefault = bitstampAPIURL + b.API.Endpoints.URL = b.API.Endpoints.URLDefault + b.API.Endpoints.WebsocketURL = bitstampWSURL + b.WebsocketInit() + b.Websocket.Functionality = exchange.WebsocketOrderbookSupported | + exchange.WebsocketTradeDataSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup sets configuration values to bitstamp +func (b *Bitstamp) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + b.SetEnabled(false) + return nil + } + + err := b.SetupDefaults(exch) + if err != nil { + return err + } + + return b.WebsocketSetup(b.WsConnect, + b.Subscribe, + b.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + bitstampWSURL, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the Bitstamp go routine func (b *Bitstamp) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -29,38 +129,52 @@ func (b *Bitstamp) Start(wg *sync.WaitGroup) { func (b *Bitstamp) Run() { if b.Verbose { log.Debugf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled())) - log.Debugf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + b.PrintEnabledPairs() } - pairs, err := b.GetTradingPairs() + if !b.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := b.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to get trading pairs. Err: %s", b.Name, err) - } else { - var currencies []string - for x := range pairs { - if pairs[x].Trading != "Enabled" { - continue - } - p := strings.Split(pairs[x].Name, "/") - currencies = append(currencies, p[0]+p[1]) - } - - var newCurrencies currency.Pairs - for _, p := range currencies { - newCurrencies = append(newCurrencies, - currency.NewPairFromString(p)) - } - - err = b.UpdateCurrencies(newCurrencies, false, false) - if err != nil { - log.Errorf("%s Failed to update available currencies.\n", b.Name) - } + log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (b *Bitstamp) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + pairs, err := b.GetTradingPairs() + if err != nil { + return nil, err + } + + var products []string + for x := range pairs { + if pairs[x].Trading != "Enabled" { + continue + } + + pair := common.SplitStrings(pairs[x].Name, "/") + products = append(products, pair[0]+pair[1]) + } + + return products, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (b *Bitstamp) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := b.GetTicker(p.String(), false) if err != nil { @@ -83,8 +197,8 @@ func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType string) (ticker.Price return ticker.GetTicker(b.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (b *Bitstamp) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (b *Bitstamp) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tick, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -94,7 +208,7 @@ func (b *Bitstamp) GetTickerPrice(p currency.Pair, assetType string) (ticker.Pri // GetFeeByType returns an estimate of fee based on type of transaction func (b *Bitstamp) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (b.APIKey == "" || b.APISecret == "") && // Todo check connection status + if !b.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -102,8 +216,8 @@ func (b *Bitstamp) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error } -// GetOrderbookEx returns the orderbook for a currency pair -func (b *Bitstamp) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns the orderbook for a currency pair +func (b *Bitstamp) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -112,7 +226,7 @@ func (b *Bitstamp) GetOrderbookEx(p currency.Pair, assetType string) (orderbook. } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bitstamp) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (b *Bitstamp) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(p.String()) if err != nil { @@ -188,10 +302,8 @@ func (b *Bitstamp) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bitstamp) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (b *Bitstamp) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -253,7 +365,7 @@ func (b *Bitstamp) GetDepositAddress(cryptocurrency currency.Code, _ string) (st // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (b *Bitstamp) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitstamp) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { resp, err := b.CryptoWithdrawal(withdrawRequest.Amount, withdrawRequest.Address, withdrawRequest.Currency.String(), withdrawRequest.AddressTag, true) if err != nil { return "", err @@ -267,7 +379,7 @@ func (b *Bitstamp) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.Withdra // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (b *Bitstamp) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitstamp) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { resp, err := b.OpenBankWithdrawal(withdrawRequest.Amount, withdrawRequest.Currency.String(), withdrawRequest.BankAccountName, withdrawRequest.IBAN, withdrawRequest.SwiftCode, withdrawRequest.BankAddress, withdrawRequest.BankPostalCode, withdrawRequest.BankCity, withdrawRequest.BankCountry, @@ -284,7 +396,7 @@ func (b *Bitstamp) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (b *Bitstamp) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bitstamp) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { resp, err := b.OpenInternationalBankWithdrawal(withdrawRequest.Amount, withdrawRequest.Currency.String(), withdrawRequest.BankAccountName, withdrawRequest.IBAN, withdrawRequest.SwiftCode, withdrawRequest.BankAddress, withdrawRequest.BankPostalCode, withdrawRequest.BankCity, withdrawRequest.BankCountry, @@ -336,7 +448,6 @@ func (b *Bitstamp) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } @@ -381,10 +492,9 @@ func (b *Bitstamp) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) if quoteCurrency.String() != "" && baseCurrency.String() != "" { currPair = currency.NewPairWithDelimiter(baseCurrency.String(), quoteCurrency.String(), - b.ConfigCurrencyPairFormat.Delimiter) + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) } orderDate := time.Unix(order.Date, 0) - orders = append(orders, exchange.OrderDetail{ ID: fmt.Sprintf("%v", order.OrderID), OrderDate: orderDate, @@ -396,7 +506,6 @@ func (b *Bitstamp) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } diff --git a/exchanges/bittrex/README.md b/exchanges/bittrex/README.md index e503988e..60f8ab5a 100644 --- a/exchanges/bittrex/README.md +++ b/exchanges/bittrex/README.md @@ -47,22 +47,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Bittrex" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Bittrex" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index 68a835a7..8da32db1 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -6,15 +6,11 @@ import ( "net/http" "net/url" "strconv" - "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -64,74 +60,11 @@ type Bittrex struct { exchange.Base } -// SetDefaults method assignes the default values for Bittrex -func (b *Bittrex) SetDefaults() { - b.Name = "Bittrex" - b.Enabled = false - b.Verbose = false - b.RESTPollingDelay = 10 - b.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithAPIPermission | - exchange.NoFiatWithdrawals - b.RequestCurrencyPairFormat.Delimiter = "-" - b.RequestCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Delimiter = "-" - b.ConfigCurrencyPairFormat.Uppercase = true - b.AssetTypes = []string{ticker.Spot} - b.SupportsAutoPairUpdating = true - b.SupportsRESTTickerBatching = true - b.Requester = request.New(b.Name, - request.NewRateLimit(time.Second, bittrexAuthRate), - request.NewRateLimit(time.Second, bittrexUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - b.APIUrlDefault = bittrexAPIURL - b.APIUrl = b.APIUrlDefault - b.WebsocketInit() -} - -// Setup method sets current configuration details if enabled -func (b *Bittrex) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - b.SetEnabled(false) - } else { - b.Enabled = true - b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - b.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, false) - b.SetHTTPClientTimeout(exch.HTTPTimeout) - b.SetHTTPClientUserAgent(exch.HTTPUserAgent) - b.RESTPollingDelay = exch.RESTPollingDelay - b.Verbose = exch.Verbose - b.HTTPDebugging = exch.HTTPDebugging - b.BaseCurrencies = exch.BaseCurrencies - b.AvailablePairs = exch.AvailablePairs - b.EnabledPairs = exch.EnabledPairs - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = b.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = b.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = b.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - } -} - // GetMarkets is used to get the open and available trading markets at Bittrex // along with other meta data. func (b *Bittrex) GetMarkets() (Market, error) { var markets Market - path := fmt.Sprintf("%s/%s/", b.APIUrl, bittrexAPIGetMarkets) + path := fmt.Sprintf("%s/%s/", b.API.Endpoints.URL, bittrexAPIGetMarkets) if err := b.SendHTTPRequest(path, &markets); err != nil { return markets, err @@ -146,7 +79,7 @@ func (b *Bittrex) GetMarkets() (Market, error) { // GetCurrencies is used to get all supported currencies at Bittrex func (b *Bittrex) GetCurrencies() (Currency, error) { var currencies Currency - path := fmt.Sprintf("%s/%s/", b.APIUrl, bittrexAPIGetCurrencies) + path := fmt.Sprintf("%s/%s/", b.API.Endpoints.URL, bittrexAPIGetCurrencies) if err := b.SendHTTPRequest(path, ¤cies); err != nil { return currencies, err @@ -162,7 +95,7 @@ func (b *Bittrex) GetCurrencies() (Currency, error) { // on the supplied currency. Example currency input param "btc-ltc". func (b *Bittrex) GetTicker(currencyPair string) (Ticker, error) { tick := Ticker{} - path := fmt.Sprintf("%s/%s?market=%s", b.APIUrl, bittrexAPIGetTicker, + path := fmt.Sprintf("%s/%s?market=%s", b.API.Endpoints.URL, bittrexAPIGetTicker, common.StringToUpper(currencyPair), ) @@ -180,7 +113,7 @@ func (b *Bittrex) GetTicker(currencyPair string) (Ticker, error) { // exchanges func (b *Bittrex) GetMarketSummaries() (MarketSummary, error) { var summaries MarketSummary - path := fmt.Sprintf("%s/%s/", b.APIUrl, bittrexAPIGetMarketSummaries) + path := fmt.Sprintf("%s/%s/", b.API.Endpoints.URL, bittrexAPIGetMarketSummaries) if err := b.SendHTTPRequest(path, &summaries); err != nil { return summaries, err @@ -196,7 +129,7 @@ func (b *Bittrex) GetMarketSummaries() (MarketSummary, error) { // exchanges by currency pair (btc-ltc). func (b *Bittrex) GetMarketSummary(currencyPair string) (MarketSummary, error) { var summary MarketSummary - path := fmt.Sprintf("%s/%s?market=%s", b.APIUrl, + path := fmt.Sprintf("%s/%s?market=%s", b.API.Endpoints.URL, bittrexAPIGetMarketSummary, common.StringToLower(currencyPair), ) @@ -219,7 +152,7 @@ func (b *Bittrex) GetMarketSummary(currencyPair string) (MarketSummary, error) { // it returns full depth. So depth default is 50. func (b *Bittrex) GetOrderbook(currencyPair string) (OrderBooks, error) { var orderbooks OrderBooks - path := fmt.Sprintf("%s/%s?market=%s&type=both&depth=50", b.APIUrl, + path := fmt.Sprintf("%s/%s?market=%s&type=both&depth=50", b.API.Endpoints.URL, bittrexAPIGetOrderbook, common.StringToUpper(currencyPair), ) @@ -237,7 +170,7 @@ func (b *Bittrex) GetOrderbook(currencyPair string) (OrderBooks, error) { // market func (b *Bittrex) GetMarketHistory(currencyPair string) (MarketHistory, error) { var marketHistoriae MarketHistory - path := fmt.Sprintf("%s/%s?market=%s", b.APIUrl, + path := fmt.Sprintf("%s/%s?market=%s", b.API.Endpoints.URL, bittrexAPIGetMarketHistory, common.StringToUpper(currencyPair), ) @@ -263,7 +196,7 @@ func (b *Bittrex) PlaceBuyLimit(currencyPair string, quantity, rate float64) (UU values.Set("market", currencyPair) values.Set("quantity", strconv.FormatFloat(quantity, 'E', -1, 64)) values.Set("rate", strconv.FormatFloat(rate, 'E', -1, 64)) - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPIBuyLimit) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPIBuyLimit) if err := b.SendAuthenticatedHTTPRequest(path, values, &id); err != nil { return id, err @@ -287,7 +220,7 @@ func (b *Bittrex) PlaceSellLimit(currencyPair string, quantity, rate float64) (U values.Set("market", currencyPair) values.Set("quantity", strconv.FormatFloat(quantity, 'E', -1, 64)) values.Set("rate", strconv.FormatFloat(rate, 'E', -1, 64)) - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPISellLimit) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPISellLimit) if err := b.SendAuthenticatedHTTPRequest(path, values, &id); err != nil { return id, err @@ -307,7 +240,7 @@ func (b *Bittrex) GetOpenOrders(currencyPair string) (Order, error) { if !(currencyPair == "" || currencyPair == " ") { values.Set("market", currencyPair) } - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPIGetOpenOrders) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPIGetOpenOrders) if err := b.SendAuthenticatedHTTPRequest(path, values, &orders); err != nil { return orders, err @@ -324,7 +257,7 @@ func (b *Bittrex) CancelExistingOrder(uuid string) (Balances, error) { var balances Balances values := url.Values{} values.Set("uuid", uuid) - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPICancel) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPICancel) if err := b.SendAuthenticatedHTTPRequest(path, values, &balances); err != nil { return balances, err @@ -339,7 +272,7 @@ func (b *Bittrex) CancelExistingOrder(uuid string) (Balances, error) { // GetAccountBalances is used to retrieve all balances from your account func (b *Bittrex) GetAccountBalances() (Balances, error) { var balances Balances - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPIGetBalances) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPIGetBalances) if err := b.SendAuthenticatedHTTPRequest(path, url.Values{}, &balances); err != nil { return balances, err @@ -357,7 +290,7 @@ func (b *Bittrex) GetAccountBalanceByCurrency(currency string) (Balance, error) var balance Balance values := url.Values{} values.Set("currency", currency) - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPIGetBalance) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPIGetBalance) if err := b.SendAuthenticatedHTTPRequest(path, values, &balance); err != nil { return balance, err @@ -376,7 +309,7 @@ func (b *Bittrex) GetCryptoDepositAddress(currency string) (DepositAddress, erro var address DepositAddress values := url.Values{} values.Set("currency", currency) - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPIGetDepositAddress) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPIGetDepositAddress) if err := b.SendAuthenticatedHTTPRequest(path, values, &address); err != nil { return address, err @@ -400,7 +333,7 @@ func (b *Bittrex) Withdraw(currency, paymentID, address string, quantity float64 values.Set("paymentid", paymentID) } - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPIWithdraw) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPIWithdraw) if err := b.SendAuthenticatedHTTPRequest(path, values, &id); err != nil { return id, err @@ -417,7 +350,7 @@ func (b *Bittrex) GetOrder(uuid string) (Order, error) { var order Order values := url.Values{} values.Set("uuid", uuid) - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPIGetOrder) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPIGetOrder) if err := b.SendAuthenticatedHTTPRequest(path, values, &order); err != nil { return order, err @@ -438,7 +371,7 @@ func (b *Bittrex) GetOrderHistoryForCurrency(currencyPair string) (Order, error) if !(currencyPair == "" || currencyPair == " ") { values.Set("market", currencyPair) } - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPIGetOrderHistory) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPIGetOrderHistory) if err := b.SendAuthenticatedHTTPRequest(path, values, &orders); err != nil { return orders, err @@ -459,7 +392,7 @@ func (b *Bittrex) GetWithdrawalHistory(currency string) (WithdrawalHistory, erro if !(currency == "" || currency == " ") { values.Set("currency", currency) } - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPIGetWithdrawalHistory) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPIGetWithdrawalHistory) if err := b.SendAuthenticatedHTTPRequest(path, values, &history); err != nil { return history, err @@ -480,7 +413,7 @@ func (b *Bittrex) GetDepositHistory(currency string) (WithdrawalHistory, error) if !(currency == "" || currency == " ") { values.Set("currency", currency) } - path := fmt.Sprintf("%s/%s", b.APIUrl, bittrexAPIGetDepositHistory) + path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bittrexAPIGetDepositHistory) if err := b.SendAuthenticatedHTTPRequest(path, values, &history); err != nil { return history, err @@ -500,20 +433,20 @@ func (b *Bittrex) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated http request to a desired // path func (b *Bittrex) SendAuthenticatedHTTPRequest(path string, values url.Values, result interface{}) (err error) { - if !b.AuthenticatedAPISupport { + if !b.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } n := b.Requester.GetNonce(true).String() - values.Set("apikey", b.APIKey) + values.Set("apikey", b.API.Credentials.Key) values.Set("nonce", n) rawQuery := path + "?" + values.Encode() - hmac := common.GetHMAC( - common.HashSHA512, []byte(rawQuery), []byte(b.APISecret), + hmac := crypto.GetHMAC( + crypto.HashSHA512, []byte(rawQuery), []byte(b.API.Credentials.Secret), ) headers := make(map[string]string) - headers["apisign"] = common.HexEncodeToString(hmac) + headers["apisign"] = crypto.HexEncodeToString(hmac) return b.SendPayload(http.MethodGet, rawQuery, headers, nil, result, true, true, b.Verbose, b.HTTPDebugging) } diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 37d29260..323f87b3 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -2,7 +2,6 @@ package bittrex import ( "testing" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" @@ -33,16 +32,14 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Bittrex Setup() init error") } - bConfig.APIKey = apiKey - bConfig.APISecret = apiSecret - bConfig.AuthenticatedAPISupport = true + bConfig.API.Credentials.Key = apiKey + bConfig.API.Credentials.Secret = apiSecret + bConfig.API.AuthenticatedSupport = true - b.Setup(&bConfig) + b.Setup(bConfig) - if !b.IsEnabled() || - b.RESTPollingDelay != time.Duration(10) || b.Verbose || - b.Websocket.IsEnabled() || len(b.BaseCurrencies) < 1 || - len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 { + if !b.IsEnabled() || !b.API.AuthenticatedSupport || + b.Verbose || len(b.BaseCurrencies) < 1 { t.Error("Test Failed - Bittrex Setup values not set correctly") } } @@ -365,11 +362,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if b.APIKey != "" && b.APIKey != "Key" && - b.APISecret != "" && b.APISecret != "Secret" { - return true - } - return false + return b.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -460,11 +453,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { b.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -488,7 +483,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { @@ -504,7 +499,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index bfae5b73..9f04cddb 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -8,13 +8,97 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (b *Bittrex) GetDefaultConfig() (*config.ExchangeConfig, error) { + b.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = b.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = b.BaseCurrencies + + err := b.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if b.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = b.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults method assignes the default values for Bittrex +func (b *Bittrex) SetDefaults() { + b.Name = "Bittrex" + b.Enabled = true + b.Verbose = true + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + + b.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + } + + b.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: false, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + b.Requester = request.New(b.Name, + request.NewRateLimit(time.Second, bittrexAuthRate), + request.NewRateLimit(time.Second, bittrexUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + b.API.Endpoints.URLDefault = bittrexAPIURL + b.API.Endpoints.URL = b.API.Endpoints.URLDefault +} + +// Setup method sets current configuration details if enabled +func (b *Bittrex) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + b.SetEnabled(false) + return nil + } + + return b.SetupDefaults(exch) +} + // Start starts the Bittrex go routine func (b *Bittrex) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -27,51 +111,59 @@ func (b *Bittrex) Start(wg *sync.WaitGroup) { // Run implements the Bittrex wrapper func (b *Bittrex) Run() { if b.Verbose { - log.Debugf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + b.PrintEnabledPairs() } - exchangeProducts, err := b.GetMarkets() - if err != nil { - log.Errorf("%s Failed to get available symbols.\n", b.GetName()) - } else { - forceUpgrade := false - if !common.StringDataContains(b.EnabledPairs.Strings(), "-") || - !common.StringDataContains(b.AvailablePairs.Strings(), "-") { - forceUpgrade = true - } - var currencies []string - for x := range exchangeProducts.Result { - if !exchangeProducts.Result[x].IsActive || - exchangeProducts.Result[x].MarketName == "" { - continue - } - currencies = append(currencies, exchangeProducts.Result[x].MarketName) - } + forceUpdate := false + if !common.StringDataContains(b.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "-") || + !common.StringDataContains(b.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "-") { + forceUpdate = true + enabledPairs := []string{"USDT-BTC"} + log.Warn("WARNING: Available pairs for Bittrex reset due to config upgrade, please enable the ones you would like again") - if forceUpgrade { - enabledPairs := currency.Pairs{currency.Pair{Base: currency.USDT, - Quote: currency.BTC, Delimiter: "-"}} - - log.Warn("Available pairs for Bittrex reset due to config upgrade, please enable the ones you would like again") - - err = b.UpdateCurrencies(enabledPairs, true, true) - if err != nil { - log.Errorf("%s Failed to get config.", b.GetName()) - } - } - - var newCurrencies currency.Pairs - for _, p := range currencies { - newCurrencies = append(newCurrencies, - currency.NewPairFromString(p)) - } - - err = b.UpdateCurrencies(newCurrencies, false, forceUpgrade) + err := b.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), assets.AssetTypeSpot, true, true) if err != nil { - log.Errorf("%s Failed to get config.", b.GetName()) + log.Errorf("%s failed to update currencies. Err: %s\n", b.Name, err) } } + + if !b.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { + return + } + + err := b.UpdateTradablePairs(forceUpdate) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + } +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (b *Bittrex) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + markets, err := b.GetMarkets() + if err != nil { + return nil, err + } + + var pairs []string + for x := range markets.Result { + if !markets.Result[x].IsActive || markets.Result[x].MarketName == "" { + continue + } + pairs = append(pairs, markets.Result[x].MarketName) + } + + return pairs, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (b *Bittrex) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) } // GetAccountInfo Retrieves balances for all enabled currencies for the @@ -101,15 +193,15 @@ func (b *Bittrex) GetAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bittrex) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (b *Bittrex) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := b.GetMarketSummaries() if err != nil { return tickerPrice, err } - for _, x := range b.GetEnabledCurrencies() { - curr := exchange.FormatExchangeCurrency(b.Name, x) + for _, x := range b.GetEnabledPairs(assetType) { + curr := b.FormatExchangeCurrency(x, assetType) for y := range tick.Result { if tick.Result[y].MarketName != curr.String() { continue @@ -127,17 +219,17 @@ func (b *Bittrex) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, return ticker.GetTicker(b.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (b *Bittrex) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { - tick, err := ticker.GetTicker(b.GetName(), p, ticker.Spot) +// FetchTicker returns the ticker for a currency pair +func (b *Bittrex) FetchTicker(p currency.Pair, assetType assets.AssetType) (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 *Bittrex) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns the orderbook for a currency pair +func (b *Bittrex) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -146,9 +238,9 @@ func (b *Bittrex) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.B } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bittrex) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (b *Bittrex) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := b.GetOrderbook(exchange.FormatExchangeCurrency(b.GetName(), p).String()) + orderbookNew, err := b.GetOrderbook(b.FormatExchangeCurrency(p, assetType).String()) if err != nil { return orderBook, err } @@ -191,10 +283,8 @@ func (b *Bittrex) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bittrex) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (b *Bittrex) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -276,20 +366,20 @@ func (b *Bittrex) GetDepositAddress(cryptocurrency currency.Code, _ string) (str // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (b *Bittrex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bittrex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { uuid, err := b.Withdraw(withdrawRequest.Currency.String(), withdrawRequest.AddressTag, withdrawRequest.Address, withdrawRequest.Amount) return fmt.Sprintf("%v", uuid), err } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (b *Bittrex) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bittrex) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (b *Bittrex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *Bittrex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -300,7 +390,7 @@ func (b *Bittrex) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (b *Bittrex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (b.APIKey == "" || b.APISecret == "") && // Todo check connection status + if !b.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -329,7 +419,7 @@ func (b *Bittrex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( } pair := currency.NewPairDelimiter(resp.Result[i].Exchange, - b.ConfigCurrencyPairFormat.Delimiter) + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderType := exchange.OrderType(strings.ToUpper(resp.Result[i].Type)) orders = append(orders, exchange.OrderDetail{ @@ -348,7 +438,6 @@ func (b *Bittrex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } @@ -374,7 +463,7 @@ func (b *Bittrex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( } pair := currency.NewPairDelimiter(resp.Result[i].Exchange, - b.ConfigCurrencyPairFormat.Delimiter) + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderType := exchange.OrderType(strings.ToUpper(resp.Result[i].Type)) orders = append(orders, exchange.OrderDetail{ @@ -394,7 +483,6 @@ func (b *Bittrex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } diff --git a/exchanges/btcc/README.md b/exchanges/btcc/README.md index df3e7678..2a9ca277 100644 --- a/exchanges/btcc/README.md +++ b/exchanges/btcc/README.md @@ -48,22 +48,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "BTCC" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "BTCC" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/btcc/btcc.go b/exchanges/btcc/btcc.go index 06dfd718..8706b35b 100644 --- a/exchanges/btcc/btcc.go +++ b/exchanges/btcc/btcc.go @@ -2,16 +2,10 @@ package btcc import ( "sync" - "time" "github.com/gorilla/websocket" - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -28,82 +22,6 @@ type BTCC struct { wsRequestMtx sync.Mutex } -// SetDefaults sets default values for the exchange -func (b *BTCC) SetDefaults() { - b.Name = "BTCC" - b.Enabled = false - b.Fee = 0 - b.Verbose = false - b.RESTPollingDelay = 10 - b.APIWithdrawPermissions = exchange.NoAPIWithdrawalMethods - b.RequestCurrencyPairFormat.Delimiter = "" - b.RequestCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Uppercase = true - b.AssetTypes = []string{ticker.Spot} - b.SupportsAutoPairUpdating = true - b.SupportsRESTTickerBatching = false - b.Requester = request.New(b.Name, - request.NewRateLimit(time.Second, btccAuthRate), - request.NewRateLimit(time.Second, btccUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - b.WebsocketInit() - b.Websocket.Functionality = - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup is run on startup to setup exchange with config values -func (b *BTCC) 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.SetHTTPClientTimeout(exch.HTTPTimeout) - b.SetHTTPClientUserAgent(exch.HTTPUserAgent) - b.RESTPollingDelay = exch.RESTPollingDelay - b.Verbose = exch.Verbose - b.HTTPDebugging = exch.HTTPDebugging - b.Websocket.SetWsStatusAndConnection(exch.Websocket) - b.BaseCurrencies = exch.BaseCurrencies - b.AvailablePairs = exch.AvailablePairs - b.EnabledPairs = exch.EnabledPairs - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = b.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = b.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = b.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = b.WebsocketSetup(b.WsConnect, - b.Subscribe, - b.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - btccSocketioAddress, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetFee returns an estimate of fee based on type of transaction func (b *BTCC) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) { var fee float64 diff --git a/exchanges/btcc/btcc_test.go b/exchanges/btcc/btcc_test.go index 6b81eba6..3818cc5d 100644 --- a/exchanges/btcc/btcc_test.go +++ b/exchanges/btcc/btcc_test.go @@ -2,7 +2,6 @@ package btcc import ( "testing" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" @@ -30,12 +29,11 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - BTCC Setup() init error") } - b.Setup(&bConfig) + b.Setup(bConfig) - if !b.IsEnabled() || b.AuthenticatedAPISupport || - b.RESTPollingDelay != time.Duration(10) || b.Verbose || + if !b.IsEnabled() || b.API.AuthenticatedSupport || b.Websocket.IsEnabled() || len(b.BaseCurrencies) < 1 || - len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 { + b.Verbose { t.Error("Test Failed - BTCC Setup values not set correctly") } } @@ -217,11 +215,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if b.APIKey != "" && b.APIKey != "Key" && - b.APISecret != "" && b.APISecret != "Secret" { - return true - } - return false + return b.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -293,11 +287,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestWithdraw(t *testing.T) { b.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -325,8 +321,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -341,8 +336,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/btcc/btcc_websocket.go b/exchanges/btcc/btcc_websocket.go index 7c5ac45a..6bc7fddd 100644 --- a/exchanges/btcc/btcc_websocket.go +++ b/exchanges/btcc/btcc_websocket.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -206,7 +207,7 @@ func (b *BTCC) WsHandleData() { } tick := exchange.TickerData{} - tick.AssetType = "SPOT" + tick.AssetType = assets.AssetTypeSpot tick.ClosePrice = ticker.PrevCls tick.Exchange = b.GetName() tick.HighPrice = ticker.High @@ -272,7 +273,7 @@ func (b *BTCC) WsUpdateCurrencyPairs() error { currency.NewPairFromString(tickers[i].Symbol)) } - err = b.UpdateCurrencies(availableTickers, false, true) + err = b.UpdatePairs(availableTickers, assets.AssetTypeSpot, false, true) if err != nil { return fmt.Errorf("%s failed to update available currencies. %s", b.Name, @@ -315,7 +316,7 @@ func (b *BTCC) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { var newOrderBook orderbook.Base newOrderBook.Asks = asks - newOrderBook.AssetType = "SPOT" + newOrderBook.AssetType = assets.AssetTypeSpot newOrderBook.Bids = bids newOrderBook.Pair = currency.NewPairFromString(ob.Symbol) @@ -326,7 +327,7 @@ func (b *BTCC) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: b.GetName(), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Pair: currency.NewPairFromString(ob.Symbol), } @@ -368,14 +369,14 @@ func (b *BTCC) WsProcessOrderbookUpdate(ob *WsOrderbookSnapshot) error { p := currency.NewPairFromString(ob.Symbol) - err := b.Websocket.Orderbook.Update(bids, asks, p, time.Now(), b.GetName(), "SPOT") + err := b.Websocket.Orderbook.Update(bids, asks, p, time.Now(), b.GetName(), assets.AssetTypeSpot) if err != nil { return err } b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: b.GetName(), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Pair: currency.NewPairFromString(ob.Symbol), } @@ -454,8 +455,7 @@ func (b *BTCC) WsProcessOldOrderbookSnapshot(ob WsOrderbookSnapshotOld, symbol s } p := currency.NewPairFromString(symbol) - - err := b.Websocket.Orderbook.Update(bids, asks, p, time.Now(), b.GetName(), "SPOT") + err := b.Websocket.Orderbook.Update(bids, asks, p, time.Now(), b.GetName(), assets.AssetTypeSpot) if err != nil { return err } @@ -463,7 +463,7 @@ func (b *BTCC) WsProcessOldOrderbookSnapshot(ob WsOrderbookSnapshotOld, symbol s b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: b.GetName(), Pair: p, - Asset: "SPOT", + Asset: assets.AssetTypeSpot, } return nil @@ -477,7 +477,7 @@ func (b *BTCC) GenerateDefaultSubscriptions() { }) var channels = []string{"SubOrderBook", "GetTrades", "Subscribe"} - enabledCurrencies := b.GetEnabledCurrencies() + enabledCurrencies := b.GetEnabledPairs(assets.AssetTypeSpot) for i := range channels { for j := range enabledCurrencies { params := make(map[string]interface{}) diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index 673640d9..55537005 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -2,16 +2,111 @@ package btcc import ( "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (b *BTCC) GetDefaultConfig() (*config.ExchangeConfig, error) { + b.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = b.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = b.BaseCurrencies + + err := b.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if b.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = b.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets default values for the exchange +func (b *BTCC) SetDefaults() { + b.Name = "BTCC" + b.Enabled = true + b.Verbose = true + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + + b.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + + b.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: false, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: false, + TickerBatching: false, + }, + WithdrawPermissions: exchange.NoAPIWithdrawalMethods, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: false, + }, + } + + b.Requester = request.New(b.Name, + request.NewRateLimit(time.Second, btccAuthRate), + request.NewRateLimit(time.Second, btccUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + b.WebsocketInit() + b.Websocket.Functionality = + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup is run on startup to setup exchange with config values +func (b *BTCC) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + b.SetEnabled(false) + return nil + } + + err := b.SetupDefaults(exch) + if err != nil { + return err + } + + return b.WebsocketSetup(b.WsConnect, + b.Subscribe, + b.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + btccSocketioAddress, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the BTCC go routine func (b *BTCC) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -25,16 +120,14 @@ func (b *BTCC) Start(wg *sync.WaitGroup) { func (b *BTCC) Run() { if b.Verbose { log.Debugf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled())) - log.Debugf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + b.PrintEnabledPairs() } - if common.StringDataContains(b.EnabledPairs.Strings(), "CNY") || - common.StringDataContains(b.AvailablePairs.Strings(), "CNY") || + if common.StringDataContains(b.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "CNY") || + common.StringDataContains(b.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "CNY") || common.StringDataContains(b.BaseCurrencies.Strings(), "CNY") { - log.Warn("BTCC only supports BTCUSD now, upgrading available, enabled and base currencies to BTCUSD/USD") - pairs := currency.Pairs{currency.Pair{Base: currency.BTC, - Quote: currency.USD}} + log.Warn("WARNING: BTCC only supports BTCUSD now, upgrading available, enabled and base currencies to BTCUSD/USD") + pairs := currency.NewPairsFromStrings([]string{"BTCUSD"}) cfg := config.GetConfig() exchCfg, err := cfg.GetExchangeConfig(b.Name) if err != nil { @@ -43,21 +136,21 @@ func (b *BTCC) Run() { } exchCfg.BaseCurrencies = currency.Currencies{currency.USD} - exchCfg.AvailablePairs = pairs - exchCfg.EnabledPairs = pairs + exchCfg.CurrencyPairs.StorePairs(assets.AssetTypeSpot, pairs, true) + exchCfg.CurrencyPairs.StorePairs(assets.AssetTypeSpot, pairs, false) b.BaseCurrencies = currency.Currencies{currency.USD} - err = b.UpdateCurrencies(pairs, false, true) + err = b.UpdatePairs(pairs, assets.AssetTypeSpot, false, true) if err != nil { log.Errorf("%s failed to update available currencies. %s\n", b.Name, err) } - err = b.UpdateCurrencies(pairs, true, true) + err = b.UpdatePairs(pairs, assets.AssetTypeSpot, true, true) if err != nil { log.Errorf("%s failed to update enabled currencies. %s\n", b.Name, err) } - err = cfg.UpdateExchangeConfig(&exchCfg) + err = cfg.UpdateExchangeConfig(exchCfg) if err != nil { log.Errorf("%s failed to update config. %s\n", b.Name, err) return @@ -65,23 +158,34 @@ func (b *BTCC) Run() { } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (b *BTCC) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + return nil, common.ErrFunctionNotSupported +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (b *BTCC) UpdateTradablePairs(forceUpdate bool) error { + return common.ErrFunctionNotSupported +} + // UpdateTicker updates and returns the ticker for a currency pair -func (b *BTCC) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (b *BTCC) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { return ticker.Price{}, common.ErrFunctionNotSupported } -// GetTickerPrice returns the ticker for a currency pair -func (b *BTCC) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (b *BTCC) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { return ticker.Price{}, common.ErrFunctionNotSupported } -// GetOrderbookEx returns the orderbook for a currency pair -func (b *BTCC) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns the orderbook for a currency pair +func (b *BTCC) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { return orderbook.Base{}, common.ErrFunctionNotSupported } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *BTCC) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (b *BTCC) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { return orderbook.Base{}, common.ErrFunctionNotSupported } @@ -98,7 +202,7 @@ func (b *BTCC) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *BTCC) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { +func (b *BTCC) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { return nil, common.ErrFunctionNotSupported } @@ -135,19 +239,19 @@ func (b *BTCC) GetDepositAddress(cryptocurrency currency.Code, accountID string) // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (b *BTCC) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *BTCC) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (b *BTCC) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *BTCC) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (b *BTCC) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *BTCC) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -158,7 +262,7 @@ func (b *BTCC) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (b *BTCC) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (b.APIKey == "" || b.APISecret == "") && // Todo check connection status + if !b.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } diff --git a/exchanges/btcmarkets/README.md b/exchanges/btcmarkets/README.md index 27f61a44..726b11b7 100644 --- a/exchanges/btcmarkets/README.md +++ b/exchanges/btcmarkets/README.md @@ -47,22 +47,22 @@ main.go ```go var b exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "BTCMarkets" { - b = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "BTCMarkets" { + b = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := b.GetTickerPrice() +tick, err := b.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := b.GetOrderbookEx() +ob, err := b.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index b70e4c52..d6c98144 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -6,14 +6,11 @@ import ( "fmt" "net/http" "net/url" - "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -48,72 +45,6 @@ const ( // BTCMarkets is the overarching type across the BTCMarkets package type BTCMarkets struct { exchange.Base - Ticker map[string]Ticker -} - -// SetDefaults sets basic defaults -func (b *BTCMarkets) SetDefaults() { - b.Name = "BTC Markets" - b.Enabled = false - b.Fee = 0.85 - b.Verbose = false - b.RESTPollingDelay = 10 - b.Ticker = make(map[string]Ticker) - b.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.AutoWithdrawFiat - b.RequestCurrencyPairFormat.Delimiter = "" - b.RequestCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Delimiter = "-" - b.ConfigCurrencyPairFormat.Uppercase = true - b.AssetTypes = []string{ticker.Spot} - b.SupportsAutoPairUpdating = true - b.SupportsRESTTickerBatching = false - b.Requester = request.New(b.Name, - request.NewRateLimit(time.Second*10, btcmarketsAuthLimit), - request.NewRateLimit(time.Second*10, btcmarketsUnauthLimit), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - b.APIUrlDefault = btcMarketsAPIURL - b.APIUrl = b.APIUrlDefault - b.WebsocketInit() -} - -// Setup takes in an exchange configuration and sets all parameters -func (b *BTCMarkets) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - b.SetEnabled(false) - } else { - b.Enabled = true - b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - b.SetAPIKeys(exch.APIKey, exch.APISecret, "", true) - b.SetHTTPClientTimeout(exch.HTTPTimeout) - b.SetHTTPClientUserAgent(exch.HTTPUserAgent) - b.RESTPollingDelay = exch.RESTPollingDelay - b.Verbose = exch.Verbose - b.HTTPDebugging = exch.HTTPDebugging - b.BaseCurrencies = exch.BaseCurrencies - b.AvailablePairs = exch.AvailablePairs - b.EnabledPairs = exch.EnabledPairs - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = b.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = b.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = b.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - } } // GetMarkets returns the BTCMarkets instruments @@ -124,7 +55,7 @@ func (b *BTCMarkets) GetMarkets() ([]Market, error) { } var resp marketsResp - path := fmt.Sprintf("%s/v2/market/active", b.APIUrl) + path := fmt.Sprintf("%s/v2/market/active", b.API.Endpoints.URL) err := b.SendHTTPRequest(path, &resp) if err != nil { @@ -143,7 +74,7 @@ func (b *BTCMarkets) GetMarkets() ([]Market, error) { func (b *BTCMarkets) GetTicker(firstPair, secondPair string) (Ticker, error) { tick := Ticker{} path := fmt.Sprintf("%s/market/%s/%s/tick", - b.APIUrl, + b.API.Endpoints.URL, common.StringToUpper(firstPair), common.StringToUpper(secondPair)) @@ -155,7 +86,7 @@ func (b *BTCMarkets) GetTicker(firstPair, secondPair string) (Ticker, error) { func (b *BTCMarkets) GetOrderbook(firstPair, secondPair string) (Orderbook, error) { orderbook := Orderbook{} path := fmt.Sprintf("%s/market/%s/%s/orderbook", - b.APIUrl, + b.API.Endpoints.URL, common.StringToUpper(firstPair), common.StringToUpper(secondPair)) @@ -168,7 +99,7 @@ func (b *BTCMarkets) GetOrderbook(firstPair, secondPair string) (Orderbook, erro func (b *BTCMarkets) GetTrades(firstPair, secondPair string, values url.Values) ([]Trade, error) { var trades []Trade path := common.EncodeURLValues(fmt.Sprintf("%s/market/%s/%s/trades", - b.APIUrl, common.StringToUpper(firstPair), + b.API.Endpoints.URL, common.StringToUpper(firstPair), common.StringToUpper(secondPair)), values) return trades, b.SendHTTPRequest(path, &trades) @@ -182,7 +113,7 @@ func (b *BTCMarkets) GetTrades(firstPair, secondPair string, values url.Values) // orderside - example "Bid" or "Ask" // orderType - example "limit" // clientReq - example "abc-cdf-1000" -func (b *BTCMarkets) NewOrder(currency, instrument string, price, amount float64, orderSide, orderType, clientReq string) (int64, error) { +func (b *BTCMarkets) NewOrder(instrument, currency string, price, amount float64, orderSide, orderType, clientReq string) (int64, error) { newPrice := int64(price * float64(common.SatoshisPerBTC)) newVolume := int64(amount * float64(common.SatoshisPerBTC)) @@ -437,7 +368,7 @@ func (b *BTCMarkets) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedRequest sends an authenticated HTTP request func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data, result interface{}) (err error) { - if !b.AuthenticatedAPISupport { + if !b.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } @@ -457,13 +388,13 @@ func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data, result req = path + "\n" + n + "\n" } - hmac := common.GetHMAC(common.HashSHA512, - []byte(req), []byte(b.APISecret)) + hmac := crypto.GetHMAC(crypto.HashSHA512, + []byte(req), []byte(b.API.Credentials.Secret)) if b.Verbose { log.Debugf("Sending %s request to URL %s with params %s\n", reqType, - b.APIUrl+path, + b.API.Endpoints.URL+path, req) } @@ -471,12 +402,12 @@ func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data, result headers["Accept"] = "application/json" headers["Accept-Charset"] = "UTF-8" headers["Content-Type"] = "application/json" - headers["apikey"] = b.APIKey + headers["apikey"] = b.API.Credentials.Key headers["timestamp"] = n - headers["signature"] = common.Base64Encode(hmac) + headers["signature"] = crypto.Base64Encode(hmac) return b.SendPayload(reqType, - b.APIUrl+path, + b.API.Endpoints.URL+path, headers, bytes.NewBuffer(payload), result, diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index 795458fd..e7b90f71 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -30,11 +30,11 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - BTC Markets Setup() init error") } - bConfig.APIKey = apiKey - bConfig.APISecret = apiSecret - bConfig.AuthenticatedAPISupport = true + bConfig.API.Credentials.Key = apiKey + bConfig.API.Credentials.Secret = apiSecret + bConfig.API.AuthenticatedSupport = true - b.Setup(&bConfig) + b.Setup(bConfig) } func TestGetMarkets(t *testing.T) { @@ -78,7 +78,8 @@ func TestGetTrades(t *testing.T) { func TestNewOrder(t *testing.T) { t.Parallel() - _, err := b.NewOrder("AUD", "BTC", 0, 0, "Bid", "limit", "testTest") + _, err := b.NewOrder("AUD", "BTC", 0, 0, "Bid", + exchange.LimitOrderType.ToLower().ToString(), "testTest") if err == nil { t.Error("Test failed - NewOrder() error", err) } @@ -320,11 +321,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if b.APIKey != "" && b.APIKey != "Key" && - b.APISecret != "" && b.APISecret != "Secret" { - return true - } - return false + return b.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -415,11 +412,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { b.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -443,10 +442,12 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.AUD, - Description: "WITHDRAW IT ALL", + var withdrawFiatRequest = exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.USD, + Description: "WITHDRAW IT ALL", + }, BankAccountName: "Satoshi Nakamoto", BankAccountNumber: 12345, BankAddress: "123 Fake St", @@ -476,8 +477,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := b.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/btcmarkets/btcmarkets_types.go b/exchanges/btcmarkets/btcmarkets_types.go index 95f72a76..203663f1 100644 --- a/exchanges/btcmarkets/btcmarkets_types.go +++ b/exchanges/btcmarkets/btcmarkets_types.go @@ -78,7 +78,7 @@ type OrderToGo struct { // Order holds order information type Order struct { - ID string `json:"id"` + ID int64 `json:"id"` Currency string `json:"currency"` Instrument string `json:"instrument"` OrderSide string `json:"orderSide"` diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 338300ce..a79cc1bb 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -9,13 +9,96 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (b *BTCMarkets) GetDefaultConfig() (*config.ExchangeConfig, error) { + b.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = b.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = b.BaseCurrencies + + err := b.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if b.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = b.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets basic defaults +func (b *BTCMarkets) SetDefaults() { + b.Name = "BTC Markets" + b.Enabled = true + b.Verbose = true + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + b.API.CredentialsValidator.RequiresBase64DecodeSecret = true + b.API.Endpoints.URLDefault = btcMarketsAPIURL + b.API.Endpoints.URL = b.API.Endpoints.URLDefault + + b.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + } + + b.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: false, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.AutoWithdrawFiat, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + b.Requester = request.New(b.Name, + request.NewRateLimit(time.Second*10, btcmarketsAuthLimit), + request.NewRateLimit(time.Second*10, btcmarketsUnauthLimit), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) +} + +// Setup takes in an exchange configuration and sets all parameters +func (b *BTCMarkets) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + b.SetEnabled(false) + return nil + } + + return b.SetupDefaults(exch) +} + // Start starts the BTC Markets go routine func (b *BTCMarkets) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -28,47 +111,60 @@ func (b *BTCMarkets) Start(wg *sync.WaitGroup) { // Run implements the BTC Markets wrapper func (b *BTCMarkets) Run() { if b.Verbose { - log.Debugf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + b.PrintEnabledPairs() } - markets, err := b.GetMarkets() - if err != nil { - log.Errorf("%s failed to get active market. Err: %s", b.Name, err) - } else { - forceUpgrade := false - if !common.StringDataContains(b.EnabledPairs.Strings(), "-") || - !common.StringDataContains(b.AvailablePairs.Strings(), "-") { - forceUpgrade = true - } + forceUpdate := false + if !common.StringDataContains(b.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "-") || + !common.StringDataContains(b.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "-") { + enabledPairs := []string{"BTC-AUD"} + log.Println("WARNING: Available pairs for BTC Makrets reset due to config upgrade, please enable the pairs you would like again.") + forceUpdate = true - var currencies currency.Pairs - for x := range markets { - currencies = append(currencies, - currency.NewPairWithDelimiter(markets[x].Instrument, - markets[x].Currency, "-")) - } - - if forceUpgrade { - enabledPairs := currency.Pairs{currency.Pair{Base: currency.BTC, - Quote: currency.AUD, Delimiter: "-"}} - - log.Warn("Available pairs for BTC Makrets reset due to config upgrade, please enable the pairs you would like again.") - - err = b.UpdateCurrencies(enabledPairs, true, true) - if err != nil { - log.Errorf("%s failed to update currencies. Err: %s", b.Name, err) - } - } - err = b.UpdateCurrencies(currencies, false, forceUpgrade) + err := b.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), assets.AssetTypeSpot, true, true) if err != nil { log.Errorf("%s failed to update currencies. Err: %s", b.Name, err) } } + + if !b.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { + return + } + + err := b.UpdateTradablePairs(forceUpdate) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + } +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (b *BTCMarkets) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + markets, err := b.GetMarkets() + if err != nil { + return nil, err + } + + var pairs []string + for x := range markets { + pairs = append(pairs, markets[x].Instrument+"-"+markets[x].Currency) + } + + return pairs, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (b *BTCMarkets) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := b.GetTicker(p.Base.String(), p.Quote.String()) if err != nil { @@ -87,8 +183,8 @@ func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType string) (ticker.Pri return ticker.GetTicker(b.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (b *BTCMarkets) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (b *BTCMarkets) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -96,8 +192,8 @@ func (b *BTCMarkets) GetTickerPrice(p currency.Pair, assetType string) (ticker.P return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (b *BTCMarkets) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (b *BTCMarkets) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -106,7 +202,7 @@ func (b *BTCMarkets) GetOrderbookEx(p currency.Pair, assetType string) (orderboo } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(p.Base.String(), p.Quote.String()) @@ -172,10 +268,8 @@ func (b *BTCMarkets) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *BTCMarkets) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (b *BTCMarkets) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -229,11 +323,7 @@ func (b *BTCMarkets) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Ca var orderList []int64 for i := range openOrders { - orderIDInt, err := strconv.ParseInt(openOrders[i].ID, 10, 64) - if err != nil { - cancelAllOrdersResponse.OrderStatus[openOrders[i].ID] = err.Error() - } - orderList = append(orderList, orderIDInt) + orderList = append(orderList, openOrders[i].ID) } if len(orderList) > 0 { @@ -286,7 +376,7 @@ func (b *BTCMarkets) GetOrderInfo(orderID string) (exchange.OrderDetail, error) OrderDetail.Amount = orders[i].Volume OrderDetail.OrderDate = orderDate OrderDetail.Exchange = b.GetName() - OrderDetail.ID = orders[i].ID + OrderDetail.ID = strconv.FormatInt(orders[i].ID, 10) OrderDetail.RemainingAmount = orders[i].OpenVolume OrderDetail.OrderSide = side OrderDetail.OrderType = orderType @@ -294,7 +384,7 @@ func (b *BTCMarkets) GetOrderInfo(orderID string) (exchange.OrderDetail, error) OrderDetail.Status = orders[i].Status OrderDetail.CurrencyPair = currency.NewPairWithDelimiter(orders[i].Instrument, orders[i].Currency, - b.ConfigCurrencyPairFormat.Delimiter) + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) } return OrderDetail, nil @@ -306,13 +396,13 @@ func (b *BTCMarkets) GetDepositAddress(cryptocurrency currency.Code, accountID s } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is submitted -func (b *BTCMarkets) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *BTCMarkets) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return b.WithdrawCrypto(withdrawRequest.Amount, withdrawRequest.Currency.String(), withdrawRequest.Address) } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (b *BTCMarkets) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *BTCMarkets) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { if withdrawRequest.Currency != currency.AUD { return "", errors.New("only AUD is supported for withdrawals") } @@ -321,7 +411,7 @@ func (b *BTCMarkets) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (b *BTCMarkets) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *BTCMarkets) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -332,7 +422,7 @@ func (b *BTCMarkets) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (b *BTCMarkets) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (b.APIKey == "" || b.APISecret == "") && // Todo check connection status + if !b.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -358,7 +448,7 @@ func (b *BTCMarkets) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest orderType := exchange.OrderType(strings.ToUpper(resp[i].OrderType)) openOrder := exchange.OrderDetail{ - ID: resp[i].ID, + ID: strconv.FormatInt(resp[i].ID, 10), Amount: resp[i].Volume, Exchange: b.Name, RemainingAmount: resp[i].OpenVolume, @@ -369,7 +459,7 @@ func (b *BTCMarkets) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest Status: resp[i].Status, CurrencyPair: currency.NewPairWithDelimiter(resp[i].Instrument, resp[i].Currency, - b.ConfigCurrencyPairFormat.Delimiter), + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), } for j := range resp[i].Trades { @@ -392,7 +482,6 @@ func (b *BTCMarkets) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } @@ -428,7 +517,7 @@ func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest orderType := exchange.OrderType(strings.ToUpper(respOrders[i].OrderType)) openOrder := exchange.OrderDetail{ - ID: respOrders[i].ID, + ID: strconv.FormatInt(respOrders[i].ID, 10), Amount: respOrders[i].Volume, Exchange: b.Name, RemainingAmount: respOrders[i].OpenVolume, @@ -439,7 +528,7 @@ func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest Status: respOrders[i].Status, CurrencyPair: currency.NewPairWithDelimiter(respOrders[i].Instrument, respOrders[i].Currency, - b.ConfigCurrencyPairFormat.Delimiter), + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), } for j := range respOrders[i].Trades { @@ -454,14 +543,12 @@ func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest Description: respOrders[i].Trades[j].Description, }) } - orders = append(orders, openOrder) } exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } diff --git a/exchanges/btse/btse.go b/exchanges/btse/btse.go index b71c7dd8..78f0fc4d 100644 --- a/exchanges/btse/btse.go +++ b/exchanges/btse/btse.go @@ -11,11 +11,8 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -46,83 +43,6 @@ const ( btseFills = "fills" ) -// SetDefaults sets the basic defaults for BTSE -func (b *BTSE) SetDefaults() { - b.Name = "BTSE" - b.Enabled = false - b.Verbose = false - b.RESTPollingDelay = 10 - b.APIWithdrawPermissions = exchange.NoAPIWithdrawalMethods - b.RequestCurrencyPairFormat.Delimiter = "-" - b.RequestCurrencyPairFormat.Uppercase = true - b.ConfigCurrencyPairFormat.Delimiter = "-" - b.ConfigCurrencyPairFormat.Uppercase = true - b.AssetTypes = []string{ticker.Spot} - b.Requester = request.New(b.Name, - request.NewRateLimit(time.Second, 0), - request.NewRateLimit(time.Second, 0), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - b.APIUrlDefault = btseAPIURL - b.APIUrl = b.APIUrlDefault - b.SupportsAutoPairUpdating = true - b.SupportsRESTTickerBatching = false - b.WebsocketInit() - b.Websocket.Functionality = exchange.WebsocketOrderbookSupported | - exchange.WebsocketTickerSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup takes in the supplied exchange configuration details and sets params -func (b *BTSE) 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.SetHTTPClientTimeout(exch.HTTPTimeout) - b.SetHTTPClientUserAgent(exch.HTTPUserAgent) - b.RESTPollingDelay = exch.RESTPollingDelay - b.Verbose = exch.Verbose - b.Websocket.SetWsStatusAndConnection(exch.Websocket) - b.BaseCurrencies = exch.BaseCurrencies - b.AvailablePairs = exch.AvailablePairs - b.EnabledPairs = exch.EnabledPairs - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = b.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = b.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = b.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = b.WebsocketSetup(b.WsConnect, - b.Subscribe, - b.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - btseWebsocket, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetMarkets returns a list of markets available on BTSE func (b *BTSE) GetMarkets() (*Markets, error) { var m Markets @@ -258,14 +178,15 @@ func (b *BTSE) GetFills(orderID, productID, before, after, limit string) (*Fille // SendHTTPRequest sends an HTTP request to the desired endpoint func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}) error { - p := fmt.Sprintf("%s/%s", btseAPIURL, endpoint) + p := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, endpoint) return b.SendPayload(method, p, nil, nil, &result, false, false, b.Verbose, b.HTTPDebugging) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the desired endpoint func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[string]interface{}, result interface{}) error { - if !b.AuthenticatedAPISupport { - return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) + if !b.AllowAuthenticatedRequest() { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, + b.Name) } payload, err := common.JSONEncode(req) @@ -274,13 +195,13 @@ func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[str } headers := make(map[string]string) - headers["API-KEY"] = b.APIKey - headers["API-PASSPHRASE"] = b.APISecret + headers["API-KEY"] = b.API.Credentials.Key + headers["API-PASSPHRASE"] = b.API.Credentials.Secret if len(payload) > 0 { headers["Content-Type"] = "application/json" } - p := fmt.Sprintf("%s/%s", btseAPIURL, endpoint) + p := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, endpoint) if b.Verbose { log.Debugf("Sending %s request to URL %s with params %s\n", method, p, string(payload)) } diff --git a/exchanges/btse/btse_test.go b/exchanges/btse/btse_test.go index f7673d41..d46f9d74 100644 --- a/exchanges/btse/btse_test.go +++ b/exchanges/btse/btse_test.go @@ -30,11 +30,11 @@ func TestSetup(t *testing.T) { t.Error("Test Failed - BTSE Setup() init error") } - btseConfig.AuthenticatedAPISupport = true - btseConfig.APIKey = apiKey - btseConfig.APISecret = apiSecret + btseConfig.API.AuthenticatedSupport = true + btseConfig.API.Credentials.Key = apiKey + btseConfig.API.Credentials.Secret = apiSecret - b.Setup(&btseConfig) + b.Setup(btseConfig) } func TestGetMarkets(t *testing.T) { @@ -229,11 +229,7 @@ func TestParseOrderTime(t *testing.T) { } func areTestAPIKeysSet() bool { - if b.APIKey != "" && b.APIKey != "Key" && - b.APISecret != "" && b.APISecret != "Secret" { - return true - } - return false + return b.ValidateAPICredentials() } // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index df3984c6..28ca7d65 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" @@ -125,7 +127,7 @@ func (b *BTSE) WsHandleData() { b.Websocket.DataHandler <- exchange.TickerData{ Timestamp: time.Now(), Pair: currency.NewPairDelimiter(t.ProductID, "-"), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Exchange: b.GetName(), OpenPrice: price, } @@ -185,7 +187,7 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error { } p := currency.NewPairDelimiter(snapshot.ProductID, "-") - base.AssetType = "SPOT" + base.AssetType = assets.AssetTypeSpot base.Pair = p base.LastUpdated = time.Now() base.ExchangeName = b.Name @@ -197,7 +199,7 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error { b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p, - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Exchange: b.GetName(), } @@ -207,7 +209,7 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (b *BTSE) GenerateDefaultSubscriptions() { var channels = []string{"snapshot", "ticker"} - enabledCurrencies := b.GetEnabledCurrencies() + enabledCurrencies := b.GetEnabledPairs(assets.AssetTypeSpot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { for j := range enabledCurrencies { diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 3cd625e0..bdd1bf57 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -8,13 +8,112 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (b *BTSE) GetDefaultConfig() (*config.ExchangeConfig, error) { + b.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = b.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = b.BaseCurrencies + + err := b.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if b.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = b.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets the basic defaults for BTSE +func (b *BTSE) SetDefaults() { + b.Name = "BTSE" + b.Enabled = true + b.Verbose = true + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + + b.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + } + + b.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + }, + WithdrawPermissions: exchange.NoAPIWithdrawalMethods, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + b.Requester = request.New(b.Name, + request.NewRateLimit(time.Second, 0), + request.NewRateLimit(time.Second, 0), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + b.API.Endpoints.URLDefault = btseAPIURL + b.API.Endpoints.URL = b.API.Endpoints.URLDefault + b.WebsocketInit() + b.Websocket.Functionality = exchange.WebsocketOrderbookSupported | + exchange.WebsocketTickerSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup takes in the supplied exchange configuration details and sets params +func (b *BTSE) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + b.SetEnabled(false) + return nil + } + + err := b.SetupDefaults(exch) + if err != nil { + return err + } + + return b.WebsocketSetup(b.WsConnect, + b.Subscribe, + b.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + btseWebsocket, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the BTSE go routine func (b *BTSE) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -27,39 +126,57 @@ func (b *BTSE) Start(wg *sync.WaitGroup) { // Run implements the BTSE wrapper func (b *BTSE) Run() { if b.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.Websocket.GetWebsocketURL()) - log.Debugf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + b.PrintEnabledPairs() } + if !b.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := b.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + } +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (b *BTSE) FetchTradablePairs(asset assets.AssetType) ([]string, error) { markets, err := b.GetMarkets() if err != nil { - log.Errorf("%s failed to get trading pairs. Err: %s", b.Name, err) - } else { - var currencies []string - for _, m := range *markets { - currencies = append(currencies, m.ID) - } - err = b.UpdateCurrencies(currency.NewPairsFromStrings(currencies), - false, - false) - if err != nil { - log.Errorf("%s Failed to update available currencies.\n", b.Name) - } + return nil, err } + var pairs []string + for _, m := range *markets { + pairs = append(pairs, m.ID) + } + + return pairs, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (b *BTSE) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (b *BTSE) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (b *BTSE) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - t, err := b.GetTicker(exchange.FormatExchangeCurrency(b.Name, p).String()) + t, err := b.GetTicker(b.FormatExchangeCurrency(p, + assetType).String()) if err != nil { return tickerPrice, err } - s, err := b.GetMarketStatistics(exchange.FormatExchangeCurrency(b.Name, p).String()) + s, err := b.GetMarketStatistics(b.FormatExchangeCurrency(p, + assetType).String()) if err != nil { return tickerPrice, err @@ -80,8 +197,8 @@ func (b *BTSE) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, er return ticker.GetTicker(b.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (b *BTSE) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (b *BTSE) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -89,8 +206,8 @@ func (b *BTSE) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (b *BTSE) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (b *BTSE) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -99,7 +216,7 @@ func (b *BTSE) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *BTSE) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (b *BTSE) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { return orderbook.Base{}, common.ErrFunctionNotSupported } @@ -138,7 +255,7 @@ func (b *BTSE) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { +func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -146,7 +263,8 @@ func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange func (b *BTSE) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { var resp exchange.SubmitOrderResponse r, err := b.CreateOrder(amount, price, side.ToString(), - orderType.ToString(), exchange.FormatExchangeCurrency(b.Name, p).String(), "GTC", clientID) + orderType.ToString(), b.FormatExchangeCurrency(p, + assets.AssetTypeSpot).String(), "GTC", clientID) if err != nil { return resp, err } @@ -168,7 +286,8 @@ func (b *BTSE) ModifyOrder(action *exchange.ModifyOrder) (string, error) { // CancelOrder cancels an order by its corresponding ID number func (b *BTSE) CancelOrder(order *exchange.OrderCancellation) error { r, err := b.CancelExistingOrder(order.OrderID, - exchange.FormatExchangeCurrency(b.Name, order.CurrencyPair).String()) + b.FormatExchangeCurrency(order.CurrencyPair, + assets.AssetTypeSpot).String()) if err != nil { return err } @@ -187,8 +306,9 @@ func (b *BTSE) CancelOrder(order *exchange.OrderCancellation) error { // If product ID is sent, all orders of that specified market will be cancelled // If not specified, all orders of all markets will be cancelled func (b *BTSE) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - r, err := b.CancelOrders(exchange.FormatExchangeCurrency(b.Name, - orderCancellation.CurrencyPair).String()) + r, err := b.CancelOrders(b.FormatExchangeCurrency( + orderCancellation.CurrencyPair, assets.AssetTypeSpot).String(), + ) if err != nil { return exchange.CancelAllOrdersResponse{}, err } @@ -228,7 +348,7 @@ func (b *BTSE) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { } od.CurrencyPair = currency.NewPairDelimiter(o.ProductID, - b.ConfigCurrencyPairFormat.Delimiter) + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) od.Exchange = b.Name od.Amount = o.Amount od.ID = o.ID @@ -267,19 +387,19 @@ func (b *BTSE) GetDepositAddress(cryptocurrency currency.Code, accountID string) // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (b *BTSE) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *BTSE) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is // submitted -func (b *BTSE) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *BTSE) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is // submitted -func (b *BTSE) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (b *BTSE) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -305,7 +425,7 @@ func (b *BTSE) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]e openOrder := exchange.OrderDetail{ CurrencyPair: currency.NewPairDelimiter(order.ProductID, - b.ConfigCurrencyPairFormat.Delimiter), + b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), Exchange: b.Name, Amount: order.Amount, ID: order.ID, @@ -352,7 +472,7 @@ func (b *BTSE) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]e // GetFeeByType returns an estimate of fee based on type of transaction func (b *BTSE) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (b.APIKey == "" || b.APISecret == "") && // Todo check connection status + if !b.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } diff --git a/exchanges/coinbasepro/README.md b/exchanges/coinbasepro/README.md index f4ef8083..1ed08021 100644 --- a/exchanges/coinbasepro/README.md +++ b/exchanges/coinbasepro/README.md @@ -48,22 +48,22 @@ main.go ```go var c exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "CoinbasePro" { - c = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "CoinbasePro" { + c = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := c.GetTickerPrice() +tick, err := c.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := c.GetOrderbookEx() +ob, err := c.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index fb557de4..0bc9fdf5 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -9,15 +9,12 @@ import ( "strconv" "strings" "sync" - "time" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -65,106 +62,22 @@ type CoinbasePro struct { wsRequestMtx sync.Mutex } -// SetDefaults sets default values for the exchange -func (c *CoinbasePro) SetDefaults() { - c.Name = "CoinbasePro" - c.Enabled = false - c.Verbose = false - c.TakerFee = 0.25 - c.MakerFee = 0 - c.RESTPollingDelay = 10 - c.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithAPIPermission | - exchange.AutoWithdrawFiatWithAPIPermission - c.RequestCurrencyPairFormat.Delimiter = "-" - c.RequestCurrencyPairFormat.Uppercase = true - c.ConfigCurrencyPairFormat.Delimiter = "" - c.ConfigCurrencyPairFormat.Uppercase = true - c.AssetTypes = []string{ticker.Spot} - c.SupportsAutoPairUpdating = true - c.SupportsRESTTickerBatching = false - c.Requester = request.New(c.Name, - request.NewRateLimit(time.Second, coinbaseproAuthRate), - request.NewRateLimit(time.Second, coinbaseproUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - c.APIUrlDefault = coinbaseproAPIURL - c.APIUrl = c.APIUrlDefault - c.WebsocketInit() - c.Websocket.Functionality = exchange.WebsocketTickerSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup initialises the exchange parameters with the current configuration -func (c *CoinbasePro) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - c.SetEnabled(false) - } else { - c.Enabled = true - c.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - c.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, true) - c.SetHTTPClientTimeout(exch.HTTPTimeout) - c.SetHTTPClientUserAgent(exch.HTTPUserAgent) - c.RESTPollingDelay = exch.RESTPollingDelay - c.Verbose = exch.Verbose - c.HTTPDebugging = exch.HTTPDebugging - c.Websocket.SetWsStatusAndConnection(exch.Websocket) - c.BaseCurrencies = exch.BaseCurrencies - c.AvailablePairs = exch.AvailablePairs - c.EnabledPairs = exch.EnabledPairs - if exch.UseSandbox { - c.APIUrl = coinbaseproSandboxAPIURL - } - err := c.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = c.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = c.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = c.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = c.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = c.WebsocketSetup(c.WsConnect, - c.Subscribe, - c.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - coinbaseproWebsocketURL, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetProducts returns supported currency pairs on the exchange with specific // information about the pair func (c *CoinbasePro) GetProducts() ([]Product, error) { var products []Product - return products, c.SendHTTPRequest(c.APIUrl+coinbaseproProducts, &products) + return products, c.SendHTTPRequest(c.API.Endpoints.URL+coinbaseproProducts, &products) } // GetOrderbook returns orderbook by currency pair and level func (c *CoinbasePro) GetOrderbook(symbol string, level int) (interface{}, error) { orderbook := OrderbookResponse{} - path := fmt.Sprintf("%s/%s/%s", c.APIUrl+coinbaseproProducts, symbol, coinbaseproOrderbook) + path := fmt.Sprintf("%s/%s/%s", c.API.Endpoints.URL+coinbaseproProducts, symbol, coinbaseproOrderbook) if level > 0 { levelStr := strconv.Itoa(level) - path = fmt.Sprintf("%s/%s/%s?level=%s", c.APIUrl+coinbaseproProducts, symbol, coinbaseproOrderbook, levelStr) + path = fmt.Sprintf("%s/%s/%s?level=%s", c.API.Endpoints.URL+coinbaseproProducts, symbol, coinbaseproOrderbook, levelStr) } if err := c.SendHTTPRequest(path, &orderbook); err != nil { @@ -234,7 +147,7 @@ func (c *CoinbasePro) GetOrderbook(symbol string, level int) (interface{}, error func (c *CoinbasePro) GetTicker(currencyPair string) (Ticker, error) { tick := Ticker{} path := fmt.Sprintf( - "%s/%s/%s", c.APIUrl+coinbaseproProducts, currencyPair, coinbaseproTicker) + "%s/%s/%s", c.API.Endpoints.URL+coinbaseproProducts, currencyPair, coinbaseproTicker) return tick, c.SendHTTPRequest(path, &tick) } @@ -243,8 +156,7 @@ func (c *CoinbasePro) GetTicker(currencyPair string) (Ticker, error) { func (c *CoinbasePro) GetTrades(currencyPair string) ([]Trade, error) { var trades []Trade path := fmt.Sprintf( - "%s/%s/%s", c.APIUrl+coinbaseproProducts, currencyPair, coinbaseproTrades) - + "%s/%s/%s", c.API.Endpoints.URL+coinbaseproProducts, currencyPair, coinbaseproTrades) return trades, c.SendHTTPRequest(path, &trades) } @@ -268,7 +180,7 @@ func (c *CoinbasePro) GetHistoricRates(currencyPair string, start, end, granular } path := common.EncodeURLValues( - fmt.Sprintf("%s/%s/%s", c.APIUrl+coinbaseproProducts, currencyPair, coinbaseproHistory), + fmt.Sprintf("%s/%s/%s", c.API.Endpoints.URL+coinbaseproProducts, currencyPair, coinbaseproHistory), values) if err := c.SendHTTPRequest(path, &resp); err != nil { @@ -300,7 +212,7 @@ func (c *CoinbasePro) GetHistoricRates(currencyPair string, start, end, granular func (c *CoinbasePro) GetStats(currencyPair string) (Stats, error) { stats := Stats{} path := fmt.Sprintf( - "%s/%s/%s", c.APIUrl+coinbaseproProducts, currencyPair, coinbaseproStats) + "%s/%s/%s", c.API.Endpoints.URL+coinbaseproProducts, currencyPair, coinbaseproStats) return stats, c.SendHTTPRequest(path, &stats) } @@ -310,14 +222,14 @@ func (c *CoinbasePro) GetStats(currencyPair string) (Stats, error) { func (c *CoinbasePro) GetCurrencies() ([]Currency, error) { var currencies []Currency - return currencies, c.SendHTTPRequest(c.APIUrl+coinbaseproCurrencies, ¤cies) + return currencies, c.SendHTTPRequest(c.API.Endpoints.URL+coinbaseproCurrencies, ¤cies) } // GetServerTime returns the API server time func (c *CoinbasePro) GetServerTime() (ServerTime, error) { serverTime := ServerTime{} - return serverTime, c.SendHTTPRequest(c.APIUrl+coinbaseproTime, &serverTime) + return serverTime, c.SendHTTPRequest(c.API.Endpoints.URL+coinbaseproTime, &serverTime) } // GetAccounts returns a list of trading accounts associated with the APIKEYS @@ -378,7 +290,7 @@ func (c *CoinbasePro) GetHolds(accountID string) ([]AccountHolds, error) { func (c *CoinbasePro) PlaceLimitOrder(clientRef string, price, amount float64, side, timeInforce, cancelAfter, productID, stp string, postOnly bool) (string, error) { resp := GeneralizedOrderResponse{} req := make(map[string]interface{}) - req["type"] = "limit" + req["type"] = exchange.LimitOrderType.ToLower().ToString() req["price"] = strconv.FormatFloat(price, 'f', -1, 64) req["size"] = strconv.FormatFloat(amount, 'f', -1, 64) req["side"] = side @@ -429,7 +341,7 @@ func (c *CoinbasePro) PlaceMarketOrder(clientRef string, size, funds float64, si req := make(map[string]interface{}) req["side"] = side req["product_id"] = productID - req["type"] = "market" + req["type"] = exchange.MarketOrderType.ToLower().ToString() if size != 0 { req["size"] = strconv.FormatFloat(size, 'f', -1, 64) @@ -532,7 +444,7 @@ func (c *CoinbasePro) GetOrders(status []string, currencyPair string) ([]General params.Set("product_id", currencyPair) } - path := common.EncodeURLValues(c.APIUrl+coinbaseproOrders, params) + path := common.EncodeURLValues(c.API.Endpoints.URL+coinbaseproOrders, params) path = common.GetURIPath(path) return resp, @@ -562,7 +474,7 @@ func (c *CoinbasePro) GetFills(orderID, currencyPair string) ([]FillResponse, er return resp, errors.New("no parameters set") } - path := common.EncodeURLValues(c.APIUrl+coinbaseproFills, params) + path := common.EncodeURLValues(c.API.Endpoints.URL+coinbaseproFills, params) uri := common.GetURIPath(path) return resp, @@ -578,7 +490,7 @@ func (c *CoinbasePro) GetFundingRecords(status string) ([]Funding, error) { params := url.Values{} params.Set("status", status) - path := common.EncodeURLValues(c.APIUrl+coinbaseproFunding, params) + path := common.EncodeURLValues(c.API.Endpoints.URL+coinbaseproFunding, params) uri := common.GetURIPath(path) return resp, @@ -805,7 +717,7 @@ func (c *CoinbasePro) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated HTTP reque func (c *CoinbasePro) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) { - if !c.AuthenticatedAPISupport { + if !c.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, c.Name) } @@ -825,16 +737,16 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(method, path string, params m n := c.Requester.GetNonce(true).String() message := n + method + "/" + path + string(payload) - hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(c.APISecret)) + hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(message), []byte(c.API.Credentials.Secret)) headers := make(map[string]string) - headers["CB-ACCESS-SIGN"] = common.Base64Encode(hmac) + headers["CB-ACCESS-SIGN"] = crypto.Base64Encode(hmac) headers["CB-ACCESS-TIMESTAMP"] = n - headers["CB-ACCESS-KEY"] = c.APIKey - headers["CB-ACCESS-PASSPHRASE"] = c.ClientID + headers["CB-ACCESS-KEY"] = c.API.Credentials.Key + headers["CB-ACCESS-PASSPHRASE"] = c.API.Credentials.ClientID headers["Content-Type"] = "application/json" return c.SendPayload(method, - c.APIUrl+path, + c.API.Endpoints.URL+path, headers, bytes.NewBuffer(payload), result, diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 46cb5d4a..af15e153 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -31,10 +31,10 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - coinbasepro Setup() init error") } - gdxConfig.APIKey = apiKey - gdxConfig.APISecret = apiSecret - gdxConfig.AuthenticatedAPISupport = true - c.Setup(&gdxConfig) + gdxConfig.API.Credentials.Key = apiKey + gdxConfig.API.Credentials.Secret = apiSecret + gdxConfig.API.AuthenticatedSupport = true + c.Setup(gdxConfig) } func TestGetProducts(t *testing.T) { @@ -88,7 +88,7 @@ func TestGetServerTime(t *testing.T) { func TestAuthRequests(t *testing.T) { - if c.APIKey != "" && c.APISecret != "" && c.ClientID != "" { + if c.ValidateAPICredentials() { _, err := c.GetAccounts() if err == nil { @@ -110,12 +110,14 @@ func TestAuthRequests(t *testing.T) { t.Error("Test failed - GetHolds() error", err) } - _, err = c.PlaceLimitOrder("", 0, 0, "buy", "", "", "BTC-USD", "", false) + _, err = c.PlaceLimitOrder("", 0, 0, exchange.BuyOrderSide.ToLower().ToString(), + "", "", "BTC-USD", "", false) if err == nil { t.Error("Test failed - PlaceLimitOrder() error", err) } - _, err = c.PlaceMarketOrder("", 1, 0, "buy", "BTC-USD", "") + _, err = c.PlaceMarketOrder("", 1, 0, exchange.BuyOrderSide.ToLower().ToString(), + "BTC-USD", "") if err == nil { t.Error("Test failed - PlaceMarketOrder() error", err) } @@ -457,11 +459,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if c.APIKey != "" && c.APIKey != "Key" && - c.APISecret != "" && c.APISecret != "Secret" { - return true - } - return false + return c.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -477,7 +475,8 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.LTC, } - response, err := c.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") + response, err := c.SubmitOrder(p, exchange.BuyOrderSide, + exchange.LimitOrderType, 1, 1, "clientId") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -554,11 +553,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { c.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -582,9 +583,11 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.USD, + var withdrawFiatRequest = exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: 100, + Currency: currency.USD, + }, BankName: "Federal Reserve Bank", } @@ -605,9 +608,11 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.USD, + var withdrawFiatRequest = exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: 100, + Currency: currency.USD, + }, BankName: "Federal Reserve Bank", } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index a773d928..6cec637a 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -112,7 +113,7 @@ func (c *CoinbasePro) WsHandleData() { c.Websocket.DataHandler <- exchange.TickerData{ Timestamp: ticker.Time, Pair: currency.NewPairFromString(ticker.ProductID), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Exchange: c.GetName(), OpenPrice: ticker.Open24H, HighPrice: ticker.High24H, @@ -186,9 +187,10 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro orderbook.Item{Price: price, Amount: amount}) } - pair := currency.NewPairFromString(snapshot.ProductID) - base.AssetType = "SPOT" - base.Pair = pair + p := currency.NewPairFromString(snapshot.ProductID) + base.AssetType = assets.AssetTypeSpot + base.Pair = p + base.LastUpdated = time.Now() err := c.Websocket.Orderbook.LoadSnapshot(&base, c.GetName(), false) if err != nil { @@ -196,8 +198,8 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro } c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ - Pair: pair, - Asset: "SPOT", + Pair: p, + Asset: assets.AssetTypeSpot, Exchange: c.GetName(), } @@ -212,7 +214,7 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { price, _ := strconv.ParseFloat(data[1].(string), 64) volume, _ := strconv.ParseFloat(data[2].(string), 64) - if data[0].(string) == "buy" { + if data[0].(string) == exchange.BuyOrderSide.ToLower().ToString() { Bids = append(Bids, orderbook.Item{Price: price, Amount: volume}) } else { Asks = append(Asks, orderbook.Item{Price: price, Amount: volume}) @@ -225,14 +227,14 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { p := currency.NewPairFromString(update.ProductID) - err := c.Websocket.Orderbook.Update(Bids, Asks, p, time.Now(), c.GetName(), "SPOT") + err := c.Websocket.Orderbook.Update(Bids, Asks, p, time.Now(), c.GetName(), assets.AssetTypeSpot) if err != nil { return err } c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p, - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Exchange: c.GetName(), } @@ -242,7 +244,7 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (c *CoinbasePro) GenerateDefaultSubscriptions() { var channels = []string{"heartbeat", "level2", "ticker"} - enabledCurrencies := c.GetEnabledCurrencies() + enabledCurrencies := c.GetEnabledPairs(assets.AssetTypeSpot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { for j := range enabledCurrencies { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 62e1883d..ddcee869 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -8,13 +8,115 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (c *CoinbasePro) GetDefaultConfig() (*config.ExchangeConfig, error) { + c.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = c.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = c.BaseCurrencies + + err := c.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if c.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = c.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets default values for the exchange +func (c *CoinbasePro) SetDefaults() { + c.Name = "CoinbasePro" + c.Enabled = true + c.Verbose = true + c.API.CredentialsValidator.RequiresKey = true + c.API.CredentialsValidator.RequiresSecret = true + c.API.CredentialsValidator.RequiresClientID = true + c.API.CredentialsValidator.RequiresBase64DecodeSecret = true + + c.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + + c.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | + exchange.AutoWithdrawFiatWithAPIPermission, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + c.Requester = request.New(c.Name, + request.NewRateLimit(time.Second, coinbaseproAuthRate), + request.NewRateLimit(time.Second, coinbaseproUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + c.API.Endpoints.URLDefault = coinbaseproAPIURL + c.API.Endpoints.URL = c.API.Endpoints.URLDefault + c.WebsocketInit() + c.Websocket.Functionality = exchange.WebsocketTickerSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup initialises the exchange parameters with the current configuration +func (c *CoinbasePro) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + c.SetEnabled(false) + return nil + } + + err := c.SetupDefaults(exch) + if err != nil { + return err + } + + return c.WebsocketSetup(c.WsConnect, + c.Subscribe, + c.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + coinbaseproWebsocketURL, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the coinbasepro go routine func (c *CoinbasePro) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -28,34 +130,45 @@ func (c *CoinbasePro) Start(wg *sync.WaitGroup) { func (c *CoinbasePro) Run() { if c.Verbose { log.Debugf("%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket.IsEnabled()), coinbaseproWebsocketURL) - log.Debugf("%s polling delay: %ds.\n", c.GetName(), c.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", c.GetName(), len(c.EnabledPairs), c.EnabledPairs) + c.PrintEnabledPairs() } - exchangeProducts, err := c.GetProducts() + if !c.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := c.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s Failed to get available products.\n", c.GetName()) - } else { - var currencies []string - for _, x := range exchangeProducts { - if x.ID != "BTC" && x.ID != "USD" && x.ID != "GBP" { - currencies = append(currencies, x.ID[0:3]+x.ID[4:]) - } - } - - var newCurrencies currency.Pairs - for _, p := range currencies { - newCurrencies = append(newCurrencies, - currency.NewPairFromString(p)) - } - - err = c.UpdateCurrencies(newCurrencies, false, false) - if err != nil { - log.Errorf("%s Failed to update available currencies.\n", c.GetName()) - } + log.Errorf("%s failed to update tradable pairs. Err: %s", c.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (c *CoinbasePro) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + pairs, err := c.GetProducts() + if err != nil { + return nil, err + } + + var products []string + for x := range pairs { + products = append(products, pairs[x].BaseCurrency+pairs[x].QuoteCurrency) + } + + return products, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (c *CoinbasePro) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := c.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return c.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // GetAccountInfo retrieves balances for all enabled currencies for the // coinbasepro exchange func (c *CoinbasePro) GetAccountInfo() (exchange.AccountInfo, error) { @@ -84,14 +197,14 @@ func (c *CoinbasePro) GetAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - tick, err := c.GetTicker(exchange.FormatExchangeCurrency(c.Name, p).String()) + tick, err := c.GetTicker(c.FormatExchangeCurrency(p, assetType).String()) if err != nil { return ticker.Price{}, err } - stats, err := c.GetStats(exchange.FormatExchangeCurrency(c.Name, p).String()) + stats, err := c.GetStats(c.FormatExchangeCurrency(p, assetType).String()) if err != nil { return ticker.Price{}, err @@ -111,8 +224,8 @@ func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType string) (ticker.Pr return ticker.GetTicker(c.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (c *CoinbasePro) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (c *CoinbasePro) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(c.GetName(), p, assetType) if err != nil { return c.UpdateTicker(p, assetType) @@ -120,8 +233,8 @@ func (c *CoinbasePro) GetTickerPrice(p currency.Pair, assetType string) (ticker. return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (c *CoinbasePro) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (c *CoinbasePro) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(c.GetName(), p, assetType) if err != nil { return c.UpdateOrderbook(p, assetType) @@ -130,9 +243,9 @@ func (c *CoinbasePro) GetOrderbookEx(p currency.Pair, assetType string) (orderbo } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (c *CoinbasePro) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (c *CoinbasePro) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := c.GetOrderbook(exchange.FormatExchangeCurrency(c.Name, p).String(), 2) + orderbookNew, err := c.GetOrderbook(c.FormatExchangeCurrency(p, assetType).String(), 2) if err != nil { return orderBook, err } @@ -167,10 +280,8 @@ func (c *CoinbasePro) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (c *CoinbasePro) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (c *CoinbasePro) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -243,14 +354,14 @@ func (c *CoinbasePro) GetDepositAddress(cryptocurrency currency.Code, accountID // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (c *CoinbasePro) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (c *CoinbasePro) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { resp, err := c.WithdrawCrypto(withdrawRequest.Amount, withdrawRequest.Currency.String(), withdrawRequest.Address) return resp.ID, err } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is // submitted -func (c *CoinbasePro) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (c *CoinbasePro) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { paymentMethods, err := c.GetPayMethods() if err != nil { return "", err @@ -277,7 +388,7 @@ func (c *CoinbasePro) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawReques // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (c *CoinbasePro) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (c *CoinbasePro) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return c.WithdrawFiatFunds(withdrawRequest) } @@ -288,7 +399,7 @@ func (c *CoinbasePro) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (c *CoinbasePro) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (c.APIKey == "" || c.APISecret == "") && // Todo check connection status + if !c.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -300,7 +411,7 @@ func (c *CoinbasePro) GetActiveOrders(getOrdersRequest *exchange.GetOrdersReques var respOrders []GeneralizedOrderResponse for i := range getOrdersRequest.Currencies { resp, err := c.GetOrders([]string{"open", "pending", "active"}, - exchange.FormatExchangeCurrency(c.Name, getOrdersRequest.Currencies[i]).String()) + c.FormatExchangeCurrency(getOrdersRequest.Currencies[i], assets.AssetTypeSpot).String()) if err != nil { return nil, err } @@ -310,7 +421,7 @@ func (c *CoinbasePro) GetActiveOrders(getOrdersRequest *exchange.GetOrdersReques var orders []exchange.OrderDetail for i := range respOrders { currency := currency.NewPairDelimiter(respOrders[i].ProductID, - c.ConfigCurrencyPairFormat.Delimiter) + c.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderSide := exchange.OrderSide(strings.ToUpper(respOrders[i].Side)) orderType := exchange.OrderType(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) @@ -334,7 +445,6 @@ func (c *CoinbasePro) GetActiveOrders(getOrdersRequest *exchange.GetOrdersReques exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } @@ -344,7 +454,7 @@ func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersReques var respOrders []GeneralizedOrderResponse for _, currency := range getOrdersRequest.Currencies { resp, err := c.GetOrders([]string{"done", "settled"}, - exchange.FormatExchangeCurrency(c.Name, currency).String()) + c.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String()) if err != nil { return nil, err } @@ -354,7 +464,7 @@ func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersReques var orders []exchange.OrderDetail for i := range respOrders { currency := currency.NewPairDelimiter(respOrders[i].ProductID, - c.ConfigCurrencyPairFormat.Delimiter) + c.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderSide := exchange.OrderSide(strings.ToUpper(respOrders[i].Side)) orderType := exchange.OrderType(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) @@ -379,7 +489,6 @@ func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersReques exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } diff --git a/exchanges/coinut/README.md b/exchanges/coinut/README.md index d212c721..b6d18a1b 100644 --- a/exchanges/coinut/README.md +++ b/exchanges/coinut/README.md @@ -48,22 +48,22 @@ main.go ```go var c exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Coinut" { - c = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Coinut" { + c = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := c.GetTickerPrice() +tick, err := c.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := c.GetOrderbookEx() +ob, err := c.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 3990cecb..c51c99bb 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -7,15 +7,12 @@ import ( "fmt" "net/http" "sync" - "time" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -52,89 +49,6 @@ type COINUT struct { wsRequestMtx sync.Mutex } -// SetDefaults sets current default values -func (c *COINUT) SetDefaults() { - c.Name = "COINUT" - c.Enabled = false - c.Verbose = false - c.TakerFee = 0.1 // spot - c.MakerFee = 0 - c.Verbose = false - c.RESTPollingDelay = 10 - c.APIWithdrawPermissions = exchange.WithdrawCryptoViaWebsiteOnly | - exchange.WithdrawFiatViaWebsiteOnly - c.RequestCurrencyPairFormat.Delimiter = "" - c.RequestCurrencyPairFormat.Uppercase = true - c.ConfigCurrencyPairFormat.Delimiter = "" - c.ConfigCurrencyPairFormat.Uppercase = true - c.AssetTypes = []string{ticker.Spot} - c.SupportsAutoPairUpdating = true - c.SupportsRESTTickerBatching = false - c.Requester = request.New(c.Name, - request.NewRateLimit(time.Second, coinutAuthRate), - request.NewRateLimit(time.Second, coinutUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - c.APIUrlDefault = coinutAPIURL - c.APIUrl = c.APIUrlDefault - c.WebsocketInit() - c.Websocket.Functionality = exchange.WebsocketTickerSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketTradeDataSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup sets the current exchange configuration -func (c *COINUT) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - c.SetEnabled(false) - } else { - c.Enabled = true - c.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - c.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, false) - c.SetHTTPClientTimeout(exch.HTTPTimeout) - c.SetHTTPClientUserAgent(exch.HTTPUserAgent) - c.RESTPollingDelay = exch.RESTPollingDelay - c.Verbose = exch.Verbose - c.HTTPDebugging = exch.HTTPDebugging - c.Websocket.SetWsStatusAndConnection(exch.Websocket) - c.BaseCurrencies = exch.BaseCurrencies - c.AvailablePairs = exch.AvailablePairs - c.EnabledPairs = exch.EnabledPairs - err := c.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = c.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = c.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = c.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = c.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = c.WebsocketSetup(c.WsConnect, - c.Subscribe, - c.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - coinutWebsocketURL, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetInstruments returns instruments func (c *COINUT) GetInstruments() (Instruments, error) { var result Instruments @@ -190,9 +104,9 @@ func (c *COINUT) NewOrder(instrumentID int, quantity, price float64, buy bool, o params["price"] = fmt.Sprintf("%v", price) } params["qty"] = fmt.Sprintf("%v", quantity) - params["side"] = "BUY" + params["side"] = exchange.BuyOrderSide.ToString() if !buy { - params["side"] = "SELL" + params["side"] = exchange.SellOrderSide.ToString() } params["client_ord_id"] = orderID @@ -342,7 +256,7 @@ func (c *COINUT) GetOpenPositions(instrumentID int) ([]OpenPosition, error) { // SendHTTPRequest sends either an authenticated or unauthenticated HTTP request func (c *COINUT) SendHTTPRequest(apiRequest string, params map[string]interface{}, authenticated bool, result interface{}) (err error) { - if !c.AuthenticatedAPISupport && authenticated { + if !c.API.AuthenticatedSupport && authenticated { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, c.Name) } @@ -365,15 +279,15 @@ func (c *COINUT) SendHTTPRequest(apiRequest string, params map[string]interface{ headers := make(map[string]string) if authenticated { - headers["X-USER"] = c.ClientID - hmac := common.GetHMAC(common.HashSHA256, payload, []byte(c.APIKey)) - headers["X-SIGNATURE"] = common.HexEncodeToString(hmac) + headers["X-USER"] = c.API.Credentials.ClientID + hmac := crypto.GetHMAC(crypto.HashSHA256, payload, []byte(c.API.Credentials.Key)) + headers["X-SIGNATURE"] = crypto.HexEncodeToString(hmac) } headers["Content-Type"] = "application/json" var rawMsg json.RawMessage err = c.SendPayload(http.MethodPost, - c.APIUrl, + c.API.Endpoints.URL, headers, bytes.NewBuffer(payload), &rawMsg, diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 755748d2..484646b8 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -2,7 +2,6 @@ package coinut import ( "testing" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" @@ -30,15 +29,14 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Coinut Setup() init error") } - bConfig.AuthenticatedAPISupport = true - bConfig.APIKey = apiKey - c.Setup(&bConfig) - c.ClientID = clientID + bConfig.API.AuthenticatedSupport = true + bConfig.API.Credentials.Key = apiKey + bConfig.API.Credentials.ClientID = clientID + bConfig.Verbose = true + c.Setup(bConfig) - if !c.IsEnabled() || - c.RESTPollingDelay != time.Duration(10) || - c.Websocket.IsEnabled() || len(c.BaseCurrencies) < 1 || - len(c.AvailablePairs) < 1 || len(c.EnabledPairs) < 1 { + if !c.IsEnabled() || !c.Verbose || + c.Websocket.IsEnabled() || len(c.BaseCurrencies) < 1 { t.Error("Test Failed - Coinut Setup values not set correctly") } } @@ -237,10 +235,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if c.APIKey != "" && c.APIKey != "Key" { - return true - } - return false + return c.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -347,11 +342,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { c.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -372,8 +369,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := c.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -388,8 +384,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := c.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index 22218d70..42079b88 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -80,7 +81,7 @@ func (c *COINUT) WsHandleData() { c.Websocket.DataHandler <- exchange.TickerData{ Timestamp: time.Unix(0, ticker.Timestamp), Exchange: c.GetName(), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, HighPrice: ticker.HighestBuy, LowPrice: ticker.LowestSell, ClosePrice: ticker.Last, @@ -105,7 +106,7 @@ func (c *COINUT) WsHandleData() { c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: c.GetName(), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Pair: currency.NewPairFromString(currencyPair), } @@ -127,7 +128,7 @@ func (c *COINUT) WsHandleData() { c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: c.GetName(), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Pair: currency.NewPairFromString(currencyPair), } @@ -152,7 +153,7 @@ func (c *COINUT) WsHandleData() { c.Websocket.DataHandler <- exchange.TradeData{ Timestamp: time.Unix(tradeUpdate.Timestamp, 0), CurrencyPair: currency.NewPairFromString(currencyPair), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Exchange: c.GetName(), Price: tradeUpdate.Price, Side: tradeUpdate.Side, @@ -224,7 +225,7 @@ func (c *COINUT) GetNonce() int64 { func (c *COINUT) WsSetInstrumentList() error { err := c.wsSend(wsRequest{ Request: "inst_list", - SecType: "SPOT", + SecType: "spot", Nonce: c.GetNonce(), }) if err != nil { @@ -278,7 +279,8 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { newOrderBook.Asks = asks newOrderBook.Bids = bids newOrderBook.Pair = currency.NewPairFromString(instrumentListByCode[ob.InstID]) - newOrderBook.AssetType = "SPOT" + newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.LastUpdated = time.Now() return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook, c.GetName(), false) } @@ -287,14 +289,14 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { func (c *COINUT) WsProcessOrderbookUpdate(ob *WsOrderbookUpdate) error { p := currency.NewPairFromString(instrumentListByCode[ob.InstID]) - if ob.Side == "buy" { + if ob.Side == exchange.BuyOrderSide.ToLower().ToString() { return c.Websocket.Orderbook.Update([]orderbook.Item{ {Price: ob.Price, Amount: ob.Volume}}, nil, p, time.Now(), c.GetName(), - "SPOT") + assets.AssetTypeSpot) } return c.Websocket.Orderbook.Update([]orderbook.Item{ @@ -303,14 +305,14 @@ func (c *COINUT) WsProcessOrderbookUpdate(ob *WsOrderbookUpdate) error { p, time.Now(), c.GetName(), - "SPOT") + assets.AssetTypeSpot) } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (c *COINUT) GenerateDefaultSubscriptions() { var channels = []string{"inst_tick", "inst_order_book"} subscriptions := []exchange.WebsocketChannelSubscription{} - enabledCurrencies := c.GetEnabledCurrencies() + enabledCurrencies := c.GetEnabledPairs(assets.AssetTypeSpot) for i := range channels { for j := range enabledCurrencies { subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{ diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 5807adbe..33c7a6aa 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -9,13 +9,113 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (c *COINUT) GetDefaultConfig() (*config.ExchangeConfig, error) { + c.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = c.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = c.BaseCurrencies + + err := c.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if c.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = c.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets current default values +func (c *COINUT) SetDefaults() { + c.Name = "COINUT" + c.Enabled = true + c.Verbose = true + c.API.CredentialsValidator.RequiresKey = true + c.API.CredentialsValidator.RequiresClientID = true + + c.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + + c.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.WithdrawCryptoViaWebsiteOnly | + exchange.WithdrawFiatViaWebsiteOnly, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + c.Requester = request.New(c.Name, + request.NewRateLimit(time.Second, coinutAuthRate), + request.NewRateLimit(time.Second, coinutUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + c.API.Endpoints.URLDefault = coinutAPIURL + c.API.Endpoints.URL = c.API.Endpoints.URLDefault + c.WebsocketInit() + c.Websocket.Functionality = exchange.WebsocketTickerSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketTradeDataSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup sets the current exchange configuration +func (c *COINUT) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + c.SetEnabled(false) + return nil + } + + err := c.SetupDefaults(exch) + if err != nil { + return err + } + + return c.WebsocketSetup(c.WsConnect, + c.Subscribe, + c.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + coinutWebsocketURL, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the COINUT go routine func (c *COINUT) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -29,35 +129,47 @@ func (c *COINUT) Start(wg *sync.WaitGroup) { func (c *COINUT) Run() { if c.Verbose { log.Debugf("%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket.IsEnabled()), coinutWebsocketURL) - log.Debugf("%s polling delay: %ds.\n", c.GetName(), c.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", c.GetName(), len(c.EnabledPairs), c.EnabledPairs) + c.PrintEnabledPairs() } - exchangeProducts, err := c.GetInstruments() - if err != nil { - log.Debugf("%s Failed to get available products.\n", c.GetName()) + if !c.GetEnabledFeatures().AutoPairUpdates { return } - var currencies []string - c.InstrumentMap = make(map[string]int) - for x, y := range exchangeProducts.Instruments { - c.InstrumentMap[x] = y[0].InstID - currencies = append(currencies, x) - } - - var newCurrencies currency.Pairs - for _, p := range currencies { - newCurrencies = append(newCurrencies, - currency.NewPairFromString(p)) - } - - err = c.UpdateCurrencies(newCurrencies, false, false) + err := c.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s Failed to update available currencies.\n", c.GetName()) + log.Errorf("%s failed to update tradable pairs. Err: %s", c.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (c *COINUT) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + i, err := c.GetInstruments() + if err != nil { + return nil, err + } + + var pairs []string + c.InstrumentMap = make(map[string]int) + for x, y := range i.Instruments { + c.InstrumentMap[x] = y[0].InstID + pairs = append(pairs, x) + } + + return pairs, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (c *COINUT) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := c.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return c.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // GetAccountInfo retrieves balances for all enabled currencies for the // COINUT exchange func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { @@ -134,7 +246,7 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (c *COINUT) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (c *COINUT) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := c.GetInstrumentTicker(c.InstrumentMap[p.String()]) if err != nil { @@ -156,8 +268,8 @@ func (c *COINUT) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, } -// GetTickerPrice returns the ticker for a currency pair -func (c *COINUT) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (c *COINUT) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(c.GetName(), p, assetType) if err != nil { return c.UpdateTicker(p, assetType) @@ -165,8 +277,8 @@ func (c *COINUT) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (c *COINUT) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (c *COINUT) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(c.GetName(), p, assetType) if err != nil { return c.UpdateOrderbook(p, assetType) @@ -175,7 +287,7 @@ func (c *COINUT) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Ba } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := c.GetInstrumentOrderbook(c.InstrumentMap[p.String()], 200) if err != nil { @@ -211,10 +323,8 @@ func (c *COINUT) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (c *COINUT) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (c *COINUT) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -288,7 +398,8 @@ func (c *COINUT) CancelOrder(order *exchange.OrderCancellation) error { return err } - currencyArray := instruments.Instruments[exchange.FormatExchangeCurrency(c.Name, order.CurrencyPair).String()] + currencyArray := instruments.Instruments[c.FormatExchangeCurrency(order.CurrencyPair, + order.AssetType).String()] currencyID := currencyArray[0].InstID _, err = c.CancelExistingOrder(currencyID, int(orderIDInt)) @@ -360,19 +471,19 @@ func (c *COINUT) GetDepositAddress(cryptocurrency currency.Code, accountID strin // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (c *COINUT) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (c *COINUT) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (c *COINUT) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (c *COINUT) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (c *COINUT) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (c *COINUT) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -383,7 +494,7 @@ func (c *COINUT) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (c *COINUT) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if c.APIKey == "" && // Todo check connection status + if !c.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -403,7 +514,7 @@ func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ for _, currency := range getOrdersRequest.Currencies { currStr := fmt.Sprintf("%v%v%v", currency.Base.String(), - c.ConfigCurrencyPairFormat.Delimiter, + c.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter, currency.Quote.String()) if strings.EqualFold(currStr, instrument) { openOrders, err := c.GetOpenOrders(instrumentData.InstID) @@ -442,7 +553,6 @@ func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } @@ -458,10 +568,8 @@ func (c *COINUT) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ for instrument, allInstrumentData := range instruments.Instruments { for _, instrumentData := range allInstrumentData { for _, currency := range getOrdersRequest.Currencies { - currStr := fmt.Sprintf("%v%v%v", - currency.Base.String(), - c.ConfigCurrencyPairFormat.Delimiter, - currency.Quote.String()) + currStr := fmt.Sprintf("%v%v", + currency.Base.String(), currency.Quote.String()) if strings.EqualFold(currStr, instrument) { orders, err := c.GetTradeHistory(instrumentData.InstID, -1, -1) if err != nil { @@ -500,7 +608,6 @@ func (c *COINUT) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index a163c8dd..75869c9f 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -5,17 +5,15 @@ import ( "fmt" "net/http" "net/url" - "sort" "strings" - "sync" "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -23,358 +21,41 @@ const ( warningBase64DecryptSecretKeyFailed = "exchange %s unable to base64 decode secret key.. Disabling Authenticated API support" // nolint:gosec // WarningAuthenticatedRequestWithoutCredentialsSet error message for authenticated request without credentials set WarningAuthenticatedRequestWithoutCredentialsSet = "exchange %s authenticated HTTP request called but not supported due to unset/default API keys" - // ErrExchangeNotFound is a stand for an error message - ErrExchangeNotFound = "exchange not found in dataset" // DefaultHTTPTimeout is the default HTTP/HTTPS Timeout for exchange requests DefaultHTTPTimeout = time.Second * 15 ) -// FeeType custom type for calculating fees based on method -type FeeType uint8 - -// Const declarations for fee types -const ( - BankFee FeeType = iota - InternationalBankDepositFee - InternationalBankWithdrawalFee - CryptocurrencyTradeFee - CyptocurrencyDepositFee - CryptocurrencyWithdrawalFee - OfflineTradeFee -) - -// InternationalBankTransactionType custom type for calculating fees based on fiat transaction types -type InternationalBankTransactionType uint8 - -// Const declarations for international transaction types -const ( - WireTransfer InternationalBankTransactionType = iota - PerfectMoney - Neteller - AdvCash - Payeer - Skrill - Simplex - SEPA - Swift - RapidTransfer - MisterTangoSEPA - Qiwi - VisaMastercard - WebMoney - Capitalist - WesternUnion - MoneyGram - Contact -) - -// SubmitOrderResponse is what is returned after submitting an order to an -// exchange -type SubmitOrderResponse struct { - IsOrderPlaced bool - OrderID string -} - -// FeeBuilder is the type which holds all parameters required to calculate a fee -// for an exchange -type FeeBuilder struct { - IsMaker bool - PurchasePrice float64 - Amount float64 - FeeType FeeType - FiatCurrency currency.Code - BankTransactionType InternationalBankTransactionType - Pair currency.Pair -} - -// OrderCancellation type required when requesting to cancel an order -type OrderCancellation struct { - AccountID string - OrderID string - WalletAddress string - Side OrderSide - CurrencyPair currency.Pair -} - -// WithdrawRequest used for wrapper crypto and FIAT withdraw methods -type WithdrawRequest struct { - // General withdraw information - Description string - OneTimePassword int64 - AccountID string - PIN int64 - TradePassword string - Amount float64 - Currency currency.Code - // Crypto related information - Address string - AddressTag string - FeeAmount float64 - // FIAT related information - BankAccountName string - BankAccountNumber float64 - BankName string - BankAddress string - BankCity string - BankCountry string - BankPostalCode string - SwiftCode string - IBAN string - BankCode float64 - IsExpressWire bool - // Intermediary bank information - RequiresIntermediaryBank bool - IntermediaryBankAccountNumber float64 - IntermediaryBankName string - IntermediaryBankAddress string - IntermediaryBankCity string - IntermediaryBankCountry string - IntermediaryBankPostalCode string - IntermediarySwiftCode string - IntermediaryBankCode float64 - IntermediaryIBAN string - WireCurrency string -} - -// Definitions for each type of withdrawal method for a given exchange -const ( - // No withdraw - NoAPIWithdrawalMethods uint32 = 0 - NoAPIWithdrawalMethodsText string = "NONE, WEBSITE ONLY" - AutoWithdrawCrypto uint32 = (1 << 0) - AutoWithdrawCryptoWithAPIPermission uint32 = (1 << 1) - AutoWithdrawCryptoWithSetup uint32 = (1 << 2) - AutoWithdrawCryptoText string = "AUTO WITHDRAW CRYPTO" - AutoWithdrawCryptoWithAPIPermissionText string = "AUTO WITHDRAW CRYPTO WITH API PERMISSION" - AutoWithdrawCryptoWithSetupText string = "AUTO WITHDRAW CRYPTO WITH SETUP" - WithdrawCryptoWith2FA uint32 = (1 << 3) - WithdrawCryptoWithSMS uint32 = (1 << 4) - WithdrawCryptoWithEmail uint32 = (1 << 5) - WithdrawCryptoWithWebsiteApproval uint32 = (1 << 6) - WithdrawCryptoWithAPIPermission uint32 = (1 << 7) - WithdrawCryptoWith2FAText string = "WITHDRAW CRYPTO WITH 2FA" - WithdrawCryptoWithSMSText string = "WITHDRAW CRYPTO WITH SMS" - WithdrawCryptoWithEmailText string = "WITHDRAW CRYPTO WITH EMAIL" - WithdrawCryptoWithWebsiteApprovalText string = "WITHDRAW CRYPTO WITH WEBSITE APPROVAL" - WithdrawCryptoWithAPIPermissionText string = "WITHDRAW CRYPTO WITH API PERMISSION" - AutoWithdrawFiat uint32 = (1 << 8) - AutoWithdrawFiatWithAPIPermission uint32 = (1 << 9) - AutoWithdrawFiatWithSetup uint32 = (1 << 10) - AutoWithdrawFiatText string = "AUTO WITHDRAW FIAT" - AutoWithdrawFiatWithAPIPermissionText string = "AUTO WITHDRAW FIAT WITH API PERMISSION" - AutoWithdrawFiatWithSetupText string = "AUTO WITHDRAW FIAT WITH SETUP" - WithdrawFiatWith2FA uint32 = (1 << 11) - WithdrawFiatWithSMS uint32 = (1 << 12) - WithdrawFiatWithEmail uint32 = (1 << 13) - WithdrawFiatWithWebsiteApproval uint32 = (1 << 14) - WithdrawFiatWithAPIPermission uint32 = (1 << 15) - WithdrawFiatWith2FAText string = "WITHDRAW FIAT WITH 2FA" - WithdrawFiatWithSMSText string = "WITHDRAW FIAT WITH SMS" - WithdrawFiatWithEmailText string = "WITHDRAW FIAT WITH EMAIL" - WithdrawFiatWithWebsiteApprovalText string = "WITHDRAW FIAT WITH WEBSITE APPROVAL" - WithdrawFiatWithAPIPermissionText string = "WITHDRAW FIAT WITH API PERMISSION" - WithdrawCryptoViaWebsiteOnly uint32 = (1 << 16) - WithdrawFiatViaWebsiteOnly uint32 = (1 << 17) - WithdrawCryptoViaWebsiteOnlyText string = "WITHDRAW CRYPTO VIA WEBSITE ONLY" - WithdrawFiatViaWebsiteOnlyText string = "WITHDRAW FIAT VIA WEBSITE ONLY" - NoFiatWithdrawals uint32 = (1 << 18) - NoFiatWithdrawalsText string = "NO FIAT WITHDRAWAL" - - UnknownWithdrawalTypeText string = "UNKNOWN" -) - -// AccountInfo is a Generic type to hold each exchange's holdings in -// all enabled currencies -type AccountInfo struct { - Exchange string - Accounts []Account -} - -// Account defines a singular account type with asocciated currencies -type Account struct { - ID string - Currencies []AccountCurrencyInfo -} - -// AccountCurrencyInfo is a sub type to store currency name and value -type AccountCurrencyInfo struct { - CurrencyName currency.Code - TotalValue float64 - Hold float64 -} - -// TradeHistory holds exchange history data -type TradeHistory struct { - Timestamp time.Time - TID int64 - Price float64 - Amount float64 - Exchange string - Type string - Fee float64 - Description string -} - -// OrderDetail holds order detail data -type OrderDetail struct { - Exchange string - AccountID string - ID string - CurrencyPair currency.Pair - OrderSide OrderSide - OrderType OrderType - OrderDate time.Time - Status string - Price float64 - Amount float64 - ExecutedAmount float64 - RemainingAmount float64 - Fee float64 - Trades []TradeHistory -} - -// FundHistory holds exchange funding history data -type FundHistory struct { - ExchangeName string - Status string - TransferID string - Description string - Timestamp time.Time - Currency string - Amount float64 - Fee float64 - TransferType string - CryptoToAddress string - CryptoFromAddress string - CryptoTxID string - BankTo string - BankFrom string -} - -// Base stores the individual exchange information -type Base struct { - Name string - Enabled bool - Verbose bool - RESTPollingDelay time.Duration - AuthenticatedAPISupport bool - APIWithdrawPermissions uint32 - APIAuthPEMKeySupport bool - APISecret, APIKey, APIAuthPEMKey, ClientID string - TakerFee, MakerFee, Fee float64 - BaseCurrencies currency.Currencies - AvailablePairs currency.Pairs - EnabledPairs currency.Pairs - AssetTypes []string - PairsLastUpdated int64 - SupportsAutoPairUpdating bool - SupportsRESTTickerBatching bool - HTTPTimeout time.Duration - HTTPUserAgent string - HTTPDebugging bool - WebsocketURL string - APIUrl string - APIUrlDefault string - APIUrlSecondary string - APIUrlSecondaryDefault string - RequestCurrencyPairFormat config.CurrencyPairFormatConfig - ConfigCurrencyPairFormat config.CurrencyPairFormatConfig - Websocket *Websocket - *request.Requester -} - -// IBotExchange enforces standard functions for all exchanges supported in -// GoCryptoTrader -type IBotExchange interface { - Setup(exch *config.ExchangeConfig) - Start(wg *sync.WaitGroup) - SetDefaults() - GetName() string - IsEnabled() bool - SetEnabled(bool) - GetTickerPrice(currency currency.Pair, assetType string) (ticker.Price, error) - UpdateTicker(currency currency.Pair, assetType string) (ticker.Price, error) - GetOrderbookEx(currency currency.Pair, assetType string) (orderbook.Base, error) - UpdateOrderbook(currency currency.Pair, assetType string) (orderbook.Base, error) - GetEnabledCurrencies() currency.Pairs - GetAvailableCurrencies() currency.Pairs - GetAssetTypes() []string - GetAccountInfo() (AccountInfo, error) - GetAuthenticatedAPISupport() bool - SetCurrencies(pairs []currency.Pair, enabledPairs bool) error - GetExchangeHistory(p currency.Pair, assetType string) ([]TradeHistory, error) - SupportsAutoPairUpdates() bool - GetLastPairsUpdateTime() int64 - SupportsRESTTickerBatchUpdates() bool - GetFeeByType(feeBuilder *FeeBuilder) (float64, error) - GetWithdrawPermissions() uint32 - FormatWithdrawPermissions() string - SupportsWithdrawPermissions(permissions uint32) bool - GetFundingHistory() ([]FundHistory, error) - SubmitOrder(p currency.Pair, side OrderSide, orderType OrderType, amount, price float64, clientID string) (SubmitOrderResponse, error) - ModifyOrder(action *ModifyOrder) (string, error) - CancelOrder(order *OrderCancellation) error - CancelAllOrders(orders *OrderCancellation) (CancelAllOrdersResponse, error) - GetOrderInfo(orderID string) (OrderDetail, error) - GetDepositAddress(cryptocurrency currency.Code, accountID string) (string, error) - GetOrderHistory(getOrdersRequest *GetOrdersRequest) ([]OrderDetail, error) - GetActiveOrders(getOrdersRequest *GetOrdersRequest) ([]OrderDetail, error) - WithdrawCryptocurrencyFunds(withdrawRequest *WithdrawRequest) (string, error) - WithdrawFiatFunds(withdrawRequest *WithdrawRequest) (string, error) - WithdrawFiatFundsToInternationalBank(withdrawRequest *WithdrawRequest) (string, error) - GetWebsocket() (*Websocket, error) - SubscribeToWebsocketChannels(channels []WebsocketChannelSubscription) error - UnsubscribeToWebsocketChannels(channels []WebsocketChannelSubscription) error -} - -// SupportsRESTTickerBatchUpdates returns whether or not the -// exhange supports REST batch ticker fetching -func (e *Base) SupportsRESTTickerBatchUpdates() bool { - return e.SupportsRESTTickerBatching +func (e *Base) checkAndInitRequester() { + if e.Requester == nil { + e.Requester = request.New(e.Name, + request.NewRateLimit(time.Second, 0), + request.NewRateLimit(time.Second, 0), + new(http.Client)) + } } // SetHTTPClientTimeout sets the timeout value for the exchanges // HTTP Client func (e *Base) SetHTTPClientTimeout(t time.Duration) { - if e.Requester == nil { - e.Requester = request.New(e.Name, - request.NewRateLimit(time.Second, 0), - request.NewRateLimit(time.Second, 0), - new(http.Client)) - } + e.checkAndInitRequester() e.Requester.HTTPClient.Timeout = t } // SetHTTPClient sets exchanges HTTP client func (e *Base) SetHTTPClient(h *http.Client) { - if e.Requester == nil { - e.Requester = request.New(e.Name, - request.NewRateLimit(time.Second, 0), - request.NewRateLimit(time.Second, 0), - new(http.Client)) - } + e.checkAndInitRequester() e.Requester.HTTPClient = h } // GetHTTPClient gets the exchanges HTTP client func (e *Base) GetHTTPClient() *http.Client { - if e.Requester == nil { - e.Requester = request.New(e.Name, - request.NewRateLimit(time.Second, 0), - request.NewRateLimit(time.Second, 0), - new(http.Client)) - } + e.checkAndInitRequester() return e.Requester.HTTPClient } // SetHTTPClientUserAgent sets the exchanges HTTP user agent func (e *Base) SetHTTPClientUserAgent(ua string) { - if e.Requester == nil { - e.Requester = request.New(e.Name, - request.NewRateLimit(time.Second, 0), - request.NewRateLimit(time.Second, 0), - new(http.Client)) - } + e.checkAndInitRequester() e.Requester.UserAgent = ua e.HTTPUserAgent = ua } @@ -409,88 +90,137 @@ func (e *Base) SetClientProxyAddress(addr string) error { return nil } -// SetAutoPairDefaults sets the default values for whether or not the exchange -// supports auto pair updating or not -func (e *Base) SetAutoPairDefaults() error { - cfg := config.GetConfig() - exch, err := cfg.GetExchangeConfig(e.Name) - if err != nil { - return err - } - - update := false - if e.SupportsAutoPairUpdating { - if !exch.SupportsAutoPairUpdates { - exch.SupportsAutoPairUpdates = true - exch.PairsLastUpdated = 0 - update = true +// SetFeatureDefaults sets the exchanges default feature +// support set +func (e *Base) SetFeatureDefaults() { + if e.Config.Features == nil { + s := &config.FeaturesConfig{ + Supports: config.FeaturesSupportedConfig{ + Websocket: e.Features.Supports.Websocket, + REST: e.Features.Supports.REST, + RESTCapabilities: config.ProtocolFeaturesConfig{ + AutoPairUpdates: e.Features.Supports.RESTCapabilities.AutoPairUpdates, + }, + }, } + + if e.Config.SupportsAutoPairUpdates != nil { + s.Supports.RESTCapabilities.AutoPairUpdates = *e.Config.SupportsAutoPairUpdates + s.Enabled.AutoPairUpdates = *e.Config.SupportsAutoPairUpdates + } else { + s.Supports.RESTCapabilities.AutoPairUpdates = e.Features.Supports.RESTCapabilities.AutoPairUpdates + s.Enabled.AutoPairUpdates = e.Features.Supports.RESTCapabilities.AutoPairUpdates + if !s.Supports.RESTCapabilities.AutoPairUpdates { + e.Config.CurrencyPairs.LastUpdated = time.Now().Unix() + e.CurrencyPairs.LastUpdated = e.Config.CurrencyPairs.LastUpdated + } + } + e.Config.Features = s + e.Config.SupportsAutoPairUpdates = nil } else { - if exch.PairsLastUpdated == 0 { - exch.PairsLastUpdated = time.Now().Unix() - e.PairsLastUpdated = exch.PairsLastUpdated - update = true + if e.Features.Supports.RESTCapabilities.AutoPairUpdates != e.Config.Features.Supports.RESTCapabilities.AutoPairUpdates { + e.Config.Features.Supports.RESTCapabilities.AutoPairUpdates = e.Features.Supports.RESTCapabilities.AutoPairUpdates + + if !e.Config.Features.Supports.RESTCapabilities.AutoPairUpdates { + e.Config.CurrencyPairs.LastUpdated = time.Now().Unix() + } } + + if e.Features.Supports.REST != e.Config.Features.Supports.REST { + e.Config.Features.Supports.REST = e.Features.Supports.REST + } + + if e.Features.Supports.RESTCapabilities.TickerBatching != e.Config.Features.Supports.RESTCapabilities.TickerBatching { + e.Config.Features.Supports.RESTCapabilities.TickerBatching = e.Features.Supports.RESTCapabilities.TickerBatching + } + + if e.Features.Supports.Websocket != e.Config.Features.Supports.Websocket { + e.Config.Features.Supports.Websocket = e.Features.Supports.Websocket + } + + e.Features.Enabled.AutoPairUpdates = e.Config.Features.Enabled.AutoPairUpdates + } +} + +// SetAPICredentialDefaults sets the API Credential validator defaults +func (e *Base) SetAPICredentialDefaults() { + // Exchange hardcoded settings take precedence and overwrite the config settings + if e.Config.API.CredentialsValidator.RequiresKey != e.API.CredentialsValidator.RequiresKey { + e.Config.API.CredentialsValidator.RequiresKey = e.API.CredentialsValidator.RequiresKey } - if update { - return cfg.UpdateExchangeConfig(&exch) + if e.Config.API.CredentialsValidator.RequiresSecret != e.API.CredentialsValidator.RequiresSecret { + e.Config.API.CredentialsValidator.RequiresSecret = e.API.CredentialsValidator.RequiresSecret } - return nil + + if e.Config.API.CredentialsValidator.RequiresBase64DecodeSecret != e.API.CredentialsValidator.RequiresBase64DecodeSecret { + e.Config.API.CredentialsValidator.RequiresBase64DecodeSecret = e.API.CredentialsValidator.RequiresBase64DecodeSecret + } + + if e.Config.API.CredentialsValidator.RequiresClientID != e.API.CredentialsValidator.RequiresClientID { + e.Config.API.CredentialsValidator.RequiresClientID = e.API.CredentialsValidator.RequiresClientID + } + + if e.Config.API.CredentialsValidator.RequiresPEM != e.API.CredentialsValidator.RequiresPEM { + e.Config.API.CredentialsValidator.RequiresPEM = e.API.CredentialsValidator.RequiresPEM + } +} + +// SetHTTPRateLimiter sets the exchanges default HTTP rate limiter and updates the exchange's config +// to default settings if it doesn't exist +func (e *Base) SetHTTPRateLimiter() { + e.checkAndInitRequester() + + if e.RequiresRateLimiter() { + if e.Config.HTTPRateLimiter == nil { + e.Config.HTTPRateLimiter = new(config.HTTPRateLimitConfig) + e.Config.HTTPRateLimiter.Authenticated.Duration = e.GetRateLimit(true).Duration + e.Config.HTTPRateLimiter.Authenticated.Rate = e.GetRateLimit(true).Rate + e.Config.HTTPRateLimiter.Unauthenticated.Duration = e.GetRateLimit(false).Duration + e.Config.HTTPRateLimiter.Unauthenticated.Rate = e.GetRateLimit(false).Rate + } else { + e.SetRateLimit(true, e.Config.HTTPRateLimiter.Authenticated.Duration, + e.Config.HTTPRateLimiter.Authenticated.Rate) + e.SetRateLimit(false, e.Config.HTTPRateLimiter.Unauthenticated.Duration, + e.Config.HTTPRateLimiter.Unauthenticated.Rate) + } + } +} + +// SupportsRESTTickerBatchUpdates returns whether or not the +// exhange supports REST batch ticker fetching +func (e *Base) SupportsRESTTickerBatchUpdates() bool { + return e.Features.Supports.RESTCapabilities.TickerBatching } // SupportsAutoPairUpdates returns whether or not the exchange supports // auto currency pair updating func (e *Base) SupportsAutoPairUpdates() bool { - return e.SupportsAutoPairUpdating + if e.Features.Supports.RESTCapabilities.AutoPairUpdates || e.Features.Supports.WebsocketCapabilities.AutoPairUpdates { + return true + } + return false } // GetLastPairsUpdateTime returns the unix timestamp of when the exchanges // currency pairs were last updated func (e *Base) GetLastPairsUpdateTime() int64 { - return e.PairsLastUpdated + return e.CurrencyPairs.LastUpdated } // 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 +func (e *Base) SetAssetTypes() { + if e.Config.CurrencyPairs.AssetTypes.JoinToString(",") == "" { + e.Config.CurrencyPairs.AssetTypes = e.CurrencyPairs.AssetTypes + } else if e.Config.CurrencyPairs.AssetTypes.JoinToString(",") != e.CurrencyPairs.AssetTypes.JoinToString(",") { + e.Config.CurrencyPairs.AssetTypes = e.CurrencyPairs.AssetTypes } - - var update bool - if exch.AssetTypes == "" { - exch.AssetTypes = common.JoinStrings(e.AssetTypes, ",") - update = true - } else { - e.AssetTypes = common.SplitStrings(exch.AssetTypes, ",") - update = true - } - - if update { - return cfg.UpdateExchangeConfig(&exch) - } - - return nil } // GetAssetTypes returns the available asset types for an individual exchange -func (e *Base) GetAssetTypes() []string { - return e.AssetTypes -} - -// 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 +func (e *Base) GetAssetTypes() assets.AssetTypes { + return e.CurrencyPairs.AssetTypes } // GetClientBankAccounts returns banking details associated with @@ -507,74 +237,51 @@ func (e *Base) GetExchangeBankAccounts(exchangeName, depositCurrency string) (co return cfg.GetExchangeBankAccounts(exchangeName, depositCurrency) } -// CompareCurrencyPairFormats checks and returns whether or not the two supplied -// config currency pairs match -func CompareCurrencyPairFormats(pair1 config.CurrencyPairFormatConfig, pair2 *config.CurrencyPairFormatConfig) bool { - if pair1.Delimiter != pair2.Delimiter || - pair1.Uppercase != pair2.Uppercase || - pair1.Separator != pair2.Separator || - pair1.Index != pair2.Index { - return false - } - return true -} - // 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 +func (e *Base) SetCurrencyPairFormat() { + if e.Config.CurrencyPairs == nil { + e.Config.CurrencyPairs = new(currency.PairsManager) } - 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, + e.Config.CurrencyPairs.UseGlobalFormat = e.CurrencyPairs.UseGlobalFormat + + if e.Config.CurrencyPairs.UseGlobalFormat { + if e.Config.CurrencyPairs.ConfigFormat == nil { + e.Config.CurrencyPairs.ConfigFormat = e.CurrencyPairs.ConfigFormat } - update = true - } else { - if CompareCurrencyPairFormats(e.RequestCurrencyPairFormat, - exch.RequestCurrencyPairFormat) { - e.RequestCurrencyPairFormat = *exch.RequestCurrencyPairFormat - } else { - *exch.RequestCurrencyPairFormat = e.RequestCurrencyPairFormat - update = true + + if e.Config.CurrencyPairs.RequestFormat == nil { + e.Config.CurrencyPairs.RequestFormat = e.CurrencyPairs.RequestFormat } + + return } - 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 { - if CompareCurrencyPairFormats(e.ConfigCurrencyPairFormat, - exch.ConfigCurrencyPairFormat) { - e.ConfigCurrencyPairFormat = *exch.ConfigCurrencyPairFormat - } else { - *exch.ConfigCurrencyPairFormat = e.ConfigCurrencyPairFormat - update = true - } + if e.Config.CurrencyPairs.ConfigFormat != nil { + e.Config.CurrencyPairs.ConfigFormat = nil } - if update { - return cfg.UpdateExchangeConfig(&exch) + if e.Config.CurrencyPairs.RequestFormat != nil { + e.Config.CurrencyPairs.RequestFormat = nil + } + + assetTypes := e.GetAssetTypes() + for x := range assetTypes { + if e.Config.CurrencyPairs.Get(assetTypes[x]) == nil { + r := e.CurrencyPairs.Get(assetTypes[x]) + if r == nil { + continue + } + e.Config.CurrencyPairs.Store(assetTypes[x], *e.CurrencyPairs.Get(assetTypes[x])) + } } - return nil } // GetAuthenticatedAPISupport returns whether the exchange supports // authenticated API requests func (e *Base) GetAuthenticatedAPISupport() bool { - return e.AuthenticatedAPISupport + return e.API.AuthenticatedSupport } // GetName is a method that returns the name of the exchange base @@ -582,82 +289,82 @@ func (e *Base) GetName() string { return e.Name } -// GetEnabledCurrencies is a method that returns the enabled currency pairs of -// the exchange base -func (e *Base) GetEnabledCurrencies() currency.Pairs { - return e.EnabledPairs.Format(e.ConfigCurrencyPairFormat.Delimiter, - e.ConfigCurrencyPairFormat.Index, - e.ConfigCurrencyPairFormat.Uppercase) +// GetEnabledFeatures returns the exchanges enabled features +func (e *Base) GetEnabledFeatures() FeaturesEnabled { + return e.Features.Enabled } -// GetAvailableCurrencies is a method that returns the available currency pairs -// of the exchange base -func (e *Base) GetAvailableCurrencies() currency.Pairs { - return e.AvailablePairs.Format(e.ConfigCurrencyPairFormat.Delimiter, - e.ConfigCurrencyPairFormat.Index, - e.ConfigCurrencyPairFormat.Uppercase) +// GetSupportedFeatures returns the exchanges supported features +func (e *Base) GetSupportedFeatures() FeaturesSupported { + return e.Features.Supports } -// SupportsCurrency returns true or not whether a currency pair exists in the +// GetPairFormat returns the pair format based on the exchange and +// asset type +func (e *Base) GetPairFormat(assetType assets.AssetType, requestFormat bool) currency.PairFormat { + if e.CurrencyPairs.UseGlobalFormat { + if requestFormat { + return *e.CurrencyPairs.RequestFormat + } + return *e.CurrencyPairs.ConfigFormat + } + + if requestFormat { + return *e.CurrencyPairs.Get(assetType).RequestFormat + } + return *e.CurrencyPairs.Get(assetType).ConfigFormat +} + +// GetEnabledPairs is a method that returns the enabled currency pairs of +// the exchange by asset type +func (e *Base) GetEnabledPairs(assetType assets.AssetType) currency.Pairs { + format := e.GetPairFormat(assetType, false) + pairs := e.CurrencyPairs.GetPairs(assetType, true) + return pairs.Format(format.Delimiter, format.Index, format.Uppercase) +} + +// GetAvailablePairs is a method that returns the available currency pairs +// of the exchange by asset type +func (e *Base) GetAvailablePairs(assetType assets.AssetType) currency.Pairs { + format := e.GetPairFormat(assetType, false) + pairs := e.CurrencyPairs.GetPairs(assetType, false) + return pairs.Format(format.Delimiter, format.Index, format.Uppercase) +} + +// SupportsPair returns true or not whether a currency pair exists in the // exchange available currencies or not -func (e *Base) SupportsCurrency(p currency.Pair, enabledPairs bool) bool { +func (e *Base) SupportsPair(p currency.Pair, enabledPairs bool, assetType assets.AssetType) bool { if enabledPairs { - return e.GetEnabledCurrencies().Contains(p, false) + return e.GetEnabledPairs(assetType).Contains(p, false) } - return e.GetAvailableCurrencies().Contains(p, false) + return e.GetAvailablePairs(assetType).Contains(p, false) } -// 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 +// FormatExchangeCurrencies returns a string containing // the exchanges formatted currency pairs -func GetAndFormatExchangeCurrencies(exchName string, pairs []currency.Pair) (string, error) { +func (e *Base) FormatExchangeCurrencies(pairs []currency.Pair, assetType assets.AssetType) (string, error) { var currencyItems string - cfg := config.GetConfig() - exch, err := cfg.GetExchangeConfig(exchName) - if err != nil { - return currencyItems, err - } + pairFmt := e.GetPairFormat(assetType, true) for x := range pairs { - currencyItems += FormatExchangeCurrency(exchName, pairs[x]).String() + currencyItems += e.FormatExchangeCurrency(pairs[x], assetType).String() if x == len(pairs)-1 { continue } - currencyItems += exch.RequestCurrencyPairFormat.Separator + currencyItems += pairFmt.Separator + } + + if currencyItems == "" { + return "", errors.New("returned empty string") } return currencyItems, nil } // FormatExchangeCurrency is a method that formats and returns a currency pair -// based on the exchange currency format -func FormatExchangeCurrency(exchName string, p currency.Pair) currency.Pair { - cfg := config.GetConfig() - exch, _ := cfg.GetExchangeConfig(exchName) - - return p.Format(exch.RequestCurrencyPairFormat.Delimiter, - exch.RequestCurrencyPairFormat.Uppercase) -} - -// FormatCurrency is a method that formats and returns a currency pair // based on the user currency display preferences -func FormatCurrency(p currency.Pair) currency.Pair { - cfg := config.GetConfig() - return p.Format(cfg.Currency.CurrencyPairFormat.Delimiter, - cfg.Currency.CurrencyPairFormat.Uppercase) +func (e *Base) FormatExchangeCurrency(p currency.Pair, assetType assets.AssetType) currency.Pair { + pairFmt := e.GetPairFormat(assetType, true) + return p.Format(pairFmt.Delimiter, pairFmt.Uppercase) } // SetEnabled is a method that sets if the exchange is enabled @@ -671,65 +378,171 @@ 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 - } +func (e *Base) SetAPIKeys(apiKey, apiSecret, clientID string) { + e.API.Credentials.Key = apiKey + e.API.Credentials.ClientID = clientID - e.APIKey = apiKey - e.ClientID = clientID - - if b64Decode { - result, err := common.Base64Decode(apiSecret) + if e.API.CredentialsValidator.RequiresBase64DecodeSecret { + result, err := crypto.Base64Decode(apiSecret) if err != nil { - e.AuthenticatedAPISupport = false + e.API.AuthenticatedSupport = false log.Warnf(warningBase64DecryptSecretKeyFailed, e.Name) } - e.APISecret = string(result) + e.API.Credentials.Secret = string(result) } else { - e.APISecret = apiSecret + e.API.Credentials.Secret = apiSecret } } -// SetCurrencies sets the exchange currency pairs for either enabledPairs or +// SetupDefaults sets the exchange settings based on the supplied config +func (e *Base) SetupDefaults(exch *config.ExchangeConfig) error { + e.Enabled = true + e.LoadedByConfig = true + e.Config = exch + e.Verbose = exch.Verbose + + e.API.AuthenticatedSupport = exch.API.AuthenticatedSupport + if e.API.AuthenticatedSupport { + e.SetAPIKeys(exch.API.Credentials.Key, exch.API.Credentials.Secret, exch.API.Credentials.ClientID) + } + + if exch.HTTPTimeout <= time.Duration(0) { + exch.HTTPTimeout = DefaultHTTPTimeout + } else { + e.SetHTTPClientTimeout(exch.HTTPTimeout) + } + + if exch.CurrencyPairs == nil { + exch.CurrencyPairs = new(currency.PairsManager) + } + + e.HTTPDebugging = exch.HTTPDebugging + e.SetHTTPClientUserAgent(exch.HTTPUserAgent) + e.SetHTTPRateLimiter() + e.SetAssetTypes() + e.SetCurrencyPairFormat() + e.SetFeatureDefaults() + e.SetAPIURL() + e.SetAPICredentialDefaults() + e.SetClientProxyAddress(exch.ProxyAddress) + e.SetHTTPRateLimiter() + + e.BaseCurrencies = exch.BaseCurrencies + + assetTypes := e.GetAssetTypes() + for x := range assetTypes { + p := exch.CurrencyPairs.Get(assetTypes[x]) + if p != nil { + e.CurrencyPairs.Store(assetTypes[x], *p) + } + } + + if e.Features.Supports.Websocket { + e.Websocket.SetWsStatusAndConnection(exch.Features.Enabled.Websocket) + } + return nil +} + +// AllowAuthenticatedRequest checks to see if the required fields have been set before sending an authenticated +// API request +func (e *Base) AllowAuthenticatedRequest() bool { + // Individual package usage, allow request if API credentials are valid a + // and without needing to set AuthenticatedSupport to true + if !e.LoadedByConfig && !e.ValidateAPICredentials() { + return false + } + + // Bot usage, AuthenticatedSupport can be disabled by user if desired, so don't + // allow authenticated requests. + if !e.API.AuthenticatedSupport && e.LoadedByConfig { + return false + } + + // Check to see if the user has enabled AuthenticatedSupport, but has invalid + // API credentials set and loaded by config + if e.API.AuthenticatedSupport && e.LoadedByConfig && !e.ValidateAPICredentials() { + return false + } + + return true +} + +// ValidateAPICredentials validates the exchanges API credentials +func (e *Base) ValidateAPICredentials() bool { + if e.API.CredentialsValidator.RequiresKey { + if e.API.Credentials.Key == "" || + e.API.Credentials.Key == config.DefaultAPIKey { + log.Warnf("exchange %s requires API key but default/empty one set", + e.Name) + return false + } + } + + if e.API.CredentialsValidator.RequiresSecret { + if e.API.Credentials.Secret == "" || + e.API.Credentials.Secret == config.DefaultAPISecret { + log.Warnf("exchange %s requires API secret but default/empty one set", + e.Name) + return false + } + } + + if e.API.CredentialsValidator.RequiresPEM { + if e.API.Credentials.PEMKey == "" || + common.StringContains(e.API.Credentials.PEMKey, "JUSTADUMMY") { + log.Warnf("exchange %s requires API PEM key but default/empty one set", + e.Name) + return false + } + } + + if e.API.CredentialsValidator.RequiresClientID { + if e.API.Credentials.ClientID == "" || + e.API.Credentials.ClientID == config.DefaultAPIClientID { + log.Warnf("exchange %s requires API ClientID but default/empty one set", + e.Name) + return false + } + } + + if e.API.CredentialsValidator.RequiresBase64DecodeSecret && !e.LoadedByConfig { + _, err := crypto.Base64Decode(e.API.Credentials.Secret) + if err != nil { + log.Warnf("exchange %s API secret base64 decode failed: %s", + e.Name, err) + return false + } + } + return true +} + +// SetPairs sets the exchange currency pairs for either enabledPairs or // availablePairs -func (e *Base) SetCurrencies(pairs []currency.Pair, enabledPairs bool) error { +func (e *Base) SetPairs(pairs currency.Pairs, assetType assets.AssetType, enabled bool) error { if len(pairs) == 0 { - return fmt.Errorf("%s SetCurrencies error - pairs is empty", e.Name) - } - - cfg := config.GetConfig() - exchCfg, err := cfg.GetExchangeConfig(e.Name) - if err != nil { - return err + return fmt.Errorf("%s SetPairs error - pairs is empty", e.Name) } + pairFmt := e.GetPairFormat(assetType, false) var newPairs currency.Pairs for x := range pairs { - newPairs = append(newPairs, pairs[x].Format(exchCfg.ConfigCurrencyPairFormat.Delimiter, - exchCfg.ConfigCurrencyPairFormat.Uppercase)) + newPairs = append(newPairs, pairs[x].Format(pairFmt.Delimiter, + pairFmt.Uppercase)) } - if enabledPairs { - exchCfg.EnabledPairs = newPairs - e.EnabledPairs = newPairs - } else { - exchCfg.AvailablePairs = newPairs - e.AvailablePairs = newPairs - } - - return cfg.UpdateExchangeConfig(&exchCfg) + e.CurrencyPairs.StorePairs(assetType, newPairs, enabled) + e.Config.CurrencyPairs.StorePairs(assetType, newPairs, enabled) + return nil } -// UpdateCurrencies updates the exchange currency pairs for either enabledPairs or +// UpdatePairs updates the exchange currency pairs for either enabledPairs or // availablePairs -func (e *Base) UpdateCurrencies(exchangeProducts currency.Pairs, enabled, force bool) error { +func (e *Base) UpdatePairs(exchangeProducts currency.Pairs, assetType assets.AssetType, enabled, force bool) error { if len(exchangeProducts) == 0 { - return fmt.Errorf("%s UpdateCurrencies error - exchangeProducts is empty", e.Name) + return fmt.Errorf("%s UpdatePairs error - exchangeProducts is empty", e.Name) } exchangeProducts = exchangeProducts.Upper() - var products currency.Pairs for x := range exchangeProducts { if exchangeProducts[x].String() == "" { @@ -740,155 +553,94 @@ func (e *Base) UpdateCurrencies(exchangeProducts currency.Pairs, enabled, force var newPairs, removedPairs currency.Pairs var updateType string + targetPairs := e.CurrencyPairs.GetPairs(assetType, enabled) if enabled { - newPairs, removedPairs = e.EnabledPairs.FindDifferences(products) + newPairs, removedPairs = targetPairs.FindDifferences(products) updateType = "enabled" } else { - newPairs, removedPairs = e.AvailablePairs.FindDifferences(products) + newPairs, removedPairs = targetPairs.FindDifferences(products) updateType = "available" } if force || len(newPairs) > 0 || len(removedPairs) > 0 { - cfg := config.GetConfig() - exch, err := cfg.GetExchangeConfig(e.Name) - if err != nil { - return err - } - if force { - log.Debugf("%s forced update of %s pairs.", e.Name, updateType) + log.Debugf("%s forced update of %s [%v] pairs.", e.Name, updateType, + strings.ToUpper(assetType.String())) } else { if len(newPairs) > 0 { - log.Debugf("%s Updating pairs - New: %s.\n", e.Name, newPairs) + log.Debugf("%s Updating pairs [%v] - New: %s.\n", e.Name, + strings.ToUpper(assetType.String()), newPairs) } if len(removedPairs) > 0 { - log.Debugf("%s Updating pairs - Removed: %s.\n", e.Name, removedPairs) + log.Debugf("%s Updating pairs [%v] - Removed: %s.\n", e.Name, + strings.ToUpper(assetType.String()), removedPairs) } } - - if enabled { - exch.EnabledPairs = products - e.EnabledPairs = products - } else { - exch.AvailablePairs = products - e.AvailablePairs = products - } - return cfg.UpdateExchangeConfig(&exch) + e.Config.CurrencyPairs.StorePairs(assetType, products, enabled) + e.CurrencyPairs.StorePairs(assetType, products, enabled) } return nil } -// ModifyOrder is a an order modifyer -type ModifyOrder struct { - OrderID string - OrderType - OrderSide - Price float64 - Amount float64 - LimitPriceUpper float64 - LimitPriceLower float64 - CurrencyPair currency.Pair - - ImmediateOrCancel bool - HiddenOrder bool - FillOrKill bool - PostOnly bool -} - -// ModifyOrderResponse is an order modifying return type -type ModifyOrderResponse struct { - OrderID string -} - -// Format holds exchange formatting -type Format struct { - ExchangeName string - OrderType map[string]string - OrderSide map[string]string -} - -// CancelAllOrdersResponse returns the status from attempting to cancel all orders on an exchagne -type CancelAllOrdersResponse struct { - OrderStatus map[string]string -} - -// Formatting contain a range of exchanges formatting -type Formatting []Format - -// OrderType enforces a standard for Ordertypes across the code base -type OrderType string - -// OrderType ...types -const ( - AnyOrderType OrderType = "ANY" - LimitOrderType OrderType = "LIMIT" - MarketOrderType OrderType = "MARKET" - ImmediateOrCancelOrderType OrderType = "IMMEDIATE_OR_CANCEL" - StopOrderType OrderType = "STOP" - TrailingStopOrderType OrderType = "TRAILINGSTOP" - UnknownOrderType OrderType = "UNKNOWN" -) - -// ToString changes the ordertype to the exchange standard and returns a string -func (o OrderType) ToString() string { - return fmt.Sprintf("%v", o) -} - -// OrderSide enforces a standard for OrderSides across the code base -type OrderSide string - -// OrderSide types -const ( - AnyOrderSide OrderSide = "ANY" - BuyOrderSide OrderSide = "BUY" - SellOrderSide OrderSide = "SELL" - BidOrderSide OrderSide = "BID" - AskOrderSide OrderSide = "ASK" -) - -// ToString changes the ordertype to the exchange standard and returns a string -func (o OrderSide) ToString() string { - return fmt.Sprintf("%v", o) -} - // SetAPIURL sets configuration API URL for an exchange -func (e *Base) SetAPIURL(ec *config.ExchangeConfig) error { - if ec.APIURL == "" || ec.APIURLSecondary == "" { - return errors.New("empty config API URLs") +func (e *Base) SetAPIURL() error { + if e.Config.API.Endpoints.URL == "" || e.Config.API.Endpoints.URLSecondary == "" { + return fmt.Errorf("exchange %s: SetAPIURL error. URL vals are empty", e.Name) } - if ec.APIURL != config.APIURLNonDefaultMessage { - e.APIUrl = ec.APIURL + if e.Config.API.Endpoints.URL != config.APIURLNonDefaultMessage { + e.API.Endpoints.URL = e.Config.API.Endpoints.URL } - if ec.APIURLSecondary != config.APIURLNonDefaultMessage { - e.APIUrlSecondary = ec.APIURLSecondary + if e.Config.API.Endpoints.URLSecondary != config.APIURLNonDefaultMessage { + e.API.Endpoints.URLSecondary = e.Config.API.Endpoints.URLSecondary } return nil } // GetAPIURL returns the set API URL func (e *Base) GetAPIURL() string { - return e.APIUrl + return e.API.Endpoints.URL } // GetSecondaryAPIURL returns the set Secondary API URL func (e *Base) GetSecondaryAPIURL() string { - return e.APIUrlSecondary + return e.API.Endpoints.URLSecondary } // GetAPIURLDefault returns exchange default URL func (e *Base) GetAPIURLDefault() string { - return e.APIUrlDefault + return e.API.Endpoints.URLDefault } // GetAPIURLSecondaryDefault returns exchange default secondary URL func (e *Base) GetAPIURLSecondaryDefault() string { - return e.APIUrlSecondaryDefault + return e.API.Endpoints.URLSecondaryDefault +} + +// SupportsWebsocket returns whether or not the exchange supports +// websocket +func (e *Base) SupportsWebsocket() bool { + return e.Features.Supports.Websocket +} + +// SupportsREST returns whether or not the exchange supports +// REST +func (e *Base) SupportsREST() bool { + return e.Features.Supports.REST +} + +// IsWebsocketEnabled returns whether or not the exchange has its +// websocket client enabled +func (e *Base) IsWebsocketEnabled() bool { + if e.Websocket != nil { + return e.Websocket.IsEnabled() + } + return false } // GetWithdrawPermissions passes through the exchange's withdraw permissions func (e *Base) GetWithdrawPermissions() uint32 { - return e.APIWithdrawPermissions + return e.Features.Supports.WithdrawPermissions } // SupportsWithdrawPermissions compares the supplied permissions with the exchange's to verify they're supported @@ -954,223 +706,16 @@ func (e *Base) FormatWithdrawPermissions() string { return NoAPIWithdrawalMethodsText } -// GetOrdersRequest used for GetOrderHistory and GetOpenOrders wrapper functions -type GetOrdersRequest struct { - OrderType OrderType - OrderSide OrderSide - StartTicks time.Time - EndTicks time.Time - // Currencies Empty array = all currencies. Some endpoints only support singular currency enquiries - Currencies []currency.Pair +// IsAssetTypeSupported whether or not the supplied asset is supported +// by the exchange +func (e *Base) IsAssetTypeSupported(asset assets.AssetType) bool { + return e.CurrencyPairs.AssetTypes.Contains(asset) } -// OrderStatus defines order status types -type OrderStatus string - -// All OrderStatus types -const ( - AnyOrderStatus OrderStatus = "ANY" - NewOrderStatus OrderStatus = "NEW" - ActiveOrderStatus OrderStatus = "ACTIVE" - PartiallyFilledOrderStatus OrderStatus = "PARTIALLY_FILLED" - FilledOrderStatus OrderStatus = "FILLED" - CancelledOrderStatus OrderStatus = "CANCELED" - PendingCancelOrderStatus OrderStatus = "PENDING_CANCEL" - RejectedOrderStatus OrderStatus = "REJECTED" - ExpiredOrderStatus OrderStatus = "EXPIRED" - HiddenOrderStatus OrderStatus = "HIDDEN" - UnknownOrderStatus OrderStatus = "UNKNOWN" -) - -// FilterOrdersBySide removes any OrderDetails that don't match the orderStatus provided -func FilterOrdersBySide(orders *[]OrderDetail, orderSide OrderSide) { - if orderSide == "" || orderSide == AnyOrderSide { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - if strings.EqualFold(string((*orders)[i].OrderSide), string(orderSide)) { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// FilterOrdersByType removes any OrderDetails that don't match the orderType provided -func FilterOrdersByType(orders *[]OrderDetail, orderType OrderType) { - if orderType == "" || orderType == AnyOrderType { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - if strings.EqualFold(string((*orders)[i].OrderType), string(orderType)) { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// FilterOrdersByTickRange removes any OrderDetails outside of the tick range -func FilterOrdersByTickRange(orders *[]OrderDetail, startTicks, endTicks time.Time) { - if startTicks.IsZero() || endTicks.IsZero() || - startTicks.Unix() == 0 || endTicks.Unix() == 0 || endTicks.Before(startTicks) { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - if (*orders)[i].OrderDate.Unix() >= startTicks.Unix() && (*orders)[i].OrderDate.Unix() <= endTicks.Unix() { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// FilterOrdersByCurrencies removes any OrderDetails that do not match the provided currency list -// It is forgiving in that the provided currencies can match quote or base currencies -func FilterOrdersByCurrencies(orders *[]OrderDetail, currencies []currency.Pair) { - if len(currencies) == 0 { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - matchFound := false - for _, c := range currencies { - if !matchFound && (*orders)[i].CurrencyPair.EqualIncludeReciprocal(c) { - matchFound = true - } - } - - if matchFound { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// ByPrice used for sorting orders by price -type ByPrice []OrderDetail - -func (b ByPrice) Len() int { - return len(b) -} - -func (b ByPrice) Less(i, j int) bool { - return b[i].Price < b[j].Price -} - -func (b ByPrice) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByPrice the caller function to sort orders -func SortOrdersByPrice(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByPrice(*orders))) - } else { - sort.Sort(ByPrice(*orders)) - } -} - -// ByOrderType used for sorting orders by order type -type ByOrderType []OrderDetail - -func (b ByOrderType) Len() int { - return len(b) -} - -func (b ByOrderType) Less(i, j int) bool { - return b[i].OrderType.ToString() < b[j].OrderType.ToString() -} - -func (b ByOrderType) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByType the caller function to sort orders -func SortOrdersByType(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByOrderType(*orders))) - } else { - sort.Sort(ByOrderType(*orders)) - } -} - -// ByCurrency used for sorting orders by order currency -type ByCurrency []OrderDetail - -func (b ByCurrency) Len() int { - return len(b) -} - -func (b ByCurrency) Less(i, j int) bool { - return b[i].CurrencyPair.String() < b[j].CurrencyPair.String() -} - -func (b ByCurrency) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByCurrency the caller function to sort orders -func SortOrdersByCurrency(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByCurrency(*orders))) - } else { - sort.Sort(ByCurrency(*orders)) - } -} - -// ByDate used for sorting orders by order date -type ByDate []OrderDetail - -func (b ByDate) Len() int { - return len(b) -} - -func (b ByDate) Less(i, j int) bool { - return b[i].OrderDate.Unix() < b[j].OrderDate.Unix() -} - -func (b ByDate) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByDate the caller function to sort orders -func SortOrdersByDate(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByDate(*orders))) - } else { - sort.Sort(ByDate(*orders)) - } -} - -// ByOrderSide used for sorting orders by order side (buy sell) -type ByOrderSide []OrderDetail - -func (b ByOrderSide) Len() int { - return len(b) -} - -func (b ByOrderSide) Less(i, j int) bool { - return b[i].OrderSide.ToString() < b[j].OrderSide.ToString() -} - -func (b ByOrderSide) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersBySide the caller function to sort orders -func SortOrdersBySide(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByOrderSide(*orders))) - } else { - sort.Sort(ByOrderSide(*orders)) +// PrintEnabledPairs prints the exchanges enabled asset pairs +func (e *Base) PrintEnabledPairs() { + for k, v := range e.CurrencyPairs.Pairs { + log.Infof("Asset type %v:", k) + log.Infof("\t Enabled pairs: %v", v.Enabled) } } diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index a0fd98db..2a65bc9a 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -9,8 +9,8 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -20,8 +20,15 @@ const ( func TestSupportsRESTTickerBatchUpdates(t *testing.T) { b := Base{ - Name: "RAWR", - SupportsRESTTickerBatching: true, + Name: "RAWR", + Features: Features{ + Supports: FeaturesSupported{ + REST: true, + RESTCapabilities: ProtocolFeatures{ + TickerBatching: true, + }, + }, + }, } if !b.SupportsRESTTickerBatchUpdates() { @@ -107,79 +114,35 @@ func TestSetAutoPairDefaults(t *testing.T) { t.Fatalf("Test failed. TestSetAutoPairDefaults failed to load config file. Error: %s", err) } - b := Base{ - Name: "TESTNAME", - SupportsAutoPairUpdating: true, - } - - err = b.SetAutoPairDefaults() - if err == nil { - t.Fatal("Test failed. TestSetAutoPairDefaults returned nil error for a non-existent exchange") - } - - b.Name = "Bitstamp" - err = b.SetAutoPairDefaults() - if err != nil { - t.Fatalf("Test failed. TestSetAutoPairDefaults. Error %s", err) - } - - exch, err := cfg.GetExchangeConfig(b.Name) + exch, err := cfg.GetExchangeConfig("Bitstamp") if err != nil { t.Fatalf("Test failed. TestSetAutoPairDefaults load config failed. Error %s", err) } - if !exch.SupportsAutoPairUpdates { + if !exch.Features.Supports.RESTCapabilities.AutoPairUpdates { t.Fatalf("Test failed. TestSetAutoPairDefaults Incorrect value") } - if exch.PairsLastUpdated != 0 { + if exch.CurrencyPairs.LastUpdated != 0 { t.Fatalf("Test failed. TestSetAutoPairDefaults Incorrect value") } - exch.SupportsAutoPairUpdates = false - err = cfg.UpdateExchangeConfig(&exch) - if err != nil { - t.Fatalf("Test failed. TestSetAutoPairDefaults update config failed. Error %s", err) - } + exch.Features.Supports.RESTCapabilities.AutoPairUpdates = false + cfg.UpdateExchangeConfig(exch) - exch, err = cfg.GetExchangeConfig(b.Name) + exch, err = cfg.GetExchangeConfig("Bitstamp") if err != nil { t.Fatalf("Test failed. TestSetAutoPairDefaults load config failed. Error %s", err) } - if exch.SupportsAutoPairUpdates { - t.Fatal("Test failed. TestSetAutoPairDefaults Incorrect value") - } - - err = b.SetAutoPairDefaults() - if err != nil { - t.Fatalf("Test failed. TestSetAutoPairDefaults. Error %s", err) - } - - exch, err = cfg.GetExchangeConfig(b.Name) - if err != nil { - t.Fatalf("Test failed. TestSetAutoPairDefaults load config failed. Error %s", err) - } - - if !exch.SupportsAutoPairUpdates { - t.Fatal("Test failed. TestSetAutoPairDefaults Incorrect value") - } - - b.SupportsAutoPairUpdating = false - err = b.SetAutoPairDefaults() - if err != nil { - t.Fatalf("Test failed. TestSetAutoPairDefaults. Error %s", err) - } - - if b.PairsLastUpdated == 0 { + if exch.Features.Supports.RESTCapabilities.AutoPairUpdates { t.Fatal("Test failed. TestSetAutoPairDefaults Incorrect value") } } func TestSupportsAutoPairUpdates(t *testing.T) { b := Base{ - Name: "TESTNAME", - SupportsAutoPairUpdating: false, + Name: "TESTNAME", } if b.SupportsAutoPairUpdates() { @@ -189,10 +152,8 @@ func TestSupportsAutoPairUpdates(t *testing.T) { func TestGetLastPairsUpdateTime(t *testing.T) { testTime := time.Now().Unix() - b := Base{ - Name: "TESTNAME", - PairsLastUpdated: testTime, - } + var b Base + b.CurrencyPairs.LastUpdated = testTime if b.GetLastPairsUpdateTime() != testTime { t.Fatal("Test failed. TestGetLastPairsUpdateTim Incorrect value") @@ -210,25 +171,15 @@ func TestSetAssetTypes(t *testing.T) { Name: "TESTNAME", } - err = b.SetAssetTypes() - if err == nil { - t.Fatal("Test failed. TestSetAssetTypes returned nil error for a non-existent exchange") - } - - b.Name = defaultTestExchange - b.AssetTypes = []string{"SPOT"} - err = b.SetAssetTypes() - if err != nil { - t.Fatalf("Test failed. TestSetAssetTypes. Error %s", err) - } - + b.Name = "ANX" + b.CurrencyPairs.AssetTypes = assets.AssetTypes{assets.AssetTypeSpot} 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) + exch.CurrencyPairs.AssetTypes = assets.New("") + err = cfg.UpdateExchangeConfig(exch) if err != nil { t.Fatalf("Test failed. TestSetAssetTypes update config failed. Error %s", err) } @@ -237,24 +188,27 @@ func TestSetAssetTypes(t *testing.T) { if err != nil { t.Fatalf("Test failed. TestSetAssetTypes load config failed. Error %s", err) } + b.Config = exch - if exch.AssetTypes != "" { + if exch.CurrencyPairs.AssetTypes.JoinToString(",") != "" { t.Fatal("Test failed. TestSetAssetTypes assetTypes != ''") } - err = b.SetAssetTypes() - if err != nil { - t.Fatalf("Test failed. TestSetAssetTypes. Error %s", err) - } - - if !common.StringDataCompare(b.AssetTypes, ticker.Spot) { + b.SetAssetTypes() + if !common.StringDataCompare(b.CurrencyPairs.AssetTypes.Strings(), assets.AssetTypeSpot.String()) { t.Fatal("Test failed. TestSetAssetTypes assetTypes is not set") } } func TestGetAssetTypes(t *testing.T) { testExchange := Base{ - AssetTypes: []string{"SPOT", "Binary", "Futures"}, + CurrencyPairs: currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + assets.AssetTypeBinary, + assets.AssetTypeFutures, + }, + }, } aT := testExchange.GetAssetTypes() @@ -263,48 +217,9 @@ func TestGetAssetTypes(t *testing.T) { } } -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.StringDataCompare(result, ticker.Spot) { - t.Fatal("Test failed. Bitfinex does not contain default asset type 'SPOT'") - } - - _, err = GetExchangeAssetTypes("non-existent-exchange") - if err == nil { - t.Fatal("Test failed. Got asset types for non-existent exchange") - } -} - -func TestCompareCurrencyPairFormats(t *testing.T) { - cfgOne := config.CurrencyPairFormatConfig{ - Delimiter: "-", - Uppercase: true, - Index: "", - Separator: ",", - } - - cfgTwo := cfgOne - if !CompareCurrencyPairFormats(cfgOne, &cfgTwo) { - t.Fatal("Test failed. CompareCurrencyPairFormats should be true") - } - - cfgTwo.Delimiter = "~" - if CompareCurrencyPairFormats(cfgOne, &cfgTwo) { - t.Fatal("Test failed. CompareCurrencyPairFormats should not be true") - } -} - func TestSetCurrencyPairFormat(t *testing.T) { + t.Skip() + // TO-DO cfg := config.GetConfig() err := cfg.LoadConfig(config.ConfigTestFile) if err != nil { @@ -315,25 +230,20 @@ func TestSetCurrencyPairFormat(t *testing.T) { Name: "TESTNAME", } - err = b.SetCurrencyPairFormat() - if err == nil { - t.Fatal("Test failed. TestSetCurrencyPairFormat returned nil error for a non-existent exchange") - } - - b.Name = defaultTestExchange - err = b.SetCurrencyPairFormat() - if err != nil { - t.Fatalf("Test failed. TestSetCurrencyPairFormat. Error %s", err) - } - + b.Name = "ANX" exch, err := cfg.GetExchangeConfig(b.Name) if err != nil { t.Fatalf("Test failed. TestSetCurrencyPairFormat load config failed. Error %s", err) } + b.Config = exch + b.SetCurrencyPairFormat() + if exch.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter != "-" && !exch.CurrencyPairs.ConfigFormat.Uppercase { + t.Fatal("Test failed. TestSetCurrencyPairFormat exch values are not nil") + } - exch.ConfigCurrencyPairFormat = nil - exch.RequestCurrencyPairFormat = nil - err = cfg.UpdateExchangeConfig(&exch) + exch.CurrencyPairs.ConfigFormat = nil + exch.CurrencyPairs.RequestFormat = nil + err = cfg.UpdateExchangeConfig(exch) if err != nil { t.Fatalf("Test failed. TestSetCurrencyPairFormat update config failed. Error %s", err) } @@ -343,39 +253,18 @@ func TestSetCurrencyPairFormat(t *testing.T) { t.Fatalf("Test failed. TestSetCurrencyPairFormat load config failed. Error %s", err) } - if exch.ConfigCurrencyPairFormat != nil && exch.RequestCurrencyPairFormat != nil { + if exch.CurrencyPairs.ConfigFormat != nil && exch.CurrencyPairs.RequestFormat != 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 != currency.BTC.String() && - b.ConfigCurrencyPairFormat.Uppercase { - t.Fatal("Test failed. TestSetCurrencyPairFormat ConfigCurrencyPairFormat values are incorrect") - } - - if b.RequestCurrencyPairFormat.Delimiter != "" && - b.RequestCurrencyPairFormat.Index != currency.BTC.String() && - b.RequestCurrencyPairFormat.Uppercase { - t.Fatal("Test failed. TestSetCurrencyPairFormat RequestCurrencyPairFormat values are incorrect") - } - - // if currency pairs are the same as the config, should load from config - err = b.SetCurrencyPairFormat() - if err != nil { - t.Fatalf("Test failed. TestSetCurrencyPairFormat. Error %s", err) + b.SetCurrencyPairFormat() + if exch.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter != "-" && !exch.CurrencyPairs.ConfigFormat.Uppercase { + t.Fatal("Test failed. TestSetCurrencyPairFormat exch values are not nil") } } func TestGetAuthenticatedAPISupport(t *testing.T) { - base := Base{ - AuthenticatedAPISupport: false, - } - + var base Base if base.GetAuthenticatedAPISupport() { t.Fatal("Test failed. TestGetAuthenticatedAPISupport returned true when it should of been false.") } @@ -388,207 +277,208 @@ func TestGetName(t *testing.T) { name := GetName.GetName() if name != "TESTNAME" { - t.Error("Test Failed - Exchange getName() returned incorrect name") + t.Error("Test Failed - Exchange GetName() returned incorrect name") } } -func TestGetEnabledCurrencies(t *testing.T) { +func TestGetEnabledPairs(t *testing.T) { b := Base{ Name: "TESTNAME", } - b.EnabledPairs = currency.NewPairsFromStrings([]string{"BTC-USD"}) - format := config.CurrencyPairFormatConfig{ + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTC-USD"}), true) + format := currency.PairFormat{ Delimiter: "-", Index: "", Uppercase: true, } - b.RequestCurrencyPairFormat = format - b.ConfigCurrencyPairFormat = format - c := b.GetEnabledCurrencies() + assetType := assets.AssetTypeSpot + b.CurrencyPairs.UseGlobalFormat = true + b.CurrencyPairs.RequestFormat = &format + b.CurrencyPairs.ConfigFormat = &format + + c := b.GetEnabledPairs(assetType) if c[0].String() != defaultTestCurrencyPair { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } format.Delimiter = "~" - b.RequestCurrencyPairFormat = format - c = b.GetEnabledCurrencies() - if c[0].String() != defaultTestCurrencyPair { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + b.CurrencyPairs.RequestFormat = &format + c = b.GetEnabledPairs(assetType) + if c[0].String() != "BTC~USD" { + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } format.Delimiter = "" - b.ConfigCurrencyPairFormat = format - c = b.GetEnabledCurrencies() + b.CurrencyPairs.ConfigFormat = &format + c = b.GetEnabledPairs(assetType) if c[0].String() != "BTCUSD" { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.EnabledPairs = currency.NewPairsFromStrings([]string{"BTCDOGE"}) - format.Index = "BTC" - b.ConfigCurrencyPairFormat = format - c = b.GetEnabledCurrencies() - if c[0].Base.String() != "BTC" && c[0].Quote.String() != "DOGE" { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") - } - - b.EnabledPairs = currency.NewPairsFromStrings([]string{"BTC_USD"}) - b.RequestCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Delimiter = "_" - c = b.GetEnabledCurrencies() - if c[0].Base != currency.BTC && c[0].Quote != currency.USD { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") - } - - b.EnabledPairs = currency.NewPairsFromStrings([]string{"BTCDOGE"}) - b.RequestCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Index = currency.BTC.String() - c = b.GetEnabledCurrencies() - if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") - } - - b.EnabledPairs = currency.NewPairsFromStrings([]string{"BTCUSD"}) - b.ConfigCurrencyPairFormat.Index = "" - c = b.GetEnabledCurrencies() - if c[0].Base != currency.BTC && c[0].Quote != currency.USD { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") - } -} - -func TestGetAvailableCurrencies(t *testing.T) { - b := Base{ - Name: "TESTNAME", - } - - b.AvailablePairs = currency.NewPairsFromStrings([]string{"BTC-USD"}) - format := config.CurrencyPairFormatConfig{ - Delimiter: "-", - Index: "", - Uppercase: true, - } - - b.RequestCurrencyPairFormat = format - b.ConfigCurrencyPairFormat = format - c := b.GetAvailableCurrencies() - if c[0].String() != defaultTestCurrencyPair { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") - } - - format.Delimiter = "~" - b.RequestCurrencyPairFormat = format - c = b.GetAvailableCurrencies() - if c[0].String() != defaultTestCurrencyPair { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") - } - - format.Delimiter = "" - b.ConfigCurrencyPairFormat = format - c = b.GetAvailableCurrencies() - if c[0].String() != "BTCUSD" { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string", c[0]) - } - - b.AvailablePairs = currency.NewPairsFromStrings([]string{"BTCDOGE"}) + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTCDOGE"}), true) format.Index = currency.BTC.String() - b.ConfigCurrencyPairFormat = format - c = b.GetAvailableCurrencies() + b.CurrencyPairs.ConfigFormat = &format + c = b.GetEnabledPairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.AvailablePairs = currency.NewPairsFromStrings([]string{"BTC_USD"}) - b.RequestCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Delimiter = "_" - c = b.GetAvailableCurrencies() + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTC_USD"}), true) + b.CurrencyPairs.RequestFormat.Delimiter = "" + b.CurrencyPairs.ConfigFormat.Delimiter = "_" + c = b.GetEnabledPairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.USD { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.AvailablePairs = currency.NewPairsFromStrings([]string{"BTCDOGE"}) - b.RequestCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Index = currency.BTC.String() - c = b.GetAvailableCurrencies() + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTCDOGE"}), true) + b.CurrencyPairs.RequestFormat.Delimiter = "" + b.CurrencyPairs.ConfigFormat.Delimiter = "" + b.CurrencyPairs.ConfigFormat.Index = currency.BTC.String() + c = b.GetEnabledPairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.AvailablePairs = currency.NewPairsFromStrings([]string{"BTCUSD"}) - b.ConfigCurrencyPairFormat.Index = "" - c = b.GetAvailableCurrencies() + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTCUSD"}), true) + b.CurrencyPairs.ConfigFormat.Index = "" + c = b.GetEnabledPairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.USD { - t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } } -func TestSupportsCurrency(t *testing.T) { +func TestGetAvailablePairs(t *testing.T) { b := Base{ Name: "TESTNAME", } - b.AvailablePairs = currency.NewPairsFromStrings([]string{defaultTestCurrencyPair, "ETH-USD"}) - b.EnabledPairs = currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}) + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}), false) + format := currency.PairFormat{ + Delimiter: "-", + Index: "", + Uppercase: true, + } - format := config.CurrencyPairFormatConfig{ + assetType := assets.AssetTypeSpot + b.CurrencyPairs.UseGlobalFormat = true + b.CurrencyPairs.RequestFormat = &format + b.CurrencyPairs.ConfigFormat = &format + + c := b.GetAvailablePairs(assetType) + if c[0].String() != defaultTestCurrencyPair { + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + } + + format.Delimiter = "~" + b.CurrencyPairs.RequestFormat = &format + c = b.GetAvailablePairs(assetType) + if c[0].String() != "BTC~USD" { + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + } + + format.Delimiter = "" + b.CurrencyPairs.ConfigFormat = &format + c = b.GetAvailablePairs(assetType) + if c[0].String() != "BTCUSD" { + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + } + + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTCDOGE"}), false) + format.Index = currency.BTC.String() + b.CurrencyPairs.ConfigFormat = &format + c = b.GetAvailablePairs(assetType) + if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + } + + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTC_USD"}), false) + b.CurrencyPairs.RequestFormat.Delimiter = "" + b.CurrencyPairs.ConfigFormat.Delimiter = "_" + c = b.GetAvailablePairs(assetType) + if c[0].Base != currency.BTC && c[0].Quote != currency.USD { + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + } + + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTCDOGE"}), false) + b.CurrencyPairs.RequestFormat.Delimiter = "" + b.CurrencyPairs.ConfigFormat.Delimiter = "_" + b.CurrencyPairs.ConfigFormat.Index = currency.BTC.String() + c = b.GetAvailablePairs(assetType) + if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + } + + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTCUSD"}), false) + b.CurrencyPairs.ConfigFormat.Index = "" + c = b.GetAvailablePairs(assetType) + if c[0].Base != currency.BTC && c[0].Quote != currency.USD { + t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + } +} + +func TestSupportsPair(t *testing.T) { + b := Base{ + Name: "TESTNAME", + } + + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{ + defaultTestCurrencyPair, "ETH-USD"}), false) + b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}), true) + + format := ¤cy.PairFormat{ Delimiter: "-", Index: "", } - b.RequestCurrencyPairFormat = format - b.ConfigCurrencyPairFormat = format + b.CurrencyPairs.UseGlobalFormat = true + b.CurrencyPairs.RequestFormat = format + b.CurrencyPairs.ConfigFormat = format + assetType := assets.AssetTypeSpot - if !b.SupportsCurrency(currency.NewPairFromStrings("BTC", "USD"), true) { - t.Error("Test Failed - Exchange SupportsCurrency() incorrect value") + if !b.SupportsPair(currency.NewPair(currency.BTC, currency.USD), true, assetType) { + t.Error("Test Failed - Exchange SupportsPair() incorrect value") } - if !b.SupportsCurrency(currency.NewPairFromStrings("ETH", "USD"), false) { - t.Error("Test Failed - Exchange SupportsCurrency() incorrect value") + if !b.SupportsPair(currency.NewPair(currency.ETH, currency.USD), false, assetType) { + t.Error("Test Failed - Exchange SupportsPair() incorrect value") } - if b.SupportsCurrency(currency.NewPairFromStrings("ASD", "ASDF"), true) { - t.Error("Test Failed - Exchange SupportsCurrency() incorrect value") - } -} -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("Yobit") - - 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) - } - - expected = false - actual = GetExchangeFormatCurrencySeperator("blah") - - if expected != actual { - t.Errorf("Test failed - TestGetExchangeFormatCurrencySeperator expected %v != actual %v", - expected, actual) + if b.SupportsPair(currency.NewPairFromStrings("ASD", "ASDF"), true, assetType) { + t.Error("Test Failed - Exchange SupportsPair() incorrect value") } } -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) +func TestFormatExchangeCurrencies(t *testing.T) { + e := Base{ + CurrencyPairs: currency.PairsManager{ + UseGlobalFormat: true, + + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + Delimiter: "~", + Separator: "^", + }, + + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "_", + }, + }, } var pairs = []currency.Pair{ @@ -596,33 +486,29 @@ func TestGetAndFormatExchangeCurrencies(t *testing.T) { currency.NewPairDelimiter("LTC_BTC", "_"), } - actual, err := GetAndFormatExchangeCurrencies("Yobit", pairs) + actual, err := e.FormatExchangeCurrencies(pairs, assets.AssetTypeSpot) if err != nil { - t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies error %s", err) + t.Errorf("Test failed - Exchange TestFormatExchangeCurrencies error %s", err) } - expected := "btc_usd-ltc_btc" + expected := "btc~usd^ltc~btc" if actual != expected { - t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies %s != %s", + t.Errorf("Test failed - Exchange TestFormatExchangeCurrencies %s != %s", actual, expected) } - - _, err = GetAndFormatExchangeCurrencies("non-existent", pairs) - if err == nil { - t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies returned nil error on non-existent exchange") - } } 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) + var b Base + b.CurrencyPairs.UseGlobalFormat = true + b.CurrencyPairs.RequestFormat = ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", } p := currency.NewPair(currency.BTC, currency.USD) expected := defaultTestCurrencyPair - actual := FormatExchangeCurrency("CoinbasePro", p) + actual := b.FormatExchangeCurrency(p, assets.AssetTypeSpot) if actual.String() != expected { t.Errorf("Test failed - Exchange TestFormatExchangeCurrency %s != %s", @@ -630,22 +516,6 @@ func TestFormatExchangeCurrency(t *testing.T) { } } -func TestFormatCurrency(t *testing.T) { - cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) - if err != nil { - t.Fatalf("Failed to load config file. Error: %s", err) - } - - p := currency.NewPair(currency.BTC, currency.USD) - expected := defaultTestCurrencyPair - actual := FormatCurrency(p).String() - if actual != expected { - t.Errorf("Test failed - Exchange TestFormatCurrency %s != %s", - actual, expected) - } -} - func TestSetEnabled(t *testing.T) { SetEnabled := Base{ Name: "TESTNAME", @@ -671,141 +541,128 @@ func TestIsEnabled(t *testing.T) { func TestSetAPIKeys(t *testing.T) { SetAPIKeys := Base{ - Name: "TESTNAME", - Enabled: false, - AuthenticatedAPISupport: false, + Name: "TESTNAME", + Enabled: 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.SetAPIKeys("RocketMan", "Digereedoo", "007") + if SetAPIKeys.API.Credentials.Key != "RocketMan" && SetAPIKeys.API.Credentials.Secret != "Digereedoo" && SetAPIKeys.API.Credentials.ClientID != "007" { + t.Error("Test Failed - SetAPIKeys() unable to set API credentials") } - 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") - } - SetAPIKeys.SetAPIKeys("RocketMan", "Digereedoo", "007", true) + SetAPIKeys.API.CredentialsValidator.RequiresBase64DecodeSecret = true + SetAPIKeys.SetAPIKeys("RocketMan", "Digereedoo", "007") } -func TestSetCurrencies(t *testing.T) { +func TestSetPairs(t *testing.T) { + t.Skip() + // TO-DO cfg := config.GetConfig() err := cfg.LoadConfig(config.ConfigTestFile) if err != nil { - t.Fatal("Test failed. TestSetCurrencies failed to load config") - } - - UAC := Base{Name: "ASDF"} - UAC.AvailablePairs = currency.NewPairsFromStrings([]string{"ETHLTC", "LTCBTC"}) - UAC.EnabledPairs = currency.NewPairsFromStrings([]string{"ETHLTC"}) - newPair := currency.NewPairDelimiter("ETH_USDT", "_") - - err = UAC.SetCurrencies([]currency.Pair{newPair}, true) - if err == nil { - t.Fatal("Test failed. TestSetCurrencies returned nil error on non-existent exchange") + t.Fatal("Test failed. TestSetPairs failed to load config") } anxCfg, err := cfg.GetExchangeConfig(defaultTestExchange) if err != nil { - t.Fatal("Test failed. TestSetCurrencies failed to load config") + t.Fatal("Test failed. TestSetPairs failed to load config") } - UAC.Name = defaultTestExchange - UAC.ConfigCurrencyPairFormat.Delimiter = anxCfg.ConfigCurrencyPairFormat.Delimiter - UAC.SetCurrencies(currency.Pairs{newPair}, true) - if !UAC.GetEnabledCurrencies().Contains(newPair, true) { - t.Fatal("Test failed. TestSetCurrencies failed to set currencies") + newPair := currency.NewPairDelimiter("ETH_USDT", "_") + assetType := assets.AssetTypeSpot + + var UAC Base + UAC.Name = "ANX" + UAC.Config = anxCfg + err = UAC.SetupDefaults(anxCfg) + if err != nil { + t.Fatalf("Test failed. TestSetPairs unable to set defaults: %s", err) } - UAC.SetCurrencies(currency.Pairs{newPair}, false) - if !UAC.GetAvailableCurrencies().Contains(newPair, true) { - t.Fatal("Test failed. TestSetCurrencies failed to set currencies") + err = UAC.SetPairs([]currency.Pair{newPair}, assets.AssetTypeSpot, true) + if err != nil { + t.Fatalf("Test failed. TestSetPairs failed to set currencies: %s", err) } - err = UAC.SetCurrencies(nil, false) + if !UAC.GetEnabledPairs(assetType).Contains(newPair, true) { + t.Fatal("Test failed. TestSetPairs failed to set currencies") + } + + UAC.SetPairs([]currency.Pair{newPair}, assets.AssetTypeSpot, false) + if !UAC.GetAvailablePairs(assetType).Contains(newPair, true) { + t.Fatal("Test failed. TestSetPairs failed to set currencies") + } + + err = UAC.SetPairs(nil, assets.AssetTypeSpot, false) if err == nil { - t.Fatal("Test failed. TestSetCurrencies should return an error when attempting to set an empty pairs array") + t.Fatal("Test failed. TestSetPairs should return an error when attempting to set an empty pairs array") } } -func TestUpdateCurrencies(t *testing.T) { +func TestUpdatePairs(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig(config.ConfigTestFile) if err != nil { - t.Fatal("Test failed. TestUpdateEnabledCurrencies failed to load config") + t.Fatal("Test failed. TestUpdatePairs failed to load config") + } + + anxCfg, err := cfg.GetExchangeConfig("ANX") + if err != nil { + t.Fatal("Test failed. TestUpdatePairs failed to load config") } UAC := Base{Name: "ANX"} + UAC.Config = anxCfg exchangeProducts := currency.NewPairsFromStrings([]string{"ltc", "btc", "usd", "aud", ""}) - - // Test updating exchange products for an exchange which doesn't exist - UAC.Name = "Blah" - err = UAC.UpdateCurrencies(exchangeProducts, true, false) - if err == nil { - t.Errorf("Test Failed - Exchange TestUpdateCurrencies succeeded on an exchange which doesn't exist") - } - - // Test updating exchange products - UAC.Name = defaultTestExchange - err = UAC.UpdateCurrencies(exchangeProducts, true, false) + err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, true, false) if err != nil { - t.Errorf("Test Failed - Exchange TestUpdateCurrencies error: %s", err) + t.Errorf("Test Failed - TestUpdatePairs error: %s", err) } // Test updating the same new products, diff should be 0 - UAC.Name = defaultTestExchange - err = UAC.UpdateCurrencies(exchangeProducts, true, false) + err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, true, false) if err != nil { - t.Errorf("Test Failed - Exchange TestUpdateCurrencies error: %s", err) + t.Errorf("Test Failed - TestUpdatePairs error: %s", err) } // Test force updating to only one product exchangeProducts = currency.NewPairsFromStrings([]string{"btc"}) - err = UAC.UpdateCurrencies(exchangeProducts, true, true) + err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, true, true) if err != nil { - t.Errorf("Test Failed - Forced Exchange TestUpdateCurrencies error: %s", err) + t.Errorf("Test Failed - TestUpdatePairs error: %s", err) } + // Test updating exchange products exchangeProducts = currency.NewPairsFromStrings([]string{"ltc", "btc", "usd", "aud"}) - // Test updating exchange products for an exchange which doesn't exist - UAC.Name = "Blah" - err = UAC.UpdateCurrencies(exchangeProducts, false, false) - if err == nil { - t.Errorf("Test Failed - Exchange UpdateCurrencies() succeeded on an exchange which doesn't exist") - } - - // Test updating exchange products - UAC.Name = defaultTestExchange - err = UAC.UpdateCurrencies(exchangeProducts, false, false) + UAC.Name = "ANX" + err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, false, false) if err != nil { - t.Errorf("Test Failed - Exchange UpdateCurrencies() error: %s", err) + t.Errorf("Test Failed - Exchange UpdatePairs() error: %s", err) } // Test updating the same new products, diff should be 0 - UAC.Name = defaultTestExchange - err = UAC.UpdateCurrencies(exchangeProducts, false, false) + err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, false, false) if err != nil { - t.Errorf("Test Failed - Exchange UpdateCurrencies() error: %s", err) + t.Errorf("Test Failed - Exchange UpdatePairs() error: %s", err) } // Test force updating to only one product exchangeProducts = currency.NewPairsFromStrings([]string{"btc"}) - err = UAC.UpdateCurrencies(exchangeProducts, false, true) + err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, false, true) if err != nil { - t.Errorf("Test Failed - Forced Exchange UpdateCurrencies() error: %s", err) + t.Errorf("Test Failed - Forced Exchange UpdatePairs() error: %s", err) } // Test update currency pairs with btc excluded exchangeProducts = currency.NewPairsFromStrings([]string{"ltc", "eth"}) - err = UAC.UpdateCurrencies(exchangeProducts, false, false) + err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, false, false) if err != nil { - t.Errorf("Test Failed - Forced Exchange UpdateCurrencies() error: %s", err) + t.Errorf("Test Failed - Forced Exchange UpdatePairs() error: %s", err) } // Test that empty exchange products should return an error exchangeProducts = nil - err = UAC.UpdateCurrencies(exchangeProducts, false, false) + err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, false, false) if err == nil { t.Errorf("Test failed - empty available pairs should return an error") } @@ -818,21 +675,20 @@ func TestSetAPIURL(t *testing.T) { testURLSecondaryDefault := "https://api.defaultsomethingelse.com" tester := Base{Name: "test"} + tester.Config = new(config.ExchangeConfig) - test := config.ExchangeConfig{} - - err := tester.SetAPIURL(&test) + err := tester.SetAPIURL() if err == nil { t.Error("test failed - setting zero value config") } - test.APIURL = testURL - test.APIURLSecondary = testURLSecondary + tester.Config.API.Endpoints.URL = testURL + tester.Config.API.Endpoints.URLSecondary = testURLSecondary - tester.APIUrlDefault = testURLDefault - tester.APIUrlSecondaryDefault = testURLSecondaryDefault + tester.API.Endpoints.URLDefault = testURLDefault + tester.API.Endpoints.URLSecondaryDefault = testURLSecondaryDefault - err = tester.SetAPIURL(&test) + err = tester.SetAPIURL() if err != nil { t.Error("test failed", err) } @@ -859,14 +715,16 @@ func BenchmarkSetAPIURL(b *testing.B) { test := config.ExchangeConfig{} - test.APIURL = "https://api.something.com" - test.APIURLSecondary = "https://api.somethingelse.com" + test.API.Endpoints.URL = "https://api.something.com" + test.API.Endpoints.URLSecondary = "https://api.somethingelse.com" - tester.APIUrlDefault = "https://api.defaultsomething.com" - tester.APIUrlSecondaryDefault = "https://api.defaultsomethingelse.com" + tester.API.Endpoints.URLDefault = "https://api.defaultsomething.com" + tester.API.Endpoints.URLDefault = "https://api.defaultsomethingelse.com" + + tester.Config = &test for i := 0; i < b.N; i++ { - err := tester.SetAPIURL(&test) + err := tester.SetAPIURL() if err != nil { b.Errorf("Benchmark failed %v", err) } @@ -875,7 +733,7 @@ func BenchmarkSetAPIURL(b *testing.B) { func TestSupportsWithdrawPermissions(t *testing.T) { UAC := Base{Name: defaultTestExchange} - UAC.APIWithdrawPermissions = AutoWithdrawCrypto | AutoWithdrawCryptoWithAPIPermission + UAC.Features.Supports.WithdrawPermissions = AutoWithdrawCrypto | AutoWithdrawCryptoWithAPIPermission withdrawPermissions := UAC.SupportsWithdrawPermissions(AutoWithdrawCrypto) if !withdrawPermissions { @@ -904,14 +762,8 @@ func TestSupportsWithdrawPermissions(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) - if err != nil { - t.Fatal("Test failed. TestUpdateEnabledCurrencies failed to load config") - } - - UAC := Base{Name: defaultTestExchange} - UAC.APIWithdrawPermissions = AutoWithdrawCrypto | + UAC := Base{Name: "ANX"} + UAC.Features.Supports.WithdrawPermissions = AutoWithdrawCrypto | AutoWithdrawCryptoWithAPIPermission | AutoWithdrawCryptoWithSetup | WithdrawCryptoWith2FA | @@ -936,7 +788,7 @@ func TestFormatWithdrawPermissions(t *testing.T) { t.Errorf("Expected: %s, Received: %s", AutoWithdrawCryptoText+" & "+AutoWithdrawCryptoWithAPIPermissionText, withdrawPermissions) } - UAC.APIWithdrawPermissions = NoAPIWithdrawalMethods + UAC.Features.Supports.WithdrawPermissions = NoAPIWithdrawalMethods withdrawPermissions = UAC.FormatWithdrawPermissions() if withdrawPermissions != NoAPIWithdrawalMethodsText { @@ -944,6 +796,17 @@ func TestFormatWithdrawPermissions(t *testing.T) { } } +func TestOrderSides(t *testing.T) { + var os = BuyOrderSide + if os.ToString() != "BUY" { + t.Errorf("test failed - unexpected string %s", os.ToString()) + } + + if os.ToLower() != "buy" { + t.Errorf("test failed - unexpected string %s", os.ToString()) + } +} + func TestOrderTypes(t *testing.T) { var ot OrderType = "Mo'Money" @@ -951,10 +814,8 @@ func TestOrderTypes(t *testing.T) { t.Errorf("test failed - unexpected string %s", ot.ToString()) } - var os OrderSide = "BUY" - - if os.ToString() != "BUY" { - t.Errorf("test failed - unexpected string %s", os.ToString()) + if ot.ToLower() != "mo'money" { + t.Errorf("test failed - unexpected string %s", ot.ToString()) } } diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go new file mode 100644 index 00000000..1b4a5ba0 --- /dev/null +++ b/exchanges/exchange_types.go @@ -0,0 +1,643 @@ +package exchange + +import ( + "fmt" + "sort" + "strings" + "time" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/request" +) + +// FeeType custom type for calculating fees based on method +type FeeType uint8 + +// Const declarations for fee types +const ( + BankFee FeeType = iota + InternationalBankDepositFee + InternationalBankWithdrawalFee + CryptocurrencyTradeFee + CyptocurrencyDepositFee + CryptocurrencyWithdrawalFee + OfflineTradeFee +) + +// InternationalBankTransactionType custom type for calculating fees based on fiat transaction types +type InternationalBankTransactionType uint8 + +// Const declarations for international transaction types +const ( + WireTransfer InternationalBankTransactionType = iota + PerfectMoney + Neteller + AdvCash + Payeer + Skrill + Simplex + SEPA + Swift + RapidTransfer + MisterTangoSEPA + Qiwi + VisaMastercard + WebMoney + Capitalist + WesternUnion + MoneyGram + Contact +) + +// SubmitOrderResponse is what is returned after submitting an order to an exchange +type SubmitOrderResponse struct { + IsOrderPlaced bool + OrderID string +} + +// FeeBuilder is the type which holds all parameters required to calculate a fee +// for an exchange +type FeeBuilder struct { + FeeType FeeType + // Used for calculating crypto trading fees, deposits & withdrawals + Pair currency.Pair + IsMaker bool + // Fiat currency used for bank deposits & withdrawals + FiatCurrency currency.Code + BankTransactionType InternationalBankTransactionType + // Used to multiply for fee calculations + PurchasePrice float64 + Amount float64 +} + +// Definitions for each type of withdrawal method for a given exchange +const ( + // No withdraw + NoAPIWithdrawalMethods uint32 = 0 + NoAPIWithdrawalMethodsText string = "NONE, WEBSITE ONLY" + AutoWithdrawCrypto uint32 = (1 << 0) + AutoWithdrawCryptoWithAPIPermission uint32 = (1 << 1) + AutoWithdrawCryptoWithSetup uint32 = (1 << 2) + AutoWithdrawCryptoText string = "AUTO WITHDRAW CRYPTO" + AutoWithdrawCryptoWithAPIPermissionText string = "AUTO WITHDRAW CRYPTO WITH API PERMISSION" + AutoWithdrawCryptoWithSetupText string = "AUTO WITHDRAW CRYPTO WITH SETUP" + WithdrawCryptoWith2FA uint32 = (1 << 3) + WithdrawCryptoWithSMS uint32 = (1 << 4) + WithdrawCryptoWithEmail uint32 = (1 << 5) + WithdrawCryptoWithWebsiteApproval uint32 = (1 << 6) + WithdrawCryptoWithAPIPermission uint32 = (1 << 7) + WithdrawCryptoWith2FAText string = "WITHDRAW CRYPTO WITH 2FA" + WithdrawCryptoWithSMSText string = "WITHDRAW CRYPTO WITH SMS" + WithdrawCryptoWithEmailText string = "WITHDRAW CRYPTO WITH EMAIL" + WithdrawCryptoWithWebsiteApprovalText string = "WITHDRAW CRYPTO WITH WEBSITE APPROVAL" + WithdrawCryptoWithAPIPermissionText string = "WITHDRAW CRYPTO WITH API PERMISSION" + AutoWithdrawFiat uint32 = (1 << 8) + AutoWithdrawFiatWithAPIPermission uint32 = (1 << 9) + AutoWithdrawFiatWithSetup uint32 = (1 << 10) + AutoWithdrawFiatText string = "AUTO WITHDRAW FIAT" + AutoWithdrawFiatWithAPIPermissionText string = "AUTO WITHDRAW FIAT WITH API PERMISSION" + AutoWithdrawFiatWithSetupText string = "AUTO WITHDRAW FIAT WITH SETUP" + WithdrawFiatWith2FA uint32 = (1 << 11) + WithdrawFiatWithSMS uint32 = (1 << 12) + WithdrawFiatWithEmail uint32 = (1 << 13) + WithdrawFiatWithWebsiteApproval uint32 = (1 << 14) + WithdrawFiatWithAPIPermission uint32 = (1 << 15) + WithdrawFiatWith2FAText string = "WITHDRAW FIAT WITH 2FA" + WithdrawFiatWithSMSText string = "WITHDRAW FIAT WITH SMS" + WithdrawFiatWithEmailText string = "WITHDRAW FIAT WITH EMAIL" + WithdrawFiatWithWebsiteApprovalText string = "WITHDRAW FIAT WITH WEBSITE APPROVAL" + WithdrawFiatWithAPIPermissionText string = "WITHDRAW FIAT WITH API PERMISSION" + WithdrawCryptoViaWebsiteOnly uint32 = (1 << 16) + WithdrawFiatViaWebsiteOnly uint32 = (1 << 17) + WithdrawCryptoViaWebsiteOnlyText string = "WITHDRAW CRYPTO VIA WEBSITE ONLY" + WithdrawFiatViaWebsiteOnlyText string = "WITHDRAW FIAT VIA WEBSITE ONLY" + NoFiatWithdrawals uint32 = (1 << 18) + NoFiatWithdrawalsText string = "NO FIAT WITHDRAWAL" + + UnknownWithdrawalTypeText string = "UNKNOWN" +) + +// ModifyOrder is a an order modifyer +// ModifyOrder is a an order modifyer +type ModifyOrder struct { + OrderID string + OrderType + OrderSide + Price float64 + Amount float64 + LimitPriceUpper float64 + LimitPriceLower float64 + CurrencyPair currency.Pair + + ImmediateOrCancel bool + HiddenOrder bool + FillOrKill bool + PostOnly bool +} + +// ModifyOrderResponse is an order modifying return type +type ModifyOrderResponse struct { + OrderID string +} + +// CancelAllOrdersResponse returns the status from attempting to cancel all orders on an exchagne +type CancelAllOrdersResponse struct { + OrderStatus map[string]string +} + +// OrderType enforces a standard for Ordertypes across the code base +type OrderType string + +// OrderType ...types +const ( + AnyOrderType OrderType = "ANY" + LimitOrderType OrderType = "LIMIT" + MarketOrderType OrderType = "MARKET" + ImmediateOrCancelOrderType OrderType = "IMMEDIATE_OR_CANCEL" + StopOrderType OrderType = "STOP" + TrailingStopOrderType OrderType = "TRAILINGSTOP" + UnknownOrderType OrderType = "UNKNOWN" +) + +// ToLower changes the ordertype to lower case +func (o OrderType) ToLower() OrderType { + return OrderType(common.StringToLower(string(o))) +} + +// ToString changes the ordertype to the exchange standard and returns a string +func (o OrderType) ToString() string { + return fmt.Sprintf("%v", o) +} + +// OrderSide enforces a standard for OrderSides across the code base +type OrderSide string + +// OrderSide types +const ( + AnyOrderSide OrderSide = "ANY" + BuyOrderSide OrderSide = "BUY" + SellOrderSide OrderSide = "SELL" + BidOrderSide OrderSide = "BID" + AskOrderSide OrderSide = "ASK" +) + +// ToLower changes the ordertype to lower case +func (o OrderSide) ToLower() OrderSide { + return OrderSide(common.StringToLower(string(o))) +} + +// ToString changes the ordertype to the exchange standard and returns a string +func (o OrderSide) ToString() string { + return fmt.Sprintf("%v", o) +} + +// AccountInfo is a Generic type to hold each exchange's holdings in +// all enabled currencies +type AccountInfo struct { + Exchange string + Accounts []Account +} + +// Account defines a singular account type with asocciated currencies +type Account struct { + ID string + Currencies []AccountCurrencyInfo +} + +// AccountCurrencyInfo is a sub type to store currency name and value +type AccountCurrencyInfo struct { + CurrencyName currency.Code + TotalValue float64 + Hold float64 +} + +// TradeHistory holds exchange history data +type TradeHistory struct { + Timestamp time.Time + TID int64 + Price float64 + Amount float64 + Exchange string + Type string + Fee float64 + Description string +} + +// OrderDetail holds order detail data +type OrderDetail struct { + Exchange string + AccountID string + ID string + CurrencyPair currency.Pair + OrderSide OrderSide + OrderType OrderType + OrderDate time.Time + Status string + Price float64 + Amount float64 + ExecutedAmount float64 + RemainingAmount float64 + Fee float64 + Trades []TradeHistory +} + +// OrderCancellation type required when requesting to cancel an order +type OrderCancellation struct { + AccountID string + OrderID string + CurrencyPair currency.Pair + AssetType assets.AssetType + WalletAddress string + Side OrderSide +} + +// FundHistory holds exchange funding history data +type FundHistory struct { + ExchangeName string + Status string + TransferID string + Description string + Timestamp time.Time + Currency string + Amount float64 + Fee float64 + TransferType string + CryptoToAddress string + CryptoFromAddress string + CryptoTxID string + BankTo string + BankFrom string +} + +// GenericWithdrawRequestInfo stores genric withdraw request info +type GenericWithdrawRequestInfo struct { + // General withdraw information + Currency currency.Code + Description string + OneTimePassword int64 + AccountID string + PIN int64 + TradePassword string + Amount float64 +} + +// CryptoWithdrawRequest stores the info required for a crypto withdrawal request +type CryptoWithdrawRequest struct { + GenericWithdrawRequestInfo + // Crypto related information + Address string + AddressTag string + FeeAmount float64 +} + +// FiatWithdrawRequest used for fiat withdrawal requests +type FiatWithdrawRequest struct { + GenericWithdrawRequestInfo + // FIAT related information + BankAccountName string + BankAccountNumber float64 + BankName string + BankAddress string + BankCity string + BankCountry string + BankPostalCode string + SwiftCode string + IBAN string + BankCode float64 + IsExpressWire bool + // Intermediary bank information + RequiresIntermediaryBank bool + IntermediaryBankAccountNumber float64 + IntermediaryBankName string + IntermediaryBankAddress string + IntermediaryBankCity string + IntermediaryBankCountry string + IntermediaryBankPostalCode string + IntermediarySwiftCode string + IntermediaryBankCode float64 + IntermediaryIBAN string + WireCurrency string +} + +// Features stores the supported and enabled features +// for the exchange +type Features struct { + Supports FeaturesSupported + Enabled FeaturesEnabled +} + +// FeaturesEnabled stores the exchange enabled features +type FeaturesEnabled struct { + AutoPairUpdates bool +} + +// ProtocolFeatures holds all variables for the exchanges supported features +// for a protocol (e.g REST or Websocket) +type ProtocolFeatures struct { + TickerBatching bool + TickerFetching bool + OrderbookFetching bool + AutoPairUpdates bool + AccountInfo bool + CryptoDeposit bool + CryptoWithdrawal uint32 + FiatWithdraw bool + GetOrder bool + GetOrders bool + CancelOrders bool + CancelOrder bool + SubmitOrder bool + SubmitOrders bool + ModifyOrder bool + DepositHistory bool + WithdrawalHistory bool + TradeHistory bool + UserTradeHistory bool + TradeFee bool + FiatDepositFee bool + FiatWithdrawalFee bool + CryptoDepositFee bool + CryptoWithdrawalFee bool +} + +// FeaturesSupported stores the exchanges supported features +type FeaturesSupported struct { + REST bool + RESTCapabilities ProtocolFeatures + Websocket bool + WebsocketCapabilities ProtocolFeatures + WithdrawPermissions uint32 +} + +// API stores the exchange API settings +type API struct { + AuthenticatedSupport bool + PEMKeySupport bool + + Endpoints struct { + URL string + URLDefault string + URLSecondary string + URLSecondaryDefault string + WebsocketURL string + } + + Credentials struct { + Key string + Secret string + ClientID string + PEMKey string + } + + CredentialsValidator struct { + // For Huobi (optional) + RequiresPEM bool + + RequiresKey bool + RequiresSecret bool + RequiresClientID bool + RequiresBase64DecodeSecret bool + } +} + +// GetOrdersRequest used for GetOrderHistory and GetOpenOrders wrapper functions +type GetOrdersRequest struct { + OrderType OrderType + OrderSide OrderSide + StartTicks time.Time + EndTicks time.Time + // Currencies Empty array = all currencies. Some endpoints only support singular currency enquiries + Currencies []currency.Pair +} + +// OrderStatus defines order status types +type OrderStatus string + +// All OrderStatus types +const ( + AnyOrderStatus OrderStatus = "ANY" + NewOrderStatus OrderStatus = "NEW" + ActiveOrderStatus OrderStatus = "ACTIVE" + PartiallyFilledOrderStatus OrderStatus = "PARTIALLY_FILLED" + FilledOrderStatus OrderStatus = "FILLED" + CancelledOrderStatus OrderStatus = "CANCELED" + PendingCancelOrderStatus OrderStatus = "PENDING_CANCEL" + RejectedOrderStatus OrderStatus = "REJECTED" + ExpiredOrderStatus OrderStatus = "EXPIRED" + HiddenOrderStatus OrderStatus = "HIDDEN" + UnknownOrderStatus OrderStatus = "UNKNOWN" +) + +// FilterOrdersBySide removes any OrderDetails that don't match the orderStatus provided +func FilterOrdersBySide(orders *[]OrderDetail, orderSide OrderSide) { + if orderSide == "" || orderSide == AnyOrderSide { + return + } + + var filteredOrders []OrderDetail + for i := range *orders { + if strings.EqualFold(string((*orders)[i].OrderSide), string(orderSide)) { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByType removes any OrderDetails that don't match the orderType provided +func FilterOrdersByType(orders *[]OrderDetail, orderType OrderType) { + if orderType == "" || orderType == AnyOrderType { + return + } + + var filteredOrders []OrderDetail + for i := range *orders { + if strings.EqualFold(string((*orders)[i].OrderType), string(orderType)) { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByTickRange removes any OrderDetails outside of the tick range +func FilterOrdersByTickRange(orders *[]OrderDetail, startTicks, endTicks time.Time) { + if startTicks.IsZero() || endTicks.IsZero() || + startTicks.Unix() == 0 || endTicks.Unix() == 0 || endTicks.Before(startTicks) { + return + } + + var filteredOrders []OrderDetail + for i := range *orders { + if (*orders)[i].OrderDate.Unix() >= startTicks.Unix() && (*orders)[i].OrderDate.Unix() <= endTicks.Unix() { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByCurrencies removes any OrderDetails that do not match the provided currency list +// It is forgiving in that the provided currencies can match quote or base currencies +func FilterOrdersByCurrencies(orders *[]OrderDetail, currencies []currency.Pair) { + if len(currencies) == 0 { + return + } + + var filteredOrders []OrderDetail + for i := range *orders { + matchFound := false + for _, c := range currencies { + if !matchFound && (*orders)[i].CurrencyPair.EqualIncludeReciprocal(c) { + matchFound = true + } + } + + if matchFound { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// ByPrice used for sorting orders by price +type ByPrice []OrderDetail + +func (b ByPrice) Len() int { + return len(b) +} + +func (b ByPrice) Less(i, j int) bool { + return b[i].Price < b[j].Price +} + +func (b ByPrice) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByPrice the caller function to sort orders +func SortOrdersByPrice(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByPrice(*orders))) + } else { + sort.Sort(ByPrice(*orders)) + } +} + +// ByOrderType used for sorting orders by order type +type ByOrderType []OrderDetail + +func (b ByOrderType) Len() int { + return len(b) +} + +func (b ByOrderType) Less(i, j int) bool { + return b[i].OrderType.ToString() < b[j].OrderType.ToString() +} + +func (b ByOrderType) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByType the caller function to sort orders +func SortOrdersByType(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByOrderType(*orders))) + } else { + sort.Sort(ByOrderType(*orders)) + } +} + +// ByCurrency used for sorting orders by order currency +type ByCurrency []OrderDetail + +func (b ByCurrency) Len() int { + return len(b) +} + +func (b ByCurrency) Less(i, j int) bool { + return b[i].CurrencyPair.String() < b[j].CurrencyPair.String() +} + +func (b ByCurrency) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByCurrency the caller function to sort orders +func SortOrdersByCurrency(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByCurrency(*orders))) + } else { + sort.Sort(ByCurrency(*orders)) + } +} + +// ByDate used for sorting orders by order date +type ByDate []OrderDetail + +func (b ByDate) Len() int { + return len(b) +} + +func (b ByDate) Less(i, j int) bool { + return b[i].OrderDate.Unix() < b[j].OrderDate.Unix() +} + +func (b ByDate) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByDate the caller function to sort orders +func SortOrdersByDate(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByDate(*orders))) + } else { + sort.Sort(ByDate(*orders)) + } +} + +// ByOrderSide used for sorting orders by order side (buy sell) +type ByOrderSide []OrderDetail + +func (b ByOrderSide) Len() int { + return len(b) +} + +func (b ByOrderSide) Less(i, j int) bool { + return b[i].OrderSide.ToString() < b[j].OrderSide.ToString() +} + +func (b ByOrderSide) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersBySide the caller function to sort orders +func SortOrdersBySide(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByOrderSide(*orders))) + } else { + sort.Sort(ByOrderSide(*orders)) + } +} + +// Base stores the individual exchange information +type Base struct { + Name string + Enabled bool + Verbose bool + LoadedByConfig bool + API API + BaseCurrencies currency.Currencies + CurrencyPairs currency.PairsManager + Features Features + HTTPTimeout time.Duration + HTTPUserAgent string + HTTPDebugging bool + Websocket *Websocket + *request.Requester + Config *config.ExchangeConfig +} diff --git a/exchanges/exmo/README.md b/exchanges/exmo/README.md index 064c1e71..94bb2a6d 100644 --- a/exchanges/exmo/README.md +++ b/exchanges/exmo/README.md @@ -47,22 +47,22 @@ main.go ```go var e exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Exmo" { - e = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Exmo" { + e = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := e.GetTickerPrice() +tick, err := e.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := e.GetOrderbookEx() +ob, err := e.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/exmo/exmo.go b/exchanges/exmo/exmo.go index 06b6db70..5736ffcb 100644 --- a/exchanges/exmo/exmo.go +++ b/exchanges/exmo/exmo.go @@ -7,14 +7,11 @@ import ( "net/url" "strconv" "strings" - "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -52,76 +49,12 @@ type EXMO struct { exchange.Base } -// SetDefaults sets the basic defaults for exmo -func (e *EXMO) SetDefaults() { - e.Name = "EXMO" - e.Enabled = false - e.Verbose = false - e.RESTPollingDelay = 10 - e.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithSetup | exchange.NoFiatWithdrawals - e.RequestCurrencyPairFormat.Delimiter = "_" - e.RequestCurrencyPairFormat.Uppercase = true - e.RequestCurrencyPairFormat.Separator = "," - e.ConfigCurrencyPairFormat.Delimiter = "_" - e.ConfigCurrencyPairFormat.Uppercase = true - e.AssetTypes = []string{ticker.Spot} - e.SupportsAutoPairUpdating = true - e.SupportsRESTTickerBatching = true - e.Requester = request.New(e.Name, - request.NewRateLimit(time.Minute, exmoAuthRate), - request.NewRateLimit(time.Minute, exmoUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - e.APIUrlDefault = exmoAPIURL - e.APIUrl = e.APIUrlDefault - e.WebsocketInit() -} - -// Setup takes in the supplied exchange configuration details and sets params -func (e *EXMO) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - e.SetEnabled(false) - } else { - e.Enabled = true - e.HTTPDebugging = exch.HTTPDebugging - e.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - e.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - e.SetHTTPClientTimeout(exch.HTTPTimeout) - e.SetHTTPClientUserAgent(exch.HTTPUserAgent) - e.RESTPollingDelay = exch.RESTPollingDelay - e.Verbose = exch.Verbose - e.BaseCurrencies = exch.BaseCurrencies - e.AvailablePairs = exch.AvailablePairs - e.EnabledPairs = exch.EnabledPairs - err := e.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = e.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = e.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = e.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = e.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - } -} - // GetTrades returns the trades for a symbol or symbols func (e *EXMO) GetTrades(symbol string) (map[string][]Trades, error) { v := url.Values{} v.Set("pair", symbol) result := make(map[string][]Trades) - urlPath := fmt.Sprintf("%s/v%s/%s", e.APIUrl, exmoAPIVersion, exmoTrades) - + urlPath := fmt.Sprintf("%s/v%s/%s", e.API.Endpoints.URL, exmoAPIVersion, exmoTrades) return result, e.SendHTTPRequest(common.EncodeURLValues(urlPath, v), &result) } @@ -130,8 +63,7 @@ func (e *EXMO) GetOrderbook(symbol string) (map[string]Orderbook, error) { v := url.Values{} v.Set("pair", symbol) result := make(map[string]Orderbook) - urlPath := fmt.Sprintf("%s/v%s/%s", e.APIUrl, exmoAPIVersion, exmoOrderbook) - + urlPath := fmt.Sprintf("%s/v%s/%s", e.API.Endpoints.URL, exmoAPIVersion, exmoOrderbook) return result, e.SendHTTPRequest(common.EncodeURLValues(urlPath, v), &result) } @@ -140,23 +72,21 @@ func (e *EXMO) GetTicker(symbol string) (map[string]Ticker, error) { v := url.Values{} v.Set("pair", symbol) result := make(map[string]Ticker) - urlPath := fmt.Sprintf("%s/v%s/%s", e.APIUrl, exmoAPIVersion, exmoTicker) - + urlPath := fmt.Sprintf("%s/v%s/%s", e.API.Endpoints.URL, exmoAPIVersion, exmoTicker) return result, e.SendHTTPRequest(common.EncodeURLValues(urlPath, v), &result) } // GetPairSettings returns the pair settings for a symbol or symbols func (e *EXMO) GetPairSettings() (map[string]PairSettings, error) { result := make(map[string]PairSettings) - urlPath := fmt.Sprintf("%s/v%s/%s", e.APIUrl, exmoAPIVersion, exmoPairSettings) - + urlPath := fmt.Sprintf("%s/v%s/%s", e.API.Endpoints.URL, exmoAPIVersion, exmoPairSettings) return result, e.SendHTTPRequest(urlPath, &result) } // GetCurrency returns a list of currencies func (e *EXMO) GetCurrency() ([]string, error) { var result []string - urlPath := fmt.Sprintf("%s/v%s/%s", e.APIUrl, exmoAPIVersion, exmoCurrency) + urlPath := fmt.Sprintf("%s/v%s/%s", e.API.Endpoints.URL, exmoAPIVersion, exmoCurrency) return result, e.SendHTTPRequest(urlPath, &result) } @@ -377,7 +307,7 @@ func (e *EXMO) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated HTTP request func (e *EXMO) SendAuthenticatedHTTPRequest(method, endpoint string, vals url.Values, result interface{}) error { - if !e.AuthenticatedAPISupport { + if !e.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, e.Name) } @@ -386,9 +316,9 @@ func (e *EXMO) SendAuthenticatedHTTPRequest(method, endpoint string, vals url.Va vals.Set("nonce", n) payload := vals.Encode() - hash := common.GetHMAC(common.HashSHA512, + hash := crypto.GetHMAC(crypto.HashSHA512, []byte(payload), - []byte(e.APISecret)) + []byte(e.API.Credentials.Secret)) if e.Verbose { log.Debugf("Sending %s request to %s with params %s\n", @@ -398,11 +328,11 @@ func (e *EXMO) SendAuthenticatedHTTPRequest(method, endpoint string, vals url.Va } headers := make(map[string]string) - headers["Key"] = e.APIKey - headers["Sign"] = common.HexEncodeToString(hash) + headers["Key"] = e.API.Credentials.Key + headers["Sign"] = crypto.HexEncodeToString(hash) headers["Content-Type"] = "application/x-www-form-urlencoded" - path := fmt.Sprintf("%s/v%s/%s", e.APIUrl, exmoAPIVersion, endpoint) + path := fmt.Sprintf("%s/v%s/%s", e.API.Endpoints.URL, exmoAPIVersion, endpoint) return e.SendPayload(method, path, diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index 2c624f14..dcc79153 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -30,15 +30,12 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - OKCoin Setup() init error") } - exmoConf.AuthenticatedAPISupport = true - exmoConf.APIKey = APIKey - exmoConf.APISecret = APISecret - e.Setup(&exmoConf) + e.Setup(exmoConf) - e.AuthenticatedAPISupport = true - e.APIKey = APIKey - e.APISecret = APISecret + e.API.AuthenticatedSupport = true + e.API.Credentials.Key = APIKey + e.API.Credentials.Secret = APISecret } func TestGetTrades(t *testing.T) { @@ -299,11 +296,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if e.APIKey != "" && e.APIKey != "Key" && - e.APISecret != "" && e.APISecret != "Secret" { - return true - } - return false + return e.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -318,7 +311,8 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.USD, } - response, err := e.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "1234234") + response, err := e.SubmitOrder(p, exchange.BuyOrderSide, + exchange.LimitOrderType, 1, 10, "1234234") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -393,11 +387,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { e.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -421,8 +417,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := e.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -437,8 +432,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := e.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index 0f1d8cc0..822821fb 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -9,13 +9,98 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (e *EXMO) GetDefaultConfig() (*config.ExchangeConfig, error) { + e.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = e.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = e.BaseCurrencies + + err := e.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if e.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = e.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets the basic defaults for exmo +func (e *EXMO) SetDefaults() { + e.Name = "EXMO" + e.Enabled = true + e.Verbose = true + e.API.CredentialsValidator.RequiresKey = true + e.API.CredentialsValidator.RequiresSecret = true + + e.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: true, + Separator: ",", + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: true, + }, + } + + e.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: false, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + e.Requester = request.New(e.Name, + request.NewRateLimit(time.Minute, exmoAuthRate), + request.NewRateLimit(time.Minute, exmoUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + e.API.Endpoints.URLDefault = exmoAPIURL + e.API.Endpoints.URL = e.API.Endpoints.URLDefault +} + +// Setup takes in the supplied exchange configuration details and sets params +func (e *EXMO) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + e.SetEnabled(false) + return nil + } + + return e.SetupDefaults(exch) +} + // Start starts the EXMO go routine func (e *EXMO) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -28,36 +113,49 @@ func (e *EXMO) Start(wg *sync.WaitGroup) { // Run implements the EXMO wrapper func (e *EXMO) Run() { if e.Verbose { - log.Debugf("%s polling delay: %ds.\n", e.GetName(), e.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", e.GetName(), len(e.EnabledPairs), e.EnabledPairs) + e.PrintEnabledPairs() } - exchangeProducts, err := e.GetPairSettings() + if !e.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := e.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s Failed to get available products.\n", e.GetName()) - } else { - var currencies []string - for x := range exchangeProducts { - currencies = append(currencies, x) - } - - var newCurrencies currency.Pairs - for _, p := range currencies { - newCurrencies = append(newCurrencies, - currency.NewPairFromString(p)) - } - - err = e.UpdateCurrencies(newCurrencies, false, false) - if err != nil { - log.Errorf("%s Failed to update available currencies.\n", e.GetName()) - } + log.Errorf("%s failed to update tradable pairs. Err: %s", e.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (e *EXMO) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + pairs, err := e.GetPairSettings() + if err != nil { + return nil, err + } + + var currencies []string + for x := range pairs { + currencies = append(currencies, x) + } + + return currencies, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (e *EXMO) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := e.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return e.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (e *EXMO) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (e *EXMO) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(e.Name, e.GetEnabledCurrencies()) + pairsCollated, err := e.FormatExchangeCurrencies(e.GetEnabledPairs(assetType), assetType) if err != nil { return tickerPrice, err } @@ -67,8 +165,8 @@ func (e *EXMO) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, er return tickerPrice, err } - for _, x := range e.GetEnabledCurrencies() { - currency := exchange.FormatExchangeCurrency(e.Name, x).String() + for _, x := range e.GetEnabledPairs(assetType) { + currency := e.FormatExchangeCurrency(x, assetType).String() var tickerPrice ticker.Price tickerPrice.Pair = x tickerPrice.Last = result[currency].Last @@ -87,8 +185,8 @@ func (e *EXMO) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, er return ticker.GetTicker(e.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (e *EXMO) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (e *EXMO) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tick, err := ticker.GetTicker(e.GetName(), p, assetType) if err != nil { return e.UpdateTicker(p, assetType) @@ -96,8 +194,8 @@ func (e *EXMO) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, return tick, nil } -// GetOrderbookEx returns the orderbook for a currency pair -func (e *EXMO) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns the orderbook for a currency pair +func (e *EXMO) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(e.GetName(), p, assetType) if err != nil { return e.UpdateOrderbook(p, assetType) @@ -106,9 +204,9 @@ func (e *EXMO) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(e.Name, e.GetEnabledCurrencies()) + pairsCollated, err := e.FormatExchangeCurrencies(e.GetEnabledPairs(assetType), assetType) if err != nil { return orderBook, err } @@ -118,8 +216,8 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Bas return orderBook, err } - for _, x := range e.GetEnabledCurrencies() { - currency := exchange.FormatExchangeCurrency(e.Name, x) + for _, x := range e.GetEnabledPairs(assetType) { + currency := e.FormatExchangeCurrency(x, assetType) data, ok := result[currency.String()] if !ok { continue @@ -195,10 +293,8 @@ func (e *EXMO) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (e *EXMO) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (e *EXMO) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -291,7 +387,7 @@ func (e *EXMO) GetDepositAddress(cryptocurrency currency.Code, _ string) (string // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (e *EXMO) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (e *EXMO) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { resp, err := e.WithdrawCryptocurrency(withdrawRequest.Currency.String(), withdrawRequest.Address, withdrawRequest.AddressTag, withdrawRequest.Amount) return fmt.Sprintf("%v", resp), err @@ -299,13 +395,13 @@ func (e *EXMO) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawReq // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (e *EXMO) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (e *EXMO) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (e *EXMO) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (e *EXMO) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -316,7 +412,7 @@ func (e *EXMO) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (e *EXMO) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (e.APIKey == "" || e.APISecret == "") && // Todo check connection status + if !e.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -348,7 +444,6 @@ func (e *EXMO) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]e exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } @@ -361,7 +456,7 @@ func (e *EXMO) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]e var allTrades []UserTrades for _, currency := range getOrdersRequest.Currencies { - resp, err := e.GetUserTrades(exchange.FormatExchangeCurrency(e.Name, currency).String(), "", "10000") + resp, err := e.GetUserTrades(e.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String(), "", "10000") if err != nil { return nil, err } @@ -389,7 +484,6 @@ func (e *EXMO) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]e exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } diff --git a/exchanges/gateio/README.md b/exchanges/gateio/README.md index f093feb3..bbd78991 100644 --- a/exchanges/gateio/README.md +++ b/exchanges/gateio/README.md @@ -47,22 +47,22 @@ main.go ```go var g exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "GateIO" { - g = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "GateIO" { + g = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := g.GetTickerPrice() +tick, err := g.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := g.GetOrderbookEx() +ob, err := g.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index 7936bb1c..4146c541 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -8,16 +8,12 @@ import ( "strconv" "strings" "sync" - "time" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -53,96 +49,10 @@ type Gateio struct { wsRequestMtx sync.Mutex } -// SetDefaults sets default values for the exchange -func (g *Gateio) SetDefaults() { - g.Name = "GateIO" - g.Enabled = false - g.Verbose = false - g.RESTPollingDelay = 10 - g.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.NoFiatWithdrawals - g.RequestCurrencyPairFormat.Delimiter = "_" - g.RequestCurrencyPairFormat.Uppercase = false - g.ConfigCurrencyPairFormat.Delimiter = "_" - g.ConfigCurrencyPairFormat.Uppercase = true - g.AssetTypes = []string{ticker.Spot} - g.SupportsAutoPairUpdating = true - g.SupportsRESTTickerBatching = true - g.Requester = request.New(g.Name, - request.NewRateLimit(time.Second*10, gateioAuthRate), - request.NewRateLimit(time.Second*10, gateioUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - g.APIUrlDefault = gateioTradeURL - g.APIUrl = g.APIUrlDefault - g.APIUrlSecondaryDefault = gateioMarketURL - g.APIUrlSecondary = g.APIUrlSecondaryDefault - g.WebsocketInit() - g.Websocket.Functionality = exchange.WebsocketTickerSupported | - exchange.WebsocketTradeDataSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketKlineSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup sets user configuration -func (g *Gateio) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - g.SetEnabled(false) - } else { - g.Enabled = true - g.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - g.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - g.APIAuthPEMKey = exch.APIAuthPEMKey - g.SetHTTPClientTimeout(exch.HTTPTimeout) - g.SetHTTPClientUserAgent(exch.HTTPUserAgent) - g.RESTPollingDelay = exch.RESTPollingDelay - g.Verbose = exch.Verbose - g.BaseCurrencies = exch.BaseCurrencies - g.AvailablePairs = exch.AvailablePairs - g.EnabledPairs = exch.EnabledPairs - g.WebsocketURL = gateioWebsocketEndpoint - g.HTTPDebugging = exch.HTTPDebugging - err := g.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = g.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = g.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = g.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = g.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = g.WebsocketSetup(g.WsConnect, - g.Subscribe, - g.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - gateioWebsocketEndpoint, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetSymbols returns all supported symbols func (g *Gateio) GetSymbols() ([]string, error) { var result []string - - urlPath := fmt.Sprintf("%s/%s/%s", g.APIUrlSecondary, gateioAPIVersion, gateioSymbol) - + urlPath := fmt.Sprintf("%s/%s/%s", g.API.Endpoints.URLSecondary, gateioAPIVersion, gateioSymbol) err := g.SendHTTPRequest(urlPath, &result) if err != nil { return nil, nil @@ -158,8 +68,7 @@ func (g *Gateio) GetMarketInfo() (MarketInfoResponse, error) { Pairs []interface{} `json:"pairs"` } - urlPath := fmt.Sprintf("%s/%s/%s", g.APIUrlSecondary, gateioAPIVersion, gateioMarketInfo) - + urlPath := fmt.Sprintf("%s/%s/%s", g.API.Endpoints.URLSecondary, gateioAPIVersion, gateioMarketInfo) var res response var result MarketInfoResponse err := g.SendHTTPRequest(urlPath, &res) @@ -199,15 +108,14 @@ func (g *Gateio) GetLatestSpotPrice(symbol string) (float64, error) { // GetTicker returns a ticker for the supplied symbol // updated every 10 seconds func (g *Gateio) GetTicker(symbol string) (TickerResponse, error) { - urlPath := fmt.Sprintf("%s/%s/%s/%s", g.APIUrlSecondary, gateioAPIVersion, gateioTicker, symbol) + urlPath := fmt.Sprintf("%s/%s/%s/%s", g.API.Endpoints.URLSecondary, gateioAPIVersion, gateioTicker, symbol) var res TickerResponse return res, g.SendHTTPRequest(urlPath, &res) } // GetTickers returns tickers for all symbols func (g *Gateio) GetTickers() (map[string]TickerResponse, error) { - urlPath := fmt.Sprintf("%s/%s/%s", g.APIUrlSecondary, gateioAPIVersion, gateioTickers) - + urlPath := fmt.Sprintf("%s/%s/%s", g.API.Endpoints.URLSecondary, gateioAPIVersion, gateioTickers) resp := make(map[string]TickerResponse) err := g.SendHTTPRequest(urlPath, &resp) if err != nil { @@ -218,8 +126,7 @@ func (g *Gateio) GetTickers() (map[string]TickerResponse, error) { // GetOrderbook returns the orderbook data for a suppled symbol func (g *Gateio) GetOrderbook(symbol string) (Orderbook, error) { - urlPath := fmt.Sprintf("%s/%s/%s/%s", g.APIUrlSecondary, gateioAPIVersion, gateioOrderbook, symbol) - + urlPath := fmt.Sprintf("%s/%s/%s/%s", g.API.Endpoints.URLSecondary, gateioAPIVersion, gateioOrderbook, symbol) var resp OrderbookResponse err := g.SendHTTPRequest(urlPath, &resp) if err != nil { @@ -281,7 +188,7 @@ func (g *Gateio) GetOrderbook(symbol string) (Orderbook, error) { // GetSpotKline returns kline data for the most recent time period func (g *Gateio) GetSpotKline(arg KlinesRequestParams) ([]*KLineResponse, error) { urlPath := fmt.Sprintf("%s/%s/%s/%s?group_sec=%d&range_hour=%d", - g.APIUrlSecondary, + g.API.Endpoints.URLSecondary, gateioAPIVersion, gateioKline, arg.Symbol, @@ -470,25 +377,26 @@ func (g *Gateio) GetTradeHistory(symbol string) (TradHistoryResponse, error) { // GenerateSignature returns hash for authenticated requests func (g *Gateio) GenerateSignature(message string) []byte { - return common.GetHMAC(common.HashSHA512, []byte(message), []byte(g.APISecret)) + return crypto.GetHMAC(crypto.HashSHA512, []byte(message), + []byte(g.API.Credentials.Secret)) } // SendAuthenticatedHTTPRequest sends authenticated requests to the Gateio API // To use this you must setup an APIKey and APISecret from the exchange func (g *Gateio) SendAuthenticatedHTTPRequest(method, endpoint, param string, result interface{}) error { - if !g.AuthenticatedAPISupport { + if !g.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name) } headers := make(map[string]string) headers["Content-Type"] = "application/x-www-form-urlencoded" - headers["key"] = g.APIKey + headers["key"] = g.API.Credentials.Key hmac := g.GenerateSignature(param) - headers["sign"] = common.HexEncodeToString(hmac) + headers["sign"] = crypto.HexEncodeToString(hmac) - urlPath := fmt.Sprintf("%s/%s/%s", g.APIUrl, gateioAPIVersion, endpoint) + urlPath := fmt.Sprintf("%s/%s/%s", g.API.Endpoints.URL, gateioAPIVersion, endpoint) var intermidiary json.RawMessage diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index efd770f6..4adc7866 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -30,11 +30,11 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - GateIO Setup() init error") } - gateioConfig.AuthenticatedAPISupport = true - gateioConfig.APIKey = apiKey - gateioConfig.APISecret = apiSecret + gateioConfig.API.AuthenticatedSupport = true + gateioConfig.API.Credentials.Key = apiKey + gateioConfig.API.Credentials.Secret = apiSecret - g.Setup(&gateioConfig) + g.Setup(gateioConfig) } func TestGetSymbols(t *testing.T) { @@ -64,7 +64,7 @@ func TestSpotNewOrder(t *testing.T) { Symbol: "btc_usdt", Amount: 1.1, Price: 10.1, - Type: SpotNewOrderRequestParamsTypeSell, + Type: exchange.SellOrderSide.ToLower().ToString(), }) if err != nil { t.Errorf("Test failed - Gateio SpotNewOrder: %s", err) @@ -301,11 +301,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if g.APIKey != "" && g.APIKey != "Key" && - g.APISecret != "" && g.APISecret != "Secret" { - return true - } - return false + return g.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -321,7 +317,8 @@ func TestSubmitOrder(t *testing.T) { Base: currency.LTC, Quote: currency.BTC, } - response, err := g.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "1234234") + response, err := g.SubmitOrder(p, exchange.BuyOrderSide, + exchange.LimitOrderType, 1, 10, "1234234") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -410,11 +407,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { g.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -438,8 +437,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := g.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -454,8 +452,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := g.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index b55dbadf..e4581515 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -7,17 +7,6 @@ import ( "github.com/thrasher-/gocryptotrader/currency" ) -// SpotNewOrderRequestParamsType order type (buy or sell) -type SpotNewOrderRequestParamsType string - -var ( - // SpotNewOrderRequestParamsTypeBuy buy order - SpotNewOrderRequestParamsTypeBuy = SpotNewOrderRequestParamsType("buy") - - // SpotNewOrderRequestParamsTypeSell sell order - SpotNewOrderRequestParamsTypeSell = SpotNewOrderRequestParamsType("sell") -) - // TimeInterval Interval represents interval enum. type TimeInterval int @@ -124,10 +113,10 @@ type Orderbook struct { // SpotNewOrderRequestParams Order params type SpotNewOrderRequestParams struct { - Amount float64 `json:"amount"` // Order quantity - Price float64 `json:"price"` // Order price - Symbol string `json:"symbol"` // Trading pair; btc_usdt, eth_btc...... - Type SpotNewOrderRequestParamsType `json:"type"` // Order type (buy or sell), + Amount float64 `json:"amount"` // Order quantity + Price float64 `json:"price"` // Order price + Symbol string `json:"symbol"` // Trading pair; btc_usdt, eth_btc...... + Type string `json:"type"` // Order type (buy or sell), } // SpotNewOrderResponse Order response diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index f973a4c2..1caa17ac 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -12,8 +12,10 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -47,7 +49,7 @@ func (g *Gateio) WsConnect() error { return err } - if g.AuthenticatedAPISupport { + if g.API.AuthenticatedSupport { err = g.wsServerSignIn() if err != nil { log.Errorf("%v - wsServerSignin() failed: %v", g.GetName(), err) @@ -64,11 +66,11 @@ func (g *Gateio) WsConnect() error { func (g *Gateio) wsServerSignIn() error { nonce := int(time.Now().Unix() * 1000) sigTemp := g.GenerateSignature(strconv.Itoa(nonce)) - signature := common.Base64Encode(sigTemp) + signature := crypto.Base64Encode(sigTemp) signinWsRequest := WebsocketRequest{ ID: IDSignIn, Method: "server.sign", - Params: []interface{}{g.APIKey, signature, nonce}, + Params: []interface{}{g.API.Credentials.Key, signature, nonce}, } return g.wsSend(signinWsRequest) } @@ -119,7 +121,7 @@ func (g *Gateio) WsHandleData() { if common.StringContains(result.Error.Message, "authentication") { g.Websocket.DataHandler <- fmt.Errorf("%v - WebSocket authentication failed ", g.GetName()) - g.AuthenticatedAPISupport = false + g.API.AuthenticatedSupport = false continue } g.Websocket.DataHandler <- fmt.Errorf("gateio_websocket.go error %s", @@ -186,7 +188,7 @@ func (g *Gateio) WsHandleData() { g.Websocket.DataHandler <- exchange.TickerData{ Timestamp: time.Now(), Pair: currency.NewPairFromString(c), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Exchange: g.GetName(), ClosePrice: ticker.Close, Quantity: ticker.BaseVolume, @@ -214,7 +216,7 @@ func (g *Gateio) WsHandleData() { g.Websocket.DataHandler <- exchange.TradeData{ Timestamp: time.Now(), CurrencyPair: currency.NewPairFromString(c), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Exchange: g.GetName(), Price: trade.Price, Amount: trade.Amount, @@ -282,7 +284,8 @@ func (g *Gateio) WsHandleData() { var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.AssetType = "SPOT" + newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.LastUpdated = time.Now() newOrderBook.Pair = currency.NewPairFromString(c) err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook, @@ -297,7 +300,7 @@ func (g *Gateio) WsHandleData() { currency.NewPairFromString(c), time.Now(), g.GetName(), - "SPOT") + assets.AssetTypeSpot) if err != nil { g.Websocket.DataHandler <- err } @@ -305,7 +308,7 @@ func (g *Gateio) WsHandleData() { g.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: currency.NewPairFromString(c), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Exchange: g.GetName(), } @@ -326,7 +329,7 @@ func (g *Gateio) WsHandleData() { g.Websocket.DataHandler <- exchange.KlineData{ Timestamp: time.Now(), Pair: currency.NewPairFromString(data[7].(string)), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Exchange: g.GetName(), OpenPrice: open, ClosePrice: closePrice, @@ -342,12 +345,12 @@ func (g *Gateio) WsHandleData() { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (g *Gateio) GenerateDefaultSubscriptions() { var channels = []string{"ticker.subscribe", "trades.subscribe", "depth.subscribe", "kline.subscribe"} - if g.AuthenticatedAPISupport { + if g.AllowAuthenticatedRequest() { channels = append(channels, "balance.subscribe", "order.subscribe") } subscriptions := []exchange.WebsocketChannelSubscription{} - enabledCurrencies := g.GetEnabledCurrencies() + enabledCurrencies := g.GetEnabledPairs(assets.AssetTypeSpot) for i := range channels { for j := range enabledCurrencies { params := make(map[string]interface{}) diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index b7e91baa..3613f798 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -9,13 +9,118 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (g *Gateio) GetDefaultConfig() (*config.ExchangeConfig, error) { + g.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = g.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = g.BaseCurrencies + + err := g.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if g.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = g.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets default values for the exchange +func (g *Gateio) SetDefaults() { + g.Name = "GateIO" + g.Enabled = true + g.Verbose = true + g.API.CredentialsValidator.RequiresKey = true + g.API.CredentialsValidator.RequiresSecret = true + + g.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Delimiter: "_", + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: true, + }, + } + + g.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + g.Requester = request.New(g.Name, + request.NewRateLimit(time.Second*10, gateioAuthRate), + request.NewRateLimit(time.Second*10, gateioUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + g.API.Endpoints.URLDefault = gateioTradeURL + g.API.Endpoints.URL = g.API.Endpoints.URLDefault + g.API.Endpoints.URLSecondaryDefault = gateioMarketURL + g.API.Endpoints.URLSecondary = g.API.Endpoints.URLSecondaryDefault + g.API.Endpoints.WebsocketURL = gateioWebsocketEndpoint + g.WebsocketInit() + g.Websocket.Functionality = exchange.WebsocketTickerSupported | + exchange.WebsocketTradeDataSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketKlineSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup sets user configuration +func (g *Gateio) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + g.SetEnabled(false) + return nil + } + + err := g.SetupDefaults(exch) + if err != nil { + return err + } + + return g.WebsocketSetup(g.WsConnect, + g.Subscribe, + g.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + gateioWebsocketEndpoint, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the GateIO go routine func (g *Gateio) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -28,38 +133,45 @@ func (g *Gateio) Start(wg *sync.WaitGroup) { // Run implements the GateIO wrapper func (g *Gateio) Run() { if g.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", g.GetName(), common.IsEnabled(g.Websocket.IsEnabled()), g.WebsocketURL) - log.Debugf("%s polling delay: %ds.\n", g.GetName(), g.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", g.GetName(), len(g.EnabledPairs), g.EnabledPairs) + g.PrintEnabledPairs() } - symbols, err := g.GetSymbols() - if err != nil { - log.Errorf("%s Unable to fetch symbols.\n", g.GetName()) - } else { - var newCurrencies currency.Pairs - for _, p := range symbols { - newCurrencies = append(newCurrencies, - currency.NewPairFromString(p)) - } + if !g.GetEnabledFeatures().AutoPairUpdates { + return + } - err = g.UpdateCurrencies(newCurrencies, false, false) - if err != nil { - log.Errorf("%s Failed to update available currencies.\n", g.GetName()) - } + err := g.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", g.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (g *Gateio) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + return g.GetSymbols() +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (g *Gateio) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := g.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return g.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (g *Gateio) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (g *Gateio) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price result, err := g.GetTickers() if err != nil { return tickerPrice, err } - for _, x := range g.GetEnabledCurrencies() { - currency := exchange.FormatExchangeCurrency(g.Name, x).String() + for _, x := range g.GetEnabledPairs(assetType) { + currency := g.FormatExchangeCurrency(x, assetType).String() var tp ticker.Price tp.Pair = x tp.High = result[currency].High @@ -77,8 +189,8 @@ func (g *Gateio) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, return ticker.GetTicker(g.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (g *Gateio) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (g *Gateio) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType) if err != nil { return g.UpdateTicker(p, assetType) @@ -86,19 +198,19 @@ func (g *Gateio) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (g *Gateio) GetOrderbookEx(currency currency.Pair, assetType string) (orderbook.Base, error) { - ob, err := orderbook.Get(g.GetName(), currency, assetType) +// FetchOrderbook returns orderbook base on the currency pair +func (g *Gateio) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { + ob, err := orderbook.Get(g.GetName(), p, assetType) if err != nil { - return g.UpdateOrderbook(currency, assetType) + return g.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - currency := exchange.FormatExchangeCurrency(g.Name, p).String() + currency := g.FormatExchangeCurrency(p, assetType).String() orderbookNew, err := g.GetOrderbook(currency) if err != nil { @@ -200,22 +312,20 @@ func (g *Gateio) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (g *Gateio) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (g *Gateio) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order // TODO: support multiple order types (IOC) func (g *Gateio) SubmitOrder(p currency.Pair, side exchange.OrderSide, _ exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - var orderTypeFormat SpotNewOrderRequestParamsType + var orderTypeFormat string if side == exchange.BuyOrderSide { - orderTypeFormat = SpotNewOrderRequestParamsTypeBuy + orderTypeFormat = exchange.BuyOrderSide.ToLower().ToString() } else { - orderTypeFormat = SpotNewOrderRequestParamsTypeSell + orderTypeFormat = exchange.SellOrderSide.ToLower().ToString() } var spotNewOrderRequestParams = SpotNewOrderRequestParams{ @@ -251,7 +361,8 @@ func (g *Gateio) CancelOrder(order *exchange.OrderCancellation) error { if err != nil { return err } - _, err = g.CancelExistingOrder(orderIDInt, exchange.FormatExchangeCurrency(g.Name, order.CurrencyPair).String()) + _, err = g.CancelExistingOrder(orderIDInt, g.FormatExchangeCurrency(order.CurrencyPair, + order.AssetType).String()) return err } @@ -301,7 +412,8 @@ func (g *Gateio) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { orderDetail.OrderDate = time.Unix(orders.Orders[x].Timestamp, 0) orderDetail.Status = orders.Orders[x].Status orderDetail.Price = orders.Orders[x].Rate - orderDetail.CurrencyPair = currency.NewPairDelimiter(orders.Orders[x].CurrencyPair, g.ConfigCurrencyPairFormat.Delimiter) + orderDetail.CurrencyPair = currency.NewPairDelimiter(orders.Orders[x].CurrencyPair, + g.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) if strings.EqualFold(orders.Orders[x].Type, exchange.AskOrderSide.ToString()) { orderDetail.OrderSide = exchange.AskOrderSide } else if strings.EqualFold(orders.Orders[x].Type, exchange.BidOrderSide.ToString()) { @@ -338,19 +450,19 @@ func (g *Gateio) GetDepositAddress(cryptocurrency currency.Code, _ string) (stri // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (g *Gateio) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (g *Gateio) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return g.WithdrawCrypto(withdrawRequest.Currency.String(), withdrawRequest.Address, withdrawRequest.Amount) } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (g *Gateio) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (g *Gateio) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (g *Gateio) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (g *Gateio) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -361,7 +473,7 @@ func (g *Gateio) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (g *Gateio) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (g.APIKey == "" || g.APISecret == "") && // Todo check connection status + if !g.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -387,7 +499,7 @@ func (g *Gateio) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ } symbol := currency.NewPairDelimiter(resp.Orders[i].CurrencyPair, - g.ConfigCurrencyPairFormat.Delimiter) + g.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(resp.Orders[i].Type)) orderDate := time.Unix(resp.Orders[i].Timestamp, 0) @@ -406,7 +518,6 @@ func (g *Gateio) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } @@ -425,7 +536,7 @@ func (g *Gateio) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for _, trade := range trades { symbol := currency.NewPairDelimiter(trade.Pair, - g.ConfigCurrencyPairFormat.Delimiter) + g.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(trade.Type)) orderDate := time.Unix(trade.TimeUnix, 0) orders = append(orders, exchange.OrderDetail{ @@ -442,7 +553,6 @@ func (g *Gateio) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } diff --git a/exchanges/gemini/README.md b/exchanges/gemini/README.md index 1435a893..e641636a 100644 --- a/exchanges/gemini/README.md +++ b/exchanges/gemini/README.md @@ -47,22 +47,22 @@ main.go ```go var g exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Gemini" { - g = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Gemini" { + g = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := g.GetTickerPrice() +tick, err := g.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := g.GetOrderbookEx() +ob, err := g.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index cc9a0811..2994d2d7 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -7,14 +7,11 @@ import ( "net/url" "strconv" "strings" - "time" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -84,14 +81,14 @@ func AddSession(g *Gemini, sessionID int, apiKey, apiSecret, role string, needsH return errors.New("sessionID already being used") } - g.APIKey = apiKey - g.APISecret = apiSecret + g.API.Credentials.Key = apiKey + g.API.Credentials.Secret = apiSecret g.Role = role g.RequiresHeartBeat = needsHeartbeat - g.APIUrl = geminiAPIURL + g.API.Endpoints.URL = geminiAPIURL if isSandbox { - g.APIUrl = geminiSandboxAPIURL + g.API.Endpoints.URL = geminiSandboxAPIURL } Session[sessionID] = g @@ -99,92 +96,10 @@ func AddSession(g *Gemini, sessionID int, apiKey, apiSecret, role string, needsH return nil } -// SetDefaults sets package defaults for gemini exchange -func (g *Gemini) SetDefaults() { - g.Name = "Gemini" - g.Enabled = false - g.Verbose = false - g.RESTPollingDelay = 10 - g.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithAPIPermission | - exchange.AutoWithdrawCryptoWithSetup | - exchange.WithdrawFiatViaWebsiteOnly - g.RequestCurrencyPairFormat.Delimiter = "" - g.RequestCurrencyPairFormat.Uppercase = true - g.ConfigCurrencyPairFormat.Delimiter = "" - g.ConfigCurrencyPairFormat.Uppercase = true - g.AssetTypes = []string{ticker.Spot} - g.SupportsAutoPairUpdating = true - g.SupportsRESTTickerBatching = false - g.Requester = request.New(g.Name, - request.NewRateLimit(time.Minute, geminiAuthRate), - request.NewRateLimit(time.Minute, geminiUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - g.APIUrlDefault = geminiAPIURL - g.APIUrl = g.APIUrlDefault - g.WebsocketInit() - g.Websocket.Functionality = exchange.WebsocketOrderbookSupported | - exchange.WebsocketTradeDataSupported -} - -// Setup sets exchange configuration parameters -func (g *Gemini) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - g.SetEnabled(false) - } else { - g.Enabled = true - g.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - g.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - g.SetHTTPClientTimeout(exch.HTTPTimeout) - g.SetHTTPClientUserAgent(exch.HTTPUserAgent) - g.RESTPollingDelay = exch.RESTPollingDelay - g.Verbose = exch.Verbose - g.HTTPDebugging = exch.HTTPDebugging - g.BaseCurrencies = exch.BaseCurrencies - g.AvailablePairs = exch.AvailablePairs - g.EnabledPairs = exch.EnabledPairs - - err := g.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = g.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = g.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = g.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - if exch.UseSandbox { - g.APIUrl = geminiSandboxAPIURL - } - err = g.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = g.WebsocketSetup(g.WsConnect, - nil, - nil, - exch.Name, - exch.Websocket, - exch.Verbose, - geminiWebsocketEndpoint, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetSymbols returns all available symbols for trading func (g *Gemini) GetSymbols() ([]string, error) { var symbols []string - path := fmt.Sprintf("%s/v%s/%s", g.APIUrl, geminiAPIVersion, geminiSymbols) - + path := fmt.Sprintf("%s/v%s/%s", g.API.Endpoints.URL, geminiAPIVersion, geminiSymbols) return symbols, g.SendHTTPRequest(path, &symbols) } @@ -201,7 +116,7 @@ func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { ticker := Ticker{} resp := TickerResponse{} - path := fmt.Sprintf("%s/v%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiTicker, currencyPair) + path := fmt.Sprintf("%s/v%s/%s/%s", g.API.Endpoints.URL, geminiAPIVersion, geminiTicker, currencyPair) err := g.SendHTTPRequest(path, &resp) if err != nil { @@ -242,7 +157,7 @@ func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { // 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", g.APIUrl, geminiAPIVersion, geminiOrderbook, currencyPair), params) + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", g.API.Endpoints.URL, geminiAPIVersion, geminiOrderbook, currencyPair), params) orderbook := Orderbook{} return orderbook, g.SendHTTPRequest(path, &orderbook) @@ -258,7 +173,7 @@ func (g *Gemini) GetOrderbook(currencyPair string, params url.Values) (Orderbook // 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", g.APIUrl, geminiAPIVersion, geminiTrades, currencyPair), params) + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", g.API.Endpoints.URL, geminiAPIVersion, geminiTrades, currencyPair), params) var trades []Trade return trades, g.SendHTTPRequest(path, &trades) @@ -266,7 +181,7 @@ func (g *Gemini) GetTrades(currencyPair string, params url.Values) ([]Trade, err // GetAuction returns auction information func (g *Gemini) GetAuction(currencyPair string) (Auction, error) { - path := fmt.Sprintf("%s/v%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiAuction, currencyPair) + path := fmt.Sprintf("%s/v%s/%s/%s", g.API.Endpoints.URL, geminiAPIVersion, geminiAuction, currencyPair) auction := Auction{} return auction, g.SendHTTPRequest(path, &auction) @@ -284,9 +199,8 @@ func (g *Gemini) GetAuction(currencyPair string) (Auction, error) { // 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", g.APIUrl, geminiAPIVersion, geminiAuction, currencyPair, geminiAuctionHistory), params) + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", g.API.Endpoints.URL, geminiAPIVersion, geminiAuction, currencyPair, geminiAuctionHistory), params) var auctionHist []AuctionHistory - return auctionHist, g.SendHTTPRequest(path, &auctionHist) } @@ -500,7 +414,7 @@ func (g *Gemini) SendHTTPRequest(path string, result interface{}) error { // 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 { + if !g.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name) } @@ -522,17 +436,18 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st log.Debugf("Request JSON: %s", PayloadJSON) } - PayloadBase64 := common.Base64Encode(PayloadJSON) - hmac := common.GetHMAC(common.HashSHA512_384, []byte(PayloadBase64), []byte(g.APISecret)) + PayloadBase64 := crypto.Base64Encode(PayloadJSON) + hmac := crypto.GetHMAC(crypto.HashSHA512_384, []byte(PayloadBase64), []byte(g.API.Credentials.Secret)) headers["Content-Length"] = "0" headers["Content-Type"] = "text/plain" - headers["X-GEMINI-APIKEY"] = g.APIKey + headers["X-GEMINI-APIKEY"] = g.API.Credentials.Key headers["X-GEMINI-PAYLOAD"] = PayloadBase64 - headers["X-GEMINI-SIGNATURE"] = common.HexEncodeToString(hmac) + headers["X-GEMINI-SIGNATURE"] = crypto.HexEncodeToString(hmac) headers["Cache-Control"] = "no-cache" - return g.SendPayload(method, g.APIUrl+"/v1/"+path, headers, strings.NewReader(""), result, true, false, g.Verbose, g.HTTPDebugging) + return g.SendPayload(method, g.API.Endpoints.URL+"/v1/"+path, headers, + strings.NewReader(""), result, true, false, g.Verbose, g.HTTPDebugging) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index 1620e60e..1078befe 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -61,19 +61,19 @@ func TestSetup(t *testing.T) { t.Error("Test Failed - Gemini Setup() init error") } - geminiConfig.AuthenticatedAPISupport = true + geminiConfig.API.AuthenticatedSupport = true - Session[1].Setup(&geminiConfig) - Session[2].Setup(&geminiConfig) + Session[1].Setup(geminiConfig) + Session[2].Setup(geminiConfig) - Session[1].APIKey = apiKey1 - Session[1].APISecret = apiSecret1 + Session[1].API.Credentials.Key = apiKey1 + Session[1].API.Credentials.Secret = apiSecret1 - Session[2].APIKey = apiKey2 - Session[2].APISecret = apiSecret2 + Session[2].API.Credentials.Key = apiKey2 + Session[2].API.Credentials.Secret = apiSecret2 - Session[1].APIUrl = geminiSandboxAPIURL - Session[2].APIUrl = geminiSandboxAPIURL + Session[1].API.Endpoints.URL = geminiSandboxAPIURL + Session[2].API.Endpoints.URL = geminiSandboxAPIURL } func TestGetSymbols(t *testing.T) { @@ -140,11 +140,13 @@ func TestGetAuctionHistory(t *testing.T) { func TestNewOrder(t *testing.T) { t.Parallel() - _, err := Session[1].NewOrder("btcusd", 1, 4500, "buy", "exchange limit") + _, err := Session[1].NewOrder("btcusd", 1, 4500, + exchange.BuyOrderSide.ToLower().ToString(), "exchange limit") if err == nil { t.Error("Test Failed - NewOrder() error", err) } - _, err = Session[2].NewOrder("btcusd", 1, 4500, "buy", "exchange limit") + _, err = Session[2].NewOrder("btcusd", 1, 4500, + exchange.BuyOrderSide.ToLower().ToString(), "exchange limit") if err == nil { t.Error("Test Failed - NewOrder() error", err) } @@ -395,11 +397,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if Session[1].APIKey != "" && Session[1].APIKey != "Key" && - Session[1].APISecret != "" && Session[1].APISecret != "Secret" { - return true - } - return false + return Session[1].ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -416,7 +414,8 @@ func TestSubmitOrder(t *testing.T) { Base: currency.LTC, Quote: currency.BTC, } - response, err := Session[1].SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "1234234") + response, err := Session[1].SubmitOrder(p, exchange.BuyOrderSide, + exchange.LimitOrderType, 1, 10, "1234234") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -494,11 +493,13 @@ func TestWithdraw(t *testing.T) { TestAddSession(t) TestSetDefaults(t) TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -523,8 +524,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := Session[1].WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -540,8 +540,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := Session[1].WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 79727206..264f37c5 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" ) @@ -48,7 +49,7 @@ func (g *Gemini) WsConnect() error { // WsSubscribe subscribes to the full websocket suite on gemini exchange func (g *Gemini) WsSubscribe(dialer *websocket.Dialer) error { - enabledCurrencies := g.GetEnabledCurrencies() + enabledCurrencies := g.GetEnabledPairs(assets.AssetTypeSpot) for i, c := range enabledCurrencies { val := url.Values{} val.Set("heartbeat", "true") @@ -156,7 +157,8 @@ func (g *Gemini) WsHandleData() { var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.AssetType = "SPOT" + newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.LastUpdated = time.Now() newOrderBook.Pair = resp.Currency err := g.Websocket.Orderbook.LoadSnapshot(&newOrderBook, @@ -168,7 +170,7 @@ func (g *Gemini) WsHandleData() { } g.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: resp.Currency, - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Exchange: g.GetName()} } else { @@ -177,7 +179,7 @@ func (g *Gemini) WsHandleData() { g.Websocket.DataHandler <- exchange.TradeData{ Timestamp: time.Now(), CurrencyPair: resp.Currency, - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Exchange: g.GetName(), EventTime: result.Timestamp, Price: event.Price, @@ -195,7 +197,7 @@ func (g *Gemini) WsHandleData() { resp.Currency, time.Now(), g.GetName(), - "SPOT") + assets.AssetTypeSpot) if err != nil { g.Websocket.DataHandler <- err } @@ -205,7 +207,7 @@ func (g *Gemini) WsHandleData() { resp.Currency, time.Now(), g.GetName(), - "SPOT") + assets.AssetTypeSpot) if err != nil { g.Websocket.DataHandler <- err } @@ -214,7 +216,7 @@ func (g *Gemini) WsHandleData() { } g.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: resp.Currency, - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Exchange: g.GetName()} } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 44bd055b..f73ad915 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -10,13 +10,115 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (g *Gemini) GetDefaultConfig() (*config.ExchangeConfig, error) { + g.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = g.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = g.BaseCurrencies + + err := g.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if g.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = g.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets package defaults for gemini exchange +func (g *Gemini) SetDefaults() { + g.Name = "Gemini" + g.Enabled = true + g.Verbose = true + g.API.CredentialsValidator.RequiresKey = true + g.API.CredentialsValidator.RequiresSecret = true + + g.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + + g.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | + exchange.AutoWithdrawCryptoWithSetup | + exchange.WithdrawFiatViaWebsiteOnly, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + g.Requester = request.New(g.Name, + request.NewRateLimit(time.Minute, geminiAuthRate), + request.NewRateLimit(time.Minute, geminiUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + g.API.Endpoints.URLDefault = geminiAPIURL + g.API.Endpoints.URL = g.API.Endpoints.URLDefault + g.WebsocketInit() + g.Websocket.Functionality = exchange.WebsocketOrderbookSupported | + exchange.WebsocketTradeDataSupported +} + +// Setup sets exchange configuration parameters +func (g *Gemini) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + g.SetEnabled(false) + return nil + } + + err := g.SetupDefaults(exch) + if err != nil { + return err + } + + if exch.UseSandbox { + g.API.Endpoints.URL = geminiSandboxAPIURL + } + + return g.WebsocketSetup(g.WsConnect, + nil, + nil, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + geminiWebsocketEndpoint, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the Gemini go routine func (g *Gemini) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -29,27 +131,35 @@ func (g *Gemini) Start(wg *sync.WaitGroup) { // Run implements the Gemini wrapper func (g *Gemini) Run() { if g.Verbose { - log.Debugf("%s polling delay: %ds.\n", g.GetName(), g.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", g.GetName(), len(g.EnabledPairs), g.EnabledPairs) + g.PrintEnabledPairs() } - exchangeProducts, err := g.GetSymbols() + if !g.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := g.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s Failed to get available symbols.\n", g.GetName()) - } else { - var newExchangeProducts currency.Pairs - for _, p := range exchangeProducts { - newExchangeProducts = append(newExchangeProducts, - currency.NewPairFromString(p)) - } - - err = g.UpdateCurrencies(newExchangeProducts, false, false) - if err != nil { - log.Errorf("%s Failed to update available currencies.\n", g.GetName()) - } + log.Errorf("%s failed to update tradable pairs. Err: %s", g.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (g *Gemini) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + return g.GetSymbols() +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (g *Gemini) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := g.GetSymbols() + if err != nil { + return err + } + + return g.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // GetAccountInfo Retrieves balances for all enabled currencies for the // Gemini exchange func (g *Gemini) GetAccountInfo() (exchange.AccountInfo, error) { @@ -77,7 +187,7 @@ func (g *Gemini) GetAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (g *Gemini) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (g *Gemini) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := g.GetTicker(p.String()) if err != nil { @@ -97,8 +207,8 @@ func (g *Gemini) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, return ticker.GetTicker(g.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (g *Gemini) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (g *Gemini) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType) if err != nil { return g.UpdateTicker(p, assetType) @@ -106,8 +216,8 @@ func (g *Gemini) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (g *Gemini) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (g *Gemini) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(g.GetName(), p, assetType) if err != nil { return g.UpdateOrderbook(p, assetType) @@ -116,7 +226,7 @@ func (g *Gemini) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Ba } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (g *Gemini) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (g *Gemini) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := g.GetOrderbook(p.String(), url.Values{}) if err != nil { @@ -151,10 +261,8 @@ func (g *Gemini) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (g *Gemini) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (g *Gemini) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -228,7 +336,7 @@ func (g *Gemini) GetDepositAddress(cryptocurrency currency.Code, _ string) (stri // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (g *Gemini) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (g *Gemini) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { resp, err := g.WithdrawCrypto(withdrawRequest.Address, withdrawRequest.Currency.String(), withdrawRequest.Amount) if err != nil { return "", err @@ -242,13 +350,13 @@ func (g *Gemini) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawR // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (g *Gemini) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (g *Gemini) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (g *Gemini) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (g *Gemini) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -259,7 +367,7 @@ func (g *Gemini) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (g *Gemini) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (g.APIKey == "" || g.APISecret == "") && // Todo check connection status + if !g.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -276,7 +384,7 @@ func (g *Gemini) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp { symbol := currency.NewPairDelimiter(resp[i].Symbol, - g.ConfigCurrencyPairFormat.Delimiter) + g.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) var orderType exchange.OrderType if resp[i].Type == "exchange limit" { orderType = exchange.LimitOrderType @@ -306,7 +414,6 @@ func (g *Gemini) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } @@ -319,8 +426,8 @@ func (g *Gemini) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var trades []TradeHistory for _, currency := range getOrdersRequest.Currencies { - resp, err := g.GetTradeHistory(exchange.FormatExchangeCurrency(g.Name, currency).String(), - getOrdersRequest.StartTicks.Unix()) + resp, err := g.GetTradeHistory(g.FormatExchangeCurrency(currency, + assets.AssetTypeSpot).String(), getOrdersRequest.StartTicks.Unix()) if err != nil { return nil, err } @@ -347,14 +454,13 @@ func (g *Gemini) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ Price: trades[i].Price, CurrencyPair: currency.NewPairWithDelimiter(trades[i].BaseCurrency, trades[i].QuoteCurrency, - g.ConfigCurrencyPairFormat.Delimiter), + g.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), }) } exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } diff --git a/exchanges/hitbtc/README.md b/exchanges/hitbtc/README.md index 4b5bd63c..2c8b0508 100644 --- a/exchanges/hitbtc/README.md +++ b/exchanges/hitbtc/README.md @@ -48,22 +48,22 @@ main.go ```go var h exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "HitBTC" { - h = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "HitBTC" { + h = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := h.GetTickerPrice() +tick, err := h.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := h.GetOrderbookEx() +ob, err := h.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index 63f439f2..f3fffcf4 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -8,16 +8,11 @@ import ( "net/url" "strconv" "sync" - "time" "github.com/gorilla/websocket" - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -41,8 +36,7 @@ const ( apiv2OpenOrders = "api/2/order" apiV2FeeInfo = "api/2/trading/fee" orders = "order" - orderBuy = "api/2/order" - orderSell = "sell" + apiOrder = "api/2/order" orderMove = "moveOrder" tradableBalances = "returnTradableBalances" transferBalance = "transferBalance" @@ -58,86 +52,6 @@ type HitBTC struct { wsRequestMtx sync.Mutex } -// SetDefaults sets default settings for hitbtc -func (h *HitBTC) SetDefaults() { - h.Name = "HitBTC" - h.Enabled = false - h.Fee = 0 - h.Verbose = false - h.RESTPollingDelay = 10 - h.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.NoFiatWithdrawals - h.RequestCurrencyPairFormat.Delimiter = "" - h.RequestCurrencyPairFormat.Uppercase = true - h.ConfigCurrencyPairFormat.Delimiter = "-" - h.ConfigCurrencyPairFormat.Uppercase = true - h.AssetTypes = []string{ticker.Spot} - h.SupportsAutoPairUpdating = true - h.SupportsRESTTickerBatching = true - h.Requester = request.New(h.Name, - request.NewRateLimit(time.Second, hitbtcAuthRate), - request.NewRateLimit(time.Second, hitbtcUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - h.APIUrlDefault = apiURL - h.APIUrl = h.APIUrlDefault - h.WebsocketInit() - h.Websocket.Functionality = exchange.WebsocketTickerSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup sets user exchange configuration settings -func (h *HitBTC) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - h.SetEnabled(false) - } else { - h.Enabled = true - h.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - h.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - h.SetHTTPClientTimeout(exch.HTTPTimeout) - h.SetHTTPClientUserAgent(exch.HTTPUserAgent) - h.RESTPollingDelay = exch.RESTPollingDelay // Max 60000ms - h.Verbose = exch.Verbose - h.HTTPDebugging = exch.HTTPDebugging - h.Websocket.SetWsStatusAndConnection(exch.Websocket) - h.BaseCurrencies = exch.BaseCurrencies - h.AvailablePairs = exch.AvailablePairs - h.EnabledPairs = exch.EnabledPairs - err := h.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = h.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = h.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = h.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = h.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = h.WebsocketSetup(h.WsConnect, - h.Subscribe, - h.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - hitbtcWebsocketAddress, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // Public Market Data // https://api.hitbtc.com/?python#market-data @@ -148,7 +62,7 @@ func (h *HitBTC) GetCurrencies() (map[string]Currencies, error) { Data []Currencies } resp := Response{} - path := fmt.Sprintf("%s/%s", h.APIUrl, apiV2Currency) + path := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, apiV2Currency) ret := make(map[string]Currencies) err := h.SendHTTPRequest(path, &resp.Data) @@ -169,7 +83,7 @@ func (h *HitBTC) GetCurrency(currency string) (Currencies, error) { Data Currencies } resp := Response{} - path := fmt.Sprintf("%s/%s/%s", h.APIUrl, apiV2Currency, currency) + path := fmt.Sprintf("%s/%s/%s", h.API.Endpoints.URL, apiV2Currency, currency) return resp.Data, h.SendHTTPRequest(path, &resp.Data) } @@ -181,7 +95,7 @@ func (h *HitBTC) GetCurrency(currency string) (Currencies, error) { // of the base currency. func (h *HitBTC) GetSymbols(symbol string) ([]string, error) { var resp []Symbol - path := fmt.Sprintf("%s/%s/%s", h.APIUrl, apiV2Symbol, symbol) + path := fmt.Sprintf("%s/%s/%s", h.API.Endpoints.URL, apiV2Symbol, symbol) ret := make([]string, 0, len(resp)) err := h.SendHTTPRequest(path, &resp) @@ -199,8 +113,7 @@ func (h *HitBTC) GetSymbols(symbol string) ([]string, error) { // all their details. func (h *HitBTC) GetSymbolsDetailed() ([]Symbol, error) { var resp []Symbol - path := fmt.Sprintf("%s/%s", h.APIUrl, apiV2Symbol) - + path := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, apiV2Symbol) return resp, h.SendHTTPRequest(path, &resp) } @@ -210,7 +123,7 @@ func (h *HitBTC) GetTicker(symbol string) (map[string]Ticker, error) { resp2 := TickerResponse{} ret := make(map[string]TickerResponse) result := make(map[string]Ticker) - path := fmt.Sprintf("%s/%s/%s", h.APIUrl, apiV2Ticker, symbol) + path := fmt.Sprintf("%s/%s/%s", h.API.Endpoints.URL, apiV2Ticker, symbol) var err error if symbol == "" { @@ -301,8 +214,7 @@ func (h *HitBTC) GetTrades(currencyPair, from, till, limit, offset, by, sort str } var resp []TradeHistory - path := fmt.Sprintf("%s/%s/%s?%s", h.APIUrl, apiV2Trades, currencyPair, vals.Encode()) - + path := fmt.Sprintf("%s/%s/%s?%s", h.API.Endpoints.URL, apiV2Trades, currencyPair, vals.Encode()) return resp, h.SendHTTPRequest(path, &resp) } @@ -317,7 +229,7 @@ func (h *HitBTC) GetOrderbook(currencyPair string, limit int) (Orderbook, error) } resp := OrderbookResponse{} - path := fmt.Sprintf("%s/%s/%s?%s", h.APIUrl, apiV2Orderbook, currencyPair, vals.Encode()) + path := fmt.Sprintf("%s/%s/%s?%s", h.API.Endpoints.URL, apiV2Orderbook, currencyPair, vals.Encode()) err := h.SendHTTPRequest(path, &resp) if err != nil { @@ -346,8 +258,7 @@ func (h *HitBTC) GetCandles(currencyPair, limit, period string) ([]ChartData, er } var resp []ChartData - path := fmt.Sprintf("%s/%s/%s?%s", h.APIUrl, apiV2Candles, currencyPair, vals.Encode()) - + path := fmt.Sprintf("%s/%s/%s?%s", h.API.Endpoints.URL, apiV2Candles, currencyPair, vals.Encode()) return resp, h.SendHTTPRequest(path, &resp) } @@ -464,7 +375,7 @@ func (h *HitBTC) PlaceOrder(currency string, rate, amount float64, orderType, si values.Set("price", strconv.FormatFloat(rate, 'f', -1, 64)) values.Set("type", orderType) - return result, h.SendAuthenticatedHTTPRequest(http.MethodPost, orderBuy, values, &result) + return result, h.SendAuthenticatedHTTPRequest(http.MethodPost, apiOrder, values, &result) } // CancelExistingOrder cancels a specific order by OrderID @@ -472,7 +383,7 @@ func (h *HitBTC) CancelExistingOrder(orderID int64) (bool, error) { result := GenericResponse{} values := url.Values{} - err := h.SendAuthenticatedHTTPRequest(http.MethodDelete, orderBuy+"/"+strconv.FormatInt(orderID, 10), values, &result) + err := h.SendAuthenticatedHTTPRequest(http.MethodDelete, apiOrder+"/"+strconv.FormatInt(orderID, 10), values, &result) if err != nil { return false, err @@ -489,7 +400,7 @@ func (h *HitBTC) CancelExistingOrder(orderID int64) (bool, error) { func (h *HitBTC) CancelAllExistingOrders() ([]Order, error) { var result []Order values := url.Values{} - return result, h.SendAuthenticatedHTTPRequest(http.MethodDelete, orderBuy, values, &result) + return result, h.SendAuthenticatedHTTPRequest(http.MethodDelete, apiOrder, values, &result) } // MoveOrder generates a new move order @@ -604,14 +515,14 @@ func (h *HitBTC) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated http request func (h *HitBTC) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error { - if !h.AuthenticatedAPISupport { + if !h.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name) } headers := make(map[string]string) - headers["Authorization"] = "Basic " + common.Base64Encode([]byte(h.APIKey+":"+h.APISecret)) + headers["Authorization"] = "Basic " + crypto.Base64Encode([]byte(h.API.Credentials.Key+":"+h.API.Credentials.Secret)) - path := fmt.Sprintf("%s/%s", h.APIUrl, endpoint) + path := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, endpoint) return h.SendPayload(method, path, diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index 52b0082b..ee1428f0 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -29,11 +29,11 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - HitBTC Setup() init error") } - hitbtcConfig.AuthenticatedAPISupport = true - hitbtcConfig.APIKey = apiKey - hitbtcConfig.APISecret = apiSecret + hitbtcConfig.API.AuthenticatedSupport = true + hitbtcConfig.API.Credentials.Key = apiKey + hitbtcConfig.API.Credentials.Secret = apiSecret - h.Setup(&hitbtcConfig) + h.Setup(hitbtcConfig) } func TestGetOrderbook(t *testing.T) { @@ -221,11 +221,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if h.APIKey != "" && h.APIKey != "Key" && - h.APISecret != "" && h.APISecret != "Secret" { - return true - } - return false + return h.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -241,7 +237,8 @@ func TestSubmitOrder(t *testing.T) { Base: currency.DGD, Quote: currency.BTC, } - response, err := h.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "1234234") + response, err := h.SubmitOrder(p, exchange.BuyOrderSide, + exchange.LimitOrderType, 1, 10, "1234234") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -316,11 +313,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { h.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -344,8 +343,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := h.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -360,8 +358,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := h.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index 1178d2f7..025380c6 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -116,7 +117,7 @@ func (h *HitBTC) WsHandleData() { h.Websocket.DataHandler <- exchange.TickerData{ Exchange: h.GetName(), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Pair: currency.NewPairFromString(ticker.Params.Symbol), Quantity: ticker.Params.Volume, Timestamp: ts, @@ -190,7 +191,8 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.AssetType = "SPOT" + newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.LastUpdated = time.Now() newOrderBook.Pair = p err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, h.GetName(), false) @@ -200,7 +202,7 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: h.GetName(), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Pair: p, } @@ -224,14 +226,14 @@ func (h *HitBTC) WsProcessOrderbookUpdate(ob WsOrderbook) error { p := currency.NewPairFromString(ob.Params.Symbol) - err := h.Websocket.Orderbook.Update(bids, asks, p, time.Now(), h.GetName(), "SPOT") + err := h.Websocket.Orderbook.Update(bids, asks, p, time.Now(), h.GetName(), assets.AssetTypeSpot) if err != nil { return err } h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: h.GetName(), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Pair: p, } return nil @@ -241,7 +243,7 @@ func (h *HitBTC) WsProcessOrderbookUpdate(ob WsOrderbook) error { func (h *HitBTC) GenerateDefaultSubscriptions() { var channels = []string{"subscribeTicker", "subscribeOrderbook", "subscribeTrades", "subscribeCandles"} subscriptions := []exchange.WebsocketChannelSubscription{} - enabledCurrencies := h.GetEnabledCurrencies() + enabledCurrencies := h.GetEnabledPairs(assets.AssetTypeSpot) for i := range channels { for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "" diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index ec149444..2899dc1e 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -9,14 +9,111 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (h *HitBTC) GetDefaultConfig() (*config.ExchangeConfig, error) { + h.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = h.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = h.BaseCurrencies + + err := h.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if h.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = h.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets default settings for hitbtc +func (h *HitBTC) SetDefaults() { + h.Name = "HitBTC" + h.Enabled = true + h.Verbose = true + h.API.CredentialsValidator.RequiresKey = true + h.API.CredentialsValidator.RequiresSecret = true + + h.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + } + + h.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + h.Requester = request.New(h.Name, + request.NewRateLimit(time.Second, hitbtcAuthRate), + request.NewRateLimit(time.Second, hitbtcUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + h.API.Endpoints.URLDefault = apiURL + h.API.Endpoints.URL = h.API.Endpoints.URLDefault + h.WebsocketInit() + h.Websocket.Functionality = exchange.WebsocketTickerSupported | + exchange.WebsocketOrderbookSupported +} + +// Setup sets user exchange configuration settings +func (h *HitBTC) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + h.SetEnabled(false) + return nil + } + + err := h.SetupDefaults(exch) + if err != nil { + return err + } + + return h.WebsocketSetup(h.WsConnect, + h.Subscribe, + h.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + hitbtcWebsocketAddress, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the HitBTC go routine func (h *HitBTC) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -30,59 +127,67 @@ func (h *HitBTC) Start(wg *sync.WaitGroup) { func (h *HitBTC) Run() { if h.Verbose { log.Debugf("%s Websocket: %s (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket.IsEnabled()), hitbtcWebsocketAddress) - log.Debugf("%s polling delay: %ds.\n", h.GetName(), h.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", h.GetName(), len(h.EnabledPairs), h.EnabledPairs) + h.PrintEnabledPairs() } - exchangeProducts, err := h.GetSymbolsDetailed() - if err != nil { - log.Errorf("%s Failed to get available symbols.\n", h.GetName()) - } else { - forceUpgrade := false - if !common.StringDataContains(h.EnabledPairs.Strings(), "-") || - !common.StringDataContains(h.AvailablePairs.Strings(), "-") { - forceUpgrade = true - } - var currencies []string - for x := range exchangeProducts { - currencies = append(currencies, exchangeProducts[x].BaseCurrency+"-"+exchangeProducts[x].QuoteCurrency) - } + forceUpdate := false + if !common.StringDataContains(h.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "-") || + !common.StringDataContains(h.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "-") { + enabledPairs := []string{"BTC-USD"} + log.Warn("WARNING: Available pairs for HitBTC reset due to config upgrade, please enable the ones you would like again.") + forceUpdate = true - if forceUpgrade { - enabledPairs := currency.Pairs{currency.Pair{Base: currency.BTC, - Quote: currency.USD, Delimiter: "-"}} - - log.Warn("Available pairs for HitBTC reset due to config upgrade, please enable the ones you would like again.") - - err = h.UpdateCurrencies(enabledPairs, true, true) - if err != nil { - log.Errorf("%s Failed to update enabled currencies.\n", h.GetName()) - } - } - - var newCurrencies currency.Pairs - for _, p := range currencies { - newCurrencies = append(newCurrencies, - currency.NewPairFromString(p)) - } - - err = h.UpdateCurrencies(newCurrencies, false, forceUpgrade) + err := h.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), assets.AssetTypeSpot, true, true) if err != nil { - log.Errorf("%s Failed to update available currencies.\n", h.GetName()) + log.Errorf("%s failed to update enabled currencies.\n", h.GetName()) } } + + if !h.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { + return + } + + err := h.UpdateTradablePairs(forceUpdate) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", h.Name, err) + } +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (h *HitBTC) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + symbols, err := h.GetSymbolsDetailed() + if err != nil { + return nil, err + } + + var pairs []string + for x := range symbols { + pairs = append(pairs, symbols[x].BaseCurrency+"-"+symbols[x].QuoteCurrency) + } + return pairs, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (h *HitBTC) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := h.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return h.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType string) (ticker.Price, error) { +func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tick, err := h.GetTicker("") if err != nil { return ticker.Price{}, err } - for _, x := range h.GetEnabledCurrencies() { + for _, x := range h.GetEnabledPairs(assetType) { var tp ticker.Price - curr := exchange.FormatExchangeCurrency(h.GetName(), x).String() + curr := h.FormatExchangeCurrency(x, assetType).String() tp.Pair = x tp.Ask = tick[curr].Ask tp.Bid = tick[curr].Bid @@ -99,28 +204,28 @@ func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType string) (tic return ticker.GetTicker(h.Name, currencyPair, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (h *HitBTC) GetTickerPrice(currencyPair currency.Pair, assetType string) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(h.GetName(), currencyPair, assetType) +// FetchTicker returns the ticker for a currency pair +func (h *HitBTC) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType) if err != nil { - return h.UpdateTicker(currencyPair, assetType) + return h.UpdateTicker(p, assetType) } return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (h *HitBTC) GetOrderbookEx(currencyPair currency.Pair, assetType string) (orderbook.Base, error) { - ob, err := orderbook.Get(h.GetName(), currencyPair, assetType) +// FetchOrderbook returns orderbook base on the currency pair +func (h *HitBTC) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { + ob, err := orderbook.Get(h.GetName(), p, assetType) if err != nil { - return h.UpdateOrderbook(currencyPair, assetType) + return h.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType string) (orderbook.Base, error) { +func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := h.GetOrderbook(exchange.FormatExchangeCurrency(h.GetName(), currencyPair).String(), 1000) + orderbookNew, err := h.GetOrderbook(h.FormatExchangeCurrency(currencyPair, assetType).String(), 1000) if err != nil { return orderBook, err } @@ -181,10 +286,8 @@ func (h *HitBTC) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (h *HitBTC) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (h *HitBTC) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -261,7 +364,7 @@ func (h *HitBTC) GetDepositAddress(currency currency.Code, _ string) (string, er // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (h *HitBTC) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (h *HitBTC) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { _, err := h.Withdraw(withdrawRequest.Currency.String(), withdrawRequest.Address, withdrawRequest.Amount) return "", err @@ -269,13 +372,13 @@ func (h *HitBTC) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawR // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (h *HitBTC) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (h *HitBTC) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (h *HitBTC) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (h *HitBTC) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -286,7 +389,7 @@ func (h *HitBTC) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (h *HitBTC) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (h.APIKey == "" || h.APISecret == "") && // Todo check connection status + if !h.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -311,7 +414,7 @@ func (h *HitBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.ConfigCurrencyPairFormat.Delimiter) + h.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[i].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[i].CreatedAt) if err != nil { @@ -333,7 +436,6 @@ func (h *HitBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } @@ -356,7 +458,7 @@ func (h *HitBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.ConfigCurrencyPairFormat.Delimiter) + h.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[i].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[i].CreatedAt) if err != nil { @@ -377,7 +479,6 @@ func (h *HitBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } diff --git a/exchanges/huobi/README.md b/exchanges/huobi/README.md index f05859c5..a9de1d26 100644 --- a/exchanges/huobi/README.md +++ b/exchanges/huobi/README.md @@ -47,22 +47,22 @@ main.go ```go var h exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Huobi" { - h = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Huobi" { + h = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := h.GetTickerPrice() +tick, err := h.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := h.GetOrderbookEx() +ob, err := h.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index 80931dcb..a9290c94 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -19,12 +19,9 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -72,89 +69,6 @@ type HUOBI struct { wsRequestMtx sync.Mutex } -// SetDefaults sets default values for the exchange -func (h *HUOBI) SetDefaults() { - h.Name = "Huobi" - h.Enabled = false - h.Fee = 0 - h.Verbose = false - h.RESTPollingDelay = 10 - h.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithSetup | - exchange.NoFiatWithdrawals - h.RequestCurrencyPairFormat.Delimiter = "" - h.RequestCurrencyPairFormat.Uppercase = false - h.ConfigCurrencyPairFormat.Delimiter = "-" - h.ConfigCurrencyPairFormat.Uppercase = true - h.AssetTypes = []string{ticker.Spot} - h.SupportsAutoPairUpdating = true - h.SupportsRESTTickerBatching = false - h.Requester = request.New(h.Name, - request.NewRateLimit(time.Second*10, huobiAuthRate), - request.NewRateLimit(time.Second*10, huobiUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - h.APIUrlDefault = huobiAPIURL - h.APIUrl = h.APIUrlDefault - h.WebsocketInit() - h.Websocket.Functionality = exchange.WebsocketKlineSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketTradeDataSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup sets user configuration -func (h *HUOBI) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - h.SetEnabled(false) - } else { - h.Enabled = true - h.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - h.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - h.APIAuthPEMKeySupport = exch.APIAuthPEMKeySupport - h.APIAuthPEMKey = exch.APIAuthPEMKey - h.SetHTTPClientTimeout(exch.HTTPTimeout) - h.SetHTTPClientUserAgent(exch.HTTPUserAgent) - h.RESTPollingDelay = exch.RESTPollingDelay - h.Verbose = exch.Verbose - h.HTTPDebugging = exch.HTTPDebugging - h.Websocket.SetWsStatusAndConnection(exch.Websocket) - h.BaseCurrencies = exch.BaseCurrencies - h.AvailablePairs = exch.AvailablePairs - h.EnabledPairs = exch.EnabledPairs - err := h.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = h.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = h.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = h.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = h.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = h.WebsocketSetup(h.WsConnect, - h.Subscribe, - h.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - huobiSocketIOAddress, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetSpotKline returns kline data // KlinesRequestParams contains symbol, period and size func (h *HUOBI) GetSpotKline(arg KlinesRequestParams) ([]KlineItem, error) { @@ -172,7 +86,7 @@ func (h *HUOBI) GetSpotKline(arg KlinesRequestParams) ([]KlineItem, error) { } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketHistoryKline) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketHistoryKline) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -192,7 +106,7 @@ func (h *HUOBI) GetMarketDetailMerged(symbol string) (DetailMerged, error) { } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketDetailMerged) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketDetailMerged) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -216,7 +130,7 @@ func (h *HUOBI) GetDepth(obd OrderBookDataRequestParams) (Orderbook, error) { } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketDepth) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketDepth) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -238,7 +152,7 @@ func (h *HUOBI) GetTrades(symbol string) ([]Trade, error) { } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketTrade) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketTrade) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -278,7 +192,7 @@ func (h *HUOBI) GetTradeHistory(symbol, size string) ([]TradeHistory, error) { } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketTradeHistory) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketTradeHistory) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -298,7 +212,7 @@ func (h *HUOBI) GetMarketDetail(symbol string) (Detail, error) { } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobiMarketDetail) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketDetail) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -315,7 +229,7 @@ func (h *HUOBI) GetSymbols() ([]Symbol, error) { } var result response - urlPath := fmt.Sprintf("%s/v%s/%s", h.APIUrl, huobiAPIVersion, huobiSymbols) + urlPath := fmt.Sprintf("%s/v%s/%s", h.API.Endpoints.URL, huobiAPIVersion, huobiSymbols) err := h.SendHTTPRequest(urlPath, &result) if result.ErrorMessage != "" { @@ -332,7 +246,7 @@ func (h *HUOBI) GetCurrencies() ([]string, error) { } var result response - urlPath := fmt.Sprintf("%s/v%s/%s", h.APIUrl, huobiAPIVersion, huobiCurrencies) + urlPath := fmt.Sprintf("%s/v%s/%s", h.API.Endpoints.URL, huobiAPIVersion, huobiCurrencies) err := h.SendHTTPRequest(urlPath, &result) if result.ErrorMessage != "" { @@ -349,7 +263,7 @@ func (h *HUOBI) GetTimestamp() (int64, error) { } var result response - urlPath := fmt.Sprintf("%s/v%s/%s", h.APIUrl, huobiAPIVersion, huobiTimestamp) + urlPath := fmt.Sprintf("%s/v%s/%s", h.API.Endpoints.URL, huobiAPIVersion, huobiTimestamp) err := h.SendHTTPRequest(urlPath, &result) if result.ErrorMessage != "" { @@ -844,7 +758,7 @@ func (h *HUOBI) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends authenticated requests to the HUOBI API func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, data, result interface{}) error { - if !h.AuthenticatedAPISupport { + if !h.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name) } @@ -852,7 +766,7 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url values = url.Values{} } - values.Set("AccessKeyId", h.APIKey) + values.Set("AccessKeyId", h.API.Credentials.Key) values.Set("SignatureMethod", "HmacSHA256") values.Set("SignatureVersion", "2") values.Set("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05")) @@ -869,12 +783,12 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url headers["Content-Type"] = "application/json" } - hmac := common.GetHMAC(common.HashSHA256, []byte(payload), []byte(h.APISecret)) - signature := common.Base64Encode(hmac) + hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(h.API.Credentials.Secret)) + signature := crypto.Base64Encode(hmac) values.Set("Signature", signature) - if h.APIAuthPEMKeySupport { - pemKey := strings.NewReader(h.APIAuthPEMKey) + if h.API.Credentials.PEMKey != "" && h.API.PEMKeySupport { + pemKey := strings.NewReader(h.API.Credentials.PEMKey) pemBytes, err := ioutil.ReadAll(pemKey) if err != nil { return fmt.Errorf("%s unable to ioutil.ReadAll PEM key: %s", h.Name, err) @@ -891,19 +805,18 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url return fmt.Errorf("%s unable to ParseECPrivKey: %s", h.Name, err) } - r, s, err := ecdsa.Sign(rand.Reader, privKey, common.GetSHA256([]byte(signature))) + r, s, err := ecdsa.Sign(rand.Reader, privKey, crypto.GetSHA256([]byte(signature))) if err != nil { return fmt.Errorf("%s unable to sign: %s", h.Name, err) } privSig := r.Bytes() privSig = append(privSig, s.Bytes()...) - values.Set("PrivateSignature", common.Base64Encode(privSig)) + values.Set("PrivateSignature", crypto.Base64Encode(privSig)) } - urlPath := common.EncodeURLValues( - fmt.Sprintf("%s%s", h.APIUrl, endpoint), values, - ) + urlPath := fmt.Sprintf("%s%s", common.EncodeURLValues(h.API.Endpoints.URL, values), + endpoint) var body []byte diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index a7081a28..4400a84b 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" @@ -25,35 +26,6 @@ const ( var h HUOBI -// getDefaultConfig returns a default huobi config -func getDefaultConfig() config.ExchangeConfig { - return config.ExchangeConfig{ - Name: "Huobi", - Enabled: true, - Verbose: true, - Websocket: false, - UseSandbox: false, - RESTPollingDelay: 10, - HTTPTimeout: 15000000000, - AuthenticatedAPISupport: true, - APIKey: "", - APISecret: "", - ClientID: "", - AvailablePairs: currency.NewPairsFromStrings([]string{"BTC-USDT", "BCH-USDT"}), - EnabledPairs: currency.NewPairsFromStrings([]string{"BTC-USDT"}), - BaseCurrencies: currency.NewCurrenciesFromStringArray([]string{"USD"}), - AssetTypes: "SPOT", - SupportsAutoPairUpdates: false, - ConfigCurrencyPairFormat: &config.CurrencyPairFormatConfig{ - Uppercase: true, - Delimiter: "-", - }, - RequestCurrencyPairFormat: &config.CurrencyPairFormatConfig{ - Uppercase: false, - }, - } -} - func TestSetDefaults(t *testing.T) { h.SetDefaults() } @@ -65,11 +37,11 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Huobi Setup() init error") } - hConfig.AuthenticatedAPISupport = true - hConfig.APIKey = apiKey - hConfig.APISecret = apiSecret + hConfig.API.AuthenticatedSupport = true + hConfig.API.Credentials.Key = apiKey + hConfig.API.Credentials.Secret = apiSecret - h.Setup(&hConfig) + h.Setup(hConfig) } func TestGetSpotKline(t *testing.T) { @@ -163,7 +135,7 @@ func TestGetTimestamp(t *testing.T) { func TestGetAccounts(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -176,7 +148,7 @@ func TestGetAccounts(t *testing.T) { func TestGetAccountBalance(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -195,7 +167,7 @@ func TestGetAccountBalance(t *testing.T) { func TestSpotNewOrder(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -234,7 +206,7 @@ func TestGetOrder(t *testing.T) { func TestGetMarginLoanOrders(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -247,7 +219,7 @@ func TestGetMarginLoanOrders(t *testing.T) { func TestGetMarginAccountBalance(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -269,7 +241,7 @@ func TestCancelWithdraw(t *testing.T) { func TestPEMLoadAndSign(t *testing.T) { t.Parallel() - pemKey := strings.NewReader(h.APIAuthPEMKey) + pemKey := strings.NewReader(h.API.Credentials.PEMKey) pemBytes, err := ioutil.ReadAll(pemKey) if err != nil { t.Fatalf("Test Failed. TestPEMLoadAndSign Unable to ioutil.ReadAll PEM key: %s", err) @@ -286,7 +258,7 @@ func TestPEMLoadAndSign(t *testing.T) { t.Fatalf("Test Failed. TestPEMLoadAndSign Unable to ParseECPrivKey: %s", err) } - _, _, err = ecdsa.Sign(rand.Reader, privKey, common.GetSHA256([]byte("test"))) + _, _, err = ecdsa.Sign(rand.Reader, privKey, crypto.GetSHA256([]byte("test"))) if err != nil { t.Fatalf("Test Failed. TestPEMLoadAndSign Unable to sign: %s", err) } @@ -444,24 +416,19 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if h.APIKey != "" && h.APIKey != "Key" && - h.APISecret != "" && h.APISecret != "Secret" { - return true - } - return false + return h.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { h.SetDefaults() TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") + if !h.ValidateAPICredentials() { + t.Skip() } - if (h.APIKey == "" || h.APIKey == "Key") && - (h.APISecret == "" || h.APISecret == "Secret") { - t.Skip() + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") } var p = currency.Pair{ @@ -472,14 +439,12 @@ func TestSubmitOrder(t *testing.T) { accounts, err := h.GetAccounts() if err != nil { - t.Errorf("Failed to get accounts. Err: %s", err) + t.Fatalf("Failed to get accounts. Err: %s", err) } response, err := h.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, strconv.FormatInt(accounts[0].ID, 10)) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") } } @@ -563,11 +528,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { h.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -591,8 +558,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := h.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -607,8 +573,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := h.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index f59d576b..de2e20d3 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -150,7 +151,7 @@ func (h *HUOBI) WsHandleData() { h.Websocket.DataHandler <- exchange.KlineData{ Timestamp: time.Unix(0, kline.Timestamp), Exchange: h.GetName(), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Pair: currency.NewPairFromString(data[1]), OpenPrice: kline.Tick.Open, ClosePrice: kline.Tick.Close, @@ -171,7 +172,7 @@ func (h *HUOBI) WsHandleData() { h.Websocket.DataHandler <- exchange.TradeData{ Exchange: h.GetName(), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, CurrencyPair: currency.NewPairFromString(data[1]), Timestamp: time.Unix(0, trade.Tick.Timestamp), } @@ -211,7 +212,7 @@ func (h *HUOBI) WsProcessOrderbook(ob *WsDepth, symbol string) error { h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p, Exchange: h.GetName(), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, } return nil @@ -220,7 +221,7 @@ func (h *HUOBI) WsProcessOrderbook(ob *WsDepth, symbol string) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (h *HUOBI) GenerateDefaultSubscriptions() { var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade} - enabledCurrencies := h.GetEnabledCurrencies() + enabledCurrencies := h.GetEnabledPairs(assets.AssetTypeSpot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { for j := range enabledCurrencies { diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index e1c5a02f..6c256834 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -12,11 +12,115 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (h *HUOBI) GetDefaultConfig() (*config.ExchangeConfig, error) { + h.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = h.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = h.BaseCurrencies + + err := h.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if h.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = h.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets default values for the exchange +func (h *HUOBI) SetDefaults() { + h.Name = "Huobi" + h.Enabled = true + h.Verbose = true + h.API.CredentialsValidator.RequiresKey = true + h.API.CredentialsValidator.RequiresSecret = true + + h.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + } + + h.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + h.Requester = request.New(h.Name, + request.NewRateLimit(time.Second*10, huobiAuthRate), + request.NewRateLimit(time.Second*10, huobiUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + h.API.Endpoints.URLDefault = huobiAPIURL + h.API.Endpoints.URL = h.API.Endpoints.URLDefault + h.WebsocketInit() + h.Websocket.Functionality = exchange.WebsocketKlineSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketTradeDataSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup sets user configuration +func (h *HUOBI) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + h.SetEnabled(false) + return nil + } + + err := h.SetupDefaults(exch) + if err != nil { + return err + } + + h.API.PEMKeySupport = exch.API.PEMKeySupport + h.API.Credentials.PEMKey = exch.API.Credentials.PEMKey + + return h.WebsocketSetup(h.WsConnect, + h.Subscribe, + h.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + huobiSocketIOAddress, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the HUOBI go routine func (h *HUOBI) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -30,75 +134,87 @@ func (h *HUOBI) Start(wg *sync.WaitGroup) { func (h *HUOBI) Run() { if h.Verbose { log.Debugf("%s Websocket: %s (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket.IsEnabled()), huobiSocketIOAddress) - log.Debugf("%s polling delay: %ds.\n", h.GetName(), h.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", h.GetName(), len(h.EnabledPairs), h.EnabledPairs) + h.PrintEnabledPairs() } - exchangeProducts, err := h.GetSymbols() - if err != nil { - log.Errorf("%s Failed to get available symbols.\n", h.GetName()) - } else { - forceUpgrade := false - if common.StringDataContains(h.EnabledPairs.Strings(), "CNY") || - common.StringDataContains(h.AvailablePairs.Strings(), "CNY") { - forceUpgrade = true - } + var forceUpdate bool + if common.StringDataContains(h.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "CNY") || + common.StringDataContains(h.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "CNY") { + forceUpdate = true + } - if common.StringDataContains(h.BaseCurrencies.Strings(), "CNY") { - cfg := config.GetConfig() - exchCfg, errCNY := cfg.GetExchangeConfig(h.Name) - if err != nil { - log.Errorf("%s failed to get exchange config. %s\n", h.Name, errCNY) - return - } - exchCfg.BaseCurrencies = currency.Currencies{currency.USD} - h.BaseCurrencies = currency.Currencies{currency.USD} - - errCNY = cfg.UpdateExchangeConfig(&exchCfg) - if errCNY != nil { - log.Errorf("%s failed to update config. %s\n", h.Name, errCNY) - return - } - } - - var currencies []string - for x := range exchangeProducts { - newCurrency := exchangeProducts[x].BaseCurrency + "-" + exchangeProducts[x].QuoteCurrency - currencies = append(currencies, newCurrency) - } - - if forceUpgrade { - enabledPairs := currency.Pairs{currency.Pair{ - Base: currency.BTC.Lower(), - Quote: currency.USDT.Lower(), - Delimiter: "-", - }, - } - log.Warn("Available and enabled pairs for Huobi reset due to config upgrade, please enable the ones you would like again") - - err = h.UpdateCurrencies(enabledPairs, true, true) - if err != nil { - log.Errorf("%s Failed to update enabled currencies.\n", h.GetName()) - } - } - - var newCurrencies currency.Pairs - for _, p := range currencies { - newCurrencies = append(newCurrencies, - currency.NewPairFromString(p)) - } - - err = h.UpdateCurrencies(newCurrencies, false, forceUpgrade) + if common.StringDataContains(h.BaseCurrencies.Strings(), "CNY") { + cfg := config.GetConfig() + exchCfg, err := cfg.GetExchangeConfig(h.Name) if err != nil { - log.Errorf("%s Failed to update available currencies.\n", h.GetName()) + log.Errorf("%s failed to get exchange config. %s\n", h.Name, err) + return } + exchCfg.BaseCurrencies = currency.Currencies{currency.USD} + h.BaseCurrencies = currency.Currencies{currency.USD} + + err = cfg.UpdateExchangeConfig(exchCfg) + if err != nil { + log.Errorf("%s failed to update config. %s\n", h.Name, err) + return + } + } + + if forceUpdate { + enabledPairs := currency.Pairs{currency.Pair{ + Base: currency.BTC.Lower(), + Quote: currency.USDT.Lower(), + Delimiter: "-", + }, + } + log.Warn("WARNING: Available and enabled pairs for Huobi reset due to config upgrade, please enable the ones you would like again") + + err := h.UpdatePairs(enabledPairs, assets.AssetTypeSpot, true, true) + if err != nil { + log.Errorf("%s Failed to update enabled currencies.\n", h.GetName()) + } + } + + if !h.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { + return + } + + err := h.UpdateTradablePairs(forceUpdate) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", h.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (h *HUOBI) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + symbols, err := h.GetSymbols() + if err != nil { + return nil, err + } + + var pairs []string + for x := range symbols { + pairs = append(pairs, symbols[x].BaseCurrency+"-"+symbols[x].QuoteCurrency) + } + + return pairs, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (h *HUOBI) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := h.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return h.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (h *HUOBI) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (h *HUOBI) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - tick, err := h.GetMarketDetailMerged(exchange.FormatExchangeCurrency(h.Name, p).String()) + tick, err := h.GetMarketDetailMerged(h.FormatExchangeCurrency(p, assetType).String()) if err != nil { return tickerPrice, err } @@ -125,8 +241,8 @@ func (h *HUOBI) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, e return ticker.GetTicker(h.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (h *HUOBI) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (h *HUOBI) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType) if err != nil { return h.UpdateTicker(p, assetType) @@ -134,8 +250,8 @@ func (h *HUOBI) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (h *HUOBI) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (h *HUOBI) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(h.GetName(), p, assetType) if err != nil { return h.UpdateOrderbook(p, assetType) @@ -144,10 +260,10 @@ func (h *HUOBI) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Bas } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (h *HUOBI) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (h *HUOBI) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := h.GetDepth(OrderBookDataRequestParams{ - Symbol: exchange.FormatExchangeCurrency(h.Name, p).String(), + Symbol: h.FormatExchangeCurrency(p, assetType).String(), Type: OrderBookDataRequestParamsTypeStep1, }) if err != nil { @@ -264,10 +380,8 @@ func (h *HUOBI) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (h *HUOBI) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (h *HUOBI) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -338,8 +452,9 @@ func (h *HUOBI) CancelAllOrders(orderCancellation *exchange.OrderCancellation) ( cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ OrderStatus: make(map[string]string), } - for _, currency := range h.GetEnabledCurrencies() { - resp, err := h.CancelOpenOrdersBatch(orderCancellation.AccountID, exchange.FormatExchangeCurrency(h.Name, currency).String()) + for _, currency := range h.GetEnabledPairs(assets.AssetTypeSpot) { + resp, err := h.CancelOpenOrdersBatch(orderCancellation.AccountID, + h.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String()) if err != nil { return cancelAllOrdersResponse, err } @@ -369,20 +484,20 @@ func (h *HUOBI) GetDepositAddress(cryptocurrency currency.Code, accountID string // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (h *HUOBI) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (h *HUOBI) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { resp, err := h.Withdraw(withdrawRequest.Currency, withdrawRequest.Address, withdrawRequest.AddressTag, withdrawRequest.Amount, withdrawRequest.FeeAmount) return fmt.Sprintf("%v", resp), err } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (h *HUOBI) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (h *HUOBI) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (h *HUOBI) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (h *HUOBI) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -393,7 +508,7 @@ func (h *HUOBI) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (h *HUOBI) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (h.APIKey == "" || h.APISecret == "") && // Todo check connection status + if !h.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -416,7 +531,8 @@ func (h *HUOBI) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] var orders []exchange.OrderDetail for _, c := range getOrdersRequest.Currencies { - resp, err := h.GetOpenOrders(h.ClientID, c.Lower().String(), side, 500) + resp, err := h.GetOpenOrders(h.API.Credentials.ClientID, + c.Lower().String(), side, 500) if err != nil { return nil, err } @@ -443,7 +559,6 @@ func (h *HUOBI) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - return orders, nil } @@ -491,7 +606,6 @@ func (h *HUOBI) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - return orders, nil } diff --git a/exchanges/huobihadax/README.md b/exchanges/huobihadax/README.md index 09ca5c0a..4dfc84aa 100644 --- a/exchanges/huobihadax/README.md +++ b/exchanges/huobihadax/README.md @@ -47,22 +47,22 @@ main.go ```go var h exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "HuobiHadax" { - h = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "HuobiHadax" { + h = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := h.GetTickerPrice() +tick, err := h.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := h.GetOrderbookEx() +ob, err := h.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/huobihadax/huobihadax.go b/exchanges/huobihadax/huobihadax.go index 7c3333c8..691b7532 100644 --- a/exchanges/huobihadax/huobihadax.go +++ b/exchanges/huobihadax/huobihadax.go @@ -13,12 +13,9 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -67,88 +64,6 @@ type HUOBIHADAX struct { wsRequestMtx sync.Mutex } -// SetDefaults sets default values for the exchange -func (h *HUOBIHADAX) SetDefaults() { - h.Name = "HuobiHadax" - h.Enabled = false - h.Fee = 0 - h.Verbose = false - h.RESTPollingDelay = 10 - h.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithSetup | - exchange.NoFiatWithdrawals - h.RequestCurrencyPairFormat.Delimiter = "" - h.RequestCurrencyPairFormat.Uppercase = false - h.ConfigCurrencyPairFormat.Delimiter = "-" - h.ConfigCurrencyPairFormat.Uppercase = true - h.AssetTypes = []string{ticker.Spot} - h.SupportsAutoPairUpdating = true - h.SupportsRESTTickerBatching = false - h.Requester = request.New(h.Name, - request.NewRateLimit(time.Second*10, huobihadaxAuthRate), - request.NewRateLimit(time.Second*10, huobihadaxUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - h.APIUrlDefault = huobihadaxAPIURL - h.APIUrl = h.APIUrlDefault - h.WebsocketInit() - h.Websocket.Functionality = exchange.WebsocketKlineSupported | - exchange.WebsocketTradeDataSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup sets user configuration -func (h *HUOBIHADAX) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - h.SetEnabled(false) - } else { - h.Enabled = true - h.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - h.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - h.APIAuthPEMKeySupport = exch.APIAuthPEMKeySupport - h.APIAuthPEMKey = exch.APIAuthPEMKey - h.SetHTTPClientTimeout(exch.HTTPTimeout) - h.SetHTTPClientUserAgent(exch.HTTPUserAgent) - h.RESTPollingDelay = exch.RESTPollingDelay - h.Verbose = exch.Verbose - h.HTTPDebugging = exch.HTTPDebugging - h.BaseCurrencies = exch.BaseCurrencies - h.AvailablePairs = exch.AvailablePairs - h.EnabledPairs = exch.EnabledPairs - err := h.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = h.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = h.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = h.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = h.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = h.WebsocketSetup(h.WsConnect, - h.Subscribe, - h.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - huobiGlobalWebsocketEndpoint, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetSpotKline returns kline data // KlinesRequestParams holds the Kline request params func (h *HUOBIHADAX) GetSpotKline(arg KlinesRequestParams) ([]KlineItem, error) { @@ -166,7 +81,7 @@ func (h *HUOBIHADAX) GetSpotKline(arg KlinesRequestParams) ([]KlineItem, error) } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobihadaxMarketHistoryKline) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobihadaxMarketHistoryKline) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -186,7 +101,7 @@ func (h *HUOBIHADAX) GetMarketDetailMerged(symbol string) (DetailMerged, error) } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobihadaxMarketDetailMerged) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobihadaxMarketDetailMerged) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -210,7 +125,7 @@ func (h *HUOBIHADAX) GetDepth(symbol, depthType string) (Orderbook, error) { } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobihadaxMarketDepth) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobihadaxMarketDepth) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -232,7 +147,7 @@ func (h *HUOBIHADAX) GetTrades(symbol string) ([]Trade, error) { } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobihadaxMarketTrade) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobihadaxMarketTrade) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -272,7 +187,7 @@ func (h *HUOBIHADAX) GetTradeHistory(symbol, size string) ([]TradeHistory, error } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobihadaxMarketTradeHistory) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobihadaxMarketTradeHistory) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -292,7 +207,7 @@ func (h *HUOBIHADAX) GetMarketDetail(symbol string) (Detail, error) { } var result response - urlPath := fmt.Sprintf("%s/%s", h.APIUrl, huobihadaxMarketDetail) + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobihadaxMarketDetail) err := h.SendHTTPRequest(common.EncodeURLValues(urlPath, vals), &result) if result.ErrorMessage != "" { @@ -309,7 +224,7 @@ func (h *HUOBIHADAX) GetSymbols() ([]Symbol, error) { } var result response - urlPath := fmt.Sprintf("%s/v%s/%s/%s", h.APIUrl, huobihadaxAPIVersion, huobihadaxAPIName, huobihadaxSymbols) + urlPath := fmt.Sprintf("%s/v%s/%s/%s", h.API.Endpoints.URL, huobihadaxAPIVersion, huobihadaxAPIName, huobihadaxSymbols) err := h.SendHTTPRequest(urlPath, &result) if result.ErrorMessage != "" { @@ -326,7 +241,7 @@ func (h *HUOBIHADAX) GetCurrencies() ([]string, error) { } var result response - urlPath := fmt.Sprintf("%s/v%s/%s/%s", h.APIUrl, huobihadaxAPIVersion, huobihadaxAPIName, huobihadaxCurrencies) + urlPath := fmt.Sprintf("%s/v%s/%s/%s", h.API.Endpoints.URL, huobihadaxAPIVersion, huobihadaxAPIName, huobihadaxCurrencies) err := h.SendHTTPRequest(urlPath, &result) if result.ErrorMessage != "" { @@ -343,7 +258,7 @@ func (h *HUOBIHADAX) GetTimestamp() (int64, error) { } var result response - urlPath := fmt.Sprintf("%s/v%s/%s", h.APIUrl, huobihadaxAPIVersion, huobihadaxTimestamp) + urlPath := fmt.Sprintf("%s/v%s/%s", h.API.Endpoints.URL, huobihadaxAPIVersion, huobihadaxTimestamp) err := h.SendHTTPRequest(urlPath, &result) if result.ErrorMessage != "" { @@ -839,12 +754,12 @@ func (h *HUOBIHADAX) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPPostRequest sends authenticated requests to the HUOBI API func (h *HUOBIHADAX) SendAuthenticatedHTTPPostRequest(method, endpoint, postBodyValues string, result interface{}) error { - if !h.AuthenticatedAPISupport { + if !h.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name) } signatureParams := url.Values{} - signatureParams.Set("AccessKeyId", h.APIKey) + signatureParams.Set("AccessKeyId", h.API.Credentials.Key) signatureParams.Set("SignatureMethod", "HmacSHA256") signatureParams.Set("SignatureVersion", "2") signatureParams.Set("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05")) @@ -857,20 +772,21 @@ func (h *HUOBIHADAX) SendAuthenticatedHTTPPostRequest(method, endpoint, postBody headers["Content-Type"] = "application/json" headers["Accept-Language"] = "zh-cn" - hmac := common.GetHMAC(common.HashSHA256, []byte(payload), []byte(h.APISecret)) - signatureParams.Set("Signature", common.Base64Encode(hmac)) - urlPath := common.EncodeURLValues(fmt.Sprintf("%s%s", h.APIUrl, endpoint), + hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(h.API.Credentials.Secret)) + signatureParams.Set("Signature", crypto.Base64Encode(hmac)) + + urlPath := common.EncodeURLValues(fmt.Sprintf("%s%s", h.API.Endpoints.URL, endpoint), signatureParams) return h.SendPayload(method, urlPath, headers, bytes.NewBufferString(postBodyValues), result, true, false, h.Verbose, h.HTTPDebugging) } // SendAuthenticatedHTTPRequest sends authenticated requests to the HUOBI API func (h *HUOBIHADAX) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error { - if !h.AuthenticatedAPISupport { + if !h.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name) } - values.Set("AccessKeyId", h.APIKey) + values.Set("AccessKeyId", h.API.Credentials.Key) values.Set("SignatureMethod", "HmacSHA256") values.Set("SignatureVersion", "2") values.Set("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05")) @@ -882,10 +798,10 @@ func (h *HUOBIHADAX) SendAuthenticatedHTTPRequest(method, endpoint string, value headers := make(map[string]string) headers["Content-Type"] = "application/x-www-form-urlencoded" - hmac := common.GetHMAC(common.HashSHA256, []byte(payload), []byte(h.APISecret)) - values.Set("Signature", common.Base64Encode(hmac)) + hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(h.API.Credentials.Secret)) + values.Set("Signature", crypto.Base64Encode(hmac)) - urlPath := common.EncodeURLValues(fmt.Sprintf("%s%s", h.APIUrl, endpoint), + urlPath := common.EncodeURLValues(fmt.Sprintf("%s%s", h.API.Endpoints.URL, endpoint), values) return h.SendPayload(method, urlPath, headers, bytes.NewBufferString(""), result, true, false, h.Verbose, h.HTTPDebugging) } diff --git a/exchanges/huobihadax/huobihadax_test.go b/exchanges/huobihadax/huobihadax_test.go index 505dfaf6..248dd3ad 100644 --- a/exchanges/huobihadax/huobihadax_test.go +++ b/exchanges/huobihadax/huobihadax_test.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) // Please supply your own APIKEYS here for due diligence testing @@ -21,33 +22,58 @@ const ( var h HUOBIHADAX -// getDefaultConfig returns a default huobi config +// getDefaultConfig returns a default hadax config func getDefaultConfig() config.ExchangeConfig { - return config.ExchangeConfig{ - Name: "huobihadax", - Enabled: true, - Verbose: true, - Websocket: false, - UseSandbox: false, - RESTPollingDelay: 10, - HTTPTimeout: 15000000000, - AuthenticatedAPISupport: true, - APIKey: "", - APISecret: "", - ClientID: "", - AvailablePairs: currency.NewPairsFromStrings([]string{"BTC-USDT", "BCH-USDT"}), - EnabledPairs: currency.NewPairsFromStrings([]string{"BTC-USDT"}), - BaseCurrencies: currency.NewCurrenciesFromStringArray([]string{"USD"}), - AssetTypes: "SPOT", - SupportsAutoPairUpdates: false, - ConfigCurrencyPairFormat: &config.CurrencyPairFormatConfig{ - Uppercase: true, - Delimiter: "-", + exchCfg := config.ExchangeConfig{ + Name: "Huobi", + Enabled: true, + Verbose: true, + + Features: &config.FeaturesConfig{ + Supports: config.FeaturesSupportedConfig{ + REST: true, + Websocket: false, + + RESTCapabilities: config.ProtocolFeaturesConfig{ + AutoPairUpdates: false, + }, + }, + Enabled: config.FeaturesEnabledConfig{ + AutoPairUpdates: false, + Websocket: false, + }, }, - RequestCurrencyPairFormat: &config.CurrencyPairFormatConfig{ - Uppercase: false, + + API: config.APIConfig{ + AuthenticatedSupport: false, }, + + HTTPTimeout: 15000000000, + CurrencyPairs: ¤cy.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + }, + }, + + BaseCurrencies: currency.NewCurrenciesFromStringArray([]string{"USD"}), } + + exchCfg.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTC-USDT", "BCH-USDT"}), + false) + exchCfg.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + currency.NewPairsFromStrings([]string{"BTC-USDT"}), + true) + + return exchCfg } func TestSetDefaults(t *testing.T) { @@ -61,11 +87,11 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - HuobiHadax Setup() init error") } - hadaxConfig.AuthenticatedAPISupport = true - hadaxConfig.APIKey = apiKey - hadaxConfig.APISecret = apiSecret + hadaxConfig.API.AuthenticatedSupport = true + hadaxConfig.API.Credentials.Key = apiKey + hadaxConfig.API.Credentials.Secret = apiSecret - h.Setup(&hadaxConfig) + h.Setup(hadaxConfig) } func TestGetSpotKline(t *testing.T) { @@ -155,7 +181,7 @@ func TestGetTimestamp(t *testing.T) { func TestGetAccounts(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -168,7 +194,7 @@ func TestGetAccounts(t *testing.T) { func TestGetAccountBalance(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -187,7 +213,7 @@ func TestGetAccountBalance(t *testing.T) { func TestSpotNewOrder(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -210,7 +236,7 @@ func TestSpotNewOrder(t *testing.T) { func TestCancelExistingOrder(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -223,7 +249,7 @@ func TestCancelExistingOrder(t *testing.T) { func TestGetOrder(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -236,7 +262,7 @@ func TestGetOrder(t *testing.T) { func TestGetMarginLoanOrders(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -249,7 +275,7 @@ func TestGetMarginLoanOrders(t *testing.T) { func TestGetMarginAccountBalance(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -262,7 +288,7 @@ func TestGetMarginAccountBalance(t *testing.T) { func TestCancelWithdraw(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } @@ -424,11 +450,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if h.APIKey != "" && h.APIKey != "Key" && - h.APISecret != "" && h.APISecret != "Secret" { - return true - } - return false + return h.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -439,8 +461,7 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - if (h.APIKey == "" || h.APIKey == "Key") && - (h.APISecret == "" || h.APISecret == "Secret") { + if !h.ValidateAPICredentials() { t.Skip() } @@ -452,14 +473,12 @@ func TestSubmitOrder(t *testing.T) { accounts, err := h.GetAccounts() if err != nil { - t.Errorf("Failed to get accounts. Err: %s", err) + t.Fatalf("Failed to get accounts. Err: %s", err) } response, err := h.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, strconv.FormatInt(accounts[0].ID, 10)) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") } } @@ -544,11 +563,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { h.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -572,8 +593,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := h.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -588,8 +608,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := h.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/huobihadax/huobihadax_websocket.go b/exchanges/huobihadax/huobihadax_websocket.go index b3fcdbe5..75b03a7e 100644 --- a/exchanges/huobihadax/huobihadax_websocket.go +++ b/exchanges/huobihadax/huobihadax_websocket.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -153,7 +154,7 @@ func (h *HUOBIHADAX) WsHandleData() { h.Websocket.DataHandler <- exchange.KlineData{ Timestamp: time.Unix(0, kline.Timestamp), Exchange: h.GetName(), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Pair: currency.NewPairFromString(data[1]), OpenPrice: kline.Tick.Open, ClosePrice: kline.Tick.Close, @@ -174,7 +175,7 @@ func (h *HUOBIHADAX) WsHandleData() { h.Websocket.DataHandler <- exchange.TradeData{ Exchange: h.GetName(), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, CurrencyPair: currency.NewPairFromString(data[1]), Timestamp: time.Unix(0, trade.Tick.Timestamp), } @@ -214,7 +215,7 @@ func (h *HUOBIHADAX) WsProcessOrderbook(ob *WsDepth, symbol string) error { h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p, Exchange: h.GetName(), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, } return nil @@ -223,7 +224,7 @@ func (h *HUOBIHADAX) WsProcessOrderbook(ob *WsDepth, symbol string) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (h *HUOBIHADAX) GenerateDefaultSubscriptions() { var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade} - enabledCurrencies := h.GetEnabledCurrencies() + enabledCurrencies := h.GetEnabledPairs(assets.AssetTypeSpot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { for j := range enabledCurrencies { diff --git a/exchanges/huobihadax/huobihadax_wrapper.go b/exchanges/huobihadax/huobihadax_wrapper.go index 190ac046..ddcc481b 100644 --- a/exchanges/huobihadax/huobihadax_wrapper.go +++ b/exchanges/huobihadax/huobihadax_wrapper.go @@ -9,14 +9,116 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) -// Start starts the OKEX go routine +// GetDefaultConfig returns a default exchange config +func (h *HUOBIHADAX) GetDefaultConfig() (*config.ExchangeConfig, error) { + h.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = h.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = h.BaseCurrencies + + err := h.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if h.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = h.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets default values for the exchange +func (h *HUOBIHADAX) SetDefaults() { + h.Name = "HuobiHadax" + h.Enabled = true + h.Verbose = true + h.API.CredentialsValidator.RequiresKey = true + h.API.CredentialsValidator.RequiresSecret = true + + h.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + } + + h.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + h.Requester = request.New(h.Name, + request.NewRateLimit(time.Second*10, huobihadaxAuthRate), + request.NewRateLimit(time.Second*10, huobihadaxUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + h.API.Endpoints.URLDefault = huobihadaxAPIURL + h.API.Endpoints.URL = h.API.Endpoints.URLDefault + h.WebsocketInit() + h.Websocket.Functionality = exchange.WebsocketKlineSupported | + exchange.WebsocketTradeDataSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup sets user configuration +func (h *HUOBIHADAX) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + h.SetEnabled(false) + return nil + } + + err := h.SetupDefaults(exch) + if err != nil { + return err + } + + return h.WebsocketSetup(h.WsConnect, + h.Subscribe, + h.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + huobiGlobalWebsocketEndpoint, + exch.API.Endpoints.WebsocketURL) +} + +// Start starts the HUOBIHADAX go routine func (h *HUOBIHADAX) Start(wg *sync.WaitGroup) { wg.Add(1) go func() { @@ -25,37 +127,52 @@ func (h *HUOBIHADAX) Start(wg *sync.WaitGroup) { }() } -// Run implements the OKEX wrapper +// Run implements the HUOBIHADAX wrapper func (h *HUOBIHADAX) Run() { if h.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket.IsEnabled()), h.WebsocketURL) - log.Debugf("%s polling delay: %ds.\n", h.GetName(), h.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", h.GetName(), len(h.EnabledPairs), h.EnabledPairs) + h.PrintEnabledPairs() } - exchangeProducts, err := h.GetSymbols() - if err != nil { - log.Debugf("%s Failed to get available symbols.\n", h.GetName()) - } else { - var currencies currency.Pairs - for x := range exchangeProducts { - currencies = append(currencies, - currency.NewPairWithDelimiter(exchangeProducts[x].BaseCurrency, - exchangeProducts[x].QuoteCurrency, - "-")) - } + if !h.GetEnabledFeatures().AutoPairUpdates { + return + } - err = h.UpdateCurrencies(currencies, false, false) - if err != nil { - log.Debugf("%s Failed to update available currencies.\n", h.GetName()) - } + err := h.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", h.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (h *HUOBIHADAX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + symbols, err := h.GetSymbols() + if err != nil { + return nil, err + } + + var pairs []string + for x := range symbols { + pairs = append(pairs, symbols[x].BaseCurrency+"-"+symbols[x].QuoteCurrency) + } + + return pairs, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (h *HUOBIHADAX) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := h.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return h.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (h *HUOBIHADAX) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (h *HUOBIHADAX) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - tick, err := h.GetMarketDetailMerged(exchange.FormatExchangeCurrency(h.Name, p).String()) + tick, err := h.GetMarketDetailMerged(h.FormatExchangeCurrency(p, assetType).String()) if err != nil { return tickerPrice, err } @@ -82,8 +199,8 @@ func (h *HUOBIHADAX) UpdateTicker(p currency.Pair, assetType string) (ticker.Pri return ticker.GetTicker(h.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (h *HUOBIHADAX) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (h *HUOBIHADAX) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType) if err != nil { return h.UpdateTicker(p, assetType) @@ -91,8 +208,8 @@ func (h *HUOBIHADAX) GetTickerPrice(p currency.Pair, assetType string) (ticker.P return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (h *HUOBIHADAX) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (h *HUOBIHADAX) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(h.GetName(), p, assetType) if err != nil { return h.UpdateOrderbook(p, assetType) @@ -101,9 +218,9 @@ func (h *HUOBIHADAX) GetOrderbookEx(p currency.Pair, assetType string) (orderboo } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (h *HUOBIHADAX) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (h *HUOBIHADAX) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := h.GetDepth(exchange.FormatExchangeCurrency(h.Name, p).String(), "step1") + orderbookNew, err := h.GetDepth(h.FormatExchangeCurrency(p, assetType).String(), "step1") if err != nil { return orderBook, err } @@ -218,10 +335,8 @@ func (h *HUOBIHADAX) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (h *HUOBIHADAX) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (h *HUOBIHADAX) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -294,8 +409,8 @@ func (h *HUOBIHADAX) CancelAllOrders(orderCancellation *exchange.OrderCancellati cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ OrderStatus: make(map[string]string), } - for _, currency := range h.GetEnabledCurrencies() { - resp, err := h.CancelOpenOrdersBatch(orderCancellation.AccountID, exchange.FormatExchangeCurrency(h.Name, currency).String()) + for _, currency := range h.GetEnabledPairs(assets.AssetTypeSpot) { + resp, err := h.CancelOpenOrdersBatch(orderCancellation.AccountID, h.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String()) if err != nil { return cancelAllOrdersResponse, err } @@ -325,20 +440,20 @@ func (h *HUOBIHADAX) GetDepositAddress(cryptocurrency currency.Code, accountID s // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (h *HUOBIHADAX) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (h *HUOBIHADAX) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { resp, err := h.Withdraw(withdrawRequest.Currency, withdrawRequest.Address, withdrawRequest.AddressTag, withdrawRequest.Amount, withdrawRequest.FeeAmount) return fmt.Sprintf("%v", resp), err } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (h *HUOBIHADAX) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (h *HUOBIHADAX) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (h *HUOBIHADAX) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (h *HUOBIHADAX) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -349,7 +464,7 @@ func (h *HUOBIHADAX) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (h *HUOBIHADAX) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (h.APIKey == "" || h.APISecret == "") && // Todo check connection status + if !h.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -371,7 +486,7 @@ func (h *HUOBIHADAX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest var allOrders []OrderInfo for _, currency := range getOrdersRequest.Currencies { - resp, err := h.GetOpenOrders(h.ClientID, + resp, err := h.GetOpenOrders(h.API.Credentials.ClientID, currency.Lower().String(), side, 500) @@ -384,7 +499,7 @@ func (h *HUOBIHADAX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.ConfigCurrencyPairFormat.Delimiter) + h.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderDate := time.Unix(0, allOrders[i].CreatedAt*int64(time.Millisecond)) orders = append(orders, exchange.OrderDetail{ @@ -398,10 +513,8 @@ func (h *HUOBIHADAX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest CurrencyPair: symbol, }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - return orders, nil } @@ -432,7 +545,7 @@ func (h *HUOBIHADAX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.ConfigCurrencyPairFormat.Delimiter) + h.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderDate := time.Unix(0, allOrders[i].CreatedAt*int64(time.Millisecond)) orders = append(orders, exchange.OrderDetail{ @@ -447,7 +560,6 @@ func (h *HUOBIHADAX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - return orders, nil } diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go new file mode 100644 index 00000000..4723ab5a --- /dev/null +++ b/exchanges/interfaces.go @@ -0,0 +1,64 @@ +package exchange + +import ( + "sync" + + "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +// IBotExchange enforces standard functions for all exchanges supported in +// GoCryptoTrader +type IBotExchange interface { + Setup(exch *config.ExchangeConfig) error + Start(wg *sync.WaitGroup) + SetDefaults() + GetName() string + IsEnabled() bool + SetEnabled(bool) + FetchTicker(currency currency.Pair, assetType assets.AssetType) (ticker.Price, error) + UpdateTicker(currency currency.Pair, assetType assets.AssetType) (ticker.Price, error) + FetchOrderbook(currency currency.Pair, assetType assets.AssetType) (orderbook.Base, error) + UpdateOrderbook(currency currency.Pair, assetType assets.AssetType) (orderbook.Base, error) + FetchTradablePairs(assetType assets.AssetType) ([]string, error) + UpdateTradablePairs(forceUpdate bool) error + GetEnabledPairs(assetType assets.AssetType) currency.Pairs + GetAvailablePairs(assetType assets.AssetType) currency.Pairs + GetAccountInfo() (AccountInfo, error) + GetAuthenticatedAPISupport() bool + SetPairs(pairs currency.Pairs, assetType assets.AssetType, enabled bool) error + GetAssetTypes() assets.AssetTypes + GetExchangeHistory(currencyPair currency.Pair, assetType assets.AssetType) ([]TradeHistory, error) + SupportsAutoPairUpdates() bool + SupportsRESTTickerBatchUpdates() bool + GetFeeByType(feeBuilder *FeeBuilder) (float64, error) + GetLastPairsUpdateTime() int64 + GetWithdrawPermissions() uint32 + FormatWithdrawPermissions() string + SupportsWithdrawPermissions(permissions uint32) bool + GetFundingHistory() ([]FundHistory, error) + SubmitOrder(p currency.Pair, side OrderSide, orderType OrderType, amount, price float64, clientID string) (SubmitOrderResponse, error) + ModifyOrder(action *ModifyOrder) (string, error) + CancelOrder(order *OrderCancellation) error + CancelAllOrders(orders *OrderCancellation) (CancelAllOrdersResponse, error) + GetOrderInfo(orderID string) (OrderDetail, error) + GetDepositAddress(cryptocurrency currency.Code, accountID string) (string, error) + GetOrderHistory(getOrdersRequest *GetOrdersRequest) ([]OrderDetail, error) + GetActiveOrders(getOrdersRequest *GetOrdersRequest) ([]OrderDetail, error) + WithdrawCryptocurrencyFunds(withdrawRequest *CryptoWithdrawRequest) (string, error) + WithdrawFiatFunds(withdrawRequest *FiatWithdrawRequest) (string, error) + WithdrawFiatFundsToInternationalBank(withdrawRequest *FiatWithdrawRequest) (string, error) + SetHTTPClientUserAgent(ua string) + GetHTTPClientUserAgent() string + SetClientProxyAddress(addr string) error + SupportsWebsocket() bool + SupportsREST() bool + IsWebsocketEnabled() bool + GetWebsocket() (*Websocket, error) + SubscribeToWebsocketChannels(channels []WebsocketChannelSubscription) error + UnsubscribeToWebsocketChannels(channels []WebsocketChannelSubscription) error + GetDefaultConfig() (*config.ExchangeConfig, error) +} diff --git a/exchanges/itbit/README.md b/exchanges/itbit/README.md index fa954149..65a6138a 100644 --- a/exchanges/itbit/README.md +++ b/exchanges/itbit/README.md @@ -47,22 +47,22 @@ main.go ```go var i exchange.IBotExchange -for x := range bot.exchanges { - if bot.exchanges[x].GetName() == "Itbit" { - i = bot.exchanges[x] +for x := range bot.Exchanges { + if bot.Exchanges[x].GetName() == "Itbit" { + i = bot.Exchanges[x] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := i.GetTickerPrice() +tick, err := i.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := i.GetOrderbookEx() +ob, err := i.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index d1bbf83f..a0a5d707 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -11,11 +11,9 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -42,76 +40,11 @@ type ItBit struct { exchange.Base } -// SetDefaults sets the defaults for the exchange -func (i *ItBit) SetDefaults() { - i.Name = "ITBIT" - i.Enabled = false - i.MakerFee = -0.10 - i.TakerFee = 0.50 - i.Verbose = false - i.RESTPollingDelay = 10 - i.APIWithdrawPermissions = exchange.WithdrawCryptoViaWebsiteOnly | - exchange.WithdrawFiatViaWebsiteOnly - i.RequestCurrencyPairFormat.Delimiter = "" - i.RequestCurrencyPairFormat.Uppercase = true - i.ConfigCurrencyPairFormat.Delimiter = "" - i.ConfigCurrencyPairFormat.Uppercase = true - i.AssetTypes = []string{ticker.Spot} - i.SupportsAutoPairUpdating = false - i.SupportsRESTTickerBatching = false - i.Requester = request.New(i.Name, - request.NewRateLimit(time.Second, itbitAuthRate), - request.NewRateLimit(time.Second, itbitUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - i.APIUrlDefault = itbitAPIURL - i.APIUrl = i.APIUrlDefault - i.WebsocketInit() -} - -// Setup sets the exchange parameters from exchange config -func (i *ItBit) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - i.SetEnabled(false) - } else { - i.Enabled = true - i.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - i.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, false) - i.SetHTTPClientTimeout(exch.HTTPTimeout) - i.SetHTTPClientUserAgent(exch.HTTPUserAgent) - i.RESTPollingDelay = exch.RESTPollingDelay - i.Verbose = exch.Verbose - i.HTTPDebugging = exch.HTTPDebugging - i.BaseCurrencies = exch.BaseCurrencies - i.AvailablePairs = exch.AvailablePairs - i.EnabledPairs = exch.EnabledPairs - err := i.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = i.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = i.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = i.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = i.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - } -} - // 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", i.APIUrl, itbitMarkets, currencyPair, itbitTicker) + path := fmt.Sprintf("%s/%s/%s/%s", i.API.Endpoints.URL, itbitMarkets, currencyPair, itbitTicker) return response, i.SendHTTPRequest(path, &response) } @@ -120,7 +53,7 @@ func (i *ItBit) GetTicker(currencyPair string) (Ticker, error) { // currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" func (i *ItBit) GetOrderbook(currencyPair string) (OrderbookResponse, error) { response := OrderbookResponse{} - path := fmt.Sprintf("%s/%s/%s/%s", i.APIUrl, itbitMarkets, currencyPair, itbitOrderbook) + path := fmt.Sprintf("%s/%s/%s/%s", i.API.Endpoints.URL, itbitMarkets, currencyPair, itbitOrderbook) return response, i.SendHTTPRequest(path, &response) } @@ -132,7 +65,7 @@ func (i *ItBit) GetOrderbook(currencyPair string) (OrderbookResponse, error) { func (i *ItBit) GetTradeHistory(currencyPair, timestamp string) (Trades, error) { response := Trades{} req := "trades?since=" + timestamp - path := fmt.Sprintf("%s/%s/%s/%s", i.APIUrl, itbitMarkets, currencyPair, req) + path := fmt.Sprintf("%s/%s/%s/%s", i.API.Endpoints.URL, itbitMarkets, currencyPair, req) return response, i.SendHTTPRequest(path, &response) } @@ -144,9 +77,8 @@ func (i *ItBit) GetTradeHistory(currencyPair, timestamp string) (Trades, error) // perPage - [optional] items per page example 50, default 50 max 50 func (i *ItBit) GetWallets(params url.Values) ([]Wallet, error) { var resp []Wallet - params.Set("userId", i.ClientID) + params.Set("userId", i.API.Credentials.ClientID) path := fmt.Sprintf("/%s?%s", itbitWallets, params.Encode()) - return resp, i.SendAuthenticatedHTTPRequest(http.MethodGet, path, nil, &resp) } @@ -154,7 +86,7 @@ func (i *ItBit) GetWallets(params url.Values) ([]Wallet, error) { func (i *ItBit) CreateWallet(walletName string) (Wallet, error) { resp := Wallet{} params := make(map[string]interface{}) - params["userId"] = i.ClientID + params["userId"] = i.API.Credentials.ClientID params["name"] = walletName err := i.SendAuthenticatedHTTPRequest(http.MethodPost, "/"+itbitWallets, params, &resp) @@ -349,16 +281,12 @@ func (i *ItBit) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated request to itBit func (i *ItBit) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) error { - if !i.AuthenticatedAPISupport { + if !i.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, i.Name) } - if i.ClientID == "" { - return errors.New("client ID not set") - } - req := make(map[string]interface{}) - urlPath := i.APIUrl + path + urlPath := i.API.Endpoints.URL + path for key, value := range params { req[key] = value @@ -380,18 +308,17 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method, path string, params map[str n := i.Requester.GetNonce(true).String() timestamp := strconv.FormatInt(time.Now().UnixNano()/1000000, 10) - message, err := common.JSONEncode([]string{method, urlPath, string(PayloadJSON), n, timestamp}) if err != nil { return err } - hash := common.GetSHA256([]byte(n + string(message))) - hmac := common.GetHMAC(common.HashSHA512, []byte(urlPath+string(hash)), []byte(i.APISecret)) - signature := common.Base64Encode(hmac) + hash := crypto.GetSHA256([]byte(n + string(message))) + hmac := crypto.GetHMAC(crypto.HashSHA512, []byte(urlPath+string(hash)), []byte(i.API.Credentials.Secret)) + signature := crypto.Base64Encode(hmac) headers := make(map[string]string) - headers["Authorization"] = i.ClientID + ":" + signature + headers["Authorization"] = i.API.Credentials.ClientID + ":" + signature headers["X-Auth-Timestamp"] = timestamp headers["X-Auth-Nonce"] = n headers["Content-Type"] = "application/json" diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index 93f5dd26..0c76ab13 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -31,12 +31,12 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Gemini Setup() init error") } - itbitConfig.AuthenticatedAPISupport = true - itbitConfig.APIKey = apiKey - itbitConfig.APISecret = apiSecret - itbitConfig.ClientID = clientID + itbitConfig.API.AuthenticatedSupport = true + itbitConfig.API.Credentials.Key = apiKey + itbitConfig.API.Credentials.Secret = apiSecret + itbitConfig.API.Credentials.ClientID = clientID - i.Setup(&itbitConfig) + i.Setup(itbitConfig) } func TestGetTicker(t *testing.T) { @@ -106,7 +106,9 @@ func TestGetFundingHistory(t *testing.T) { } func TestPlaceOrder(t *testing.T) { - _, err := i.PlaceOrder("1337", "buy", "limit", "USD", 1, 0.2, "banjo", "sauce") + _, err := i.PlaceOrder("1337", exchange.BuyOrderSide.ToLower().ToString(), + exchange.LimitOrderType.ToLower().ToString(), "USD", 1, 0.2, "banjo", + "sauce") if err == nil { t.Error("Test Failed - PlaceOrder() error", err) } @@ -291,11 +293,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if i.APIKey != "" && i.APIKey != "Key" && - i.APISecret != "" && i.APISecret != "Secret" { - return true - } - return false + return i.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -395,11 +393,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { i.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -420,8 +420,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := i.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -436,8 +435,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := i.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 8af6facb..328a8126 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -9,13 +9,95 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (i *ItBit) GetDefaultConfig() (*config.ExchangeConfig, error) { + i.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = i.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = i.BaseCurrencies + + err := i.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if i.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = i.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets the defaults for the exchange +func (i *ItBit) SetDefaults() { + i.Name = "ITBIT" + i.Enabled = true + i.Verbose = true + i.API.CredentialsValidator.RequiresClientID = true + i.API.CredentialsValidator.RequiresSecret = true + + i.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + + i.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: false, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: false, + TickerBatching: false, + }, + WithdrawPermissions: exchange.WithdrawCryptoViaWebsiteOnly | + exchange.WithdrawFiatViaWebsiteOnly, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: false, + }, + } + + i.Requester = request.New(i.Name, + request.NewRateLimit(time.Second, itbitAuthRate), + request.NewRateLimit(time.Second, itbitUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + i.API.Endpoints.URLDefault = itbitAPIURL + i.API.Endpoints.URL = i.API.Endpoints.URLDefault +} + +// Setup sets the exchange parameters from exchange config +func (i *ItBit) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + i.SetEnabled(false) + return nil + } + + return i.SetupDefaults(exch) +} + // Start starts the ItBit go routine func (i *ItBit) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -28,16 +110,25 @@ func (i *ItBit) Start(wg *sync.WaitGroup) { // Run implements the ItBit wrapper func (i *ItBit) Run() { if i.Verbose { - log.Debugf("%s polling delay: %ds.\n", i.GetName(), i.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", i.GetName(), len(i.EnabledPairs), i.EnabledPairs) + i.PrintEnabledPairs() } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (i *ItBit) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + return nil, common.ErrFunctionNotSupported +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (i *ItBit) UpdateTradablePairs(forceUpdate bool) error { + return common.ErrFunctionNotSupported +} + // UpdateTicker updates and returns the ticker for a currency pair -func (i *ItBit) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (i *ItBit) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - tick, err := i.GetTicker(exchange.FormatExchangeCurrency(i.Name, - p).String()) + tick, err := i.GetTicker(i.FormatExchangeCurrency(p, assetType).String()) if err != nil { return tickerPrice, err } @@ -58,8 +149,8 @@ func (i *ItBit) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, e return ticker.GetTicker(i.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (i *ItBit) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (i *ItBit) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(i.GetName(), p, assetType) if err != nil { return i.UpdateTicker(p, assetType) @@ -67,8 +158,8 @@ func (i *ItBit) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (i *ItBit) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (i *ItBit) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(i.GetName(), p, assetType) if err != nil { return i.UpdateOrderbook(p, assetType) @@ -77,10 +168,9 @@ func (i *ItBit) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Bas } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := i.GetOrderbook(exchange.FormatExchangeCurrency(i.Name, - p).String()) + orderbookNew, err := i.GetOrderbook(i.FormatExchangeCurrency(p, assetType).String()) if err != nil { return orderBook, err } @@ -178,10 +268,8 @@ func (i *ItBit) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (i *ItBit) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (i *ItBit) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -278,19 +366,19 @@ func (i *ItBit) GetDepositAddress(cryptocurrency currency.Code, accountID string // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (i *ItBit) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (i *ItBit) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (i *ItBit) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (i *ItBit) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (i *ItBit) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (i *ItBit) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -301,7 +389,7 @@ func (i *ItBit) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (i *ItBit) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (i.APIKey == "" || i.APISecret == "") && // Todo check connection status + if !i.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -327,7 +415,7 @@ func (i *ItBit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] var orders []exchange.OrderDetail for j := range allOrders { symbol := currency.NewPairDelimiter(allOrders[j].Instrument, - i.ConfigCurrencyPairFormat.Delimiter) + i.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { @@ -351,7 +439,6 @@ func (i *ItBit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } @@ -379,7 +466,7 @@ func (i *ItBit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] } symbol := currency.NewPairDelimiter(allOrders[j].Instrument, - i.ConfigCurrencyPairFormat.Delimiter) + i.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { @@ -403,7 +490,6 @@ func (i *ItBit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } diff --git a/exchanges/kraken/README.md b/exchanges/kraken/README.md index ea094b9d..3891d934 100644 --- a/exchanges/kraken/README.md +++ b/exchanges/kraken/README.md @@ -47,22 +47,22 @@ main.go ```go var k exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Kraken" { - k = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Kraken" { + k = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := k.GetTickerPrice() +tick, err := k.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := k.GetOrderbookEx() +ob, err := k.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 0350a490..a6502e7a 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -8,15 +8,12 @@ import ( "strconv" "strings" "sync" - "time" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -58,102 +55,13 @@ const ( // Kraken is the overarching type across the alphapoint package type Kraken struct { exchange.Base - WebsocketConn *websocket.Conn - CryptoFee, FiatFee float64 - wsRequestMtx sync.Mutex -} - -// SetDefaults sets current default settings -func (k *Kraken) SetDefaults() { - k.Name = "Kraken" - k.Enabled = false - k.FiatFee = 0.35 - k.CryptoFee = 0.10 - k.Verbose = false - k.RESTPollingDelay = 10 - k.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithSetup | - exchange.WithdrawCryptoWith2FA | - exchange.AutoWithdrawFiatWithSetup | - exchange.WithdrawFiatWith2FA - k.RequestCurrencyPairFormat.Delimiter = "" - k.RequestCurrencyPairFormat.Uppercase = true - k.RequestCurrencyPairFormat.Separator = "," - k.ConfigCurrencyPairFormat.Delimiter = "-" - k.ConfigCurrencyPairFormat.Uppercase = true - k.AssetTypes = []string{ticker.Spot} - k.SupportsAutoPairUpdating = true - k.SupportsRESTTickerBatching = true - k.Requester = request.New(k.Name, - request.NewRateLimit(time.Second, krakenAuthRate), - request.NewRateLimit(time.Second, krakenUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - k.APIUrlDefault = krakenAPIURL - k.APIUrl = k.APIUrlDefault - k.WebsocketInit() - k.WebsocketURL = krakenWSURL - k.Websocket.Functionality = exchange.WebsocketTickerSupported | - exchange.WebsocketTradeDataSupported | - exchange.WebsocketKlineSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported - -} - -// Setup sets current exchange configuration -func (k *Kraken) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - k.SetEnabled(false) - } else { - k.Enabled = true - k.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - k.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - k.SetHTTPClientTimeout(exch.HTTPTimeout) - k.SetHTTPClientUserAgent(exch.HTTPUserAgent) - k.RESTPollingDelay = exch.RESTPollingDelay - k.Verbose = exch.Verbose - k.HTTPDebugging = exch.HTTPDebugging - k.Websocket.SetWsStatusAndConnection(exch.Websocket) - k.BaseCurrencies = exch.BaseCurrencies - k.AvailablePairs = exch.AvailablePairs - k.EnabledPairs = exch.EnabledPairs - err := k.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = k.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = k.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = k.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = k.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = k.WebsocketSetup(k.WsConnect, - k.Subscribe, - k.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - krakenWSURL, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } + WebsocketConn *websocket.Conn + wsRequestMtx sync.Mutex } // GetServerTime returns current server time func (k *Kraken) GetServerTime() (TimeResponse, error) { - path := fmt.Sprintf("%s/%s/public/%s", k.APIUrl, krakenAPIVersion, krakenServerTime) + path := fmt.Sprintf("%s/%s/public/%s", k.API.Endpoints.URL, krakenAPIVersion, krakenServerTime) var response struct { Error []string `json:"error"` @@ -169,7 +77,7 @@ func (k *Kraken) GetServerTime() (TimeResponse, error) { // GetAssets returns a full asset list func (k *Kraken) GetAssets() (map[string]Asset, error) { - path := fmt.Sprintf("%s/%s/public/%s", k.APIUrl, krakenAPIVersion, krakenAssets) + path := fmt.Sprintf("%s/%s/public/%s", k.API.Endpoints.URL, krakenAPIVersion, krakenAssets) var response struct { Error []string `json:"error"` @@ -185,7 +93,7 @@ func (k *Kraken) GetAssets() (map[string]Asset, error) { // GetAssetPairs returns a full asset pair list func (k *Kraken) GetAssetPairs() (map[string]AssetPairs, error) { - path := fmt.Sprintf("%s/%s/public/%s", k.APIUrl, krakenAPIVersion, krakenAssetPairs) + path := fmt.Sprintf("%s/%s/public/%s", k.API.Endpoints.URL, krakenAPIVersion, krakenAssetPairs) var response struct { Error []string `json:"error"` @@ -211,7 +119,7 @@ func (k *Kraken) GetTicker(symbol string) (Ticker, error) { } resp := Response{} - path := fmt.Sprintf("%s/%s/public/%s?%s", k.APIUrl, krakenAPIVersion, krakenTicker, values.Encode()) + path := fmt.Sprintf("%s/%s/public/%s?%s", k.API.Endpoints.URL, krakenAPIVersion, krakenTicker, values.Encode()) err := k.SendHTTPRequest(path, &resp) if err != nil { @@ -249,7 +157,7 @@ func (k *Kraken) GetTickers(pairList string) (Tickers, error) { } resp := Response{} - path := fmt.Sprintf("%s/%s/public/%s?%s", krakenAPIURL, krakenAPIVersion, krakenTicker, values.Encode()) + path := fmt.Sprintf("%s/%s/public/%s?%s", k.API.Endpoints.URL, krakenAPIVersion, krakenTicker, values.Encode()) err := k.SendHTTPRequest(path, &resp) if err != nil { @@ -291,7 +199,7 @@ func (k *Kraken) GetOHLC(symbol string) ([]OpenHighLowClose, error) { var OHLC []OpenHighLowClose var result Response - path := fmt.Sprintf("%s/%s/public/%s?%s", k.APIUrl, krakenAPIVersion, krakenOHLC, values.Encode()) + path := fmt.Sprintf("%s/%s/public/%s?%s", k.API.Endpoints.URL, krakenAPIVersion, krakenOHLC, values.Encode()) err := k.SendHTTPRequest(path, &result) if err != nil { @@ -337,14 +245,21 @@ func (k *Kraken) GetDepth(symbol string) (Orderbook, error) { var result interface{} var orderBook Orderbook - path := fmt.Sprintf("%s/%s/public/%s?%s", k.APIUrl, krakenAPIVersion, krakenDepth, values.Encode()) + path := fmt.Sprintf("%s/%s/public/%s?%s", k.API.Endpoints.URL, krakenAPIVersion, krakenDepth, values.Encode()) err := k.SendHTTPRequest(path, &result) if err != nil { return orderBook, err } + if result == nil { + return orderBook, fmt.Errorf("%s GetDepth result is nil", k.Name) + } + data := result.(map[string]interface{}) + if data["result"] == nil { + return orderBook, fmt.Errorf("%s GetDepth data[result] is nil", k.Name) + } orderbookData := data["result"].(map[string]interface{}) var bidsData []interface{} @@ -392,7 +307,7 @@ func (k *Kraken) GetTrades(symbol string) ([]RecentTrades, error) { var recentTrades []RecentTrades var result interface{} - path := fmt.Sprintf("%s/%s/public/%s?%s", k.APIUrl, krakenAPIVersion, krakenTrades, values.Encode()) + path := fmt.Sprintf("%s/%s/public/%s?%s", k.API.Endpoints.URL, krakenAPIVersion, krakenTrades, values.Encode()) err := k.SendHTTPRequest(path, &result) if err != nil { @@ -433,7 +348,7 @@ func (k *Kraken) GetSpread(symbol string) ([]Spread, error) { var peanutButter []Spread var response interface{} - path := fmt.Sprintf("%s/%s/public/%s?%s", k.APIUrl, krakenAPIVersion, krakenSpread, values.Encode()) + path := fmt.Sprintf("%s/%s/public/%s?%s", k.API.Endpoints.URL, krakenAPIVersion, krakenSpread, values.Encode()) err := k.SendHTTPRequest(path, &response) if err != nil { @@ -841,7 +756,7 @@ func (k *Kraken) AddOrder(symbol, side, orderType string, volume, price, price2, "volume": {strconv.FormatFloat(volume, 'f', -1, 64)}, } - if orderType == "limit" || price > 0 { + if orderType == exchange.LimitOrderType.ToLower().ToString() || price > 0 { params.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) } @@ -937,39 +852,32 @@ func (k *Kraken) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated HTTP request func (k *Kraken) SendAuthenticatedHTTPRequest(method string, params url.Values, result interface{}) (err error) { - if !k.AuthenticatedAPISupport { + if !k.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, k.Name) } path := fmt.Sprintf("/%s/private/%s", krakenAPIVersion, method) - n := k.Requester.GetNonce(true).String() - params.Set("nonce", n) - - secret, err := common.Base64Decode(k.APISecret) - if err != nil { - return err - } - + params.Set("nonce", k.Requester.GetNonce(true).String()) encoded := params.Encode() - shasum := common.GetSHA256([]byte(params.Get("nonce") + encoded)) - signature := common.Base64Encode(common.GetHMAC(common.HashSHA512, - append([]byte(path), shasum...), secret)) + shasum := crypto.GetSHA256([]byte(params.Get("nonce") + encoded)) + signature := crypto.Base64Encode(crypto.GetHMAC(crypto.HashSHA512, + append([]byte(path), shasum...), []byte(k.API.Credentials.Secret))) if k.Verbose { log.Debugf("Sending POST request to %s, path: %s, params: %s", - k.APIUrl, + k.API.Endpoints.URL, path, encoded) } headers := make(map[string]string) - headers["API-Key"] = k.APIKey + headers["API-Key"] = k.API.Credentials.Key headers["API-Sign"] = signature return k.SendPayload(http.MethodPost, - k.APIUrl+path, + k.API.Endpoints.URL+path, headers, strings.NewReader(encoded), result, diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 767a5e1c..4b255fbf 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -34,13 +34,14 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - kraken Setup() init error", err) } - krakenConfig.AuthenticatedAPISupport = true - krakenConfig.APIKey = apiKey - krakenConfig.APISecret = apiSecret - krakenConfig.ClientID = clientID - krakenConfig.WebsocketURL = k.WebsocketURL + krakenConfig.API.AuthenticatedSupport = true + krakenConfig.API.Credentials.Key = apiKey + krakenConfig.API.Credentials.Secret = apiSecret + krakenConfig.API.Credentials.ClientID = clientID + krakenConfig.API.Endpoints.WebsocketURL = k.API.Endpoints.WebsocketURL subscribeToDefaultChannels = false - k.Setup(&krakenConfig) + + k.Setup(krakenConfig) } // TestGetServerTime API endpoint test @@ -233,7 +234,9 @@ func TestGetTradeVolume(t *testing.T) { func TestAddOrder(t *testing.T) { t.Parallel() args := AddOrderOptions{Oflags: "fcib"} - _, err := k.AddOrder("XXBTZUSD", "sell", "market", 0.00000001, 0, 0, 0, &args) + _, err := k.AddOrder("XXBTZUSD", + exchange.SellOrderSide.ToLower().ToString(), exchange.LimitOrderType.ToLower().ToString(), + 0.00000001, 0, 0, 0, &args) if err == nil { t.Error("Test Failed - AddOrder() error", err) } @@ -407,11 +410,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if k.APIKey != "" && k.APIKey != "Key" && - k.APISecret != "" && k.APISecret != "Secret" { - return true - } - return false + return k.ValidateAPICredentials() } // TestSubmitOrder wrapper test @@ -428,7 +427,7 @@ func TestSubmitOrder(t *testing.T) { Base: currency.XBT, Quote: currency.CAD, } - response, err := k.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") + response, err := k.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -522,12 +521,15 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { k.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.XXBT, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "donation", - TradePassword: "Key", + + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.XXBT, + Description: "WITHDRAW IT ALL", + TradePassword: "Key", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -552,12 +554,13 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.EUR, - Address: "", - Description: "donation", - TradePassword: "someBank", + var withdrawFiatRequest = exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.EUR, + Description: "WITHDRAW IT ALL", + TradePassword: "someBank", + }, } _, err := k.WithdrawFiatFunds(&withdrawFiatRequest) @@ -578,12 +581,13 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.EUR, - Address: "", - Description: "donation", - TradePassword: "someBank", + var withdrawFiatRequest = exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.EUR, + Description: "WITHDRAW IT ALL", + TradePassword: "someBank", + }, } _, err := k.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index f064cd65..448e3ea4 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -18,6 +18,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -750,7 +751,7 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data interf // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (k *Kraken) GenerateDefaultSubscriptions() { - enabledCurrencies := k.GetEnabledCurrencies() + enabledCurrencies := k.GetEnabledPairs(assets.AssetTypeSpot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range defaultSubscribedChannels { for j := range enabledCurrencies { diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index c3f8fd72..f6b5c78d 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -8,13 +8,122 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (k *Kraken) GetDefaultConfig() (*config.ExchangeConfig, error) { + k.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = k.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = k.BaseCurrencies + + err := k.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if k.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = k.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets current default settings +func (k *Kraken) SetDefaults() { + k.Name = "Kraken" + k.Enabled = true + k.Verbose = true + k.API.CredentialsValidator.RequiresKey = true + k.API.CredentialsValidator.RequiresSecret = true + k.API.CredentialsValidator.RequiresBase64DecodeSecret = true + + k.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + Separator: ",", + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + } + + k.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: false, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | + exchange.WithdrawCryptoWith2FA | + exchange.AutoWithdrawFiatWithSetup | + exchange.WithdrawFiatWith2FA, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + k.Requester = request.New(k.Name, + request.NewRateLimit(time.Second, krakenAuthRate), + request.NewRateLimit(time.Second, krakenUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + k.API.Endpoints.URLDefault = krakenAPIURL + k.API.Endpoints.URL = k.API.Endpoints.URLDefault + + k.WebsocketInit() + k.API.Endpoints.WebsocketURL = krakenWSURL + k.Websocket.Functionality = exchange.WebsocketTickerSupported | + exchange.WebsocketTradeDataSupported | + exchange.WebsocketKlineSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup sets current exchange configuration +func (k *Kraken) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + k.SetEnabled(false) + return nil + } + + err := k.SetupDefaults(exch) + if err != nil { + return err + } + + return k.WebsocketSetup(k.WsConnect, + k.Subscribe, + k.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + krakenWSURL, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the Kraken go routine func (k *Kraken) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -27,67 +136,74 @@ func (k *Kraken) Start(wg *sync.WaitGroup) { // Run implements the Kraken wrapper func (k *Kraken) Run() { if k.Verbose { - log.Debugf("%s polling delay: %ds.\n", k.GetName(), k.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", k.GetName(), len(k.EnabledPairs), k.EnabledPairs) + k.PrintEnabledPairs() } - assetPairs, err := k.GetAssetPairs() - if err != nil { - log.Errorf("%s Failed to get available symbols.\n", k.GetName()) - } else { - forceUpgrade := false - if !common.StringDataContains(k.EnabledPairs.Strings(), "-") || - !common.StringDataContains(k.AvailablePairs.Strings(), "-") { - forceUpgrade = true - } + forceUpdate := false + if !common.StringDataContains(k.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "-") || + !common.StringDataContains(k.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "-") { + enabledPairs := currency.NewPairsFromStrings([]string{"XBT-USD"}) + log.Warn("WARNING: Available pairs for Kraken reset due to config upgrade, please enable the ones you would like again") + forceUpdate = true - var exchangeProducts []string - for i := range assetPairs { - v := assetPairs[i] - if common.StringContains(v.Altname, ".d") { - continue - } - if v.Base[0] == 'X' { - if len(v.Base) > 3 { - v.Base = v.Base[1:] - } - } - if v.Quote[0] == 'Z' || v.Quote[0] == 'X' { - v.Quote = v.Quote[1:] - } - exchangeProducts = append(exchangeProducts, v.Base+"-"+v.Quote) - } - - if forceUpgrade { - enabledPairs := currency.Pairs{currency.Pair{ - Base: currency.XBT, Quote: currency.USD, Delimiter: "-"}} - - log.Warn("Available pairs for Kraken reset due to config upgrade, please enable the ones you would like again") - - err = k.UpdateCurrencies(enabledPairs, true, true) - if err != nil { - log.Errorf("%s Failed to get config.\n", k.GetName()) - } - } - - var newExchangeProducts currency.Pairs - for _, p := range exchangeProducts { - newExchangeProducts = append(newExchangeProducts, - currency.NewPairFromString(p)) - } - - err = k.UpdateCurrencies(newExchangeProducts, false, forceUpgrade) + err := k.UpdatePairs(enabledPairs, assets.AssetTypeSpot, true, true) if err != nil { - log.Errorf("%s Failed to get config.\n", k.GetName()) + log.Errorf("%s failed to update currencies. Err: %s\n", k.Name, err) } } + + if !k.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { + return + } + + err := k.UpdateTradablePairs(forceUpdate) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", k.Name, err) + } +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (k *Kraken) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + pairs, err := k.GetAssetPairs() + if err != nil { + return nil, err + } + + var products []string + for i := range pairs { + v := pairs[i] + if common.StringContains(v.Altname, ".d") { + continue + } + if v.Base[0] == 'X' { + if len(v.Base) > 3 { + v.Base = v.Base[1:] + } + } + if v.Quote[0] == 'Z' || v.Quote[0] == 'X' { + v.Quote = v.Quote[1:] + } + products = append(products, v.Base+"-"+v.Quote) + } + return products, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (k *Kraken) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := k.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return k.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (k *Kraken) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (k *Kraken) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - pairs := k.GetEnabledCurrencies() - pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(k.Name, pairs) + pairs := k.GetEnabledPairs(assetType) + pairsCollated, err := k.FormatExchangeCurrencies(pairs, assetType) if err != nil { return tickerPrice, err } @@ -116,8 +232,8 @@ func (k *Kraken) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, return ticker.GetTicker(k.GetName(), p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (k *Kraken) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (k *Kraken) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(k.GetName(), p, assetType) if err != nil { return k.UpdateTicker(p, assetType) @@ -125,8 +241,8 @@ func (k *Kraken) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (k *Kraken) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (k *Kraken) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(k.GetName(), p, assetType) if err != nil { return k.UpdateOrderbook(p, assetType) @@ -135,9 +251,10 @@ func (k *Kraken) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Ba } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (k *Kraken) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (k *Kraken) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := k.GetDepth(exchange.FormatExchangeCurrency(k.GetName(), p).String()) + orderbookNew, err := k.GetDepth(k.FormatExchangeCurrency(p, + assetType).String()) if err != nil { return orderBook, err } @@ -196,10 +313,8 @@ func (k *Kraken) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (k *Kraken) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (k *Kraken) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -290,20 +405,20 @@ func (k *Kraken) GetDepositAddress(cryptocurrency currency.Code, _ string) (stri // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal // Populate exchange.WithdrawRequest.TradePassword with withdrawal key name, as set up on your account -func (k *Kraken) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (k *Kraken) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return k.Withdraw(withdrawRequest.Currency.String(), withdrawRequest.TradePassword, withdrawRequest.Amount) } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (k *Kraken) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { - return k.WithdrawCryptocurrencyFunds(withdrawRequest) +func (k *Kraken) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { + return k.Withdraw(withdrawRequest.Currency.String(), withdrawRequest.TradePassword, withdrawRequest.Amount) } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (k *Kraken) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { - return k.WithdrawCryptocurrencyFunds(withdrawRequest) +func (k *Kraken) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { + return k.Withdraw(withdrawRequest.Currency.String(), withdrawRequest.TradePassword, withdrawRequest.Amount) } // GetWebsocket returns a pointer to the exchange websocket @@ -313,7 +428,7 @@ func (k *Kraken) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (k *Kraken) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (k.APIKey == "" || k.APISecret == "") && // Todo check connection status + if !k.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -330,7 +445,7 @@ func (k *Kraken) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp.Open { symbol := currency.NewPairDelimiter(resp.Open[i].Descr.Pair, - k.ConfigCurrencyPairFormat.Delimiter) + k.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(resp.Open[i].StartTm), 0) side := exchange.OrderSide(strings.ToUpper(resp.Open[i].Descr.Type)) @@ -351,7 +466,6 @@ func (k *Kraken) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } @@ -374,7 +488,7 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp.Closed { symbol := currency.NewPairDelimiter(resp.Closed[i].Descr.Pair, - k.ConfigCurrencyPairFormat.Delimiter) + k.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(resp.Closed[i].StartTm), 0) side := exchange.OrderSide(strings.ToUpper(resp.Closed[i].Descr.Type)) @@ -393,7 +507,6 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - return orders, nil } diff --git a/exchanges/lakebtc/README.md b/exchanges/lakebtc/README.md index b2b12118..93a0252a 100644 --- a/exchanges/lakebtc/README.md +++ b/exchanges/lakebtc/README.md @@ -47,22 +47,22 @@ main.go ```go var l exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "LakeBTC" { - l = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "LakeBTC" { + l = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := l.GetTickerPrice() +tick, err := l.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := l.GetOrderbookEx() +ob, err := l.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index a220b46a..7a4877ab 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -6,14 +6,11 @@ import ( "net/http" "strconv" "strings" - "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -42,90 +39,10 @@ type LakeBTC struct { exchange.Base } -// SetDefaults sets LakeBTC defaults -func (l *LakeBTC) SetDefaults() { - l.Name = "LakeBTC" - l.Enabled = false - l.TakerFee = 0.2 - l.MakerFee = 0.15 - l.Verbose = false - l.RESTPollingDelay = 10 - l.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.WithdrawFiatViaWebsiteOnly - l.RequestCurrencyPairFormat.Delimiter = "" - l.RequestCurrencyPairFormat.Uppercase = true - l.ConfigCurrencyPairFormat.Delimiter = "" - l.ConfigCurrencyPairFormat.Uppercase = true - l.AssetTypes = []string{ticker.Spot} - l.SupportsAutoPairUpdating = true - l.SupportsRESTTickerBatching = true - l.Requester = request.New(l.Name, - request.NewRateLimit(time.Second, lakeBTCAuthRate), - request.NewRateLimit(time.Second, lakeBTCUnauth), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - l.APIUrlDefault = lakeBTCAPIURL - l.APIUrl = l.APIUrlDefault - l.WebsocketInit() -} - -// Setup sets exchange configuration profile -func (l *LakeBTC) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - l.SetEnabled(false) - } else { - l.Enabled = true - l.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - l.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - l.SetHTTPClientTimeout(exch.HTTPTimeout) - l.SetHTTPClientUserAgent(exch.HTTPUserAgent) - l.RESTPollingDelay = exch.RESTPollingDelay - l.Verbose = exch.Verbose - l.HTTPDebugging = exch.HTTPDebugging - l.BaseCurrencies = exch.BaseCurrencies - l.AvailablePairs = exch.AvailablePairs - l.EnabledPairs = exch.EnabledPairs - err := l.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = l.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = l.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = l.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = l.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - } -} - -// GetTradablePairs returns a list of available pairs from the exchange -func (l *LakeBTC) GetTradablePairs() ([]string, error) { - result, err := l.GetTicker() - if err != nil { - return nil, err - } - - var currencies []string - for x := range result { - currencies = append(currencies, common.StringToUpper(x)) - } - - return currencies, nil -} - // GetTicker returns the current ticker from lakeBTC func (l *LakeBTC) GetTicker() (map[string]Ticker, error) { response := make(map[string]TickerResponse) - path := fmt.Sprintf("%s/%s", l.APIUrl, lakeBTCTicker) + path := fmt.Sprintf("%s/%s", l.API.Endpoints.URL, lakeBTCTicker) if err := l.SendHTTPRequest(path, &response); err != nil { return nil, err @@ -165,7 +82,7 @@ func (l *LakeBTC) GetOrderBook(currency string) (Orderbook, error) { Bids [][]string `json:"bids"` Asks [][]string `json:"asks"` } - path := fmt.Sprintf("%s/%s?symbol=%s", l.APIUrl, lakeBTCOrderbook, common.StringToLower(currency)) + path := fmt.Sprintf("%s/%s?symbol=%s", l.API.Endpoints.URL, lakeBTCOrderbook, common.StringToLower(currency)) resp := Response{} err := l.SendHTTPRequest(path, &resp) if err != nil { @@ -205,16 +122,14 @@ func (l *LakeBTC) GetOrderBook(currency string) (Orderbook, error) { // GetTradeHistory returns the trade history for a given currency pair func (l *LakeBTC) GetTradeHistory(currency string) ([]TradeHistory, error) { - path := fmt.Sprintf("%s/%s?symbol=%s", l.APIUrl, lakeBTCTrades, common.StringToLower(currency)) + path := fmt.Sprintf("%s/%s?symbol=%s", l.API.Endpoints.URL, lakeBTCTrades, common.StringToLower(currency)) var resp []TradeHistory - return resp, l.SendHTTPRequest(path, &resp) } // GetAccountInformation returns your current account information func (l *LakeBTC) GetAccountInformation() (AccountInfo, error) { resp := AccountInfo{} - return resp, l.SendAuthenticatedHTTPRequest(lakeBTCGetAccountInfo, "", &resp) } @@ -340,17 +255,17 @@ func (l *LakeBTC) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an autheticated HTTP request to a LakeBTC func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result interface{}) (err error) { - if !l.AuthenticatedAPISupport { + if !l.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name) } n := l.Requester.GetNonce(true).String() - req := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=1&method=%s¶ms=%s", n, l.APIKey, method, params) - hmac := common.GetHMAC(common.HashSHA1, []byte(req), []byte(l.APISecret)) + req := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=1&method=%s¶ms=%s", n, l.API.Credentials.Key, method, params) + hmac := crypto.GetHMAC(crypto.HashSHA1, []byte(req), []byte(l.API.Credentials.Secret)) if l.Verbose { - log.Debugf("Sending POST request to %s calling method %s with params %s\n", l.APIUrl, method, req) + log.Debugf("Sending POST request to %s calling method %s with params %s\n", l.API.Endpoints.URL, method, req) } postData := make(map[string]interface{}) @@ -365,10 +280,11 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int headers := make(map[string]string) headers["Json-Rpc-Tonce"] = l.Nonce.String() - headers["Authorization"] = "Basic " + common.Base64Encode([]byte(l.APIKey+":"+common.HexEncodeToString(hmac))) + headers["Authorization"] = "Basic " + crypto.Base64Encode([]byte(l.API.Credentials.Key+":"+crypto.HexEncodeToString(hmac))) headers["Content-Type"] = "application/json-rpc" - return l.SendPayload(http.MethodPost, l.APIUrl, headers, strings.NewReader(string(data)), result, true, true, l.Verbose, l.HTTPDebugging) + return l.SendPayload(http.MethodPost, l.API.Endpoints.URL, headers, + strings.NewReader(string(data)), result, true, true, l.Verbose, l.HTTPDebugging) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index 7b067abf..1d52981b 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) var l LakeBTC @@ -29,16 +30,16 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - LakeBTC Setup() init error") } - lakebtcConfig.AuthenticatedAPISupport = true - lakebtcConfig.APIKey = apiKey - lakebtcConfig.APISecret = apiSecret + lakebtcConfig.API.AuthenticatedSupport = true + lakebtcConfig.API.Credentials.Key = apiKey + lakebtcConfig.API.Credentials.Secret = apiSecret - l.Setup(&lakebtcConfig) + l.Setup(lakebtcConfig) } -func TestGetTradablePairs(t *testing.T) { +func TestFetchTradablePairs(t *testing.T) { t.Parallel() - _, err := l.GetTradablePairs() + _, err := l.FetchTradablePairs(assets.AssetTypeSpot) if err != nil { t.Fatalf("Test failed. GetTradablePairs err: %s", err) } @@ -70,7 +71,7 @@ func TestGetTradeHistory(t *testing.T) { func TestTrade(t *testing.T) { t.Parallel() - if l.APIKey == "" || l.APISecret == "" { + if !l.ValidateAPICredentials() { t.Skip() } _, err := l.Trade(false, 0, 0, "USD") @@ -81,7 +82,7 @@ func TestTrade(t *testing.T) { func TestGetOpenOrders(t *testing.T) { t.Parallel() - if l.APIKey == "" || l.APISecret == "" { + if !l.ValidateAPICredentials() { t.Skip() } _, err := l.GetOpenOrders() @@ -92,7 +93,7 @@ func TestGetOpenOrders(t *testing.T) { func TestGetOrders(t *testing.T) { t.Parallel() - if l.APIKey == "" || l.APISecret == "" { + if !l.ValidateAPICredentials() { t.Skip() } _, err := l.GetOrders([]int64{1, 2}) @@ -103,7 +104,7 @@ func TestGetOrders(t *testing.T) { func TestCancelOrder(t *testing.T) { t.Parallel() - if l.APIKey == "" || l.APISecret == "" { + if !l.ValidateAPICredentials() { t.Skip() } err := l.CancelExistingOrder(1337) @@ -114,7 +115,7 @@ func TestCancelOrder(t *testing.T) { func TestGetTrades(t *testing.T) { t.Parallel() - if l.APIKey == "" || l.APISecret == "" { + if !l.ValidateAPICredentials() { t.Skip() } _, err := l.GetTrades(1337) @@ -125,7 +126,7 @@ func TestGetTrades(t *testing.T) { func TestGetExternalAccounts(t *testing.T) { t.Parallel() - if l.APIKey == "" || l.APISecret == "" { + if !l.ValidateAPICredentials() { t.Skip() } _, err := l.GetExternalAccounts() @@ -285,11 +286,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if l.APIKey != "" && l.APIKey != "Key" && - l.APISecret != "" && l.APISecret != "Secret" { - return true - } - return false + return l.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -305,7 +302,7 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.EUR, } - response, err := l.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") + response, err := l.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -380,11 +377,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { l.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "7860767916", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -408,8 +407,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := l.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -424,8 +422,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := l.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index dac301be..5572a458 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -9,13 +9,96 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (l *LakeBTC) GetDefaultConfig() (*config.ExchangeConfig, error) { + l.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = l.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = l.BaseCurrencies + + err := l.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if l.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = l.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets LakeBTC defaults +func (l *LakeBTC) SetDefaults() { + l.Name = "LakeBTC" + l.Enabled = true + l.Verbose = true + l.API.CredentialsValidator.RequiresKey = true + l.API.CredentialsValidator.RequiresSecret = true + + l.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + + l.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: false, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.WithdrawFiatViaWebsiteOnly, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + l.Requester = request.New(l.Name, + request.NewRateLimit(time.Second, lakeBTCAuthRate), + request.NewRateLimit(time.Second, lakeBTCUnauth), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + l.API.Endpoints.URLDefault = lakeBTCAPIURL + l.API.Endpoints.URL = l.API.Endpoints.URLDefault +} + +// Setup sets exchange configuration profile +func (l *LakeBTC) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + l.SetEnabled(false) + return nil + } + + return l.SetupDefaults(exch) +} + // Start starts the LakeBTC go routine func (l *LakeBTC) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -28,36 +111,54 @@ func (l *LakeBTC) Start(wg *sync.WaitGroup) { // Run implements the LakeBTC wrapper func (l *LakeBTC) Run() { if l.Verbose { - log.Debugf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", l.GetName(), len(l.EnabledPairs), l.EnabledPairs) + l.PrintEnabledPairs() } - exchangeProducts, err := l.GetTradablePairs() - if err != nil { - log.Errorf("%s Failed to get available products.\n", l.GetName()) - } else { - var newExchangeProducts currency.Pairs - for _, p := range exchangeProducts { - newExchangeProducts = append(newExchangeProducts, - currency.NewPairFromString(p)) - } + if !l.GetEnabledFeatures().AutoPairUpdates { + return + } - err = l.UpdateCurrencies(newExchangeProducts, false, false) - if err != nil { - log.Errorf("%s Failed to update available currencies.\n", l.GetName()) - } + err := l.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", l.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (l *LakeBTC) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + result, err := l.GetTicker() + if err != nil { + return nil, err + } + + var currencies []string + for x := range result { + currencies = append(currencies, common.StringToUpper(x)) + } + + return currencies, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (l *LakeBTC) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := l.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return l.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tick, err := l.GetTicker() if err != nil { return ticker.Price{}, err } - for _, x := range l.GetEnabledCurrencies() { - currency := exchange.FormatExchangeCurrency(l.Name, x).String() + for _, x := range l.GetEnabledPairs(assetType) { + currency := l.FormatExchangeCurrency(x, assetType).String() var tickerPrice ticker.Price tickerPrice.Pair = x tickerPrice.Ask = tick[currency].Ask @@ -75,8 +176,8 @@ func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, return ticker.GetTicker(l.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (l *LakeBTC) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (l *LakeBTC) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType) if err != nil { return l.UpdateTicker(p, assetType) @@ -84,8 +185,8 @@ func (l *LakeBTC) GetTickerPrice(p currency.Pair, assetType string) (ticker.Pric return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (l *LakeBTC) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (l *LakeBTC) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(l.GetName(), p, assetType) if err != nil { return l.UpdateOrderbook(p, assetType) @@ -94,7 +195,7 @@ func (l *LakeBTC) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.B } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (l *LakeBTC) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (l *LakeBTC) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := l.GetOrderBook(p.String()) if err != nil { @@ -160,10 +261,8 @@ func (l *LakeBTC) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (l *LakeBTC) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (l *LakeBTC) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -243,7 +342,7 @@ func (l *LakeBTC) GetDepositAddress(cryptocurrency currency.Code, _ string) (str // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (l *LakeBTC) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (l *LakeBTC) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { if withdrawRequest.Currency != currency.BTC { return "", errors.New("only BTC supported for withdrawals") } @@ -258,13 +357,13 @@ func (l *LakeBTC) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.Withdraw // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (l *LakeBTC) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (l *LakeBTC) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (l *LakeBTC) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (l *LakeBTC) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -276,7 +375,7 @@ func (l *LakeBTC) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (l *LakeBTC) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (l.APIKey == "" || l.APISecret == "") && // Todo check connection status + if !l.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -292,7 +391,7 @@ func (l *LakeBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( var orders []exchange.OrderDetail for _, order := range resp { - symbol := currency.NewPairDelimiter(order.Symbol, l.ConfigCurrencyPairFormat.Delimiter) + symbol := currency.NewPairDelimiter(order.Symbol, l.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderDate := time.Unix(order.At, 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) @@ -329,7 +428,7 @@ func (l *LakeBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( } symbol := currency.NewPairDelimiter(order.Symbol, - l.ConfigCurrencyPairFormat.Delimiter) + l.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderDate := time.Unix(order.At, 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) diff --git a/exchanges/localbitcoins/README.md b/exchanges/localbitcoins/README.md index ce56533e..962684f6 100644 --- a/exchanges/localbitcoins/README.md +++ b/exchanges/localbitcoins/README.md @@ -47,22 +47,22 @@ main.go ```go var l exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "LocalBitcoins" { - l = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "LocalBitcoins" { + l = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := l.GetTickerPrice() +tick, err := l.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := l.GetOrderbookEx() +ob, err := l.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index 2aaaef85..fe61955a 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -7,12 +7,10 @@ import ( "net/url" "strconv" "strings" - "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -113,65 +111,6 @@ type LocalBitcoins struct { exchange.Base } -// SetDefaults sets the package defaults for localbitcoins -func (l *LocalBitcoins) SetDefaults() { - l.Name = "LocalBitcoins" - l.Enabled = false - l.Verbose = false - l.Verbose = false - l.RESTPollingDelay = 10 - l.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.WithdrawFiatViaWebsiteOnly - l.RequestCurrencyPairFormat.Delimiter = "" - l.RequestCurrencyPairFormat.Uppercase = true - l.ConfigCurrencyPairFormat.Delimiter = "" - l.ConfigCurrencyPairFormat.Uppercase = true - l.SupportsAutoPairUpdating = true - l.SupportsRESTTickerBatching = true - l.Requester = request.New(l.Name, - request.NewRateLimit(time.Millisecond*500, localbitcoinsAuthRate), - request.NewRateLimit(time.Millisecond*500, localbitcoinsUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - l.APIUrlDefault = localbitcoinsAPIURL - l.APIUrl = l.APIUrlDefault - l.WebsocketInit() -} - -// Setup sets exchange configuration parameters -func (l *LocalBitcoins) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - l.SetEnabled(false) - } else { - l.Enabled = true - l.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - l.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - l.SetHTTPClientTimeout(exch.HTTPTimeout) - l.SetHTTPClientUserAgent(exch.HTTPUserAgent) - l.RESTPollingDelay = exch.RESTPollingDelay - l.Verbose = exch.Verbose - l.HTTPDebugging = exch.HTTPDebugging - l.BaseCurrencies = exch.BaseCurrencies - l.AvailablePairs = exch.AvailablePairs - l.EnabledPairs = exch.EnabledPairs - err := l.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = l.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = l.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = l.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - } -} - // GetAccountInformation lets you retrieve the public user information on a // LocalBitcoins user. The response contains the same information that is found // on an account's public profile page. @@ -187,7 +126,7 @@ func (l *LocalBitcoins) GetAccountInformation(username string, self bool) (Accou return resp.Data, err } } else { - path := fmt.Sprintf("%s/%s/%s/", l.APIUrl, localbitcoinsAPIAccountInfo, username) + path := fmt.Sprintf("%s/%s/%s/", l.API.Endpoints.URL, localbitcoinsAPIAccountInfo, username) err := l.SendHTTPRequest(path, &resp) if err != nil { return resp.Data, err @@ -341,14 +280,14 @@ func (l *LocalBitcoins) GetTradeInfo(contactID string) (dbi DashBoardInfo, err e // GetCountryCodes returns a list of valid and recognized countrycodes func (l *LocalBitcoins) GetCountryCodes() error { - return l.SendHTTPRequest(l.APIUrl+localbitcoinsAPICountryCodes, nil) + return l.SendHTTPRequest(l.API.Endpoints.URL+localbitcoinsAPICountryCodes, nil) } // GetCurrencies returns a list of valid and recognized fiat currencies. Also // contains human readable name for every currency and boolean that tells if // currency is an altcoin. func (l *LocalBitcoins) GetCurrencies() error { - return l.SendHTTPRequest(l.APIUrl+localbitcoinsAPICurrencies, nil) + return l.SendHTTPRequest(l.API.Endpoints.URL+localbitcoinsAPICurrencies, nil) } // GetDashboardInfo returns a list of trades on the data key contact_list. This @@ -476,13 +415,13 @@ func (l *LocalBitcoins) MarkNotifications() error { // and code for payment methods, and possible limitations in currencies and bank // name choices. func (l *LocalBitcoins) GetPaymentMethods() error { - return l.SendHTTPRequest(l.APIUrl+localbitcoinsAPIPaymentMethods, nil) + return l.SendHTTPRequest(l.API.Endpoints.URL+localbitcoinsAPIPaymentMethods, nil) } // GetPaymentMethodsByCountry returns a list of valid payment methods filtered // by countrycodes. func (l *LocalBitcoins) GetPaymentMethodsByCountry(countryCode string) error { - return l.SendHTTPRequest(l.APIUrl+localbitcoinsAPIPaymentMethods+countryCode, nil) + return l.SendHTTPRequest(l.API.Endpoints.URL+localbitcoinsAPIPaymentMethods+countryCode, nil) } // CheckPincode checks the given PIN code against the token owners currently @@ -517,7 +456,7 @@ func (l *LocalBitcoins) CheckPincode(pin int) (bool, error) { // sell listings for each. // TODO func (l *LocalBitcoins) GetPlaces() error { - return l.SendHTTPRequest(l.APIUrl+localbitcoinsAPIPlaces, nil) + return l.SendHTTPRequest(l.API.Endpoints.URL+localbitcoinsAPIPlaces, nil) } // VerifyUsername returns list of real name verifiers for the user. Returns a @@ -634,20 +573,20 @@ func (l *LocalBitcoins) GetWalletAddress() (string, error) { // GetBitcoinsWithCashAd returns buy or sell as cash local advertisements. // TODO func (l *LocalBitcoins) GetBitcoinsWithCashAd() error { - return l.SendHTTPRequest(l.APIUrl+localbitcoinsAPICashBuy, nil) + return l.SendHTTPRequest(l.API.Endpoints.URL+localbitcoinsAPICashBuy, nil) } // GetBitcoinsOnlineAd this API returns buy or sell Bitcoin online ads. // TODO func (l *LocalBitcoins) GetBitcoinsOnlineAd() error { - return l.SendHTTPRequest(l.APIUrl+localbitcoinsAPIOnlineBuy, nil) + return l.SendHTTPRequest(l.API.Endpoints.URL+localbitcoinsAPIOnlineBuy, nil) } // GetTicker returns list of all completed trades. func (l *LocalBitcoins) GetTicker() (map[string]Ticker, error) { result := make(map[string]Ticker) - return result, l.SendHTTPRequest(l.APIUrl+localbitcoinsAPITicker, &result) + return result, l.SendHTTPRequest(l.API.Endpoints.URL+localbitcoinsAPITicker, &result) } // GetTradableCurrencies returns a list of tradable fiat currencies @@ -668,9 +607,8 @@ func (l *LocalBitcoins) GetTradableCurrencies() ([]string, error) { // GetTrades returns all closed trades in online buy and online sell categories, // updated every 15 minutes. func (l *LocalBitcoins) GetTrades(currency string, values url.Values) ([]Trade, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/%s/trades.json", l.APIUrl+localbitcoinsAPIBitcoincharts, currency), values) + path := common.EncodeURLValues(fmt.Sprintf("%s/%s/trades.json", l.API.Endpoints.URL+localbitcoinsAPIBitcoincharts, currency), values) var result []Trade - return result, l.SendHTTPRequest(path, &result) } @@ -684,7 +622,7 @@ func (l *LocalBitcoins) GetOrderbook(currency string) (Orderbook, error) { Asks [][]string `json:"asks"` } - path := fmt.Sprintf("%s/%s/orderbook.json", l.APIUrl+localbitcoinsAPIBitcoincharts, currency) + path := fmt.Sprintf("%s/%s/orderbook.json", l.API.Endpoints.URL+localbitcoinsAPIBitcoincharts, currency) resp := response{} err := l.SendHTTPRequest(path, &resp) @@ -733,7 +671,7 @@ func (l *LocalBitcoins) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to // localbitcoins func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, params url.Values, result interface{}) (err error) { - if !l.AuthenticatedAPISupport { + if !l.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name) } @@ -741,23 +679,24 @@ func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, params path = "/api/" + path encoded := params.Encode() - message := n + l.APIKey + path + encoded - hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(l.APISecret)) + message := n + l.API.Credentials.Key + path + encoded + hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(message), []byte(l.API.Credentials.Secret)) headers := make(map[string]string) - headers["Apiauth-Key"] = l.APIKey + headers["Apiauth-Key"] = l.API.Credentials.Key headers["Apiauth-Nonce"] = n - headers["Apiauth-Signature"] = common.StringToUpper(common.HexEncodeToString(hmac)) + headers["Apiauth-Signature"] = common.StringToUpper(crypto.HexEncodeToString(hmac)) headers["Content-Type"] = "application/x-www-form-urlencoded" if l.Verbose { - log.Debugf("Sending POST request to `%s`, path: `%s`, params: `%s`.", l.APIUrl, path, encoded) + log.Debugf("Sending POST request to `%s`, path: `%s`, params: `%s`.", l.API.Endpoints.URL, path, encoded) } if method == http.MethodGet && len(encoded) > 0 { path += "?" + encoded } - return l.SendPayload(method, l.APIUrl+path, headers, strings.NewReader(encoded), result, true, true, l.Verbose, l.HTTPDebugging) + return l.SendPayload(method, l.API.Endpoints.URL+path, headers, + strings.NewReader(encoded), result, true, true, l.Verbose, l.HTTPDebugging) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/localbitcoins/localbitcoins_test.go b/exchanges/localbitcoins/localbitcoins_test.go index 12f5d17c..87b0ef9f 100644 --- a/exchanges/localbitcoins/localbitcoins_test.go +++ b/exchanges/localbitcoins/localbitcoins_test.go @@ -30,11 +30,11 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - LakeBTC Setup() init error") } - localbitcoinsConfig.AuthenticatedAPISupport = true - localbitcoinsConfig.APIKey = apiKey - localbitcoinsConfig.APISecret = apiSecret + localbitcoinsConfig.API.AuthenticatedSupport = true + localbitcoinsConfig.API.Credentials.Key = apiKey + localbitcoinsConfig.API.Credentials.Secret = apiSecret - l.Setup(&localbitcoinsConfig) + l.Setup(localbitcoinsConfig) } func TestGetTicker(t *testing.T) { @@ -55,7 +55,7 @@ func TestGetTradableCurrencies(t *testing.T) { func TestGetAccountInfo(t *testing.T) { t.Parallel() - if l.APIKey == "" || l.APISecret == "" { + if !l.ValidateAPICredentials() { t.Skip() } _, err := l.GetAccountInformation("", true) @@ -70,7 +70,7 @@ func TestGetAccountInfo(t *testing.T) { func TestGetads(t *testing.T) { t.Parallel() - if l.APIKey == "" || l.APISecret == "" { + if !l.ValidateAPICredentials() { t.Skip() } _, err := l.Getads("") @@ -85,7 +85,7 @@ func TestGetads(t *testing.T) { func TestEditAd(t *testing.T) { t.Parallel() - if l.APIKey == "" || l.APISecret == "" { + if !l.ValidateAPICredentials() { t.Skip() } edit := AdEdit{} @@ -245,11 +245,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if l.APIKey != "" && l.APIKey != "Key" && - l.APISecret != "" && l.APISecret != "Secret" { - return true - } - return false + return l.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -265,7 +261,7 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.EUR, } - response, err := l.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") + response, err := l.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -340,11 +336,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { l.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -368,8 +366,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := l.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -384,8 +381,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := l.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 865243d9..1f5b6d9a 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -10,13 +10,96 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (l *LocalBitcoins) GetDefaultConfig() (*config.ExchangeConfig, error) { + l.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = l.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = l.BaseCurrencies + + err := l.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if l.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = l.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets the package defaults for localbitcoins +func (l *LocalBitcoins) SetDefaults() { + l.Name = "LocalBitcoins" + l.Enabled = true + l.Verbose = true + l.API.CredentialsValidator.RequiresKey = true + l.API.CredentialsValidator.RequiresSecret = true + + l.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + + l.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: false, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.WithdrawFiatViaWebsiteOnly, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + l.Requester = request.New(l.Name, + request.NewRateLimit(time.Second*0, localbitcoinsAuthRate), + request.NewRateLimit(time.Second*0, localbitcoinsUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + l.API.Endpoints.URLDefault = localbitcoinsAPIURL + l.API.Endpoints.URL = l.API.Endpoints.URLDefault +} + +// Setup sets exchange configuration parameters +func (l *LocalBitcoins) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + l.SetEnabled(false) + return nil + } + + return l.SetupDefaults(exch) +} + // Start starts the LocalBitcoins go routine func (l *LocalBitcoins) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -29,14 +112,24 @@ func (l *LocalBitcoins) Start(wg *sync.WaitGroup) { // Run implements the LocalBitcoins wrapper func (l *LocalBitcoins) Run() { if l.Verbose { - log.Debugf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", l.GetName(), len(l.EnabledPairs), l.EnabledPairs) + l.PrintEnabledPairs() } + if !l.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := l.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", l.Name, err) + } +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (l *LocalBitcoins) FetchTradablePairs(asset assets.AssetType) ([]string, error) { currencies, err := l.GetTradableCurrencies() if err != nil { - log.Errorf("%s failed to obtain available tradable currencies. Err: %s", l.Name, err) - return + return nil, err } var pairs []string @@ -44,28 +137,29 @@ func (l *LocalBitcoins) Run() { pairs = append(pairs, "BTC"+currencies[x]) } - var newExchangeProducts currency.Pairs - for _, p := range pairs { - newExchangeProducts = append(newExchangeProducts, - currency.NewPairFromString(p)) - } + return pairs, nil +} - err = l.UpdateCurrencies(newExchangeProducts, false, false) +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (l *LocalBitcoins) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := l.FetchTradablePairs(assets.AssetTypeSpot) if err != nil { - log.Errorf("%s failed to update available currencies. Err %s", l.Name, err) + return err } + return l.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (l *LocalBitcoins) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (l *LocalBitcoins) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := l.GetTicker() if err != nil { return tickerPrice, err } - for _, x := range l.GetEnabledCurrencies() { + for _, x := range l.GetEnabledPairs(assetType) { currency := x.Quote.String() var tp ticker.Price tp.Pair = x @@ -81,8 +175,8 @@ func (l *LocalBitcoins) UpdateTicker(p currency.Pair, assetType string) (ticker. return ticker.GetTicker(l.GetName(), p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (l *LocalBitcoins) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (l *LocalBitcoins) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType) if err != nil { return l.UpdateTicker(p, assetType) @@ -90,8 +184,8 @@ func (l *LocalBitcoins) GetTickerPrice(p currency.Pair, assetType string) (ticke return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (l *LocalBitcoins) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (l *LocalBitcoins) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(l.GetName(), p, assetType) if err != nil { return l.UpdateOrderbook(p, assetType) @@ -100,7 +194,7 @@ func (l *LocalBitcoins) GetOrderbookEx(p currency.Pair, assetType string) (order } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (l *LocalBitcoins) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (l *LocalBitcoins) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := l.GetOrderbook(p.Quote.String()) if err != nil { @@ -156,10 +250,8 @@ func (l *LocalBitcoins) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (l *LocalBitcoins) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (l *LocalBitcoins) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -279,20 +371,20 @@ func (l *LocalBitcoins) GetDepositAddress(cryptocurrency currency.Code, _ string // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (l *LocalBitcoins) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (l *LocalBitcoins) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { _, err := l.WalletSend(withdrawRequest.Address, withdrawRequest.Amount, withdrawRequest.PIN) return "", err } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (l *LocalBitcoins) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (l *LocalBitcoins) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (l *LocalBitcoins) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (l *LocalBitcoins) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -303,7 +395,7 @@ func (l *LocalBitcoins) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (l *LocalBitcoins) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (l.APIKey == "" || l.APISecret == "") && // Todo check connection status + if !l.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -344,7 +436,7 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequ OrderSide: side, CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), resp[i].Data.Currency, - l.ConfigCurrencyPairFormat.Delimiter), + l.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), Exchange: l.Name, }) } @@ -417,7 +509,7 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequ Status: status, CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), allTrades[i].Data.Currency, - l.ConfigCurrencyPairFormat.Delimiter), + l.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), Exchange: l.Name, }) } diff --git a/exchanges/okcoin/README.md b/exchanges/okcoin/README.md index 192e4671..533770d1 100644 --- a/exchanges/okcoin/README.md +++ b/exchanges/okcoin/README.md @@ -48,22 +48,22 @@ main.go ```go var o exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "OKCoin" { - y = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "OKCoin" { + y = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := o.GetTickerPrice() +tick, err := o.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := o.GetOrderbookEx() +ob, err := o.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/okcoin/okcoin.go b/exchanges/okcoin/okcoin.go index bc0a2572..c68524ec 100644 --- a/exchanges/okcoin/okcoin.go +++ b/exchanges/okcoin/okcoin.go @@ -1,13 +1,7 @@ package okcoin import ( - "time" - - "github.com/thrasher-/gocryptotrader/common" - exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/okgroup" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -24,37 +18,3 @@ const ( type OKCoin struct { okgroup.OKGroup } - -// SetDefaults method assignes the default values for OKEX -func (o *OKCoin) SetDefaults() { - o.SetErrorDefaults() - o.SetCheckVarDefaults() - o.Name = okCoinExchangeName - o.Enabled = false - o.Verbose = false - o.RESTPollingDelay = 10 - o.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.NoFiatWithdrawals - o.RequestCurrencyPairFormat.Delimiter = "_" - o.RequestCurrencyPairFormat.Uppercase = false - o.ConfigCurrencyPairFormat.Delimiter = "_" - o.ConfigCurrencyPairFormat.Uppercase = true - o.SupportsAutoPairUpdating = true - o.SupportsRESTTickerBatching = false - o.Requester = request.New(o.Name, - request.NewRateLimit(time.Second, okCoinAuthRate), - request.NewRateLimit(time.Second, okCoinUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - o.APIUrlDefault = okCoinAPIURL - o.APIUrl = okCoinAPIURL - o.AssetTypes = []string{ticker.Spot} - o.WebsocketInit() - o.WebsocketURL = okCoinWebsocketURL - o.APIVersion = okCoinAPIVersion - o.Websocket.Functionality = exchange.WebsocketTickerSupported | - exchange.WebsocketTradeDataSupported | - exchange.WebsocketKlineSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index d7470cd8..aa1622d8 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -53,8 +53,8 @@ func TestSetup(t *testing.T) { if testSetupRan { return } - if o.APIKey == apiKey && o.APISecret == apiSecret && - o.ClientID == passphrase && apiKey != "" && apiSecret != "" && passphrase != "" { + if o.API.Credentials.Key == apiKey && o.API.Credentials.Secret == apiSecret && + o.API.Credentials.ClientID == passphrase && apiKey != "" && apiSecret != "" && passphrase != "" { return } o.ExchangeName = OKGroupExchange @@ -64,26 +64,22 @@ func TestSetup(t *testing.T) { if err != nil { t.Fatalf("Test Failed - %v Setup() init error", OKGroupExchange) } - if okcoinConfig.Websocket { + if okcoinConfig.Features.Enabled.Websocket { websocketEnabled = true } - okcoinConfig.AuthenticatedAPISupport = true - okcoinConfig.APIKey = apiKey - okcoinConfig.APISecret = apiSecret - okcoinConfig.ClientID = passphrase - okcoinConfig.WebsocketURL = o.WebsocketURL - o.Setup(&okcoinConfig) + okcoinConfig.API.AuthenticatedSupport = true + okcoinConfig.API.Credentials.Key = apiKey + okcoinConfig.API.Credentials.Secret = apiSecret + okcoinConfig.API.Credentials.ClientID = passphrase + okcoinConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL + o.Setup(okcoinConfig) testSetupRan = true o.Websocket.DataHandler = make(chan interface{}, 999) } func areTestAPIKeysSet() bool { - if o.APIKey != "" && o.APIKey != "Key" && - o.APISecret != "" && o.APISecret != "Secret" { - return true - } - return false + return o.ValidateAPICredentials() } func testStandardErrorHandling(t *testing.T, err error) { @@ -279,8 +275,8 @@ func TestPlaceSpotOrderLimit(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "limit", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Price: "100", Size: "100", @@ -295,10 +291,10 @@ func TestPlaceSpotOrderMarket(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.MarketOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", - Size: "100", + Size: "-100", Notional: "100", } @@ -311,8 +307,8 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { TestSetRealOrderDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -333,8 +329,8 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { TestSetDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -359,8 +355,8 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { TestSetDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -606,8 +602,8 @@ func TestPlaceMarginOrderLimit(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "limit", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "2", Price: "100", Size: "100", @@ -622,10 +618,10 @@ func TestPlaceMarginOrderMarket(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.MarketOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "2", - Size: "100", + Size: "-100", Notional: "100", } @@ -638,8 +634,8 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { TestSetRealOrderDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -660,8 +656,8 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { TestSetDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -686,8 +682,8 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { TestSetDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -1054,7 +1050,8 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.EUR, } - response, err := o.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") + response, err := o.SubmitOrder(p, exchange.BuyOrderSide, + exchange.LimitOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -1114,14 +1111,18 @@ func TestModifyOrder(t *testing.T) { // TestWithdraw Wrapper test func TestWithdraw(t *testing.T) { TestSetRealOrderDefaults(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", - TradePassword: "Password", - FeeAmount: 1, + + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + TradePassword: "Password", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", + FeeAmount: 1, } + _, err := o.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) testStandardErrorHandling(t, err) } @@ -1129,7 +1130,7 @@ func TestWithdraw(t *testing.T) { // TestWithdrawFiat Wrapper test func TestWithdrawFiat(t *testing.T) { TestSetRealOrderDefaults(t) - var withdrawFiatRequest = exchange.WithdrawRequest{} + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := o.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -1139,7 +1140,7 @@ func TestWithdrawFiat(t *testing.T) { // TestSubmitOrder Wrapper test func TestWithdrawInternationalBank(t *testing.T) { TestSetRealOrderDefaults(t) - var withdrawFiatRequest = exchange.WithdrawRequest{} + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := o.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go new file mode 100644 index 00000000..68397c3f --- /dev/null +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -0,0 +1,154 @@ +package okcoin + +import ( + "sync" + "time" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/currency" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/request" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// GetDefaultConfig returns a default exchange config +func (o *OKCoin) GetDefaultConfig() (*config.ExchangeConfig, error) { + o.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = o.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = o.BaseCurrencies + + err := o.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if o.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = o.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults method assignes the default values for OKEX +func (o *OKCoin) SetDefaults() { + o.SetErrorDefaults() + o.SetCheckVarDefaults() + o.Name = okCoinExchangeName + o.Enabled = true + o.Verbose = true + + o.API.CredentialsValidator.RequiresKey = true + o.API.CredentialsValidator.RequiresSecret = true + o.API.CredentialsValidator.RequiresClientID = true + + o.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + assets.AssetTypeMargin, + }, + + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + Delimiter: "_", + }, + + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "_", + }, + } + + o.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: false, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + o.Requester = request.New(o.Name, + request.NewRateLimit(time.Second, okCoinAuthRate), + request.NewRateLimit(time.Second, okCoinUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), + ) + + o.API.Endpoints.URLDefault = okCoinAPIURL + o.API.Endpoints.URL = okCoinAPIURL + o.API.Endpoints.WebsocketURL = okCoinWebsocketURL + o.APIVersion = okCoinAPIVersion + o.WebsocketInit() + o.Websocket.Functionality = exchange.WebsocketTickerSupported | + exchange.WebsocketTradeDataSupported | + exchange.WebsocketKlineSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Start starts the OKGroup go routine +func (o *OKCoin) Start(wg *sync.WaitGroup) { + wg.Add(1) + go func() { + o.Run() + wg.Done() + }() +} + +// Run implements the OKEX wrapper +func (o *OKCoin) Run() { + if o.Verbose { + log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) + } + + if !o.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := o.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", o.Name, err) + } +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (o *OKCoin) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + prods, err := o.GetSpotTokenPairDetails() + if err != nil { + return nil, err + } + + var pairs []string + for x := range prods { + pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency) + } + + return pairs, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (o *OKCoin) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := o.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return o.UpdatePairs(currency.NewPairsFromStrings(pairs), + assets.AssetTypeSpot, false, forceUpdate) +} diff --git a/exchanges/okex/README.md b/exchanges/okex/README.md index d1baf62e..43e6438b 100644 --- a/exchanges/okex/README.md +++ b/exchanges/okex/README.md @@ -47,22 +47,22 @@ main.go ```go var o exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "OKex" { - y = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "OKex" { + y = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := o.GetTickerPrice() +tick, err := o.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := o.GetOrderbookEx() +ob, err := o.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/okex/okex.go b/exchanges/okex/okex.go index 01f17834..cc682f4c 100644 --- a/exchanges/okex/okex.go +++ b/exchanges/okex/okex.go @@ -7,10 +7,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" - exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/okgroup" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -50,40 +47,6 @@ type OKEX struct { okgroup.OKGroup } -// SetDefaults method assignes the default values for OKEX -func (o *OKEX) SetDefaults() { - o.SetErrorDefaults() - o.SetCheckVarDefaults() - o.Name = okExExchangeName - o.Enabled = false - o.Verbose = false - o.RESTPollingDelay = 10 - o.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.NoFiatWithdrawals - o.RequestCurrencyPairFormat.Delimiter = "_" - o.RequestCurrencyPairFormat.Uppercase = false - o.ConfigCurrencyPairFormat.Delimiter = "_" - o.ConfigCurrencyPairFormat.Uppercase = true - o.SupportsAutoPairUpdating = true - o.SupportsRESTTickerBatching = false - o.Requester = request.New(o.Name, - request.NewRateLimit(time.Second, okExAuthRate), - request.NewRateLimit(time.Second, okExUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - o.APIUrlDefault = okExAPIURL - o.APIUrl = okExAPIURL - o.AssetTypes = []string{ticker.Spot} - o.WebsocketInit() - o.APIVersion = okExAPIVersion - o.WebsocketURL = OkExWebsocketURL - o.Websocket.Functionality = exchange.WebsocketTickerSupported | - exchange.WebsocketTradeDataSupported | - exchange.WebsocketKlineSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - // GetFuturesPostions Get the information of all holding positions in futures trading. // Due to high energy consumption, you are advised to capture data with the "Futures Account of a Currency" API instead. func (o *OKEX) GetFuturesPostions() (resp okgroup.GetFuturesPositionsResponse, _ error) { @@ -189,7 +152,7 @@ func (o *OKEX) GetFuturesOrderBook(request okgroup.GetFuturesOrderBookRequest) ( } var tmpOB tempOB - err = o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &tmpOB, true) + err = o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &tmpOB, false) if err != nil { return resp, err } @@ -250,7 +213,7 @@ func (o *OKEX) GetAllFuturesTokenInfo() (resp []okgroup.GetFuturesTokenInfoRespo // GetFuturesTokenInfoForCurrency Get the last traded price, best bid/ask price, 24 hour trading volume and more info of a contract. func (o *OKEX) GetFuturesTokenInfoForCurrency(instrumentID string) (resp okgroup.GetFuturesTokenInfoResponse, _ error) { requestURL := fmt.Sprintf("%v/%v/%v", okgroup.OKGroupInstruments, instrumentID, okgroup.OKGroupTicker) - return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, false) } // GetFuturesFilledOrder Get the recent 300 transactions of all contracts. Pagination is not supported here. diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index 69792eb5..6eac2880 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -54,8 +54,8 @@ func TestSetup(t *testing.T) { if testSetupRan { return } - if o.APIKey == apiKey && o.APISecret == apiSecret && - o.ClientID == passphrase && apiKey != "" && apiSecret != "" && passphrase != "" { + if o.API.Credentials.Key == apiKey && o.API.Credentials.Secret == apiSecret && + o.API.Credentials.ClientID == passphrase && apiKey != "" && apiSecret != "" && passphrase != "" { return } o.ExchangeName = OKGroupExchange @@ -66,25 +66,21 @@ func TestSetup(t *testing.T) { if err != nil { t.Fatalf("Test Failed - %v Setup() init error", OKGroupExchange) } - if okexConfig.Websocket { + if okexConfig.Features.Enabled.Websocket { websocketEnabled = true } - okexConfig.AuthenticatedAPISupport = true - okexConfig.APIKey = apiKey - okexConfig.APISecret = apiSecret - okexConfig.ClientID = passphrase - okexConfig.WebsocketURL = o.WebsocketURL - o.Setup(&okexConfig) + okexConfig.API.AuthenticatedSupport = true + okexConfig.API.Credentials.Key = apiKey + okexConfig.API.Credentials.Secret = apiSecret + okexConfig.API.Credentials.ClientID = passphrase + okexConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL + o.Setup(okexConfig) testSetupRan = true o.Websocket.DataHandler = make(chan interface{}, 999) } func areTestAPIKeysSet() bool { - if o.APIKey != "" && o.APIKey != "Key" && - o.APISecret != "" && o.APISecret != "Secret" { - return true - } - return false + return o.ValidateAPICredentials() } func testStandardErrorHandling(t *testing.T, err error) { @@ -300,8 +296,8 @@ func TestPlaceSpotOrderLimit(t *testing.T) { t.Parallel() request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "limit", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Price: "100", Size: "100", @@ -317,10 +313,10 @@ func TestPlaceSpotOrderMarket(t *testing.T) { t.Parallel() request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.MarketOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", - Size: "100", + Size: "-100", Notional: "100", } @@ -334,8 +330,8 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -357,8 +353,8 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -384,8 +380,8 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -654,8 +650,8 @@ func TestPlaceMarginOrderLimit(t *testing.T) { t.Parallel() request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "limit", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "2", Price: "100", Size: "100", @@ -671,10 +667,10 @@ func TestPlaceMarginOrderMarket(t *testing.T) { t.Parallel() request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.MarketOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "2", - Size: "100", + Size: "-100", Notional: "100", } @@ -688,8 +684,8 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -711,8 +707,8 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -738,8 +734,8 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: "market", - Side: "buy", + Type: exchange.LimitOrderType.ToLower().ToString(), + Side: exchange.BuyOrderSide.ToLower().ToString(), MarginTrading: "1", Size: "100", Notional: "100", @@ -1044,9 +1040,7 @@ func TestGetAllFuturesTokenInfo(t *testing.T) { TestSetDefaults(t) t.Parallel() _, err := o.GetAllFuturesTokenInfo() - if err != nil { - t.Error(err) - } + testStandardErrorHandling(t, err) } // TestGetAllFuturesTokenInfo API endpoint test @@ -1064,9 +1058,7 @@ func TestGetFuturesFilledOrder(t *testing.T) { _, err := o.GetFuturesFilledOrder(okgroup.GetFuturesFilledOrderRequest{ InstrumentID: getFutureInstrumentID(), }) - if err != nil { - t.Error(err) - } + testStandardErrorHandling(t, err) } // TestGetFuturesHoldAmount API endpoint test @@ -1828,7 +1820,8 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.USDT, } - response, err := o.SubmitOrder(p, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") + response, err := o.SubmitOrder(p, exchange.BuyOrderSide, + exchange.LimitOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -1893,13 +1886,15 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", - TradePassword: "Password", - FeeAmount: 1, + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + TradePassword: "Password", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", + FeeAmount: 1, } _, err := o.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) testStandardErrorHandling(t, err) @@ -1909,8 +1904,7 @@ func TestWithdraw(t *testing.T) { func TestWithdrawFiat(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := o.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -1921,8 +1915,7 @@ func TestWithdrawFiat(t *testing.T) { func TestWithdrawInternationalBank(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := o.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go new file mode 100644 index 00000000..475e5ed7 --- /dev/null +++ b/exchanges/okex/okex_wrapper.go @@ -0,0 +1,189 @@ +package okex + +import ( + "fmt" + "sync" + "time" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/currency" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/request" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// GetDefaultConfig returns a default exchange config +func (o *OKEX) GetDefaultConfig() (*config.ExchangeConfig, error) { + o.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = o.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = o.BaseCurrencies + + err := o.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if o.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = o.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults method assignes the default values for OKEX +func (o *OKEX) SetDefaults() { + o.SetErrorDefaults() + o.SetCheckVarDefaults() + o.Name = okExExchangeName + o.Enabled = true + o.Verbose = true + o.API.CredentialsValidator.RequiresKey = true + o.API.CredentialsValidator.RequiresSecret = true + o.API.CredentialsValidator.RequiresClientID = true + + o.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + assets.AssetTypeFutures, + assets.AssetTypePerpetualSwap, + assets.AssetTypeIndex, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + Delimiter: "_", + }, + + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "_", + }, + } + + o.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + o.Requester = request.New(o.Name, + request.NewRateLimit(time.Second, okExAuthRate), + request.NewRateLimit(time.Second, okExUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), + ) + + o.API.Endpoints.URLDefault = okExAPIURL + o.API.Endpoints.URL = okExAPIURL + o.API.Endpoints.WebsocketURL = OkExWebsocketURL + o.APIVersion = okExAPIVersion + o.WebsocketInit() + o.Websocket.Functionality = exchange.WebsocketTickerSupported | + exchange.WebsocketTradeDataSupported | + exchange.WebsocketKlineSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Start starts the OKGroup go routine +func (o *OKEX) Start(wg *sync.WaitGroup) { + wg.Add(1) + go func() { + o.Run() + wg.Done() + }() +} + +// Run implements the OKEX wrapper +func (o *OKEX) Run() { + if o.Verbose { + log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) + } + + if !o.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := o.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", o.Name, err) + } +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (o *OKEX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + var pairs []string + switch asset { + case assets.AssetTypeSpot: + prods, err := o.GetSpotTokenPairDetails() + if err != nil { + return nil, err + } + + for x := range prods { + pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency) + } + return pairs, nil + case assets.AssetTypeFutures: + prods, err := o.GetFuturesContractInformation() + if err != nil { + return nil, err + } + + var pairs []string + for x := range prods { + pairs = append(pairs, prods[x].UnderlyingIndex+prods[x].QuoteCurrency+"_"+prods[x].Delivery) + } + return pairs, nil + + case assets.AssetTypePerpetualSwap: + prods, err := o.GetAllSwapTokensInformation() + if err != nil { + return nil, err + } + + var pairs []string + for x := range prods { + pairs = append(pairs, prods[x].InstrumentID) + } + return pairs, nil + case assets.AssetTypeIndex: + return []string{"BTC_USD"}, nil + } + + return nil, fmt.Errorf("%s invalid asset type", o.Name) +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (o *OKEX) UpdateTradablePairs(forceUpdate bool) error { + for x := range o.CurrencyPairs.AssetTypes { + a := o.CurrencyPairs.AssetTypes[x] + pairs, err := o.FetchTradablePairs(a) + if err != nil { + return err + } + + err = o.UpdatePairs(currency.NewPairsFromStrings(pairs), a, false, forceUpdate) + if err != nil { + return err + } + } + return nil +} diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go index bc6f63e1..ee606771 100644 --- a/exchanges/okgroup/okgroup.go +++ b/exchanges/okgroup/okgroup.go @@ -16,7 +16,7 @@ import ( "github.com/google/go-querystring/query" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" exchange "github.com/thrasher-/gocryptotrader/exchanges" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -92,68 +92,16 @@ type OKGroup struct { // Spot and contract market error codes as per https://www.okex.com/rest_request.html ErrorCodes map[string]error // Stores for corresponding variable checks - ContractTypes []string - CurrencyPairs []string - ContractPosition []string - Types []string + ContractTypes []string + CurrencyPairsDefaults []string + ContractPosition []string + Types []string // URLs to be overridden by implementations of OKGroup APIURL string APIVersion string WebsocketURL string } -// Setup method sets current configuration details if enabled -func (o *OKGroup) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - o.SetEnabled(false) - } else { - o.Name = exch.Name - o.Enabled = true - o.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - o.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, false) - o.SetHTTPClientTimeout(exch.HTTPTimeout) - o.SetHTTPClientUserAgent(exch.HTTPUserAgent) - o.RESTPollingDelay = exch.RESTPollingDelay - o.Verbose = exch.Verbose - o.HTTPDebugging = exch.HTTPDebugging - o.Websocket.SetWsStatusAndConnection(exch.Websocket) - o.BaseCurrencies = exch.BaseCurrencies - o.AvailablePairs = exch.AvailablePairs - o.EnabledPairs = exch.EnabledPairs - err := o.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = o.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = o.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = o.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = o.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = o.WebsocketSetup(o.WsConnect, - o.Subscribe, - o.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - o.WebsocketURL, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetAccountCurrencies returns a list of tradable spot instruments and their properties func (o *OKGroup) GetAccountCurrencies() (resp []GetAccountCurrenciesResponse, _ error) { return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, okGroupGetAccountCurrencies, nil, &resp, false) @@ -580,8 +528,9 @@ func (o *OKGroup) GetErrorCode(code interface{}) error { // path with a JSON payload (of present) // URL arguments must be in the request path and not as url.URL values func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, data, result interface{}, authenticated bool) (err error) { - if authenticated && !o.AuthenticatedAPISupport { - return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, o.Name) + if authenticated && !o.AllowAuthenticatedRequest() { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, + o.Name) } utcTime := time.Now().UTC() @@ -601,7 +550,7 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d } } - path := o.APIUrl + requestType + o.APIVersion + requestPath + path := o.API.Endpoints.URL + requestType + o.APIVersion + requestPath if o.Verbose { log.Debugf("Sending %v request to %s \n", requestType, path) } @@ -609,13 +558,15 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d headers := make(map[string]string) headers["Content-Type"] = "application/json" if authenticated { - signPath := fmt.Sprintf("/%v%v%v%v", OKGroupAPIPath, requestType, o.APIVersion, requestPath) - hmac := common.GetHMAC(common.HashSHA256, []byte(iso+httpMethod+signPath+string(payload)), []byte(o.APISecret)) - base64 := common.Base64Encode(hmac) - headers["OK-ACCESS-KEY"] = o.APIKey - headers["OK-ACCESS-SIGN"] = base64 + signPath := fmt.Sprintf("/%v%v%v%v", OKGroupAPIPath, + requestType, o.APIVersion, requestPath) + hmac := crypto.GetHMAC(crypto.HashSHA256, + []byte(iso+httpMethod+signPath+string(payload)), + []byte(o.API.Credentials.Secret)) + headers["OK-ACCESS-KEY"] = o.API.Credentials.Key + headers["OK-ACCESS-SIGN"] = crypto.Base64Encode(hmac) headers["OK-ACCESS-TIMESTAMP"] = iso - headers["OK-ACCESS-PASSPHRASE"] = o.ClientID + headers["OK-ACCESS-PASSPHRASE"] = o.API.Credentials.ClientID } var intermediary json.RawMessage @@ -655,7 +606,7 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d // to check on this, this end. func (o *OKGroup) SetCheckVarDefaults() { o.ContractTypes = []string{"this_week", "next_week", "quarter"} - o.CurrencyPairs = []string{"btc_usd", "ltc_usd", "eth_usd", "etc_usd", "bch_usd"} + o.CurrencyPairsDefaults = []string{"btc_usd", "ltc_usd", "eth_usd", "etc_usd", "bch_usd"} o.Types = []string{"1min", "3min", "5min", "15min", "30min", "1day", "3day", "1week", "1hour", "2hour", "4hour", "6hour", "12hour"} o.ContractPosition = []string{"1", "2", "3", "4"} diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index 84fafe1c..445de1c3 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -15,6 +15,9 @@ import ( "sync" "time" + "github.com/thrasher-/gocryptotrader/common/crypto" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/currency" "github.com/gorilla/websocket" @@ -313,11 +316,12 @@ func (o *OKGroup) WsLogin() error { utcTime := time.Now().UTC() unixTime := utcTime.Unix() signPath := "/users/self/verify" - hmac := common.GetHMAC(common.HashSHA256, []byte(fmt.Sprintf("%v", unixTime)+http.MethodGet+signPath), []byte(o.APISecret)) - base64 := common.Base64Encode(hmac) + hmac := crypto.GetHMAC(crypto.HashSHA256, + []byte(fmt.Sprintf("%v", unixTime)+http.MethodGet+signPath), []byte(o.API.Credentials.Secret)) + base64 := crypto.Base64Encode(hmac) resp := WebsocketEventRequest{ Operation: "login", - Arguments: []string{o.APIKey, o.ClientID, fmt.Sprintf("%v", unixTime), base64}, + Arguments: []string{o.API.Credentials.Key, o.API.Credentials.ClientID, fmt.Sprintf("%v", unixTime), base64}, } json, err := common.JSONEncode(resp) if err != nil { @@ -359,9 +363,9 @@ func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string { // GetAssetTypeFromTableName gets the asset type from the table name // eg "spot/ticker:BTCUSD" results in "SPOT" -func (o *OKGroup) GetAssetTypeFromTableName(table string) string { +func (o *OKGroup) GetAssetTypeFromTableName(table string) assets.AssetType { assetIndex := strings.Index(table, "/") - return strings.ToUpper(table[:assetIndex]) + return assets.AssetType(strings.ToUpper(table[:assetIndex])) } // WsHandleDataResponse classifies the WS response and sends to appropriate handler @@ -545,7 +549,7 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i // WsProcessUpdateOrderbook updates an existing orderbook using websocket data // After merging WS data, it will sort, validate and finally update the existing orderbook func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, tableName string) error { - internalOrderbook, err := o.GetOrderbookEx(instrument, o.GetAssetTypeFromTableName(tableName)) + internalOrderbook, err := o.FetchOrderbook(instrument, o.GetAssetTypeFromTableName(tableName)) if err != nil { return errors.New("orderbook nil, could not load existing orderbook") } @@ -679,7 +683,7 @@ func (o *OKGroup) CalculateUpdateOrderbookChecksum(orderbookData *orderbook.Base // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (o *OKGroup) GenerateDefaultSubscriptions() { - enabledCurrencies := o.GetEnabledCurrencies() + enabledCurrencies := o.GetEnabledPairs(assets.AssetTypeSpot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range defaultSubscribedChannels { for j := range enabledCurrencies { diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index b74cf7fd..a16c104a 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -4,11 +4,12 @@ import ( "fmt" "strconv" "strings" - "sync" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" @@ -18,44 +19,31 @@ import ( // Therefore this OKGroup_Wrapper can be shared between OKEX and OKCoin. // When circumstances change, wrapper funcs can be split appropriately -// Start starts the OKGroup go routine -func (o *OKGroup) Start(wg *sync.WaitGroup) { - wg.Add(1) - go func() { - o.Run() - wg.Done() - }() -} - -// Run implements the OKEX wrapper -func (o *OKGroup) Run() { - if o.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) - log.Debugf("%s polling delay: %ds.\n", o.GetName(), o.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", o.GetName(), len(o.EnabledPairs), o.EnabledPairs) +// Setup sets user exchange configuration settings +func (o *OKGroup) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + o.SetEnabled(false) + return nil } - prods, err := o.GetSpotTokenPairDetails() + err := o.SetupDefaults(exch) if err != nil { - log.Errorf("%v failed to obtain available spot instruments. Err: %s", o.Name, err) - return + return err } - var pairs currency.Pairs - for x := range prods { - pairs = append(pairs, currency.NewPairFromString(prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency)) - } - - err = o.UpdateCurrencies(pairs, false, false) - if err != nil { - log.Errorf("%v failed to update available currencies. Err: %s", o.Name, err) - return - } + return o.WebsocketSetup(o.WsConnect, + o.Subscribe, + o.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + o.WebsocketURL, + exch.API.Endpoints.WebsocketURL) } // UpdateTicker updates and returns the ticker for a currency pair -func (o *OKGroup) UpdateTicker(p currency.Pair, assetType string) (tickerData ticker.Price, err error) { - resp, err := o.GetSpotAllTokenPairsInformationForCurrency(exchange.FormatExchangeCurrency(o.Name, p).String()) +func (o *OKGroup) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tickerData ticker.Price, err error) { + resp, err := o.GetSpotAllTokenPairsInformationForCurrency(o.FormatExchangeCurrency(p, assetType).String()) if err != nil { return } @@ -66,7 +54,7 @@ func (o *OKGroup) UpdateTicker(p currency.Pair, assetType string) (tickerData ti Last: resp.Last, LastUpdated: resp.Timestamp, Low: resp.Low24h, - Pair: exchange.FormatExchangeCurrency(o.Name, p), + Pair: o.FormatExchangeCurrency(p, assetType), Volume: resp.BaseVolume24h, } @@ -74,8 +62,8 @@ func (o *OKGroup) UpdateTicker(p currency.Pair, assetType string) (tickerData ti return } -// GetTickerPrice returns the ticker for a currency pair -func (o *OKGroup) GetTickerPrice(p currency.Pair, assetType string) (tickerData ticker.Price, err error) { +// FetchTicker returns the ticker for a currency pair +func (o *OKGroup) FetchTicker(p currency.Pair, assetType assets.AssetType) (tickerData ticker.Price, err error) { tickerData, err = ticker.GetTicker(o.GetName(), p, assetType) if err != nil { return o.UpdateTicker(p, assetType) @@ -83,8 +71,8 @@ func (o *OKGroup) GetTickerPrice(p currency.Pair, assetType string) (tickerData return } -// GetOrderbookEx returns orderbook base on the currency pair -func (o *OKGroup) GetOrderbookEx(p currency.Pair, assetType string) (resp orderbook.Base, err error) { +// FetchOrderbook returns orderbook base on the currency pair +func (o *OKGroup) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (resp orderbook.Base, err error) { ob, err := orderbook.Get(o.GetName(), p, assetType) if err != nil { return o.UpdateOrderbook(p, assetType) @@ -93,9 +81,9 @@ func (o *OKGroup) GetOrderbookEx(p currency.Pair, assetType string) (resp orderb } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (o *OKGroup) UpdateOrderbook(p currency.Pair, assetType string) (resp orderbook.Base, err error) { +func (o *OKGroup) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (resp orderbook.Base, err error) { orderbookNew, err := o.GetSpotOrderBook(GetSpotOrderBookRequest{ - InstrumentID: exchange.FormatExchangeCurrency(o.Name, p).String(), + InstrumentID: o.FormatExchangeCurrency(p, assetType).String(), }) if err != nil { return @@ -213,7 +201,7 @@ func (o *OKGroup) GetFundingHistory() (resp []exchange.FundHistory, err error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (o *OKGroup) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { +func (o *OKGroup) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -221,7 +209,7 @@ func (o *OKGroup) GetExchangeHistory(p currency.Pair, assetType string) ([]excha func (o *OKGroup) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (resp exchange.SubmitOrderResponse, err error) { request := PlaceSpotOrderRequest{ ClientOID: clientID, - InstrumentID: exchange.FormatExchangeCurrency(o.Name, p).String(), + InstrumentID: o.FormatExchangeCurrency(p, assets.AssetTypeSpot).String(), Side: strings.ToLower(side.ToString()), Type: strings.ToLower(orderType.ToString()), Size: strconv.FormatFloat(amount, 'f', -1, 64), @@ -253,8 +241,9 @@ func (o *OKGroup) CancelOrder(orderCancellation *exchange.OrderCancellation) (er return } orderCancellationResponse, err := o.CancelSpotOrder(CancelSpotOrderRequest{ - InstrumentID: exchange.FormatExchangeCurrency(o.Name, orderCancellation.CurrencyPair).String(), - OrderID: orderID, + InstrumentID: o.FormatExchangeCurrency(orderCancellation.CurrencyPair, + assets.AssetTypeSpot).String(), + OrderID: orderID, }) if !orderCancellationResponse.Result { err = fmt.Errorf("order %v failed to be cancelled", orderCancellationResponse.OrderID) @@ -276,8 +265,9 @@ func (o *OKGroup) CancelAllOrders(orderCancellation *exchange.OrderCancellation) } cancelOrdersResponse, err := o.CancelMultipleSpotOrders(CancelMultipleSpotOrdersRequest{ - InstrumentID: exchange.FormatExchangeCurrency(o.Name, orderCancellation.CurrencyPair).String(), - OrderIDs: orderIDNumbers, + InstrumentID: o.FormatExchangeCurrency(orderCancellation.CurrencyPair, + assets.AssetTypeSpot).String(), + OrderIDs: orderIDNumbers, }) if err != nil { return @@ -301,7 +291,7 @@ func (o *OKGroup) GetOrderInfo(orderID string) (resp exchange.OrderDetail, err e resp = exchange.OrderDetail{ Amount: order.Size, CurrencyPair: currency.NewPairDelimiter(order.InstrumentID, - o.ConfigCurrencyPairFormat.Delimiter), + o.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), Exchange: o.Name, OrderDate: order.Timestamp, ExecutedAmount: order.FilledSize, @@ -322,7 +312,7 @@ func (o *OKGroup) GetDepositAddress(p currency.Code, accountID string) (_ string // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (o *OKGroup) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (o *OKGroup) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { withdrawal, err := o.AccountWithdraw(AccountWithdrawRequest{ Amount: withdrawRequest.Amount, Currency: withdrawRequest.Currency.Lower().String(), @@ -343,13 +333,13 @@ func (o *OKGroup) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.Withdraw // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (o *OKGroup) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (o *OKGroup) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (o *OKGroup) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (o *OKGroup) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -357,7 +347,8 @@ func (o *OKGroup) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange func (o *OKGroup) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) (resp []exchange.OrderDetail, err error) { for _, currency := range getOrdersRequest.Currencies { spotOpenOrders, err := o.GetSpotOpenOrders(GetSpotOpenOrdersRequest{ - InstrumentID: exchange.FormatExchangeCurrency(o.Name, currency).String(), + InstrumentID: o.FormatExchangeCurrency(currency, + assets.AssetTypeSpot).String(), }) if err != nil { return resp, err @@ -386,8 +377,9 @@ func (o *OKGroup) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( func (o *OKGroup) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) (resp []exchange.OrderDetail, err error) { for _, currency := range getOrdersRequest.Currencies { spotOpenOrders, err := o.GetSpotOrders(GetSpotOrdersRequest{ - Status: strings.Join([]string{"filled", "cancelled", "failure"}, "|"), - InstrumentID: exchange.FormatExchangeCurrency(o.Name, currency).String(), + Status: strings.Join([]string{"filled", "cancelled", "failure"}, "|"), + InstrumentID: o.FormatExchangeCurrency(currency, + assets.AssetTypeSpot).String(), }) if err != nil { return resp, err @@ -418,7 +410,7 @@ func (o *OKGroup) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (o *OKGroup) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (o.APIKey == "" || o.APISecret == "") && // Todo check connection status + if !o.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } diff --git a/exchanges/orderbook/README.md b/exchanges/orderbook/README.md index cb7e1987..5668bbd7 100644 --- a/exchanges/orderbook/README.md +++ b/exchanges/orderbook/README.md @@ -34,7 +34,7 @@ exchange interface system set by exchange wrapper orderbook functions in Examples below: ```go -ob, err := yobitExchange.GetOrderbookEx() +ob, err := yobitExchange.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index d225985c..f5267ad3 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -6,6 +6,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) // Const values for orderbook package @@ -13,8 +14,6 @@ const ( ErrOrderbookForExchangeNotFound = "ticker for exchange does not exist" ErrPrimaryCurrencyNotFound = "primary currency for orderbook not found" ErrSecondaryCurrencyNotFound = "secondary currency for orderbook not found" - - Spot = "SPOT" ) // Vars for the orderbook package @@ -32,17 +31,17 @@ type Item struct { // Base holds the fields for the orderbook base type Base struct { - Pair currency.Pair `json:"pair"` - Bids []Item `json:"bids"` - Asks []Item `json:"asks"` - LastUpdated time.Time `json:"lastUpdated"` - AssetType string `json:"assetType"` - ExchangeName string `json:"exchangeName"` + Pair currency.Pair `json:"pair"` + Bids []Item `json:"bids"` + Asks []Item `json:"asks"` + LastUpdated time.Time `json:"lastUpdated"` + AssetType assets.AssetType `json:"assetType"` + ExchangeName string `json:"exchangeName"` } // Orderbook holds the orderbook information for a currency pair and type type Orderbook struct { - Orderbook map[*currency.Item]map[*currency.Item]map[string]Base + Orderbook map[*currency.Item]map[*currency.Item]map[assets.AssetType]Base ExchangeName string } @@ -75,7 +74,7 @@ func (o *Base) Update(bids, asks []Item) { // Get checks and returns the orderbook given an exchange name and currency pair // if it exists -func Get(exchange string, p currency.Pair, orderbookType string) (Base, error) { +func Get(exchange string, p currency.Pair, orderbookType assets.AssetType) (Base, error) { orderbook, err := GetByExchange(exchange) if err != nil { return Base{}, err @@ -137,14 +136,14 @@ func QuoteCurrencyExists(exchange string, p currency.Pair) bool { } // CreateNewOrderbook creates a new orderbook -func CreateNewOrderbook(exchangeName string, orderbookNew *Base, orderbookType string) *Orderbook { +func CreateNewOrderbook(exchangeName string, orderbookNew *Base, orderbookType assets.AssetType) *Orderbook { m.Lock() defer m.Unlock() orderbook := Orderbook{} orderbook.ExchangeName = exchangeName - orderbook.Orderbook = make(map[*currency.Item]map[*currency.Item]map[string]Base) - a := make(map[*currency.Item]map[string]Base) - b := make(map[string]Base) + orderbook.Orderbook = make(map[*currency.Item]map[*currency.Item]map[assets.AssetType]Base) + a := make(map[*currency.Item]map[assets.AssetType]Base) + b := make(map[assets.AssetType]Base) b[orderbookType] = *orderbookNew a[orderbookNew.Pair.Quote.Item] = b orderbook.Orderbook[orderbookNew.Pair.Base.Item] = a @@ -175,7 +174,7 @@ func (o *Base) Process() error { if BaseCurrencyExists(o.ExchangeName, o.Pair.Base) { m.Lock() - a := make(map[string]Base) + a := make(map[assets.AssetType]Base) a[o.AssetType] = *o orderbook.Orderbook[o.Pair.Base.Item][o.Pair.Quote.Item] = a m.Unlock() @@ -183,8 +182,8 @@ func (o *Base) Process() error { } m.Lock() - a := make(map[*currency.Item]map[string]Base) - b := make(map[string]Base) + a := make(map[*currency.Item]map[assets.AssetType]Base) + b := make(map[assets.AssetType]Base) b[o.AssetType] = *o a[o.Pair.Quote.Item] = b orderbook.Orderbook[o.Pair.Base.Item] = a diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index b18cefc5..cea7c580 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -79,9 +80,9 @@ func TestGetOrderbook(t *testing.T) { Bids: []Item{{Price: 200, Amount: 10}}, } - CreateNewOrderbook("Exchange", &base, Spot) + CreateNewOrderbook("Exchange", &base, assets.AssetTypeSpot) - result, err := Get("Exchange", c, Spot) + result, err := Get("Exchange", c, assets.AssetTypeSpot) if err != nil { t.Fatalf("Test failed. TestGetOrderbook failed to get orderbook. Error %s", err) @@ -90,19 +91,19 @@ func TestGetOrderbook(t *testing.T) { t.Fatal("Test failed. TestGetOrderbook failed. Mismatched pairs") } - _, err = Get("nonexistent", c, Spot) + _, err = Get("nonexistent", c, assets.AssetTypeSpot) if err == nil { t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook") } c.Base = currency.NewCode("blah") - _, err = Get("Exchange", c, Spot) + _, err = Get("Exchange", c, assets.AssetTypeSpot) if err == nil { t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook using invalid first currency") } newCurrency := currency.NewPairFromStrings("BTC", "AUD") - _, err = Get("Exchange", newCurrency, Spot) + _, err = Get("Exchange", newCurrency, assets.AssetTypeSpot) if err == nil { t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook using invalid second currency") } @@ -116,7 +117,7 @@ func TestGetOrderbookByExchange(t *testing.T) { Bids: []Item{{Price: 200, Amount: 10}}, } - CreateNewOrderbook("Exchange", &base, Spot) + CreateNewOrderbook("Exchange", &base, assets.AssetTypeSpot) _, err := GetByExchange("Exchange") if err != nil { @@ -138,7 +139,7 @@ func TestFirstCurrencyExists(t *testing.T) { Bids: []Item{{Price: 200, Amount: 10}}, } - CreateNewOrderbook("Exchange", &base, Spot) + CreateNewOrderbook("Exchange", &base, assets.AssetTypeSpot) if !BaseCurrencyExists("Exchange", c.Base) { t.Fatal("Test failed. TestFirstCurrencyExists expected first currency doesn't exist") @@ -158,7 +159,7 @@ func TestSecondCurrencyExists(t *testing.T) { Bids: []Item{{Price: 200, Amount: 10}}, } - CreateNewOrderbook("Exchange", &base, Spot) + CreateNewOrderbook("Exchange", &base, assets.AssetTypeSpot) if !QuoteCurrencyExists("Exchange", c) { t.Fatal("Test failed. TestSecondCurrencyExists expected first currency doesn't exist") @@ -178,9 +179,9 @@ func TestCreateNewOrderbook(t *testing.T) { Bids: []Item{{Price: 200, Amount: 10}}, } - CreateNewOrderbook("Exchange", &base, Spot) + CreateNewOrderbook("Exchange", &base, assets.AssetTypeSpot) - result, err := Get("Exchange", c, Spot) + result, err := Get("Exchange", c, assets.AssetTypeSpot) if err != nil { t.Fatal("Test failed. TestCreateNewOrderbook failed to create new orderbook") } @@ -208,7 +209,7 @@ func TestProcessOrderbook(t *testing.T) { Asks: []Item{{Price: 100, Amount: 10}}, Bids: []Item{{Price: 200, Amount: 10}}, ExchangeName: "Exchange", - AssetType: Spot, + AssetType: assets.AssetTypeSpot, } err := base.Process() @@ -216,7 +217,7 @@ func TestProcessOrderbook(t *testing.T) { t.Error("Test Failed - Process() error", err) } - result, err := Get("Exchange", c, Spot) + result, err := Get("Exchange", c, assets.AssetTypeSpot) if err != nil { t.Fatal("Test failed. TestProcessOrderbook failed to create new orderbook") } @@ -233,7 +234,7 @@ func TestProcessOrderbook(t *testing.T) { t.Error("Test Failed - Process() error", err) } - result, err = Get("Exchange", c, Spot) + result, err = Get("Exchange", c, assets.AssetTypeSpot) if err != nil { t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") } @@ -310,7 +311,7 @@ func TestProcessOrderbook(t *testing.T) { Asks: asks, Bids: bids, ExchangeName: newName, - AssetType: Spot, + AssetType: assets.AssetTypeSpot, } m.Lock() @@ -337,7 +338,7 @@ func TestProcessOrderbook(t *testing.T) { wg.Add(1) fatalErr := false go func(test quick) { - result, err := Get(test.Name, test.P, Spot) + result, err := Get(test.Name, test.P, assets.AssetTypeSpot) if err != nil { fatalErr = true return diff --git a/exchanges/poloniex/README.md b/exchanges/poloniex/README.md index ea80ddcd..15abb971 100644 --- a/exchanges/poloniex/README.md +++ b/exchanges/poloniex/README.md @@ -48,22 +48,22 @@ main.go ```go var p exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Poloniex" { - y = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Poloniex" { + y = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := p.GetTickerPrice() +tick, err := p.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := p.GetOrderbookEx() +ob, err := p.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 3d0b8e0d..e29fd18c 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -12,13 +12,9 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -32,8 +28,6 @@ const ( poloniexDepositsWithdrawals = "returnDepositsWithdrawals" poloniexOrders = "returnOpenOrders" poloniexTradeHistory = "returnTradeHistory" - poloniexOrderBuy = "buy" - poloniexOrderSell = "sell" poloniexOrderCancel = "cancelOrder" poloniexOrderMove = "moveOrder" poloniexWithdraw = "withdraw" @@ -66,87 +60,6 @@ type Poloniex struct { wsRequestMtx sync.Mutex } -// SetDefaults sets default settings for poloniex -func (p *Poloniex) SetDefaults() { - p.Name = "Poloniex" - p.Enabled = false - p.Fee = 0 - p.Verbose = false - p.RESTPollingDelay = 10 - p.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithAPIPermission | - exchange.NoFiatWithdrawals - p.RequestCurrencyPairFormat.Delimiter = "_" - p.RequestCurrencyPairFormat.Uppercase = true - p.ConfigCurrencyPairFormat.Delimiter = "_" - p.ConfigCurrencyPairFormat.Uppercase = true - p.AssetTypes = []string{ticker.Spot} - p.SupportsAutoPairUpdating = true - p.SupportsRESTTickerBatching = true - p.Requester = request.New(p.Name, - request.NewRateLimit(time.Second, poloniexAuthRate), - request.NewRateLimit(time.Second, poloniexUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - p.APIUrlDefault = poloniexAPIURL - p.APIUrl = p.APIUrlDefault - p.WebsocketInit() - p.Websocket.Functionality = exchange.WebsocketTradeDataSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketTickerSupported | - exchange.WebsocketSubscribeSupported | - exchange.WebsocketUnsubscribeSupported -} - -// Setup sets user exchange configuration settings -func (p *Poloniex) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - p.SetEnabled(false) - } else { - p.Enabled = true - p.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - p.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - p.SetHTTPClientTimeout(exch.HTTPTimeout) - p.SetHTTPClientUserAgent(exch.HTTPUserAgent) - p.RESTPollingDelay = exch.RESTPollingDelay - p.Verbose = exch.Verbose - p.HTTPDebugging = exch.HTTPDebugging - p.Websocket.SetWsStatusAndConnection(exch.Websocket) - p.BaseCurrencies = exch.BaseCurrencies - p.AvailablePairs = exch.AvailablePairs - p.EnabledPairs = exch.EnabledPairs - err := p.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = p.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = p.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = p.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = p.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = p.WebsocketSetup(p.WsConnect, - p.Subscribe, - p.Unsubscribe, - exch.Name, - exch.Websocket, - exch.Verbose, - poloniexWebsocketAddress, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // GetTicker returns current ticker information func (p *Poloniex) GetTicker() (map[string]Ticker, error) { type response struct { @@ -154,7 +67,7 @@ func (p *Poloniex) GetTicker() (map[string]Ticker, error) { } resp := response{} - path := fmt.Sprintf("%s/public?command=returnTicker", p.APIUrl) + path := fmt.Sprintf("%s/public?command=returnTicker", p.API.Endpoints.URL) return resp.Data, p.SendHTTPRequest(path, &resp.Data) } @@ -162,7 +75,7 @@ func (p *Poloniex) GetTicker() (map[string]Ticker, error) { // GetVolume returns a list of currencies with associated volume func (p *Poloniex) GetVolume() (interface{}, error) { var resp interface{} - path := fmt.Sprintf("%s/public?command=return24hVolume", p.APIUrl) + path := fmt.Sprintf("%s/public?command=return24hVolume", p.API.Endpoints.URL) return resp, p.SendHTTPRequest(path, &resp) } @@ -179,7 +92,7 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (OrderbookAll, e if currencyPair != "" { vals.Set("currencyPair", currencyPair) resp := OrderbookResponse{} - path := fmt.Sprintf("%s/public?command=returnOrderBook&%s", p.APIUrl, vals.Encode()) + path := fmt.Sprintf("%s/public?command=returnOrderBook&%s", p.API.Endpoints.URL, vals.Encode()) err := p.SendHTTPRequest(path, &resp) if err != nil { return oba, err @@ -211,7 +124,7 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (OrderbookAll, e } else { vals.Set("currencyPair", "all") resp := OrderbookResponseAll{} - path := fmt.Sprintf("%s/public?command=returnOrderBook&%s", p.APIUrl, vals.Encode()) + path := fmt.Sprintf("%s/public?command=returnOrderBook&%s", p.API.Endpoints.URL, vals.Encode()) err := p.SendHTTPRequest(path, &resp.Data) if err != nil { return oba, err @@ -257,7 +170,7 @@ func (p *Poloniex) GetTradeHistory(currencyPair, start, end string) ([]TradeHist } var resp []TradeHistory - path := fmt.Sprintf("%s/public?command=returnTradeHistory&%s", p.APIUrl, vals.Encode()) + path := fmt.Sprintf("%s/public?command=returnTradeHistory&%s", p.API.Endpoints.URL, vals.Encode()) return resp, p.SendHTTPRequest(path, &resp) } @@ -280,7 +193,7 @@ func (p *Poloniex) GetChartData(currencyPair, start, end, period string) ([]Char } var resp []ChartData - path := fmt.Sprintf("%s/public?command=returnChartData&%s", p.APIUrl, vals.Encode()) + path := fmt.Sprintf("%s/public?command=returnChartData&%s", p.API.Endpoints.URL, vals.Encode()) err := p.SendHTTPRequest(path, &resp) if err != nil { @@ -296,32 +209,16 @@ func (p *Poloniex) GetCurrencies() (map[string]Currencies, error) { Data map[string]Currencies } resp := Response{} - path := fmt.Sprintf("%s/public?command=returnCurrencies", p.APIUrl) + path := fmt.Sprintf("%s/public?command=returnCurrencies", p.API.Endpoints.URL) return resp.Data, p.SendHTTPRequest(path, &resp.Data) } -// GetExchangeCurrencies returns a list of currencies using the GetTicker API -// as the GetExchangeCurrencies information doesn't return currency pair information -func (p *Poloniex) GetExchangeCurrencies() ([]string, error) { - response, err := p.GetTicker() - if err != nil { - return nil, err - } - - var currencies []string - for x := range response { - currencies = append(currencies, x) - } - - return currencies, nil -} - // GetLoanOrders returns the list of loan offers and demands for a given // currency, specified by the "currency" GET parameter. func (p *Poloniex) GetLoanOrders(currency string) (LoanOrders, error) { resp := LoanOrders{} - path := fmt.Sprintf("%s/public?command=returnLoanOrders¤cy=%s", p.APIUrl, currency) + path := fmt.Sprintf("%s/public?command=returnLoanOrders¤cy=%s", p.API.Endpoints.URL, currency) return resp, p.SendHTTPRequest(path, &resp) } @@ -512,9 +409,9 @@ func (p *Poloniex) PlaceOrder(currency string, rate, amount float64, immediate, var orderType string if buy { - orderType = poloniexOrderBuy + orderType = exchange.BuyOrderSide.ToLower().ToString() } else { - orderType = poloniexOrderSell + orderType = exchange.SellOrderSide.ToLower().ToString() } values.Set("currencyPair", currency) @@ -879,26 +776,26 @@ func (p *Poloniex) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated HTTP request func (p *Poloniex) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error { - if !p.AuthenticatedAPISupport { + if !p.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, p.Name) } headers := make(map[string]string) headers["Content-Type"] = "application/x-www-form-urlencoded" - headers["Key"] = p.APIKey + headers["Key"] = p.API.Credentials.Key n := p.Requester.GetNonce(true).String() values.Set("nonce", n) values.Set("command", endpoint) - hmac := common.GetHMAC(common.HashSHA512, + hmac := crypto.GetHMAC(crypto.HashSHA512, []byte(values.Encode()), - []byte(p.APISecret)) + []byte(p.API.Credentials.Secret)) - headers["Sign"] = common.HexEncodeToString(hmac) + headers["Sign"] = crypto.HexEncodeToString(hmac) - path := fmt.Sprintf("%s/%s", p.APIUrl, poloniexAPITradingEndpoint) + path := fmt.Sprintf("%s/%s", p.API.Endpoints.URL, poloniexAPITradingEndpoint) return p.SendPayload(method, path, diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index 7c80c5f1..5a8bbaed 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -29,11 +29,11 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Poloniex Setup() init error") } - poloniexConfig.AuthenticatedAPISupport = true - poloniexConfig.APIKey = apiKey - poloniexConfig.APISecret = apiSecret + poloniexConfig.API.AuthenticatedSupport = true + poloniexConfig.API.Credentials.Key = apiKey + poloniexConfig.API.Credentials.Secret = apiSecret p.SetDefaults() - p.Setup(&poloniexConfig) + p.Setup(poloniexConfig) isSetup = true } } @@ -242,11 +242,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if p.APIKey != "" && p.APIKey != "Key" && - p.APISecret != "" && p.APISecret != "Secret" { - return true - } - return false + return p.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -265,7 +261,7 @@ func TestSubmitOrder(t *testing.T) { response, err := p.SubmitOrder(pair, exchange.BuyOrderSide, - exchange.MarketOrderType, + exchange.LimitOrderType, 1, 10, "hi") @@ -346,11 +342,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { t.Parallel() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -374,8 +372,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := p.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -390,8 +387,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := p.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index 40e6c83c..4060f78c 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -159,7 +160,7 @@ func (p *Poloniex) WsHandleData() { p.Websocket.DataHandler <- exchange.TickerData{ Timestamp: time.Now(), Exchange: p.GetName(), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, LowPrice: t.LowestAsk, HighPrice: t.HighestBid, } @@ -196,7 +197,7 @@ func (p *Poloniex) WsHandleData() { p.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: p.GetName(), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Pair: currency.NewPairFromString(currencyPair), } case "o": @@ -209,7 +210,7 @@ func (p *Poloniex) WsHandleData() { p.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: p.GetName(), - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Pair: currency.NewPairFromString(currencyPair), } case "t": @@ -287,7 +288,8 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.AssetType = "SPOT" + newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.LastUpdated = time.Now() newOrderBook.Pair = currency.NewPairFromString(symbol) return p.Websocket.Orderbook.LoadSnapshot(&newOrderBook, p.GetName(), false) @@ -315,7 +317,7 @@ func (p *Poloniex) WsProcessOrderbookUpdate(target []interface{}, symbol string) cP, time.Now(), p.GetName(), - "SPOT") + assets.AssetTypeSpot) } return p.Websocket.Orderbook.Update([]orderbook.Item{{Price: price, Amount: volume}}, @@ -323,7 +325,7 @@ func (p *Poloniex) WsProcessOrderbookUpdate(target []interface{}, symbol string) cP, time.Now(), p.GetName(), - "SPOT") + assets.AssetTypeSpot) } // CurrencyPairID contains a list of IDS for currency pairs. @@ -443,7 +445,7 @@ func (p *Poloniex) GenerateDefaultSubscriptions() { Channel: fmt.Sprintf("%v", wsTickerDataID), }) - enabledCurrencies := p.GetEnabledCurrencies() + enabledCurrencies := p.GetEnabledPairs(assets.AssetTypeSpot) for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "_" subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{ diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 5e93a95a..318822cc 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -8,13 +8,115 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (p *Poloniex) GetDefaultConfig() (*config.ExchangeConfig, error) { + p.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = p.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = p.BaseCurrencies + + err := p.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if p.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = p.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets default settings for poloniex +func (p *Poloniex) SetDefaults() { + p.Name = "Poloniex" + p.Enabled = true + p.Verbose = true + p.API.CredentialsValidator.RequiresKey = true + p.API.CredentialsValidator.RequiresSecret = true + + p.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: true, + }, + } + + p.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + p.Requester = request.New(p.Name, + request.NewRateLimit(time.Second, poloniexAuthRate), + request.NewRateLimit(time.Second, poloniexUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + p.API.Endpoints.URLDefault = poloniexAPIURL + p.API.Endpoints.URL = p.API.Endpoints.URLDefault + p.WebsocketInit() + p.Websocket.Functionality = exchange.WebsocketTradeDataSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketTickerSupported | + exchange.WebsocketSubscribeSupported | + exchange.WebsocketUnsubscribeSupported +} + +// Setup sets user exchange configuration settings +func (p *Poloniex) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + p.SetEnabled(false) + return nil + } + + err := p.SetupDefaults(exch) + if err != nil { + return err + } + + return p.WebsocketSetup(p.WsConnect, + p.Subscribe, + p.Unsubscribe, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + poloniexWebsocketAddress, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the Poloniex go routine func (p *Poloniex) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -28,45 +130,63 @@ func (p *Poloniex) Start(wg *sync.WaitGroup) { func (p *Poloniex) Run() { if p.Verbose { log.Debugf("%s Websocket: %s (url: %s).\n", p.GetName(), common.IsEnabled(p.Websocket.IsEnabled()), poloniexWebsocketAddress) - log.Debugf("%s polling delay: %ds.\n", p.GetName(), p.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", p.GetName(), len(p.EnabledPairs), p.EnabledPairs) + p.PrintEnabledPairs() } - exchangeCurrencies, err := p.GetExchangeCurrencies() + forceUpdate := false + if common.StringDataCompare(p.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "BTC_USDT") { + log.Warnf("%s contains invalid pair, forcing upgrade of available currencies.\n", + p.Name) + forceUpdate = true + } + + if !p.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { + return + } + + err := p.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf("%s Failed to get available symbols.\n", p.GetName()) - } else { - forceUpdate := false - if common.StringDataCompare(p.AvailablePairs.Strings(), "BTC_USDT") { - log.Warnf("%s contains invalid pair, forcing upgrade of available currencies.\n", - p.GetName()) - forceUpdate = true - } - - var newExchangeCurrencies currency.Pairs - for _, p := range exchangeCurrencies { - newExchangeCurrencies = append(newExchangeCurrencies, - currency.NewPairFromString(p)) - } - - err = p.UpdateCurrencies(newExchangeCurrencies, false, forceUpdate) - if err != nil { - log.Errorf("%s Failed to update available currencies %s.\n", p.GetName(), err) - } + log.Errorf("%s failed to update tradable pairs. Err: %s", p.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (p *Poloniex) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + resp, err := p.GetTicker() + if err != nil { + return nil, err + } + + var currencies []string + for x := range resp { + currencies = append(currencies, x) + } + + return currencies, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (p *Poloniex) UpdateTradablePairs(forceUpgrade bool) error { + pairs, err := p.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return p.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpgrade) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType string) (ticker.Price, error) { +func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := p.GetTicker() if err != nil { return tickerPrice, err } - for _, x := range p.GetEnabledCurrencies() { + for _, x := range p.GetEnabledPairs(assetType) { var tp ticker.Price - curr := exchange.FormatExchangeCurrency(p.GetName(), x).String() + curr := p.FormatExchangeCurrency(x, assetType).String() tp.Pair = x tp.Ask = tick[curr].LowestAsk tp.Bid = tick[curr].HighestBid @@ -83,8 +203,8 @@ func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType string) (t return ticker.GetTicker(p.Name, currencyPair, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (p *Poloniex) GetTickerPrice(currencyPair currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (p *Poloniex) FetchTicker(currencyPair currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair, assetType) if err != nil { return p.UpdateTicker(currencyPair, assetType) @@ -92,8 +212,8 @@ func (p *Poloniex) GetTickerPrice(currencyPair currency.Pair, assetType string) return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (p *Poloniex) GetOrderbookEx(currencyPair currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns orderbook base on the currency pair +func (p *Poloniex) FetchOrderbook(currencyPair currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(p.GetName(), currencyPair, assetType) if err != nil { return p.UpdateOrderbook(currencyPair, assetType) @@ -102,15 +222,15 @@ func (p *Poloniex) GetOrderbookEx(currencyPair currency.Pair, assetType string) } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType string) (orderbook.Base, error) { +func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := p.GetOrderbook("", 1000) if err != nil { return orderBook, err } - for _, x := range p.GetEnabledCurrencies() { - currency := exchange.FormatExchangeCurrency(p.Name, x).String() + for _, x := range p.GetEnabledPairs(assetType) { + currency := p.FormatExchangeCurrency(x, assetType).String() data, ok := orderbookNew.Data[currency] if !ok { continue @@ -177,10 +297,8 @@ func (p *Poloniex) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (p *Poloniex) GetExchangeHistory(currencyPair currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (p *Poloniex) GetExchangeHistory(currencyPair currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -286,20 +404,20 @@ func (p *Poloniex) GetDepositAddress(cryptocurrency currency.Code, _ string) (st // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (p *Poloniex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (p *Poloniex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { _, err := p.Withdraw(withdrawRequest.Currency.String(), withdrawRequest.Address, withdrawRequest.Amount) return "", err } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (p *Poloniex) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (p *Poloniex) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (p *Poloniex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (p *Poloniex) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -310,7 +428,7 @@ func (p *Poloniex) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (p *Poloniex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (p.APIKey == "" || p.APISecret == "") && // Todo check connection status + if !p.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -327,7 +445,7 @@ func (p *Poloniex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) var orders []exchange.OrderDetail for currencyPair, openOrders := range resp.Data { symbol := currency.NewPairDelimiter(currencyPair, - p.ConfigCurrencyPairFormat.Delimiter) + p.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) for _, order := range openOrders { orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) @@ -369,7 +487,7 @@ func (p *Poloniex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) var orders []exchange.OrderDetail for currencyPair, historicOrders := range resp.Data { symbol := currency.NewPairDelimiter(currencyPair, - p.ConfigCurrencyPairFormat.Delimiter) + p.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) for _, order := range historicOrders { orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) diff --git a/exchanges/request/request.go b/exchanges/request/request.go index 052a6bf9..388afd94 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -21,10 +21,19 @@ import ( var supportedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead, http.MethodPut, http.MethodDelete, http.MethodOptions, http.MethodConnect} +// Const vars for rate limiter const ( - maxRequestJobs = 50 - proxyTLSTimeout = 15 * time.Second - defaultTimeoutRetryAttempts = 3 + DefaultMaxRequestJobs = 50 + DefaultTimeoutRetryAttempts = 3 + + proxyTLSTimeout = 15 * time.Second +) + +// Vars for rate limiter +var ( + MaxRequestJobs = DefaultMaxRequestJobs + TimeoutRetryAttempts = DefaultTimeoutRetryAttempts + DisableRateLimiter bool ) // Requester struct for the request client @@ -42,6 +51,7 @@ type Requester struct { WorkerStarted bool Nonce nonce.Nonce fifoLock sync.Mutex + DisableRateLimiter bool } // RateLimit struct @@ -147,6 +157,10 @@ func (r *Requester) IsRateLimited(auth bool) bool { // RequiresRateLimiter returns whether or not the request Requester requires a rate limiter func (r *Requester) RequiresRateLimiter() bool { + if DisableRateLimiter { + return false + } + if r.AuthLimit.GetRate() != 0 || r.UnauthLimit.GetRate() != 0 { return true } @@ -219,9 +233,9 @@ func New(name string, authLimit, unauthLimit *RateLimit, httpRequester *http.Cli UnauthLimit: unauthLimit, AuthLimit: authLimit, Name: name, - Jobs: make(chan Job, maxRequestJobs), + Jobs: make(chan Job, MaxRequestJobs), disengage: make(chan struct{}, 1), - timeoutRetryAttempts: defaultTimeoutRetryAttempts, + timeoutRetryAttempts: TimeoutRetryAttempts, } } @@ -447,7 +461,7 @@ func (r *Requester) SendPayload(method, path string, headers map[string]string, return r.DoRequest(req, path, body, result, authRequest, verbose, httpDebugging) } - if len(r.Jobs) == maxRequestJobs { + if len(r.Jobs) == MaxRequestJobs { r.unlock() return errors.New("max request jobs reached") } diff --git a/exchanges/stats/stats.go b/exchanges/stats/stats.go index 96616b7e..d9470159 100644 --- a/exchanges/stats/stats.go +++ b/exchanges/stats/stats.go @@ -4,13 +4,14 @@ import ( "sort" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) // Item holds various fields for storing currency pair stats type Item struct { Exchange string Pair currency.Pair - AssetType string + AssetType assets.AssetType Price float64 Volume float64 } @@ -49,7 +50,7 @@ func (b ByVolume) Swap(i, j int) { } // Add adds or updates the item stats -func Add(exchange string, p currency.Pair, assetType string, price, volume float64) { +func Add(exchange string, p currency.Pair, assetType assets.AssetType, price, volume float64) { if exchange == "" || assetType == "" || price == 0 || @@ -74,7 +75,7 @@ func Add(exchange string, p currency.Pair, assetType string, price, volume float // Append adds or updates the item stats for a specific // currency pair and asset type -func Append(exchange string, p currency.Pair, assetType string, price, volume float64) { +func Append(exchange string, p currency.Pair, assetType assets.AssetType, price, volume float64) { if AlreadyExists(exchange, p, assetType, price, volume) { return } @@ -92,7 +93,7 @@ func Append(exchange string, p currency.Pair, assetType string, price, volume fl // AlreadyExists checks to see if item info already exists // for a specific currency pair and asset type -func AlreadyExists(exchange string, p currency.Pair, assetType string, price, volume float64) bool { +func AlreadyExists(exchange string, p currency.Pair, assetType assets.AssetType, price, volume float64) bool { for i := range Items { if Items[i].Exchange == exchange && Items[i].Pair.EqualIncludeReciprocal(p) && @@ -107,7 +108,7 @@ func AlreadyExists(exchange string, p currency.Pair, assetType string, price, vo // 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 currency.Pair, assetType string, reverse bool) []Item { +func SortExchangesByVolume(p currency.Pair, assetType assets.AssetType, reverse bool) []Item { var result []Item for x := range Items { if Items[x].Pair.EqualIncludeReciprocal(p) && @@ -127,7 +128,7 @@ func SortExchangesByVolume(p currency.Pair, assetType string, reverse bool) []It // 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 currency.Pair, assetType string, reverse bool) []Item { +func SortExchangesByPrice(p currency.Pair, assetType assets.AssetType, reverse bool) []Item { var result []Item for x := range Items { if Items[x].Pair.EqualIncludeReciprocal(p) && diff --git a/exchanges/stats/stats_test.go b/exchanges/stats/stats_test.go index 1b0231e0..36084e7d 100644 --- a/exchanges/stats/stats_test.go +++ b/exchanges/stats/stats_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) func TestLenByPrice(t *testing.T) { @@ -12,7 +13,7 @@ func TestLenByPrice(t *testing.T) { { Exchange: "ANX", Pair: p, - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Price: 1200, Volume: 5, }, @@ -30,14 +31,14 @@ func TestLessByPrice(t *testing.T) { { Exchange: "alphapoint", Pair: p, - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Price: 1200, Volume: 5, }, { Exchange: "bitfinex", Pair: p, - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Price: 1198, Volume: 20, }, @@ -58,14 +59,14 @@ func TestSwapByPrice(t *testing.T) { { Exchange: "bitstamp", Pair: p, - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Price: 1324, Volume: 5, }, { Exchange: "btcc", Pair: p, - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Price: 7863, Volume: 20, }, @@ -103,7 +104,7 @@ func TestSwapByVolume(t *testing.T) { func TestAdd(t *testing.T) { Items = Items[:0] p := currency.NewPairFromStrings("BTC", "USD") - Add("ANX", p, "SPOT", 1200, 42) + Add("ANX", p, assets.AssetTypeSpot, 1200, 42) if len(Items) < 1 { t.Error("Test Failed - stats Add did not add exchange info.") @@ -116,14 +117,14 @@ func TestAdd(t *testing.T) { } p.Base = currency.XBT - Add("ANX", p, "SPOT", 1201, 43) + Add("ANX", p, assets.AssetTypeSpot, 1201, 43) if Items[1].Pair.String() != "XBTUSD" { t.Fatal("Test failed. stats Add did not add exchange info.") } p = currency.NewPairFromStrings("ETH", "USDT") - Add("ANX", p, "SPOT", 300, 1000) + Add("ANX", p, assets.AssetTypeSpot, 300, 1000) if Items[2].Pair.String() != "ETHUSD" { t.Fatal("Test failed. stats Add did not add exchange info.") @@ -132,12 +133,12 @@ func TestAdd(t *testing.T) { func TestAppend(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") - Append("sillyexchange", p, "SPOT", 1234, 45) + Append("sillyexchange", p, assets.AssetTypeSpot, 1234, 45) if len(Items) < 2 { t.Error("Test Failed - stats Append did not add exchange values.") } - Append("sillyexchange", p, "SPOT", 1234, 45) + Append("sillyexchange", p, assets.AssetTypeSpot, 1234, 45) if len(Items) == 3 { t.Error("Test Failed - stats Append added exchange values") } @@ -145,23 +146,23 @@ func TestAppend(t *testing.T) { func TestAlreadyExists(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") - if !AlreadyExists("ANX", p, "SPOT", 1200, 42) { + if !AlreadyExists("ANX", p, assets.AssetTypeSpot, 1200, 42) { t.Error("Test Failed - stats AlreadyExists exchange does not exist.") } p.Base = currency.NewCode("dii") - if AlreadyExists("bla", p, "SPOT", 1234, 123) { + if AlreadyExists("bla", p, assets.AssetTypeSpot, 1234, 123) { t.Error("Test Failed - stats AlreadyExists found incorrect exchange.") } } func TestSortExchangesByVolume(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") - topVolume := SortExchangesByVolume(p, "SPOT", true) + topVolume := SortExchangesByVolume(p, assets.AssetTypeSpot, true) if topVolume[0].Exchange != "sillyexchange" { t.Error("Test Failed - stats SortExchangesByVolume incorrectly sorted values.") } - topVolume = SortExchangesByVolume(p, "SPOT", false) + topVolume = SortExchangesByVolume(p, assets.AssetTypeSpot, false) if topVolume[0].Exchange != "ANX" { t.Error("Test Failed - stats SortExchangesByVolume incorrectly sorted values.") } @@ -169,12 +170,12 @@ func TestSortExchangesByVolume(t *testing.T) { func TestSortExchangesByPrice(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") - topPrice := SortExchangesByPrice(p, "SPOT", true) + topPrice := SortExchangesByPrice(p, assets.AssetTypeSpot, true) if topPrice[0].Exchange != "sillyexchange" { t.Error("Test Failed - stats SortExchangesByPrice incorrectly sorted values.") } - topPrice = SortExchangesByPrice(p, "SPOT", false) + topPrice = SortExchangesByPrice(p, assets.AssetTypeSpot, false) if topPrice[0].Exchange != "ANX" { t.Error("Test Failed - stats SortExchangesByPrice incorrectly sorted values.") } diff --git a/exchanges/support.go b/exchanges/support.go new file mode 100644 index 00000000..7e7a1098 --- /dev/null +++ b/exchanges/support.go @@ -0,0 +1,33 @@ +package exchange + +// Exchanges stores a list of supported exchanges +var Exchanges = []string{ + "anx", + "binance", + "bitfinex", + "bitflyer", + "bithumb", + "bitmex", + "bitstamp", + "bittrex", + "btcc", + "btc markets", + "btse", + "coinbasepro", + "coinut", + "exmo", + "gateio", + "gemini", + "hitbtc", + "huobi", + "huobihadax", + "itbit", + "kraken", + "lakebtc", + "localbitcoins", + "okcoin international", + "okex", + "poloniex", + "yobit", + "zb", +} diff --git a/exchanges/ticker/README.md b/exchanges/ticker/README.md index 5a695366..e21f692c 100644 --- a/exchanges/ticker/README.md +++ b/exchanges/ticker/README.md @@ -35,7 +35,7 @@ exchange interface system set by exchange wrapper orderbook functions in Examples below: ```go -tick, err := yobitExchange.GetTickerPrice() +tick, err := yobitExchange.FetchTicker() if err != nil { // Handle error } diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index 4b75fd27..f8732a96 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -8,15 +8,14 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) // Const values for the ticker package const ( ErrTickerForExchangeNotFound = "ticker for exchange does not exist" - ErrPrimaryCurrencyNotFound = "primary currency for ticker not found" - ErrSecondaryCurrencyNotFound = "secondary currency for ticker not found" - - Spot = "SPOT" + ErrPrimaryCurrencyNotFound = "error primary currency for ticker not found" + ErrSecondaryCurrencyNotFound = "error secondary currency for ticker not found" ) // Vars for the ticker package @@ -45,31 +44,31 @@ type Ticker struct { } // PriceToString returns the string version of a stored price field -func (t *Ticker) PriceToString(p currency.Pair, priceType, tickerType string) string { +func (t *Ticker) PriceToString(p currency.Pair, priceType string, tickerType assets.AssetType) string { priceType = common.StringToLower(priceType) switch priceType { case "last": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType].Last, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].Last, 'f', -1, 64) case "high": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType].High, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].High, 'f', -1, 64) case "low": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType].Low, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].Low, 'f', -1, 64) case "bid": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType].Bid, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].Bid, 'f', -1, 64) case "ask": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType].Ask, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].Ask, 'f', -1, 64) case "volume": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType].Volume, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].Volume, 'f', -1, 64) case "ath": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType].PriceATH, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].PriceATH, 'f', -1, 64) default: return "" } } // GetTicker checks and returns a requested ticker if it exists -func GetTicker(exchange string, p currency.Pair, tickerType string) (Price, error) { +func GetTicker(exchange string, p currency.Pair, tickerType assets.AssetType) (Price, error) { ticker, err := GetTickerByExchange(exchange) if err != nil { return Price{}, err @@ -83,7 +82,7 @@ func GetTicker(exchange string, p currency.Pair, tickerType string) (Price, erro return Price{}, errors.New(ErrSecondaryCurrencyNotFound) } - return ticker.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType], nil + return ticker.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()], nil } // GetTickerByExchange returns an exchange Ticker @@ -131,7 +130,7 @@ func SecondCurrencyExists(exchange string, p currency.Pair) bool { } // CreateNewTicker creates a new Ticker -func CreateNewTicker(exchangeName string, tickerNew *Price, tickerType string) Ticker { +func CreateNewTicker(exchangeName string, tickerNew *Price, tickerType assets.AssetType) Ticker { m.Lock() defer m.Unlock() ticker := Ticker{} @@ -139,7 +138,7 @@ func CreateNewTicker(exchangeName string, tickerNew *Price, tickerType string) T ticker.Price = make(map[string]map[string]map[string]Price) a := make(map[string]map[string]Price) b := make(map[string]Price) - b[tickerType] = *tickerNew + b[tickerType.String()] = *tickerNew a[tickerNew.Pair.Quote.Upper().String()] = b ticker.Price[tickerNew.Pair.Base.Upper().String()] = a Tickers = append(Tickers, ticker) @@ -148,7 +147,7 @@ func CreateNewTicker(exchangeName string, tickerNew *Price, tickerType string) T // ProcessTicker processes incoming tickers, creating or updating the Tickers // list -func ProcessTicker(exchangeName string, tickerNew *Price, tickerType string) error { +func ProcessTicker(exchangeName string, tickerNew *Price, tickerType assets.AssetType) error { if tickerNew.Pair.String() == "" { return errors.New("") } @@ -164,7 +163,7 @@ func ProcessTicker(exchangeName string, tickerNew *Price, tickerType string) err if FirstCurrencyExists(exchangeName, tickerNew.Pair.Base) { m.Lock() a := make(map[string]Price) - a[tickerType] = *tickerNew + a[tickerType.String()] = *tickerNew ticker.Price[tickerNew.Pair.Base.Upper().String()][tickerNew.Pair.Quote.Upper().String()] = a m.Unlock() return nil @@ -173,7 +172,7 @@ func ProcessTicker(exchangeName string, tickerNew *Price, tickerType string) err m.Lock() a := make(map[string]map[string]Price) b := make(map[string]Price) - b[tickerType] = *tickerNew + b[tickerType.String()] = *tickerNew a[tickerNew.Pair.Quote.Upper().String()] = b ticker.Price[tickerNew.Pair.Base.Upper().String()] = a m.Unlock() diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index 0b969afd..3b3b2931 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -25,30 +26,30 @@ func TestPriceToString(t *testing.T) { PriceATH: 1337, } - newTicker := CreateNewTicker("ANX", &priceStruct, Spot) + newTicker := CreateNewTicker("ANX", &priceStruct, assets.AssetTypeSpot) - if newTicker.PriceToString(newPair, "last", Spot) != "1200" { + if newTicker.PriceToString(newPair, "last", assets.AssetTypeSpot) != "1200" { t.Error("Test Failed - ticker PriceToString last value is incorrect") } - if newTicker.PriceToString(newPair, "high", Spot) != "1298" { + if newTicker.PriceToString(newPair, "high", assets.AssetTypeSpot) != "1298" { t.Error("Test Failed - ticker PriceToString high value is incorrect") } - if newTicker.PriceToString(newPair, "low", Spot) != "1148" { + if newTicker.PriceToString(newPair, "low", assets.AssetTypeSpot) != "1148" { t.Error("Test Failed - ticker PriceToString low value is incorrect") } - if newTicker.PriceToString(newPair, "bid", Spot) != "1195" { + if newTicker.PriceToString(newPair, "bid", assets.AssetTypeSpot) != "1195" { t.Error("Test Failed - ticker PriceToString bid value is incorrect") } - if newTicker.PriceToString(newPair, "ask", Spot) != "1220" { + if newTicker.PriceToString(newPair, "ask", assets.AssetTypeSpot) != "1220" { t.Error("Test Failed - ticker PriceToString ask value is incorrect") } - if newTicker.PriceToString(newPair, "volume", Spot) != "5" { + if newTicker.PriceToString(newPair, "volume", assets.AssetTypeSpot) != "5" { t.Error("Test Failed - ticker PriceToString volume value is incorrect") } - if newTicker.PriceToString(newPair, "ath", Spot) != "1337" { + if newTicker.PriceToString(newPair, "ath", assets.AssetTypeSpot) != "1337" { t.Error("Test Failed - ticker PriceToString ath value is incorrect") } - if newTicker.PriceToString(newPair, "obtuse", Spot) != "" { + if newTicker.PriceToString(newPair, "obtuse", assets.AssetTypeSpot) != "" { t.Error("Test Failed - ticker PriceToString obtuse value is incorrect") } } @@ -66,12 +67,12 @@ func TestGetTicker(t *testing.T) { PriceATH: 1337, } - err := ProcessTicker("bitfinex", &priceStruct, Spot) + err := ProcessTicker("bitfinex", &priceStruct, assets.AssetTypeSpot) if err != nil { t.Fatal("Test failed. ProcessTicker error", err) } - tickerPrice, err := GetTicker("bitfinex", newPair, Spot) + tickerPrice, err := GetTicker("bitfinex", newPair, assets.AssetTypeSpot) if err != nil { t.Errorf("Test Failed - Ticker GetTicker init error: %s", err) } @@ -79,19 +80,19 @@ func TestGetTicker(t *testing.T) { t.Error("Test Failed - ticker tickerPrice.CurrencyPair value is incorrect") } - _, err = GetTicker("blah", newPair, Spot) + _, err = GetTicker("blah", newPair, assets.AssetTypeSpot) if err == nil { t.Fatal("Test Failed. TestGetTicker returned nil error on invalid exchange") } newPair.Base = currency.ETH - _, err = GetTicker("bitfinex", newPair, Spot) + _, err = GetTicker("bitfinex", newPair, assets.AssetTypeSpot) if err == nil { t.Fatal("Test Failed. TestGetTicker returned ticker for invalid first currency") } btcltcPair := currency.NewPairFromStrings("BTC", "LTC") - _, err = GetTicker("bitfinex", btcltcPair, Spot) + _, err = GetTicker("bitfinex", btcltcPair, assets.AssetTypeSpot) if err == nil { t.Fatal("Test Failed. TestGetTicker returned ticker for invalid second currency") } @@ -126,7 +127,7 @@ func TestGetTickerByExchange(t *testing.T) { PriceATH: 1337, } - anxTicker := CreateNewTicker("ANX", &priceStruct, Spot) + anxTicker := CreateNewTicker("ANX", &priceStruct, assets.AssetTypeSpot) Tickers = append(Tickers, anxTicker) tickerPtr, err := GetTickerByExchange("ANX") @@ -151,7 +152,7 @@ func TestFirstCurrencyExists(t *testing.T) { PriceATH: 1337, } - alphaTicker := CreateNewTicker("alphapoint", &priceStruct, Spot) + alphaTicker := CreateNewTicker("alphapoint", &priceStruct, assets.AssetTypeSpot) Tickers = append(Tickers, alphaTicker) if !FirstCurrencyExists("alphapoint", currency.BTC) { @@ -177,7 +178,7 @@ func TestSecondCurrencyExists(t *testing.T) { PriceATH: 1337, } - bitstampTicker := CreateNewTicker("bitstamp", &priceStruct, "SPOT") + bitstampTicker := CreateNewTicker("bitstamp", &priceStruct, assets.AssetTypeSpot) Tickers = append(Tickers, bitstampTicker) if !SecondCurrencyExists("bitstamp", newPair) { @@ -204,7 +205,7 @@ func TestCreateNewTicker(t *testing.T) { PriceATH: 1337, } - newTicker := CreateNewTicker("ANX", &priceStruct, Spot) + newTicker := CreateNewTicker("ANX", &priceStruct, assets.AssetTypeSpot) if reflect.ValueOf(newTicker).NumField() != 2 { t.Error("Test Failed - ticker CreateNewTicker struct change/or updated") @@ -216,31 +217,31 @@ func TestCreateNewTicker(t *testing.T) { t.Error("Test Failed - ticker CreateNewTicker.ExchangeName value is not ANX") } - if newTicker.Price[currency.BTC.Upper().String()][currency.USD.Upper().String()][Spot].Pair.String() != "BTCUSD" { + if newTicker.Price[currency.BTC.Upper().String()][currency.USD.Upper().String()][assets.AssetTypeSpot.String()].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"][Spot].Ask).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Ask).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Ask value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Bid).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Bid).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Bid value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Pair).String() != "currency.Pair" { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Pair).String() != "currency.Pair" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].CurrencyPair value is not a currency.Pair") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].High).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].High).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].High value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Last).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Last).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Last value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Low).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Low).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Low value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].PriceATH).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].PriceATH).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].PriceATH value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Volume).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Volume).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Volume value is not a float64") } } @@ -259,17 +260,17 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers PriceATH: 1337, } - err := ProcessTicker("btcc", &Price{}, Spot) + err := ProcessTicker("btcc", &Price{}, assets.AssetTypeSpot) if err == nil { t.Fatal("Test failed. ProcessTicker error cannot be nil") } - err = ProcessTicker("btcc", &priceStruct, Spot) + err = ProcessTicker("btcc", &priceStruct, assets.AssetTypeSpot) if err != nil { t.Fatal("Test failed. ProcessTicker error", err) } - result, err := GetTicker("btcc", newPair, Spot) + result, err := GetTicker("btcc", newPair, assets.AssetTypeSpot) if err != nil { t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker") } @@ -280,17 +281,17 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers secondPair := currency.NewPairFromStrings("BTC", "AUD") priceStruct.Pair = secondPair - err = ProcessTicker("btcc", &priceStruct, Spot) + err = ProcessTicker("btcc", &priceStruct, assets.AssetTypeSpot) if err != nil { t.Fatal("Test failed. ProcessTicker error", err) } - result, err = GetTicker("btcc", secondPair, Spot) + result, err = GetTicker("btcc", secondPair, assets.AssetTypeSpot) if err != nil { t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker") } - result, err = GetTicker("btcc", newPair, Spot) + result, err = GetTicker("btcc", newPair, assets.AssetTypeSpot) if err != nil { t.Fatal("Test failed. TestProcessTicker failed to return an existing ticker") } @@ -326,7 +327,7 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers } sm.Lock() - err = ProcessTicker(newName, &tp, Spot) + err = ProcessTicker(newName, &tp, assets.AssetTypeSpot) if err != nil { log.Error(err) catastrophicFailure = true @@ -349,7 +350,7 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers wg.Add(1) fatalErr := false go func(test quick) { - result, err := GetTicker(test.Name, test.P, Spot) + result, err := GetTicker(test.Name, test.P, assets.AssetTypeSpot) if err != nil { fatalErr = true return diff --git a/exchanges/exchange_websocket.go b/exchanges/websocket.go similarity index 99% rename from exchanges/exchange_websocket.go rename to exchanges/websocket.go index 63ba1c7e..00540a16 100644 --- a/exchanges/exchange_websocket.go +++ b/exchanges/websocket.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -428,7 +429,7 @@ func (w *Websocket) GetName() string { func (w *WebsocketOrderbookLocal) Update(bidTargets, askTargets []orderbook.Item, p currency.Pair, updated time.Time, - exchName, assetType string) error { + exchName string, assetType assets.AssetType) error { if bidTargets == nil && askTargets == nil { return errors.New("exchange.go websocket orderbook cache Update() error - cannot have bids and ask targets both nil") } @@ -555,7 +556,7 @@ func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base, exc // UpdateUsingID updates orderbooks using specified ID func (w *WebsocketOrderbookLocal) UpdateUsingID(bidTargets, askTargets []orderbook.Item, p currency.Pair, - exchName, assetType, action string) error { + exchName string, assetType assets.AssetType, action string) error { w.m.Lock() defer w.m.Unlock() diff --git a/exchanges/exchange_websocket_test.go b/exchanges/websocket_test.go similarity index 98% rename from exchanges/exchange_websocket_test.go rename to exchanges/websocket_test.go index 8210d5bd..792d25b1 100644 --- a/exchanges/exchange_websocket_test.go +++ b/exchanges/websocket_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" ) @@ -160,7 +161,7 @@ func TestInsertingSnapShots(t *testing.T) { snapShot1.Asks = asks snapShot1.Bids = bids - snapShot1.AssetType = "SPOT" + snapShot1.AssetType = assets.AssetTypeSpot snapShot1.Pair = currency.NewPairFromString("BTCUSD") wsTest.Websocket.Orderbook.LoadSnapshot(&snapShot1, "ExchangeTest", false) @@ -196,7 +197,7 @@ func TestInsertingSnapShots(t *testing.T) { snapShot2.Asks = asks snapShot2.Bids = bids - snapShot2.AssetType = "SPOT" + snapShot2.AssetType = assets.AssetTypeSpot snapShot2.Pair = currency.NewPairFromString("LTCUSD") wsTest.Websocket.Orderbook.LoadSnapshot(&snapShot2, "ExchangeTest", false) @@ -264,7 +265,7 @@ func TestUpdate(t *testing.T) { LTCUSDPAIR, time.Now(), "ExchangeTest", - "SPOT") + assets.AssetTypeSpot) if err != nil { t.Error("test failed - OrderbookUpdate error", err) @@ -300,7 +301,7 @@ func TestUpdate(t *testing.T) { BTCUSDPAIR, time.Now(), "ExchangeTest", - "SPOT") + assets.AssetTypeSpot) if err != nil { t.Error("test failed - OrderbookUpdate error", err) diff --git a/exchanges/exchange_websocket_types.go b/exchanges/websocket_types.go similarity index 96% rename from exchanges/exchange_websocket_types.go rename to exchanges/websocket_types.go index b488dfd8..b81bfb71 100644 --- a/exchanges/exchange_websocket_types.go +++ b/exchanges/websocket_types.go @@ -5,6 +5,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" ) @@ -117,7 +118,7 @@ type WebsocketResponse struct { // has been updated in the orderbook package type WebsocketOrderbookUpdate struct { Pair currency.Pair - Asset string + Asset assets.AssetType Exchange string } @@ -125,7 +126,7 @@ type WebsocketOrderbookUpdate struct { type TradeData struct { Timestamp time.Time CurrencyPair currency.Pair - AssetType string + AssetType assets.AssetType Exchange string EventType string EventTime int64 @@ -138,7 +139,7 @@ type TradeData struct { type TickerData struct { Timestamp time.Time Pair currency.Pair - AssetType string + AssetType assets.AssetType Exchange string ClosePrice float64 Quantity float64 @@ -151,7 +152,7 @@ type TickerData struct { type KlineData struct { Timestamp time.Time Pair currency.Pair - AssetType string + AssetType assets.AssetType Exchange string StartTime time.Time CloseTime time.Time @@ -167,6 +168,6 @@ type KlineData struct { type WebsocketPositionUpdated struct { Timestamp time.Time Pair currency.Pair - AssetType string + AssetType assets.AssetType Exchange string } diff --git a/exchanges/yobit/README.md b/exchanges/yobit/README.md index dd6bd4a2..eca7d1ca 100644 --- a/exchanges/yobit/README.md +++ b/exchanges/yobit/README.md @@ -47,22 +47,22 @@ main.go ```go var y exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Yobit" { - y = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "Yobit" { + y = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := y.GetTickerPrice() +tick, err := y.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := y.GetOrderbookEx() +ob, err := y.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/yobit/yobit.go b/exchanges/yobit/yobit.go index c25a739b..0e10a69a 100644 --- a/exchanges/yobit/yobit.go +++ b/exchanges/yobit/yobit.go @@ -7,14 +7,10 @@ import ( "net/url" "strconv" "strings" - "time" - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -44,83 +40,12 @@ const ( // Yobit is the overarching type across the Yobit package type Yobit struct { exchange.Base - Ticker map[string]Ticker -} - -// SetDefaults sets current default value for Yobit -func (y *Yobit) SetDefaults() { - y.Name = "Yobit" - y.Enabled = true - y.Fee = 0.2 - y.Verbose = false - y.RESTPollingDelay = 10 - y.AuthenticatedAPISupport = true - y.Ticker = make(map[string]Ticker) - y.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithAPIPermission | - exchange.WithdrawFiatViaWebsiteOnly - y.RequestCurrencyPairFormat.Delimiter = "_" - y.RequestCurrencyPairFormat.Uppercase = false - y.RequestCurrencyPairFormat.Separator = "-" - y.ConfigCurrencyPairFormat.Delimiter = "_" - y.ConfigCurrencyPairFormat.Uppercase = true - y.AssetTypes = []string{ticker.Spot} - y.SupportsAutoPairUpdating = false - y.SupportsRESTTickerBatching = true - y.Requester = request.New(y.Name, - request.NewRateLimit(time.Second, yobitAuthRate), - request.NewRateLimit(time.Second, yobitUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - y.APIUrlDefault = apiPublicURL - y.APIUrl = y.APIUrlDefault - y.APIUrlSecondaryDefault = apiPrivateURL - y.APIUrlSecondary = y.APIUrlSecondaryDefault - y.WebsocketInit() -} - -// Setup sets exchange configuration parameters for Yobit -func (y *Yobit) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - y.SetEnabled(false) - } else { - y.Enabled = true - y.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - y.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - y.RESTPollingDelay = exch.RESTPollingDelay - y.Verbose = exch.Verbose - y.HTTPDebugging = exch.HTTPDebugging - y.Websocket.SetWsStatusAndConnection(exch.Websocket) - y.BaseCurrencies = exch.BaseCurrencies - y.AvailablePairs = exch.AvailablePairs - y.EnabledPairs = exch.EnabledPairs - y.SetHTTPClientTimeout(exch.HTTPTimeout) - y.SetHTTPClientUserAgent(exch.HTTPUserAgent) - err := y.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = y.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = y.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = y.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = y.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - } } // GetInfo returns the Yobit info func (y *Yobit) GetInfo() (Info, error) { resp := Info{} - path := fmt.Sprintf("%s/%s/%s/", y.APIUrl, apiPublicVersion, publicInfo) + path := fmt.Sprintf("%s/%s/%s/", y.API.Endpoints.URL, apiPublicVersion, publicInfo) return resp, y.SendHTTPRequest(path, &resp) } @@ -132,7 +57,7 @@ func (y *Yobit) GetTicker(symbol string) (map[string]Ticker, error) { } response := Response{} - path := fmt.Sprintf("%s/%s/%s/%s", y.APIUrl, apiPublicVersion, publicTicker, symbol) + path := fmt.Sprintf("%s/%s/%s/%s", y.API.Endpoints.URL, apiPublicVersion, publicTicker, symbol) return response.Data, y.SendHTTPRequest(path, &response.Data) } @@ -144,7 +69,7 @@ func (y *Yobit) GetDepth(symbol string) (Orderbook, error) { } response := Response{} - path := fmt.Sprintf("%s/%s/%s/%s", y.APIUrl, apiPublicVersion, publicDepth, symbol) + path := fmt.Sprintf("%s/%s/%s/%s", y.API.Endpoints.URL, apiPublicVersion, publicDepth, symbol) return response.Data[symbol], y.SendHTTPRequest(path, &response.Data) @@ -157,7 +82,7 @@ func (y *Yobit) GetTrades(symbol string) ([]Trades, error) { } response := Response{} - path := fmt.Sprintf("%s/%s/%s/%s", y.APIUrl, apiPublicVersion, publicTrades, symbol) + path := fmt.Sprintf("%s/%s/%s/%s", y.API.Endpoints.URL, apiPublicVersion, publicTrades, symbol) return response.Data[symbol], y.SendHTTPRequest(path, &response.Data) } @@ -344,9 +269,8 @@ func (y *Yobit) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to Yobit func (y *Yobit) SendAuthenticatedHTTPRequest(path string, params url.Values, result interface{}) (err error) { - if !y.AuthenticatedAPISupport { - return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, - y.Name) + if !y.AllowAuthenticatedRequest() { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, y.Name) } if params == nil { @@ -359,9 +283,7 @@ func (y *Yobit) SendAuthenticatedHTTPRequest(path string, params url.Values, res params.Set("method", path) encoded := params.Encode() - hmac := common.GetHMAC(common.HashSHA512, - []byte(encoded), - []byte(y.APISecret)) + hmac := crypto.GetHMAC(crypto.HashSHA512, []byte(encoded), []byte(y.API.Credentials.Secret)) if y.Verbose { log.Debugf("Sending POST request to %s calling path %s with params %s\n", @@ -371,8 +293,8 @@ func (y *Yobit) SendAuthenticatedHTTPRequest(path string, params url.Values, res } headers := make(map[string]string) - headers["Key"] = y.APIKey - headers["Sign"] = common.HexEncodeToString(hmac) + headers["Key"] = y.API.Credentials.Key + headers["Sign"] = crypto.HexEncodeToString(hmac) headers["Content-Type"] = "application/x-www-form-urlencoded" return y.SendPayload(http.MethodPost, diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index a6c48978..56f2ee2f 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) var y Yobit @@ -31,11 +32,19 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - Yobit init error") } - conf.APIKey = apiKey - conf.APISecret = apiSecret - conf.AuthenticatedAPISupport = true + conf.API.Credentials.Key = apiKey + conf.API.Credentials.Secret = apiSecret + conf.API.AuthenticatedSupport = true - y.Setup(&conf) + y.Setup(conf) +} + +func TestFetchTradablePairs(t *testing.T) { + t.Parallel() + _, err := y.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + t.Errorf("Test failed. FetchTradablePairs err: %s", err) + } } func TestGetInfo(t *testing.T) { @@ -104,7 +113,7 @@ func TestCancelOrder(t *testing.T) { func TestTrade(t *testing.T) { t.Parallel() - _, err := y.Trade("", "buy", 0, 0) + _, err := y.Trade("", exchange.BuyOrderSide.ToLower().ToString(), 0, 0) if err == nil { t.Error("Test Failed - Trade() error", err) } @@ -352,11 +361,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if y.APIKey != "" && y.APIKey != "Key" && - y.APISecret != "" && y.APISecret != "Secret" { - return true - } - return false + return y.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -372,7 +377,7 @@ func TestSubmitOrder(t *testing.T) { Base: currency.BTC, Quote: currency.USD, } - response, err := y.SubmitOrder(pair, exchange.BuyOrderSide, exchange.MarketOrderType, 1, 10, "hi") + response, err := y.SubmitOrder(pair, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "hi") if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { @@ -447,11 +452,13 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { y.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.LTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -475,8 +482,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := y.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -491,8 +497,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := y.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 6a541c0c..766a7add 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -10,14 +10,101 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) -// Start starts the Yobit go routine +// GetDefaultConfig returns a default exchange config +func (y *Yobit) GetDefaultConfig() (*config.ExchangeConfig, error) { + y.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = y.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = y.BaseCurrencies + + err := y.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if y.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = y.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets current default value for Yobit +func (y *Yobit) SetDefaults() { + y.Name = "Yobit" + y.Enabled = true + y.Verbose = true + y.API.CredentialsValidator.RequiresKey = true + y.API.CredentialsValidator.RequiresSecret = true + + y.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: false, + Separator: "-", + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: true, + }, + } + + y.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: false, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | + exchange.WithdrawFiatViaWebsiteOnly, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + y.Requester = request.New(y.Name, + request.NewRateLimit(time.Second, yobitAuthRate), + request.NewRateLimit(time.Second, yobitUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + y.API.Endpoints.URLDefault = apiPublicURL + y.API.Endpoints.URL = y.API.Endpoints.URLDefault + y.API.Endpoints.URLSecondaryDefault = apiPrivateURL + y.API.Endpoints.URLSecondary = y.API.Endpoints.URLSecondaryDefault +} + +// Setup sets exchange configuration parameters for Yobit +func (y *Yobit) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + y.SetEnabled(false) + return nil + } + + return y.SetupDefaults(exch) +} + +// Start starts the WEX go routine func (y *Yobit) Start(wg *sync.WaitGroup) { wg.Add(1) go func() { @@ -29,16 +116,49 @@ func (y *Yobit) Start(wg *sync.WaitGroup) { // Run implements the Yobit wrapper func (y *Yobit) Run() { if y.Verbose { - log.Debugf("%s Websocket: %s.", y.GetName(), common.IsEnabled(y.Websocket.IsEnabled())) - log.Debugf("%s polling delay: %ds.\n", y.GetName(), y.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", y.GetName(), len(y.EnabledPairs), y.EnabledPairs) + y.PrintEnabledPairs() + } + + if !y.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := y.UpdateTradablePairs(false) + if err != nil { + log.Errorf("%s failed to update tradable pairs. Err: %s", y.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (y *Yobit) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + info, err := y.GetInfo() + if err != nil { + return nil, err + } + + var currencies []string + for x := range info.Pairs { + currencies = append(currencies, common.StringToUpper(x)) + } + + return currencies, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (y *Yobit) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := y.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return err + } + + return y.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (y *Yobit) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (y *Yobit) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price - pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(y.Name, y.GetEnabledCurrencies()) + pairsCollated, err := y.FormatExchangeCurrencies(y.GetEnabledPairs(assetType), assetType) if err != nil { return tickerPrice, err } @@ -48,8 +168,8 @@ func (y *Yobit) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, e return tickerPrice, err } - for _, x := range y.GetEnabledCurrencies() { - currency := exchange.FormatExchangeCurrency(y.Name, x).Lower().String() + for _, x := range y.GetEnabledPairs(assetType) { + currency := y.FormatExchangeCurrency(x, assetType).Lower().String() var tickerPrice ticker.Price tickerPrice.Pair = x tickerPrice.Last = result[currency].Last @@ -67,8 +187,8 @@ func (y *Yobit) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, e return ticker.GetTicker(y.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (y *Yobit) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (y *Yobit) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tick, err := ticker.GetTicker(y.GetName(), p, assetType) if err != nil { return y.UpdateTicker(p, assetType) @@ -76,8 +196,8 @@ func (y *Yobit) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, return tick, nil } -// GetOrderbookEx returns the orderbook for a currency pair -func (y *Yobit) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Base, error) { +// FetchOrderbook returns the orderbook for a currency pair +func (y *Yobit) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { ob, err := orderbook.Get(y.GetName(), p, assetType) if err != nil { return y.UpdateOrderbook(p, assetType) @@ -86,9 +206,9 @@ func (y *Yobit) GetOrderbookEx(p currency.Pair, assetType string) (orderbook.Bas } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (y *Yobit) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (y *Yobit) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := y.GetDepth(exchange.FormatExchangeCurrency(y.Name, p).String()) + orderbookNew, err := y.GetDepth(y.FormatExchangeCurrency(p, assetType).String()) if err != nil { return orderBook, err } @@ -155,10 +275,8 @@ func (y *Yobit) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (y *Yobit) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (y *Yobit) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -206,8 +324,9 @@ func (y *Yobit) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelA } var allActiveOrders []map[string]ActiveOrders - for _, pair := range y.EnabledPairs { - activeOrdersForPair, err := y.GetOpenOrders(pair.String()) + for _, pair := range y.GetEnabledPairs(assets.AssetTypeSpot) { + activeOrdersForPair, err := y.GetOpenOrders(y.FormatExchangeCurrency(pair, + assets.AssetTypeSpot).String()) if err != nil { return cancelAllOrdersResponse, err } @@ -250,7 +369,7 @@ func (y *Yobit) GetDepositAddress(cryptocurrency currency.Code, _ string) (strin // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (y *Yobit) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (y *Yobit) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { resp, err := y.WithdrawCoinsToAddress(withdrawRequest.Currency.String(), withdrawRequest.Amount, withdrawRequest.Address) if err != nil { return "", err @@ -263,13 +382,13 @@ func (y *Yobit) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRe // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (y *Yobit) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (y *Yobit) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (y *Yobit) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (y *Yobit) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -280,7 +399,7 @@ func (y *Yobit) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (y *Yobit) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (y.APIKey == "" || y.APISecret == "") && // Todo check connection status + if !y.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -291,15 +410,15 @@ func (y *Yobit) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { func (y *Yobit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { var orders []exchange.OrderDetail for _, c := range getOrdersRequest.Currencies { - resp, err := y.GetOpenOrders(exchange.FormatExchangeCurrency(y.Name, - c).String()) + resp, err := y.GetOpenOrders(y.FormatExchangeCurrency(c, + assets.AssetTypeSpot).String()) if err != nil { return nil, err } for ID, order := range resp { symbol := currency.NewPairDelimiter(order.Pair, - y.ConfigCurrencyPairFormat.Delimiter) + y.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(order.TimestampCreated), 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) orders = append(orders, exchange.OrderDetail{ @@ -317,7 +436,6 @@ func (y *Yobit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } @@ -332,7 +450,7 @@ func (y *Yobit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] getOrdersRequest.StartTicks.Unix(), getOrdersRequest.EndTicks.Unix(), "DESC", - exchange.FormatExchangeCurrency(y.Name, currency).String()) + y.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String()) if err != nil { return nil, err } @@ -345,7 +463,7 @@ func (y *Yobit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] var orders []exchange.OrderDetail for _, order := range allOrders { symbol := currency.NewPairDelimiter(order.Pair, - y.ConfigCurrencyPairFormat.Delimiter) + y.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(order.Timestamp), 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/zb/README.md b/exchanges/zb/README.md index f0c50cec..8d04d502 100644 --- a/exchanges/zb/README.md +++ b/exchanges/zb/README.md @@ -47,22 +47,22 @@ main.go ```go var z exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "ZB" { - z = bot.exchanges[i] +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "ZB" { + z = bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := z.GetTickerPrice() +tick, err := z.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := z.GetOrderbookEx() +ob, err := z.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index 575ed837..349f8895 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -11,15 +11,11 @@ import ( "sync" "time" - "github.com/thrasher-/gocryptotrader/currency" - "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/common/crypto" + "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/request" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -53,95 +49,12 @@ type ZB struct { wsRequestMtx sync.Mutex } -// SetDefaults sets default values for the exchange -func (z *ZB) SetDefaults() { - z.Name = "ZB" - z.Enabled = false - z.Fee = 0 - z.Verbose = false - z.RESTPollingDelay = 10 - z.APIWithdrawPermissions = exchange.AutoWithdrawCrypto | - exchange.NoFiatWithdrawals - z.RequestCurrencyPairFormat.Delimiter = "_" - z.RequestCurrencyPairFormat.Uppercase = false - z.ConfigCurrencyPairFormat.Delimiter = "_" - z.ConfigCurrencyPairFormat.Uppercase = true - z.AssetTypes = []string{ticker.Spot} - z.SupportsAutoPairUpdating = true - z.SupportsRESTTickerBatching = true - z.Requester = request.New(z.Name, - request.NewRateLimit(time.Second*10, zbAuthRate), - request.NewRateLimit(time.Second*10, zbUnauthRate), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - z.APIUrlDefault = zbTradeURL - z.APIUrl = z.APIUrlDefault - z.APIUrlSecondaryDefault = zbMarketURL - z.APIUrlSecondary = z.APIUrlSecondaryDefault - z.WebsocketInit() - z.Websocket.Functionality = exchange.WebsocketTickerSupported | - exchange.WebsocketOrderbookSupported | - exchange.WebsocketTradeDataSupported | - exchange.WebsocketSubscribeSupported -} - -// Setup sets user configuration -func (z *ZB) Setup(exch *config.ExchangeConfig) { - if !exch.Enabled { - z.SetEnabled(false) - } else { - z.Enabled = true - z.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - z.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - z.APIAuthPEMKey = exch.APIAuthPEMKey - z.SetHTTPClientTimeout(exch.HTTPTimeout) - z.SetHTTPClientUserAgent(exch.HTTPUserAgent) - z.RESTPollingDelay = exch.RESTPollingDelay - z.Verbose = exch.Verbose - z.HTTPDebugging = exch.HTTPDebugging - z.Websocket.SetWsStatusAndConnection(exch.Websocket) - z.BaseCurrencies = exch.BaseCurrencies - z.AvailablePairs = exch.AvailablePairs - z.EnabledPairs = exch.EnabledPairs - err := z.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = z.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = z.SetAutoPairDefaults() - if err != nil { - log.Fatal(err) - } - err = z.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = z.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - err = z.WebsocketSetup(z.WsConnect, - z.Subscribe, - nil, - exch.Name, - exch.Websocket, - exch.Verbose, - zbWebsocketAPI, - exch.WebsocketURL) - if err != nil { - log.Fatal(err) - } - } -} - // SpotNewOrder submits an order to ZB func (z *ZB) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) { var result SpotNewOrderResponse vals := url.Values{} - vals.Set("accesskey", z.APIKey) + vals.Set("accesskey", z.API.Credentials.Key) vals.Set("method", "order") vals.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64)) vals.Set("currency", arg.Symbol) @@ -170,7 +83,7 @@ func (z *ZB) CancelExistingOrder(orderID int64, symbol string) error { } vals := url.Values{} - vals.Set("accesskey", z.APIKey) + vals.Set("accesskey", z.API.Credentials.Key) vals.Set("method", "cancelOrder") vals.Set("id", strconv.FormatInt(orderID, 10)) vals.Set("currency", symbol) @@ -193,7 +106,7 @@ func (z *ZB) GetAccountInformation() (AccountsResponse, error) { var result AccountsResponse vals := url.Values{} - vals.Set("accesskey", z.APIKey) + vals.Set("accesskey", z.API.Credentials.Key) vals.Set("method", "getAccountInfo") return result, z.SendAuthenticatedHTTPRequest(http.MethodGet, vals, &result) @@ -203,7 +116,7 @@ func (z *ZB) GetAccountInformation() (AccountsResponse, error) { func (z *ZB) GetUnfinishedOrdersIgnoreTradeType(currency string, pageindex, pagesize int64) ([]Order, error) { var result []Order vals := url.Values{} - vals.Set("accesskey", z.APIKey) + vals.Set("accesskey", z.API.Credentials.Key) vals.Set("method", zbUnfinishedOrdersIgnoreTradeType) vals.Set("currency", currency) vals.Set("pageIndex", strconv.FormatInt(pageindex, 10)) @@ -217,7 +130,7 @@ func (z *ZB) GetUnfinishedOrdersIgnoreTradeType(currency string, pageindex, page func (z *ZB) GetOrders(currency string, pageindex, side int64) ([]Order, error) { var response []Order vals := url.Values{} - vals.Set("accesskey", z.APIKey) + vals.Set("accesskey", z.API.Credentials.Key) vals.Set("method", zbGetOrdersGet) vals.Set("currency", currency) vals.Set("pageIndex", strconv.FormatInt(pageindex, 10)) @@ -228,7 +141,7 @@ func (z *ZB) GetOrders(currency string, pageindex, side int64) ([]Order, error) // GetMarkets returns market information including pricing, symbols and // each symbols decimal precision func (z *ZB) GetMarkets() (map[string]MarketResponseItem, error) { - endpoint := fmt.Sprintf("%s/%s/%s", z.APIUrl, zbAPIVersion, zbMarkets) + endpoint := fmt.Sprintf("%s/%s/%s", z.API.Endpoints.URL, zbAPIVersion, zbMarkets) var res interface{} err := z.SendHTTPRequest(endpoint, &res) @@ -264,25 +177,23 @@ func (z *ZB) GetLatestSpotPrice(symbol string) (float64, error) { // GetTicker returns a ticker for a given symbol func (z *ZB) GetTicker(symbol string) (TickerResponse, error) { - urlPath := fmt.Sprintf("%s/%s/%s?market=%s", z.APIUrl, zbAPIVersion, zbTicker, symbol) + urlPath := fmt.Sprintf("%s/%s/%s?market=%s", z.API.Endpoints.URL, zbAPIVersion, zbTicker, symbol) var res TickerResponse - err := z.SendHTTPRequest(urlPath, &res) return res, err } // GetTickers returns ticker data for all supported symbols func (z *ZB) GetTickers() (map[string]TickerChildResponse, error) { - urlPath := fmt.Sprintf("%s/%s/%s", z.APIUrl, zbAPIVersion, zbTickers) + urlPath := fmt.Sprintf("%s/%s/%s", z.API.Endpoints.URL, zbAPIVersion, zbTickers) resp := make(map[string]TickerChildResponse) - err := z.SendHTTPRequest(urlPath, &resp) return resp, err } // GetOrderbook returns the orderbook for a given symbol func (z *ZB) GetOrderbook(symbol string) (OrderbookResponse, error) { - urlPath := fmt.Sprintf("%s/%s/%s?market=%s", z.APIUrl, zbAPIVersion, zbDepth, symbol) + urlPath := fmt.Sprintf("%s/%s/%s?market=%s", z.API.Endpoints.URL, zbAPIVersion, zbDepth, symbol) var res OrderbookResponse err := z.SendHTTPRequest(urlPath, &res) @@ -290,6 +201,14 @@ func (z *ZB) GetOrderbook(symbol string) (OrderbookResponse, error) { return res, err } + if len(res.Asks) == 0 { + return res, fmt.Errorf("ZB GetOrderbook asks is empty") + } + + if len(res.Bids) == 0 { + return res, fmt.Errorf("ZB GetOrderbook bids is empty") + } + // reverse asks data var data [][]float64 for x := len(res.Asks); x > 0; x-- { @@ -312,7 +231,7 @@ func (z *ZB) GetSpotKline(arg KlinesRequestParams) (KLineResponse, error) { vals.Set("size", fmt.Sprintf("%d", arg.Size)) } - urlPath := fmt.Sprintf("%s/%s/%s?%s", z.APIUrl, zbAPIVersion, zbKline, vals.Encode()) + urlPath := fmt.Sprintf("%s/%s/%s?%s", z.API.Endpoints.URL, zbAPIVersion, zbKline, vals.Encode()) var res KLineResponse var rawKlines map[string]interface{} @@ -373,21 +292,21 @@ func (z *ZB) SendHTTPRequest(path string, result interface{}) error { // SendAuthenticatedHTTPRequest sends authenticated requests to the zb API func (z *ZB) SendAuthenticatedHTTPRequest(httpMethod string, params url.Values, result interface{}) error { - if !z.AuthenticatedAPISupport { + if !z.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, z.Name) } - params.Set("accesskey", z.APIKey) + params.Set("accesskey", z.API.Credentials.Key) - hmac := common.GetHMAC(common.HashMD5, + hmac := crypto.GetHMAC(crypto.HashMD5, []byte(params.Encode()), - []byte(common.Sha1ToHex(z.APISecret))) + []byte(crypto.Sha1ToHex(z.API.Credentials.Secret))) params.Set("reqTime", fmt.Sprintf("%d", common.UnixMillis(time.Now()))) params.Set("sign", fmt.Sprintf("%x", hmac)) urlPath := fmt.Sprintf("%s/%s?%s", - z.APIUrlSecondary, + z.API.Endpoints.URLSecondary, params.Get("method"), params.Encode()) @@ -495,7 +414,7 @@ func (z *ZB) Withdraw(currency, address, safepassword string, amount, fees float } vals := url.Values{} - vals.Set("accesskey", z.APIKey) + vals.Set("accesskey", z.API.Credentials.Key) vals.Set("amount", fmt.Sprintf("%v", amount)) vals.Set("currency", currency) vals.Set("fees", fmt.Sprintf("%v", fees)) diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index a60b042a..14a50e1a 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -30,17 +30,17 @@ func TestSetup(t *testing.T) { if err != nil { t.Error("Test Failed - ZB Setup() init error") } - zbConfig.AuthenticatedAPISupport = true - zbConfig.APIKey = apiKey - zbConfig.APISecret = apiSecret + zbConfig.API.AuthenticatedSupport = true + zbConfig.API.Credentials.Key = apiKey + zbConfig.API.Credentials.Secret = apiSecret - z.Setup(&zbConfig) + z.Setup(zbConfig) } func TestSpotNewOrder(t *testing.T) { t.Parallel() - if z.APIKey == "" || z.APISecret == "" { + if !z.ValidateAPICredentials() { t.Skip() } @@ -61,7 +61,7 @@ func TestSpotNewOrder(t *testing.T) { func TestCancelExistingOrder(t *testing.T) { t.Parallel() - if z.APIKey == "" || z.APISecret == "" { + if !z.ValidateAPICredentials() { t.Skip() } @@ -282,11 +282,7 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - if z.APIKey != "" && z.APIKey != "Key" && - z.APISecret != "" && z.APISecret != "Secret" { - return true - } - return false + return z.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { @@ -295,7 +291,7 @@ func TestSubmitOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip(fmt.Sprintf("ApiKey: %s. Can place orders: %v", - z.APIKey, + z.API.Credentials.Key, canManipulateRealOrders)) } var pair = currency.Pair{ @@ -306,7 +302,7 @@ func TestSubmitOrder(t *testing.T) { response, err := z.SubmitOrder(pair, exchange.BuyOrderSide, - exchange.MarketOrderType, + exchange.LimitOrderType, 1, 10, "hi") @@ -376,7 +372,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - if apiKey != "" || apiSecret != "" { + if z.ValidateAPICredentials() { _, err := z.GetAccountInfo() if err != nil { t.Error("Test Failed - GetAccountInfo() error", err) @@ -399,12 +395,14 @@ func TestModifyOrder(t *testing.T) { func TestWithdraw(t *testing.T) { z.SetDefaults() TestSetup(t) - var withdrawCryptoRequest = exchange.WithdrawRequest{ - Amount: 100, - Currency: currency.BTC, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - Description: "WITHDRAW IT ALL", - FeeAmount: 1, + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ + Amount: -1, + Currency: currency.BTC, + Description: "WITHDRAW IT ALL", + }, + Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", + FeeAmount: 1, } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -428,8 +426,7 @@ func TestWithdrawFiat(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := z.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -444,8 +441,7 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.WithdrawRequest{} - + var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := z.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index b219cee9..4d5bc2ac 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -115,7 +116,7 @@ func (z *ZB) WsHandleData() { z.Websocket.DataHandler <- exchange.TickerData{ Timestamp: time.Unix(0, ticker.Date), Pair: currency.NewPairFromString(cPair[0]), - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Exchange: z.GetName(), ClosePrice: ticker.Data.Last, HighPrice: ticker.Data.High, @@ -154,7 +155,7 @@ func (z *ZB) WsHandleData() { var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.AssetType = "SPOT" + newOrderBook.AssetType = assets.AssetTypeSpot newOrderBook.Pair = cPair err = z.Websocket.Orderbook.LoadSnapshot(&newOrderBook, @@ -167,7 +168,7 @@ func (z *ZB) WsHandleData() { z.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: cPair, - Asset: "SPOT", + Asset: assets.AssetTypeSpot, Exchange: z.GetName(), } @@ -188,7 +189,7 @@ func (z *ZB) WsHandleData() { z.Websocket.DataHandler <- exchange.TradeData{ Timestamp: time.Unix(0, t.Date), CurrencyPair: cPair, - AssetType: "SPOT", + AssetType: assets.AssetTypeSpot, Exchange: z.GetName(), EventTime: t.Date, Price: t.Price, @@ -246,7 +247,7 @@ func (z *ZB) GenerateDefaultSubscriptions() { Channel: "markets", }) channels := []string{"%s_ticker", "%s_depth", "%s_trades"} - enabledCurrencies := z.GetEnabledCurrencies() + enabledCurrencies := z.GetEnabledPairs(assets.AssetTypeSpot) for i := range channels { for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "" diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index cbf1bb9d..3a6d0ff9 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -8,13 +8,116 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func (z *ZB) GetDefaultConfig() (*config.ExchangeConfig, error) { + z.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = z.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = z.BaseCurrencies + + err := z.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if z.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = z.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets default values for the exchange +func (z *ZB) SetDefaults() { + z.Name = "ZB" + z.Enabled = true + z.Verbose = true + z.API.CredentialsValidator.RequiresKey = true + z.API.CredentialsValidator.RequiresSecret = true + + z.CurrencyPairs = currency.PairsManager{ + AssetTypes: assets.AssetTypes{ + assets.AssetTypeSpot, + }, + + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Delimiter: "_", + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "_", + Uppercase: true, + }, + } + + z.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + Websocket: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + TickerBatching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + z.Requester = request.New(z.Name, + request.NewRateLimit(time.Second*10, zbAuthRate), + request.NewRateLimit(time.Second*10, zbUnauthRate), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + z.API.Endpoints.URLDefault = zbTradeURL + z.API.Endpoints.URL = z.API.Endpoints.URLDefault + z.API.Endpoints.URLSecondaryDefault = zbMarketURL + z.API.Endpoints.URLSecondary = z.API.Endpoints.URLSecondaryDefault + z.WebsocketInit() + z.Websocket.Functionality = exchange.WebsocketTickerSupported | + exchange.WebsocketOrderbookSupported | + exchange.WebsocketTradeDataSupported | + exchange.WebsocketSubscribeSupported +} + +// Setup sets user configuration +func (z *ZB) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + z.SetEnabled(false) + return nil + } + + err := z.SetupDefaults(exch) + if err != nil { + return err + } + + return z.WebsocketSetup(z.WsConnect, + z.Subscribe, + nil, + exch.Name, + exch.Features.Enabled.Websocket, + exch.Verbose, + zbWebsocketAPI, + exch.API.Endpoints.WebsocketURL) +} + // Start starts the OKEX go routine func (z *ZB) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -27,35 +130,47 @@ func (z *ZB) Start(wg *sync.WaitGroup) { // Run implements the OKEX wrapper func (z *ZB) Run() { if z.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", z.GetName(), common.IsEnabled(z.Websocket.IsEnabled()), z.WebsocketURL) - log.Debugf("%s polling delay: %ds.\n", z.GetName(), z.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", z.GetName(), len(z.EnabledPairs), z.EnabledPairs) + z.PrintEnabledPairs() } - markets, err := z.GetMarkets() + if !z.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := z.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s Unable to fetch symbols.\n", z.GetName()) - } else { - var currencies []string - for x := range markets { - currencies = append(currencies, x) - } - - var newCurrencies currency.Pairs - for _, p := range currencies { - newCurrencies = append(newCurrencies, - currency.NewPairFromString(p)) - } - - err = z.UpdateCurrencies(newCurrencies, false, false) - if err != nil { - log.Errorf("%s Failed to update available currencies.\n", z.GetName()) - } + log.Errorf("%s failed to update tradable pairs. Err: %s", z.Name, err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (z *ZB) FetchTradablePairs(asset assets.AssetType) ([]string, error) { + markets, err := z.GetMarkets() + if err != nil { + return nil, err + } + + var currencies []string + for x := range markets { + currencies = append(currencies, x) + } + + return currencies, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (z *ZB) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := z.FetchTradablePairs(assets.AssetTypeSpot) + if err != nil { + return nil + } + + return z.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) +} + // UpdateTicker updates and returns the ticker for a currency pair -func (z *ZB) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) { +func (z *ZB) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { var tickerPrice ticker.Price result, err := z.GetTickers() @@ -63,8 +178,8 @@ func (z *ZB) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, erro return tickerPrice, err } - for _, x := range z.GetEnabledCurrencies() { - currencySplit := common.SplitStrings(exchange.FormatExchangeCurrency(z.Name, x).String(), "_") + for _, x := range z.GetEnabledPairs(assetType) { + currencySplit := common.SplitStrings(z.FormatExchangeCurrency(x, assetType).String(), "_") currency := currencySplit[0] + currencySplit[1] var tp ticker.Price tp.Pair = x @@ -85,8 +200,8 @@ func (z *ZB) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, erro return ticker.GetTicker(z.Name, p, assetType) } -// GetTickerPrice returns the ticker for a currency pair -func (z *ZB) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) { +// FetchTicker returns the ticker for a currency pair +func (z *ZB) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(z.GetName(), p, assetType) if err != nil { return z.UpdateTicker(p, assetType) @@ -94,19 +209,19 @@ func (z *ZB) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, er return tickerNew, nil } -// GetOrderbookEx returns orderbook base on the currency pair -func (z *ZB) GetOrderbookEx(currency currency.Pair, assetType string) (orderbook.Base, error) { - ob, err := orderbook.Get(z.GetName(), currency, assetType) +// FetchOrderbook returns orderbook base on the currency pair +func (z *ZB) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { + ob, err := orderbook.Get(z.GetName(), p, assetType) if err != nil { - return z.UpdateOrderbook(currency, assetType) + return z.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (z *ZB) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) { +func (z *ZB) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { var orderBook orderbook.Base - currency := exchange.FormatExchangeCurrency(z.Name, p).String() + currency := z.FormatExchangeCurrency(p, assetType).String() orderbookNew, err := z.GetOrderbook(currency) if err != nil { @@ -179,10 +294,8 @@ func (z *ZB) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (z *ZB) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented +func (z *ZB) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order @@ -229,7 +342,8 @@ func (z *ZB) CancelOrder(order *exchange.OrderCancellation) error { return err } - return z.CancelExistingOrder(orderIDInt, exchange.FormatExchangeCurrency(z.Name, order.CurrencyPair).String()) + return z.CancelExistingOrder(orderIDInt, z.FormatExchangeCurrency(order.CurrencyPair, + order.AssetType).String()) } // CancelAllOrders cancels all orders associated with a currency pair @@ -238,11 +352,11 @@ func (z *ZB) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllO OrderStatus: make(map[string]string), } var allOpenOrders []Order - for _, currency := range z.GetEnabledCurrencies() { + for _, currency := range z.GetEnabledPairs(assets.AssetTypeSpot) { var pageNumber int64 // Limiting to 10 pages for i := 0; i < 10; i++ { - openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(exchange.FormatExchangeCurrency(z.Name, currency).String(), 1, 10) + openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(z.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String(), 1, 10) if err != nil { return cancelAllOrdersResponse, err } @@ -284,19 +398,19 @@ func (z *ZB) GetDepositAddress(cryptocurrency currency.Code, _ string) (string, // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func (z *ZB) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (z *ZB) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return z.Withdraw(withdrawRequest.Currency.Lower().String(), withdrawRequest.Address, withdrawRequest.TradePassword, withdrawRequest.Amount, withdrawRequest.FeeAmount, false) } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted -func (z *ZB) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (z *ZB) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted -func (z *ZB) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func (z *ZB) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrFunctionNotSupported } @@ -307,7 +421,7 @@ func (z *ZB) GetWebsocket() (*exchange.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (z *ZB) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if (z.APIKey == "" || z.APISecret == "") && // Todo check connection status + if !z.AllowAuthenticatedRequest() && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -322,7 +436,7 @@ func (z *ZB) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exc var pageNumber int64 // Limiting to 10 pages for i := 0; i < 10; i++ { - resp, err := z.GetUnfinishedOrdersIgnoreTradeType(exchange.FormatExchangeCurrency(z.Name, currency).String(), pageNumber, 10) + resp, err := z.GetUnfinishedOrdersIgnoreTradeType(z.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String(), pageNumber, 10) if err != nil { return nil, err } @@ -338,7 +452,7 @@ func (z *ZB) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exc var orders []exchange.OrderDetail for _, order := range allOrders { symbol := currency.NewPairDelimiter(order.Currency, - z.ConfigCurrencyPairFormat.Delimiter) + z.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(order.TradeDate), 0) orderSide := orderSideMap[order.Type] orders = append(orders, exchange.OrderDetail{ @@ -355,7 +469,6 @@ func (z *ZB) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exc exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil } @@ -378,7 +491,7 @@ func (z *ZB) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exc var pageNumber int64 // Limiting to 10 pages for i := 0; i < 10; i++ { - resp, err := z.GetOrders(exchange.FormatExchangeCurrency(z.Name, currency).String(), pageNumber, side) + resp, err := z.GetOrders(z.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String(), pageNumber, side) if err != nil { return nil, err } @@ -395,7 +508,7 @@ func (z *ZB) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exc var orders []exchange.OrderDetail for _, order := range allOrders { symbol := currency.NewPairDelimiter(order.Currency, - z.ConfigCurrencyPairFormat.Delimiter) + z.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(order.TradeDate), 0) orderSide := orderSideMap[order.Type] orders = append(orders, exchange.OrderDetail{ @@ -410,7 +523,6 @@ func (z *ZB) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exc } exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - return orders, nil } diff --git a/gctrpc/auth/auth.go b/gctrpc/auth/auth.go new file mode 100644 index 00000000..e8076222 --- /dev/null +++ b/gctrpc/auth/auth.go @@ -0,0 +1,26 @@ +package auth + +import ( + "context" + "encoding/base64" +) + +// BasicAuth stores a basic auth username/password +type BasicAuth struct { + Username string + Password string +} + +// GetRequestMetadata is a implementation of the GetRequestMetadata function +func (b BasicAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) { + auth := b.Username + ":" + b.Password + enc := base64.StdEncoding.EncodeToString([]byte(auth)) + return map[string]string{ + "authorization": "Basic " + enc, + }, nil +} + +// RequireTransportSecurity is required for basic auth +func (BasicAuth) RequireTransportSecurity() bool { + return true +} diff --git a/gctrpc/gen_pb_linux.sh b/gctrpc/gen_pb_linux.sh new file mode 100644 index 00000000..8240e5ee --- /dev/null +++ b/gctrpc/gen_pb_linux.sh @@ -0,0 +1,4 @@ +echo "GoCryptoTrader: Generating gRPC, proxy and swagger files." +protoc -I=. -I=$GOPATH/src -I=$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=plugins=grpc:. rpc.proto +protoc -I=. -I=$GOPATH/src -I=$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:. rpc.proto +protoc -I=. -I=$GOPATH/src -I=$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --swagger_out=logtostderr=true:. rpc.proto \ No newline at end of file diff --git a/gctrpc/gen_pb_win.bat b/gctrpc/gen_pb_win.bat new file mode 100644 index 00000000..246b7dfe --- /dev/null +++ b/gctrpc/gen_pb_win.bat @@ -0,0 +1,5 @@ +@echo off +echo GoCryptoTrader: Generating gRPC, proxy and swagger files. +protoc -I=. -I=%GOPATH%\src -I=%GOPATH%\src\github.com\grpc-ecosystem\grpc-gateway\third_party\googleapis --go_out=plugins=grpc:. rpc.proto +protoc -I=. -I=%GOPATH%\src -I=%GOPATH%\src\github.com\grpc-ecosystem\grpc-gateway\third_party\googleapis --grpc-gateway_out=logtostderr=true:. rpc.proto +protoc -I=. -I=%GOPATH%\src -I=%GOPATH%\src\github.com\grpc-ecosystem\grpc-gateway\third_party\googleapis --swagger_out=logtostderr=true:. rpc.proto \ No newline at end of file diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go new file mode 100644 index 00000000..b563f77b --- /dev/null +++ b/gctrpc/rpc.pb.go @@ -0,0 +1,4987 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: rpc.proto + +package gctrpc + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type GetInfoRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetInfoRequest) Reset() { *m = GetInfoRequest{} } +func (m *GetInfoRequest) String() string { return proto.CompactTextString(m) } +func (*GetInfoRequest) ProtoMessage() {} +func (*GetInfoRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{0} +} + +func (m *GetInfoRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetInfoRequest.Unmarshal(m, b) +} +func (m *GetInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetInfoRequest.Marshal(b, m, deterministic) +} +func (m *GetInfoRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetInfoRequest.Merge(m, src) +} +func (m *GetInfoRequest) XXX_Size() int { + return xxx_messageInfo_GetInfoRequest.Size(m) +} +func (m *GetInfoRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetInfoRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetInfoRequest proto.InternalMessageInfo + +type GetInfoResponse struct { + Uptime string `protobuf:"bytes,1,opt,name=uptime,proto3" json:"uptime,omitempty"` + AvailableExchanges int64 `protobuf:"varint,2,opt,name=available_exchanges,json=availableExchanges,proto3" json:"available_exchanges,omitempty"` + EnabledExchanges int64 `protobuf:"varint,3,opt,name=enabled_exchanges,json=enabledExchanges,proto3" json:"enabled_exchanges,omitempty"` + DefaultForexProvider string `protobuf:"bytes,4,opt,name=default_forex_provider,json=defaultForexProvider,proto3" json:"default_forex_provider,omitempty"` + DefaultFiatCurrency string `protobuf:"bytes,5,opt,name=default_fiat_currency,json=defaultFiatCurrency,proto3" json:"default_fiat_currency,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetInfoResponse) Reset() { *m = GetInfoResponse{} } +func (m *GetInfoResponse) String() string { return proto.CompactTextString(m) } +func (*GetInfoResponse) ProtoMessage() {} +func (*GetInfoResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{1} +} + +func (m *GetInfoResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetInfoResponse.Unmarshal(m, b) +} +func (m *GetInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetInfoResponse.Marshal(b, m, deterministic) +} +func (m *GetInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetInfoResponse.Merge(m, src) +} +func (m *GetInfoResponse) XXX_Size() int { + return xxx_messageInfo_GetInfoResponse.Size(m) +} +func (m *GetInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetInfoResponse proto.InternalMessageInfo + +func (m *GetInfoResponse) GetUptime() string { + if m != nil { + return m.Uptime + } + return "" +} + +func (m *GetInfoResponse) GetAvailableExchanges() int64 { + if m != nil { + return m.AvailableExchanges + } + return 0 +} + +func (m *GetInfoResponse) GetEnabledExchanges() int64 { + if m != nil { + return m.EnabledExchanges + } + return 0 +} + +func (m *GetInfoResponse) GetDefaultForexProvider() string { + if m != nil { + return m.DefaultForexProvider + } + return "" +} + +func (m *GetInfoResponse) GetDefaultFiatCurrency() string { + if m != nil { + return m.DefaultFiatCurrency + } + return "" +} + +type GenericExchangeNameRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GenericExchangeNameRequest) Reset() { *m = GenericExchangeNameRequest{} } +func (m *GenericExchangeNameRequest) String() string { return proto.CompactTextString(m) } +func (*GenericExchangeNameRequest) ProtoMessage() {} +func (*GenericExchangeNameRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{2} +} + +func (m *GenericExchangeNameRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GenericExchangeNameRequest.Unmarshal(m, b) +} +func (m *GenericExchangeNameRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GenericExchangeNameRequest.Marshal(b, m, deterministic) +} +func (m *GenericExchangeNameRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenericExchangeNameRequest.Merge(m, src) +} +func (m *GenericExchangeNameRequest) XXX_Size() int { + return xxx_messageInfo_GenericExchangeNameRequest.Size(m) +} +func (m *GenericExchangeNameRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GenericExchangeNameRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GenericExchangeNameRequest proto.InternalMessageInfo + +func (m *GenericExchangeNameRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +type GenericExchangeNameResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GenericExchangeNameResponse) Reset() { *m = GenericExchangeNameResponse{} } +func (m *GenericExchangeNameResponse) String() string { return proto.CompactTextString(m) } +func (*GenericExchangeNameResponse) ProtoMessage() {} +func (*GenericExchangeNameResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{3} +} + +func (m *GenericExchangeNameResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GenericExchangeNameResponse.Unmarshal(m, b) +} +func (m *GenericExchangeNameResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GenericExchangeNameResponse.Marshal(b, m, deterministic) +} +func (m *GenericExchangeNameResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenericExchangeNameResponse.Merge(m, src) +} +func (m *GenericExchangeNameResponse) XXX_Size() int { + return xxx_messageInfo_GenericExchangeNameResponse.Size(m) +} +func (m *GenericExchangeNameResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GenericExchangeNameResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GenericExchangeNameResponse proto.InternalMessageInfo + +type GetExchangesRequest struct { + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangesRequest) Reset() { *m = GetExchangesRequest{} } +func (m *GetExchangesRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangesRequest) ProtoMessage() {} +func (*GetExchangesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{4} +} + +func (m *GetExchangesRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangesRequest.Unmarshal(m, b) +} +func (m *GetExchangesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangesRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangesRequest.Merge(m, src) +} +func (m *GetExchangesRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangesRequest.Size(m) +} +func (m *GetExchangesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangesRequest proto.InternalMessageInfo + +func (m *GetExchangesRequest) GetEnabled() bool { + if m != nil { + return m.Enabled + } + return false +} + +type GetExchangesResponse struct { + Exchanges string `protobuf:"bytes,1,opt,name=exchanges,proto3" json:"exchanges,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangesResponse) Reset() { *m = GetExchangesResponse{} } +func (m *GetExchangesResponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangesResponse) ProtoMessage() {} +func (*GetExchangesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{5} +} + +func (m *GetExchangesResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangesResponse.Unmarshal(m, b) +} +func (m *GetExchangesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangesResponse.Marshal(b, m, deterministic) +} +func (m *GetExchangesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangesResponse.Merge(m, src) +} +func (m *GetExchangesResponse) XXX_Size() int { + return xxx_messageInfo_GetExchangesResponse.Size(m) +} +func (m *GetExchangesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangesResponse proto.InternalMessageInfo + +func (m *GetExchangesResponse) GetExchanges() string { + if m != nil { + return m.Exchanges + } + return "" +} + +type DisableExchangeRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DisableExchangeRequest) Reset() { *m = DisableExchangeRequest{} } +func (m *DisableExchangeRequest) String() string { return proto.CompactTextString(m) } +func (*DisableExchangeRequest) ProtoMessage() {} +func (*DisableExchangeRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{6} +} + +func (m *DisableExchangeRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DisableExchangeRequest.Unmarshal(m, b) +} +func (m *DisableExchangeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DisableExchangeRequest.Marshal(b, m, deterministic) +} +func (m *DisableExchangeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_DisableExchangeRequest.Merge(m, src) +} +func (m *DisableExchangeRequest) XXX_Size() int { + return xxx_messageInfo_DisableExchangeRequest.Size(m) +} +func (m *DisableExchangeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_DisableExchangeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_DisableExchangeRequest proto.InternalMessageInfo + +func (m *DisableExchangeRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +type GetExchangeInfoResponse struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + Verbose bool `protobuf:"varint,3,opt,name=verbose,proto3" json:"verbose,omitempty"` + UsingSandbox bool `protobuf:"varint,4,opt,name=using_sandbox,json=usingSandbox,proto3" json:"using_sandbox,omitempty"` + HttpTimeout string `protobuf:"bytes,5,opt,name=http_timeout,json=httpTimeout,proto3" json:"http_timeout,omitempty"` + HttpUseragent string `protobuf:"bytes,6,opt,name=http_useragent,json=httpUseragent,proto3" json:"http_useragent,omitempty"` + HttpProxy string `protobuf:"bytes,7,opt,name=http_proxy,json=httpProxy,proto3" json:"http_proxy,omitempty"` + BaseCurrencies string `protobuf:"bytes,8,opt,name=base_currencies,json=baseCurrencies,proto3" json:"base_currencies,omitempty"` + SupportedAssets string `protobuf:"bytes,9,opt,name=supported_assets,json=supportedAssets,proto3" json:"supported_assets,omitempty"` + EnabledPairs string `protobuf:"bytes,10,opt,name=enabled_pairs,json=enabledPairs,proto3" json:"enabled_pairs,omitempty"` + AvailablePairs string `protobuf:"bytes,11,opt,name=available_pairs,json=availablePairs,proto3" json:"available_pairs,omitempty"` + AuthenticatedApi bool `protobuf:"varint,12,opt,name=authenticated_api,json=authenticatedApi,proto3" json:"authenticated_api,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangeInfoResponse) Reset() { *m = GetExchangeInfoResponse{} } +func (m *GetExchangeInfoResponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangeInfoResponse) ProtoMessage() {} +func (*GetExchangeInfoResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{7} +} + +func (m *GetExchangeInfoResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeInfoResponse.Unmarshal(m, b) +} +func (m *GetExchangeInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeInfoResponse.Marshal(b, m, deterministic) +} +func (m *GetExchangeInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeInfoResponse.Merge(m, src) +} +func (m *GetExchangeInfoResponse) XXX_Size() int { + return xxx_messageInfo_GetExchangeInfoResponse.Size(m) +} +func (m *GetExchangeInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeInfoResponse proto.InternalMessageInfo + +func (m *GetExchangeInfoResponse) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *GetExchangeInfoResponse) GetEnabled() bool { + if m != nil { + return m.Enabled + } + return false +} + +func (m *GetExchangeInfoResponse) GetVerbose() bool { + if m != nil { + return m.Verbose + } + return false +} + +func (m *GetExchangeInfoResponse) GetUsingSandbox() bool { + if m != nil { + return m.UsingSandbox + } + return false +} + +func (m *GetExchangeInfoResponse) GetHttpTimeout() string { + if m != nil { + return m.HttpTimeout + } + return "" +} + +func (m *GetExchangeInfoResponse) GetHttpUseragent() string { + if m != nil { + return m.HttpUseragent + } + return "" +} + +func (m *GetExchangeInfoResponse) GetHttpProxy() string { + if m != nil { + return m.HttpProxy + } + return "" +} + +func (m *GetExchangeInfoResponse) GetBaseCurrencies() string { + if m != nil { + return m.BaseCurrencies + } + return "" +} + +func (m *GetExchangeInfoResponse) GetSupportedAssets() string { + if m != nil { + return m.SupportedAssets + } + return "" +} + +func (m *GetExchangeInfoResponse) GetEnabledPairs() string { + if m != nil { + return m.EnabledPairs + } + return "" +} + +func (m *GetExchangeInfoResponse) GetAvailablePairs() string { + if m != nil { + return m.AvailablePairs + } + return "" +} + +func (m *GetExchangeInfoResponse) GetAuthenticatedApi() bool { + if m != nil { + return m.AuthenticatedApi + } + return false +} + +type GetTickerRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetTickerRequest) Reset() { *m = GetTickerRequest{} } +func (m *GetTickerRequest) String() string { return proto.CompactTextString(m) } +func (*GetTickerRequest) ProtoMessage() {} +func (*GetTickerRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{8} +} + +func (m *GetTickerRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetTickerRequest.Unmarshal(m, b) +} +func (m *GetTickerRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetTickerRequest.Marshal(b, m, deterministic) +} +func (m *GetTickerRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetTickerRequest.Merge(m, src) +} +func (m *GetTickerRequest) XXX_Size() int { + return xxx_messageInfo_GetTickerRequest.Size(m) +} +func (m *GetTickerRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetTickerRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetTickerRequest proto.InternalMessageInfo + +func (m *GetTickerRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *GetTickerRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *GetTickerRequest) GetAssetType() string { + if m != nil { + return m.AssetType + } + return "" +} + +type CurrencyPair struct { + Delimiter string `protobuf:"bytes,1,opt,name=delimiter,proto3" json:"delimiter,omitempty"` + Base string `protobuf:"bytes,2,opt,name=base,proto3" json:"base,omitempty"` + Quote string `protobuf:"bytes,3,opt,name=quote,proto3" json:"quote,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CurrencyPair) Reset() { *m = CurrencyPair{} } +func (m *CurrencyPair) String() string { return proto.CompactTextString(m) } +func (*CurrencyPair) ProtoMessage() {} +func (*CurrencyPair) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{9} +} + +func (m *CurrencyPair) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CurrencyPair.Unmarshal(m, b) +} +func (m *CurrencyPair) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CurrencyPair.Marshal(b, m, deterministic) +} +func (m *CurrencyPair) XXX_Merge(src proto.Message) { + xxx_messageInfo_CurrencyPair.Merge(m, src) +} +func (m *CurrencyPair) XXX_Size() int { + return xxx_messageInfo_CurrencyPair.Size(m) +} +func (m *CurrencyPair) XXX_DiscardUnknown() { + xxx_messageInfo_CurrencyPair.DiscardUnknown(m) +} + +var xxx_messageInfo_CurrencyPair proto.InternalMessageInfo + +func (m *CurrencyPair) GetDelimiter() string { + if m != nil { + return m.Delimiter + } + return "" +} + +func (m *CurrencyPair) GetBase() string { + if m != nil { + return m.Base + } + return "" +} + +func (m *CurrencyPair) GetQuote() string { + if m != nil { + return m.Quote + } + return "" +} + +type TickerResponse struct { + Pair *CurrencyPair `protobuf:"bytes,1,opt,name=pair,proto3" json:"pair,omitempty"` + LastUpdated int64 `protobuf:"varint,2,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"` + CurrencyPair string `protobuf:"bytes,3,opt,name=currency_pair,json=currencyPair,proto3" json:"currency_pair,omitempty"` + Last float64 `protobuf:"fixed64,4,opt,name=last,proto3" json:"last,omitempty"` + High float64 `protobuf:"fixed64,5,opt,name=high,proto3" json:"high,omitempty"` + Low float64 `protobuf:"fixed64,6,opt,name=low,proto3" json:"low,omitempty"` + Bid float64 `protobuf:"fixed64,7,opt,name=bid,proto3" json:"bid,omitempty"` + Ask float64 `protobuf:"fixed64,8,opt,name=ask,proto3" json:"ask,omitempty"` + Volume float64 `protobuf:"fixed64,9,opt,name=volume,proto3" json:"volume,omitempty"` + PriceAth float64 `protobuf:"fixed64,10,opt,name=price_ath,json=priceAth,proto3" json:"price_ath,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TickerResponse) Reset() { *m = TickerResponse{} } +func (m *TickerResponse) String() string { return proto.CompactTextString(m) } +func (*TickerResponse) ProtoMessage() {} +func (*TickerResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{10} +} + +func (m *TickerResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TickerResponse.Unmarshal(m, b) +} +func (m *TickerResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TickerResponse.Marshal(b, m, deterministic) +} +func (m *TickerResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_TickerResponse.Merge(m, src) +} +func (m *TickerResponse) XXX_Size() int { + return xxx_messageInfo_TickerResponse.Size(m) +} +func (m *TickerResponse) XXX_DiscardUnknown() { + xxx_messageInfo_TickerResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_TickerResponse proto.InternalMessageInfo + +func (m *TickerResponse) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *TickerResponse) GetLastUpdated() int64 { + if m != nil { + return m.LastUpdated + } + return 0 +} + +func (m *TickerResponse) GetCurrencyPair() string { + if m != nil { + return m.CurrencyPair + } + return "" +} + +func (m *TickerResponse) GetLast() float64 { + if m != nil { + return m.Last + } + return 0 +} + +func (m *TickerResponse) GetHigh() float64 { + if m != nil { + return m.High + } + return 0 +} + +func (m *TickerResponse) GetLow() float64 { + if m != nil { + return m.Low + } + return 0 +} + +func (m *TickerResponse) GetBid() float64 { + if m != nil { + return m.Bid + } + return 0 +} + +func (m *TickerResponse) GetAsk() float64 { + if m != nil { + return m.Ask + } + return 0 +} + +func (m *TickerResponse) GetVolume() float64 { + if m != nil { + return m.Volume + } + return 0 +} + +func (m *TickerResponse) GetPriceAth() float64 { + if m != nil { + return m.PriceAth + } + return 0 +} + +type GetTickersRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetTickersRequest) Reset() { *m = GetTickersRequest{} } +func (m *GetTickersRequest) String() string { return proto.CompactTextString(m) } +func (*GetTickersRequest) ProtoMessage() {} +func (*GetTickersRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{11} +} + +func (m *GetTickersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetTickersRequest.Unmarshal(m, b) +} +func (m *GetTickersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetTickersRequest.Marshal(b, m, deterministic) +} +func (m *GetTickersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetTickersRequest.Merge(m, src) +} +func (m *GetTickersRequest) XXX_Size() int { + return xxx_messageInfo_GetTickersRequest.Size(m) +} +func (m *GetTickersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetTickersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetTickersRequest proto.InternalMessageInfo + +type Tickers struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Tickers []*TickerResponse `protobuf:"bytes,2,rep,name=tickers,proto3" json:"tickers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Tickers) Reset() { *m = Tickers{} } +func (m *Tickers) String() string { return proto.CompactTextString(m) } +func (*Tickers) ProtoMessage() {} +func (*Tickers) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{12} +} + +func (m *Tickers) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Tickers.Unmarshal(m, b) +} +func (m *Tickers) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Tickers.Marshal(b, m, deterministic) +} +func (m *Tickers) XXX_Merge(src proto.Message) { + xxx_messageInfo_Tickers.Merge(m, src) +} +func (m *Tickers) XXX_Size() int { + return xxx_messageInfo_Tickers.Size(m) +} +func (m *Tickers) XXX_DiscardUnknown() { + xxx_messageInfo_Tickers.DiscardUnknown(m) +} + +var xxx_messageInfo_Tickers proto.InternalMessageInfo + +func (m *Tickers) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *Tickers) GetTickers() []*TickerResponse { + if m != nil { + return m.Tickers + } + return nil +} + +type GetTickersResponse struct { + Tickers []*Tickers `protobuf:"bytes,1,rep,name=tickers,proto3" json:"tickers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetTickersResponse) Reset() { *m = GetTickersResponse{} } +func (m *GetTickersResponse) String() string { return proto.CompactTextString(m) } +func (*GetTickersResponse) ProtoMessage() {} +func (*GetTickersResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{13} +} + +func (m *GetTickersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetTickersResponse.Unmarshal(m, b) +} +func (m *GetTickersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetTickersResponse.Marshal(b, m, deterministic) +} +func (m *GetTickersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetTickersResponse.Merge(m, src) +} +func (m *GetTickersResponse) XXX_Size() int { + return xxx_messageInfo_GetTickersResponse.Size(m) +} +func (m *GetTickersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetTickersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetTickersResponse proto.InternalMessageInfo + +func (m *GetTickersResponse) GetTickers() []*Tickers { + if m != nil { + return m.Tickers + } + return nil +} + +type GetOrderbookRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderbookRequest) Reset() { *m = GetOrderbookRequest{} } +func (m *GetOrderbookRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderbookRequest) ProtoMessage() {} +func (*GetOrderbookRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{14} +} + +func (m *GetOrderbookRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderbookRequest.Unmarshal(m, b) +} +func (m *GetOrderbookRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderbookRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderbookRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderbookRequest.Merge(m, src) +} +func (m *GetOrderbookRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderbookRequest.Size(m) +} +func (m *GetOrderbookRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderbookRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderbookRequest proto.InternalMessageInfo + +func (m *GetOrderbookRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *GetOrderbookRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *GetOrderbookRequest) GetAssetType() string { + if m != nil { + return m.AssetType + } + return "" +} + +type OrderbookItem struct { + Amount float64 `protobuf:"fixed64,1,opt,name=amount,proto3" json:"amount,omitempty"` + Price float64 `protobuf:"fixed64,2,opt,name=price,proto3" json:"price,omitempty"` + Id int64 `protobuf:"varint,3,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderbookItem) Reset() { *m = OrderbookItem{} } +func (m *OrderbookItem) String() string { return proto.CompactTextString(m) } +func (*OrderbookItem) ProtoMessage() {} +func (*OrderbookItem) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{15} +} + +func (m *OrderbookItem) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderbookItem.Unmarshal(m, b) +} +func (m *OrderbookItem) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderbookItem.Marshal(b, m, deterministic) +} +func (m *OrderbookItem) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderbookItem.Merge(m, src) +} +func (m *OrderbookItem) XXX_Size() int { + return xxx_messageInfo_OrderbookItem.Size(m) +} +func (m *OrderbookItem) XXX_DiscardUnknown() { + xxx_messageInfo_OrderbookItem.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderbookItem proto.InternalMessageInfo + +func (m *OrderbookItem) GetAmount() float64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *OrderbookItem) GetPrice() float64 { + if m != nil { + return m.Price + } + return 0 +} + +func (m *OrderbookItem) GetId() int64 { + if m != nil { + return m.Id + } + return 0 +} + +type OrderbookResponse struct { + Pair *CurrencyPair `protobuf:"bytes,1,opt,name=pair,proto3" json:"pair,omitempty"` + CurrencyPair string `protobuf:"bytes,2,opt,name=currency_pair,json=currencyPair,proto3" json:"currency_pair,omitempty"` + Bids []*OrderbookItem `protobuf:"bytes,3,rep,name=bids,proto3" json:"bids,omitempty"` + Asks []*OrderbookItem `protobuf:"bytes,4,rep,name=asks,proto3" json:"asks,omitempty"` + LastUpdated int64 `protobuf:"varint,5,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"` + AssetType string `protobuf:"bytes,6,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderbookResponse) Reset() { *m = OrderbookResponse{} } +func (m *OrderbookResponse) String() string { return proto.CompactTextString(m) } +func (*OrderbookResponse) ProtoMessage() {} +func (*OrderbookResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{16} +} + +func (m *OrderbookResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderbookResponse.Unmarshal(m, b) +} +func (m *OrderbookResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderbookResponse.Marshal(b, m, deterministic) +} +func (m *OrderbookResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderbookResponse.Merge(m, src) +} +func (m *OrderbookResponse) XXX_Size() int { + return xxx_messageInfo_OrderbookResponse.Size(m) +} +func (m *OrderbookResponse) XXX_DiscardUnknown() { + xxx_messageInfo_OrderbookResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderbookResponse proto.InternalMessageInfo + +func (m *OrderbookResponse) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *OrderbookResponse) GetCurrencyPair() string { + if m != nil { + return m.CurrencyPair + } + return "" +} + +func (m *OrderbookResponse) GetBids() []*OrderbookItem { + if m != nil { + return m.Bids + } + return nil +} + +func (m *OrderbookResponse) GetAsks() []*OrderbookItem { + if m != nil { + return m.Asks + } + return nil +} + +func (m *OrderbookResponse) GetLastUpdated() int64 { + if m != nil { + return m.LastUpdated + } + return 0 +} + +func (m *OrderbookResponse) GetAssetType() string { + if m != nil { + return m.AssetType + } + return "" +} + +type GetOrderbooksRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderbooksRequest) Reset() { *m = GetOrderbooksRequest{} } +func (m *GetOrderbooksRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderbooksRequest) ProtoMessage() {} +func (*GetOrderbooksRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{17} +} + +func (m *GetOrderbooksRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderbooksRequest.Unmarshal(m, b) +} +func (m *GetOrderbooksRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderbooksRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderbooksRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderbooksRequest.Merge(m, src) +} +func (m *GetOrderbooksRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderbooksRequest.Size(m) +} +func (m *GetOrderbooksRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderbooksRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderbooksRequest proto.InternalMessageInfo + +type Orderbooks struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Orderbooks []*OrderbookResponse `protobuf:"bytes,2,rep,name=orderbooks,proto3" json:"orderbooks,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Orderbooks) Reset() { *m = Orderbooks{} } +func (m *Orderbooks) String() string { return proto.CompactTextString(m) } +func (*Orderbooks) ProtoMessage() {} +func (*Orderbooks) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{18} +} + +func (m *Orderbooks) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Orderbooks.Unmarshal(m, b) +} +func (m *Orderbooks) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Orderbooks.Marshal(b, m, deterministic) +} +func (m *Orderbooks) XXX_Merge(src proto.Message) { + xxx_messageInfo_Orderbooks.Merge(m, src) +} +func (m *Orderbooks) XXX_Size() int { + return xxx_messageInfo_Orderbooks.Size(m) +} +func (m *Orderbooks) XXX_DiscardUnknown() { + xxx_messageInfo_Orderbooks.DiscardUnknown(m) +} + +var xxx_messageInfo_Orderbooks proto.InternalMessageInfo + +func (m *Orderbooks) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *Orderbooks) GetOrderbooks() []*OrderbookResponse { + if m != nil { + return m.Orderbooks + } + return nil +} + +type GetOrderbooksResponse struct { + Orderbooks []*Orderbooks `protobuf:"bytes,1,rep,name=orderbooks,proto3" json:"orderbooks,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderbooksResponse) Reset() { *m = GetOrderbooksResponse{} } +func (m *GetOrderbooksResponse) String() string { return proto.CompactTextString(m) } +func (*GetOrderbooksResponse) ProtoMessage() {} +func (*GetOrderbooksResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{19} +} + +func (m *GetOrderbooksResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderbooksResponse.Unmarshal(m, b) +} +func (m *GetOrderbooksResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderbooksResponse.Marshal(b, m, deterministic) +} +func (m *GetOrderbooksResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderbooksResponse.Merge(m, src) +} +func (m *GetOrderbooksResponse) XXX_Size() int { + return xxx_messageInfo_GetOrderbooksResponse.Size(m) +} +func (m *GetOrderbooksResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderbooksResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderbooksResponse proto.InternalMessageInfo + +func (m *GetOrderbooksResponse) GetOrderbooks() []*Orderbooks { + if m != nil { + return m.Orderbooks + } + return nil +} + +type GetAccountInfoRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetAccountInfoRequest) Reset() { *m = GetAccountInfoRequest{} } +func (m *GetAccountInfoRequest) String() string { return proto.CompactTextString(m) } +func (*GetAccountInfoRequest) ProtoMessage() {} +func (*GetAccountInfoRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{20} +} + +func (m *GetAccountInfoRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAccountInfoRequest.Unmarshal(m, b) +} +func (m *GetAccountInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAccountInfoRequest.Marshal(b, m, deterministic) +} +func (m *GetAccountInfoRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAccountInfoRequest.Merge(m, src) +} +func (m *GetAccountInfoRequest) XXX_Size() int { + return xxx_messageInfo_GetAccountInfoRequest.Size(m) +} +func (m *GetAccountInfoRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetAccountInfoRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAccountInfoRequest proto.InternalMessageInfo + +func (m *GetAccountInfoRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +type Account struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Currencies []*AccountCurrencyInfo `protobuf:"bytes,2,rep,name=currencies,proto3" json:"currencies,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Account) Reset() { *m = Account{} } +func (m *Account) String() string { return proto.CompactTextString(m) } +func (*Account) ProtoMessage() {} +func (*Account) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{21} +} + +func (m *Account) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Account.Unmarshal(m, b) +} +func (m *Account) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Account.Marshal(b, m, deterministic) +} +func (m *Account) XXX_Merge(src proto.Message) { + xxx_messageInfo_Account.Merge(m, src) +} +func (m *Account) XXX_Size() int { + return xxx_messageInfo_Account.Size(m) +} +func (m *Account) XXX_DiscardUnknown() { + xxx_messageInfo_Account.DiscardUnknown(m) +} + +var xxx_messageInfo_Account proto.InternalMessageInfo + +func (m *Account) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Account) GetCurrencies() []*AccountCurrencyInfo { + if m != nil { + return m.Currencies + } + return nil +} + +type AccountCurrencyInfo struct { + Currency string `protobuf:"bytes,1,opt,name=currency,proto3" json:"currency,omitempty"` + TotalValue float64 `protobuf:"fixed64,2,opt,name=total_value,json=totalValue,proto3" json:"total_value,omitempty"` + Hold float64 `protobuf:"fixed64,3,opt,name=hold,proto3" json:"hold,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AccountCurrencyInfo) Reset() { *m = AccountCurrencyInfo{} } +func (m *AccountCurrencyInfo) String() string { return proto.CompactTextString(m) } +func (*AccountCurrencyInfo) ProtoMessage() {} +func (*AccountCurrencyInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{22} +} + +func (m *AccountCurrencyInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AccountCurrencyInfo.Unmarshal(m, b) +} +func (m *AccountCurrencyInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AccountCurrencyInfo.Marshal(b, m, deterministic) +} +func (m *AccountCurrencyInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_AccountCurrencyInfo.Merge(m, src) +} +func (m *AccountCurrencyInfo) XXX_Size() int { + return xxx_messageInfo_AccountCurrencyInfo.Size(m) +} +func (m *AccountCurrencyInfo) XXX_DiscardUnknown() { + xxx_messageInfo_AccountCurrencyInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_AccountCurrencyInfo proto.InternalMessageInfo + +func (m *AccountCurrencyInfo) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +func (m *AccountCurrencyInfo) GetTotalValue() float64 { + if m != nil { + return m.TotalValue + } + return 0 +} + +func (m *AccountCurrencyInfo) GetHold() float64 { + if m != nil { + return m.Hold + } + return 0 +} + +type GetAccountInfoResponse struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Accounts []*Account `protobuf:"bytes,2,rep,name=accounts,proto3" json:"accounts,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetAccountInfoResponse) Reset() { *m = GetAccountInfoResponse{} } +func (m *GetAccountInfoResponse) String() string { return proto.CompactTextString(m) } +func (*GetAccountInfoResponse) ProtoMessage() {} +func (*GetAccountInfoResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{23} +} + +func (m *GetAccountInfoResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAccountInfoResponse.Unmarshal(m, b) +} +func (m *GetAccountInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAccountInfoResponse.Marshal(b, m, deterministic) +} +func (m *GetAccountInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAccountInfoResponse.Merge(m, src) +} +func (m *GetAccountInfoResponse) XXX_Size() int { + return xxx_messageInfo_GetAccountInfoResponse.Size(m) +} +func (m *GetAccountInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetAccountInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAccountInfoResponse proto.InternalMessageInfo + +func (m *GetAccountInfoResponse) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *GetAccountInfoResponse) GetAccounts() []*Account { + if m != nil { + return m.Accounts + } + return nil +} + +type GetConfigRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetConfigRequest) Reset() { *m = GetConfigRequest{} } +func (m *GetConfigRequest) String() string { return proto.CompactTextString(m) } +func (*GetConfigRequest) ProtoMessage() {} +func (*GetConfigRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{24} +} + +func (m *GetConfigRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetConfigRequest.Unmarshal(m, b) +} +func (m *GetConfigRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetConfigRequest.Marshal(b, m, deterministic) +} +func (m *GetConfigRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetConfigRequest.Merge(m, src) +} +func (m *GetConfigRequest) XXX_Size() int { + return xxx_messageInfo_GetConfigRequest.Size(m) +} +func (m *GetConfigRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetConfigRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetConfigRequest proto.InternalMessageInfo + +type GetConfigResponse struct { + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetConfigResponse) Reset() { *m = GetConfigResponse{} } +func (m *GetConfigResponse) String() string { return proto.CompactTextString(m) } +func (*GetConfigResponse) ProtoMessage() {} +func (*GetConfigResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{25} +} + +func (m *GetConfigResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetConfigResponse.Unmarshal(m, b) +} +func (m *GetConfigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetConfigResponse.Marshal(b, m, deterministic) +} +func (m *GetConfigResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetConfigResponse.Merge(m, src) +} +func (m *GetConfigResponse) XXX_Size() int { + return xxx_messageInfo_GetConfigResponse.Size(m) +} +func (m *GetConfigResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetConfigResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetConfigResponse proto.InternalMessageInfo + +func (m *GetConfigResponse) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +type PortfolioAddress struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + CoinType string `protobuf:"bytes,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Balance float64 `protobuf:"fixed64,4,opt,name=balance,proto3" json:"balance,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PortfolioAddress) Reset() { *m = PortfolioAddress{} } +func (m *PortfolioAddress) String() string { return proto.CompactTextString(m) } +func (*PortfolioAddress) ProtoMessage() {} +func (*PortfolioAddress) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{26} +} + +func (m *PortfolioAddress) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PortfolioAddress.Unmarshal(m, b) +} +func (m *PortfolioAddress) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PortfolioAddress.Marshal(b, m, deterministic) +} +func (m *PortfolioAddress) XXX_Merge(src proto.Message) { + xxx_messageInfo_PortfolioAddress.Merge(m, src) +} +func (m *PortfolioAddress) XXX_Size() int { + return xxx_messageInfo_PortfolioAddress.Size(m) +} +func (m *PortfolioAddress) XXX_DiscardUnknown() { + xxx_messageInfo_PortfolioAddress.DiscardUnknown(m) +} + +var xxx_messageInfo_PortfolioAddress proto.InternalMessageInfo + +func (m *PortfolioAddress) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *PortfolioAddress) GetCoinType() string { + if m != nil { + return m.CoinType + } + return "" +} + +func (m *PortfolioAddress) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *PortfolioAddress) GetBalance() float64 { + if m != nil { + return m.Balance + } + return 0 +} + +type GetPortfolioRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetPortfolioRequest) Reset() { *m = GetPortfolioRequest{} } +func (m *GetPortfolioRequest) String() string { return proto.CompactTextString(m) } +func (*GetPortfolioRequest) ProtoMessage() {} +func (*GetPortfolioRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{27} +} + +func (m *GetPortfolioRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPortfolioRequest.Unmarshal(m, b) +} +func (m *GetPortfolioRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPortfolioRequest.Marshal(b, m, deterministic) +} +func (m *GetPortfolioRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPortfolioRequest.Merge(m, src) +} +func (m *GetPortfolioRequest) XXX_Size() int { + return xxx_messageInfo_GetPortfolioRequest.Size(m) +} +func (m *GetPortfolioRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetPortfolioRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPortfolioRequest proto.InternalMessageInfo + +type GetPortfolioResponse struct { + Portfolio []*PortfolioAddress `protobuf:"bytes,1,rep,name=portfolio,proto3" json:"portfolio,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetPortfolioResponse) Reset() { *m = GetPortfolioResponse{} } +func (m *GetPortfolioResponse) String() string { return proto.CompactTextString(m) } +func (*GetPortfolioResponse) ProtoMessage() {} +func (*GetPortfolioResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{28} +} + +func (m *GetPortfolioResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPortfolioResponse.Unmarshal(m, b) +} +func (m *GetPortfolioResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPortfolioResponse.Marshal(b, m, deterministic) +} +func (m *GetPortfolioResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPortfolioResponse.Merge(m, src) +} +func (m *GetPortfolioResponse) XXX_Size() int { + return xxx_messageInfo_GetPortfolioResponse.Size(m) +} +func (m *GetPortfolioResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetPortfolioResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPortfolioResponse proto.InternalMessageInfo + +func (m *GetPortfolioResponse) GetPortfolio() []*PortfolioAddress { + if m != nil { + return m.Portfolio + } + return nil +} + +type GetPortfolioSummaryRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetPortfolioSummaryRequest) Reset() { *m = GetPortfolioSummaryRequest{} } +func (m *GetPortfolioSummaryRequest) String() string { return proto.CompactTextString(m) } +func (*GetPortfolioSummaryRequest) ProtoMessage() {} +func (*GetPortfolioSummaryRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{29} +} + +func (m *GetPortfolioSummaryRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPortfolioSummaryRequest.Unmarshal(m, b) +} +func (m *GetPortfolioSummaryRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPortfolioSummaryRequest.Marshal(b, m, deterministic) +} +func (m *GetPortfolioSummaryRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPortfolioSummaryRequest.Merge(m, src) +} +func (m *GetPortfolioSummaryRequest) XXX_Size() int { + return xxx_messageInfo_GetPortfolioSummaryRequest.Size(m) +} +func (m *GetPortfolioSummaryRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetPortfolioSummaryRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPortfolioSummaryRequest proto.InternalMessageInfo + +type Coin struct { + Coin string `protobuf:"bytes,1,opt,name=coin,proto3" json:"coin,omitempty"` + Balance float64 `protobuf:"fixed64,2,opt,name=balance,proto3" json:"balance,omitempty"` + Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + Percentage float64 `protobuf:"fixed64,4,opt,name=percentage,proto3" json:"percentage,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Coin) Reset() { *m = Coin{} } +func (m *Coin) String() string { return proto.CompactTextString(m) } +func (*Coin) ProtoMessage() {} +func (*Coin) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{30} +} + +func (m *Coin) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Coin.Unmarshal(m, b) +} +func (m *Coin) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Coin.Marshal(b, m, deterministic) +} +func (m *Coin) XXX_Merge(src proto.Message) { + xxx_messageInfo_Coin.Merge(m, src) +} +func (m *Coin) XXX_Size() int { + return xxx_messageInfo_Coin.Size(m) +} +func (m *Coin) XXX_DiscardUnknown() { + xxx_messageInfo_Coin.DiscardUnknown(m) +} + +var xxx_messageInfo_Coin proto.InternalMessageInfo + +func (m *Coin) GetCoin() string { + if m != nil { + return m.Coin + } + return "" +} + +func (m *Coin) GetBalance() float64 { + if m != nil { + return m.Balance + } + return 0 +} + +func (m *Coin) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *Coin) GetPercentage() float64 { + if m != nil { + return m.Percentage + } + return 0 +} + +type OfflineCoinSummary struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Balance float64 `protobuf:"fixed64,2,opt,name=balance,proto3" json:"balance,omitempty"` + Percentage float64 `protobuf:"fixed64,3,opt,name=percentage,proto3" json:"percentage,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OfflineCoinSummary) Reset() { *m = OfflineCoinSummary{} } +func (m *OfflineCoinSummary) String() string { return proto.CompactTextString(m) } +func (*OfflineCoinSummary) ProtoMessage() {} +func (*OfflineCoinSummary) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{31} +} + +func (m *OfflineCoinSummary) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OfflineCoinSummary.Unmarshal(m, b) +} +func (m *OfflineCoinSummary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OfflineCoinSummary.Marshal(b, m, deterministic) +} +func (m *OfflineCoinSummary) XXX_Merge(src proto.Message) { + xxx_messageInfo_OfflineCoinSummary.Merge(m, src) +} +func (m *OfflineCoinSummary) XXX_Size() int { + return xxx_messageInfo_OfflineCoinSummary.Size(m) +} +func (m *OfflineCoinSummary) XXX_DiscardUnknown() { + xxx_messageInfo_OfflineCoinSummary.DiscardUnknown(m) +} + +var xxx_messageInfo_OfflineCoinSummary proto.InternalMessageInfo + +func (m *OfflineCoinSummary) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *OfflineCoinSummary) GetBalance() float64 { + if m != nil { + return m.Balance + } + return 0 +} + +func (m *OfflineCoinSummary) GetPercentage() float64 { + if m != nil { + return m.Percentage + } + return 0 +} + +type OnlineCoinSummary struct { + Balance float64 `protobuf:"fixed64,1,opt,name=balance,proto3" json:"balance,omitempty"` + Percentage float64 `protobuf:"fixed64,2,opt,name=percentage,proto3" json:"percentage,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OnlineCoinSummary) Reset() { *m = OnlineCoinSummary{} } +func (m *OnlineCoinSummary) String() string { return proto.CompactTextString(m) } +func (*OnlineCoinSummary) ProtoMessage() {} +func (*OnlineCoinSummary) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{32} +} + +func (m *OnlineCoinSummary) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OnlineCoinSummary.Unmarshal(m, b) +} +func (m *OnlineCoinSummary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OnlineCoinSummary.Marshal(b, m, deterministic) +} +func (m *OnlineCoinSummary) XXX_Merge(src proto.Message) { + xxx_messageInfo_OnlineCoinSummary.Merge(m, src) +} +func (m *OnlineCoinSummary) XXX_Size() int { + return xxx_messageInfo_OnlineCoinSummary.Size(m) +} +func (m *OnlineCoinSummary) XXX_DiscardUnknown() { + xxx_messageInfo_OnlineCoinSummary.DiscardUnknown(m) +} + +var xxx_messageInfo_OnlineCoinSummary proto.InternalMessageInfo + +func (m *OnlineCoinSummary) GetBalance() float64 { + if m != nil { + return m.Balance + } + return 0 +} + +func (m *OnlineCoinSummary) GetPercentage() float64 { + if m != nil { + return m.Percentage + } + return 0 +} + +type OfflineCoins struct { + Addresses []*OfflineCoinSummary `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OfflineCoins) Reset() { *m = OfflineCoins{} } +func (m *OfflineCoins) String() string { return proto.CompactTextString(m) } +func (*OfflineCoins) ProtoMessage() {} +func (*OfflineCoins) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{33} +} + +func (m *OfflineCoins) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OfflineCoins.Unmarshal(m, b) +} +func (m *OfflineCoins) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OfflineCoins.Marshal(b, m, deterministic) +} +func (m *OfflineCoins) XXX_Merge(src proto.Message) { + xxx_messageInfo_OfflineCoins.Merge(m, src) +} +func (m *OfflineCoins) XXX_Size() int { + return xxx_messageInfo_OfflineCoins.Size(m) +} +func (m *OfflineCoins) XXX_DiscardUnknown() { + xxx_messageInfo_OfflineCoins.DiscardUnknown(m) +} + +var xxx_messageInfo_OfflineCoins proto.InternalMessageInfo + +func (m *OfflineCoins) GetAddresses() []*OfflineCoinSummary { + if m != nil { + return m.Addresses + } + return nil +} + +type OnlineCoins struct { + Coins map[string]*OnlineCoinSummary `protobuf:"bytes,1,rep,name=coins,proto3" json:"coins,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OnlineCoins) Reset() { *m = OnlineCoins{} } +func (m *OnlineCoins) String() string { return proto.CompactTextString(m) } +func (*OnlineCoins) ProtoMessage() {} +func (*OnlineCoins) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{34} +} + +func (m *OnlineCoins) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OnlineCoins.Unmarshal(m, b) +} +func (m *OnlineCoins) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OnlineCoins.Marshal(b, m, deterministic) +} +func (m *OnlineCoins) XXX_Merge(src proto.Message) { + xxx_messageInfo_OnlineCoins.Merge(m, src) +} +func (m *OnlineCoins) XXX_Size() int { + return xxx_messageInfo_OnlineCoins.Size(m) +} +func (m *OnlineCoins) XXX_DiscardUnknown() { + xxx_messageInfo_OnlineCoins.DiscardUnknown(m) +} + +var xxx_messageInfo_OnlineCoins proto.InternalMessageInfo + +func (m *OnlineCoins) GetCoins() map[string]*OnlineCoinSummary { + if m != nil { + return m.Coins + } + return nil +} + +type GetPortfolioSummaryResponse struct { + CoinTotals []*Coin `protobuf:"bytes,1,rep,name=coin_totals,json=coinTotals,proto3" json:"coin_totals,omitempty"` + CoinsOffline []*Coin `protobuf:"bytes,2,rep,name=coins_offline,json=coinsOffline,proto3" json:"coins_offline,omitempty"` + CoinsOfflineSummary map[string]*OfflineCoins `protobuf:"bytes,3,rep,name=coins_offline_summary,json=coinsOfflineSummary,proto3" json:"coins_offline_summary,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + CoinsOnline []*Coin `protobuf:"bytes,4,rep,name=coins_online,json=coinsOnline,proto3" json:"coins_online,omitempty"` + CoinsOnlineSummary map[string]*OnlineCoins `protobuf:"bytes,5,rep,name=coins_online_summary,json=coinsOnlineSummary,proto3" json:"coins_online_summary,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetPortfolioSummaryResponse) Reset() { *m = GetPortfolioSummaryResponse{} } +func (m *GetPortfolioSummaryResponse) String() string { return proto.CompactTextString(m) } +func (*GetPortfolioSummaryResponse) ProtoMessage() {} +func (*GetPortfolioSummaryResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{35} +} + +func (m *GetPortfolioSummaryResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPortfolioSummaryResponse.Unmarshal(m, b) +} +func (m *GetPortfolioSummaryResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPortfolioSummaryResponse.Marshal(b, m, deterministic) +} +func (m *GetPortfolioSummaryResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPortfolioSummaryResponse.Merge(m, src) +} +func (m *GetPortfolioSummaryResponse) XXX_Size() int { + return xxx_messageInfo_GetPortfolioSummaryResponse.Size(m) +} +func (m *GetPortfolioSummaryResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetPortfolioSummaryResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPortfolioSummaryResponse proto.InternalMessageInfo + +func (m *GetPortfolioSummaryResponse) GetCoinTotals() []*Coin { + if m != nil { + return m.CoinTotals + } + return nil +} + +func (m *GetPortfolioSummaryResponse) GetCoinsOffline() []*Coin { + if m != nil { + return m.CoinsOffline + } + return nil +} + +func (m *GetPortfolioSummaryResponse) GetCoinsOfflineSummary() map[string]*OfflineCoins { + if m != nil { + return m.CoinsOfflineSummary + } + return nil +} + +func (m *GetPortfolioSummaryResponse) GetCoinsOnline() []*Coin { + if m != nil { + return m.CoinsOnline + } + return nil +} + +func (m *GetPortfolioSummaryResponse) GetCoinsOnlineSummary() map[string]*OnlineCoins { + if m != nil { + return m.CoinsOnlineSummary + } + return nil +} + +type AddPortfolioAddressRequest struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + CoinType string `protobuf:"bytes,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Balance float64 `protobuf:"fixed64,4,opt,name=balance,proto3" json:"balance,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AddPortfolioAddressRequest) Reset() { *m = AddPortfolioAddressRequest{} } +func (m *AddPortfolioAddressRequest) String() string { return proto.CompactTextString(m) } +func (*AddPortfolioAddressRequest) ProtoMessage() {} +func (*AddPortfolioAddressRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{36} +} + +func (m *AddPortfolioAddressRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AddPortfolioAddressRequest.Unmarshal(m, b) +} +func (m *AddPortfolioAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AddPortfolioAddressRequest.Marshal(b, m, deterministic) +} +func (m *AddPortfolioAddressRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddPortfolioAddressRequest.Merge(m, src) +} +func (m *AddPortfolioAddressRequest) XXX_Size() int { + return xxx_messageInfo_AddPortfolioAddressRequest.Size(m) +} +func (m *AddPortfolioAddressRequest) XXX_DiscardUnknown() { + xxx_messageInfo_AddPortfolioAddressRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_AddPortfolioAddressRequest proto.InternalMessageInfo + +func (m *AddPortfolioAddressRequest) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *AddPortfolioAddressRequest) GetCoinType() string { + if m != nil { + return m.CoinType + } + return "" +} + +func (m *AddPortfolioAddressRequest) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *AddPortfolioAddressRequest) GetBalance() float64 { + if m != nil { + return m.Balance + } + return 0 +} + +type AddPortfolioAddressResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AddPortfolioAddressResponse) Reset() { *m = AddPortfolioAddressResponse{} } +func (m *AddPortfolioAddressResponse) String() string { return proto.CompactTextString(m) } +func (*AddPortfolioAddressResponse) ProtoMessage() {} +func (*AddPortfolioAddressResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{37} +} + +func (m *AddPortfolioAddressResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AddPortfolioAddressResponse.Unmarshal(m, b) +} +func (m *AddPortfolioAddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AddPortfolioAddressResponse.Marshal(b, m, deterministic) +} +func (m *AddPortfolioAddressResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddPortfolioAddressResponse.Merge(m, src) +} +func (m *AddPortfolioAddressResponse) XXX_Size() int { + return xxx_messageInfo_AddPortfolioAddressResponse.Size(m) +} +func (m *AddPortfolioAddressResponse) XXX_DiscardUnknown() { + xxx_messageInfo_AddPortfolioAddressResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_AddPortfolioAddressResponse proto.InternalMessageInfo + +type RemovePortfolioAddressRequest struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + CoinType string `protobuf:"bytes,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RemovePortfolioAddressRequest) Reset() { *m = RemovePortfolioAddressRequest{} } +func (m *RemovePortfolioAddressRequest) String() string { return proto.CompactTextString(m) } +func (*RemovePortfolioAddressRequest) ProtoMessage() {} +func (*RemovePortfolioAddressRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{38} +} + +func (m *RemovePortfolioAddressRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RemovePortfolioAddressRequest.Unmarshal(m, b) +} +func (m *RemovePortfolioAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RemovePortfolioAddressRequest.Marshal(b, m, deterministic) +} +func (m *RemovePortfolioAddressRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_RemovePortfolioAddressRequest.Merge(m, src) +} +func (m *RemovePortfolioAddressRequest) XXX_Size() int { + return xxx_messageInfo_RemovePortfolioAddressRequest.Size(m) +} +func (m *RemovePortfolioAddressRequest) XXX_DiscardUnknown() { + xxx_messageInfo_RemovePortfolioAddressRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_RemovePortfolioAddressRequest proto.InternalMessageInfo + +func (m *RemovePortfolioAddressRequest) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *RemovePortfolioAddressRequest) GetCoinType() string { + if m != nil { + return m.CoinType + } + return "" +} + +func (m *RemovePortfolioAddressRequest) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +type RemovePortfolioAddressResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RemovePortfolioAddressResponse) Reset() { *m = RemovePortfolioAddressResponse{} } +func (m *RemovePortfolioAddressResponse) String() string { return proto.CompactTextString(m) } +func (*RemovePortfolioAddressResponse) ProtoMessage() {} +func (*RemovePortfolioAddressResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{39} +} + +func (m *RemovePortfolioAddressResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RemovePortfolioAddressResponse.Unmarshal(m, b) +} +func (m *RemovePortfolioAddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RemovePortfolioAddressResponse.Marshal(b, m, deterministic) +} +func (m *RemovePortfolioAddressResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_RemovePortfolioAddressResponse.Merge(m, src) +} +func (m *RemovePortfolioAddressResponse) XXX_Size() int { + return xxx_messageInfo_RemovePortfolioAddressResponse.Size(m) +} +func (m *RemovePortfolioAddressResponse) XXX_DiscardUnknown() { + xxx_messageInfo_RemovePortfolioAddressResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_RemovePortfolioAddressResponse proto.InternalMessageInfo + +type GetForexProvidersRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetForexProvidersRequest) Reset() { *m = GetForexProvidersRequest{} } +func (m *GetForexProvidersRequest) String() string { return proto.CompactTextString(m) } +func (*GetForexProvidersRequest) ProtoMessage() {} +func (*GetForexProvidersRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{40} +} + +func (m *GetForexProvidersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetForexProvidersRequest.Unmarshal(m, b) +} +func (m *GetForexProvidersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetForexProvidersRequest.Marshal(b, m, deterministic) +} +func (m *GetForexProvidersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetForexProvidersRequest.Merge(m, src) +} +func (m *GetForexProvidersRequest) XXX_Size() int { + return xxx_messageInfo_GetForexProvidersRequest.Size(m) +} +func (m *GetForexProvidersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetForexProvidersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetForexProvidersRequest proto.InternalMessageInfo + +type ForexProvider struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + Verbose bool `protobuf:"varint,3,opt,name=verbose,proto3" json:"verbose,omitempty"` + RestRollingDelay string `protobuf:"bytes,4,opt,name=rest_rolling_delay,json=restRollingDelay,proto3" json:"rest_rolling_delay,omitempty"` + ApiKey string `protobuf:"bytes,5,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` + ApiKeyLevel int64 `protobuf:"varint,6,opt,name=api_key_level,json=apiKeyLevel,proto3" json:"api_key_level,omitempty"` + PrimaryProvider bool `protobuf:"varint,7,opt,name=primary_provider,json=primaryProvider,proto3" json:"primary_provider,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForexProvider) Reset() { *m = ForexProvider{} } +func (m *ForexProvider) String() string { return proto.CompactTextString(m) } +func (*ForexProvider) ProtoMessage() {} +func (*ForexProvider) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{41} +} + +func (m *ForexProvider) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForexProvider.Unmarshal(m, b) +} +func (m *ForexProvider) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForexProvider.Marshal(b, m, deterministic) +} +func (m *ForexProvider) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForexProvider.Merge(m, src) +} +func (m *ForexProvider) XXX_Size() int { + return xxx_messageInfo_ForexProvider.Size(m) +} +func (m *ForexProvider) XXX_DiscardUnknown() { + xxx_messageInfo_ForexProvider.DiscardUnknown(m) +} + +var xxx_messageInfo_ForexProvider proto.InternalMessageInfo + +func (m *ForexProvider) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *ForexProvider) GetEnabled() bool { + if m != nil { + return m.Enabled + } + return false +} + +func (m *ForexProvider) GetVerbose() bool { + if m != nil { + return m.Verbose + } + return false +} + +func (m *ForexProvider) GetRestRollingDelay() string { + if m != nil { + return m.RestRollingDelay + } + return "" +} + +func (m *ForexProvider) GetApiKey() string { + if m != nil { + return m.ApiKey + } + return "" +} + +func (m *ForexProvider) GetApiKeyLevel() int64 { + if m != nil { + return m.ApiKeyLevel + } + return 0 +} + +func (m *ForexProvider) GetPrimaryProvider() bool { + if m != nil { + return m.PrimaryProvider + } + return false +} + +type GetForexProvidersResponse struct { + ForexProviders []*ForexProvider `protobuf:"bytes,1,rep,name=forex_providers,json=forexProviders,proto3" json:"forex_providers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetForexProvidersResponse) Reset() { *m = GetForexProvidersResponse{} } +func (m *GetForexProvidersResponse) String() string { return proto.CompactTextString(m) } +func (*GetForexProvidersResponse) ProtoMessage() {} +func (*GetForexProvidersResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{42} +} + +func (m *GetForexProvidersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetForexProvidersResponse.Unmarshal(m, b) +} +func (m *GetForexProvidersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetForexProvidersResponse.Marshal(b, m, deterministic) +} +func (m *GetForexProvidersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetForexProvidersResponse.Merge(m, src) +} +func (m *GetForexProvidersResponse) XXX_Size() int { + return xxx_messageInfo_GetForexProvidersResponse.Size(m) +} +func (m *GetForexProvidersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetForexProvidersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetForexProvidersResponse proto.InternalMessageInfo + +func (m *GetForexProvidersResponse) GetForexProviders() []*ForexProvider { + if m != nil { + return m.ForexProviders + } + return nil +} + +type GetForexRatesRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetForexRatesRequest) Reset() { *m = GetForexRatesRequest{} } +func (m *GetForexRatesRequest) String() string { return proto.CompactTextString(m) } +func (*GetForexRatesRequest) ProtoMessage() {} +func (*GetForexRatesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{43} +} + +func (m *GetForexRatesRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetForexRatesRequest.Unmarshal(m, b) +} +func (m *GetForexRatesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetForexRatesRequest.Marshal(b, m, deterministic) +} +func (m *GetForexRatesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetForexRatesRequest.Merge(m, src) +} +func (m *GetForexRatesRequest) XXX_Size() int { + return xxx_messageInfo_GetForexRatesRequest.Size(m) +} +func (m *GetForexRatesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetForexRatesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetForexRatesRequest proto.InternalMessageInfo + +type ForexRatesConversion struct { + From string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + To string `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` + Rate float64 `protobuf:"fixed64,3,opt,name=rate,proto3" json:"rate,omitempty"` + InverseRate float64 `protobuf:"fixed64,4,opt,name=inverse_rate,json=inverseRate,proto3" json:"inverse_rate,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForexRatesConversion) Reset() { *m = ForexRatesConversion{} } +func (m *ForexRatesConversion) String() string { return proto.CompactTextString(m) } +func (*ForexRatesConversion) ProtoMessage() {} +func (*ForexRatesConversion) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{44} +} + +func (m *ForexRatesConversion) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForexRatesConversion.Unmarshal(m, b) +} +func (m *ForexRatesConversion) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForexRatesConversion.Marshal(b, m, deterministic) +} +func (m *ForexRatesConversion) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForexRatesConversion.Merge(m, src) +} +func (m *ForexRatesConversion) XXX_Size() int { + return xxx_messageInfo_ForexRatesConversion.Size(m) +} +func (m *ForexRatesConversion) XXX_DiscardUnknown() { + xxx_messageInfo_ForexRatesConversion.DiscardUnknown(m) +} + +var xxx_messageInfo_ForexRatesConversion proto.InternalMessageInfo + +func (m *ForexRatesConversion) GetFrom() string { + if m != nil { + return m.From + } + return "" +} + +func (m *ForexRatesConversion) GetTo() string { + if m != nil { + return m.To + } + return "" +} + +func (m *ForexRatesConversion) GetRate() float64 { + if m != nil { + return m.Rate + } + return 0 +} + +func (m *ForexRatesConversion) GetInverseRate() float64 { + if m != nil { + return m.InverseRate + } + return 0 +} + +type GetForexRatesResponse struct { + ForexRates []*ForexRatesConversion `protobuf:"bytes,1,rep,name=forex_rates,json=forexRates,proto3" json:"forex_rates,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetForexRatesResponse) Reset() { *m = GetForexRatesResponse{} } +func (m *GetForexRatesResponse) String() string { return proto.CompactTextString(m) } +func (*GetForexRatesResponse) ProtoMessage() {} +func (*GetForexRatesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{45} +} + +func (m *GetForexRatesResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetForexRatesResponse.Unmarshal(m, b) +} +func (m *GetForexRatesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetForexRatesResponse.Marshal(b, m, deterministic) +} +func (m *GetForexRatesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetForexRatesResponse.Merge(m, src) +} +func (m *GetForexRatesResponse) XXX_Size() int { + return xxx_messageInfo_GetForexRatesResponse.Size(m) +} +func (m *GetForexRatesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetForexRatesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetForexRatesResponse proto.InternalMessageInfo + +func (m *GetForexRatesResponse) GetForexRates() []*ForexRatesConversion { + if m != nil { + return m.ForexRates + } + return nil +} + +type OrderDetails struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + BaseCurrency string `protobuf:"bytes,3,opt,name=base_currency,json=baseCurrency,proto3" json:"base_currency,omitempty"` + QuoteCurrency string `protobuf:"bytes,4,opt,name=quote_currency,json=quoteCurrency,proto3" json:"quote_currency,omitempty"` + AssetType string `protobuf:"bytes,5,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + OrderSide string `protobuf:"bytes,6,opt,name=order_side,json=orderSide,proto3" json:"order_side,omitempty"` + OrderType string `protobuf:"bytes,7,opt,name=order_type,json=orderType,proto3" json:"order_type,omitempty"` + CreationTime int64 `protobuf:"varint,8,opt,name=creation_time,json=creationTime,proto3" json:"creation_time,omitempty"` + Status string `protobuf:"bytes,9,opt,name=status,proto3" json:"status,omitempty"` + Price float64 `protobuf:"fixed64,10,opt,name=price,proto3" json:"price,omitempty"` + Amount float64 `protobuf:"fixed64,11,opt,name=amount,proto3" json:"amount,omitempty"` + OpenVolume float64 `protobuf:"fixed64,12,opt,name=open_volume,json=openVolume,proto3" json:"open_volume,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderDetails) Reset() { *m = OrderDetails{} } +func (m *OrderDetails) String() string { return proto.CompactTextString(m) } +func (*OrderDetails) ProtoMessage() {} +func (*OrderDetails) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{46} +} + +func (m *OrderDetails) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderDetails.Unmarshal(m, b) +} +func (m *OrderDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderDetails.Marshal(b, m, deterministic) +} +func (m *OrderDetails) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderDetails.Merge(m, src) +} +func (m *OrderDetails) XXX_Size() int { + return xxx_messageInfo_OrderDetails.Size(m) +} +func (m *OrderDetails) XXX_DiscardUnknown() { + xxx_messageInfo_OrderDetails.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderDetails proto.InternalMessageInfo + +func (m *OrderDetails) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *OrderDetails) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *OrderDetails) GetBaseCurrency() string { + if m != nil { + return m.BaseCurrency + } + return "" +} + +func (m *OrderDetails) GetQuoteCurrency() string { + if m != nil { + return m.QuoteCurrency + } + return "" +} + +func (m *OrderDetails) GetAssetType() string { + if m != nil { + return m.AssetType + } + return "" +} + +func (m *OrderDetails) GetOrderSide() string { + if m != nil { + return m.OrderSide + } + return "" +} + +func (m *OrderDetails) GetOrderType() string { + if m != nil { + return m.OrderType + } + return "" +} + +func (m *OrderDetails) GetCreationTime() int64 { + if m != nil { + return m.CreationTime + } + return 0 +} + +func (m *OrderDetails) GetStatus() string { + if m != nil { + return m.Status + } + return "" +} + +func (m *OrderDetails) GetPrice() float64 { + if m != nil { + return m.Price + } + return 0 +} + +func (m *OrderDetails) GetAmount() float64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *OrderDetails) GetOpenVolume() float64 { + if m != nil { + return m.OpenVolume + } + return 0 +} + +type GetOrdersRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + AssetType string `protobuf:"bytes,2,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,3,opt,name=pair,proto3" json:"pair,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrdersRequest) Reset() { *m = GetOrdersRequest{} } +func (m *GetOrdersRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrdersRequest) ProtoMessage() {} +func (*GetOrdersRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{47} +} + +func (m *GetOrdersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrdersRequest.Unmarshal(m, b) +} +func (m *GetOrdersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrdersRequest.Marshal(b, m, deterministic) +} +func (m *GetOrdersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrdersRequest.Merge(m, src) +} +func (m *GetOrdersRequest) XXX_Size() int { + return xxx_messageInfo_GetOrdersRequest.Size(m) +} +func (m *GetOrdersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrdersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrdersRequest proto.InternalMessageInfo + +func (m *GetOrdersRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *GetOrdersRequest) GetAssetType() string { + if m != nil { + return m.AssetType + } + return "" +} + +func (m *GetOrdersRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +type GetOrdersResponse struct { + Orders []*OrderDetails `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrdersResponse) Reset() { *m = GetOrdersResponse{} } +func (m *GetOrdersResponse) String() string { return proto.CompactTextString(m) } +func (*GetOrdersResponse) ProtoMessage() {} +func (*GetOrdersResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{48} +} + +func (m *GetOrdersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrdersResponse.Unmarshal(m, b) +} +func (m *GetOrdersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrdersResponse.Marshal(b, m, deterministic) +} +func (m *GetOrdersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrdersResponse.Merge(m, src) +} +func (m *GetOrdersResponse) XXX_Size() int { + return xxx_messageInfo_GetOrdersResponse.Size(m) +} +func (m *GetOrdersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrdersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrdersResponse proto.InternalMessageInfo + +func (m *GetOrdersResponse) GetOrders() []*OrderDetails { + if m != nil { + return m.Orders + } + return nil +} + +type GetOrderRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + OrderId string `protobuf:"bytes,2,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderRequest) Reset() { *m = GetOrderRequest{} } +func (m *GetOrderRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderRequest) ProtoMessage() {} +func (*GetOrderRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{49} +} + +func (m *GetOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderRequest.Unmarshal(m, b) +} +func (m *GetOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderRequest.Merge(m, src) +} +func (m *GetOrderRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderRequest.Size(m) +} +func (m *GetOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderRequest proto.InternalMessageInfo + +func (m *GetOrderRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *GetOrderRequest) GetOrderId() string { + if m != nil { + return m.OrderId + } + return "" +} + +type SubmitOrderRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + Side string `protobuf:"bytes,3,opt,name=side,proto3" json:"side,omitempty"` + OrderType string `protobuf:"bytes,4,opt,name=order_type,json=orderType,proto3" json:"order_type,omitempty"` + Amount float64 `protobuf:"fixed64,5,opt,name=amount,proto3" json:"amount,omitempty"` + Price float64 `protobuf:"fixed64,6,opt,name=price,proto3" json:"price,omitempty"` + ClientId string `protobuf:"bytes,7,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SubmitOrderRequest) Reset() { *m = SubmitOrderRequest{} } +func (m *SubmitOrderRequest) String() string { return proto.CompactTextString(m) } +func (*SubmitOrderRequest) ProtoMessage() {} +func (*SubmitOrderRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{50} +} + +func (m *SubmitOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SubmitOrderRequest.Unmarshal(m, b) +} +func (m *SubmitOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SubmitOrderRequest.Marshal(b, m, deterministic) +} +func (m *SubmitOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SubmitOrderRequest.Merge(m, src) +} +func (m *SubmitOrderRequest) XXX_Size() int { + return xxx_messageInfo_SubmitOrderRequest.Size(m) +} +func (m *SubmitOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SubmitOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SubmitOrderRequest proto.InternalMessageInfo + +func (m *SubmitOrderRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *SubmitOrderRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *SubmitOrderRequest) GetSide() string { + if m != nil { + return m.Side + } + return "" +} + +func (m *SubmitOrderRequest) GetOrderType() string { + if m != nil { + return m.OrderType + } + return "" +} + +func (m *SubmitOrderRequest) GetAmount() float64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *SubmitOrderRequest) GetPrice() float64 { + if m != nil { + return m.Price + } + return 0 +} + +func (m *SubmitOrderRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +type SubmitOrderResponse struct { + OrderPlaced bool `protobuf:"varint,1,opt,name=order_placed,json=orderPlaced,proto3" json:"order_placed,omitempty"` + OrderId string `protobuf:"bytes,2,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SubmitOrderResponse) Reset() { *m = SubmitOrderResponse{} } +func (m *SubmitOrderResponse) String() string { return proto.CompactTextString(m) } +func (*SubmitOrderResponse) ProtoMessage() {} +func (*SubmitOrderResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{51} +} + +func (m *SubmitOrderResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SubmitOrderResponse.Unmarshal(m, b) +} +func (m *SubmitOrderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SubmitOrderResponse.Marshal(b, m, deterministic) +} +func (m *SubmitOrderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_SubmitOrderResponse.Merge(m, src) +} +func (m *SubmitOrderResponse) XXX_Size() int { + return xxx_messageInfo_SubmitOrderResponse.Size(m) +} +func (m *SubmitOrderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_SubmitOrderResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_SubmitOrderResponse proto.InternalMessageInfo + +func (m *SubmitOrderResponse) GetOrderPlaced() bool { + if m != nil { + return m.OrderPlaced + } + return false +} + +func (m *SubmitOrderResponse) GetOrderId() string { + if m != nil { + return m.OrderId + } + return "" +} + +type CancelOrderRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + AccountId string `protobuf:"bytes,2,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + OrderId string `protobuf:"bytes,3,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,4,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,5,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + WalletAddress string `protobuf:"bytes,6,opt,name=wallet_address,json=walletAddress,proto3" json:"wallet_address,omitempty"` + Side string `protobuf:"bytes,7,opt,name=side,proto3" json:"side,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CancelOrderRequest) Reset() { *m = CancelOrderRequest{} } +func (m *CancelOrderRequest) String() string { return proto.CompactTextString(m) } +func (*CancelOrderRequest) ProtoMessage() {} +func (*CancelOrderRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{52} +} + +func (m *CancelOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CancelOrderRequest.Unmarshal(m, b) +} +func (m *CancelOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CancelOrderRequest.Marshal(b, m, deterministic) +} +func (m *CancelOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CancelOrderRequest.Merge(m, src) +} +func (m *CancelOrderRequest) XXX_Size() int { + return xxx_messageInfo_CancelOrderRequest.Size(m) +} +func (m *CancelOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CancelOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CancelOrderRequest proto.InternalMessageInfo + +func (m *CancelOrderRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *CancelOrderRequest) GetAccountId() string { + if m != nil { + return m.AccountId + } + return "" +} + +func (m *CancelOrderRequest) GetOrderId() string { + if m != nil { + return m.OrderId + } + return "" +} + +func (m *CancelOrderRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *CancelOrderRequest) GetAssetType() string { + if m != nil { + return m.AssetType + } + return "" +} + +func (m *CancelOrderRequest) GetWalletAddress() string { + if m != nil { + return m.WalletAddress + } + return "" +} + +func (m *CancelOrderRequest) GetSide() string { + if m != nil { + return m.Side + } + return "" +} + +type CancelOrderResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CancelOrderResponse) Reset() { *m = CancelOrderResponse{} } +func (m *CancelOrderResponse) String() string { return proto.CompactTextString(m) } +func (*CancelOrderResponse) ProtoMessage() {} +func (*CancelOrderResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{53} +} + +func (m *CancelOrderResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CancelOrderResponse.Unmarshal(m, b) +} +func (m *CancelOrderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CancelOrderResponse.Marshal(b, m, deterministic) +} +func (m *CancelOrderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_CancelOrderResponse.Merge(m, src) +} +func (m *CancelOrderResponse) XXX_Size() int { + return xxx_messageInfo_CancelOrderResponse.Size(m) +} +func (m *CancelOrderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_CancelOrderResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_CancelOrderResponse proto.InternalMessageInfo + +type CancelAllOrdersRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CancelAllOrdersRequest) Reset() { *m = CancelAllOrdersRequest{} } +func (m *CancelAllOrdersRequest) String() string { return proto.CompactTextString(m) } +func (*CancelAllOrdersRequest) ProtoMessage() {} +func (*CancelAllOrdersRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{54} +} + +func (m *CancelAllOrdersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CancelAllOrdersRequest.Unmarshal(m, b) +} +func (m *CancelAllOrdersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CancelAllOrdersRequest.Marshal(b, m, deterministic) +} +func (m *CancelAllOrdersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CancelAllOrdersRequest.Merge(m, src) +} +func (m *CancelAllOrdersRequest) XXX_Size() int { + return xxx_messageInfo_CancelAllOrdersRequest.Size(m) +} +func (m *CancelAllOrdersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CancelAllOrdersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CancelAllOrdersRequest proto.InternalMessageInfo + +func (m *CancelAllOrdersRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +type CancelAllOrdersResponse struct { + Orders []*CancelAllOrdersResponse_Orders `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CancelAllOrdersResponse) Reset() { *m = CancelAllOrdersResponse{} } +func (m *CancelAllOrdersResponse) String() string { return proto.CompactTextString(m) } +func (*CancelAllOrdersResponse) ProtoMessage() {} +func (*CancelAllOrdersResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{55} +} + +func (m *CancelAllOrdersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CancelAllOrdersResponse.Unmarshal(m, b) +} +func (m *CancelAllOrdersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CancelAllOrdersResponse.Marshal(b, m, deterministic) +} +func (m *CancelAllOrdersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_CancelAllOrdersResponse.Merge(m, src) +} +func (m *CancelAllOrdersResponse) XXX_Size() int { + return xxx_messageInfo_CancelAllOrdersResponse.Size(m) +} +func (m *CancelAllOrdersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_CancelAllOrdersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_CancelAllOrdersResponse proto.InternalMessageInfo + +func (m *CancelAllOrdersResponse) GetOrders() []*CancelAllOrdersResponse_Orders { + if m != nil { + return m.Orders + } + return nil +} + +type CancelAllOrdersResponse_Orders struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + OrderStatus map[string]string `protobuf:"bytes,2,rep,name=order_status,json=orderStatus,proto3" json:"order_status,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CancelAllOrdersResponse_Orders) Reset() { *m = CancelAllOrdersResponse_Orders{} } +func (m *CancelAllOrdersResponse_Orders) String() string { return proto.CompactTextString(m) } +func (*CancelAllOrdersResponse_Orders) ProtoMessage() {} +func (*CancelAllOrdersResponse_Orders) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{55, 0} +} + +func (m *CancelAllOrdersResponse_Orders) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CancelAllOrdersResponse_Orders.Unmarshal(m, b) +} +func (m *CancelAllOrdersResponse_Orders) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CancelAllOrdersResponse_Orders.Marshal(b, m, deterministic) +} +func (m *CancelAllOrdersResponse_Orders) XXX_Merge(src proto.Message) { + xxx_messageInfo_CancelAllOrdersResponse_Orders.Merge(m, src) +} +func (m *CancelAllOrdersResponse_Orders) XXX_Size() int { + return xxx_messageInfo_CancelAllOrdersResponse_Orders.Size(m) +} +func (m *CancelAllOrdersResponse_Orders) XXX_DiscardUnknown() { + xxx_messageInfo_CancelAllOrdersResponse_Orders.DiscardUnknown(m) +} + +var xxx_messageInfo_CancelAllOrdersResponse_Orders proto.InternalMessageInfo + +func (m *CancelAllOrdersResponse_Orders) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *CancelAllOrdersResponse_Orders) GetOrderStatus() map[string]string { + if m != nil { + return m.OrderStatus + } + return nil +} + +type GetEventsRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetEventsRequest) Reset() { *m = GetEventsRequest{} } +func (m *GetEventsRequest) String() string { return proto.CompactTextString(m) } +func (*GetEventsRequest) ProtoMessage() {} +func (*GetEventsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{56} +} + +func (m *GetEventsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetEventsRequest.Unmarshal(m, b) +} +func (m *GetEventsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetEventsRequest.Marshal(b, m, deterministic) +} +func (m *GetEventsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetEventsRequest.Merge(m, src) +} +func (m *GetEventsRequest) XXX_Size() int { + return xxx_messageInfo_GetEventsRequest.Size(m) +} +func (m *GetEventsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetEventsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetEventsRequest proto.InternalMessageInfo + +type ConditionParams struct { + Condition string `protobuf:"bytes,1,opt,name=condition,proto3" json:"condition,omitempty"` + Price float64 `protobuf:"fixed64,2,opt,name=price,proto3" json:"price,omitempty"` + CheckBids bool `protobuf:"varint,3,opt,name=check_bids,json=checkBids,proto3" json:"check_bids,omitempty"` + CheckBidsAndAsks bool `protobuf:"varint,4,opt,name=check_bids_and_asks,json=checkBidsAndAsks,proto3" json:"check_bids_and_asks,omitempty"` + OrderbookAmount float64 `protobuf:"fixed64,5,opt,name=orderbook_amount,json=orderbookAmount,proto3" json:"orderbook_amount,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ConditionParams) Reset() { *m = ConditionParams{} } +func (m *ConditionParams) String() string { return proto.CompactTextString(m) } +func (*ConditionParams) ProtoMessage() {} +func (*ConditionParams) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{57} +} + +func (m *ConditionParams) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ConditionParams.Unmarshal(m, b) +} +func (m *ConditionParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ConditionParams.Marshal(b, m, deterministic) +} +func (m *ConditionParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConditionParams.Merge(m, src) +} +func (m *ConditionParams) XXX_Size() int { + return xxx_messageInfo_ConditionParams.Size(m) +} +func (m *ConditionParams) XXX_DiscardUnknown() { + xxx_messageInfo_ConditionParams.DiscardUnknown(m) +} + +var xxx_messageInfo_ConditionParams proto.InternalMessageInfo + +func (m *ConditionParams) GetCondition() string { + if m != nil { + return m.Condition + } + return "" +} + +func (m *ConditionParams) GetPrice() float64 { + if m != nil { + return m.Price + } + return 0 +} + +func (m *ConditionParams) GetCheckBids() bool { + if m != nil { + return m.CheckBids + } + return false +} + +func (m *ConditionParams) GetCheckBidsAndAsks() bool { + if m != nil { + return m.CheckBidsAndAsks + } + return false +} + +func (m *ConditionParams) GetOrderbookAmount() float64 { + if m != nil { + return m.OrderbookAmount + } + return 0 +} + +type GetEventsResponse struct { + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Exchange string `protobuf:"bytes,2,opt,name=exchange,proto3" json:"exchange,omitempty"` + Item string `protobuf:"bytes,3,opt,name=item,proto3" json:"item,omitempty"` + ConditionParams *ConditionParams `protobuf:"bytes,4,opt,name=condition_params,json=conditionParams,proto3" json:"condition_params,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,5,opt,name=pair,proto3" json:"pair,omitempty"` + Action string `protobuf:"bytes,6,opt,name=action,proto3" json:"action,omitempty"` + Executed bool `protobuf:"varint,7,opt,name=executed,proto3" json:"executed,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetEventsResponse) Reset() { *m = GetEventsResponse{} } +func (m *GetEventsResponse) String() string { return proto.CompactTextString(m) } +func (*GetEventsResponse) ProtoMessage() {} +func (*GetEventsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{58} +} + +func (m *GetEventsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetEventsResponse.Unmarshal(m, b) +} +func (m *GetEventsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetEventsResponse.Marshal(b, m, deterministic) +} +func (m *GetEventsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetEventsResponse.Merge(m, src) +} +func (m *GetEventsResponse) XXX_Size() int { + return xxx_messageInfo_GetEventsResponse.Size(m) +} +func (m *GetEventsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetEventsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetEventsResponse proto.InternalMessageInfo + +func (m *GetEventsResponse) GetId() int64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *GetEventsResponse) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *GetEventsResponse) GetItem() string { + if m != nil { + return m.Item + } + return "" +} + +func (m *GetEventsResponse) GetConditionParams() *ConditionParams { + if m != nil { + return m.ConditionParams + } + return nil +} + +func (m *GetEventsResponse) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *GetEventsResponse) GetAction() string { + if m != nil { + return m.Action + } + return "" +} + +func (m *GetEventsResponse) GetExecuted() bool { + if m != nil { + return m.Executed + } + return false +} + +type AddEventRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Item string `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` + ConditionParams *ConditionParams `protobuf:"bytes,3,opt,name=condition_params,json=conditionParams,proto3" json:"condition_params,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,4,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,5,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Action string `protobuf:"bytes,6,opt,name=action,proto3" json:"action,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AddEventRequest) Reset() { *m = AddEventRequest{} } +func (m *AddEventRequest) String() string { return proto.CompactTextString(m) } +func (*AddEventRequest) ProtoMessage() {} +func (*AddEventRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{59} +} + +func (m *AddEventRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AddEventRequest.Unmarshal(m, b) +} +func (m *AddEventRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AddEventRequest.Marshal(b, m, deterministic) +} +func (m *AddEventRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddEventRequest.Merge(m, src) +} +func (m *AddEventRequest) XXX_Size() int { + return xxx_messageInfo_AddEventRequest.Size(m) +} +func (m *AddEventRequest) XXX_DiscardUnknown() { + xxx_messageInfo_AddEventRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_AddEventRequest proto.InternalMessageInfo + +func (m *AddEventRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *AddEventRequest) GetItem() string { + if m != nil { + return m.Item + } + return "" +} + +func (m *AddEventRequest) GetConditionParams() *ConditionParams { + if m != nil { + return m.ConditionParams + } + return nil +} + +func (m *AddEventRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *AddEventRequest) GetAssetType() string { + if m != nil { + return m.AssetType + } + return "" +} + +func (m *AddEventRequest) GetAction() string { + if m != nil { + return m.Action + } + return "" +} + +type AddEventResponse struct { + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AddEventResponse) Reset() { *m = AddEventResponse{} } +func (m *AddEventResponse) String() string { return proto.CompactTextString(m) } +func (*AddEventResponse) ProtoMessage() {} +func (*AddEventResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{60} +} + +func (m *AddEventResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AddEventResponse.Unmarshal(m, b) +} +func (m *AddEventResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AddEventResponse.Marshal(b, m, deterministic) +} +func (m *AddEventResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddEventResponse.Merge(m, src) +} +func (m *AddEventResponse) XXX_Size() int { + return xxx_messageInfo_AddEventResponse.Size(m) +} +func (m *AddEventResponse) XXX_DiscardUnknown() { + xxx_messageInfo_AddEventResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_AddEventResponse proto.InternalMessageInfo + +func (m *AddEventResponse) GetId() int64 { + if m != nil { + return m.Id + } + return 0 +} + +type RemoveEventRequest struct { + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RemoveEventRequest) Reset() { *m = RemoveEventRequest{} } +func (m *RemoveEventRequest) String() string { return proto.CompactTextString(m) } +func (*RemoveEventRequest) ProtoMessage() {} +func (*RemoveEventRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{61} +} + +func (m *RemoveEventRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RemoveEventRequest.Unmarshal(m, b) +} +func (m *RemoveEventRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RemoveEventRequest.Marshal(b, m, deterministic) +} +func (m *RemoveEventRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_RemoveEventRequest.Merge(m, src) +} +func (m *RemoveEventRequest) XXX_Size() int { + return xxx_messageInfo_RemoveEventRequest.Size(m) +} +func (m *RemoveEventRequest) XXX_DiscardUnknown() { + xxx_messageInfo_RemoveEventRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_RemoveEventRequest proto.InternalMessageInfo + +func (m *RemoveEventRequest) GetId() int64 { + if m != nil { + return m.Id + } + return 0 +} + +type RemoveEventResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RemoveEventResponse) Reset() { *m = RemoveEventResponse{} } +func (m *RemoveEventResponse) String() string { return proto.CompactTextString(m) } +func (*RemoveEventResponse) ProtoMessage() {} +func (*RemoveEventResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{62} +} + +func (m *RemoveEventResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RemoveEventResponse.Unmarshal(m, b) +} +func (m *RemoveEventResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RemoveEventResponse.Marshal(b, m, deterministic) +} +func (m *RemoveEventResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_RemoveEventResponse.Merge(m, src) +} +func (m *RemoveEventResponse) XXX_Size() int { + return xxx_messageInfo_RemoveEventResponse.Size(m) +} +func (m *RemoveEventResponse) XXX_DiscardUnknown() { + xxx_messageInfo_RemoveEventResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_RemoveEventResponse proto.InternalMessageInfo + +type GetCryptocurrencyDepositAddressesRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetCryptocurrencyDepositAddressesRequest) Reset() { + *m = GetCryptocurrencyDepositAddressesRequest{} +} +func (m *GetCryptocurrencyDepositAddressesRequest) String() string { return proto.CompactTextString(m) } +func (*GetCryptocurrencyDepositAddressesRequest) ProtoMessage() {} +func (*GetCryptocurrencyDepositAddressesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{63} +} + +func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest.Unmarshal(m, b) +} +func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest.Marshal(b, m, deterministic) +} +func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest.Merge(m, src) +} +func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Size() int { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest.Size(m) +} +func (m *GetCryptocurrencyDepositAddressesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest proto.InternalMessageInfo + +func (m *GetCryptocurrencyDepositAddressesRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +type GetCryptocurrencyDepositAddressesResponse struct { + Addresses map[string]string `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetCryptocurrencyDepositAddressesResponse) Reset() { + *m = GetCryptocurrencyDepositAddressesResponse{} +} +func (m *GetCryptocurrencyDepositAddressesResponse) String() string { return proto.CompactTextString(m) } +func (*GetCryptocurrencyDepositAddressesResponse) ProtoMessage() {} +func (*GetCryptocurrencyDepositAddressesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{64} +} + +func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse.Unmarshal(m, b) +} +func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse.Marshal(b, m, deterministic) +} +func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse.Merge(m, src) +} +func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Size() int { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse.Size(m) +} +func (m *GetCryptocurrencyDepositAddressesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse proto.InternalMessageInfo + +func (m *GetCryptocurrencyDepositAddressesResponse) GetAddresses() map[string]string { + if m != nil { + return m.Addresses + } + return nil +} + +type GetCryptocurrencyDepositAddressRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Cryptocurrency string `protobuf:"bytes,2,opt,name=cryptocurrency,proto3" json:"cryptocurrency,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetCryptocurrencyDepositAddressRequest) Reset() { + *m = GetCryptocurrencyDepositAddressRequest{} +} +func (m *GetCryptocurrencyDepositAddressRequest) String() string { return proto.CompactTextString(m) } +func (*GetCryptocurrencyDepositAddressRequest) ProtoMessage() {} +func (*GetCryptocurrencyDepositAddressRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{65} +} + +func (m *GetCryptocurrencyDepositAddressRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCryptocurrencyDepositAddressRequest.Unmarshal(m, b) +} +func (m *GetCryptocurrencyDepositAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCryptocurrencyDepositAddressRequest.Marshal(b, m, deterministic) +} +func (m *GetCryptocurrencyDepositAddressRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCryptocurrencyDepositAddressRequest.Merge(m, src) +} +func (m *GetCryptocurrencyDepositAddressRequest) XXX_Size() int { + return xxx_messageInfo_GetCryptocurrencyDepositAddressRequest.Size(m) +} +func (m *GetCryptocurrencyDepositAddressRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetCryptocurrencyDepositAddressRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCryptocurrencyDepositAddressRequest proto.InternalMessageInfo + +func (m *GetCryptocurrencyDepositAddressRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *GetCryptocurrencyDepositAddressRequest) GetCryptocurrency() string { + if m != nil { + return m.Cryptocurrency + } + return "" +} + +type GetCryptocurrencyDepositAddressResponse struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetCryptocurrencyDepositAddressResponse) Reset() { + *m = GetCryptocurrencyDepositAddressResponse{} +} +func (m *GetCryptocurrencyDepositAddressResponse) String() string { return proto.CompactTextString(m) } +func (*GetCryptocurrencyDepositAddressResponse) ProtoMessage() {} +func (*GetCryptocurrencyDepositAddressResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{66} +} + +func (m *GetCryptocurrencyDepositAddressResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCryptocurrencyDepositAddressResponse.Unmarshal(m, b) +} +func (m *GetCryptocurrencyDepositAddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCryptocurrencyDepositAddressResponse.Marshal(b, m, deterministic) +} +func (m *GetCryptocurrencyDepositAddressResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCryptocurrencyDepositAddressResponse.Merge(m, src) +} +func (m *GetCryptocurrencyDepositAddressResponse) XXX_Size() int { + return xxx_messageInfo_GetCryptocurrencyDepositAddressResponse.Size(m) +} +func (m *GetCryptocurrencyDepositAddressResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetCryptocurrencyDepositAddressResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCryptocurrencyDepositAddressResponse proto.InternalMessageInfo + +func (m *GetCryptocurrencyDepositAddressResponse) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +type WithdrawCurrencyRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + OneTimePassword string `protobuf:"bytes,3,opt,name=one_time_password,json=oneTimePassword,proto3" json:"one_time_password,omitempty"` + AccountId string `protobuf:"bytes,4,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + Pin int64 `protobuf:"varint,5,opt,name=pin,proto3" json:"pin,omitempty"` + TradePassword string `protobuf:"bytes,6,opt,name=trade_password,json=tradePassword,proto3" json:"trade_password,omitempty"` + Currency string `protobuf:"bytes,7,opt,name=currency,proto3" json:"currency,omitempty"` + Address string `protobuf:"bytes,8,opt,name=address,proto3" json:"address,omitempty"` + AddressTag string `protobuf:"bytes,9,opt,name=address_tag,json=addressTag,proto3" json:"address_tag,omitempty"` + Amount float64 `protobuf:"fixed64,10,opt,name=amount,proto3" json:"amount,omitempty"` + FeeAmount float64 `protobuf:"fixed64,11,opt,name=fee_amount,json=feeAmount,proto3" json:"fee_amount,omitempty"` + BankName string `protobuf:"bytes,12,opt,name=bank_name,json=bankName,proto3" json:"bank_name,omitempty"` + BankAddress string `protobuf:"bytes,13,opt,name=bank_address,json=bankAddress,proto3" json:"bank_address,omitempty"` + BankCity string `protobuf:"bytes,14,opt,name=bank_city,json=bankCity,proto3" json:"bank_city,omitempty"` + BankCountry string `protobuf:"bytes,15,opt,name=bank_country,json=bankCountry,proto3" json:"bank_country,omitempty"` + SwifeCode string `protobuf:"bytes,16,opt,name=swife_code,json=swifeCode,proto3" json:"swife_code,omitempty"` + WireCurrency string `protobuf:"bytes,17,opt,name=wire_currency,json=wireCurrency,proto3" json:"wire_currency,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WithdrawCurrencyRequest) Reset() { *m = WithdrawCurrencyRequest{} } +func (m *WithdrawCurrencyRequest) String() string { return proto.CompactTextString(m) } +func (*WithdrawCurrencyRequest) ProtoMessage() {} +func (*WithdrawCurrencyRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{67} +} + +func (m *WithdrawCurrencyRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawCurrencyRequest.Unmarshal(m, b) +} +func (m *WithdrawCurrencyRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawCurrencyRequest.Marshal(b, m, deterministic) +} +func (m *WithdrawCurrencyRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawCurrencyRequest.Merge(m, src) +} +func (m *WithdrawCurrencyRequest) XXX_Size() int { + return xxx_messageInfo_WithdrawCurrencyRequest.Size(m) +} +func (m *WithdrawCurrencyRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawCurrencyRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawCurrencyRequest proto.InternalMessageInfo + +func (m *WithdrawCurrencyRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetOneTimePassword() string { + if m != nil { + return m.OneTimePassword + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetAccountId() string { + if m != nil { + return m.AccountId + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetPin() int64 { + if m != nil { + return m.Pin + } + return 0 +} + +func (m *WithdrawCurrencyRequest) GetTradePassword() string { + if m != nil { + return m.TradePassword + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetAddressTag() string { + if m != nil { + return m.AddressTag + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetAmount() float64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *WithdrawCurrencyRequest) GetFeeAmount() float64 { + if m != nil { + return m.FeeAmount + } + return 0 +} + +func (m *WithdrawCurrencyRequest) GetBankName() string { + if m != nil { + return m.BankName + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetBankAddress() string { + if m != nil { + return m.BankAddress + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetBankCity() string { + if m != nil { + return m.BankCity + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetBankCountry() string { + if m != nil { + return m.BankCountry + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetSwifeCode() string { + if m != nil { + return m.SwifeCode + } + return "" +} + +func (m *WithdrawCurrencyRequest) GetWireCurrency() string { + if m != nil { + return m.WireCurrency + } + return "" +} + +type WithdrawResponse struct { + Result string `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WithdrawResponse) Reset() { *m = WithdrawResponse{} } +func (m *WithdrawResponse) String() string { return proto.CompactTextString(m) } +func (*WithdrawResponse) ProtoMessage() {} +func (*WithdrawResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{68} +} + +func (m *WithdrawResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawResponse.Unmarshal(m, b) +} +func (m *WithdrawResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawResponse.Marshal(b, m, deterministic) +} +func (m *WithdrawResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawResponse.Merge(m, src) +} +func (m *WithdrawResponse) XXX_Size() int { + return xxx_messageInfo_WithdrawResponse.Size(m) +} +func (m *WithdrawResponse) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawResponse proto.InternalMessageInfo + +func (m *WithdrawResponse) GetResult() string { + if m != nil { + return m.Result + } + return "" +} + +func init() { + proto.RegisterType((*GetInfoRequest)(nil), "gctrpc.GetInfoRequest") + proto.RegisterType((*GetInfoResponse)(nil), "gctrpc.GetInfoResponse") + proto.RegisterType((*GenericExchangeNameRequest)(nil), "gctrpc.GenericExchangeNameRequest") + proto.RegisterType((*GenericExchangeNameResponse)(nil), "gctrpc.GenericExchangeNameResponse") + proto.RegisterType((*GetExchangesRequest)(nil), "gctrpc.GetExchangesRequest") + proto.RegisterType((*GetExchangesResponse)(nil), "gctrpc.GetExchangesResponse") + proto.RegisterType((*DisableExchangeRequest)(nil), "gctrpc.DisableExchangeRequest") + proto.RegisterType((*GetExchangeInfoResponse)(nil), "gctrpc.GetExchangeInfoResponse") + proto.RegisterType((*GetTickerRequest)(nil), "gctrpc.GetTickerRequest") + proto.RegisterType((*CurrencyPair)(nil), "gctrpc.CurrencyPair") + proto.RegisterType((*TickerResponse)(nil), "gctrpc.TickerResponse") + proto.RegisterType((*GetTickersRequest)(nil), "gctrpc.GetTickersRequest") + proto.RegisterType((*Tickers)(nil), "gctrpc.Tickers") + proto.RegisterType((*GetTickersResponse)(nil), "gctrpc.GetTickersResponse") + proto.RegisterType((*GetOrderbookRequest)(nil), "gctrpc.GetOrderbookRequest") + proto.RegisterType((*OrderbookItem)(nil), "gctrpc.OrderbookItem") + proto.RegisterType((*OrderbookResponse)(nil), "gctrpc.OrderbookResponse") + proto.RegisterType((*GetOrderbooksRequest)(nil), "gctrpc.GetOrderbooksRequest") + proto.RegisterType((*Orderbooks)(nil), "gctrpc.Orderbooks") + proto.RegisterType((*GetOrderbooksResponse)(nil), "gctrpc.GetOrderbooksResponse") + proto.RegisterType((*GetAccountInfoRequest)(nil), "gctrpc.GetAccountInfoRequest") + proto.RegisterType((*Account)(nil), "gctrpc.Account") + proto.RegisterType((*AccountCurrencyInfo)(nil), "gctrpc.AccountCurrencyInfo") + proto.RegisterType((*GetAccountInfoResponse)(nil), "gctrpc.GetAccountInfoResponse") + proto.RegisterType((*GetConfigRequest)(nil), "gctrpc.GetConfigRequest") + proto.RegisterType((*GetConfigResponse)(nil), "gctrpc.GetConfigResponse") + proto.RegisterType((*PortfolioAddress)(nil), "gctrpc.PortfolioAddress") + proto.RegisterType((*GetPortfolioRequest)(nil), "gctrpc.GetPortfolioRequest") + proto.RegisterType((*GetPortfolioResponse)(nil), "gctrpc.GetPortfolioResponse") + proto.RegisterType((*GetPortfolioSummaryRequest)(nil), "gctrpc.GetPortfolioSummaryRequest") + proto.RegisterType((*Coin)(nil), "gctrpc.Coin") + proto.RegisterType((*OfflineCoinSummary)(nil), "gctrpc.OfflineCoinSummary") + proto.RegisterType((*OnlineCoinSummary)(nil), "gctrpc.OnlineCoinSummary") + proto.RegisterType((*OfflineCoins)(nil), "gctrpc.OfflineCoins") + proto.RegisterType((*OnlineCoins)(nil), "gctrpc.OnlineCoins") + proto.RegisterMapType((map[string]*OnlineCoinSummary)(nil), "gctrpc.OnlineCoins.CoinsEntry") + proto.RegisterType((*GetPortfolioSummaryResponse)(nil), "gctrpc.GetPortfolioSummaryResponse") + proto.RegisterMapType((map[string]*OfflineCoins)(nil), "gctrpc.GetPortfolioSummaryResponse.CoinsOfflineSummaryEntry") + proto.RegisterMapType((map[string]*OnlineCoins)(nil), "gctrpc.GetPortfolioSummaryResponse.CoinsOnlineSummaryEntry") + proto.RegisterType((*AddPortfolioAddressRequest)(nil), "gctrpc.AddPortfolioAddressRequest") + proto.RegisterType((*AddPortfolioAddressResponse)(nil), "gctrpc.AddPortfolioAddressResponse") + proto.RegisterType((*RemovePortfolioAddressRequest)(nil), "gctrpc.RemovePortfolioAddressRequest") + proto.RegisterType((*RemovePortfolioAddressResponse)(nil), "gctrpc.RemovePortfolioAddressResponse") + proto.RegisterType((*GetForexProvidersRequest)(nil), "gctrpc.GetForexProvidersRequest") + proto.RegisterType((*ForexProvider)(nil), "gctrpc.ForexProvider") + proto.RegisterType((*GetForexProvidersResponse)(nil), "gctrpc.GetForexProvidersResponse") + proto.RegisterType((*GetForexRatesRequest)(nil), "gctrpc.GetForexRatesRequest") + proto.RegisterType((*ForexRatesConversion)(nil), "gctrpc.ForexRatesConversion") + proto.RegisterType((*GetForexRatesResponse)(nil), "gctrpc.GetForexRatesResponse") + proto.RegisterType((*OrderDetails)(nil), "gctrpc.OrderDetails") + proto.RegisterType((*GetOrdersRequest)(nil), "gctrpc.GetOrdersRequest") + proto.RegisterType((*GetOrdersResponse)(nil), "gctrpc.GetOrdersResponse") + proto.RegisterType((*GetOrderRequest)(nil), "gctrpc.GetOrderRequest") + proto.RegisterType((*SubmitOrderRequest)(nil), "gctrpc.SubmitOrderRequest") + proto.RegisterType((*SubmitOrderResponse)(nil), "gctrpc.SubmitOrderResponse") + proto.RegisterType((*CancelOrderRequest)(nil), "gctrpc.CancelOrderRequest") + proto.RegisterType((*CancelOrderResponse)(nil), "gctrpc.CancelOrderResponse") + proto.RegisterType((*CancelAllOrdersRequest)(nil), "gctrpc.CancelAllOrdersRequest") + proto.RegisterType((*CancelAllOrdersResponse)(nil), "gctrpc.CancelAllOrdersResponse") + proto.RegisterType((*CancelAllOrdersResponse_Orders)(nil), "gctrpc.CancelAllOrdersResponse.Orders") + proto.RegisterMapType((map[string]string)(nil), "gctrpc.CancelAllOrdersResponse.Orders.OrderStatusEntry") + proto.RegisterType((*GetEventsRequest)(nil), "gctrpc.GetEventsRequest") + proto.RegisterType((*ConditionParams)(nil), "gctrpc.ConditionParams") + proto.RegisterType((*GetEventsResponse)(nil), "gctrpc.GetEventsResponse") + proto.RegisterType((*AddEventRequest)(nil), "gctrpc.AddEventRequest") + proto.RegisterType((*AddEventResponse)(nil), "gctrpc.AddEventResponse") + proto.RegisterType((*RemoveEventRequest)(nil), "gctrpc.RemoveEventRequest") + proto.RegisterType((*RemoveEventResponse)(nil), "gctrpc.RemoveEventResponse") + proto.RegisterType((*GetCryptocurrencyDepositAddressesRequest)(nil), "gctrpc.GetCryptocurrencyDepositAddressesRequest") + proto.RegisterType((*GetCryptocurrencyDepositAddressesResponse)(nil), "gctrpc.GetCryptocurrencyDepositAddressesResponse") + proto.RegisterMapType((map[string]string)(nil), "gctrpc.GetCryptocurrencyDepositAddressesResponse.AddressesEntry") + proto.RegisterType((*GetCryptocurrencyDepositAddressRequest)(nil), "gctrpc.GetCryptocurrencyDepositAddressRequest") + proto.RegisterType((*GetCryptocurrencyDepositAddressResponse)(nil), "gctrpc.GetCryptocurrencyDepositAddressResponse") + proto.RegisterType((*WithdrawCurrencyRequest)(nil), "gctrpc.WithdrawCurrencyRequest") + proto.RegisterType((*WithdrawResponse)(nil), "gctrpc.WithdrawResponse") +} + +func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } + +var fileDescriptor_77a6da22d6a3feb1 = []byte{ + // 3460 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3a, 0x4d, 0x6f, 0xdc, 0xd6, + 0xb5, 0xe0, 0xe8, 0x73, 0xce, 0x8c, 0x34, 0xa3, 0x3b, 0xfa, 0x18, 0x8d, 0x24, 0x5b, 0x66, 0x9e, + 0x1d, 0xdb, 0x49, 0xac, 0xc4, 0x31, 0xde, 0xcb, 0x4b, 0xf2, 0xf2, 0x9e, 0x22, 0x7f, 0xc4, 0xc8, + 0x4b, 0x6c, 0xd0, 0x8e, 0x03, 0x24, 0x45, 0x09, 0x8a, 0xbc, 0x23, 0x11, 0xe2, 0x90, 0x0c, 0xc9, + 0x91, 0xac, 0xa0, 0x40, 0x81, 0x00, 0xdd, 0xb6, 0x8b, 0xa2, 0x40, 0x17, 0xfd, 0x05, 0x45, 0xbb, + 0xe9, 0x0f, 0x08, 0xba, 0x2d, 0xba, 0xec, 0xa6, 0x3f, 0xa0, 0xe8, 0xae, 0x2d, 0xba, 0xe8, 0xa6, + 0xab, 0xe2, 0x9e, 0xfb, 0x41, 0x5e, 0x72, 0x66, 0x34, 0x6e, 0xda, 0x6c, 0x24, 0xf2, 0xdc, 0x73, + 0xcf, 0xf7, 0x3d, 0xf7, 0x9c, 0xc3, 0x81, 0x7a, 0x12, 0xbb, 0xb7, 0xe2, 0x24, 0xca, 0x22, 0x32, + 0x7f, 0xe4, 0x66, 0x49, 0xec, 0xf6, 0xb6, 0x8f, 0xa2, 0xe8, 0x28, 0xa0, 0x7b, 0x4e, 0xec, 0xef, + 0x39, 0x61, 0x18, 0x65, 0x4e, 0xe6, 0x47, 0x61, 0xca, 0xb1, 0xcc, 0x36, 0x2c, 0x3f, 0xa0, 0xd9, + 0xc3, 0xb0, 0x1f, 0x59, 0xf4, 0x8b, 0x21, 0x4d, 0x33, 0xf3, 0xaf, 0x06, 0xb4, 0x14, 0x28, 0x8d, + 0xa3, 0x30, 0xa5, 0x64, 0x1d, 0xe6, 0x87, 0x71, 0xe6, 0x0f, 0x68, 0xd7, 0xd8, 0x35, 0xae, 0xd7, + 0x2d, 0xf1, 0x46, 0xf6, 0xa0, 0xe3, 0x9c, 0x3a, 0x7e, 0xe0, 0x1c, 0x06, 0xd4, 0xa6, 0xcf, 0xdd, + 0x63, 0x27, 0x3c, 0xa2, 0x69, 0xb7, 0xb6, 0x6b, 0x5c, 0x9f, 0xb1, 0x88, 0x5a, 0xba, 0x27, 0x57, + 0xc8, 0x2b, 0xb0, 0x42, 0x43, 0x06, 0xf2, 0x0a, 0xe8, 0x33, 0x88, 0xde, 0x16, 0x0b, 0x39, 0xf2, + 0x1d, 0x58, 0xf7, 0x68, 0xdf, 0x19, 0x06, 0x99, 0xdd, 0x8f, 0x12, 0xfa, 0xdc, 0x8e, 0x93, 0xe8, + 0xd4, 0xf7, 0x68, 0xd2, 0x9d, 0x45, 0x29, 0x56, 0xc5, 0xea, 0x7d, 0xb6, 0xf8, 0x58, 0xac, 0x91, + 0xdb, 0xb0, 0xa6, 0x76, 0xf9, 0x4e, 0x66, 0xbb, 0xc3, 0x24, 0xa1, 0xa1, 0x7b, 0xde, 0x9d, 0xc3, + 0x4d, 0x1d, 0xb9, 0xc9, 0x77, 0xb2, 0x03, 0xb1, 0x64, 0xbe, 0x05, 0xbd, 0x07, 0x34, 0xa4, 0x89, + 0xef, 0x4a, 0xee, 0x1f, 0x3b, 0x03, 0x2a, 0x2c, 0x42, 0x7a, 0xb0, 0x28, 0x85, 0x15, 0xfa, 0xab, + 0x77, 0x73, 0x07, 0xb6, 0x46, 0xee, 0xe4, 0x86, 0x33, 0xf7, 0xa0, 0xf3, 0x80, 0x66, 0x4a, 0x25, + 0x49, 0xb1, 0x0b, 0x0b, 0x42, 0x5b, 0x24, 0xb8, 0x68, 0xc9, 0x57, 0xf3, 0x0e, 0xac, 0xea, 0x1b, + 0x84, 0x07, 0xb6, 0xa1, 0x9e, 0x1b, 0x8c, 0x0b, 0x91, 0x03, 0xcc, 0x3b, 0xb0, 0x7e, 0xd7, 0x4f, + 0x8b, 0xa6, 0x9e, 0x46, 0xf6, 0xaf, 0x67, 0x60, 0xa3, 0xc0, 0x4c, 0xf3, 0x38, 0x81, 0xd9, 0xd0, + 0x51, 0xfe, 0xc6, 0xe7, 0xa2, 0xd4, 0x35, 0x4d, 0x6a, 0xb6, 0x72, 0x4a, 0x93, 0xc3, 0x28, 0xa5, + 0xe8, 0xcc, 0x45, 0x4b, 0xbe, 0x92, 0x97, 0x60, 0x69, 0x98, 0xfa, 0xe1, 0x91, 0x9d, 0x3a, 0xa1, + 0x77, 0x18, 0x3d, 0x47, 0xd7, 0x2d, 0x5a, 0x4d, 0x04, 0x3e, 0xe1, 0x30, 0x72, 0x05, 0x9a, 0xc7, + 0x59, 0x16, 0xdb, 0x2c, 0xa6, 0xa2, 0x61, 0x26, 0x3c, 0xd5, 0x60, 0xb0, 0xa7, 0x1c, 0x44, 0xae, + 0xc2, 0x32, 0xa2, 0x0c, 0x53, 0x9a, 0x38, 0x47, 0x34, 0xcc, 0xba, 0xf3, 0x88, 0xb4, 0xc4, 0xa0, + 0x9f, 0x48, 0x20, 0xd9, 0x01, 0x40, 0xb4, 0x38, 0x89, 0x9e, 0x9f, 0x77, 0x17, 0xb8, 0x9d, 0x18, + 0xe4, 0x31, 0x03, 0x90, 0x97, 0xa1, 0x75, 0xe8, 0xa4, 0x54, 0xc6, 0x84, 0x4f, 0xd3, 0xee, 0x22, + 0xe2, 0x2c, 0x33, 0xf0, 0x81, 0x82, 0x92, 0x1b, 0xd0, 0x4e, 0x87, 0x71, 0x1c, 0x25, 0x19, 0xf5, + 0x6c, 0x27, 0x4d, 0x69, 0x96, 0x76, 0xeb, 0x88, 0xd9, 0x52, 0xf0, 0x7d, 0x04, 0x33, 0x0d, 0x65, + 0x48, 0xc7, 0x8e, 0x9f, 0xa4, 0x5d, 0x40, 0xbc, 0xa6, 0x00, 0x3e, 0x66, 0x30, 0xc6, 0x38, 0x3f, + 0x28, 0x1c, 0xad, 0xc1, 0x19, 0x2b, 0x30, 0x47, 0x7c, 0x05, 0x56, 0x9c, 0x61, 0x76, 0x4c, 0xc3, + 0xcc, 0x77, 0x1d, 0x64, 0x1e, 0xfb, 0xdd, 0x26, 0xda, 0xac, 0xad, 0x2d, 0xec, 0xc7, 0xbe, 0x79, + 0x06, 0xed, 0x07, 0x34, 0x7b, 0xea, 0xbb, 0x27, 0x34, 0x99, 0xc2, 0xe1, 0xe4, 0x3a, 0xcc, 0x32, + 0xde, 0xe8, 0xbd, 0xc6, 0xed, 0xd5, 0x5b, 0x3c, 0x43, 0xdc, 0x92, 0xc7, 0x80, 0x49, 0x60, 0x21, + 0x06, 0xb3, 0x23, 0x6a, 0x6d, 0x67, 0xe7, 0x31, 0xf7, 0x69, 0xdd, 0xaa, 0x23, 0xe4, 0xe9, 0x79, + 0x4c, 0xcd, 0x67, 0xd0, 0x2c, 0x6e, 0x62, 0xd1, 0xe9, 0xd1, 0xc0, 0x1f, 0xf8, 0x19, 0x4d, 0x64, + 0x74, 0x2a, 0x00, 0x8b, 0x25, 0x66, 0x5e, 0x64, 0x5b, 0xb7, 0xf0, 0x99, 0xac, 0xc2, 0xdc, 0x17, + 0xc3, 0x28, 0x93, 0xb4, 0xf9, 0x8b, 0xf9, 0x93, 0x1a, 0x2c, 0x4b, 0x75, 0x44, 0x20, 0x4a, 0x99, + 0x8d, 0x0b, 0x65, 0xbe, 0x02, 0xcd, 0xc0, 0x49, 0x33, 0x7b, 0x18, 0x7b, 0xcc, 0x40, 0x22, 0x0b, + 0x35, 0x18, 0xec, 0x13, 0x0e, 0x62, 0xbe, 0x92, 0xe9, 0x00, 0xbd, 0x20, 0xb8, 0x37, 0xdd, 0xa2, + 0x32, 0x04, 0x66, 0xd9, 0x1e, 0x8c, 0x54, 0xc3, 0xc2, 0x67, 0x06, 0x3b, 0xf6, 0x8f, 0x8e, 0x31, + 0x32, 0x0d, 0x0b, 0x9f, 0x49, 0x1b, 0x66, 0x82, 0xe8, 0x0c, 0xe3, 0xd0, 0xb0, 0xd8, 0x23, 0x83, + 0x1c, 0xfa, 0x1e, 0x86, 0x9d, 0x61, 0xb1, 0x47, 0x06, 0x71, 0xd2, 0x13, 0x0c, 0x32, 0xc3, 0x62, + 0x8f, 0x2c, 0x95, 0x9e, 0x46, 0xc1, 0x70, 0x40, 0x31, 0x9e, 0x0c, 0x4b, 0xbc, 0x91, 0x2d, 0xa8, + 0xc7, 0x89, 0xef, 0x52, 0xdb, 0xc9, 0x8e, 0x31, 0x84, 0x0c, 0x6b, 0x11, 0x01, 0xfb, 0xd9, 0xb1, + 0xd9, 0x81, 0x15, 0xe5, 0x68, 0x99, 0x44, 0xcc, 0x4f, 0x61, 0x41, 0x40, 0x26, 0x3a, 0xfd, 0x75, + 0x58, 0xc8, 0x38, 0x5a, 0xb7, 0xb6, 0x3b, 0x73, 0xbd, 0x71, 0x7b, 0x5d, 0xda, 0x50, 0xb7, 0xb4, + 0x25, 0xd1, 0xcc, 0xff, 0x05, 0x52, 0xe4, 0x26, 0x1c, 0x71, 0x23, 0xa7, 0x63, 0x20, 0x9d, 0x96, + 0x4e, 0x27, 0xcd, 0x09, 0x7c, 0x89, 0x59, 0xef, 0x51, 0xe2, 0xb1, 0x24, 0x10, 0x9d, 0x7c, 0xab, + 0xa1, 0xf9, 0x11, 0x2c, 0x29, 0xc6, 0x0f, 0x33, 0x3a, 0x60, 0x06, 0x77, 0x06, 0xd1, 0x30, 0xcc, + 0x90, 0xa7, 0x61, 0x89, 0x37, 0x16, 0x81, 0x68, 0x5f, 0x64, 0x69, 0x58, 0xfc, 0x85, 0x2c, 0x43, + 0xcd, 0xf7, 0xc4, 0x8d, 0x54, 0xf3, 0x3d, 0xf3, 0xef, 0x06, 0xac, 0x14, 0x14, 0x79, 0xe1, 0xa0, + 0xac, 0x44, 0x5c, 0x6d, 0x44, 0xc4, 0xdd, 0x80, 0xd9, 0x43, 0xdf, 0x63, 0x17, 0x21, 0xb3, 0xeb, + 0x9a, 0x24, 0xa7, 0xe9, 0x61, 0x21, 0x0a, 0x43, 0x75, 0xd2, 0x93, 0xb4, 0x3b, 0x3b, 0x11, 0x95, + 0xa1, 0x54, 0xce, 0xc3, 0x5c, 0xf5, 0x3c, 0xe8, 0xb6, 0x9c, 0x2f, 0xdb, 0x72, 0x1d, 0x2f, 0x23, + 0x45, 0x5b, 0x45, 0x9e, 0x0b, 0x90, 0x03, 0x27, 0xba, 0xf5, 0xbf, 0x01, 0x22, 0x85, 0x29, 0xe2, + 0x6f, 0xb3, 0x22, 0xb4, 0x0a, 0xc1, 0x02, 0xb2, 0xf9, 0x21, 0xac, 0x95, 0x98, 0x0b, 0xe3, 0xdf, + 0xd6, 0x68, 0xf2, 0x58, 0x24, 0x15, 0x9a, 0xa9, 0x46, 0xec, 0x4d, 0x24, 0xb6, 0xef, 0xba, 0xcc, + 0xf5, 0x85, 0x6a, 0x67, 0xe2, 0xfd, 0xf8, 0x0c, 0x16, 0xc4, 0x0e, 0x11, 0x16, 0x1c, 0xa1, 0xe6, + 0x7b, 0xe4, 0x1d, 0x80, 0xc2, 0x1d, 0xc2, 0xf5, 0xda, 0x92, 0x32, 0x88, 0x4d, 0x32, 0x1a, 0x90, + 0x5d, 0x01, 0xdd, 0xec, 0x43, 0x67, 0x04, 0x0a, 0x13, 0x45, 0xd5, 0x2a, 0x42, 0x14, 0xf9, 0x4e, + 0x2e, 0x43, 0x23, 0x8b, 0x32, 0x27, 0xb0, 0x4f, 0x9d, 0x60, 0x28, 0x43, 0x16, 0x10, 0xf4, 0x8c, + 0x41, 0x30, 0x41, 0x45, 0x01, 0x8f, 0x5c, 0x96, 0xa0, 0xa2, 0xc0, 0x33, 0x1d, 0x58, 0x2f, 0x2b, + 0x2d, 0x4c, 0x38, 0xc9, 0x65, 0xaf, 0xc0, 0xa2, 0xc3, 0xb7, 0x48, 0xc5, 0x5a, 0x25, 0xc5, 0x2c, + 0x85, 0x60, 0x12, 0xbc, 0x81, 0x0e, 0xa2, 0xb0, 0xef, 0x1f, 0xc9, 0xe8, 0x78, 0x19, 0x93, 0x95, + 0x84, 0xe5, 0xf5, 0x84, 0xe7, 0x64, 0x0e, 0x72, 0x6b, 0x5a, 0xf8, 0x6c, 0xfe, 0xc0, 0x80, 0xf6, + 0xe3, 0x28, 0xc9, 0xfa, 0x51, 0xe0, 0x47, 0xfb, 0x9e, 0x97, 0xd0, 0x34, 0x65, 0xa5, 0x84, 0xc3, + 0x1f, 0x85, 0x64, 0xf2, 0x95, 0x65, 0x48, 0x37, 0xf2, 0x43, 0x1e, 0xab, 0x35, 0x61, 0xa0, 0xc8, + 0x0f, 0x59, 0xa8, 0x92, 0x5d, 0x68, 0x78, 0x34, 0x75, 0x13, 0x3f, 0x66, 0xd5, 0xad, 0x48, 0x0b, + 0x45, 0x10, 0x23, 0x7c, 0xe8, 0x04, 0x4e, 0xe8, 0x52, 0x91, 0xd9, 0xe5, 0xab, 0xb9, 0x86, 0xe9, + 0x4a, 0x49, 0x22, 0xf5, 0xf8, 0x18, 0xa3, 0xbf, 0x00, 0x16, 0xaa, 0xfc, 0x27, 0xd4, 0x63, 0x09, + 0x14, 0xe1, 0xd7, 0x95, 0x16, 0x2a, 0xab, 0x63, 0xe5, 0xa8, 0xe6, 0x36, 0x2b, 0x32, 0x73, 0x7a, + 0x4f, 0x86, 0x83, 0x81, 0x93, 0x9c, 0x4b, 0x6e, 0x21, 0xcc, 0x1e, 0x44, 0x7e, 0xc8, 0x0c, 0xc5, + 0x94, 0x92, 0x85, 0x17, 0x7b, 0x2e, 0x8a, 0x5e, 0xd3, 0x44, 0x2f, 0x5a, 0x6b, 0x46, 0xb7, 0xd6, + 0x25, 0x80, 0x98, 0x26, 0x2e, 0x0d, 0x33, 0xe7, 0x48, 0x6a, 0x5c, 0x80, 0x98, 0xc7, 0x40, 0x1e, + 0xf5, 0xfb, 0x81, 0x1f, 0x52, 0xc6, 0x56, 0x08, 0x33, 0xc1, 0xfa, 0xe3, 0x65, 0xd0, 0x39, 0xcd, + 0x54, 0x38, 0x7d, 0x04, 0x2b, 0x8f, 0xc2, 0x11, 0x8c, 0x24, 0x39, 0x63, 0x12, 0xb9, 0x5a, 0x85, + 0xdc, 0x07, 0xd0, 0x2c, 0x08, 0x9e, 0x92, 0xb7, 0xa0, 0x2e, 0x64, 0xa4, 0x32, 0x1b, 0xf4, 0x54, + 0x36, 0xa8, 0x68, 0x68, 0xe5, 0xc8, 0xe6, 0x4f, 0x0d, 0x68, 0xe4, 0x92, 0xb1, 0x7e, 0x63, 0x8e, + 0x99, 0x5b, 0x52, 0xb9, 0xa4, 0xa8, 0xe4, 0x38, 0xb7, 0xf0, 0xef, 0xbd, 0x30, 0x4b, 0xce, 0x2d, + 0x8e, 0xdc, 0x7b, 0x02, 0x90, 0x03, 0xd9, 0x85, 0x7f, 0x42, 0xe5, 0xf9, 0x65, 0x8f, 0x64, 0x0f, + 0xe6, 0xf2, 0x43, 0x5b, 0xcc, 0x7e, 0x65, 0x9b, 0x58, 0x1c, 0xef, 0xed, 0xda, 0x5b, 0x86, 0xf9, + 0xdb, 0x59, 0xd6, 0x57, 0x8c, 0x08, 0x16, 0x11, 0x83, 0xaf, 0x41, 0x83, 0x9f, 0x05, 0x96, 0x01, + 0xa4, 0xc0, 0x4d, 0x75, 0x0f, 0x45, 0x7e, 0x68, 0x01, 0x9e, 0x0d, 0x5c, 0x27, 0x6f, 0xc0, 0x12, + 0x0a, 0x6b, 0x47, 0xdc, 0x20, 0xe2, 0x60, 0xeb, 0x1b, 0x9a, 0x88, 0x22, 0x4c, 0x46, 0x62, 0x58, + 0xd3, 0xb6, 0xd8, 0x29, 0x17, 0x41, 0x5c, 0x52, 0xef, 0xca, 0xad, 0x13, 0xa4, 0xe4, 0xc6, 0x12, + 0x04, 0xc5, 0x1a, 0x37, 0x5d, 0xc7, 0xad, 0xae, 0x90, 0x3d, 0x68, 0x0a, 0x8e, 0x68, 0x19, 0x71, + 0xc5, 0xe9, 0x32, 0x36, 0xf8, 0x46, 0x44, 0x20, 0x03, 0x58, 0x2d, 0x6e, 0x50, 0x12, 0xce, 0xe1, + 0xc6, 0x77, 0xa6, 0x97, 0x30, 0xac, 0x08, 0x48, 0xdc, 0xca, 0x42, 0xef, 0x3b, 0xd0, 0x1d, 0xa7, + 0xd0, 0x08, 0xb7, 0xdf, 0xd4, 0xdd, 0xbe, 0x3a, 0x22, 0x24, 0xd3, 0x82, 0xc7, 0x7b, 0x9f, 0xc1, + 0xc6, 0x18, 0x61, 0x46, 0x10, 0xbf, 0xa1, 0x13, 0xef, 0x8c, 0x88, 0xd4, 0x62, 0x34, 0xfd, 0xc8, + 0x80, 0xde, 0xbe, 0xe7, 0x55, 0x92, 0x53, 0xde, 0x8d, 0x7e, 0xdb, 0x29, 0x77, 0x07, 0xb6, 0x46, + 0x0a, 0x24, 0xda, 0xe6, 0xe7, 0xb0, 0x63, 0xd1, 0x41, 0x74, 0x4a, 0xbf, 0x6d, 0x91, 0xcd, 0x5d, + 0xb8, 0x34, 0x8e, 0xb3, 0x90, 0xad, 0x07, 0xdd, 0x07, 0x54, 0x9f, 0x39, 0xa8, 0xc2, 0xe8, 0x4f, + 0x06, 0x2c, 0xe9, 0xd3, 0x88, 0x7f, 0x55, 0x1f, 0xfd, 0x2a, 0x90, 0x84, 0xa6, 0x99, 0x9d, 0x44, + 0x41, 0xc0, 0xda, 0x69, 0x8f, 0x06, 0xce, 0xb9, 0x98, 0x83, 0xb4, 0xd9, 0x8a, 0xc5, 0x17, 0xee, + 0x32, 0x38, 0xd9, 0x80, 0x05, 0x27, 0xf6, 0x6d, 0x16, 0x35, 0xbc, 0x97, 0x9e, 0x77, 0x62, 0xff, + 0x43, 0x7a, 0x4e, 0x4c, 0x58, 0x12, 0x0b, 0x76, 0x40, 0x4f, 0x69, 0x80, 0x35, 0xdf, 0x8c, 0xd5, + 0xe0, 0xcb, 0xff, 0xcf, 0x40, 0xac, 0xf7, 0x8d, 0x13, 0x9f, 0x85, 0x5f, 0x3e, 0x70, 0x59, 0x40, + 0x69, 0x5a, 0x02, 0x2e, 0xb5, 0x33, 0x3f, 0x87, 0xcd, 0x11, 0xb6, 0x10, 0x39, 0xea, 0x3d, 0x68, + 0xe9, 0x63, 0x1b, 0x99, 0xa7, 0x54, 0xd5, 0xaa, 0x6d, 0xb4, 0x96, 0xfb, 0x1a, 0x1d, 0x51, 0x7d, + 0x22, 0x8e, 0xe5, 0x64, 0x6a, 0x78, 0x62, 0x7e, 0x01, 0xab, 0x39, 0xf0, 0x20, 0x0a, 0x4f, 0x69, + 0x92, 0xb2, 0x68, 0x23, 0x30, 0xdb, 0x4f, 0xa2, 0x81, 0x34, 0x35, 0x7b, 0x66, 0x75, 0x5b, 0x16, + 0x89, 0x30, 0xa8, 0x65, 0x11, 0xc3, 0x49, 0x9c, 0x4c, 0xde, 0x52, 0xf8, 0xcc, 0xea, 0x64, 0x1f, + 0x89, 0x50, 0x1b, 0xd7, 0x78, 0xa8, 0x36, 0x04, 0x8c, 0x71, 0x31, 0x9f, 0x61, 0xf9, 0x58, 0x14, + 0x45, 0xe8, 0xf8, 0x3f, 0xd0, 0xe0, 0x3a, 0xb2, 0x9d, 0x52, 0xbf, 0x6d, 0x4d, 0xbf, 0x92, 0x98, + 0x16, 0xf4, 0x15, 0xd4, 0xfc, 0x4b, 0x0d, 0x9a, 0x58, 0xb1, 0xde, 0xa5, 0x99, 0xe3, 0x07, 0x93, + 0x6b, 0x69, 0x5e, 0x83, 0xd6, 0x54, 0x0d, 0xfa, 0x12, 0x2c, 0x15, 0x87, 0x19, 0xe7, 0xb2, 0x99, + 0x2d, 0x8c, 0x32, 0xce, 0xc9, 0x55, 0x58, 0xc6, 0xd6, 0x3a, 0xc7, 0xe2, 0x31, 0xb3, 0x84, 0x50, + 0x85, 0xa6, 0x37, 0x02, 0x73, 0xa5, 0x46, 0x80, 0x2d, 0x63, 0x31, 0x6d, 0xa7, 0xbe, 0xa7, 0xfa, + 0x04, 0x84, 0x3c, 0xf1, 0xbd, 0xc2, 0x32, 0xee, 0x5e, 0x28, 0x2c, 0xe3, 0x6e, 0xd6, 0x03, 0x25, + 0x14, 0xc7, 0x8e, 0x38, 0xe2, 0xc1, 0x76, 0x78, 0xc6, 0x6a, 0x4a, 0xe0, 0x53, 0x7f, 0x80, 0x23, + 0xc6, 0x34, 0x73, 0xb2, 0xa1, 0x9c, 0xb3, 0x88, 0xb7, 0xbc, 0x4d, 0x83, 0x62, 0x9b, 0x96, 0x37, + 0x75, 0x0d, 0xad, 0xa9, 0xbb, 0x0c, 0x8d, 0x28, 0xa6, 0xa1, 0x2d, 0x5a, 0xec, 0x26, 0xaf, 0x1e, + 0x18, 0xe8, 0x19, 0x42, 0xc4, 0xc8, 0x04, 0x6d, 0x9e, 0x4e, 0xd3, 0x97, 0xea, 0x86, 0xa9, 0x95, + 0x0d, 0x23, 0x1b, 0xc1, 0x99, 0x8b, 0x1a, 0x41, 0x73, 0x1f, 0xab, 0x62, 0xc9, 0x58, 0x84, 0xcf, + 0xab, 0x30, 0x8f, 0x66, 0x92, 0x91, 0xb3, 0xaa, 0xb5, 0x31, 0x22, 0x28, 0x2c, 0x81, 0x63, 0x7e, + 0x80, 0x83, 0x59, 0x5c, 0x9a, 0x46, 0xf4, 0x4d, 0x58, 0xe4, 0x5e, 0x51, 0x51, 0xb3, 0x80, 0xef, + 0x0f, 0x3d, 0xf3, 0xf7, 0x06, 0x90, 0x27, 0xc3, 0xc3, 0x81, 0x3f, 0x3d, 0xb5, 0xe9, 0x1b, 0x74, + 0x02, 0xb3, 0x18, 0x26, 0x3c, 0x1c, 0xf1, 0xb9, 0x14, 0x21, 0xb3, 0xe5, 0x08, 0xc9, 0xdd, 0x39, + 0x37, 0xba, 0x47, 0x9f, 0x2f, 0x3a, 0x9f, 0xa5, 0xf8, 0xc0, 0xa7, 0x61, 0x66, 0x8b, 0x61, 0x0b, + 0x4b, 0xf1, 0x08, 0x78, 0xe8, 0x99, 0x4f, 0xa0, 0xa3, 0x69, 0x26, 0x2c, 0x7d, 0x05, 0x9a, 0x5c, + 0x80, 0x38, 0x70, 0x5c, 0x35, 0x76, 0x6d, 0x20, 0xec, 0x31, 0x82, 0x26, 0xd9, 0xeb, 0xcf, 0x06, + 0x90, 0x03, 0x76, 0x71, 0x05, 0x53, 0xdb, 0x8b, 0x05, 0x0e, 0xef, 0x92, 0x72, 0x7a, 0x75, 0x01, + 0x79, 0xa8, 0x33, 0x9b, 0xd1, 0x98, 0x29, 0x4b, 0xcf, 0xbe, 0xe0, 0x28, 0xa4, 0x72, 0x6a, 0xaf, + 0xc2, 0xf2, 0x99, 0x13, 0x04, 0x34, 0xb3, 0xe5, 0x5d, 0x29, 0x66, 0xa6, 0x1c, 0x2a, 0x3b, 0x2e, + 0xe9, 0xaf, 0x85, 0xdc, 0x5f, 0xac, 0x25, 0xd2, 0xf4, 0x15, 0x77, 0xdf, 0x1d, 0x58, 0xe7, 0xe0, + 0xfd, 0x20, 0x98, 0xfa, 0x0c, 0x99, 0x3f, 0xab, 0xc1, 0x46, 0x65, 0x9b, 0xba, 0x24, 0xf4, 0x13, + 0x70, 0x4d, 0xa9, 0x3b, 0x7a, 0xc3, 0x2d, 0xf1, 0x2a, 0x76, 0xf5, 0x7e, 0x6d, 0xc0, 0x3c, 0x07, + 0x4d, 0xf4, 0xc6, 0x67, 0xd2, 0xfd, 0x22, 0xc7, 0xf0, 0xfa, 0xf7, 0xbf, 0xa6, 0x63, 0xc6, 0xff, + 0x3d, 0xc1, 0x9d, 0xbc, 0x3c, 0xe4, 0x71, 0xc3, 0x21, 0xbd, 0xf7, 0xa0, 0x5d, 0x46, 0x18, 0x51, + 0xb2, 0xad, 0x16, 0x4b, 0xb6, 0x7a, 0xb1, 0x3a, 0xe3, 0x3d, 0xf4, 0xbd, 0x53, 0x1a, 0x66, 0xea, + 0x8e, 0xfb, 0xda, 0x80, 0xd6, 0x41, 0x14, 0x7a, 0x3e, 0xcb, 0x8f, 0x8f, 0x9d, 0xc4, 0x19, 0xa4, + 0x64, 0x9b, 0x55, 0x36, 0x02, 0x24, 0x87, 0xac, 0x0a, 0x30, 0x66, 0x9c, 0xb5, 0x03, 0xe0, 0x1e, + 0x53, 0xf7, 0xc4, 0x16, 0xf3, 0x25, 0x16, 0xf4, 0x75, 0x84, 0xbc, 0xef, 0x7b, 0x29, 0x79, 0x0d, + 0x3a, 0xf9, 0xb2, 0xed, 0x84, 0x9e, 0x2d, 0x86, 0x4b, 0x38, 0x6f, 0x56, 0x78, 0xfb, 0xa1, 0xb7, + 0x9f, 0x9e, 0xe0, 0x54, 0x5c, 0xcd, 0x54, 0x6c, 0xed, 0xc0, 0xb6, 0x14, 0x7c, 0x1f, 0xc1, 0xe6, + 0xdf, 0x0c, 0xcc, 0x77, 0x52, 0x2b, 0xe1, 0xed, 0x7c, 0x8c, 0x82, 0xd3, 0x35, 0xcd, 0x65, 0xb5, + 0x92, 0xcb, 0x08, 0xcc, 0xfa, 0x19, 0x1d, 0xc8, 0x34, 0xc2, 0x9e, 0xc9, 0xfb, 0xd0, 0x56, 0x1a, + 0xdb, 0x31, 0x9a, 0x45, 0x1c, 0x93, 0x8d, 0xbc, 0x4d, 0xd0, 0xac, 0x66, 0xb5, 0xdc, 0x92, 0x19, + 0xe5, 0xf1, 0x9a, 0xbb, 0xf0, 0x78, 0xb1, 0xac, 0xe4, 0xa2, 0xb5, 0xe7, 0x45, 0x11, 0x85, 0x6f, + 0x5c, 0x6a, 0xea, 0x0e, 0x33, 0xea, 0x89, 0xc2, 0x48, 0xbd, 0x9b, 0x7f, 0x34, 0xa0, 0xb5, 0xef, + 0x79, 0xa8, 0xf7, 0x34, 0x69, 0x42, 0x6a, 0x59, 0xbb, 0x40, 0xcb, 0x99, 0x7f, 0x52, 0xcb, 0x6f, + 0x9c, 0x44, 0xc6, 0x18, 0xc1, 0x34, 0xa1, 0x9d, 0xeb, 0x39, 0xda, 0xbd, 0xe6, 0x7f, 0x00, 0xe1, + 0xc5, 0xb4, 0x66, 0x8e, 0x32, 0xd6, 0x1a, 0x74, 0x34, 0x2c, 0x91, 0x6b, 0xee, 0xc3, 0xf5, 0x07, + 0x34, 0x3b, 0x48, 0xce, 0xe3, 0x2c, 0x92, 0xc5, 0xcb, 0x5d, 0x1a, 0x47, 0xa9, 0x2f, 0x33, 0x17, + 0x9d, 0x2a, 0xfb, 0xfc, 0xc6, 0x80, 0x1b, 0x53, 0x10, 0x12, 0x2a, 0x7c, 0xb7, 0x3a, 0x4d, 0xf8, + 0xbf, 0x42, 0x23, 0x39, 0x1d, 0x95, 0x5b, 0x0a, 0xc2, 0xd3, 0x45, 0x4e, 0xb2, 0xf7, 0x2e, 0x2c, + 0xeb, 0x8b, 0x2f, 0x94, 0x2a, 0x02, 0xb8, 0x76, 0x81, 0x10, 0xd3, 0xc4, 0xdc, 0x35, 0x58, 0x76, + 0x35, 0x12, 0x82, 0x51, 0x09, 0x6a, 0x1e, 0xc0, 0xcb, 0x17, 0x72, 0x13, 0x66, 0x1b, 0xdb, 0x8f, + 0x99, 0xbf, 0x9c, 0x85, 0x8d, 0x4f, 0xfd, 0xec, 0xd8, 0x4b, 0x9c, 0x33, 0x19, 0x7d, 0xd3, 0x08, + 0x59, 0x6a, 0xd5, 0x6a, 0xd5, 0xee, 0xf2, 0x26, 0xac, 0x44, 0x21, 0xc5, 0x8a, 0xd2, 0x8e, 0x9d, + 0x34, 0x3d, 0x8b, 0x12, 0x79, 0x97, 0xb6, 0xa2, 0x90, 0xb2, 0xaa, 0xf2, 0xb1, 0x00, 0x97, 0x6e, + 0xe3, 0xd9, 0xf2, 0x6d, 0xdc, 0x86, 0x99, 0xd8, 0x0f, 0xc5, 0x84, 0x9c, 0x3d, 0xb2, 0xbb, 0x33, + 0x4b, 0x1c, 0xaf, 0x40, 0x59, 0xdc, 0x9d, 0x08, 0x55, 0x74, 0x8b, 0x33, 0xdb, 0x85, 0xd2, 0xcc, + 0xb6, 0x60, 0x93, 0x45, 0xbd, 0x47, 0xbd, 0x0c, 0x0d, 0xf1, 0x68, 0x67, 0xce, 0x91, 0x28, 0x78, + 0x41, 0x80, 0x9e, 0x3a, 0x47, 0x85, 0x7a, 0x08, 0xb4, 0x7a, 0x68, 0x07, 0xa0, 0x4f, 0xa9, 0xad, + 0x95, 0xbe, 0xf5, 0x3e, 0xa5, 0x3c, 0xe9, 0xb2, 0xc2, 0xe8, 0xd0, 0x09, 0x4f, 0x6c, 0xec, 0x38, + 0x9b, 0x5c, 0x1c, 0x06, 0xf8, 0x98, 0x75, 0x9d, 0x57, 0xa0, 0x89, 0x8b, 0x52, 0xa6, 0x25, 0x6e, + 0x51, 0x06, 0xdb, 0xcf, 0x7b, 0x67, 0x44, 0x71, 0xfd, 0xec, 0xbc, 0xbb, 0x9c, 0xef, 0x3f, 0xf0, + 0xb3, 0x73, 0xb5, 0x1f, 0x6d, 0x96, 0x9c, 0x77, 0x5b, 0xf9, 0xfe, 0x03, 0x0e, 0x62, 0xe2, 0xa5, + 0x67, 0x7e, 0x9f, 0xda, 0x6e, 0xe4, 0xd1, 0x6e, 0x9b, 0x5b, 0x19, 0x21, 0x07, 0x91, 0x87, 0x7d, + 0xc0, 0x99, 0x9f, 0x14, 0x5a, 0x91, 0x15, 0xde, 0xb0, 0x30, 0xa0, 0xfa, 0x14, 0x7f, 0x13, 0xda, + 0x32, 0x5c, 0x8a, 0x3f, 0x3f, 0x48, 0x68, 0x3a, 0x0c, 0x32, 0xf9, 0xf3, 0x03, 0xfe, 0x76, 0xfb, + 0x17, 0x9b, 0xb0, 0xfc, 0x20, 0xe2, 0x01, 0xfa, 0x94, 0xf9, 0x25, 0x21, 0x8f, 0x60, 0x41, 0xfc, + 0x78, 0x81, 0xac, 0x17, 0xce, 0x6d, 0x61, 0xe4, 0xdf, 0xdb, 0xa8, 0xc0, 0x45, 0xc6, 0xe9, 0x7c, + 0xf5, 0xbb, 0x3f, 0xfc, 0xb8, 0xb6, 0x44, 0x1a, 0x7b, 0xa7, 0x6f, 0xec, 0x1d, 0xd1, 0xcc, 0x67, + 0x54, 0x5c, 0x68, 0x16, 0x3f, 0xc8, 0x93, 0xad, 0xc2, 0xee, 0xf2, 0x77, 0xfd, 0xde, 0xf6, 0xe8, + 0x45, 0x41, 0xbf, 0x8b, 0xf4, 0x09, 0x69, 0x0b, 0xfa, 0xea, 0xfb, 0x3d, 0xf9, 0x12, 0x5a, 0xa5, + 0xef, 0xf7, 0xc4, 0xcc, 0x49, 0x8d, 0xfb, 0x61, 0x42, 0xef, 0xa5, 0x89, 0x38, 0x82, 0xeb, 0x25, + 0xe4, 0xda, 0x35, 0x3b, 0x8c, 0xab, 0xc7, 0xb9, 0x48, 0xce, 0x6f, 0x1b, 0x37, 0x49, 0x8a, 0x5d, + 0x45, 0xf1, 0x47, 0x00, 0x53, 0xf1, 0xbe, 0x3c, 0x42, 0x55, 0xcd, 0x9a, 0x5b, 0xc8, 0x77, 0x8d, + 0x74, 0x4a, 0xda, 0xa2, 0x55, 0x9f, 0xc3, 0xf2, 0xbd, 0xf0, 0xdf, 0xa3, 0xef, 0x0e, 0xf2, 0xdd, + 0x30, 0x09, 0xe3, 0xcb, 0x07, 0x2b, 0x45, 0x75, 0x3f, 0x85, 0xba, 0xfa, 0xb8, 0x49, 0xba, 0x05, + 0x25, 0xb4, 0xcf, 0xe8, 0xbd, 0x31, 0x1f, 0x49, 0xa5, 0x0f, 0xcd, 0x25, 0xa1, 0x15, 0xff, 0xe4, + 0xc9, 0x08, 0x7f, 0x0e, 0x90, 0x7f, 0x35, 0x25, 0x9b, 0x15, 0xca, 0x2a, 0x48, 0x7a, 0xa3, 0x96, + 0x04, 0xf9, 0x75, 0x24, 0xdf, 0x26, 0xcb, 0x1a, 0xf9, 0x54, 0x44, 0xa1, 0xfa, 0xb8, 0xa5, 0x45, + 0x61, 0xf9, 0x3b, 0x6b, 0x6f, 0xfc, 0x07, 0x36, 0xe9, 0x14, 0x53, 0x86, 0xa0, 0x2a, 0xdb, 0x98, + 0x06, 0x47, 0xb0, 0xa4, 0x7d, 0x71, 0x23, 0xdb, 0xa3, 0xb8, 0x28, 0x3d, 0x76, 0xc6, 0xac, 0x0a, + 0x56, 0x9b, 0xc8, 0xaa, 0x43, 0x56, 0xca, 0xac, 0x52, 0x72, 0x82, 0x3f, 0x3a, 0x2a, 0x7c, 0x98, + 0x22, 0x45, 0x5a, 0xd5, 0xaf, 0x74, 0xbd, 0x4b, 0xe3, 0x96, 0xe5, 0x4c, 0x0e, 0x79, 0xad, 0x12, + 0x22, 0x78, 0x89, 0xcc, 0x8e, 0xa1, 0xc6, 0x1d, 0xce, 0x3f, 0x47, 0x69, 0x0e, 0xd7, 0xbe, 0x5a, + 0xf5, 0x36, 0x47, 0xac, 0x08, 0xea, 0x6b, 0x48, 0xbd, 0x45, 0xa4, 0xcf, 0x5d, 0x4e, 0x8b, 0xfb, + 0x44, 0xcd, 0x09, 0x35, 0x9f, 0x94, 0x3f, 0x26, 0x69, 0x99, 0xa1, 0xf2, 0x49, 0xa9, 0x92, 0x19, + 0xd4, 0x47, 0x23, 0xf2, 0x7d, 0xfd, 0xdb, 0x94, 0x9c, 0x95, 0x9b, 0x13, 0x87, 0xdb, 0x95, 0xd3, + 0x32, 0x76, 0x00, 0x6e, 0x5e, 0x46, 0xce, 0x9b, 0x64, 0xa3, 0xcc, 0x59, 0x0c, 0xd3, 0xc9, 0x57, + 0x06, 0x74, 0x46, 0x8c, 0x6a, 0x73, 0x09, 0xc6, 0x0f, 0x96, 0x73, 0x09, 0x26, 0xcd, 0x7a, 0x4d, + 0x94, 0x60, 0xdb, 0x44, 0x09, 0x1c, 0xcf, 0x53, 0x12, 0x88, 0x8b, 0x8a, 0x45, 0xe6, 0x0f, 0x0d, + 0x58, 0x1f, 0x3d, 0x96, 0x25, 0x57, 0x25, 0x8f, 0x89, 0x03, 0xe3, 0xde, 0xb5, 0x8b, 0xd0, 0x84, + 0x34, 0x57, 0x51, 0x9a, 0xcb, 0x66, 0x8f, 0x49, 0x93, 0x20, 0xee, 0x28, 0x81, 0xce, 0xb0, 0xbb, + 0xd1, 0x07, 0x9f, 0x64, 0xb7, 0x60, 0xf0, 0x91, 0xf3, 0xe1, 0xde, 0x95, 0x09, 0x18, 0x7a, 0xfa, + 0x22, 0x6b, 0xc2, 0x21, 0x38, 0x2d, 0x54, 0x13, 0x54, 0x71, 0x46, 0xf3, 0xc1, 0xa2, 0x76, 0x46, + 0x2b, 0xb3, 0x52, 0xed, 0x8c, 0x56, 0xc7, 0x97, 0x95, 0x33, 0x8a, 0xcc, 0x70, 0x94, 0x49, 0x3e, + 0xc3, 0x63, 0x23, 0x5a, 0xeb, 0x6e, 0xf9, 0xa8, 0xa7, 0xa3, 0x8e, 0x8d, 0xde, 0x3c, 0x57, 0x52, + 0x25, 0xef, 0xd8, 0x99, 0xf5, 0x2c, 0x58, 0x94, 0xe8, 0x64, 0xa3, 0x4c, 0x40, 0x52, 0x1e, 0x39, + 0x0b, 0x33, 0x37, 0x90, 0xe8, 0x8a, 0xd9, 0x2c, 0x12, 0x65, 0x34, 0x0f, 0xa1, 0x51, 0x98, 0xfb, + 0x10, 0x95, 0x64, 0xab, 0x63, 0xae, 0xde, 0xd6, 0xc8, 0x35, 0x3d, 0x95, 0x98, 0x2d, 0xc6, 0x20, + 0x45, 0x84, 0x22, 0x8f, 0xc2, 0x54, 0x24, 0xe7, 0x51, 0x1d, 0x0d, 0xe5, 0x3c, 0x46, 0x8d, 0x51, + 0x34, 0x1e, 0x2e, 0x22, 0x28, 0x1e, 0x09, 0xb4, 0x4a, 0xd3, 0x08, 0x72, 0x69, 0xec, 0x98, 0xa2, + 0x74, 0x15, 0x8f, 0x19, 0x63, 0xe8, 0x25, 0x00, 0xe7, 0xe7, 0x04, 0x41, 0xee, 0x0f, 0x9e, 0x22, + 0x79, 0xaf, 0xae, 0xf9, 0x5a, 0x1b, 0x4a, 0x68, 0xbe, 0xd6, 0x1b, 0xfb, 0x4a, 0x8a, 0xa4, 0x9c, + 0xd6, 0x33, 0x58, 0x94, 0x4d, 0x62, 0xee, 0xe8, 0x52, 0x7b, 0xdc, 0xeb, 0x56, 0x17, 0x04, 0x55, + 0xcd, 0xd9, 0x8e, 0xe7, 0x21, 0x55, 0xe1, 0x88, 0x42, 0xcb, 0x98, 0x3b, 0xa2, 0xda, 0x6d, 0xe6, + 0x8e, 0x18, 0xd5, 0x63, 0x6a, 0x8e, 0xe0, 0xa7, 0x5d, 0xf1, 0xf8, 0x95, 0x01, 0x57, 0x2e, 0xec, + 0xf8, 0xc8, 0xeb, 0x2f, 0xd0, 0x1c, 0x72, 0x81, 0xde, 0x78, 0xe1, 0x76, 0xd2, 0xbc, 0x8e, 0x62, + 0x9a, 0xe6, 0x8e, 0xbc, 0x80, 0x70, 0x9b, 0xc7, 0xd1, 0x55, 0x6f, 0xc9, 0x84, 0xfe, 0xb9, 0x01, + 0x97, 0x2f, 0xa0, 0x4b, 0x6e, 0x4d, 0x29, 0x80, 0x14, 0x78, 0x6f, 0x6a, 0x7c, 0x21, 0xee, 0x35, + 0x14, 0x77, 0xd7, 0xdc, 0x9a, 0x20, 0x2e, 0x13, 0xf6, 0x7b, 0xb0, 0xa5, 0x3a, 0x43, 0x8d, 0xee, + 0xfd, 0x61, 0xe8, 0xa5, 0x44, 0x85, 0xf5, 0x98, 0xf6, 0x31, 0x0f, 0x9c, 0x72, 0xc3, 0xa0, 0xdf, + 0x29, 0x67, 0x62, 0x95, 0x8b, 0xd1, 0x67, 0xb4, 0x19, 0xf7, 0x18, 0x56, 0xe4, 0xbe, 0xfb, 0xbe, + 0x93, 0x7d, 0x63, 0x9e, 0xbb, 0xc8, 0xb3, 0x67, 0xae, 0x15, 0x79, 0xf6, 0x7d, 0x27, 0x93, 0x1c, + 0x0f, 0xe7, 0xf1, 0x27, 0xd7, 0x6f, 0xfe, 0x23, 0x00, 0x00, 0xff, 0xff, 0x49, 0x38, 0x72, 0xc9, + 0xa5, 0x2d, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// GoCryptoTraderClient is the client API for GoCryptoTrader service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GoCryptoTraderClient interface { + GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) + GetExchanges(ctx context.Context, in *GetExchangesRequest, opts ...grpc.CallOption) (*GetExchangesResponse, error) + DisableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) + GetExchangeInfo(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeInfoResponse, error) + EnableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) + GetTicker(ctx context.Context, in *GetTickerRequest, opts ...grpc.CallOption) (*TickerResponse, error) + GetTickers(ctx context.Context, in *GetTickersRequest, opts ...grpc.CallOption) (*GetTickersResponse, error) + GetOrderbook(ctx context.Context, in *GetOrderbookRequest, opts ...grpc.CallOption) (*OrderbookResponse, error) + GetOrderbooks(ctx context.Context, in *GetOrderbooksRequest, opts ...grpc.CallOption) (*GetOrderbooksResponse, error) + GetAccountInfo(ctx context.Context, in *GetAccountInfoRequest, opts ...grpc.CallOption) (*GetAccountInfoResponse, error) + GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error) + GetPortfolio(ctx context.Context, in *GetPortfolioRequest, opts ...grpc.CallOption) (*GetPortfolioResponse, error) + GetPortfolioSummary(ctx context.Context, in *GetPortfolioSummaryRequest, opts ...grpc.CallOption) (*GetPortfolioSummaryResponse, error) + AddPortfolioAddress(ctx context.Context, in *AddPortfolioAddressRequest, opts ...grpc.CallOption) (*AddPortfolioAddressResponse, error) + RemovePortfolioAddress(ctx context.Context, in *RemovePortfolioAddressRequest, opts ...grpc.CallOption) (*RemovePortfolioAddressResponse, error) + GetForexProviders(ctx context.Context, in *GetForexProvidersRequest, opts ...grpc.CallOption) (*GetForexProvidersResponse, error) + GetForexRates(ctx context.Context, in *GetForexRatesRequest, opts ...grpc.CallOption) (*GetForexRatesResponse, error) + GetOrders(ctx context.Context, in *GetOrdersRequest, opts ...grpc.CallOption) (*GetOrdersResponse, error) + GetOrder(ctx context.Context, in *GetOrderRequest, opts ...grpc.CallOption) (*OrderDetails, error) + SubmitOrder(ctx context.Context, in *SubmitOrderRequest, opts ...grpc.CallOption) (*SubmitOrderResponse, error) + CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*CancelOrderResponse, error) + CancelAllOrders(ctx context.Context, in *CancelAllOrdersRequest, opts ...grpc.CallOption) (*CancelAllOrdersResponse, error) + GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error) + AddEvent(ctx context.Context, in *AddEventRequest, opts ...grpc.CallOption) (*AddEventResponse, error) + RemoveEvent(ctx context.Context, in *RemoveEventRequest, opts ...grpc.CallOption) (*RemoveEventResponse, error) + GetCryptocurrencyDepositAddresses(ctx context.Context, in *GetCryptocurrencyDepositAddressesRequest, opts ...grpc.CallOption) (*GetCryptocurrencyDepositAddressesResponse, error) + GetCryptocurrencyDepositAddress(ctx context.Context, in *GetCryptocurrencyDepositAddressRequest, opts ...grpc.CallOption) (*GetCryptocurrencyDepositAddressResponse, error) + WithdrawCryptocurrencyFunds(ctx context.Context, in *WithdrawCurrencyRequest, opts ...grpc.CallOption) (*WithdrawResponse, error) + WithdrawFiatFunds(ctx context.Context, in *WithdrawCurrencyRequest, opts ...grpc.CallOption) (*WithdrawResponse, error) +} + +type goCryptoTraderClient struct { + cc *grpc.ClientConn +} + +func NewGoCryptoTraderClient(cc *grpc.ClientConn) GoCryptoTraderClient { + return &goCryptoTraderClient{cc} +} + +func (c *goCryptoTraderClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) { + out := new(GetInfoResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetExchanges(ctx context.Context, in *GetExchangesRequest, opts ...grpc.CallOption) (*GetExchangesResponse, error) { + out := new(GetExchangesResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetExchanges", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) DisableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) { + out := new(GenericExchangeNameResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/DisableExchange", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetExchangeInfo(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeInfoResponse, error) { + out := new(GetExchangeInfoResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetExchangeInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) EnableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) { + out := new(GenericExchangeNameResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/EnableExchange", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetTicker(ctx context.Context, in *GetTickerRequest, opts ...grpc.CallOption) (*TickerResponse, error) { + out := new(TickerResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetTicker", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetTickers(ctx context.Context, in *GetTickersRequest, opts ...grpc.CallOption) (*GetTickersResponse, error) { + out := new(GetTickersResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetTickers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetOrderbook(ctx context.Context, in *GetOrderbookRequest, opts ...grpc.CallOption) (*OrderbookResponse, error) { + out := new(OrderbookResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetOrderbook", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetOrderbooks(ctx context.Context, in *GetOrderbooksRequest, opts ...grpc.CallOption) (*GetOrderbooksResponse, error) { + out := new(GetOrderbooksResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetOrderbooks", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetAccountInfo(ctx context.Context, in *GetAccountInfoRequest, opts ...grpc.CallOption) (*GetAccountInfoResponse, error) { + out := new(GetAccountInfoResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetAccountInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error) { + out := new(GetConfigResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetConfig", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetPortfolio(ctx context.Context, in *GetPortfolioRequest, opts ...grpc.CallOption) (*GetPortfolioResponse, error) { + out := new(GetPortfolioResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetPortfolio", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetPortfolioSummary(ctx context.Context, in *GetPortfolioSummaryRequest, opts ...grpc.CallOption) (*GetPortfolioSummaryResponse, error) { + out := new(GetPortfolioSummaryResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetPortfolioSummary", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) AddPortfolioAddress(ctx context.Context, in *AddPortfolioAddressRequest, opts ...grpc.CallOption) (*AddPortfolioAddressResponse, error) { + out := new(AddPortfolioAddressResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/AddPortfolioAddress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) RemovePortfolioAddress(ctx context.Context, in *RemovePortfolioAddressRequest, opts ...grpc.CallOption) (*RemovePortfolioAddressResponse, error) { + out := new(RemovePortfolioAddressResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/RemovePortfolioAddress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetForexProviders(ctx context.Context, in *GetForexProvidersRequest, opts ...grpc.CallOption) (*GetForexProvidersResponse, error) { + out := new(GetForexProvidersResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetForexProviders", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetForexRates(ctx context.Context, in *GetForexRatesRequest, opts ...grpc.CallOption) (*GetForexRatesResponse, error) { + out := new(GetForexRatesResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetForexRates", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetOrders(ctx context.Context, in *GetOrdersRequest, opts ...grpc.CallOption) (*GetOrdersResponse, error) { + out := new(GetOrdersResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetOrders", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetOrder(ctx context.Context, in *GetOrderRequest, opts ...grpc.CallOption) (*OrderDetails, error) { + out := new(OrderDetails) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetOrder", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) SubmitOrder(ctx context.Context, in *SubmitOrderRequest, opts ...grpc.CallOption) (*SubmitOrderResponse, error) { + out := new(SubmitOrderResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/SubmitOrder", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*CancelOrderResponse, error) { + out := new(CancelOrderResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/CancelOrder", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) CancelAllOrders(ctx context.Context, in *CancelAllOrdersRequest, opts ...grpc.CallOption) (*CancelAllOrdersResponse, error) { + out := new(CancelAllOrdersResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/CancelAllOrders", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error) { + out := new(GetEventsResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetEvents", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) AddEvent(ctx context.Context, in *AddEventRequest, opts ...grpc.CallOption) (*AddEventResponse, error) { + out := new(AddEventResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/AddEvent", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) RemoveEvent(ctx context.Context, in *RemoveEventRequest, opts ...grpc.CallOption) (*RemoveEventResponse, error) { + out := new(RemoveEventResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/RemoveEvent", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetCryptocurrencyDepositAddresses(ctx context.Context, in *GetCryptocurrencyDepositAddressesRequest, opts ...grpc.CallOption) (*GetCryptocurrencyDepositAddressesResponse, error) { + out := new(GetCryptocurrencyDepositAddressesResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetCryptocurrencyDepositAddresses", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetCryptocurrencyDepositAddress(ctx context.Context, in *GetCryptocurrencyDepositAddressRequest, opts ...grpc.CallOption) (*GetCryptocurrencyDepositAddressResponse, error) { + out := new(GetCryptocurrencyDepositAddressResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetCryptocurrencyDepositAddress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) WithdrawCryptocurrencyFunds(ctx context.Context, in *WithdrawCurrencyRequest, opts ...grpc.CallOption) (*WithdrawResponse, error) { + out := new(WithdrawResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/WithdrawCryptocurrencyFunds", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) WithdrawFiatFunds(ctx context.Context, in *WithdrawCurrencyRequest, opts ...grpc.CallOption) (*WithdrawResponse, error) { + out := new(WithdrawResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/WithdrawFiatFunds", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GoCryptoTraderServer is the server API for GoCryptoTrader service. +type GoCryptoTraderServer interface { + GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) + GetExchanges(context.Context, *GetExchangesRequest) (*GetExchangesResponse, error) + DisableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) + GetExchangeInfo(context.Context, *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) + EnableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) + GetTicker(context.Context, *GetTickerRequest) (*TickerResponse, error) + GetTickers(context.Context, *GetTickersRequest) (*GetTickersResponse, error) + GetOrderbook(context.Context, *GetOrderbookRequest) (*OrderbookResponse, error) + GetOrderbooks(context.Context, *GetOrderbooksRequest) (*GetOrderbooksResponse, error) + GetAccountInfo(context.Context, *GetAccountInfoRequest) (*GetAccountInfoResponse, error) + GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error) + GetPortfolio(context.Context, *GetPortfolioRequest) (*GetPortfolioResponse, error) + GetPortfolioSummary(context.Context, *GetPortfolioSummaryRequest) (*GetPortfolioSummaryResponse, error) + AddPortfolioAddress(context.Context, *AddPortfolioAddressRequest) (*AddPortfolioAddressResponse, error) + RemovePortfolioAddress(context.Context, *RemovePortfolioAddressRequest) (*RemovePortfolioAddressResponse, error) + GetForexProviders(context.Context, *GetForexProvidersRequest) (*GetForexProvidersResponse, error) + GetForexRates(context.Context, *GetForexRatesRequest) (*GetForexRatesResponse, error) + GetOrders(context.Context, *GetOrdersRequest) (*GetOrdersResponse, error) + GetOrder(context.Context, *GetOrderRequest) (*OrderDetails, error) + SubmitOrder(context.Context, *SubmitOrderRequest) (*SubmitOrderResponse, error) + CancelOrder(context.Context, *CancelOrderRequest) (*CancelOrderResponse, error) + CancelAllOrders(context.Context, *CancelAllOrdersRequest) (*CancelAllOrdersResponse, error) + GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error) + AddEvent(context.Context, *AddEventRequest) (*AddEventResponse, error) + RemoveEvent(context.Context, *RemoveEventRequest) (*RemoveEventResponse, error) + GetCryptocurrencyDepositAddresses(context.Context, *GetCryptocurrencyDepositAddressesRequest) (*GetCryptocurrencyDepositAddressesResponse, error) + GetCryptocurrencyDepositAddress(context.Context, *GetCryptocurrencyDepositAddressRequest) (*GetCryptocurrencyDepositAddressResponse, error) + WithdrawCryptocurrencyFunds(context.Context, *WithdrawCurrencyRequest) (*WithdrawResponse, error) + WithdrawFiatFunds(context.Context, *WithdrawCurrencyRequest) (*WithdrawResponse, error) +} + +func RegisterGoCryptoTraderServer(s *grpc.Server, srv GoCryptoTraderServer) { + s.RegisterService(&_GoCryptoTrader_serviceDesc, srv) +} + +func _GoCryptoTrader_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetInfo(ctx, req.(*GetInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetExchanges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetExchangesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetExchanges(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetExchanges", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetExchanges(ctx, req.(*GetExchangesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_DisableExchange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GenericExchangeNameRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).DisableExchange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/DisableExchange", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).DisableExchange(ctx, req.(*GenericExchangeNameRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetExchangeInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GenericExchangeNameRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetExchangeInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetExchangeInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetExchangeInfo(ctx, req.(*GenericExchangeNameRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_EnableExchange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GenericExchangeNameRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).EnableExchange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/EnableExchange", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).EnableExchange(ctx, req.(*GenericExchangeNameRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetTicker_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTickerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetTicker(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetTicker", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetTicker(ctx, req.(*GetTickerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetTickers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTickersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetTickers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetTickers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetTickers(ctx, req.(*GetTickersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetOrderbook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrderbookRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetOrderbook(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetOrderbook", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetOrderbook(ctx, req.(*GetOrderbookRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetOrderbooks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrderbooksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetOrderbooks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetOrderbooks", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetOrderbooks(ctx, req.(*GetOrderbooksRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetAccountInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAccountInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetAccountInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetAccountInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetAccountInfo(ctx, req.(*GetAccountInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetConfig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetConfig(ctx, req.(*GetConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetPortfolio_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPortfolioRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetPortfolio(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetPortfolio", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetPortfolio(ctx, req.(*GetPortfolioRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetPortfolioSummary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPortfolioSummaryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetPortfolioSummary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetPortfolioSummary", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetPortfolioSummary(ctx, req.(*GetPortfolioSummaryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_AddPortfolioAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddPortfolioAddressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).AddPortfolioAddress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/AddPortfolioAddress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).AddPortfolioAddress(ctx, req.(*AddPortfolioAddressRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_RemovePortfolioAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RemovePortfolioAddressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).RemovePortfolioAddress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/RemovePortfolioAddress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).RemovePortfolioAddress(ctx, req.(*RemovePortfolioAddressRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetForexProviders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetForexProvidersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetForexProviders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetForexProviders", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetForexProviders(ctx, req.(*GetForexProvidersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetForexRates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetForexRatesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetForexRates(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetForexRates", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetForexRates(ctx, req.(*GetForexRatesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetOrders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrdersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetOrders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetOrders", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetOrders(ctx, req.(*GetOrdersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetOrder", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetOrder(ctx, req.(*GetOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_SubmitOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubmitOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).SubmitOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/SubmitOrder", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).SubmitOrder(ctx, req.(*SubmitOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_CancelOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CancelOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).CancelOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/CancelOrder", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).CancelOrder(ctx, req.(*CancelOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_CancelAllOrders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CancelAllOrdersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).CancelAllOrders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/CancelAllOrders", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).CancelAllOrders(ctx, req.(*CancelAllOrdersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetEvents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetEventsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetEvents(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetEvents", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetEvents(ctx, req.(*GetEventsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_AddEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddEventRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).AddEvent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/AddEvent", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).AddEvent(ctx, req.(*AddEventRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_RemoveEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RemoveEventRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).RemoveEvent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/RemoveEvent", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).RemoveEvent(ctx, req.(*RemoveEventRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetCryptocurrencyDepositAddresses_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCryptocurrencyDepositAddressesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetCryptocurrencyDepositAddresses(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetCryptocurrencyDepositAddresses", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetCryptocurrencyDepositAddresses(ctx, req.(*GetCryptocurrencyDepositAddressesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetCryptocurrencyDepositAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCryptocurrencyDepositAddressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetCryptocurrencyDepositAddress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetCryptocurrencyDepositAddress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetCryptocurrencyDepositAddress(ctx, req.(*GetCryptocurrencyDepositAddressRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_WithdrawCryptocurrencyFunds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WithdrawCurrencyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).WithdrawCryptocurrencyFunds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/WithdrawCryptocurrencyFunds", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).WithdrawCryptocurrencyFunds(ctx, req.(*WithdrawCurrencyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_WithdrawFiatFunds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WithdrawCurrencyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).WithdrawFiatFunds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/WithdrawFiatFunds", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).WithdrawFiatFunds(ctx, req.(*WithdrawCurrencyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ + ServiceName: "gctrpc.GoCryptoTrader", + HandlerType: (*GoCryptoTraderServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetInfo", + Handler: _GoCryptoTrader_GetInfo_Handler, + }, + { + MethodName: "GetExchanges", + Handler: _GoCryptoTrader_GetExchanges_Handler, + }, + { + MethodName: "DisableExchange", + Handler: _GoCryptoTrader_DisableExchange_Handler, + }, + { + MethodName: "GetExchangeInfo", + Handler: _GoCryptoTrader_GetExchangeInfo_Handler, + }, + { + MethodName: "EnableExchange", + Handler: _GoCryptoTrader_EnableExchange_Handler, + }, + { + MethodName: "GetTicker", + Handler: _GoCryptoTrader_GetTicker_Handler, + }, + { + MethodName: "GetTickers", + Handler: _GoCryptoTrader_GetTickers_Handler, + }, + { + MethodName: "GetOrderbook", + Handler: _GoCryptoTrader_GetOrderbook_Handler, + }, + { + MethodName: "GetOrderbooks", + Handler: _GoCryptoTrader_GetOrderbooks_Handler, + }, + { + MethodName: "GetAccountInfo", + Handler: _GoCryptoTrader_GetAccountInfo_Handler, + }, + { + MethodName: "GetConfig", + Handler: _GoCryptoTrader_GetConfig_Handler, + }, + { + MethodName: "GetPortfolio", + Handler: _GoCryptoTrader_GetPortfolio_Handler, + }, + { + MethodName: "GetPortfolioSummary", + Handler: _GoCryptoTrader_GetPortfolioSummary_Handler, + }, + { + MethodName: "AddPortfolioAddress", + Handler: _GoCryptoTrader_AddPortfolioAddress_Handler, + }, + { + MethodName: "RemovePortfolioAddress", + Handler: _GoCryptoTrader_RemovePortfolioAddress_Handler, + }, + { + MethodName: "GetForexProviders", + Handler: _GoCryptoTrader_GetForexProviders_Handler, + }, + { + MethodName: "GetForexRates", + Handler: _GoCryptoTrader_GetForexRates_Handler, + }, + { + MethodName: "GetOrders", + Handler: _GoCryptoTrader_GetOrders_Handler, + }, + { + MethodName: "GetOrder", + Handler: _GoCryptoTrader_GetOrder_Handler, + }, + { + MethodName: "SubmitOrder", + Handler: _GoCryptoTrader_SubmitOrder_Handler, + }, + { + MethodName: "CancelOrder", + Handler: _GoCryptoTrader_CancelOrder_Handler, + }, + { + MethodName: "CancelAllOrders", + Handler: _GoCryptoTrader_CancelAllOrders_Handler, + }, + { + MethodName: "GetEvents", + Handler: _GoCryptoTrader_GetEvents_Handler, + }, + { + MethodName: "AddEvent", + Handler: _GoCryptoTrader_AddEvent_Handler, + }, + { + MethodName: "RemoveEvent", + Handler: _GoCryptoTrader_RemoveEvent_Handler, + }, + { + MethodName: "GetCryptocurrencyDepositAddresses", + Handler: _GoCryptoTrader_GetCryptocurrencyDepositAddresses_Handler, + }, + { + MethodName: "GetCryptocurrencyDepositAddress", + Handler: _GoCryptoTrader_GetCryptocurrencyDepositAddress_Handler, + }, + { + MethodName: "WithdrawCryptocurrencyFunds", + Handler: _GoCryptoTrader_WithdrawCryptocurrencyFunds_Handler, + }, + { + MethodName: "WithdrawFiatFunds", + Handler: _GoCryptoTrader_WithdrawFiatFunds_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "rpc.proto", +} diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go new file mode 100644 index 00000000..3da1471e --- /dev/null +++ b/gctrpc/rpc.pb.gw.go @@ -0,0 +1,1191 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: rpc.proto + +/* +Package gctrpc is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package gctrpc + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray + +func request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetInfoRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_GetExchanges_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangesRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetExchanges(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_DisableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DisableExchange(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_GetExchangeInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetExchangeInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.EnableExchange(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetTicker_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetTickerRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetTicker(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetTickers_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetTickersRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetTickers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetOrderbook_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrderbookRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetOrderbook(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetOrderbooks_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrderbooksRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetOrderbooks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_GetAccountInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetAccountInfoRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetAccountInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetConfigRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetConfig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetPortfolio_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetPortfolioRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetPortfolio(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetPortfolioSummary_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetPortfolioSummaryRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetPortfolioSummary(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_AddPortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddPortfolioAddressRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.AddPortfolioAddress(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_RemovePortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RemovePortfolioAddressRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.RemovePortfolioAddress(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetForexProviders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetForexProvidersRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetForexProviders(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetForexRates_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetForexRatesRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetForexRates(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetOrders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrdersRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetOrders(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetOrder(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SubmitOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.SubmitOrder(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CancelOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.CancelOrder(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_CancelAllOrders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CancelAllOrdersRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.CancelAllOrders(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetEvents_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetEventsRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetEvents(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_AddEvent_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddEventRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.AddEvent(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_RemoveEvent_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RemoveEventRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.RemoveEvent(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCryptocurrencyDepositAddressesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetCryptocurrencyDepositAddresses(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCryptocurrencyDepositAddressRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetCryptocurrencyDepositAddress(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WithdrawCurrencyRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.WithdrawCryptocurrencyFunds(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WithdrawCurrencyRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.WithdrawFiatFunds(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterGoCryptoTraderHandlerFromEndpoint is same as RegisterGoCryptoTraderHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterGoCryptoTraderHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterGoCryptoTraderHandler(ctx, mux, conn) +} + +// RegisterGoCryptoTraderHandler registers the http handlers for service GoCryptoTrader to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterGoCryptoTraderHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterGoCryptoTraderHandlerClient(ctx, mux, NewGoCryptoTraderClient(conn)) +} + +// RegisterGoCryptoTraderHandlerClient registers the http handlers for service GoCryptoTrader +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "GoCryptoTraderClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "GoCryptoTraderClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "GoCryptoTraderClient" to call the correct interceptors. +func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.ServeMux, client GoCryptoTraderClient) error { + + mux.Handle("GET", pattern_GoCryptoTrader_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetInfo_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchanges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetExchanges_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchanges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_DisableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_DisableExchange_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_DisableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetExchangeInfo_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_EnableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_EnableExchange_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_EnableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetTicker_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetTicker_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetTicker_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetTickers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetTickers_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetTickers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetOrderbook_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetOrderbook_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrderbook_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetOrderbooks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetOrderbooks_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrderbooks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetAccountInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetAccountInfo_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetAccountInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetConfig_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetConfig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetPortfolio_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetPortfolio_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetPortfolio_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetPortfolioSummary_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetPortfolioSummary_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetPortfolioSummary_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_AddPortfolioAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_AddPortfolioAddress_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_AddPortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_RemovePortfolioAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_RemovePortfolioAddress_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_RemovePortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetForexProviders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetForexProviders_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetForexProviders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetForexRates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetForexRates_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetForexRates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetOrders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetOrders_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetOrder_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_SubmitOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_SubmitOrder_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SubmitOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_CancelOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_CancelOrder_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_CancelOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_CancelAllOrders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_CancelAllOrders_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_CancelAllOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetEvents_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetEvents_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_AddEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_AddEvent_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_AddEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_RemoveEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_RemoveEvent_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_RemoveEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_WithdrawFiatFunds_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_WithdrawFiatFunds_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WithdrawFiatFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_GoCryptoTrader_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, "")) + + pattern_GoCryptoTrader_GetExchanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchanges"}, "")) + + pattern_GoCryptoTrader_DisableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchange"}, "")) + + pattern_GoCryptoTrader_GetExchangeInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeinfo"}, "")) + + pattern_GoCryptoTrader_EnableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchange"}, "")) + + pattern_GoCryptoTrader_GetTicker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getticker"}, "")) + + pattern_GoCryptoTrader_GetTickers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "gettickers"}, "")) + + pattern_GoCryptoTrader_GetOrderbook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbook"}, "")) + + pattern_GoCryptoTrader_GetOrderbooks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbooks"}, "")) + + pattern_GoCryptoTrader_GetAccountInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getaccountinfo"}, "")) + + pattern_GoCryptoTrader_GetConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getconfig"}, "")) + + pattern_GoCryptoTrader_GetPortfolio_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfolio"}, "")) + + pattern_GoCryptoTrader_GetPortfolioSummary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfoliosummary"}, "")) + + pattern_GoCryptoTrader_AddPortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addportfolioaddress"}, "")) + + pattern_GoCryptoTrader_RemovePortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeportfolioaddress"}, "")) + + pattern_GoCryptoTrader_GetForexProviders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexproviders"}, "")) + + pattern_GoCryptoTrader_GetForexRates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexrates"}, "")) + + pattern_GoCryptoTrader_GetOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorders"}, "")) + + pattern_GoCryptoTrader_GetOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorder"}, "")) + + pattern_GoCryptoTrader_SubmitOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "submitorder"}, "")) + + pattern_GoCryptoTrader_CancelOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelorder"}, "")) + + pattern_GoCryptoTrader_CancelAllOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelallorders"}, "")) + + pattern_GoCryptoTrader_GetEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getevents"}, "")) + + pattern_GoCryptoTrader_AddEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addevent"}, "")) + + pattern_GoCryptoTrader_RemoveEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeevent"}, "")) + + pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddresses"}, "")) + + pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddress"}, "")) + + pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawcryptofunds"}, "")) + + pattern_GoCryptoTrader_WithdrawFiatFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawfiatfunds"}, "")) +) + +var ( + forward_GoCryptoTrader_GetInfo_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetExchanges_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_DisableExchange_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetExchangeInfo_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_EnableExchange_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetTicker_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetTickers_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetOrderbook_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetOrderbooks_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetAccountInfo_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetConfig_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetPortfolio_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetPortfolioSummary_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_AddPortfolioAddress_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_RemovePortfolioAddress_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetForexProviders_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetForexRates_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetOrders_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetOrder_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_SubmitOrder_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_CancelOrder_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_CancelAllOrders_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetEvents_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_AddEvent_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_RemoveEvent_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetCryptocurrencyDepositAddress_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_WithdrawCryptocurrencyFunds_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_WithdrawFiatFunds_0 = runtime.ForwardResponseMessage +) diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto new file mode 100644 index 00000000..54030bf2 --- /dev/null +++ b/gctrpc/rpc.proto @@ -0,0 +1,581 @@ +syntax = "proto3"; + +import "google/api/annotations.proto"; + +package gctrpc; + +message GetInfoRequest {} + +message GetInfoResponse { + string uptime = 1; + int64 available_exchanges = 2; + int64 enabled_exchanges = 3; + string default_forex_provider = 4; + string default_fiat_currency = 5; +} + +message GenericExchangeNameRequest { + string exchange = 1; +} + +message GenericExchangeNameResponse {} + +message GetExchangesRequest { + bool enabled = 1; +} + +message GetExchangesResponse { + string exchanges = 1; +} + +message DisableExchangeRequest { + string exchange = 1; +} + +message GetExchangeInfoResponse { + string name = 1; + bool enabled = 2; + bool verbose = 3; + bool using_sandbox = 4; + string http_timeout = 5; + string http_useragent = 6; + string http_proxy = 7; + string base_currencies = 8; + string supported_assets = 9; + string enabled_pairs = 10; + string available_pairs = 11; + bool authenticated_api = 12; +} + +message GetTickerRequest { + string exchange = 1; + CurrencyPair pair = 2; + string asset_type = 3; +} + +message CurrencyPair { + string delimiter = 1; + string base = 2; + string quote = 3; +} + +message TickerResponse { + CurrencyPair pair = 1; + int64 last_updated = 2; + string currency_pair = 3; + double last = 4; + double high = 5; + double low = 6; + double bid = 7; + double ask = 8; + double volume = 9; + double price_ath = 10; +} + +message GetTickersRequest {} + +message Tickers { + string exchange = 1; + repeated TickerResponse tickers = 2; +} + +message GetTickersResponse { + repeated Tickers tickers = 1; +} + +message GetOrderbookRequest { + string exchange = 1; + CurrencyPair pair = 2; + string asset_type = 3; +} + +message OrderbookItem { + double amount = 1; + double price = 2; + int64 id = 3; +} + +message OrderbookResponse { + CurrencyPair pair = 1; + string currency_pair = 2; + repeated OrderbookItem bids = 3; + repeated OrderbookItem asks = 4; + int64 last_updated = 5; + string asset_type = 6; +} + +message GetOrderbooksRequest {} + +message Orderbooks { + string exchange = 1; + repeated OrderbookResponse orderbooks = 2; +} + +message GetOrderbooksResponse { + repeated Orderbooks orderbooks = 1; +} + +message GetAccountInfoRequest { + string exchange = 1; +} + +message Account { + string id = 1; + repeated AccountCurrencyInfo currencies = 2; +} + +message AccountCurrencyInfo { + string currency = 1; + double total_value = 2; + double hold = 3; +} + +message GetAccountInfoResponse { + string exchange = 1; + repeated Account accounts = 2; +} + +message GetConfigRequest {} + +message GetConfigResponse { + bytes data = 1; +} + +message PortfolioAddress { + string address = 1; + string coin_type = 2; + string description = 3; + double balance = 4; +} + +message GetPortfolioRequest {} + +message GetPortfolioResponse { + repeated PortfolioAddress portfolio = 1; +} + +message GetPortfolioSummaryRequest {} + +message Coin { + string coin = 1; + double balance = 2; + string address = 3; + double percentage = 4; +} + +message OfflineCoinSummary { + string address = 1; + double balance = 2; + double percentage = 3; +} + +message OnlineCoinSummary { + double balance = 1; + double percentage = 2; +} + +message OfflineCoins { + repeated OfflineCoinSummary addresses = 1; +} + +message OnlineCoins { + map coins = 1; +} + +message GetPortfolioSummaryResponse { + repeated Coin coin_totals = 1; + repeated Coin coins_offline = 2; + map coins_offline_summary = 3; + repeated Coin coins_online = 4; + map coins_online_summary = 5; +} + +message AddPortfolioAddressRequest { + string address = 1; + string coin_type = 2; + string description = 3; + double balance = 4; +} + +message AddPortfolioAddressResponse {} + +message RemovePortfolioAddressRequest { + string address = 1; + string coin_type = 2; + string description = 3; +} + +message RemovePortfolioAddressResponse {} + +message GetForexProvidersRequest {} + +message ForexProvider { + string name = 1; + bool enabled = 2; + bool verbose = 3; + string rest_rolling_delay = 4; + string api_key = 5; + int64 api_key_level =6; + bool primary_provider = 7; +} + +message GetForexProvidersResponse { + repeated ForexProvider forex_providers = 1; +} + +message GetForexRatesRequest {} + +message ForexRatesConversion { + string from = 1; + string to = 2; + double rate = 3; + double inverse_rate = 4; + +} +message GetForexRatesResponse { + repeated ForexRatesConversion forex_rates = 1; +} + +message OrderDetails { + string exchange = 1; + string id = 2; + string base_currency = 3; + string quote_currency = 4; + string asset_type = 5; + string order_side = 6; + string order_type = 7; + int64 creation_time = 8; + string status = 9; + double price = 10; + double amount = 11; + double open_volume = 12; +} + +message GetOrdersRequest { + string exchange = 1; + string asset_type = 2; + CurrencyPair pair = 3; +} + +message GetOrdersResponse { + repeated OrderDetails orders = 1; +} + +message GetOrderRequest { + string exchange = 1; + string order_id = 2; +} + +message SubmitOrderRequest { + string exchange = 1; + CurrencyPair pair = 2; + string side = 3; + string order_type = 4; + double amount = 5; + double price = 6; + string client_id = 7; +} + +message SubmitOrderResponse { + bool order_placed = 1; + string order_id = 2; +} + +message CancelOrderRequest { + string exchange = 1; + string account_id = 2; + string order_id = 3; + CurrencyPair pair = 4; + string asset_type = 5; + string wallet_address = 6; + string side = 7; +} + +message CancelOrderResponse {} + +message CancelAllOrdersRequest { + string exchange = 1; +} + +message CancelAllOrdersResponse { + message Orders { + string exchange = 1; + map order_status = 2; + } + repeated Orders orders = 1; +} + +message GetEventsRequest {} + + +message ConditionParams { + string condition = 1; + double price = 2; + bool check_bids = 3; + bool check_bids_and_asks = 4; + double orderbook_amount = 5; +} + +message GetEventsResponse { + int64 id = 1; + string exchange = 2; + string item = 3; + ConditionParams condition_params = 4; + CurrencyPair pair = 5; + string action = 6; + bool executed = 7; +} + +message AddEventRequest { + string exchange = 1; + string item = 2; + ConditionParams condition_params = 3; + CurrencyPair pair = 4; + string asset_type = 5; + string action = 6; +} + +message AddEventResponse { + int64 id = 1; +} + +message RemoveEventRequest { + int64 id = 1; +} + +message RemoveEventResponse {} + +message GetCryptocurrencyDepositAddressesRequest { + string exchange = 1; +} + +message GetCryptocurrencyDepositAddressesResponse { + map addresses = 1; +} + +message GetCryptocurrencyDepositAddressRequest { + string exchange = 1; + string cryptocurrency = 2; +} + +message GetCryptocurrencyDepositAddressResponse { + string address = 1; +} + +message WithdrawCurrencyRequest { + string exchange = 1; + string description = 2; + string one_time_password = 3; + string account_id = 4; + int64 pin = 5; + string trade_password = 6; + string currency = 7; + string address = 8; + string address_tag = 9; + double amount = 10; + double fee_amount = 11; + string bank_name = 12; + string bank_address = 13; + string bank_city = 14; + string bank_country = 15; + string swife_code = 16; + string wire_currency = 17; +} + +message WithdrawResponse { + string result = 1; +} + +service GoCryptoTrader { + rpc GetInfo (GetInfoRequest) returns (GetInfoResponse) { + option (google.api.http) = { + get :"/v1/getinfo" + }; + } + + rpc GetExchanges (GetExchangesRequest) returns (GetExchangesResponse) { + option (google.api.http) = { + get: "/v1/getexchanges" + }; + } + + rpc DisableExchange (GenericExchangeNameRequest) returns (GenericExchangeNameResponse) { + option (google.api.http) = { + post: "/v1/disableexchange" + body: "*" + }; + } + + rpc GetExchangeInfo (GenericExchangeNameRequest) returns (GetExchangeInfoResponse) { + option (google.api.http) = { + get: "/v1/getexchangeinfo" + }; + } + + rpc EnableExchange (GenericExchangeNameRequest) returns (GenericExchangeNameResponse) { + option (google.api.http) = { + post: "/v1/enableexchange" + body: "*" + }; + } + + rpc GetTicker (GetTickerRequest) returns (TickerResponse) { + option (google.api.http) = { + post: "/v1/getticker" + body: "*" + }; + } + + rpc GetTickers (GetTickersRequest) returns (GetTickersResponse) { + option (google.api.http) = { + get: "/v1/gettickers" + }; + } + + rpc GetOrderbook (GetOrderbookRequest) returns (OrderbookResponse) { + option (google.api.http) = { + post: "/v1/getorderbook" + body: "*" + }; + } + + rpc GetOrderbooks (GetOrderbooksRequest) returns (GetOrderbooksResponse) { + option (google.api.http) = { + get: "/v1/getorderbooks" + }; + } + + rpc GetAccountInfo (GetAccountInfoRequest) returns (GetAccountInfoResponse) { + option (google.api.http) = { + get: "/v1/getaccountinfo" + }; + } + + rpc GetConfig (GetConfigRequest) returns (GetConfigResponse) { + option (google.api.http) = { + get: "/v1/getconfig" + }; + } + + rpc GetPortfolio (GetPortfolioRequest) returns (GetPortfolioResponse) { + option (google.api.http) = { + get: "/v1/getportfolio" + }; + } + + rpc GetPortfolioSummary (GetPortfolioSummaryRequest) returns (GetPortfolioSummaryResponse) { + option (google.api.http) = { + get: "/v1/getportfoliosummary" + }; + } + + + rpc AddPortfolioAddress (AddPortfolioAddressRequest) returns (AddPortfolioAddressResponse) { + option (google.api.http) = { + post: "/v1/addportfolioaddress" + body: "*" + }; + } + + rpc RemovePortfolioAddress (RemovePortfolioAddressRequest) returns (RemovePortfolioAddressResponse) { + option (google.api.http) = { + post: "/v1/removeportfolioaddress" + body: "*" + }; + } + + rpc GetForexProviders (GetForexProvidersRequest) returns (GetForexProvidersResponse) { + option (google.api.http) = { + get: "/v1/getforexproviders" + }; + } + + rpc GetForexRates (GetForexRatesRequest) returns (GetForexRatesResponse) { + option (google.api.http) = { + get: "/v1/getforexrates" + }; + } + + rpc GetOrders (GetOrdersRequest) returns (GetOrdersResponse) { + option (google.api.http) = { + post: "/v1/getorders" + body: "*" + }; + } + + rpc GetOrder (GetOrderRequest) returns (OrderDetails) { + option (google.api.http) = { + post: "/v1/getorder" + body: "*" + }; + } + + rpc SubmitOrder (SubmitOrderRequest) returns (SubmitOrderResponse) { + option (google.api.http) = { + post: "/v1/submitorder" + body: "*" + }; + } + + rpc CancelOrder (CancelOrderRequest) returns (CancelOrderResponse) { + option (google.api.http) = { + post: "/v1/cancelorder" + body: "*" + }; + } + + rpc CancelAllOrders (CancelAllOrdersRequest) returns (CancelAllOrdersResponse) { + option (google.api.http) = { + post: "/v1/cancelallorders" + body: "*" + }; + } + + rpc GetEvents(GetEventsRequest) returns (GetEventsResponse) { + option (google.api.http) = { + get: "/v1/getevents" + }; + } + + rpc AddEvent(AddEventRequest) returns (AddEventResponse) { + option (google.api.http) = { + post: "/v1/addevent" + body: "*" + }; + } + + rpc RemoveEvent(RemoveEventRequest) returns (RemoveEventResponse) { + option (google.api.http) = { + post: "/v1/removeevent" + body: "*" + }; + } + + rpc GetCryptocurrencyDepositAddresses(GetCryptocurrencyDepositAddressesRequest) returns (GetCryptocurrencyDepositAddressesResponse) { + option (google.api.http) = { + post: "/v1/getcryptodepositaddresses" + body: "*" + }; + } + + rpc GetCryptocurrencyDepositAddress(GetCryptocurrencyDepositAddressRequest) returns (GetCryptocurrencyDepositAddressResponse) { + option (google.api.http) = { + post: "/v1/getcryptodepositaddress" + body: "*" + }; + } + + rpc WithdrawCryptocurrencyFunds(WithdrawCurrencyRequest) returns (WithdrawResponse) { + option (google.api.http) = { + post: "/v1/withdrawcryptofunds" + body: "*" + }; + } + + rpc WithdrawFiatFunds(WithdrawCurrencyRequest) returns (WithdrawResponse) { + option (google.api.http) = { + post: "/v1/withdrawfiatfunds" + body: "*" + }; + } +} diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json new file mode 100644 index 00000000..12564431 --- /dev/null +++ b/gctrpc/rpc.swagger.json @@ -0,0 +1,1628 @@ +{ + "swagger": "2.0", + "info": { + "title": "rpc.proto", + "version": "version not set" + }, + "schemes": [ + "http", + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/addevent": { + "post": { + "operationId": "AddEvent", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcAddEventResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcAddEventRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/addportfolioaddress": { + "post": { + "operationId": "AddPortfolioAddress", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcAddPortfolioAddressResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcAddPortfolioAddressRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/cancelallorders": { + "post": { + "operationId": "CancelAllOrders", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcCancelAllOrdersResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcCancelAllOrdersRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/cancelorder": { + "post": { + "operationId": "CancelOrder", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcCancelOrderResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcCancelOrderRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/disableexchange": { + "post": { + "operationId": "DisableExchange", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericExchangeNameResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcGenericExchangeNameRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/enableexchange": { + "post": { + "operationId": "EnableExchange", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericExchangeNameResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcGenericExchangeNameRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getaccountinfo": { + "get": { + "operationId": "GetAccountInfo", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetAccountInfoResponse" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getconfig": { + "get": { + "operationId": "GetConfig", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetConfigResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getcryptodepositaddress": { + "post": { + "operationId": "GetCryptocurrencyDepositAddress", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetCryptocurrencyDepositAddressResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcGetCryptocurrencyDepositAddressRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getcryptodepositaddresses": { + "post": { + "operationId": "GetCryptocurrencyDepositAddresses", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetCryptocurrencyDepositAddressesResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcGetCryptocurrencyDepositAddressesRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getevents": { + "get": { + "operationId": "GetEvents", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetEventsResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getexchangeinfo": { + "get": { + "operationId": "GetExchangeInfo", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetExchangeInfoResponse" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getexchanges": { + "get": { + "operationId": "GetExchanges", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetExchangesResponse" + } + } + }, + "parameters": [ + { + "name": "enabled", + "in": "query", + "required": false, + "type": "boolean", + "format": "boolean" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getforexproviders": { + "get": { + "operationId": "GetForexProviders", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetForexProvidersResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getforexrates": { + "get": { + "operationId": "GetForexRates", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetForexRatesResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getinfo": { + "get": { + "operationId": "GetInfo", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetInfoResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getorder": { + "post": { + "operationId": "GetOrder", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcOrderDetails" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcGetOrderRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getorderbook": { + "post": { + "operationId": "GetOrderbook", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcOrderbookResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcGetOrderbookRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getorderbooks": { + "get": { + "operationId": "GetOrderbooks", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetOrderbooksResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getorders": { + "post": { + "operationId": "GetOrders", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetOrdersResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcGetOrdersRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getportfolio": { + "get": { + "operationId": "GetPortfolio", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetPortfolioResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getportfoliosummary": { + "get": { + "operationId": "GetPortfolioSummary", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetPortfolioSummaryResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getticker": { + "post": { + "operationId": "GetTicker", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcTickerResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcGetTickerRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/gettickers": { + "get": { + "operationId": "GetTickers", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetTickersResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/removeevent": { + "post": { + "operationId": "RemoveEvent", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcRemoveEventResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcRemoveEventRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/removeportfolioaddress": { + "post": { + "operationId": "RemovePortfolioAddress", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcRemovePortfolioAddressResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcRemovePortfolioAddressRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/submitorder": { + "post": { + "operationId": "SubmitOrder", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcSubmitOrderResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcSubmitOrderRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/withdrawcryptofunds": { + "post": { + "operationId": "WithdrawCryptocurrencyFunds", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcWithdrawResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcWithdrawCurrencyRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/withdrawfiatfunds": { + "post": { + "operationId": "WithdrawFiatFunds", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcWithdrawResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcWithdrawCurrencyRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + } + }, + "definitions": { + "CancelAllOrdersResponseOrders": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "order_status": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "gctrpcAccount": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "currencies": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcAccountCurrencyInfo" + } + } + } + }, + "gctrpcAccountCurrencyInfo": { + "type": "object", + "properties": { + "currency": { + "type": "string" + }, + "total_value": { + "type": "number", + "format": "double" + }, + "hold": { + "type": "number", + "format": "double" + } + } + }, + "gctrpcAddEventRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "item": { + "type": "string" + }, + "condition_params": { + "$ref": "#/definitions/gctrpcConditionParams" + }, + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + }, + "asset_type": { + "type": "string" + }, + "action": { + "type": "string" + } + } + }, + "gctrpcAddEventResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "int64" + } + } + }, + "gctrpcAddPortfolioAddressRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "coin_type": { + "type": "string" + }, + "description": { + "type": "string" + }, + "balance": { + "type": "number", + "format": "double" + } + } + }, + "gctrpcAddPortfolioAddressResponse": { + "type": "object" + }, + "gctrpcCancelAllOrdersRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + } + } + }, + "gctrpcCancelAllOrdersResponse": { + "type": "object", + "properties": { + "orders": { + "type": "array", + "items": { + "$ref": "#/definitions/CancelAllOrdersResponseOrders" + } + } + } + }, + "gctrpcCancelOrderRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "account_id": { + "type": "string" + }, + "order_id": { + "type": "string" + }, + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + }, + "asset_type": { + "type": "string" + }, + "wallet_address": { + "type": "string" + }, + "side": { + "type": "string" + } + } + }, + "gctrpcCancelOrderResponse": { + "type": "object" + }, + "gctrpcCoin": { + "type": "object", + "properties": { + "coin": { + "type": "string" + }, + "balance": { + "type": "number", + "format": "double" + }, + "address": { + "type": "string" + }, + "percentage": { + "type": "number", + "format": "double" + } + } + }, + "gctrpcConditionParams": { + "type": "object", + "properties": { + "condition": { + "type": "string" + }, + "price": { + "type": "number", + "format": "double" + }, + "check_bids": { + "type": "boolean", + "format": "boolean" + }, + "check_bids_and_asks": { + "type": "boolean", + "format": "boolean" + }, + "orderbook_amount": { + "type": "number", + "format": "double" + } + } + }, + "gctrpcCurrencyPair": { + "type": "object", + "properties": { + "delimiter": { + "type": "string" + }, + "base": { + "type": "string" + }, + "quote": { + "type": "string" + } + } + }, + "gctrpcForexProvider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "enabled": { + "type": "boolean", + "format": "boolean" + }, + "verbose": { + "type": "boolean", + "format": "boolean" + }, + "rest_rolling_delay": { + "type": "string" + }, + "api_key": { + "type": "string" + }, + "api_key_level": { + "type": "string", + "format": "int64" + }, + "primary_provider": { + "type": "boolean", + "format": "boolean" + } + } + }, + "gctrpcForexRatesConversion": { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "rate": { + "type": "number", + "format": "double" + }, + "inverse_rate": { + "type": "number", + "format": "double" + } + } + }, + "gctrpcGenericExchangeNameRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + } + } + }, + "gctrpcGenericExchangeNameResponse": { + "type": "object" + }, + "gctrpcGetAccountInfoResponse": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcAccount" + } + } + } + }, + "gctrpcGetConfigResponse": { + "type": "object", + "properties": { + "data": { + "type": "string", + "format": "byte" + } + } + }, + "gctrpcGetCryptocurrencyDepositAddressRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "cryptocurrency": { + "type": "string" + } + } + }, + "gctrpcGetCryptocurrencyDepositAddressResponse": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "gctrpcGetCryptocurrencyDepositAddressesRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + } + } + }, + "gctrpcGetCryptocurrencyDepositAddressesResponse": { + "type": "object", + "properties": { + "addresses": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "gctrpcGetEventsResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "int64" + }, + "exchange": { + "type": "string" + }, + "item": { + "type": "string" + }, + "condition_params": { + "$ref": "#/definitions/gctrpcConditionParams" + }, + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + }, + "action": { + "type": "string" + }, + "executed": { + "type": "boolean", + "format": "boolean" + } + } + }, + "gctrpcGetExchangeInfoResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "enabled": { + "type": "boolean", + "format": "boolean" + }, + "verbose": { + "type": "boolean", + "format": "boolean" + }, + "using_sandbox": { + "type": "boolean", + "format": "boolean" + }, + "http_timeout": { + "type": "string" + }, + "http_useragent": { + "type": "string" + }, + "http_proxy": { + "type": "string" + }, + "base_currencies": { + "type": "string" + }, + "supported_assets": { + "type": "string" + }, + "enabled_pairs": { + "type": "string" + }, + "available_pairs": { + "type": "string" + }, + "authenticated_api": { + "type": "boolean", + "format": "boolean" + } + } + }, + "gctrpcGetExchangesResponse": { + "type": "object", + "properties": { + "exchanges": { + "type": "string" + } + } + }, + "gctrpcGetForexProvidersResponse": { + "type": "object", + "properties": { + "forex_providers": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcForexProvider" + } + } + } + }, + "gctrpcGetForexRatesResponse": { + "type": "object", + "properties": { + "forex_rates": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcForexRatesConversion" + } + } + } + }, + "gctrpcGetInfoResponse": { + "type": "object", + "properties": { + "uptime": { + "type": "string" + }, + "available_exchanges": { + "type": "string", + "format": "int64" + }, + "enabled_exchanges": { + "type": "string", + "format": "int64" + }, + "default_forex_provider": { + "type": "string" + }, + "default_fiat_currency": { + "type": "string" + } + } + }, + "gctrpcGetOrderRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "order_id": { + "type": "string" + } + } + }, + "gctrpcGetOrderbookRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + }, + "asset_type": { + "type": "string" + } + } + }, + "gctrpcGetOrderbooksResponse": { + "type": "object", + "properties": { + "orderbooks": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcOrderbooks" + } + } + } + }, + "gctrpcGetOrdersRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "asset_type": { + "type": "string" + }, + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + } + } + }, + "gctrpcGetOrdersResponse": { + "type": "object", + "properties": { + "orders": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcOrderDetails" + } + } + } + }, + "gctrpcGetPortfolioResponse": { + "type": "object", + "properties": { + "portfolio": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcPortfolioAddress" + } + } + } + }, + "gctrpcGetPortfolioSummaryResponse": { + "type": "object", + "properties": { + "coin_totals": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcCoin" + } + }, + "coins_offline": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcCoin" + } + }, + "coins_offline_summary": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/gctrpcOfflineCoins" + } + }, + "coins_online": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcCoin" + } + }, + "coins_online_summary": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/gctrpcOnlineCoins" + } + } + } + }, + "gctrpcGetTickerRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + }, + "asset_type": { + "type": "string" + } + } + }, + "gctrpcGetTickersResponse": { + "type": "object", + "properties": { + "tickers": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcTickers" + } + } + } + }, + "gctrpcOfflineCoinSummary": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "balance": { + "type": "number", + "format": "double" + }, + "percentage": { + "type": "number", + "format": "double" + } + } + }, + "gctrpcOfflineCoins": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcOfflineCoinSummary" + } + } + } + }, + "gctrpcOnlineCoinSummary": { + "type": "object", + "properties": { + "balance": { + "type": "number", + "format": "double" + }, + "percentage": { + "type": "number", + "format": "double" + } + } + }, + "gctrpcOnlineCoins": { + "type": "object", + "properties": { + "coins": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/gctrpcOnlineCoinSummary" + } + } + } + }, + "gctrpcOrderDetails": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "id": { + "type": "string" + }, + "base_currency": { + "type": "string" + }, + "quote_currency": { + "type": "string" + }, + "asset_type": { + "type": "string" + }, + "order_side": { + "type": "string" + }, + "order_type": { + "type": "string" + }, + "creation_time": { + "type": "string", + "format": "int64" + }, + "status": { + "type": "string" + }, + "price": { + "type": "number", + "format": "double" + }, + "amount": { + "type": "number", + "format": "double" + }, + "open_volume": { + "type": "number", + "format": "double" + } + } + }, + "gctrpcOrderbookItem": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "format": "double" + }, + "price": { + "type": "number", + "format": "double" + }, + "id": { + "type": "string", + "format": "int64" + } + } + }, + "gctrpcOrderbookResponse": { + "type": "object", + "properties": { + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + }, + "currency_pair": { + "type": "string" + }, + "bids": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcOrderbookItem" + } + }, + "asks": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcOrderbookItem" + } + }, + "last_updated": { + "type": "string", + "format": "int64" + }, + "asset_type": { + "type": "string" + } + } + }, + "gctrpcOrderbooks": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "orderbooks": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcOrderbookResponse" + } + } + } + }, + "gctrpcPortfolioAddress": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "coin_type": { + "type": "string" + }, + "description": { + "type": "string" + }, + "balance": { + "type": "number", + "format": "double" + } + } + }, + "gctrpcRemoveEventRequest": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "int64" + } + } + }, + "gctrpcRemoveEventResponse": { + "type": "object" + }, + "gctrpcRemovePortfolioAddressRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "coin_type": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "gctrpcRemovePortfolioAddressResponse": { + "type": "object" + }, + "gctrpcSubmitOrderRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + }, + "side": { + "type": "string" + }, + "order_type": { + "type": "string" + }, + "amount": { + "type": "number", + "format": "double" + }, + "price": { + "type": "number", + "format": "double" + }, + "client_id": { + "type": "string" + } + } + }, + "gctrpcSubmitOrderResponse": { + "type": "object", + "properties": { + "order_placed": { + "type": "boolean", + "format": "boolean" + }, + "order_id": { + "type": "string" + } + } + }, + "gctrpcTickerResponse": { + "type": "object", + "properties": { + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + }, + "last_updated": { + "type": "string", + "format": "int64" + }, + "currency_pair": { + "type": "string" + }, + "last": { + "type": "number", + "format": "double" + }, + "high": { + "type": "number", + "format": "double" + }, + "low": { + "type": "number", + "format": "double" + }, + "bid": { + "type": "number", + "format": "double" + }, + "ask": { + "type": "number", + "format": "double" + }, + "volume": { + "type": "number", + "format": "double" + }, + "price_ath": { + "type": "number", + "format": "double" + } + } + }, + "gctrpcTickers": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "tickers": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcTickerResponse" + } + } + } + }, + "gctrpcWithdrawCurrencyRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "description": { + "type": "string" + }, + "one_time_password": { + "type": "string" + }, + "account_id": { + "type": "string" + }, + "pin": { + "type": "string", + "format": "int64" + }, + "trade_password": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "address": { + "type": "string" + }, + "address_tag": { + "type": "string" + }, + "amount": { + "type": "number", + "format": "double" + }, + "fee_amount": { + "type": "number", + "format": "double" + }, + "bank_name": { + "type": "string" + }, + "bank_address": { + "type": "string" + }, + "bank_city": { + "type": "string" + }, + "bank_country": { + "type": "string" + }, + "swife_code": { + "type": "string" + }, + "wire_currency": { + "type": "string" + } + } + }, + "gctrpcWithdrawResponse": { + "type": "object", + "properties": { + "result": { + "type": "string" + } + } + } + } +} diff --git a/go.mod b/go.mod index edd6a53b..81f659b2 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,17 @@ module github.com/thrasher-/gocryptotrader go 1.12 require ( + github.com/boombuler/barcode v1.0.0 // indirect + github.com/golang/protobuf v1.3.1 github.com/google/go-querystring v1.0.0 github.com/gorilla/mux v1.7.2 github.com/gorilla/websocket v1.4.0 + github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 + github.com/grpc-ecosystem/grpc-gateway v1.9.0 + github.com/pquerna/otp v1.1.0 + github.com/urfave/cli v1.20.0 golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f + golang.org/x/net v0.0.0-20190520210107-018c4d40a106 + google.golang.org/genproto v0.0.0-20190516172635-bb713bdc0e52 + google.golang.org/grpc v1.20.1 ) diff --git a/go.sum b/go.sum index 1d5e8821..ed94e9f4 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,68 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pquerna/otp v1.1.0 h1:q2gMsMuMl3JzneUaAX1MRGxLvOG6bzXV51hivBaStf0= +github.com/pquerna/otp v1.1.0/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190520210107-018c4d40a106 h1:EZofHp/BzEf3j39/+7CX1JvH0WaPG+ikBrqAdAPf+GM= +golang.org/x/net v0.0.0-20190520210107-018c4d40a106/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190516172635-bb713bdc0e52 h1:LHc/6x2dMeCKkSsrVgo4DY+Z566T1OeoMwLtdfoy8LE= +google.golang.org/genproto v0.0.0-20190516172635-bb713bdc0e52/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/helpers.go b/helpers.go deleted file mode 100644 index e980a0fc..00000000 --- a/helpers.go +++ /dev/null @@ -1,422 +0,0 @@ -package main - -import ( - "errors" - "fmt" - - "github.com/thrasher-/gocryptotrader/currency" - exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" - log "github.com/thrasher-/gocryptotrader/logger" - "github.com/thrasher-/gocryptotrader/portfolio" -) - -// GetAllAvailablePairs returns a list of all available pairs on either enabled -// or disabled exchanges -func GetAllAvailablePairs(enabledExchangesOnly bool) currency.Pairs { - var pairList currency.Pairs - for x := range bot.config.Exchanges { - if enabledExchangesOnly && !bot.config.Exchanges[x].Enabled { - continue - } - - exchName := bot.config.Exchanges[x].Name - pairs, err := bot.config.GetAvailablePairs(exchName) - if err != nil { - continue - } - - for y := range pairs { - if pairList.Contains(pairs[y], false) { - continue - } - pairList = append(pairList, pairs[y]) - } - } - return pairList -} - -// GetSpecificAvailablePairs returns a list of supported pairs based on specific -// parameters -func GetSpecificAvailablePairs(enabledExchangesOnly, fiatPairs, includeUSDT, cryptoPairs bool) currency.Pairs { - var pairList currency.Pairs - supportedPairs := GetAllAvailablePairs(enabledExchangesOnly) - - for x := range supportedPairs { - if fiatPairs { - if supportedPairs[x].IsCryptoFiatPair() && - !supportedPairs[x].ContainsCurrency(currency.USDT) || - (includeUSDT && - supportedPairs[x].ContainsCurrency(currency.USDT) && - supportedPairs[x].IsCryptoPair()) { - if pairList.Contains(supportedPairs[x], false) { - continue - } - pairList = append(pairList, supportedPairs[x]) - } - } - if cryptoPairs { - if supportedPairs[x].IsCryptoPair() { - if pairList.Contains(supportedPairs[x], false) { - continue - } - pairList = append(pairList, supportedPairs[x]) - } - } - } - return pairList -} - -// IsRelatablePairs checks to see if the two pairs are relatable -func IsRelatablePairs(p1, p2 currency.Pair, includeUSDT bool) bool { - if p1.EqualIncludeReciprocal(p2) { - return true - } - - var relatablePairs = GetRelatableCurrencies(p1, true, includeUSDT) - if p1.IsCryptoFiatPair() { - for x := range relatablePairs { - relatablePairs = append(relatablePairs, - GetRelatableFiatCurrencies(relatablePairs[x])...) - } - } - return relatablePairs.Contains(p2, false) -} - -// MapCurrenciesByExchange returns a list of currency pairs mapped to an -// exchange -func MapCurrenciesByExchange(p []currency.Pair, enabledExchangesOnly bool) map[string]currency.Pairs { - currencyExchange := make(map[string]currency.Pairs) - for x := range p { - for y := range bot.config.Exchanges { - if enabledExchangesOnly && !bot.config.Exchanges[y].Enabled { - continue - } - exchName := bot.config.Exchanges[y].Name - success, err := bot.config.SupportsPair(exchName, p[x]) - if err != nil || !success { - continue - } - - result, ok := currencyExchange[exchName] - if !ok { - var pairs []currency.Pair - pairs = append(pairs, p[x]) - currencyExchange[exchName] = pairs - } else { - if result.Contains(p[x], false) { - continue - } - result = append(result, p[x]) - currencyExchange[exchName] = result - } - } - } - return currencyExchange -} - -// GetExchangeNamesByCurrency returns a list of exchanges supporting -// a currency pair based on whether the exchange is enabled or not -func GetExchangeNamesByCurrency(p currency.Pair, enabled bool) []string { - var exchanges []string - for x := range bot.config.Exchanges { - if enabled != bot.config.Exchanges[x].Enabled { - continue - } - - exchName := bot.config.Exchanges[x].Name - success, err := bot.config.SupportsPair(exchName, p) - if err != nil { - continue - } - - if success { - exchanges = append(exchanges, exchName) - } - } - return exchanges -} - -// GetRelatableCryptocurrencies returns a list of currency pairs if it can find -// any relatable currencies (e.g ETHBTC -> ETHLTC -> ETHUSDT -> ETHREP) -func GetRelatableCryptocurrencies(p currency.Pair) currency.Pairs { - var pairs currency.Pairs - cryptocurrencies := currency.GetCryptocurrencies() - - for x := range cryptocurrencies { - newPair := currency.NewPair(p.Base, cryptocurrencies[x]) - if newPair.IsInvalid() { - continue - } - - if newPair.Base.Upper() == p.Base.Upper() && - newPair.Quote.Upper() == p.Quote.Upper() { - continue - } - - if pairs.Contains(newPair, false) { - continue - } - pairs = append(pairs, newPair) - } - return pairs -} - -// GetRelatableFiatCurrencies returns a list of currency pairs if it can find -// any relatable currencies (e.g ETHUSD -> ETHAUD -> ETHGBP -> ETHJPY) -func GetRelatableFiatCurrencies(p currency.Pair) currency.Pairs { - var pairs currency.Pairs - fiatCurrencies := currency.GetFiatCurrencies() - - for x := range fiatCurrencies { - newPair := currency.NewPair(p.Base, fiatCurrencies[x]) - if newPair.Base.Upper() == newPair.Quote.Upper() { - continue - } - - if newPair.Base.Upper() == p.Base.Upper() && - newPair.Quote.Upper() == p.Quote.Upper() { - continue - } - - if pairs.Contains(newPair, false) { - continue - } - pairs = append(pairs, newPair) - } - return pairs -} - -// GetRelatableCurrencies returns a list of currency pairs if it can find -// any relatable currencies (e.g BTCUSD -> BTC USDT -> XBT USDT -> XBT USD) -// incOrig includes the supplied pair if desired -func GetRelatableCurrencies(p currency.Pair, incOrig, incUSDT bool) currency.Pairs { - var pairs currency.Pairs - - addPair := func(p currency.Pair) { - if pairs.Contains(p, true) { - return - } - pairs = append(pairs, p) - } - - buildPairs := func(p currency.Pair, incOrig bool) { - if incOrig { - addPair(p) - } - - first, ok := currency.GetTranslation(p.Base) - if ok { - addPair(currency.NewPair(first, p.Quote)) - - var second currency.Code - second, ok = currency.GetTranslation(p.Quote) - if ok { - addPair(currency.NewPair(first, second)) - } - } - - second, ok := currency.GetTranslation(p.Quote) - if ok { - addPair(currency.NewPair(p.Base, second)) - } - } - - buildPairs(p, incOrig) - buildPairs(p.Swap(), incOrig) - - if !incUSDT { - pairs = pairs.RemovePairsByFilter(currency.USDT) - } - - return pairs -} - -// GetSpecificOrderbook returns a specific orderbook given the currency, -// exchangeName and assetType -func GetSpecificOrderbook(currencyPair, exchangeName, assetType string) (orderbook.Base, error) { - var specificOrderbook orderbook.Base - var err error - for x := range bot.exchanges { - if bot.exchanges[x] != nil { - if bot.exchanges[x].GetName() == exchangeName { - specificOrderbook, err = bot.exchanges[x].GetOrderbookEx( - currency.NewPairFromString(currencyPair), - assetType, - ) - break - } - } - } - return specificOrderbook, err -} - -// GetSpecificTicker returns a specific ticker given the currency, -// exchangeName and assetType -func GetSpecificTicker(currencyPair, exchangeName, assetType string) (ticker.Price, error) { - var specificTicker ticker.Price - var err error - for x := range bot.exchanges { - if bot.exchanges[x] != nil { - if bot.exchanges[x].GetName() == exchangeName { - specificTicker, err = bot.exchanges[x].GetTickerPrice( - currency.NewPairFromString(currencyPair), - assetType, - ) - break - } - } - } - return specificTicker, err -} - -// GetCollatedExchangeAccountInfoByCoin collates individual exchange account -// information and turns into into a map string of -// exchange.AccountCurrencyInfo -func GetCollatedExchangeAccountInfoByCoin(exchAccounts []exchange.AccountInfo) map[currency.Code]exchange.AccountCurrencyInfo { - result := make(map[currency.Code]exchange.AccountCurrencyInfo) - for _, accounts := range exchAccounts { - for _, account := range accounts.Accounts { - for _, accountCurrencyInfo := range account.Currencies { - currencyName := accountCurrencyInfo.CurrencyName - avail := accountCurrencyInfo.TotalValue - onHold := accountCurrencyInfo.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].Exchange == 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 currency.Pair, assetType string) (string, error) { - result := stats.SortExchangesByPrice(p, assetType, true) - if len(result) == 0 { - 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 currency.Pair, assetType string) (string, error) { - result := stats.SortExchangesByPrice(p, assetType, false) - if len(result) == 0 { - return "", fmt.Errorf("no stats for supplied currency pair and asset type") - } - - return result[0].Exchange, nil -} - -// SeedExchangeAccountInfo seeds account info -func SeedExchangeAccountInfo(data []exchange.AccountInfo) { - if len(data) == 0 { - return - } - - port := portfolio.GetPortfolio() - - for _, exchangeData := range data { - exchangeName := exchangeData.Exchange - - var currencies []exchange.AccountCurrencyInfo - for _, account := range exchangeData.Accounts { - for _, info := range account.Currencies { - - var update bool - for i := range currencies { - if info.CurrencyName == currencies[i].CurrencyName { - currencies[i].Hold += info.Hold - currencies[i].TotalValue += info.TotalValue - update = true - } - } - - if update { - continue - } - - currencies = append(currencies, exchange.AccountCurrencyInfo{ - CurrencyName: info.CurrencyName, - TotalValue: info.TotalValue, - Hold: info.Hold, - }) - } - } - - for _, total := range currencies { - currencyName := total.CurrencyName - total := total.TotalValue - - if !port.ExchangeAddressExists(exchangeName, currencyName) { - if total <= 0 { - continue - } - - log.Debugf("Portfolio: Adding new exchange address: %s, %s, %f, %s\n", - exchangeName, - currencyName, - total, - portfolio.PortfolioAddressExchange) - - port.Addresses = append( - port.Addresses, - portfolio.Address{Address: exchangeName, - CoinType: currencyName, - Balance: total, - Description: portfolio.PortfolioAddressExchange}) - - } else { - if total <= 0 { - log.Debugf("Portfolio: Removing %s %s entry.\n", - exchangeName, - currencyName) - - port.RemoveExchangeAddress(exchangeName, currencyName) - } else { - balance, ok := port.GetAddressBalance(exchangeName, - portfolio.PortfolioAddressExchange, - currencyName) - - if !ok { - continue - } - - if balance != total { - log.Debugf("Portfolio: Updating %s %s entry with balance %f.\n", - exchangeName, - currencyName, - total) - - port.UpdateExchangeAddressBalance(exchangeName, - currencyName, - total) - } - } - } - } - } -} diff --git a/main.go b/main.go index 8b43d399..a30aa448 100644 --- a/main.go +++ b/main.go @@ -3,282 +3,91 @@ package main import ( "flag" "fmt" - "net/http" "os" - "os/signal" "runtime" - "strconv" - "sync" - "syscall" "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/communications" "github.com/thrasher-/gocryptotrader/config" - "github.com/thrasher-/gocryptotrader/connchecker" - "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/currency/coinmarketcap" - exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/core" + "github.com/thrasher-/gocryptotrader/engine" + "github.com/thrasher-/gocryptotrader/exchanges/request" log "github.com/thrasher-/gocryptotrader/logger" - "github.com/thrasher-/gocryptotrader/ntpclient" - "github.com/thrasher-/gocryptotrader/portfolio" ) -// Bot contains configuration, portfolio, exchange & ticker data and is the -// overarching type across this code base. -type Bot struct { - config *config.Config - portfolio *portfolio.Base - exchanges []exchange.IBotExchange - comms *communications.Communications - shutdown chan bool - dryRun bool - configFile string - dataDir string - connectivity *connchecker.Checker - sync.Mutex -} - -const banner = ` - ______ ______ __ ______ __ - / ____/____ / ____/_____ __ __ ____ / /_ ____ /_ __/_____ ______ ____/ /___ _____ - / / __ / __ \ / / / ___// / / // __ \ / __// __ \ / / / ___// __ // __ // _ \ / ___/ -/ /_/ // /_/ // /___ / / / /_/ // /_/ // /_ / /_/ // / / / / /_/ // /_/ // __// / -\____/ \____/ \____//_/ \__, // .___/ \__/ \____//_/ /_/ \__,_/ \__,_/ \___//_/ - /____//_/ -` - -var bot Bot - func main() { - bot.shutdown = make(chan bool) - HandleInterrupt() - defaultPath, err := config.GetFilePath("") if err != nil { log.Fatal(err) } // Handle flags - flag.StringVar(&bot.configFile, "config", defaultPath, "config file to load") - flag.StringVar(&bot.dataDir, "datadir", common.GetDefaultDataDir(runtime.GOOS), "default data directory for GoCryptoTrader files") - dryrun := flag.Bool("dryrun", false, "dry runs bot, doesn't save config file") - version := flag.Bool("version", false, "retrieves current GoCryptoTrader version") - verbosity := flag.Bool("verbose", false, "increases logging verbosity for GoCryptoTrader") + var settings engine.Settings + versionFlag := flag.Bool("version", false, "retrieves current GoCryptoTrader version") - Coinmarketcap := flag.Bool("c", false, "overrides config and runs currency analaysis") - FxCurrencyConverter := flag.Bool("fxa", false, "overrides config and sets up foreign exchange Currency Converter") - FxCurrencyLayer := flag.Bool("fxb", false, "overrides config and sets up foreign exchange Currency Layer") - FxFixer := flag.Bool("fxc", false, "overrides config and sets up foreign exchange Fixer.io") - FxOpenExchangeRates := flag.Bool("fxd", false, "overrides config and sets up foreign exchange Open Exchange Rates") + // Core settings + flag.StringVar(&settings.ConfigFile, "config", defaultPath, "config file to load") + flag.StringVar(&settings.DataDir, "datadir", common.GetDefaultDataDir(runtime.GOOS), "default data directory for GoCryptoTrader files") + flag.IntVar(&settings.GoMaxProcs, "gomaxprocs", runtime.NumCPU(), "sets the runtime GOMAXPROCS value") + flag.BoolVar(&settings.EnableDryRun, "dryrun", false, "dry runs bot, doesn't save config file") + flag.BoolVar(&settings.EnableAllExchanges, "enableallexchanges", false, "enables all exchanges") + flag.BoolVar(&settings.EnableAllPairs, "enableallpairs", false, "enables all pairs for enabled exchanges") + flag.BoolVar(&settings.EnablePortfolioWatcher, "portfoliowatcher", true, "enables the portfolio watcher") + flag.BoolVar(&settings.EnableGRPC, "grpc", true, "enables the grpc server") + flag.BoolVar(&settings.EnableGRPCProxy, "grpcproxy", true, "enables the grpc proxy server") + flag.BoolVar(&settings.EnableWebsocketRPC, "websocketrpc", true, "enables the websocket RPC server") + flag.BoolVar(&settings.EnableDeprecatedRPC, "deprecatedrpc", true, "enables the deprecated RPC server") + flag.BoolVar(&settings.EnableCommsRelayer, "enablecommsrelayer", true, "enables available communications relayer") + flag.BoolVar(&settings.Verbose, "verbose", false, "increases logging verbosity for GoCryptoTrader") + flag.BoolVar(&settings.EnableTickerRoutine, "tickerroutine", true, "enables the ticker routine for all loaded exchanges") + flag.BoolVar(&settings.EnableOrderbookRoutine, "orderbookroutine", true, "enables the orderbook routine for all loaded exchanges") + flag.BoolVar(&settings.EnableWebsocketRoutine, "websocketroutine", true, "enables the websocket routine for all loaded exchanges") + flag.BoolVar(&settings.EnableCoinmarketcapAnalysis, "coinmarketcap", false, "overrides config and runs currency analysis") + flag.BoolVar(&settings.EnableEventManager, "enableeventmanager", true, "enables the event manager") + flag.DurationVar(&settings.EventManagerDelay, "eventmanagerdelay", time.Duration(0), "sets the event managers sleep delay between event checking") + flag.BoolVar(&settings.EnableNTPClient, "ntpclient", true, "enables the NTP client to check system clock drift") + + // Forex provider settings + flag.BoolVar(&settings.EnableCurrencyConverter, "currencyconverter", false, "overrides config and sets up foreign exchange Currency Converter") + flag.BoolVar(&settings.EnableCurrencyLayer, "currencylayer", false, "overrides config and sets up foreign exchange Currency Layer") + flag.BoolVar(&settings.EnableFixer, "fixer", false, "overrides config and sets up foreign exchange Fixer.io") + flag.BoolVar(&settings.EnableOpenExchangeRates, "openexchangerates", false, "overrides config and sets up foreign exchange Open Exchange Rates") + + // Exchange tuning settings + flag.BoolVar(&settings.EnableExchangeAutoPairUpdates, "exchangeautopairupdates", true, "enables automatic available currency pair updates for supported exchanges") + flag.BoolVar(&settings.DisableExchangeAutoPairUpdates, "exchangedisableautopairupdates", false, "disables exchange auto pair updates") + flag.BoolVar(&settings.EnableExchangeWebsocketSupport, "exchangewebsocketsupport", true, "enables Websocket support for exchanges") + flag.BoolVar(&settings.EnableExchangeRESTSupport, "exchangerestsupport", true, "enables REST support for exchanges") + flag.BoolVar(&settings.EnableExchangeVerbose, "exchangeverbose", false, "increases exchange logging verbosity") + flag.BoolVar(&settings.ExchangePurgeCredentials, "exchangepurgecredentials", false, "purges the stored exchange API credentials") + flag.BoolVar(&settings.EnableExchangeHTTPRateLimiter, "ratelimiter", true, "enables the rate limiter for HTTP requests") + flag.IntVar(&settings.MaxHTTPRequestJobsLimit, "maxhttprequestjobslimit", request.DefaultMaxRequestJobs, "sets the max amount of jobs the HTTP request package stores") + flag.IntVar(&settings.RequestTimeoutRetryAttempts, "exchangehttptimeoutretryattempts", request.DefaultTimeoutRetryAttempts, "sets the amount of retry attempts after a HTTP request times out") + flag.DurationVar(&settings.ExchangeHTTPTimeout, "exchangehttptimeout", time.Duration(0), "sets the exchangs HTTP timeout value for HTTP requests") + flag.StringVar(&settings.ExchangeHTTPUserAgent, "exchangehttpuseragent", "", "sets the exchanges HTTP user agent") + flag.StringVar(&settings.ExchangeHTTPProxy, "exchangehttpproxy", "", "sets the exchanges HTTP proxy server") + flag.BoolVar(&settings.EnableExchangeHTTPDebugging, "exchangehttpdebugging", false, "sets the exchanges HTTP debugging") + + // Common tuning settings + flag.DurationVar(&settings.GlobalHTTPTimeout, "globalhttptimeout", time.Duration(0), "sets common HTTP timeout value for HTTP requests") + flag.StringVar(&settings.GlobalHTTPUserAgent, "globalhttpuseragent", "", "sets the common HTTP client's user agent") + flag.StringVar(&settings.GlobalHTTPProxy, "globalhttpproxy", "", "sets the common HTTP client's proxy server") flag.Parse() - if *version { - fmt.Print(BuildVersion(true)) + if *versionFlag { + fmt.Print(core.Version(true)) os.Exit(0) } - if *dryrun { - bot.dryRun = true + fmt.Println(core.Banner) + fmt.Println(core.Version(false)) + + engine.Bot, err = engine.NewFromSettings(&settings) + if engine.Bot == nil || err != nil { + log.Fatalf("Unable to initialise bot engine. Err: %s", err) } - fmt.Println(banner) - fmt.Println(BuildVersion(false)) - - bot.config = &config.Cfg - log.Debugf("Loading config file %s..\n", bot.configFile) - err = bot.config.LoadConfig(bot.configFile) - if err != nil { - log.Fatalf("Failed to load config. Err: %s", err) - } - - err = common.CreateDir(bot.dataDir) - if err != nil { - log.Fatalf("Failed to open/create data directory: %s. Err: %s", bot.dataDir, err) - } - log.Debugf("Using data directory: %s.\n", bot.dataDir) - - err = bot.config.CheckLoggerConfig() - if err != nil { - log.Errorf("Failed to configure logger reason: %s", err) - } - - err = log.SetupLogger() - if err != nil { - log.Errorf("Failed to setup logger reason: %s", err) - } - - if bot.config.NTPClient.Level != -1 { - bot.config.CheckNTPConfig() - NTPTime, errNTP := ntpclient.NTPClient(bot.config.NTPClient.Pool) - currentTime := time.Now() - if errNTP != nil { - log.Warnf("NTPClient failed to create: %v", errNTP) - } else { - NTPcurrentTimeDifference := NTPTime.Sub(currentTime) - configNTPTime := *bot.config.NTPClient.AllowedDifference - configNTPNegativeTime := (*bot.config.NTPClient.AllowedNegativeDifference - (*bot.config.NTPClient.AllowedNegativeDifference * 2)) - if NTPcurrentTimeDifference > configNTPTime || NTPcurrentTimeDifference < configNTPNegativeTime { - log.Warnf("Time out of sync (NTP): %v | (time.Now()): %v | (Difference): %v | (Allowed): +%v / %v", NTPTime, currentTime, NTPcurrentTimeDifference, configNTPTime, configNTPNegativeTime) - if bot.config.NTPClient.Level == 0 { - disable, errNTP := bot.config.DisableNTPCheck(os.Stdin) - if errNTP != nil { - log.Errorf("failed to disable ntp time check reason: %v", errNTP) - } else { - log.Info(disable) - } - } - } - } - } - - // Sets up internet connectivity monitor - bot.connectivity, err = connchecker.New(bot.config.ConnectionMonitor.DNSList, - bot.config.ConnectionMonitor.PublicDomainList, - bot.config.ConnectionMonitor.CheckInterval) - if err != nil { - log.Fatalf("Connectivity checker failure: %s", err) - } - - AdjustGoMaxProcs() - log.Debugf("Bot '%s' started.\n", bot.config.Name) - log.Debugf("Bot dry run mode: %v.\n", common.IsEnabled(bot.dryRun)) - - log.Debugf("Available Exchanges: %d. Enabled Exchanges: %d.\n", - len(bot.config.Exchanges), - bot.config.CountEnabledExchanges()) - - common.HTTPClient = common.NewHTTPClientWithTimeout(bot.config.GlobalHTTPTimeout) - log.Debugf("Global HTTP request timeout: %v.\n", common.HTTPClient.Timeout) - - SetupExchanges() - if len(bot.exchanges) == 0 { - log.Fatalf("No exchanges were able to be loaded. Exiting") - } - - log.Debugf("Starting communication mediums..") - cfg := bot.config.GetCommunicationsConfig() - bot.comms = communications.NewComm(&cfg) - bot.comms.GetEnabledCommunicationMediums() - - var newFxSettings []currency.FXSettings - for _, d := range bot.config.Currency.ForexProviders { - newFxSettings = append(newFxSettings, currency.FXSettings(d)) - } - - err = currency.RunStorageUpdater(currency.BotOverrides{ - Coinmarketcap: *Coinmarketcap, - FxCurrencyConverter: *FxCurrencyConverter, - FxCurrencyLayer: *FxCurrencyLayer, - FxFixer: *FxFixer, - FxOpenExchangeRates: *FxOpenExchangeRates, - }, - ¤cy.MainConfiguration{ - ForexProviders: newFxSettings, - CryptocurrencyProvider: coinmarketcap.Settings(bot.config.Currency.CryptocurrencyProvider), - Cryptocurrencies: bot.config.Currency.Cryptocurrencies, - FiatDisplayCurrency: bot.config.Currency.FiatDisplayCurrency, - CurrencyDelay: bot.config.Currency.CurrencyFileUpdateDuration, - FxRateDelay: bot.config.Currency.ForeignExchangeUpdateDuration, - }, - bot.dataDir, - *verbosity) - if err != nil { - log.Fatalf("currency updater system failed to start %v", err) - - } - - bot.portfolio = &portfolio.Portfolio - bot.portfolio.SeedPortfolio(bot.config.Portfolio) - SeedExchangeAccountInfo(GetAllEnabledExchangeAccountInfo().Data) - - if bot.config.Webserver.Enabled { - listenAddr := bot.config.Webserver.ListenAddress - log.Debugf( - "HTTP Webserver support enabled. Listen URL: http://%s:%d/\n", - common.ExtractHost(listenAddr), common.ExtractPort(listenAddr), - ) - - router := NewRouter() - go func() { - err = http.ListenAndServe(listenAddr, router) - if err != nil { - log.Fatal(err) - } - }() - - log.Debugln("HTTP Webserver started successfully.") - log.Debugln("Starting websocket handler.") - StartWebsocketHandler() - } else { - log.Debugln("HTTP RESTful Webserver support disabled.") - } - - go portfolio.StartPortfolioWatcher() - - go TickerUpdaterRoutine() - go OrderbookUpdaterRoutine() - go WebsocketRoutine(*verbosity) - - <-bot.shutdown - Shutdown() -} - -// AdjustGoMaxProcs adjusts the maximum processes that the CPU can handle. -func AdjustGoMaxProcs() { - log.Debugln("Adjusting bot runtime performance..") - maxProcsEnv := os.Getenv("GOMAXPROCS") - maxProcs := runtime.NumCPU() - log.Debugln("Number of CPU's detected:", maxProcs) - - if maxProcsEnv != "" { - log.Debugln("GOMAXPROCS env =", maxProcsEnv) - env, err := strconv.Atoi(maxProcsEnv) - if err != nil { - log.Debugf("Unable to convert GOMAXPROCS to int, using %d", maxProcs) - } else { - maxProcs = env - } - } - if i := runtime.GOMAXPROCS(maxProcs); i != maxProcs { - log.Error("Go Max Procs were not set correctly.") - } - log.Debugln("Set GOMAXPROCS to:", maxProcs) -} - -// HandleInterrupt monitors and captures the SIGTERM in a new goroutine then -// shuts down bot -func HandleInterrupt() { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - sig := <-c - log.Debugf("Captured %v, shutdown requested.", sig) - close(bot.shutdown) - }() -} - -// Shutdown correctly shuts down bot saving configuration files -func Shutdown() { - log.Debugln("Bot shutting down..") - - if len(portfolio.Portfolio.Addresses) != 0 { - bot.config.Portfolio = portfolio.Portfolio - } - - if !bot.dryRun { - err := bot.config.SaveConfig(bot.configFile) - - if err != nil { - log.Warn("Unable to save config.") - } else { - log.Debugln("Config file saved successfully.") - } - } - - log.Debugln("Exiting.") - - log.CloseLogFile() - os.Exit(0) + engine.PrintSettings(&engine.Bot.Settings) + engine.Bot.Start() } diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index 0903998d..24f9456b 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -388,9 +388,9 @@ func (p *Base) GetPortfolioGroupedCoin() map[currency.Code][]string { return result } -// SeedPortfolio appends a portfolio base object with another base portfolio +// Seed appends a portfolio base object with another base portfolio // addresses -func (p *Base) SeedPortfolio(port Base) { +func (p *Base) Seed(port Base) { p.Addresses = port.Addresses } diff --git a/portfolio/portfolio_test.go b/portfolio/portfolio_test.go index 125595ab..225010ba 100644 --- a/portfolio/portfolio_test.go +++ b/portfolio/portfolio_test.go @@ -177,7 +177,7 @@ func TestUpdateExchangeAddressBalance(t *testing.T) { newbase := Base{} newbase.AddExchangeAddress("someaddress", currency.LTC, 0.02) portfolio := GetPortfolio() - portfolio.SeedPortfolio(newbase) + portfolio.Seed(newbase) portfolio.UpdateExchangeAddressBalance("someaddress", currency.LTC, 0.04) value := portfolio.GetPortfolioSummary() @@ -194,7 +194,7 @@ func TestAddAddress(t *testing.T) { 0.02) portfolio := GetPortfolio() - portfolio.SeedPortfolio(newbase) + portfolio.Seed(newbase) if !portfolio.AddressExists("Gibson") { t.Error("Test Failed - portfolio_test.go - AddAddress error") } @@ -219,7 +219,7 @@ func TestUpdatePortfolio(t *testing.T) { 0.02) portfolio := GetPortfolio() - portfolio.SeedPortfolio(newbase) + portfolio.Seed(newbase) value := portfolio.UpdatePortfolio( []string{"LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL"}, currency.LTC, @@ -274,7 +274,7 @@ func TestGetPortfolioByExchange(t *testing.T) { newbase.AddExchangeAddress("Bitfinex", currency.LTC, 0.05) newbase.AddAddress("someaddress", "LTC", currency.NewCode(PortfolioAddressPersonal), 0.03) portfolio := GetPortfolio() - portfolio.SeedPortfolio(newbase) + portfolio.Seed(newbase) value := portfolio.GetPortfolioByExchange("ANX") result, ok := value[currency.LTC] if !ok { @@ -302,7 +302,7 @@ func TestGetExchangePortfolio(t *testing.T) { newbase.AddAddress("Bitfinex", PortfolioAddressExchange, currency.LTC, 0.05) newbase.AddAddress("someaddress", PortfolioAddressPersonal, currency.LTC, 0.03) portfolio := GetPortfolio() - portfolio.SeedPortfolio(newbase) + portfolio.Seed(newbase) value := portfolio.GetExchangePortfolio() result, ok := value[currency.LTC] @@ -321,7 +321,7 @@ func TestGetPersonalPortfolio(t *testing.T) { newbase.AddAddress("anotheraddress", PortfolioAddressPersonal, currency.N2O, 0.03) newbase.AddAddress("Exchange", PortfolioAddressExchange, currency.N2O, 0.01) portfolio := GetPortfolio() - portfolio.SeedPortfolio(newbase) + portfolio.Seed(newbase) value := portfolio.GetPersonalPortfolio() result, ok := value[currency.N2O] if !ok { @@ -350,7 +350,7 @@ func TestGetPortfolioSummary(t *testing.T) { newbase.AddExchangeAddress("ANX", currency.ETH, 42) portfolio := GetPortfolio() - portfolio.SeedPortfolio(newbase) + portfolio.Seed(newbase) value := portfolio.GetPortfolioSummary() getTotalsVal := func(c currency.Code) Coin { @@ -384,21 +384,21 @@ func TestGetPortfolioGroupedCoin(t *testing.T) { newbase.AddAddress("someaddress", currency.LTC.String(), currency.LTC, 0.02) newbase.AddAddress("Exchange", PortfolioAddressExchange, currency.LTC, 0.05) portfolio := GetPortfolio() - portfolio.SeedPortfolio(newbase) + portfolio.Seed(newbase) value := portfolio.GetPortfolioGroupedCoin() if value[currency.LTC][0] != "someaddress" && len(value[currency.LTC][0]) != 1 { t.Error("Test Failed - portfolio_test.go - GetPortfolioGroupedCoin error") } } -func TestSeedPortfolio(t *testing.T) { +func TestSeed(t *testing.T) { newbase := Base{} newbase.AddAddress("someaddress", currency.LTC.String(), currency.LTC, 0.02) portfolio := GetPortfolio() - portfolio.SeedPortfolio(newbase) + portfolio.Seed(newbase) if !portfolio.AddressExists("someaddress") { - t.Error("Test Failed - portfolio_test.go - SeedPortfolio error") + t.Error("Test Failed - portfolio_test.go - Seed error") } } @@ -415,7 +415,7 @@ func TestStartPortfolioWatcher(t *testing.T) { 0.02) portfolio := GetPortfolio() - portfolio.SeedPortfolio(newBase) + portfolio.Seed(newBase) if !portfolio.AddressExists("LX2LMYXtuv5tiYEMztSSoEZcafFPYJFRK1") { t.Error("Test Failed - portfolio_test.go - TestStartPortfolioWatcher") diff --git a/portfolio/portfolio_types.go b/portfolio/portfolio_types.go index 40123724..4d76161b 100644 --- a/portfolio/portfolio_types.go +++ b/portfolio/portfolio_types.go @@ -4,7 +4,7 @@ import "github.com/thrasher-/gocryptotrader/currency" // Base holds the portfolio base addresses type Base struct { - Addresses []Address + Addresses []Address `json:"addresses"` } // Address sub type holding address information for portfolio diff --git a/restful_router.go b/restful_router.go deleted file mode 100644 index 91e27799..00000000 --- a/restful_router.go +++ /dev/null @@ -1,141 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "strconv" - "time" - - "github.com/gorilla/mux" - "github.com/thrasher-/gocryptotrader/common" - log "github.com/thrasher-/gocryptotrader/logger" - - _ "net/http/pprof" -) - -// 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.Debugf( - "%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() *mux.Router { - router := mux.NewRouter().StrictSlash(true) - var listenAddr string - - if common.ExtractPort(bot.config.Webserver.ListenAddress) == 80 { - listenAddr = common.ExtractHost(bot.config.Webserver.ListenAddress) - } else { - listenAddr = common.JoinStrings([]string{common.ExtractHost(bot.config.Webserver.ListenAddress), - strconv.Itoa(common.ExtractPort(bot.config.Webserver.ListenAddress))}, ":") - } - - routes = Routes{ - Route{ - "", - http.MethodGet, - "/", - getIndex, - }, - Route{ - "GetAllSettings", - http.MethodGet, - "/config/all", - RESTGetAllSettings, - }, - Route{ - "SaveAllSettings", - http.MethodPost, - "/config/all/save", - RESTSaveAllSettings, - }, - Route{ - "AllEnabledAccountInfo", - http.MethodGet, - "/exchanges/enabled/accounts/all", - RESTGetAllEnabledAccountInfo, - }, - Route{ - "AllActiveExchangesAndCurrencies", - http.MethodGet, - "/exchanges/enabled/latest/all", - RESTGetAllActiveTickers, - }, - Route{ - "IndividualExchangeAndCurrency", - http.MethodGet, - "/exchanges/{exchangeName}/latest/{currency}", - RESTGetTicker, - }, - Route{ - "GetPortfolio", - http.MethodGet, - "/portfolio/all", - RESTGetPortfolio, - }, - Route{ - "AllActiveExchangesAndOrderbooks", - http.MethodGet, - "/exchanges/orderbook/latest/all", - RESTGetAllActiveOrderbooks, - }, - Route{ - "IndividualExchangeOrderbook", - http.MethodGet, - "/exchanges/{exchangeName}/orderbook/latest/{currency}", - RESTGetOrderbook, - }, - Route{ - "ws", - http.MethodGet, - "/ws", - WebsocketClientHandler, - }, - } - - for _, route := range routes { - router. - Methods(route.Method). - Path(route.Pattern). - Name(route.Name). - Handler(RESTLogger(route.HandlerFunc, route.Name)). - Host(listenAddr) - } - - if bot.config.Profiler.Enabled { - log.Debugln("Profiler enabled") - router.PathPrefix("/debug").Handler(http.DefaultServeMux) - } - - return router -} - -func getIndex(w http.ResponseWriter, _ *http.Request) { - fmt.Fprint(w, "GoCryptoTrader RESTful interface. For the web GUI, please visit the web GUI readme.") -} diff --git a/restful_server.go b/restful_server.go deleted file mode 100644 index bf21e703..00000000 --- a/restful_server.go +++ /dev/null @@ -1,299 +0,0 @@ -package main - -import ( - "encoding/json" - "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" - log "github.com/thrasher-/gocryptotrader/logger" -) - -// 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 response interface -func RESTfulJSONResponse(w http.ResponseWriter, response interface{}) error { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - return json.NewEncoder(w).Encode(response) -} - -// RESTfulError prints the REST method and error -func RESTfulError(method string, err error) { - log.Errorf("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, 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, bot.config) - if err != nil { - RESTfulError(r.Method, err) - } - - SetupExchanges() -} - -// 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"] - exchangeName := vars["exchangeName"] - assetType := vars["assetType"] - - if assetType == "" { - assetType = orderbook.Spot - } - - response, err := GetSpecificOrderbook(currency, exchangeName, assetType) - if err != nil { - log.Errorf("Failed to fetch orderbook for %s currency: %s\n", exchangeName, - currency) - return - } - - err = RESTfulJSONResponse(w, 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() { - continue - } - - var individualExchange EnabledExchangeOrderbooks - exchangeName := individualBot.GetName() - individualExchange.ExchangeName = exchangeName - currencies := individualBot.GetEnabledCurrencies() - assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName) - if err != nil { - log.Errorf("failed to get %s exchange asset types. Error: %s", - exchangeName, err) - continue - } - for _, x := range currencies { - pair := x - - var ob orderbook.Base - if len(assetTypes) > 1 { - for y := range assetTypes { - ob, err = individualBot.GetOrderbookEx(pair, - assetTypes[y]) - } - } else { - ob, err = individualBot.GetOrderbookEx(pair, - assetTypes[0]) - } - - if err != nil { - log.Errorf("failed to get %s %s orderbook. Error: %s", - pair, - 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, 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, 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"] - exchangeName := vars["exchangeName"] - assetType := vars["assetType"] - - if assetType == "" { - assetType = ticker.Spot - } - response, err := GetSpecificTicker(currency, exchangeName, assetType) - if err != nil { - log.Errorf("Failed to fetch ticker for %s currency: %s\n", exchangeName, - currency) - return - } - err = RESTfulJSONResponse(w, 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() { - continue - } - - var individualExchange EnabledExchangeCurrencies - exchangeName := individualBot.GetName() - individualExchange.ExchangeName = exchangeName - currencies := individualBot.GetEnabledCurrencies() - for _, x := range currencies { - pair := x - assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName) - if err != nil { - log.Errorf("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(pair, - assetTypes[y]) - } - } else { - tickerPrice, err = individualBot.GetTickerPrice(pair, - assetTypes[0]) - } - - if err != nil { - log.Errorf("failed to get %s %s ticker. Error: %s", - pair, - 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, 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.Warnf("GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.", individualBot.GetName()) - continue - } - individualExchange, err := individualBot.GetAccountInfo() - if err != nil { - log.Errorf("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, response) - if err != nil { - RESTfulError(r.Method, err) - } -} diff --git a/testdata/configtest.json b/testdata/configtest.json index 0a749a05..50f8bf74 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -125,7 +125,7 @@ } }, "portfolioAddresses": { - "Addresses": [ + "addresses": [ { "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", "CoinType": "BTC", @@ -153,13 +153,13 @@ ] }, "webserver": { - "enabled": false, + "enabled": true, "adminUsername": "admin", "adminPassword": "Password", "listenAddress": ":9050", "websocketConnectionLimit": 1, "websocketMaxAuthFailures": 3, - "websocketAllowInsecureOrigin": false + "websocketAllowInsecureOrigin": true }, "exchanges": [ { @@ -1300,7 +1300,7 @@ "websocket": false, "useSandbox": false, "restPollingDelay": 10, - "httpTimeout": 10, + "httpTimeout": 15000000000, "httpUserAgent": "", "httpDebugging": false, "authenticatedApiSupport": false, diff --git a/tools/README.md b/tools/README.md deleted file mode 100644 index 1a719198..00000000 --- a/tools/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# GoCryptoTrader package Tools - - - - -[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader) -[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader/) -[![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader) - - -This tools package is part of the GoCryptoTrader codebase. - -## This is still in active development - -You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). - -Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY) - -## Current Features - -This folder contains an assortment of tools. - -+ Configuration -+ Documentation creation -+ Portfolio monitoring -+ Exchange deployment -+ Websocket client - -Please see individual tool's README file - -## Contribution - -Please feel free to submit any pull requests or suggest any desired features to be added. - -When submitting a PR, please abide by our coding guidelines: - -+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). -+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. -+ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md). -+ Pull requests need to be based on and opened against the `master` branch. - -## Donations - - - -If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: - -***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 00000000..5aa49b7d --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,36 @@ +package utils + +import ( + "errors" + "runtime" + + "github.com/thrasher-/gocryptotrader/common" +) + +const ( + defaultTLSDir = "tls" +) + +// Util vars +var ( + ErrGoMaxProcsFailure = errors.New("failed to set GOMAXPROCS") +) + +// AdjustGoMaxProcs sets the runtime GOMAXPROCS val +func AdjustGoMaxProcs(maxProcs int) error { + n := runtime.NumCPU() + if maxProcs < 0 || maxProcs > n { + maxProcs = n + } + + if i := runtime.GOMAXPROCS(maxProcs); i != maxProcs { + return ErrGoMaxProcsFailure + } + + return nil +} + +// GetTLSDir returns the default TLS dir +func GetTLSDir(dir string) string { + return dir + common.GetOSPathSlash() + defaultTLSDir +} diff --git a/utils/utils_test.go b/utils/utils_test.go new file mode 100644 index 00000000..12189173 --- /dev/null +++ b/utils/utils_test.go @@ -0,0 +1,22 @@ +package utils + +import ( + "runtime" + "testing" +) + +func TestAdjustGoMaxProcs(t *testing.T) { + // Ensure that a supplied crazy number is set to a valid one and doesn't + // return an error + err := AdjustGoMaxProcs(1000) + if err != nil { + t.Fatalf("TestAdjustGoMaxProcs returned err: %v", err) + } + + // This time use the num of logical CPU's and ensure it doesn't + // return an error + err = AdjustGoMaxProcs(runtime.NumCPU()) + if err != nil { + t.Fatalf("TestAdjustGoMaxProcs returned err: %v", err) + } +} diff --git a/web/src/app/pages/settings/settings.component.html b/web/src/app/pages/settings/settings.component.html index e61f9d24..5dd47adb 100644 --- a/web/src/app/pages/settings/settings.component.html +++ b/web/src/app/pages/settings/settings.component.html @@ -226,13 +226,13 @@ Enabled
- + - + - +

From 8c62316e8255bea39bd034c37aea116720477448 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 4 Jun 2019 14:34:00 +1000 Subject: [PATCH 02/71] websocket QA --- exchanges/bitmex/bitmex_websocket.go | 2 +- exchanges/bitmex/bitmex_wrapper.go | 4 +- exchanges/btse/btse_websocket.go | 13 ++- exchanges/coinut/coinut_websocket.go | 4 +- exchanges/huobihadax/huobihadax_types.go | 4 +- exchanges/okex/okex_wrapper.go | 6 +- exchanges/okgroup/okgroup_wrapper.go | 2 +- exchanges/poloniex/poloniex_types.go | 1 + exchanges/poloniex/poloniex_websocket.go | 141 +++-------------------- 9 files changed, 38 insertions(+), 139 deletions(-) diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 6527b38b..33ad3182 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -388,7 +388,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (b *Bitmex) GenerateDefaultSubscriptions() { - contracts := b.GetEnabledPairs(assets.AssetTypeSpot) + contracts := b.GetEnabledPairs(assets.AssetTypePerpetualContract) channels := []string{bitmexWSOrderbookL2, bitmexWSTrade} subscriptions := []exchange.WebsocketChannelSubscription{ { diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 440cce33..a066d301 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -75,11 +75,11 @@ func (b *Bitmex) SetDefaults() { // Upside and Downside profit contracts use the same format fmt2 := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ - Delimiter: "-", + Delimiter: "_", Uppercase: true, }, ConfigFormat: ¤cy.PairFormat{ - Delimiter: "-", + Delimiter: "_", Uppercase: true, }, } diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index 0308ee07..245cb2f2 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -91,7 +91,7 @@ func (b *BTSE) WsHandleData() { ProductID string `json:"product_id"` } - if strings.Contains(string(resp.Raw), "connect success") { + if strings.Contains(string(resp.Raw), "Welcome to BTSE") { if b.Verbose { log.Debugf("%s websocket client successfully connected to %s", b.Name, b.Websocket.GetWebsocketURL()) @@ -121,11 +121,12 @@ func (b *BTSE) WsHandleData() { } b.Websocket.DataHandler <- exchange.TickerData{ - Timestamp: time.Now(), - Pair: currency.NewPairDelimiter(t.ProductID, "-"), - AssetType: assets.AssetTypeSpot, - Exchange: b.GetName(), - OpenPrice: price, + Timestamp: time.Now(), + Pair: currency.NewPairDelimiter(t.ProductID, "-"), + AssetType: assets.AssetTypeSpot, + Exchange: b.GetName(), + ClosePrice: price, + Quantity: t.LastSize, } case "snapshot": snapshot := websocketOrderbookSnapshot{} diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index 42079b88..a70481eb 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -78,8 +78,10 @@ func (c *COINUT) WsHandleData() { continue } + currencyPair := instrumentListByCode[ticker.InstID] c.Websocket.DataHandler <- exchange.TickerData{ Timestamp: time.Unix(0, ticker.Timestamp), + Pair: currency.NewPairFromString(currencyPair), Exchange: c.GetName(), AssetType: assets.AssetTypeSpot, HighPrice: ticker.HighestBuy, @@ -225,7 +227,7 @@ func (c *COINUT) GetNonce() int64 { func (c *COINUT) WsSetInstrumentList() error { err := c.wsSend(wsRequest{ Request: "inst_list", - SecType: "spot", + SecType: "SPOT", Nonce: c.GetNonce(), }) if err != nil { diff --git a/exchanges/huobihadax/huobihadax_types.go b/exchanges/huobihadax/huobihadax_types.go index c533c317..34efc9e6 100644 --- a/exchanges/huobihadax/huobihadax_types.go +++ b/exchanges/huobihadax/huobihadax_types.go @@ -313,9 +313,9 @@ type WsTrade struct { ID int64 `json:"id"` Timestamp int64 `json:"ts"` Data []struct { - Amount float64 `json:"amount"` + ID float64 `json:"id"` Timestamp int64 `json:"ts"` - ID float64 `json:"id,string"` + Amount float64 `json:"amount"` Price float64 `json:"price"` Direction string `json:"direction"` } `json:"data"` diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go index 475e5ed7..24105113 100644 --- a/exchanges/okex/okex_wrapper.go +++ b/exchanges/okex/okex_wrapper.go @@ -113,7 +113,7 @@ func (o *OKEX) Start(wg *sync.WaitGroup) { // Run implements the OKEX wrapper func (o *OKEX) Run() { if o.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) + log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.API.Endpoints.WebsocketURL) } if !o.GetEnabledFeatures().AutoPairUpdates { @@ -153,14 +153,14 @@ func (o *OKEX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { return pairs, nil case assets.AssetTypePerpetualSwap: - prods, err := o.GetAllSwapTokensInformation() + prods, err := o.GetSwapContractInformation() if err != nil { return nil, err } var pairs []string for x := range prods { - pairs = append(pairs, prods[x].InstrumentID) + pairs = append(pairs, prods[x].UnderlyingIndex+"_"+prods[x].QuoteCurrency+"_SWAP") } return pairs, nil case assets.AssetTypeIndex: diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index 5dfbce0a..f9f1e54a 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -37,7 +37,7 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) error { exch.Name, exch.Features.Enabled.Websocket, exch.Verbose, - o.WebsocketURL, + o.API.Endpoints.WebsocketURL, exch.API.Endpoints.WebsocketURL) } diff --git a/exchanges/poloniex/poloniex_types.go b/exchanges/poloniex/poloniex_types.go index c4faae61..9ecb7616 100644 --- a/exchanges/poloniex/poloniex_types.go +++ b/exchanges/poloniex/poloniex_types.go @@ -4,6 +4,7 @@ import "github.com/thrasher-/gocryptotrader/currency" // Ticker holds ticker data type Ticker struct { + ID int `json:"id"` Last float64 `json:"last,string"` LowestAsk float64 `json:"lowestAsk,string"` HighestBid float64 `json:"highestBid,string"` diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index 4060f78c..ed43d954 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -27,8 +27,8 @@ const ( ) var ( - // CurrencyIDMap stores a map of currencies associated with their ID - CurrencyIDMap map[string]int + // currencyIDMap stores a map of currencies associated with their ID + currencyIDMap map[int]string ) // WsConnect initiates a websocket connection @@ -54,15 +54,15 @@ func (p *Poloniex) WsConnect() error { return err } - if CurrencyIDMap == nil { - CurrencyIDMap = make(map[string]int) - resp, err := p.GetCurrencies() + if currencyIDMap == nil { + currencyIDMap = make(map[int]string) + resp, err := p.GetTicker() if err != nil { return err } for k, v := range resp { - CurrencyIDMap[k] = v.ID + currencyIDMap[v.ID] = k } } @@ -143,6 +143,8 @@ func (p *Poloniex) WsHandleData() { case wsTickerDataID: tickerData := data[2].([]interface{}) var t WsTicker + + currencyPair := currencyIDMap[int(tickerData[0].(float64))] t.LastPrice, _ = strconv.ParseFloat(tickerData[1].(string), 64) t.LowestAsk, _ = strconv.ParseFloat(tickerData[2].(string), 64) t.HighestBid, _ = strconv.ParseFloat(tickerData[3].(string), 64) @@ -158,11 +160,13 @@ func (p *Poloniex) WsHandleData() { t.LowestTradePrice24H, _ = strconv.ParseFloat(tickerData[9].(string), 64) p.Websocket.DataHandler <- exchange.TickerData{ - Timestamp: time.Now(), - Exchange: p.GetName(), - AssetType: assets.AssetTypeSpot, - LowPrice: t.LowestAsk, - HighPrice: t.HighestBid, + Timestamp: time.Now(), + Pair: currency.NewPairDelimiter(currencyPair, "_"), + Exchange: p.GetName(), + AssetType: assets.AssetTypeSpot, + ClosePrice: t.LastPrice, + LowPrice: t.LowestAsk, + HighPrice: t.HighestBid, } case ws24HourExchangeVolumeID: case wsHeartbeat: @@ -201,7 +205,7 @@ func (p *Poloniex) WsHandleData() { Pair: currency.NewPairFromString(currencyPair), } case "o": - currencyPair := CurrencyPairID[chanID] + currencyPair := currencyIDMap[chanID] err := p.WsProcessOrderbookUpdate(dataL3, currencyPair) if err != nil { p.Websocket.DataHandler <- err @@ -214,9 +218,9 @@ func (p *Poloniex) WsHandleData() { Pair: currency.NewPairFromString(currencyPair), } case "t": - currencyPair := CurrencyPairID[chanID] + currencyPair := currencyIDMap[chanID] var trade WsTrade - trade.Symbol = CurrencyPairID[chanID] + trade.Symbol = currencyIDMap[chanID] trade.TradeID, _ = strconv.ParseInt(dataL3[1].(string), 10, 64) // 1 for buy 0 for sell side := "buy" @@ -328,115 +332,6 @@ func (p *Poloniex) WsProcessOrderbookUpdate(target []interface{}, symbol string) assets.AssetTypeSpot) } -// CurrencyPairID contains a list of IDS for currency pairs. -var CurrencyPairID = map[int]string{ - 7: "BTC_BCN", - 14: "BTC_BTS", - 15: "BTC_BURST", - 20: "BTC_CLAM", - 25: "BTC_DGB", - 27: "BTC_DOGE", - 24: "BTC_DASH", - 38: "BTC_GAME", - 43: "BTC_HUC", - 50: "BTC_LTC", - 51: "BTC_MAID", - 58: "BTC_OMNI", - 61: "BTC_NAV", - 64: "BTC_NMC", - 69: "BTC_NXT", - 75: "BTC_PPC", - 89: "BTC_STR", - 92: "BTC_SYS", - 97: "BTC_VIA", - 100: "BTC_VTC", - 108: "BTC_XCP", - 114: "BTC_XMR", - 116: "BTC_XPM", - 117: "BTC_XRP", - 112: "BTC_XEM", - 148: "BTC_ETH", - 150: "BTC_SC", - 153: "BTC_EXP", - 155: "BTC_FCT", - 160: "BTC_AMP", - 162: "BTC_DCR", - 163: "BTC_LSK", - 167: "BTC_LBC", - 168: "BTC_STEEM", - 170: "BTC_SBD", - 171: "BTC_ETC", - 174: "BTC_REP", - 177: "BTC_ARDR", - 178: "BTC_ZEC", - 182: "BTC_STRAT", // nolint: misspell - 184: "BTC_PASC", - 185: "BTC_GNT", - 187: "BTC_GNO", - 189: "BTC_BCH", - 192: "BTC_ZRX", - 194: "BTC_CVC", - 196: "BTC_OMG", - 198: "BTC_GAS", - 200: "BTC_STORJ", - 201: "BTC_EOS", - 204: "BTC_SNT", - 207: "BTC_KNC", - 210: "BTC_BAT", - 213: "BTC_LOOM", - 221: "BTC_QTUM", - 121: "USDT_BTC", - 216: "USDT_DOGE", - 122: "USDT_DASH", - 123: "USDT_LTC", - 124: "USDT_NXT", - 125: "USDT_STR", - 126: "USDT_XMR", - 127: "USDT_XRP", - 149: "USDT_ETH", - 219: "USDT_SC", - 218: "USDT_LSK", - 173: "USDT_ETC", - 175: "USDT_REP", - 180: "USDT_ZEC", - 217: "USDT_GNT", - 191: "USDT_BCH", - 220: "USDT_ZRX", - 203: "USDT_EOS", - 206: "USDT_SNT", - 209: "USDT_KNC", - 212: "USDT_BAT", - 215: "USDT_LOOM", - 223: "USDT_QTUM", - 129: "XMR_BCN", - 132: "XMR_DASH", - 137: "XMR_LTC", - 138: "XMR_MAID", - 140: "XMR_NXT", - 181: "XMR_ZEC", - 166: "ETH_LSK", - 169: "ETH_STEEM", - 172: "ETH_ETC", - 176: "ETH_REP", - 179: "ETH_ZEC", - 186: "ETH_GNT", - 188: "ETH_GNO", - 190: "ETH_BCH", - 193: "ETH_ZRX", - 195: "ETH_CVC", - 197: "ETH_OMG", - 199: "ETH_GAS", - 202: "ETH_EOS", - 205: "ETH_SNT", - 208: "ETH_KNC", - 211: "ETH_BAT", - 214: "ETH_LOOM", - 222: "ETH_QTUM", - 224: "USDC_BTC", - 226: "USDC_USDT", - 225: "USDC_ETH", -} - // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (p *Poloniex) GenerateDefaultSubscriptions() { subscriptions := []exchange.WebsocketChannelSubscription{} From e965e54e096f892cf21f6d24c466c8fb5f600d6e Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 4 Jun 2019 17:04:18 +1000 Subject: [PATCH 03/71] Split up common.go, file path fixes and much more --- .../common_templates/common_readme.tmpl | 2 +- cmd/documentation/documentation.go | 37 ++-- cmd/exchange_template/exchange_template.go | 41 ++-- cmd/exchange_template/main_file.tmpl | 6 +- common/README.md | 2 +- common/common.go | 108 +-------- common/common_test.go | 205 +----------------- common/convert/convert.go | 55 +++++ common/convert/convert_test.go | 96 ++++++++ communications/base/base.go | 6 +- communications/slack/slack.go | 15 +- communications/smsglobal/smsglobal.go | 6 +- communications/smtpservice/smtpservice.go | 3 +- communications/telegram/telegram.go | 15 +- config/config.go | 31 ++- currency/code.go | 9 +- currency/code_test.go | 72 +++--- currency/conversion_test.go | 13 +- currency/currencies.go | 10 +- currency/currency_test.go | 8 +- currency/forexprovider/base/base_interface.go | 3 +- .../currencyconverterapi.go | 9 +- .../currencylayer/currencylayer.go | 7 +- .../exchangeratesapi.io/exchangeratesapi.go | 4 +- currency/forexprovider/fixer.io/fixer.go | 7 +- .../openexchangerates/openexchangerates.go | 9 +- currency/pairs.go | 5 +- currency/storage.go | 4 +- engine/events/events.go | 24 +- engine/exchange.go | 2 +- engine/restful_router.go | 3 +- engine/routines.go | 3 +- engine/rpcserver.go | 17 +- engine/websocket.go | 3 +- exchanges/alphapoint/alphapoint.go | 3 +- exchanges/assets/assets.go | 4 +- exchanges/binance/binance.go | 23 +- exchanges/bitfinex/bitfinex_wrapper.go | 2 +- exchanges/bitflyer/bitflyer_wrapper.go | 5 +- exchanges/bithumb/bithumb.go | 37 ++-- exchanges/bithumb/bithumb_wrapper.go | 3 +- exchanges/bitmex/bitmex_parameters.go | 3 +- exchanges/bitmex/bitmex_websocket.go | 5 +- exchanges/bitstamp/bitstamp.go | 16 +- exchanges/bitstamp/bitstamp_websocket.go | 9 +- exchanges/bitstamp/bitstamp_wrapper.go | 3 +- exchanges/bittrex/bittrex.go | 10 +- exchanges/btcmarkets/btcmarkets.go | 23 +- exchanges/exchange.go | 3 +- exchanges/exchange_types.go | 5 +- exchanges/exmo/exmo.go | 2 +- exchanges/gateio/gateio.go | 15 +- exchanges/gateio/gateio_websocket.go | 10 +- exchanges/gemini/gemini.go | 4 +- exchanges/hitbtc/hitbtc_wrapper.go | 4 +- exchanges/huobi/huobi_websocket.go | 13 +- exchanges/huobi/huobi_wrapper.go | 2 +- exchanges/huobihadax/huobihadax.go | 3 +- exchanges/huobihadax/huobihadax_websocket.go | 13 +- exchanges/huobihadax/huobihadax_wrapper.go | 2 +- exchanges/kraken/kraken.go | 5 +- exchanges/kraken/kraken_wrapper.go | 6 +- exchanges/lakebtc/lakebtc.go | 12 +- exchanges/lakebtc/lakebtc_wrapper.go | 2 +- exchanges/localbitcoins/localbitcoins.go | 2 +- exchanges/request/request.go | 3 +- exchanges/ticker/ticker.go | 4 +- exchanges/websocket_test.go | 4 +- exchanges/yobit/yobit_wrapper.go | 2 +- exchanges/zb/zb.go | 5 +- exchanges/zb/zb_websocket.go | 15 +- exchanges/zb/zb_wrapper.go | 5 +- portfolio/portfolio.go | 9 +- utils/utils.go | 5 +- 74 files changed, 524 insertions(+), 617 deletions(-) create mode 100644 common/convert/convert.go create mode 100644 common/convert/convert_test.go diff --git a/cmd/documentation/common_templates/common_readme.tmpl b/cmd/documentation/common_templates/common_readme.tmpl index fb3f828e..71db2823 100644 --- a/cmd/documentation/common_templates/common_readme.tmpl +++ b/cmd/documentation/common_templates/common_readme.tmpl @@ -11,7 +11,7 @@ import "github.com/thrasher-/gocryptotrader/common" testString := "aAaAa" -upper := common.StringToUpper(testString) +upper := strings.ToUpper(testString) // upper == "AAAAA" ``` diff --git a/cmd/documentation/documentation.go b/cmd/documentation/documentation.go index d484ea13..b5d348d3 100644 --- a/cmd/documentation/documentation.go +++ b/cmd/documentation/documentation.go @@ -6,6 +6,7 @@ import ( "html/template" "log" "os" + "runtime" "strings" "time" @@ -118,7 +119,7 @@ func main() { codebasePaths = make(map[string]string) codebaseTemplatePath = make(map[string]string) codebaseReadme = make(map[string]readme) - path = common.GetOSPathSlash() + path = getOSPathSlash() if err := getContributorList(); err != nil { log.Fatal("GoCryptoTrader: Exchange documentation tool GET error ", err) @@ -139,6 +140,16 @@ func main() { fmt.Println("\nTool finished") } +// getOSPathSlash returns the slash used by the operating systems +// file system +// TO-DO: Change all paths to not use this +func getOSPathSlash() string { + if runtime.GOOS == "windows" { + return "\\" + } + return "/" +} + // updateReadme iterates through codebase paths to check for readme files and either adds // or replaces with new readme files. func updateReadme() error { @@ -295,18 +306,18 @@ func getslashFromName(packageName string) string { } var globS = []string{ - fmt.Sprintf("common_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("communications_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("config_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("currency_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("events_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("exchanges_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("portfolio_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("root_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("sub_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("testdata_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("tools_templates%s*", common.GetOSPathSlash()), - fmt.Sprintf("web_templates%s*", common.GetOSPathSlash()), + fmt.Sprintf("common_templates%s*", getOSPathSlash()), + fmt.Sprintf("communications_templates%s*", getOSPathSlash()), + fmt.Sprintf("config_templates%s*", getOSPathSlash()), + fmt.Sprintf("currency_templates%s*", getOSPathSlash()), + fmt.Sprintf("events_templates%s*", getOSPathSlash()), + fmt.Sprintf("exchanges_templates%s*", getOSPathSlash()), + fmt.Sprintf("portfolio_templates%s*", getOSPathSlash()), + fmt.Sprintf("root_templates%s*", getOSPathSlash()), + fmt.Sprintf("sub_templates%s*", getOSPathSlash()), + fmt.Sprintf("testdata_templates%s*", getOSPathSlash()), + fmt.Sprintf("tools_templates%s*", getOSPathSlash()), + fmt.Sprintf("web_templates%s*", getOSPathSlash()), } // addTemplates adds all the template files diff --git a/cmd/exchange_template/exchange_template.go b/cmd/exchange_template/exchange_template.go index bce03308..d7e44f7f 100644 --- a/cmd/exchange_template/exchange_template.go +++ b/cmd/exchange_template/exchange_template.go @@ -7,12 +7,13 @@ import ( "log" "os" "os/exec" - - "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "path/filepath" + "strings" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" ) const ( @@ -22,9 +23,9 @@ const ( packageMain = "%s.go" packageReadme = "README.md" - exchangePackageLocation = "..%s..%sexchanges%s" - exchangeLocation = "..%s..%sexchange.go" - exchangeConfigPath = "..%s..%stestdata%sconfigtest.json" + exchangePackageLocation = "../../exchanges" + exchangeLocation = "../../exchange.go" + exchangeConfigPath = "../../testdata/configtest.json" ) var ( @@ -84,9 +85,9 @@ func main() { log.Fatal("GoCryptoTrader: Exchange templating tool stopped...") } - newExchangeName = common.StringToLower(newExchangeName) + newExchangeName = strings.ToLower(newExchangeName) v := newExchangeName[:1] - capName := common.StringToUpper(v) + newExchangeName[1:] + capName := strings.ToUpper(v) + newExchangeName[1:] exch := exchange{ Name: newExchangeName, @@ -97,18 +98,14 @@ func main() { FIX: fixSupport, } - osPathSlash := common.GetOSPathSlash() - exchangeJSON := fmt.Sprintf(exchangeConfigPath, osPathSlash, osPathSlash, osPathSlash) - configTestFile := config.GetConfig() - err = configTestFile.LoadConfig(exchangeJSON) + err = configTestFile.LoadConfig(exchangeConfigPath) if err != nil { log.Fatal("GoCryptoTrader: Exchange templating configuration retrieval error ", err) } // NOTE need to nullify encrypt configuration var configTestExchanges []string - for x := range configTestFile.Exchanges { configTestExchanges = append(configTestExchanges, configTestFile.Exchanges[x].Name) } @@ -137,18 +134,12 @@ func main() { log.Fatal("GoCryptoTrader: Exchange templating configuration error - cannot save") } - exchangeDirectory = fmt.Sprintf( - exchangePackageLocation+newExchangeName+"%s", - osPathSlash, - osPathSlash, - osPathSlash, - osPathSlash) - - exchangeTest = fmt.Sprintf(exchangeDirectory+packageTests, newExchangeName) - exchangeTypes = fmt.Sprintf(exchangeDirectory+packageTypes, newExchangeName) - exchangeWrapper = fmt.Sprintf(exchangeDirectory+packageWrapper, newExchangeName) - exchangeMain = fmt.Sprintf(exchangeDirectory+packageMain, newExchangeName) - exchangeReadme = exchangeDirectory + packageReadme + exchangeDirectory = filepath.Join(exchangePackageLocation, newExchangeName) + exchangeTest = filepath.Join(exchangeDirectory, fmt.Sprintf(packageTests, newExchangeName)) + exchangeTypes = filepath.Join(exchangeDirectory, fmt.Sprintf(packageTypes, newExchangeName)) + exchangeWrapper = filepath.Join(exchangeDirectory, fmt.Sprintf(packageWrapper, newExchangeName)) + exchangeMain = filepath.Join(exchangeDirectory, fmt.Sprintf(packageMain, newExchangeName)) + exchangeReadme = filepath.Join(exchangeDirectory, packageReadme) err = os.Mkdir(exchangeDirectory, 0700) if err != nil { diff --git a/cmd/exchange_template/main_file.tmpl b/cmd/exchange_template/main_file.tmpl index 024711dc..242b633c 100644 --- a/cmd/exchange_template/main_file.tmpl +++ b/cmd/exchange_template/main_file.tmpl @@ -60,9 +60,9 @@ func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) error {{.Variable}}.SetHTTPClientUserAgent(exch.HTTPUserAgent) {{.Variable}}.Verbose = exch.Verbose {{.Variable}}.Websocket.SetWsStatusAndConnection(exch.Features.Enabled.Websocket) - {{.Variable}}.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") - {{.Variable}}.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") - {{.Variable}}.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + {{.Variable}}.BaseCurrencies = strings.Split(exch.BaseCurrencies, ",") + {{.Variable}}.AvailablePairs = strings.Split(exch.AvailablePairs, ",") + {{.Variable}}.EnabledPairs = strings.Split(exch.EnabledPairs, ",") err := {{.Variable}}.SetCurrencyPairFormat() if err != nil { log.Fatal(err) diff --git a/common/README.md b/common/README.md index 3720ba1c..70004ef8 100644 --- a/common/README.md +++ b/common/README.md @@ -29,7 +29,7 @@ import "github.com/thrasher-/gocryptotrader/common" testString := "aAaAa" -upper := common.StringToUpper(testString) +upper := strings.ToUpper(testString) // upper == "AAAAA" ``` diff --git a/common/common.go b/common/common.go index 426317d6..f2af18db 100644 --- a/common/common.go +++ b/common/common.go @@ -14,7 +14,6 @@ import ( "path/filepath" "reflect" "regexp" - "runtime" "strconv" "strings" "time" @@ -81,12 +80,6 @@ func StringSliceDifference(slice1, slice2 []string) []string { return diff } -// StringContains checks a substring if it contains your input then returns a -// bool -func StringContains(input, substring string) bool { - return strings.Contains(input, substring) -} - // StringDataContains checks the substring array with an input and returns a bool func StringDataContains(haystack []string, needle string) bool { data := strings.Join(haystack, ",") @@ -118,45 +111,13 @@ func StringDataCompareInsensitive(haystack []string, needle string) bool { // a bool irrespective of lower or upper case strings func StringDataContainsInsensitive(haystack []string, needle string) bool { for _, data := range haystack { - if strings.Contains(StringToUpper(data), StringToUpper(needle)) { + if strings.Contains(strings.ToUpper(data), strings.ToUpper(needle)) { return true } } return false } -// JoinStrings joins an array together with the required separator and returns -// it as a string -func JoinStrings(input []string, separator string) string { - return strings.Join(input, separator) -} - -// SplitStrings splits blocks of strings from string into a string array using -// a separator ie "," or "_" -func SplitStrings(input, separator string) []string { - return strings.Split(input, separator) -} - -// TrimString trims unwanted prefixes or postfixes -func TrimString(input, cutset string) string { - return strings.Trim(input, cutset) -} - -// ReplaceString replaces a string with another -func ReplaceString(input, old, newStr string, n int) string { - return strings.Replace(input, old, newStr, n) -} - -// StringToUpper changes strings to uppercase -func StringToUpper(input string) string { - return strings.ToUpper(input) -} - -// StringToLower changes strings to lowercase -func StringToLower(input string) string { - return strings.ToLower(input) -} - // IsEnabled takes in a boolean param and returns a string if it is enabled // or disabled func IsEnabled(isEnabled bool) string { @@ -170,7 +131,7 @@ func IsEnabled(isEnabled bool) string { // regexp package // Validation issues occurring because "3" is contained in // litecoin and Bitcoin addresses - non-fatal func IsValidCryptoAddress(address, crypto string) (bool, error) { - switch StringToLower(crypto) { + switch strings.ToLower(crypto) { case "btc": return regexp.MatchString("^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$", address) case "ltc": @@ -184,7 +145,7 @@ func IsValidCryptoAddress(address, crypto string) (bool, error) { // YesOrNo returns a boolean variable to check if input is "y" or "yes" func YesOrNo(input string) bool { - if StringToLower(input) == "y" || StringToLower(input) == "yes" { + if strings.ToLower(input) == "y" || strings.ToLower(input) == "yes" { return true } return false @@ -276,7 +237,7 @@ 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(), "*") { + if !strings.Contains(reflect.ValueOf(to).Type().String(), "*") { return errors.New("json decode error - memory address not supplied") } return json.Unmarshal(data, to) @@ -294,7 +255,7 @@ func EncodeURLValues(urlPath string, values url.Values) string { // ExtractHost returns the hostname out of a string func ExtractHost(address string) string { - host := SplitStrings(address, ":")[0] + host := strings.Split(address, ":")[0] if host == "" { return "localhost" } @@ -303,7 +264,7 @@ func ExtractHost(address string) string { // ExtractPort returns the port name out of a string func ExtractPort(host string) int { - portStr := SplitStrings(host, ":")[1] + portStr := strings.Split(host, ":")[1] port, _ := strconv.Atoi(portStr) return port } @@ -386,15 +347,6 @@ func GetExecutablePath() (string, error) { return filepath.Dir(ex), nil } -// GetOSPathSlash returns the slash used by the operating systems -// file system -func GetOSPathSlash() string { - if runtime.GOOS == "windows" { - return "\\" - } - return "/" -} - // UnixMillis converts a UnixNano timestamp to milliseconds func UnixMillis(t time.Time) int64 { return t.UnixNano() / int64(time.Millisecond) @@ -405,54 +357,6 @@ func RecvWindow(d time.Duration) int64 { return int64(d) / int64(time.Millisecond) } -// FloatFromString format -func FloatFromString(raw interface{}) (float64, error) { - str, ok := raw.(string) - if !ok { - return 0, fmt.Errorf("unable to parse, value not string: %T", raw) - } - flt, err := strconv.ParseFloat(str, 64) - if err != nil { - return 0, fmt.Errorf("could not convert value: %s Error: %s", str, err) - } - return flt, nil -} - -// IntFromString format -func IntFromString(raw interface{}) (int, error) { - str, ok := raw.(string) - if !ok { - return 0, fmt.Errorf("unable to parse, value not string: %T", raw) - } - n, err := strconv.Atoi(str) - if err != nil { - return 0, fmt.Errorf("unable to parse as int: %T", raw) - } - return n, nil -} - -// Int64FromString format -func Int64FromString(raw interface{}) (int64, error) { - str, ok := raw.(string) - if !ok { - return 0, fmt.Errorf("unable to parse, value not string: %T", raw) - } - n, err := strconv.ParseInt(str, 10, 64) - if err != nil { - return 0, fmt.Errorf("unable to parse as int64: %T", raw) - } - return n, nil -} - -// TimeFromUnixTimestampFloat format -func TimeFromUnixTimestampFloat(raw interface{}) (time.Time, error) { - ts, ok := raw.(float64) - if !ok { - return time.Time{}, fmt.Errorf("unable to parse, value not float64: %T", raw) - } - return time.Unix(0, int64(ts)*int64(time.Millisecond)), nil -} - // GetDefaultDataDir returns the default data directory // Windows - C:\Users\%USER%\AppData\Roaming\GoCryptoTrader // Linux/Unix or OSX - $HOME/.gocryptotrader diff --git a/common/common_test.go b/common/common_test.go index 16d73227..97dc144c 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -73,28 +73,6 @@ func TestIsValidCryptoAddress(t *testing.T) { } } -func TestStringToLower(t *testing.T) { - t.Parallel() - upperCaseString := "HEY MAN" - expectedResult := "hey man" - actualResult := StringToLower(upperCaseString) - if actualResult != expectedResult { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", - expectedResult, actualResult) - } -} - -func TestStringToUpper(t *testing.T) { - t.Parallel() - upperCaseString := "hey man" - expectedResult := "HEY MAN" - actualResult := StringToUpper(upperCaseString) - if actualResult != expectedResult { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", - expectedResult, actualResult) - } -} - func TestStringSliceDifference(t *testing.T) { t.Parallel() originalInputOne := []string{"hello"} @@ -107,18 +85,6 @@ func TestStringSliceDifference(t *testing.T) { } } -func TestStringContains(t *testing.T) { - t.Parallel() - originalInput := "hello" - originalInputSubstring := "he" - expectedOutput := true - actualResult := StringContains(originalInput, originalInputSubstring) - if actualResult != expectedOutput { - t.Errorf("Test failed. Expected '%v'. Actual '%v'", - expectedOutput, actualResult) - } -} - func TestStringDataContains(t *testing.T) { t.Parallel() originalHaystack := []string{"hello", "world", "USDT", "Contains", "string"} @@ -196,64 +162,6 @@ func TestStringDataContainsUpper(t *testing.T) { } } -func TestJoinStrings(t *testing.T) { - t.Parallel() - originalInputOne := []string{"hello", "moto"} - separator := "," - expectedOutput := "hello,moto" - actualResult := JoinStrings(originalInputOne, separator) - if expectedOutput != actualResult { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", - expectedOutput, actualResult) - } -} - -func TestSplitStrings(t *testing.T) { - t.Parallel() - originalInputOne := "hello,moto" - separator := "," - expectedOutput := []string{"hello", "moto"} - actualResult := SplitStrings(originalInputOne, separator) - if !reflect.DeepEqual(expectedOutput, actualResult) { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", - expectedOutput, actualResult) - } -} - -func TestTrimString(t *testing.T) { - t.Parallel() - originalInput := "abcd" - cutset := "ad" - expectedOutput := "bc" - actualResult := TrimString(originalInput, cutset) - if expectedOutput != actualResult { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", - expectedOutput, actualResult) - } -} - -// TestReplaceString replaces a string with another -func TestReplaceString(t *testing.T) { - t.Parallel() - currency := "BTC-USD" - expectedOutput := "BTCUSD" - - actualResult := ReplaceString(currency, "-", "", -1) - if expectedOutput != actualResult { - t.Errorf( - "Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult, - ) - } - - currency = "BTC-USD--" - actualResult = ReplaceString(currency, "-", "", 3) - if expectedOutput != actualResult { - t.Errorf( - "Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult, - ) - } -} - func TestYesOrNo(t *testing.T) { t.Parallel() if !YesOrNo("y") { @@ -580,14 +488,6 @@ func TestGetExecutablePath(t *testing.T) { } } -func TestGetOSPathSlash(t *testing.T) { - output := GetOSPathSlash() - if output != "/" && output != "\\" { - t.Errorf("Test failed. Common GetOSPathSlash. Returned '%s'", output) - } - -} - func TestUnixMillis(t *testing.T) { t.Parallel() testTime := time.Date(2014, time.October, 28, 0, 32, 0, 0, time.UTC) @@ -612,96 +512,6 @@ func TestRecvWindow(t *testing.T) { } } -func TestFloatFromString(t *testing.T) { - t.Parallel() - testString := "1.41421356237" - expectedOutput := float64(1.41421356237) - - actualOutput, err := FloatFromString(testString) - if actualOutput != expectedOutput || err != nil { - t.Errorf("Test failed. Common FloatFromString. Expected '%v'. Actual '%v'. Error: %s", - expectedOutput, actualOutput, err) - } - - var testByte []byte - _, err = FloatFromString(testByte) - if err == nil { - t.Error("Test failed. Common FloatFromString. Converted non-string.") - } - - testString = " something unconvertible " - _, err = FloatFromString(testString) - if err == nil { - t.Error("Test failed. Common FloatFromString. Converted invalid syntax.") - } -} - -func TestIntFromString(t *testing.T) { - t.Parallel() - testString := "1337" - expectedOutput := 1337 - - actualOutput, err := IntFromString(testString) - if actualOutput != expectedOutput || err != nil { - t.Errorf("Test failed. Common IntFromString. Expected '%v'. Actual '%v'. Error: %s", - expectedOutput, actualOutput, err) - } - - var testByte []byte - _, err = IntFromString(testByte) - if err == nil { - t.Error("Test failed. Common IntFromString. Converted non-string.") - } - - testString = "1.41421356237" - _, err = IntFromString(testString) - if err == nil { - t.Error("Test failed. Common IntFromString. Converted invalid syntax.") - } -} - -func TestInt64FromString(t *testing.T) { - t.Parallel() - testString := "4398046511104" - expectedOutput := int64(1 << 42) - - actualOutput, err := Int64FromString(testString) - if actualOutput != expectedOutput || err != nil { - t.Errorf("Test failed. Common Int64FromString. Expected '%v'. Actual '%v'. Error: %s", - expectedOutput, actualOutput, err) - } - - var testByte []byte - _, err = Int64FromString(testByte) - if err == nil { - t.Error("Test failed. Common Int64FromString. Converted non-string.") - } - - testString = "1.41421356237" - _, err = Int64FromString(testString) - if err == nil { - t.Error("Test failed. Common Int64FromString. Converted invalid syntax.") - } -} - -func TestTimeFromUnixTimestampFloat(t *testing.T) { - t.Parallel() - testTimestamp := float64(1414456320000) - expectedOutput := time.Date(2014, time.October, 28, 0, 32, 0, 0, time.UTC) - - actualOutput, err := TimeFromUnixTimestampFloat(testTimestamp) - if actualOutput.UTC().String() != expectedOutput.UTC().String() || err != nil { - t.Errorf("Test failed. Common TimeFromUnixTimestampFloat. Expected '%v'. Actual '%v'. Error: %s", - expectedOutput, actualOutput, err) - } - - testString := "Time" - _, err = TimeFromUnixTimestampFloat(testString) - if err == nil { - t.Error("Test failed. Common TimeFromUnixTimestampFloat. Converted invalid syntax.") - } -} - func TestGetDefaultDataDir(t *testing.T) { switch runtime.GOOS { case "windows": @@ -758,7 +568,7 @@ func TestCreateDir(t *testing.T) { if !ok { t.Fatal("LookupEnv failed. APPDATA is not set") } - dir = dir + GetOSPathSlash() + "GoCryptoTrader\\TestFileASDFG" + dir = filepath.Join(dir, "GoCryptoTrader", "TestFileASDFG") err = CreateDir(dir) if err != nil { t.Fatalf("CreateDir failed. Err: %v", err) @@ -796,13 +606,14 @@ func TestCreateDir(t *testing.T) { } func TestChangePerm(t *testing.T) { + testDir := filepath.Join(GetDefaultDataDir(runtime.GOOS), "TestFileASDFGHJ") switch runtime.GOOS { case "windows": err := ChangePerm("*") if err == nil { t.Fatal("expected an error on non-existent path") } - err = os.Mkdir(GetDefaultDataDir(runtime.GOOS)+GetOSPathSlash()+"TestFileASDFGHJ", 0777) + err = os.Mkdir(testDir, 0777) if err != nil { t.Fatalf("Mkdir failed. Err: %v", err) } @@ -810,11 +621,11 @@ func TestChangePerm(t *testing.T) { if err != nil { t.Fatalf("ChangePerm was unsuccessful. Err: %v", err) } - _, err = os.Stat(GetDefaultDataDir(runtime.GOOS) + GetOSPathSlash() + "TestFileASDFGHJ") + _, err = os.Stat(testDir) if err != nil { t.Fatalf("os.Stat failed. Err: %v", err) } - err = RemoveFile(GetDefaultDataDir(runtime.GOOS) + GetOSPathSlash() + "TestFileASDFGHJ") + err = RemoveFile(testDir) if err != nil { t.Fatalf("RemoveFile failed. Err: %v", err) } @@ -823,7 +634,7 @@ func TestChangePerm(t *testing.T) { if err == nil { t.Fatal("expected an error on non-existent path") } - err = os.Mkdir(GetDefaultDataDir(runtime.GOOS)+GetOSPathSlash()+"TestFileASDFGHJ", 0777) + err = os.Mkdir(testDir, 0777) if err != nil { t.Fatalf("Mkdir failed. Err: %v", err) } @@ -832,14 +643,14 @@ func TestChangePerm(t *testing.T) { t.Fatalf("ChangePerm was unsuccessful. Err: %v", err) } var a os.FileInfo - a, err = os.Stat(GetDefaultDataDir(runtime.GOOS) + GetOSPathSlash() + "TestFileASDFGHJ") + a, err = os.Stat(testDir) if err != nil { t.Fatalf("os.Stat failed. Err: %v", err) } if a.Mode().Perm() != 0770 { t.Fatalf("expected file permissions differ. expecting 0770 got %#o", a.Mode().Perm()) } - err = RemoveFile(GetDefaultDataDir(runtime.GOOS) + GetOSPathSlash() + "TestFileASDFGHJ") + err = RemoveFile(testDir) if err != nil { t.Fatalf("RemoveFile failed. Err: %v", err) } diff --git a/common/convert/convert.go b/common/convert/convert.go new file mode 100644 index 00000000..27c6b1cf --- /dev/null +++ b/common/convert/convert.go @@ -0,0 +1,55 @@ +package convert + +import ( + "fmt" + "strconv" + "time" +) + +// FloatFromString format +func FloatFromString(raw interface{}) (float64, error) { + str, ok := raw.(string) + if !ok { + return 0, fmt.Errorf("unable to parse, value not string: %T", raw) + } + flt, err := strconv.ParseFloat(str, 64) + if err != nil { + return 0, fmt.Errorf("could not convert value: %s Error: %s", str, err) + } + return flt, nil +} + +// IntFromString format +func IntFromString(raw interface{}) (int, error) { + str, ok := raw.(string) + if !ok { + return 0, fmt.Errorf("unable to parse, value not string: %T", raw) + } + n, err := strconv.Atoi(str) + if err != nil { + return 0, fmt.Errorf("unable to parse as int: %T", raw) + } + return n, nil +} + +// Int64FromString format +func Int64FromString(raw interface{}) (int64, error) { + str, ok := raw.(string) + if !ok { + return 0, fmt.Errorf("unable to parse, value not string: %T", raw) + } + n, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0, fmt.Errorf("unable to parse as int64: %T", raw) + } + return n, nil +} + +// TimeFromUnixTimestampFloat format +func TimeFromUnixTimestampFloat(raw interface{}) (time.Time, error) { + ts, ok := raw.(float64) + if !ok { + return time.Time{}, fmt.Errorf("unable to parse, value not float64: %T", raw) + } + return time.Unix(0, int64(ts)*int64(time.Millisecond)), nil +} diff --git a/common/convert/convert_test.go b/common/convert/convert_test.go new file mode 100644 index 00000000..c3aef95a --- /dev/null +++ b/common/convert/convert_test.go @@ -0,0 +1,96 @@ +package convert + +import ( + "testing" + "time" +) + +func TestFloatFromString(t *testing.T) { + t.Parallel() + testString := "1.41421356237" + expectedOutput := float64(1.41421356237) + + actualOutput, err := FloatFromString(testString) + if actualOutput != expectedOutput || err != nil { + t.Errorf("Test failed. Common FloatFromString. Expected '%v'. Actual '%v'. Error: %s", + expectedOutput, actualOutput, err) + } + + var testByte []byte + _, err = FloatFromString(testByte) + if err == nil { + t.Error("Test failed. Common FloatFromString. Converted non-string.") + } + + testString = " something unconvertible " + _, err = FloatFromString(testString) + if err == nil { + t.Error("Test failed. Common FloatFromString. Converted invalid syntax.") + } +} + +func TestIntFromString(t *testing.T) { + t.Parallel() + testString := "1337" + expectedOutput := 1337 + + actualOutput, err := IntFromString(testString) + if actualOutput != expectedOutput || err != nil { + t.Errorf("Test failed. Common IntFromString. Expected '%v'. Actual '%v'. Error: %s", + expectedOutput, actualOutput, err) + } + + var testByte []byte + _, err = IntFromString(testByte) + if err == nil { + t.Error("Test failed. Common IntFromString. Converted non-string.") + } + + testString = "1.41421356237" + _, err = IntFromString(testString) + if err == nil { + t.Error("Test failed. Common IntFromString. Converted invalid syntax.") + } +} + +func TestInt64FromString(t *testing.T) { + t.Parallel() + testString := "4398046511104" + expectedOutput := int64(1 << 42) + + actualOutput, err := Int64FromString(testString) + if actualOutput != expectedOutput || err != nil { + t.Errorf("Test failed. Common Int64FromString. Expected '%v'. Actual '%v'. Error: %s", + expectedOutput, actualOutput, err) + } + + var testByte []byte + _, err = Int64FromString(testByte) + if err == nil { + t.Error("Test failed. Common Int64FromString. Converted non-string.") + } + + testString = "1.41421356237" + _, err = Int64FromString(testString) + if err == nil { + t.Error("Test failed. Common Int64FromString. Converted invalid syntax.") + } +} + +func TestTimeFromUnixTimestampFloat(t *testing.T) { + t.Parallel() + testTimestamp := float64(1414456320000) + expectedOutput := time.Date(2014, time.October, 28, 0, 32, 0, 0, time.UTC) + + actualOutput, err := TimeFromUnixTimestampFloat(testTimestamp) + if actualOutput.UTC().String() != expectedOutput.UTC().String() || err != nil { + t.Errorf("Test failed. Common TimeFromUnixTimestampFloat. Expected '%v'. Actual '%v'. Error: %s", + expectedOutput, actualOutput, err) + } + + testString := "Time" + _, err = TimeFromUnixTimestampFloat(testString) + if err == nil { + t.Error("Test failed. Common TimeFromUnixTimestampFloat. Converted invalid syntax.") + } +} diff --git a/communications/base/base.go b/communications/base/base.go index 5a6701fd..cd047aea 100644 --- a/communications/base/base.go +++ b/communications/base/base.go @@ -2,10 +2,10 @@ package base import ( "fmt" + "strings" "sync" "time" - "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -112,7 +112,7 @@ func (b *Base) GetTicker(exchangeName string) string { tickerPrices[i].PriceATH, tickerPrices[i].Volume)) } - return common.JoinStrings(packagedTickers, "\n") + return strings.Join(packagedTickers, "\n") } // GetOrderbook returns staged orderbook data @@ -142,7 +142,7 @@ func (b *Base) GetOrderbook(exchangeName string) string { orderbooks[i].TotalAsks, orderbooks[i].TotalBids)) } - return common.JoinStrings(packagedOrderbooks, "\n") + return strings.Join(packagedOrderbooks, "\n") } // GetPortfolio returns staged portfolio info diff --git a/communications/slack/slack.go b/communications/slack/slack.go index 3627455c..444ee9c6 100644 --- a/communications/slack/slack.go +++ b/communications/slack/slack.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "strings" "sync" "time" @@ -364,24 +365,24 @@ func (s *Slack) HandleMessage(msg *Message) error { return errors.New("msg is nil") } - msg.Text = common.StringToLower(msg.Text) + msg.Text = strings.ToLower(msg.Text) switch { - case common.StringContains(msg.Text, cmdStatus): + case strings.Contains(msg.Text, cmdStatus): return s.WebsocketSend("message", s.GetStatus()) - case common.StringContains(msg.Text, cmdHelp): + case strings.Contains(msg.Text, cmdHelp): return s.WebsocketSend("message", getHelp) - case common.StringContains(msg.Text, cmdTicker): + case strings.Contains(msg.Text, cmdTicker): return s.WebsocketSend("message", s.GetTicker("ANX")) - case common.StringContains(msg.Text, cmdOrderbook): + case strings.Contains(msg.Text, cmdOrderbook): return s.WebsocketSend("message", s.GetOrderbook("ANX")) - case common.StringContains(msg.Text, cmdSettings): + case strings.Contains(msg.Text, cmdSettings): return s.WebsocketSend("message", s.GetSettings()) - case common.StringContains(msg.Text, cmdPortfolio): + case strings.Contains(msg.Text, cmdPortfolio): return s.WebsocketSend("message", s.GetPortfolio()) default: diff --git a/communications/smsglobal/smsglobal.go b/communications/smsglobal/smsglobal.go index 2d23c44e..2157c663 100644 --- a/communications/smsglobal/smsglobal.go +++ b/communications/smsglobal/smsglobal.go @@ -89,7 +89,7 @@ func (s *SMSGlobal) GetContactByNumber(number string) (Contact, error) { // GetContactByName returns a contact with supplied name func (s *SMSGlobal) GetContactByName(name string) (Contact, error) { for x := range s.Contacts { - if common.StringToLower(s.Contacts[x].Name) == common.StringToLower(name) { + if strings.ToLower(s.Contacts[x].Name) == strings.ToLower(name) { return s.Contacts[x], nil } } @@ -113,7 +113,7 @@ func (s *SMSGlobal) AddContact(contact Contact) error { // ContactExists checks to see if a contact exists func (s *SMSGlobal) 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) { + if s.Contacts[x].Number == contact.Number && strings.ToLower(s.Contacts[x].Name) == strings.ToLower(contact.Name) { return true } } @@ -173,7 +173,7 @@ func (s *SMSGlobal) SendMessage(to, message string) error { return err } - if !common.StringContains(resp, "OK: 0; Sent queued message") { + if !strings.Contains(resp, "OK: 0; Sent queued message") { return errSMSNotSent } return nil diff --git a/communications/smtpservice/smtpservice.go b/communications/smtpservice/smtpservice.go index 79a46264..7a1fb05b 100644 --- a/communications/smtpservice/smtpservice.go +++ b/communications/smtpservice/smtpservice.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/smtp" + "strings" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/communications/base" @@ -56,7 +57,7 @@ func (s *SMTPservice) Send(subject, alert string) error { return errors.New("STMPservice Send() please add subject and alert") } - list := common.SplitStrings(s.RecipientList, ",") + list := strings.Split(s.RecipientList, ",") for i := range list { messageToSend := fmt.Sprintf( diff --git a/communications/telegram/telegram.go b/communications/telegram/telegram.go index 8fbdd83a..33f0d19c 100644 --- a/communications/telegram/telegram.go +++ b/communications/telegram/telegram.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "strings" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/communications/base" @@ -139,25 +140,25 @@ func (t *Telegram) InitialConnect() { // HandleMessages handles incoming message from the long polling routine func (t *Telegram) HandleMessages(text string, chatID int64) error { switch { - case common.StringContains(text, cmdHelp): + case strings.Contains(text, cmdHelp): return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, cmdHelpReply), chatID) - case common.StringContains(text, cmdStart): + case strings.Contains(text, cmdStart): return t.SendMessage(fmt.Sprintf("%s: START COMMANDS HERE", talkRoot), chatID) - case common.StringContains(text, cmdOrders): + case strings.Contains(text, cmdOrders): return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetOrderbook("ANX")), chatID) - case common.StringContains(text, cmdStatus): + case strings.Contains(text, cmdStatus): return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetStatus()), chatID) - case common.StringContains(text, cmdTicker): + case strings.Contains(text, cmdTicker): return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetTicker("ANX")), chatID) - case common.StringContains(text, cmdSettings): + case strings.Contains(text, cmdSettings): return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetSettings()), chatID) - case common.StringContains(text, cmdPortfolio): + case strings.Contains(text, cmdPortfolio): return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetPortfolio()), chatID) default: diff --git a/config/config.go b/config/config.go index dc6f7f23..5c7e60e8 100644 --- a/config/config.go +++ b/config/config.go @@ -47,7 +47,7 @@ const ( // Constants here hold some messages const ( ErrExchangeNameEmpty = "exchange #%d name is empty" - ErrExchangeAvailablePairsEmpty = "exchange %s avaiable pairs is empty" + ErrExchangeAvailablePairsEmpty = "exchange %s available pairs is empty" ErrExchangeEnabledPairsEmpty = "exchange %s enabled pairs is empty" ErrExchangeBaseCurrenciesEmpty = "exchange %s base currencies is empty" ErrExchangeNotFound = "exchange %s not found" @@ -95,7 +95,7 @@ func (c *Config) GetExchangeBankAccounts(exchangeName, depositingCurrency string for x := range c.Exchanges { if strings.EqualFold(c.Exchanges[x].Name, exchangeName) { for y := range c.Exchanges[x].BankAccounts { - if common.StringContains(c.Exchanges[x].BankAccounts[y].SupportedCurrencies, + if strings.Contains(c.Exchanges[x].BankAccounts[y].SupportedCurrencies, depositingCurrency) { return c.Exchanges[x].BankAccounts[y], nil } @@ -130,9 +130,9 @@ func (c *Config) GetClientBankAccounts(exchangeName, targetCurrency string) (Ban defer m.Unlock() for x := range c.BankAccounts { - if (common.StringContains(c.BankAccounts[x].SupportedExchanges, exchangeName) || + if (strings.Contains(c.BankAccounts[x].SupportedExchanges, exchangeName) || c.BankAccounts[x].SupportedExchanges == "ALL") && - common.StringContains(c.BankAccounts[x].SupportedCurrencies, targetCurrency) { + strings.Contains(c.BankAccounts[x].SupportedCurrencies, targetCurrency) { return c.BankAccounts[x], nil } @@ -518,13 +518,13 @@ func (c *Config) CheckPairConfigFormats(exchName string) error { for y := range loadedPairs { if pairFmt.Delimiter != "" { - if !common.StringContains(loadedPairs[y].String(), pairFmt.Delimiter) { + if !strings.Contains(loadedPairs[y].String(), pairFmt.Delimiter) { return fmt.Errorf("exchange %s %s %v pairs does not contain delimiter", exchName, pairsType, assetType) } } if pairFmt.Index != "" { - if !common.StringContains(loadedPairs[y].String(), pairFmt.Index) { + if !strings.Contains(loadedPairs[y].String(), pairFmt.Index) { return fmt.Errorf("exchange %s %s %v pairs does not contain an index", exchName, pairsType, assetType) } } @@ -602,7 +602,8 @@ func (c *Config) CheckPairConsistency(exchName string) error { if err != nil { return fmt.Errorf("exchange %s failed to set pairs: %v", exchName, err) } - log.Warnf("Exchange %s: No enabled pairs found in available pairs, randomly added %v pair.\n", exchName, newPair) + log.Warnf("Exchange %s: [%v] No enabled pairs found in available pairs, randomly added %v pair.\n", + exchName, assetTypes[x], newPair) continue } else { err = c.SetPairs(exchName, assetTypes[x], true, pairs) @@ -610,7 +611,8 @@ func (c *Config) CheckPairConsistency(exchName string) error { return fmt.Errorf("exchange %s failed to set pairs: %v", exchName, err) } } - log.Warnf("Exchange %s: Removing enabled pair(s) %v from enabled pairs as it isn't an available pair.", exchName, pairsRemoved.Strings()) + log.Warnf("Exchange %s: [%v] Removing enabled pair(s) %v from enabled pairs as it isn't an available pair.", + exchName, assetTypes[x], pairsRemoved.Strings()) } return nil } @@ -1326,15 +1328,20 @@ func GetFilePath(file string) (string, error) { return "", err } - oldDir := exePath + common.GetOSPathSlash() - oldDirs := []string{oldDir + ConfigFile, oldDir + EncryptedConfigFile} + oldDirs := []string{ + filepath.Join(exePath, ConfigFile), + filepath.Join(exePath, EncryptedConfigFile), + } - newDir := common.GetDefaultDataDir(runtime.GOOS) + common.GetOSPathSlash() + newDir := common.GetDefaultDataDir(runtime.GOOS) err = common.CreateDir(newDir) if err != nil { return "", err } - newDirs := []string{newDir + ConfigFile, newDir + EncryptedConfigFile} + newDirs := []string{ + filepath.Join(newDir, ConfigFile), + filepath.Join(newDir, EncryptedConfigFile), + } // First upgrade the old dir config file if it exists to the corresponding new one for x := range oldDirs { diff --git a/currency/code.go b/currency/code.go index 2d00e5b3..3dcabdab 100644 --- a/currency/code.go +++ b/currency/code.go @@ -3,6 +3,7 @@ package currency import ( "errors" "fmt" + "strings" "sync" "time" @@ -294,8 +295,8 @@ func (b *BaseCodes) UpdateContract(fullName, symbol, assocExchange string) error // Register registers a currency from a string and returns a currency code func (b *BaseCodes) Register(c string) Code { - NewUpperCode := common.StringToUpper(c) - format := common.StringContains(c, NewUpperCode) + NewUpperCode := strings.ToUpper(c) + format := strings.Contains(c, NewUpperCode) b.mtx.Lock() defer b.mtx.Unlock() @@ -321,7 +322,7 @@ func (b *BaseCodes) Register(c string) Code { // RegisterFiat registers a fiat currency from a string and returns a currency // code func (b *BaseCodes) RegisterFiat(c string) (Code, error) { - c = common.StringToUpper(c) + c = strings.ToUpper(c) b.mtx.Lock() defer b.mtx.Unlock() @@ -427,7 +428,7 @@ func (c Code) String() string { if c.UpperCase { return c.Item.Symbol } - return common.StringToLower(c.Item.Symbol) + return strings.ToLower(c.Item.Symbol) } // Lower converts the code to lowercase formatting diff --git a/currency/code_test.go b/currency/code_test.go index 4d675b85..573461a0 100644 --- a/currency/code_test.go +++ b/currency/code_test.go @@ -8,31 +8,31 @@ import ( func TestRoleString(t *testing.T) { if Unset.String() != UnsetRollString { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", UnsetRollString, Unset) } if Fiat.String() != FiatCurrencyString { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", FiatCurrencyString, Fiat) } if Cryptocurrency.String() != CryptocurrencyString { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", CryptocurrencyString, Cryptocurrency) } if Token.String() != TokenString { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", TokenString, Token) } if Contract.String() != ContractString { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", ContractString, Contract) } @@ -40,7 +40,7 @@ func TestRoleString(t *testing.T) { var random Role = 1 << 7 if random.String() != "UNKNOWN" { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", "UNKNOWN", random) } @@ -54,7 +54,7 @@ func TestRoleMarshalJSON(t *testing.T) { expected := `"fiatCurrency"` if string(d) != expected { - t.Errorf("Test Failed - Role MarshalJSON() error expected %s but recieved %s", + t.Errorf("Test Failed - Role MarshalJSON() error expected %s but received %s", expected, string(d)) } @@ -100,37 +100,37 @@ func TestRoleUnmarshalJSON(t *testing.T) { } if incoming.RoleOne != Unset { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", Unset, incoming.RoleOne) } if incoming.RoleTwo != Cryptocurrency { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", Cryptocurrency, incoming.RoleTwo) } if incoming.RoleThree != Fiat { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", Fiat, incoming.RoleThree) } if incoming.RoleFour != Token { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", Token, incoming.RoleFour) } if incoming.RoleFive != Contract { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", Contract, incoming.RoleFive) } if incoming.RoleUnknown != Unset { - t.Errorf("Test Failed - Role String() error expected %s but recieved %s", + t.Errorf("Test Failed - Role String() error expected %s but received %s", incoming.RoleFive, incoming.RoleUnknown) } @@ -139,35 +139,35 @@ func TestRoleUnmarshalJSON(t *testing.T) { func TestBaseCode(t *testing.T) { var main BaseCodes if main.HasData() { - t.Errorf("Test Failed - BaseCode HasData() error expected false but recieved %v", + t.Errorf("Test Failed - BaseCode HasData() error expected false but received %v", main.HasData()) } catsCode := main.Register("CATS") if !main.HasData() { - t.Errorf("Test Failed - BaseCode HasData() error expected true but recieved %v", + t.Errorf("Test Failed - BaseCode HasData() error expected true but received %v", main.HasData()) } if !main.Register("CATS").Match(catsCode) { - t.Errorf("Test Failed - BaseCode Match() error expected true but recieved %v", + t.Errorf("Test Failed - BaseCode Match() error expected true but received %v", false) } if main.Register("DOGS").Match(catsCode) { - t.Errorf("Test Failed - BaseCode Match() error expected false but recieved %v", + t.Errorf("Test Failed - BaseCode Match() error expected false but received %v", true) } loadedCurrencies := main.GetCurrencies() if loadedCurrencies.Contains(main.Register("OWLS")) { - t.Errorf("Test Failed - BaseCode Contains() error expected false but recieved %v", + t.Errorf("Test Failed - BaseCode Contains() error expected false but received %v", true) } if !loadedCurrencies.Contains(catsCode) { - t.Errorf("Test Failed - BaseCode Contains() error expected true but recieved %v", + t.Errorf("Test Failed - BaseCode Contains() error expected true but received %v", false) } @@ -194,22 +194,22 @@ func TestBaseCode(t *testing.T) { contract := main.Register("XBTUSD") if contract.IsFiatCurrency() { - t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but recieved %v", + t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but received %v", true) } if contract.IsCryptocurrency() { - t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but recieved %v", + t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but received %v", true) } if contract.IsDefaultFiatCurrency() { - t.Errorf("Test Failed - BaseCode IsDefaultFiatCurrency() error expected false but recieved %v", + t.Errorf("Test Failed - BaseCode IsDefaultFiatCurrency() error expected false but received %v", true) } if contract.IsDefaultFiatCurrency() { - t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but recieved %v", + t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but received %v", true) } @@ -229,32 +229,32 @@ func TestBaseCode(t *testing.T) { } if len(full.Contracts) != 1 { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but recieved %v", + t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but received %v", len(full.Contracts)) } if len(full.Cryptocurrency) != 2 { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but recieved %v", + t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but received %v", len(full.Cryptocurrency)) } if len(full.FiatCurrency) != 1 { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but recieved %v", + t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but received %v", len(full.FiatCurrency)) } if len(full.Token) != 1 { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but recieved %v", + t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but received %v", len(full.Token)) } if len(full.UnsetCurrency) != 3 { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 3 but recieved %v", + t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 3 but received %v", len(full.UnsetCurrency)) } if !full.LastMainUpdate.IsZero() { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 0 but recieved %s", + t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 0 but received %s", full.LastMainUpdate) } } @@ -263,7 +263,7 @@ func TestCodeString(t *testing.T) { expected := "TEST" cc := NewCode("TEST") if cc.String() != expected { - t.Errorf("Test Failed - Currency Code String() error expected %s but recieved %s", + t.Errorf("Test Failed - Currency Code String() error expected %s but received %s", expected, cc) } } @@ -272,7 +272,7 @@ func TestCodeLower(t *testing.T) { expected := "test" cc := NewCode("TEST") if cc.Lower().String() != expected { - t.Errorf("Test Failed - Currency Code Lower() error expected %s but recieved %s", + t.Errorf("Test Failed - Currency Code Lower() error expected %s but received %s", expected, cc.Lower()) } @@ -282,7 +282,7 @@ func TestCodeUpper(t *testing.T) { expected := "TEST" cc := NewCode("test") if cc.Upper().String() != expected { - t.Errorf("Test Failed - Currency Code Upper() error expected %s but recieved %s", + t.Errorf("Test Failed - Currency Code Upper() error expected %s but received %s", expected, cc.Upper()) } @@ -307,7 +307,7 @@ func TestCodeUnmarshalJSON(t *testing.T) { } if unmarshalHere.String() != expected { - t.Errorf("Test Failed - Currency Code Upper() error expected %s but recieved %s", + t.Errorf("Test Failed - Currency Code Upper() error expected %s but received %s", expected, unmarshalHere) } @@ -328,7 +328,7 @@ func TestCodeMarshalJSON(t *testing.T) { } if string(encoded) != expectedJSON { - t.Errorf("Test Failed - Currency Code Upper() error expected %s but recieved %s", + t.Errorf("Test Failed - Currency Code Upper() error expected %s but received %s", expectedJSON, string(encoded)) } @@ -346,7 +346,7 @@ func TestCodeMarshalJSON(t *testing.T) { newExpectedJSON := `{"sweetCodes":""}` if string(encoded) != newExpectedJSON { - t.Errorf("Test Failed - Currency Code Upper() error expected %s but recieved %s", + t.Errorf("Test Failed - Currency Code Upper() error expected %s but received %s", newExpectedJSON, string(encoded)) } } @@ -419,7 +419,7 @@ func TestItemString(t *testing.T) { } if newItem.String() != expected { - t.Errorf("Test Failed - Item String() error expected %s but recieved %s", + t.Errorf("Test Failed - Item String() error expected %s but received %s", expected, &newItem) } diff --git a/currency/conversion_test.go b/currency/conversion_test.go index a2cbf08c..da61200b 100644 --- a/currency/conversion_test.go +++ b/currency/conversion_test.go @@ -1,9 +1,8 @@ package currency import ( + "strings" "testing" - - "github.com/thrasher-/gocryptotrader/common" ) func TestNewConversionFromString(t *testing.T) { @@ -18,7 +17,7 @@ func TestNewConversionFromString(t *testing.T) { conv) } - newexpected := common.StringToLower(expected) + newexpected := strings.ToLower(expected) conv, err = NewConversionFromString(newexpected) if err != nil { t.Error("Test Failed - NewConversionFromString() error", err) @@ -110,7 +109,7 @@ func TestConversionsRatesSystem(t *testing.T) { var SuperDuperConversionSystem ConversionRates if SuperDuperConversionSystem.HasData() { - t.Fatalf("Test Failed - HasData() error expected false but recieved %v", + t.Fatalf("Test Failed - HasData() error expected false but received %v", SuperDuperConversionSystem.HasData()) } @@ -151,7 +150,7 @@ func TestConversionsRatesSystem(t *testing.T) { } if !SuperDuperConversionSystem.HasData() { - t.Fatalf("Test Failed - HasData() error expected true but recieved %v", + t.Fatalf("Test Failed - HasData() error expected true but received %v", SuperDuperConversionSystem.HasData()) } @@ -162,7 +161,7 @@ func TestConversionsRatesSystem(t *testing.T) { r := *p * 1000 expectedRate := 1396.9317581 if r != expectedRate { - t.Errorf("Test Failed - Convert() error expected %.13f but recieved %.13f", + t.Errorf("Test Failed - Convert() error expected %.13f but received %.13f", expectedRate, r) } @@ -170,7 +169,7 @@ func TestConversionsRatesSystem(t *testing.T) { inverseR := *pi * expectedRate expectedInverseRate := float64(1000) if inverseR != expectedInverseRate { - t.Errorf("Test Failed - Convert() error expected %.13f but recieved %.13f", + t.Errorf("Test Failed - Convert() error expected %.13f but received %.13f", expectedInverseRate, inverseR) } diff --git a/currency/currencies.go b/currency/currencies.go index 853aabd4..db96fa4e 100644 --- a/currency/currencies.go +++ b/currency/currencies.go @@ -1,6 +1,10 @@ package currency -import "github.com/thrasher-/gocryptotrader/common" +import ( + "strings" + + "github.com/thrasher-/gocryptotrader/common" +) // NewCurrenciesFromStringArray returns a Currencies object from strings func NewCurrenciesFromStringArray(currencies []string) Currencies { @@ -38,7 +42,7 @@ func (c Currencies) Contains(cc Code) bool { // Join returns a comma serparated string func (c Currencies) Join() string { - return common.JoinStrings(c.Strings(), ",") + return strings.Join(c.Strings(), ",") } // UnmarshalJSON comforms type to the umarshaler interface @@ -50,7 +54,7 @@ func (c *Currencies) UnmarshalJSON(d []byte) error { } var allTheCurrencies Currencies - for _, data := range common.SplitStrings(configCurrencies, ",") { + for _, data := range strings.Split(configCurrencies, ",") { allTheCurrencies = append(allTheCurrencies, NewCode(data)) } diff --git a/currency/currency_test.go b/currency/currency_test.go index 20008769..f6e3dae7 100644 --- a/currency/currency_test.go +++ b/currency/currency_test.go @@ -44,14 +44,14 @@ func TestUpdateBaseCurrency(t *testing.T) { } if GetBaseCurrency() != AUD { - t.Errorf("Test failed - GetBaseCurrency() expected %s but recieved %s", + t.Errorf("Test failed - GetBaseCurrency() expected %s but received %s", AUD, GetBaseCurrency()) } } func TestGetDefaultBaseCurrency(t *testing.T) { if GetDefaultBaseCurrency() != USD { - t.Errorf("Test failed - GetDefaultBaseCurrency() expected %s but recieved %s", + t.Errorf("Test failed - GetDefaultBaseCurrency() expected %s but received %s", USD, GetDefaultBaseCurrency()) } } @@ -59,7 +59,7 @@ func TestGetDefaultBaseCurrency(t *testing.T) { func TestGetDefaulCryptoCurrencies(t *testing.T) { expected := Currencies{BTC, LTC, ETH, DOGE, DASH, XRP, XMR} if !GetDefaultCryptocurrencies().Match(expected) { - t.Errorf("Test failed - GetDefaultCryptocurrencies() expected %s but recieved %s", + t.Errorf("Test failed - GetDefaultCryptocurrencies() expected %s but received %s", expected, GetDefaultCryptocurrencies()) } } @@ -67,7 +67,7 @@ func TestGetDefaulCryptoCurrencies(t *testing.T) { func TestGetDefaultFiatCurrencies(t *testing.T) { expected := Currencies{USD, AUD, EUR, CNY} if !GetDefaultFiatCurrencies().Match(expected) { - t.Errorf("Test failed - GetDefaultFiatCurrencies() expected %s but recieved %s", + t.Errorf("Test failed - GetDefaultFiatCurrencies() expected %s but received %s", expected, GetDefaultFiatCurrencies()) } } diff --git a/currency/forexprovider/base/base_interface.go b/currency/forexprovider/base/base_interface.go index 5e360833..dabdf01a 100644 --- a/currency/forexprovider/base/base_interface.go +++ b/currency/forexprovider/base/base_interface.go @@ -3,6 +3,7 @@ package base import ( "errors" "fmt" + "strings" "sync" "github.com/thrasher-/gocryptotrader/common" @@ -49,7 +50,7 @@ func (p *Provider) GetNewRate(base string, currencies []string) (map[string]floa return p.Provider.GetRates(base, "") // Zero value to get all rates default: - return p.Provider.GetRates(base, common.JoinStrings(currencies, ",")) + return p.Provider.GetRates(base, strings.Join(currencies, ",")) } } diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go index 5c23ebd7..d63082db 100644 --- a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" "github.com/thrasher-/gocryptotrader/common" @@ -54,7 +55,7 @@ func (c *CurrencyConverter) Setup(config base.Settings) error { // GetRates is a wrapper function to return rates func (c *CurrencyConverter) GetRates(baseCurrency, symbols string) (map[string]float64, error) { - splitSymbols := common.SplitStrings(symbols, ",") + splitSymbols := strings.Split(symbols, ",") if len(splitSymbols) == 1 { return c.Convert(baseCurrency, symbols) @@ -79,7 +80,7 @@ func (c *CurrencyConverter) GetRates(baseCurrency, symbols string) (map[string]f continue } for k, v := range result { - rates[common.ReplaceString(k, "_", "", -1)] = v + rates[strings.Replace(k, "_", "", -1)] = v } } } @@ -98,7 +99,7 @@ func (c *CurrencyConverter) GetRates(baseCurrency, symbols string) (map[string]f } for k, v := range result { - rates[common.ReplaceString(k, "_", "", -1)] = v + rates[strings.Replace(k, "_", "", -1)] = v } return rates, nil @@ -113,7 +114,7 @@ func (c *CurrencyConverter) ConvertMany(currencies []string) (map[string]float64 result := make(map[string]float64) v := url.Values{} - joined := common.JoinStrings(currencies, ",") + joined := strings.Join(currencies, ",") v.Set("q", joined) v.Set("compact", "ultra") diff --git a/currency/forexprovider/currencylayer/currencylayer.go b/currency/forexprovider/currencylayer/currencylayer.go index 7b9d9345..45f33592 100644 --- a/currency/forexprovider/currencylayer/currencylayer.go +++ b/currency/forexprovider/currencylayer/currencylayer.go @@ -17,6 +17,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "github.com/thrasher-/gocryptotrader/common" @@ -125,7 +126,7 @@ func (c *CurrencyLayer) GetliveData(currencies, source string) (map[string]float func (c *CurrencyLayer) GetHistoricalData(date string, currencies []string, source string) (map[string]float64, error) { var resp HistoricalRates v := url.Values{} - v.Set("currencies", common.JoinStrings(currencies, ",")) + v.Set("currencies", strings.Join(currencies, ",")) v.Set("source", source) v.Set("date", date) @@ -179,7 +180,7 @@ func (c *CurrencyLayer) QueryTimeFrame(startDate, endDate, baseCurrency string, v.Set("start_date", startDate) v.Set("end_date", endDate) v.Set("base", baseCurrency) - v.Set("currencies", common.JoinStrings(currencies, ",")) + v.Set("currencies", strings.Join(currencies, ",")) err := c.SendHTTPRequest(APIEndpointTimeframe, v, &resp) if err != nil { @@ -205,7 +206,7 @@ func (c *CurrencyLayer) QueryCurrencyChange(startDate, endDate, baseCurrency str v.Set("start_date", startDate) v.Set("end_date", endDate) v.Set("base", baseCurrency) - v.Set("currencies", common.JoinStrings(currencies, ",")) + v.Set("currencies", strings.Join(currencies, ",")) err := c.SendHTTPRequest(APIEndpointChange, v, &resp) if err != nil { diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go index 2b58bc79..25326139 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go @@ -66,7 +66,7 @@ func cleanCurrencies(baseCurrency, symbols string) string { } // remove and warn about any unsupported currencies - if !common.StringContains(exchangeRatesSupportedCurrencies, x) { + if !strings.Contains(exchangeRatesSupportedCurrencies, x) { log.Warnf("Forex provider ExchangeRatesAPI does not support currency %s, removing from forex rates query.", x) continue } @@ -164,7 +164,7 @@ func (e *ExchangeRates) GetRates(baseCurrency, symbols string) (map[string]float // GetSupportedCurrencies returns the supported currency list func (e *ExchangeRates) GetSupportedCurrencies() ([]string, error) { - return common.SplitStrings(exchangeRatesSupportedCurrencies, ","), nil + return strings.Split(exchangeRatesSupportedCurrencies, ","), nil } // SendHTTPRequest sends a HTTPS request to the desired endpoint and returns the result diff --git a/currency/forexprovider/fixer.io/fixer.go b/currency/forexprovider/fixer.io/fixer.go index d83642ae..85848cce 100644 --- a/currency/forexprovider/fixer.io/fixer.go +++ b/currency/forexprovider/fixer.io/fixer.go @@ -13,6 +13,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "github.com/thrasher-/gocryptotrader/common" @@ -142,7 +143,7 @@ func (f *Fixer) GetHistoricalRates(date, baseCurrency string, symbols []string) var resp Rates v := url.Values{} - v.Set("symbols", common.JoinStrings(symbols, ",")) + v.Set("symbols", strings.Join(symbols, ",")) if baseCurrency != "" { v.Set("base", baseCurrency) @@ -205,7 +206,7 @@ func (f *Fixer) GetTimeSeriesData(startDate, endDate, baseCurrency string, symbo v.Set("start_date", startDate) v.Set("end_date", endDate) v.Set("base", baseCurrency) - v.Set("symbols", common.JoinStrings(symbols, ",")) + v.Set("symbols", strings.Join(symbols, ",")) err := f.SendOpenHTTPRequest(fixerAPITimeSeries, v, &resp) if err != nil { @@ -231,7 +232,7 @@ func (f *Fixer) GetFluctuationData(startDate, endDate, baseCurrency string, symb v.Set("start_date", startDate) v.Set("end_date", endDate) v.Set("base", baseCurrency) - v.Set("symbols", common.JoinStrings(symbols, ",")) + v.Set("symbols", strings.Join(symbols, ",")) err := f.SendOpenHTTPRequest(fixerAPIFluctuation, v, &resp) if err != nil { diff --git a/currency/forexprovider/openexchangerates/openexchangerates.go b/currency/forexprovider/openexchangerates/openexchangerates.go index 9c00b676..6ac7cfcf 100644 --- a/currency/forexprovider/openexchangerates/openexchangerates.go +++ b/currency/forexprovider/openexchangerates/openexchangerates.go @@ -14,6 +14,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "github.com/thrasher-/gocryptotrader/common" @@ -126,7 +127,7 @@ func (o *OXR) GetHistoricalRates(date, baseCurrency string, symbols []string, pr v := url.Values{} v.Set("base", baseCurrency) - v.Set("symbols", common.JoinStrings(symbols, ",")) + v.Set("symbols", strings.Join(symbols, ",")) v.Set("prettyprint", strconv.FormatBool(prettyPrint)) v.Set("show_alternative", strconv.FormatBool(showAlternative)) endpoint := fmt.Sprintf(APIEndpointHistorical, date) @@ -156,7 +157,7 @@ func (o *OXR) GetCurrencies(showInactive, prettyPrint, showAlternative bool) (ma // GetSupportedCurrencies returns a list of supported currencies func (o *OXR) GetSupportedCurrencies() ([]string, error) { - return common.SplitStrings(oxrSupportedCurrencies, ","), nil + return strings.Split(oxrSupportedCurrencies, ","), nil } // GetTimeSeries returns historical exchange rates for a given time period, @@ -172,7 +173,7 @@ func (o *OXR) GetTimeSeries(baseCurrency, startDate, endDate string, symbols []s v.Set("base", baseCurrency) v.Set("start", startDate) v.Set("end", endDate) - v.Set("symbols", common.JoinStrings(symbols, ",")) + v.Set("symbols", strings.Join(symbols, ",")) v.Set("prettyprint", strconv.FormatBool(prettyPrint)) v.Set("show_alternative", strconv.FormatBool(showAlternative)) @@ -220,7 +221,7 @@ func (o *OXR) GetOHLC(startTime, period, baseCurrency string, symbols []string, v.Set("start_time", startTime) v.Set("period", period) v.Set("base", baseCurrency) - v.Set("symbols", common.JoinStrings(symbols, ",")) + v.Set("symbols", strings.Join(symbols, ",")) v.Set("prettyprint", strconv.FormatBool(prettyPrint)) if err := o.SendHTTPRequest(APIEndpointOHLC, v, &resp); err != nil { diff --git a/currency/pairs.go b/currency/pairs.go index 53c468a6..c6882e66 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -2,6 +2,7 @@ package currency import ( "math/rand" + "strings" "github.com/thrasher-/gocryptotrader/common" log "github.com/thrasher-/gocryptotrader/logger" @@ -35,7 +36,7 @@ func (p Pairs) Strings() []string { // Join returns a comma separated list of currency pairs func (p Pairs) Join() string { - return common.JoinStrings(p.Strings(), ",") + return strings.Join(p.Strings(), ",") } // Format formats the pair list to the exchange format configuration @@ -75,7 +76,7 @@ func (p *Pairs) UnmarshalJSON(d []byte) error { } var allThePairs Pairs - for _, data := range common.SplitStrings(pairs, ",") { + for _, data := range strings.Split(pairs, ",") { allThePairs = append(allThePairs, NewPairFromString(data)) } diff --git a/currency/storage.go b/currency/storage.go index 39a567c3..57f75468 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "path/filepath" "sync" "time" @@ -19,6 +20,7 @@ import ( const ( DefaultCurrencyFileDelay = 168 * time.Hour DefaultForeignExchangeDelay = 1 * time.Minute + DefaultStorageFile = "currency.json" ) func init() { @@ -132,7 +134,7 @@ func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration return errors.New("currency package runUpdater error filepath not set") } - s.path = filePath + common.GetOSPathSlash() + "currency.json" + s.path = filepath.Join(filePath, DefaultStorageFile) if settings.CurrencyDelay.Nanoseconds() == 0 { s.currencyFileUpdateDelay = DefaultCurrencyFileDelay diff --git a/engine/events/events.go b/engine/events/events.go index 12807b9e..f5f37400 100644 --- a/engine/events/events.go +++ b/engine/events/events.go @@ -3,9 +3,9 @@ package events import ( "errors" "fmt" + "strings" "time" - "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/communications" "github.com/thrasher-/gocryptotrader/communications/base" "github.com/thrasher-/gocryptotrader/config" @@ -134,8 +134,8 @@ func GetEventCounter() (total, executed int) { // ExecuteAction will execute the action pending on the chain func (e *Event) ExecuteAction() bool { - if common.StringContains(e.Action, ",") { - action := common.SplitStrings(e.Action, ",") + if strings.Contains(e.Action, ",") { + action := strings.Split(e.Action, ",") if action[0] == ActionSMSNotify { message := fmt.Sprintf("Event triggered: %s", e.String()) if action[1] == "ALL" { @@ -251,9 +251,9 @@ func (e *Event) CheckCondition() bool { // IsValidEvent checks the actions to be taken and returns an error if incorrect func IsValidEvent(exchange, item string, condition ConditionParams, action string) error { - exchange = common.StringToUpper(exchange) - item = common.StringToUpper(item) - action = common.StringToUpper(action) + exchange = strings.ToUpper(exchange) + item = strings.ToUpper(item) + action = strings.ToUpper(action) if !IsValidExchange(exchange) { return errExchangeDisabled @@ -279,8 +279,8 @@ func IsValidEvent(exchange, item string, condition ConditionParams, action strin } } - if common.StringContains(action, ",") { - a := common.SplitStrings(action, ",") + if strings.Contains(action, ",") { + a := strings.Split(action, ",") if a[0] != ActionSMSNotify { return errInvalidAction @@ -326,10 +326,10 @@ func EventManger() { // IsValidExchange validates the exchange func IsValidExchange(exchangeName string) bool { - exchangeName = common.StringToLower(exchangeName) + exchangeName = strings.ToLower(exchangeName) cfg := config.GetConfig() for x := range cfg.Exchanges { - if common.StringToLower(cfg.Exchanges[x].Name) == exchangeName && cfg.Exchanges[x].Enabled { + if strings.ToLower(cfg.Exchanges[x].Name) == exchangeName && cfg.Exchanges[x].Enabled { return true } } @@ -347,7 +347,7 @@ func IsValidCondition(condition string) bool { // IsValidAction validates passed in action func IsValidAction(action string) bool { - action = common.StringToUpper(action) + action = strings.ToUpper(action) switch action { case ActionSMSNotify, ActionConsolePrint, ActionTest: return true @@ -357,7 +357,7 @@ func IsValidAction(action string) bool { // IsValidItem validates passed in Item func IsValidItem(item string) bool { - item = common.StringToUpper(item) + item = strings.ToUpper(item) switch item { case ItemPrice, ItemOrderbook: return true diff --git a/engine/exchange.go b/engine/exchange.go index db4bbacd..55d66781 100644 --- a/engine/exchange.go +++ b/engine/exchange.go @@ -121,7 +121,7 @@ func UnloadExchange(name string) error { // LoadExchange loads an exchange by name func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { - nameLower := common.StringToLower(name) + nameLower := strings.ToLower(name) var exch exchange.IBotExchange if len(Bot.Exchanges) > 0 { diff --git a/engine/restful_router.go b/engine/restful_router.go index 4c7048a6..3e988597 100644 --- a/engine/restful_router.go +++ b/engine/restful_router.go @@ -5,6 +5,7 @@ import ( "net/http" _ "net/http/pprof" // blank import required for pprof "strconv" + "strings" "time" "github.com/gorilla/mux" @@ -64,7 +65,7 @@ func newRouter(isREST bool) *mux.Router { if common.ExtractPort(listenAddr) == 80 { listenAddr = common.ExtractHost(listenAddr) } else { - listenAddr = common.JoinStrings([]string{common.ExtractHost(listenAddr), + listenAddr = strings.Join([]string{common.ExtractHost(listenAddr), strconv.Itoa(common.ExtractPort(listenAddr))}, ":") } diff --git a/engine/routines.go b/engine/routines.go index 4c1b4c76..c050a647 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -3,6 +3,7 @@ package engine import ( "errors" "fmt" + "strings" "sync" "time" @@ -398,7 +399,7 @@ func WebsocketDataHandler(ws *exchange.Websocket) { case error: switch { - case common.StringContains(d.Error(), "close 1006"): + case strings.Contains(d.Error(), "close 1006"): go ws.WebsocketReset() continue default: diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 216bf673..a20e7671 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "path/filepath" + "strings" "time" grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" @@ -41,17 +42,17 @@ func authenticateClient(ctx context.Context) (context.Context, error) { return ctx, fmt.Errorf("authorization header missing") } - if !common.StringContains(authStr[0], "Basic") { + if !strings.Contains(authStr[0], "Basic") { return ctx, fmt.Errorf("basic not found in authorization header") } - decoded, err := crypto.Base64Decode(common.SplitStrings(authStr[0], " ")[1]) + decoded, err := crypto.Base64Decode(strings.Split(authStr[0], " ")[1]) if err != nil { return ctx, fmt.Errorf("unable to base64 decode authorization header") } - username := common.SplitStrings(string(decoded), ":")[0] - password := common.SplitStrings(string(decoded), ":")[1] + username := strings.Split(string(decoded), ":")[0] + password := strings.Split(string(decoded), ":")[1] if username != Bot.Config.RemoteControl.Username || password != Bot.Config.RemoteControl.Password { return ctx, fmt.Errorf("username/password mismatch") @@ -159,7 +160,7 @@ func (s *RPCServer) GetInfo(ctx context.Context, r *gctrpc.GetInfoRequest) (*gct // GetExchanges returns a list of exchanges // Param is whether or not you wish to list enabled exchanges func (s *RPCServer) GetExchanges(ctx context.Context, r *gctrpc.GetExchangesRequest) (*gctrpc.GetExchangesResponse, error) { - exchanges := common.JoinStrings(GetExchanges(r.Enabled), ",") + exchanges := strings.Join(GetExchanges(r.Enabled), ",") return &gctrpc.GetExchangesResponse{Exchanges: exchanges}, nil } @@ -190,13 +191,13 @@ func (s *RPCServer) GetExchangeInfo(ctx context.Context, r *gctrpc.GenericExchan HttpTimeout: exchCfg.HTTPTimeout.String(), HttpUseragent: exchCfg.HTTPUserAgent, HttpProxy: exchCfg.ProxyAddress, - BaseCurrencies: common.JoinStrings(exchCfg.BaseCurrencies.Strings(), ","), + BaseCurrencies: strings.Join(exchCfg.BaseCurrencies.Strings(), ","), SupportedAssets: exchCfg.CurrencyPairs.AssetTypes.JoinToString(","), // TO-DO fix pairs - //EnabledPairs: common.JoinStrings( + //EnabledPairs: strings.Join( // exchCfg.CurrencyPairs.Pairs.GetPairs().Enabled.Strings(), ","), - //AvailablePairs: common.JoinStrings( + //AvailablePairs: strings.Join( // exchCfg.CurrencyPairs.Spot.Available.Strings(), ","), }, nil } diff --git a/engine/websocket.go b/engine/websocket.go index 6adbbda4..bcb8016e 100644 --- a/engine/websocket.go +++ b/engine/websocket.go @@ -3,6 +3,7 @@ package engine import ( "errors" "net/http" + "strings" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" @@ -122,7 +123,7 @@ func (c *WebsocketClient) read() { break } - req := common.StringToLower(evt.Event) + req := strings.ToLower(evt.Event) log.Debugf("websocket: request received: %s", req) result, ok := wsHandlers[req] diff --git a/exchanges/alphapoint/alphapoint.go b/exchanges/alphapoint/alphapoint.go index 15d7a650..c32e2213 100644 --- a/exchanges/alphapoint/alphapoint.go +++ b/exchanges/alphapoint/alphapoint.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "strconv" + "strings" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" @@ -534,7 +535,7 @@ func (a *Alphapoint) SendAuthenticatedHTTPRequest(method, path string, data map[ hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(n.String()+a.API.Credentials.ClientID+a.API.Credentials.Key), []byte(a.API.Credentials.Secret)) - data["apiSig"] = common.StringToUpper(crypto.HexEncodeToString(hmac)) + data["apiSig"] = strings.ToUpper(crypto.HexEncodeToString(hmac)) path = fmt.Sprintf("%s/ajax/v%s/%s", a.API.Endpoints.URL, alphapointAPIVersion, path) PayloadJSON, err := common.JSONEncode(data) diff --git a/exchanges/assets/assets.go b/exchanges/assets/assets.go index ea95eac8..57e50ba3 100644 --- a/exchanges/assets/assets.go +++ b/exchanges/assets/assets.go @@ -2,8 +2,6 @@ package assets import ( "strings" - - "github.com/thrasher-/gocryptotrader/common" ) // AssetType stores the asset type @@ -93,7 +91,7 @@ func IsValid(input AssetType) bool { // New takes an input of asset types as string and returns an AssetTypes // array func New(input string) AssetTypes { - if !common.StringContains(input, ",") { + if !strings.Contains(input, ",") { if IsValid(AssetType(input)) { return AssetTypes{ AssetType(input), diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 0d5a05e6..93fb514d 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "github.com/gorilla/websocket" @@ -92,7 +93,7 @@ func (b *Binance) GetOrderBook(obd OrderBookDataRequestParams) (OrderBook, error } params := url.Values{} - params.Set("symbol", common.StringToUpper(obd.Symbol)) + params.Set("symbol", strings.ToUpper(obd.Symbol)) params.Set("limit", fmt.Sprintf("%d", obd.Limit)) path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, orderBookDepth, params.Encode()) @@ -143,7 +144,7 @@ func (b *Binance) GetRecentTrades(rtr RecentTradeRequestParams) ([]RecentTrade, var resp []RecentTrade params := url.Values{} - params.Set("symbol", common.StringToUpper(rtr.Symbol)) + params.Set("symbol", strings.ToUpper(rtr.Symbol)) params.Set("limit", fmt.Sprintf("%d", rtr.Limit)) path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, recentTrades, params.Encode()) @@ -164,7 +165,7 @@ func (b *Binance) GetHistoricalTrades(symbol string, limit int, fromID int64) ([ } params := url.Values{} - params.Set("symbol", common.StringToUpper(symbol)) + params.Set("symbol", strings.ToUpper(symbol)) params.Set("limit", strconv.Itoa(limit)) params.Set("fromid", strconv.FormatInt(fromID, 10)) @@ -185,7 +186,7 @@ func (b *Binance) GetAggregatedTrades(symbol string, limit int) ([]AggregatedTra } params := url.Values{} - params.Set("symbol", common.StringToUpper(symbol)) + params.Set("symbol", strings.ToUpper(symbol)) params.Set("limit", strconv.Itoa(limit)) path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, aggregatedTrades, params.Encode()) @@ -263,7 +264,7 @@ func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) { func (b *Binance) GetAveragePrice(symbol string) (AveragePrice, error) { resp := AveragePrice{} params := url.Values{} - params.Set("symbol", common.StringToUpper(symbol)) + params.Set("symbol", strings.ToUpper(symbol)) path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, averagePrice, params.Encode()) @@ -276,7 +277,7 @@ func (b *Binance) GetAveragePrice(symbol string) (AveragePrice, error) { func (b *Binance) GetPriceChangeStats(symbol string) (PriceChangeStats, error) { resp := PriceChangeStats{} params := url.Values{} - params.Set("symbol", common.StringToUpper(symbol)) + params.Set("symbol", strings.ToUpper(symbol)) path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, priceChange, params.Encode()) @@ -296,7 +297,7 @@ func (b *Binance) GetTickers() ([]PriceChangeStats, error) { func (b *Binance) GetLatestSpotPrice(symbol string) (SymbolPrice, error) { resp := SymbolPrice{} params := url.Values{} - params.Set("symbol", common.StringToUpper(symbol)) + params.Set("symbol", strings.ToUpper(symbol)) path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, symbolPrice, params.Encode()) @@ -309,7 +310,7 @@ func (b *Binance) GetLatestSpotPrice(symbol string) (SymbolPrice, error) { func (b *Binance) GetBestPrice(symbol string) (BestPrice, error) { resp := BestPrice{} params := url.Values{} - params.Set("symbol", common.StringToUpper(symbol)) + params.Set("symbol", strings.ToUpper(symbol)) path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, bestPrice, params.Encode()) @@ -391,7 +392,7 @@ func (b *Binance) OpenOrders(symbol string) ([]QueryOrderData, error) { params := url.Values{} if symbol != "" { - params.Set("symbol", common.StringToUpper(symbol)) + params.Set("symbol", strings.ToUpper(symbol)) } if err := b.SendAuthHTTPRequest(http.MethodGet, path, params, &resp); err != nil { @@ -410,7 +411,7 @@ func (b *Binance) AllOrders(symbol, orderID, limit string) ([]QueryOrderData, er path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, allOrders) params := url.Values{} - params.Set("symbol", common.StringToUpper(symbol)) + params.Set("symbol", strings.ToUpper(symbol)) if orderID != "" { params.Set("orderId", orderID) } @@ -431,7 +432,7 @@ func (b *Binance) QueryOrder(symbol, origClientOrderID string, orderID int64) (Q path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, queryOrder) params := url.Values{} - params.Set("symbol", common.StringToUpper(symbol)) + params.Set("symbol", strings.ToUpper(symbol)) if origClientOrderID != "" { params.Set("origClientOrderId", origClientOrderID) } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 07d9b81c..e4424615 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -170,7 +170,7 @@ func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ti pairs = append(pairs, "t"+enabledPairs[x].String()) } - tickerNew, err := b.GetTickersV2(common.JoinStrings(pairs, ",")) + tickerNew, err := b.GetTickersV2(strings.Join(pairs, ",")) if err != nil { return tickerPrice, err } diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index 21f3be5c..97a2339f 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -2,6 +2,7 @@ package bitflyer import ( "errors" + "strings" "sync" "time" @@ -136,7 +137,7 @@ func (b *Bitflyer) FetchTradablePairs(assetType assets.AssetType) ([]string, err for _, info := range pairs { if info.Alias != "" && assetType == assets.AssetTypeFutures { products = append(products, info.Alias) - } else if info.Alias == "" && assetType == assets.AssetTypeSpot && common.StringContains(info.ProductCode, "_") { + } else if info.Alias == "" && assetType == assets.AssetTypeSpot && strings.Contains(info.ProductCode, "_") { products = append(products, info.ProductCode) } } @@ -198,7 +199,7 @@ func (b *Bitflyer) FetchTicker(p currency.Pair, assetType assets.AssetType) (tic // CheckFXString upgrades currency pair if needed func (b *Bitflyer) CheckFXString(p currency.Pair) currency.Pair { - if common.StringContains(p.Base.String(), "FX") { + if strings.Contains(p.Base.String(), "FX") { p.Base = currency.FX_BTC return p } diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go index 63d19651..4c7d9515 100644 --- a/exchanges/bithumb/bithumb.go +++ b/exchanges/bithumb/bithumb.go @@ -9,6 +9,7 @@ import ( "net/url" "reflect" "strconv" + "strings" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/common/crypto" @@ -74,7 +75,7 @@ func (b *Bithumb) GetTradablePairs() ([]string, error) { // symbol e.g. "btc" func (b *Bithumb) GetTicker(symbol string) (Ticker, error) { response := Ticker{} - path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTicker, common.StringToUpper(symbol)) + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTicker, strings.ToUpper(symbol)) err := b.SendHTTPRequest(path, &response) if err != nil { @@ -140,7 +141,7 @@ func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) { // symbol e.g. "btc" func (b *Bithumb) GetOrderBook(symbol string) (Orderbook, error) { response := Orderbook{} - path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicOrderBook, common.StringToUpper(symbol)) + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicOrderBook, strings.ToUpper(symbol)) err := b.SendHTTPRequest(path, &response) if err != nil { @@ -159,7 +160,7 @@ func (b *Bithumb) GetOrderBook(symbol string) (Orderbook, error) { // symbol e.g. "btc" func (b *Bithumb) GetTransactionHistory(symbol string) (TransactionHistory, error) { response := TransactionHistory{} - path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTransactionHistory, common.StringToUpper(symbol)) + path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTransactionHistory, strings.ToUpper(symbol)) err := b.SendHTTPRequest(path, &response) if err != nil { @@ -210,7 +211,7 @@ func (b *Bithumb) GetAccountBalance(c string) (FullBalance, error) { // Added due to increasing of the usuable currencies on exchange, usually // without notificatation, so we dont need to update structs later on for tag, datum := range response.Data { - splitTag := common.SplitStrings(tag, "_") + splitTag := strings.Split(tag, "_") c := splitTag[len(splitTag)-1] var val float64 if reflect.TypeOf(datum).String() != "float64" { @@ -253,7 +254,7 @@ func (b *Bithumb) GetAccountBalance(c string) (FullBalance, error) { func (b *Bithumb) GetWalletAddress(currency string) (WalletAddressRes, error) { response := WalletAddressRes{} params := url.Values{} - params.Set("currency", common.StringToUpper(currency)) + params.Set("currency", strings.ToUpper(currency)) err := b.SendAuthenticatedHTTPRequest(privateWalletAdd, params, &response) if err != nil { @@ -305,7 +306,7 @@ func (b *Bithumb) GetOrders(orderID, transactionType, count, after, currency str } if len(currency) > 0 { - params.Set("currency", common.StringToUpper(currency)) + params.Set("currency", strings.ToUpper(currency)) } return response, @@ -331,9 +332,9 @@ func (b *Bithumb) PlaceTrade(orderCurrency, transactionType string, units float6 response := OrderPlace{} params := url.Values{} - params.Set("order_currency", common.StringToUpper(orderCurrency)) + params.Set("order_currency", strings.ToUpper(orderCurrency)) params.Set("Payment_currency", "KRW") - params.Set("type", common.StringToUpper(transactionType)) + params.Set("type", strings.ToUpper(transactionType)) params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) params.Set("price", strconv.FormatInt(price, 10)) @@ -346,9 +347,9 @@ func (b *Bithumb) ModifyTrade(orderID, orderCurrency, transactionType string, un response := OrderPlace{} params := url.Values{} - params.Set("order_currency", common.StringToUpper(orderCurrency)) + params.Set("order_currency", strings.ToUpper(orderCurrency)) params.Set("Payment_currency", "KRW") - params.Set("type", common.StringToUpper(transactionType)) + params.Set("type", strings.ToUpper(transactionType)) params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) params.Set("price", strconv.FormatInt(price, 10)) params.Set("order_id", orderID) @@ -367,9 +368,9 @@ func (b *Bithumb) GetOrderDetails(orderID, transactionType, currency string) (Or response := OrderDetails{} params := url.Values{} - params.Set("order_id", common.StringToUpper(orderID)) + params.Set("order_id", strings.ToUpper(orderID)) params.Set("type", transactionType) - params.Set("currency", common.StringToUpper(currency)) + params.Set("currency", strings.ToUpper(currency)) return response, b.SendAuthenticatedHTTPRequest(privateOrderDetail, params, &response) @@ -384,9 +385,9 @@ func (b *Bithumb) CancelTrade(transactionType, orderID, currency string) (Action response := ActionStatus{} params := url.Values{} - params.Set("order_id", common.StringToUpper(orderID)) - params.Set("type", common.StringToUpper(transactionType)) - params.Set("currency", common.StringToUpper(currency)) + params.Set("order_id", strings.ToUpper(orderID)) + params.Set("type", strings.ToUpper(transactionType)) + params.Set("currency", strings.ToUpper(currency)) return response, b.SendAuthenticatedHTTPRequest(privateCancelTrade, nil, &response) @@ -408,7 +409,7 @@ func (b *Bithumb) WithdrawCrypto(address, destination, currency string, units fl if len(destination) > 0 { params.Set("destination", destination) } - params.Set("currency", common.StringToUpper(currency)) + params.Set("currency", strings.ToUpper(currency)) params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) return response, @@ -450,7 +451,7 @@ func (b *Bithumb) MarketBuyOrder(currency string, units float64) (MarketBuy, err response := MarketBuy{} params := url.Values{} - params.Set("currency", common.StringToUpper(currency)) + params.Set("currency", strings.ToUpper(currency)) params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) return response, @@ -466,7 +467,7 @@ func (b *Bithumb) MarketSellOrder(currency string, units float64) (MarketSell, e response := MarketSell{} params := url.Values{} - params.Set("currency", common.StringToUpper(currency)) + params.Set("currency", strings.ToUpper(currency)) params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) return response, diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index e895378d..4164f8bb 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "strconv" + "strings" "sync" "time" @@ -301,7 +302,7 @@ func (b *Bithumb) SubmitOrder(p currency.Pair, side exchange.OrderSide, _ exchan func (b *Bithumb) ModifyOrder(action *exchange.ModifyOrder) (string, error) { order, err := b.ModifyTrade(action.OrderID, action.CurrencyPair.Base.String(), - common.StringToLower(action.OrderSide.ToString()), + strings.ToLower(action.OrderSide.ToString()), action.Amount, int64(action.Price)) diff --git a/exchanges/bitmex/bitmex_parameters.go b/exchanges/bitmex/bitmex_parameters.go index 1f39af6f..e08e17d5 100644 --- a/exchanges/bitmex/bitmex_parameters.go +++ b/exchanges/bitmex/bitmex_parameters.go @@ -5,6 +5,7 @@ import ( "net/url" "reflect" "strconv" + "strings" "github.com/thrasher-/gocryptotrader/common" ) @@ -35,7 +36,7 @@ func StructValsToURLVals(v interface{}) (url.Values, error) { if structType.Field(i).Tag != "" { jsonTag := structType.Field(i).Tag.Get("json") if jsonTag != "" { - split := common.SplitStrings(jsonTag, ",") + split := strings.Split(jsonTag, ",") outgoingTag = split[0] } } diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 33ad3182..9c240510 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "github.com/gorilla/websocket" @@ -152,12 +153,12 @@ func (b *Bitmex) wsHandleIncomingData() { } message := string(resp.Raw) - if common.StringContains(message, "pong") { + if strings.Contains(message, "pong") { pongChan <- 1 continue } - if common.StringContains(message, "ping") { + if strings.Contains(message, "ping") { err = b.wsSend("pong") if err != nil { b.Websocket.DataHandler <- err diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 0b5931a4..91dc3a0f 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -157,7 +157,7 @@ func (b *Bitstamp) GetTicker(currency string, hourly bool) (Ticker, error) { b.API.Endpoints.URL, bitstampAPIVersion, tickerEndpoint, - common.StringToLower(currency), + strings.ToLower(currency), ) return response, b.SendHTTPRequest(path, &response) } @@ -178,7 +178,7 @@ func (b *Bitstamp) GetOrderbook(currency string) (Orderbook, error) { b.API.Endpoints.URL, bitstampAPIVersion, bitstampAPIOrderbook, - common.StringToLower(currency), + strings.ToLower(currency), ) err := b.SendHTTPRequest(path, &resp) @@ -244,7 +244,7 @@ func (b *Bitstamp) GetTransactions(currencyPair string, values url.Values) ([]Tr b.API.Endpoints.URL, bitstampAPIVersion, bitstampAPITransactions, - common.StringToLower(currencyPair), + strings.ToLower(currencyPair), ), values, ) @@ -339,7 +339,7 @@ func (b *Bitstamp) GetUserTransactions(currencyPair string) ([]UserTransactions, func (b *Bitstamp) GetOpenOrders(currencyPair string) ([]Order, error) { var resp []Order path := fmt.Sprintf( - "%s/%s", bitstampAPIOpenOrders, common.StringToLower(currencyPair), + "%s/%s", bitstampAPIOpenOrders, strings.ToLower(currencyPair), ) return resp, b.SendAuthenticatedHTTPRequest(path, true, nil, &resp) @@ -385,10 +385,10 @@ func (b *Bitstamp) PlaceOrder(currencyPair string, price, amount float64, buy, m orderType = exchange.SellOrderSide.ToLower().ToString() } - path := fmt.Sprintf("%s/%s", orderType, common.StringToLower(currencyPair)) + path := fmt.Sprintf("%s/%s", orderType, strings.ToLower(currencyPair)) if market { - path = fmt.Sprintf("%s/%s/%s", orderType, bitstampAPIMarket, common.StringToLower(currencyPair)) + path = fmt.Sprintf("%s/%s/%s", orderType, bitstampAPIMarket, strings.ToLower(currencyPair)) } return response, @@ -428,7 +428,7 @@ func (b *Bitstamp) CryptoWithdrawal(amount float64, address, symbol, destTag str resp := CryptoWithdrawalResponse{} var endpoint string - switch common.StringToLower(symbol) { + switch strings.ToLower(symbol) { case "btc": if instant { req.Add("instant", "1") @@ -586,7 +586,7 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(n+b.API.Credentials.ClientID+b.API.Credentials.Key), []byte(b.API.Credentials.Secret)) - values.Set("signature", common.StringToUpper(crypto.HexEncodeToString(hmac))) + values.Set("signature", strings.ToUpper(crypto.HexEncodeToString(hmac))) if v2 { path = fmt.Sprintf("%s/v%s/%s/", b.API.Endpoints.URL, bitstampAPIVersion, path) diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 7526907d..aeb61fbd 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "github.com/gorilla/websocket" @@ -117,8 +118,8 @@ func (b *Bitstamp) WsHandleData() { continue } - currencyPair := common.SplitStrings(wsResponse.Channel, "_") - p := currency.NewPairFromString(common.StringToUpper(currencyPair[3])) + currencyPair := strings.Split(wsResponse.Channel, "_") + p := currency.NewPairFromString(strings.ToUpper(currencyPair[3])) err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, assets.AssetTypeSpot) if err != nil { @@ -135,8 +136,8 @@ func (b *Bitstamp) WsHandleData() { continue } - currencyPair := common.SplitStrings(wsResponse.Channel, "_") - p := currency.NewPairFromString(common.StringToUpper(currencyPair[2])) + currencyPair := strings.Split(wsResponse.Channel, "_") + p := currency.NewPairFromString(strings.ToUpper(currencyPair[2])) b.Websocket.DataHandler <- exchange.TradeData{ Price: wsTradeTemp.Data.Price, diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 1e72fc03..973de297 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" "time" @@ -155,7 +156,7 @@ func (b *Bitstamp) FetchTradablePairs(asset assets.AssetType) ([]string, error) continue } - pair := common.SplitStrings(pairs[x].Name, "/") + pair := strings.Split(pairs[x].Name, "/") products = append(products, pair[0]+pair[1]) } diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index 8da32db1..57dd26fd 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -6,8 +6,8 @@ import ( "net/http" "net/url" "strconv" + "strings" - "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" @@ -96,7 +96,7 @@ func (b *Bittrex) GetCurrencies() (Currency, error) { func (b *Bittrex) GetTicker(currencyPair string) (Ticker, error) { tick := Ticker{} path := fmt.Sprintf("%s/%s?market=%s", b.API.Endpoints.URL, bittrexAPIGetTicker, - common.StringToUpper(currencyPair), + strings.ToUpper(currencyPair), ) if err := b.SendHTTPRequest(path, &tick); err != nil { @@ -130,7 +130,7 @@ func (b *Bittrex) GetMarketSummaries() (MarketSummary, error) { func (b *Bittrex) GetMarketSummary(currencyPair string) (MarketSummary, error) { var summary MarketSummary path := fmt.Sprintf("%s/%s?market=%s", b.API.Endpoints.URL, - bittrexAPIGetMarketSummary, common.StringToLower(currencyPair), + bittrexAPIGetMarketSummary, strings.ToLower(currencyPair), ) if err := b.SendHTTPRequest(path, &summary); err != nil { @@ -153,7 +153,7 @@ func (b *Bittrex) GetMarketSummary(currencyPair string) (MarketSummary, error) { func (b *Bittrex) GetOrderbook(currencyPair string) (OrderBooks, error) { var orderbooks OrderBooks path := fmt.Sprintf("%s/%s?market=%s&type=both&depth=50", b.API.Endpoints.URL, - bittrexAPIGetOrderbook, common.StringToUpper(currencyPair), + bittrexAPIGetOrderbook, strings.ToUpper(currencyPair), ) if err := b.SendHTTPRequest(path, &orderbooks); err != nil { @@ -171,7 +171,7 @@ func (b *Bittrex) GetOrderbook(currencyPair string) (OrderBooks, error) { func (b *Bittrex) GetMarketHistory(currencyPair string) (MarketHistory, error) { var marketHistoriae MarketHistory path := fmt.Sprintf("%s/%s?market=%s", b.API.Endpoints.URL, - bittrexAPIGetMarketHistory, common.StringToUpper(currencyPair), + bittrexAPIGetMarketHistory, strings.ToUpper(currencyPair), ) if err := b.SendHTTPRequest(path, &marketHistoriae); err != nil { diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index d6c98144..84fa534a 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/common/crypto" @@ -75,8 +76,8 @@ func (b *BTCMarkets) GetTicker(firstPair, secondPair string) (Ticker, error) { tick := Ticker{} path := fmt.Sprintf("%s/market/%s/%s/tick", b.API.Endpoints.URL, - common.StringToUpper(firstPair), - common.StringToUpper(secondPair)) + strings.ToUpper(firstPair), + strings.ToUpper(secondPair)) return tick, b.SendHTTPRequest(path, &tick) } @@ -87,8 +88,8 @@ func (b *BTCMarkets) GetOrderbook(firstPair, secondPair string) (Orderbook, erro orderbook := Orderbook{} path := fmt.Sprintf("%s/market/%s/%s/orderbook", b.API.Endpoints.URL, - common.StringToUpper(firstPair), - common.StringToUpper(secondPair)) + strings.ToUpper(firstPair), + strings.ToUpper(secondPair)) return orderbook, b.SendHTTPRequest(path, &orderbook) } @@ -99,8 +100,8 @@ func (b *BTCMarkets) GetOrderbook(firstPair, secondPair string) (Orderbook, erro func (b *BTCMarkets) GetTrades(firstPair, secondPair string, values url.Values) ([]Trade, error) { var trades []Trade path := common.EncodeURLValues(fmt.Sprintf("%s/market/%s/%s/trades", - b.API.Endpoints.URL, common.StringToUpper(firstPair), - common.StringToUpper(secondPair)), values) + b.API.Endpoints.URL, strings.ToUpper(firstPair), + strings.ToUpper(secondPair)), values) return trades, b.SendHTTPRequest(path, &trades) } @@ -118,8 +119,8 @@ func (b *BTCMarkets) NewOrder(instrument, currency string, price, amount float64 newVolume := int64(amount * float64(common.SatoshisPerBTC)) order := OrderToGo{ - Currency: common.StringToUpper(currency), - Instrument: common.StringToUpper(instrument), + Currency: strings.ToUpper(currency), + Instrument: strings.ToUpper(instrument), Price: newPrice, Volume: newVolume, OrderSide: orderSide, @@ -172,10 +173,10 @@ func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, req := make(map[string]interface{}) if currency != "" { - req["currency"] = common.StringToUpper(currency) + req["currency"] = strings.ToUpper(currency) } if instrument != "" { - req["instrument"] = common.StringToUpper(instrument) + req["instrument"] = strings.ToUpper(instrument) } if limit != 0 { req["limit"] = limit @@ -312,7 +313,7 @@ func (b *BTCMarkets) WithdrawCrypto(amount float64, currency, address string) (s req := WithdrawRequestCrypto{ Amount: newAmount, - Currency: common.StringToUpper(currency), + Currency: strings.ToUpper(currency), Address: address, } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 75869c9f..37ba6b3f 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -8,7 +8,6 @@ import ( "strings" "time" - "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" @@ -489,7 +488,7 @@ func (e *Base) ValidateAPICredentials() bool { if e.API.CredentialsValidator.RequiresPEM { if e.API.Credentials.PEMKey == "" || - common.StringContains(e.API.Credentials.PEMKey, "JUSTADUMMY") { + strings.Contains(e.API.Credentials.PEMKey, "JUSTADUMMY") { log.Warnf("exchange %s requires API PEM key but default/empty one set", e.Name) return false diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index 1b4a5ba0..23060843 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -6,7 +6,6 @@ import ( "strings" "time" - "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/exchanges/assets" @@ -164,7 +163,7 @@ const ( // ToLower changes the ordertype to lower case func (o OrderType) ToLower() OrderType { - return OrderType(common.StringToLower(string(o))) + return OrderType(strings.ToLower(string(o))) } // ToString changes the ordertype to the exchange standard and returns a string @@ -186,7 +185,7 @@ const ( // ToLower changes the ordertype to lower case func (o OrderSide) ToLower() OrderSide { - return OrderSide(common.StringToLower(string(o))) + return OrderSide(strings.ToLower(string(o))) } // ToString changes the ordertype to the exchange standard and returns a string diff --git a/exchanges/exmo/exmo.go b/exchanges/exmo/exmo.go index 5736ffcb..5833ddf0 100644 --- a/exchanges/exmo/exmo.go +++ b/exchanges/exmo/exmo.go @@ -238,7 +238,7 @@ func (e *EXMO) WithdrawCryptocurrency(currency, address, invoice string, amount v.Set("currency", currency) v.Set("address", address) - if common.StringToUpper(currency) == "XRP" { + if strings.ToUpper(currency) == "XRP" { v.Set(invoice, invoice) } diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index 4146c541..7aeda956 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -11,6 +11,7 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/convert" "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" @@ -214,31 +215,31 @@ func (g *Gateio) GetSpotKline(arg KlinesRequestParams) ([]*KLineResponse, error) for _, k := range rawKlineDatas { otString, _ := strconv.ParseFloat(k[0].(string), 64) - ot, err := common.TimeFromUnixTimestampFloat(otString) + ot, err := convert.TimeFromUnixTimestampFloat(otString) if err != nil { return nil, fmt.Errorf("cannot parse Kline.OpenTime. Err: %s", err) } - _vol, err := common.FloatFromString(k[1]) + _vol, err := convert.FloatFromString(k[1]) if err != nil { return nil, fmt.Errorf("cannot parse Kline.Volume. Err: %s", err) } - _id, err := common.FloatFromString(k[0]) + _id, err := convert.FloatFromString(k[0]) if err != nil { return nil, fmt.Errorf("cannot parse Kline.Id. Err: %s", err) } - _close, err := common.FloatFromString(k[2]) + _close, err := convert.FloatFromString(k[2]) if err != nil { return nil, fmt.Errorf("cannot parse Kline.Close. Err: %s", err) } - _high, err := common.FloatFromString(k[3]) + _high, err := convert.FloatFromString(k[3]) if err != nil { return nil, fmt.Errorf("cannot parse Kline.High. Err: %s", err) } - _low, err := common.FloatFromString(k[4]) + _low, err := convert.FloatFromString(k[4]) if err != nil { return nil, fmt.Errorf("cannot parse Kline.Low. Err: %s", err) } - _open, err := common.FloatFromString(k[5]) + _open, err := convert.FloatFromString(k[5]) if err != nil { return nil, fmt.Errorf("cannot parse Kline.Open. Err: %s", err) } diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 1caa17ac..80aeb672 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -118,7 +118,7 @@ func (g *Gateio) WsHandleData() { } if result.Error.Code != 0 { - if common.StringContains(result.Error.Message, "authentication") { + if strings.Contains(result.Error.Message, "authentication") { g.Websocket.DataHandler <- fmt.Errorf("%v - WebSocket authentication failed ", g.GetName()) g.API.AuthenticatedSupport = false @@ -170,7 +170,7 @@ func (g *Gateio) WsHandleData() { } switch { - case common.StringContains(result.Method, "ticker"): + case strings.Contains(result.Method, "ticker"): var ticker WebsocketTicker var c string err = common.JSONDecode(result.Params[1], &ticker) @@ -197,7 +197,7 @@ func (g *Gateio) WsHandleData() { LowPrice: ticker.Low, } - case common.StringContains(result.Method, "trades"): + case strings.Contains(result.Method, "trades"): var trades []WebsocketTrade var c string err = common.JSONDecode(result.Params[1], &trades) @@ -224,7 +224,7 @@ func (g *Gateio) WsHandleData() { } } - case common.StringContains(result.Method, "depth"): + case strings.Contains(result.Method, "depth"): var IsSnapshot bool var c string var data = make(map[string][][]string) @@ -312,7 +312,7 @@ func (g *Gateio) WsHandleData() { Exchange: g.GetName(), } - case common.StringContains(result.Method, "kline"): + case strings.Contains(result.Method, "kline"): var data []interface{} err = common.JSONDecode(result.Params[0], &data) if err != nil { diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index e2d9a197..423124ba 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -133,7 +133,7 @@ func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { ticker.Volume.Currency, _ = strconv.ParseFloat(resp.Volume[currencyPair[0:3]].(string), 64) - if common.StringContains(currencyPair, "USD") { + if strings.Contains(currencyPair, "USD") { ticker.Volume.USD, _ = strconv.ParseFloat(resp.Volume["USD"].(string), 64) } else { if resp.Volume["ETH"] != nil { @@ -377,7 +377,7 @@ func (g *Gemini) WithdrawCrypto(address, currency string, amount float64) (Withd req["address"] = address req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) - err := g.SendAuthenticatedHTTPRequest(http.MethodPost, geminiWithdraw+common.StringToLower(currency), req, &response) + err := g.SendAuthenticatedHTTPRequest(http.MethodPost, geminiWithdraw+strings.ToLower(currency), req, &response) if err != nil { return response, err } diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 4a52893c..7eea50c1 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -296,8 +296,8 @@ func (h *HitBTC) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType response, err := h.PlaceOrder(p.String(), price, amount, - common.StringToLower(orderType.ToString()), - common.StringToLower(side.ToString())) + strings.ToLower(orderType.ToString()), + strings.ToLower(side.ToString())) if response.OrderNumber > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response.OrderNumber) diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index de2e20d3..886b1fed 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/http" "net/url" + "strings" "time" "github.com/gorilla/websocket" @@ -126,7 +127,7 @@ func (h *HUOBI) WsHandleData() { } switch { - case common.StringContains(init.Channel, "depth"): + case strings.Contains(init.Channel, "depth"): var depth WsDepth err := common.JSONDecode(resp.Raw, &depth) if err != nil { @@ -134,11 +135,11 @@ func (h *HUOBI) WsHandleData() { continue } - data := common.SplitStrings(depth.Channel, ".") + data := strings.Split(depth.Channel, ".") h.WsProcessOrderbook(&depth, data[1]) - case common.StringContains(init.Channel, "kline"): + case strings.Contains(init.Channel, "kline"): var kline WsKline err := common.JSONDecode(resp.Raw, &kline) if err != nil { @@ -146,7 +147,7 @@ func (h *HUOBI) WsHandleData() { continue } - data := common.SplitStrings(kline.Channel, ".") + data := strings.Split(kline.Channel, ".") h.Websocket.DataHandler <- exchange.KlineData{ Timestamp: time.Unix(0, kline.Timestamp), @@ -160,7 +161,7 @@ func (h *HUOBI) WsHandleData() { Volume: kline.Tick.Volume, } - case common.StringContains(init.Channel, "trade"): + case strings.Contains(init.Channel, "trade"): var trade WsTrade err := common.JSONDecode(resp.Raw, &trade) if err != nil { @@ -168,7 +169,7 @@ func (h *HUOBI) WsHandleData() { continue } - data := common.SplitStrings(trade.Channel, ".") + data := strings.Split(trade.Channel, ".") h.Websocket.DataHandler <- exchange.TradeData{ Exchange: h.GetName(), diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 23a59cf1..48905574 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -390,7 +390,7 @@ func (h *HUOBI) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType var params = SpotNewOrderRequestParams{ Amount: amount, Source: "api", - Symbol: common.StringToLower(p.String()), + Symbol: strings.ToLower(p.String()), AccountID: int(accountID), } diff --git a/exchanges/huobihadax/huobihadax.go b/exchanges/huobihadax/huobihadax.go index 691b7532..ace01ae7 100644 --- a/exchanges/huobihadax/huobihadax.go +++ b/exchanges/huobihadax/huobihadax.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "sync" "time" @@ -843,7 +844,7 @@ func (h *HUOBIHADAX) GetDepositWithdrawalHistory(associatedID, currency string, vals.Set("from", associatedID) vals.Set("size", strconv.FormatInt(size, 10)) - vals.Set("currency", common.StringToLower(currency)) + vals.Set("currency", strings.ToLower(currency)) err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiHadaxDepositAddress, diff --git a/exchanges/huobihadax/huobihadax_websocket.go b/exchanges/huobihadax/huobihadax_websocket.go index 75b03a7e..7ef4ca59 100644 --- a/exchanges/huobihadax/huobihadax_websocket.go +++ b/exchanges/huobihadax/huobihadax_websocket.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/http" "net/url" + "strings" "time" "github.com/gorilla/websocket" @@ -129,7 +130,7 @@ func (h *HUOBIHADAX) WsHandleData() { } switch { - case common.StringContains(init.Channel, "depth"): + case strings.Contains(init.Channel, "depth"): var depth WsDepth err := common.JSONDecode(resp.Raw, &depth) if err != nil { @@ -137,11 +138,11 @@ func (h *HUOBIHADAX) WsHandleData() { continue } - data := common.SplitStrings(depth.Channel, ".") + data := strings.Split(depth.Channel, ".") h.WsProcessOrderbook(&depth, data[1]) - case common.StringContains(init.Channel, "kline"): + case strings.Contains(init.Channel, "kline"): var kline WsKline err := common.JSONDecode(resp.Raw, &kline) if err != nil { @@ -149,7 +150,7 @@ func (h *HUOBIHADAX) WsHandleData() { continue } - data := common.SplitStrings(kline.Channel, ".") + data := strings.Split(kline.Channel, ".") h.Websocket.DataHandler <- exchange.KlineData{ Timestamp: time.Unix(0, kline.Timestamp), @@ -163,7 +164,7 @@ func (h *HUOBIHADAX) WsHandleData() { Volume: kline.Tick.Volume, } - case common.StringContains(init.Channel, "trade"): + case strings.Contains(init.Channel, "trade"): var trade WsTrade err := common.JSONDecode(resp.Raw, &trade) if err != nil { @@ -171,7 +172,7 @@ func (h *HUOBIHADAX) WsHandleData() { continue } - data := common.SplitStrings(trade.Channel, ".") + data := strings.Split(trade.Channel, ".") h.Websocket.DataHandler <- exchange.TradeData{ Exchange: h.GetName(), diff --git a/exchanges/huobihadax/huobihadax_wrapper.go b/exchanges/huobihadax/huobihadax_wrapper.go index 67b2412b..6923b317 100644 --- a/exchanges/huobihadax/huobihadax_wrapper.go +++ b/exchanges/huobihadax/huobihadax_wrapper.go @@ -351,7 +351,7 @@ func (h *HUOBIHADAX) SubmitOrder(p currency.Pair, side exchange.OrderSide, order var params = SpotNewOrderRequestParams{ Amount: amount, Source: "api", - Symbol: common.StringToLower(p.String()), + Symbol: strings.ToLower(p.String()), AccountID: int(accountID), } diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index a6502e7a..be73a5ac 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -10,7 +10,6 @@ import ( "sync" "github.com/gorilla/websocket" - "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" @@ -751,8 +750,8 @@ func (k *Kraken) GetTradeVolume(feeinfo bool, symbol ...string) (TradeVolumeResp func (k *Kraken) AddOrder(symbol, side, orderType string, volume, price, price2, leverage float64, args *AddOrderOptions) (AddOrderResponse, error) { params := url.Values{ "pair": {symbol}, - "type": {common.StringToLower(side)}, - "ordertype": {common.StringToLower(orderType)}, + "type": {strings.ToLower(side)}, + "ordertype": {strings.ToLower(orderType)}, "volume": {strconv.FormatFloat(volume, 'f', -1, 64)}, } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 874e61b0..7a98bc28 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -172,7 +172,7 @@ func (k *Kraken) FetchTradablePairs(asset assets.AssetType) ([]string, error) { var products []string for i := range pairs { v := pairs[i] - if common.StringContains(v.Altname, ".d") { + if strings.Contains(v.Altname, ".d") { continue } if v.Base[0] == 'X' { @@ -214,8 +214,8 @@ func (k *Kraken) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tick for _, x := range pairs { for y, z := range tickers { - if !common.StringContains(y, x.Base.Upper().String()) || - !common.StringContains(y, x.Quote.Upper().String()) { + if !strings.Contains(y, x.Base.Upper().String()) || + !strings.Contains(y, x.Quote.Upper().String()) { continue } var tp ticker.Price diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 7a4877ab..8fec64cc 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -52,7 +52,7 @@ func (l *LakeBTC) GetTicker() (map[string]Ticker, error) { for k, v := range response { var tick Ticker - key := common.StringToUpper(k) + key := strings.ToUpper(k) if v.Ask != nil { tick.Ask, _ = strconv.ParseFloat(v.Ask.(string), 64) } @@ -82,7 +82,7 @@ func (l *LakeBTC) GetOrderBook(currency string) (Orderbook, error) { Bids [][]string `json:"bids"` Asks [][]string `json:"asks"` } - path := fmt.Sprintf("%s/%s?symbol=%s", l.API.Endpoints.URL, lakeBTCOrderbook, common.StringToLower(currency)) + path := fmt.Sprintf("%s/%s?symbol=%s", l.API.Endpoints.URL, lakeBTCOrderbook, strings.ToLower(currency)) resp := Response{} err := l.SendHTTPRequest(path, &resp) if err != nil { @@ -122,7 +122,7 @@ func (l *LakeBTC) GetOrderBook(currency string) (Orderbook, error) { // GetTradeHistory returns the trade history for a given currency pair func (l *LakeBTC) GetTradeHistory(currency string) ([]TradeHistory, error) { - path := fmt.Sprintf("%s/%s?symbol=%s", l.API.Endpoints.URL, lakeBTCTrades, common.StringToLower(currency)) + path := fmt.Sprintf("%s/%s?symbol=%s", l.API.Endpoints.URL, lakeBTCTrades, strings.ToLower(currency)) var resp []TradeHistory return resp, l.SendHTTPRequest(path, &resp) } @@ -172,7 +172,7 @@ func (l *LakeBTC) GetOrders(orders []int64) ([]Orders, error) { var resp []Orders return resp, - l.SendAuthenticatedHTTPRequest(lakeBTCGetOrders, common.JoinStrings(ordersStr, ","), &resp) + l.SendAuthenticatedHTTPRequest(lakeBTCGetOrders, strings.Join(ordersStr, ","), &resp) } // CancelExistingOrder cancels an order by ID number and returns an error @@ -201,7 +201,7 @@ func (l *LakeBTC) CancelExistingOrders(orderIDs []string) error { } resp := Response{} - params := common.JoinStrings(orderIDs, ",") + params := strings.Join(orderIDs, ",") err := l.SendAuthenticatedHTTPRequest(lakeBTCCancelOrder, params, &resp) if err != nil { return err @@ -271,7 +271,7 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int postData := make(map[string]interface{}) postData["method"] = method postData["id"] = 1 - postData["params"] = common.SplitStrings(params, ",") + postData["params"] = strings.Split(params, ",") data, err := common.JSONEncode(postData) if err != nil { diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index ae8a6d9c..9b563996 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -133,7 +133,7 @@ func (l *LakeBTC) FetchTradablePairs(asset assets.AssetType) ([]string, error) { var currencies []string for x := range result { - currencies = append(currencies, common.StringToUpper(x)) + currencies = append(currencies, strings.ToUpper(x)) } return currencies, nil diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index fe61955a..5a34882b 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -684,7 +684,7 @@ func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, params headers := make(map[string]string) headers["Apiauth-Key"] = l.API.Credentials.Key headers["Apiauth-Nonce"] = n - headers["Apiauth-Signature"] = common.StringToUpper(crypto.HexEncodeToString(hmac)) + headers["Apiauth-Signature"] = strings.ToUpper(crypto.HexEncodeToString(hmac)) headers["Content-Type"] = "application/x-www-form-urlencoded" if l.Verbose { diff --git a/exchanges/request/request.go b/exchanges/request/request.go index 388afd94..93c9a5f9 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "strings" "sync" "time" @@ -327,7 +328,7 @@ func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, re default: switch { - case common.StringContains(resp.Header.Get("Content-Type"), "application/json"): + case strings.Contains(resp.Header.Get("Content-Type"), "application/json"): reader = resp.Body default: diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index f8732a96..e30bcaf7 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -3,10 +3,10 @@ package ticker import ( "errors" "strconv" + "strings" "sync" "time" - "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/exchanges/assets" ) @@ -45,7 +45,7 @@ type Ticker struct { // PriceToString returns the string version of a stored price field func (t *Ticker) PriceToString(p currency.Pair, priceType string, tickerType assets.AssetType) string { - priceType = common.StringToLower(priceType) + priceType = strings.ToLower(priceType) switch priceType { case "last": diff --git a/exchanges/websocket_test.go b/exchanges/websocket_test.go index 792d25b1..8917beef 100644 --- a/exchanges/websocket_test.go +++ b/exchanges/websocket_test.go @@ -389,7 +389,7 @@ func TestSubscriptionWithExistingEntry(t *testing.T) { w.SetChannelSubscriber(placeholderSubscriber) w.subscribeToChannels() if len(w.subscribedChannels) != 1 { - t.Errorf("Subscription should not have occured") + t.Errorf("Subscription should not have occurred") } } @@ -410,7 +410,7 @@ func TestUnsubscriptionWithExistingEntry(t *testing.T) { w.SetChannelUnsubscriber(placeholderSubscriber) w.unsubscribeToChannels() if len(w.subscribedChannels) != 1 { - t.Errorf("Unsubscription should not have occured") + t.Errorf("Unsubscription should not have occurred") } } diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 2244ed68..81a12f69 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -138,7 +138,7 @@ func (y *Yobit) FetchTradablePairs(asset assets.AssetType) ([]string, error) { var currencies []string for x := range info.Pairs { - currencies = append(currencies, common.StringToUpper(x)) + currencies = append(currencies, strings.ToUpper(x)) } return currencies, nil diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index 349f8895..04cfd44e 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -13,6 +13,7 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/convert" "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" @@ -66,7 +67,7 @@ func (z *ZB) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) { return 0, err } if result.Code != 1000 { - return 0, fmt.Errorf("unsucessful new order, message: %s code: %d", result.Message, result.Code) + return 0, fmt.Errorf("unsuccessful new order, message: %s code: %d", result.Message, result.Code) } newOrderID, err := strconv.ParseInt(result.ID, 10, 64) if err != nil { @@ -252,7 +253,7 @@ func (z *ZB) GetSpotKline(arg KlinesRequestParams) (KLineResponse, error) { return res, errors.New("zb rawKlines unmarshal failed") } for _, k := range rawKlineDatas { - ot, err := common.TimeFromUnixTimestampFloat(k[0]) + ot, err := convert.TimeFromUnixTimestampFloat(k[0]) if err != nil { return res, errors.New("zb cannot parse Kline.OpenTime") } diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 4d5bc2ac..6e44beef 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" "github.com/gorilla/websocket" @@ -89,7 +90,7 @@ func (z *ZB) WsHandleData() { continue } switch { - case common.StringContains(result.Channel, "markets"): + case strings.Contains(result.Channel, "markets"): if !result.Success { z.Websocket.DataHandler <- fmt.Errorf("zb_websocket.go error - unsuccessful market response %s", wsErrCodes[result.Code]) continue @@ -102,8 +103,8 @@ func (z *ZB) WsHandleData() { continue } - case common.StringContains(result.Channel, "ticker"): - cPair := common.SplitStrings(result.Channel, "_") + case strings.Contains(result.Channel, "ticker"): + cPair := strings.Split(result.Channel, "_") var ticker WsTicker @@ -123,7 +124,7 @@ func (z *ZB) WsHandleData() { LowPrice: ticker.Data.Low, } - case common.StringContains(result.Channel, "depth"): + case strings.Contains(result.Channel, "depth"): var depth WsDepth err := common.JSONDecode(resp.Raw, &depth) if err != nil { @@ -149,7 +150,7 @@ func (z *ZB) WsHandleData() { }) } - channelInfo := common.SplitStrings(result.Channel, "_") + channelInfo := strings.Split(result.Channel, "_") cPair := currency.NewPairFromString(channelInfo[0]) var newOrderBook orderbook.Base @@ -172,7 +173,7 @@ func (z *ZB) WsHandleData() { Exchange: z.GetName(), } - case common.StringContains(result.Channel, "trades"): + case strings.Contains(result.Channel, "trades"): var trades WsTrades err := common.JSONDecode(resp.Raw, &trades) if err != nil { @@ -183,7 +184,7 @@ func (z *ZB) WsHandleData() { // Most up to date trade t := trades.Data[len(trades.Data)-1] - channelInfo := common.SplitStrings(result.Channel, "_") + channelInfo := strings.Split(result.Channel, "_") cPair := currency.NewPairFromString(channelInfo[0]) z.Websocket.DataHandler <- exchange.TradeData{ diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 7fad5b7f..ad1d52e6 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" "time" @@ -179,7 +180,7 @@ func (z *ZB) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.P } for _, x := range z.GetEnabledPairs(assetType) { - currencySplit := common.SplitStrings(z.FormatExchangeCurrency(x, assetType).String(), "_") + currencySplit := strings.Split(z.FormatExchangeCurrency(x, assetType).String(), "_") currency := currencySplit[0] + currencySplit[1] var tp ticker.Price tp.Pair = x @@ -312,7 +313,7 @@ func (z *ZB) SubmitOrder(p currency.Pair, side exchange.OrderSide, _ exchange.Or var params = SpotNewOrderRequestParams{ Amount: amount, Price: price, - Symbol: common.StringToLower(p.String()), + Symbol: strings.ToLower(p.String()), Type: oT, } response, err := z.SpotNewOrder(params) diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index 24f9456b..c6751422 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -3,6 +3,7 @@ package portfolio import ( "errors" "fmt" + "strings" "time" "github.com/thrasher-/gocryptotrader/common" @@ -182,8 +183,8 @@ func (p *Base) RemoveAddress(address, description string, coinType currency.Code // UpdatePortfolio adds to the portfolio addresses by coin type func (p *Base) UpdatePortfolio(addresses []string, coinType currency.Code) bool { - if common.StringContains(common.JoinStrings(addresses, ","), PortfolioAddressExchange) || - common.StringContains(common.JoinStrings(addresses, ","), PortfolioAddressPersonal) { + if strings.Contains(strings.Join(addresses, ","), PortfolioAddressExchange) || + strings.Contains(strings.Join(addresses, ","), PortfolioAddressPersonal) { return true } @@ -224,7 +225,7 @@ func (p *Base) UpdatePortfolio(addresses []string, coinType currency.Code) bool func (p *Base) GetPortfolioByExchange(exchangeName string) map[currency.Code]float64 { result := make(map[currency.Code]float64) for x := range p.Addresses { - if common.StringContains(p.Addresses[x].Address, exchangeName) { + if strings.Contains(p.Addresses[x].Address, exchangeName) { result[p.Addresses[x].CoinType] = p.Addresses[x].Balance } } @@ -380,7 +381,7 @@ func (p *Base) GetPortfolioSummary() Summary { func (p *Base) GetPortfolioGroupedCoin() map[currency.Code][]string { result := make(map[currency.Code][]string) for _, x := range p.Addresses { - if common.StringContains(x.Description, PortfolioAddressExchange) { + if strings.Contains(x.Description, PortfolioAddressExchange) { continue } result[x.CoinType] = append(result[x.CoinType], x.Address) diff --git a/utils/utils.go b/utils/utils.go index 5aa49b7d..7334aa92 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -2,9 +2,8 @@ package utils import ( "errors" + "path/filepath" "runtime" - - "github.com/thrasher-/gocryptotrader/common" ) const ( @@ -32,5 +31,5 @@ func AdjustGoMaxProcs(maxProcs int) error { // GetTLSDir returns the default TLS dir func GetTLSDir(dir string) string { - return dir + common.GetOSPathSlash() + defaultTLSDir + return filepath.Join(dir, defaultTLSDir) } From 3010b62ac1af1f4aca29e89bcd83bf73c599824d Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 4 Jun 2019 17:27:00 +1000 Subject: [PATCH 04/71] Use string.EqualFold where necessary --- common/common.go | 2 +- common/convert/convert.go | 110 +++++----- common/convert/convert_test.go | 192 +++++++++--------- communications/smsglobal/smsglobal.go | 4 +- .../exchangeratesapi.io/exchangeratesapi.go | 2 +- engine/events/events.go | 2 +- exchanges/exmo/exmo.go | 2 +- 7 files changed, 157 insertions(+), 157 deletions(-) diff --git a/common/common.go b/common/common.go index f2af18db..595c91bb 100644 --- a/common/common.go +++ b/common/common.go @@ -145,7 +145,7 @@ func IsValidCryptoAddress(address, crypto string) (bool, error) { // YesOrNo returns a boolean variable to check if input is "y" or "yes" func YesOrNo(input string) bool { - if strings.ToLower(input) == "y" || strings.ToLower(input) == "yes" { + if strings.EqualFold(input, "y") || strings.EqualFold(input, "yes") { return true } return false diff --git a/common/convert/convert.go b/common/convert/convert.go index 27c6b1cf..3fe3f96c 100644 --- a/common/convert/convert.go +++ b/common/convert/convert.go @@ -1,55 +1,55 @@ -package convert - -import ( - "fmt" - "strconv" - "time" -) - -// FloatFromString format -func FloatFromString(raw interface{}) (float64, error) { - str, ok := raw.(string) - if !ok { - return 0, fmt.Errorf("unable to parse, value not string: %T", raw) - } - flt, err := strconv.ParseFloat(str, 64) - if err != nil { - return 0, fmt.Errorf("could not convert value: %s Error: %s", str, err) - } - return flt, nil -} - -// IntFromString format -func IntFromString(raw interface{}) (int, error) { - str, ok := raw.(string) - if !ok { - return 0, fmt.Errorf("unable to parse, value not string: %T", raw) - } - n, err := strconv.Atoi(str) - if err != nil { - return 0, fmt.Errorf("unable to parse as int: %T", raw) - } - return n, nil -} - -// Int64FromString format -func Int64FromString(raw interface{}) (int64, error) { - str, ok := raw.(string) - if !ok { - return 0, fmt.Errorf("unable to parse, value not string: %T", raw) - } - n, err := strconv.ParseInt(str, 10, 64) - if err != nil { - return 0, fmt.Errorf("unable to parse as int64: %T", raw) - } - return n, nil -} - -// TimeFromUnixTimestampFloat format -func TimeFromUnixTimestampFloat(raw interface{}) (time.Time, error) { - ts, ok := raw.(float64) - if !ok { - return time.Time{}, fmt.Errorf("unable to parse, value not float64: %T", raw) - } - return time.Unix(0, int64(ts)*int64(time.Millisecond)), nil -} +package convert + +import ( + "fmt" + "strconv" + "time" +) + +// FloatFromString format +func FloatFromString(raw interface{}) (float64, error) { + str, ok := raw.(string) + if !ok { + return 0, fmt.Errorf("unable to parse, value not string: %T", raw) + } + flt, err := strconv.ParseFloat(str, 64) + if err != nil { + return 0, fmt.Errorf("could not convert value: %s Error: %s", str, err) + } + return flt, nil +} + +// IntFromString format +func IntFromString(raw interface{}) (int, error) { + str, ok := raw.(string) + if !ok { + return 0, fmt.Errorf("unable to parse, value not string: %T", raw) + } + n, err := strconv.Atoi(str) + if err != nil { + return 0, fmt.Errorf("unable to parse as int: %T", raw) + } + return n, nil +} + +// Int64FromString format +func Int64FromString(raw interface{}) (int64, error) { + str, ok := raw.(string) + if !ok { + return 0, fmt.Errorf("unable to parse, value not string: %T", raw) + } + n, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0, fmt.Errorf("unable to parse as int64: %T", raw) + } + return n, nil +} + +// TimeFromUnixTimestampFloat format +func TimeFromUnixTimestampFloat(raw interface{}) (time.Time, error) { + ts, ok := raw.(float64) + if !ok { + return time.Time{}, fmt.Errorf("unable to parse, value not float64: %T", raw) + } + return time.Unix(0, int64(ts)*int64(time.Millisecond)), nil +} diff --git a/common/convert/convert_test.go b/common/convert/convert_test.go index c3aef95a..b6dd1f90 100644 --- a/common/convert/convert_test.go +++ b/common/convert/convert_test.go @@ -1,96 +1,96 @@ -package convert - -import ( - "testing" - "time" -) - -func TestFloatFromString(t *testing.T) { - t.Parallel() - testString := "1.41421356237" - expectedOutput := float64(1.41421356237) - - actualOutput, err := FloatFromString(testString) - if actualOutput != expectedOutput || err != nil { - t.Errorf("Test failed. Common FloatFromString. Expected '%v'. Actual '%v'. Error: %s", - expectedOutput, actualOutput, err) - } - - var testByte []byte - _, err = FloatFromString(testByte) - if err == nil { - t.Error("Test failed. Common FloatFromString. Converted non-string.") - } - - testString = " something unconvertible " - _, err = FloatFromString(testString) - if err == nil { - t.Error("Test failed. Common FloatFromString. Converted invalid syntax.") - } -} - -func TestIntFromString(t *testing.T) { - t.Parallel() - testString := "1337" - expectedOutput := 1337 - - actualOutput, err := IntFromString(testString) - if actualOutput != expectedOutput || err != nil { - t.Errorf("Test failed. Common IntFromString. Expected '%v'. Actual '%v'. Error: %s", - expectedOutput, actualOutput, err) - } - - var testByte []byte - _, err = IntFromString(testByte) - if err == nil { - t.Error("Test failed. Common IntFromString. Converted non-string.") - } - - testString = "1.41421356237" - _, err = IntFromString(testString) - if err == nil { - t.Error("Test failed. Common IntFromString. Converted invalid syntax.") - } -} - -func TestInt64FromString(t *testing.T) { - t.Parallel() - testString := "4398046511104" - expectedOutput := int64(1 << 42) - - actualOutput, err := Int64FromString(testString) - if actualOutput != expectedOutput || err != nil { - t.Errorf("Test failed. Common Int64FromString. Expected '%v'. Actual '%v'. Error: %s", - expectedOutput, actualOutput, err) - } - - var testByte []byte - _, err = Int64FromString(testByte) - if err == nil { - t.Error("Test failed. Common Int64FromString. Converted non-string.") - } - - testString = "1.41421356237" - _, err = Int64FromString(testString) - if err == nil { - t.Error("Test failed. Common Int64FromString. Converted invalid syntax.") - } -} - -func TestTimeFromUnixTimestampFloat(t *testing.T) { - t.Parallel() - testTimestamp := float64(1414456320000) - expectedOutput := time.Date(2014, time.October, 28, 0, 32, 0, 0, time.UTC) - - actualOutput, err := TimeFromUnixTimestampFloat(testTimestamp) - if actualOutput.UTC().String() != expectedOutput.UTC().String() || err != nil { - t.Errorf("Test failed. Common TimeFromUnixTimestampFloat. Expected '%v'. Actual '%v'. Error: %s", - expectedOutput, actualOutput, err) - } - - testString := "Time" - _, err = TimeFromUnixTimestampFloat(testString) - if err == nil { - t.Error("Test failed. Common TimeFromUnixTimestampFloat. Converted invalid syntax.") - } -} +package convert + +import ( + "testing" + "time" +) + +func TestFloatFromString(t *testing.T) { + t.Parallel() + testString := "1.41421356237" + expectedOutput := float64(1.41421356237) + + actualOutput, err := FloatFromString(testString) + if actualOutput != expectedOutput || err != nil { + t.Errorf("Test failed. Common FloatFromString. Expected '%v'. Actual '%v'. Error: %s", + expectedOutput, actualOutput, err) + } + + var testByte []byte + _, err = FloatFromString(testByte) + if err == nil { + t.Error("Test failed. Common FloatFromString. Converted non-string.") + } + + testString = " something unconvertible " + _, err = FloatFromString(testString) + if err == nil { + t.Error("Test failed. Common FloatFromString. Converted invalid syntax.") + } +} + +func TestIntFromString(t *testing.T) { + t.Parallel() + testString := "1337" + expectedOutput := 1337 + + actualOutput, err := IntFromString(testString) + if actualOutput != expectedOutput || err != nil { + t.Errorf("Test failed. Common IntFromString. Expected '%v'. Actual '%v'. Error: %s", + expectedOutput, actualOutput, err) + } + + var testByte []byte + _, err = IntFromString(testByte) + if err == nil { + t.Error("Test failed. Common IntFromString. Converted non-string.") + } + + testString = "1.41421356237" + _, err = IntFromString(testString) + if err == nil { + t.Error("Test failed. Common IntFromString. Converted invalid syntax.") + } +} + +func TestInt64FromString(t *testing.T) { + t.Parallel() + testString := "4398046511104" + expectedOutput := int64(1 << 42) + + actualOutput, err := Int64FromString(testString) + if actualOutput != expectedOutput || err != nil { + t.Errorf("Test failed. Common Int64FromString. Expected '%v'. Actual '%v'. Error: %s", + expectedOutput, actualOutput, err) + } + + var testByte []byte + _, err = Int64FromString(testByte) + if err == nil { + t.Error("Test failed. Common Int64FromString. Converted non-string.") + } + + testString = "1.41421356237" + _, err = Int64FromString(testString) + if err == nil { + t.Error("Test failed. Common Int64FromString. Converted invalid syntax.") + } +} + +func TestTimeFromUnixTimestampFloat(t *testing.T) { + t.Parallel() + testTimestamp := float64(1414456320000) + expectedOutput := time.Date(2014, time.October, 28, 0, 32, 0, 0, time.UTC) + + actualOutput, err := TimeFromUnixTimestampFloat(testTimestamp) + if actualOutput.UTC().String() != expectedOutput.UTC().String() || err != nil { + t.Errorf("Test failed. Common TimeFromUnixTimestampFloat. Expected '%v'. Actual '%v'. Error: %s", + expectedOutput, actualOutput, err) + } + + testString := "Time" + _, err = TimeFromUnixTimestampFloat(testString) + if err == nil { + t.Error("Test failed. Common TimeFromUnixTimestampFloat. Converted invalid syntax.") + } +} diff --git a/communications/smsglobal/smsglobal.go b/communications/smsglobal/smsglobal.go index 2157c663..ad051f7b 100644 --- a/communications/smsglobal/smsglobal.go +++ b/communications/smsglobal/smsglobal.go @@ -89,7 +89,7 @@ func (s *SMSGlobal) GetContactByNumber(number string) (Contact, error) { // GetContactByName returns a contact with supplied name func (s *SMSGlobal) GetContactByName(name string) (Contact, error) { for x := range s.Contacts { - if strings.ToLower(s.Contacts[x].Name) == strings.ToLower(name) { + if strings.EqualFold(s.Contacts[x].Name, name) { return s.Contacts[x], nil } } @@ -113,7 +113,7 @@ func (s *SMSGlobal) AddContact(contact Contact) error { // ContactExists checks to see if a contact exists func (s *SMSGlobal) ContactExists(contact Contact) bool { for x := range s.Contacts { - if s.Contacts[x].Number == contact.Number && strings.ToLower(s.Contacts[x].Name) == strings.ToLower(contact.Name) { + if s.Contacts[x].Number == contact.Number && strings.EqualFold(s.Contacts[x].Name, contact.Name) { return true } } diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go index 25326139..5a5289a6 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go @@ -66,7 +66,7 @@ func cleanCurrencies(baseCurrency, symbols string) string { } // remove and warn about any unsupported currencies - if !strings.Contains(exchangeRatesSupportedCurrencies, x) { + if !strings.Contains(exchangeRatesSupportedCurrencies, x) { // nolint:gocritic log.Warnf("Forex provider ExchangeRatesAPI does not support currency %s, removing from forex rates query.", x) continue } diff --git a/engine/events/events.go b/engine/events/events.go index f5f37400..9fb444d6 100644 --- a/engine/events/events.go +++ b/engine/events/events.go @@ -329,7 +329,7 @@ func IsValidExchange(exchangeName string) bool { exchangeName = strings.ToLower(exchangeName) cfg := config.GetConfig() for x := range cfg.Exchanges { - if strings.ToLower(cfg.Exchanges[x].Name) == exchangeName && cfg.Exchanges[x].Enabled { + if strings.EqualFold(cfg.Exchanges[x].Name, exchangeName) && cfg.Exchanges[x].Enabled { return true } } diff --git a/exchanges/exmo/exmo.go b/exchanges/exmo/exmo.go index 5833ddf0..de89512b 100644 --- a/exchanges/exmo/exmo.go +++ b/exchanges/exmo/exmo.go @@ -238,7 +238,7 @@ func (e *EXMO) WithdrawCryptocurrency(currency, address, invoice string, amount v.Set("currency", currency) v.Set("address", address) - if strings.ToUpper(currency) == "XRP" { + if strings.EqualFold(currency, "XRP") { v.Set(invoice, invoice) } From bd8dc47c38eec91ba534ad89f05005f6bd8fc3f2 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 6 Jun 2019 17:20:40 +1000 Subject: [PATCH 05/71] daily progress build --- config/config.go | 64 +++++++++++++++++--------- config/config_test.go | 30 +------------ connchecker/connchecker.go | 2 +- currency/storage.go | 2 +- engine/engine.go | 90 +++++++++++++++++-------------------- engine/engine_types.go | 11 +++-- engine/exchange.go | 5 ++- engine/helpers.go | 8 ++++ engine/portfolio.go | 81 +++++++++++++++++++++++++++++++++ engine/restful_server.go | 4 +- engine/rpcserver.go | 8 ++-- main.go | 11 +++-- portfolio/portfolio.go | 26 +++++++++-- portfolio/portfolio_test.go | 40 ++++++++++++++++- 14 files changed, 261 insertions(+), 121 deletions(-) create mode 100644 engine/portfolio.go diff --git a/config/config.go b/config/config.go index 5c7e60e8..e8068c58 100644 --- a/config/config.go +++ b/config/config.go @@ -158,7 +158,7 @@ func (c *Config) UpdateClientBankAccounts(bankCfg *BankAccount) error { } // CheckClientBankAccounts checks client bank details -func (c *Config) CheckClientBankAccounts() error { +func (c *Config) CheckClientBankAccounts() { m.Lock() defer m.Unlock() @@ -175,24 +175,30 @@ func (c *Config) CheckClientBankAccounts() error { SupportedExchanges: "ANX,Kraken", }, ) - return nil + return } for i := range c.BankAccounts { if c.BankAccounts[i].Enabled { if c.BankAccounts[i].BankName == "" || c.BankAccounts[i].BankAddress == "" { - return fmt.Errorf("banking details for %s is enabled but variables not set correctly", + c.BankAccounts[i].Enabled = false + log.Warnf("banking details for %s is enabled but variables not set correctly", c.BankAccounts[i].BankName) + continue } if c.BankAccounts[i].AccountName == "" || c.BankAccounts[i].AccountNumber == "" { - return fmt.Errorf("banking account details for %s variables not set correctly", + c.BankAccounts[i].Enabled = false + log.Warnf("banking account details for %s variables not set correctly", c.BankAccounts[i].BankName) + continue } if c.BankAccounts[i].IBAN == "" && c.BankAccounts[i].SWIFTCode == "" && c.BankAccounts[i].BSBNumber == "" { - return fmt.Errorf("critical banking numbers not set for %s in %s account", + c.BankAccounts[i].Enabled = false + log.Warnf("critical banking numbers not set for %s in %s account", c.BankAccounts[i].BankName, c.BankAccounts[i].AccountName) + continue } if c.BankAccounts[i].SupportedExchanges == "" { @@ -200,7 +206,6 @@ func (c *Config) CheckClientBankAccounts() error { } } } - return nil } // PurgeExchangeAPICredentials purges the stored API credentials @@ -1254,7 +1259,7 @@ func (c *Config) CheckNTPConfig() { } if len(c.NTPClient.Pool) < 1 { - log.Warn("NTPClient enabled with no servers configured enabling default pool") + log.Warn("NTPClient enabled with no servers configured, enabling default pool.") c.NTPClient.Pool = []string{"pool.ntp.org:123"} } } @@ -1265,8 +1270,8 @@ func (c *Config) DisableNTPCheck(input io.Reader) (string, error) { defer m.Unlock() reader := bufio.NewReader(input) - log.Warn("Your system time is out of sync this may cause issues with trading") - log.Warn("How would you like to show future notifications? (a)lert / (w)arn / (d)isable \n") + log.Warn("Your system time is out of sync, this may cause issues with trading.") + log.Warn("How would you like to show future notifications? (a)lert / (w)arn / (d)isable. \n") var answered = false for ok := true; ok; ok = (!answered) { @@ -1291,7 +1296,7 @@ func (c *Config) DisableNTPCheck(input io.Reader) (string, error) { return "Future notications for out time sync have been disabled", nil } } - return "", errors.New("something went wrong NTPCheck should never make it this far") + return "", errors.New("something went wrong, NTPCheck should never make it this far") } // CheckConnectionMonitorConfig checks and if zero value assigns default values @@ -1502,15 +1507,11 @@ func (c *Config) SaveConfig(configPath string) error { return common.WriteFile(defaultPath, payload) } -// CheckConfig checks all config settings -func (c *Config) CheckConfig() error { - err := c.CheckExchangeConfigValues() - if err != nil { - return fmt.Errorf(ErrCheckingConfigValues, err) - } - - c.CheckConnectionMonitorConfig() - c.CheckCommunicationsConfig() +// CheckRemoteControlConfig checks to see if the old c.Webserver field is used +// and migrates the existing settings to the new RemoteControl struct +func (c *Config) CheckRemoteControlConfig() { + m.Lock() + defer m.Unlock() if c.Webserver != nil { port := common.ExtractPort(c.Webserver.ListenAddress) @@ -1548,6 +1549,25 @@ func (c *Config) CheckConfig() error { c.Webserver = nil } +} + +// CheckConfig checks all config settings +func (c *Config) CheckConfig() error { + err := c.CheckLoggerConfig() + if err != nil { + log.Errorf("Failed to configure logger. Err: %s", err) + } + + err = c.CheckExchangeConfigValues() + if err != nil { + return fmt.Errorf(ErrCheckingConfigValues, err) + } + + c.CheckConnectionMonitorConfig() + c.CheckCommunicationsConfig() + c.CheckClientBankAccounts() + c.CheckRemoteControlConfig() + err = c.CheckCurrencyConfigValues() if err != nil { return err @@ -1558,7 +1578,11 @@ func (c *Config) CheckConfig() error { c.GlobalHTTPTimeout = configDefaultHTTPTimeout } - return c.CheckClientBankAccounts() + if c.NTPClient.Level != 0 { + c.CheckNTPConfig() + } + + return nil } // LoadConfig loads your configuration file into your configuration object diff --git a/config/config_test.go b/config/config_test.go index 2161dbfe..68d55afb 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -130,7 +130,7 @@ func TestCheckClientBankAccounts(t *testing.T) { } cfg.BankAccounts = nil - err = cfg.CheckClientBankAccounts() + cfg.CheckClientBankAccounts() if err != nil || len(cfg.BankAccounts) == 0 { t.Error("Test failed. CheckClientBankAccounts error:", err) } @@ -140,33 +140,7 @@ func TestCheckClientBankAccounts(t *testing.T) { Enabled: true, BankName: "test", }) - err = cfg.CheckClientBankAccounts() - if err.Error() != "banking details for test is enabled but variables not set correctly" { - t.Error("Test failed. CheckClientBankAccounts unexpected error:", err) - } - - cfg.BankAccounts[0].BankAddress = "test" - err = cfg.CheckClientBankAccounts() - if err.Error() != "banking account details for test variables not set correctly" { - t.Error("Test failed. CheckClientBankAccounts unexpected error:", err) - } - - cfg.BankAccounts[0].AccountName = "Thrasher" - cfg.BankAccounts[0].AccountNumber = "1337" - err = cfg.CheckClientBankAccounts() - if err.Error() != "critical banking numbers not set for test in Thrasher account" { - t.Error("Test failed. CheckClientBankAccounts unexpected error:", err) - } - - cfg.BankAccounts[0].IBAN = "12345678" - err = cfg.CheckClientBankAccounts() - if err != nil { - t.Error("Test failed. CheckClientBankAccounts error:", err) - } - if cfg.BankAccounts[0].SupportedExchanges == "" { - t.Error("Test failed. CheckClientBankAccounts SupportedExchanges unexpectedly nil, data:", - cfg.BankAccounts[0]) - } + // TO-DO: Complete test coverage } func TestGetCommunicationsConfig(t *testing.T) { diff --git a/connchecker/connchecker.go b/connchecker/connchecker.go index 2c8e338c..6c9c4f99 100644 --- a/connchecker/connchecker.go +++ b/connchecker/connchecker.go @@ -86,7 +86,7 @@ func (c *Checker) Shutdown() { // Monitor determines internet connectivity via a DNS lookup func (c *Checker) Monitor(wg *sync.WaitGroup) { c.wg.Add(1) - tick := time.NewTicker(time.Second) + tick := time.NewTicker(c.CheckInterval) defer func() { tick.Stop(); c.wg.Done() }() wg.Done() for { diff --git a/currency/storage.go b/currency/storage.go index 57f75468..233457db 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -343,7 +343,7 @@ func (s *Storage) SeedCurrencyAnalysisData() error { // loads it into memory func (s *Storage) FetchCurrencyAnalysisData() error { if s.currencyAnalysis == nil { - log.Warn("Currency analysis system offline please set api keys for coinmarketcap") + log.Warn("Currency analysis system offline, please set api keys for coinmarketcap if you wish to use this feature.") return errors.New("currency analysis system offline") } diff --git a/engine/engine.go b/engine/engine.go index 412a74d0..74018480 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -33,6 +33,7 @@ type Engine struct { Exchanges []exchange.IBotExchange ExchangeCurrencyPairManager *ExchangeCurrencyPairSyncer OrderManager *OrderManager + PortfolioManager portfolioManager CommsRelayer *communications.Communications Connectivity *connchecker.Checker Shutdown chan bool @@ -86,11 +87,6 @@ func NewFromSettings(settings *Settings) (*Engine, error) { return nil, fmt.Errorf("failed to open/create data directory: %s. Err: %s", settings.DataDir, err) } - err = b.Config.CheckLoggerConfig() - if err != nil { - log.Errorf("Failed to configure logger. Err: %s", err) - } - err = log.SetupLogger() if err != nil { log.Errorf("Failed to setup logger. Err: %s", err) @@ -119,7 +115,7 @@ func ValidateSettings(b *Engine, s *Settings) { b.Settings.EnableDryRun = s.EnableDryRun b.Settings.EnableAllExchanges = s.EnableAllExchanges b.Settings.EnableAllPairs = s.EnableAllPairs - b.Settings.EnablePortfolioWatcher = s.EnablePortfolioWatcher + b.Settings.EnablePortfolioManager = s.EnablePortfolioManager b.Settings.EnableCoinmarketcapAnalysis = s.EnableCoinmarketcapAnalysis // TO-DO: FIXME @@ -159,10 +155,11 @@ func ValidateSettings(b *Engine, s *Settings) { } } + b.Settings.EnableConnectivityMonitor = s.EnableConnectivityMonitor b.Settings.EnableNTPClient = s.EnableNTPClient - b.Settings.EnableTickerRoutine = s.EnableTickerRoutine - b.Settings.EnableOrderbookRoutine = s.EnableOrderbookRoutine - b.Settings.EnableWebsocketRoutine = s.EnableWebsocketRoutine + b.Settings.EnableExchangeSyncManager = s.EnableExchangeSyncManager + b.Settings.EnableTickerSyncing = s.EnableTickerSyncing + b.Settings.EnableOrderbookSyncing = s.EnableOrderbookSyncing b.Settings.EnableExchangeAutoPairUpdates = s.EnableExchangeAutoPairUpdates b.Settings.EnableExchangeWebsocketSupport = s.EnableExchangeWebsocketSupport b.Settings.EnableExchangeRESTSupport = s.EnableExchangeRESTSupport @@ -222,7 +219,7 @@ func PrintSettings(s *Settings) { log.Debugf("\t Enable all exchanges: %v", s.EnableAllExchanges) log.Debugf("\t Enable all pairs: %v", s.EnableAllPairs) log.Debugf("\t Enable coinmarketcap analaysis: %v", s.EnableCoinmarketcapAnalysis) - log.Debugf("\t Enable portfolio watcher: %v", s.EnablePortfolioWatcher) + log.Debugf("\t Enable portfolio watcher: %v", s.EnablePortfolioManager) log.Debugf("\t Enable gPRC: %v", s.EnableGRPC) log.Debugf("\t Enable gRPC Proxy: %v", s.EnableGRPCProxy) log.Debugf("\t Enable websocket RPC: %v", s.EnableWebsocketRPC) @@ -230,8 +227,10 @@ func PrintSettings(s *Settings) { log.Debugf("\t Enable comms relayer: %v", s.EnableCommsRelayer) log.Debugf("\t Enable event manager: %v", s.EnableEventManager) log.Debugf("\t Event manager sleep delay: %v", s.EventManagerDelay) - log.Debugf("\t Enable ticker routine: %v", s.EnableTickerRoutine) - log.Debugf("\t Enable orderbook routine: %v", s.EnableOrderbookRoutine) + log.Debugf("\t Enable order manager: %v", s.EnableOrderManager) + log.Debugf("\t Enable exchange sync manager: %v", s.EnableExchangeSyncManager) + log.Debugf("\t Enable ticker syncing: %v", s.EnableTickerSyncing) + log.Debugf("\t Enable orderbook syncing: %v", s.EnableOrderbookSyncing) log.Debugf("\t Enable websocket routine: %v\n", s.EnableWebsocketRoutine) log.Debugf("\t Enable NTP client: %v", s.EnableNTPClient) log.Debugf("- FOREX SETTINGS:") @@ -266,16 +265,17 @@ func (e *Engine) Start() { // Sets up internet connectivity monitor var err error - e.Connectivity, err = connchecker.New(e.Config.ConnectionMonitor.DNSList, - e.Config.ConnectionMonitor.PublicDomainList, - e.Config.ConnectionMonitor.CheckInterval) - if err != nil { - log.Fatalf("Connectivity checker failure: %s", err) + if e.Settings.EnableConnectivityMonitor { + e.Connectivity, err = connchecker.New(e.Config.ConnectionMonitor.DNSList, + e.Config.ConnectionMonitor.PublicDomainList, + e.Config.ConnectionMonitor.CheckInterval) + if err != nil { + log.Fatalf("Connectivity checker failure: %s", err) + } } if e.Settings.EnableNTPClient { if e.Config.NTPClient.Level != -1 { - e.Config.CheckNTPConfig() NTPTime, errNTP := ntpclient.NTPClient(e.Config.NTPClient.Pool) currentTime := time.Now() if errNTP != nil { @@ -356,10 +356,6 @@ func (e *Engine) Start() { log.Warn("currency updater system failed to start", err) } - e.Portfolio = &portfolio.Portfolio - e.Portfolio.Seed(e.Config.Portfolio) - SeedExchangeAccountInfo(GetAllEnabledExchangeAccountInfo().Data) - e.CryptocurrencyDepositAddresses = GetExchangeCryptocurrencyDepositAddresses() if e.Settings.EnableGRPC { @@ -375,42 +371,31 @@ func (e *Engine) Start() { StartWebsocketHandler() } - if e.Settings.EnablePortfolioWatcher { - go portfolio.StartPortfolioWatcher() + if e.Settings.EnablePortfolioManager { + if err = e.PortfolioManager.Start(); err != nil { + log.Errorf("Fund manager unable to start: %v", err) + } } - /* + if e.Settings.EnableExchangeSyncManager { exchangeSyncCfg := CurrencyPairSyncerConfig{ - SyncTicker: true, - SyncOrderbook: true, + SyncTicker: e.Settings.EnableTickerSyncing, + SyncOrderbook: e.Settings.EnableOrderbookSyncing, SyncContinuously: true, NumWorkers: 15, } - - e.ExchangeCurrencyPairManager, err = NewCurrencyPairSyncer(exchangeSyncCfg) - if err != nil { - log.Warnf("Unable to initialise exchange currency pair syncer. Err: %s", err) - } else { - e.ExchangeCurrencyPairManager.Start() - } - */ - - go StartOrderManagerRoutine() - - if e.Settings.EnableTickerRoutine { - go TickerUpdaterRoutine() + e.ExchangeCurrencyPairManager, err = NewCurrencyPairSyncer(exchangeSyncCfg) + if err != nil { + log.Warnf("Unable to initialise exchange currency pair syncer. Err: %s", err) + } else { + go e.ExchangeCurrencyPairManager.Start() + } } - /* - if e.Settings.EnableOrderbookRoutine { - go OrderbookUpdaterRoutine() - } - - if e.Settings.EnableWebsocketRoutine { - go WebsocketRoutine() - } - */ + if e.Settings.EnableOrderManager { + go StartOrderManagerRoutine() + } if e.Settings.EnableEventManager { go events.EventManger() @@ -428,9 +413,14 @@ func (e *Engine) Stop() { e.Config.Portfolio = portfolio.Portfolio } + if e.PortfolioManager.Started() { + if err := e.PortfolioManager.Stop(); err != nil { + log.Errorf("Fund manager unable to stop. Error: %v", err) + } + } + if !e.Settings.EnableDryRun { err := e.Config.SaveConfig(e.Settings.ConfigFile) - if err != nil { log.Error("Unable to save config.") } else { diff --git a/engine/engine_types.go b/engine/engine_types.go index ca80ba22..32728c93 100644 --- a/engine/engine_types.go +++ b/engine/engine_types.go @@ -14,17 +14,20 @@ type Settings struct { EnableAllExchanges bool EnableAllPairs bool EnableCoinmarketcapAnalysis bool - EnablePortfolioWatcher bool + EnablePortfolioManager bool EnableGRPC bool EnableGRPCProxy bool EnableWebsocketRPC bool EnableDeprecatedRPC bool - EnableTickerRoutine bool - EnableOrderbookRoutine bool - EnableWebsocketRoutine bool EnableCommsRelayer bool + EnableExchangeSyncManager bool + EnableTickerSyncing bool + EnableOrderbookSyncing bool EnableEventManager bool + EnableOrderManager bool + EnableConnectivityMonitor bool EnableNTPClient bool + EnableWebsocketRoutine bool EventManagerDelay time.Duration Verbose bool diff --git a/engine/exchange.go b/engine/exchange.go index 55d66781..92b7aa50 100644 --- a/engine/exchange.go +++ b/engine/exchange.go @@ -269,8 +269,9 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { // SetupExchanges sets up the exchanges used by the Bot func SetupExchanges() { var wg sync.WaitGroup - for x := range Bot.Config.Exchanges { - exch := &Bot.Config.Exchanges[x] + exchanges := Bot.Config.GetAllExchangeConfigs() + for x := range exchanges { + exch := exchanges[x] if CheckExchangeExists(exch.Name) { e := GetExchangeByName(exch.Name) if e == nil { diff --git a/engine/helpers.go b/engine/helpers.go index 10ef93ff..07c8b863 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -26,6 +26,14 @@ import ( "github.com/thrasher-/gocryptotrader/utils" ) +// IsOnline returns whether or not the engine has Internet connectivity +func IsOnline() bool { + if Bot.Connectivity == nil { + log.Warnf("IsOnline called but Bot.Connectivity is nil") + } + return Bot.Connectivity.IsConnected() +} + // GetAvailableExchanges returns a list of enabled exchanges func GetAvailableExchanges() []string { var enExchanges []string diff --git a/engine/portfolio.go b/engine/portfolio.go new file mode 100644 index 00000000..36d613d7 --- /dev/null +++ b/engine/portfolio.go @@ -0,0 +1,81 @@ +package engine + +import ( + "errors" + "sync/atomic" + "time" + + log "github.com/thrasher-/gocryptotrader/logger" + "github.com/thrasher-/gocryptotrader/portfolio" +) + +// vars for the fund manager package +var ( + PortfolioSleepDelay = time.Minute +) + +type portfolioManager struct { + started int32 + stopped int32 + shutdown chan struct{} +} + +func (p *portfolioManager) Started() bool { + return atomic.LoadInt32(&p.started) == 1 +} + +func (p *portfolioManager) Start() error { + if atomic.AddInt32(&p.started, 1) != 1 { + return errors.New("portfolio manager already started") + } + + log.Debugln("Portfolio manager starting...") + Bot.Portfolio = &portfolio.Portfolio + Bot.Portfolio.Seed(Bot.Config.Portfolio) + p.shutdown = make(chan struct{}) + go p.run() + return nil +} +func (p *portfolioManager) Stop() error { + if atomic.AddInt32(&p.stopped, 1) != 1 { + return errors.New("portfolio manager is already stopped") + } + + log.Debugln("Portfolio manager shutting down...") + close(p.shutdown) + return nil +} + +func (p *portfolioManager) run() { + log.Debugln("Portfolio manager started.") + tick := time.NewTicker(PortfolioSleepDelay) + defer func() { + log.Debugf("Portfolio manager shutdown.") + tick.Stop() + }() + + for { + select { + case <-p.shutdown: + return + + case <-tick.C: + p.processPortfolio() + } + } +} + +func (p *portfolioManager) processPortfolio() { + pf := portfolio.GetPortfolio() + data := pf.GetPortfolioGroupedCoin() + for key, value := range data { + success := pf.UpdatePortfolio(value, key) + if success { + log.Debugf( + "Portfolio manager: Successfully updated address balance for %s address(es) %s\n", + key, value, + ) + } + } + SeedExchangeAccountInfo(GetAllEnabledExchangeAccountInfo().Data) +} diff --git a/engine/restful_server.go b/engine/restful_server.go index cb25f7bb..6139d3c0 100644 --- a/engine/restful_server.go +++ b/engine/restful_server.go @@ -6,6 +6,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" log "github.com/thrasher-/gocryptotrader/logger" + "github.com/thrasher-/gocryptotrader/portfolio" ) // RESTfulJSONResponse outputs a JSON response of the response interface @@ -100,7 +101,8 @@ func RESTGetAllActiveOrderbooks(w http.ResponseWriter, r *http.Request) { // RESTGetPortfolio returns the Bot portfolio func RESTGetPortfolio(w http.ResponseWriter, r *http.Request) { - result := Bot.Portfolio.GetPortfolioSummary() + p := portfolio.GetPortfolio() + result := p.GetPortfolioSummary() err := RESTfulJSONResponse(w, result) if err != nil { RESTfulError(r.Method, err) diff --git a/engine/rpcserver.go b/engine/rpcserver.go index a20e7671..5131021f 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -466,14 +466,14 @@ func (s *RPCServer) GetPortfolioSummary(ctx context.Context, r *gctrpc.GetPortfo // AddPortfolioAddress adds an address to the portfolio manager func (s *RPCServer) AddPortfolioAddress(ctx context.Context, r *gctrpc.AddPortfolioAddressRequest) (*gctrpc.AddPortfolioAddressResponse, error) { - Bot.Portfolio.AddAddress(r.Address, r.Description, currency.NewCode(r.CoinType), r.Balance) - return &gctrpc.AddPortfolioAddressResponse{}, nil + err := Bot.Portfolio.AddAddress(r.Address, r.Description, currency.NewCode(r.CoinType), r.Balance) + return &gctrpc.AddPortfolioAddressResponse{}, err } // RemovePortfolioAddress removes an address from the portfolio manager func (s *RPCServer) RemovePortfolioAddress(ctx context.Context, r *gctrpc.RemovePortfolioAddressRequest) (*gctrpc.RemovePortfolioAddressResponse, error) { - Bot.Portfolio.RemoveAddress(r.Address, r.Description, currency.NewCode(r.CoinType)) - return &gctrpc.RemovePortfolioAddressResponse{}, nil + err := Bot.Portfolio.RemoveAddress(r.Address, r.Description, currency.NewCode(r.CoinType)) + return &gctrpc.RemovePortfolioAddressResponse{}, err } // GetForexProviders returns a list of available forex providers diff --git a/main.go b/main.go index a30aa448..1ce4b8fd 100644 --- a/main.go +++ b/main.go @@ -32,18 +32,21 @@ func main() { flag.BoolVar(&settings.EnableDryRun, "dryrun", false, "dry runs bot, doesn't save config file") flag.BoolVar(&settings.EnableAllExchanges, "enableallexchanges", false, "enables all exchanges") flag.BoolVar(&settings.EnableAllPairs, "enableallpairs", false, "enables all pairs for enabled exchanges") - flag.BoolVar(&settings.EnablePortfolioWatcher, "portfoliowatcher", true, "enables the portfolio watcher") + flag.BoolVar(&settings.EnablePortfolioManager, "portfoliomanager", true, "enables the portfolio manager") flag.BoolVar(&settings.EnableGRPC, "grpc", true, "enables the grpc server") flag.BoolVar(&settings.EnableGRPCProxy, "grpcproxy", true, "enables the grpc proxy server") flag.BoolVar(&settings.EnableWebsocketRPC, "websocketrpc", true, "enables the websocket RPC server") flag.BoolVar(&settings.EnableDeprecatedRPC, "deprecatedrpc", true, "enables the deprecated RPC server") flag.BoolVar(&settings.EnableCommsRelayer, "enablecommsrelayer", true, "enables available communications relayer") flag.BoolVar(&settings.Verbose, "verbose", false, "increases logging verbosity for GoCryptoTrader") - flag.BoolVar(&settings.EnableTickerRoutine, "tickerroutine", true, "enables the ticker routine for all loaded exchanges") - flag.BoolVar(&settings.EnableOrderbookRoutine, "orderbookroutine", true, "enables the orderbook routine for all loaded exchanges") + flag.BoolVar(&settings.EnableExchangeSyncManager, "syncmanager", true, "enables to exchange sync manager") + flag.BoolVar(&settings.EnableTickerSyncing, "tickersync", true, "enables ticker syncing for all enabled exchanges") + flag.BoolVar(&settings.EnableOrderbookSyncing, "orderbooksync", true, "enables orderbook syncing for all enabled exchanges") flag.BoolVar(&settings.EnableWebsocketRoutine, "websocketroutine", true, "enables the websocket routine for all loaded exchanges") flag.BoolVar(&settings.EnableCoinmarketcapAnalysis, "coinmarketcap", false, "overrides config and runs currency analysis") - flag.BoolVar(&settings.EnableEventManager, "enableeventmanager", true, "enables the event manager") + flag.BoolVar(&settings.EnableEventManager, "eventmanager", true, "enables the event manager") + flag.BoolVar(&settings.EnableOrderManager, "ordermanager", true, "enables the order manager") + flag.BoolVar(&settings.EnableConnectivityMonitor, "connectivitymonitor", true, "enables the connectivity monitor") flag.DurationVar(&settings.EventManagerDelay, "eventmanagerdelay", time.Duration(0), "sets the event managers sleep delay between event checking") flag.BoolVar(&settings.EnableNTPClient, "ntpclient", true, "enables the NTP client to check system clock drift") diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index c6751422..e8862592 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -149,10 +149,17 @@ func (p *Base) UpdateExchangeAddressBalance(exchangeName string, coinType curren } // AddAddress adds an address to the portfolio base -func (p *Base) AddAddress(address, description string, coinType currency.Code, balance float64) { +func (p *Base) AddAddress(address, description string, coinType currency.Code, balance float64) error { + if address == "" { + return errors.New("address is empty") + } + + if coinType.String() == "" { + return errors.New("coin type is empty") + } + if description == PortfolioAddressExchange { p.AddExchangeAddress(address, coinType, balance) - return } if !p.AddressExists(address) { p.Addresses = append( @@ -166,19 +173,30 @@ func (p *Base) AddAddress(address, description string, coinType currency.Code, b p.UpdateAddressBalance(address, balance) } } + return nil } // RemoveAddress removes an address when checked against the correct address and // coinType -func (p *Base) RemoveAddress(address, description string, coinType currency.Code) { +func (p *Base) RemoveAddress(address, description string, coinType currency.Code) error { + if address == "" { + return errors.New("address is empty") + } + + if coinType.String() == "" { + return errors.New("coin type is empty") + } + for x := range p.Addresses { if p.Addresses[x].Address == address && p.Addresses[x].CoinType == coinType && p.Addresses[x].Description == description { p.Addresses = append(p.Addresses[:x], p.Addresses[x+1:]...) - return + return nil } } + + return errors.New("portfolio item does not exist") } // UpdatePortfolio adds to the portfolio addresses by coin type diff --git a/portfolio/portfolio_test.go b/portfolio/portfolio_test.go index 225010ba..2d69a267 100644 --- a/portfolio/portfolio_test.go +++ b/portfolio/portfolio_test.go @@ -138,7 +138,19 @@ func TestUpdateAddressBalance(t *testing.T) { } func TestRemoveAddress(t *testing.T) { - newbase := Base{} + var newbase Base + if err := newbase.RemoveAddress("", "MEOW", currency.LTC); err == nil { + t.Error("invalid address should throw an error") + } + + if err := newbase.RemoveAddress("Gibson", "", currency.NewCode("")); err == nil { + t.Error("invalid coin type should throw an error") + } + + if err := newbase.RemoveAddress("HIDDENERINO", "MEOW", currency.LTC); err == nil { + t.Error("non-existent address should throw an error") + } + newbase.AddAddress("someaddr", currency.LTC.String(), currency.NewCode("LTCWALLETTEST"), @@ -187,12 +199,36 @@ func TestUpdateExchangeAddressBalance(t *testing.T) { } func TestAddAddress(t *testing.T) { - newbase := Base{} + var newbase Base + if err := newbase.AddAddress("", "MEOW", currency.LTC, 1); err == nil { + t.Error("invalid address should throw an error") + } + + if err := newbase.AddAddress("Gibson", "", currency.NewCode(""), 1); err == nil { + t.Error("invalid coin type should throw an error") + } + + // test adding an exchange address + err := newbase.AddAddress("COINUT", PortfolioAddressExchange, currency.LTC, 0) + if err != nil { + t.Errorf("failed to add address: %v", err) + } + + // add a test portfolio address and amount newbase.AddAddress("Gibson", currency.LTC.String(), currency.NewCode("LTCWALLETTEST"), 0.02) + // test updating the balance and make sure it's reflected + newbase.AddAddress("Gibson", currency.LTC.String(), + currency.NewCode("LTCWALLETTEST"), 0.05) + b, _ := newbase.GetAddressBalance("Gibson", "LTC", + currency.NewCode("LTCWALLETTEST")) + if b != 0.05 { + t.Error("invalid portfolio amount") + } + portfolio := GetPortfolio() portfolio.Seed(newbase) if !portfolio.AddressExists("Gibson") { From cbd3e7bacd779dc63df8982a56bbecf854d3c99c Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 7 Jun 2019 17:52:53 +1000 Subject: [PATCH 06/71] Order manager changes --- common/common.go | 6 + engine/engine.go | 27 +++-- engine/helpers.go | 12 ++ engine/orders.go | 130 +++++++++++++++++---- exchanges/btcmarkets/btcmarkets_wrapper.go | 3 +- go.mod | 12 +- go.sum | 31 +++-- 7 files changed, 169 insertions(+), 52 deletions(-) diff --git a/common/common.go b/common/common.go index 595c91bb..189490b9 100644 --- a/common/common.go +++ b/common/common.go @@ -18,6 +18,7 @@ import ( "strings" "time" + uuid "github.com/satori/go.uuid" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -42,6 +43,11 @@ const ( WeiPerEther = 1000000000000000000 ) +// GetV4UUID returns a RFC 4122 UUID based on random numbers +func GetV4UUID() uuid.UUID { + return uuid.NewV4() +} + func initialiseHTTPClient() { // If the HTTPClient isn't set, start a new client with a default timeout of 15 seconds if HTTPClient == nil { diff --git a/engine/engine.go b/engine/engine.go index 74018480..28f92b5b 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -32,11 +32,11 @@ type Engine struct { Portfolio *portfolio.Base Exchanges []exchange.IBotExchange ExchangeCurrencyPairManager *ExchangeCurrencyPairSyncer - OrderManager *OrderManager + OrderManager orderManager PortfolioManager portfolioManager CommsRelayer *communications.Communications Connectivity *connchecker.Checker - Shutdown chan bool + Shutdown chan struct{} Settings Settings CryptocurrencyDepositAddresses map[string]map[string]string Uptime time.Time @@ -157,6 +157,7 @@ func ValidateSettings(b *Engine, s *Settings) { b.Settings.EnableConnectivityMonitor = s.EnableConnectivityMonitor b.Settings.EnableNTPClient = s.EnableNTPClient + b.Settings.EnableOrderManager = s.EnableOrderManager b.Settings.EnableExchangeSyncManager = s.EnableExchangeSyncManager b.Settings.EnableTickerSyncing = s.EnableTickerSyncing b.Settings.EnableOrderbookSyncing = s.EnableOrderbookSyncing @@ -219,7 +220,7 @@ func PrintSettings(s *Settings) { log.Debugf("\t Enable all exchanges: %v", s.EnableAllExchanges) log.Debugf("\t Enable all pairs: %v", s.EnableAllPairs) log.Debugf("\t Enable coinmarketcap analaysis: %v", s.EnableCoinmarketcapAnalysis) - log.Debugf("\t Enable portfolio watcher: %v", s.EnablePortfolioManager) + log.Debugf("\t Enable portfolio manager: %v", s.EnablePortfolioManager) log.Debugf("\t Enable gPRC: %v", s.EnableGRPC) log.Debugf("\t Enable gRPC Proxy: %v", s.EnableGRPCProxy) log.Debugf("\t Enable websocket RPC: %v", s.EnableWebsocketRPC) @@ -377,6 +378,12 @@ func (e *Engine) Start() { } } + if e.Settings.EnableOrderManager { + if err = e.OrderManager.Start(); err != nil { + log.Errorf("Order manager unable to start: %v", err) + } + } + if e.Settings.EnableExchangeSyncManager { exchangeSyncCfg := CurrencyPairSyncerConfig{ SyncTicker: e.Settings.EnableTickerSyncing, @@ -393,10 +400,6 @@ func (e *Engine) Start() { } } - if e.Settings.EnableOrderManager { - go StartOrderManagerRoutine() - } - if e.Settings.EnableEventManager { go events.EventManger() } @@ -413,6 +416,12 @@ func (e *Engine) Stop() { e.Config.Portfolio = portfolio.Portfolio } + if e.OrderManager.Started() { + if err := e.OrderManager.Stop(); err != nil { + log.Errorf("Order manager unable to stop. Error: %v", err) + } + } + if e.PortfolioManager.Started() { if err := e.PortfolioManager.Stop(); err != nil { log.Errorf("Fund manager unable to stop. Error: %v", err) @@ -436,11 +445,11 @@ func (e *Engine) Stop() { // shuts down the engine instance func (e *Engine) handleInterrupt() { c := make(chan os.Signal, 1) - e.Shutdown = make(chan bool) + e.Shutdown = make(chan struct{}) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { sig := <-c log.Debugf("Captured %v, shutdown requested.", sig) - e.Shutdown <- true + close(e.Shutdown) }() } diff --git a/engine/helpers.go b/engine/helpers.go index 07c8b863..826dab3e 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -26,6 +26,18 @@ import ( "github.com/thrasher-/gocryptotrader/utils" ) +// GetAuthAPISupportedExchanges returns a list of auth api enabled exchanges +func GetAuthAPISupportedExchanges() []string { + var exchanges []string + for x := range Bot.Exchanges { + if !Bot.Exchanges[x].GetAuthenticatedAPISupport() { + continue + } + exchanges = append(exchanges, Bot.Exchanges[x].GetName()) + } + return exchanges +} + // IsOnline returns whether or not the engine has Internet connectivity func IsOnline() bool { if Bot.Connectivity == nil { diff --git a/engine/orders.go b/engine/orders.go index b56e37ad..60fc3bae 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -1,47 +1,131 @@ package engine import ( + "errors" "sync" + "sync/atomic" "time" exchange "github.com/thrasher-/gocryptotrader/exchanges" log "github.com/thrasher-/gocryptotrader/logger" ) -// OrderManager manages orders for all enabled exchanges -type OrderManager struct { +// vars for the fund manager package +var ( + OrderManagerDelay = time.Second * 10 + ErrOrdersAlreadyExists = errors.New("order already exists") +) + +type orderStore struct { m sync.Mutex Orders map[string][]exchange.OrderDetail } -func (o *OrderManager) add() { +func (o *orderStore) exists(order *exchange.OrderDetail) bool { + r, ok := o.Orders[order.Exchange] + if !ok { + return false + } + + for x := range r { + if r[x].ID == order.ID { + return true + } + } + + return false +} + +func (o *orderStore) Add(order *exchange.OrderDetail) error { o.m.Lock() defer o.m.Unlock() + + if o.exists(order) { + return ErrOrdersAlreadyExists + } + + orders := o.Orders[order.Exchange] + orders = append(orders, *order) + o.Orders[order.Exchange] = orders + return nil } -// StartOrderManagerRoutine starts the orderbook manage routine -func StartOrderManagerRoutine() { - log.Debugln("Starting order manager routine") - if Bot.OrderManager == nil { - Bot.OrderManager = new(OrderManager) +type orderManager struct { + started int32 + stopped int32 + shutdown chan struct{} + orderStore orderStore +} + +func (o *orderManager) Started() bool { + return atomic.LoadInt32(&o.started) == 1 +} + +func (o *orderManager) Start() error { + if atomic.AddInt32(&o.started, 1) != 1 { + return errors.New("order manager already started") } + log.Debugln("Order manager starting...") + o.shutdown = make(chan struct{}) + o.orderStore.Orders = make(map[string][]exchange.OrderDetail) + go o.run() + return nil +} +func (o *orderManager) Stop() error { + if atomic.AddInt32(&o.stopped, 1) != 1 { + return errors.New("order manager is already stopped") + } + + log.Debugln("Order manager shutting down...") + close(o.shutdown) + return nil +} + +func (o *orderManager) run() { + log.Debugln("Order manager started.") + tick := time.NewTicker(OrderManagerDelay) + defer func() { + log.Debugf("Order manager shutdown.") + tick.Stop() + }() + for { - for x := range Bot.Exchanges { - if !Bot.Exchanges[x].IsEnabled() || !Bot.Exchanges[x].GetAuthenticatedAPISupport() { - continue - } - exchName := Bot.Exchanges[x].GetName() - log.Printf("Getting active orders for %s", exchName) - - orders, err := Bot.Exchanges[x].GetActiveOrders(&exchange.GetOrdersRequest{}) - if err != nil { - log.Printf("Get active orders failed: %s", err) - continue - } - - log.Printf("Orders for exchange %s: %v", exchName, orders) + select { + case <-o.shutdown: + return + case <-tick.C: + o.processOrders() + } + } +} + +func (o *orderManager) Cancel() {} + +func (o *orderManager) Place() {} + +func (o *orderManager) processOrders() { + authExchanges := GetAuthAPISupportedExchanges() + for x := range authExchanges { + log.Debugf("Order manager: Procesing orders for exchange %v.", authExchanges[x]) + exch := GetExchangeByName(authExchanges[x]) + req := exchange.GetOrdersRequest{ + OrderSide: exchange.AnyOrderSide, + OrderType: exchange.AnyOrderType, + } + result, err := exch.GetActiveOrders(&req) + if err != nil { + log.Debugf("Order manager: Unable to get active orders: %s", err) + continue + } + + for x := range result { + order := &result[x] + result := o.orderStore.Add(order) + if result != ErrOrdersAlreadyExists { + log.Debugf("Order manager: Exchange %s added order ID=%v pair=%v price=%v amount=%v side=%v type=%v.", + order.Exchange, order.ID, order.CurrencyPair, order.Price, order.Amount, order.OrderSide, order.OrderType) + } } - time.Sleep(time.Second * 1) } } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index eba69d8a..2cc4b659 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -458,8 +458,7 @@ func (b *BTCMarkets) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest Price: resp[i].Price, Status: resp[i].Status, CurrencyPair: currency.NewPairWithDelimiter(resp[i].Instrument, - resp[i].Currency, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + resp[i].Currency, "-"), } for j := range resp[i].Trades { diff --git a/go.mod b/go.mod index 81f659b2..c814a465 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,17 @@ module github.com/thrasher-/gocryptotrader go 1.12 require ( - github.com/boombuler/barcode v1.0.0 // indirect github.com/golang/protobuf v1.3.1 github.com/google/go-querystring v1.0.0 github.com/gorilla/mux v1.7.2 github.com/gorilla/websocket v1.4.0 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/grpc-ecosystem/grpc-gateway v1.9.0 - github.com/pquerna/otp v1.1.0 + github.com/pquerna/otp v1.2.0 + github.com/satori/go.uuid v1.2.0 github.com/urfave/cli v1.20.0 - golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f - golang.org/x/net v0.0.0-20190520210107-018c4d40a106 - google.golang.org/genproto v0.0.0-20190516172635-bb713bdc0e52 - google.golang.org/grpc v1.20.1 + golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 + golang.org/x/net v0.0.0-20190606173856-1492cefac77f + google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6 + google.golang.org/grpc v1.21.1 ) diff --git a/go.sum b/go.sum index ed94e9f4..a729b15d 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,16 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= @@ -22,14 +24,19 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pquerna/otp v1.1.0 h1:q2gMsMuMl3JzneUaAX1MRGxLvOG6bzXV51hivBaStf0= -github.com/pquerna/otp v1.1.0/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= +github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo= -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -40,8 +47,8 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190520210107-018c4d40a106 h1:EZofHp/BzEf3j39/+7CX1JvH0WaPG+ikBrqAdAPf+GM= -golang.org/x/net v0.0.0-20190520210107-018c4d40a106/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190606173856-1492cefac77f h1:IWHgpgFqnL5AhBUBZSgBdjl2vkQUEzcY+JNKWfcgAU0= +golang.org/x/net v0.0.0-20190606173856-1492cefac77f/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -57,11 +64,11 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190516172635-bb713bdc0e52 h1:LHc/6x2dMeCKkSsrVgo4DY+Z566T1OeoMwLtdfoy8LE= -google.golang.org/genproto v0.0.0-20190516172635-bb713bdc0e52/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6 h1:XRqWpmQ5ACYxWuYX495S0sHawhPGOVrh62WzgXsQnWs= +google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= From 04c7c4895f2ec9bd82ee11e87fab8960338ffd87 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 7 Jun 2019 20:52:44 +1000 Subject: [PATCH 07/71] Split common package more and QA --- cmd/config/config.go | 3 +- cmd/huobi_auth/main.go | 5 +- common/common.go | 50 ++------------ common/common_test.go | 115 ++----------------------------- common/convert/convert.go | 24 +++++++ common/convert/convert_test.go | 55 +++++++++++++++ config/config.go | 24 ++++--- config/config_encryption_test.go | 5 +- config/config_test.go | 3 +- config/config_types.go | 10 +-- currency/storage.go | 3 +- exchanges/binance/binance.go | 3 +- exchanges/exchange.go | 3 + exchanges/zb/zb.go | 2 +- go.mod | 3 +- go.sum | 20 +++++- 16 files changed, 148 insertions(+), 180 deletions(-) diff --git a/cmd/config/config.go b/cmd/config/config.go index 2271c185..daa8613e 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -2,6 +2,7 @@ package main import ( "flag" + "io/ioutil" "log" "github.com/thrasher-/gocryptotrader/common" @@ -42,7 +43,7 @@ func main() { key = string(result) } - file, err := common.ReadFile(inFile) + file, err := ioutil.ReadFile(inFile) if err != nil { log.Fatalf("Unable to read input file %s. Error: %s.", inFile, err) } diff --git a/cmd/huobi_auth/main.go b/cmd/huobi_auth/main.go index d0a82a87..1ffa60d0 100644 --- a/cmd/huobi_auth/main.go +++ b/cmd/huobi_auth/main.go @@ -10,6 +10,7 @@ import ( "encoding/pem" "errors" "fmt" + "io/ioutil" "log" "github.com/thrasher-/gocryptotrader/common" @@ -59,7 +60,7 @@ func writeFile(file string, data []byte) error { func main() { genKeys := false - privKeyData, err := common.ReadFile("privatekey.pem") + privKeyData, err := ioutil.ReadFile("privatekey.pem") if err != nil { genKeys = true } @@ -100,7 +101,7 @@ func main() { } else { var pubKeyData []byte - pubKeyData, err = common.ReadFile("publickey.pem") + pubKeyData, err = ioutil.ReadFile("publickey.pem") if err != nil { log.Fatal(err) } diff --git a/common/common.go b/common/common.go index 189490b9..67e2e05c 100644 --- a/common/common.go +++ b/common/common.go @@ -44,7 +44,7 @@ const ( ) // GetV4UUID returns a RFC 4122 UUID based on random numbers -func GetV4UUID() uuid.UUID { +func GetV4UUID() (uuid.UUID, error) { return uuid.NewV4() } @@ -277,7 +277,7 @@ func ExtractPort(host string) int { // OutputCSV dumps data into a file as comma-separated values func OutputCSV(filePath string, data [][]string) error { - _, err := ReadFile(filePath) + _, err := ioutil.ReadFile(filePath) if err != nil { errTwo := WriteFile(filePath, nil) if errTwo != nil { @@ -289,37 +289,10 @@ func OutputCSV(filePath string, data [][]string) error { if err != nil { return err } + defer file.Close() writer := csv.NewWriter(file) - - err = writer.WriteAll(data) - if err != nil { - return err - } - - writer.Flush() - file.Close() - return nil -} - -// UnixTimestampToTime returns time.time -func UnixTimestampToTime(timeint64 int64) time.Time { - return time.Unix(timeint64, 0) -} - -// UnixTimestampStrToTime returns a time.time and an error -func UnixTimestampStrToTime(timeStr string) (time.Time, error) { - i, err := strconv.ParseInt(timeStr, 10, 64) - if err != nil { - return time.Time{}, err - } - - return time.Unix(i, 0), nil -} - -// ReadFile reads a file and returns read data as byte array. -func ReadFile(file string) ([]byte, error) { - return ioutil.ReadFile(file) + return writer.WriteAll(data) } // WriteFile writes selected data to a file and returns an error @@ -327,11 +300,6 @@ func WriteFile(file string, data []byte) error { return ioutil.WriteFile(file, data, 0644) } -// RemoveFile removes a file -func RemoveFile(file string) error { - return os.Remove(file) -} - // GetURIPath returns the path of a URL given a URI func GetURIPath(uri string) string { urip, err := url.Parse(uri) @@ -353,16 +321,6 @@ func GetExecutablePath() (string, error) { return filepath.Dir(ex), nil } -// UnixMillis converts a UnixNano timestamp to milliseconds -func UnixMillis(t time.Time) int64 { - return t.UnixNano() / int64(time.Millisecond) -} - -// RecvWindow converts a supplied time.Duration to milliseconds -func RecvWindow(d time.Duration) int64 { - return int64(d) / int64(time.Millisecond) -} - // GetDefaultDataDir returns the default data directory // Windows - C:\Users\%USER%\AppData\Roaming\GoCryptoTrader // Linux/Unix or OSX - $HOME/.gocryptotrader diff --git a/common/common_test.go b/common/common_test.go index 97dc144c..41f7e35d 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -9,7 +9,6 @@ import ( "runtime" "strings" "testing" - "time" ) func TestIsEnabled(t *testing.T) { @@ -29,7 +28,6 @@ func TestIsEnabled(t *testing.T) { func TestIsValidCryptoAddress(t *testing.T) { t.Parallel() - b, err := IsValidCryptoAddress("1Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX", "bTC") if err != nil && !b { t.Errorf("Test Failed - Common IsValidCryptoAddress error: %s", err) @@ -229,6 +227,7 @@ func TestSendHTTPRequest(t *testing.T) { } func TestSendHTTPGetRequest(t *testing.T) { + t.Parallel() type test struct { Address string `json:"address"` ETH struct { @@ -265,6 +264,7 @@ func TestSendHTTPGetRequest(t *testing.T) { } func TestJSONEncode(t *testing.T) { + t.Parallel() type test struct { Status int `json:"status"` Data []struct { @@ -320,6 +320,7 @@ func TestJSONDecode(t *testing.T) { } func TestEncodeURLValues(t *testing.T) { + t.Parallel() urlstring := "https://www.test.com" expectedOutput := `https://www.test.com?env=TEST%2FDATABASE&format=json` values := url.Values{} @@ -385,84 +386,6 @@ func TestOutputCSV(t *testing.T) { } } -func TestUnixTimestampToTime(t *testing.T) { - t.Parallel() - testTime := int64(1489439831) - tm := time.Unix(testTime, 0) - expectedOutput := "2017-03-13 21:17:11 +0000 UTC" - actualResult := UnixTimestampToTime(testTime) - if tm.String() != actualResult.String() { - t.Errorf( - "Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult) - } -} - -func TestUnixTimestampStrToTime(t *testing.T) { - t.Parallel() - testTime := "1489439831" - incorrectTime := "DINGDONG" - expectedOutput := "2017-03-13 21:17:11 +0000 UTC" - actualResult, err := UnixTimestampStrToTime(testTime) - if err != nil { - t.Error(err) - } - if actualResult.UTC().String() != expectedOutput { - t.Errorf( - "Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult) - } - actualResult, err = UnixTimestampStrToTime(incorrectTime) - if err == nil { - t.Error("Test failed. Common UnixTimestampStrToTime error") - } -} - -func TestReadFile(t *testing.T) { - pathCorrect := "../testdata/dump" - pathIncorrect := "testdata/dump" - - _, err := ReadFile(pathCorrect) - if err != nil { - t.Errorf("Test failed - Common ReadFile error: %s", err) - } - _, err = ReadFile(pathIncorrect) - if err == nil { - t.Errorf("Test failed - Common ReadFile error") - } -} - -func TestWriteFile(t *testing.T) { - path := "../testdata/writefiletest" - err := WriteFile(path, nil) - if err != nil { - t.Errorf("Test failed. Common WriteFile error: %s", err) - } - _, err = ReadFile(path) - 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) { - TestWriteFile(t) - path := "../testdata/writefiletest" - err := RemoveFile(path) - if err != nil { - t.Errorf("Test failed. Common RemoveFile error: %s", err) - } - - TestOutputCSV(t) - path = "../testdata/dump" - err = RemoveFile(path) - if err != nil { - t.Errorf("Test failed. Common RemoveFile error: %s", err) - } -} - func TestGetURIPath(t *testing.T) { t.Parallel() // mapping of input vs expected result @@ -488,30 +411,6 @@ func TestGetExecutablePath(t *testing.T) { } } -func TestUnixMillis(t *testing.T) { - t.Parallel() - testTime := time.Date(2014, time.October, 28, 0, 32, 0, 0, time.UTC) - expectedOutput := int64(1414456320000) - - actualOutput := UnixMillis(testTime) - if actualOutput != expectedOutput { - t.Errorf("Test failed. Common UnixMillis. Expected '%d'. Actual '%d'.", - expectedOutput, actualOutput) - } -} - -func TestRecvWindow(t *testing.T) { - t.Parallel() - testTime := time.Duration(24760000) - expectedOutput := int64(24) - - actualOutput := RecvWindow(testTime) - if actualOutput != expectedOutput { - t.Errorf("Test failed. Common RecvWindow. Expected '%d'. Actual '%d'", - expectedOutput, actualOutput) - } -} - func TestGetDefaultDataDir(t *testing.T) { switch runtime.GOOS { case "windows": @@ -625,9 +524,9 @@ func TestChangePerm(t *testing.T) { if err != nil { t.Fatalf("os.Stat failed. Err: %v", err) } - err = RemoveFile(testDir) + err = os.Remove(testDir) if err != nil { - t.Fatalf("RemoveFile failed. Err: %v", err) + t.Fatalf("os.Remove failed. Err: %v", err) } default: err := ChangePerm("") @@ -650,9 +549,9 @@ func TestChangePerm(t *testing.T) { if a.Mode().Perm() != 0770 { t.Fatalf("expected file permissions differ. expecting 0770 got %#o", a.Mode().Perm()) } - err = RemoveFile(testDir) + err = os.Remove(testDir) if err != nil { - t.Fatalf("RemoveFile failed. Err: %v", err) + t.Fatalf("os.Remove failed. Err: %v", err) } } } diff --git a/common/convert/convert.go b/common/convert/convert.go index 3fe3f96c..5bf7e740 100644 --- a/common/convert/convert.go +++ b/common/convert/convert.go @@ -53,3 +53,27 @@ func TimeFromUnixTimestampFloat(raw interface{}) (time.Time, error) { } return time.Unix(0, int64(ts)*int64(time.Millisecond)), nil } + +// UnixTimestampToTime returns time.time +func UnixTimestampToTime(timeint64 int64) time.Time { + return time.Unix(timeint64, 0) +} + +// UnixTimestampStrToTime returns a time.time and an error +func UnixTimestampStrToTime(timeStr string) (time.Time, error) { + i, err := strconv.ParseInt(timeStr, 10, 64) + if err != nil { + return time.Time{}, err + } + return time.Unix(i, 0), nil +} + +// UnixMillis converts a UnixNano timestamp to milliseconds +func UnixMillis(t time.Time) int64 { + return t.UnixNano() / int64(time.Millisecond) +} + +// RecvWindow converts a supplied time.Duration to milliseconds +func RecvWindow(d time.Duration) int64 { + return int64(d) / int64(time.Millisecond) +} diff --git a/common/convert/convert_test.go b/common/convert/convert_test.go index b6dd1f90..e4fe11a5 100644 --- a/common/convert/convert_test.go +++ b/common/convert/convert_test.go @@ -94,3 +94,58 @@ func TestTimeFromUnixTimestampFloat(t *testing.T) { t.Error("Test failed. Common TimeFromUnixTimestampFloat. Converted invalid syntax.") } } + +func TestUnixTimestampToTime(t *testing.T) { + t.Parallel() + testTime := int64(1489439831) + tm := time.Unix(testTime, 0) + expectedOutput := "2017-03-13 21:17:11 +0000 UTC" + actualResult := UnixTimestampToTime(testTime) + if tm.String() != actualResult.String() { + t.Errorf( + "Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult) + } +} + +func TestUnixTimestampStrToTime(t *testing.T) { + t.Parallel() + testTime := "1489439831" + incorrectTime := "DINGDONG" + expectedOutput := "2017-03-13 21:17:11 +0000 UTC" + actualResult, err := UnixTimestampStrToTime(testTime) + if err != nil { + t.Error(err) + } + if actualResult.UTC().String() != expectedOutput { + t.Errorf( + "Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult) + } + actualResult, err = UnixTimestampStrToTime(incorrectTime) + if err == nil { + t.Error("Test failed. Common UnixTimestampStrToTime error") + } +} + +func TestUnixMillis(t *testing.T) { + t.Parallel() + testTime := time.Date(2014, time.October, 28, 0, 32, 0, 0, time.UTC) + expectedOutput := int64(1414456320000) + + actualOutput := UnixMillis(testTime) + if actualOutput != expectedOutput { + t.Errorf("Test failed. Common UnixMillis. Expected '%d'. Actual '%d'.", + expectedOutput, actualOutput) + } +} + +func TestRecvWindow(t *testing.T) { + t.Parallel() + testTime := time.Duration(24760000) + expectedOutput := int64(24) + + actualOutput := RecvWindow(testTime) + if actualOutput != expectedOutput { + t.Errorf("Test failed. Common RecvWindow. Expected '%d'. Actual '%d'", + expectedOutput, actualOutput) + } +} diff --git a/config/config.go b/config/config.go index e8068c58..51ef8545 100644 --- a/config/config.go +++ b/config/config.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "io" + "io/ioutil" "os" "path/filepath" "runtime" @@ -16,6 +17,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/convert" "github.com/thrasher-/gocryptotrader/connchecker" "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/forexprovider" @@ -947,7 +949,7 @@ func (c *Config) CheckExchangeConfigValues() error { } } if !c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates && !c.Exchanges[i].Features.Supports.WebsocketCapabilities.AutoPairUpdates { - lastUpdated := common.UnixTimestampToTime(c.Exchanges[i].CurrencyPairs.LastUpdated) + lastUpdated := convert.UnixTimestampToTime(c.Exchanges[i].CurrencyPairs.LastUpdated) lastUpdated = lastUpdated.AddDate(0, 0, configPairsLastUpdatedWarningThreshold) if lastUpdated.Unix() <= time.Now().Unix() { log.Warnf(WarningPairsLastUpdatedThresholdExceeded, c.Exchanges[i].Name, configPairsLastUpdatedWarningThreshold) @@ -1125,8 +1127,8 @@ func (c *Config) CheckCurrencyConfigValues() error { } if c.Currency.Cryptocurrencies.Join() == "" { - if c.Cryptocurrencies.Join() != "" { - c.Currency.Cryptocurrencies = c.Cryptocurrencies + if c.Cryptocurrencies != nil { + c.Currency.Cryptocurrencies = *c.Cryptocurrencies c.Cryptocurrencies = nil } else { c.Currency.Cryptocurrencies = currency.GetDefaultCryptocurrencies() @@ -1146,13 +1148,19 @@ func (c *Config) CheckCurrencyConfigValues() error { } if c.Currency.FiatDisplayCurrency.IsEmpty() { - if c.FiatDisplayCurrency.IsEmpty() { - c.Currency.FiatDisplayCurrency = c.FiatDisplayCurrency - c.FiatDisplayCurrency = currency.NewCode("") + if c.FiatDisplayCurrency != nil { + c.Currency.FiatDisplayCurrency = *c.FiatDisplayCurrency + c.FiatDisplayCurrency = nil } else { c.Currency.FiatDisplayCurrency = currency.USD } } + + // Flush old setting which still exists + if c.FiatDisplayCurrency != nil { + c.FiatDisplayCurrency = nil + } + return nil } @@ -1376,7 +1384,7 @@ func GetFilePath(file string) (string, error) { continue } - data, err := common.ReadFile(newDirs[x]) + data, err := ioutil.ReadFile(newDirs[x]) if err != nil { return "", err } @@ -1416,7 +1424,7 @@ func (c *Config) ReadConfig(configPath string) error { return err } - file, err := common.ReadFile(defaultPath) + file, err := ioutil.ReadFile(defaultPath) if err != nil { return err } diff --git a/config/config_encryption_test.go b/config/config_encryption_test.go index f75166b4..a907f1f2 100644 --- a/config/config_encryption_test.go +++ b/config/config_encryption_test.go @@ -1,9 +1,8 @@ package config import ( + "io/ioutil" "testing" - - "github.com/thrasher-/gocryptotrader/common" ) func TestPromptForConfigEncryption(t *testing.T) { @@ -87,7 +86,7 @@ func TestDecryptConfigFile(t *testing.T) { func TestConfirmConfigJSON(t *testing.T) { var result interface{} - testConfirmJSON, err := common.ReadFile(ConfigTestFile) + testConfirmJSON, err := ioutil.ReadFile(ConfigTestFile) if err != nil { t.Errorf("Test failed. testConfirmJSON: %s", err) } diff --git a/config/config_test.go b/config/config_test.go index 68d55afb..cf881fa2 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -682,7 +682,8 @@ func TestCheckExchangeConfigValues(t *testing.T) { } checkExchangeConfigValues.Exchanges = checkExchangeConfigValues.Exchanges[:0] - checkExchangeConfigValues.Cryptocurrencies = currency.NewCurrenciesFromStringArray([]string{"TESTYTEST"}) + cryptos := currency.NewCurrenciesFromStringArray([]string{"TESTYTEST"}) + checkExchangeConfigValues.Cryptocurrencies = &cryptos err = checkExchangeConfigValues.CheckExchangeConfigValues() if err == nil { t.Errorf( diff --git a/config/config_types.go b/config/config_types.go index 7b6aed41..9c317693 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -30,8 +30,8 @@ type Config struct { // Deprecated config settings, will be removed at a future date Webserver *WebserverConfig `json:"webserver,omitempty"` CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat,omitempty"` - FiatDisplayCurrency currency.Code `json:"fiatDispayCurrency,omitempty"` - Cryptocurrencies currency.Currencies `json:"cryptocurrencies,omitempty"` + FiatDisplayCurrency *currency.Code `json:"fiatDispayCurrency,omitempty"` + Cryptocurrencies *currency.Currencies `json:"cryptocurrencies,omitempty"` SMS *SMSGlobalConfig `json:"smsGlobal,omitempty"` } @@ -328,9 +328,9 @@ type APIConfig struct { AuthenticatedSupport bool `json:"authenticatedSupport"` PEMKeySupport bool `json:"pemKeySupport,omitempty"` - Endpoints APIEndpointsConfig `json:"endpoints"` - Credentials APICredentialsConfig `json:"credentials"` - CredentialsValidator APICredentialsValidatorConfig `json:"credentialsValidator"` + Endpoints APIEndpointsConfig `json:"endpoints"` + Credentials APICredentialsConfig `json:"credentials"` + CredentialsValidator *APICredentialsValidatorConfig `json:"credentialsValidator,omitempty"` } // HTTPRateConfig stores the exchanges HTTP rate limiter config diff --git a/currency/storage.go b/currency/storage.go index 233457db..87b1a5f1 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" "path/filepath" "sync" "time" @@ -304,7 +305,7 @@ func (s *Storage) ForeignExchangeUpdater() { // SeedCurrencyAnalysisData sets a new instance of a coinmarketcap data. func (s *Storage) SeedCurrencyAnalysisData() error { - b, err := common.ReadFile(s.path) + b, err := ioutil.ReadFile(s.path) if err != nil { err = s.FetchCurrencyAnalysisData() if err != nil { diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 93fb514d..31b90935 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -13,6 +13,7 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/common/convert" "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" @@ -487,7 +488,7 @@ func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, re if params == nil { params = url.Values{} } - params.Set("recvWindow", strconv.FormatInt(common.RecvWindow(5*time.Second), 10)) + params.Set("recvWindow", strconv.FormatInt(convert.RecvWindow(5*time.Second), 10)) params.Set("timestamp", strconv.FormatInt(time.Now().Unix()*1000, 10)) signature := params.Encode() diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 37ba6b3f..6d43e09f 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -144,6 +144,9 @@ func (e *Base) SetFeatureDefaults() { // SetAPICredentialDefaults sets the API Credential validator defaults func (e *Base) SetAPICredentialDefaults() { // Exchange hardcoded settings take precedence and overwrite the config settings + if e.Config.API.CredentialsValidator == nil { + e.Config.API.CredentialsValidator = new(config.APICredentialsValidatorConfig) + } if e.Config.API.CredentialsValidator.RequiresKey != e.API.CredentialsValidator.RequiresKey { e.Config.API.CredentialsValidator.RequiresKey = e.API.CredentialsValidator.RequiresKey } diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index 04cfd44e..264942b8 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -303,7 +303,7 @@ func (z *ZB) SendAuthenticatedHTTPRequest(httpMethod string, params url.Values, []byte(params.Encode()), []byte(crypto.Sha1ToHex(z.API.Credentials.Secret))) - params.Set("reqTime", fmt.Sprintf("%d", common.UnixMillis(time.Now()))) + params.Set("reqTime", fmt.Sprintf("%d", convert.UnixMillis(time.Now()))) params.Set("sign", fmt.Sprintf("%x", hmac)) urlPath := fmt.Sprintf("%s/%s?%s", diff --git a/go.mod b/go.mod index c814a465..00e0d429 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/thrasher-/gocryptotrader go 1.12 require ( + github.com/gogo/protobuf v1.2.1 // indirect github.com/golang/protobuf v1.3.1 github.com/google/go-querystring v1.0.0 github.com/gorilla/mux v1.7.2 @@ -10,7 +11,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/grpc-ecosystem/grpc-gateway v1.9.0 github.com/pquerna/otp v1.2.0 - github.com/satori/go.uuid v1.2.0 + github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b github.com/urfave/cli v1.20.0 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 golang.org/x/net v0.0.0-20190606173856-1492cefac77f diff --git a/go.sum b/go.sum index a729b15d..8fc938f2 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,15 @@ +cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -21,16 +26,22 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmo github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -49,19 +60,23 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190606173856-1492cefac77f h1:IWHgpgFqnL5AhBUBZSgBdjl2vkQUEzcY+JNKWfcgAU0= golang.org/x/net v0.0.0-20190606173856-1492cefac77f/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6 h1:XRqWpmQ5ACYxWuYX495S0sHawhPGOVrh62WzgXsQnWs= @@ -69,6 +84,7 @@ google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6/go.mod h1:z3L6/3dT google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= From f777e68716f5647cbb1a1b05ec4fcade8dcd13af Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 10 Jun 2019 20:02:09 +1000 Subject: [PATCH 08/71] Engine improvements --- .golangci.yml | 2 +- cmd/exchange_wrapper_coverage/main.go | 10 +- cmd/gctcli/commands.go | 47 ++ cmd/gctcli/main.go | 1 + cmd/{otp_gen => gen_otp}/otp_gen.go | 0 communications/base/base.go | 125 +-- communications/base/base_interface.go | 49 +- communications/base/base_test.go | 94 --- communications/slack/slack.go | 74 +- communications/slack/slack_test.go | 21 - communications/smsglobal/smsglobal.go | 19 +- communications/smsglobal/smsglobal_test.go | 2 +- communications/smtpservice/smtpservice.go | 5 + communications/telegram/telegram.go | 35 +- communications/telegram/telegram_test.go | 15 - config/config.go | 7 +- config/config_types.go | 1 + engine/engine.go | 4 + engine/events/events.go | 5 +- engine/helpers.go | 18 + engine/orders.go | 165 +++- engine/orders_types.go | 36 + engine/portfolio.go | 2 + engine/routines.go | 15 +- engine/rpcserver.go | 18 +- engine/syncer.go | 12 +- exchanges/alphapoint/alphapoint_test.go | 19 +- exchanges/alphapoint/alphapoint_wrapper.go | 21 +- exchanges/anx/anx_test.go | 19 +- exchanges/anx/anx_wrapper.go | 25 +- exchanges/binance/binance_test.go | 19 +- exchanges/binance/binance_wrapper.go | 19 +- exchanges/bitfinex/bitfinex_test.go | 17 +- exchanges/bitfinex/bitfinex_wrapper.go | 21 +- exchanges/bitflyer/bitflyer_test.go | 16 +- exchanges/bitflyer/bitflyer_wrapper.go | 6 +- exchanges/bithumb/bithumb_test.go | 16 +- exchanges/bithumb/bithumb_wrapper.go | 22 +- exchanges/bitmex/bitmex_test.go | 16 +- exchanges/bitmex/bitmex_wrapper.go | 25 +- exchanges/bitstamp/bitstamp_test.go | 17 +- exchanges/bitstamp/bitstamp_wrapper.go | 17 +- exchanges/bittrex/bittrex_test.go | 17 +- exchanges/bittrex/bittrex_wrapper.go | 22 +- exchanges/btcmarkets/btcmarkets_test.go | 17 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 31 +- exchanges/btse/btse_test.go | 16 +- exchanges/btse/btse_wrapper.go | 16 +- exchanges/coinbasepro/coinbasepro_test.go | 18 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 31 +- exchanges/coinut/coinut_test.go | 16 +- exchanges/coinut/coinut_wrapper.go | 33 +- exchanges/exchange_types.go | 333 -------- exchanges/exmo/exmo_test.go | 18 +- exchanges/exmo/exmo_wrapper.go | 21 +- exchanges/gateio/gateio_test.go | 18 +- exchanges/gateio/gateio_wrapper.go | 20 +- exchanges/gemini/gemini_test.go | 18 +- exchanges/gemini/gemini_wrapper.go | 21 +- exchanges/hitbtc/hitbtc_test.go | 17 +- exchanges/hitbtc/hitbtc_wrapper.go | 21 +- exchanges/huobi/huobi_test.go | 19 +- exchanges/huobi/huobi_wrapper.go | 32 +- exchanges/huobihadax/huobihadax_test.go | 19 +- exchanges/huobihadax/huobihadax_wrapper.go | 34 +- exchanges/interfaces.go | 2 +- exchanges/itbit/itbit_test.go | 16 +- exchanges/itbit/itbit_wrapper.go | 33 +- exchanges/kraken/kraken_test.go | 16 +- exchanges/kraken/kraken_wrapper.go | 24 +- exchanges/lakebtc/lakebtc_test.go | 16 +- exchanges/lakebtc/lakebtc_wrapper.go | 16 +- exchanges/localbitcoins/localbitcoins_test.go | 16 +- .../localbitcoins/localbitcoins_wrapper.go | 16 +- exchanges/okcoin/okcoin_test.go | 17 +- exchanges/okex/okex_test.go | 17 +- exchanges/okgroup/okgroup_wrapper.go | 29 +- exchanges/order_types.go | 380 +++++++++ exchanges/poloniex/poloniex_test.go | 24 +- exchanges/poloniex/poloniex_wrapper.go | 22 +- exchanges/yobit/yobit_test.go | 17 +- exchanges/yobit/yobit_wrapper.go | 16 +- exchanges/zb/zb_test.go | 26 +- exchanges/zb/zb_wrapper.go | 22 +- gctrpc/rpc.pb.go | 737 +++++++++++------- gctrpc/rpc.pb.gw.go | 61 +- gctrpc/rpc.proto | 10 + gctrpc/rpc.swagger.json | 32 + 88 files changed, 2037 insertions(+), 1413 deletions(-) rename cmd/{otp_gen => gen_otp}/otp_gen.go (100%) create mode 100644 engine/orders_types.go create mode 100644 exchanges/order_types.go diff --git a/.golangci.yml b/.golangci.yml index a0835d4f..2fcc3b32 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,5 @@ run: - deadline: 30s + deadline: 40s issues-exit-code: 1 tests: true skip-dirs: diff --git a/cmd/exchange_wrapper_coverage/main.go b/cmd/exchange_wrapper_coverage/main.go index f786ac6d..c8953421 100644 --- a/cmd/exchange_wrapper_coverage/main.go +++ b/cmd/exchange_wrapper_coverage/main.go @@ -116,7 +116,15 @@ func testWrappers(e exchange.IBotExchange) []string { funcs = append(funcs, "GetFundingHistory") } - _, err = e.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1000000, 10000000000, "meow") + s := &exchange.OrderSubmission{ + Pair: p, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Amount: 1000000, + Price: 10000000000, + ClientID: "meow", + } + _, err = e.SubmitOrder(s) if err == common.ErrNotYetImplemented { funcs = append(funcs, "SubmitOrder") } diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index 20438447..61e4dc84 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -171,6 +171,53 @@ func disableExchange(c *cli.Context) error { return nil } +var getExchangeOTPCommand = cli.Command{ + Name: "getexchangeotp", + Usage: "gets a specific exchanges otp code", + ArgsUsage: "", + Action: getExchangeOTPCode, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the otp code for", + }, + }, +} + +func getExchangeOTPCode(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getexchangeotp") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetExchangeOTPCode(context.Background(), + &gctrpc.GenericExchangeNameRequest{ + Exchange: exchangeName, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + var getExchangeInfoCommand = cli.Command{ Name: "getexchangeinfo", Usage: "gets a specific exchanges info", diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index e3d758d7..fb5e39bf 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -81,6 +81,7 @@ func main() { getExchangesCommand, enableExchangeCommand, disableExchangeCommand, + getExchangeOTPCommand, getExchangeInfoCommand, getTickerCommand, getTickersCommand, diff --git a/cmd/otp_gen/otp_gen.go b/cmd/gen_otp/otp_gen.go similarity index 100% rename from cmd/otp_gen/otp_gen.go rename to cmd/gen_otp/otp_gen.go diff --git a/communications/base/base.go b/communications/base/base.go index cd047aea..a4ee6b09 100644 --- a/communications/base/base.go +++ b/communications/base/base.go @@ -1,56 +1,15 @@ package base import ( - "fmt" - "strings" - "sync" "time" - - "github.com/thrasher-/gocryptotrader/exchanges/assets" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) // global vars contain staged update data that will be sent to the communication // mediums var ( - TickerStaged map[string]map[assets.AssetType]map[string]ticker.Price - OrderbookStaged map[string]map[assets.AssetType]map[string]Orderbook - PortfolioStaged Portfolio - SettingsStaged Settings - ServiceStarted time.Time - m sync.Mutex + ServiceStarted time.Time ) -// Orderbook holds the minimal orderbook details to be sent to a communication -// medium -type Orderbook struct { - CurrencyPair string - AssetType string - TotalAsks float64 - TotalBids float64 - LastUpdated string -} - -// Ticker holds the minimal orderbook details to be sent to a communication -// medium -type Ticker struct { - CurrencyPair string - LastUpdated string -} - -// Portfolio holds the minimal portfolio details to be sent to a communication -// medium -type Portfolio struct { - ProfitLoss string -} - -// Settings holds the minimal setting details to be sent to a communication -// medium -type Settings struct { - EnabledExchanges string - EnabledCommunications string -} - // Base enforces standard variables across communication packages type Base struct { Name string @@ -61,9 +20,8 @@ type Base struct { // Event is a generalise event type type Event struct { - Type string - GainLoss string - TradeDetails string + Type string + Message string } // IsEnabled returns if the comms package has been enabled in the configuration @@ -82,83 +40,6 @@ func (b *Base) GetName() string { return b.Name } -// GetTicker returns staged ticker data -func (b *Base) GetTicker(exchangeName string) string { - m.Lock() - defer m.Unlock() - - tickerPrice, ok := TickerStaged[exchangeName] - if !ok { - return "" - } - - var tickerPrices []ticker.Price - for x := range tickerPrice { - for y := range tickerPrice[x] { - tickerPrices = append(tickerPrices, tickerPrice[x][y]) - } - } - - var packagedTickers []string - for i := range tickerPrices { - packagedTickers = append(packagedTickers, fmt.Sprintf( - "Currency Pair: %s Ask: %f, Bid: %f High: %f Last: %f Low: %f ATH: %f Volume: %f", - tickerPrices[i].Pair, - tickerPrices[i].Ask, - tickerPrices[i].Bid, - tickerPrices[i].High, - tickerPrices[i].Last, - tickerPrices[i].Low, - tickerPrices[i].PriceATH, - tickerPrices[i].Volume)) - } - return strings.Join(packagedTickers, "\n") -} - -// GetOrderbook returns staged orderbook data -func (b *Base) GetOrderbook(exchangeName string) string { - m.Lock() - defer m.Unlock() - - orderbook, ok := OrderbookStaged[exchangeName] - if !ok { - return "" - } - - var orderbooks []Orderbook - for _, x := range orderbook { - for _, y := range x { - orderbooks = append(orderbooks, y) - } - } - - var packagedOrderbooks []string - for i := range orderbooks { - packagedOrderbooks = append(packagedOrderbooks, fmt.Sprintf( - "Currency Pair: %s AssetType: %s, LastUpdated: %s TotalAsks: %f TotalBids: %f", - orderbooks[i].CurrencyPair, - orderbooks[i].AssetType, - orderbooks[i].LastUpdated, - orderbooks[i].TotalAsks, - orderbooks[i].TotalBids)) - } - return strings.Join(packagedOrderbooks, "\n") -} - -// GetPortfolio returns staged portfolio info -func (b *Base) GetPortfolio() string { - m.Lock() - defer m.Unlock() - return fmt.Sprintf("%v", PortfolioStaged) -} - -// GetSettings returns stage setting info -func (b *Base) GetSettings() string { - m.Lock() - defer m.Unlock() - return fmt.Sprintf("%v", SettingsStaged) -} - // GetStatus returns status data func (b *Base) GetStatus() string { return ` diff --git a/communications/base/base_interface.go b/communications/base/base_interface.go index 2348c9dd..2ec99f6e 100644 --- a/communications/base/base_interface.go +++ b/communications/base/base_interface.go @@ -4,9 +4,6 @@ import ( "time" "github.com/thrasher-/gocryptotrader/config" - "github.com/thrasher-/gocryptotrader/exchanges/assets" - "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -26,10 +23,7 @@ type ICommunicate interface { // Setup sets up communication variables and intiates a connection to the // communication mediums func (c IComm) Setup() { - TickerStaged = make(map[string]map[assets.AssetType]map[string]ticker.Price) - OrderbookStaged = make(map[string]map[assets.AssetType]map[string]Orderbook) ServiceStarted = time.Now() - for i := range c { if c[i].IsEnabled() && !c[i].IsConnected() { err := c[i].Connect() @@ -46,8 +40,8 @@ func (c IComm) PushEvent(event Event) { if c[i].IsEnabled() && c[i].IsConnected() { err := c[i].PushEvent(event) if err != nil { - log.Errorf("Communications error - PushEvent() in package %s with %v", - c[i].GetName(), event) + log.Errorf("Communications error - PushEvent() in package %s with %v. Err %s", + c[i].GetName(), event, err) } } } @@ -68,42 +62,3 @@ func (c IComm) GetEnabledCommunicationMediums() { log.Warnf("Communications: No communication mediums are enabled.") } } - -// StageTickerData stages updated ticker data for the communications package -func (c IComm) StageTickerData(exchangeName string, assetType assets.AssetType, tickerPrice *ticker.Price) { - m.Lock() - defer m.Unlock() - - if _, ok := TickerStaged[exchangeName]; !ok { - TickerStaged[exchangeName] = make(map[assets.AssetType]map[string]ticker.Price) - } - - if _, ok := TickerStaged[exchangeName][assetType]; !ok { - TickerStaged[exchangeName][assetType] = make(map[string]ticker.Price) - } - - TickerStaged[exchangeName][assetType][tickerPrice.Pair.String()] = *tickerPrice -} - -// StageOrderbookData stages updated orderbook data for the communications -// package -func (c IComm) StageOrderbookData(exchangeName string, assetType assets.AssetType, ob *orderbook.Base) { - m.Lock() - defer m.Unlock() - - if _, ok := OrderbookStaged[exchangeName]; !ok { - OrderbookStaged[exchangeName] = make(map[assets.AssetType]map[string]Orderbook) - } - - if _, ok := OrderbookStaged[exchangeName][assetType]; !ok { - OrderbookStaged[exchangeName][assetType] = make(map[string]Orderbook) - } - - _, totalAsks := ob.TotalAsksAmount() - _, totalBids := ob.TotalBidsAmount() - - OrderbookStaged[exchangeName][assetType][ob.Pair.String()] = Orderbook{ - CurrencyPair: ob.Pair.String(), - TotalAsks: totalAsks, - TotalBids: totalBids} -} diff --git a/communications/base/base_test.go b/communications/base/base_test.go index 3dc5d0e7..71cb86cb 100644 --- a/communications/base/base_test.go +++ b/communications/base/base_test.go @@ -2,14 +2,10 @@ package base import ( "testing" - - "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) var ( b Base - i IComm ) func TestStart(t *testing.T) { @@ -39,41 +35,6 @@ func TestGetName(t *testing.T) { } } -func TestGetTicker(t *testing.T) { - v := b.GetTicker("ANX") - if v != "" { - t.Error("test failed - base GetTicker() error") - } -} - -func TestGetOrderbook(t *testing.T) { - v := b.GetOrderbook("ANX") - if v != "" { - t.Error("test failed - base GetOrderbook() error") - } -} - -func TestGetPortfolio(t *testing.T) { - v := b.GetPortfolio() - if v != "{}" { - t.Error("test failed - base GetPortfolio() error") - } -} - -func TestGetSettings(t *testing.T) { - v := b.GetSettings() - if v != "{ }" { - t.Error("test failed - base GetSettings() error") - } -} - -func TestGetStatus(t *testing.T) { - v := b.GetStatus() - if v == "" { - t.Error("test failed - base GetStatus() error") - } -} - type CommunicationProvider struct { ICommunicate @@ -166,58 +127,3 @@ func TestPushEvent(t *testing.T) { } } } - -func TestStageTickerData(t *testing.T) { - _, ok := TickerStaged["bitstamp"]["someAsset"]["BTCUSD"] - if ok { - t.Fatalf("key should not exists") - } - - price := ticker.Price{} - var i IComm - i.Setup() - - i.StageTickerData("bitstamp", "someAsset", &price) - - _, ok = TickerStaged["bitstamp"]["someAsset"][price.Pair.String()] - if !ok { - t.Fatalf("key should exists") - } -} - -func TestOrderbookData(t *testing.T) { - _, ok := OrderbookStaged["bitstamp"]["someAsset"]["someOrderbook"] - if ok { - t.Fatal("key should not exists") - } - - ob := orderbook.Base{ - Asks: []orderbook.Item{ - {Amount: 1, Price: 2, ID: 3}, - {Amount: 4, Price: 5, ID: 6}, - }, - } - var i IComm - i.Setup() - - i.StageOrderbookData("bitstamp", "someAsset", &ob) - - orderbook, ok := OrderbookStaged["bitstamp"]["someAsset"][ob.Pair.String()] - if !ok { - t.Fatal("key should exists") - } - - if ob.Pair.String() != orderbook.CurrencyPair { - t.Fatal("currency missmatched") - } - - _, totalAsks := ob.TotalAsksAmount() - if totalAsks != orderbook.TotalAsks { - t.Fatal("total asks missmatched") - } - - _, totalBids := ob.TotalBidsAmount() - if totalBids != orderbook.TotalBids { - t.Fatal("total bids missmatched") - } -} diff --git a/communications/slack/slack.go b/communications/slack/slack.go index 444ee9c6..dad5a45e 100644 --- a/communications/slack/slack.go +++ b/communications/slack/slack.go @@ -24,21 +24,13 @@ import ( const ( SlackURL = "https://slack.com/api/rtm.start" - cmdStatus = "!status" - cmdHelp = "!help" - cmdSettings = "!settings" - cmdTicker = "!ticker" - cmdPortfolio = "!portfolio" - cmdOrderbook = "!orderbook" + cmdStatus = "!status" + cmdHelp = "!help" getHelp = `GoCryptoTrader SlackBot, thank you for using this service! Current commands are: !status - Displays current working status of bot - !help - Displays help text - !settings - Displays current settings - !ticker - Displays recent ANX ticker - !portfolio - Displays portfolio data - !orderbook - Displays current ANX orderbook` + !help - Displays help text` ) // Slack starts a websocket connection and uses https://api.slack.com/rtm real @@ -58,6 +50,11 @@ type Slack struct { sync.Mutex } +// IsConnected returns whether or not the connection is connected +func (s *Slack) IsConnected() bool { + return s.Connected +} + // Setup takes in a slack configuration, sets bots target channel and // sets verification token to access workspace func (s *Slack) Setup(cfg *config.CommunicationsConfig) { @@ -70,12 +67,22 @@ func (s *Slack) Setup(cfg *config.CommunicationsConfig) { // Connect connects to the service func (s *Slack) Connect() error { - return s.NewConnection() + err := s.NewConnection() + if err != nil { + return err + } + + s.Connected = true + return nil } // PushEvent pushes an event to either a slack channel or specific client -func (s *Slack) PushEvent(base.Event) error { - return common.ErrNotYetImplemented +func (s *Slack) PushEvent(event base.Event) error { + if s.Connected { + return s.WebsocketSend("message", + fmt.Sprintf("event: %s %s", event.Type, event.Message)) + } + return nil } // BuildURL returns an appended token string with the SlackURL @@ -155,19 +162,22 @@ func (s *Slack) NewConnection() error { } if s.Verbose { - log.Debugf("%s [%s] connected to %s [%s] \nWebsocket URL: %s.\n", + log.Debugf("Slack: %s [%s] connected to %s [%s] \nWebsocket URL: %s.\n", s.Details.Self.Name, s.Details.Self.ID, s.Details.Team.Domain, s.Details.Team.ID, s.Details.URL) - log.Debugf("Slack channels: %s", s.GetChannelsString()) + log.Debugf("Slack: Public channels: %s", s.GetChannelsString()) } s.TargetChannelID, err = s.GetIDByName(s.TargetChannel) if err != nil { return err } + + log.Debugf("Slack: Target channel ID: %v [#%v]", s.TargetChannelID, + s.TargetChannel) return s.WebsocketConnect() } return errors.New("slack.go NewConnection() Already Connected") @@ -202,7 +212,6 @@ func (s *Slack) WebsocketReader() { } var data WebsocketResponse - err = common.JSONDecode(resp, &data) if err != nil { log.Error(err) @@ -240,7 +249,7 @@ func (s *Slack) WebsocketReader() { case "pong": if s.Verbose { - log.Debugf("Pong received from server") + log.Debugf("Slack: Pong received from server") } default: log.Debugf(string(resp)) @@ -255,7 +264,7 @@ func (s *Slack) handlePresenceChange(resp []byte) error { return err } if s.Verbose { - log.Debugf("Presence change. User %s [%s] changed status to %s\n", + log.Debugf("Slack: Presence change. User %s [%s] changed status to %s\n", s.GetUsernameByID(pres.User), pres.User, pres.Presence) } @@ -272,7 +281,7 @@ func (s *Slack) handleMessageResponse(resp []byte, data WebsocketResponse) error return err } if s.Verbose { - log.Debugf("Msg received by %s [%s] with text: %s\n", + log.Debugf("Slack: Message received by %s [%s] with text: %s\n", s.GetUsernameByID(msg.User), msg.User, msg.Text) } @@ -304,7 +313,7 @@ func (s *Slack) handleErrorResponse(data WebsocketResponse) error { func (s *Slack) handleHelloResponse() { if s.Verbose { - log.Debugln("Websocket connected successfully.") + log.Debugln("Slack: Websocket connected successfully.") } s.Connected = true go s.WebsocketKeepAlive() @@ -321,7 +330,7 @@ func (s *Slack) handleReconnectResponse(resp []byte) error { } s.ReconnectURL = recURL.URL if s.Verbose { - log.Debugf("Reconnect URL set to %s\n", s.ReconnectURL) + log.Debugf("Slack: Reconnect URL set to %s\n", s.ReconnectURL) } return nil } @@ -334,7 +343,7 @@ func (s *Slack) WebsocketKeepAlive() { for { <-ticker.C if err := s.WebsocketSend("ping", ""); err != nil { - log.Debugf("slack WebsocketKeepAlive() error %s", err) + log.Debugf("Slack: WebsocketKeepAlive() error %s", err) } } } @@ -353,6 +362,11 @@ func (s *Slack) WebsocketSend(eventType, text string) error { if err != nil { return err } + + if s.Verbose { + log.Debugf("Slack: Sending websocket message: %s", string(data)) + } + if s.WebsocketConn == nil { return errors.New("websocket not connected") } @@ -362,7 +376,7 @@ func (s *Slack) WebsocketSend(eventType, text string) error { // HandleMessage handles incoming messages and/or commands from slack func (s *Slack) HandleMessage(msg *Message) error { if msg == nil { - return errors.New("msg is nil") + return errors.New("slack msg is nil") } msg.Text = strings.ToLower(msg.Text) @@ -373,18 +387,6 @@ func (s *Slack) HandleMessage(msg *Message) error { case strings.Contains(msg.Text, cmdHelp): return s.WebsocketSend("message", getHelp) - case strings.Contains(msg.Text, cmdTicker): - return s.WebsocketSend("message", s.GetTicker("ANX")) - - case strings.Contains(msg.Text, cmdOrderbook): - return s.WebsocketSend("message", s.GetOrderbook("ANX")) - - case strings.Contains(msg.Text, cmdSettings): - return s.WebsocketSend("message", s.GetSettings()) - - case strings.Contains(msg.Text, cmdPortfolio): - return s.WebsocketSend("message", s.GetPortfolio()) - default: return s.WebsocketSend("message", "GoCryptoTrader SlackBot - Command Unknown!") } diff --git a/communications/slack/slack_test.go b/communications/slack/slack_test.go index fb2c5089..050cf4fb 100644 --- a/communications/slack/slack_test.go +++ b/communications/slack/slack_test.go @@ -343,7 +343,6 @@ func TestWebsocketSend(t *testing.T) { func TestHandleMessage(t *testing.T) { msg := &Message{} - err := s.HandleMessage(msg) if err == nil { t.Error("test failed - slack HandleMessage(), Sent message through nil websocket") @@ -358,24 +357,4 @@ func TestHandleMessage(t *testing.T) { if err == nil { t.Error("test failed - slack HandleMessage(), Sent message through nil websocket") } - msg.Text = cmdTicker - err = s.HandleMessage(msg) - if err == nil { - t.Error("test failed - slack HandleMessage(), Sent message through nil websocket") - } - msg.Text = cmdOrderbook - err = s.HandleMessage(msg) - if err == nil { - t.Error("test failed - slack HandleMessage(), Sent message through nil websocket") - } - msg.Text = cmdSettings - err = s.HandleMessage(msg) - if err == nil { - t.Error("test failed - slack HandleMessage(), Sent message through nil websocket") - } - msg.Text = cmdPortfolio - err = s.HandleMessage(msg) - if err == nil { - t.Error("test failed - slack HandleMessage(), Sent message through nil websocket") - } } diff --git a/communications/smsglobal/smsglobal.go b/communications/smsglobal/smsglobal.go index ad051f7b..7f21bf61 100644 --- a/communications/smsglobal/smsglobal.go +++ b/communications/smsglobal/smsglobal.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/communications/base" "github.com/thrasher-/gocryptotrader/config" + log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -39,6 +40,7 @@ func (s *SMSGlobal) Setup(cfg *config.CommunicationsConfig) { s.Verbose = cfg.SMSGlobalConfig.Verbose s.Username = cfg.SMSGlobalConfig.Username s.Password = cfg.SMSGlobalConfig.Password + s.SendFrom = cfg.SMSGlobalConfig.From var contacts []Contact for x := range cfg.SMSGlobalConfig.Contacts { @@ -49,10 +51,19 @@ func (s *SMSGlobal) Setup(cfg *config.CommunicationsConfig) { Enabled: cfg.SMSGlobalConfig.Contacts[x].Enabled, }, ) + log.Debugf("SMSGlobal: SMS Contact: %s. Number: %s. Enabled: %v", + cfg.SMSGlobalConfig.Contacts[x].Name, + cfg.SMSGlobalConfig.Contacts[x].Number, + cfg.SMSGlobalConfig.Contacts[x].Enabled) } s.Contacts = contacts } +// IsConnected returns whether or not the connection is connected +func (s *SMSGlobal) IsConnected() bool { + return s.Connected +} + // Connect connects to the service func (s *SMSGlobal) Connect() error { s.Connected = true @@ -60,8 +71,8 @@ func (s *SMSGlobal) Connect() error { } // PushEvent pushes an event to a contact list via SMS -func (s *SMSGlobal) PushEvent(base.Event) error { - return common.ErrNotYetImplemented +func (s *SMSGlobal) PushEvent(event base.Event) error { + return s.SendMessageToAll(event.Message) } // GetEnabledContacts returns how many SMS contacts are enabled in the @@ -139,6 +150,10 @@ func (s *SMSGlobal) RemoveContact(contact Contact) error { func (s *SMSGlobal) SendMessageToAll(message string) error { for x := range s.Contacts { if s.Contacts[x].Enabled { + if s.Verbose { + log.Debugf("SMSGlobal: Sending SMS to %s. Number: %s. Message: %s [From: %s]", + s.Contacts[x].Name, s.Contacts[x].Number, message, s.SendFrom) + } err := s.SendMessage(s.Contacts[x].Number, message) if err != nil { return err diff --git a/communications/smsglobal/smsglobal_test.go b/communications/smsglobal/smsglobal_test.go index 7a379eb2..09930200 100644 --- a/communications/smsglobal/smsglobal_test.go +++ b/communications/smsglobal/smsglobal_test.go @@ -25,7 +25,7 @@ func TestConnect(t *testing.T) { func TestPushEvent(t *testing.T) { err := s.PushEvent(base.Event{}) - if err == nil { + if err != nil { t.Error("test failed - SMSGlobal PushEvent() error") } } diff --git a/communications/smtpservice/smtpservice.go b/communications/smtpservice/smtpservice.go index 7a1fb05b..042ae6f8 100644 --- a/communications/smtpservice/smtpservice.go +++ b/communications/smtpservice/smtpservice.go @@ -39,6 +39,11 @@ func (s *SMTPservice) Setup(cfg *config.CommunicationsConfig) { s.RecipientList = cfg.SMTPConfig.RecipientList } +// IsConnected returns whether or not the connection is connected +func (s *SMTPservice) IsConnected() bool { + return s.Connected +} + // Connect connects to service func (s *SMTPservice) Connect() error { s.Connected = true diff --git a/communications/telegram/telegram.go b/communications/telegram/telegram.go index 33f0d19c..11a200f0 100644 --- a/communications/telegram/telegram.go +++ b/communications/telegram/telegram.go @@ -23,23 +23,17 @@ const ( methodGetUpdates = "getUpdates" methodSendMessage = "sendMessage" - cmdStart = "/start" - cmdStatus = "/status" - cmdHelp = "/help" - cmdSettings = "/settings" - cmdTicker = "/ticker" - cmdPortfolio = "/portfolio" - cmdOrders = "/orderbooks" + cmdStart = "/start" + cmdStatus = "/status" + cmdHelp = "/help" + cmdSettings = "/settings" cmdHelpReply = `GoCryptoTrader TelegramBot, thank you for using this service! Current commands are: /start - Will authenticate your ID /status - Displays the status of the bot /help - Displays current command list - /settings - Displays current bot settings - /ticker - Displays current ANX ticker data - /portfolio - Displays your current portfolio - /orderbooks - Displays current orderbooks for ANX` + /settings - Displays current bot settings` talkRoot = "GoCryptoTrader bot" ) @@ -52,6 +46,9 @@ type Telegram struct { AuthorisedClients []int64 } +// IsConnected returns whether or not the connection is connected +func (t *Telegram) IsConnected() bool { return t.Connected } + // Setup takes in a Telegram configuration and sets verification token func (t *Telegram) Setup(cfg *config.CommunicationsConfig) { t.Name = cfg.TelegramConfig.Name @@ -73,8 +70,8 @@ func (t *Telegram) Connect() error { // PushEvent sends an event to a supplied recipient list via telegram func (t *Telegram) PushEvent(event base.Event) error { for i := range t.AuthorisedClients { - err := t.SendMessage(fmt.Sprintf("Type: %s Details: %s GainOrLoss: %s", - event.Type, event.TradeDetails, event.GainLoss), t.AuthorisedClients[i]) + err := t.SendMessage(fmt.Sprintf("Type: %s Message: %s", + event.Type, event.Message), t.AuthorisedClients[i]) if err != nil { return err } @@ -146,21 +143,9 @@ func (t *Telegram) HandleMessages(text string, chatID int64) error { case strings.Contains(text, cmdStart): return t.SendMessage(fmt.Sprintf("%s: START COMMANDS HERE", talkRoot), chatID) - case strings.Contains(text, cmdOrders): - return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetOrderbook("ANX")), chatID) - case strings.Contains(text, cmdStatus): return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetStatus()), chatID) - case strings.Contains(text, cmdTicker): - return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetTicker("ANX")), chatID) - - case strings.Contains(text, cmdSettings): - return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetSettings()), chatID) - - case strings.Contains(text, cmdPortfolio): - return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetPortfolio()), chatID) - default: return t.SendMessage(fmt.Sprintf("command %s not recognized", text), chatID) } diff --git a/communications/telegram/telegram_test.go b/communications/telegram/telegram_test.go index 277439ba..994b511d 100644 --- a/communications/telegram/telegram_test.go +++ b/communications/telegram/telegram_test.go @@ -58,31 +58,16 @@ func TestHandleMessages(t *testing.T) { t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", err) } - err = T.HandleMessages(cmdOrders, chatID) - if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", - err) - } err = T.HandleMessages(cmdStatus, chatID) if err.Error() != testErrNotFound { t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", err) } - err = T.HandleMessages(cmdTicker, chatID) - if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", - err) - } err = T.HandleMessages(cmdSettings, chatID) if err.Error() != testErrNotFound { t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", err) } - err = T.HandleMessages(cmdPortfolio, chatID) - if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", - err) - } err = T.HandleMessages("Not a command", chatID) if err.Error() != testErrNotFound { t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", diff --git a/config/config.go b/config/config.go index 51ef8545..31a5ea7b 100644 --- a/config/config.go +++ b/config/config.go @@ -299,6 +299,7 @@ func (c *Config) CheckCommunicationsConfig() { } else { c.Communications.SMSGlobalConfig = SMSGlobalConfig{ Name: "SMSGlobal", + From: c.Name, Username: "main", Password: "test", @@ -328,6 +329,10 @@ func (c *Config) CheckCommunicationsConfig() { } } else { + if c.Communications.SMSGlobalConfig.From == "" { + c.Communications.SMSGlobalConfig.From = c.Name + } + if c.SMS != nil { // flush old SMS config c.SMS = nil @@ -931,7 +936,7 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].Enabled = false continue } - if c.Exchanges[i].API.AuthenticatedSupport { + if c.Exchanges[i].API.AuthenticatedSupport && c.Exchanges[i].API.CredentialsValidator != nil { if c.Exchanges[i].API.CredentialsValidator.RequiresKey && (c.Exchanges[i].API.Credentials.Key == "" || c.Exchanges[i].API.Credentials.Key == DefaultAPIKey) { c.Exchanges[i].API.AuthenticatedSupport = false } diff --git a/config/config_types.go b/config/config_types.go index 9c317693..cd6b59f2 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -222,6 +222,7 @@ type SMSContact struct { // messaging and broadcast used by SMSGlobal type SMSGlobalConfig struct { Name string `json:"name"` + From string `json:"from"` Enabled bool `json:"enabled"` Verbose bool `json:"verbose"` Username string `json:"username"` diff --git a/engine/engine.go b/engine/engine.go index 28f92b5b..87c50ec1 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "path" + "sync" "syscall" "time" @@ -40,6 +41,7 @@ type Engine struct { Settings Settings CryptocurrencyDepositAddresses map[string]map[string]string Uptime time.Time + ServicesWG sync.WaitGroup } // Vars for engine @@ -436,6 +438,8 @@ func (e *Engine) Stop() { log.Debugln("Config file saved successfully.") } } + // Wait for services to gracefully shutdown + e.ServicesWG.Wait() log.Debugln("Exiting.") log.CloseLogFile() os.Exit(0) diff --git a/engine/events/events.go b/engine/events/events.go index 9fb444d6..5b6df499 100644 --- a/engine/events/events.go +++ b/engine/events/events.go @@ -139,7 +139,10 @@ func (e *Event) ExecuteAction() bool { if action[0] == ActionSMSNotify { message := fmt.Sprintf("Event triggered: %s", e.String()) if action[1] == "ALL" { - comms.PushEvent(base.Event{TradeDetails: message}) + comms.PushEvent(base.Event{ + Type: "event", + Message: message, + }) } } } else { diff --git a/engine/helpers.go b/engine/helpers.go index 826dab3e..9b21b3f0 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -7,13 +7,16 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "errors" "fmt" "math/big" "net" "os" "path/filepath" + "strings" "time" + "github.com/pquerna/otp/totp" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" @@ -26,6 +29,21 @@ import ( "github.com/thrasher-/gocryptotrader/utils" ) +// GetOTPByExchange returns a OTP code for the desired exchange +// if it exists +func GetOTPByExchange(exchName string) (string, error) { + for x := range Bot.Config.Exchanges { + if !strings.EqualFold(Bot.Config.Exchanges[x].Name, exchName) { + continue + } + + if otpSecret := Bot.Config.Exchanges[x].API.Credentials.OTPSecret; otpSecret != "" { + return totp.GenerateCode(otpSecret, time.Now()) + } + } + return "", errors.New("exchange does not have a otpsecret stored") +} + // GetAuthAPISupportedExchanges returns a list of auth api enabled exchanges func GetAuthAPISupportedExchanges() []string { var exchanges []string diff --git a/engine/orders.go b/engine/orders.go index 60fc3bae..c6870a13 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -2,10 +2,12 @@ package engine import ( "errors" - "sync" + "fmt" "sync/atomic" "time" + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/communications/base" exchange "github.com/thrasher-/gocryptotrader/exchanges" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -16,9 +18,10 @@ var ( ErrOrdersAlreadyExists = errors.New("order already exists") ) -type orderStore struct { - m sync.Mutex - Orders map[string][]exchange.OrderDetail +func (o *orderStore) Get() map[string][]exchange.OrderDetail { + o.m.Lock() + defer o.m.Unlock() + return o.Orders } func (o *orderStore) exists(order *exchange.OrderDetail) bool { @@ -50,13 +53,6 @@ func (o *orderStore) Add(order *exchange.OrderDetail) error { return nil } -type orderManager struct { - started int32 - stopped int32 - shutdown chan struct{} - orderStore orderStore -} - func (o *orderManager) Started() bool { return atomic.LoadInt32(&o.started) == 1 } @@ -67,6 +63,9 @@ func (o *orderManager) Start() error { } log.Debugln("Order manager starting...") + + // test param + o.cfg.CancelOrdersOnShutdown = true o.shutdown = make(chan struct{}) o.orderStore.Orders = make(map[string][]exchange.OrderDetail) go o.run() @@ -82,17 +81,59 @@ func (o *orderManager) Stop() error { return nil } +func (o *orderManager) gracefulShutdown() { + if o.cfg.CancelOrdersOnShutdown { + log.Debug("Order manager: Cancelling any open orders...") + orders := o.orderStore.Get() + if orders == nil { + return + } + + for k, v := range orders { + log.Debugf("Order manager: Cancelling order(s) for exchange %s.", k) + for y := range v { + log.Debugf("order manager: Cancelling order ID %v [%v]", + v[y].ID, v[y]) + err := o.Cancel(k, &exchange.OrderCancellation{ + OrderID: v[y].ID, + }) + if err != nil { + msg := fmt.Sprintf("Order manager: Exchange %s unable to cancel order ID=%v. Err: %s", + k, v[y].ID, err) + log.Debugln(msg) + Bot.CommsRelayer.PushEvent(base.Event{ + Type: "order", + Message: msg, + }) + continue + } + + msg := fmt.Sprintf("Order manager: Exchange %s order ID=%v cancelled.", + k, v[y].ID) + log.Debugln(msg) + Bot.CommsRelayer.PushEvent(base.Event{ + Type: "order", + Message: msg, + }) + } + } + } +} + func (o *orderManager) run() { log.Debugln("Order manager started.") tick := time.NewTicker(OrderManagerDelay) + Bot.ServicesWG.Add(1) defer func() { log.Debugf("Order manager shutdown.") tick.Stop() + Bot.ServicesWG.Done() }() for { select { case <-o.shutdown: + o.gracefulShutdown() return case <-tick.C: o.processOrders() @@ -100,9 +141,99 @@ func (o *orderManager) run() { } } -func (o *orderManager) Cancel() {} +func (o *orderManager) CancelAllOrders() {} -func (o *orderManager) Place() {} +func (o *orderManager) Cancel(exchName string, order *exchange.OrderCancellation) error { + if exchName == "" { + return errors.New("order exchange name is empty") + } + + if order == nil { + return errors.New("order cancel param is nil") + } + + if order.OrderID == "" { + return errors.New("order id is empty") + } + + exch := GetExchangeByName(exchName) + if exch == nil { + return errors.New("unable to get exchange by name") + } + + if order.AssetType.String() != "" && !exch.GetAssetTypes().Contains(order.AssetType) { + return errors.New("order asset type not supported by exchange") + } + + return exch.CancelOrder(order) +} + +func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) (*orderSubmitResponse, error) { + if exchName == "" { + return nil, errors.New("order exchange name must be specified") + } + + if order == nil { + return nil, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return nil, err + } + + if o.cfg.EnforceLimitConfig { + if !o.cfg.AllowMarketOrders && order.OrderType == exchange.MarketOrderType { + return nil, errors.New("order market type is not allowed") + } + + if o.cfg.LimitAmount > 0 && order.Amount > o.cfg.LimitAmount { + return nil, errors.New("order limit exceeds allowed limit") + } + + if len(o.cfg.AllowedExchanges) > 0 && + !common.StringDataCompareInsensitive(o.cfg.AllowedExchanges, exchName) { + return nil, errors.New("order exchange not found in allowed list") + } + + if len(o.cfg.AllowedPairs) > 0 && !o.cfg.AllowedPairs.Contains(order.Pair, true) { + return nil, errors.New("order pair not found in allowed list") + } + } + + exch := GetExchangeByName(exchName) + if exch == nil { + return nil, errors.New("unable to get exchange by name") + } + + id, err := common.GetV4UUID() + if err != nil { + log.Warnf("Order manager: Unable to generate UUID. Err: %s", err) + } + + result, err := exch.SubmitOrder(order) + if err != nil { + return nil, err + } + + if result.IsOrderPlaced { + return nil, errors.New("order unable to be placed") + } + + msg := fmt.Sprintf("Order manager: Exchange %s submitted order ID=%v [Ours: %v] pair=%v price=%v amount=%v side=%v type=%v.", + exchName, result.OrderID, id.String(), order.Pair, order.Price, order.Amount, order.OrderSide, order.OrderType) + log.Debugln(msg) + Bot.CommsRelayer.PushEvent(base.Event{ + Type: "order", + Message: msg, + }) + + return &orderSubmitResponse{ + SubmitOrderResponse: exchange.SubmitOrderResponse{ + OrderID: result.OrderID, + }, + OurOrderID: id.String(), + }, nil +} func (o *orderManager) processOrders() { authExchanges := GetAuthAPISupportedExchanges() @@ -123,8 +254,14 @@ func (o *orderManager) processOrders() { order := &result[x] result := o.orderStore.Add(order) if result != ErrOrdersAlreadyExists { - log.Debugf("Order manager: Exchange %s added order ID=%v pair=%v price=%v amount=%v side=%v type=%v.", + msg := fmt.Sprintf("Order manager: Exchange %s added order ID=%v pair=%v price=%v amount=%v side=%v type=%v.", order.Exchange, order.ID, order.CurrencyPair, order.Price, order.Amount, order.OrderSide, order.OrderType) + log.Debug(msg) + Bot.CommsRelayer.PushEvent(base.Event{ + Type: "order", + Message: msg, + }) + continue } } } diff --git a/engine/orders_types.go b/engine/orders_types.go new file mode 100644 index 00000000..db9c460e --- /dev/null +++ b/engine/orders_types.go @@ -0,0 +1,36 @@ +package engine + +import ( + "sync" + + "github.com/thrasher-/gocryptotrader/currency" + exchange "github.com/thrasher-/gocryptotrader/exchanges" +) + +type orderManagerConfig struct { + EnforceLimitConfig bool + AllowMarketOrders bool + CancelOrdersOnShutdown bool + LimitAmount float64 + AllowedPairs currency.Pairs + AllowedExchanges []string + OrderSubmissionRetries int64 +} + +type orderStore struct { + m sync.Mutex + Orders map[string][]exchange.OrderDetail +} + +type orderManager struct { + started int32 + stopped int32 + shutdown chan struct{} + orderStore orderStore + cfg orderManagerConfig +} + +type orderSubmitResponse struct { + exchange.SubmitOrderResponse + OurOrderID string +} diff --git a/engine/portfolio.go b/engine/portfolio.go index 36d613d7..5d91d2b7 100644 --- a/engine/portfolio.go +++ b/engine/portfolio.go @@ -48,10 +48,12 @@ func (p *portfolioManager) Stop() error { func (p *portfolioManager) run() { log.Debugln("Portfolio manager started.") + Bot.ServicesWG.Add(1) tick := time.NewTicker(PortfolioSleepDelay) defer func() { log.Debugf("Portfolio manager shutdown.") tick.Stop() + Bot.ServicesWG.Done() }() for { diff --git a/engine/routines.go b/engine/routines.go index c050a647..1a553114 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -214,7 +214,6 @@ func TickerUpdaterRoutine() { } printTickerSummary(&result, c, assetType, exchangeName, err) if err == nil { - Bot.CommsRelayer.StageTickerData(exchangeName, assetType, &result) if Bot.Config.RemoteControl.WebsocketRPC.Enabled { relayWebsocketEvent(result, "ticker_update", assetType.String(), exchangeName) } @@ -261,7 +260,6 @@ func OrderbookUpdaterRoutine() { result, err := exch.UpdateOrderbook(c, assetType) printOrderbookSummary(&result, c, assetType, exchangeName, err) if err == nil { - Bot.CommsRelayer.StageOrderbookData(exchangeName, assetType, &result) if Bot.Config.RemoteControl.WebsocketRPC.Enabled { relayWebsocketEvent(result, "orderbook_update", assetType.String(), exchangeName) } @@ -324,7 +322,7 @@ var wg sync.WaitGroup func Websocketshutdown(ws *exchange.Websocket) error { err := ws.Shutdown() // shutdown routines on the exchange if err != nil { - log.Errorf("routines.go error - failed to shutodwn %s", err) + log.Errorf("routines.go error - failed to shutdown %s", err) } timer := time.NewTimer(5 * time.Second) @@ -426,7 +424,10 @@ func WebsocketDataHandler(ws *exchange.Websocket) { Low: d.LowPrice, Volume: d.Quantity, } - Bot.ExchangeCurrencyPairManager.update(ws.GetName(), d.Pair, d.AssetType, SyncItemTicker, nil) + if Bot.Settings.EnableExchangeSyncManager && Bot.ExchangeCurrencyPairManager != nil { + Bot.ExchangeCurrencyPairManager.update(ws.GetName(), + d.Pair, d.AssetType, SyncItemTicker, nil) + } ticker.ProcessTicker(ws.GetName(), &tickerNew, d.AssetType) printTickerSummary(&tickerNew, tickerNew.Pair, d.AssetType, ws.GetName(), nil) case exchange.KlineData: @@ -437,7 +438,11 @@ func WebsocketDataHandler(ws *exchange.Websocket) { case exchange.WebsocketOrderbookUpdate: // Orderbook data result := data.(exchange.WebsocketOrderbookUpdate) - Bot.ExchangeCurrencyPairManager.update(ws.GetName(), result.Pair, result.Asset, SyncItemOrderbook, nil) + if Bot.Settings.EnableExchangeSyncManager && Bot.ExchangeCurrencyPairManager != nil { + Bot.ExchangeCurrencyPairManager.update(ws.GetName(), + result.Pair, result.Asset, SyncItemOrderbook, nil) + } + // TO-DO: printOrderbookSummary //nolint:gocritic log.Infof("Websocket %s %s orderbook updated", ws.GetName(), result.Pair.Pair().String()) default: if Bot.Settings.Verbose { diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 5131021f..c057f464 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -176,6 +176,12 @@ func (s *RPCServer) EnableExchange(ctx context.Context, r *gctrpc.GenericExchang return &gctrpc.GenericExchangeNameResponse{}, err } +// GetExchangeOTPCode retrieves an exchanges OTP code +func (s *RPCServer) GetExchangeOTPCode(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeOTPReponse, error) { + result, err := GetOTPByExchange(r.Exchange) + return &gctrpc.GetExchangeOTPReponse{OtpCode: result}, err +} + // GetExchangeInfo gets info for a specific exchange func (s *RPCServer) GetExchangeInfo(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeInfoResponse, error) { exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) @@ -580,9 +586,15 @@ func (s *RPCServer) SubmitOrder(ctx context.Context, r *gctrpc.SubmitOrderReques } p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) - result, err := exch.SubmitOrder(p, exchange.OrderSide(r.Side), - exchange.OrderType(r.OrderType), r.Amount, r.Price, r.ClientId) - + submission := &exchange.OrderSubmission{ + Pair: p, + OrderSide: exchange.OrderSide(r.Side), + OrderType: exchange.OrderType(r.OrderType), + Amount: r.Amount, + Price: r.Price, + ClientID: r.ClientId, + } + result, err := exch.SubmitOrder(submission) return &gctrpc.SubmitOrderResponse{ OrderId: result.OrderID, OrderPlaced: result.IsOrderPlaced, diff --git a/engine/syncer.go b/engine/syncer.go index 30178d94..0c3bf9e5 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -197,7 +197,7 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair return } default: - log.Warnf("ExchangeCurrencyPairSyncer: unkown sync item %v", syncType) + log.Warnf("ExchangeCurrencyPairSyncer: unknown sync item %v", syncType) return } @@ -267,10 +267,6 @@ func (e *ExchangeCurrencyPairSyncer) worker() { continue } - if Bot.Exchanges[x].GetName() == "BTCC" { - continue - } - exchangeName := Bot.Exchanges[x].GetName() assetTypes := Bot.Exchanges[x].GetAssetTypes() supportsREST := Bot.Exchanges[x].SupportsREST() @@ -456,17 +452,13 @@ func (e *ExchangeCurrencyPairSyncer) Start() { continue } - if Bot.Exchanges[x].GetName() == "BTCC" { - continue - } - exchangeName := Bot.Exchanges[x].GetName() supportsWebsocket := Bot.Exchanges[x].SupportsWebsocket() assetTypes := Bot.Exchanges[x].GetAssetTypes() supportsREST := Bot.Exchanges[x].SupportsREST() if !supportsREST && !supportsWebsocket { - log.Warnf("Loaded exchange %s does not support REST or Websocket", exchangeName) + log.Warnf("Loaded exchange %s does not support REST or Websocket.", exchangeName) continue } diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index 66d9b021..3065127e 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -536,12 +536,21 @@ func TestSubmitOrder(t *testing.T) { if areTestAPIKeysSet(a) && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "_", - Base: currency.BTC, - Quote: currency.USD, + + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "_", + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := a.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") + + response, err := a.SubmitOrder(orderSubmission) if !areTestAPIKeysSet(a) && err == nil { t.Error("Expecting an error when no keys are set") } diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 7d0d7be6..81d24eb1 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -183,20 +183,25 @@ func (a *Alphapoint) GetFundingHistory() ([]exchange.FundHistory, error) { // GetExchangeHistory returns historic trade data since exchange opening. func (a *Alphapoint) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { - var resp []exchange.TradeHistory - - return resp, common.ErrNotYetImplemented + return nil, common.ErrNotYetImplemented } // SubmitOrder submits a new order and returns a true value when // successfully submitted -func (a *Alphapoint) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (a *Alphapoint) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } - response, err := a.CreateOrder(p.String(), - side.ToString(), - orderType.ToString(), - amount, price) + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + response, err := a.CreateOrder(order.Pair.String(), + order.OrderSide.ToString(), + order.OrderSide.ToString(), + order.Amount, order.Price) if response > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response) diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index a56afc1a..fc81f78d 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -259,13 +259,20 @@ func TestSubmitOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "_", - Base: currency.BTC, - Quote: currency.USD, + + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "_", + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := a.SubmitOrder(p, - exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") + response, err := a.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 79ee9209..896fca2e 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -339,26 +339,33 @@ func (a *ANX) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([ } // SubmitOrder submits a new order -func (a *ANX) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (a *ANX) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } var isBuying bool var limitPriceInSettlementCurrency float64 - if side == exchange.BuyOrderSide { + if order.OrderSide == exchange.BuyOrderSide { isBuying = true } - if orderType == exchange.LimitOrderType { - limitPriceInSettlementCurrency = price + if order.OrderType == exchange.LimitOrderType { + limitPriceInSettlementCurrency = order.Price } - response, err := a.NewOrder(orderType.ToString(), + response, err := a.NewOrder(order.OrderType.ToString(), isBuying, - p.Base.String(), - amount, - p.Quote.String(), - amount, + order.Pair.Base.String(), + order.Amount, + order.Pair.Quote.String(), + order.Amount, limitPriceInSettlementCurrency, false, "", diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index d0ad8212..077d91b0 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -402,13 +402,20 @@ func TestSubmitOrder(t *testing.T) { b.SetDefaults() TestSetup(t) - var p = currency.Pair{ - Delimiter: "", - Base: currency.LTC, - Quote: currency.BTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "_", + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, - exchange.LimitOrderType, 1, 1, "clientId") + + response, err := b.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index ec0b8e31..ac66d9d4 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -312,18 +312,25 @@ func (b *Binance) GetExchangeHistory(p currency.Pair, assetType assets.AssetType } // SubmitOrder submits a new order -func (b *Binance) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (b *Binance) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } var sideType string - if side == exchange.BuyOrderSide { + if order.OrderSide == exchange.BuyOrderSide { sideType = exchange.BuyOrderSide.ToString() } else { sideType = exchange.SellOrderSide.ToString() } var requestParamsOrderType RequestParamsOrderType - switch orderType { + switch order.OrderType { case exchange.MarketOrderType: requestParamsOrderType = BinanceRequestParamsOrderMarket case exchange.LimitOrderType: @@ -334,10 +341,10 @@ func (b *Binance) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderTyp } var orderRequest = NewOrderRequest{ - Symbol: p.Base.String() + p.Quote.String(), + Symbol: order.Pair.Base.String() + order.Pair.Quote.String(), Side: sideType, - Price: price, - Quantity: amount, + Price: order.Price, + Quantity: order.Amount, TradeType: requestParamsOrderType, TimeInForce: BinanceRequestParamsTimeGTC, } diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index cd190774..5f975349 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -764,12 +764,19 @@ func TestSubmitOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.LTC, - Quote: currency.BTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "_", + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index e4424615..eff51be5 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -295,19 +295,26 @@ func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType assets.AssetTyp } // SubmitOrder submits a new order -func (b *Bitfinex) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (b *Bitfinex) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - var isBuying bool + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } - if side == exchange.BuyOrderSide { + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + var isBuying bool + if order.OrderSide == exchange.BuyOrderSide { isBuying = true } - response, err := b.NewOrder(p.String(), - amount, - price, + response, err := b.NewOrder(order.Pair.String(), + order.Amount, + order.Price, isBuying, - orderType.ToString(), + order.OrderType.ToString(), false) if response.OrderID > 0 { diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index 6eac9747..7e712f31 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -303,12 +303,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.LTC, - Quote: currency.BTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.LTC, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - _, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") + _, err := b.SubmitOrder(orderSubmission) if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not Yet Implemented', received %v", err) } diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index 97a2339f..dc502b2e 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -277,10 +277,8 @@ func (b *Bitflyer) GetExchangeHistory(p currency.Pair, assetType assets.AssetTyp } // SubmitOrder submits a new order -func (b *Bitflyer) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - - return submitOrderResponse, common.ErrNotYetImplemented +func (b *Bitflyer) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { + return exchange.SubmitOrderResponse{}, common.ErrNotYetImplemented } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index 497006a2..aa8f7e02 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -345,12 +345,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.LTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.LTC, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 4164f8bb..3ac1b575 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -272,28 +272,34 @@ func (b *Bithumb) GetExchangeHistory(p currency.Pair, assetType assets.AssetType // SubmitOrder submits a new order // TODO: Fill this out to support limit orders -func (b *Bithumb) SubmitOrder(p currency.Pair, side exchange.OrderSide, _ exchange.OrderType, amount, _ float64, _ string) (exchange.SubmitOrderResponse, error) { +func (b *Bithumb) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - var err error + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + var orderID string - if side == exchange.BuyOrderSide { + var err error + if order.OrderSide == exchange.BuyOrderSide { var result MarketBuy - result, err = b.MarketBuyOrder(p.Base.String(), amount) + result, err = b.MarketBuyOrder(order.Pair.Base.String(), order.Amount) orderID = result.OrderID - } else if side == exchange.SellOrderSide { + } else if order.OrderSide == exchange.SellOrderSide { var result MarketSell - result, err = b.MarketSellOrder(p.Base.String(), amount) + result, err = b.MarketSellOrder(order.Pair.Base.String(), order.Amount) orderID = result.OrderID } if orderID != "" { submitOrderResponse.OrderID = fmt.Sprintf("%v", orderID) } - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index 3c303d9e..aca033b1 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -516,12 +516,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.XBT, - Quote: currency.USD, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.XBT, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index a066d301..7368ed03 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -343,23 +343,30 @@ func (b *Bitmex) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) } // SubmitOrder submits a new order -func (b *Bitmex) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (b *Bitmex) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } - if math.Mod(amount, 1) != 0 { + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + if math.Mod(order.Amount, 1) != 0 { return submitOrderResponse, - errors.New("contract amount can not have decimals") + errors.New("order contract amount can not have decimals") } var orderNewParams = OrderNewParams{ - OrdType: side.ToString(), - Symbol: p.String(), - OrderQty: amount, - Side: side.ToString(), + OrdType: order.OrderSide.ToString(), + Symbol: order.Pair.String(), + OrderQty: order.Amount, + Side: order.OrderSide.ToString(), } - if orderType == exchange.LimitOrderType { - orderNewParams.Price = price + if order.OrderType == exchange.LimitOrderType { + orderNewParams.Price = order.Price } response, err := b.CreateOrder(&orderNewParams) diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index c01438f8..e09c09d0 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -417,13 +417,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.USD, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := b.SubmitOrder(p, - exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 973de297..a28841ab 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -308,11 +308,20 @@ func (b *Bitstamp) GetExchangeHistory(p currency.Pair, assetType assets.AssetTyp } // SubmitOrder submits a new order -func (b *Bitstamp) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (b *Bitstamp) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - buy := side == exchange.BuyOrderSide - market := orderType == exchange.MarketOrderType - response, err := b.PlaceOrder(p.String(), price, amount, buy, market) + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + buy := order.OrderSide == exchange.BuyOrderSide + market := order.OrderType == exchange.MarketOrderType + response, err := b.PlaceOrder(order.Pair.String(), order.Price, order.Amount, + buy, market) if response.ID > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response.ID) diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 323f87b3..3198fe8a 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -373,12 +373,19 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "-", - Base: currency.BTC, - Quote: currency.LTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "-", + Base: currency.BTC, + Quote: currency.LTC, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 9f04cddb..9d418cf5 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -288,20 +288,30 @@ func (b *Bittrex) GetExchangeHistory(p currency.Pair, assetType assets.AssetType } // SubmitOrder submits a new order -func (b *Bittrex) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (b *Bittrex) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - buy := side == exchange.BuyOrderSide + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + buy := order.OrderSide == exchange.BuyOrderSide var response UUID var err error - if orderType != exchange.LimitOrderType { - return submitOrderResponse, errors.New("not supported on exchange") + if order.OrderType != exchange.LimitOrderType { + return submitOrderResponse, errors.New("limit order not supported on exchange") } if buy { - response, err = b.PlaceBuyLimit(p.String(), amount, price) + response, err = b.PlaceBuyLimit(order.Pair.String(), order.Amount, + order.Price) } else { - response, err = b.PlaceSellLimit(p.String(), amount, price) + response, err = b.PlaceSellLimit(order.Pair.String(), order.Amount, + order.Price) } if response.Result.ID != "" { diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index e7b90f71..f1d50089 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -332,12 +332,19 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "-", - Base: currency.BTC, - Quote: currency.LTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "-", + Base: currency.BTC, + Quote: currency.LTC, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := b.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 1, "clientId") + response, err := b.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 2cc4b659..79d2f6a0 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -273,15 +273,30 @@ func (b *BTCMarkets) GetExchangeHistory(p currency.Pair, assetType assets.AssetT } // SubmitOrder submits a new order -func (b *BTCMarkets) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { +func (b *BTCMarkets) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - response, err := b.NewOrder(p.Base.Upper().String(), - p.Quote.Upper().String(), - price, - amount, - side.ToString(), - orderType.ToString(), - clientID) + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + if strings.EqualFold(order.OrderSide.ToString(), exchange.SellOrderSide.ToString()) { + order.OrderSide = exchange.AskOrderSide + } + if strings.EqualFold(order.OrderSide.ToString(), exchange.BuyOrderSide.ToString()) { + order.OrderSide = exchange.BuyOrderSide + } + + response, err := b.NewOrder(order.Pair.Base.Upper().String(), + order.Pair.Quote.Upper().String(), + order.Price, + order.Amount, + order.OrderSide.ToString(), + order.OrderType.ToString(), + order.ClientID) if response > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response) diff --git a/exchanges/btse/btse_test.go b/exchanges/btse/btse_test.go index d46f9d74..f3c36817 100644 --- a/exchanges/btse/btse_test.go +++ b/exchanges/btse/btse_test.go @@ -242,12 +242,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.USD, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := b.SubmitOrder(p, exchange.SellOrderSide, exchange.LimitOrderType, 0.01, 1000000, "clientId") + response, err := b.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 626331ae..d9665df8 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -260,11 +260,19 @@ func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ( } // SubmitOrder submits a new order -func (b *BTSE) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { +func (b *BTSE) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var resp exchange.SubmitOrderResponse - r, err := b.CreateOrder(amount, price, side.ToString(), - orderType.ToString(), b.FormatExchangeCurrency(p, - assets.AssetTypeSpot).String(), "GTC", clientID) + if order == nil { + return resp, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return resp, err + } + + r, err := b.CreateOrder(order.Amount, order.Price, order.OrderSide.ToString(), + order.OrderType.ToString(), b.FormatExchangeCurrency(order.Pair, + assets.AssetTypeSpot).String(), "GTC", order.ClientID) if err != nil { return resp, err } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index af15e153..8e080db2 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -470,13 +470,19 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "-", - Base: currency.BTC, - Quote: currency.LTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "-", + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := c.SubmitOrder(p, exchange.BuyOrderSide, - exchange.LimitOrderType, 1, 1, "clientId") + response, err := c.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index ddcee869..5325b53e 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -285,27 +285,34 @@ func (c *CoinbasePro) GetExchangeHistory(p currency.Pair, assetType assets.Asset } // SubmitOrder submits a new order -func (c *CoinbasePro) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (c *CoinbasePro) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + var response string var err error - - switch orderType { + switch order.OrderType { case exchange.MarketOrderType: - response, err = c.PlaceMarginOrder("", - amount, - amount, - side.ToString(), - p.String(), + response, err = c.PlaceMarketOrder("", + order.Amount, + order.Amount, + order.OrderSide.ToString(), + order.Pair.String(), "") case exchange.LimitOrderType: response, err = c.PlaceLimitOrder("", - price, - amount, - side.ToString(), + order.Price, + order.Amount, + order.OrderSide.ToString(), "", "", - p.String(), + order.Pair.String(), "", false) default: diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 484646b8..30d5cbe3 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -246,12 +246,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.USD, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := c.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "1234234") + response, err := c.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 33c7a6aa..a1958a6e 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -1,7 +1,6 @@ package coinut import ( - "errors" "fmt" "strconv" "strings" @@ -328,33 +327,41 @@ func (c *COINUT) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) } // SubmitOrder submits a new order -func (c *COINUT) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { +func (c *COINUT) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - var err error - var APIresponse interface{} - isBuyOrder := side == exchange.BuyOrderSide - clientIDInt, err := strconv.ParseUint(clientID, 0, 32) - clientIDUint := uint32(clientIDInt) + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + var APIresponse interface{} + isBuyOrder := order.OrderSide == exchange.BuyOrderSide + clientIDInt, err := strconv.ParseUint(order.ClientID, 0, 32) if err != nil { return submitOrderResponse, err } + + clientIDUint := uint32(clientIDInt) + // Need to get the ID of the currency sent instruments, err := c.GetInstruments() if err != nil { return submitOrderResponse, err } - currencyArray := instruments.Instruments[p.String()] + currencyArray := instruments.Instruments[order.Pair.String()] currencyID := currencyArray[0].InstID - switch orderType { + switch order.OrderType { case exchange.LimitOrderType: - APIresponse, err = c.NewOrder(currencyID, amount, price, isBuyOrder, clientIDUint) + APIresponse, err = c.NewOrder(currencyID, order.Amount, order.Price, + isBuyOrder, clientIDUint) case exchange.MarketOrderType: - APIresponse, err = c.NewOrder(currencyID, amount, 0, isBuyOrder, clientIDUint) - default: - return submitOrderResponse, errors.New("unsupported order type") + APIresponse, err = c.NewOrder(currencyID, order.Amount, 0, isBuyOrder, + clientIDUint) } switch apiResp := APIresponse.(type) { diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index 23060843..3adb5b5d 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -1,14 +1,10 @@ package exchange import ( - "fmt" - "sort" - "strings" "time" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/exchanges/request" ) @@ -51,12 +47,6 @@ const ( Contact ) -// SubmitOrderResponse is what is returned after submitting an order to an exchange -type SubmitOrderResponse struct { - IsOrderPlaced bool - OrderID string -} - // FeeBuilder is the type which holds all parameters required to calculate a fee // for an exchange type FeeBuilder struct { @@ -119,80 +109,6 @@ const ( UnknownWithdrawalTypeText string = "UNKNOWN" ) -// ModifyOrder is a an order modifyer -// ModifyOrder is a an order modifyer -type ModifyOrder struct { - OrderID string - OrderType - OrderSide - Price float64 - Amount float64 - LimitPriceUpper float64 - LimitPriceLower float64 - CurrencyPair currency.Pair - - ImmediateOrCancel bool - HiddenOrder bool - FillOrKill bool - PostOnly bool -} - -// ModifyOrderResponse is an order modifying return type -type ModifyOrderResponse struct { - OrderID string -} - -// CancelAllOrdersResponse returns the status from attempting to cancel all orders on an exchagne -type CancelAllOrdersResponse struct { - OrderStatus map[string]string -} - -// OrderType enforces a standard for Ordertypes across the code base -type OrderType string - -// OrderType ...types -const ( - AnyOrderType OrderType = "ANY" - LimitOrderType OrderType = "LIMIT" - MarketOrderType OrderType = "MARKET" - ImmediateOrCancelOrderType OrderType = "IMMEDIATE_OR_CANCEL" - StopOrderType OrderType = "STOP" - TrailingStopOrderType OrderType = "TRAILINGSTOP" - UnknownOrderType OrderType = "UNKNOWN" -) - -// ToLower changes the ordertype to lower case -func (o OrderType) ToLower() OrderType { - return OrderType(strings.ToLower(string(o))) -} - -// ToString changes the ordertype to the exchange standard and returns a string -func (o OrderType) ToString() string { - return fmt.Sprintf("%v", o) -} - -// OrderSide enforces a standard for OrderSides across the code base -type OrderSide string - -// OrderSide types -const ( - AnyOrderSide OrderSide = "ANY" - BuyOrderSide OrderSide = "BUY" - SellOrderSide OrderSide = "SELL" - BidOrderSide OrderSide = "BID" - AskOrderSide OrderSide = "ASK" -) - -// ToLower changes the ordertype to lower case -func (o OrderSide) ToLower() OrderSide { - return OrderSide(strings.ToLower(string(o))) -} - -// ToString changes the ordertype to the exchange standard and returns a string -func (o OrderSide) ToString() string { - return fmt.Sprintf("%v", o) -} - // AccountInfo is a Generic type to hold each exchange's holdings in // all enabled currencies type AccountInfo struct { @@ -225,34 +141,6 @@ type TradeHistory struct { Description string } -// OrderDetail holds order detail data -type OrderDetail struct { - Exchange string - AccountID string - ID string - CurrencyPair currency.Pair - OrderSide OrderSide - OrderType OrderType - OrderDate time.Time - Status string - Price float64 - Amount float64 - ExecutedAmount float64 - RemainingAmount float64 - Fee float64 - Trades []TradeHistory -} - -// OrderCancellation type required when requesting to cancel an order -type OrderCancellation struct { - AccountID string - OrderID string - CurrencyPair currency.Pair - AssetType assets.AssetType - WalletAddress string - Side OrderSide -} - // FundHistory holds exchange funding history data type FundHistory struct { ExchangeName string @@ -402,227 +290,6 @@ type API struct { } } -// GetOrdersRequest used for GetOrderHistory and GetOpenOrders wrapper functions -type GetOrdersRequest struct { - OrderType OrderType - OrderSide OrderSide - StartTicks time.Time - EndTicks time.Time - // Currencies Empty array = all currencies. Some endpoints only support singular currency enquiries - Currencies []currency.Pair -} - -// OrderStatus defines order status types -type OrderStatus string - -// All OrderStatus types -const ( - AnyOrderStatus OrderStatus = "ANY" - NewOrderStatus OrderStatus = "NEW" - ActiveOrderStatus OrderStatus = "ACTIVE" - PartiallyFilledOrderStatus OrderStatus = "PARTIALLY_FILLED" - FilledOrderStatus OrderStatus = "FILLED" - CancelledOrderStatus OrderStatus = "CANCELED" - PendingCancelOrderStatus OrderStatus = "PENDING_CANCEL" - RejectedOrderStatus OrderStatus = "REJECTED" - ExpiredOrderStatus OrderStatus = "EXPIRED" - HiddenOrderStatus OrderStatus = "HIDDEN" - UnknownOrderStatus OrderStatus = "UNKNOWN" -) - -// FilterOrdersBySide removes any OrderDetails that don't match the orderStatus provided -func FilterOrdersBySide(orders *[]OrderDetail, orderSide OrderSide) { - if orderSide == "" || orderSide == AnyOrderSide { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - if strings.EqualFold(string((*orders)[i].OrderSide), string(orderSide)) { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// FilterOrdersByType removes any OrderDetails that don't match the orderType provided -func FilterOrdersByType(orders *[]OrderDetail, orderType OrderType) { - if orderType == "" || orderType == AnyOrderType { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - if strings.EqualFold(string((*orders)[i].OrderType), string(orderType)) { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// FilterOrdersByTickRange removes any OrderDetails outside of the tick range -func FilterOrdersByTickRange(orders *[]OrderDetail, startTicks, endTicks time.Time) { - if startTicks.IsZero() || endTicks.IsZero() || - startTicks.Unix() == 0 || endTicks.Unix() == 0 || endTicks.Before(startTicks) { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - if (*orders)[i].OrderDate.Unix() >= startTicks.Unix() && (*orders)[i].OrderDate.Unix() <= endTicks.Unix() { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// FilterOrdersByCurrencies removes any OrderDetails that do not match the provided currency list -// It is forgiving in that the provided currencies can match quote or base currencies -func FilterOrdersByCurrencies(orders *[]OrderDetail, currencies []currency.Pair) { - if len(currencies) == 0 { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - matchFound := false - for _, c := range currencies { - if !matchFound && (*orders)[i].CurrencyPair.EqualIncludeReciprocal(c) { - matchFound = true - } - } - - if matchFound { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// ByPrice used for sorting orders by price -type ByPrice []OrderDetail - -func (b ByPrice) Len() int { - return len(b) -} - -func (b ByPrice) Less(i, j int) bool { - return b[i].Price < b[j].Price -} - -func (b ByPrice) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByPrice the caller function to sort orders -func SortOrdersByPrice(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByPrice(*orders))) - } else { - sort.Sort(ByPrice(*orders)) - } -} - -// ByOrderType used for sorting orders by order type -type ByOrderType []OrderDetail - -func (b ByOrderType) Len() int { - return len(b) -} - -func (b ByOrderType) Less(i, j int) bool { - return b[i].OrderType.ToString() < b[j].OrderType.ToString() -} - -func (b ByOrderType) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByType the caller function to sort orders -func SortOrdersByType(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByOrderType(*orders))) - } else { - sort.Sort(ByOrderType(*orders)) - } -} - -// ByCurrency used for sorting orders by order currency -type ByCurrency []OrderDetail - -func (b ByCurrency) Len() int { - return len(b) -} - -func (b ByCurrency) Less(i, j int) bool { - return b[i].CurrencyPair.String() < b[j].CurrencyPair.String() -} - -func (b ByCurrency) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByCurrency the caller function to sort orders -func SortOrdersByCurrency(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByCurrency(*orders))) - } else { - sort.Sort(ByCurrency(*orders)) - } -} - -// ByDate used for sorting orders by order date -type ByDate []OrderDetail - -func (b ByDate) Len() int { - return len(b) -} - -func (b ByDate) Less(i, j int) bool { - return b[i].OrderDate.Unix() < b[j].OrderDate.Unix() -} - -func (b ByDate) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByDate the caller function to sort orders -func SortOrdersByDate(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByDate(*orders))) - } else { - sort.Sort(ByDate(*orders)) - } -} - -// ByOrderSide used for sorting orders by order side (buy sell) -type ByOrderSide []OrderDetail - -func (b ByOrderSide) Len() int { - return len(b) -} - -func (b ByOrderSide) Less(i, j int) bool { - return b[i].OrderSide.ToString() < b[j].OrderSide.ToString() -} - -func (b ByOrderSide) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersBySide the caller function to sort orders -func SortOrdersBySide(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByOrderSide(*orders))) - } else { - sort.Sort(ByOrderSide(*orders)) - } -} - // Base stores the individual exchange information type Base struct { Name string diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index dcc79153..6d6537a6 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -306,13 +306,19 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "_", - Base: currency.BTC, - Quote: currency.USD, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "_", + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := e.SubmitOrder(p, exchange.BuyOrderSide, - exchange.LimitOrderType, 1, 10, "1234234") + response, err := e.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index a46ffc05..0703dca5 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -298,24 +298,29 @@ func (e *EXMO) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ( } // SubmitOrder submits a new order -func (e *EXMO) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (e *EXMO) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - var oT string + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } - switch orderType { + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + var oT string + switch order.OrderType { case exchange.LimitOrderType: return submitOrderResponse, errors.New("unsupported order type") case exchange.MarketOrderType: oT = "market_buy" - if side == exchange.SellOrderSide { + if order.OrderSide == exchange.SellOrderSide { oT = "market_sell" } - default: - return submitOrderResponse, errors.New("unsupported order type") } - response, err := e.CreateOrder(p.String(), oT, price, amount) - + response, err := e.CreateOrder(order.Pair.String(), oT, order.Price, + order.Amount) if response > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response) } diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 4adc7866..c4466f15 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -312,13 +312,19 @@ func TestSubmitOrder(t *testing.T) { t.Skip() } - var p = currency.Pair{ - Delimiter: "_", - Base: currency.LTC, - Quote: currency.BTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "_", + Base: currency.LTC, + Quote: currency.BTC, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := g.SubmitOrder(p, exchange.BuyOrderSide, - exchange.LimitOrderType, 1, 10, "1234234") + response, err := g.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 36786d63..a9aa82c2 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -318,25 +318,31 @@ func (g *Gateio) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) // SubmitOrder submits a new order // TODO: support multiple order types (IOC) -func (g *Gateio) SubmitOrder(p currency.Pair, side exchange.OrderSide, _ exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (g *Gateio) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - var orderTypeFormat string + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } - if side == exchange.BuyOrderSide { + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + var orderTypeFormat string + if order.OrderSide == exchange.BuyOrderSide { orderTypeFormat = exchange.BuyOrderSide.ToLower().ToString() } else { orderTypeFormat = exchange.SellOrderSide.ToLower().ToString() } var spotNewOrderRequestParams = SpotNewOrderRequestParams{ - Amount: amount, - Price: price, - Symbol: p.String(), + Amount: order.Amount, + Price: order.Price, + Symbol: order.Pair.String(), Type: orderTypeFormat, } response, err := g.SpotNewOrder(spotNewOrderRequestParams) - if response.OrderNumber > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response.OrderNumber) } diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index 1078befe..162c8eec 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -409,13 +409,19 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "_", - Base: currency.LTC, - Quote: currency.BTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "_", + Base: currency.LTC, + Quote: currency.BTC, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := Session[1].SubmitOrder(p, exchange.BuyOrderSide, - exchange.LimitOrderType, 1, 10, "1234234") + response, err := Session[1].SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index f73ad915..ad5968b7 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -266,22 +266,27 @@ func (g *Gemini) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) } // SubmitOrder submits a new order -func (g *Gemini) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (g *Gemini) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - response, err := g.NewOrder(p.String(), - amount, - price, - side.ToString(), - orderType.ToString()) + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + response, err := g.NewOrder(order.Pair.String(), + order.Amount, + order.Price, + order.OrderSide.ToString(), + order.OrderType.ToString()) if response > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response) } - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index ee1428f0..d8fad344 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -232,13 +232,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.DGD, - Quote: currency.BTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.DGD, + Quote: currency.BTC, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := h.SubmitOrder(p, exchange.BuyOrderSide, - exchange.LimitOrderType, 1, 10, "1234234") + response, err := h.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 7eea50c1..f60982d9 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -291,22 +291,27 @@ func (h *HitBTC) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) } // SubmitOrder submits a new order -func (h *HitBTC) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (h *HitBTC) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - response, err := h.PlaceOrder(p.String(), - price, - amount, - strings.ToLower(orderType.ToString()), - strings.ToLower(side.ToString())) + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + response, err := h.PlaceOrder(order.Pair.String(), + order.Price, + order.Amount, + strings.ToLower(order.OrderType.ToString()), + strings.ToLower(order.OrderSide.ToString())) if response.OrderNumber > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response.OrderNumber) } - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 0ce83171..033dbfcb 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -432,18 +432,23 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.USDT, - } - accounts, err := h.GetAccounts() if err != nil { t.Fatalf("Failed to get accounts. Err: %s", err) } - response, err := h.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, strconv.FormatInt(accounts[0].ID, 10)) + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.USDT, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: strconv.FormatInt(accounts[0].ID, 10), + } + response, err := h.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 48905574..b503dfb0 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -379,34 +379,40 @@ func (h *HUOBI) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) } // SubmitOrder submits a new order -func (h *HUOBI) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { +func (h *HUOBI) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - accountID, err := strconv.ParseInt(clientID, 10, 64) + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + accountID, err := strconv.ParseInt(order.ClientID, 10, 64) if err != nil { return submitOrderResponse, err } var formattedType SpotNewOrderRequestParamsType var params = SpotNewOrderRequestParams{ - Amount: amount, + Amount: order.Amount, Source: "api", - Symbol: strings.ToLower(p.String()), + Symbol: strings.ToLower(order.Pair.String()), AccountID: int(accountID), } switch { - case side == exchange.BuyOrderSide && orderType == exchange.MarketOrderType: + case order.OrderSide == exchange.BuyOrderSide && order.OrderType == exchange.MarketOrderType: formattedType = SpotNewOrderRequestTypeBuyMarket - case side == exchange.SellOrderSide && orderType == exchange.MarketOrderType: + case order.OrderSide == exchange.SellOrderSide && order.OrderType == exchange.MarketOrderType: formattedType = SpotNewOrderRequestTypeSellMarket - case side == exchange.BuyOrderSide && orderType == exchange.LimitOrderType: + case order.OrderSide == exchange.BuyOrderSide && order.OrderType == exchange.LimitOrderType: formattedType = SpotNewOrderRequestTypeBuyLimit - params.Price = price - case side == exchange.SellOrderSide && orderType == exchange.LimitOrderType: + params.Price = order.Price + case order.OrderSide == exchange.SellOrderSide && order.OrderType == exchange.LimitOrderType: formattedType = SpotNewOrderRequestTypeSellLimit - params.Price = price - default: - return submitOrderResponse, errors.New("unsupported order type") + params.Price = order.Price } params.Type = formattedType @@ -414,11 +420,9 @@ func (h *HUOBI) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType if response > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response) } - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } diff --git a/exchanges/huobihadax/huobihadax_test.go b/exchanges/huobihadax/huobihadax_test.go index ea0c57d3..4e4ea485 100644 --- a/exchanges/huobihadax/huobihadax_test.go +++ b/exchanges/huobihadax/huobihadax_test.go @@ -466,18 +466,23 @@ func TestSubmitOrder(t *testing.T) { t.Skip() } - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.USDT, - } - accounts, err := h.GetAccounts() if err != nil { t.Fatalf("Failed to get accounts. Err: %s", err) } - response, err := h.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, strconv.FormatInt(accounts[0].ID, 10)) + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.USDT, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: strconv.FormatInt(accounts[0].ID, 10), + } + response, err := h.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } diff --git a/exchanges/huobihadax/huobihadax_wrapper.go b/exchanges/huobihadax/huobihadax_wrapper.go index 6923b317..f2f456e9 100644 --- a/exchanges/huobihadax/huobihadax_wrapper.go +++ b/exchanges/huobihadax/huobihadax_wrapper.go @@ -340,48 +340,50 @@ func (h *HUOBIHADAX) GetExchangeHistory(p currency.Pair, assetType assets.AssetT } // SubmitOrder submits a new order -func (h *HUOBIHADAX) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { +func (h *HUOBIHADAX) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - accountID, err := strconv.ParseInt(clientID, 0, 64) + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + accountID, err := strconv.ParseInt(order.ClientID, 10, 64) if err != nil { return submitOrderResponse, err } var formattedType SpotNewOrderRequestParamsType var params = SpotNewOrderRequestParams{ - Amount: amount, + Amount: order.Amount, Source: "api", - Symbol: strings.ToLower(p.String()), + Symbol: strings.ToLower(order.Pair.String()), AccountID: int(accountID), } switch { - case side == exchange.BuyOrderSide && orderType == exchange.MarketOrderType: + case order.OrderSide == exchange.BuyOrderSide && order.OrderType == exchange.MarketOrderType: formattedType = SpotNewOrderRequestTypeBuyMarket - case side == exchange.SellOrderSide && orderType == exchange.MarketOrderType: + case order.OrderSide == exchange.SellOrderSide && order.OrderType == exchange.MarketOrderType: formattedType = SpotNewOrderRequestTypeSellMarket - case side == exchange.BuyOrderSide && orderType == exchange.LimitOrderType: + case order.OrderSide == exchange.BuyOrderSide && order.OrderType == exchange.LimitOrderType: formattedType = SpotNewOrderRequestTypeBuyLimit - params.Price = price - case side == exchange.SellOrderSide && orderType == exchange.LimitOrderType: + params.Price = order.Price + case order.OrderSide == exchange.SellOrderSide && order.OrderType == exchange.LimitOrderType: formattedType = SpotNewOrderRequestTypeSellLimit - params.Price = price - default: - return submitOrderResponse, errors.New("unsupported order type") + params.Price = order.Price } params.Type = formattedType - response, err := h.SpotNewOrder(params) - if response > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response) } - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go index 4723ab5a..e5cf96f0 100644 --- a/exchanges/interfaces.go +++ b/exchanges/interfaces.go @@ -40,7 +40,7 @@ type IBotExchange interface { FormatWithdrawPermissions() string SupportsWithdrawPermissions(permissions uint32) bool GetFundingHistory() ([]FundHistory, error) - SubmitOrder(p currency.Pair, side OrderSide, orderType OrderType, amount, price float64, clientID string) (SubmitOrderResponse, error) + SubmitOrder(order *OrderSubmission) (SubmitOrderResponse, error) ModifyOrder(action *ModifyOrder) (string, error) CancelOrder(order *OrderCancellation) error CancelAllOrders(orders *OrderCancellation) (CancelAllOrdersResponse, error) diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index 0c76ab13..212f1e79 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -303,12 +303,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.USDT, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := i.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "hi") + response, err := i.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 328a8126..a5397099 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -273,10 +273,17 @@ func (i *ItBit) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) } // SubmitOrder submits a new order -func (i *ItBit) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (i *ItBit) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - var wallet string + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + var wallet string wallets, err := i.GetWallets(url.Values{}) if err != nil { return submitOrderResponse, err @@ -285,8 +292,8 @@ func (i *ItBit) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType // Determine what wallet ID to use if there is any actual available currency to make the trade! for _, i := range wallets { for j := range i.Balances { - if i.Balances[j].Currency == p.Base.String() && - i.Balances[j].AvailableBalance >= amount { + if i.Balances[j].Currency == order.Pair.Base.String() && + i.Balances[j].AvailableBalance >= order.Amount { wallet = i.ID } } @@ -295,23 +302,21 @@ func (i *ItBit) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType if wallet == "" { return submitOrderResponse, fmt.Errorf("no wallet found with currency: %s with amount >= %v", - p.Base, - amount) + order.Pair.Base, + order.Amount) } response, err := i.PlaceOrder(wallet, - side.ToString(), - orderType.ToString(), - p.Base.String(), - amount, - price, - p.String(), + order.OrderSide.ToString(), + order.OrderType.ToString(), + order.Pair.Base.String(), + order.Amount, + order.Price, + order.Pair.String(), "") - if response.ID != "" { submitOrderResponse.OrderID = response.ID } - if err == nil { submitOrderResponse.IsOrderPlaced = true } diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 4b255fbf..b6d1ed52 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -422,12 +422,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.XBT, - Quote: currency.CAD, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.XBT, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := k.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "hi") + response, err := k.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 7a98bc28..297ef16c 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -318,27 +318,31 @@ func (k *Kraken) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) } // SubmitOrder submits a new order -func (k *Kraken) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (k *Kraken) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - var args = AddOrderOptions{} + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } - response, err := k.AddOrder(p.String(), - side.ToString(), - orderType.ToString(), - amount, - price, + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + var args = AddOrderOptions{} + response, err := k.AddOrder(order.Pair.String(), + order.OrderSide.ToString(), + order.OrderType.ToString(), + order.Amount, + order.Price, 0, 0, &args) - if len(response.TransactionIds) > 0 { submitOrderResponse.OrderID = strings.Join(response.TransactionIds, ", ") } - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index 1d52981b..a6e0ce57 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -297,12 +297,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.EUR, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.EUR, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := l.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "hi") + response, err := l.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 9b563996..d49333b9 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -266,19 +266,25 @@ func (l *LakeBTC) GetExchangeHistory(p currency.Pair, assetType assets.AssetType } // SubmitOrder submits a new order -func (l *LakeBTC) SubmitOrder(p currency.Pair, side exchange.OrderSide, _ exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (l *LakeBTC) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - isBuyOrder := side == exchange.BuyOrderSide - response, err := l.Trade(isBuyOrder, amount, price, p.Lower().String()) + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + isBuyOrder := order.OrderSide == exchange.BuyOrderSide + response, err := l.Trade(isBuyOrder, order.Amount, order.Price, + order.Pair.Lower().String()) if response.ID > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response.ID) } - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } diff --git a/exchanges/localbitcoins/localbitcoins_test.go b/exchanges/localbitcoins/localbitcoins_test.go index 87b0ef9f..8fcd85df 100644 --- a/exchanges/localbitcoins/localbitcoins_test.go +++ b/exchanges/localbitcoins/localbitcoins_test.go @@ -256,12 +256,18 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.EUR, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.EUR, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := l.SubmitOrder(p, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "hi") + response, err := l.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index e2f5ff64..caa93459 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -255,8 +255,16 @@ func (l *LocalBitcoins) GetExchangeHistory(p currency.Pair, assetType assets.Ass } // SubmitOrder submits a new order -func (l *LocalBitcoins) SubmitOrder(p currency.Pair, side exchange.OrderSide, _ exchange.OrderType, amount, _ float64, _ string) (exchange.SubmitOrderResponse, error) { +func (l *LocalBitcoins) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + // These are placeholder details // TODO store a user's localbitcoin details to use here var params = AdCreate{ @@ -266,17 +274,17 @@ func (l *LocalBitcoins) SubmitOrder(p currency.Pair, side exchange.OrderSide, _ City: "City", Location: "Location", CountryCode: "US", - Currency: p.Quote.String(), + Currency: order.Pair.Quote.String(), AccountInfo: "-", BankName: "Bank", - MSG: side.ToString(), + MSG: order.OrderSide.ToString(), SMSVerficationRequired: true, TrackMaxAmount: true, RequireTrustedByAdvertiser: true, RequireIdentification: true, OnlineProvider: "", TradeType: "", - MinAmount: int(math.Round(amount)), + MinAmount: int(math.Round(order.Amount)), } // Does not return any orderID, so create the add, then get the order diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index aa1622d8..231d9324 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -1045,13 +1045,18 @@ func TestFormatWithdrawPermissions(t *testing.T) { // TestSubmitOrder Wrapper test func TestSubmitOrder(t *testing.T) { TestSetRealOrderDefaults(t) - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.EUR, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := o.SubmitOrder(p, exchange.BuyOrderSide, - exchange.LimitOrderType, 1, 10, "hi") + response, err := o.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index 6eac2880..06271113 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -1815,13 +1815,18 @@ func TestFormatWithdrawPermissions(t *testing.T) { func TestSubmitOrder(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - var p = currency.Pair{ - Delimiter: "", - Base: currency.BTC, - Quote: currency.USDT, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.USDT, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := o.SubmitOrder(p, exchange.BuyOrderSide, - exchange.LimitOrderType, 1, 10, "hi") + response, err := o.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index f9f1e54a..f4367e2c 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -206,25 +206,34 @@ func (o *OKGroup) GetExchangeHistory(p currency.Pair, assetType assets.AssetType } // SubmitOrder submits a new order -func (o *OKGroup) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (resp exchange.SubmitOrderResponse, err error) { - request := PlaceSpotOrderRequest{ - ClientOID: clientID, - InstrumentID: o.FormatExchangeCurrency(p, assets.AssetTypeSpot).String(), - Side: strings.ToLower(side.ToString()), - Type: strings.ToLower(orderType.ToString()), - Size: strconv.FormatFloat(amount, 'f', -1, 64), +func (o *OKGroup) SubmitOrder(order *exchange.OrderSubmission) (resp exchange.SubmitOrderResponse, err error) { + if order == nil { + return resp, exchange.ErrOrderSubmissionIsNil } - if orderType == exchange.LimitOrderType { - request.Price = strconv.FormatFloat(price, 'f', -1, 64) + + err = order.Validate() + if err != nil { + return resp, err + } + + request := PlaceSpotOrderRequest{ + ClientOID: order.ClientID, + InstrumentID: o.FormatExchangeCurrency(order.Pair, assets.AssetTypeSpot).String(), + Side: strings.ToLower(order.OrderSide.ToString()), + Type: strings.ToLower(order.OrderType.ToString()), + Size: strconv.FormatFloat(order.Amount, 'f', -1, 64), + } + if order.OrderType == exchange.LimitOrderType { + request.Price = strconv.FormatFloat(order.Price, 'f', -1, 64) } orderResponse, err := o.PlaceSpotOrder(&request) if err != nil { return } + resp.IsOrderPlaced = orderResponse.Result resp.OrderID = orderResponse.OrderID - return } diff --git a/exchanges/order_types.go b/exchanges/order_types.go new file mode 100644 index 00000000..ebfe59f5 --- /dev/null +++ b/exchanges/order_types.go @@ -0,0 +1,380 @@ +package exchange + +import ( + "errors" + "fmt" + "sort" + "strings" + "time" + + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" +) + +// vars related to orders +var ( + ErrOrderSubmissionIsNil = errors.New("order submission is nil") +) + +// OrderSubmission contains the order submission data +type OrderSubmission struct { + Pair currency.Pair + OrderSide OrderSide + OrderType OrderType + Price float64 + Amount float64 + ClientID string +} + +// Validate checks the supplied data and returns whether or not its valid +func (o *OrderSubmission) Validate() error { + if o.Pair.IsEmpty() { + return errors.New("order pair is empty") + } + + if o.OrderSide != BuyOrderSide && o.OrderSide != SellOrderSide || + o.OrderSide != BidOrderSide && o.OrderSide != AskOrderSide { + return errors.New("order side is invalid") + } + + if o.OrderType != MarketOrderType && o.OrderType != LimitOrderType { + return errors.New("order type is invalid") + } + + if o.Amount <= 0 { + return errors.New("order amount is invalid") + } + + if o.OrderType == LimitOrderType && o.Price <= 0 { + return errors.New("order price must be set if limit order type is desired") + } + + return nil +} + +// SubmitOrderResponse is what is returned after submitting an order to an exchange +type SubmitOrderResponse struct { + IsOrderPlaced bool + OrderID string +} + +// ModifyOrder is a an order modifyer +type ModifyOrder struct { + OrderID string + OrderType + OrderSide + Price float64 + Amount float64 + LimitPriceUpper float64 + LimitPriceLower float64 + CurrencyPair currency.Pair + ImmediateOrCancel bool + HiddenOrder bool + FillOrKill bool + PostOnly bool +} + +// ModifyOrderResponse is an order modifying return type +type ModifyOrderResponse struct { + OrderID string +} + +// CancelAllOrdersResponse returns the status from attempting to cancel all orders on an exchagne +type CancelAllOrdersResponse struct { + OrderStatus map[string]string +} + +// OrderType enforces a standard for Ordertypes across the code base +type OrderType string + +// OrderType ...types +const ( + AnyOrderType OrderType = "ANY" + LimitOrderType OrderType = "LIMIT" + MarketOrderType OrderType = "MARKET" + ImmediateOrCancelOrderType OrderType = "IMMEDIATE_OR_CANCEL" + StopOrderType OrderType = "STOP" + TrailingStopOrderType OrderType = "TRAILINGSTOP" + UnknownOrderType OrderType = "UNKNOWN" +) + +// ToLower changes the ordertype to lower case +func (o OrderType) ToLower() OrderType { + return OrderType(strings.ToLower(string(o))) +} + +// ToString changes the ordertype to the exchange standard and returns a string +func (o OrderType) ToString() string { + return fmt.Sprintf("%v", o) +} + +// OrderSide enforces a standard for OrderSides across the code base +type OrderSide string + +// OrderSide types +const ( + AnyOrderSide OrderSide = "ANY" + BuyOrderSide OrderSide = "BUY" + SellOrderSide OrderSide = "SELL" + BidOrderSide OrderSide = "BID" + AskOrderSide OrderSide = "ASK" +) + +// ToLower changes the ordertype to lower case +func (o OrderSide) ToLower() OrderSide { + return OrderSide(strings.ToLower(string(o))) +} + +// ToString changes the ordertype to the exchange standard and returns a string +func (o OrderSide) ToString() string { + return fmt.Sprintf("%v", o) +} + +// OrderDetail holds order detail data +type OrderDetail struct { + Exchange string + AccountID string + ID string + CurrencyPair currency.Pair + OrderSide OrderSide + OrderType OrderType + OrderDate time.Time + Status string + Price float64 + Amount float64 + ExecutedAmount float64 + RemainingAmount float64 + Fee float64 + Trades []TradeHistory +} + +// OrderCancellation type required when requesting to cancel an order +type OrderCancellation struct { + AccountID string + OrderID string + CurrencyPair currency.Pair + AssetType assets.AssetType + WalletAddress string + Side OrderSide +} + +// GetOrdersRequest used for GetOrderHistory and GetOpenOrders wrapper functions +type GetOrdersRequest struct { + OrderType OrderType + OrderSide OrderSide + StartTicks time.Time + EndTicks time.Time + // Currencies Empty array = all currencies. Some endpoints only support singular currency enquiries + Currencies []currency.Pair +} + +// OrderStatus defines order status types +type OrderStatus string + +// All OrderStatus types +const ( + AnyOrderStatus OrderStatus = "ANY" + NewOrderStatus OrderStatus = "NEW" + ActiveOrderStatus OrderStatus = "ACTIVE" + PartiallyFilledOrderStatus OrderStatus = "PARTIALLY_FILLED" + FilledOrderStatus OrderStatus = "FILLED" + CancelledOrderStatus OrderStatus = "CANCELED" + PendingCancelOrderStatus OrderStatus = "PENDING_CANCEL" + RejectedOrderStatus OrderStatus = "REJECTED" + ExpiredOrderStatus OrderStatus = "EXPIRED" + HiddenOrderStatus OrderStatus = "HIDDEN" + UnknownOrderStatus OrderStatus = "UNKNOWN" +) + +// FilterOrdersBySide removes any OrderDetails that don't match the orderStatus provided +func FilterOrdersBySide(orders *[]OrderDetail, orderSide OrderSide) { + if orderSide == "" || orderSide == AnyOrderSide { + return + } + + var filteredOrders []OrderDetail + for i := range *orders { + if strings.EqualFold(string((*orders)[i].OrderSide), string(orderSide)) { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByType removes any OrderDetails that don't match the orderType provided +func FilterOrdersByType(orders *[]OrderDetail, orderType OrderType) { + if orderType == "" || orderType == AnyOrderType { + return + } + + var filteredOrders []OrderDetail + for i := range *orders { + if strings.EqualFold(string((*orders)[i].OrderType), string(orderType)) { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByTickRange removes any OrderDetails outside of the tick range +func FilterOrdersByTickRange(orders *[]OrderDetail, startTicks, endTicks time.Time) { + if startTicks.IsZero() || endTicks.IsZero() || + startTicks.Unix() == 0 || endTicks.Unix() == 0 || endTicks.Before(startTicks) { + return + } + + var filteredOrders []OrderDetail + for i := range *orders { + if (*orders)[i].OrderDate.Unix() >= startTicks.Unix() && (*orders)[i].OrderDate.Unix() <= endTicks.Unix() { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByCurrencies removes any OrderDetails that do not match the provided currency list +// It is forgiving in that the provided currencies can match quote or base currencies +func FilterOrdersByCurrencies(orders *[]OrderDetail, currencies []currency.Pair) { + if len(currencies) == 0 { + return + } + + var filteredOrders []OrderDetail + for i := range *orders { + matchFound := false + for _, c := range currencies { + if !matchFound && (*orders)[i].CurrencyPair.EqualIncludeReciprocal(c) { + matchFound = true + } + } + + if matchFound { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// ByPrice used for sorting orders by price +type ByPrice []OrderDetail + +func (b ByPrice) Len() int { + return len(b) +} + +func (b ByPrice) Less(i, j int) bool { + return b[i].Price < b[j].Price +} + +func (b ByPrice) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByPrice the caller function to sort orders +func SortOrdersByPrice(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByPrice(*orders))) + } else { + sort.Sort(ByPrice(*orders)) + } +} + +// ByOrderType used for sorting orders by order type +type ByOrderType []OrderDetail + +func (b ByOrderType) Len() int { + return len(b) +} + +func (b ByOrderType) Less(i, j int) bool { + return b[i].OrderType.ToString() < b[j].OrderType.ToString() +} + +func (b ByOrderType) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByType the caller function to sort orders +func SortOrdersByType(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByOrderType(*orders))) + } else { + sort.Sort(ByOrderType(*orders)) + } +} + +// ByCurrency used for sorting orders by order currency +type ByCurrency []OrderDetail + +func (b ByCurrency) Len() int { + return len(b) +} + +func (b ByCurrency) Less(i, j int) bool { + return b[i].CurrencyPair.String() < b[j].CurrencyPair.String() +} + +func (b ByCurrency) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByCurrency the caller function to sort orders +func SortOrdersByCurrency(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByCurrency(*orders))) + } else { + sort.Sort(ByCurrency(*orders)) + } +} + +// ByDate used for sorting orders by order date +type ByDate []OrderDetail + +func (b ByDate) Len() int { + return len(b) +} + +func (b ByDate) Less(i, j int) bool { + return b[i].OrderDate.Unix() < b[j].OrderDate.Unix() +} + +func (b ByDate) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByDate the caller function to sort orders +func SortOrdersByDate(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByDate(*orders))) + } else { + sort.Sort(ByDate(*orders)) + } +} + +// ByOrderSide used for sorting orders by order side (buy sell) +type ByOrderSide []OrderDetail + +func (b ByOrderSide) Len() int { + return len(b) +} + +func (b ByOrderSide) Less(i, j int) bool { + return b[i].OrderSide.ToString() < b[j].OrderSide.ToString() +} + +func (b ByOrderSide) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersBySide the caller function to sort orders +func SortOrdersBySide(orders *[]OrderDetail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByOrderSide(*orders))) + } else { + sort.Sort(ByOrderSide(*orders)) + } +} diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index 5a8bbaed..b4122ca2 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -253,19 +253,19 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var pair = currency.Pair{ - Delimiter: "_", - Base: currency.BTC, - Quote: currency.LTC, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "_", + Base: currency.BTC, + Quote: currency.LTC, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - - response, err := p.SubmitOrder(pair, - exchange.BuyOrderSide, - exchange.LimitOrderType, - 1, - 10, - "hi") - + response, err := p.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 49ab7f05..341de2fc 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -302,26 +302,30 @@ func (p *Poloniex) GetExchangeHistory(currencyPair currency.Pair, assetType asse } // SubmitOrder submits a new order -func (p *Poloniex) SubmitOrder(currencyPair currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (p *Poloniex) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - fillOrKill := orderType == exchange.MarketOrderType - isBuyOrder := side == exchange.BuyOrderSide + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } - response, err := p.PlaceOrder(currencyPair.String(), - price, - amount, + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + fillOrKill := order.OrderType == exchange.MarketOrderType + isBuyOrder := order.OrderSide == exchange.BuyOrderSide + response, err := p.PlaceOrder(order.Pair.String(), + order.Price, + order.Amount, false, fillOrKill, isBuyOrder) - if response.OrderNumber > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response.OrderNumber) } - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index 56f2ee2f..7c9f15dc 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -372,12 +372,19 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var pair = currency.Pair{ - Delimiter: "_", - Base: currency.BTC, - Quote: currency.USD, + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "_", + Base: currency.BTC, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - response, err := y.SubmitOrder(pair, exchange.BuyOrderSide, exchange.LimitOrderType, 1, 10, "hi") + response, err := y.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 81a12f69..e224587c 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -281,22 +281,28 @@ func (y *Yobit) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) // SubmitOrder submits a new order // Yobit only supports limit orders -func (y *Yobit) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (y *Yobit) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } - if orderType != exchange.LimitOrderType { + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + if order.OrderType != exchange.LimitOrderType { return submitOrderResponse, errors.New("only limit orders are allowed") } - response, err := y.Trade(p.String(), side.ToString(), amount, price) + response, err := y.Trade(order.Pair.String(), order.OrderSide.ToString(), + order.Amount, order.Price) if response > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response) } - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index 14a50e1a..2cde1af8 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -288,25 +288,25 @@ func areTestAPIKeysSet() bool { func TestSubmitOrder(t *testing.T) { z.SetDefaults() TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip(fmt.Sprintf("ApiKey: %s. Can place orders: %v", z.API.Credentials.Key, canManipulateRealOrders)) } - var pair = currency.Pair{ - Delimiter: "_", - Base: currency.QTUM, - Quote: currency.USDT, + + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Delimiter: "_", + Base: currency.QTUM, + Quote: currency.USD, + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", } - - response, err := z.SubmitOrder(pair, - exchange.BuyOrderSide, - exchange.LimitOrderType, - 1, - 10, - "hi") - + response, err := z.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index ad1d52e6..43ea8243 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -300,32 +300,36 @@ func (z *ZB) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([] } // SubmitOrder submits a new order -func (z *ZB) SubmitOrder(p currency.Pair, side exchange.OrderSide, _ exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) { +func (z *ZB) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { var submitOrderResponse exchange.SubmitOrderResponse - var oT SpotNewOrderRequestParamsType + if order == nil { + return submitOrderResponse, exchange.ErrOrderSubmissionIsNil + } - if side == exchange.BuyOrderSide { + if err := order.Validate(); err != nil { + return submitOrderResponse, err + } + + var oT SpotNewOrderRequestParamsType + if order.OrderSide == exchange.BuyOrderSide { oT = SpotNewOrderRequestParamsTypeBuy } else { oT = SpotNewOrderRequestParamsTypeSell } var params = SpotNewOrderRequestParams{ - Amount: amount, - Price: price, - Symbol: strings.ToLower(p.String()), + Amount: order.Amount, + Price: order.Price, + Symbol: order.Pair.Lower().String(), Type: oT, } response, err := z.SpotNewOrder(params) - if response > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response) } - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index b563f77b..40bd05a3 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -9,6 +9,8 @@ import ( proto "github.com/golang/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" math "math" ) @@ -273,6 +275,45 @@ func (m *GetExchangesResponse) GetExchanges() string { return "" } +type GetExchangeOTPReponse struct { + OtpCode string `protobuf:"bytes,1,opt,name=otp_code,json=otpCode,proto3" json:"otp_code,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangeOTPReponse) Reset() { *m = GetExchangeOTPReponse{} } +func (m *GetExchangeOTPReponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangeOTPReponse) ProtoMessage() {} +func (*GetExchangeOTPReponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{6} +} + +func (m *GetExchangeOTPReponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeOTPReponse.Unmarshal(m, b) +} +func (m *GetExchangeOTPReponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeOTPReponse.Marshal(b, m, deterministic) +} +func (m *GetExchangeOTPReponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeOTPReponse.Merge(m, src) +} +func (m *GetExchangeOTPReponse) XXX_Size() int { + return xxx_messageInfo_GetExchangeOTPReponse.Size(m) +} +func (m *GetExchangeOTPReponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeOTPReponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeOTPReponse proto.InternalMessageInfo + +func (m *GetExchangeOTPReponse) GetOtpCode() string { + if m != nil { + return m.OtpCode + } + return "" +} + type DisableExchangeRequest struct { Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -284,7 +325,7 @@ func (m *DisableExchangeRequest) Reset() { *m = DisableExchangeRequest{} func (m *DisableExchangeRequest) String() string { return proto.CompactTextString(m) } func (*DisableExchangeRequest) ProtoMessage() {} func (*DisableExchangeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{6} + return fileDescriptor_77a6da22d6a3feb1, []int{7} } func (m *DisableExchangeRequest) XXX_Unmarshal(b []byte) error { @@ -334,7 +375,7 @@ func (m *GetExchangeInfoResponse) Reset() { *m = GetExchangeInfoResponse func (m *GetExchangeInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetExchangeInfoResponse) ProtoMessage() {} func (*GetExchangeInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{7} + return fileDescriptor_77a6da22d6a3feb1, []int{8} } func (m *GetExchangeInfoResponse) XXX_Unmarshal(b []byte) error { @@ -452,7 +493,7 @@ func (m *GetTickerRequest) Reset() { *m = GetTickerRequest{} } func (m *GetTickerRequest) String() string { return proto.CompactTextString(m) } func (*GetTickerRequest) ProtoMessage() {} func (*GetTickerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{8} + return fileDescriptor_77a6da22d6a3feb1, []int{9} } func (m *GetTickerRequest) XXX_Unmarshal(b []byte) error { @@ -507,7 +548,7 @@ func (m *CurrencyPair) Reset() { *m = CurrencyPair{} } func (m *CurrencyPair) String() string { return proto.CompactTextString(m) } func (*CurrencyPair) ProtoMessage() {} func (*CurrencyPair) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{9} + return fileDescriptor_77a6da22d6a3feb1, []int{10} } func (m *CurrencyPair) XXX_Unmarshal(b []byte) error { @@ -569,7 +610,7 @@ func (m *TickerResponse) Reset() { *m = TickerResponse{} } func (m *TickerResponse) String() string { return proto.CompactTextString(m) } func (*TickerResponse) ProtoMessage() {} func (*TickerResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{10} + return fileDescriptor_77a6da22d6a3feb1, []int{11} } func (m *TickerResponse) XXX_Unmarshal(b []byte) error { @@ -670,7 +711,7 @@ func (m *GetTickersRequest) Reset() { *m = GetTickersRequest{} } func (m *GetTickersRequest) String() string { return proto.CompactTextString(m) } func (*GetTickersRequest) ProtoMessage() {} func (*GetTickersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{11} + return fileDescriptor_77a6da22d6a3feb1, []int{12} } func (m *GetTickersRequest) XXX_Unmarshal(b []byte) error { @@ -703,7 +744,7 @@ func (m *Tickers) Reset() { *m = Tickers{} } func (m *Tickers) String() string { return proto.CompactTextString(m) } func (*Tickers) ProtoMessage() {} func (*Tickers) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{12} + return fileDescriptor_77a6da22d6a3feb1, []int{13} } func (m *Tickers) XXX_Unmarshal(b []byte) error { @@ -749,7 +790,7 @@ func (m *GetTickersResponse) Reset() { *m = GetTickersResponse{} } func (m *GetTickersResponse) String() string { return proto.CompactTextString(m) } func (*GetTickersResponse) ProtoMessage() {} func (*GetTickersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{13} + return fileDescriptor_77a6da22d6a3feb1, []int{14} } func (m *GetTickersResponse) XXX_Unmarshal(b []byte) error { @@ -790,7 +831,7 @@ func (m *GetOrderbookRequest) Reset() { *m = GetOrderbookRequest{} } func (m *GetOrderbookRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderbookRequest) ProtoMessage() {} func (*GetOrderbookRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{14} + return fileDescriptor_77a6da22d6a3feb1, []int{15} } func (m *GetOrderbookRequest) XXX_Unmarshal(b []byte) error { @@ -845,7 +886,7 @@ func (m *OrderbookItem) Reset() { *m = OrderbookItem{} } func (m *OrderbookItem) String() string { return proto.CompactTextString(m) } func (*OrderbookItem) ProtoMessage() {} func (*OrderbookItem) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{15} + return fileDescriptor_77a6da22d6a3feb1, []int{16} } func (m *OrderbookItem) XXX_Unmarshal(b []byte) error { @@ -903,7 +944,7 @@ func (m *OrderbookResponse) Reset() { *m = OrderbookResponse{} } func (m *OrderbookResponse) String() string { return proto.CompactTextString(m) } func (*OrderbookResponse) ProtoMessage() {} func (*OrderbookResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{16} + return fileDescriptor_77a6da22d6a3feb1, []int{17} } func (m *OrderbookResponse) XXX_Unmarshal(b []byte) error { @@ -976,7 +1017,7 @@ func (m *GetOrderbooksRequest) Reset() { *m = GetOrderbooksRequest{} } func (m *GetOrderbooksRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderbooksRequest) ProtoMessage() {} func (*GetOrderbooksRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{17} + return fileDescriptor_77a6da22d6a3feb1, []int{18} } func (m *GetOrderbooksRequest) XXX_Unmarshal(b []byte) error { @@ -1009,7 +1050,7 @@ func (m *Orderbooks) Reset() { *m = Orderbooks{} } func (m *Orderbooks) String() string { return proto.CompactTextString(m) } func (*Orderbooks) ProtoMessage() {} func (*Orderbooks) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{18} + return fileDescriptor_77a6da22d6a3feb1, []int{19} } func (m *Orderbooks) XXX_Unmarshal(b []byte) error { @@ -1055,7 +1096,7 @@ func (m *GetOrderbooksResponse) Reset() { *m = GetOrderbooksResponse{} } func (m *GetOrderbooksResponse) String() string { return proto.CompactTextString(m) } func (*GetOrderbooksResponse) ProtoMessage() {} func (*GetOrderbooksResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{19} + return fileDescriptor_77a6da22d6a3feb1, []int{20} } func (m *GetOrderbooksResponse) XXX_Unmarshal(b []byte) error { @@ -1094,7 +1135,7 @@ func (m *GetAccountInfoRequest) Reset() { *m = GetAccountInfoRequest{} } func (m *GetAccountInfoRequest) String() string { return proto.CompactTextString(m) } func (*GetAccountInfoRequest) ProtoMessage() {} func (*GetAccountInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{20} + return fileDescriptor_77a6da22d6a3feb1, []int{21} } func (m *GetAccountInfoRequest) XXX_Unmarshal(b []byte) error { @@ -1134,7 +1175,7 @@ func (m *Account) Reset() { *m = Account{} } func (m *Account) String() string { return proto.CompactTextString(m) } func (*Account) ProtoMessage() {} func (*Account) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{21} + return fileDescriptor_77a6da22d6a3feb1, []int{22} } func (m *Account) XXX_Unmarshal(b []byte) error { @@ -1182,7 +1223,7 @@ func (m *AccountCurrencyInfo) Reset() { *m = AccountCurrencyInfo{} } func (m *AccountCurrencyInfo) String() string { return proto.CompactTextString(m) } func (*AccountCurrencyInfo) ProtoMessage() {} func (*AccountCurrencyInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{22} + return fileDescriptor_77a6da22d6a3feb1, []int{23} } func (m *AccountCurrencyInfo) XXX_Unmarshal(b []byte) error { @@ -1236,7 +1277,7 @@ func (m *GetAccountInfoResponse) Reset() { *m = GetAccountInfoResponse{} func (m *GetAccountInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetAccountInfoResponse) ProtoMessage() {} func (*GetAccountInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{23} + return fileDescriptor_77a6da22d6a3feb1, []int{24} } func (m *GetAccountInfoResponse) XXX_Unmarshal(b []byte) error { @@ -1281,7 +1322,7 @@ func (m *GetConfigRequest) Reset() { *m = GetConfigRequest{} } func (m *GetConfigRequest) String() string { return proto.CompactTextString(m) } func (*GetConfigRequest) ProtoMessage() {} func (*GetConfigRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{24} + return fileDescriptor_77a6da22d6a3feb1, []int{25} } func (m *GetConfigRequest) XXX_Unmarshal(b []byte) error { @@ -1313,7 +1354,7 @@ func (m *GetConfigResponse) Reset() { *m = GetConfigResponse{} } func (m *GetConfigResponse) String() string { return proto.CompactTextString(m) } func (*GetConfigResponse) ProtoMessage() {} func (*GetConfigResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{25} + return fileDescriptor_77a6da22d6a3feb1, []int{26} } func (m *GetConfigResponse) XXX_Unmarshal(b []byte) error { @@ -1355,7 +1396,7 @@ func (m *PortfolioAddress) Reset() { *m = PortfolioAddress{} } func (m *PortfolioAddress) String() string { return proto.CompactTextString(m) } func (*PortfolioAddress) ProtoMessage() {} func (*PortfolioAddress) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{26} + return fileDescriptor_77a6da22d6a3feb1, []int{27} } func (m *PortfolioAddress) XXX_Unmarshal(b []byte) error { @@ -1414,7 +1455,7 @@ func (m *GetPortfolioRequest) Reset() { *m = GetPortfolioRequest{} } func (m *GetPortfolioRequest) String() string { return proto.CompactTextString(m) } func (*GetPortfolioRequest) ProtoMessage() {} func (*GetPortfolioRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{27} + return fileDescriptor_77a6da22d6a3feb1, []int{28} } func (m *GetPortfolioRequest) XXX_Unmarshal(b []byte) error { @@ -1446,7 +1487,7 @@ func (m *GetPortfolioResponse) Reset() { *m = GetPortfolioResponse{} } func (m *GetPortfolioResponse) String() string { return proto.CompactTextString(m) } func (*GetPortfolioResponse) ProtoMessage() {} func (*GetPortfolioResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{28} + return fileDescriptor_77a6da22d6a3feb1, []int{29} } func (m *GetPortfolioResponse) XXX_Unmarshal(b []byte) error { @@ -1484,7 +1525,7 @@ func (m *GetPortfolioSummaryRequest) Reset() { *m = GetPortfolioSummaryR func (m *GetPortfolioSummaryRequest) String() string { return proto.CompactTextString(m) } func (*GetPortfolioSummaryRequest) ProtoMessage() {} func (*GetPortfolioSummaryRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{29} + return fileDescriptor_77a6da22d6a3feb1, []int{30} } func (m *GetPortfolioSummaryRequest) XXX_Unmarshal(b []byte) error { @@ -1519,7 +1560,7 @@ func (m *Coin) Reset() { *m = Coin{} } func (m *Coin) String() string { return proto.CompactTextString(m) } func (*Coin) ProtoMessage() {} func (*Coin) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{30} + return fileDescriptor_77a6da22d6a3feb1, []int{31} } func (m *Coin) XXX_Unmarshal(b []byte) error { @@ -1581,7 +1622,7 @@ func (m *OfflineCoinSummary) Reset() { *m = OfflineCoinSummary{} } func (m *OfflineCoinSummary) String() string { return proto.CompactTextString(m) } func (*OfflineCoinSummary) ProtoMessage() {} func (*OfflineCoinSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{31} + return fileDescriptor_77a6da22d6a3feb1, []int{32} } func (m *OfflineCoinSummary) XXX_Unmarshal(b []byte) error { @@ -1635,7 +1676,7 @@ func (m *OnlineCoinSummary) Reset() { *m = OnlineCoinSummary{} } func (m *OnlineCoinSummary) String() string { return proto.CompactTextString(m) } func (*OnlineCoinSummary) ProtoMessage() {} func (*OnlineCoinSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{32} + return fileDescriptor_77a6da22d6a3feb1, []int{33} } func (m *OnlineCoinSummary) XXX_Unmarshal(b []byte) error { @@ -1681,7 +1722,7 @@ func (m *OfflineCoins) Reset() { *m = OfflineCoins{} } func (m *OfflineCoins) String() string { return proto.CompactTextString(m) } func (*OfflineCoins) ProtoMessage() {} func (*OfflineCoins) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{33} + return fileDescriptor_77a6da22d6a3feb1, []int{34} } func (m *OfflineCoins) XXX_Unmarshal(b []byte) error { @@ -1720,7 +1761,7 @@ func (m *OnlineCoins) Reset() { *m = OnlineCoins{} } func (m *OnlineCoins) String() string { return proto.CompactTextString(m) } func (*OnlineCoins) ProtoMessage() {} func (*OnlineCoins) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{34} + return fileDescriptor_77a6da22d6a3feb1, []int{35} } func (m *OnlineCoins) XXX_Unmarshal(b []byte) error { @@ -1763,7 +1804,7 @@ func (m *GetPortfolioSummaryResponse) Reset() { *m = GetPortfolioSummary func (m *GetPortfolioSummaryResponse) String() string { return proto.CompactTextString(m) } func (*GetPortfolioSummaryResponse) ProtoMessage() {} func (*GetPortfolioSummaryResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{35} + return fileDescriptor_77a6da22d6a3feb1, []int{36} } func (m *GetPortfolioSummaryResponse) XXX_Unmarshal(b []byte) error { @@ -1833,7 +1874,7 @@ func (m *AddPortfolioAddressRequest) Reset() { *m = AddPortfolioAddressR func (m *AddPortfolioAddressRequest) String() string { return proto.CompactTextString(m) } func (*AddPortfolioAddressRequest) ProtoMessage() {} func (*AddPortfolioAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{36} + return fileDescriptor_77a6da22d6a3feb1, []int{37} } func (m *AddPortfolioAddressRequest) XXX_Unmarshal(b []byte) error { @@ -1892,7 +1933,7 @@ func (m *AddPortfolioAddressResponse) Reset() { *m = AddPortfolioAddress func (m *AddPortfolioAddressResponse) String() string { return proto.CompactTextString(m) } func (*AddPortfolioAddressResponse) ProtoMessage() {} func (*AddPortfolioAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{37} + return fileDescriptor_77a6da22d6a3feb1, []int{38} } func (m *AddPortfolioAddressResponse) XXX_Unmarshal(b []byte) error { @@ -1926,7 +1967,7 @@ func (m *RemovePortfolioAddressRequest) Reset() { *m = RemovePortfolioAd func (m *RemovePortfolioAddressRequest) String() string { return proto.CompactTextString(m) } func (*RemovePortfolioAddressRequest) ProtoMessage() {} func (*RemovePortfolioAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{38} + return fileDescriptor_77a6da22d6a3feb1, []int{39} } func (m *RemovePortfolioAddressRequest) XXX_Unmarshal(b []byte) error { @@ -1978,7 +2019,7 @@ func (m *RemovePortfolioAddressResponse) Reset() { *m = RemovePortfolioA func (m *RemovePortfolioAddressResponse) String() string { return proto.CompactTextString(m) } func (*RemovePortfolioAddressResponse) ProtoMessage() {} func (*RemovePortfolioAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{39} + return fileDescriptor_77a6da22d6a3feb1, []int{40} } func (m *RemovePortfolioAddressResponse) XXX_Unmarshal(b []byte) error { @@ -2009,7 +2050,7 @@ func (m *GetForexProvidersRequest) Reset() { *m = GetForexProvidersReque func (m *GetForexProvidersRequest) String() string { return proto.CompactTextString(m) } func (*GetForexProvidersRequest) ProtoMessage() {} func (*GetForexProvidersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{40} + return fileDescriptor_77a6da22d6a3feb1, []int{41} } func (m *GetForexProvidersRequest) XXX_Unmarshal(b []byte) error { @@ -2047,7 +2088,7 @@ func (m *ForexProvider) Reset() { *m = ForexProvider{} } func (m *ForexProvider) String() string { return proto.CompactTextString(m) } func (*ForexProvider) ProtoMessage() {} func (*ForexProvider) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{41} + return fileDescriptor_77a6da22d6a3feb1, []int{42} } func (m *ForexProvider) XXX_Unmarshal(b []byte) error { @@ -2128,7 +2169,7 @@ func (m *GetForexProvidersResponse) Reset() { *m = GetForexProvidersResp func (m *GetForexProvidersResponse) String() string { return proto.CompactTextString(m) } func (*GetForexProvidersResponse) ProtoMessage() {} func (*GetForexProvidersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{42} + return fileDescriptor_77a6da22d6a3feb1, []int{43} } func (m *GetForexProvidersResponse) XXX_Unmarshal(b []byte) error { @@ -2166,7 +2207,7 @@ func (m *GetForexRatesRequest) Reset() { *m = GetForexRatesRequest{} } func (m *GetForexRatesRequest) String() string { return proto.CompactTextString(m) } func (*GetForexRatesRequest) ProtoMessage() {} func (*GetForexRatesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{43} + return fileDescriptor_77a6da22d6a3feb1, []int{44} } func (m *GetForexRatesRequest) XXX_Unmarshal(b []byte) error { @@ -2201,7 +2242,7 @@ func (m *ForexRatesConversion) Reset() { *m = ForexRatesConversion{} } func (m *ForexRatesConversion) String() string { return proto.CompactTextString(m) } func (*ForexRatesConversion) ProtoMessage() {} func (*ForexRatesConversion) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{44} + return fileDescriptor_77a6da22d6a3feb1, []int{45} } func (m *ForexRatesConversion) XXX_Unmarshal(b []byte) error { @@ -2261,7 +2302,7 @@ func (m *GetForexRatesResponse) Reset() { *m = GetForexRatesResponse{} } func (m *GetForexRatesResponse) String() string { return proto.CompactTextString(m) } func (*GetForexRatesResponse) ProtoMessage() {} func (*GetForexRatesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{45} + return fileDescriptor_77a6da22d6a3feb1, []int{46} } func (m *GetForexRatesResponse) XXX_Unmarshal(b []byte) error { @@ -2311,7 +2352,7 @@ func (m *OrderDetails) Reset() { *m = OrderDetails{} } func (m *OrderDetails) String() string { return proto.CompactTextString(m) } func (*OrderDetails) ProtoMessage() {} func (*OrderDetails) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{46} + return fileDescriptor_77a6da22d6a3feb1, []int{47} } func (m *OrderDetails) XXX_Unmarshal(b []byte) error { @@ -2429,7 +2470,7 @@ func (m *GetOrdersRequest) Reset() { *m = GetOrdersRequest{} } func (m *GetOrdersRequest) String() string { return proto.CompactTextString(m) } func (*GetOrdersRequest) ProtoMessage() {} func (*GetOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{47} + return fileDescriptor_77a6da22d6a3feb1, []int{48} } func (m *GetOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -2482,7 +2523,7 @@ func (m *GetOrdersResponse) Reset() { *m = GetOrdersResponse{} } func (m *GetOrdersResponse) String() string { return proto.CompactTextString(m) } func (*GetOrdersResponse) ProtoMessage() {} func (*GetOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{48} + return fileDescriptor_77a6da22d6a3feb1, []int{49} } func (m *GetOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -2522,7 +2563,7 @@ func (m *GetOrderRequest) Reset() { *m = GetOrderRequest{} } func (m *GetOrderRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderRequest) ProtoMessage() {} func (*GetOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{49} + return fileDescriptor_77a6da22d6a3feb1, []int{50} } func (m *GetOrderRequest) XXX_Unmarshal(b []byte) error { @@ -2574,7 +2615,7 @@ func (m *SubmitOrderRequest) Reset() { *m = SubmitOrderRequest{} } func (m *SubmitOrderRequest) String() string { return proto.CompactTextString(m) } func (*SubmitOrderRequest) ProtoMessage() {} func (*SubmitOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{50} + return fileDescriptor_77a6da22d6a3feb1, []int{51} } func (m *SubmitOrderRequest) XXX_Unmarshal(b []byte) error { @@ -2656,7 +2697,7 @@ func (m *SubmitOrderResponse) Reset() { *m = SubmitOrderResponse{} } func (m *SubmitOrderResponse) String() string { return proto.CompactTextString(m) } func (*SubmitOrderResponse) ProtoMessage() {} func (*SubmitOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{51} + return fileDescriptor_77a6da22d6a3feb1, []int{52} } func (m *SubmitOrderResponse) XXX_Unmarshal(b []byte) error { @@ -2708,7 +2749,7 @@ func (m *CancelOrderRequest) Reset() { *m = CancelOrderRequest{} } func (m *CancelOrderRequest) String() string { return proto.CompactTextString(m) } func (*CancelOrderRequest) ProtoMessage() {} func (*CancelOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{52} + return fileDescriptor_77a6da22d6a3feb1, []int{53} } func (m *CancelOrderRequest) XXX_Unmarshal(b []byte) error { @@ -2788,7 +2829,7 @@ func (m *CancelOrderResponse) Reset() { *m = CancelOrderResponse{} } func (m *CancelOrderResponse) String() string { return proto.CompactTextString(m) } func (*CancelOrderResponse) ProtoMessage() {} func (*CancelOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{53} + return fileDescriptor_77a6da22d6a3feb1, []int{54} } func (m *CancelOrderResponse) XXX_Unmarshal(b []byte) error { @@ -2820,7 +2861,7 @@ func (m *CancelAllOrdersRequest) Reset() { *m = CancelAllOrdersRequest{} func (m *CancelAllOrdersRequest) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersRequest) ProtoMessage() {} func (*CancelAllOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{54} + return fileDescriptor_77a6da22d6a3feb1, []int{55} } func (m *CancelAllOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -2859,7 +2900,7 @@ func (m *CancelAllOrdersResponse) Reset() { *m = CancelAllOrdersResponse func (m *CancelAllOrdersResponse) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse) ProtoMessage() {} func (*CancelAllOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{55} + return fileDescriptor_77a6da22d6a3feb1, []int{56} } func (m *CancelAllOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -2899,7 +2940,7 @@ func (m *CancelAllOrdersResponse_Orders) Reset() { *m = CancelAllOrdersR func (m *CancelAllOrdersResponse_Orders) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse_Orders) ProtoMessage() {} func (*CancelAllOrdersResponse_Orders) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{55, 0} + return fileDescriptor_77a6da22d6a3feb1, []int{56, 0} } func (m *CancelAllOrdersResponse_Orders) XXX_Unmarshal(b []byte) error { @@ -2944,7 +2985,7 @@ func (m *GetEventsRequest) Reset() { *m = GetEventsRequest{} } func (m *GetEventsRequest) String() string { return proto.CompactTextString(m) } func (*GetEventsRequest) ProtoMessage() {} func (*GetEventsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{56} + return fileDescriptor_77a6da22d6a3feb1, []int{57} } func (m *GetEventsRequest) XXX_Unmarshal(b []byte) error { @@ -2980,7 +3021,7 @@ func (m *ConditionParams) Reset() { *m = ConditionParams{} } func (m *ConditionParams) String() string { return proto.CompactTextString(m) } func (*ConditionParams) ProtoMessage() {} func (*ConditionParams) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{57} + return fileDescriptor_77a6da22d6a3feb1, []int{58} } func (m *ConditionParams) XXX_Unmarshal(b []byte) error { @@ -3053,7 +3094,7 @@ func (m *GetEventsResponse) Reset() { *m = GetEventsResponse{} } func (m *GetEventsResponse) String() string { return proto.CompactTextString(m) } func (*GetEventsResponse) ProtoMessage() {} func (*GetEventsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{58} + return fileDescriptor_77a6da22d6a3feb1, []int{59} } func (m *GetEventsResponse) XXX_Unmarshal(b []byte) error { @@ -3139,7 +3180,7 @@ func (m *AddEventRequest) Reset() { *m = AddEventRequest{} } func (m *AddEventRequest) String() string { return proto.CompactTextString(m) } func (*AddEventRequest) ProtoMessage() {} func (*AddEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{59} + return fileDescriptor_77a6da22d6a3feb1, []int{60} } func (m *AddEventRequest) XXX_Unmarshal(b []byte) error { @@ -3213,7 +3254,7 @@ func (m *AddEventResponse) Reset() { *m = AddEventResponse{} } func (m *AddEventResponse) String() string { return proto.CompactTextString(m) } func (*AddEventResponse) ProtoMessage() {} func (*AddEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{60} + return fileDescriptor_77a6da22d6a3feb1, []int{61} } func (m *AddEventResponse) XXX_Unmarshal(b []byte) error { @@ -3252,7 +3293,7 @@ func (m *RemoveEventRequest) Reset() { *m = RemoveEventRequest{} } func (m *RemoveEventRequest) String() string { return proto.CompactTextString(m) } func (*RemoveEventRequest) ProtoMessage() {} func (*RemoveEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{61} + return fileDescriptor_77a6da22d6a3feb1, []int{62} } func (m *RemoveEventRequest) XXX_Unmarshal(b []byte) error { @@ -3290,7 +3331,7 @@ func (m *RemoveEventResponse) Reset() { *m = RemoveEventResponse{} } func (m *RemoveEventResponse) String() string { return proto.CompactTextString(m) } func (*RemoveEventResponse) ProtoMessage() {} func (*RemoveEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{62} + return fileDescriptor_77a6da22d6a3feb1, []int{63} } func (m *RemoveEventResponse) XXX_Unmarshal(b []byte) error { @@ -3324,7 +3365,7 @@ func (m *GetCryptocurrencyDepositAddressesRequest) Reset() { func (m *GetCryptocurrencyDepositAddressesRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{63} + return fileDescriptor_77a6da22d6a3feb1, []int{64} } func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Unmarshal(b []byte) error { @@ -3365,7 +3406,7 @@ func (m *GetCryptocurrencyDepositAddressesResponse) Reset() { func (m *GetCryptocurrencyDepositAddressesResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{64} + return fileDescriptor_77a6da22d6a3feb1, []int{65} } func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Unmarshal(b []byte) error { @@ -3407,7 +3448,7 @@ func (m *GetCryptocurrencyDepositAddressRequest) Reset() { func (m *GetCryptocurrencyDepositAddressRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{65} + return fileDescriptor_77a6da22d6a3feb1, []int{66} } func (m *GetCryptocurrencyDepositAddressRequest) XXX_Unmarshal(b []byte) error { @@ -3455,7 +3496,7 @@ func (m *GetCryptocurrencyDepositAddressResponse) Reset() { func (m *GetCryptocurrencyDepositAddressResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{66} + return fileDescriptor_77a6da22d6a3feb1, []int{67} } func (m *GetCryptocurrencyDepositAddressResponse) XXX_Unmarshal(b []byte) error { @@ -3510,7 +3551,7 @@ func (m *WithdrawCurrencyRequest) Reset() { *m = WithdrawCurrencyRequest func (m *WithdrawCurrencyRequest) String() string { return proto.CompactTextString(m) } func (*WithdrawCurrencyRequest) ProtoMessage() {} func (*WithdrawCurrencyRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{67} + return fileDescriptor_77a6da22d6a3feb1, []int{68} } func (m *WithdrawCurrencyRequest) XXX_Unmarshal(b []byte) error { @@ -3661,7 +3702,7 @@ func (m *WithdrawResponse) Reset() { *m = WithdrawResponse{} } func (m *WithdrawResponse) String() string { return proto.CompactTextString(m) } func (*WithdrawResponse) ProtoMessage() {} func (*WithdrawResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{68} + return fileDescriptor_77a6da22d6a3feb1, []int{69} } func (m *WithdrawResponse) XXX_Unmarshal(b []byte) error { @@ -3696,6 +3737,7 @@ func init() { proto.RegisterType((*GenericExchangeNameResponse)(nil), "gctrpc.GenericExchangeNameResponse") proto.RegisterType((*GetExchangesRequest)(nil), "gctrpc.GetExchangesRequest") proto.RegisterType((*GetExchangesResponse)(nil), "gctrpc.GetExchangesResponse") + proto.RegisterType((*GetExchangeOTPReponse)(nil), "gctrpc.GetExchangeOTPReponse") proto.RegisterType((*DisableExchangeRequest)(nil), "gctrpc.DisableExchangeRequest") proto.RegisterType((*GetExchangeInfoResponse)(nil), "gctrpc.GetExchangeInfoResponse") proto.RegisterType((*GetTickerRequest)(nil), "gctrpc.GetTickerRequest") @@ -3770,224 +3812,227 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 3460 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3a, 0x4d, 0x6f, 0xdc, 0xd6, - 0xb5, 0xe0, 0xe8, 0x73, 0xce, 0x8c, 0x34, 0xa3, 0x3b, 0xfa, 0x18, 0x8d, 0x24, 0x5b, 0x66, 0x9e, - 0x1d, 0xdb, 0x49, 0xac, 0xc4, 0x31, 0xde, 0xcb, 0x4b, 0xf2, 0xf2, 0x9e, 0x22, 0x7f, 0xc4, 0xc8, - 0x4b, 0x6c, 0xd0, 0x8e, 0x03, 0x24, 0x45, 0x09, 0x8a, 0xbc, 0x23, 0x11, 0xe2, 0x90, 0x0c, 0xc9, - 0x91, 0xac, 0xa0, 0x40, 0x81, 0x00, 0xdd, 0xb6, 0x8b, 0xa2, 0x40, 0x17, 0xfd, 0x05, 0x45, 0xbb, - 0xe9, 0x0f, 0x08, 0xba, 0x2d, 0xba, 0xec, 0xa6, 0x3f, 0xa0, 0xe8, 0xae, 0x2d, 0xba, 0xe8, 0xa6, - 0xab, 0xe2, 0x9e, 0xfb, 0x41, 0x5e, 0x72, 0x66, 0x34, 0x6e, 0xda, 0x6c, 0x24, 0xf2, 0xdc, 0x73, - 0xcf, 0xf7, 0x3d, 0xf7, 0x9c, 0xc3, 0x81, 0x7a, 0x12, 0xbb, 0xb7, 0xe2, 0x24, 0xca, 0x22, 0x32, - 0x7f, 0xe4, 0x66, 0x49, 0xec, 0xf6, 0xb6, 0x8f, 0xa2, 0xe8, 0x28, 0xa0, 0x7b, 0x4e, 0xec, 0xef, - 0x39, 0x61, 0x18, 0x65, 0x4e, 0xe6, 0x47, 0x61, 0xca, 0xb1, 0xcc, 0x36, 0x2c, 0x3f, 0xa0, 0xd9, - 0xc3, 0xb0, 0x1f, 0x59, 0xf4, 0x8b, 0x21, 0x4d, 0x33, 0xf3, 0xaf, 0x06, 0xb4, 0x14, 0x28, 0x8d, - 0xa3, 0x30, 0xa5, 0x64, 0x1d, 0xe6, 0x87, 0x71, 0xe6, 0x0f, 0x68, 0xd7, 0xd8, 0x35, 0xae, 0xd7, - 0x2d, 0xf1, 0x46, 0xf6, 0xa0, 0xe3, 0x9c, 0x3a, 0x7e, 0xe0, 0x1c, 0x06, 0xd4, 0xa6, 0xcf, 0xdd, - 0x63, 0x27, 0x3c, 0xa2, 0x69, 0xb7, 0xb6, 0x6b, 0x5c, 0x9f, 0xb1, 0x88, 0x5a, 0xba, 0x27, 0x57, - 0xc8, 0x2b, 0xb0, 0x42, 0x43, 0x06, 0xf2, 0x0a, 0xe8, 0x33, 0x88, 0xde, 0x16, 0x0b, 0x39, 0xf2, - 0x1d, 0x58, 0xf7, 0x68, 0xdf, 0x19, 0x06, 0x99, 0xdd, 0x8f, 0x12, 0xfa, 0xdc, 0x8e, 0x93, 0xe8, - 0xd4, 0xf7, 0x68, 0xd2, 0x9d, 0x45, 0x29, 0x56, 0xc5, 0xea, 0x7d, 0xb6, 0xf8, 0x58, 0xac, 0x91, - 0xdb, 0xb0, 0xa6, 0x76, 0xf9, 0x4e, 0x66, 0xbb, 0xc3, 0x24, 0xa1, 0xa1, 0x7b, 0xde, 0x9d, 0xc3, - 0x4d, 0x1d, 0xb9, 0xc9, 0x77, 0xb2, 0x03, 0xb1, 0x64, 0xbe, 0x05, 0xbd, 0x07, 0x34, 0xa4, 0x89, - 0xef, 0x4a, 0xee, 0x1f, 0x3b, 0x03, 0x2a, 0x2c, 0x42, 0x7a, 0xb0, 0x28, 0x85, 0x15, 0xfa, 0xab, - 0x77, 0x73, 0x07, 0xb6, 0x46, 0xee, 0xe4, 0x86, 0x33, 0xf7, 0xa0, 0xf3, 0x80, 0x66, 0x4a, 0x25, - 0x49, 0xb1, 0x0b, 0x0b, 0x42, 0x5b, 0x24, 0xb8, 0x68, 0xc9, 0x57, 0xf3, 0x0e, 0xac, 0xea, 0x1b, - 0x84, 0x07, 0xb6, 0xa1, 0x9e, 0x1b, 0x8c, 0x0b, 0x91, 0x03, 0xcc, 0x3b, 0xb0, 0x7e, 0xd7, 0x4f, - 0x8b, 0xa6, 0x9e, 0x46, 0xf6, 0xaf, 0x67, 0x60, 0xa3, 0xc0, 0x4c, 0xf3, 0x38, 0x81, 0xd9, 0xd0, - 0x51, 0xfe, 0xc6, 0xe7, 0xa2, 0xd4, 0x35, 0x4d, 0x6a, 0xb6, 0x72, 0x4a, 0x93, 0xc3, 0x28, 0xa5, - 0xe8, 0xcc, 0x45, 0x4b, 0xbe, 0x92, 0x97, 0x60, 0x69, 0x98, 0xfa, 0xe1, 0x91, 0x9d, 0x3a, 0xa1, - 0x77, 0x18, 0x3d, 0x47, 0xd7, 0x2d, 0x5a, 0x4d, 0x04, 0x3e, 0xe1, 0x30, 0x72, 0x05, 0x9a, 0xc7, - 0x59, 0x16, 0xdb, 0x2c, 0xa6, 0xa2, 0x61, 0x26, 0x3c, 0xd5, 0x60, 0xb0, 0xa7, 0x1c, 0x44, 0xae, - 0xc2, 0x32, 0xa2, 0x0c, 0x53, 0x9a, 0x38, 0x47, 0x34, 0xcc, 0xba, 0xf3, 0x88, 0xb4, 0xc4, 0xa0, - 0x9f, 0x48, 0x20, 0xd9, 0x01, 0x40, 0xb4, 0x38, 0x89, 0x9e, 0x9f, 0x77, 0x17, 0xb8, 0x9d, 0x18, - 0xe4, 0x31, 0x03, 0x90, 0x97, 0xa1, 0x75, 0xe8, 0xa4, 0x54, 0xc6, 0x84, 0x4f, 0xd3, 0xee, 0x22, - 0xe2, 0x2c, 0x33, 0xf0, 0x81, 0x82, 0x92, 0x1b, 0xd0, 0x4e, 0x87, 0x71, 0x1c, 0x25, 0x19, 0xf5, - 0x6c, 0x27, 0x4d, 0x69, 0x96, 0x76, 0xeb, 0x88, 0xd9, 0x52, 0xf0, 0x7d, 0x04, 0x33, 0x0d, 0x65, - 0x48, 0xc7, 0x8e, 0x9f, 0xa4, 0x5d, 0x40, 0xbc, 0xa6, 0x00, 0x3e, 0x66, 0x30, 0xc6, 0x38, 0x3f, - 0x28, 0x1c, 0xad, 0xc1, 0x19, 0x2b, 0x30, 0x47, 0x7c, 0x05, 0x56, 0x9c, 0x61, 0x76, 0x4c, 0xc3, - 0xcc, 0x77, 0x1d, 0x64, 0x1e, 0xfb, 0xdd, 0x26, 0xda, 0xac, 0xad, 0x2d, 0xec, 0xc7, 0xbe, 0x79, - 0x06, 0xed, 0x07, 0x34, 0x7b, 0xea, 0xbb, 0x27, 0x34, 0x99, 0xc2, 0xe1, 0xe4, 0x3a, 0xcc, 0x32, - 0xde, 0xe8, 0xbd, 0xc6, 0xed, 0xd5, 0x5b, 0x3c, 0x43, 0xdc, 0x92, 0xc7, 0x80, 0x49, 0x60, 0x21, - 0x06, 0xb3, 0x23, 0x6a, 0x6d, 0x67, 0xe7, 0x31, 0xf7, 0x69, 0xdd, 0xaa, 0x23, 0xe4, 0xe9, 0x79, - 0x4c, 0xcd, 0x67, 0xd0, 0x2c, 0x6e, 0x62, 0xd1, 0xe9, 0xd1, 0xc0, 0x1f, 0xf8, 0x19, 0x4d, 0x64, - 0x74, 0x2a, 0x00, 0x8b, 0x25, 0x66, 0x5e, 0x64, 0x5b, 0xb7, 0xf0, 0x99, 0xac, 0xc2, 0xdc, 0x17, - 0xc3, 0x28, 0x93, 0xb4, 0xf9, 0x8b, 0xf9, 0x93, 0x1a, 0x2c, 0x4b, 0x75, 0x44, 0x20, 0x4a, 0x99, - 0x8d, 0x0b, 0x65, 0xbe, 0x02, 0xcd, 0xc0, 0x49, 0x33, 0x7b, 0x18, 0x7b, 0xcc, 0x40, 0x22, 0x0b, - 0x35, 0x18, 0xec, 0x13, 0x0e, 0x62, 0xbe, 0x92, 0xe9, 0x00, 0xbd, 0x20, 0xb8, 0x37, 0xdd, 0xa2, - 0x32, 0x04, 0x66, 0xd9, 0x1e, 0x8c, 0x54, 0xc3, 0xc2, 0x67, 0x06, 0x3b, 0xf6, 0x8f, 0x8e, 0x31, - 0x32, 0x0d, 0x0b, 0x9f, 0x49, 0x1b, 0x66, 0x82, 0xe8, 0x0c, 0xe3, 0xd0, 0xb0, 0xd8, 0x23, 0x83, - 0x1c, 0xfa, 0x1e, 0x86, 0x9d, 0x61, 0xb1, 0x47, 0x06, 0x71, 0xd2, 0x13, 0x0c, 0x32, 0xc3, 0x62, - 0x8f, 0x2c, 0x95, 0x9e, 0x46, 0xc1, 0x70, 0x40, 0x31, 0x9e, 0x0c, 0x4b, 0xbc, 0x91, 0x2d, 0xa8, - 0xc7, 0x89, 0xef, 0x52, 0xdb, 0xc9, 0x8e, 0x31, 0x84, 0x0c, 0x6b, 0x11, 0x01, 0xfb, 0xd9, 0xb1, - 0xd9, 0x81, 0x15, 0xe5, 0x68, 0x99, 0x44, 0xcc, 0x4f, 0x61, 0x41, 0x40, 0x26, 0x3a, 0xfd, 0x75, - 0x58, 0xc8, 0x38, 0x5a, 0xb7, 0xb6, 0x3b, 0x73, 0xbd, 0x71, 0x7b, 0x5d, 0xda, 0x50, 0xb7, 0xb4, - 0x25, 0xd1, 0xcc, 0xff, 0x05, 0x52, 0xe4, 0x26, 0x1c, 0x71, 0x23, 0xa7, 0x63, 0x20, 0x9d, 0x96, - 0x4e, 0x27, 0xcd, 0x09, 0x7c, 0x89, 0x59, 0xef, 0x51, 0xe2, 0xb1, 0x24, 0x10, 0x9d, 0x7c, 0xab, - 0xa1, 0xf9, 0x11, 0x2c, 0x29, 0xc6, 0x0f, 0x33, 0x3a, 0x60, 0x06, 0x77, 0x06, 0xd1, 0x30, 0xcc, - 0x90, 0xa7, 0x61, 0x89, 0x37, 0x16, 0x81, 0x68, 0x5f, 0x64, 0x69, 0x58, 0xfc, 0x85, 0x2c, 0x43, - 0xcd, 0xf7, 0xc4, 0x8d, 0x54, 0xf3, 0x3d, 0xf3, 0xef, 0x06, 0xac, 0x14, 0x14, 0x79, 0xe1, 0xa0, - 0xac, 0x44, 0x5c, 0x6d, 0x44, 0xc4, 0xdd, 0x80, 0xd9, 0x43, 0xdf, 0x63, 0x17, 0x21, 0xb3, 0xeb, - 0x9a, 0x24, 0xa7, 0xe9, 0x61, 0x21, 0x0a, 0x43, 0x75, 0xd2, 0x93, 0xb4, 0x3b, 0x3b, 0x11, 0x95, - 0xa1, 0x54, 0xce, 0xc3, 0x5c, 0xf5, 0x3c, 0xe8, 0xb6, 0x9c, 0x2f, 0xdb, 0x72, 0x1d, 0x2f, 0x23, - 0x45, 0x5b, 0x45, 0x9e, 0x0b, 0x90, 0x03, 0x27, 0xba, 0xf5, 0xbf, 0x01, 0x22, 0x85, 0x29, 0xe2, - 0x6f, 0xb3, 0x22, 0xb4, 0x0a, 0xc1, 0x02, 0xb2, 0xf9, 0x21, 0xac, 0x95, 0x98, 0x0b, 0xe3, 0xdf, - 0xd6, 0x68, 0xf2, 0x58, 0x24, 0x15, 0x9a, 0xa9, 0x46, 0xec, 0x4d, 0x24, 0xb6, 0xef, 0xba, 0xcc, - 0xf5, 0x85, 0x6a, 0x67, 0xe2, 0xfd, 0xf8, 0x0c, 0x16, 0xc4, 0x0e, 0x11, 0x16, 0x1c, 0xa1, 0xe6, - 0x7b, 0xe4, 0x1d, 0x80, 0xc2, 0x1d, 0xc2, 0xf5, 0xda, 0x92, 0x32, 0x88, 0x4d, 0x32, 0x1a, 0x90, - 0x5d, 0x01, 0xdd, 0xec, 0x43, 0x67, 0x04, 0x0a, 0x13, 0x45, 0xd5, 0x2a, 0x42, 0x14, 0xf9, 0x4e, - 0x2e, 0x43, 0x23, 0x8b, 0x32, 0x27, 0xb0, 0x4f, 0x9d, 0x60, 0x28, 0x43, 0x16, 0x10, 0xf4, 0x8c, - 0x41, 0x30, 0x41, 0x45, 0x01, 0x8f, 0x5c, 0x96, 0xa0, 0xa2, 0xc0, 0x33, 0x1d, 0x58, 0x2f, 0x2b, - 0x2d, 0x4c, 0x38, 0xc9, 0x65, 0xaf, 0xc0, 0xa2, 0xc3, 0xb7, 0x48, 0xc5, 0x5a, 0x25, 0xc5, 0x2c, - 0x85, 0x60, 0x12, 0xbc, 0x81, 0x0e, 0xa2, 0xb0, 0xef, 0x1f, 0xc9, 0xe8, 0x78, 0x19, 0x93, 0x95, - 0x84, 0xe5, 0xf5, 0x84, 0xe7, 0x64, 0x0e, 0x72, 0x6b, 0x5a, 0xf8, 0x6c, 0xfe, 0xc0, 0x80, 0xf6, - 0xe3, 0x28, 0xc9, 0xfa, 0x51, 0xe0, 0x47, 0xfb, 0x9e, 0x97, 0xd0, 0x34, 0x65, 0xa5, 0x84, 0xc3, - 0x1f, 0x85, 0x64, 0xf2, 0x95, 0x65, 0x48, 0x37, 0xf2, 0x43, 0x1e, 0xab, 0x35, 0x61, 0xa0, 0xc8, - 0x0f, 0x59, 0xa8, 0x92, 0x5d, 0x68, 0x78, 0x34, 0x75, 0x13, 0x3f, 0x66, 0xd5, 0xad, 0x48, 0x0b, - 0x45, 0x10, 0x23, 0x7c, 0xe8, 0x04, 0x4e, 0xe8, 0x52, 0x91, 0xd9, 0xe5, 0xab, 0xb9, 0x86, 0xe9, - 0x4a, 0x49, 0x22, 0xf5, 0xf8, 0x18, 0xa3, 0xbf, 0x00, 0x16, 0xaa, 0xfc, 0x27, 0xd4, 0x63, 0x09, - 0x14, 0xe1, 0xd7, 0x95, 0x16, 0x2a, 0xab, 0x63, 0xe5, 0xa8, 0xe6, 0x36, 0x2b, 0x32, 0x73, 0x7a, - 0x4f, 0x86, 0x83, 0x81, 0x93, 0x9c, 0x4b, 0x6e, 0x21, 0xcc, 0x1e, 0x44, 0x7e, 0xc8, 0x0c, 0xc5, - 0x94, 0x92, 0x85, 0x17, 0x7b, 0x2e, 0x8a, 0x5e, 0xd3, 0x44, 0x2f, 0x5a, 0x6b, 0x46, 0xb7, 0xd6, - 0x25, 0x80, 0x98, 0x26, 0x2e, 0x0d, 0x33, 0xe7, 0x48, 0x6a, 0x5c, 0x80, 0x98, 0xc7, 0x40, 0x1e, - 0xf5, 0xfb, 0x81, 0x1f, 0x52, 0xc6, 0x56, 0x08, 0x33, 0xc1, 0xfa, 0xe3, 0x65, 0xd0, 0x39, 0xcd, - 0x54, 0x38, 0x7d, 0x04, 0x2b, 0x8f, 0xc2, 0x11, 0x8c, 0x24, 0x39, 0x63, 0x12, 0xb9, 0x5a, 0x85, - 0xdc, 0x07, 0xd0, 0x2c, 0x08, 0x9e, 0x92, 0xb7, 0xa0, 0x2e, 0x64, 0xa4, 0x32, 0x1b, 0xf4, 0x54, - 0x36, 0xa8, 0x68, 0x68, 0xe5, 0xc8, 0xe6, 0x4f, 0x0d, 0x68, 0xe4, 0x92, 0xb1, 0x7e, 0x63, 0x8e, - 0x99, 0x5b, 0x52, 0xb9, 0xa4, 0xa8, 0xe4, 0x38, 0xb7, 0xf0, 0xef, 0xbd, 0x30, 0x4b, 0xce, 0x2d, - 0x8e, 0xdc, 0x7b, 0x02, 0x90, 0x03, 0xd9, 0x85, 0x7f, 0x42, 0xe5, 0xf9, 0x65, 0x8f, 0x64, 0x0f, - 0xe6, 0xf2, 0x43, 0x5b, 0xcc, 0x7e, 0x65, 0x9b, 0x58, 0x1c, 0xef, 0xed, 0xda, 0x5b, 0x86, 0xf9, - 0xdb, 0x59, 0xd6, 0x57, 0x8c, 0x08, 0x16, 0x11, 0x83, 0xaf, 0x41, 0x83, 0x9f, 0x05, 0x96, 0x01, - 0xa4, 0xc0, 0x4d, 0x75, 0x0f, 0x45, 0x7e, 0x68, 0x01, 0x9e, 0x0d, 0x5c, 0x27, 0x6f, 0xc0, 0x12, - 0x0a, 0x6b, 0x47, 0xdc, 0x20, 0xe2, 0x60, 0xeb, 0x1b, 0x9a, 0x88, 0x22, 0x4c, 0x46, 0x62, 0x58, - 0xd3, 0xb6, 0xd8, 0x29, 0x17, 0x41, 0x5c, 0x52, 0xef, 0xca, 0xad, 0x13, 0xa4, 0xe4, 0xc6, 0x12, - 0x04, 0xc5, 0x1a, 0x37, 0x5d, 0xc7, 0xad, 0xae, 0x90, 0x3d, 0x68, 0x0a, 0x8e, 0x68, 0x19, 0x71, - 0xc5, 0xe9, 0x32, 0x36, 0xf8, 0x46, 0x44, 0x20, 0x03, 0x58, 0x2d, 0x6e, 0x50, 0x12, 0xce, 0xe1, - 0xc6, 0x77, 0xa6, 0x97, 0x30, 0xac, 0x08, 0x48, 0xdc, 0xca, 0x42, 0xef, 0x3b, 0xd0, 0x1d, 0xa7, - 0xd0, 0x08, 0xb7, 0xdf, 0xd4, 0xdd, 0xbe, 0x3a, 0x22, 0x24, 0xd3, 0x82, 0xc7, 0x7b, 0x9f, 0xc1, - 0xc6, 0x18, 0x61, 0x46, 0x10, 0xbf, 0xa1, 0x13, 0xef, 0x8c, 0x88, 0xd4, 0x62, 0x34, 0xfd, 0xc8, - 0x80, 0xde, 0xbe, 0xe7, 0x55, 0x92, 0x53, 0xde, 0x8d, 0x7e, 0xdb, 0x29, 0x77, 0x07, 0xb6, 0x46, - 0x0a, 0x24, 0xda, 0xe6, 0xe7, 0xb0, 0x63, 0xd1, 0x41, 0x74, 0x4a, 0xbf, 0x6d, 0x91, 0xcd, 0x5d, - 0xb8, 0x34, 0x8e, 0xb3, 0x90, 0xad, 0x07, 0xdd, 0x07, 0x54, 0x9f, 0x39, 0xa8, 0xc2, 0xe8, 0x4f, - 0x06, 0x2c, 0xe9, 0xd3, 0x88, 0x7f, 0x55, 0x1f, 0xfd, 0x2a, 0x90, 0x84, 0xa6, 0x99, 0x9d, 0x44, - 0x41, 0xc0, 0xda, 0x69, 0x8f, 0x06, 0xce, 0xb9, 0x98, 0x83, 0xb4, 0xd9, 0x8a, 0xc5, 0x17, 0xee, - 0x32, 0x38, 0xd9, 0x80, 0x05, 0x27, 0xf6, 0x6d, 0x16, 0x35, 0xbc, 0x97, 0x9e, 0x77, 0x62, 0xff, - 0x43, 0x7a, 0x4e, 0x4c, 0x58, 0x12, 0x0b, 0x76, 0x40, 0x4f, 0x69, 0x80, 0x35, 0xdf, 0x8c, 0xd5, - 0xe0, 0xcb, 0xff, 0xcf, 0x40, 0xac, 0xf7, 0x8d, 0x13, 0x9f, 0x85, 0x5f, 0x3e, 0x70, 0x59, 0x40, - 0x69, 0x5a, 0x02, 0x2e, 0xb5, 0x33, 0x3f, 0x87, 0xcd, 0x11, 0xb6, 0x10, 0x39, 0xea, 0x3d, 0x68, - 0xe9, 0x63, 0x1b, 0x99, 0xa7, 0x54, 0xd5, 0xaa, 0x6d, 0xb4, 0x96, 0xfb, 0x1a, 0x1d, 0x51, 0x7d, - 0x22, 0x8e, 0xe5, 0x64, 0x6a, 0x78, 0x62, 0x7e, 0x01, 0xab, 0x39, 0xf0, 0x20, 0x0a, 0x4f, 0x69, - 0x92, 0xb2, 0x68, 0x23, 0x30, 0xdb, 0x4f, 0xa2, 0x81, 0x34, 0x35, 0x7b, 0x66, 0x75, 0x5b, 0x16, - 0x89, 0x30, 0xa8, 0x65, 0x11, 0xc3, 0x49, 0x9c, 0x4c, 0xde, 0x52, 0xf8, 0xcc, 0xea, 0x64, 0x1f, - 0x89, 0x50, 0x1b, 0xd7, 0x78, 0xa8, 0x36, 0x04, 0x8c, 0x71, 0x31, 0x9f, 0x61, 0xf9, 0x58, 0x14, - 0x45, 0xe8, 0xf8, 0x3f, 0xd0, 0xe0, 0x3a, 0xb2, 0x9d, 0x52, 0xbf, 0x6d, 0x4d, 0xbf, 0x92, 0x98, - 0x16, 0xf4, 0x15, 0xd4, 0xfc, 0x4b, 0x0d, 0x9a, 0x58, 0xb1, 0xde, 0xa5, 0x99, 0xe3, 0x07, 0x93, - 0x6b, 0x69, 0x5e, 0x83, 0xd6, 0x54, 0x0d, 0xfa, 0x12, 0x2c, 0x15, 0x87, 0x19, 0xe7, 0xb2, 0x99, - 0x2d, 0x8c, 0x32, 0xce, 0xc9, 0x55, 0x58, 0xc6, 0xd6, 0x3a, 0xc7, 0xe2, 0x31, 0xb3, 0x84, 0x50, - 0x85, 0xa6, 0x37, 0x02, 0x73, 0xa5, 0x46, 0x80, 0x2d, 0x63, 0x31, 0x6d, 0xa7, 0xbe, 0xa7, 0xfa, - 0x04, 0x84, 0x3c, 0xf1, 0xbd, 0xc2, 0x32, 0xee, 0x5e, 0x28, 0x2c, 0xe3, 0x6e, 0xd6, 0x03, 0x25, - 0x14, 0xc7, 0x8e, 0x38, 0xe2, 0xc1, 0x76, 0x78, 0xc6, 0x6a, 0x4a, 0xe0, 0x53, 0x7f, 0x80, 0x23, - 0xc6, 0x34, 0x73, 0xb2, 0xa1, 0x9c, 0xb3, 0x88, 0xb7, 0xbc, 0x4d, 0x83, 0x62, 0x9b, 0x96, 0x37, - 0x75, 0x0d, 0xad, 0xa9, 0xbb, 0x0c, 0x8d, 0x28, 0xa6, 0xa1, 0x2d, 0x5a, 0xec, 0x26, 0xaf, 0x1e, - 0x18, 0xe8, 0x19, 0x42, 0xc4, 0xc8, 0x04, 0x6d, 0x9e, 0x4e, 0xd3, 0x97, 0xea, 0x86, 0xa9, 0x95, - 0x0d, 0x23, 0x1b, 0xc1, 0x99, 0x8b, 0x1a, 0x41, 0x73, 0x1f, 0xab, 0x62, 0xc9, 0x58, 0x84, 0xcf, - 0xab, 0x30, 0x8f, 0x66, 0x92, 0x91, 0xb3, 0xaa, 0xb5, 0x31, 0x22, 0x28, 0x2c, 0x81, 0x63, 0x7e, - 0x80, 0x83, 0x59, 0x5c, 0x9a, 0x46, 0xf4, 0x4d, 0x58, 0xe4, 0x5e, 0x51, 0x51, 0xb3, 0x80, 0xef, - 0x0f, 0x3d, 0xf3, 0xf7, 0x06, 0x90, 0x27, 0xc3, 0xc3, 0x81, 0x3f, 0x3d, 0xb5, 0xe9, 0x1b, 0x74, - 0x02, 0xb3, 0x18, 0x26, 0x3c, 0x1c, 0xf1, 0xb9, 0x14, 0x21, 0xb3, 0xe5, 0x08, 0xc9, 0xdd, 0x39, - 0x37, 0xba, 0x47, 0x9f, 0x2f, 0x3a, 0x9f, 0xa5, 0xf8, 0xc0, 0xa7, 0x61, 0x66, 0x8b, 0x61, 0x0b, - 0x4b, 0xf1, 0x08, 0x78, 0xe8, 0x99, 0x4f, 0xa0, 0xa3, 0x69, 0x26, 0x2c, 0x7d, 0x05, 0x9a, 0x5c, - 0x80, 0x38, 0x70, 0x5c, 0x35, 0x76, 0x6d, 0x20, 0xec, 0x31, 0x82, 0x26, 0xd9, 0xeb, 0xcf, 0x06, - 0x90, 0x03, 0x76, 0x71, 0x05, 0x53, 0xdb, 0x8b, 0x05, 0x0e, 0xef, 0x92, 0x72, 0x7a, 0x75, 0x01, - 0x79, 0xa8, 0x33, 0x9b, 0xd1, 0x98, 0x29, 0x4b, 0xcf, 0xbe, 0xe0, 0x28, 0xa4, 0x72, 0x6a, 0xaf, - 0xc2, 0xf2, 0x99, 0x13, 0x04, 0x34, 0xb3, 0xe5, 0x5d, 0x29, 0x66, 0xa6, 0x1c, 0x2a, 0x3b, 0x2e, - 0xe9, 0xaf, 0x85, 0xdc, 0x5f, 0xac, 0x25, 0xd2, 0xf4, 0x15, 0x77, 0xdf, 0x1d, 0x58, 0xe7, 0xe0, - 0xfd, 0x20, 0x98, 0xfa, 0x0c, 0x99, 0x3f, 0xab, 0xc1, 0x46, 0x65, 0x9b, 0xba, 0x24, 0xf4, 0x13, - 0x70, 0x4d, 0xa9, 0x3b, 0x7a, 0xc3, 0x2d, 0xf1, 0x2a, 0x76, 0xf5, 0x7e, 0x6d, 0xc0, 0x3c, 0x07, - 0x4d, 0xf4, 0xc6, 0x67, 0xd2, 0xfd, 0x22, 0xc7, 0xf0, 0xfa, 0xf7, 0xbf, 0xa6, 0x63, 0xc6, 0xff, - 0x3d, 0xc1, 0x9d, 0xbc, 0x3c, 0xe4, 0x71, 0xc3, 0x21, 0xbd, 0xf7, 0xa0, 0x5d, 0x46, 0x18, 0x51, - 0xb2, 0xad, 0x16, 0x4b, 0xb6, 0x7a, 0xb1, 0x3a, 0xe3, 0x3d, 0xf4, 0xbd, 0x53, 0x1a, 0x66, 0xea, - 0x8e, 0xfb, 0xda, 0x80, 0xd6, 0x41, 0x14, 0x7a, 0x3e, 0xcb, 0x8f, 0x8f, 0x9d, 0xc4, 0x19, 0xa4, - 0x64, 0x9b, 0x55, 0x36, 0x02, 0x24, 0x87, 0xac, 0x0a, 0x30, 0x66, 0x9c, 0xb5, 0x03, 0xe0, 0x1e, - 0x53, 0xf7, 0xc4, 0x16, 0xf3, 0x25, 0x16, 0xf4, 0x75, 0x84, 0xbc, 0xef, 0x7b, 0x29, 0x79, 0x0d, - 0x3a, 0xf9, 0xb2, 0xed, 0x84, 0x9e, 0x2d, 0x86, 0x4b, 0x38, 0x6f, 0x56, 0x78, 0xfb, 0xa1, 0xb7, - 0x9f, 0x9e, 0xe0, 0x54, 0x5c, 0xcd, 0x54, 0x6c, 0xed, 0xc0, 0xb6, 0x14, 0x7c, 0x1f, 0xc1, 0xe6, - 0xdf, 0x0c, 0xcc, 0x77, 0x52, 0x2b, 0xe1, 0xed, 0x7c, 0x8c, 0x82, 0xd3, 0x35, 0xcd, 0x65, 0xb5, - 0x92, 0xcb, 0x08, 0xcc, 0xfa, 0x19, 0x1d, 0xc8, 0x34, 0xc2, 0x9e, 0xc9, 0xfb, 0xd0, 0x56, 0x1a, - 0xdb, 0x31, 0x9a, 0x45, 0x1c, 0x93, 0x8d, 0xbc, 0x4d, 0xd0, 0xac, 0x66, 0xb5, 0xdc, 0x92, 0x19, - 0xe5, 0xf1, 0x9a, 0xbb, 0xf0, 0x78, 0xb1, 0xac, 0xe4, 0xa2, 0xb5, 0xe7, 0x45, 0x11, 0x85, 0x6f, - 0x5c, 0x6a, 0xea, 0x0e, 0x33, 0xea, 0x89, 0xc2, 0x48, 0xbd, 0x9b, 0x7f, 0x34, 0xa0, 0xb5, 0xef, - 0x79, 0xa8, 0xf7, 0x34, 0x69, 0x42, 0x6a, 0x59, 0xbb, 0x40, 0xcb, 0x99, 0x7f, 0x52, 0xcb, 0x6f, - 0x9c, 0x44, 0xc6, 0x18, 0xc1, 0x34, 0xa1, 0x9d, 0xeb, 0x39, 0xda, 0xbd, 0xe6, 0x7f, 0x00, 0xe1, - 0xc5, 0xb4, 0x66, 0x8e, 0x32, 0xd6, 0x1a, 0x74, 0x34, 0x2c, 0x91, 0x6b, 0xee, 0xc3, 0xf5, 0x07, - 0x34, 0x3b, 0x48, 0xce, 0xe3, 0x2c, 0x92, 0xc5, 0xcb, 0x5d, 0x1a, 0x47, 0xa9, 0x2f, 0x33, 0x17, - 0x9d, 0x2a, 0xfb, 0xfc, 0xc6, 0x80, 0x1b, 0x53, 0x10, 0x12, 0x2a, 0x7c, 0xb7, 0x3a, 0x4d, 0xf8, - 0xbf, 0x42, 0x23, 0x39, 0x1d, 0x95, 0x5b, 0x0a, 0xc2, 0xd3, 0x45, 0x4e, 0xb2, 0xf7, 0x2e, 0x2c, - 0xeb, 0x8b, 0x2f, 0x94, 0x2a, 0x02, 0xb8, 0x76, 0x81, 0x10, 0xd3, 0xc4, 0xdc, 0x35, 0x58, 0x76, - 0x35, 0x12, 0x82, 0x51, 0x09, 0x6a, 0x1e, 0xc0, 0xcb, 0x17, 0x72, 0x13, 0x66, 0x1b, 0xdb, 0x8f, - 0x99, 0xbf, 0x9c, 0x85, 0x8d, 0x4f, 0xfd, 0xec, 0xd8, 0x4b, 0x9c, 0x33, 0x19, 0x7d, 0xd3, 0x08, - 0x59, 0x6a, 0xd5, 0x6a, 0xd5, 0xee, 0xf2, 0x26, 0xac, 0x44, 0x21, 0xc5, 0x8a, 0xd2, 0x8e, 0x9d, - 0x34, 0x3d, 0x8b, 0x12, 0x79, 0x97, 0xb6, 0xa2, 0x90, 0xb2, 0xaa, 0xf2, 0xb1, 0x00, 0x97, 0x6e, - 0xe3, 0xd9, 0xf2, 0x6d, 0xdc, 0x86, 0x99, 0xd8, 0x0f, 0xc5, 0x84, 0x9c, 0x3d, 0xb2, 0xbb, 0x33, - 0x4b, 0x1c, 0xaf, 0x40, 0x59, 0xdc, 0x9d, 0x08, 0x55, 0x74, 0x8b, 0x33, 0xdb, 0x85, 0xd2, 0xcc, - 0xb6, 0x60, 0x93, 0x45, 0xbd, 0x47, 0xbd, 0x0c, 0x0d, 0xf1, 0x68, 0x67, 0xce, 0x91, 0x28, 0x78, - 0x41, 0x80, 0x9e, 0x3a, 0x47, 0x85, 0x7a, 0x08, 0xb4, 0x7a, 0x68, 0x07, 0xa0, 0x4f, 0xa9, 0xad, - 0x95, 0xbe, 0xf5, 0x3e, 0xa5, 0x3c, 0xe9, 0xb2, 0xc2, 0xe8, 0xd0, 0x09, 0x4f, 0x6c, 0xec, 0x38, - 0x9b, 0x5c, 0x1c, 0x06, 0xf8, 0x98, 0x75, 0x9d, 0x57, 0xa0, 0x89, 0x8b, 0x52, 0xa6, 0x25, 0x6e, - 0x51, 0x06, 0xdb, 0xcf, 0x7b, 0x67, 0x44, 0x71, 0xfd, 0xec, 0xbc, 0xbb, 0x9c, 0xef, 0x3f, 0xf0, - 0xb3, 0x73, 0xb5, 0x1f, 0x6d, 0x96, 0x9c, 0x77, 0x5b, 0xf9, 0xfe, 0x03, 0x0e, 0x62, 0xe2, 0xa5, - 0x67, 0x7e, 0x9f, 0xda, 0x6e, 0xe4, 0xd1, 0x6e, 0x9b, 0x5b, 0x19, 0x21, 0x07, 0x91, 0x87, 0x7d, - 0xc0, 0x99, 0x9f, 0x14, 0x5a, 0x91, 0x15, 0xde, 0xb0, 0x30, 0xa0, 0xfa, 0x14, 0x7f, 0x13, 0xda, - 0x32, 0x5c, 0x8a, 0x3f, 0x3f, 0x48, 0x68, 0x3a, 0x0c, 0x32, 0xf9, 0xf3, 0x03, 0xfe, 0x76, 0xfb, - 0x17, 0x9b, 0xb0, 0xfc, 0x20, 0xe2, 0x01, 0xfa, 0x94, 0xf9, 0x25, 0x21, 0x8f, 0x60, 0x41, 0xfc, - 0x78, 0x81, 0xac, 0x17, 0xce, 0x6d, 0x61, 0xe4, 0xdf, 0xdb, 0xa8, 0xc0, 0x45, 0xc6, 0xe9, 0x7c, - 0xf5, 0xbb, 0x3f, 0xfc, 0xb8, 0xb6, 0x44, 0x1a, 0x7b, 0xa7, 0x6f, 0xec, 0x1d, 0xd1, 0xcc, 0x67, - 0x54, 0x5c, 0x68, 0x16, 0x3f, 0xc8, 0x93, 0xad, 0xc2, 0xee, 0xf2, 0x77, 0xfd, 0xde, 0xf6, 0xe8, - 0x45, 0x41, 0xbf, 0x8b, 0xf4, 0x09, 0x69, 0x0b, 0xfa, 0xea, 0xfb, 0x3d, 0xf9, 0x12, 0x5a, 0xa5, - 0xef, 0xf7, 0xc4, 0xcc, 0x49, 0x8d, 0xfb, 0x61, 0x42, 0xef, 0xa5, 0x89, 0x38, 0x82, 0xeb, 0x25, - 0xe4, 0xda, 0x35, 0x3b, 0x8c, 0xab, 0xc7, 0xb9, 0x48, 0xce, 0x6f, 0x1b, 0x37, 0x49, 0x8a, 0x5d, - 0x45, 0xf1, 0x47, 0x00, 0x53, 0xf1, 0xbe, 0x3c, 0x42, 0x55, 0xcd, 0x9a, 0x5b, 0xc8, 0x77, 0x8d, - 0x74, 0x4a, 0xda, 0xa2, 0x55, 0x9f, 0xc3, 0xf2, 0xbd, 0xf0, 0xdf, 0xa3, 0xef, 0x0e, 0xf2, 0xdd, - 0x30, 0x09, 0xe3, 0xcb, 0x07, 0x2b, 0x45, 0x75, 0x3f, 0x85, 0xba, 0xfa, 0xb8, 0x49, 0xba, 0x05, - 0x25, 0xb4, 0xcf, 0xe8, 0xbd, 0x31, 0x1f, 0x49, 0xa5, 0x0f, 0xcd, 0x25, 0xa1, 0x15, 0xff, 0xe4, - 0xc9, 0x08, 0x7f, 0x0e, 0x90, 0x7f, 0x35, 0x25, 0x9b, 0x15, 0xca, 0x2a, 0x48, 0x7a, 0xa3, 0x96, - 0x04, 0xf9, 0x75, 0x24, 0xdf, 0x26, 0xcb, 0x1a, 0xf9, 0x54, 0x44, 0xa1, 0xfa, 0xb8, 0xa5, 0x45, - 0x61, 0xf9, 0x3b, 0x6b, 0x6f, 0xfc, 0x07, 0x36, 0xe9, 0x14, 0x53, 0x86, 0xa0, 0x2a, 0xdb, 0x98, - 0x06, 0x47, 0xb0, 0xa4, 0x7d, 0x71, 0x23, 0xdb, 0xa3, 0xb8, 0x28, 0x3d, 0x76, 0xc6, 0xac, 0x0a, - 0x56, 0x9b, 0xc8, 0xaa, 0x43, 0x56, 0xca, 0xac, 0x52, 0x72, 0x82, 0x3f, 0x3a, 0x2a, 0x7c, 0x98, - 0x22, 0x45, 0x5a, 0xd5, 0xaf, 0x74, 0xbd, 0x4b, 0xe3, 0x96, 0xe5, 0x4c, 0x0e, 0x79, 0xad, 0x12, - 0x22, 0x78, 0x89, 0xcc, 0x8e, 0xa1, 0xc6, 0x1d, 0xce, 0x3f, 0x47, 0x69, 0x0e, 0xd7, 0xbe, 0x5a, - 0xf5, 0x36, 0x47, 0xac, 0x08, 0xea, 0x6b, 0x48, 0xbd, 0x45, 0xa4, 0xcf, 0x5d, 0x4e, 0x8b, 0xfb, - 0x44, 0xcd, 0x09, 0x35, 0x9f, 0x94, 0x3f, 0x26, 0x69, 0x99, 0xa1, 0xf2, 0x49, 0xa9, 0x92, 0x19, - 0xd4, 0x47, 0x23, 0xf2, 0x7d, 0xfd, 0xdb, 0x94, 0x9c, 0x95, 0x9b, 0x13, 0x87, 0xdb, 0x95, 0xd3, - 0x32, 0x76, 0x00, 0x6e, 0x5e, 0x46, 0xce, 0x9b, 0x64, 0xa3, 0xcc, 0x59, 0x0c, 0xd3, 0xc9, 0x57, - 0x06, 0x74, 0x46, 0x8c, 0x6a, 0x73, 0x09, 0xc6, 0x0f, 0x96, 0x73, 0x09, 0x26, 0xcd, 0x7a, 0x4d, - 0x94, 0x60, 0xdb, 0x44, 0x09, 0x1c, 0xcf, 0x53, 0x12, 0x88, 0x8b, 0x8a, 0x45, 0xe6, 0x0f, 0x0d, - 0x58, 0x1f, 0x3d, 0x96, 0x25, 0x57, 0x25, 0x8f, 0x89, 0x03, 0xe3, 0xde, 0xb5, 0x8b, 0xd0, 0x84, - 0x34, 0x57, 0x51, 0x9a, 0xcb, 0x66, 0x8f, 0x49, 0x93, 0x20, 0xee, 0x28, 0x81, 0xce, 0xb0, 0xbb, - 0xd1, 0x07, 0x9f, 0x64, 0xb7, 0x60, 0xf0, 0x91, 0xf3, 0xe1, 0xde, 0x95, 0x09, 0x18, 0x7a, 0xfa, - 0x22, 0x6b, 0xc2, 0x21, 0x38, 0x2d, 0x54, 0x13, 0x54, 0x71, 0x46, 0xf3, 0xc1, 0xa2, 0x76, 0x46, - 0x2b, 0xb3, 0x52, 0xed, 0x8c, 0x56, 0xc7, 0x97, 0x95, 0x33, 0x8a, 0xcc, 0x70, 0x94, 0x49, 0x3e, - 0xc3, 0x63, 0x23, 0x5a, 0xeb, 0x6e, 0xf9, 0xa8, 0xa7, 0xa3, 0x8e, 0x8d, 0xde, 0x3c, 0x57, 0x52, - 0x25, 0xef, 0xd8, 0x99, 0xf5, 0x2c, 0x58, 0x94, 0xe8, 0x64, 0xa3, 0x4c, 0x40, 0x52, 0x1e, 0x39, - 0x0b, 0x33, 0x37, 0x90, 0xe8, 0x8a, 0xd9, 0x2c, 0x12, 0x65, 0x34, 0x0f, 0xa1, 0x51, 0x98, 0xfb, - 0x10, 0x95, 0x64, 0xab, 0x63, 0xae, 0xde, 0xd6, 0xc8, 0x35, 0x3d, 0x95, 0x98, 0x2d, 0xc6, 0x20, - 0x45, 0x84, 0x22, 0x8f, 0xc2, 0x54, 0x24, 0xe7, 0x51, 0x1d, 0x0d, 0xe5, 0x3c, 0x46, 0x8d, 0x51, - 0x34, 0x1e, 0x2e, 0x22, 0x28, 0x1e, 0x09, 0xb4, 0x4a, 0xd3, 0x08, 0x72, 0x69, 0xec, 0x98, 0xa2, - 0x74, 0x15, 0x8f, 0x19, 0x63, 0xe8, 0x25, 0x00, 0xe7, 0xe7, 0x04, 0x41, 0xee, 0x0f, 0x9e, 0x22, - 0x79, 0xaf, 0xae, 0xf9, 0x5a, 0x1b, 0x4a, 0x68, 0xbe, 0xd6, 0x1b, 0xfb, 0x4a, 0x8a, 0xa4, 0x9c, - 0xd6, 0x33, 0x58, 0x94, 0x4d, 0x62, 0xee, 0xe8, 0x52, 0x7b, 0xdc, 0xeb, 0x56, 0x17, 0x04, 0x55, - 0xcd, 0xd9, 0x8e, 0xe7, 0x21, 0x55, 0xe1, 0x88, 0x42, 0xcb, 0x98, 0x3b, 0xa2, 0xda, 0x6d, 0xe6, - 0x8e, 0x18, 0xd5, 0x63, 0x6a, 0x8e, 0xe0, 0xa7, 0x5d, 0xf1, 0xf8, 0x95, 0x01, 0x57, 0x2e, 0xec, - 0xf8, 0xc8, 0xeb, 0x2f, 0xd0, 0x1c, 0x72, 0x81, 0xde, 0x78, 0xe1, 0x76, 0xd2, 0xbc, 0x8e, 0x62, - 0x9a, 0xe6, 0x8e, 0xbc, 0x80, 0x70, 0x9b, 0xc7, 0xd1, 0x55, 0x6f, 0xc9, 0x84, 0xfe, 0xb9, 0x01, - 0x97, 0x2f, 0xa0, 0x4b, 0x6e, 0x4d, 0x29, 0x80, 0x14, 0x78, 0x6f, 0x6a, 0x7c, 0x21, 0xee, 0x35, - 0x14, 0x77, 0xd7, 0xdc, 0x9a, 0x20, 0x2e, 0x13, 0xf6, 0x7b, 0xb0, 0xa5, 0x3a, 0x43, 0x8d, 0xee, - 0xfd, 0x61, 0xe8, 0xa5, 0x44, 0x85, 0xf5, 0x98, 0xf6, 0x31, 0x0f, 0x9c, 0x72, 0xc3, 0xa0, 0xdf, - 0x29, 0x67, 0x62, 0x95, 0x8b, 0xd1, 0x67, 0xb4, 0x19, 0xf7, 0x18, 0x56, 0xe4, 0xbe, 0xfb, 0xbe, - 0x93, 0x7d, 0x63, 0x9e, 0xbb, 0xc8, 0xb3, 0x67, 0xae, 0x15, 0x79, 0xf6, 0x7d, 0x27, 0x93, 0x1c, - 0x0f, 0xe7, 0xf1, 0x27, 0xd7, 0x6f, 0xfe, 0x23, 0x00, 0x00, 0xff, 0xff, 0x49, 0x38, 0x72, 0xc9, - 0xa5, 0x2d, 0x00, 0x00, + // 3508 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x1a, 0x4d, 0x6f, 0xdc, 0xc6, + 0x15, 0x5c, 0x7d, 0xbf, 0x5d, 0x69, 0x57, 0xa3, 0xaf, 0xd5, 0x4a, 0xb2, 0xe5, 0x49, 0xed, 0xd8, + 0x4e, 0x62, 0x25, 0x8e, 0xd1, 0xa6, 0x49, 0x9a, 0x56, 0x91, 0x3f, 0x62, 0xa4, 0x89, 0x05, 0xda, + 0x71, 0x80, 0xa4, 0x28, 0x41, 0x91, 0xb3, 0x12, 0x21, 0x8a, 0x64, 0x48, 0xae, 0x64, 0x05, 0x05, + 0x0a, 0x04, 0xe8, 0xb5, 0x3d, 0x14, 0x05, 0x7a, 0xe8, 0xa5, 0xd7, 0x02, 0xbd, 0xf4, 0x07, 0x04, + 0xbd, 0x16, 0x3d, 0xf6, 0xd2, 0x1f, 0x50, 0xf4, 0xd6, 0x16, 0x3d, 0xf4, 0xd2, 0x53, 0x31, 0x6f, + 0x66, 0x48, 0x0e, 0xc9, 0x5d, 0xad, 0x9b, 0x36, 0x17, 0x89, 0x7c, 0xf3, 0xe6, 0x7d, 0xcf, 0x9b, + 0xf7, 0x1e, 0x17, 0xe6, 0xe2, 0xc8, 0xb9, 0x15, 0xc5, 0x61, 0x1a, 0x92, 0xe9, 0x43, 0x27, 0x8d, + 0x23, 0xa7, 0xb7, 0x79, 0x18, 0x86, 0x87, 0x3e, 0xdb, 0xb1, 0x23, 0x6f, 0xc7, 0x0e, 0x82, 0x30, + 0xb5, 0x53, 0x2f, 0x0c, 0x12, 0x81, 0x45, 0x3b, 0xb0, 0xf0, 0x80, 0xa5, 0x0f, 0x83, 0x7e, 0x68, + 0xb2, 0xcf, 0x06, 0x2c, 0x49, 0xe9, 0x3f, 0x0d, 0x68, 0x67, 0xa0, 0x24, 0x0a, 0x83, 0x84, 0x91, + 0x55, 0x98, 0x1e, 0x44, 0xa9, 0x77, 0xc2, 0xba, 0xc6, 0xb6, 0x71, 0x7d, 0xce, 0x94, 0x6f, 0x64, + 0x07, 0x96, 0xec, 0x53, 0xdb, 0xf3, 0xed, 0x03, 0x9f, 0x59, 0xec, 0x99, 0x73, 0x64, 0x07, 0x87, + 0x2c, 0xe9, 0x36, 0xb6, 0x8d, 0xeb, 0x13, 0x26, 0xc9, 0x96, 0xee, 0xa9, 0x15, 0xf2, 0x12, 0x2c, + 0xb2, 0x80, 0x83, 0xdc, 0x02, 0xfa, 0x04, 0xa2, 0x77, 0xe4, 0x42, 0x8e, 0x7c, 0x07, 0x56, 0x5d, + 0xd6, 0xb7, 0x07, 0x7e, 0x6a, 0xf5, 0xc3, 0x98, 0x3d, 0xb3, 0xa2, 0x38, 0x3c, 0xf5, 0x5c, 0x16, + 0x77, 0x27, 0x51, 0x8a, 0x65, 0xb9, 0x7a, 0x9f, 0x2f, 0xee, 0xcb, 0x35, 0x72, 0x1b, 0x56, 0xb2, + 0x5d, 0x9e, 0x9d, 0x5a, 0xce, 0x20, 0x8e, 0x59, 0xe0, 0x9c, 0x77, 0xa7, 0x70, 0xd3, 0x92, 0xda, + 0xe4, 0xd9, 0xe9, 0x9e, 0x5c, 0xa2, 0x6f, 0x40, 0xef, 0x01, 0x0b, 0x58, 0xec, 0x39, 0x8a, 0xfb, + 0x87, 0xf6, 0x09, 0x93, 0x16, 0x21, 0x3d, 0x98, 0x55, 0xc2, 0x4a, 0xfd, 0xb3, 0x77, 0xba, 0x05, + 0x1b, 0xb5, 0x3b, 0x85, 0xe1, 0xe8, 0x0e, 0x2c, 0x3d, 0x60, 0x69, 0xa6, 0x92, 0xa2, 0xd8, 0x85, + 0x19, 0xa9, 0x2d, 0x12, 0x9c, 0x35, 0xd5, 0x2b, 0xbd, 0x03, 0xcb, 0xfa, 0x06, 0xe9, 0x81, 0x4d, + 0x98, 0xcb, 0x0d, 0x26, 0x84, 0xc8, 0x01, 0xf4, 0x36, 0xac, 0x14, 0x76, 0x3d, 0x7a, 0xb2, 0x6f, + 0x32, 0xb1, 0x6d, 0x1d, 0x66, 0xc3, 0x34, 0xb2, 0x9c, 0xd0, 0x55, 0xa2, 0xcf, 0x84, 0x69, 0xb4, + 0x17, 0xba, 0x8c, 0xde, 0x81, 0xd5, 0xbb, 0x5e, 0x52, 0x74, 0xcf, 0x38, 0xfa, 0x7e, 0x39, 0x01, + 0x6b, 0x05, 0x56, 0x5a, 0x94, 0x10, 0x98, 0x0c, 0xec, 0x2c, 0x46, 0xf0, 0xb9, 0xa8, 0x69, 0x43, + 0xd3, 0x94, 0xaf, 0x9c, 0xb2, 0xf8, 0x20, 0x4c, 0x18, 0x06, 0xc0, 0xac, 0xa9, 0x5e, 0xc9, 0x0b, + 0x30, 0x3f, 0x48, 0xbc, 0xe0, 0xd0, 0x4a, 0xec, 0xc0, 0x3d, 0x08, 0x9f, 0xa1, 0xbb, 0x67, 0xcd, + 0x16, 0x02, 0x1f, 0x0b, 0x18, 0xb9, 0x02, 0xad, 0xa3, 0x34, 0x8d, 0x2c, 0x1e, 0x87, 0xe1, 0x20, + 0x95, 0xde, 0x6d, 0x72, 0xd8, 0x13, 0x01, 0x22, 0x57, 0x61, 0x01, 0x51, 0x06, 0x09, 0x8b, 0xed, + 0x43, 0x16, 0xa4, 0xdd, 0x69, 0x44, 0x9a, 0xe7, 0xd0, 0x8f, 0x14, 0x90, 0x6c, 0x01, 0x20, 0x5a, + 0x14, 0x87, 0xcf, 0xce, 0xbb, 0x33, 0xc2, 0xb6, 0x1c, 0xb2, 0xcf, 0x01, 0xe4, 0x45, 0x68, 0x1f, + 0xd8, 0x09, 0x53, 0x71, 0xe4, 0xb1, 0xa4, 0x3b, 0x8b, 0x38, 0x0b, 0x1c, 0xbc, 0x97, 0x41, 0xc9, + 0x0d, 0xe8, 0x24, 0x83, 0x28, 0x0a, 0xe3, 0x94, 0xb9, 0x96, 0x9d, 0x24, 0x2c, 0x4d, 0xba, 0x73, + 0x88, 0xd9, 0xce, 0xe0, 0xbb, 0x08, 0xe6, 0x1a, 0xaa, 0x63, 0x10, 0xd9, 0x5e, 0x9c, 0x74, 0x01, + 0xf1, 0x5a, 0x12, 0xb8, 0xcf, 0x61, 0x9c, 0x71, 0x7e, 0xb8, 0x04, 0x5a, 0x53, 0x30, 0xce, 0xc0, + 0x02, 0xf1, 0x25, 0x58, 0xb4, 0x07, 0xe9, 0x11, 0x0b, 0x52, 0xcf, 0xb1, 0x91, 0x79, 0xe4, 0x75, + 0x5b, 0x68, 0xb3, 0x8e, 0xb6, 0xb0, 0x1b, 0x79, 0xf4, 0x0c, 0x3a, 0x0f, 0x58, 0xfa, 0xc4, 0x73, + 0x8e, 0x59, 0x3c, 0x86, 0xc3, 0xc9, 0x75, 0x98, 0xe4, 0xbc, 0xd1, 0x7b, 0xcd, 0xdb, 0xcb, 0xb7, + 0x44, 0x56, 0xb9, 0xa5, 0x8e, 0x0e, 0x97, 0xc0, 0x44, 0x0c, 0x6e, 0x47, 0xd4, 0xda, 0x4a, 0xcf, + 0x23, 0xe1, 0xd3, 0x39, 0x73, 0x0e, 0x21, 0x4f, 0xce, 0x23, 0x46, 0x9f, 0x42, 0xab, 0xb8, 0x89, + 0x47, 0xb4, 0xcb, 0x7c, 0xef, 0xc4, 0x4b, 0x59, 0xac, 0x22, 0x3a, 0x03, 0xf0, 0x58, 0xe2, 0xe6, + 0x45, 0xb6, 0x73, 0x26, 0x3e, 0x93, 0x65, 0x98, 0xfa, 0x6c, 0x10, 0xa6, 0x8a, 0xb6, 0x78, 0xa1, + 0xbf, 0x68, 0xc0, 0x82, 0x52, 0x47, 0x06, 0xa2, 0x92, 0xd9, 0xb8, 0x50, 0xe6, 0x2b, 0xd0, 0xf2, + 0xed, 0x24, 0xb5, 0x06, 0x91, 0xcb, 0x0d, 0x24, 0x33, 0x57, 0x93, 0xc3, 0x3e, 0x12, 0x20, 0xee, + 0x2b, 0x95, 0x42, 0xd0, 0x0b, 0x92, 0x7b, 0xcb, 0x29, 0x2a, 0x43, 0x60, 0x92, 0xef, 0xc1, 0x48, + 0x35, 0x4c, 0x7c, 0xe6, 0xb0, 0x23, 0xef, 0xf0, 0x08, 0x23, 0xd3, 0x30, 0xf1, 0x99, 0x74, 0x60, + 0xc2, 0x0f, 0xcf, 0x30, 0x0e, 0x0d, 0x93, 0x3f, 0x72, 0xc8, 0x81, 0xe7, 0x62, 0xd8, 0x19, 0x26, + 0x7f, 0xe4, 0x10, 0x3b, 0x39, 0xc6, 0x20, 0x33, 0x4c, 0xfe, 0xc8, 0xd3, 0xef, 0x69, 0xe8, 0x0f, + 0x4e, 0x18, 0xc6, 0x93, 0x61, 0xca, 0x37, 0xb2, 0x01, 0x73, 0x51, 0xec, 0x39, 0xcc, 0xb2, 0xd3, + 0x23, 0x0c, 0x21, 0xc3, 0x9c, 0x45, 0xc0, 0x6e, 0x7a, 0x44, 0x97, 0x60, 0x31, 0x73, 0xb4, 0x4a, + 0x3c, 0xf4, 0x63, 0x98, 0x91, 0x90, 0x91, 0x4e, 0x7f, 0x15, 0x66, 0x52, 0x81, 0xd6, 0x6d, 0x6c, + 0x4f, 0x5c, 0x6f, 0xde, 0x5e, 0x55, 0x36, 0xd4, 0x2d, 0x6d, 0x2a, 0x34, 0xfa, 0x5d, 0x20, 0x45, + 0x6e, 0xd2, 0x11, 0x37, 0x72, 0x3a, 0x06, 0xd2, 0x69, 0xeb, 0x74, 0x92, 0x9c, 0xc0, 0xe7, 0x98, + 0x29, 0x1f, 0xc5, 0x2e, 0x4f, 0x02, 0xe1, 0xf1, 0xd7, 0x1a, 0x9a, 0x1f, 0xc0, 0x7c, 0xc6, 0xf8, + 0x61, 0xca, 0x4e, 0xb8, 0xc1, 0xed, 0x93, 0x70, 0x10, 0xa4, 0xc8, 0xd3, 0x30, 0xe5, 0x1b, 0x8f, + 0x40, 0xb4, 0x2f, 0xb2, 0x34, 0x4c, 0xf1, 0x42, 0x16, 0xa0, 0xe1, 0xb9, 0xf2, 0x16, 0x6b, 0x78, + 0x2e, 0xfd, 0xb7, 0x01, 0x8b, 0x05, 0x45, 0x9e, 0x3b, 0x28, 0x2b, 0x11, 0xd7, 0xa8, 0x89, 0xb8, + 0x1b, 0x30, 0x79, 0xe0, 0xb9, 0xfc, 0xf2, 0xe4, 0x76, 0x5d, 0x51, 0xe4, 0x34, 0x3d, 0x4c, 0x44, + 0xe1, 0xa8, 0x76, 0x72, 0x9c, 0x74, 0x27, 0x47, 0xa2, 0x72, 0x94, 0xca, 0x79, 0x98, 0xaa, 0x9e, + 0x07, 0xdd, 0x96, 0xd3, 0x65, 0x5b, 0xae, 0xe2, 0x05, 0x96, 0xd1, 0xce, 0x22, 0xcf, 0x01, 0xc8, + 0x81, 0x23, 0xdd, 0xfa, 0x6d, 0x80, 0x30, 0xc3, 0x94, 0xf1, 0xb7, 0x5e, 0x11, 0x3a, 0x0b, 0xc1, + 0x02, 0x32, 0x7d, 0x1f, 0xef, 0xc1, 0x22, 0x73, 0x69, 0xfc, 0xdb, 0x1a, 0x4d, 0x11, 0x8b, 0xa4, + 0x42, 0x33, 0xd1, 0x88, 0xbd, 0x8e, 0xc4, 0x76, 0x1d, 0x87, 0xbb, 0xbe, 0x50, 0x21, 0x8d, 0xbc, + 0x1f, 0x9f, 0xc2, 0x8c, 0xdc, 0x21, 0xc3, 0x42, 0x20, 0x34, 0x3c, 0x97, 0xbc, 0x05, 0x50, 0xb8, + 0x43, 0x84, 0x5e, 0x1b, 0x4a, 0x06, 0xb9, 0x49, 0x45, 0x03, 0xb2, 0x2b, 0xa0, 0xd3, 0x3e, 0x2c, + 0xd5, 0xa0, 0x70, 0x51, 0xb2, 0xfa, 0x46, 0x8a, 0xa2, 0xde, 0xc9, 0x65, 0x68, 0xa6, 0x61, 0x6a, + 0xfb, 0xd6, 0xa9, 0xed, 0x0f, 0x54, 0xc8, 0x02, 0x82, 0x9e, 0x72, 0x08, 0x26, 0xa8, 0xd0, 0x17, + 0x91, 0xcb, 0x13, 0x54, 0xe8, 0xbb, 0xd4, 0x86, 0xd5, 0xb2, 0xd2, 0xd2, 0x84, 0xa3, 0x5c, 0xf6, + 0x12, 0xcc, 0xda, 0x62, 0x8b, 0x52, 0xac, 0x5d, 0x52, 0xcc, 0xcc, 0x10, 0x28, 0xc1, 0x1b, 0x68, + 0x2f, 0x0c, 0xfa, 0xde, 0xa1, 0x8a, 0x8e, 0x17, 0x31, 0x59, 0x29, 0x58, 0x5e, 0x4f, 0xb8, 0x76, + 0x6a, 0x23, 0xb7, 0x96, 0x89, 0xcf, 0xf4, 0x27, 0x06, 0x74, 0xf6, 0xc3, 0x38, 0xed, 0x87, 0xbe, + 0x17, 0xee, 0xba, 0x6e, 0xcc, 0x92, 0x84, 0x97, 0x12, 0xb6, 0x78, 0x54, 0x45, 0x8e, 0x7c, 0xe5, + 0x19, 0xd2, 0x09, 0xbd, 0x40, 0xc4, 0x6a, 0x43, 0x1a, 0x28, 0xf4, 0x02, 0x1e, 0xaa, 0x64, 0x1b, + 0x9a, 0x2e, 0x4b, 0x9c, 0xd8, 0x8b, 0x78, 0x45, 0x2c, 0xd3, 0x42, 0x11, 0xc4, 0x09, 0x1f, 0xd8, + 0xbe, 0x1d, 0x38, 0x4c, 0x66, 0x76, 0xf5, 0x4a, 0x57, 0x30, 0x5d, 0x65, 0x92, 0x28, 0x3d, 0x3e, + 0xc4, 0xe8, 0x2f, 0x80, 0xa5, 0x2a, 0xdf, 0x84, 0xb9, 0x48, 0x01, 0x65, 0xf8, 0x75, 0x95, 0x85, + 0xca, 0xea, 0x98, 0x39, 0x2a, 0xdd, 0xe4, 0x85, 0x69, 0x4e, 0xef, 0xf1, 0xe0, 0xe4, 0xc4, 0x8e, + 0xcf, 0x15, 0xb7, 0x00, 0x26, 0xf7, 0x42, 0x2f, 0xe0, 0x86, 0xe2, 0x4a, 0xa9, 0xc2, 0x8b, 0x3f, + 0x17, 0x45, 0x6f, 0x68, 0xa2, 0x17, 0xad, 0x35, 0xa1, 0x5b, 0xeb, 0x12, 0x40, 0xc4, 0x62, 0x87, + 0x05, 0xa9, 0x7d, 0xa8, 0x34, 0x2e, 0x40, 0xe8, 0x11, 0x90, 0x47, 0xfd, 0xbe, 0xef, 0x05, 0x8c, + 0xb3, 0x95, 0xc2, 0x8c, 0xb0, 0xfe, 0x70, 0x19, 0x74, 0x4e, 0x13, 0x15, 0x4e, 0x1f, 0xc0, 0xe2, + 0xa3, 0xa0, 0x86, 0x91, 0x22, 0x67, 0x8c, 0x22, 0xd7, 0xa8, 0x90, 0x7b, 0x0f, 0x5a, 0x05, 0xc1, + 0x13, 0xf2, 0x06, 0xcc, 0x49, 0x19, 0x99, 0xca, 0x06, 0xbd, 0x2c, 0x1b, 0x54, 0x34, 0x34, 0x73, + 0x64, 0xfa, 0x4b, 0x03, 0x9a, 0xb9, 0x64, 0xbc, 0x47, 0x99, 0xe2, 0xe6, 0x56, 0x54, 0x2e, 0x65, + 0x54, 0x72, 0x9c, 0x5b, 0xf8, 0xf7, 0x5e, 0x90, 0xc6, 0xe7, 0xa6, 0x40, 0xee, 0x3d, 0x06, 0xc8, + 0x81, 0xfc, 0xc2, 0x3f, 0x66, 0xea, 0xfc, 0xf2, 0x47, 0xb2, 0x03, 0x53, 0xf9, 0xa1, 0x2d, 0x66, + 0xbf, 0xb2, 0x4d, 0x4c, 0x81, 0xf7, 0x66, 0xe3, 0x0d, 0x83, 0xfe, 0x71, 0x92, 0xf7, 0x22, 0x35, + 0xc1, 0x22, 0x63, 0xf0, 0x15, 0x68, 0x8a, 0xb3, 0xc0, 0x33, 0x80, 0x12, 0xb8, 0x95, 0xdd, 0x43, + 0xa1, 0x17, 0x98, 0x80, 0x67, 0x03, 0xd7, 0xc9, 0x6b, 0x30, 0x8f, 0xc2, 0x5a, 0xa1, 0x30, 0x88, + 0x3c, 0xd8, 0xfa, 0x86, 0x16, 0xa2, 0x48, 0x93, 0x91, 0x08, 0x56, 0xb4, 0x2d, 0x56, 0x22, 0x44, + 0x90, 0x97, 0xd4, 0xdb, 0x6a, 0xeb, 0x08, 0x29, 0x85, 0xb1, 0x24, 0x41, 0xb9, 0x26, 0x4c, 0xb7, + 0xe4, 0x54, 0x57, 0xc8, 0x0e, 0xb4, 0x24, 0x47, 0xb4, 0x8c, 0xbc, 0xe2, 0x74, 0x19, 0x9b, 0x62, + 0x23, 0x22, 0x90, 0x13, 0x58, 0x2e, 0x6e, 0xc8, 0x24, 0x9c, 0xc2, 0x8d, 0x6f, 0x8d, 0x2f, 0x61, + 0x50, 0x11, 0x90, 0x38, 0x95, 0x85, 0xde, 0x0f, 0xa0, 0x3b, 0x4c, 0xa1, 0x1a, 0xb7, 0xdf, 0xd4, + 0xdd, 0xbe, 0x5c, 0x13, 0x92, 0x49, 0xc1, 0xe3, 0xbd, 0x4f, 0x60, 0x6d, 0x88, 0x30, 0x35, 0xc4, + 0x6f, 0xe8, 0xc4, 0x97, 0x6a, 0x22, 0xb5, 0x18, 0x4d, 0x3f, 0x33, 0xa0, 0xb7, 0xeb, 0xba, 0x95, + 0xe4, 0x94, 0x77, 0xb0, 0x5f, 0x77, 0xca, 0xdd, 0x82, 0x8d, 0x5a, 0x81, 0x64, 0xab, 0xfd, 0x0c, + 0xb6, 0x4c, 0x76, 0x12, 0x9e, 0xb2, 0xaf, 0x5b, 0x64, 0xba, 0x0d, 0x97, 0x86, 0x71, 0x96, 0xb2, + 0xf5, 0xa0, 0xfb, 0x80, 0xe9, 0x73, 0x8a, 0xac, 0x30, 0xfa, 0x9b, 0x01, 0xf3, 0xfa, 0x04, 0xe3, + 0x7f, 0xd5, 0x47, 0xbf, 0x0c, 0x24, 0x66, 0x49, 0x6a, 0xc5, 0xa1, 0xef, 0xf3, 0x76, 0xda, 0x65, + 0xbe, 0x7d, 0x2e, 0x67, 0x27, 0x1d, 0xbe, 0x62, 0x8a, 0x85, 0xbb, 0x1c, 0x4e, 0xd6, 0x60, 0xc6, + 0x8e, 0x3c, 0x8b, 0x47, 0x8d, 0xe8, 0xa5, 0xa7, 0xed, 0xc8, 0x7b, 0x9f, 0x9d, 0x13, 0x0a, 0xf3, + 0x72, 0xc1, 0xf2, 0xd9, 0x29, 0xf3, 0xb1, 0xe6, 0x9b, 0x30, 0x9b, 0x62, 0xf9, 0xfb, 0x1c, 0xc4, + 0x7b, 0xdf, 0x28, 0xf6, 0x78, 0xf8, 0xe5, 0x43, 0x9a, 0x19, 0x94, 0xa6, 0x2d, 0xe1, 0x4a, 0x3b, + 0xfa, 0x29, 0xac, 0xd7, 0xd8, 0x42, 0xe6, 0xa8, 0x77, 0xa0, 0xad, 0x8f, 0x7a, 0x54, 0x9e, 0xca, + 0xaa, 0x56, 0x6d, 0xa3, 0xb9, 0xd0, 0xd7, 0xe8, 0xc8, 0xea, 0x13, 0x71, 0x4c, 0x3b, 0xcd, 0x06, + 0x2e, 0xf4, 0x33, 0x58, 0xce, 0x81, 0x7b, 0x61, 0x70, 0xca, 0xe2, 0x84, 0x47, 0x1b, 0x81, 0xc9, + 0x7e, 0x1c, 0x9e, 0x28, 0x53, 0xf3, 0x67, 0x5e, 0xb7, 0xa5, 0xa1, 0x0c, 0x83, 0x46, 0x1a, 0x72, + 0x9c, 0xd8, 0x4e, 0xd5, 0x2d, 0x85, 0xcf, 0xbc, 0x4e, 0xf6, 0x90, 0x08, 0xb3, 0x70, 0x4d, 0x84, + 0x6a, 0x53, 0xc2, 0x38, 0x17, 0xfa, 0x14, 0xcb, 0xc7, 0xa2, 0x28, 0x52, 0xc7, 0xef, 0x40, 0x53, + 0xe8, 0xc8, 0x77, 0x2a, 0xfd, 0x36, 0x35, 0xfd, 0x4a, 0x62, 0x9a, 0xd0, 0xcf, 0xa0, 0xf4, 0x1f, + 0x0d, 0x68, 0x61, 0xc5, 0x7a, 0x97, 0xa5, 0xb6, 0xe7, 0x8f, 0xae, 0xa5, 0x45, 0x0d, 0xda, 0xc8, + 0x6a, 0xd0, 0x17, 0x60, 0xbe, 0x38, 0xcc, 0x38, 0x57, 0xcd, 0x6c, 0x61, 0x94, 0x71, 0x4e, 0xae, + 0xc2, 0x02, 0xb6, 0xd6, 0x39, 0x96, 0x88, 0x99, 0x79, 0x84, 0x66, 0x68, 0x7a, 0x23, 0x30, 0x55, + 0x6a, 0x04, 0xf8, 0x32, 0x16, 0xd3, 0x56, 0xe2, 0xb9, 0x59, 0x9f, 0x80, 0x90, 0xc7, 0x9e, 0x5b, + 0x58, 0xc6, 0xdd, 0x33, 0x85, 0x65, 0xdc, 0xcd, 0x7b, 0xa0, 0x98, 0xe1, 0xa8, 0x12, 0x47, 0x3c, + 0xd8, 0x0e, 0x4f, 0x98, 0x2d, 0x05, 0x7c, 0xe2, 0x9d, 0xe0, 0x58, 0x32, 0x49, 0xed, 0x74, 0xa0, + 0xe6, 0x2c, 0xf2, 0x2d, 0x6f, 0xd3, 0xa0, 0xd8, 0xa6, 0xe5, 0x4d, 0x5d, 0x53, 0x6b, 0xea, 0x2e, + 0x43, 0x33, 0x8c, 0x58, 0x60, 0xc9, 0x16, 0xbb, 0x25, 0xaa, 0x07, 0x0e, 0x7a, 0x8a, 0x10, 0x39, + 0x32, 0x41, 0x9b, 0x27, 0xe3, 0xf4, 0xa5, 0xba, 0x61, 0x1a, 0x65, 0xc3, 0xa8, 0x46, 0x70, 0xe2, + 0xa2, 0x46, 0x90, 0xee, 0x62, 0x55, 0xac, 0x18, 0xcb, 0xf0, 0x79, 0x19, 0xa6, 0xd1, 0x4c, 0x2a, + 0x72, 0x96, 0xb5, 0x36, 0x46, 0x06, 0x85, 0x29, 0x71, 0xe8, 0x7b, 0x38, 0xcc, 0xc5, 0xa5, 0x71, + 0x44, 0x5f, 0x87, 0x59, 0xe1, 0x95, 0x2c, 0x6a, 0x66, 0xf0, 0xfd, 0xa1, 0x4b, 0xff, 0x6c, 0x00, + 0x79, 0x3c, 0x38, 0x38, 0xf1, 0xc6, 0xa7, 0x36, 0x7e, 0x83, 0x4e, 0x60, 0x12, 0xc3, 0x44, 0x84, + 0x23, 0x3e, 0x97, 0x22, 0x64, 0xb2, 0x1c, 0x21, 0xb9, 0x3b, 0xa7, 0xea, 0x7b, 0xf4, 0xe9, 0xa2, + 0xf3, 0x79, 0x8a, 0xf7, 0x3d, 0x16, 0xa4, 0x96, 0x1c, 0xb6, 0xf0, 0x14, 0x8f, 0x80, 0x87, 0x2e, + 0x7d, 0x0c, 0x4b, 0x9a, 0x66, 0xd2, 0xd2, 0x57, 0xa0, 0x25, 0x04, 0x88, 0x7c, 0xdb, 0xc9, 0x46, + 0xb5, 0x4d, 0x84, 0xed, 0x23, 0x68, 0x94, 0xbd, 0xfe, 0x6e, 0x00, 0xd9, 0xe3, 0x17, 0x97, 0x3f, + 0xb6, 0xbd, 0x78, 0xe0, 0x88, 0x2e, 0x29, 0xa7, 0x37, 0x27, 0x21, 0x0f, 0x75, 0x66, 0x13, 0x1a, + 0xb3, 0xcc, 0xd2, 0x93, 0xcf, 0x39, 0x0a, 0xa9, 0x9c, 0xda, 0xab, 0xb0, 0x70, 0x66, 0xfb, 0x3e, + 0x4b, 0x2d, 0x75, 0x57, 0xca, 0x99, 0xa9, 0x80, 0xaa, 0x8e, 0x4b, 0xf9, 0x6b, 0x26, 0xf7, 0x17, + 0x6f, 0x89, 0x34, 0x7d, 0xe5, 0xdd, 0x77, 0x07, 0x56, 0x05, 0x78, 0xd7, 0xf7, 0xc7, 0x3e, 0x43, + 0xf4, 0x57, 0x0d, 0x58, 0xab, 0x6c, 0xcb, 0x2e, 0x09, 0xfd, 0x04, 0x5c, 0xcb, 0xd4, 0xad, 0xdf, + 0x70, 0x4b, 0xbe, 0xca, 0x5d, 0xbd, 0xdf, 0x1b, 0x30, 0x2d, 0x40, 0x23, 0xbd, 0xf1, 0x89, 0x72, + 0xbf, 0xcc, 0x31, 0xa2, 0xfe, 0xfd, 0xd6, 0x78, 0xcc, 0xc4, 0xbf, 0xc7, 0xb8, 0x53, 0x94, 0x87, + 0x22, 0x6e, 0x04, 0xa4, 0xf7, 0x0e, 0x74, 0xca, 0x08, 0x35, 0x25, 0xdb, 0x72, 0xb1, 0x64, 0x9b, + 0x2b, 0x56, 0x67, 0xa2, 0x87, 0xbe, 0x77, 0xca, 0x82, 0x34, 0xbb, 0xe3, 0xbe, 0x34, 0xa0, 0xbd, + 0x17, 0x06, 0xae, 0xc7, 0xf3, 0xe3, 0xbe, 0x1d, 0xdb, 0x27, 0x09, 0xd9, 0xe4, 0x95, 0x8d, 0x04, + 0xa9, 0x21, 0x6b, 0x06, 0x18, 0x32, 0xce, 0xda, 0x02, 0x70, 0x8e, 0x98, 0x73, 0x6c, 0xc9, 0xf9, + 0x12, 0x0f, 0xfa, 0x39, 0x84, 0xbc, 0xeb, 0xb9, 0x09, 0x79, 0x05, 0x96, 0xf2, 0x65, 0xcb, 0x0e, + 0x5c, 0x4b, 0x0e, 0x97, 0x70, 0xde, 0x9c, 0xe1, 0xed, 0x06, 0xee, 0x6e, 0x72, 0x8c, 0x53, 0xf1, + 0x6c, 0xa6, 0x62, 0x69, 0x07, 0xb6, 0x9d, 0xc1, 0x77, 0x11, 0x4c, 0xff, 0x65, 0x60, 0xbe, 0x53, + 0x5a, 0x49, 0x6f, 0xe7, 0x63, 0x14, 0x9c, 0xae, 0x69, 0x2e, 0x6b, 0x94, 0x5c, 0x46, 0x60, 0xd2, + 0x4b, 0xd9, 0x89, 0x4a, 0x23, 0xfc, 0x99, 0xbc, 0x0b, 0x9d, 0x4c, 0x63, 0x2b, 0x42, 0xb3, 0xc8, + 0x63, 0xb2, 0x96, 0xb7, 0x09, 0x9a, 0xd5, 0xcc, 0xb6, 0x53, 0x32, 0xa3, 0x3a, 0x5e, 0x53, 0x17, + 0x1e, 0x2f, 0x9e, 0x95, 0x1c, 0xb4, 0xf6, 0xb4, 0x2c, 0xa2, 0xf0, 0x4d, 0x48, 0xcd, 0x9c, 0x41, + 0xca, 0x5c, 0x59, 0x18, 0x65, 0xef, 0xf4, 0xaf, 0x06, 0xb4, 0x77, 0x5d, 0x17, 0xf5, 0x1e, 0x27, + 0x4d, 0x28, 0x2d, 0x1b, 0x17, 0x68, 0x39, 0xf1, 0x5f, 0x6a, 0xf9, 0x95, 0x93, 0xc8, 0x10, 0x23, + 0x50, 0x0a, 0x9d, 0x5c, 0xcf, 0x7a, 0xf7, 0xd2, 0x6f, 0x00, 0x11, 0xc5, 0xb4, 0x66, 0x8e, 0x32, + 0xd6, 0x0a, 0x2c, 0x69, 0x58, 0x32, 0xd7, 0xdc, 0x87, 0xeb, 0x0f, 0x58, 0xba, 0x17, 0x9f, 0x47, + 0x69, 0xa8, 0x8a, 0x97, 0xbb, 0x2c, 0x0a, 0x13, 0x4f, 0x65, 0x2e, 0x36, 0x56, 0xf6, 0xf9, 0x83, + 0x01, 0x37, 0xc6, 0x20, 0x24, 0x55, 0xf8, 0x61, 0x75, 0x9a, 0xf0, 0xbd, 0x42, 0x23, 0x39, 0x1e, + 0x95, 0x5b, 0x19, 0x44, 0xa4, 0x8b, 0x9c, 0x64, 0xef, 0x6d, 0x58, 0xd0, 0x17, 0x9f, 0x2b, 0x55, + 0xf8, 0x70, 0xed, 0x02, 0x21, 0xc6, 0x89, 0xb9, 0x6b, 0xb0, 0xe0, 0x68, 0x24, 0x24, 0xa3, 0x12, + 0x94, 0xee, 0xc1, 0x8b, 0x17, 0x72, 0x93, 0x66, 0x1b, 0xda, 0x8f, 0xd1, 0xdf, 0x4e, 0xc2, 0xda, + 0xc7, 0x5e, 0x7a, 0xe4, 0xc6, 0xf6, 0x99, 0x8a, 0xbe, 0x71, 0x84, 0x2c, 0xb5, 0x6a, 0x8d, 0x6a, + 0x77, 0x79, 0x13, 0x16, 0xc3, 0x80, 0x61, 0x45, 0x69, 0x45, 0x76, 0x92, 0x9c, 0x85, 0xb1, 0xba, + 0x4b, 0xdb, 0x61, 0xc0, 0x78, 0x55, 0xb9, 0x2f, 0xc1, 0xa5, 0xdb, 0x78, 0xb2, 0x7c, 0x1b, 0x77, + 0x60, 0x22, 0xf2, 0x02, 0x39, 0x21, 0xe7, 0x8f, 0xfc, 0xee, 0x4c, 0x63, 0xdb, 0x2d, 0x50, 0x96, + 0x77, 0x27, 0x42, 0x33, 0xba, 0xc5, 0x99, 0xed, 0x4c, 0x69, 0x66, 0x5b, 0xb0, 0xc9, 0xac, 0xde, + 0xa3, 0x5e, 0x86, 0xa6, 0x7c, 0xb4, 0x52, 0xfb, 0x50, 0x16, 0xbc, 0x20, 0x41, 0x4f, 0xec, 0xc3, + 0x42, 0x3d, 0x04, 0x5a, 0x3d, 0xb4, 0x05, 0xd0, 0x67, 0xcc, 0xd2, 0x4a, 0xdf, 0xb9, 0x3e, 0x63, + 0x22, 0xe9, 0xf2, 0xc2, 0xe8, 0xc0, 0x0e, 0x8e, 0x2d, 0xec, 0x38, 0x5b, 0x42, 0x1c, 0x0e, 0xf8, + 0x90, 0x77, 0x9d, 0x57, 0xa0, 0x85, 0x8b, 0x4a, 0xa6, 0x79, 0x61, 0x51, 0x0e, 0xdb, 0xcd, 0x7b, + 0x67, 0x44, 0x71, 0xbc, 0xf4, 0xbc, 0xbb, 0x90, 0xef, 0xdf, 0xf3, 0xd2, 0xf3, 0x6c, 0x3f, 0xda, + 0x2c, 0x3e, 0xef, 0xb6, 0xf3, 0xfd, 0x7b, 0x02, 0xc4, 0xc5, 0x4b, 0xce, 0xbc, 0x3e, 0x13, 0xdf, + 0xa8, 0x3b, 0xc2, 0xca, 0x08, 0xd9, 0x0b, 0x5d, 0xec, 0x03, 0xce, 0xbc, 0xb8, 0xd0, 0x8a, 0x2c, + 0x8a, 0x86, 0x85, 0x03, 0xb3, 0xcf, 0xf7, 0x37, 0xa1, 0xa3, 0xc2, 0xa5, 0xf8, 0x93, 0x85, 0x98, + 0x25, 0x03, 0x3f, 0x55, 0x3f, 0x59, 0x10, 0x6f, 0xb7, 0x7f, 0xdd, 0x83, 0x85, 0x07, 0xa1, 0x08, + 0xd0, 0x27, 0xdc, 0x2f, 0x31, 0x79, 0x04, 0x33, 0xf2, 0x07, 0x0f, 0x64, 0xb5, 0x70, 0x6e, 0x0b, + 0x23, 0xff, 0xde, 0x5a, 0x05, 0x2e, 0x33, 0xce, 0xd2, 0x17, 0x7f, 0xfa, 0xcb, 0xcf, 0x1b, 0xf3, + 0xa4, 0xb9, 0x73, 0xfa, 0xda, 0xce, 0x21, 0x4b, 0x3d, 0x4e, 0xc5, 0x81, 0x56, 0xf1, 0x23, 0x3e, + 0xd9, 0x28, 0xec, 0x2e, 0xff, 0x16, 0xa0, 0xb7, 0x59, 0xbf, 0x28, 0xe9, 0x77, 0x91, 0x3e, 0x21, + 0x1d, 0x49, 0x3f, 0xfb, 0xe6, 0x4f, 0x3e, 0x87, 0x76, 0xe9, 0xfb, 0x3d, 0xa1, 0x39, 0xa9, 0x61, + 0x3f, 0x66, 0xe8, 0xbd, 0x30, 0x12, 0x47, 0x72, 0xbd, 0x84, 0x5c, 0xbb, 0x74, 0x89, 0x73, 0x75, + 0x05, 0x17, 0xc5, 0xf9, 0x4d, 0xe3, 0x26, 0x49, 0xb0, 0xab, 0x28, 0xfe, 0x08, 0x60, 0x2c, 0xde, + 0x97, 0x6b, 0x54, 0xd5, 0xac, 0xb9, 0x81, 0x7c, 0x57, 0xc8, 0x52, 0x49, 0x5b, 0xb4, 0x6a, 0x82, + 0x9f, 0x18, 0x0b, 0x3f, 0x72, 0xc0, 0x00, 0x19, 0x87, 0xef, 0x56, 0x0d, 0xdf, 0xfc, 0x47, 0x12, + 0xb4, 0x87, 0x5c, 0x97, 0x09, 0x29, 0x71, 0x0d, 0xd3, 0x88, 0x3c, 0x83, 0x85, 0x7b, 0xc1, 0xff, + 0xc7, 0xc8, 0x5b, 0xc8, 0x76, 0x8d, 0x22, 0x5b, 0x31, 0xcd, 0x29, 0xda, 0xf8, 0x63, 0x98, 0xcb, + 0xbe, 0xa8, 0x92, 0x6e, 0x41, 0x03, 0xed, 0xdb, 0x7d, 0x6f, 0xc8, 0x97, 0x59, 0x15, 0x38, 0x74, + 0x5e, 0x2a, 0x25, 0xbe, 0xb3, 0x72, 0xc2, 0x9f, 0x02, 0xe4, 0x9f, 0x6a, 0xc9, 0x7a, 0x85, 0x72, + 0x16, 0x99, 0xbd, 0xba, 0x25, 0x49, 0x7e, 0x15, 0xc9, 0x77, 0xc8, 0x82, 0x46, 0x3e, 0x91, 0xa1, + 0x9f, 0x7d, 0x51, 0xd3, 0x42, 0xbf, 0xfc, 0x71, 0xb7, 0x37, 0xfc, 0xab, 0x9e, 0x8a, 0x04, 0xaa, + 0xe2, 0x3e, 0xab, 0x15, 0xb9, 0x06, 0x87, 0x30, 0xaf, 0x7d, 0xe6, 0x23, 0x9b, 0x75, 0x5c, 0x92, + 0x3a, 0xf7, 0x57, 0xbf, 0x0d, 0xd2, 0x75, 0x64, 0xb5, 0x44, 0x16, 0xcb, 0xac, 0x12, 0x72, 0x8c, + 0xbf, 0x8e, 0x2a, 0x7c, 0x0d, 0x23, 0x45, 0x5a, 0xd5, 0x4f, 0x83, 0xbd, 0x4b, 0xc3, 0x96, 0x93, + 0xfa, 0x50, 0x93, 0xd7, 0x09, 0xc6, 0xb7, 0x70, 0xb8, 0xf8, 0x06, 0xa6, 0x39, 0x5c, 0xfb, 0x54, + 0xd6, 0x5b, 0xaf, 0x59, 0x91, 0xd4, 0x57, 0x90, 0x7a, 0x9b, 0x28, 0x9f, 0x3b, 0x82, 0x96, 0xf0, + 0x49, 0x36, 0x9c, 0xd4, 0x7c, 0x52, 0xfe, 0x82, 0xa5, 0xa5, 0xa3, 0xca, 0x77, 0xac, 0x4a, 0x3a, + 0xca, 0xbe, 0x54, 0x91, 0x1f, 0xeb, 0x1f, 0xc4, 0xd4, 0x80, 0x9e, 0x8e, 0x9c, 0xa8, 0x57, 0x4e, + 0xcb, 0xd0, 0xa9, 0x3b, 0xbd, 0x8c, 0x9c, 0xd7, 0xc9, 0x5a, 0x99, 0xb3, 0x9c, 0xe0, 0x93, 0x2f, + 0x0c, 0x58, 0xaa, 0x99, 0x0f, 0xe7, 0x12, 0x0c, 0x9f, 0x66, 0xe7, 0x12, 0x8c, 0x1a, 0x30, 0x53, + 0x94, 0x60, 0x93, 0xa2, 0x04, 0xb6, 0xeb, 0x66, 0x12, 0xc8, 0xdb, 0x91, 0x47, 0xe6, 0x4f, 0x0d, + 0x58, 0xad, 0x9f, 0x05, 0x93, 0xab, 0x8a, 0xc7, 0xc8, 0x29, 0x75, 0xef, 0xda, 0x45, 0x68, 0x52, + 0x9a, 0xab, 0x28, 0xcd, 0x65, 0xda, 0xe3, 0xd2, 0xc4, 0x88, 0x5b, 0x27, 0xd0, 0x19, 0xb6, 0x54, + 0xfa, 0xb4, 0x95, 0x6c, 0x17, 0x0c, 0x5e, 0x3b, 0x94, 0xee, 0x5d, 0x19, 0x81, 0xa1, 0xa7, 0x2f, + 0xb2, 0x22, 0x1d, 0x82, 0x23, 0xca, 0x6c, 0x6c, 0x2b, 0xcf, 0x68, 0x3e, 0xcd, 0xd4, 0xce, 0x68, + 0x65, 0x40, 0xab, 0x9d, 0xd1, 0xea, 0xcc, 0xb4, 0x72, 0x46, 0x91, 0x19, 0xce, 0x4f, 0xc9, 0x27, + 0x78, 0x6c, 0x64, 0x3f, 0xdf, 0x2d, 0x1f, 0xf5, 0xa4, 0xee, 0xd8, 0xe8, 0x1d, 0x7b, 0x25, 0x55, + 0x8a, 0x31, 0x01, 0xb7, 0x9e, 0x09, 0xb3, 0x0a, 0x9d, 0xac, 0x95, 0x09, 0x28, 0xca, 0xb5, 0x03, + 0x38, 0xba, 0x86, 0x44, 0x17, 0x69, 0xab, 0x48, 0x94, 0xd3, 0x3c, 0x80, 0x66, 0x61, 0xd8, 0x44, + 0xb2, 0x24, 0x5b, 0x9d, 0xad, 0xf5, 0x36, 0x6a, 0xd7, 0xf4, 0x54, 0x42, 0xdb, 0x9c, 0x41, 0x82, + 0x08, 0x45, 0x1e, 0x85, 0x51, 0x4c, 0xce, 0xa3, 0x3a, 0x8f, 0xca, 0x79, 0xd4, 0xcd, 0x6e, 0x34, + 0x1e, 0x0e, 0x22, 0x64, 0x3c, 0x62, 0x68, 0x97, 0x46, 0x20, 0xe4, 0xd2, 0xd0, 0xd9, 0x48, 0xe9, + 0xfe, 0x1f, 0x32, 0x3b, 0xd1, 0xeb, 0x0e, 0xc1, 0xcf, 0xf6, 0xfd, 0xdc, 0x1f, 0x22, 0x45, 0x8a, + 0x01, 0x81, 0xe6, 0x6b, 0x6d, 0x12, 0xa2, 0xf9, 0x5a, 0x9f, 0x26, 0x54, 0x52, 0x24, 0x13, 0xb4, + 0x9e, 0xc2, 0xac, 0xea, 0x4c, 0x73, 0x47, 0x97, 0x7a, 0xf2, 0x5e, 0xb7, 0xba, 0x20, 0xa9, 0x6a, + 0xce, 0xb6, 0x5d, 0x17, 0xa9, 0x4a, 0x47, 0x14, 0xfa, 0xd4, 0xdc, 0x11, 0xd5, 0x16, 0x37, 0x77, + 0x44, 0x5d, 0x63, 0xab, 0x39, 0x42, 0x9c, 0xf6, 0x8c, 0xc7, 0xef, 0x0c, 0xb8, 0x72, 0x61, 0x9b, + 0x49, 0x5e, 0x7d, 0x8e, 0x8e, 0x54, 0x08, 0xf4, 0xda, 0x73, 0xf7, 0xb0, 0xf4, 0x3a, 0x8a, 0x49, + 0xe9, 0x96, 0xba, 0x80, 0x70, 0x9b, 0x2b, 0xd0, 0xb3, 0x86, 0x96, 0x0b, 0xfd, 0x1b, 0x03, 0x2e, + 0x5f, 0x40, 0x97, 0xdc, 0x1a, 0x53, 0x00, 0x25, 0xf0, 0xce, 0xd8, 0xf8, 0x52, 0xdc, 0x6b, 0x28, + 0xee, 0x36, 0xdd, 0x18, 0x21, 0x2e, 0x17, 0xf6, 0x47, 0xb0, 0x91, 0xb5, 0xa3, 0x1a, 0xdd, 0xfb, + 0x83, 0xc0, 0x4d, 0x48, 0x16, 0xd6, 0x43, 0x7a, 0xd6, 0x3c, 0x70, 0xca, 0x5d, 0x8a, 0x7e, 0xa7, + 0x9c, 0xc9, 0x55, 0x21, 0x46, 0x9f, 0xd3, 0xe6, 0xdc, 0x23, 0x58, 0x54, 0xfb, 0xee, 0x7b, 0x76, + 0xfa, 0x95, 0x79, 0x6e, 0x23, 0xcf, 0x1e, 0x5d, 0x29, 0xf2, 0xec, 0x7b, 0x76, 0xaa, 0x38, 0x1e, + 0x4c, 0xe3, 0x6f, 0xc3, 0x5f, 0xff, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6e, 0xa0, 0xe9, 0x5c, + 0x4e, 0x2e, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -4006,6 +4051,7 @@ type GoCryptoTraderClient interface { GetExchanges(ctx context.Context, in *GetExchangesRequest, opts ...grpc.CallOption) (*GetExchangesResponse, error) DisableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) GetExchangeInfo(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeInfoResponse, error) + GetExchangeOTPCode(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeOTPReponse, error) EnableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) GetTicker(ctx context.Context, in *GetTickerRequest, opts ...grpc.CallOption) (*TickerResponse, error) GetTickers(ctx context.Context, in *GetTickersRequest, opts ...grpc.CallOption) (*GetTickersResponse, error) @@ -4077,6 +4123,15 @@ func (c *goCryptoTraderClient) GetExchangeInfo(ctx context.Context, in *GenericE return out, nil } +func (c *goCryptoTraderClient) GetExchangeOTPCode(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeOTPReponse, error) { + out := new(GetExchangeOTPReponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetExchangeOTPCode", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *goCryptoTraderClient) EnableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) { out := new(GenericExchangeNameResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/EnableExchange", in, out, opts...) @@ -4308,6 +4363,7 @@ type GoCryptoTraderServer interface { GetExchanges(context.Context, *GetExchangesRequest) (*GetExchangesResponse, error) DisableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) GetExchangeInfo(context.Context, *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) + GetExchangeOTPCode(context.Context, *GenericExchangeNameRequest) (*GetExchangeOTPReponse, error) EnableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) GetTicker(context.Context, *GetTickerRequest) (*TickerResponse, error) GetTickers(context.Context, *GetTickersRequest) (*GetTickersResponse, error) @@ -4335,6 +4391,101 @@ type GoCryptoTraderServer interface { WithdrawFiatFunds(context.Context, *WithdrawCurrencyRequest) (*WithdrawResponse, error) } +// UnimplementedGoCryptoTraderServer can be embedded to have forward compatible implementations. +type UnimplementedGoCryptoTraderServer struct { +} + +func (*UnimplementedGoCryptoTraderServer) GetInfo(ctx context.Context, req *GetInfoRequest) (*GetInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchanges(ctx context.Context, req *GetExchangesRequest) (*GetExchangesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchanges not implemented") +} +func (*UnimplementedGoCryptoTraderServer) DisableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DisableExchange not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeInfo(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangeInfo not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCode(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeOTPReponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangeOTPCode not implemented") +} +func (*UnimplementedGoCryptoTraderServer) EnableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EnableExchange not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetTicker(ctx context.Context, req *GetTickerRequest) (*TickerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTicker not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetTickers(ctx context.Context, req *GetTickersRequest) (*GetTickersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTickers not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrderbook(ctx context.Context, req *GetOrderbookRequest) (*OrderbookResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrderbook not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrderbooks(ctx context.Context, req *GetOrderbooksRequest) (*GetOrderbooksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrderbooks not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetAccountInfo(ctx context.Context, req *GetAccountInfoRequest) (*GetAccountInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAccountInfo not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetConfig(ctx context.Context, req *GetConfigRequest) (*GetConfigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetPortfolio(ctx context.Context, req *GetPortfolioRequest) (*GetPortfolioResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPortfolio not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetPortfolioSummary(ctx context.Context, req *GetPortfolioSummaryRequest) (*GetPortfolioSummaryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPortfolioSummary not implemented") +} +func (*UnimplementedGoCryptoTraderServer) AddPortfolioAddress(ctx context.Context, req *AddPortfolioAddressRequest) (*AddPortfolioAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddPortfolioAddress not implemented") +} +func (*UnimplementedGoCryptoTraderServer) RemovePortfolioAddress(ctx context.Context, req *RemovePortfolioAddressRequest) (*RemovePortfolioAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemovePortfolioAddress not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetForexProviders(ctx context.Context, req *GetForexProvidersRequest) (*GetForexProvidersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetForexProviders not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetForexRates(ctx context.Context, req *GetForexRatesRequest) (*GetForexRatesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetForexRates not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrders(ctx context.Context, req *GetOrdersRequest) (*GetOrdersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrders not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrder(ctx context.Context, req *GetOrderRequest) (*OrderDetails, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) SubmitOrder(ctx context.Context, req *SubmitOrderRequest) (*SubmitOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) CancelOrder(ctx context.Context, req *CancelOrderRequest) (*CancelOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CancelOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) CancelAllOrders(ctx context.Context, req *CancelAllOrdersRequest) (*CancelAllOrdersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CancelAllOrders not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetEvents(ctx context.Context, req *GetEventsRequest) (*GetEventsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented") +} +func (*UnimplementedGoCryptoTraderServer) AddEvent(ctx context.Context, req *AddEventRequest) (*AddEventResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddEvent not implemented") +} +func (*UnimplementedGoCryptoTraderServer) RemoveEvent(ctx context.Context, req *RemoveEventRequest) (*RemoveEventResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveEvent not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddresses(ctx context.Context, req *GetCryptocurrencyDepositAddressesRequest) (*GetCryptocurrencyDepositAddressesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddresses not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddress(ctx context.Context, req *GetCryptocurrencyDepositAddressRequest) (*GetCryptocurrencyDepositAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddress not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WithdrawCryptocurrencyFunds(ctx context.Context, req *WithdrawCurrencyRequest) (*WithdrawResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WithdrawCryptocurrencyFunds not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WithdrawFiatFunds(ctx context.Context, req *WithdrawCurrencyRequest) (*WithdrawResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WithdrawFiatFunds not implemented") +} + func RegisterGoCryptoTraderServer(s *grpc.Server, srv GoCryptoTraderServer) { s.RegisterService(&_GoCryptoTrader_serviceDesc, srv) } @@ -4411,6 +4562,24 @@ func _GoCryptoTrader_GetExchangeInfo_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _GoCryptoTrader_GetExchangeOTPCode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GenericExchangeNameRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetExchangeOTPCode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetExchangeOTPCode", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetExchangeOTPCode(ctx, req.(*GenericExchangeNameRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _GoCryptoTrader_EnableExchange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GenericExchangeNameRequest) if err := dec(in); err != nil { @@ -4881,6 +5050,10 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ MethodName: "GetExchangeInfo", Handler: _GoCryptoTrader_GetExchangeInfo_Handler, }, + { + MethodName: "GetExchangeOTPCode", + Handler: _GoCryptoTrader_GetExchangeOTPCode_Handler, + }, { MethodName: "EnableExchange", Handler: _GoCryptoTrader_EnableExchange_Handler, diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 3da1471e..8619468a 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -9,13 +9,13 @@ It translates gRPC into RESTful JSON APIs. package gctrpc import ( + "context" "io" "net/http" "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/grpc-ecosystem/grpc-gateway/utilities" - "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/grpclog" @@ -45,7 +45,10 @@ func request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtim var protoReq GetExchangesRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchanges_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -79,7 +82,10 @@ func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler run var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -88,6 +94,26 @@ func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler run } +var ( + filter_GoCryptoTrader_GetExchangeOTPCode_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetExchangeOTPCode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + func request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata @@ -165,7 +191,10 @@ func request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runt var protoReq GetAccountInfoRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetAccountInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -567,6 +596,26 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOTPCode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetExchangeOTPCode_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeOTPCode_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_GoCryptoTrader_EnableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1079,6 +1128,8 @@ var ( pattern_GoCryptoTrader_GetExchangeInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeinfo"}, "")) + pattern_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotp"}, "")) + pattern_GoCryptoTrader_EnableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchange"}, "")) pattern_GoCryptoTrader_GetTicker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getticker"}, "")) @@ -1139,6 +1190,8 @@ var ( forward_GoCryptoTrader_GetExchangeInfo_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_EnableExchange_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_GetTicker_0 = runtime.ForwardResponseMessage diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 54030bf2..e7552126 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -28,6 +28,10 @@ message GetExchangesResponse { string exchanges = 1; } +message GetExchangeOTPReponse { + string otp_code = 1; +} + message DisableExchangeRequest { string exchange = 1; } @@ -412,6 +416,12 @@ service GoCryptoTrader { }; } + rpc GetExchangeOTPCode (GenericExchangeNameRequest) returns (GetExchangeOTPReponse) { + option (google.api.http) = { + get: "/v1/getexchangeotp" + }; + } + rpc EnableExchange (GenericExchangeNameRequest) returns (GenericExchangeNameResponse) { option (google.api.http) = { post: "/v1/enableexchange" diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index 12564431..85c7f706 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -303,6 +303,30 @@ ] } }, + "/v1/getexchangeotp": { + "get": { + "operationId": "GetExchangeOTPCode", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetExchangeOTPReponse" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getexchanges": { "get": { "operationId": "GetExchanges", @@ -1072,6 +1096,14 @@ } } }, + "gctrpcGetExchangeOTPReponse": { + "type": "object", + "properties": { + "otp_code": { + "type": "string" + } + } + }, "gctrpcGetExchangesResponse": { "type": "object", "properties": { From 2ad808e70c722018099808fa72d91727e0f6ddb3 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 11 Jun 2019 17:02:00 +1000 Subject: [PATCH 09/71] Daily engine improvements: New GetExchangeOTPs API CLI validation Standardised pairs for GCTCLI Expand test coverage Trim SMS global from name is len > 11 Linter fixes --- cmd/gctcli/commands.go | 61 ++- cmd/gctcli/main.go | 14 +- cmd/gctcli/validation.go | 14 + config/config.go | 5 + engine/helpers.go | 31 +- engine/helpers_test.go | 107 +++- engine/rpcserver.go | 9 +- exchanges/bitstamp/bitstamp_test.go | 4 +- gctrpc/rpc.pb.go | 778 ++++++++++++++-------------- gctrpc/rpc.pb.gw.go | 55 +- gctrpc/rpc.proto | 12 + gctrpc/rpc.swagger.json | 27 + 12 files changed, 702 insertions(+), 415 deletions(-) create mode 100644 cmd/gctcli/validation.go diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index 61e4dc84..a4c5911b 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -218,6 +218,31 @@ func getExchangeOTPCode(c *cli.Context) error { return nil } +var getExchangeOTPsCommand = cli.Command{ + Name: "getexchangeotps", + Usage: "gets all exchange OTPs", + Action: getExchangeOTPCodes, +} + +func getExchangeOTPCodes(c *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetExchangeOTPCodes(context.Background(), + &gctrpc.GetExchangeOTPsRequest{}) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + var getExchangeInfoCommand = cli.Command{ Name: "getexchangeinfo", Usage: "gets a specific exchanges info", @@ -320,7 +345,11 @@ func getTicker(c *cli.Context) error { assetType = c.Args().Get(2) } - p := currency.NewPairFromString(currencyPair) + if !validPair(currencyPair) { + return errInvalidPair + } + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetTicker(context.Background(), &gctrpc.GetTickerRequest{ @@ -420,7 +449,11 @@ func getOrderbook(c *cli.Context) error { assetType = c.Args().Get(2) } - p := currency.NewPairFromString(currencyPair) + if !validPair(currencyPair) { + return errInvalidPair + } + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetOrderbook(context.Background(), &gctrpc.GetOrderbookRequest{ @@ -832,7 +865,11 @@ func getOrders(c *cli.Context) error { currencyPair = c.Args().Get(2) } - p := currency.NewPairFromString(currencyPair) + if !validPair(currencyPair) { + return errInvalidPair + } + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetOrders(context.Background(), &gctrpc.GetOrdersRequest{ Exchange: exchangeName, @@ -1007,7 +1044,11 @@ func submitOrder(c *cli.Context) error { clientID = c.Args().Get(6) } - p := currency.NewPairFromString(currencyPair) + if !validPair(currencyPair) { + return errInvalidPair + } + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.SubmitOrder(context.Background(), &gctrpc.SubmitOrderRequest{ Exchange: exchangeName, @@ -1121,8 +1162,12 @@ func cancelOrder(c *cli.Context) error { var p currency.Pair if len(currencyPair) > 0 { - p = currency.NewPairFromString(currencyPair) + if !validPair(currencyPair) { + return errInvalidPair + } + p = currency.NewPairDelimiter(currencyPair, pairDelimiter) } + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.CancelOrder(context.Background(), &gctrpc.CancelOrderRequest{ Exchange: exchangeName, @@ -1329,7 +1374,11 @@ func addEvent(c *cli.Context) error { } defer conn.Close() - p := currency.NewPairFromString(currencyPair) + if !validPair(currencyPair) { + return errInvalidPair + } + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.AddEvent(context.Background(), &gctrpc.AddEventRequest{ Exchange: exchangeName, diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index fb5e39bf..d501e69b 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -17,9 +17,10 @@ import ( ) var ( - host string - username string - password string + host string + username string + password string + pairDelimiter string ) func jsonOutput(in interface{}) { @@ -75,6 +76,12 @@ func main() { Usage: "the gRPC password", Destination: &password, }, + cli.StringFlag{ + Name: "delimiter", + Value: "-", + Usage: "the default pair delimiter used to standardise currency pair input", + Destination: &pairDelimiter, + }, } app.Commands = []cli.Command{ getInfoCommand, @@ -82,6 +89,7 @@ func main() { enableExchangeCommand, disableExchangeCommand, getExchangeOTPCommand, + getExchangeOTPsCommand, getExchangeInfoCommand, getTickerCommand, getTickersCommand, diff --git a/cmd/gctcli/validation.go b/cmd/gctcli/validation.go new file mode 100644 index 00000000..b170d98b --- /dev/null +++ b/cmd/gctcli/validation.go @@ -0,0 +1,14 @@ +package main + +import ( + "errors" + "strings" +) + +var ( + errInvalidPair = errors.New("invalid currency pair supplied") +) + +func validPair(pair string) bool { + return strings.Contains(pair, pairDelimiter) +} diff --git a/config/config.go b/config/config.go index 31a5ea7b..6b8c4eac 100644 --- a/config/config.go +++ b/config/config.go @@ -333,6 +333,11 @@ func (c *Config) CheckCommunicationsConfig() { c.Communications.SMSGlobalConfig.From = c.Name } + if len(c.Communications.SMSGlobalConfig.From) > 11 { + log.Warnf("SMSGlobal config supplied from name exceeds 11 characters, trimming.") + c.Communications.SMSGlobalConfig.From = c.Communications.SMSGlobalConfig.From[:11] + } + if c.SMS != nil { // flush old SMS config c.SMS = nil diff --git a/engine/helpers.go b/engine/helpers.go index 9b21b3f0..fb500ca0 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -29,9 +29,33 @@ import ( "github.com/thrasher-/gocryptotrader/utils" ) -// GetOTPByExchange returns a OTP code for the desired exchange +// GetExchangeOTPs returns OTP codes for all exchanges which have a otpsecret +// stored +func GetExchangeOTPs() (map[string]string, error) { + otpCodes := make(map[string]string) + for x := range Bot.Config.Exchanges { + if otpSecret := Bot.Config.Exchanges[x].API.Credentials.OTPSecret; otpSecret != "" { + exchName := Bot.Config.Exchanges[x].Name + o, err := totp.GenerateCode(otpSecret, time.Now()) + if err != nil { + log.Errorf("Unable to generate OTP code for exchange %s. Err: %s", + exchName, err) + continue + } + otpCodes[exchName] = o + } + } + + if len(otpCodes) == 0 { + return nil, errors.New("no exchanges found which have a OTP secret stored") + } + + return otpCodes, nil +} + +// GetExchangeoOTPByName returns a OTP code for the desired exchange // if it exists -func GetOTPByExchange(exchName string) (string, error) { +func GetExchangeoOTPByName(exchName string) (string, error) { for x := range Bot.Config.Exchanges { if !strings.EqualFold(Bot.Config.Exchanges[x].Name, exchName) { continue @@ -41,7 +65,7 @@ func GetOTPByExchange(exchName string) (string, error) { return totp.GenerateCode(otpSecret, time.Now()) } } - return "", errors.New("exchange does not have a otpsecret stored") + return "", errors.New("exchange does not have a OTP secret stored") } // GetAuthAPISupportedExchanges returns a list of auth api enabled exchanges @@ -60,6 +84,7 @@ func GetAuthAPISupportedExchanges() []string { func IsOnline() bool { if Bot.Connectivity == nil { log.Warnf("IsOnline called but Bot.Connectivity is nil") + return false } return Bot.Connectivity.IsConnected() } diff --git a/engine/helpers_test.go b/engine/helpers_test.go index c30a3989..7b66bee2 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -2,9 +2,11 @@ package engine import ( "testing" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/connchecker" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/assets" @@ -42,6 +44,110 @@ func SetupTestHelpers(t *testing.T) { } } +func TestGetExchangeOTPs(t *testing.T) { + SetupTestHelpers(t) + _, err := GetExchangeOTPs() + if err == nil { + t.Fatal("Expected err with no exchange OTP secrets set") + } + + bfxCfg, err := Bot.Config.GetExchangeConfig("Bitfinex") + if err != nil { + t.Fatal(err) + } + bCfg, err := Bot.Config.GetExchangeConfig("Bitstamp") + if err != nil { + t.Fatal(err) + } + + bfxCfg.API.Credentials.OTPSecret = "JBSWY3DPEHPK3PXP" + bCfg.API.Credentials.OTPSecret = "JBSWY3DPEHPK3PXP" + result, err := GetExchangeOTPs() + if err != nil { + t.Fatal(err) + } + if len(result) != 2 { + t.Fatal("Expected 2 OTP results") + } + + bfxCfg.API.Credentials.OTPSecret = "°" + result, err = GetExchangeOTPs() + if err != nil { + t.Fatal(err) + } + if len(result) != 1 { + t.Fatal("Expected 1 OTP code with invalid OTP Secret") + } + + // Flush settings + bfxCfg.API.Credentials.OTPSecret = "" + bCfg.API.Credentials.OTPSecret = "" +} + +func TestGetExchangeoOTPByName(t *testing.T) { + SetupTestHelpers(t) + _, err := GetExchangeoOTPByName("Bitstamp") + if err == nil { + t.Fatal("Expected err with no exchange OTP secrets set") + } + + bCfg, err := Bot.Config.GetExchangeConfig("Bitstamp") + if err != nil { + t.Fatal(err) + } + + bCfg.API.Credentials.OTPSecret = "JBSWY3DPEHPK3PXP" + result, err := GetExchangeoOTPByName("Bitstamp") + if err != nil { + t.Fatal(err) + } + if result == "" { + t.Fatal("Expected valid OTP code") + } +} + +func TestGetAuthAPISupportedExchanges(t *testing.T) { + SetupTestHelpers(t) + if result := GetAuthAPISupportedExchanges(); result != nil { + t.Fatal("Unexpected result") + } +} + +func TestIsOnline(t *testing.T) { + SetupTestHelpers(t) + if r := IsOnline(); r { + t.Fatal("Unexpected result") + } + + var err error + Bot.Connectivity, err = connchecker.New(Bot.Config.ConnectionMonitor.DNSList, + Bot.Config.ConnectionMonitor.PublicDomainList, + Bot.Config.ConnectionMonitor.CheckInterval) + if err != nil { + t.Fatal(err) + } + + tick := time.NewTicker(time.Second * 5) + for { + select { + case <-tick.C: + t.Fatal("Test timeout") + default: + if IsOnline() { + Bot.Connectivity.Shutdown() + return + } + } + } +} + +func TestGetAvailableExchanges(t *testing.T) { + SetupTestHelpers(t) + if r := len(GetAvailableExchanges()); r == 0 { + t.Error("Expected len > 0") + } +} + func TestGetSpecificAvailablePairs(t *testing.T) { SetupTestHelpers(t) assetType := assets.AssetTypeSpot @@ -255,7 +361,6 @@ func TestGetExchangeNamesByCurrency(t *testing.T) { } result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "JPY"), true, assetType) - t.Log(result) if !common.StringDataCompare(result, "Bitflyer") { t.Fatal("Unexpected result") } diff --git a/engine/rpcserver.go b/engine/rpcserver.go index c057f464..6fd20978 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -178,10 +178,17 @@ func (s *RPCServer) EnableExchange(ctx context.Context, r *gctrpc.GenericExchang // GetExchangeOTPCode retrieves an exchanges OTP code func (s *RPCServer) GetExchangeOTPCode(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeOTPReponse, error) { - result, err := GetOTPByExchange(r.Exchange) + result, err := GetExchangeoOTPByName(r.Exchange) return &gctrpc.GetExchangeOTPReponse{OtpCode: result}, err } +// GetExchangeOTPCodes retrieves OTP codes for all exchanges which have an +// OTP secret installed +func (s *RPCServer) GetExchangeOTPCodes(ctx context.Context, r *gctrpc.GetExchangeOTPsRequest) (*gctrpc.GetExchangeOTPsResponse, error) { + result, err := GetExchangeOTPs() + return &gctrpc.GetExchangeOTPsResponse{OtpCodes: result}, err +} + // GetExchangeInfo gets info for a specific exchange func (s *RPCServer) GetExchangeInfo(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeInfoResponse, error) { exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index e09c09d0..55f7c096 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -419,8 +419,8 @@ func TestSubmitOrder(t *testing.T) { var orderSubmission = &exchange.OrderSubmission{ Pair: currency.Pair{ - Base: currency.BTC, - Quote: currency.USD, + Base: currency.BTC, + Quote: currency.USD, }, OrderSide: exchange.BuyOrderSide, OrderType: exchange.LimitOrderType, diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index 40bd05a3..70fa45fc 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -9,8 +9,6 @@ import ( proto "github.com/golang/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" math "math" ) @@ -314,6 +312,76 @@ func (m *GetExchangeOTPReponse) GetOtpCode() string { return "" } +type GetExchangeOTPsRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangeOTPsRequest) Reset() { *m = GetExchangeOTPsRequest{} } +func (m *GetExchangeOTPsRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangeOTPsRequest) ProtoMessage() {} +func (*GetExchangeOTPsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{7} +} + +func (m *GetExchangeOTPsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeOTPsRequest.Unmarshal(m, b) +} +func (m *GetExchangeOTPsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeOTPsRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangeOTPsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeOTPsRequest.Merge(m, src) +} +func (m *GetExchangeOTPsRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangeOTPsRequest.Size(m) +} +func (m *GetExchangeOTPsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeOTPsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeOTPsRequest proto.InternalMessageInfo + +type GetExchangeOTPsResponse struct { + OtpCodes map[string]string `protobuf:"bytes,1,rep,name=otp_codes,json=otpCodes,proto3" json:"otp_codes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangeOTPsResponse) Reset() { *m = GetExchangeOTPsResponse{} } +func (m *GetExchangeOTPsResponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangeOTPsResponse) ProtoMessage() {} +func (*GetExchangeOTPsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{8} +} + +func (m *GetExchangeOTPsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeOTPsResponse.Unmarshal(m, b) +} +func (m *GetExchangeOTPsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeOTPsResponse.Marshal(b, m, deterministic) +} +func (m *GetExchangeOTPsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeOTPsResponse.Merge(m, src) +} +func (m *GetExchangeOTPsResponse) XXX_Size() int { + return xxx_messageInfo_GetExchangeOTPsResponse.Size(m) +} +func (m *GetExchangeOTPsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeOTPsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeOTPsResponse proto.InternalMessageInfo + +func (m *GetExchangeOTPsResponse) GetOtpCodes() map[string]string { + if m != nil { + return m.OtpCodes + } + return nil +} + type DisableExchangeRequest struct { Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -325,7 +393,7 @@ func (m *DisableExchangeRequest) Reset() { *m = DisableExchangeRequest{} func (m *DisableExchangeRequest) String() string { return proto.CompactTextString(m) } func (*DisableExchangeRequest) ProtoMessage() {} func (*DisableExchangeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{7} + return fileDescriptor_77a6da22d6a3feb1, []int{9} } func (m *DisableExchangeRequest) XXX_Unmarshal(b []byte) error { @@ -375,7 +443,7 @@ func (m *GetExchangeInfoResponse) Reset() { *m = GetExchangeInfoResponse func (m *GetExchangeInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetExchangeInfoResponse) ProtoMessage() {} func (*GetExchangeInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{8} + return fileDescriptor_77a6da22d6a3feb1, []int{10} } func (m *GetExchangeInfoResponse) XXX_Unmarshal(b []byte) error { @@ -493,7 +561,7 @@ func (m *GetTickerRequest) Reset() { *m = GetTickerRequest{} } func (m *GetTickerRequest) String() string { return proto.CompactTextString(m) } func (*GetTickerRequest) ProtoMessage() {} func (*GetTickerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{9} + return fileDescriptor_77a6da22d6a3feb1, []int{11} } func (m *GetTickerRequest) XXX_Unmarshal(b []byte) error { @@ -548,7 +616,7 @@ func (m *CurrencyPair) Reset() { *m = CurrencyPair{} } func (m *CurrencyPair) String() string { return proto.CompactTextString(m) } func (*CurrencyPair) ProtoMessage() {} func (*CurrencyPair) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{10} + return fileDescriptor_77a6da22d6a3feb1, []int{12} } func (m *CurrencyPair) XXX_Unmarshal(b []byte) error { @@ -610,7 +678,7 @@ func (m *TickerResponse) Reset() { *m = TickerResponse{} } func (m *TickerResponse) String() string { return proto.CompactTextString(m) } func (*TickerResponse) ProtoMessage() {} func (*TickerResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{11} + return fileDescriptor_77a6da22d6a3feb1, []int{13} } func (m *TickerResponse) XXX_Unmarshal(b []byte) error { @@ -711,7 +779,7 @@ func (m *GetTickersRequest) Reset() { *m = GetTickersRequest{} } func (m *GetTickersRequest) String() string { return proto.CompactTextString(m) } func (*GetTickersRequest) ProtoMessage() {} func (*GetTickersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{12} + return fileDescriptor_77a6da22d6a3feb1, []int{14} } func (m *GetTickersRequest) XXX_Unmarshal(b []byte) error { @@ -744,7 +812,7 @@ func (m *Tickers) Reset() { *m = Tickers{} } func (m *Tickers) String() string { return proto.CompactTextString(m) } func (*Tickers) ProtoMessage() {} func (*Tickers) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{13} + return fileDescriptor_77a6da22d6a3feb1, []int{15} } func (m *Tickers) XXX_Unmarshal(b []byte) error { @@ -790,7 +858,7 @@ func (m *GetTickersResponse) Reset() { *m = GetTickersResponse{} } func (m *GetTickersResponse) String() string { return proto.CompactTextString(m) } func (*GetTickersResponse) ProtoMessage() {} func (*GetTickersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{14} + return fileDescriptor_77a6da22d6a3feb1, []int{16} } func (m *GetTickersResponse) XXX_Unmarshal(b []byte) error { @@ -831,7 +899,7 @@ func (m *GetOrderbookRequest) Reset() { *m = GetOrderbookRequest{} } func (m *GetOrderbookRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderbookRequest) ProtoMessage() {} func (*GetOrderbookRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{15} + return fileDescriptor_77a6da22d6a3feb1, []int{17} } func (m *GetOrderbookRequest) XXX_Unmarshal(b []byte) error { @@ -886,7 +954,7 @@ func (m *OrderbookItem) Reset() { *m = OrderbookItem{} } func (m *OrderbookItem) String() string { return proto.CompactTextString(m) } func (*OrderbookItem) ProtoMessage() {} func (*OrderbookItem) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{16} + return fileDescriptor_77a6da22d6a3feb1, []int{18} } func (m *OrderbookItem) XXX_Unmarshal(b []byte) error { @@ -944,7 +1012,7 @@ func (m *OrderbookResponse) Reset() { *m = OrderbookResponse{} } func (m *OrderbookResponse) String() string { return proto.CompactTextString(m) } func (*OrderbookResponse) ProtoMessage() {} func (*OrderbookResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{17} + return fileDescriptor_77a6da22d6a3feb1, []int{19} } func (m *OrderbookResponse) XXX_Unmarshal(b []byte) error { @@ -1017,7 +1085,7 @@ func (m *GetOrderbooksRequest) Reset() { *m = GetOrderbooksRequest{} } func (m *GetOrderbooksRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderbooksRequest) ProtoMessage() {} func (*GetOrderbooksRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{18} + return fileDescriptor_77a6da22d6a3feb1, []int{20} } func (m *GetOrderbooksRequest) XXX_Unmarshal(b []byte) error { @@ -1050,7 +1118,7 @@ func (m *Orderbooks) Reset() { *m = Orderbooks{} } func (m *Orderbooks) String() string { return proto.CompactTextString(m) } func (*Orderbooks) ProtoMessage() {} func (*Orderbooks) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{19} + return fileDescriptor_77a6da22d6a3feb1, []int{21} } func (m *Orderbooks) XXX_Unmarshal(b []byte) error { @@ -1096,7 +1164,7 @@ func (m *GetOrderbooksResponse) Reset() { *m = GetOrderbooksResponse{} } func (m *GetOrderbooksResponse) String() string { return proto.CompactTextString(m) } func (*GetOrderbooksResponse) ProtoMessage() {} func (*GetOrderbooksResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{20} + return fileDescriptor_77a6da22d6a3feb1, []int{22} } func (m *GetOrderbooksResponse) XXX_Unmarshal(b []byte) error { @@ -1135,7 +1203,7 @@ func (m *GetAccountInfoRequest) Reset() { *m = GetAccountInfoRequest{} } func (m *GetAccountInfoRequest) String() string { return proto.CompactTextString(m) } func (*GetAccountInfoRequest) ProtoMessage() {} func (*GetAccountInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{21} + return fileDescriptor_77a6da22d6a3feb1, []int{23} } func (m *GetAccountInfoRequest) XXX_Unmarshal(b []byte) error { @@ -1175,7 +1243,7 @@ func (m *Account) Reset() { *m = Account{} } func (m *Account) String() string { return proto.CompactTextString(m) } func (*Account) ProtoMessage() {} func (*Account) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{22} + return fileDescriptor_77a6da22d6a3feb1, []int{24} } func (m *Account) XXX_Unmarshal(b []byte) error { @@ -1223,7 +1291,7 @@ func (m *AccountCurrencyInfo) Reset() { *m = AccountCurrencyInfo{} } func (m *AccountCurrencyInfo) String() string { return proto.CompactTextString(m) } func (*AccountCurrencyInfo) ProtoMessage() {} func (*AccountCurrencyInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{23} + return fileDescriptor_77a6da22d6a3feb1, []int{25} } func (m *AccountCurrencyInfo) XXX_Unmarshal(b []byte) error { @@ -1277,7 +1345,7 @@ func (m *GetAccountInfoResponse) Reset() { *m = GetAccountInfoResponse{} func (m *GetAccountInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetAccountInfoResponse) ProtoMessage() {} func (*GetAccountInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{24} + return fileDescriptor_77a6da22d6a3feb1, []int{26} } func (m *GetAccountInfoResponse) XXX_Unmarshal(b []byte) error { @@ -1322,7 +1390,7 @@ func (m *GetConfigRequest) Reset() { *m = GetConfigRequest{} } func (m *GetConfigRequest) String() string { return proto.CompactTextString(m) } func (*GetConfigRequest) ProtoMessage() {} func (*GetConfigRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{25} + return fileDescriptor_77a6da22d6a3feb1, []int{27} } func (m *GetConfigRequest) XXX_Unmarshal(b []byte) error { @@ -1354,7 +1422,7 @@ func (m *GetConfigResponse) Reset() { *m = GetConfigResponse{} } func (m *GetConfigResponse) String() string { return proto.CompactTextString(m) } func (*GetConfigResponse) ProtoMessage() {} func (*GetConfigResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{26} + return fileDescriptor_77a6da22d6a3feb1, []int{28} } func (m *GetConfigResponse) XXX_Unmarshal(b []byte) error { @@ -1396,7 +1464,7 @@ func (m *PortfolioAddress) Reset() { *m = PortfolioAddress{} } func (m *PortfolioAddress) String() string { return proto.CompactTextString(m) } func (*PortfolioAddress) ProtoMessage() {} func (*PortfolioAddress) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{27} + return fileDescriptor_77a6da22d6a3feb1, []int{29} } func (m *PortfolioAddress) XXX_Unmarshal(b []byte) error { @@ -1455,7 +1523,7 @@ func (m *GetPortfolioRequest) Reset() { *m = GetPortfolioRequest{} } func (m *GetPortfolioRequest) String() string { return proto.CompactTextString(m) } func (*GetPortfolioRequest) ProtoMessage() {} func (*GetPortfolioRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{28} + return fileDescriptor_77a6da22d6a3feb1, []int{30} } func (m *GetPortfolioRequest) XXX_Unmarshal(b []byte) error { @@ -1487,7 +1555,7 @@ func (m *GetPortfolioResponse) Reset() { *m = GetPortfolioResponse{} } func (m *GetPortfolioResponse) String() string { return proto.CompactTextString(m) } func (*GetPortfolioResponse) ProtoMessage() {} func (*GetPortfolioResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{29} + return fileDescriptor_77a6da22d6a3feb1, []int{31} } func (m *GetPortfolioResponse) XXX_Unmarshal(b []byte) error { @@ -1525,7 +1593,7 @@ func (m *GetPortfolioSummaryRequest) Reset() { *m = GetPortfolioSummaryR func (m *GetPortfolioSummaryRequest) String() string { return proto.CompactTextString(m) } func (*GetPortfolioSummaryRequest) ProtoMessage() {} func (*GetPortfolioSummaryRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{30} + return fileDescriptor_77a6da22d6a3feb1, []int{32} } func (m *GetPortfolioSummaryRequest) XXX_Unmarshal(b []byte) error { @@ -1560,7 +1628,7 @@ func (m *Coin) Reset() { *m = Coin{} } func (m *Coin) String() string { return proto.CompactTextString(m) } func (*Coin) ProtoMessage() {} func (*Coin) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{31} + return fileDescriptor_77a6da22d6a3feb1, []int{33} } func (m *Coin) XXX_Unmarshal(b []byte) error { @@ -1622,7 +1690,7 @@ func (m *OfflineCoinSummary) Reset() { *m = OfflineCoinSummary{} } func (m *OfflineCoinSummary) String() string { return proto.CompactTextString(m) } func (*OfflineCoinSummary) ProtoMessage() {} func (*OfflineCoinSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{32} + return fileDescriptor_77a6da22d6a3feb1, []int{34} } func (m *OfflineCoinSummary) XXX_Unmarshal(b []byte) error { @@ -1676,7 +1744,7 @@ func (m *OnlineCoinSummary) Reset() { *m = OnlineCoinSummary{} } func (m *OnlineCoinSummary) String() string { return proto.CompactTextString(m) } func (*OnlineCoinSummary) ProtoMessage() {} func (*OnlineCoinSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{33} + return fileDescriptor_77a6da22d6a3feb1, []int{35} } func (m *OnlineCoinSummary) XXX_Unmarshal(b []byte) error { @@ -1722,7 +1790,7 @@ func (m *OfflineCoins) Reset() { *m = OfflineCoins{} } func (m *OfflineCoins) String() string { return proto.CompactTextString(m) } func (*OfflineCoins) ProtoMessage() {} func (*OfflineCoins) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{34} + return fileDescriptor_77a6da22d6a3feb1, []int{36} } func (m *OfflineCoins) XXX_Unmarshal(b []byte) error { @@ -1761,7 +1829,7 @@ func (m *OnlineCoins) Reset() { *m = OnlineCoins{} } func (m *OnlineCoins) String() string { return proto.CompactTextString(m) } func (*OnlineCoins) ProtoMessage() {} func (*OnlineCoins) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{35} + return fileDescriptor_77a6da22d6a3feb1, []int{37} } func (m *OnlineCoins) XXX_Unmarshal(b []byte) error { @@ -1804,7 +1872,7 @@ func (m *GetPortfolioSummaryResponse) Reset() { *m = GetPortfolioSummary func (m *GetPortfolioSummaryResponse) String() string { return proto.CompactTextString(m) } func (*GetPortfolioSummaryResponse) ProtoMessage() {} func (*GetPortfolioSummaryResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{36} + return fileDescriptor_77a6da22d6a3feb1, []int{38} } func (m *GetPortfolioSummaryResponse) XXX_Unmarshal(b []byte) error { @@ -1874,7 +1942,7 @@ func (m *AddPortfolioAddressRequest) Reset() { *m = AddPortfolioAddressR func (m *AddPortfolioAddressRequest) String() string { return proto.CompactTextString(m) } func (*AddPortfolioAddressRequest) ProtoMessage() {} func (*AddPortfolioAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{37} + return fileDescriptor_77a6da22d6a3feb1, []int{39} } func (m *AddPortfolioAddressRequest) XXX_Unmarshal(b []byte) error { @@ -1933,7 +2001,7 @@ func (m *AddPortfolioAddressResponse) Reset() { *m = AddPortfolioAddress func (m *AddPortfolioAddressResponse) String() string { return proto.CompactTextString(m) } func (*AddPortfolioAddressResponse) ProtoMessage() {} func (*AddPortfolioAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{38} + return fileDescriptor_77a6da22d6a3feb1, []int{40} } func (m *AddPortfolioAddressResponse) XXX_Unmarshal(b []byte) error { @@ -1967,7 +2035,7 @@ func (m *RemovePortfolioAddressRequest) Reset() { *m = RemovePortfolioAd func (m *RemovePortfolioAddressRequest) String() string { return proto.CompactTextString(m) } func (*RemovePortfolioAddressRequest) ProtoMessage() {} func (*RemovePortfolioAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{39} + return fileDescriptor_77a6da22d6a3feb1, []int{41} } func (m *RemovePortfolioAddressRequest) XXX_Unmarshal(b []byte) error { @@ -2019,7 +2087,7 @@ func (m *RemovePortfolioAddressResponse) Reset() { *m = RemovePortfolioA func (m *RemovePortfolioAddressResponse) String() string { return proto.CompactTextString(m) } func (*RemovePortfolioAddressResponse) ProtoMessage() {} func (*RemovePortfolioAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{40} + return fileDescriptor_77a6da22d6a3feb1, []int{42} } func (m *RemovePortfolioAddressResponse) XXX_Unmarshal(b []byte) error { @@ -2050,7 +2118,7 @@ func (m *GetForexProvidersRequest) Reset() { *m = GetForexProvidersReque func (m *GetForexProvidersRequest) String() string { return proto.CompactTextString(m) } func (*GetForexProvidersRequest) ProtoMessage() {} func (*GetForexProvidersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{41} + return fileDescriptor_77a6da22d6a3feb1, []int{43} } func (m *GetForexProvidersRequest) XXX_Unmarshal(b []byte) error { @@ -2088,7 +2156,7 @@ func (m *ForexProvider) Reset() { *m = ForexProvider{} } func (m *ForexProvider) String() string { return proto.CompactTextString(m) } func (*ForexProvider) ProtoMessage() {} func (*ForexProvider) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{42} + return fileDescriptor_77a6da22d6a3feb1, []int{44} } func (m *ForexProvider) XXX_Unmarshal(b []byte) error { @@ -2169,7 +2237,7 @@ func (m *GetForexProvidersResponse) Reset() { *m = GetForexProvidersResp func (m *GetForexProvidersResponse) String() string { return proto.CompactTextString(m) } func (*GetForexProvidersResponse) ProtoMessage() {} func (*GetForexProvidersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{43} + return fileDescriptor_77a6da22d6a3feb1, []int{45} } func (m *GetForexProvidersResponse) XXX_Unmarshal(b []byte) error { @@ -2207,7 +2275,7 @@ func (m *GetForexRatesRequest) Reset() { *m = GetForexRatesRequest{} } func (m *GetForexRatesRequest) String() string { return proto.CompactTextString(m) } func (*GetForexRatesRequest) ProtoMessage() {} func (*GetForexRatesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{44} + return fileDescriptor_77a6da22d6a3feb1, []int{46} } func (m *GetForexRatesRequest) XXX_Unmarshal(b []byte) error { @@ -2242,7 +2310,7 @@ func (m *ForexRatesConversion) Reset() { *m = ForexRatesConversion{} } func (m *ForexRatesConversion) String() string { return proto.CompactTextString(m) } func (*ForexRatesConversion) ProtoMessage() {} func (*ForexRatesConversion) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{45} + return fileDescriptor_77a6da22d6a3feb1, []int{47} } func (m *ForexRatesConversion) XXX_Unmarshal(b []byte) error { @@ -2302,7 +2370,7 @@ func (m *GetForexRatesResponse) Reset() { *m = GetForexRatesResponse{} } func (m *GetForexRatesResponse) String() string { return proto.CompactTextString(m) } func (*GetForexRatesResponse) ProtoMessage() {} func (*GetForexRatesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{46} + return fileDescriptor_77a6da22d6a3feb1, []int{48} } func (m *GetForexRatesResponse) XXX_Unmarshal(b []byte) error { @@ -2352,7 +2420,7 @@ func (m *OrderDetails) Reset() { *m = OrderDetails{} } func (m *OrderDetails) String() string { return proto.CompactTextString(m) } func (*OrderDetails) ProtoMessage() {} func (*OrderDetails) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{47} + return fileDescriptor_77a6da22d6a3feb1, []int{49} } func (m *OrderDetails) XXX_Unmarshal(b []byte) error { @@ -2470,7 +2538,7 @@ func (m *GetOrdersRequest) Reset() { *m = GetOrdersRequest{} } func (m *GetOrdersRequest) String() string { return proto.CompactTextString(m) } func (*GetOrdersRequest) ProtoMessage() {} func (*GetOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{48} + return fileDescriptor_77a6da22d6a3feb1, []int{50} } func (m *GetOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -2523,7 +2591,7 @@ func (m *GetOrdersResponse) Reset() { *m = GetOrdersResponse{} } func (m *GetOrdersResponse) String() string { return proto.CompactTextString(m) } func (*GetOrdersResponse) ProtoMessage() {} func (*GetOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{49} + return fileDescriptor_77a6da22d6a3feb1, []int{51} } func (m *GetOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -2563,7 +2631,7 @@ func (m *GetOrderRequest) Reset() { *m = GetOrderRequest{} } func (m *GetOrderRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderRequest) ProtoMessage() {} func (*GetOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{50} + return fileDescriptor_77a6da22d6a3feb1, []int{52} } func (m *GetOrderRequest) XXX_Unmarshal(b []byte) error { @@ -2615,7 +2683,7 @@ func (m *SubmitOrderRequest) Reset() { *m = SubmitOrderRequest{} } func (m *SubmitOrderRequest) String() string { return proto.CompactTextString(m) } func (*SubmitOrderRequest) ProtoMessage() {} func (*SubmitOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{51} + return fileDescriptor_77a6da22d6a3feb1, []int{53} } func (m *SubmitOrderRequest) XXX_Unmarshal(b []byte) error { @@ -2697,7 +2765,7 @@ func (m *SubmitOrderResponse) Reset() { *m = SubmitOrderResponse{} } func (m *SubmitOrderResponse) String() string { return proto.CompactTextString(m) } func (*SubmitOrderResponse) ProtoMessage() {} func (*SubmitOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{52} + return fileDescriptor_77a6da22d6a3feb1, []int{54} } func (m *SubmitOrderResponse) XXX_Unmarshal(b []byte) error { @@ -2749,7 +2817,7 @@ func (m *CancelOrderRequest) Reset() { *m = CancelOrderRequest{} } func (m *CancelOrderRequest) String() string { return proto.CompactTextString(m) } func (*CancelOrderRequest) ProtoMessage() {} func (*CancelOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{53} + return fileDescriptor_77a6da22d6a3feb1, []int{55} } func (m *CancelOrderRequest) XXX_Unmarshal(b []byte) error { @@ -2829,7 +2897,7 @@ func (m *CancelOrderResponse) Reset() { *m = CancelOrderResponse{} } func (m *CancelOrderResponse) String() string { return proto.CompactTextString(m) } func (*CancelOrderResponse) ProtoMessage() {} func (*CancelOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{54} + return fileDescriptor_77a6da22d6a3feb1, []int{56} } func (m *CancelOrderResponse) XXX_Unmarshal(b []byte) error { @@ -2861,7 +2929,7 @@ func (m *CancelAllOrdersRequest) Reset() { *m = CancelAllOrdersRequest{} func (m *CancelAllOrdersRequest) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersRequest) ProtoMessage() {} func (*CancelAllOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{55} + return fileDescriptor_77a6da22d6a3feb1, []int{57} } func (m *CancelAllOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -2900,7 +2968,7 @@ func (m *CancelAllOrdersResponse) Reset() { *m = CancelAllOrdersResponse func (m *CancelAllOrdersResponse) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse) ProtoMessage() {} func (*CancelAllOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{56} + return fileDescriptor_77a6da22d6a3feb1, []int{58} } func (m *CancelAllOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -2940,7 +3008,7 @@ func (m *CancelAllOrdersResponse_Orders) Reset() { *m = CancelAllOrdersR func (m *CancelAllOrdersResponse_Orders) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse_Orders) ProtoMessage() {} func (*CancelAllOrdersResponse_Orders) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{56, 0} + return fileDescriptor_77a6da22d6a3feb1, []int{58, 0} } func (m *CancelAllOrdersResponse_Orders) XXX_Unmarshal(b []byte) error { @@ -2985,7 +3053,7 @@ func (m *GetEventsRequest) Reset() { *m = GetEventsRequest{} } func (m *GetEventsRequest) String() string { return proto.CompactTextString(m) } func (*GetEventsRequest) ProtoMessage() {} func (*GetEventsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{57} + return fileDescriptor_77a6da22d6a3feb1, []int{59} } func (m *GetEventsRequest) XXX_Unmarshal(b []byte) error { @@ -3021,7 +3089,7 @@ func (m *ConditionParams) Reset() { *m = ConditionParams{} } func (m *ConditionParams) String() string { return proto.CompactTextString(m) } func (*ConditionParams) ProtoMessage() {} func (*ConditionParams) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{58} + return fileDescriptor_77a6da22d6a3feb1, []int{60} } func (m *ConditionParams) XXX_Unmarshal(b []byte) error { @@ -3094,7 +3162,7 @@ func (m *GetEventsResponse) Reset() { *m = GetEventsResponse{} } func (m *GetEventsResponse) String() string { return proto.CompactTextString(m) } func (*GetEventsResponse) ProtoMessage() {} func (*GetEventsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{59} + return fileDescriptor_77a6da22d6a3feb1, []int{61} } func (m *GetEventsResponse) XXX_Unmarshal(b []byte) error { @@ -3180,7 +3248,7 @@ func (m *AddEventRequest) Reset() { *m = AddEventRequest{} } func (m *AddEventRequest) String() string { return proto.CompactTextString(m) } func (*AddEventRequest) ProtoMessage() {} func (*AddEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{60} + return fileDescriptor_77a6da22d6a3feb1, []int{62} } func (m *AddEventRequest) XXX_Unmarshal(b []byte) error { @@ -3254,7 +3322,7 @@ func (m *AddEventResponse) Reset() { *m = AddEventResponse{} } func (m *AddEventResponse) String() string { return proto.CompactTextString(m) } func (*AddEventResponse) ProtoMessage() {} func (*AddEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{61} + return fileDescriptor_77a6da22d6a3feb1, []int{63} } func (m *AddEventResponse) XXX_Unmarshal(b []byte) error { @@ -3293,7 +3361,7 @@ func (m *RemoveEventRequest) Reset() { *m = RemoveEventRequest{} } func (m *RemoveEventRequest) String() string { return proto.CompactTextString(m) } func (*RemoveEventRequest) ProtoMessage() {} func (*RemoveEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{62} + return fileDescriptor_77a6da22d6a3feb1, []int{64} } func (m *RemoveEventRequest) XXX_Unmarshal(b []byte) error { @@ -3331,7 +3399,7 @@ func (m *RemoveEventResponse) Reset() { *m = RemoveEventResponse{} } func (m *RemoveEventResponse) String() string { return proto.CompactTextString(m) } func (*RemoveEventResponse) ProtoMessage() {} func (*RemoveEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{63} + return fileDescriptor_77a6da22d6a3feb1, []int{65} } func (m *RemoveEventResponse) XXX_Unmarshal(b []byte) error { @@ -3365,7 +3433,7 @@ func (m *GetCryptocurrencyDepositAddressesRequest) Reset() { func (m *GetCryptocurrencyDepositAddressesRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{64} + return fileDescriptor_77a6da22d6a3feb1, []int{66} } func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Unmarshal(b []byte) error { @@ -3406,7 +3474,7 @@ func (m *GetCryptocurrencyDepositAddressesResponse) Reset() { func (m *GetCryptocurrencyDepositAddressesResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{65} + return fileDescriptor_77a6da22d6a3feb1, []int{67} } func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Unmarshal(b []byte) error { @@ -3448,7 +3516,7 @@ func (m *GetCryptocurrencyDepositAddressRequest) Reset() { func (m *GetCryptocurrencyDepositAddressRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{66} + return fileDescriptor_77a6da22d6a3feb1, []int{68} } func (m *GetCryptocurrencyDepositAddressRequest) XXX_Unmarshal(b []byte) error { @@ -3496,7 +3564,7 @@ func (m *GetCryptocurrencyDepositAddressResponse) Reset() { func (m *GetCryptocurrencyDepositAddressResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{67} + return fileDescriptor_77a6da22d6a3feb1, []int{69} } func (m *GetCryptocurrencyDepositAddressResponse) XXX_Unmarshal(b []byte) error { @@ -3551,7 +3619,7 @@ func (m *WithdrawCurrencyRequest) Reset() { *m = WithdrawCurrencyRequest func (m *WithdrawCurrencyRequest) String() string { return proto.CompactTextString(m) } func (*WithdrawCurrencyRequest) ProtoMessage() {} func (*WithdrawCurrencyRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{68} + return fileDescriptor_77a6da22d6a3feb1, []int{70} } func (m *WithdrawCurrencyRequest) XXX_Unmarshal(b []byte) error { @@ -3702,7 +3770,7 @@ func (m *WithdrawResponse) Reset() { *m = WithdrawResponse{} } func (m *WithdrawResponse) String() string { return proto.CompactTextString(m) } func (*WithdrawResponse) ProtoMessage() {} func (*WithdrawResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{69} + return fileDescriptor_77a6da22d6a3feb1, []int{71} } func (m *WithdrawResponse) XXX_Unmarshal(b []byte) error { @@ -3738,6 +3806,9 @@ func init() { proto.RegisterType((*GetExchangesRequest)(nil), "gctrpc.GetExchangesRequest") proto.RegisterType((*GetExchangesResponse)(nil), "gctrpc.GetExchangesResponse") proto.RegisterType((*GetExchangeOTPReponse)(nil), "gctrpc.GetExchangeOTPReponse") + proto.RegisterType((*GetExchangeOTPsRequest)(nil), "gctrpc.GetExchangeOTPsRequest") + proto.RegisterType((*GetExchangeOTPsResponse)(nil), "gctrpc.GetExchangeOTPsResponse") + proto.RegisterMapType((map[string]string)(nil), "gctrpc.GetExchangeOTPsResponse.OtpCodesEntry") proto.RegisterType((*DisableExchangeRequest)(nil), "gctrpc.DisableExchangeRequest") proto.RegisterType((*GetExchangeInfoResponse)(nil), "gctrpc.GetExchangeInfoResponse") proto.RegisterType((*GetTickerRequest)(nil), "gctrpc.GetTickerRequest") @@ -3812,227 +3883,232 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 3508 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x1a, 0x4d, 0x6f, 0xdc, 0xc6, - 0x15, 0x5c, 0x7d, 0xbf, 0x5d, 0x69, 0x57, 0xa3, 0xaf, 0xd5, 0x4a, 0xb2, 0xe5, 0x49, 0xed, 0xd8, - 0x4e, 0x62, 0x25, 0x8e, 0xd1, 0xa6, 0x49, 0x9a, 0x56, 0x91, 0x3f, 0x62, 0xa4, 0x89, 0x05, 0xda, - 0x71, 0x80, 0xa4, 0x28, 0x41, 0x91, 0xb3, 0x12, 0x21, 0x8a, 0x64, 0x48, 0xae, 0x64, 0x05, 0x05, - 0x0a, 0x04, 0xe8, 0xb5, 0x3d, 0x14, 0x05, 0x7a, 0xe8, 0xa5, 0xd7, 0x02, 0xbd, 0xf4, 0x07, 0x04, - 0xbd, 0x16, 0x3d, 0xf6, 0xd2, 0x1f, 0x50, 0xf4, 0xd6, 0x16, 0x3d, 0xf4, 0xd2, 0x53, 0x31, 0x6f, - 0x66, 0x48, 0x0e, 0xc9, 0x5d, 0xad, 0x9b, 0x36, 0x17, 0x89, 0x7c, 0xf3, 0xe6, 0x7d, 0xcf, 0x9b, - 0xf7, 0x1e, 0x17, 0xe6, 0xe2, 0xc8, 0xb9, 0x15, 0xc5, 0x61, 0x1a, 0x92, 0xe9, 0x43, 0x27, 0x8d, - 0x23, 0xa7, 0xb7, 0x79, 0x18, 0x86, 0x87, 0x3e, 0xdb, 0xb1, 0x23, 0x6f, 0xc7, 0x0e, 0x82, 0x30, - 0xb5, 0x53, 0x2f, 0x0c, 0x12, 0x81, 0x45, 0x3b, 0xb0, 0xf0, 0x80, 0xa5, 0x0f, 0x83, 0x7e, 0x68, - 0xb2, 0xcf, 0x06, 0x2c, 0x49, 0xe9, 0x3f, 0x0d, 0x68, 0x67, 0xa0, 0x24, 0x0a, 0x83, 0x84, 0x91, - 0x55, 0x98, 0x1e, 0x44, 0xa9, 0x77, 0xc2, 0xba, 0xc6, 0xb6, 0x71, 0x7d, 0xce, 0x94, 0x6f, 0x64, - 0x07, 0x96, 0xec, 0x53, 0xdb, 0xf3, 0xed, 0x03, 0x9f, 0x59, 0xec, 0x99, 0x73, 0x64, 0x07, 0x87, - 0x2c, 0xe9, 0x36, 0xb6, 0x8d, 0xeb, 0x13, 0x26, 0xc9, 0x96, 0xee, 0xa9, 0x15, 0xf2, 0x12, 0x2c, - 0xb2, 0x80, 0x83, 0xdc, 0x02, 0xfa, 0x04, 0xa2, 0x77, 0xe4, 0x42, 0x8e, 0x7c, 0x07, 0x56, 0x5d, - 0xd6, 0xb7, 0x07, 0x7e, 0x6a, 0xf5, 0xc3, 0x98, 0x3d, 0xb3, 0xa2, 0x38, 0x3c, 0xf5, 0x5c, 0x16, - 0x77, 0x27, 0x51, 0x8a, 0x65, 0xb9, 0x7a, 0x9f, 0x2f, 0xee, 0xcb, 0x35, 0x72, 0x1b, 0x56, 0xb2, - 0x5d, 0x9e, 0x9d, 0x5a, 0xce, 0x20, 0x8e, 0x59, 0xe0, 0x9c, 0x77, 0xa7, 0x70, 0xd3, 0x92, 0xda, - 0xe4, 0xd9, 0xe9, 0x9e, 0x5c, 0xa2, 0x6f, 0x40, 0xef, 0x01, 0x0b, 0x58, 0xec, 0x39, 0x8a, 0xfb, - 0x87, 0xf6, 0x09, 0x93, 0x16, 0x21, 0x3d, 0x98, 0x55, 0xc2, 0x4a, 0xfd, 0xb3, 0x77, 0xba, 0x05, - 0x1b, 0xb5, 0x3b, 0x85, 0xe1, 0xe8, 0x0e, 0x2c, 0x3d, 0x60, 0x69, 0xa6, 0x92, 0xa2, 0xd8, 0x85, - 0x19, 0xa9, 0x2d, 0x12, 0x9c, 0x35, 0xd5, 0x2b, 0xbd, 0x03, 0xcb, 0xfa, 0x06, 0xe9, 0x81, 0x4d, - 0x98, 0xcb, 0x0d, 0x26, 0x84, 0xc8, 0x01, 0xf4, 0x36, 0xac, 0x14, 0x76, 0x3d, 0x7a, 0xb2, 0x6f, - 0x32, 0xb1, 0x6d, 0x1d, 0x66, 0xc3, 0x34, 0xb2, 0x9c, 0xd0, 0x55, 0xa2, 0xcf, 0x84, 0x69, 0xb4, - 0x17, 0xba, 0x8c, 0xde, 0x81, 0xd5, 0xbb, 0x5e, 0x52, 0x74, 0xcf, 0x38, 0xfa, 0x7e, 0x39, 0x01, - 0x6b, 0x05, 0x56, 0x5a, 0x94, 0x10, 0x98, 0x0c, 0xec, 0x2c, 0x46, 0xf0, 0xb9, 0xa8, 0x69, 0x43, - 0xd3, 0x94, 0xaf, 0x9c, 0xb2, 0xf8, 0x20, 0x4c, 0x18, 0x06, 0xc0, 0xac, 0xa9, 0x5e, 0xc9, 0x0b, - 0x30, 0x3f, 0x48, 0xbc, 0xe0, 0xd0, 0x4a, 0xec, 0xc0, 0x3d, 0x08, 0x9f, 0xa1, 0xbb, 0x67, 0xcd, - 0x16, 0x02, 0x1f, 0x0b, 0x18, 0xb9, 0x02, 0xad, 0xa3, 0x34, 0x8d, 0x2c, 0x1e, 0x87, 0xe1, 0x20, - 0x95, 0xde, 0x6d, 0x72, 0xd8, 0x13, 0x01, 0x22, 0x57, 0x61, 0x01, 0x51, 0x06, 0x09, 0x8b, 0xed, - 0x43, 0x16, 0xa4, 0xdd, 0x69, 0x44, 0x9a, 0xe7, 0xd0, 0x8f, 0x14, 0x90, 0x6c, 0x01, 0x20, 0x5a, - 0x14, 0x87, 0xcf, 0xce, 0xbb, 0x33, 0xc2, 0xb6, 0x1c, 0xb2, 0xcf, 0x01, 0xe4, 0x45, 0x68, 0x1f, - 0xd8, 0x09, 0x53, 0x71, 0xe4, 0xb1, 0xa4, 0x3b, 0x8b, 0x38, 0x0b, 0x1c, 0xbc, 0x97, 0x41, 0xc9, - 0x0d, 0xe8, 0x24, 0x83, 0x28, 0x0a, 0xe3, 0x94, 0xb9, 0x96, 0x9d, 0x24, 0x2c, 0x4d, 0xba, 0x73, - 0x88, 0xd9, 0xce, 0xe0, 0xbb, 0x08, 0xe6, 0x1a, 0xaa, 0x63, 0x10, 0xd9, 0x5e, 0x9c, 0x74, 0x01, - 0xf1, 0x5a, 0x12, 0xb8, 0xcf, 0x61, 0x9c, 0x71, 0x7e, 0xb8, 0x04, 0x5a, 0x53, 0x30, 0xce, 0xc0, - 0x02, 0xf1, 0x25, 0x58, 0xb4, 0x07, 0xe9, 0x11, 0x0b, 0x52, 0xcf, 0xb1, 0x91, 0x79, 0xe4, 0x75, - 0x5b, 0x68, 0xb3, 0x8e, 0xb6, 0xb0, 0x1b, 0x79, 0xf4, 0x0c, 0x3a, 0x0f, 0x58, 0xfa, 0xc4, 0x73, - 0x8e, 0x59, 0x3c, 0x86, 0xc3, 0xc9, 0x75, 0x98, 0xe4, 0xbc, 0xd1, 0x7b, 0xcd, 0xdb, 0xcb, 0xb7, - 0x44, 0x56, 0xb9, 0xa5, 0x8e, 0x0e, 0x97, 0xc0, 0x44, 0x0c, 0x6e, 0x47, 0xd4, 0xda, 0x4a, 0xcf, - 0x23, 0xe1, 0xd3, 0x39, 0x73, 0x0e, 0x21, 0x4f, 0xce, 0x23, 0x46, 0x9f, 0x42, 0xab, 0xb8, 0x89, - 0x47, 0xb4, 0xcb, 0x7c, 0xef, 0xc4, 0x4b, 0x59, 0xac, 0x22, 0x3a, 0x03, 0xf0, 0x58, 0xe2, 0xe6, - 0x45, 0xb6, 0x73, 0x26, 0x3e, 0x93, 0x65, 0x98, 0xfa, 0x6c, 0x10, 0xa6, 0x8a, 0xb6, 0x78, 0xa1, - 0xbf, 0x68, 0xc0, 0x82, 0x52, 0x47, 0x06, 0xa2, 0x92, 0xd9, 0xb8, 0x50, 0xe6, 0x2b, 0xd0, 0xf2, - 0xed, 0x24, 0xb5, 0x06, 0x91, 0xcb, 0x0d, 0x24, 0x33, 0x57, 0x93, 0xc3, 0x3e, 0x12, 0x20, 0xee, - 0x2b, 0x95, 0x42, 0xd0, 0x0b, 0x92, 0x7b, 0xcb, 0x29, 0x2a, 0x43, 0x60, 0x92, 0xef, 0xc1, 0x48, - 0x35, 0x4c, 0x7c, 0xe6, 0xb0, 0x23, 0xef, 0xf0, 0x08, 0x23, 0xd3, 0x30, 0xf1, 0x99, 0x74, 0x60, - 0xc2, 0x0f, 0xcf, 0x30, 0x0e, 0x0d, 0x93, 0x3f, 0x72, 0xc8, 0x81, 0xe7, 0x62, 0xd8, 0x19, 0x26, - 0x7f, 0xe4, 0x10, 0x3b, 0x39, 0xc6, 0x20, 0x33, 0x4c, 0xfe, 0xc8, 0xd3, 0xef, 0x69, 0xe8, 0x0f, - 0x4e, 0x18, 0xc6, 0x93, 0x61, 0xca, 0x37, 0xb2, 0x01, 0x73, 0x51, 0xec, 0x39, 0xcc, 0xb2, 0xd3, - 0x23, 0x0c, 0x21, 0xc3, 0x9c, 0x45, 0xc0, 0x6e, 0x7a, 0x44, 0x97, 0x60, 0x31, 0x73, 0xb4, 0x4a, - 0x3c, 0xf4, 0x63, 0x98, 0x91, 0x90, 0x91, 0x4e, 0x7f, 0x15, 0x66, 0x52, 0x81, 0xd6, 0x6d, 0x6c, - 0x4f, 0x5c, 0x6f, 0xde, 0x5e, 0x55, 0x36, 0xd4, 0x2d, 0x6d, 0x2a, 0x34, 0xfa, 0x5d, 0x20, 0x45, - 0x6e, 0xd2, 0x11, 0x37, 0x72, 0x3a, 0x06, 0xd2, 0x69, 0xeb, 0x74, 0x92, 0x9c, 0xc0, 0xe7, 0x98, - 0x29, 0x1f, 0xc5, 0x2e, 0x4f, 0x02, 0xe1, 0xf1, 0xd7, 0x1a, 0x9a, 0x1f, 0xc0, 0x7c, 0xc6, 0xf8, - 0x61, 0xca, 0x4e, 0xb8, 0xc1, 0xed, 0x93, 0x70, 0x10, 0xa4, 0xc8, 0xd3, 0x30, 0xe5, 0x1b, 0x8f, - 0x40, 0xb4, 0x2f, 0xb2, 0x34, 0x4c, 0xf1, 0x42, 0x16, 0xa0, 0xe1, 0xb9, 0xf2, 0x16, 0x6b, 0x78, - 0x2e, 0xfd, 0xb7, 0x01, 0x8b, 0x05, 0x45, 0x9e, 0x3b, 0x28, 0x2b, 0x11, 0xd7, 0xa8, 0x89, 0xb8, - 0x1b, 0x30, 0x79, 0xe0, 0xb9, 0xfc, 0xf2, 0xe4, 0x76, 0x5d, 0x51, 0xe4, 0x34, 0x3d, 0x4c, 0x44, - 0xe1, 0xa8, 0x76, 0x72, 0x9c, 0x74, 0x27, 0x47, 0xa2, 0x72, 0x94, 0xca, 0x79, 0x98, 0xaa, 0x9e, - 0x07, 0xdd, 0x96, 0xd3, 0x65, 0x5b, 0xae, 0xe2, 0x05, 0x96, 0xd1, 0xce, 0x22, 0xcf, 0x01, 0xc8, - 0x81, 0x23, 0xdd, 0xfa, 0x6d, 0x80, 0x30, 0xc3, 0x94, 0xf1, 0xb7, 0x5e, 0x11, 0x3a, 0x0b, 0xc1, - 0x02, 0x32, 0x7d, 0x1f, 0xef, 0xc1, 0x22, 0x73, 0x69, 0xfc, 0xdb, 0x1a, 0x4d, 0x11, 0x8b, 0xa4, - 0x42, 0x33, 0xd1, 0x88, 0xbd, 0x8e, 0xc4, 0x76, 0x1d, 0x87, 0xbb, 0xbe, 0x50, 0x21, 0x8d, 0xbc, - 0x1f, 0x9f, 0xc2, 0x8c, 0xdc, 0x21, 0xc3, 0x42, 0x20, 0x34, 0x3c, 0x97, 0xbc, 0x05, 0x50, 0xb8, - 0x43, 0x84, 0x5e, 0x1b, 0x4a, 0x06, 0xb9, 0x49, 0x45, 0x03, 0xb2, 0x2b, 0xa0, 0xd3, 0x3e, 0x2c, - 0xd5, 0xa0, 0x70, 0x51, 0xb2, 0xfa, 0x46, 0x8a, 0xa2, 0xde, 0xc9, 0x65, 0x68, 0xa6, 0x61, 0x6a, - 0xfb, 0xd6, 0xa9, 0xed, 0x0f, 0x54, 0xc8, 0x02, 0x82, 0x9e, 0x72, 0x08, 0x26, 0xa8, 0xd0, 0x17, - 0x91, 0xcb, 0x13, 0x54, 0xe8, 0xbb, 0xd4, 0x86, 0xd5, 0xb2, 0xd2, 0xd2, 0x84, 0xa3, 0x5c, 0xf6, - 0x12, 0xcc, 0xda, 0x62, 0x8b, 0x52, 0xac, 0x5d, 0x52, 0xcc, 0xcc, 0x10, 0x28, 0xc1, 0x1b, 0x68, - 0x2f, 0x0c, 0xfa, 0xde, 0xa1, 0x8a, 0x8e, 0x17, 0x31, 0x59, 0x29, 0x58, 0x5e, 0x4f, 0xb8, 0x76, - 0x6a, 0x23, 0xb7, 0x96, 0x89, 0xcf, 0xf4, 0x27, 0x06, 0x74, 0xf6, 0xc3, 0x38, 0xed, 0x87, 0xbe, - 0x17, 0xee, 0xba, 0x6e, 0xcc, 0x92, 0x84, 0x97, 0x12, 0xb6, 0x78, 0x54, 0x45, 0x8e, 0x7c, 0xe5, - 0x19, 0xd2, 0x09, 0xbd, 0x40, 0xc4, 0x6a, 0x43, 0x1a, 0x28, 0xf4, 0x02, 0x1e, 0xaa, 0x64, 0x1b, - 0x9a, 0x2e, 0x4b, 0x9c, 0xd8, 0x8b, 0x78, 0x45, 0x2c, 0xd3, 0x42, 0x11, 0xc4, 0x09, 0x1f, 0xd8, - 0xbe, 0x1d, 0x38, 0x4c, 0x66, 0x76, 0xf5, 0x4a, 0x57, 0x30, 0x5d, 0x65, 0x92, 0x28, 0x3d, 0x3e, - 0xc4, 0xe8, 0x2f, 0x80, 0xa5, 0x2a, 0xdf, 0x84, 0xb9, 0x48, 0x01, 0x65, 0xf8, 0x75, 0x95, 0x85, - 0xca, 0xea, 0x98, 0x39, 0x2a, 0xdd, 0xe4, 0x85, 0x69, 0x4e, 0xef, 0xf1, 0xe0, 0xe4, 0xc4, 0x8e, - 0xcf, 0x15, 0xb7, 0x00, 0x26, 0xf7, 0x42, 0x2f, 0xe0, 0x86, 0xe2, 0x4a, 0xa9, 0xc2, 0x8b, 0x3f, - 0x17, 0x45, 0x6f, 0x68, 0xa2, 0x17, 0xad, 0x35, 0xa1, 0x5b, 0xeb, 0x12, 0x40, 0xc4, 0x62, 0x87, - 0x05, 0xa9, 0x7d, 0xa8, 0x34, 0x2e, 0x40, 0xe8, 0x11, 0x90, 0x47, 0xfd, 0xbe, 0xef, 0x05, 0x8c, - 0xb3, 0x95, 0xc2, 0x8c, 0xb0, 0xfe, 0x70, 0x19, 0x74, 0x4e, 0x13, 0x15, 0x4e, 0x1f, 0xc0, 0xe2, - 0xa3, 0xa0, 0x86, 0x91, 0x22, 0x67, 0x8c, 0x22, 0xd7, 0xa8, 0x90, 0x7b, 0x0f, 0x5a, 0x05, 0xc1, - 0x13, 0xf2, 0x06, 0xcc, 0x49, 0x19, 0x99, 0xca, 0x06, 0xbd, 0x2c, 0x1b, 0x54, 0x34, 0x34, 0x73, - 0x64, 0xfa, 0x4b, 0x03, 0x9a, 0xb9, 0x64, 0xbc, 0x47, 0x99, 0xe2, 0xe6, 0x56, 0x54, 0x2e, 0x65, - 0x54, 0x72, 0x9c, 0x5b, 0xf8, 0xf7, 0x5e, 0x90, 0xc6, 0xe7, 0xa6, 0x40, 0xee, 0x3d, 0x06, 0xc8, - 0x81, 0xfc, 0xc2, 0x3f, 0x66, 0xea, 0xfc, 0xf2, 0x47, 0xb2, 0x03, 0x53, 0xf9, 0xa1, 0x2d, 0x66, - 0xbf, 0xb2, 0x4d, 0x4c, 0x81, 0xf7, 0x66, 0xe3, 0x0d, 0x83, 0xfe, 0x71, 0x92, 0xf7, 0x22, 0x35, - 0xc1, 0x22, 0x63, 0xf0, 0x15, 0x68, 0x8a, 0xb3, 0xc0, 0x33, 0x80, 0x12, 0xb8, 0x95, 0xdd, 0x43, - 0xa1, 0x17, 0x98, 0x80, 0x67, 0x03, 0xd7, 0xc9, 0x6b, 0x30, 0x8f, 0xc2, 0x5a, 0xa1, 0x30, 0x88, - 0x3c, 0xd8, 0xfa, 0x86, 0x16, 0xa2, 0x48, 0x93, 0x91, 0x08, 0x56, 0xb4, 0x2d, 0x56, 0x22, 0x44, - 0x90, 0x97, 0xd4, 0xdb, 0x6a, 0xeb, 0x08, 0x29, 0x85, 0xb1, 0x24, 0x41, 0xb9, 0x26, 0x4c, 0xb7, - 0xe4, 0x54, 0x57, 0xc8, 0x0e, 0xb4, 0x24, 0x47, 0xb4, 0x8c, 0xbc, 0xe2, 0x74, 0x19, 0x9b, 0x62, - 0x23, 0x22, 0x90, 0x13, 0x58, 0x2e, 0x6e, 0xc8, 0x24, 0x9c, 0xc2, 0x8d, 0x6f, 0x8d, 0x2f, 0x61, - 0x50, 0x11, 0x90, 0x38, 0x95, 0x85, 0xde, 0x0f, 0xa0, 0x3b, 0x4c, 0xa1, 0x1a, 0xb7, 0xdf, 0xd4, - 0xdd, 0xbe, 0x5c, 0x13, 0x92, 0x49, 0xc1, 0xe3, 0xbd, 0x4f, 0x60, 0x6d, 0x88, 0x30, 0x35, 0xc4, - 0x6f, 0xe8, 0xc4, 0x97, 0x6a, 0x22, 0xb5, 0x18, 0x4d, 0x3f, 0x33, 0xa0, 0xb7, 0xeb, 0xba, 0x95, - 0xe4, 0x94, 0x77, 0xb0, 0x5f, 0x77, 0xca, 0xdd, 0x82, 0x8d, 0x5a, 0x81, 0x64, 0xab, 0xfd, 0x0c, - 0xb6, 0x4c, 0x76, 0x12, 0x9e, 0xb2, 0xaf, 0x5b, 0x64, 0xba, 0x0d, 0x97, 0x86, 0x71, 0x96, 0xb2, - 0xf5, 0xa0, 0xfb, 0x80, 0xe9, 0x73, 0x8a, 0xac, 0x30, 0xfa, 0x9b, 0x01, 0xf3, 0xfa, 0x04, 0xe3, - 0x7f, 0xd5, 0x47, 0xbf, 0x0c, 0x24, 0x66, 0x49, 0x6a, 0xc5, 0xa1, 0xef, 0xf3, 0x76, 0xda, 0x65, - 0xbe, 0x7d, 0x2e, 0x67, 0x27, 0x1d, 0xbe, 0x62, 0x8a, 0x85, 0xbb, 0x1c, 0x4e, 0xd6, 0x60, 0xc6, - 0x8e, 0x3c, 0x8b, 0x47, 0x8d, 0xe8, 0xa5, 0xa7, 0xed, 0xc8, 0x7b, 0x9f, 0x9d, 0x13, 0x0a, 0xf3, - 0x72, 0xc1, 0xf2, 0xd9, 0x29, 0xf3, 0xb1, 0xe6, 0x9b, 0x30, 0x9b, 0x62, 0xf9, 0xfb, 0x1c, 0xc4, - 0x7b, 0xdf, 0x28, 0xf6, 0x78, 0xf8, 0xe5, 0x43, 0x9a, 0x19, 0x94, 0xa6, 0x2d, 0xe1, 0x4a, 0x3b, - 0xfa, 0x29, 0xac, 0xd7, 0xd8, 0x42, 0xe6, 0xa8, 0x77, 0xa0, 0xad, 0x8f, 0x7a, 0x54, 0x9e, 0xca, - 0xaa, 0x56, 0x6d, 0xa3, 0xb9, 0xd0, 0xd7, 0xe8, 0xc8, 0xea, 0x13, 0x71, 0x4c, 0x3b, 0xcd, 0x06, - 0x2e, 0xf4, 0x33, 0x58, 0xce, 0x81, 0x7b, 0x61, 0x70, 0xca, 0xe2, 0x84, 0x47, 0x1b, 0x81, 0xc9, - 0x7e, 0x1c, 0x9e, 0x28, 0x53, 0xf3, 0x67, 0x5e, 0xb7, 0xa5, 0xa1, 0x0c, 0x83, 0x46, 0x1a, 0x72, - 0x9c, 0xd8, 0x4e, 0xd5, 0x2d, 0x85, 0xcf, 0xbc, 0x4e, 0xf6, 0x90, 0x08, 0xb3, 0x70, 0x4d, 0x84, - 0x6a, 0x53, 0xc2, 0x38, 0x17, 0xfa, 0x14, 0xcb, 0xc7, 0xa2, 0x28, 0x52, 0xc7, 0xef, 0x40, 0x53, - 0xe8, 0xc8, 0x77, 0x2a, 0xfd, 0x36, 0x35, 0xfd, 0x4a, 0x62, 0x9a, 0xd0, 0xcf, 0xa0, 0xf4, 0x1f, - 0x0d, 0x68, 0x61, 0xc5, 0x7a, 0x97, 0xa5, 0xb6, 0xe7, 0x8f, 0xae, 0xa5, 0x45, 0x0d, 0xda, 0xc8, - 0x6a, 0xd0, 0x17, 0x60, 0xbe, 0x38, 0xcc, 0x38, 0x57, 0xcd, 0x6c, 0x61, 0x94, 0x71, 0x4e, 0xae, - 0xc2, 0x02, 0xb6, 0xd6, 0x39, 0x96, 0x88, 0x99, 0x79, 0x84, 0x66, 0x68, 0x7a, 0x23, 0x30, 0x55, - 0x6a, 0x04, 0xf8, 0x32, 0x16, 0xd3, 0x56, 0xe2, 0xb9, 0x59, 0x9f, 0x80, 0x90, 0xc7, 0x9e, 0x5b, - 0x58, 0xc6, 0xdd, 0x33, 0x85, 0x65, 0xdc, 0xcd, 0x7b, 0xa0, 0x98, 0xe1, 0xa8, 0x12, 0x47, 0x3c, - 0xd8, 0x0e, 0x4f, 0x98, 0x2d, 0x05, 0x7c, 0xe2, 0x9d, 0xe0, 0x58, 0x32, 0x49, 0xed, 0x74, 0xa0, - 0xe6, 0x2c, 0xf2, 0x2d, 0x6f, 0xd3, 0xa0, 0xd8, 0xa6, 0xe5, 0x4d, 0x5d, 0x53, 0x6b, 0xea, 0x2e, - 0x43, 0x33, 0x8c, 0x58, 0x60, 0xc9, 0x16, 0xbb, 0x25, 0xaa, 0x07, 0x0e, 0x7a, 0x8a, 0x10, 0x39, - 0x32, 0x41, 0x9b, 0x27, 0xe3, 0xf4, 0xa5, 0xba, 0x61, 0x1a, 0x65, 0xc3, 0xa8, 0x46, 0x70, 0xe2, - 0xa2, 0x46, 0x90, 0xee, 0x62, 0x55, 0xac, 0x18, 0xcb, 0xf0, 0x79, 0x19, 0xa6, 0xd1, 0x4c, 0x2a, - 0x72, 0x96, 0xb5, 0x36, 0x46, 0x06, 0x85, 0x29, 0x71, 0xe8, 0x7b, 0x38, 0xcc, 0xc5, 0xa5, 0x71, - 0x44, 0x5f, 0x87, 0x59, 0xe1, 0x95, 0x2c, 0x6a, 0x66, 0xf0, 0xfd, 0xa1, 0x4b, 0xff, 0x6c, 0x00, - 0x79, 0x3c, 0x38, 0x38, 0xf1, 0xc6, 0xa7, 0x36, 0x7e, 0x83, 0x4e, 0x60, 0x12, 0xc3, 0x44, 0x84, - 0x23, 0x3e, 0x97, 0x22, 0x64, 0xb2, 0x1c, 0x21, 0xb9, 0x3b, 0xa7, 0xea, 0x7b, 0xf4, 0xe9, 0xa2, - 0xf3, 0x79, 0x8a, 0xf7, 0x3d, 0x16, 0xa4, 0x96, 0x1c, 0xb6, 0xf0, 0x14, 0x8f, 0x80, 0x87, 0x2e, - 0x7d, 0x0c, 0x4b, 0x9a, 0x66, 0xd2, 0xd2, 0x57, 0xa0, 0x25, 0x04, 0x88, 0x7c, 0xdb, 0xc9, 0x46, - 0xb5, 0x4d, 0x84, 0xed, 0x23, 0x68, 0x94, 0xbd, 0xfe, 0x6e, 0x00, 0xd9, 0xe3, 0x17, 0x97, 0x3f, - 0xb6, 0xbd, 0x78, 0xe0, 0x88, 0x2e, 0x29, 0xa7, 0x37, 0x27, 0x21, 0x0f, 0x75, 0x66, 0x13, 0x1a, - 0xb3, 0xcc, 0xd2, 0x93, 0xcf, 0x39, 0x0a, 0xa9, 0x9c, 0xda, 0xab, 0xb0, 0x70, 0x66, 0xfb, 0x3e, - 0x4b, 0x2d, 0x75, 0x57, 0xca, 0x99, 0xa9, 0x80, 0xaa, 0x8e, 0x4b, 0xf9, 0x6b, 0x26, 0xf7, 0x17, - 0x6f, 0x89, 0x34, 0x7d, 0xe5, 0xdd, 0x77, 0x07, 0x56, 0x05, 0x78, 0xd7, 0xf7, 0xc7, 0x3e, 0x43, - 0xf4, 0x57, 0x0d, 0x58, 0xab, 0x6c, 0xcb, 0x2e, 0x09, 0xfd, 0x04, 0x5c, 0xcb, 0xd4, 0xad, 0xdf, - 0x70, 0x4b, 0xbe, 0xca, 0x5d, 0xbd, 0xdf, 0x1b, 0x30, 0x2d, 0x40, 0x23, 0xbd, 0xf1, 0x89, 0x72, - 0xbf, 0xcc, 0x31, 0xa2, 0xfe, 0xfd, 0xd6, 0x78, 0xcc, 0xc4, 0xbf, 0xc7, 0xb8, 0x53, 0x94, 0x87, - 0x22, 0x6e, 0x04, 0xa4, 0xf7, 0x0e, 0x74, 0xca, 0x08, 0x35, 0x25, 0xdb, 0x72, 0xb1, 0x64, 0x9b, - 0x2b, 0x56, 0x67, 0xa2, 0x87, 0xbe, 0x77, 0xca, 0x82, 0x34, 0xbb, 0xe3, 0xbe, 0x34, 0xa0, 0xbd, - 0x17, 0x06, 0xae, 0xc7, 0xf3, 0xe3, 0xbe, 0x1d, 0xdb, 0x27, 0x09, 0xd9, 0xe4, 0x95, 0x8d, 0x04, - 0xa9, 0x21, 0x6b, 0x06, 0x18, 0x32, 0xce, 0xda, 0x02, 0x70, 0x8e, 0x98, 0x73, 0x6c, 0xc9, 0xf9, - 0x12, 0x0f, 0xfa, 0x39, 0x84, 0xbc, 0xeb, 0xb9, 0x09, 0x79, 0x05, 0x96, 0xf2, 0x65, 0xcb, 0x0e, - 0x5c, 0x4b, 0x0e, 0x97, 0x70, 0xde, 0x9c, 0xe1, 0xed, 0x06, 0xee, 0x6e, 0x72, 0x8c, 0x53, 0xf1, - 0x6c, 0xa6, 0x62, 0x69, 0x07, 0xb6, 0x9d, 0xc1, 0x77, 0x11, 0x4c, 0xff, 0x65, 0x60, 0xbe, 0x53, - 0x5a, 0x49, 0x6f, 0xe7, 0x63, 0x14, 0x9c, 0xae, 0x69, 0x2e, 0x6b, 0x94, 0x5c, 0x46, 0x60, 0xd2, - 0x4b, 0xd9, 0x89, 0x4a, 0x23, 0xfc, 0x99, 0xbc, 0x0b, 0x9d, 0x4c, 0x63, 0x2b, 0x42, 0xb3, 0xc8, - 0x63, 0xb2, 0x96, 0xb7, 0x09, 0x9a, 0xd5, 0xcc, 0xb6, 0x53, 0x32, 0xa3, 0x3a, 0x5e, 0x53, 0x17, - 0x1e, 0x2f, 0x9e, 0x95, 0x1c, 0xb4, 0xf6, 0xb4, 0x2c, 0xa2, 0xf0, 0x4d, 0x48, 0xcd, 0x9c, 0x41, - 0xca, 0x5c, 0x59, 0x18, 0x65, 0xef, 0xf4, 0xaf, 0x06, 0xb4, 0x77, 0x5d, 0x17, 0xf5, 0x1e, 0x27, - 0x4d, 0x28, 0x2d, 0x1b, 0x17, 0x68, 0x39, 0xf1, 0x5f, 0x6a, 0xf9, 0x95, 0x93, 0xc8, 0x10, 0x23, - 0x50, 0x0a, 0x9d, 0x5c, 0xcf, 0x7a, 0xf7, 0xd2, 0x6f, 0x00, 0x11, 0xc5, 0xb4, 0x66, 0x8e, 0x32, - 0xd6, 0x0a, 0x2c, 0x69, 0x58, 0x32, 0xd7, 0xdc, 0x87, 0xeb, 0x0f, 0x58, 0xba, 0x17, 0x9f, 0x47, - 0x69, 0xa8, 0x8a, 0x97, 0xbb, 0x2c, 0x0a, 0x13, 0x4f, 0x65, 0x2e, 0x36, 0x56, 0xf6, 0xf9, 0x83, - 0x01, 0x37, 0xc6, 0x20, 0x24, 0x55, 0xf8, 0x61, 0x75, 0x9a, 0xf0, 0xbd, 0x42, 0x23, 0x39, 0x1e, - 0x95, 0x5b, 0x19, 0x44, 0xa4, 0x8b, 0x9c, 0x64, 0xef, 0x6d, 0x58, 0xd0, 0x17, 0x9f, 0x2b, 0x55, - 0xf8, 0x70, 0xed, 0x02, 0x21, 0xc6, 0x89, 0xb9, 0x6b, 0xb0, 0xe0, 0x68, 0x24, 0x24, 0xa3, 0x12, - 0x94, 0xee, 0xc1, 0x8b, 0x17, 0x72, 0x93, 0x66, 0x1b, 0xda, 0x8f, 0xd1, 0xdf, 0x4e, 0xc2, 0xda, - 0xc7, 0x5e, 0x7a, 0xe4, 0xc6, 0xf6, 0x99, 0x8a, 0xbe, 0x71, 0x84, 0x2c, 0xb5, 0x6a, 0x8d, 0x6a, - 0x77, 0x79, 0x13, 0x16, 0xc3, 0x80, 0x61, 0x45, 0x69, 0x45, 0x76, 0x92, 0x9c, 0x85, 0xb1, 0xba, - 0x4b, 0xdb, 0x61, 0xc0, 0x78, 0x55, 0xb9, 0x2f, 0xc1, 0xa5, 0xdb, 0x78, 0xb2, 0x7c, 0x1b, 0x77, - 0x60, 0x22, 0xf2, 0x02, 0x39, 0x21, 0xe7, 0x8f, 0xfc, 0xee, 0x4c, 0x63, 0xdb, 0x2d, 0x50, 0x96, - 0x77, 0x27, 0x42, 0x33, 0xba, 0xc5, 0x99, 0xed, 0x4c, 0x69, 0x66, 0x5b, 0xb0, 0xc9, 0xac, 0xde, - 0xa3, 0x5e, 0x86, 0xa6, 0x7c, 0xb4, 0x52, 0xfb, 0x50, 0x16, 0xbc, 0x20, 0x41, 0x4f, 0xec, 0xc3, - 0x42, 0x3d, 0x04, 0x5a, 0x3d, 0xb4, 0x05, 0xd0, 0x67, 0xcc, 0xd2, 0x4a, 0xdf, 0xb9, 0x3e, 0x63, - 0x22, 0xe9, 0xf2, 0xc2, 0xe8, 0xc0, 0x0e, 0x8e, 0x2d, 0xec, 0x38, 0x5b, 0x42, 0x1c, 0x0e, 0xf8, - 0x90, 0x77, 0x9d, 0x57, 0xa0, 0x85, 0x8b, 0x4a, 0xa6, 0x79, 0x61, 0x51, 0x0e, 0xdb, 0xcd, 0x7b, - 0x67, 0x44, 0x71, 0xbc, 0xf4, 0xbc, 0xbb, 0x90, 0xef, 0xdf, 0xf3, 0xd2, 0xf3, 0x6c, 0x3f, 0xda, - 0x2c, 0x3e, 0xef, 0xb6, 0xf3, 0xfd, 0x7b, 0x02, 0xc4, 0xc5, 0x4b, 0xce, 0xbc, 0x3e, 0x13, 0xdf, - 0xa8, 0x3b, 0xc2, 0xca, 0x08, 0xd9, 0x0b, 0x5d, 0xec, 0x03, 0xce, 0xbc, 0xb8, 0xd0, 0x8a, 0x2c, - 0x8a, 0x86, 0x85, 0x03, 0xb3, 0xcf, 0xf7, 0x37, 0xa1, 0xa3, 0xc2, 0xa5, 0xf8, 0x93, 0x85, 0x98, - 0x25, 0x03, 0x3f, 0x55, 0x3f, 0x59, 0x10, 0x6f, 0xb7, 0x7f, 0xdd, 0x83, 0x85, 0x07, 0xa1, 0x08, - 0xd0, 0x27, 0xdc, 0x2f, 0x31, 0x79, 0x04, 0x33, 0xf2, 0x07, 0x0f, 0x64, 0xb5, 0x70, 0x6e, 0x0b, - 0x23, 0xff, 0xde, 0x5a, 0x05, 0x2e, 0x33, 0xce, 0xd2, 0x17, 0x7f, 0xfa, 0xcb, 0xcf, 0x1b, 0xf3, - 0xa4, 0xb9, 0x73, 0xfa, 0xda, 0xce, 0x21, 0x4b, 0x3d, 0x4e, 0xc5, 0x81, 0x56, 0xf1, 0x23, 0x3e, - 0xd9, 0x28, 0xec, 0x2e, 0xff, 0x16, 0xa0, 0xb7, 0x59, 0xbf, 0x28, 0xe9, 0x77, 0x91, 0x3e, 0x21, - 0x1d, 0x49, 0x3f, 0xfb, 0xe6, 0x4f, 0x3e, 0x87, 0x76, 0xe9, 0xfb, 0x3d, 0xa1, 0x39, 0xa9, 0x61, - 0x3f, 0x66, 0xe8, 0xbd, 0x30, 0x12, 0x47, 0x72, 0xbd, 0x84, 0x5c, 0xbb, 0x74, 0x89, 0x73, 0x75, - 0x05, 0x17, 0xc5, 0xf9, 0x4d, 0xe3, 0x26, 0x49, 0xb0, 0xab, 0x28, 0xfe, 0x08, 0x60, 0x2c, 0xde, - 0x97, 0x6b, 0x54, 0xd5, 0xac, 0xb9, 0x81, 0x7c, 0x57, 0xc8, 0x52, 0x49, 0x5b, 0xb4, 0x6a, 0x82, - 0x9f, 0x18, 0x0b, 0x3f, 0x72, 0xc0, 0x00, 0x19, 0x87, 0xef, 0x56, 0x0d, 0xdf, 0xfc, 0x47, 0x12, - 0xb4, 0x87, 0x5c, 0x97, 0x09, 0x29, 0x71, 0x0d, 0xd3, 0x88, 0x3c, 0x83, 0x85, 0x7b, 0xc1, 0xff, - 0xc7, 0xc8, 0x5b, 0xc8, 0x76, 0x8d, 0x22, 0x5b, 0x31, 0xcd, 0x29, 0xda, 0xf8, 0x63, 0x98, 0xcb, - 0xbe, 0xa8, 0x92, 0x6e, 0x41, 0x03, 0xed, 0xdb, 0x7d, 0x6f, 0xc8, 0x97, 0x59, 0x15, 0x38, 0x74, - 0x5e, 0x2a, 0x25, 0xbe, 0xb3, 0x72, 0xc2, 0x9f, 0x02, 0xe4, 0x9f, 0x6a, 0xc9, 0x7a, 0x85, 0x72, - 0x16, 0x99, 0xbd, 0xba, 0x25, 0x49, 0x7e, 0x15, 0xc9, 0x77, 0xc8, 0x82, 0x46, 0x3e, 0x91, 0xa1, - 0x9f, 0x7d, 0x51, 0xd3, 0x42, 0xbf, 0xfc, 0x71, 0xb7, 0x37, 0xfc, 0xab, 0x9e, 0x8a, 0x04, 0xaa, - 0xe2, 0x3e, 0xab, 0x15, 0xb9, 0x06, 0x87, 0x30, 0xaf, 0x7d, 0xe6, 0x23, 0x9b, 0x75, 0x5c, 0x92, - 0x3a, 0xf7, 0x57, 0xbf, 0x0d, 0xd2, 0x75, 0x64, 0xb5, 0x44, 0x16, 0xcb, 0xac, 0x12, 0x72, 0x8c, - 0xbf, 0x8e, 0x2a, 0x7c, 0x0d, 0x23, 0x45, 0x5a, 0xd5, 0x4f, 0x83, 0xbd, 0x4b, 0xc3, 0x96, 0x93, - 0xfa, 0x50, 0x93, 0xd7, 0x09, 0xc6, 0xb7, 0x70, 0xb8, 0xf8, 0x06, 0xa6, 0x39, 0x5c, 0xfb, 0x54, - 0xd6, 0x5b, 0xaf, 0x59, 0x91, 0xd4, 0x57, 0x90, 0x7a, 0x9b, 0x28, 0x9f, 0x3b, 0x82, 0x96, 0xf0, - 0x49, 0x36, 0x9c, 0xd4, 0x7c, 0x52, 0xfe, 0x82, 0xa5, 0xa5, 0xa3, 0xca, 0x77, 0xac, 0x4a, 0x3a, - 0xca, 0xbe, 0x54, 0x91, 0x1f, 0xeb, 0x1f, 0xc4, 0xd4, 0x80, 0x9e, 0x8e, 0x9c, 0xa8, 0x57, 0x4e, - 0xcb, 0xd0, 0xa9, 0x3b, 0xbd, 0x8c, 0x9c, 0xd7, 0xc9, 0x5a, 0x99, 0xb3, 0x9c, 0xe0, 0x93, 0x2f, - 0x0c, 0x58, 0xaa, 0x99, 0x0f, 0xe7, 0x12, 0x0c, 0x9f, 0x66, 0xe7, 0x12, 0x8c, 0x1a, 0x30, 0x53, - 0x94, 0x60, 0x93, 0xa2, 0x04, 0xb6, 0xeb, 0x66, 0x12, 0xc8, 0xdb, 0x91, 0x47, 0xe6, 0x4f, 0x0d, - 0x58, 0xad, 0x9f, 0x05, 0x93, 0xab, 0x8a, 0xc7, 0xc8, 0x29, 0x75, 0xef, 0xda, 0x45, 0x68, 0x52, - 0x9a, 0xab, 0x28, 0xcd, 0x65, 0xda, 0xe3, 0xd2, 0xc4, 0x88, 0x5b, 0x27, 0xd0, 0x19, 0xb6, 0x54, - 0xfa, 0xb4, 0x95, 0x6c, 0x17, 0x0c, 0x5e, 0x3b, 0x94, 0xee, 0x5d, 0x19, 0x81, 0xa1, 0xa7, 0x2f, - 0xb2, 0x22, 0x1d, 0x82, 0x23, 0xca, 0x6c, 0x6c, 0x2b, 0xcf, 0x68, 0x3e, 0xcd, 0xd4, 0xce, 0x68, - 0x65, 0x40, 0xab, 0x9d, 0xd1, 0xea, 0xcc, 0xb4, 0x72, 0x46, 0x91, 0x19, 0xce, 0x4f, 0xc9, 0x27, - 0x78, 0x6c, 0x64, 0x3f, 0xdf, 0x2d, 0x1f, 0xf5, 0xa4, 0xee, 0xd8, 0xe8, 0x1d, 0x7b, 0x25, 0x55, - 0x8a, 0x31, 0x01, 0xb7, 0x9e, 0x09, 0xb3, 0x0a, 0x9d, 0xac, 0x95, 0x09, 0x28, 0xca, 0xb5, 0x03, - 0x38, 0xba, 0x86, 0x44, 0x17, 0x69, 0xab, 0x48, 0x94, 0xd3, 0x3c, 0x80, 0x66, 0x61, 0xd8, 0x44, - 0xb2, 0x24, 0x5b, 0x9d, 0xad, 0xf5, 0x36, 0x6a, 0xd7, 0xf4, 0x54, 0x42, 0xdb, 0x9c, 0x41, 0x82, - 0x08, 0x45, 0x1e, 0x85, 0x51, 0x4c, 0xce, 0xa3, 0x3a, 0x8f, 0xca, 0x79, 0xd4, 0xcd, 0x6e, 0x34, - 0x1e, 0x0e, 0x22, 0x64, 0x3c, 0x62, 0x68, 0x97, 0x46, 0x20, 0xe4, 0xd2, 0xd0, 0xd9, 0x48, 0xe9, - 0xfe, 0x1f, 0x32, 0x3b, 0xd1, 0xeb, 0x0e, 0xc1, 0xcf, 0xf6, 0xfd, 0xdc, 0x1f, 0x22, 0x45, 0x8a, - 0x01, 0x81, 0xe6, 0x6b, 0x6d, 0x12, 0xa2, 0xf9, 0x5a, 0x9f, 0x26, 0x54, 0x52, 0x24, 0x13, 0xb4, - 0x9e, 0xc2, 0xac, 0xea, 0x4c, 0x73, 0x47, 0x97, 0x7a, 0xf2, 0x5e, 0xb7, 0xba, 0x20, 0xa9, 0x6a, - 0xce, 0xb6, 0x5d, 0x17, 0xa9, 0x4a, 0x47, 0x14, 0xfa, 0xd4, 0xdc, 0x11, 0xd5, 0x16, 0x37, 0x77, - 0x44, 0x5d, 0x63, 0xab, 0x39, 0x42, 0x9c, 0xf6, 0x8c, 0xc7, 0xef, 0x0c, 0xb8, 0x72, 0x61, 0x9b, - 0x49, 0x5e, 0x7d, 0x8e, 0x8e, 0x54, 0x08, 0xf4, 0xda, 0x73, 0xf7, 0xb0, 0xf4, 0x3a, 0x8a, 0x49, - 0xe9, 0x96, 0xba, 0x80, 0x70, 0x9b, 0x2b, 0xd0, 0xb3, 0x86, 0x96, 0x0b, 0xfd, 0x1b, 0x03, 0x2e, - 0x5f, 0x40, 0x97, 0xdc, 0x1a, 0x53, 0x00, 0x25, 0xf0, 0xce, 0xd8, 0xf8, 0x52, 0xdc, 0x6b, 0x28, - 0xee, 0x36, 0xdd, 0x18, 0x21, 0x2e, 0x17, 0xf6, 0x47, 0xb0, 0x91, 0xb5, 0xa3, 0x1a, 0xdd, 0xfb, - 0x83, 0xc0, 0x4d, 0x48, 0x16, 0xd6, 0x43, 0x7a, 0xd6, 0x3c, 0x70, 0xca, 0x5d, 0x8a, 0x7e, 0xa7, - 0x9c, 0xc9, 0x55, 0x21, 0x46, 0x9f, 0xd3, 0xe6, 0xdc, 0x23, 0x58, 0x54, 0xfb, 0xee, 0x7b, 0x76, - 0xfa, 0x95, 0x79, 0x6e, 0x23, 0xcf, 0x1e, 0x5d, 0x29, 0xf2, 0xec, 0x7b, 0x76, 0xaa, 0x38, 0x1e, - 0x4c, 0xe3, 0x6f, 0xc3, 0x5f, 0xff, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6e, 0xa0, 0xe9, 0x5c, - 0x4e, 0x2e, 0x00, 0x00, + // 3585 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3a, 0x4b, 0x6f, 0xe4, 0xc6, + 0xd1, 0xe0, 0xe8, 0x5d, 0x33, 0xd2, 0x8c, 0x5a, 0xaf, 0xd1, 0x48, 0xda, 0x07, 0xfd, 0xed, 0x7a, + 0x77, 0x6d, 0x4b, 0xf6, 0x7a, 0xf1, 0x7d, 0xfe, 0x6c, 0xc7, 0x89, 0xac, 0x7d, 0x78, 0xe3, 0xd8, + 0x2b, 0x70, 0xd7, 0x6b, 0xc0, 0x0e, 0x32, 0xa0, 0xc8, 0x1e, 0x89, 0x10, 0x45, 0xd2, 0x64, 0x8f, + 0xb4, 0x32, 0x02, 0x04, 0x30, 0x90, 0x6b, 0x72, 0x08, 0x12, 0xe4, 0x90, 0x53, 0x8e, 0x01, 0x72, + 0xc9, 0x0f, 0x30, 0x72, 0x0d, 0x72, 0xcc, 0x25, 0x3f, 0x20, 0xc8, 0x2d, 0x09, 0x72, 0xc8, 0x25, + 0xa7, 0xa0, 0xab, 0x1f, 0x64, 0x0f, 0x39, 0xa3, 0xd9, 0x38, 0xf1, 0x45, 0x1a, 0x56, 0x57, 0xd7, + 0xbb, 0xab, 0xab, 0x8a, 0x84, 0xb9, 0x34, 0xf1, 0xb6, 0x93, 0x34, 0x66, 0x31, 0x99, 0x3e, 0xf4, + 0x58, 0x9a, 0x78, 0x9d, 0xcd, 0xc3, 0x38, 0x3e, 0x0c, 0xe9, 0x8e, 0x9b, 0x04, 0x3b, 0x6e, 0x14, + 0xc5, 0xcc, 0x65, 0x41, 0x1c, 0x65, 0x02, 0xcb, 0x6e, 0xc1, 0xc2, 0x03, 0xca, 0x1e, 0x46, 0xbd, + 0xd8, 0xa1, 0x9f, 0xf5, 0x69, 0xc6, 0xec, 0xbf, 0x5b, 0xd0, 0xd4, 0xa0, 0x2c, 0x89, 0xa3, 0x8c, + 0x92, 0x55, 0x98, 0xee, 0x27, 0x2c, 0x38, 0xa1, 0x6d, 0xeb, 0x8a, 0x75, 0x63, 0xce, 0x91, 0x4f, + 0x64, 0x07, 0x96, 0xdc, 0x53, 0x37, 0x08, 0xdd, 0x83, 0x90, 0x76, 0xe9, 0x33, 0xef, 0xc8, 0x8d, + 0x0e, 0x69, 0xd6, 0xae, 0x5d, 0xb1, 0x6e, 0x4c, 0x38, 0x44, 0x2f, 0xdd, 0x53, 0x2b, 0xe4, 0x25, + 0x58, 0xa4, 0x11, 0x07, 0xf9, 0x05, 0xf4, 0x09, 0x44, 0x6f, 0xc9, 0x85, 0x1c, 0xf9, 0x0e, 0xac, + 0xfa, 0xb4, 0xe7, 0xf6, 0x43, 0xd6, 0xed, 0xc5, 0x29, 0x7d, 0xd6, 0x4d, 0xd2, 0xf8, 0x34, 0xf0, + 0x69, 0xda, 0x9e, 0x44, 0x29, 0x96, 0xe5, 0xea, 0x7d, 0xbe, 0xb8, 0x2f, 0xd7, 0xc8, 0x6d, 0x58, + 0xd1, 0xbb, 0x02, 0x97, 0x75, 0xbd, 0x7e, 0x9a, 0xd2, 0xc8, 0x3b, 0x6f, 0x4f, 0xe1, 0xa6, 0x25, + 0xb5, 0x29, 0x70, 0xd9, 0x9e, 0x5c, 0xb2, 0xdf, 0x80, 0xce, 0x03, 0x1a, 0xd1, 0x34, 0xf0, 0x14, + 0xf7, 0x0f, 0xdd, 0x13, 0x2a, 0x2d, 0x42, 0x3a, 0x30, 0xab, 0x84, 0x95, 0xfa, 0xeb, 0x67, 0x7b, + 0x0b, 0x36, 0x2a, 0x77, 0x0a, 0xc3, 0xd9, 0x3b, 0xb0, 0xf4, 0x80, 0x32, 0xad, 0x92, 0xa2, 0xd8, + 0x86, 0x19, 0xa9, 0x2d, 0x12, 0x9c, 0x75, 0xd4, 0xa3, 0x7d, 0x07, 0x96, 0xcd, 0x0d, 0xd2, 0x03, + 0x9b, 0x30, 0x97, 0x1b, 0x4c, 0x08, 0x91, 0x03, 0xec, 0xdb, 0xb0, 0x52, 0xd8, 0xf5, 0xe8, 0xc9, + 0xbe, 0x43, 0xc5, 0xb6, 0x75, 0x98, 0x8d, 0x59, 0xd2, 0xf5, 0x62, 0x5f, 0x89, 0x3e, 0x13, 0xb3, + 0x64, 0x2f, 0xf6, 0xa9, 0xdd, 0x86, 0x55, 0x73, 0x8f, 0x92, 0xce, 0xfe, 0xa5, 0x05, 0x6b, 0xa5, + 0x25, 0x29, 0xc7, 0xb7, 0x61, 0x4e, 0x11, 0xe4, 0x72, 0x4c, 0xdc, 0xa8, 0xdf, 0x7e, 0x65, 0x5b, + 0x44, 0xda, 0xf6, 0x90, 0x3d, 0xdb, 0x8f, 0x04, 0xc7, 0xec, 0x5e, 0xc4, 0xd2, 0x73, 0x67, 0x56, + 0x0a, 0x90, 0x75, 0xde, 0x82, 0x79, 0x63, 0x89, 0xb4, 0x60, 0xe2, 0x98, 0x9e, 0x4b, 0x41, 0xf9, + 0x4f, 0xb2, 0x0c, 0x53, 0xa7, 0x6e, 0xd8, 0xa7, 0x18, 0x52, 0x73, 0x8e, 0x78, 0x78, 0xb3, 0xf6, + 0x86, 0x65, 0xdf, 0x81, 0xd5, 0xbb, 0x41, 0x56, 0x8c, 0xae, 0x71, 0xdc, 0xf5, 0xe5, 0x84, 0xa1, + 0x9a, 0x11, 0xe4, 0x04, 0x26, 0x23, 0x57, 0x87, 0x38, 0xfe, 0x2e, 0x3a, 0xaa, 0x66, 0x38, 0x8a, + 0xaf, 0x9c, 0xd2, 0xf4, 0x20, 0xce, 0x28, 0xc6, 0xef, 0xac, 0xa3, 0x1e, 0xc9, 0x0b, 0x30, 0xdf, + 0xcf, 0x82, 0xe8, 0xb0, 0x9b, 0xb9, 0x91, 0x7f, 0x10, 0x3f, 0xc3, 0x68, 0x9d, 0x75, 0x1a, 0x08, + 0x7c, 0x2c, 0x60, 0xe4, 0x2a, 0x34, 0x8e, 0x18, 0x4b, 0xba, 0xfc, 0x18, 0xc5, 0x7d, 0x26, 0x83, + 0xb3, 0xce, 0x61, 0x4f, 0x04, 0x88, 0x5c, 0x83, 0x05, 0x44, 0xe9, 0x67, 0x34, 0x75, 0x0f, 0x69, + 0xc4, 0xda, 0xd3, 0x88, 0x34, 0xcf, 0xa1, 0x1f, 0x29, 0x20, 0xd9, 0x02, 0x40, 0xb4, 0x24, 0x8d, + 0x9f, 0x9d, 0xb7, 0x67, 0x44, 0x68, 0x70, 0xc8, 0x3e, 0x07, 0x90, 0x17, 0xa1, 0x79, 0xe0, 0x66, + 0x54, 0x1d, 0x83, 0x80, 0x66, 0xed, 0x59, 0xc4, 0x59, 0xe0, 0xe0, 0x3d, 0x0d, 0x25, 0x37, 0xa1, + 0x95, 0xf5, 0x93, 0x24, 0x4e, 0x19, 0xf5, 0xbb, 0x6e, 0x96, 0x51, 0x96, 0xb5, 0xe7, 0x10, 0xb3, + 0xa9, 0xe1, 0xbb, 0x08, 0xe6, 0x1a, 0xaa, 0x53, 0x9c, 0xb8, 0x41, 0x9a, 0xb5, 0x01, 0xf1, 0x1a, + 0x12, 0xb8, 0xcf, 0x61, 0x9c, 0x71, 0x9e, 0x1b, 0x04, 0x5a, 0x5d, 0x30, 0xd6, 0x60, 0x81, 0xf8, + 0x12, 0x2c, 0xba, 0x7d, 0x76, 0x44, 0x23, 0x16, 0x78, 0x2e, 0x32, 0x4f, 0x82, 0x76, 0x03, 0x6d, + 0xd6, 0x32, 0x16, 0x76, 0x93, 0xc0, 0x3e, 0x83, 0xd6, 0x03, 0xca, 0x9e, 0x04, 0xde, 0x31, 0x4d, + 0xc7, 0x70, 0x38, 0xb9, 0x01, 0x93, 0x9c, 0x37, 0x7a, 0xaf, 0x7e, 0x7b, 0x59, 0x85, 0xaa, 0x3a, + 0xf9, 0x5c, 0x02, 0x07, 0x31, 0xb8, 0x1d, 0x51, 0xeb, 0x2e, 0x3b, 0x4f, 0x84, 0x4f, 0xe7, 0x9c, + 0x39, 0x84, 0x3c, 0x39, 0x4f, 0xa8, 0xfd, 0x14, 0x1a, 0xc5, 0x4d, 0xfc, 0x40, 0xfa, 0x34, 0x0c, + 0x4e, 0x02, 0x46, 0x53, 0x75, 0x20, 0x35, 0x80, 0xc7, 0x12, 0x37, 0xaf, 0x0c, 0x5b, 0xfc, 0xcd, + 0x63, 0xf9, 0xb3, 0x7e, 0xcc, 0x14, 0x6d, 0xf1, 0x60, 0xff, 0xb4, 0x06, 0x0b, 0x4a, 0x1d, 0x19, + 0x88, 0x4a, 0x66, 0xeb, 0x42, 0x99, 0xaf, 0x42, 0x23, 0x74, 0x33, 0xd6, 0xed, 0x27, 0x3e, 0x37, + 0x90, 0x4c, 0xbc, 0x75, 0x0e, 0xfb, 0x48, 0x80, 0xb8, 0xaf, 0x54, 0x06, 0x44, 0x2f, 0x48, 0xee, + 0x0d, 0xaf, 0xa8, 0x0c, 0x81, 0x49, 0xbe, 0x07, 0x23, 0xd5, 0x72, 0xf0, 0x37, 0x87, 0x1d, 0x05, + 0x87, 0x47, 0x18, 0x99, 0x96, 0x83, 0xbf, 0xf9, 0x01, 0x0d, 0xe3, 0x33, 0x8c, 0x43, 0xcb, 0xe1, + 0x3f, 0x39, 0xe4, 0x20, 0xf0, 0x31, 0xec, 0x2c, 0x87, 0xff, 0xe4, 0x10, 0x37, 0x3b, 0xc6, 0x20, + 0xb3, 0x1c, 0xfe, 0x93, 0xdf, 0x1e, 0xa7, 0x71, 0xd8, 0x3f, 0xa1, 0x18, 0x4f, 0x96, 0x23, 0x9f, + 0xc8, 0x06, 0xcc, 0x25, 0x69, 0xe0, 0xd1, 0xae, 0xcb, 0x8e, 0x30, 0x84, 0x2c, 0x67, 0x16, 0x01, + 0xbb, 0xec, 0xc8, 0x5e, 0x82, 0x45, 0xed, 0x68, 0x9d, 0x99, 0x3e, 0x86, 0x19, 0x09, 0x19, 0xe9, + 0xf4, 0x57, 0x61, 0x86, 0x09, 0xb4, 0x76, 0x0d, 0x53, 0xd4, 0xaa, 0xb2, 0xa1, 0x69, 0x69, 0x47, + 0xa1, 0xd9, 0xdf, 0x04, 0x52, 0xe4, 0x26, 0x1d, 0x71, 0x33, 0xa7, 0x23, 0x52, 0x5d, 0xd3, 0xa4, + 0x93, 0xe5, 0x04, 0x3e, 0xc7, 0x44, 0xff, 0x28, 0xf5, 0x79, 0x12, 0x88, 0x8f, 0xbf, 0xd6, 0xd0, + 0xfc, 0x00, 0xe6, 0x35, 0xe3, 0x87, 0x8c, 0x9e, 0x70, 0x83, 0xbb, 0x27, 0x71, 0x3f, 0x62, 0xc8, + 0xd3, 0x72, 0xe4, 0x13, 0x8f, 0x40, 0xb4, 0x2f, 0xb2, 0xb4, 0x1c, 0xf1, 0x40, 0x16, 0xa0, 0x16, + 0xf8, 0xf2, 0x12, 0xae, 0x05, 0xbe, 0xfd, 0x4f, 0x0b, 0x16, 0x0b, 0x8a, 0x3c, 0x77, 0x50, 0x96, + 0x22, 0xae, 0x56, 0x11, 0x71, 0x37, 0x61, 0xf2, 0x20, 0xf0, 0xf9, 0xdd, 0xcf, 0xed, 0xba, 0xa2, + 0xc8, 0x19, 0x7a, 0x38, 0x88, 0xc2, 0x51, 0xdd, 0xec, 0x38, 0x6b, 0x4f, 0x8e, 0x44, 0xe5, 0x28, + 0xa5, 0xf3, 0x30, 0x55, 0x3e, 0x0f, 0xa6, 0x2d, 0xa7, 0x07, 0x6d, 0xb9, 0x8a, 0xf7, 0xaf, 0xa6, + 0xad, 0x23, 0xcf, 0x03, 0xc8, 0x81, 0x23, 0xdd, 0xfa, 0xff, 0x00, 0xb1, 0xc6, 0x94, 0xf1, 0xb7, + 0x5e, 0x12, 0x5a, 0x87, 0x60, 0x01, 0xd9, 0x7e, 0x1f, 0xaf, 0xf1, 0x22, 0x73, 0x69, 0xfc, 0xdb, + 0x06, 0x4d, 0x11, 0x8b, 0xa4, 0x44, 0x33, 0x33, 0x88, 0xbd, 0x8e, 0xc4, 0x76, 0x3d, 0x8f, 0xbb, + 0xbe, 0x50, 0xe0, 0x8d, 0xbc, 0x1f, 0x9f, 0xc2, 0x8c, 0xdc, 0x21, 0xc3, 0x42, 0x20, 0xd4, 0x02, + 0x9f, 0xbc, 0x05, 0x50, 0xb8, 0x43, 0x84, 0x5e, 0x1b, 0x4a, 0x06, 0xb9, 0x49, 0x45, 0x03, 0xb2, + 0x2b, 0xa0, 0xdb, 0x3d, 0x58, 0xaa, 0x40, 0xe1, 0xa2, 0xe8, 0xf2, 0x4c, 0x8a, 0xa2, 0x9e, 0xc9, + 0x65, 0xa8, 0xb3, 0x98, 0xb9, 0x61, 0x37, 0x2f, 0x00, 0x2c, 0x07, 0x10, 0xf4, 0x94, 0x43, 0x30, + 0x41, 0xc5, 0xa1, 0x88, 0x5c, 0x9e, 0xa0, 0xe2, 0xd0, 0xb7, 0x5d, 0x2c, 0x6a, 0x0c, 0xa5, 0xa5, + 0x09, 0x47, 0xb9, 0xec, 0x25, 0x98, 0x75, 0xc5, 0x16, 0xa5, 0x58, 0x73, 0x40, 0x31, 0x47, 0x23, + 0xd8, 0x04, 0x6f, 0xa0, 0xbd, 0x38, 0xea, 0x05, 0x87, 0x2a, 0x3a, 0x5e, 0xc4, 0x64, 0xa5, 0x60, + 0x79, 0x3d, 0xe1, 0xbb, 0xcc, 0x45, 0x6e, 0x0d, 0x07, 0x7f, 0xdb, 0x3f, 0xb4, 0xa0, 0xb5, 0x1f, + 0xa7, 0xac, 0x17, 0x87, 0x41, 0xbc, 0xeb, 0xfb, 0x29, 0xcd, 0x32, 0x5e, 0x4a, 0xb8, 0xe2, 0xa7, + 0xaa, 0xd1, 0xe4, 0x23, 0xcf, 0x90, 0x5e, 0x1c, 0x44, 0x22, 0x56, 0x6b, 0xd2, 0x40, 0x71, 0x10, + 0xf1, 0x50, 0x25, 0x57, 0xa0, 0xee, 0xd3, 0xcc, 0x4b, 0x83, 0x84, 0x17, 0xf4, 0x32, 0x2d, 0x14, + 0x41, 0x9c, 0xf0, 0x81, 0x1b, 0xba, 0x91, 0x47, 0x65, 0x66, 0x57, 0x8f, 0xf6, 0x0a, 0xa6, 0x2b, + 0x2d, 0x89, 0xd2, 0xe3, 0x43, 0x8c, 0xfe, 0x02, 0x58, 0xaa, 0xf2, 0xbf, 0x30, 0x97, 0x28, 0xa0, + 0x0c, 0xbf, 0xb6, 0xb2, 0xd0, 0xa0, 0x3a, 0x4e, 0x8e, 0x6a, 0x6f, 0xf2, 0xba, 0x3a, 0xa7, 0xf7, + 0xb8, 0x7f, 0x72, 0xe2, 0xa6, 0xe7, 0x8a, 0x5b, 0x04, 0x93, 0x7b, 0x71, 0x10, 0x71, 0x43, 0x71, + 0xa5, 0x54, 0xe1, 0xc5, 0x7f, 0x17, 0x45, 0xaf, 0x19, 0xa2, 0x17, 0xad, 0x35, 0x61, 0x5a, 0xeb, + 0x12, 0x40, 0x42, 0x53, 0x8f, 0x46, 0xcc, 0x3d, 0x54, 0x1a, 0x17, 0x20, 0xf6, 0x11, 0x90, 0x47, + 0xbd, 0x5e, 0x18, 0x44, 0x94, 0xb3, 0x95, 0xc2, 0x8c, 0xb0, 0xfe, 0x70, 0x19, 0x4c, 0x4e, 0x13, + 0x25, 0x4e, 0x1f, 0xc0, 0xe2, 0xa3, 0xa8, 0x82, 0x91, 0x22, 0x67, 0x8d, 0x22, 0x57, 0x2b, 0x91, + 0x7b, 0x0f, 0x1a, 0x05, 0xc1, 0x33, 0xf2, 0x06, 0xcc, 0x49, 0x19, 0x75, 0x11, 0xde, 0xd1, 0xd9, + 0xa0, 0xa4, 0xa1, 0x93, 0x23, 0xdb, 0x3f, 0xb7, 0xa0, 0x9e, 0x4b, 0xc6, 0x5b, 0xac, 0x29, 0x6e, + 0x6e, 0x45, 0xe5, 0x92, 0xa6, 0x92, 0xe3, 0x6c, 0xe3, 0x5f, 0x51, 0xbb, 0x0b, 0xe4, 0xce, 0x63, + 0x80, 0x1c, 0x58, 0x51, 0xb5, 0xef, 0x14, 0xab, 0xf6, 0x62, 0xf6, 0x1b, 0xb4, 0x49, 0xb1, 0xa0, + 0xff, 0xfd, 0x24, 0x6f, 0xa5, 0x2a, 0x82, 0x45, 0xc6, 0xe0, 0x2b, 0x50, 0x17, 0x67, 0x81, 0x67, + 0x00, 0x25, 0x70, 0x43, 0xdf, 0x43, 0x71, 0x10, 0x39, 0x80, 0x67, 0x03, 0xd7, 0xc9, 0x6b, 0x30, + 0x8f, 0xc2, 0x76, 0x63, 0x61, 0x10, 0x79, 0xb0, 0xcd, 0x0d, 0x0d, 0x44, 0x91, 0x26, 0x23, 0x09, + 0xac, 0x18, 0x5b, 0xba, 0x99, 0x10, 0x41, 0x5e, 0x52, 0x6f, 0x17, 0xfa, 0x9c, 0x61, 0x52, 0x0a, + 0x63, 0x49, 0x82, 0x72, 0x4d, 0x98, 0x6e, 0xc9, 0x2b, 0xaf, 0x90, 0x1d, 0x68, 0x48, 0x8e, 0x68, + 0x19, 0x79, 0xc5, 0x99, 0x32, 0xd6, 0xc5, 0x46, 0x44, 0x20, 0x27, 0xb0, 0x5c, 0xdc, 0xa0, 0x25, + 0x9c, 0xc2, 0x8d, 0x6f, 0x8d, 0x2f, 0x61, 0x54, 0x12, 0x90, 0x78, 0xa5, 0x85, 0xce, 0x77, 0xa1, + 0x3d, 0x4c, 0xa1, 0x0a, 0xb7, 0xdf, 0x32, 0xdd, 0xbe, 0x5c, 0x11, 0x92, 0x59, 0xc1, 0xe3, 0x9d, + 0x4f, 0x60, 0x6d, 0x88, 0x30, 0x15, 0xc4, 0x6f, 0x9a, 0xc4, 0x97, 0x2a, 0x22, 0xb5, 0x18, 0x4d, + 0x3f, 0xb6, 0xa0, 0xb3, 0xeb, 0xfb, 0xa5, 0xe4, 0x94, 0x37, 0xe0, 0x5f, 0x77, 0xca, 0xdd, 0x82, + 0x8d, 0x4a, 0x81, 0xe4, 0xa4, 0xe0, 0x19, 0x6c, 0x39, 0xf4, 0x24, 0x3e, 0xa5, 0x5f, 0xb7, 0xc8, + 0xf6, 0x15, 0xb8, 0x34, 0x8c, 0xb3, 0x94, 0xad, 0x03, 0xed, 0x07, 0xd4, 0x1c, 0xb3, 0xe8, 0xc2, + 0xe8, 0x2f, 0x16, 0xcc, 0x9b, 0x03, 0x98, 0xff, 0x54, 0x1f, 0xfd, 0x32, 0x90, 0x94, 0x66, 0xac, + 0x9b, 0xc6, 0x61, 0xc8, 0xdb, 0x69, 0x9f, 0x86, 0xee, 0xb9, 0x1c, 0xfd, 0xb4, 0xf8, 0x8a, 0x23, + 0x16, 0xee, 0x72, 0x38, 0x59, 0x83, 0x19, 0x37, 0x09, 0xba, 0x3c, 0x6a, 0x44, 0x2f, 0x3d, 0xed, + 0x26, 0xc1, 0xfb, 0xf4, 0x9c, 0xd8, 0x30, 0x2f, 0x17, 0xba, 0x21, 0x3d, 0xa5, 0x21, 0xd6, 0x7c, + 0x13, 0x4e, 0x5d, 0x2c, 0x7f, 0x87, 0x83, 0x78, 0xef, 0x9b, 0xa4, 0x01, 0x0f, 0xbf, 0x7c, 0xc6, + 0x34, 0x83, 0xd2, 0x34, 0x25, 0x5c, 0x69, 0x67, 0x7f, 0x0a, 0xeb, 0x15, 0xb6, 0x90, 0x39, 0xea, + 0x1d, 0x68, 0x9a, 0x93, 0x2a, 0x95, 0xa7, 0x74, 0xd5, 0x6a, 0x6c, 0x74, 0x16, 0x7a, 0x06, 0x1d, + 0x59, 0x7d, 0x22, 0x8e, 0xe3, 0x32, 0x3d, 0x2f, 0xb2, 0x3f, 0x83, 0xe5, 0x1c, 0xb8, 0x17, 0x47, + 0xa7, 0x34, 0xcd, 0x78, 0xb4, 0x11, 0x98, 0xec, 0xa5, 0xf1, 0x89, 0x32, 0x35, 0xff, 0xcd, 0xeb, + 0x36, 0x16, 0xcb, 0x30, 0xa8, 0xb1, 0x98, 0xe3, 0xa4, 0x2e, 0x53, 0xb7, 0x14, 0xfe, 0xe6, 0x75, + 0x72, 0x80, 0x44, 0x68, 0x17, 0xd7, 0x44, 0xa8, 0xd6, 0x25, 0x8c, 0x73, 0xb1, 0x9f, 0x62, 0xf9, + 0x58, 0x14, 0x45, 0xea, 0xf8, 0x0d, 0xa8, 0x0b, 0x1d, 0xf9, 0x4e, 0xa5, 0xdf, 0xa6, 0xa1, 0xdf, + 0x80, 0x98, 0x0e, 0xf4, 0x34, 0xd4, 0xfe, 0x5b, 0x0d, 0x1a, 0x58, 0xb1, 0xde, 0xa5, 0xcc, 0x0d, + 0xc2, 0xd1, 0xb5, 0xb4, 0xa8, 0x41, 0x6b, 0xba, 0x06, 0x7d, 0x01, 0xe6, 0x8b, 0xc3, 0x8c, 0x73, + 0xd5, 0xcc, 0x16, 0x46, 0x19, 0xe7, 0xe4, 0x1a, 0x2c, 0x60, 0x6b, 0x9d, 0x63, 0x89, 0x98, 0x99, + 0x47, 0xa8, 0x46, 0x33, 0x1b, 0x81, 0xa9, 0x81, 0x46, 0x80, 0x2f, 0x63, 0x31, 0xdd, 0xcd, 0x02, + 0x5f, 0xf7, 0x09, 0x08, 0x79, 0x1c, 0xf8, 0x85, 0x65, 0xdc, 0x3d, 0x53, 0x58, 0xc6, 0xdd, 0xbc, + 0x07, 0x4a, 0x29, 0x4e, 0x5a, 0x71, 0xc4, 0x83, 0xed, 0xf0, 0x84, 0xd3, 0x50, 0xc0, 0x27, 0xc1, + 0x09, 0x4e, 0x55, 0x33, 0xe6, 0xb2, 0xbe, 0x9a, 0xb3, 0xc8, 0xa7, 0xbc, 0x4d, 0x83, 0x62, 0x9b, + 0x96, 0x37, 0x75, 0x75, 0xa3, 0xa9, 0xbb, 0x0c, 0xf5, 0x38, 0xa1, 0x51, 0x57, 0xb6, 0xd8, 0x0d, + 0x51, 0x3d, 0x70, 0xd0, 0x53, 0x84, 0xc8, 0x91, 0x09, 0xda, 0x3c, 0x1b, 0xa7, 0x2f, 0x35, 0x0d, + 0x53, 0x1b, 0x34, 0x8c, 0x6a, 0x04, 0x27, 0x2e, 0x6a, 0x04, 0xed, 0x5d, 0xac, 0x8a, 0x15, 0x63, + 0x19, 0x3e, 0x2f, 0xc3, 0x34, 0x9a, 0x49, 0x45, 0xce, 0xb2, 0xd1, 0xc6, 0xc8, 0xa0, 0x70, 0x24, + 0x8e, 0xfd, 0x1e, 0xce, 0xa2, 0x71, 0x69, 0x1c, 0xd1, 0xd7, 0x61, 0x56, 0x78, 0x45, 0x47, 0xcd, + 0x0c, 0x3e, 0x3f, 0xf4, 0xed, 0x3f, 0x5a, 0x40, 0x1e, 0xf7, 0x0f, 0x4e, 0x82, 0xf1, 0xa9, 0x8d, + 0xdf, 0xa0, 0x13, 0x98, 0xc4, 0x30, 0x11, 0xe1, 0x88, 0xbf, 0x07, 0x22, 0x64, 0x72, 0x30, 0x42, + 0x72, 0x77, 0x4e, 0x55, 0xf7, 0xe8, 0xd3, 0x45, 0xe7, 0xf3, 0x14, 0x1f, 0x06, 0x34, 0x62, 0x5d, + 0x39, 0x6c, 0xe1, 0x29, 0x1e, 0x01, 0x0f, 0x7d, 0xfb, 0x31, 0x2c, 0x19, 0x9a, 0x49, 0x4b, 0x5f, + 0x85, 0x86, 0x10, 0x20, 0x09, 0x5d, 0x4f, 0x4f, 0x9a, 0xeb, 0x08, 0xdb, 0x47, 0xd0, 0x28, 0x7b, + 0xfd, 0xd5, 0x02, 0xb2, 0xc7, 0x2f, 0xae, 0x70, 0x6c, 0x7b, 0xf1, 0xc0, 0x11, 0x5d, 0x52, 0x4e, + 0x6f, 0x4e, 0x42, 0x1e, 0x9a, 0xcc, 0x26, 0x0c, 0x66, 0xda, 0xd2, 0x93, 0xcf, 0x39, 0x0a, 0x29, + 0x9d, 0xda, 0x6b, 0xb0, 0x70, 0xe6, 0x86, 0x21, 0x65, 0x5d, 0x75, 0x57, 0xca, 0x99, 0xa9, 0x80, + 0xaa, 0x8e, 0x4b, 0xf9, 0x6b, 0x26, 0xf7, 0x17, 0x6f, 0x89, 0x0c, 0x7d, 0xe5, 0xdd, 0x77, 0x07, + 0x56, 0x05, 0x78, 0x37, 0x0c, 0xc7, 0x3e, 0x43, 0xf6, 0x2f, 0x6a, 0xb0, 0x56, 0xda, 0xa6, 0x2f, + 0x09, 0xf3, 0x04, 0x5c, 0xd7, 0xea, 0x56, 0x6f, 0xd8, 0x96, 0x8f, 0x72, 0x57, 0xe7, 0xb7, 0x16, + 0x4c, 0x0b, 0xd0, 0x48, 0x6f, 0x7c, 0xa2, 0xdc, 0x2f, 0x73, 0x8c, 0xa8, 0x7f, 0xff, 0x6f, 0x3c, + 0x66, 0xe2, 0xdf, 0x63, 0xdc, 0x29, 0xca, 0x43, 0x11, 0x37, 0x02, 0xd2, 0x79, 0x07, 0x5a, 0x83, + 0x08, 0xcf, 0x35, 0xbc, 0x17, 0x3d, 0xf4, 0xbd, 0x53, 0x1a, 0x31, 0x7d, 0xc7, 0x7d, 0x69, 0x41, + 0x73, 0x2f, 0x8e, 0xfc, 0x80, 0xe7, 0xc7, 0x7d, 0x37, 0x75, 0x4f, 0x32, 0xb2, 0xc9, 0x2b, 0x1b, + 0x09, 0x52, 0x43, 0x56, 0x0d, 0x18, 0x32, 0xce, 0xda, 0x02, 0xf0, 0x8e, 0xa8, 0x77, 0xdc, 0x95, + 0xf3, 0x25, 0x1e, 0xf4, 0x73, 0x08, 0x79, 0x37, 0xf0, 0x33, 0xf2, 0x0a, 0x2c, 0xe5, 0xcb, 0x5d, + 0x37, 0xf2, 0xbb, 0x72, 0xb8, 0x84, 0xf3, 0x66, 0x8d, 0xb7, 0x1b, 0xf9, 0xbb, 0xd9, 0x31, 0x4e, + 0xc5, 0xf5, 0x4c, 0xa5, 0x6b, 0x1c, 0xd8, 0xa6, 0x86, 0xef, 0x22, 0xd8, 0xfe, 0x87, 0x85, 0xf9, + 0x4e, 0x69, 0x25, 0xbd, 0x9d, 0x8f, 0x51, 0x70, 0xba, 0x66, 0xb8, 0xac, 0x36, 0xe0, 0x32, 0x02, + 0x93, 0x01, 0xa3, 0x27, 0x2a, 0x8d, 0xf0, 0xdf, 0xe4, 0x5d, 0x68, 0x69, 0x8d, 0xbb, 0x09, 0x9a, + 0x45, 0x1e, 0x93, 0xb5, 0xbc, 0x4d, 0x30, 0xac, 0xe6, 0x34, 0xbd, 0x01, 0x33, 0xaa, 0xe3, 0x35, + 0x75, 0xe1, 0xf1, 0xe2, 0x59, 0xc9, 0x43, 0x6b, 0x4f, 0xcb, 0x22, 0x0a, 0x9f, 0x84, 0xd4, 0xd4, + 0xeb, 0x33, 0xea, 0xcb, 0xc2, 0x48, 0x3f, 0xdb, 0x7f, 0xb6, 0xa0, 0xb9, 0xeb, 0xfb, 0xa8, 0xf7, + 0x38, 0x69, 0x42, 0x69, 0x59, 0xbb, 0x40, 0xcb, 0x89, 0x7f, 0x53, 0xcb, 0xaf, 0x9c, 0x44, 0x86, + 0x18, 0xc1, 0xb6, 0xa1, 0x95, 0xeb, 0x59, 0xed, 0x5e, 0xfb, 0x7f, 0x80, 0x88, 0x62, 0xda, 0x30, + 0xc7, 0x20, 0xd6, 0x0a, 0x2c, 0x19, 0x58, 0x32, 0xd7, 0xdc, 0x87, 0x1b, 0x0f, 0x28, 0xdb, 0x4b, + 0xcf, 0x13, 0x16, 0xab, 0xe2, 0xe5, 0x2e, 0x4d, 0xe2, 0x2c, 0x50, 0x99, 0x8b, 0x8e, 0x95, 0x7d, + 0x7e, 0x67, 0xc1, 0xcd, 0x31, 0x08, 0x49, 0x15, 0xbe, 0x57, 0x9e, 0x26, 0x7c, 0xab, 0xd0, 0x48, + 0x8e, 0x47, 0x65, 0x5b, 0x43, 0x44, 0xba, 0xc8, 0x49, 0x76, 0xde, 0x86, 0x05, 0x73, 0xf1, 0xb9, + 0x52, 0x45, 0x08, 0xd7, 0x2f, 0x10, 0x62, 0x9c, 0x98, 0xbb, 0x0e, 0x0b, 0x9e, 0x41, 0x42, 0x32, + 0x1a, 0x80, 0xda, 0x7b, 0xf0, 0xe2, 0x85, 0xdc, 0xa4, 0xd9, 0x86, 0xf6, 0x63, 0xf6, 0xaf, 0x27, + 0x61, 0xed, 0xe3, 0x80, 0x1d, 0xf9, 0xa9, 0x7b, 0xa6, 0xa2, 0x6f, 0x1c, 0x21, 0x07, 0x5a, 0xb5, + 0x5a, 0xb9, 0xbb, 0xbc, 0x05, 0x8b, 0x71, 0x44, 0xb1, 0xa2, 0xec, 0x26, 0x6e, 0x96, 0x9d, 0xc5, + 0xa9, 0xba, 0x4b, 0x9b, 0x71, 0x44, 0x79, 0x55, 0xb9, 0x2f, 0xc1, 0x03, 0xb7, 0xf1, 0xe4, 0xe0, + 0x6d, 0xdc, 0x82, 0x89, 0x24, 0x88, 0xe4, 0x84, 0x9c, 0xff, 0xe4, 0x77, 0x27, 0x4b, 0x5d, 0xbf, + 0x40, 0x59, 0xde, 0x9d, 0x08, 0xd5, 0x74, 0x8b, 0x33, 0xdb, 0x99, 0x81, 0x99, 0x6d, 0xc1, 0x26, + 0xb3, 0x66, 0x8f, 0x7a, 0x19, 0xea, 0xf2, 0x67, 0x97, 0xb9, 0x87, 0xb2, 0xe0, 0x05, 0x09, 0x7a, + 0xe2, 0x1e, 0x16, 0xea, 0x21, 0x30, 0xea, 0xa1, 0x2d, 0x80, 0x1e, 0xa5, 0x5d, 0xa3, 0xf4, 0x9d, + 0xeb, 0x51, 0x2a, 0x92, 0x2e, 0x2f, 0x8c, 0x0e, 0xdc, 0xe8, 0xb8, 0x8b, 0x1d, 0x67, 0x43, 0x88, + 0xc3, 0x01, 0x1f, 0xf2, 0xae, 0xf3, 0x2a, 0x34, 0x70, 0x51, 0xc9, 0x34, 0x2f, 0x2c, 0xca, 0x61, + 0xbb, 0x79, 0xef, 0x8c, 0x28, 0x5e, 0xc0, 0xce, 0xdb, 0x0b, 0xf9, 0xfe, 0xbd, 0x80, 0x9d, 0xeb, + 0xfd, 0x68, 0xb3, 0xf4, 0xbc, 0xdd, 0xcc, 0xf7, 0xef, 0x09, 0x10, 0x17, 0x2f, 0x3b, 0x0b, 0x7a, + 0x54, 0xbc, 0x62, 0x6f, 0x09, 0x2b, 0x23, 0x64, 0x2f, 0xf6, 0xb1, 0x0f, 0x38, 0x0b, 0xd2, 0x42, + 0x2b, 0xb2, 0x28, 0x1a, 0x16, 0x0e, 0xd4, 0x5f, 0x1f, 0xdc, 0x82, 0x96, 0x0a, 0x97, 0xe2, 0x17, + 0x17, 0x29, 0xcd, 0xfa, 0x21, 0x53, 0x5f, 0x5c, 0x88, 0xa7, 0xdb, 0x3f, 0xdb, 0x80, 0x85, 0x07, + 0xb1, 0x08, 0xd0, 0x27, 0xdc, 0x2f, 0x29, 0x79, 0x04, 0x33, 0xf2, 0x7b, 0x0d, 0xb2, 0x5a, 0x38, + 0xb7, 0x85, 0x91, 0x7f, 0x67, 0xad, 0x04, 0x97, 0x19, 0x67, 0xe9, 0x8b, 0x3f, 0xfc, 0xe9, 0x27, + 0xb5, 0x79, 0x52, 0xdf, 0x39, 0x7d, 0x6d, 0xe7, 0x90, 0xb2, 0x80, 0x53, 0xf1, 0xa0, 0x51, 0xfc, + 0x06, 0x81, 0x6c, 0x54, 0xbc, 0xe0, 0x57, 0xa7, 0xae, 0xb3, 0x59, 0xbd, 0x28, 0xe9, 0xb7, 0x91, + 0x3e, 0x21, 0x2d, 0x49, 0x5f, 0x7f, 0xb2, 0x40, 0x3e, 0x87, 0xe6, 0xc0, 0xfb, 0x7b, 0x62, 0xe7, + 0xa4, 0x86, 0x7d, 0x8b, 0xd1, 0x79, 0x61, 0x24, 0x8e, 0xe4, 0x7a, 0x09, 0xb9, 0xb6, 0xed, 0x25, + 0xce, 0xd5, 0x17, 0x5c, 0x14, 0xe7, 0x37, 0xad, 0x5b, 0x24, 0xc3, 0xae, 0xa2, 0xf8, 0x11, 0xc0, + 0x58, 0xbc, 0x2f, 0x57, 0xa8, 0x6a, 0x58, 0x73, 0x03, 0xf9, 0xae, 0x90, 0xa5, 0x01, 0x6d, 0xd1, + 0xaa, 0x19, 0xbe, 0x62, 0x2c, 0x7c, 0x20, 0x81, 0x01, 0x32, 0x0e, 0xdf, 0xad, 0xea, 0x0f, 0x2c, + 0xe4, 0x37, 0x1e, 0x76, 0x07, 0xb9, 0x2e, 0x13, 0x32, 0xc0, 0x35, 0x66, 0x09, 0xc9, 0x8c, 0xef, + 0x4f, 0x24, 0xd3, 0x8c, 0x5c, 0x1a, 0xfa, 0xc9, 0xc6, 0x70, 0x4d, 0x8b, 0x9f, 0x74, 0x0c, 0xd5, + 0x34, 0x66, 0x49, 0x46, 0x9e, 0xc1, 0xc2, 0xbd, 0xe8, 0xbf, 0xe3, 0xd9, 0x2d, 0xe4, 0xbb, 0x66, + 0xa3, 0xae, 0x62, 0x84, 0x54, 0x74, 0xec, 0xc7, 0x30, 0xa7, 0x5f, 0xe3, 0x92, 0x76, 0x41, 0x09, + 0xe3, 0x83, 0x81, 0xce, 0x90, 0xd7, 0xc1, 0x2a, 0x5a, 0xed, 0x79, 0xa9, 0x95, 0x78, 0xb9, 0xcb, + 0x09, 0x7f, 0x0a, 0x90, 0xbf, 0x1f, 0x26, 0xeb, 0x25, 0xca, 0xda, 0x72, 0x9d, 0xaa, 0x25, 0x49, + 0x7e, 0x15, 0xc9, 0xb7, 0xc8, 0x82, 0x41, 0x3e, 0x93, 0xe7, 0x4d, 0xbf, 0xc6, 0x33, 0xce, 0xdb, + 0xe0, 0x1b, 0xe5, 0xce, 0xf0, 0x57, 0x89, 0xca, 0x29, 0xb6, 0x3a, 0x6c, 0xba, 0x40, 0xe5, 0x1a, + 0x1c, 0xc2, 0xbc, 0xf1, 0x6e, 0x91, 0x6c, 0x56, 0x71, 0xc9, 0xaa, 0x62, 0xae, 0xfc, 0x42, 0xd2, + 0x5e, 0x47, 0x56, 0x4b, 0x64, 0x71, 0x90, 0x55, 0x46, 0x8e, 0xf1, 0x8b, 0xb2, 0xc2, 0x2b, 0x38, + 0x52, 0xa4, 0x55, 0x7e, 0x1f, 0xd9, 0xb9, 0x34, 0x6c, 0x39, 0xab, 0x8e, 0x6f, 0x79, 0x87, 0xe1, + 0xa1, 0x12, 0x0e, 0x17, 0x2f, 0xde, 0x0c, 0x87, 0x1b, 0xef, 0xe7, 0x3a, 0xeb, 0x15, 0x2b, 0x92, + 0xfa, 0x0a, 0x52, 0x6f, 0x12, 0xe5, 0x73, 0x4f, 0xd0, 0x12, 0x3e, 0xd1, 0x13, 0x51, 0xc3, 0x27, + 0x83, 0xaf, 0xcd, 0x8c, 0x1c, 0x58, 0x7a, 0x79, 0x56, 0xca, 0x81, 0xfa, 0xf5, 0x18, 0xf9, 0x81, + 0xf9, 0x16, 0x4e, 0xbd, 0x15, 0xb0, 0x47, 0x8e, 0xf1, 0x4b, 0xa7, 0x65, 0xe8, 0xa8, 0xdf, 0xbe, + 0x8c, 0x9c, 0xd7, 0xc9, 0xda, 0x20, 0x67, 0xf9, 0xda, 0x80, 0x7c, 0x61, 0xc1, 0x52, 0xc5, 0x50, + 0x3a, 0x97, 0x60, 0xf8, 0x08, 0x3d, 0x97, 0x60, 0xd4, 0x54, 0xdb, 0x46, 0x09, 0x36, 0x6d, 0x94, + 0xc0, 0xf5, 0x7d, 0x2d, 0x81, 0xbc, 0x92, 0x79, 0x64, 0xfe, 0xc8, 0x82, 0xd5, 0xea, 0x01, 0x34, + 0xb9, 0xa6, 0x78, 0x8c, 0x1c, 0x8d, 0x77, 0xae, 0x5f, 0x84, 0x26, 0xa5, 0xb9, 0x86, 0xd2, 0x5c, + 0xb6, 0x3b, 0x5c, 0x9a, 0x14, 0x71, 0xab, 0x04, 0x3a, 0xc3, 0x3e, 0xce, 0x1c, 0xf1, 0x92, 0x2b, + 0x05, 0x83, 0x57, 0x4e, 0xc2, 0x3b, 0x57, 0x47, 0x60, 0x98, 0xe9, 0x8b, 0xac, 0x48, 0x87, 0xe0, + 0x5c, 0x54, 0xcf, 0x8a, 0xe5, 0x19, 0xcd, 0x47, 0xa8, 0xc6, 0x19, 0x2d, 0x4d, 0x85, 0x8d, 0x33, + 0x5a, 0x1e, 0xd4, 0x96, 0xce, 0x28, 0x32, 0xc3, 0xa1, 0x2d, 0xf9, 0x04, 0x8f, 0x8d, 0x1c, 0x22, + 0xb4, 0x07, 0x8f, 0x7a, 0x56, 0x75, 0x6c, 0xcc, 0x31, 0x41, 0x29, 0x55, 0x8a, 0xd9, 0x04, 0xb7, + 0x9e, 0x03, 0xb3, 0x0a, 0x9d, 0xac, 0x0d, 0x12, 0x50, 0x94, 0x2b, 0xa7, 0x7e, 0xf6, 0x1a, 0x12, + 0x5d, 0xb4, 0x1b, 0x45, 0xa2, 0x9c, 0xe6, 0x01, 0xd4, 0x0b, 0x13, 0x2e, 0xa2, 0x93, 0x6c, 0x79, + 0xa0, 0xd7, 0xd9, 0xa8, 0x5c, 0x33, 0x53, 0x89, 0xdd, 0xe4, 0x0c, 0x32, 0x44, 0x28, 0xf2, 0x28, + 0xcc, 0x7f, 0x72, 0x1e, 0xe5, 0x21, 0x58, 0xce, 0xa3, 0x6a, 0x60, 0x64, 0xf0, 0xf0, 0x10, 0x41, + 0xf3, 0x48, 0xa1, 0x39, 0x30, 0x77, 0xc9, 0xaf, 0xe2, 0xea, 0x29, 0x53, 0x7e, 0x15, 0x0f, 0x19, + 0xd8, 0x98, 0xc5, 0x8e, 0xe0, 0xe7, 0x86, 0x61, 0xee, 0x0f, 0x91, 0x22, 0xc5, 0x54, 0xc2, 0xf0, + 0xb5, 0x31, 0x7e, 0x31, 0x7c, 0x6d, 0x8e, 0x30, 0x4a, 0x29, 0x92, 0x0a, 0x5a, 0x4f, 0x61, 0x56, + 0xb5, 0xc3, 0xb9, 0xa3, 0x07, 0x06, 0x01, 0x9d, 0x76, 0x79, 0x41, 0x52, 0x35, 0x9c, 0xed, 0xfa, + 0x3e, 0x52, 0x95, 0x8e, 0x28, 0x34, 0xc7, 0xb9, 0x23, 0xca, 0x7d, 0x75, 0xee, 0x88, 0xaa, 0x6e, + 0xda, 0x70, 0x84, 0x38, 0xed, 0x9a, 0xc7, 0x6f, 0x2c, 0xb8, 0x7a, 0x61, 0x6f, 0x4b, 0x5e, 0x7d, + 0x8e, 0x36, 0x58, 0x08, 0xf4, 0xda, 0x73, 0x37, 0xce, 0xf6, 0x0d, 0x14, 0xd3, 0xb6, 0xb7, 0xd4, + 0x05, 0x84, 0xdb, 0x7c, 0x81, 0xae, 0xbb, 0x68, 0x2e, 0xf4, 0xaf, 0x2c, 0xb8, 0x7c, 0x01, 0x5d, + 0xb2, 0x3d, 0xa6, 0x00, 0x4a, 0xe0, 0x9d, 0xb1, 0xf1, 0xa5, 0xb8, 0xd7, 0x51, 0xdc, 0x2b, 0xf6, + 0xc6, 0x08, 0x71, 0xb9, 0xb0, 0xdf, 0x87, 0x0d, 0xdd, 0x03, 0x1b, 0x74, 0xef, 0xf7, 0x23, 0x3f, + 0x23, 0x3a, 0xac, 0x87, 0x34, 0xca, 0x79, 0xe0, 0x0c, 0xb6, 0x46, 0xe6, 0x9d, 0x72, 0x26, 0x57, + 0x85, 0x18, 0x3d, 0x4e, 0x9b, 0x73, 0x4f, 0x60, 0x51, 0xed, 0xbb, 0x1f, 0xb8, 0xec, 0x2b, 0xf3, + 0xbc, 0x82, 0x3c, 0x3b, 0xf6, 0x4a, 0x91, 0x67, 0x2f, 0x70, 0x99, 0xe2, 0x78, 0x30, 0x8d, 0xdf, + 0xd3, 0xbf, 0xfe, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x0a, 0xf2, 0x2b, 0x82, 0x2f, 0x00, + 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -4052,6 +4128,7 @@ type GoCryptoTraderClient interface { DisableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) GetExchangeInfo(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeInfoResponse, error) GetExchangeOTPCode(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeOTPReponse, error) + GetExchangeOTPCodes(ctx context.Context, in *GetExchangeOTPsRequest, opts ...grpc.CallOption) (*GetExchangeOTPsResponse, error) EnableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) GetTicker(ctx context.Context, in *GetTickerRequest, opts ...grpc.CallOption) (*TickerResponse, error) GetTickers(ctx context.Context, in *GetTickersRequest, opts ...grpc.CallOption) (*GetTickersResponse, error) @@ -4132,6 +4209,15 @@ func (c *goCryptoTraderClient) GetExchangeOTPCode(ctx context.Context, in *Gener return out, nil } +func (c *goCryptoTraderClient) GetExchangeOTPCodes(ctx context.Context, in *GetExchangeOTPsRequest, opts ...grpc.CallOption) (*GetExchangeOTPsResponse, error) { + out := new(GetExchangeOTPsResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetExchangeOTPCodes", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *goCryptoTraderClient) EnableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) { out := new(GenericExchangeNameResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/EnableExchange", in, out, opts...) @@ -4364,6 +4450,7 @@ type GoCryptoTraderServer interface { DisableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) GetExchangeInfo(context.Context, *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) GetExchangeOTPCode(context.Context, *GenericExchangeNameRequest) (*GetExchangeOTPReponse, error) + GetExchangeOTPCodes(context.Context, *GetExchangeOTPsRequest) (*GetExchangeOTPsResponse, error) EnableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) GetTicker(context.Context, *GetTickerRequest) (*TickerResponse, error) GetTickers(context.Context, *GetTickersRequest) (*GetTickersResponse, error) @@ -4391,101 +4478,6 @@ type GoCryptoTraderServer interface { WithdrawFiatFunds(context.Context, *WithdrawCurrencyRequest) (*WithdrawResponse, error) } -// UnimplementedGoCryptoTraderServer can be embedded to have forward compatible implementations. -type UnimplementedGoCryptoTraderServer struct { -} - -func (*UnimplementedGoCryptoTraderServer) GetInfo(ctx context.Context, req *GetInfoRequest) (*GetInfoResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetExchanges(ctx context.Context, req *GetExchangesRequest) (*GetExchangesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetExchanges not implemented") -} -func (*UnimplementedGoCryptoTraderServer) DisableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DisableExchange not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetExchangeInfo(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetExchangeInfo not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCode(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeOTPReponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetExchangeOTPCode not implemented") -} -func (*UnimplementedGoCryptoTraderServer) EnableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method EnableExchange not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetTicker(ctx context.Context, req *GetTickerRequest) (*TickerResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetTicker not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetTickers(ctx context.Context, req *GetTickersRequest) (*GetTickersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetTickers not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetOrderbook(ctx context.Context, req *GetOrderbookRequest) (*OrderbookResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOrderbook not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetOrderbooks(ctx context.Context, req *GetOrderbooksRequest) (*GetOrderbooksResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOrderbooks not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetAccountInfo(ctx context.Context, req *GetAccountInfoRequest) (*GetAccountInfoResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetAccountInfo not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetConfig(ctx context.Context, req *GetConfigRequest) (*GetConfigResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetPortfolio(ctx context.Context, req *GetPortfolioRequest) (*GetPortfolioResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetPortfolio not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetPortfolioSummary(ctx context.Context, req *GetPortfolioSummaryRequest) (*GetPortfolioSummaryResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetPortfolioSummary not implemented") -} -func (*UnimplementedGoCryptoTraderServer) AddPortfolioAddress(ctx context.Context, req *AddPortfolioAddressRequest) (*AddPortfolioAddressResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddPortfolioAddress not implemented") -} -func (*UnimplementedGoCryptoTraderServer) RemovePortfolioAddress(ctx context.Context, req *RemovePortfolioAddressRequest) (*RemovePortfolioAddressResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method RemovePortfolioAddress not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetForexProviders(ctx context.Context, req *GetForexProvidersRequest) (*GetForexProvidersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetForexProviders not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetForexRates(ctx context.Context, req *GetForexRatesRequest) (*GetForexRatesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetForexRates not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetOrders(ctx context.Context, req *GetOrdersRequest) (*GetOrdersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOrders not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetOrder(ctx context.Context, req *GetOrderRequest) (*OrderDetails, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOrder not implemented") -} -func (*UnimplementedGoCryptoTraderServer) SubmitOrder(ctx context.Context, req *SubmitOrderRequest) (*SubmitOrderResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SubmitOrder not implemented") -} -func (*UnimplementedGoCryptoTraderServer) CancelOrder(ctx context.Context, req *CancelOrderRequest) (*CancelOrderResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CancelOrder not implemented") -} -func (*UnimplementedGoCryptoTraderServer) CancelAllOrders(ctx context.Context, req *CancelAllOrdersRequest) (*CancelAllOrdersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CancelAllOrders not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetEvents(ctx context.Context, req *GetEventsRequest) (*GetEventsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented") -} -func (*UnimplementedGoCryptoTraderServer) AddEvent(ctx context.Context, req *AddEventRequest) (*AddEventResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddEvent not implemented") -} -func (*UnimplementedGoCryptoTraderServer) RemoveEvent(ctx context.Context, req *RemoveEventRequest) (*RemoveEventResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method RemoveEvent not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddresses(ctx context.Context, req *GetCryptocurrencyDepositAddressesRequest) (*GetCryptocurrencyDepositAddressesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddresses not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddress(ctx context.Context, req *GetCryptocurrencyDepositAddressRequest) (*GetCryptocurrencyDepositAddressResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddress not implemented") -} -func (*UnimplementedGoCryptoTraderServer) WithdrawCryptocurrencyFunds(ctx context.Context, req *WithdrawCurrencyRequest) (*WithdrawResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method WithdrawCryptocurrencyFunds not implemented") -} -func (*UnimplementedGoCryptoTraderServer) WithdrawFiatFunds(ctx context.Context, req *WithdrawCurrencyRequest) (*WithdrawResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method WithdrawFiatFunds not implemented") -} - func RegisterGoCryptoTraderServer(s *grpc.Server, srv GoCryptoTraderServer) { s.RegisterService(&_GoCryptoTrader_serviceDesc, srv) } @@ -4580,6 +4572,24 @@ func _GoCryptoTrader_GetExchangeOTPCode_Handler(srv interface{}, ctx context.Con return interceptor(ctx, in, info, handler) } +func _GoCryptoTrader_GetExchangeOTPCodes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetExchangeOTPsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetExchangeOTPCodes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetExchangeOTPCodes", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetExchangeOTPCodes(ctx, req.(*GetExchangeOTPsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _GoCryptoTrader_EnableExchange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GenericExchangeNameRequest) if err := dec(in); err != nil { @@ -5054,6 +5064,10 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ MethodName: "GetExchangeOTPCode", Handler: _GoCryptoTrader_GetExchangeOTPCode_Handler, }, + { + MethodName: "GetExchangeOTPCodes", + Handler: _GoCryptoTrader_GetExchangeOTPCodes_Handler, + }, { MethodName: "EnableExchange", Handler: _GoCryptoTrader_EnableExchange_Handler, diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 8619468a..7844c2e3 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -9,13 +9,13 @@ It translates gRPC into RESTful JSON APIs. package gctrpc import ( - "context" "io" "net/http" "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/grpclog" @@ -45,10 +45,7 @@ func request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtim var protoReq GetExchangesRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchanges_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -82,10 +79,7 @@ func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler run var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -102,10 +96,7 @@ func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -114,6 +105,15 @@ func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler } +func request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangeOTPsRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetExchangeOTPCodes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + func request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata @@ -191,10 +191,7 @@ func request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runt var protoReq GetAccountInfoRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetAccountInfo_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -616,6 +613,26 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOTPCodes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetExchangeOTPCodes_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeOTPCodes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_GoCryptoTrader_EnableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1130,6 +1147,8 @@ var ( pattern_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotp"}, "")) + pattern_GoCryptoTrader_GetExchangeOTPCodes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotps"}, "")) + pattern_GoCryptoTrader_EnableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchange"}, "")) pattern_GoCryptoTrader_GetTicker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getticker"}, "")) @@ -1192,6 +1211,8 @@ var ( forward_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_GetExchangeOTPCodes_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_EnableExchange_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_GetTicker_0 = runtime.ForwardResponseMessage diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index e7552126..28552d80 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -32,6 +32,12 @@ message GetExchangeOTPReponse { string otp_code = 1; } +message GetExchangeOTPsRequest {} + +message GetExchangeOTPsResponse { + map otp_codes = 1; +} + message DisableExchangeRequest { string exchange = 1; } @@ -422,6 +428,12 @@ service GoCryptoTrader { }; } + rpc GetExchangeOTPCodes (GetExchangeOTPsRequest) returns (GetExchangeOTPsResponse) { + option (google.api.http) = { + get: "/v1/getexchangeotps" + }; + } + rpc EnableExchange (GenericExchangeNameRequest) returns (GenericExchangeNameResponse) { option (google.api.http) = { post: "/v1/enableexchange" diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index 85c7f706..b4fa444f 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -327,6 +327,22 @@ ] } }, + "/v1/getexchangeotps": { + "get": { + "operationId": "GetExchangeOTPCodes", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetExchangeOTPsResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getexchanges": { "get": { "operationId": "GetExchanges", @@ -1104,6 +1120,17 @@ } } }, + "gctrpcGetExchangeOTPsResponse": { + "type": "object", + "properties": { + "otp_codes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, "gctrpcGetExchangesResponse": { "type": "object", "properties": { From 33085318c46bcb727c1f952b3297c83a46e50e1b Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 12 Jun 2019 17:52:40 +1000 Subject: [PATCH 10/71] Switch connchecker to service --- cmd/gctcli/main.go | 2 +- engine/connection.go | 63 ++++++++++++++++++++++++++++++++++++++++++ engine/engine.go | 19 +++++++------ engine/helpers.go | 6 +--- engine/helpers_test.go | 15 +++++----- 5 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 engine/connection.go diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index d501e69b..36d0f9ba 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -79,7 +79,7 @@ func main() { cli.StringFlag{ Name: "delimiter", Value: "-", - Usage: "the default pair delimiter used to standardise currency pair input", + Usage: "the default currency pair delimiter used to standardise currency pair input", Destination: &pairDelimiter, }, } diff --git a/engine/connection.go b/engine/connection.go new file mode 100644 index 00000000..f104c256 --- /dev/null +++ b/engine/connection.go @@ -0,0 +1,63 @@ +package engine + +import ( + "errors" + "sync/atomic" + + "github.com/thrasher-/gocryptotrader/connchecker" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// connectionManager manages the connchecker +type connectionManager struct { + started int32 + stopped int32 + conn *connchecker.Checker +} + +func (c *connectionManager) Started() bool { + return atomic.LoadInt32(&c.started) == 1 +} + +func (c *connectionManager) Start() error { + if atomic.AddInt32(&c.started, 1) != 1 { + return errors.New("connection manager already started") + } + + log.Debugln("Connection manager starting...") + var err error + c.conn, err = connchecker.New(Bot.Config.ConnectionMonitor.DNSList, + Bot.Config.ConnectionMonitor.PublicDomainList, + Bot.Config.ConnectionMonitor.CheckInterval) + if err != nil { + atomic.CompareAndSwapInt32(&c.started, 1, 0) + return err + } + + log.Debugln("Connection manager started.") + return nil +} + +func (c *connectionManager) Stop() error { + if atomic.LoadInt32(&c.started) == 0 { + return errors.New("connection manager not started") + } + + if atomic.AddInt32(&c.stopped, 1) != 1 { + return errors.New("connection manager is already stopped") + } + + log.Debugln("Connection manager shutting down...") + c.conn.Shutdown() + log.Debugln("Connection manager stopped.") + return nil +} + +func (c *connectionManager) IsOnline() bool { + if c.conn == nil { + log.Warnf("Connection manager: IsOnline called but conn is nil") + return false + } + + return c.conn.IsConnected() +} diff --git a/engine/engine.go b/engine/engine.go index 87c50ec1..7c513bb3 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -14,7 +14,6 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/communications" "github.com/thrasher-/gocryptotrader/config" - "github.com/thrasher-/gocryptotrader/connchecker" "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/coinmarketcap" "github.com/thrasher-/gocryptotrader/engine/events" @@ -33,10 +32,10 @@ type Engine struct { Portfolio *portfolio.Base Exchanges []exchange.IBotExchange ExchangeCurrencyPairManager *ExchangeCurrencyPairSyncer + ConnectionManager connectionManager OrderManager orderManager PortfolioManager portfolioManager CommsRelayer *communications.Communications - Connectivity *connchecker.Checker Shutdown chan struct{} Settings Settings CryptocurrencyDepositAddresses map[string]map[string]string @@ -267,13 +266,9 @@ func (e *Engine) Start() { } // Sets up internet connectivity monitor - var err error if e.Settings.EnableConnectivityMonitor { - e.Connectivity, err = connchecker.New(e.Config.ConnectionMonitor.DNSList, - e.Config.ConnectionMonitor.PublicDomainList, - e.Config.ConnectionMonitor.CheckInterval) - if err != nil { - log.Fatalf("Connectivity checker failure: %s", err) + if err := e.ConnectionManager.Start(); err != nil { + log.Errorf("Connection manager unable to start: %v", err) } } @@ -338,7 +333,7 @@ func (e *Engine) Start() { newFxSettings = append(newFxSettings, currency.FXSettings(d)) } - err = currency.RunStorageUpdater(currency.BotOverrides{ + err := currency.RunStorageUpdater(currency.BotOverrides{ Coinmarketcap: e.Settings.EnableCoinmarketcapAnalysis, FxCurrencyConverter: e.Settings.EnableCurrencyConverter, FxCurrencyLayer: e.Settings.EnableCurrencyLayer, @@ -430,6 +425,12 @@ func (e *Engine) Stop() { } } + if e.ConnectionManager.Started() { + if err := e.ConnectionManager.Stop(); err != nil { + log.Errorf("Connection manager unable to stop. Error: %v", err) + } + } + if !e.Settings.EnableDryRun { err := e.Config.SaveConfig(e.Settings.ConfigFile) if err != nil { diff --git a/engine/helpers.go b/engine/helpers.go index fb500ca0..938efa6e 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -82,11 +82,7 @@ func GetAuthAPISupportedExchanges() []string { // IsOnline returns whether or not the engine has Internet connectivity func IsOnline() bool { - if Bot.Connectivity == nil { - log.Warnf("IsOnline called but Bot.Connectivity is nil") - return false - } - return Bot.Connectivity.IsConnected() + return Bot.ConnectionManager.IsOnline() } // GetAvailableExchanges returns a list of enabled exchanges diff --git a/engine/helpers_test.go b/engine/helpers_test.go index 7b66bee2..77d5fde2 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -6,7 +6,6 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" - "github.com/thrasher-/gocryptotrader/connchecker" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/assets" @@ -104,6 +103,9 @@ func TestGetExchangeoOTPByName(t *testing.T) { if result == "" { t.Fatal("Expected valid OTP code") } + + // Flush setting + bCfg.API.Credentials.OTPSecret = "" } func TestGetAuthAPISupportedExchanges(t *testing.T) { @@ -119,22 +121,21 @@ func TestIsOnline(t *testing.T) { t.Fatal("Unexpected result") } - var err error - Bot.Connectivity, err = connchecker.New(Bot.Config.ConnectionMonitor.DNSList, - Bot.Config.ConnectionMonitor.PublicDomainList, - Bot.Config.ConnectionMonitor.CheckInterval) - if err != nil { + if err := Bot.ConnectionManager.Start(); err != nil { t.Fatal(err) } tick := time.NewTicker(time.Second * 5) + defer tick.Stop() for { select { case <-tick.C: t.Fatal("Test timeout") default: if IsOnline() { - Bot.Connectivity.Shutdown() + if err := Bot.ConnectionManager.Stop(); err != nil { + t.Fatal("unable to shutdown connection manager") + } return } } From 6b2cfe790586348efd0bd743100877e0e0c5518b Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 13 Jun 2019 17:30:50 +1000 Subject: [PATCH 11/71] Daily engine improvements Link up various subsystems to be managed atomically with the ability to start/stop them New subsystem APIs Comms changes --- cmd/gctcli/commands.go | 146 +++ cmd/gctcli/main.go | 4 + communications/base/base_interface.go | 6 +- communications/communications.go | 11 +- communications/communications_test.go | 13 +- config/config_types.go | 12 + engine/comms_relayer.go | 87 ++ engine/connection.go | 2 + engine/engine.go | 53 +- engine/{events => }/events.go | 52 +- .../{events/event_test.go => events_test.go} | 2 +- engine/helpers.go | 81 ++ engine/orders.go | 16 +- engine/portfolio.go | 4 +- engine/rpcserver.go | 46 +- engine/timekeeper.go | 140 +++ gctrpc/rpc.pb.go | 1114 ++++++++++++----- gctrpc/rpc.pb.gw.go | 148 +++ gctrpc/rpc.proto | 53 + gctrpc/rpc.swagger.json | 131 ++ 20 files changed, 1731 insertions(+), 390 deletions(-) create mode 100644 engine/comms_relayer.go rename engine/{events => }/events.go (86%) rename engine/{events/event_test.go => events_test.go} (99%) create mode 100644 engine/timekeeper.go diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index a4c5911b..5ce218e2 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -37,6 +37,152 @@ func getInfo(_ *cli.Context) error { return nil } +var getSubsystemsCommand = cli.Command{ + Name: "getsubsystems", + Usage: "gets GoCryptoTrader subsystems and their status", + Action: getSubsystems, +} + +func getSubsystems(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetSubsystems(context.Background(), + &gctrpc.GetSubsystemsRequest{}, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var enableSubsystemCommand = cli.Command{ + Name: "enablesubsystem", + Usage: "enables an engine subsystem", + ArgsUsage: "", + Action: enableSubsystem, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "subsystem", + Usage: "the subsystem to enable", + }, + }, +} + +func enableSubsystem(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "enablesubsystem") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var subsystemName string + if c.IsSet("subsystem") { + subsystemName = c.String("subsystem") + } else { + subsystemName = c.Args().First() + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.EnableSubsystem(context.Background(), + &gctrpc.GenericSubsystemRequest{ + Subsystem: subsystemName, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var disableSubsystemCommand = cli.Command{ + Name: "disablesubsystem", + Usage: "disables an engine subsystem", + ArgsUsage: "", + Action: disableSubsystem, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "subsystem", + Usage: "the subsystem to disable", + }, + }, +} + +func disableSubsystem(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "disablesubsystem") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var subsystemName string + if c.IsSet("subsystem") { + subsystemName = c.String("subsystem") + } else { + subsystemName = c.Args().First() + } + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.DisableSubsystem(context.Background(), + &gctrpc.GenericSubsystemRequest{ + Subsystem: subsystemName, + }, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var getRPCEndpointsCommand = cli.Command{ + Name: "getrpcendpoints", + Usage: "gets GoCryptoTrader endpoints info", + Action: getRPCEndpoints, +} + +func getRPCEndpoints(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetRPCEndpoints(context.Background(), + &gctrpc.GetRPCEndpointsRequest{}, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + var getExchangesCommand = cli.Command{ Name: "getexchanges", Usage: "gets a list of enabled or available exchanges", diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index 36d0f9ba..92c4d77d 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -85,6 +85,10 @@ func main() { } app.Commands = []cli.Command{ getInfoCommand, + getSubsystemsCommand, + enableSubsystemCommand, + disableSubsystemCommand, + getRPCEndpointsCommand, getExchangesCommand, enableExchangeCommand, disableExchangeCommand, diff --git a/communications/base/base_interface.go b/communications/base/base_interface.go index 2ec99f6e..a45f486f 100644 --- a/communications/base/base_interface.go +++ b/communications/base/base_interface.go @@ -1,6 +1,7 @@ package base import ( + "errors" "time" "github.com/thrasher-/gocryptotrader/config" @@ -50,7 +51,7 @@ func (c IComm) PushEvent(event Event) { // GetEnabledCommunicationMediums prints out enabled and connected communication // packages // (#debug output only) -func (c IComm) GetEnabledCommunicationMediums() { +func (c IComm) GetEnabledCommunicationMediums() error { var count int for i := range c { if c[i].IsEnabled() && c[i].IsConnected() { @@ -59,6 +60,7 @@ func (c IComm) GetEnabledCommunicationMediums() { } } if count == 0 { - log.Warnf("Communications: No communication mediums are enabled.") + return errors.New("no communication mediums are enabled") } + return nil } diff --git a/communications/communications.go b/communications/communications.go index df93e493..ca2d4497 100644 --- a/communications/communications.go +++ b/communications/communications.go @@ -1,6 +1,8 @@ package communications import ( + "errors" + "github.com/thrasher-/gocryptotrader/communications/base" "github.com/thrasher-/gocryptotrader/communications/slack" "github.com/thrasher-/gocryptotrader/communications/smsglobal" @@ -15,9 +17,12 @@ type Communications struct { } // NewComm sets up and returns a pointer to a Communications object -func NewComm(cfg *config.CommunicationsConfig) *Communications { - var comm Communications +func NewComm(cfg *config.CommunicationsConfig) (*Communications, error) { + if !cfg.IsAnyEnabled() { + return nil, errors.New("no communication relayers enabled") + } + var comm Communications if cfg.TelegramConfig.Enabled { Telegram := new(telegram.Telegram) Telegram.Setup(cfg) @@ -43,5 +48,5 @@ func NewComm(cfg *config.CommunicationsConfig) *Communications { } comm.Setup() - return &comm + return &comm, nil } diff --git a/communications/communications_test.go b/communications/communications_test.go index 04e1c632..2a214c0b 100644 --- a/communications/communications_test.go +++ b/communications/communications_test.go @@ -8,18 +8,19 @@ import ( func TestNewComm(t *testing.T) { var cfg config.CommunicationsConfig - communications := NewComm(&cfg) - - if len(communications.IComm) != 0 { - t.Errorf("Test failed, communications NewComm, expected len 0, got len %d", - len(communications.IComm)) + _, err := NewComm(&cfg) + if err == nil { + t.Error("NewComm should failed on no enabled communication mediums") } cfg.TelegramConfig.Enabled = true cfg.SMSGlobalConfig.Enabled = true cfg.SMTPConfig.Enabled = true cfg.SlackConfig.Enabled = true - communications = NewComm(&cfg) + communications, err := NewComm(&cfg) + if err != nil { + t.Error("Unexpected result") + } if len(communications.IComm) != 4 { t.Errorf("Test failed, communications NewComm, expected len 4, got len %d", diff --git a/config/config_types.go b/config/config_types.go index cd6b59f2..1a535839 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -202,6 +202,18 @@ type CommunicationsConfig struct { TelegramConfig TelegramConfig `json:"telegram"` } +// IsAnyEnabled returns whether or any any comms relayers +// are enabled +func (c *CommunicationsConfig) IsAnyEnabled() bool { + if c.SMSGlobalConfig.Enabled || + c.SMTPConfig.Enabled || + c.SlackConfig.Enabled || + c.TelegramConfig.Enabled { + return true + } + return false +} + // SlackConfig holds all variables to start and run the Slack package type SlackConfig struct { Name string `json:"name"` diff --git a/engine/comms_relayer.go b/engine/comms_relayer.go new file mode 100644 index 00000000..d1a842a1 --- /dev/null +++ b/engine/comms_relayer.go @@ -0,0 +1,87 @@ +package engine + +import ( + "errors" + "sync/atomic" + + "github.com/thrasher-/gocryptotrader/communications" + "github.com/thrasher-/gocryptotrader/communications/base" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// commsManager starts the NTP manager +type commsManager struct { + started int32 + stopped int32 + shutdown chan struct{} + relayMsg chan base.Event + comms *communications.Communications +} + +func (c *commsManager) Started() bool { + return atomic.LoadInt32(&c.started) == 1 +} + +func (c *commsManager) Start() (err error) { + if atomic.AddInt32(&c.started, 1) != 1 { + return errors.New("communications manager already started") + } + + defer func() { + if err != nil { + atomic.CompareAndSwapInt32(&c.started, 1, 0) + } + }() + + log.Debugln("Communications manager starting...") + commsCfg := Bot.Config.GetCommunicationsConfig() + c.comms, err = communications.NewComm(&commsCfg) + if err != nil { + return err + } + + c.shutdown = make(chan struct{}) + c.relayMsg = make(chan base.Event) + go c.run() + log.Debugln("Communications manager started.") + return nil +} + +func (c *commsManager) Stop() error { + if atomic.LoadInt32(&c.started) == 0 { + return errors.New("communications manager not started") + } + + if atomic.AddInt32(&c.stopped, 1) != 1 { + return errors.New("communications manager is already stopped") + } + + close(c.shutdown) + log.Debugln("Communications manager shutting down...") + return nil +} + +func (c *commsManager) PushEvent(evt base.Event) { + if !c.Started() { + return + } + c.relayMsg <- evt +} + +func (c *commsManager) run() { + defer func() { + // TO-DO shutdown comms connections for connected services (Slack etc) + atomic.CompareAndSwapInt32(&c.stopped, 1, 0) + atomic.CompareAndSwapInt32(&c.started, 1, 0) + log.Debugln("Communications manager shutdown.") + }() + + for { + select { + case msg := <-c.relayMsg: + c.comms.PushEvent(msg) + case <-c.shutdown: + return + } + } +} diff --git a/engine/connection.go b/engine/connection.go index f104c256..0b16c8e3 100644 --- a/engine/connection.go +++ b/engine/connection.go @@ -49,6 +49,8 @@ func (c *connectionManager) Stop() error { log.Debugln("Connection manager shutting down...") c.conn.Shutdown() + atomic.CompareAndSwapInt32(&c.stopped, 1, 0) + atomic.CompareAndSwapInt32(&c.started, 1, 0) log.Debugln("Connection manager stopped.") return nil } diff --git a/engine/engine.go b/engine/engine.go index 7c513bb3..15f803c4 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -12,15 +12,12 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/communications" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/coinmarketcap" - "github.com/thrasher-/gocryptotrader/engine/events" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/request" log "github.com/thrasher-/gocryptotrader/logger" - "github.com/thrasher-/gocryptotrader/ntpclient" "github.com/thrasher-/gocryptotrader/portfolio" "github.com/thrasher-/gocryptotrader/utils" ) @@ -32,10 +29,11 @@ type Engine struct { Portfolio *portfolio.Base Exchanges []exchange.IBotExchange ExchangeCurrencyPairManager *ExchangeCurrencyPairSyncer + NTPManager ntpManager ConnectionManager connectionManager OrderManager orderManager PortfolioManager portfolioManager - CommsRelayer *communications.Communications + CommsManager commsManager Shutdown chan struct{} Settings Settings CryptocurrencyDepositAddresses map[string]map[string]string @@ -148,11 +146,10 @@ func ValidateSettings(b *Engine, s *Settings) { b.Settings.EnableEventManager = s.EnableEventManager if b.Settings.EnableEventManager { - events.Verbose = b.Settings.Verbose if b.Settings.EventManagerDelay != time.Duration(0) && s.EventManagerDelay > 0 { b.Settings.EventManagerDelay = s.EventManagerDelay } else { - b.Settings.EventManagerDelay = events.SleepDelay + b.Settings.EventManagerDelay = EventSleepDelay } } @@ -273,27 +270,8 @@ func (e *Engine) Start() { } if e.Settings.EnableNTPClient { - if e.Config.NTPClient.Level != -1 { - NTPTime, errNTP := ntpclient.NTPClient(e.Config.NTPClient.Pool) - currentTime := time.Now() - if errNTP != nil { - log.Warnf("NTPClient failed to create: %v", errNTP) - } else { - NTPcurrentTimeDifference := NTPTime.Sub(currentTime) - configNTPTime := *e.Config.NTPClient.AllowedDifference - configNTPNegativeTime := (*e.Config.NTPClient.AllowedNegativeDifference - (*e.Config.NTPClient.AllowedNegativeDifference * 2)) - if NTPcurrentTimeDifference > configNTPTime || NTPcurrentTimeDifference < configNTPNegativeTime { - log.Warnf("Time out of sync (NTP): %v | (time.Now()): %v | (Difference): %v | (Allowed): +%v / %v", NTPTime, currentTime, NTPcurrentTimeDifference, configNTPTime, configNTPNegativeTime) - if e.Config.NTPClient.Level == 0 { - disable, errNTP := e.Config.DisableNTPCheck(os.Stdin) - if errNTP != nil { - log.Errorf("failed to disable ntp time check reason: %v", errNTP) - } else { - log.Info(disable) - } - } - } - } + if err := e.NTPManager.Start(); err != nil { + log.Errorf("NTP manager unable to start: %v", err) } } @@ -322,10 +300,9 @@ func (e *Engine) Start() { } if e.Settings.EnableCommsRelayer { - log.Debugln("Starting communication mediums..") - commsCfg := e.Config.GetCommunicationsConfig() - e.CommsRelayer = communications.NewComm(&commsCfg) - e.CommsRelayer.GetEnabledCommunicationMediums() + if err := e.CommsManager.Start(); err != nil { + log.Errorf("Communications manager unable to start: %v", err) + } } var newFxSettings []currency.FXSettings @@ -398,7 +375,7 @@ func (e *Engine) Start() { } if e.Settings.EnableEventManager { - go events.EventManger() + go EventManger() } <-e.Shutdown @@ -419,6 +396,18 @@ func (e *Engine) Stop() { } } + if e.NTPManager.Started() { + if err := e.NTPManager.Stop(); err != nil { + log.Errorf("NTP manager unable to stop. Error: %v", err) + } + } + + if e.CommsManager.Started() { + if err := e.CommsManager.Stop(); err != nil { + log.Errorf("Communication manager unable to stop. Error: %v", err) + } + } + if e.PortfolioManager.Started() { if err := e.PortfolioManager.Stop(); err != nil { log.Errorf("Fund manager unable to stop. Error: %v", err) diff --git a/engine/events/events.go b/engine/events.go similarity index 86% rename from engine/events/events.go rename to engine/events.go index 5b6df499..b2fa5d76 100644 --- a/engine/events/events.go +++ b/engine/events.go @@ -1,4 +1,4 @@ -package events +package engine import ( "errors" @@ -6,7 +6,6 @@ import ( "strings" "time" - "github.com/thrasher-/gocryptotrader/communications" "github.com/thrasher-/gocryptotrader/communications/base" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" @@ -16,6 +15,8 @@ import ( log "github.com/thrasher-/gocryptotrader/logger" ) +// TO-DO MAKE THIS A SERVICE SUBSYSTEM + // Event const vars const ( ItemPrice = "PRICE" @@ -32,7 +33,6 @@ const ( ActionTest = "ACTION_TEST" defaultSleepDelay = time.Millisecond * 500 - defaultVerbose = true ) // vars related to events package @@ -41,16 +41,11 @@ var ( errInvalidCondition = errors.New("invalid conditional option") errInvalidAction = errors.New("invalid action") errExchangeDisabled = errors.New("desired exchange is disabled") - - SleepDelay = defaultSleepDelay - Verbose = defaultVerbose - - // NOTE comms is an interim implementation - comms *communications.Communications + EventSleepDelay = defaultSleepDelay ) -// ConditionParams holds the event condition variables -type ConditionParams struct { +// EventConditionParams holds the event condition variables +type EventConditionParams struct { Condition string Price float64 @@ -64,7 +59,7 @@ type Event struct { ID int64 Exchange string Item string - Condition ConditionParams + Condition EventConditionParams Pair currency.Pair Asset assets.AssetType Action string @@ -75,15 +70,9 @@ type Event struct { // appended var Events []*Event -// SetComms is an interim function that will support a median integration. This -// sets the current comms package. -func SetComms(commsP *communications.Communications) { - comms = commsP -} - // Add adds an event to the Events chain and returns an index/eventID // and an error -func Add(exchange, item string, condition ConditionParams, currencyPair currency.Pair, asset assets.AssetType, action string) (int64, error) { +func Add(exchange, item string, condition EventConditionParams, currencyPair currency.Pair, asset assets.AssetType, action string) (int64, error) { err := IsValidEvent(exchange, item, condition, action) if err != nil { return 0, err @@ -139,7 +128,7 @@ func (e *Event) ExecuteAction() bool { if action[0] == ActionSMSNotify { message := fmt.Sprintf("Event triggered: %s", e.String()) if action[1] == "ALL" { - comms.PushEvent(base.Event{ + Bot.CommsManager.PushEvent(base.Event{ Type: "event", Message: message, }) @@ -164,7 +153,7 @@ func (e *Event) processTicker() bool { t, err := ticker.GetTicker(e.Exchange, e.Pair, e.Asset) if err != nil { - if Verbose { + if Bot.Settings.Verbose { log.Debugf("Events: failed to get ticker. Err: %s", err) } return false @@ -173,7 +162,7 @@ func (e *Event) processTicker() bool { lastPrice := t.Last if lastPrice == 0 { - if Verbose { + if Bot.Settings.Verbose { log.Debugln("Events: ticker last price is 0") } return false @@ -211,7 +200,7 @@ func (e *Event) processCondition(actual, threshold float64) bool { func (e *Event) processOrderbook() bool { ob, err := orderbook.Get(e.Exchange, e.Pair, e.Asset) if err != nil { - if Verbose { + if Bot.Settings.Verbose { log.Debugf("Events: Failed to get orderbook. Err: %s", err) } return false @@ -242,18 +231,17 @@ func (e *Event) processOrderbook() bool { return success } -// CheckCondition will check the event structure to see if there is a condition +// CheckEventCondition will check the event structure to see if there is a condition // met -func (e *Event) CheckCondition() bool { +func (e *Event) CheckEventCondition() bool { if e.Item == ItemPrice { return e.processTicker() } - return e.processOrderbook() } // IsValidEvent checks the actions to be taken and returns an error if incorrect -func IsValidEvent(exchange, item string, condition ConditionParams, action string) error { +func IsValidEvent(exchange, item string, condition EventConditionParams, action string) error { exchange = strings.ToUpper(exchange) item = strings.ToUpper(item) action = strings.ToUpper(action) @@ -290,7 +278,7 @@ func IsValidEvent(exchange, item string, condition ConditionParams, action strin } if a[1] != "ALL" { - comms.PushEvent(base.Event{Type: a[1]}) + Bot.CommsManager.PushEvent(base.Event{Type: a[1]}) } } else if action != ActionConsolePrint && action != ActionTest { return errInvalidAction @@ -302,17 +290,17 @@ func IsValidEvent(exchange, item string, condition ConditionParams, action strin // EventManger is the overarching routine that will iterate through the Events // chain func EventManger() { - log.Debugf("EventManager started. SleepDelay: %v", SleepDelay.String()) + log.Debugf("EventManager started. SleepDelay: %v", EventSleepDelay.String()) for { total, executed := GetEventCounter() if total > 0 && executed != total { for _, event := range Events { if !event.Executed { - if Verbose { + if Bot.Settings.Verbose { log.Debugf("Events: Processing event %s.", event.String()) } - success := event.CheckCondition() + success := event.CheckEventCondition() if success { log.Debugf( "Events: ID: %d triggered on %s successfully.\n", event.ID, @@ -323,7 +311,7 @@ func EventManger() { } } } - time.Sleep(SleepDelay) + time.Sleep(EventSleepDelay) } } diff --git a/engine/events/event_test.go b/engine/events_test.go similarity index 99% rename from engine/events/event_test.go rename to engine/events_test.go index 4cb1149b..966fd2c4 100644 --- a/engine/events/event_test.go +++ b/engine/events_test.go @@ -1,4 +1,4 @@ -package events +package engine // // import ( diff --git a/engine/helpers.go b/engine/helpers.go index 938efa6e..eb33fd9d 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -29,6 +29,87 @@ import ( "github.com/thrasher-/gocryptotrader/utils" ) +// GetSubsystemsStatus returns the status of various subsystems +func GetSubsystemsStatus() map[string]bool { + systems := make(map[string]bool) + systems["communications"] = Bot.CommsManager.Started() + systems["internet_monitor"] = Bot.ConnectionManager.Started() + systems["orders"] = Bot.OrderManager.Started() + systems["portfolio"] = Bot.PortfolioManager.Started() + systems["ntp_timekeeper"] = Bot.NTPManager.Started() + systems["exchange_syncer"] = Bot.Settings.EnableExchangeSyncManager + systems["grpc"] = Bot.Settings.EnableGRPC + systems["grpc_proxy"] = Bot.Settings.EnableGRPCProxy + systems["deprecated_rpc"] = Bot.Settings.EnableDeprecatedRPC + systems["websocket_rpc"] = Bot.Settings.EnableWebsocketRPC + return systems +} + +// RPCEndpoint stores an RPC endpoint status and addr +type RPCEndpoint struct { + Started bool + ListenAddr string +} + +// GetRPCEndpoints returns a list of RPC endpoints and their listen addrs +func GetRPCEndpoints() map[string]RPCEndpoint { + endpoints := make(map[string]RPCEndpoint) + endpoints["grpc"] = RPCEndpoint{ + Started: Bot.Settings.EnableGRPC, + ListenAddr: "grpc://" + Bot.Config.RemoteControl.GRPC.ListenAddress, + } + endpoints["grpc_proxy"] = RPCEndpoint{ + Started: Bot.Settings.EnableGRPCProxy, + ListenAddr: "http://" + Bot.Config.RemoteControl.GRPC.GRPCProxyListenAddress, + } + endpoints["deprecated_rpc"] = RPCEndpoint{ + Started: Bot.Settings.EnableDeprecatedRPC, + ListenAddr: "http://" + Bot.Config.RemoteControl.DeprecatedRPC.ListenAddress, + } + endpoints["websocket_rpc"] = RPCEndpoint{ + Started: Bot.Settings.EnableWebsocketRPC, + ListenAddr: "ws://" + Bot.Config.RemoteControl.WebsocketRPC.ListenAddress, + } + return endpoints +} + +// SetSubsystem enables or disables an engine subsystem +func SetSubsystem(subsys string, enable bool) error { + switch strings.ToLower(subsys) { + case "communications": + if enable { + return Bot.CommsManager.Start() + } + return Bot.CommsManager.Stop() + case "internet_monitor": + if enable { + return Bot.ConnectionManager.Start() + } + return Bot.CommsManager.Stop() + case "orders": + if enable { + return Bot.OrderManager.Start() + } + return Bot.OrderManager.Stop() + case "portfolio": + if enable { + return Bot.PortfolioManager.Start() + } + return Bot.OrderManager.Stop() + case "ntp_timekeeper": + if enable { + return Bot.NTPManager.Start() + } + return Bot.NTPManager.Stop() + case "exchange_syncer": + if enable { + Bot.ExchangeCurrencyPairManager.Start() + } + Bot.ExchangeCurrencyPairManager.Stop() + } + return errors.New("subsystem not found") +} + // GetExchangeOTPs returns OTP codes for all exchanges which have a otpsecret // stored func GetExchangeOTPs() (map[string]string, error) { diff --git a/engine/orders.go b/engine/orders.go index c6870a13..07aa93b1 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -72,9 +72,17 @@ func (o *orderManager) Start() error { return nil } func (o *orderManager) Stop() error { + if atomic.LoadInt32(&o.started) == 0 { + return errors.New("order manager not started") + } + if atomic.AddInt32(&o.stopped, 1) != 1 { return errors.New("order manager is already stopped") } + defer func() { + atomic.CompareAndSwapInt32(&o.stopped, 1, 0) + atomic.CompareAndSwapInt32(&o.started, 1, 0) + }() log.Debugln("Order manager shutting down...") close(o.shutdown) @@ -101,7 +109,7 @@ func (o *orderManager) gracefulShutdown() { msg := fmt.Sprintf("Order manager: Exchange %s unable to cancel order ID=%v. Err: %s", k, v[y].ID, err) log.Debugln(msg) - Bot.CommsRelayer.PushEvent(base.Event{ + Bot.CommsManager.PushEvent(base.Event{ Type: "order", Message: msg, }) @@ -111,7 +119,7 @@ func (o *orderManager) gracefulShutdown() { msg := fmt.Sprintf("Order manager: Exchange %s order ID=%v cancelled.", k, v[y].ID) log.Debugln(msg) - Bot.CommsRelayer.PushEvent(base.Event{ + Bot.CommsManager.PushEvent(base.Event{ Type: "order", Message: msg, }) @@ -222,7 +230,7 @@ func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) msg := fmt.Sprintf("Order manager: Exchange %s submitted order ID=%v [Ours: %v] pair=%v price=%v amount=%v side=%v type=%v.", exchName, result.OrderID, id.String(), order.Pair, order.Price, order.Amount, order.OrderSide, order.OrderType) log.Debugln(msg) - Bot.CommsRelayer.PushEvent(base.Event{ + Bot.CommsManager.PushEvent(base.Event{ Type: "order", Message: msg, }) @@ -257,7 +265,7 @@ func (o *orderManager) processOrders() { msg := fmt.Sprintf("Order manager: Exchange %s added order ID=%v pair=%v price=%v amount=%v side=%v type=%v.", order.Exchange, order.ID, order.CurrencyPair, order.Price, order.Amount, order.OrderSide, order.OrderType) log.Debug(msg) - Bot.CommsRelayer.PushEvent(base.Event{ + Bot.CommsManager.PushEvent(base.Event{ Type: "order", Message: msg, }) diff --git a/engine/portfolio.go b/engine/portfolio.go index 5d91d2b7..3116472f 100644 --- a/engine/portfolio.go +++ b/engine/portfolio.go @@ -51,9 +51,11 @@ func (p *portfolioManager) run() { Bot.ServicesWG.Add(1) tick := time.NewTicker(PortfolioSleepDelay) defer func() { - log.Debugf("Portfolio manager shutdown.") + atomic.CompareAndSwapInt32(&p.stopped, 1, 0) + atomic.CompareAndSwapInt32(&p.started, 1, 0) tick.Stop() Bot.ServicesWG.Done() + log.Debugf("Portfolio manager shutdown.") }() for { diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 6fd20978..958e2194 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -15,7 +15,6 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/engine/events" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/assets" "github.com/thrasher-/gocryptotrader/gctrpc" @@ -152,8 +151,47 @@ func (s *RPCServer) GetInfo(ctx context.Context, r *gctrpc.GetInfoRequest) (*gct AvailableExchanges: int64(len(Bot.Config.Exchanges)), DefaultFiatCurrency: Bot.Config.Currency.FiatDisplayCurrency.String(), DefaultForexProvider: Bot.Config.GetPrimaryForexProvider(), + SubsystemStatus: GetSubsystemsStatus(), } + endpoints := GetRPCEndpoints() + resp.RpcEndpoints = make(map[string]*gctrpc.RPCEndpoint) + for k, v := range endpoints { + resp.RpcEndpoints[k] = &gctrpc.RPCEndpoint{ + Started: v.Started, + ListenAddress: v.ListenAddr, + } + } + return &resp, nil +} +// GetSubsystems returns a list of subsystems and their status +func (s *RPCServer) GetSubsystems(ctx context.Context, r *gctrpc.GetSubsystemsRequest) (*gctrpc.GetSusbsytemsResponse, error) { + return &gctrpc.GetSusbsytemsResponse{SubsystemsStatus: GetSubsystemsStatus()}, nil +} + +// EnableSubsystem enables a engine subsytem +func (s *RPCServer) EnableSubsystem(ctx context.Context, r *gctrpc.GenericSubsystemRequest) (*gctrpc.GenericSubsystemResponse, error) { + err := SetSubsystem(r.Subsystem, true) + return &gctrpc.GenericSubsystemResponse{}, err +} + +// DisableSubsystem disables a engine subsytem +func (s *RPCServer) DisableSubsystem(ctx context.Context, r *gctrpc.GenericSubsystemRequest) (*gctrpc.GenericSubsystemResponse, error) { + err := SetSubsystem(r.Subsystem, false) + return &gctrpc.GenericSubsystemResponse{}, err +} + +// GetRPCEndpoints returns a list of API endpoints +func (s *RPCServer) GetRPCEndpoints(ctx context.Context, r *gctrpc.GetRPCEndpointsRequest) (*gctrpc.GetRPCEndpointsResponse, error) { + endpoints := GetRPCEndpoints() + var resp gctrpc.GetRPCEndpointsResponse + resp.Endpoints = make(map[string]*gctrpc.RPCEndpoint) + for k, v := range endpoints { + resp.Endpoints[k] = &gctrpc.RPCEndpoint{ + Started: v.Started, + ListenAddress: v.ListenAddr, + } + } return &resp, nil } @@ -638,7 +676,7 @@ func (s *RPCServer) GetEvents(ctx context.Context, r *gctrpc.GetEventsRequest) ( // AddEvent adds an event func (s *RPCServer) AddEvent(ctx context.Context, r *gctrpc.AddEventRequest) (*gctrpc.AddEventResponse, error) { - evtCondition := events.ConditionParams{ + evtCondition := EventConditionParams{ CheckBids: r.ConditionParams.CheckBids, CheckBidsAndAsks: r.ConditionParams.CheckBidsAndAsks, Condition: r.ConditionParams.Condition, @@ -649,7 +687,7 @@ func (s *RPCServer) AddEvent(ctx context.Context, r *gctrpc.AddEventRequest) (*g p := currency.NewPairWithDelimiter(r.Pair.Base, r.Pair.Quote, r.Pair.Delimiter) - id, err := events.Add(r.Exchange, r.Item, evtCondition, p, assets.AssetType(r.AssetType), r.Action) + id, err := Add(r.Exchange, r.Item, evtCondition, p, assets.AssetType(r.AssetType), r.Action) if err != nil { return nil, err } @@ -659,7 +697,7 @@ func (s *RPCServer) AddEvent(ctx context.Context, r *gctrpc.AddEventRequest) (*g // RemoveEvent removes an event, specified by an event ID func (s *RPCServer) RemoveEvent(ctx context.Context, r *gctrpc.RemoveEventRequest) (*gctrpc.RemoveEventResponse, error) { - events.Remove(r.Id) + Remove(r.Id) return &gctrpc.RemoveEventResponse{}, nil } diff --git a/engine/timekeeper.go b/engine/timekeeper.go new file mode 100644 index 00000000..b42fbd1c --- /dev/null +++ b/engine/timekeeper.go @@ -0,0 +1,140 @@ +package engine + +import ( + "errors" + "fmt" + "os" + "sync/atomic" + "time" + + log "github.com/thrasher-/gocryptotrader/logger" + ntpclient "github.com/thrasher-/gocryptotrader/ntpclient" +) + +// vars related to the NTP manager +var ( + NTPCheckInterval = time.Second * 30 + NTPRetryLimit = 3 + errNTPDisabled = errors.New("ntp client disabled") +) + +// ntpManager starts the NTP manager +type ntpManager struct { + started int32 + stopped int32 + inititalCheck bool + shutdown chan struct{} +} + +func (n *ntpManager) Started() bool { + return atomic.LoadInt32(&n.started) == 1 +} + +func (n *ntpManager) Start() (err error) { + if atomic.AddInt32(&n.started, 1) != 1 { + return errors.New("NTP manager already started") + } + + var disable bool + defer func() { + if err != nil || disable { + atomic.CompareAndSwapInt32(&n.started, 1, 0) + } + }() + + log.Debugln("NTP manager starting...") + if Bot.Config.NTPClient.Level == 0 { + // Initial NTP check (prompts user on how we should proceed) + n.inititalCheck = true + + // Sometimes the NTP client can have transient issues due to UDP, try + // the default retry limits before giving up + for i := 0; i < NTPRetryLimit; i++ { + err = n.processTime() + switch err { + case nil: + break + case errNTPDisabled: + log.Debugf("NTP manager: User disabled NTP prompts. Exiting.") + disable = true + err = nil + return + default: + if i == NTPRetryLimit-1 { + return err + } + } + } + } + n.shutdown = make(chan struct{}) + go n.run() + log.Debugln("NTP manager started.") + return nil +} + +func (n *ntpManager) Stop() error { + if atomic.LoadInt32(&n.started) == 0 { + return errors.New("NTP manager not started") + } + + if atomic.AddInt32(&n.stopped, 1) != 1 { + return errors.New("NTP manager is already stopped") + } + + close(n.shutdown) + log.Debugln("NTP manager shutting down...") + return nil +} + +func (n *ntpManager) run() { + t := time.NewTicker(NTPCheckInterval) + defer func() { + t.Stop() + atomic.CompareAndSwapInt32(&n.stopped, 1, 0) + atomic.CompareAndSwapInt32(&n.started, 1, 0) + log.Debugln("NTP manager shutdown.") + }() + + for { + select { + case <-n.shutdown: + return + case <-t.C: + n.processTime() + if Bot.Config.NTPClient.Level == 0 { + close(n.shutdown) + } + } + } +} + +func (n *ntpManager) FetchNTPTime() (time.Time, error) { + return ntpclient.NTPClient(Bot.Config.NTPClient.Pool) +} + +func (n *ntpManager) processTime() error { + NTPTime, err := n.FetchNTPTime() + if err != nil { + return err + } + + currentTime := time.Now() + NTPcurrentTimeDifference := NTPTime.Sub(currentTime) + configNTPTime := *Bot.Config.NTPClient.AllowedDifference + configNTPNegativeTime := (*Bot.Config.NTPClient.AllowedNegativeDifference - (*Bot.Config.NTPClient.AllowedNegativeDifference * 2)) + if NTPcurrentTimeDifference > configNTPTime || NTPcurrentTimeDifference < configNTPNegativeTime { + log.Warnf("NTP manager: Time out of sync (NTP): %v | (time.Now()): %v | (Difference): %v | (Allowed): +%v / %v", NTPTime, currentTime, NTPcurrentTimeDifference, configNTPTime, configNTPNegativeTime) + if n.inititalCheck { + n.inititalCheck = false + disable, err := Bot.Config.DisableNTPCheck(os.Stdin) + if err != nil { + return fmt.Errorf("unable to disable NTP check: %s", err) + } + log.Info(disable) + if Bot.Config.NTPClient.Level == -1 { + return errNTPDisabled + } + } + } + return nil +} diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index 70fa45fc..64af67fa 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -55,14 +55,16 @@ func (m *GetInfoRequest) XXX_DiscardUnknown() { var xxx_messageInfo_GetInfoRequest proto.InternalMessageInfo type GetInfoResponse struct { - Uptime string `protobuf:"bytes,1,opt,name=uptime,proto3" json:"uptime,omitempty"` - AvailableExchanges int64 `protobuf:"varint,2,opt,name=available_exchanges,json=availableExchanges,proto3" json:"available_exchanges,omitempty"` - EnabledExchanges int64 `protobuf:"varint,3,opt,name=enabled_exchanges,json=enabledExchanges,proto3" json:"enabled_exchanges,omitempty"` - DefaultForexProvider string `protobuf:"bytes,4,opt,name=default_forex_provider,json=defaultForexProvider,proto3" json:"default_forex_provider,omitempty"` - DefaultFiatCurrency string `protobuf:"bytes,5,opt,name=default_fiat_currency,json=defaultFiatCurrency,proto3" json:"default_fiat_currency,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Uptime string `protobuf:"bytes,1,opt,name=uptime,proto3" json:"uptime,omitempty"` + AvailableExchanges int64 `protobuf:"varint,2,opt,name=available_exchanges,json=availableExchanges,proto3" json:"available_exchanges,omitempty"` + EnabledExchanges int64 `protobuf:"varint,3,opt,name=enabled_exchanges,json=enabledExchanges,proto3" json:"enabled_exchanges,omitempty"` + DefaultForexProvider string `protobuf:"bytes,4,opt,name=default_forex_provider,json=defaultForexProvider,proto3" json:"default_forex_provider,omitempty"` + DefaultFiatCurrency string `protobuf:"bytes,5,opt,name=default_fiat_currency,json=defaultFiatCurrency,proto3" json:"default_fiat_currency,omitempty"` + SubsystemStatus map[string]bool `protobuf:"bytes,6,rep,name=subsystem_status,json=subsystemStatus,proto3" json:"subsystem_status,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + RpcEndpoints map[string]*RPCEndpoint `protobuf:"bytes,7,rep,name=rpc_endpoints,json=rpcEndpoints,proto3" json:"rpc_endpoints,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *GetInfoResponse) Reset() { *m = GetInfoResponse{} } @@ -125,6 +127,340 @@ func (m *GetInfoResponse) GetDefaultFiatCurrency() string { return "" } +func (m *GetInfoResponse) GetSubsystemStatus() map[string]bool { + if m != nil { + return m.SubsystemStatus + } + return nil +} + +func (m *GetInfoResponse) GetRpcEndpoints() map[string]*RPCEndpoint { + if m != nil { + return m.RpcEndpoints + } + return nil +} + +// TO-DO comms APIs +type GetCommunicationRelayersRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetCommunicationRelayersRequest) Reset() { *m = GetCommunicationRelayersRequest{} } +func (m *GetCommunicationRelayersRequest) String() string { return proto.CompactTextString(m) } +func (*GetCommunicationRelayersRequest) ProtoMessage() {} +func (*GetCommunicationRelayersRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{2} +} + +func (m *GetCommunicationRelayersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCommunicationRelayersRequest.Unmarshal(m, b) +} +func (m *GetCommunicationRelayersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCommunicationRelayersRequest.Marshal(b, m, deterministic) +} +func (m *GetCommunicationRelayersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCommunicationRelayersRequest.Merge(m, src) +} +func (m *GetCommunicationRelayersRequest) XXX_Size() int { + return xxx_messageInfo_GetCommunicationRelayersRequest.Size(m) +} +func (m *GetCommunicationRelayersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetCommunicationRelayersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCommunicationRelayersRequest proto.InternalMessageInfo + +type GetCommunicationRelayersResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetCommunicationRelayersResponse) Reset() { *m = GetCommunicationRelayersResponse{} } +func (m *GetCommunicationRelayersResponse) String() string { return proto.CompactTextString(m) } +func (*GetCommunicationRelayersResponse) ProtoMessage() {} +func (*GetCommunicationRelayersResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{3} +} + +func (m *GetCommunicationRelayersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCommunicationRelayersResponse.Unmarshal(m, b) +} +func (m *GetCommunicationRelayersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCommunicationRelayersResponse.Marshal(b, m, deterministic) +} +func (m *GetCommunicationRelayersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCommunicationRelayersResponse.Merge(m, src) +} +func (m *GetCommunicationRelayersResponse) XXX_Size() int { + return xxx_messageInfo_GetCommunicationRelayersResponse.Size(m) +} +func (m *GetCommunicationRelayersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetCommunicationRelayersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCommunicationRelayersResponse proto.InternalMessageInfo + +type GenericSubsystemRequest struct { + Subsystem string `protobuf:"bytes,1,opt,name=subsystem,proto3" json:"subsystem,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GenericSubsystemRequest) Reset() { *m = GenericSubsystemRequest{} } +func (m *GenericSubsystemRequest) String() string { return proto.CompactTextString(m) } +func (*GenericSubsystemRequest) ProtoMessage() {} +func (*GenericSubsystemRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{4} +} + +func (m *GenericSubsystemRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GenericSubsystemRequest.Unmarshal(m, b) +} +func (m *GenericSubsystemRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GenericSubsystemRequest.Marshal(b, m, deterministic) +} +func (m *GenericSubsystemRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenericSubsystemRequest.Merge(m, src) +} +func (m *GenericSubsystemRequest) XXX_Size() int { + return xxx_messageInfo_GenericSubsystemRequest.Size(m) +} +func (m *GenericSubsystemRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GenericSubsystemRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GenericSubsystemRequest proto.InternalMessageInfo + +func (m *GenericSubsystemRequest) GetSubsystem() string { + if m != nil { + return m.Subsystem + } + return "" +} + +type GenericSubsystemResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GenericSubsystemResponse) Reset() { *m = GenericSubsystemResponse{} } +func (m *GenericSubsystemResponse) String() string { return proto.CompactTextString(m) } +func (*GenericSubsystemResponse) ProtoMessage() {} +func (*GenericSubsystemResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{5} +} + +func (m *GenericSubsystemResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GenericSubsystemResponse.Unmarshal(m, b) +} +func (m *GenericSubsystemResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GenericSubsystemResponse.Marshal(b, m, deterministic) +} +func (m *GenericSubsystemResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenericSubsystemResponse.Merge(m, src) +} +func (m *GenericSubsystemResponse) XXX_Size() int { + return xxx_messageInfo_GenericSubsystemResponse.Size(m) +} +func (m *GenericSubsystemResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GenericSubsystemResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GenericSubsystemResponse proto.InternalMessageInfo + +type GetSubsystemsRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetSubsystemsRequest) Reset() { *m = GetSubsystemsRequest{} } +func (m *GetSubsystemsRequest) String() string { return proto.CompactTextString(m) } +func (*GetSubsystemsRequest) ProtoMessage() {} +func (*GetSubsystemsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{6} +} + +func (m *GetSubsystemsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetSubsystemsRequest.Unmarshal(m, b) +} +func (m *GetSubsystemsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetSubsystemsRequest.Marshal(b, m, deterministic) +} +func (m *GetSubsystemsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetSubsystemsRequest.Merge(m, src) +} +func (m *GetSubsystemsRequest) XXX_Size() int { + return xxx_messageInfo_GetSubsystemsRequest.Size(m) +} +func (m *GetSubsystemsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetSubsystemsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetSubsystemsRequest proto.InternalMessageInfo + +type GetSusbsytemsResponse struct { + SubsystemsStatus map[string]bool `protobuf:"bytes,1,rep,name=subsystems_status,json=subsystemsStatus,proto3" json:"subsystems_status,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetSusbsytemsResponse) Reset() { *m = GetSusbsytemsResponse{} } +func (m *GetSusbsytemsResponse) String() string { return proto.CompactTextString(m) } +func (*GetSusbsytemsResponse) ProtoMessage() {} +func (*GetSusbsytemsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{7} +} + +func (m *GetSusbsytemsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetSusbsytemsResponse.Unmarshal(m, b) +} +func (m *GetSusbsytemsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetSusbsytemsResponse.Marshal(b, m, deterministic) +} +func (m *GetSusbsytemsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetSusbsytemsResponse.Merge(m, src) +} +func (m *GetSusbsytemsResponse) XXX_Size() int { + return xxx_messageInfo_GetSusbsytemsResponse.Size(m) +} +func (m *GetSusbsytemsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetSusbsytemsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetSusbsytemsResponse proto.InternalMessageInfo + +func (m *GetSusbsytemsResponse) GetSubsystemsStatus() map[string]bool { + if m != nil { + return m.SubsystemsStatus + } + return nil +} + +type GetRPCEndpointsRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetRPCEndpointsRequest) Reset() { *m = GetRPCEndpointsRequest{} } +func (m *GetRPCEndpointsRequest) String() string { return proto.CompactTextString(m) } +func (*GetRPCEndpointsRequest) ProtoMessage() {} +func (*GetRPCEndpointsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{8} +} + +func (m *GetRPCEndpointsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetRPCEndpointsRequest.Unmarshal(m, b) +} +func (m *GetRPCEndpointsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetRPCEndpointsRequest.Marshal(b, m, deterministic) +} +func (m *GetRPCEndpointsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetRPCEndpointsRequest.Merge(m, src) +} +func (m *GetRPCEndpointsRequest) XXX_Size() int { + return xxx_messageInfo_GetRPCEndpointsRequest.Size(m) +} +func (m *GetRPCEndpointsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetRPCEndpointsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetRPCEndpointsRequest proto.InternalMessageInfo + +type RPCEndpoint struct { + Started bool `protobuf:"varint,1,opt,name=started,proto3" json:"started,omitempty"` + ListenAddress string `protobuf:"bytes,2,opt,name=listen_address,json=listenAddress,proto3" json:"listen_address,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RPCEndpoint) Reset() { *m = RPCEndpoint{} } +func (m *RPCEndpoint) String() string { return proto.CompactTextString(m) } +func (*RPCEndpoint) ProtoMessage() {} +func (*RPCEndpoint) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{9} +} + +func (m *RPCEndpoint) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RPCEndpoint.Unmarshal(m, b) +} +func (m *RPCEndpoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RPCEndpoint.Marshal(b, m, deterministic) +} +func (m *RPCEndpoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_RPCEndpoint.Merge(m, src) +} +func (m *RPCEndpoint) XXX_Size() int { + return xxx_messageInfo_RPCEndpoint.Size(m) +} +func (m *RPCEndpoint) XXX_DiscardUnknown() { + xxx_messageInfo_RPCEndpoint.DiscardUnknown(m) +} + +var xxx_messageInfo_RPCEndpoint proto.InternalMessageInfo + +func (m *RPCEndpoint) GetStarted() bool { + if m != nil { + return m.Started + } + return false +} + +func (m *RPCEndpoint) GetListenAddress() string { + if m != nil { + return m.ListenAddress + } + return "" +} + +type GetRPCEndpointsResponse struct { + Endpoints map[string]*RPCEndpoint `protobuf:"bytes,1,rep,name=endpoints,proto3" json:"endpoints,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetRPCEndpointsResponse) Reset() { *m = GetRPCEndpointsResponse{} } +func (m *GetRPCEndpointsResponse) String() string { return proto.CompactTextString(m) } +func (*GetRPCEndpointsResponse) ProtoMessage() {} +func (*GetRPCEndpointsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{10} +} + +func (m *GetRPCEndpointsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetRPCEndpointsResponse.Unmarshal(m, b) +} +func (m *GetRPCEndpointsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetRPCEndpointsResponse.Marshal(b, m, deterministic) +} +func (m *GetRPCEndpointsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetRPCEndpointsResponse.Merge(m, src) +} +func (m *GetRPCEndpointsResponse) XXX_Size() int { + return xxx_messageInfo_GetRPCEndpointsResponse.Size(m) +} +func (m *GetRPCEndpointsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetRPCEndpointsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetRPCEndpointsResponse proto.InternalMessageInfo + +func (m *GetRPCEndpointsResponse) GetEndpoints() map[string]*RPCEndpoint { + if m != nil { + return m.Endpoints + } + return nil +} + type GenericExchangeNameRequest struct { Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -136,7 +472,7 @@ func (m *GenericExchangeNameRequest) Reset() { *m = GenericExchangeNameR func (m *GenericExchangeNameRequest) String() string { return proto.CompactTextString(m) } func (*GenericExchangeNameRequest) ProtoMessage() {} func (*GenericExchangeNameRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{2} + return fileDescriptor_77a6da22d6a3feb1, []int{11} } func (m *GenericExchangeNameRequest) XXX_Unmarshal(b []byte) error { @@ -174,7 +510,7 @@ func (m *GenericExchangeNameResponse) Reset() { *m = GenericExchangeName func (m *GenericExchangeNameResponse) String() string { return proto.CompactTextString(m) } func (*GenericExchangeNameResponse) ProtoMessage() {} func (*GenericExchangeNameResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{3} + return fileDescriptor_77a6da22d6a3feb1, []int{12} } func (m *GenericExchangeNameResponse) XXX_Unmarshal(b []byte) error { @@ -206,7 +542,7 @@ func (m *GetExchangesRequest) Reset() { *m = GetExchangesRequest{} } func (m *GetExchangesRequest) String() string { return proto.CompactTextString(m) } func (*GetExchangesRequest) ProtoMessage() {} func (*GetExchangesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{4} + return fileDescriptor_77a6da22d6a3feb1, []int{13} } func (m *GetExchangesRequest) XXX_Unmarshal(b []byte) error { @@ -245,7 +581,7 @@ func (m *GetExchangesResponse) Reset() { *m = GetExchangesResponse{} } func (m *GetExchangesResponse) String() string { return proto.CompactTextString(m) } func (*GetExchangesResponse) ProtoMessage() {} func (*GetExchangesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{5} + return fileDescriptor_77a6da22d6a3feb1, []int{14} } func (m *GetExchangesResponse) XXX_Unmarshal(b []byte) error { @@ -284,7 +620,7 @@ func (m *GetExchangeOTPReponse) Reset() { *m = GetExchangeOTPReponse{} } func (m *GetExchangeOTPReponse) String() string { return proto.CompactTextString(m) } func (*GetExchangeOTPReponse) ProtoMessage() {} func (*GetExchangeOTPReponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{6} + return fileDescriptor_77a6da22d6a3feb1, []int{15} } func (m *GetExchangeOTPReponse) XXX_Unmarshal(b []byte) error { @@ -322,7 +658,7 @@ func (m *GetExchangeOTPsRequest) Reset() { *m = GetExchangeOTPsRequest{} func (m *GetExchangeOTPsRequest) String() string { return proto.CompactTextString(m) } func (*GetExchangeOTPsRequest) ProtoMessage() {} func (*GetExchangeOTPsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{7} + return fileDescriptor_77a6da22d6a3feb1, []int{16} } func (m *GetExchangeOTPsRequest) XXX_Unmarshal(b []byte) error { @@ -354,7 +690,7 @@ func (m *GetExchangeOTPsResponse) Reset() { *m = GetExchangeOTPsResponse func (m *GetExchangeOTPsResponse) String() string { return proto.CompactTextString(m) } func (*GetExchangeOTPsResponse) ProtoMessage() {} func (*GetExchangeOTPsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{8} + return fileDescriptor_77a6da22d6a3feb1, []int{17} } func (m *GetExchangeOTPsResponse) XXX_Unmarshal(b []byte) error { @@ -393,7 +729,7 @@ func (m *DisableExchangeRequest) Reset() { *m = DisableExchangeRequest{} func (m *DisableExchangeRequest) String() string { return proto.CompactTextString(m) } func (*DisableExchangeRequest) ProtoMessage() {} func (*DisableExchangeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{9} + return fileDescriptor_77a6da22d6a3feb1, []int{18} } func (m *DisableExchangeRequest) XXX_Unmarshal(b []byte) error { @@ -443,7 +779,7 @@ func (m *GetExchangeInfoResponse) Reset() { *m = GetExchangeInfoResponse func (m *GetExchangeInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetExchangeInfoResponse) ProtoMessage() {} func (*GetExchangeInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{10} + return fileDescriptor_77a6da22d6a3feb1, []int{19} } func (m *GetExchangeInfoResponse) XXX_Unmarshal(b []byte) error { @@ -561,7 +897,7 @@ func (m *GetTickerRequest) Reset() { *m = GetTickerRequest{} } func (m *GetTickerRequest) String() string { return proto.CompactTextString(m) } func (*GetTickerRequest) ProtoMessage() {} func (*GetTickerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{11} + return fileDescriptor_77a6da22d6a3feb1, []int{20} } func (m *GetTickerRequest) XXX_Unmarshal(b []byte) error { @@ -616,7 +952,7 @@ func (m *CurrencyPair) Reset() { *m = CurrencyPair{} } func (m *CurrencyPair) String() string { return proto.CompactTextString(m) } func (*CurrencyPair) ProtoMessage() {} func (*CurrencyPair) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{12} + return fileDescriptor_77a6da22d6a3feb1, []int{21} } func (m *CurrencyPair) XXX_Unmarshal(b []byte) error { @@ -678,7 +1014,7 @@ func (m *TickerResponse) Reset() { *m = TickerResponse{} } func (m *TickerResponse) String() string { return proto.CompactTextString(m) } func (*TickerResponse) ProtoMessage() {} func (*TickerResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{13} + return fileDescriptor_77a6da22d6a3feb1, []int{22} } func (m *TickerResponse) XXX_Unmarshal(b []byte) error { @@ -779,7 +1115,7 @@ func (m *GetTickersRequest) Reset() { *m = GetTickersRequest{} } func (m *GetTickersRequest) String() string { return proto.CompactTextString(m) } func (*GetTickersRequest) ProtoMessage() {} func (*GetTickersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{14} + return fileDescriptor_77a6da22d6a3feb1, []int{23} } func (m *GetTickersRequest) XXX_Unmarshal(b []byte) error { @@ -812,7 +1148,7 @@ func (m *Tickers) Reset() { *m = Tickers{} } func (m *Tickers) String() string { return proto.CompactTextString(m) } func (*Tickers) ProtoMessage() {} func (*Tickers) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{15} + return fileDescriptor_77a6da22d6a3feb1, []int{24} } func (m *Tickers) XXX_Unmarshal(b []byte) error { @@ -858,7 +1194,7 @@ func (m *GetTickersResponse) Reset() { *m = GetTickersResponse{} } func (m *GetTickersResponse) String() string { return proto.CompactTextString(m) } func (*GetTickersResponse) ProtoMessage() {} func (*GetTickersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{16} + return fileDescriptor_77a6da22d6a3feb1, []int{25} } func (m *GetTickersResponse) XXX_Unmarshal(b []byte) error { @@ -899,7 +1235,7 @@ func (m *GetOrderbookRequest) Reset() { *m = GetOrderbookRequest{} } func (m *GetOrderbookRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderbookRequest) ProtoMessage() {} func (*GetOrderbookRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{17} + return fileDescriptor_77a6da22d6a3feb1, []int{26} } func (m *GetOrderbookRequest) XXX_Unmarshal(b []byte) error { @@ -954,7 +1290,7 @@ func (m *OrderbookItem) Reset() { *m = OrderbookItem{} } func (m *OrderbookItem) String() string { return proto.CompactTextString(m) } func (*OrderbookItem) ProtoMessage() {} func (*OrderbookItem) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{18} + return fileDescriptor_77a6da22d6a3feb1, []int{27} } func (m *OrderbookItem) XXX_Unmarshal(b []byte) error { @@ -1012,7 +1348,7 @@ func (m *OrderbookResponse) Reset() { *m = OrderbookResponse{} } func (m *OrderbookResponse) String() string { return proto.CompactTextString(m) } func (*OrderbookResponse) ProtoMessage() {} func (*OrderbookResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{19} + return fileDescriptor_77a6da22d6a3feb1, []int{28} } func (m *OrderbookResponse) XXX_Unmarshal(b []byte) error { @@ -1085,7 +1421,7 @@ func (m *GetOrderbooksRequest) Reset() { *m = GetOrderbooksRequest{} } func (m *GetOrderbooksRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderbooksRequest) ProtoMessage() {} func (*GetOrderbooksRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{20} + return fileDescriptor_77a6da22d6a3feb1, []int{29} } func (m *GetOrderbooksRequest) XXX_Unmarshal(b []byte) error { @@ -1118,7 +1454,7 @@ func (m *Orderbooks) Reset() { *m = Orderbooks{} } func (m *Orderbooks) String() string { return proto.CompactTextString(m) } func (*Orderbooks) ProtoMessage() {} func (*Orderbooks) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{21} + return fileDescriptor_77a6da22d6a3feb1, []int{30} } func (m *Orderbooks) XXX_Unmarshal(b []byte) error { @@ -1164,7 +1500,7 @@ func (m *GetOrderbooksResponse) Reset() { *m = GetOrderbooksResponse{} } func (m *GetOrderbooksResponse) String() string { return proto.CompactTextString(m) } func (*GetOrderbooksResponse) ProtoMessage() {} func (*GetOrderbooksResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{22} + return fileDescriptor_77a6da22d6a3feb1, []int{31} } func (m *GetOrderbooksResponse) XXX_Unmarshal(b []byte) error { @@ -1203,7 +1539,7 @@ func (m *GetAccountInfoRequest) Reset() { *m = GetAccountInfoRequest{} } func (m *GetAccountInfoRequest) String() string { return proto.CompactTextString(m) } func (*GetAccountInfoRequest) ProtoMessage() {} func (*GetAccountInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{23} + return fileDescriptor_77a6da22d6a3feb1, []int{32} } func (m *GetAccountInfoRequest) XXX_Unmarshal(b []byte) error { @@ -1243,7 +1579,7 @@ func (m *Account) Reset() { *m = Account{} } func (m *Account) String() string { return proto.CompactTextString(m) } func (*Account) ProtoMessage() {} func (*Account) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{24} + return fileDescriptor_77a6da22d6a3feb1, []int{33} } func (m *Account) XXX_Unmarshal(b []byte) error { @@ -1291,7 +1627,7 @@ func (m *AccountCurrencyInfo) Reset() { *m = AccountCurrencyInfo{} } func (m *AccountCurrencyInfo) String() string { return proto.CompactTextString(m) } func (*AccountCurrencyInfo) ProtoMessage() {} func (*AccountCurrencyInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{25} + return fileDescriptor_77a6da22d6a3feb1, []int{34} } func (m *AccountCurrencyInfo) XXX_Unmarshal(b []byte) error { @@ -1345,7 +1681,7 @@ func (m *GetAccountInfoResponse) Reset() { *m = GetAccountInfoResponse{} func (m *GetAccountInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetAccountInfoResponse) ProtoMessage() {} func (*GetAccountInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{26} + return fileDescriptor_77a6da22d6a3feb1, []int{35} } func (m *GetAccountInfoResponse) XXX_Unmarshal(b []byte) error { @@ -1390,7 +1726,7 @@ func (m *GetConfigRequest) Reset() { *m = GetConfigRequest{} } func (m *GetConfigRequest) String() string { return proto.CompactTextString(m) } func (*GetConfigRequest) ProtoMessage() {} func (*GetConfigRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{27} + return fileDescriptor_77a6da22d6a3feb1, []int{36} } func (m *GetConfigRequest) XXX_Unmarshal(b []byte) error { @@ -1422,7 +1758,7 @@ func (m *GetConfigResponse) Reset() { *m = GetConfigResponse{} } func (m *GetConfigResponse) String() string { return proto.CompactTextString(m) } func (*GetConfigResponse) ProtoMessage() {} func (*GetConfigResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{28} + return fileDescriptor_77a6da22d6a3feb1, []int{37} } func (m *GetConfigResponse) XXX_Unmarshal(b []byte) error { @@ -1464,7 +1800,7 @@ func (m *PortfolioAddress) Reset() { *m = PortfolioAddress{} } func (m *PortfolioAddress) String() string { return proto.CompactTextString(m) } func (*PortfolioAddress) ProtoMessage() {} func (*PortfolioAddress) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{29} + return fileDescriptor_77a6da22d6a3feb1, []int{38} } func (m *PortfolioAddress) XXX_Unmarshal(b []byte) error { @@ -1523,7 +1859,7 @@ func (m *GetPortfolioRequest) Reset() { *m = GetPortfolioRequest{} } func (m *GetPortfolioRequest) String() string { return proto.CompactTextString(m) } func (*GetPortfolioRequest) ProtoMessage() {} func (*GetPortfolioRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{30} + return fileDescriptor_77a6da22d6a3feb1, []int{39} } func (m *GetPortfolioRequest) XXX_Unmarshal(b []byte) error { @@ -1555,7 +1891,7 @@ func (m *GetPortfolioResponse) Reset() { *m = GetPortfolioResponse{} } func (m *GetPortfolioResponse) String() string { return proto.CompactTextString(m) } func (*GetPortfolioResponse) ProtoMessage() {} func (*GetPortfolioResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{31} + return fileDescriptor_77a6da22d6a3feb1, []int{40} } func (m *GetPortfolioResponse) XXX_Unmarshal(b []byte) error { @@ -1593,7 +1929,7 @@ func (m *GetPortfolioSummaryRequest) Reset() { *m = GetPortfolioSummaryR func (m *GetPortfolioSummaryRequest) String() string { return proto.CompactTextString(m) } func (*GetPortfolioSummaryRequest) ProtoMessage() {} func (*GetPortfolioSummaryRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{32} + return fileDescriptor_77a6da22d6a3feb1, []int{41} } func (m *GetPortfolioSummaryRequest) XXX_Unmarshal(b []byte) error { @@ -1628,7 +1964,7 @@ func (m *Coin) Reset() { *m = Coin{} } func (m *Coin) String() string { return proto.CompactTextString(m) } func (*Coin) ProtoMessage() {} func (*Coin) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{33} + return fileDescriptor_77a6da22d6a3feb1, []int{42} } func (m *Coin) XXX_Unmarshal(b []byte) error { @@ -1690,7 +2026,7 @@ func (m *OfflineCoinSummary) Reset() { *m = OfflineCoinSummary{} } func (m *OfflineCoinSummary) String() string { return proto.CompactTextString(m) } func (*OfflineCoinSummary) ProtoMessage() {} func (*OfflineCoinSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{34} + return fileDescriptor_77a6da22d6a3feb1, []int{43} } func (m *OfflineCoinSummary) XXX_Unmarshal(b []byte) error { @@ -1744,7 +2080,7 @@ func (m *OnlineCoinSummary) Reset() { *m = OnlineCoinSummary{} } func (m *OnlineCoinSummary) String() string { return proto.CompactTextString(m) } func (*OnlineCoinSummary) ProtoMessage() {} func (*OnlineCoinSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{35} + return fileDescriptor_77a6da22d6a3feb1, []int{44} } func (m *OnlineCoinSummary) XXX_Unmarshal(b []byte) error { @@ -1790,7 +2126,7 @@ func (m *OfflineCoins) Reset() { *m = OfflineCoins{} } func (m *OfflineCoins) String() string { return proto.CompactTextString(m) } func (*OfflineCoins) ProtoMessage() {} func (*OfflineCoins) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{36} + return fileDescriptor_77a6da22d6a3feb1, []int{45} } func (m *OfflineCoins) XXX_Unmarshal(b []byte) error { @@ -1829,7 +2165,7 @@ func (m *OnlineCoins) Reset() { *m = OnlineCoins{} } func (m *OnlineCoins) String() string { return proto.CompactTextString(m) } func (*OnlineCoins) ProtoMessage() {} func (*OnlineCoins) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{37} + return fileDescriptor_77a6da22d6a3feb1, []int{46} } func (m *OnlineCoins) XXX_Unmarshal(b []byte) error { @@ -1872,7 +2208,7 @@ func (m *GetPortfolioSummaryResponse) Reset() { *m = GetPortfolioSummary func (m *GetPortfolioSummaryResponse) String() string { return proto.CompactTextString(m) } func (*GetPortfolioSummaryResponse) ProtoMessage() {} func (*GetPortfolioSummaryResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{38} + return fileDescriptor_77a6da22d6a3feb1, []int{47} } func (m *GetPortfolioSummaryResponse) XXX_Unmarshal(b []byte) error { @@ -1942,7 +2278,7 @@ func (m *AddPortfolioAddressRequest) Reset() { *m = AddPortfolioAddressR func (m *AddPortfolioAddressRequest) String() string { return proto.CompactTextString(m) } func (*AddPortfolioAddressRequest) ProtoMessage() {} func (*AddPortfolioAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{39} + return fileDescriptor_77a6da22d6a3feb1, []int{48} } func (m *AddPortfolioAddressRequest) XXX_Unmarshal(b []byte) error { @@ -2001,7 +2337,7 @@ func (m *AddPortfolioAddressResponse) Reset() { *m = AddPortfolioAddress func (m *AddPortfolioAddressResponse) String() string { return proto.CompactTextString(m) } func (*AddPortfolioAddressResponse) ProtoMessage() {} func (*AddPortfolioAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{40} + return fileDescriptor_77a6da22d6a3feb1, []int{49} } func (m *AddPortfolioAddressResponse) XXX_Unmarshal(b []byte) error { @@ -2035,7 +2371,7 @@ func (m *RemovePortfolioAddressRequest) Reset() { *m = RemovePortfolioAd func (m *RemovePortfolioAddressRequest) String() string { return proto.CompactTextString(m) } func (*RemovePortfolioAddressRequest) ProtoMessage() {} func (*RemovePortfolioAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{41} + return fileDescriptor_77a6da22d6a3feb1, []int{50} } func (m *RemovePortfolioAddressRequest) XXX_Unmarshal(b []byte) error { @@ -2087,7 +2423,7 @@ func (m *RemovePortfolioAddressResponse) Reset() { *m = RemovePortfolioA func (m *RemovePortfolioAddressResponse) String() string { return proto.CompactTextString(m) } func (*RemovePortfolioAddressResponse) ProtoMessage() {} func (*RemovePortfolioAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{42} + return fileDescriptor_77a6da22d6a3feb1, []int{51} } func (m *RemovePortfolioAddressResponse) XXX_Unmarshal(b []byte) error { @@ -2118,7 +2454,7 @@ func (m *GetForexProvidersRequest) Reset() { *m = GetForexProvidersReque func (m *GetForexProvidersRequest) String() string { return proto.CompactTextString(m) } func (*GetForexProvidersRequest) ProtoMessage() {} func (*GetForexProvidersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{43} + return fileDescriptor_77a6da22d6a3feb1, []int{52} } func (m *GetForexProvidersRequest) XXX_Unmarshal(b []byte) error { @@ -2156,7 +2492,7 @@ func (m *ForexProvider) Reset() { *m = ForexProvider{} } func (m *ForexProvider) String() string { return proto.CompactTextString(m) } func (*ForexProvider) ProtoMessage() {} func (*ForexProvider) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{44} + return fileDescriptor_77a6da22d6a3feb1, []int{53} } func (m *ForexProvider) XXX_Unmarshal(b []byte) error { @@ -2237,7 +2573,7 @@ func (m *GetForexProvidersResponse) Reset() { *m = GetForexProvidersResp func (m *GetForexProvidersResponse) String() string { return proto.CompactTextString(m) } func (*GetForexProvidersResponse) ProtoMessage() {} func (*GetForexProvidersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{45} + return fileDescriptor_77a6da22d6a3feb1, []int{54} } func (m *GetForexProvidersResponse) XXX_Unmarshal(b []byte) error { @@ -2275,7 +2611,7 @@ func (m *GetForexRatesRequest) Reset() { *m = GetForexRatesRequest{} } func (m *GetForexRatesRequest) String() string { return proto.CompactTextString(m) } func (*GetForexRatesRequest) ProtoMessage() {} func (*GetForexRatesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{46} + return fileDescriptor_77a6da22d6a3feb1, []int{55} } func (m *GetForexRatesRequest) XXX_Unmarshal(b []byte) error { @@ -2310,7 +2646,7 @@ func (m *ForexRatesConversion) Reset() { *m = ForexRatesConversion{} } func (m *ForexRatesConversion) String() string { return proto.CompactTextString(m) } func (*ForexRatesConversion) ProtoMessage() {} func (*ForexRatesConversion) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{47} + return fileDescriptor_77a6da22d6a3feb1, []int{56} } func (m *ForexRatesConversion) XXX_Unmarshal(b []byte) error { @@ -2370,7 +2706,7 @@ func (m *GetForexRatesResponse) Reset() { *m = GetForexRatesResponse{} } func (m *GetForexRatesResponse) String() string { return proto.CompactTextString(m) } func (*GetForexRatesResponse) ProtoMessage() {} func (*GetForexRatesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{48} + return fileDescriptor_77a6da22d6a3feb1, []int{57} } func (m *GetForexRatesResponse) XXX_Unmarshal(b []byte) error { @@ -2420,7 +2756,7 @@ func (m *OrderDetails) Reset() { *m = OrderDetails{} } func (m *OrderDetails) String() string { return proto.CompactTextString(m) } func (*OrderDetails) ProtoMessage() {} func (*OrderDetails) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{49} + return fileDescriptor_77a6da22d6a3feb1, []int{58} } func (m *OrderDetails) XXX_Unmarshal(b []byte) error { @@ -2538,7 +2874,7 @@ func (m *GetOrdersRequest) Reset() { *m = GetOrdersRequest{} } func (m *GetOrdersRequest) String() string { return proto.CompactTextString(m) } func (*GetOrdersRequest) ProtoMessage() {} func (*GetOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{50} + return fileDescriptor_77a6da22d6a3feb1, []int{59} } func (m *GetOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -2591,7 +2927,7 @@ func (m *GetOrdersResponse) Reset() { *m = GetOrdersResponse{} } func (m *GetOrdersResponse) String() string { return proto.CompactTextString(m) } func (*GetOrdersResponse) ProtoMessage() {} func (*GetOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{51} + return fileDescriptor_77a6da22d6a3feb1, []int{60} } func (m *GetOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -2631,7 +2967,7 @@ func (m *GetOrderRequest) Reset() { *m = GetOrderRequest{} } func (m *GetOrderRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderRequest) ProtoMessage() {} func (*GetOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{52} + return fileDescriptor_77a6da22d6a3feb1, []int{61} } func (m *GetOrderRequest) XXX_Unmarshal(b []byte) error { @@ -2683,7 +3019,7 @@ func (m *SubmitOrderRequest) Reset() { *m = SubmitOrderRequest{} } func (m *SubmitOrderRequest) String() string { return proto.CompactTextString(m) } func (*SubmitOrderRequest) ProtoMessage() {} func (*SubmitOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{53} + return fileDescriptor_77a6da22d6a3feb1, []int{62} } func (m *SubmitOrderRequest) XXX_Unmarshal(b []byte) error { @@ -2765,7 +3101,7 @@ func (m *SubmitOrderResponse) Reset() { *m = SubmitOrderResponse{} } func (m *SubmitOrderResponse) String() string { return proto.CompactTextString(m) } func (*SubmitOrderResponse) ProtoMessage() {} func (*SubmitOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{54} + return fileDescriptor_77a6da22d6a3feb1, []int{63} } func (m *SubmitOrderResponse) XXX_Unmarshal(b []byte) error { @@ -2817,7 +3153,7 @@ func (m *CancelOrderRequest) Reset() { *m = CancelOrderRequest{} } func (m *CancelOrderRequest) String() string { return proto.CompactTextString(m) } func (*CancelOrderRequest) ProtoMessage() {} func (*CancelOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{55} + return fileDescriptor_77a6da22d6a3feb1, []int{64} } func (m *CancelOrderRequest) XXX_Unmarshal(b []byte) error { @@ -2897,7 +3233,7 @@ func (m *CancelOrderResponse) Reset() { *m = CancelOrderResponse{} } func (m *CancelOrderResponse) String() string { return proto.CompactTextString(m) } func (*CancelOrderResponse) ProtoMessage() {} func (*CancelOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{56} + return fileDescriptor_77a6da22d6a3feb1, []int{65} } func (m *CancelOrderResponse) XXX_Unmarshal(b []byte) error { @@ -2929,7 +3265,7 @@ func (m *CancelAllOrdersRequest) Reset() { *m = CancelAllOrdersRequest{} func (m *CancelAllOrdersRequest) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersRequest) ProtoMessage() {} func (*CancelAllOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{57} + return fileDescriptor_77a6da22d6a3feb1, []int{66} } func (m *CancelAllOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -2968,7 +3304,7 @@ func (m *CancelAllOrdersResponse) Reset() { *m = CancelAllOrdersResponse func (m *CancelAllOrdersResponse) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse) ProtoMessage() {} func (*CancelAllOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{58} + return fileDescriptor_77a6da22d6a3feb1, []int{67} } func (m *CancelAllOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -3008,7 +3344,7 @@ func (m *CancelAllOrdersResponse_Orders) Reset() { *m = CancelAllOrdersR func (m *CancelAllOrdersResponse_Orders) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse_Orders) ProtoMessage() {} func (*CancelAllOrdersResponse_Orders) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{58, 0} + return fileDescriptor_77a6da22d6a3feb1, []int{67, 0} } func (m *CancelAllOrdersResponse_Orders) XXX_Unmarshal(b []byte) error { @@ -3053,7 +3389,7 @@ func (m *GetEventsRequest) Reset() { *m = GetEventsRequest{} } func (m *GetEventsRequest) String() string { return proto.CompactTextString(m) } func (*GetEventsRequest) ProtoMessage() {} func (*GetEventsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{59} + return fileDescriptor_77a6da22d6a3feb1, []int{68} } func (m *GetEventsRequest) XXX_Unmarshal(b []byte) error { @@ -3089,7 +3425,7 @@ func (m *ConditionParams) Reset() { *m = ConditionParams{} } func (m *ConditionParams) String() string { return proto.CompactTextString(m) } func (*ConditionParams) ProtoMessage() {} func (*ConditionParams) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{60} + return fileDescriptor_77a6da22d6a3feb1, []int{69} } func (m *ConditionParams) XXX_Unmarshal(b []byte) error { @@ -3162,7 +3498,7 @@ func (m *GetEventsResponse) Reset() { *m = GetEventsResponse{} } func (m *GetEventsResponse) String() string { return proto.CompactTextString(m) } func (*GetEventsResponse) ProtoMessage() {} func (*GetEventsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{61} + return fileDescriptor_77a6da22d6a3feb1, []int{70} } func (m *GetEventsResponse) XXX_Unmarshal(b []byte) error { @@ -3248,7 +3584,7 @@ func (m *AddEventRequest) Reset() { *m = AddEventRequest{} } func (m *AddEventRequest) String() string { return proto.CompactTextString(m) } func (*AddEventRequest) ProtoMessage() {} func (*AddEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{62} + return fileDescriptor_77a6da22d6a3feb1, []int{71} } func (m *AddEventRequest) XXX_Unmarshal(b []byte) error { @@ -3322,7 +3658,7 @@ func (m *AddEventResponse) Reset() { *m = AddEventResponse{} } func (m *AddEventResponse) String() string { return proto.CompactTextString(m) } func (*AddEventResponse) ProtoMessage() {} func (*AddEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{63} + return fileDescriptor_77a6da22d6a3feb1, []int{72} } func (m *AddEventResponse) XXX_Unmarshal(b []byte) error { @@ -3361,7 +3697,7 @@ func (m *RemoveEventRequest) Reset() { *m = RemoveEventRequest{} } func (m *RemoveEventRequest) String() string { return proto.CompactTextString(m) } func (*RemoveEventRequest) ProtoMessage() {} func (*RemoveEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{64} + return fileDescriptor_77a6da22d6a3feb1, []int{73} } func (m *RemoveEventRequest) XXX_Unmarshal(b []byte) error { @@ -3399,7 +3735,7 @@ func (m *RemoveEventResponse) Reset() { *m = RemoveEventResponse{} } func (m *RemoveEventResponse) String() string { return proto.CompactTextString(m) } func (*RemoveEventResponse) ProtoMessage() {} func (*RemoveEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{65} + return fileDescriptor_77a6da22d6a3feb1, []int{74} } func (m *RemoveEventResponse) XXX_Unmarshal(b []byte) error { @@ -3433,7 +3769,7 @@ func (m *GetCryptocurrencyDepositAddressesRequest) Reset() { func (m *GetCryptocurrencyDepositAddressesRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{66} + return fileDescriptor_77a6da22d6a3feb1, []int{75} } func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Unmarshal(b []byte) error { @@ -3474,7 +3810,7 @@ func (m *GetCryptocurrencyDepositAddressesResponse) Reset() { func (m *GetCryptocurrencyDepositAddressesResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{67} + return fileDescriptor_77a6da22d6a3feb1, []int{76} } func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Unmarshal(b []byte) error { @@ -3516,7 +3852,7 @@ func (m *GetCryptocurrencyDepositAddressRequest) Reset() { func (m *GetCryptocurrencyDepositAddressRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{68} + return fileDescriptor_77a6da22d6a3feb1, []int{77} } func (m *GetCryptocurrencyDepositAddressRequest) XXX_Unmarshal(b []byte) error { @@ -3564,7 +3900,7 @@ func (m *GetCryptocurrencyDepositAddressResponse) Reset() { func (m *GetCryptocurrencyDepositAddressResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{69} + return fileDescriptor_77a6da22d6a3feb1, []int{78} } func (m *GetCryptocurrencyDepositAddressResponse) XXX_Unmarshal(b []byte) error { @@ -3619,7 +3955,7 @@ func (m *WithdrawCurrencyRequest) Reset() { *m = WithdrawCurrencyRequest func (m *WithdrawCurrencyRequest) String() string { return proto.CompactTextString(m) } func (*WithdrawCurrencyRequest) ProtoMessage() {} func (*WithdrawCurrencyRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{70} + return fileDescriptor_77a6da22d6a3feb1, []int{79} } func (m *WithdrawCurrencyRequest) XXX_Unmarshal(b []byte) error { @@ -3770,7 +4106,7 @@ func (m *WithdrawResponse) Reset() { *m = WithdrawResponse{} } func (m *WithdrawResponse) String() string { return proto.CompactTextString(m) } func (*WithdrawResponse) ProtoMessage() {} func (*WithdrawResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{71} + return fileDescriptor_77a6da22d6a3feb1, []int{80} } func (m *WithdrawResponse) XXX_Unmarshal(b []byte) error { @@ -3801,6 +4137,19 @@ func (m *WithdrawResponse) GetResult() string { func init() { proto.RegisterType((*GetInfoRequest)(nil), "gctrpc.GetInfoRequest") proto.RegisterType((*GetInfoResponse)(nil), "gctrpc.GetInfoResponse") + proto.RegisterMapType((map[string]*RPCEndpoint)(nil), "gctrpc.GetInfoResponse.RpcEndpointsEntry") + proto.RegisterMapType((map[string]bool)(nil), "gctrpc.GetInfoResponse.SubsystemStatusEntry") + proto.RegisterType((*GetCommunicationRelayersRequest)(nil), "gctrpc.GetCommunicationRelayersRequest") + proto.RegisterType((*GetCommunicationRelayersResponse)(nil), "gctrpc.GetCommunicationRelayersResponse") + proto.RegisterType((*GenericSubsystemRequest)(nil), "gctrpc.GenericSubsystemRequest") + proto.RegisterType((*GenericSubsystemResponse)(nil), "gctrpc.GenericSubsystemResponse") + proto.RegisterType((*GetSubsystemsRequest)(nil), "gctrpc.GetSubsystemsRequest") + proto.RegisterType((*GetSusbsytemsResponse)(nil), "gctrpc.GetSusbsytemsResponse") + proto.RegisterMapType((map[string]bool)(nil), "gctrpc.GetSusbsytemsResponse.SubsystemsStatusEntry") + proto.RegisterType((*GetRPCEndpointsRequest)(nil), "gctrpc.GetRPCEndpointsRequest") + proto.RegisterType((*RPCEndpoint)(nil), "gctrpc.RPCEndpoint") + proto.RegisterType((*GetRPCEndpointsResponse)(nil), "gctrpc.GetRPCEndpointsResponse") + proto.RegisterMapType((map[string]*RPCEndpoint)(nil), "gctrpc.GetRPCEndpointsResponse.EndpointsEntry") proto.RegisterType((*GenericExchangeNameRequest)(nil), "gctrpc.GenericExchangeNameRequest") proto.RegisterType((*GenericExchangeNameResponse)(nil), "gctrpc.GenericExchangeNameResponse") proto.RegisterType((*GetExchangesRequest)(nil), "gctrpc.GetExchangesRequest") @@ -3883,232 +4232,255 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 3585 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3a, 0x4b, 0x6f, 0xe4, 0xc6, - 0xd1, 0xe0, 0xe8, 0x5d, 0x33, 0xd2, 0x8c, 0x5a, 0xaf, 0xd1, 0x48, 0xda, 0x07, 0xfd, 0xed, 0x7a, - 0x77, 0x6d, 0x4b, 0xf6, 0x7a, 0xf1, 0x7d, 0xfe, 0x6c, 0xc7, 0x89, 0xac, 0x7d, 0x78, 0xe3, 0xd8, - 0x2b, 0x70, 0xd7, 0x6b, 0xc0, 0x0e, 0x32, 0xa0, 0xc8, 0x1e, 0x89, 0x10, 0x45, 0xd2, 0x64, 0x8f, - 0xb4, 0x32, 0x02, 0x04, 0x30, 0x90, 0x6b, 0x72, 0x08, 0x12, 0xe4, 0x90, 0x53, 0x8e, 0x01, 0x72, - 0xc9, 0x0f, 0x30, 0x72, 0x0d, 0x72, 0xcc, 0x25, 0x3f, 0x20, 0xc8, 0x2d, 0x09, 0x72, 0xc8, 0x25, - 0xa7, 0xa0, 0xab, 0x1f, 0x64, 0x0f, 0x39, 0xa3, 0xd9, 0x38, 0xf1, 0x45, 0x1a, 0x56, 0x57, 0xd7, - 0xbb, 0xab, 0xab, 0x8a, 0x84, 0xb9, 0x34, 0xf1, 0xb6, 0x93, 0x34, 0x66, 0x31, 0x99, 0x3e, 0xf4, - 0x58, 0x9a, 0x78, 0x9d, 0xcd, 0xc3, 0x38, 0x3e, 0x0c, 0xe9, 0x8e, 0x9b, 0x04, 0x3b, 0x6e, 0x14, - 0xc5, 0xcc, 0x65, 0x41, 0x1c, 0x65, 0x02, 0xcb, 0x6e, 0xc1, 0xc2, 0x03, 0xca, 0x1e, 0x46, 0xbd, - 0xd8, 0xa1, 0x9f, 0xf5, 0x69, 0xc6, 0xec, 0xbf, 0x5b, 0xd0, 0xd4, 0xa0, 0x2c, 0x89, 0xa3, 0x8c, - 0x92, 0x55, 0x98, 0xee, 0x27, 0x2c, 0x38, 0xa1, 0x6d, 0xeb, 0x8a, 0x75, 0x63, 0xce, 0x91, 0x4f, - 0x64, 0x07, 0x96, 0xdc, 0x53, 0x37, 0x08, 0xdd, 0x83, 0x90, 0x76, 0xe9, 0x33, 0xef, 0xc8, 0x8d, - 0x0e, 0x69, 0xd6, 0xae, 0x5d, 0xb1, 0x6e, 0x4c, 0x38, 0x44, 0x2f, 0xdd, 0x53, 0x2b, 0xe4, 0x25, - 0x58, 0xa4, 0x11, 0x07, 0xf9, 0x05, 0xf4, 0x09, 0x44, 0x6f, 0xc9, 0x85, 0x1c, 0xf9, 0x0e, 0xac, - 0xfa, 0xb4, 0xe7, 0xf6, 0x43, 0xd6, 0xed, 0xc5, 0x29, 0x7d, 0xd6, 0x4d, 0xd2, 0xf8, 0x34, 0xf0, - 0x69, 0xda, 0x9e, 0x44, 0x29, 0x96, 0xe5, 0xea, 0x7d, 0xbe, 0xb8, 0x2f, 0xd7, 0xc8, 0x6d, 0x58, - 0xd1, 0xbb, 0x02, 0x97, 0x75, 0xbd, 0x7e, 0x9a, 0xd2, 0xc8, 0x3b, 0x6f, 0x4f, 0xe1, 0xa6, 0x25, - 0xb5, 0x29, 0x70, 0xd9, 0x9e, 0x5c, 0xb2, 0xdf, 0x80, 0xce, 0x03, 0x1a, 0xd1, 0x34, 0xf0, 0x14, - 0xf7, 0x0f, 0xdd, 0x13, 0x2a, 0x2d, 0x42, 0x3a, 0x30, 0xab, 0x84, 0x95, 0xfa, 0xeb, 0x67, 0x7b, - 0x0b, 0x36, 0x2a, 0x77, 0x0a, 0xc3, 0xd9, 0x3b, 0xb0, 0xf4, 0x80, 0x32, 0xad, 0x92, 0xa2, 0xd8, - 0x86, 0x19, 0xa9, 0x2d, 0x12, 0x9c, 0x75, 0xd4, 0xa3, 0x7d, 0x07, 0x96, 0xcd, 0x0d, 0xd2, 0x03, - 0x9b, 0x30, 0x97, 0x1b, 0x4c, 0x08, 0x91, 0x03, 0xec, 0xdb, 0xb0, 0x52, 0xd8, 0xf5, 0xe8, 0xc9, - 0xbe, 0x43, 0xc5, 0xb6, 0x75, 0x98, 0x8d, 0x59, 0xd2, 0xf5, 0x62, 0x5f, 0x89, 0x3e, 0x13, 0xb3, - 0x64, 0x2f, 0xf6, 0xa9, 0xdd, 0x86, 0x55, 0x73, 0x8f, 0x92, 0xce, 0xfe, 0xa5, 0x05, 0x6b, 0xa5, - 0x25, 0x29, 0xc7, 0xb7, 0x61, 0x4e, 0x11, 0xe4, 0x72, 0x4c, 0xdc, 0xa8, 0xdf, 0x7e, 0x65, 0x5b, - 0x44, 0xda, 0xf6, 0x90, 0x3d, 0xdb, 0x8f, 0x04, 0xc7, 0xec, 0x5e, 0xc4, 0xd2, 0x73, 0x67, 0x56, - 0x0a, 0x90, 0x75, 0xde, 0x82, 0x79, 0x63, 0x89, 0xb4, 0x60, 0xe2, 0x98, 0x9e, 0x4b, 0x41, 0xf9, - 0x4f, 0xb2, 0x0c, 0x53, 0xa7, 0x6e, 0xd8, 0xa7, 0x18, 0x52, 0x73, 0x8e, 0x78, 0x78, 0xb3, 0xf6, - 0x86, 0x65, 0xdf, 0x81, 0xd5, 0xbb, 0x41, 0x56, 0x8c, 0xae, 0x71, 0xdc, 0xf5, 0xe5, 0x84, 0xa1, - 0x9a, 0x11, 0xe4, 0x04, 0x26, 0x23, 0x57, 0x87, 0x38, 0xfe, 0x2e, 0x3a, 0xaa, 0x66, 0x38, 0x8a, - 0xaf, 0x9c, 0xd2, 0xf4, 0x20, 0xce, 0x28, 0xc6, 0xef, 0xac, 0xa3, 0x1e, 0xc9, 0x0b, 0x30, 0xdf, - 0xcf, 0x82, 0xe8, 0xb0, 0x9b, 0xb9, 0x91, 0x7f, 0x10, 0x3f, 0xc3, 0x68, 0x9d, 0x75, 0x1a, 0x08, - 0x7c, 0x2c, 0x60, 0xe4, 0x2a, 0x34, 0x8e, 0x18, 0x4b, 0xba, 0xfc, 0x18, 0xc5, 0x7d, 0x26, 0x83, - 0xb3, 0xce, 0x61, 0x4f, 0x04, 0x88, 0x5c, 0x83, 0x05, 0x44, 0xe9, 0x67, 0x34, 0x75, 0x0f, 0x69, - 0xc4, 0xda, 0xd3, 0x88, 0x34, 0xcf, 0xa1, 0x1f, 0x29, 0x20, 0xd9, 0x02, 0x40, 0xb4, 0x24, 0x8d, - 0x9f, 0x9d, 0xb7, 0x67, 0x44, 0x68, 0x70, 0xc8, 0x3e, 0x07, 0x90, 0x17, 0xa1, 0x79, 0xe0, 0x66, - 0x54, 0x1d, 0x83, 0x80, 0x66, 0xed, 0x59, 0xc4, 0x59, 0xe0, 0xe0, 0x3d, 0x0d, 0x25, 0x37, 0xa1, - 0x95, 0xf5, 0x93, 0x24, 0x4e, 0x19, 0xf5, 0xbb, 0x6e, 0x96, 0x51, 0x96, 0xb5, 0xe7, 0x10, 0xb3, - 0xa9, 0xe1, 0xbb, 0x08, 0xe6, 0x1a, 0xaa, 0x53, 0x9c, 0xb8, 0x41, 0x9a, 0xb5, 0x01, 0xf1, 0x1a, - 0x12, 0xb8, 0xcf, 0x61, 0x9c, 0x71, 0x9e, 0x1b, 0x04, 0x5a, 0x5d, 0x30, 0xd6, 0x60, 0x81, 0xf8, - 0x12, 0x2c, 0xba, 0x7d, 0x76, 0x44, 0x23, 0x16, 0x78, 0x2e, 0x32, 0x4f, 0x82, 0x76, 0x03, 0x6d, - 0xd6, 0x32, 0x16, 0x76, 0x93, 0xc0, 0x3e, 0x83, 0xd6, 0x03, 0xca, 0x9e, 0x04, 0xde, 0x31, 0x4d, - 0xc7, 0x70, 0x38, 0xb9, 0x01, 0x93, 0x9c, 0x37, 0x7a, 0xaf, 0x7e, 0x7b, 0x59, 0x85, 0xaa, 0x3a, - 0xf9, 0x5c, 0x02, 0x07, 0x31, 0xb8, 0x1d, 0x51, 0xeb, 0x2e, 0x3b, 0x4f, 0x84, 0x4f, 0xe7, 0x9c, - 0x39, 0x84, 0x3c, 0x39, 0x4f, 0xa8, 0xfd, 0x14, 0x1a, 0xc5, 0x4d, 0xfc, 0x40, 0xfa, 0x34, 0x0c, - 0x4e, 0x02, 0x46, 0x53, 0x75, 0x20, 0x35, 0x80, 0xc7, 0x12, 0x37, 0xaf, 0x0c, 0x5b, 0xfc, 0xcd, - 0x63, 0xf9, 0xb3, 0x7e, 0xcc, 0x14, 0x6d, 0xf1, 0x60, 0xff, 0xb4, 0x06, 0x0b, 0x4a, 0x1d, 0x19, - 0x88, 0x4a, 0x66, 0xeb, 0x42, 0x99, 0xaf, 0x42, 0x23, 0x74, 0x33, 0xd6, 0xed, 0x27, 0x3e, 0x37, - 0x90, 0x4c, 0xbc, 0x75, 0x0e, 0xfb, 0x48, 0x80, 0xb8, 0xaf, 0x54, 0x06, 0x44, 0x2f, 0x48, 0xee, - 0x0d, 0xaf, 0xa8, 0x0c, 0x81, 0x49, 0xbe, 0x07, 0x23, 0xd5, 0x72, 0xf0, 0x37, 0x87, 0x1d, 0x05, - 0x87, 0x47, 0x18, 0x99, 0x96, 0x83, 0xbf, 0xf9, 0x01, 0x0d, 0xe3, 0x33, 0x8c, 0x43, 0xcb, 0xe1, - 0x3f, 0x39, 0xe4, 0x20, 0xf0, 0x31, 0xec, 0x2c, 0x87, 0xff, 0xe4, 0x10, 0x37, 0x3b, 0xc6, 0x20, - 0xb3, 0x1c, 0xfe, 0x93, 0xdf, 0x1e, 0xa7, 0x71, 0xd8, 0x3f, 0xa1, 0x18, 0x4f, 0x96, 0x23, 0x9f, - 0xc8, 0x06, 0xcc, 0x25, 0x69, 0xe0, 0xd1, 0xae, 0xcb, 0x8e, 0x30, 0x84, 0x2c, 0x67, 0x16, 0x01, - 0xbb, 0xec, 0xc8, 0x5e, 0x82, 0x45, 0xed, 0x68, 0x9d, 0x99, 0x3e, 0x86, 0x19, 0x09, 0x19, 0xe9, - 0xf4, 0x57, 0x61, 0x86, 0x09, 0xb4, 0x76, 0x0d, 0x53, 0xd4, 0xaa, 0xb2, 0xa1, 0x69, 0x69, 0x47, - 0xa1, 0xd9, 0xdf, 0x04, 0x52, 0xe4, 0x26, 0x1d, 0x71, 0x33, 0xa7, 0x23, 0x52, 0x5d, 0xd3, 0xa4, - 0x93, 0xe5, 0x04, 0x3e, 0xc7, 0x44, 0xff, 0x28, 0xf5, 0x79, 0x12, 0x88, 0x8f, 0xbf, 0xd6, 0xd0, - 0xfc, 0x00, 0xe6, 0x35, 0xe3, 0x87, 0x8c, 0x9e, 0x70, 0x83, 0xbb, 0x27, 0x71, 0x3f, 0x62, 0xc8, - 0xd3, 0x72, 0xe4, 0x13, 0x8f, 0x40, 0xb4, 0x2f, 0xb2, 0xb4, 0x1c, 0xf1, 0x40, 0x16, 0xa0, 0x16, - 0xf8, 0xf2, 0x12, 0xae, 0x05, 0xbe, 0xfd, 0x4f, 0x0b, 0x16, 0x0b, 0x8a, 0x3c, 0x77, 0x50, 0x96, - 0x22, 0xae, 0x56, 0x11, 0x71, 0x37, 0x61, 0xf2, 0x20, 0xf0, 0xf9, 0xdd, 0xcf, 0xed, 0xba, 0xa2, - 0xc8, 0x19, 0x7a, 0x38, 0x88, 0xc2, 0x51, 0xdd, 0xec, 0x38, 0x6b, 0x4f, 0x8e, 0x44, 0xe5, 0x28, - 0xa5, 0xf3, 0x30, 0x55, 0x3e, 0x0f, 0xa6, 0x2d, 0xa7, 0x07, 0x6d, 0xb9, 0x8a, 0xf7, 0xaf, 0xa6, - 0xad, 0x23, 0xcf, 0x03, 0xc8, 0x81, 0x23, 0xdd, 0xfa, 0xff, 0x00, 0xb1, 0xc6, 0x94, 0xf1, 0xb7, - 0x5e, 0x12, 0x5a, 0x87, 0x60, 0x01, 0xd9, 0x7e, 0x1f, 0xaf, 0xf1, 0x22, 0x73, 0x69, 0xfc, 0xdb, - 0x06, 0x4d, 0x11, 0x8b, 0xa4, 0x44, 0x33, 0x33, 0x88, 0xbd, 0x8e, 0xc4, 0x76, 0x3d, 0x8f, 0xbb, - 0xbe, 0x50, 0xe0, 0x8d, 0xbc, 0x1f, 0x9f, 0xc2, 0x8c, 0xdc, 0x21, 0xc3, 0x42, 0x20, 0xd4, 0x02, - 0x9f, 0xbc, 0x05, 0x50, 0xb8, 0x43, 0x84, 0x5e, 0x1b, 0x4a, 0x06, 0xb9, 0x49, 0x45, 0x03, 0xb2, - 0x2b, 0xa0, 0xdb, 0x3d, 0x58, 0xaa, 0x40, 0xe1, 0xa2, 0xe8, 0xf2, 0x4c, 0x8a, 0xa2, 0x9e, 0xc9, - 0x65, 0xa8, 0xb3, 0x98, 0xb9, 0x61, 0x37, 0x2f, 0x00, 0x2c, 0x07, 0x10, 0xf4, 0x94, 0x43, 0x30, - 0x41, 0xc5, 0xa1, 0x88, 0x5c, 0x9e, 0xa0, 0xe2, 0xd0, 0xb7, 0x5d, 0x2c, 0x6a, 0x0c, 0xa5, 0xa5, - 0x09, 0x47, 0xb9, 0xec, 0x25, 0x98, 0x75, 0xc5, 0x16, 0xa5, 0x58, 0x73, 0x40, 0x31, 0x47, 0x23, - 0xd8, 0x04, 0x6f, 0xa0, 0xbd, 0x38, 0xea, 0x05, 0x87, 0x2a, 0x3a, 0x5e, 0xc4, 0x64, 0xa5, 0x60, - 0x79, 0x3d, 0xe1, 0xbb, 0xcc, 0x45, 0x6e, 0x0d, 0x07, 0x7f, 0xdb, 0x3f, 0xb4, 0xa0, 0xb5, 0x1f, - 0xa7, 0xac, 0x17, 0x87, 0x41, 0xbc, 0xeb, 0xfb, 0x29, 0xcd, 0x32, 0x5e, 0x4a, 0xb8, 0xe2, 0xa7, - 0xaa, 0xd1, 0xe4, 0x23, 0xcf, 0x90, 0x5e, 0x1c, 0x44, 0x22, 0x56, 0x6b, 0xd2, 0x40, 0x71, 0x10, - 0xf1, 0x50, 0x25, 0x57, 0xa0, 0xee, 0xd3, 0xcc, 0x4b, 0x83, 0x84, 0x17, 0xf4, 0x32, 0x2d, 0x14, - 0x41, 0x9c, 0xf0, 0x81, 0x1b, 0xba, 0x91, 0x47, 0x65, 0x66, 0x57, 0x8f, 0xf6, 0x0a, 0xa6, 0x2b, - 0x2d, 0x89, 0xd2, 0xe3, 0x43, 0x8c, 0xfe, 0x02, 0x58, 0xaa, 0xf2, 0xbf, 0x30, 0x97, 0x28, 0xa0, - 0x0c, 0xbf, 0xb6, 0xb2, 0xd0, 0xa0, 0x3a, 0x4e, 0x8e, 0x6a, 0x6f, 0xf2, 0xba, 0x3a, 0xa7, 0xf7, - 0xb8, 0x7f, 0x72, 0xe2, 0xa6, 0xe7, 0x8a, 0x5b, 0x04, 0x93, 0x7b, 0x71, 0x10, 0x71, 0x43, 0x71, - 0xa5, 0x54, 0xe1, 0xc5, 0x7f, 0x17, 0x45, 0xaf, 0x19, 0xa2, 0x17, 0xad, 0x35, 0x61, 0x5a, 0xeb, - 0x12, 0x40, 0x42, 0x53, 0x8f, 0x46, 0xcc, 0x3d, 0x54, 0x1a, 0x17, 0x20, 0xf6, 0x11, 0x90, 0x47, - 0xbd, 0x5e, 0x18, 0x44, 0x94, 0xb3, 0x95, 0xc2, 0x8c, 0xb0, 0xfe, 0x70, 0x19, 0x4c, 0x4e, 0x13, - 0x25, 0x4e, 0x1f, 0xc0, 0xe2, 0xa3, 0xa8, 0x82, 0x91, 0x22, 0x67, 0x8d, 0x22, 0x57, 0x2b, 0x91, - 0x7b, 0x0f, 0x1a, 0x05, 0xc1, 0x33, 0xf2, 0x06, 0xcc, 0x49, 0x19, 0x75, 0x11, 0xde, 0xd1, 0xd9, - 0xa0, 0xa4, 0xa1, 0x93, 0x23, 0xdb, 0x3f, 0xb7, 0xa0, 0x9e, 0x4b, 0xc6, 0x5b, 0xac, 0x29, 0x6e, - 0x6e, 0x45, 0xe5, 0x92, 0xa6, 0x92, 0xe3, 0x6c, 0xe3, 0x5f, 0x51, 0xbb, 0x0b, 0xe4, 0xce, 0x63, - 0x80, 0x1c, 0x58, 0x51, 0xb5, 0xef, 0x14, 0xab, 0xf6, 0x62, 0xf6, 0x1b, 0xb4, 0x49, 0xb1, 0xa0, - 0xff, 0xfd, 0x24, 0x6f, 0xa5, 0x2a, 0x82, 0x45, 0xc6, 0xe0, 0x2b, 0x50, 0x17, 0x67, 0x81, 0x67, - 0x00, 0x25, 0x70, 0x43, 0xdf, 0x43, 0x71, 0x10, 0x39, 0x80, 0x67, 0x03, 0xd7, 0xc9, 0x6b, 0x30, - 0x8f, 0xc2, 0x76, 0x63, 0x61, 0x10, 0x79, 0xb0, 0xcd, 0x0d, 0x0d, 0x44, 0x91, 0x26, 0x23, 0x09, - 0xac, 0x18, 0x5b, 0xba, 0x99, 0x10, 0x41, 0x5e, 0x52, 0x6f, 0x17, 0xfa, 0x9c, 0x61, 0x52, 0x0a, - 0x63, 0x49, 0x82, 0x72, 0x4d, 0x98, 0x6e, 0xc9, 0x2b, 0xaf, 0x90, 0x1d, 0x68, 0x48, 0x8e, 0x68, - 0x19, 0x79, 0xc5, 0x99, 0x32, 0xd6, 0xc5, 0x46, 0x44, 0x20, 0x27, 0xb0, 0x5c, 0xdc, 0xa0, 0x25, - 0x9c, 0xc2, 0x8d, 0x6f, 0x8d, 0x2f, 0x61, 0x54, 0x12, 0x90, 0x78, 0xa5, 0x85, 0xce, 0x77, 0xa1, - 0x3d, 0x4c, 0xa1, 0x0a, 0xb7, 0xdf, 0x32, 0xdd, 0xbe, 0x5c, 0x11, 0x92, 0x59, 0xc1, 0xe3, 0x9d, - 0x4f, 0x60, 0x6d, 0x88, 0x30, 0x15, 0xc4, 0x6f, 0x9a, 0xc4, 0x97, 0x2a, 0x22, 0xb5, 0x18, 0x4d, - 0x3f, 0xb6, 0xa0, 0xb3, 0xeb, 0xfb, 0xa5, 0xe4, 0x94, 0x37, 0xe0, 0x5f, 0x77, 0xca, 0xdd, 0x82, - 0x8d, 0x4a, 0x81, 0xe4, 0xa4, 0xe0, 0x19, 0x6c, 0x39, 0xf4, 0x24, 0x3e, 0xa5, 0x5f, 0xb7, 0xc8, - 0xf6, 0x15, 0xb8, 0x34, 0x8c, 0xb3, 0x94, 0xad, 0x03, 0xed, 0x07, 0xd4, 0x1c, 0xb3, 0xe8, 0xc2, - 0xe8, 0x2f, 0x16, 0xcc, 0x9b, 0x03, 0x98, 0xff, 0x54, 0x1f, 0xfd, 0x32, 0x90, 0x94, 0x66, 0xac, - 0x9b, 0xc6, 0x61, 0xc8, 0xdb, 0x69, 0x9f, 0x86, 0xee, 0xb9, 0x1c, 0xfd, 0xb4, 0xf8, 0x8a, 0x23, - 0x16, 0xee, 0x72, 0x38, 0x59, 0x83, 0x19, 0x37, 0x09, 0xba, 0x3c, 0x6a, 0x44, 0x2f, 0x3d, 0xed, - 0x26, 0xc1, 0xfb, 0xf4, 0x9c, 0xd8, 0x30, 0x2f, 0x17, 0xba, 0x21, 0x3d, 0xa5, 0x21, 0xd6, 0x7c, - 0x13, 0x4e, 0x5d, 0x2c, 0x7f, 0x87, 0x83, 0x78, 0xef, 0x9b, 0xa4, 0x01, 0x0f, 0xbf, 0x7c, 0xc6, - 0x34, 0x83, 0xd2, 0x34, 0x25, 0x5c, 0x69, 0x67, 0x7f, 0x0a, 0xeb, 0x15, 0xb6, 0x90, 0x39, 0xea, - 0x1d, 0x68, 0x9a, 0x93, 0x2a, 0x95, 0xa7, 0x74, 0xd5, 0x6a, 0x6c, 0x74, 0x16, 0x7a, 0x06, 0x1d, - 0x59, 0x7d, 0x22, 0x8e, 0xe3, 0x32, 0x3d, 0x2f, 0xb2, 0x3f, 0x83, 0xe5, 0x1c, 0xb8, 0x17, 0x47, - 0xa7, 0x34, 0xcd, 0x78, 0xb4, 0x11, 0x98, 0xec, 0xa5, 0xf1, 0x89, 0x32, 0x35, 0xff, 0xcd, 0xeb, - 0x36, 0x16, 0xcb, 0x30, 0xa8, 0xb1, 0x98, 0xe3, 0xa4, 0x2e, 0x53, 0xb7, 0x14, 0xfe, 0xe6, 0x75, - 0x72, 0x80, 0x44, 0x68, 0x17, 0xd7, 0x44, 0xa8, 0xd6, 0x25, 0x8c, 0x73, 0xb1, 0x9f, 0x62, 0xf9, - 0x58, 0x14, 0x45, 0xea, 0xf8, 0x0d, 0xa8, 0x0b, 0x1d, 0xf9, 0x4e, 0xa5, 0xdf, 0xa6, 0xa1, 0xdf, - 0x80, 0x98, 0x0e, 0xf4, 0x34, 0xd4, 0xfe, 0x5b, 0x0d, 0x1a, 0x58, 0xb1, 0xde, 0xa5, 0xcc, 0x0d, - 0xc2, 0xd1, 0xb5, 0xb4, 0xa8, 0x41, 0x6b, 0xba, 0x06, 0x7d, 0x01, 0xe6, 0x8b, 0xc3, 0x8c, 0x73, - 0xd5, 0xcc, 0x16, 0x46, 0x19, 0xe7, 0xe4, 0x1a, 0x2c, 0x60, 0x6b, 0x9d, 0x63, 0x89, 0x98, 0x99, - 0x47, 0xa8, 0x46, 0x33, 0x1b, 0x81, 0xa9, 0x81, 0x46, 0x80, 0x2f, 0x63, 0x31, 0xdd, 0xcd, 0x02, - 0x5f, 0xf7, 0x09, 0x08, 0x79, 0x1c, 0xf8, 0x85, 0x65, 0xdc, 0x3d, 0x53, 0x58, 0xc6, 0xdd, 0xbc, - 0x07, 0x4a, 0x29, 0x4e, 0x5a, 0x71, 0xc4, 0x83, 0xed, 0xf0, 0x84, 0xd3, 0x50, 0xc0, 0x27, 0xc1, - 0x09, 0x4e, 0x55, 0x33, 0xe6, 0xb2, 0xbe, 0x9a, 0xb3, 0xc8, 0xa7, 0xbc, 0x4d, 0x83, 0x62, 0x9b, - 0x96, 0x37, 0x75, 0x75, 0xa3, 0xa9, 0xbb, 0x0c, 0xf5, 0x38, 0xa1, 0x51, 0x57, 0xb6, 0xd8, 0x0d, - 0x51, 0x3d, 0x70, 0xd0, 0x53, 0x84, 0xc8, 0x91, 0x09, 0xda, 0x3c, 0x1b, 0xa7, 0x2f, 0x35, 0x0d, - 0x53, 0x1b, 0x34, 0x8c, 0x6a, 0x04, 0x27, 0x2e, 0x6a, 0x04, 0xed, 0x5d, 0xac, 0x8a, 0x15, 0x63, - 0x19, 0x3e, 0x2f, 0xc3, 0x34, 0x9a, 0x49, 0x45, 0xce, 0xb2, 0xd1, 0xc6, 0xc8, 0xa0, 0x70, 0x24, - 0x8e, 0xfd, 0x1e, 0xce, 0xa2, 0x71, 0x69, 0x1c, 0xd1, 0xd7, 0x61, 0x56, 0x78, 0x45, 0x47, 0xcd, - 0x0c, 0x3e, 0x3f, 0xf4, 0xed, 0x3f, 0x5a, 0x40, 0x1e, 0xf7, 0x0f, 0x4e, 0x82, 0xf1, 0xa9, 0x8d, - 0xdf, 0xa0, 0x13, 0x98, 0xc4, 0x30, 0x11, 0xe1, 0x88, 0xbf, 0x07, 0x22, 0x64, 0x72, 0x30, 0x42, - 0x72, 0x77, 0x4e, 0x55, 0xf7, 0xe8, 0xd3, 0x45, 0xe7, 0xf3, 0x14, 0x1f, 0x06, 0x34, 0x62, 0x5d, - 0x39, 0x6c, 0xe1, 0x29, 0x1e, 0x01, 0x0f, 0x7d, 0xfb, 0x31, 0x2c, 0x19, 0x9a, 0x49, 0x4b, 0x5f, - 0x85, 0x86, 0x10, 0x20, 0x09, 0x5d, 0x4f, 0x4f, 0x9a, 0xeb, 0x08, 0xdb, 0x47, 0xd0, 0x28, 0x7b, - 0xfd, 0xd5, 0x02, 0xb2, 0xc7, 0x2f, 0xae, 0x70, 0x6c, 0x7b, 0xf1, 0xc0, 0x11, 0x5d, 0x52, 0x4e, - 0x6f, 0x4e, 0x42, 0x1e, 0x9a, 0xcc, 0x26, 0x0c, 0x66, 0xda, 0xd2, 0x93, 0xcf, 0x39, 0x0a, 0x29, - 0x9d, 0xda, 0x6b, 0xb0, 0x70, 0xe6, 0x86, 0x21, 0x65, 0x5d, 0x75, 0x57, 0xca, 0x99, 0xa9, 0x80, - 0xaa, 0x8e, 0x4b, 0xf9, 0x6b, 0x26, 0xf7, 0x17, 0x6f, 0x89, 0x0c, 0x7d, 0xe5, 0xdd, 0x77, 0x07, - 0x56, 0x05, 0x78, 0x37, 0x0c, 0xc7, 0x3e, 0x43, 0xf6, 0x2f, 0x6a, 0xb0, 0x56, 0xda, 0xa6, 0x2f, - 0x09, 0xf3, 0x04, 0x5c, 0xd7, 0xea, 0x56, 0x6f, 0xd8, 0x96, 0x8f, 0x72, 0x57, 0xe7, 0xb7, 0x16, - 0x4c, 0x0b, 0xd0, 0x48, 0x6f, 0x7c, 0xa2, 0xdc, 0x2f, 0x73, 0x8c, 0xa8, 0x7f, 0xff, 0x6f, 0x3c, - 0x66, 0xe2, 0xdf, 0x63, 0xdc, 0x29, 0xca, 0x43, 0x11, 0x37, 0x02, 0xd2, 0x79, 0x07, 0x5a, 0x83, - 0x08, 0xcf, 0x35, 0xbc, 0x17, 0x3d, 0xf4, 0xbd, 0x53, 0x1a, 0x31, 0x7d, 0xc7, 0x7d, 0x69, 0x41, - 0x73, 0x2f, 0x8e, 0xfc, 0x80, 0xe7, 0xc7, 0x7d, 0x37, 0x75, 0x4f, 0x32, 0xb2, 0xc9, 0x2b, 0x1b, - 0x09, 0x52, 0x43, 0x56, 0x0d, 0x18, 0x32, 0xce, 0xda, 0x02, 0xf0, 0x8e, 0xa8, 0x77, 0xdc, 0x95, - 0xf3, 0x25, 0x1e, 0xf4, 0x73, 0x08, 0x79, 0x37, 0xf0, 0x33, 0xf2, 0x0a, 0x2c, 0xe5, 0xcb, 0x5d, - 0x37, 0xf2, 0xbb, 0x72, 0xb8, 0x84, 0xf3, 0x66, 0x8d, 0xb7, 0x1b, 0xf9, 0xbb, 0xd9, 0x31, 0x4e, - 0xc5, 0xf5, 0x4c, 0xa5, 0x6b, 0x1c, 0xd8, 0xa6, 0x86, 0xef, 0x22, 0xd8, 0xfe, 0x87, 0x85, 0xf9, - 0x4e, 0x69, 0x25, 0xbd, 0x9d, 0x8f, 0x51, 0x70, 0xba, 0x66, 0xb8, 0xac, 0x36, 0xe0, 0x32, 0x02, - 0x93, 0x01, 0xa3, 0x27, 0x2a, 0x8d, 0xf0, 0xdf, 0xe4, 0x5d, 0x68, 0x69, 0x8d, 0xbb, 0x09, 0x9a, - 0x45, 0x1e, 0x93, 0xb5, 0xbc, 0x4d, 0x30, 0xac, 0xe6, 0x34, 0xbd, 0x01, 0x33, 0xaa, 0xe3, 0x35, - 0x75, 0xe1, 0xf1, 0xe2, 0x59, 0xc9, 0x43, 0x6b, 0x4f, 0xcb, 0x22, 0x0a, 0x9f, 0x84, 0xd4, 0xd4, - 0xeb, 0x33, 0xea, 0xcb, 0xc2, 0x48, 0x3f, 0xdb, 0x7f, 0xb6, 0xa0, 0xb9, 0xeb, 0xfb, 0xa8, 0xf7, - 0x38, 0x69, 0x42, 0x69, 0x59, 0xbb, 0x40, 0xcb, 0x89, 0x7f, 0x53, 0xcb, 0xaf, 0x9c, 0x44, 0x86, - 0x18, 0xc1, 0xb6, 0xa1, 0x95, 0xeb, 0x59, 0xed, 0x5e, 0xfb, 0x7f, 0x80, 0x88, 0x62, 0xda, 0x30, - 0xc7, 0x20, 0xd6, 0x0a, 0x2c, 0x19, 0x58, 0x32, 0xd7, 0xdc, 0x87, 0x1b, 0x0f, 0x28, 0xdb, 0x4b, - 0xcf, 0x13, 0x16, 0xab, 0xe2, 0xe5, 0x2e, 0x4d, 0xe2, 0x2c, 0x50, 0x99, 0x8b, 0x8e, 0x95, 0x7d, - 0x7e, 0x67, 0xc1, 0xcd, 0x31, 0x08, 0x49, 0x15, 0xbe, 0x57, 0x9e, 0x26, 0x7c, 0xab, 0xd0, 0x48, - 0x8e, 0x47, 0x65, 0x5b, 0x43, 0x44, 0xba, 0xc8, 0x49, 0x76, 0xde, 0x86, 0x05, 0x73, 0xf1, 0xb9, - 0x52, 0x45, 0x08, 0xd7, 0x2f, 0x10, 0x62, 0x9c, 0x98, 0xbb, 0x0e, 0x0b, 0x9e, 0x41, 0x42, 0x32, - 0x1a, 0x80, 0xda, 0x7b, 0xf0, 0xe2, 0x85, 0xdc, 0xa4, 0xd9, 0x86, 0xf6, 0x63, 0xf6, 0xaf, 0x27, - 0x61, 0xed, 0xe3, 0x80, 0x1d, 0xf9, 0xa9, 0x7b, 0xa6, 0xa2, 0x6f, 0x1c, 0x21, 0x07, 0x5a, 0xb5, - 0x5a, 0xb9, 0xbb, 0xbc, 0x05, 0x8b, 0x71, 0x44, 0xb1, 0xa2, 0xec, 0x26, 0x6e, 0x96, 0x9d, 0xc5, - 0xa9, 0xba, 0x4b, 0x9b, 0x71, 0x44, 0x79, 0x55, 0xb9, 0x2f, 0xc1, 0x03, 0xb7, 0xf1, 0xe4, 0xe0, - 0x6d, 0xdc, 0x82, 0x89, 0x24, 0x88, 0xe4, 0x84, 0x9c, 0xff, 0xe4, 0x77, 0x27, 0x4b, 0x5d, 0xbf, - 0x40, 0x59, 0xde, 0x9d, 0x08, 0xd5, 0x74, 0x8b, 0x33, 0xdb, 0x99, 0x81, 0x99, 0x6d, 0xc1, 0x26, - 0xb3, 0x66, 0x8f, 0x7a, 0x19, 0xea, 0xf2, 0x67, 0x97, 0xb9, 0x87, 0xb2, 0xe0, 0x05, 0x09, 0x7a, - 0xe2, 0x1e, 0x16, 0xea, 0x21, 0x30, 0xea, 0xa1, 0x2d, 0x80, 0x1e, 0xa5, 0x5d, 0xa3, 0xf4, 0x9d, - 0xeb, 0x51, 0x2a, 0x92, 0x2e, 0x2f, 0x8c, 0x0e, 0xdc, 0xe8, 0xb8, 0x8b, 0x1d, 0x67, 0x43, 0x88, - 0xc3, 0x01, 0x1f, 0xf2, 0xae, 0xf3, 0x2a, 0x34, 0x70, 0x51, 0xc9, 0x34, 0x2f, 0x2c, 0xca, 0x61, - 0xbb, 0x79, 0xef, 0x8c, 0x28, 0x5e, 0xc0, 0xce, 0xdb, 0x0b, 0xf9, 0xfe, 0xbd, 0x80, 0x9d, 0xeb, - 0xfd, 0x68, 0xb3, 0xf4, 0xbc, 0xdd, 0xcc, 0xf7, 0xef, 0x09, 0x10, 0x17, 0x2f, 0x3b, 0x0b, 0x7a, - 0x54, 0xbc, 0x62, 0x6f, 0x09, 0x2b, 0x23, 0x64, 0x2f, 0xf6, 0xb1, 0x0f, 0x38, 0x0b, 0xd2, 0x42, - 0x2b, 0xb2, 0x28, 0x1a, 0x16, 0x0e, 0xd4, 0x5f, 0x1f, 0xdc, 0x82, 0x96, 0x0a, 0x97, 0xe2, 0x17, - 0x17, 0x29, 0xcd, 0xfa, 0x21, 0x53, 0x5f, 0x5c, 0x88, 0xa7, 0xdb, 0x3f, 0xdb, 0x80, 0x85, 0x07, - 0xb1, 0x08, 0xd0, 0x27, 0xdc, 0x2f, 0x29, 0x79, 0x04, 0x33, 0xf2, 0x7b, 0x0d, 0xb2, 0x5a, 0x38, - 0xb7, 0x85, 0x91, 0x7f, 0x67, 0xad, 0x04, 0x97, 0x19, 0x67, 0xe9, 0x8b, 0x3f, 0xfc, 0xe9, 0x27, - 0xb5, 0x79, 0x52, 0xdf, 0x39, 0x7d, 0x6d, 0xe7, 0x90, 0xb2, 0x80, 0x53, 0xf1, 0xa0, 0x51, 0xfc, - 0x06, 0x81, 0x6c, 0x54, 0xbc, 0xe0, 0x57, 0xa7, 0xae, 0xb3, 0x59, 0xbd, 0x28, 0xe9, 0xb7, 0x91, - 0x3e, 0x21, 0x2d, 0x49, 0x5f, 0x7f, 0xb2, 0x40, 0x3e, 0x87, 0xe6, 0xc0, 0xfb, 0x7b, 0x62, 0xe7, - 0xa4, 0x86, 0x7d, 0x8b, 0xd1, 0x79, 0x61, 0x24, 0x8e, 0xe4, 0x7a, 0x09, 0xb9, 0xb6, 0xed, 0x25, - 0xce, 0xd5, 0x17, 0x5c, 0x14, 0xe7, 0x37, 0xad, 0x5b, 0x24, 0xc3, 0xae, 0xa2, 0xf8, 0x11, 0xc0, - 0x58, 0xbc, 0x2f, 0x57, 0xa8, 0x6a, 0x58, 0x73, 0x03, 0xf9, 0xae, 0x90, 0xa5, 0x01, 0x6d, 0xd1, - 0xaa, 0x19, 0xbe, 0x62, 0x2c, 0x7c, 0x20, 0x81, 0x01, 0x32, 0x0e, 0xdf, 0xad, 0xea, 0x0f, 0x2c, - 0xe4, 0x37, 0x1e, 0x76, 0x07, 0xb9, 0x2e, 0x13, 0x32, 0xc0, 0x35, 0x66, 0x09, 0xc9, 0x8c, 0xef, - 0x4f, 0x24, 0xd3, 0x8c, 0x5c, 0x1a, 0xfa, 0xc9, 0xc6, 0x70, 0x4d, 0x8b, 0x9f, 0x74, 0x0c, 0xd5, - 0x34, 0x66, 0x49, 0x46, 0x9e, 0xc1, 0xc2, 0xbd, 0xe8, 0xbf, 0xe3, 0xd9, 0x2d, 0xe4, 0xbb, 0x66, - 0xa3, 0xae, 0x62, 0x84, 0x54, 0x74, 0xec, 0xc7, 0x30, 0xa7, 0x5f, 0xe3, 0x92, 0x76, 0x41, 0x09, - 0xe3, 0x83, 0x81, 0xce, 0x90, 0xd7, 0xc1, 0x2a, 0x5a, 0xed, 0x79, 0xa9, 0x95, 0x78, 0xb9, 0xcb, - 0x09, 0x7f, 0x0a, 0x90, 0xbf, 0x1f, 0x26, 0xeb, 0x25, 0xca, 0xda, 0x72, 0x9d, 0xaa, 0x25, 0x49, - 0x7e, 0x15, 0xc9, 0xb7, 0xc8, 0x82, 0x41, 0x3e, 0x93, 0xe7, 0x4d, 0xbf, 0xc6, 0x33, 0xce, 0xdb, - 0xe0, 0x1b, 0xe5, 0xce, 0xf0, 0x57, 0x89, 0xca, 0x29, 0xb6, 0x3a, 0x6c, 0xba, 0x40, 0xe5, 0x1a, - 0x1c, 0xc2, 0xbc, 0xf1, 0x6e, 0x91, 0x6c, 0x56, 0x71, 0xc9, 0xaa, 0x62, 0xae, 0xfc, 0x42, 0xd2, - 0x5e, 0x47, 0x56, 0x4b, 0x64, 0x71, 0x90, 0x55, 0x46, 0x8e, 0xf1, 0x8b, 0xb2, 0xc2, 0x2b, 0x38, - 0x52, 0xa4, 0x55, 0x7e, 0x1f, 0xd9, 0xb9, 0x34, 0x6c, 0x39, 0xab, 0x8e, 0x6f, 0x79, 0x87, 0xe1, - 0xa1, 0x12, 0x0e, 0x17, 0x2f, 0xde, 0x0c, 0x87, 0x1b, 0xef, 0xe7, 0x3a, 0xeb, 0x15, 0x2b, 0x92, - 0xfa, 0x0a, 0x52, 0x6f, 0x12, 0xe5, 0x73, 0x4f, 0xd0, 0x12, 0x3e, 0xd1, 0x13, 0x51, 0xc3, 0x27, - 0x83, 0xaf, 0xcd, 0x8c, 0x1c, 0x58, 0x7a, 0x79, 0x56, 0xca, 0x81, 0xfa, 0xf5, 0x18, 0xf9, 0x81, - 0xf9, 0x16, 0x4e, 0xbd, 0x15, 0xb0, 0x47, 0x8e, 0xf1, 0x4b, 0xa7, 0x65, 0xe8, 0xa8, 0xdf, 0xbe, - 0x8c, 0x9c, 0xd7, 0xc9, 0xda, 0x20, 0x67, 0xf9, 0xda, 0x80, 0x7c, 0x61, 0xc1, 0x52, 0xc5, 0x50, - 0x3a, 0x97, 0x60, 0xf8, 0x08, 0x3d, 0x97, 0x60, 0xd4, 0x54, 0xdb, 0x46, 0x09, 0x36, 0x6d, 0x94, - 0xc0, 0xf5, 0x7d, 0x2d, 0x81, 0xbc, 0x92, 0x79, 0x64, 0xfe, 0xc8, 0x82, 0xd5, 0xea, 0x01, 0x34, - 0xb9, 0xa6, 0x78, 0x8c, 0x1c, 0x8d, 0x77, 0xae, 0x5f, 0x84, 0x26, 0xa5, 0xb9, 0x86, 0xd2, 0x5c, - 0xb6, 0x3b, 0x5c, 0x9a, 0x14, 0x71, 0xab, 0x04, 0x3a, 0xc3, 0x3e, 0xce, 0x1c, 0xf1, 0x92, 0x2b, - 0x05, 0x83, 0x57, 0x4e, 0xc2, 0x3b, 0x57, 0x47, 0x60, 0x98, 0xe9, 0x8b, 0xac, 0x48, 0x87, 0xe0, - 0x5c, 0x54, 0xcf, 0x8a, 0xe5, 0x19, 0xcd, 0x47, 0xa8, 0xc6, 0x19, 0x2d, 0x4d, 0x85, 0x8d, 0x33, - 0x5a, 0x1e, 0xd4, 0x96, 0xce, 0x28, 0x32, 0xc3, 0xa1, 0x2d, 0xf9, 0x04, 0x8f, 0x8d, 0x1c, 0x22, - 0xb4, 0x07, 0x8f, 0x7a, 0x56, 0x75, 0x6c, 0xcc, 0x31, 0x41, 0x29, 0x55, 0x8a, 0xd9, 0x04, 0xb7, - 0x9e, 0x03, 0xb3, 0x0a, 0x9d, 0xac, 0x0d, 0x12, 0x50, 0x94, 0x2b, 0xa7, 0x7e, 0xf6, 0x1a, 0x12, - 0x5d, 0xb4, 0x1b, 0x45, 0xa2, 0x9c, 0xe6, 0x01, 0xd4, 0x0b, 0x13, 0x2e, 0xa2, 0x93, 0x6c, 0x79, - 0xa0, 0xd7, 0xd9, 0xa8, 0x5c, 0x33, 0x53, 0x89, 0xdd, 0xe4, 0x0c, 0x32, 0x44, 0x28, 0xf2, 0x28, - 0xcc, 0x7f, 0x72, 0x1e, 0xe5, 0x21, 0x58, 0xce, 0xa3, 0x6a, 0x60, 0x64, 0xf0, 0xf0, 0x10, 0x41, - 0xf3, 0x48, 0xa1, 0x39, 0x30, 0x77, 0xc9, 0xaf, 0xe2, 0xea, 0x29, 0x53, 0x7e, 0x15, 0x0f, 0x19, - 0xd8, 0x98, 0xc5, 0x8e, 0xe0, 0xe7, 0x86, 0x61, 0xee, 0x0f, 0x91, 0x22, 0xc5, 0x54, 0xc2, 0xf0, - 0xb5, 0x31, 0x7e, 0x31, 0x7c, 0x6d, 0x8e, 0x30, 0x4a, 0x29, 0x92, 0x0a, 0x5a, 0x4f, 0x61, 0x56, - 0xb5, 0xc3, 0xb9, 0xa3, 0x07, 0x06, 0x01, 0x9d, 0x76, 0x79, 0x41, 0x52, 0x35, 0x9c, 0xed, 0xfa, - 0x3e, 0x52, 0x95, 0x8e, 0x28, 0x34, 0xc7, 0xb9, 0x23, 0xca, 0x7d, 0x75, 0xee, 0x88, 0xaa, 0x6e, - 0xda, 0x70, 0x84, 0x38, 0xed, 0x9a, 0xc7, 0x6f, 0x2c, 0xb8, 0x7a, 0x61, 0x6f, 0x4b, 0x5e, 0x7d, - 0x8e, 0x36, 0x58, 0x08, 0xf4, 0xda, 0x73, 0x37, 0xce, 0xf6, 0x0d, 0x14, 0xd3, 0xb6, 0xb7, 0xd4, - 0x05, 0x84, 0xdb, 0x7c, 0x81, 0xae, 0xbb, 0x68, 0x2e, 0xf4, 0xaf, 0x2c, 0xb8, 0x7c, 0x01, 0x5d, - 0xb2, 0x3d, 0xa6, 0x00, 0x4a, 0xe0, 0x9d, 0xb1, 0xf1, 0xa5, 0xb8, 0xd7, 0x51, 0xdc, 0x2b, 0xf6, - 0xc6, 0x08, 0x71, 0xb9, 0xb0, 0xdf, 0x87, 0x0d, 0xdd, 0x03, 0x1b, 0x74, 0xef, 0xf7, 0x23, 0x3f, - 0x23, 0x3a, 0xac, 0x87, 0x34, 0xca, 0x79, 0xe0, 0x0c, 0xb6, 0x46, 0xe6, 0x9d, 0x72, 0x26, 0x57, - 0x85, 0x18, 0x3d, 0x4e, 0x9b, 0x73, 0x4f, 0x60, 0x51, 0xed, 0xbb, 0x1f, 0xb8, 0xec, 0x2b, 0xf3, - 0xbc, 0x82, 0x3c, 0x3b, 0xf6, 0x4a, 0x91, 0x67, 0x2f, 0x70, 0x99, 0xe2, 0x78, 0x30, 0x8d, 0xdf, - 0xd3, 0xbf, 0xfe, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x0a, 0xf2, 0x2b, 0x82, 0x2f, 0x00, - 0x00, + // 3962 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x4b, 0x6c, 0x24, 0x49, + 0x56, 0xca, 0xb2, 0xdb, 0x76, 0xbd, 0x2a, 0xbb, 0xec, 0xf0, 0xaf, 0x5c, 0xb6, 0xdb, 0xee, 0x1c, + 0xba, 0xa7, 0x7b, 0x3e, 0xf6, 0x4e, 0x4f, 0x8b, 0x1d, 0x76, 0x96, 0x05, 0x8f, 0xa7, 0xc7, 0xdb, + 0xec, 0xee, 0xb4, 0xc9, 0xee, 0xed, 0x91, 0x66, 0x11, 0x45, 0xba, 0x32, 0x6c, 0xa7, 0x9c, 0x95, + 0x99, 0x93, 0x19, 0x65, 0x77, 0xad, 0x90, 0x90, 0x56, 0xe2, 0x0a, 0x07, 0x84, 0xc4, 0x81, 0x13, + 0x47, 0x24, 0x2e, 0x88, 0x13, 0x87, 0x11, 0x57, 0xc4, 0x91, 0x0b, 0xdc, 0x11, 0x37, 0xe0, 0xc4, + 0x85, 0x13, 0x8a, 0x17, 0x9f, 0x8c, 0xa8, 0xcc, 0x2a, 0x57, 0xef, 0x8c, 0xe6, 0xd2, 0x5d, 0xf9, + 0xe2, 0xc5, 0x7b, 0x2f, 0xde, 0x7b, 0xf1, 0xe2, 0xbd, 0x17, 0x61, 0xa8, 0x67, 0x69, 0xef, 0x20, + 0xcd, 0x12, 0x96, 0x90, 0xb9, 0x8b, 0x1e, 0xcb, 0xd2, 0x5e, 0x67, 0xe7, 0x22, 0x49, 0x2e, 0x22, + 0x7a, 0xe8, 0xa7, 0xe1, 0xa1, 0x1f, 0xc7, 0x09, 0xf3, 0x59, 0x98, 0xc4, 0xb9, 0xc0, 0x72, 0x97, + 0x61, 0xe9, 0x84, 0xb2, 0x67, 0xf1, 0x79, 0xe2, 0xd1, 0xaf, 0x06, 0x34, 0x67, 0xee, 0x3f, 0xcc, + 0x42, 0x4b, 0x83, 0xf2, 0x34, 0x89, 0x73, 0x4a, 0x36, 0x60, 0x6e, 0x90, 0xb2, 0xb0, 0x4f, 0xdb, + 0xce, 0xbe, 0xf3, 0xb0, 0xee, 0xc9, 0x2f, 0x72, 0x08, 0xab, 0xfe, 0xb5, 0x1f, 0x46, 0xfe, 0x59, + 0x44, 0xbb, 0xf4, 0x75, 0xef, 0xd2, 0x8f, 0x2f, 0x68, 0xde, 0xae, 0xed, 0x3b, 0x0f, 0x67, 0x3c, + 0xa2, 0x87, 0x9e, 0xaa, 0x11, 0xf2, 0x2e, 0xac, 0xd0, 0x98, 0x83, 0x02, 0x03, 0x7d, 0x06, 0xd1, + 0x97, 0xe5, 0x40, 0x81, 0xfc, 0x04, 0x36, 0x02, 0x7a, 0xee, 0x0f, 0x22, 0xd6, 0x3d, 0x4f, 0x32, + 0xfa, 0xba, 0x9b, 0x66, 0xc9, 0x75, 0x18, 0xd0, 0xac, 0x3d, 0x8b, 0x52, 0xac, 0xc9, 0xd1, 0xcf, + 0xf8, 0xe0, 0xa9, 0x1c, 0x23, 0x8f, 0x61, 0x5d, 0xcf, 0x0a, 0x7d, 0xd6, 0xed, 0x0d, 0xb2, 0x8c, + 0xc6, 0xbd, 0x61, 0xfb, 0x0e, 0x4e, 0x5a, 0x55, 0x93, 0x42, 0x9f, 0x1d, 0xcb, 0x21, 0xf2, 0x05, + 0x2c, 0xe7, 0x83, 0xb3, 0x7c, 0x98, 0x33, 0xda, 0xef, 0xe6, 0xcc, 0x67, 0x83, 0xbc, 0x3d, 0xb7, + 0x3f, 0xf3, 0xb0, 0xf1, 0xf8, 0xbd, 0x03, 0xa1, 0xc6, 0x83, 0x11, 0x95, 0x1c, 0xbc, 0x50, 0xf8, + 0x2f, 0x10, 0xfd, 0x69, 0xcc, 0xb2, 0xa1, 0xd7, 0xca, 0x6d, 0x28, 0xf9, 0x1c, 0x16, 0xb3, 0xb4, + 0xd7, 0xa5, 0x71, 0x90, 0x26, 0x61, 0xcc, 0xf2, 0xf6, 0x3c, 0x52, 0x7d, 0x34, 0x8e, 0xaa, 0x97, + 0xf6, 0x9e, 0x2a, 0x5c, 0x41, 0xb2, 0x99, 0x19, 0xa0, 0xce, 0x27, 0xb0, 0x56, 0xc5, 0x98, 0x2c, + 0xc3, 0xcc, 0x15, 0x1d, 0x4a, 0xeb, 0xf0, 0x9f, 0x64, 0x0d, 0xee, 0x5c, 0xfb, 0xd1, 0x80, 0xa2, + 0x31, 0x16, 0x3c, 0xf1, 0xf1, 0x83, 0xda, 0x47, 0x4e, 0xe7, 0x25, 0xac, 0x94, 0xd8, 0x54, 0x10, + 0x78, 0x64, 0x12, 0x68, 0x3c, 0x5e, 0x55, 0x22, 0x7b, 0xa7, 0xc7, 0x6a, 0xae, 0x41, 0xd5, 0xbd, + 0x07, 0x7b, 0x27, 0x94, 0x1d, 0x27, 0xfd, 0xfe, 0x20, 0x0e, 0x7b, 0xe8, 0x63, 0x1e, 0x8d, 0xfc, + 0x21, 0xcd, 0x72, 0xe5, 0x59, 0x2e, 0xec, 0x8f, 0x47, 0x11, 0x0a, 0x70, 0xbf, 0x0f, 0x9b, 0x27, + 0x34, 0xa6, 0x59, 0xd8, 0xd3, 0xeb, 0x94, 0xd3, 0xc9, 0x0e, 0xd4, 0xb5, 0x7a, 0xa5, 0xa0, 0x05, + 0xc0, 0xed, 0x40, 0xbb, 0x3c, 0x51, 0x12, 0xdd, 0x80, 0xb5, 0x13, 0xca, 0x34, 0x5c, 0x0b, 0xf4, + 0xb5, 0x03, 0xeb, 0x38, 0x90, 0x9f, 0xe5, 0x43, 0x31, 0x20, 0x1d, 0xfe, 0x8f, 0x60, 0x45, 0x93, + 0xce, 0x95, 0x47, 0x38, 0x68, 0xbb, 0x0f, 0x0d, 0xdb, 0x95, 0x67, 0x16, 0x7e, 0x91, 0x9b, 0x8e, + 0x51, 0xb8, 0x97, 0x04, 0x77, 0x8e, 0x61, 0xbd, 0x12, 0xf5, 0x4d, 0x4c, 0xe9, 0xb6, 0x61, 0xe3, + 0x84, 0x32, 0xc3, 0x22, 0x7a, 0x69, 0x9f, 0x43, 0xc3, 0x00, 0x93, 0x36, 0xcc, 0xe7, 0xcc, 0xcf, + 0x18, 0x0d, 0x90, 0xf0, 0x82, 0xa7, 0x3e, 0xc9, 0x7d, 0x58, 0x8a, 0xc2, 0x9c, 0xd1, 0xb8, 0xeb, + 0x07, 0x41, 0x46, 0x73, 0xb1, 0x7b, 0xeb, 0xde, 0xa2, 0x80, 0x1e, 0x09, 0xa0, 0xfb, 0x8f, 0x0e, + 0x37, 0xcc, 0x08, 0x2b, 0xa9, 0xac, 0x9f, 0x42, 0xbd, 0x70, 0x70, 0xa1, 0xa4, 0x03, 0x43, 0x49, + 0x55, 0x73, 0x0e, 0x46, 0xbc, 0xbc, 0x20, 0xd0, 0xf9, 0x7d, 0x58, 0xfa, 0xb6, 0x7d, 0xf3, 0x23, + 0xe8, 0x48, 0xdf, 0x50, 0xc1, 0xe5, 0x73, 0xbf, 0x4f, 0x95, 0x5f, 0x75, 0x60, 0x41, 0xc5, 0x22, + 0xc9, 0x43, 0x7f, 0xbb, 0xbb, 0xb0, 0x5d, 0x39, 0x53, 0x3a, 0xd6, 0x21, 0xac, 0x9e, 0x50, 0xa6, + 0x23, 0x96, 0xa2, 0xd8, 0x86, 0x79, 0x19, 0xcc, 0x94, 0xb6, 0xe5, 0xa7, 0xfb, 0x04, 0x3d, 0xd1, + 0x98, 0x20, 0x55, 0xb8, 0x03, 0xf5, 0x22, 0x1e, 0x4a, 0xdf, 0xd6, 0x00, 0xf7, 0x31, 0xba, 0xa9, + 0x9a, 0xf5, 0xfc, 0xe5, 0xa9, 0x47, 0xc5, 0xb4, 0x2d, 0x58, 0x48, 0x58, 0xda, 0xed, 0x25, 0x81, + 0x12, 0x7d, 0x3e, 0x61, 0xe9, 0x71, 0x12, 0x50, 0xe9, 0x1a, 0xc6, 0x1c, 0xed, 0x1a, 0x7f, 0x23, + 0x4c, 0x69, 0x0f, 0x49, 0x39, 0x7e, 0x0f, 0xea, 0x8a, 0xa0, 0x32, 0xe5, 0xfb, 0x86, 0x29, 0xab, + 0xe6, 0x1c, 0x3c, 0x17, 0x1c, 0xa5, 0x25, 0x17, 0xa4, 0x00, 0x79, 0xe7, 0x63, 0x58, 0xb4, 0x86, + 0x6e, 0xf3, 0xec, 0xba, 0x69, 0xb2, 0x27, 0xb0, 0xf1, 0x69, 0x98, 0x9b, 0x87, 0xc7, 0x34, 0xe6, + 0xfa, 0x7a, 0xc6, 0x5a, 0x9a, 0x75, 0x86, 0x11, 0x98, 0x8d, 0x7d, 0x7d, 0x82, 0xe1, 0x6f, 0xd3, + 0x50, 0x35, 0xcb, 0x50, 0x7c, 0xe4, 0x9a, 0x66, 0x67, 0x49, 0x4e, 0xf1, 0x78, 0x5a, 0xf0, 0xd4, + 0x27, 0x79, 0x0b, 0x16, 0x07, 0x79, 0x18, 0x5f, 0x74, 0x73, 0x3f, 0x0e, 0xce, 0x92, 0xd7, 0x78, + 0x18, 0x2d, 0x78, 0x4d, 0x04, 0xbe, 0x10, 0x30, 0x72, 0x0f, 0x9a, 0x97, 0x8c, 0xa5, 0x5d, 0x7e, + 0x4a, 0x26, 0x03, 0x26, 0xcf, 0x9e, 0x06, 0x87, 0xbd, 0x14, 0x20, 0xbe, 0xf1, 0x10, 0x65, 0x90, + 0xd3, 0xcc, 0xbf, 0xa0, 0x31, 0x6b, 0xcf, 0x89, 0x8d, 0xc7, 0xa1, 0x3f, 0x57, 0x40, 0xb2, 0x0b, + 0x80, 0x68, 0x69, 0x96, 0xbc, 0x1e, 0xb6, 0xe7, 0x85, 0x6b, 0x70, 0xc8, 0x29, 0x07, 0x90, 0xb7, + 0xa1, 0x75, 0xe6, 0xe7, 0x54, 0x9d, 0x72, 0x21, 0xcd, 0xdb, 0x0b, 0x88, 0xb3, 0xc4, 0xc1, 0xc7, + 0x1a, 0x4a, 0x1e, 0xf1, 0x23, 0x2e, 0x4d, 0x13, 0xbe, 0xe9, 0xbb, 0x7e, 0x9e, 0x53, 0x96, 0xb7, + 0xeb, 0x88, 0xd9, 0xd2, 0xf0, 0x23, 0x04, 0xf3, 0x15, 0xaa, 0x43, 0x3a, 0xf5, 0xc3, 0x2c, 0x6f, + 0x03, 0xe2, 0x35, 0x25, 0xf0, 0x94, 0xc3, 0x38, 0xe3, 0xe2, 0xe8, 0x17, 0x68, 0x0d, 0xc1, 0x58, + 0x83, 0x05, 0xe2, 0xbb, 0xb0, 0xe2, 0x0f, 0xd8, 0x25, 0x8d, 0x19, 0x8f, 0xf9, 0x9c, 0x79, 0x1a, + 0xb6, 0x9b, 0xa8, 0xb3, 0x65, 0x6b, 0xe0, 0x28, 0x0d, 0xdd, 0x1b, 0x58, 0x3e, 0xa1, 0xec, 0x65, + 0xd8, 0xbb, 0xa2, 0xd9, 0x14, 0x06, 0x27, 0x0f, 0x61, 0x96, 0xf3, 0x96, 0x71, 0x60, 0x4d, 0xb9, + 0xaa, 0x3a, 0xd8, 0xb9, 0x04, 0x1e, 0x62, 0x70, 0x3d, 0xe2, 0xaa, 0xbb, 0x6c, 0x98, 0x0a, 0x9b, + 0xd6, 0xbd, 0x3a, 0x42, 0x5e, 0x0e, 0x53, 0xea, 0xbe, 0x82, 0xa6, 0x39, 0x89, 0x6f, 0xc8, 0x80, + 0x46, 0x61, 0x3f, 0x64, 0x34, 0x53, 0x1b, 0x52, 0x03, 0xb8, 0x2f, 0x71, 0xf5, 0x4a, 0xb7, 0xc5, + 0xdf, 0xdc, 0x97, 0xbf, 0x1a, 0x24, 0x4c, 0xd1, 0x16, 0x1f, 0xee, 0x5f, 0xd6, 0x60, 0x49, 0x2d, + 0x47, 0x3a, 0xa2, 0x92, 0xd9, 0xb9, 0x55, 0xe6, 0x7b, 0xd0, 0x8c, 0xfc, 0x9c, 0x75, 0x07, 0x69, + 0xc0, 0x15, 0x24, 0xf3, 0xaa, 0x06, 0x87, 0xfd, 0x5c, 0x80, 0xb8, 0xad, 0x54, 0x82, 0x83, 0x56, + 0x90, 0xdc, 0x9b, 0x3d, 0x73, 0x31, 0x04, 0x66, 0xf9, 0x1c, 0xf4, 0x54, 0xc7, 0xc3, 0xdf, 0x1c, + 0x76, 0x19, 0x5e, 0x5c, 0xa2, 0x67, 0x3a, 0x1e, 0xfe, 0xe6, 0x1b, 0x34, 0x4a, 0x6e, 0xd0, 0x0f, + 0x1d, 0x8f, 0xff, 0xe4, 0x90, 0xb3, 0x30, 0x40, 0xb7, 0x73, 0x3c, 0xfe, 0x93, 0x43, 0xfc, 0xfc, + 0x0a, 0x9d, 0xcc, 0xf1, 0xf8, 0x4f, 0x9e, 0x1c, 0x5e, 0x27, 0xd1, 0xa0, 0x4f, 0xd1, 0x9f, 0x1c, + 0x4f, 0x7e, 0x91, 0x6d, 0xa8, 0xa7, 0x59, 0xd8, 0xa3, 0x5d, 0x9f, 0x5d, 0xa2, 0x0b, 0x39, 0xde, + 0x02, 0x02, 0x8e, 0xd8, 0xa5, 0xbb, 0x0a, 0x2b, 0xda, 0xd0, 0x3a, 0x32, 0x7d, 0x01, 0xf3, 0x12, + 0x32, 0xd1, 0xe8, 0xdf, 0x83, 0x79, 0x26, 0xd0, 0xda, 0x35, 0x0c, 0x51, 0x1b, 0x4a, 0x87, 0xb6, + 0xa6, 0x3d, 0x85, 0xe6, 0xfe, 0x0e, 0x10, 0x93, 0x9b, 0x34, 0xc4, 0xa3, 0x82, 0x8e, 0x08, 0x75, + 0x2d, 0x9b, 0x4e, 0x5e, 0x10, 0xf8, 0x25, 0x06, 0xfa, 0xe7, 0x59, 0xc0, 0x83, 0x40, 0x72, 0xf5, + 0x9d, 0xba, 0xe6, 0xcf, 0x60, 0x51, 0x33, 0x7e, 0xc6, 0x68, 0x9f, 0x2b, 0xdc, 0xef, 0x27, 0x83, + 0x98, 0x21, 0x4f, 0xc7, 0x93, 0x5f, 0xdc, 0x03, 0x51, 0xbf, 0xc8, 0xd2, 0xf1, 0xc4, 0x07, 0x59, + 0x82, 0x5a, 0x18, 0xc8, 0x1c, 0xbb, 0x16, 0x06, 0xee, 0xff, 0x39, 0xb0, 0x62, 0x2c, 0xe4, 0x8d, + 0x9d, 0xb2, 0xe4, 0x71, 0xb5, 0x0a, 0x8f, 0x7b, 0x04, 0xb3, 0x67, 0x61, 0xc0, 0x53, 0x7b, 0xae, + 0xd7, 0x75, 0x45, 0xce, 0x5a, 0x87, 0x87, 0x28, 0x1c, 0xd5, 0xcf, 0xaf, 0xf2, 0xf6, 0xec, 0x44, + 0x54, 0x8e, 0x52, 0xda, 0x0f, 0x77, 0xca, 0xfb, 0xc1, 0xd6, 0xe5, 0xdc, 0xa8, 0x2e, 0x45, 0x26, + 0xa8, 0x69, 0x6b, 0xcf, 0xeb, 0x01, 0x14, 0xc0, 0x89, 0x66, 0xfd, 0x2d, 0x80, 0x44, 0x63, 0x4a, + 0xff, 0xdb, 0x2a, 0x09, 0xad, 0x5d, 0xd0, 0x40, 0x76, 0x7f, 0x82, 0xc7, 0xb8, 0xc9, 0x5c, 0x2a, + 0xff, 0xb1, 0x45, 0x53, 0xf8, 0x22, 0x29, 0xd1, 0xcc, 0x2d, 0x62, 0x1f, 0x22, 0xb1, 0xa3, 0x5e, + 0x8f, 0x9b, 0xde, 0xa8, 0xdf, 0x26, 0x9e, 0x8f, 0xaf, 0x60, 0x5e, 0xce, 0x90, 0x6e, 0x21, 0x10, + 0x6a, 0x61, 0x40, 0x3e, 0x06, 0x30, 0xce, 0x10, 0xb1, 0xae, 0x6d, 0x25, 0x83, 0x9c, 0xa4, 0xbc, + 0x01, 0xd9, 0x19, 0xe8, 0xee, 0x39, 0xac, 0x56, 0xa0, 0x70, 0x51, 0x74, 0xf5, 0x25, 0x45, 0x51, + 0xdf, 0x64, 0x0f, 0x1a, 0x2c, 0x61, 0x7e, 0xd4, 0x2d, 0x12, 0x00, 0xc7, 0x03, 0x04, 0xbd, 0xe2, + 0x10, 0x0c, 0x50, 0x49, 0x24, 0x3c, 0x97, 0x07, 0xa8, 0x24, 0x0a, 0x5c, 0x1f, 0x93, 0x1a, 0x6b, + 0xd1, 0x52, 0x85, 0x93, 0x4c, 0xf6, 0x2e, 0x2c, 0xf8, 0x62, 0x8a, 0x5a, 0x58, 0x6b, 0x64, 0x61, + 0x9e, 0x46, 0x70, 0x09, 0x9e, 0x40, 0xc7, 0x49, 0x7c, 0x1e, 0x5e, 0x28, 0xef, 0x78, 0x1b, 0x83, + 0x95, 0x82, 0x15, 0xf9, 0x44, 0xe0, 0x33, 0x1f, 0xb9, 0x35, 0x3d, 0xfc, 0xed, 0xfe, 0xa9, 0x03, + 0xcb, 0xa7, 0x49, 0xc6, 0xce, 0x93, 0x28, 0x4c, 0x64, 0xea, 0xcc, 0x53, 0x09, 0x95, 0x5a, 0xcb, + 0x1c, 0x4d, 0x7e, 0xf2, 0x08, 0xd9, 0x4b, 0xc2, 0x58, 0xf8, 0x6a, 0x4d, 0x2a, 0x28, 0x09, 0x63, + 0xee, 0xaa, 0x64, 0x1f, 0x1a, 0x01, 0xcd, 0x7b, 0x59, 0x98, 0xf2, 0x42, 0x49, 0x86, 0x05, 0x13, + 0xc4, 0x09, 0x9f, 0xf9, 0x91, 0x1f, 0xf7, 0xa8, 0x8c, 0xec, 0xea, 0xd3, 0x5d, 0xc7, 0x70, 0xa5, + 0x25, 0x29, 0x8a, 0x82, 0x35, 0x1b, 0x2c, 0x97, 0xf2, 0x9b, 0x50, 0x4f, 0x15, 0x50, 0xba, 0x5f, + 0x5b, 0x69, 0x68, 0x74, 0x39, 0x5e, 0x81, 0xea, 0xee, 0xf0, 0xbc, 0xba, 0xa0, 0xf7, 0x62, 0xd0, + 0xef, 0xfb, 0xd9, 0x50, 0x71, 0x8b, 0x61, 0xf6, 0x38, 0x09, 0x63, 0xae, 0x28, 0xbe, 0x28, 0x95, + 0x78, 0xf1, 0xdf, 0xa6, 0xe8, 0x35, 0x4b, 0x74, 0x53, 0x5b, 0x33, 0xb6, 0xb6, 0xee, 0x02, 0xa4, + 0x34, 0xeb, 0xd1, 0x98, 0xf9, 0x17, 0x6a, 0xc5, 0x06, 0xc4, 0xbd, 0x04, 0xf2, 0xfc, 0xfc, 0x3c, + 0x0a, 0x63, 0xca, 0xd9, 0x4a, 0x61, 0x26, 0x68, 0x7f, 0xbc, 0x0c, 0x36, 0xa7, 0x99, 0x12, 0xa7, + 0x9f, 0xc1, 0xca, 0xf3, 0xb8, 0x82, 0x91, 0x22, 0xe7, 0x4c, 0x22, 0x57, 0x2b, 0x91, 0xfb, 0x31, + 0x34, 0x0d, 0xc1, 0x73, 0xf2, 0x11, 0xd4, 0xa5, 0x8c, 0x3a, 0x09, 0xef, 0xe8, 0x68, 0x50, 0x5a, + 0xa1, 0x57, 0x20, 0xbb, 0x7f, 0xe5, 0x40, 0xa3, 0x90, 0x2c, 0x27, 0x4f, 0xe0, 0x0e, 0x57, 0xb7, + 0xa2, 0x72, 0x57, 0x53, 0x29, 0x70, 0x0e, 0xf0, 0x5f, 0x91, 0xbb, 0x0b, 0xe4, 0xce, 0x0b, 0x80, + 0x02, 0x58, 0x91, 0xb5, 0x1f, 0xda, 0xd5, 0xd7, 0x56, 0x99, 0xaa, 0x12, 0xcd, 0x48, 0xe8, 0xff, + 0x65, 0x96, 0x97, 0x52, 0x15, 0xce, 0x22, 0x7d, 0xf0, 0x7d, 0x68, 0x88, 0xbd, 0xc0, 0x23, 0x80, + 0x12, 0xb8, 0xa9, 0xcf, 0xa1, 0x24, 0x8c, 0x3d, 0xc0, 0xbd, 0x81, 0xe3, 0xe4, 0x03, 0x58, 0x44, + 0x61, 0xbb, 0x89, 0x50, 0x88, 0xdc, 0xd8, 0xf6, 0x84, 0x26, 0xa2, 0x48, 0x95, 0x91, 0x14, 0xd6, + 0xad, 0x29, 0xdd, 0x5c, 0x88, 0x20, 0x0f, 0xa9, 0x1f, 0x1a, 0x75, 0xce, 0x38, 0x29, 0x85, 0xb2, + 0x24, 0x41, 0x39, 0x26, 0x54, 0xb7, 0xda, 0x2b, 0x8f, 0x90, 0x43, 0x68, 0x4a, 0x8e, 0xa8, 0x19, + 0x79, 0xc4, 0xd9, 0x32, 0x36, 0xc4, 0x44, 0x44, 0x20, 0x7d, 0x58, 0x33, 0x27, 0x68, 0x09, 0xef, + 0xe0, 0xc4, 0x8f, 0xa7, 0x97, 0x30, 0x2e, 0x09, 0x48, 0x7a, 0xa5, 0x81, 0xce, 0x1f, 0x40, 0x7b, + 0xdc, 0x82, 0x2a, 0xcc, 0xfe, 0x8e, 0x6d, 0xf6, 0xb5, 0x0a, 0x97, 0xcc, 0xcd, 0x3e, 0xd3, 0x97, + 0xb0, 0x39, 0x46, 0x98, 0x37, 0xa8, 0xe8, 0x0d, 0x4f, 0x35, 0xbd, 0xe9, 0xcf, 0x1d, 0xe8, 0x1c, + 0x05, 0x41, 0x29, 0x38, 0x15, 0x05, 0xf8, 0x77, 0x1d, 0x72, 0x77, 0x61, 0xbb, 0x52, 0x20, 0xd9, + 0x29, 0x78, 0x0d, 0xbb, 0x1e, 0xed, 0x27, 0xd7, 0xf4, 0xbb, 0x16, 0xd9, 0xdd, 0x87, 0xbb, 0xe3, + 0x38, 0x4b, 0xd9, 0xb0, 0x75, 0x66, 0x77, 0x51, 0x75, 0x62, 0xf4, 0x5f, 0x0e, 0x2c, 0xda, 0xfd, + 0xd5, 0x6f, 0xab, 0x8e, 0x7e, 0x0f, 0x48, 0x46, 0x73, 0xd6, 0xcd, 0x92, 0x28, 0xe2, 0xe5, 0x74, + 0x40, 0x23, 0x7f, 0x28, 0x3b, 0xbb, 0xcb, 0x7c, 0xc4, 0x13, 0x03, 0x9f, 0x72, 0x38, 0xd9, 0x84, + 0x79, 0x3f, 0x0d, 0xbb, 0xdc, 0x6b, 0x44, 0x2d, 0x3d, 0xe7, 0xa7, 0xe1, 0x4f, 0xe8, 0x90, 0xb8, + 0xb0, 0x28, 0x07, 0xba, 0x11, 0xbd, 0xa6, 0x11, 0xe6, 0x7c, 0x33, 0x5e, 0x43, 0x0c, 0xff, 0x94, + 0x83, 0x78, 0xed, 0x9b, 0x66, 0x21, 0x77, 0xbf, 0xa2, 0x85, 0x3c, 0x8f, 0xd2, 0xb4, 0x24, 0x5c, + 0xad, 0xce, 0xfd, 0x05, 0x6c, 0x55, 0xe8, 0x42, 0xc6, 0xa8, 0x1f, 0x41, 0xcb, 0x6e, 0x44, 0xab, + 0x38, 0xa5, 0xb3, 0x56, 0x6b, 0xa2, 0xb7, 0x74, 0x6e, 0xd1, 0x91, 0xd9, 0x27, 0xe2, 0x78, 0x3e, + 0xd3, 0xfd, 0x22, 0xf7, 0x2b, 0x58, 0x2b, 0x80, 0xc7, 0x49, 0x7c, 0x4d, 0xb3, 0x9c, 0x7b, 0x1b, + 0x81, 0xd9, 0xf3, 0x2c, 0x51, 0xcd, 0x4e, 0xfc, 0xcd, 0xf3, 0x36, 0x96, 0x48, 0x37, 0xa8, 0xb1, + 0x84, 0xe3, 0x64, 0x3e, 0x53, 0xa7, 0x14, 0xfe, 0xe6, 0x79, 0x72, 0x88, 0x44, 0x68, 0x17, 0xc7, + 0x84, 0xab, 0x36, 0x24, 0x8c, 0x73, 0x71, 0x5f, 0x61, 0xfa, 0x68, 0x8a, 0x22, 0xd7, 0xf8, 0xdb, + 0xd0, 0x10, 0x6b, 0xe4, 0x33, 0xd5, 0xfa, 0x76, 0xac, 0xf5, 0x8d, 0x88, 0xe9, 0xc1, 0xb9, 0x86, + 0xba, 0xff, 0x53, 0x83, 0x26, 0x66, 0xac, 0x9f, 0x52, 0xe6, 0x87, 0xd1, 0xe4, 0x5c, 0x5a, 0xe4, + 0xa0, 0x35, 0x9d, 0x83, 0xbe, 0x05, 0x8b, 0x66, 0x33, 0x63, 0xa8, 0x8a, 0x59, 0xa3, 0x95, 0x31, + 0x24, 0xf7, 0x61, 0x09, 0x4b, 0xeb, 0x02, 0x4b, 0xf8, 0xcc, 0x22, 0x42, 0x35, 0x9a, 0x5d, 0x08, + 0xdc, 0x19, 0x29, 0x04, 0xf8, 0x30, 0x26, 0xd3, 0xdd, 0x3c, 0x0c, 0x74, 0x9d, 0x80, 0x90, 0x17, + 0x61, 0x60, 0x0c, 0xe3, 0xec, 0x79, 0x63, 0x18, 0x67, 0xf3, 0x1a, 0x28, 0xa3, 0xd8, 0xc1, 0xc6, + 0x16, 0x0f, 0x96, 0xc3, 0x33, 0x5e, 0x53, 0x01, 0x5f, 0x86, 0x7d, 0xbc, 0x34, 0x91, 0x8d, 0x63, + 0xd1, 0x67, 0x91, 0x5f, 0x45, 0x99, 0x06, 0x66, 0x99, 0x56, 0x14, 0x75, 0x0d, 0xab, 0xa8, 0xdb, + 0x83, 0x46, 0x92, 0xd2, 0xb8, 0x2b, 0x4b, 0xec, 0xa6, 0xc8, 0x1e, 0x38, 0xe8, 0x15, 0x42, 0x64, + 0xcb, 0x04, 0x75, 0x9e, 0x4f, 0x53, 0x97, 0xda, 0x8a, 0xa9, 0x8d, 0x2a, 0x46, 0x15, 0x82, 0x33, + 0xb7, 0x15, 0x82, 0xee, 0x11, 0x66, 0xc5, 0x8a, 0xb1, 0x74, 0x9f, 0xf7, 0x60, 0x0e, 0xd5, 0xa4, + 0x3c, 0x67, 0xcd, 0x2a, 0x63, 0xa4, 0x53, 0x78, 0x12, 0xc7, 0xfd, 0x31, 0x5e, 0x35, 0xe1, 0xd0, + 0x34, 0xa2, 0x6f, 0xc1, 0x82, 0xb0, 0x8a, 0xf6, 0x9a, 0x79, 0xfc, 0x7e, 0x16, 0xb8, 0xff, 0xe6, + 0x00, 0x79, 0x31, 0x38, 0xeb, 0x87, 0xd3, 0x53, 0x9b, 0xbe, 0x40, 0x27, 0x30, 0x8b, 0x6e, 0x22, + 0xdc, 0x11, 0x7f, 0x8f, 0x78, 0xc8, 0xec, 0xa8, 0x87, 0x14, 0xe6, 0xbc, 0x53, 0x5d, 0xa3, 0xcf, + 0x99, 0xc6, 0xe7, 0x21, 0x3e, 0x0a, 0x69, 0xcc, 0xba, 0xb2, 0xd9, 0xc2, 0x43, 0x3c, 0x02, 0x9e, + 0x05, 0xee, 0x0b, 0x58, 0xb5, 0x56, 0x26, 0x35, 0x7d, 0x0f, 0x9a, 0x42, 0x80, 0x34, 0xf2, 0x7b, + 0xba, 0xd3, 0xdc, 0x40, 0xd8, 0x29, 0x82, 0x26, 0xe9, 0xeb, 0xbf, 0x1d, 0x20, 0xc7, 0xfc, 0xe0, + 0x8a, 0xa6, 0xd6, 0x17, 0x77, 0x1c, 0x51, 0x25, 0x15, 0xf4, 0xea, 0x12, 0xf2, 0xcc, 0x66, 0x36, + 0x63, 0x31, 0xd3, 0x9a, 0x9e, 0x7d, 0xc3, 0x56, 0x48, 0x69, 0xd7, 0xde, 0x87, 0xa5, 0x1b, 0x3f, + 0x8a, 0x28, 0xd3, 0x97, 0x15, 0xb2, 0x67, 0x2a, 0xa0, 0xaa, 0xe2, 0x52, 0xf6, 0x9a, 0x2f, 0xec, + 0xc5, 0x4b, 0x22, 0x6b, 0xbd, 0xf2, 0xec, 0x7b, 0x02, 0x1b, 0x02, 0x7c, 0x14, 0x45, 0x53, 0xef, + 0x21, 0xf7, 0xaf, 0x6b, 0xb0, 0x59, 0x9a, 0xa6, 0x0f, 0x09, 0x7b, 0x07, 0x3c, 0xd0, 0xcb, 0xad, + 0x9e, 0x70, 0x20, 0x3f, 0xe5, 0xac, 0xce, 0x3f, 0x39, 0x30, 0x27, 0x40, 0x13, 0xad, 0xf1, 0xa5, + 0x32, 0xbf, 0x8c, 0x31, 0x22, 0xff, 0xfd, 0xfe, 0x74, 0xcc, 0xc4, 0x7f, 0xe6, 0x05, 0x95, 0xf0, + 0x1b, 0x79, 0x37, 0xf5, 0x23, 0x58, 0x1e, 0x45, 0x78, 0xa3, 0xe6, 0xbd, 0xa8, 0xa1, 0x9f, 0x5e, + 0x53, 0xe3, 0x42, 0xea, 0x6b, 0x07, 0x5a, 0xc7, 0x49, 0x1c, 0x84, 0x3c, 0x3e, 0x9e, 0xfa, 0x99, + 0xdf, 0xcf, 0xc9, 0x0e, 0xcf, 0x6c, 0x24, 0x48, 0x35, 0x59, 0x35, 0x60, 0x4c, 0x3b, 0x6b, 0x17, + 0xa0, 0x77, 0x49, 0x7b, 0x57, 0x5d, 0xd9, 0x5f, 0xe2, 0x4e, 0x5f, 0x47, 0xc8, 0x27, 0x61, 0x90, + 0x93, 0xf7, 0x61, 0xb5, 0x18, 0xee, 0xfa, 0x71, 0xd0, 0x95, 0xcd, 0x25, 0xec, 0x37, 0x6b, 0xbc, + 0xa3, 0x38, 0x38, 0xca, 0xaf, 0xb0, 0x2b, 0xae, 0x7b, 0x2a, 0x5d, 0x6b, 0xc3, 0xb6, 0x34, 0xfc, + 0x08, 0xc1, 0xee, 0xff, 0x3a, 0x18, 0xef, 0xd4, 0xaa, 0xa4, 0xb5, 0x8b, 0x36, 0x0a, 0x76, 0xd7, + 0x2c, 0x93, 0xd5, 0x46, 0x4c, 0x46, 0x60, 0x36, 0x64, 0xb4, 0xaf, 0xc2, 0x08, 0xff, 0x4d, 0x3e, + 0x81, 0x65, 0xbd, 0xe2, 0x6e, 0x8a, 0x6a, 0x91, 0xdb, 0x64, 0xb3, 0x28, 0x13, 0x2c, 0xad, 0x79, + 0xad, 0xde, 0x88, 0x1a, 0xd5, 0xf6, 0xba, 0x73, 0xeb, 0xf6, 0xe2, 0x51, 0xa9, 0x87, 0xda, 0x9e, + 0x93, 0x49, 0x14, 0x7e, 0x09, 0xa9, 0x69, 0x6f, 0xc0, 0x68, 0x20, 0x13, 0x23, 0xfd, 0xed, 0xfe, + 0xa7, 0x03, 0xad, 0xa3, 0x20, 0xc0, 0x75, 0x4f, 0x13, 0x26, 0xd4, 0x2a, 0x6b, 0xb7, 0xac, 0x72, + 0xe6, 0xd7, 0x5c, 0xe5, 0x37, 0x0e, 0x22, 0x63, 0x94, 0xe0, 0xba, 0xb0, 0x5c, 0xac, 0xb3, 0xda, + 0xbc, 0xee, 0x6f, 0x00, 0x11, 0xc9, 0xb4, 0xa5, 0x8e, 0x51, 0xac, 0x75, 0x58, 0xb5, 0xb0, 0x64, + 0xac, 0xf9, 0x0c, 0x1e, 0x9e, 0x50, 0x76, 0x9c, 0x0d, 0x53, 0x96, 0xa8, 0xe4, 0xe5, 0x53, 0x9a, + 0x26, 0x79, 0xa8, 0x22, 0x17, 0x9d, 0x2a, 0xfa, 0xfc, 0xb3, 0x03, 0x8f, 0xa6, 0x20, 0x24, 0x97, + 0xf0, 0x87, 0xe5, 0x6e, 0xc2, 0xef, 0x1a, 0x85, 0xe4, 0x74, 0x54, 0x0e, 0x34, 0x44, 0xde, 0xd7, + 0x6a, 0x92, 0x9d, 0x1f, 0xc2, 0x92, 0x3d, 0xf8, 0x46, 0xa1, 0x22, 0x82, 0x07, 0xb7, 0x08, 0x31, + 0x8d, 0xcf, 0x3d, 0x80, 0xa5, 0x9e, 0x45, 0x42, 0x32, 0x1a, 0x81, 0xba, 0xc7, 0xf0, 0xf6, 0xad, + 0xdc, 0xa4, 0xda, 0xc6, 0xd6, 0x63, 0xee, 0xdf, 0xcd, 0xc2, 0xe6, 0x17, 0x21, 0xbb, 0x0c, 0x32, + 0xff, 0x46, 0x79, 0xdf, 0x34, 0x42, 0x8e, 0x94, 0x6a, 0xb5, 0x72, 0x75, 0xf9, 0x0e, 0xac, 0x24, + 0x31, 0xc5, 0x8c, 0xb2, 0x9b, 0xfa, 0x79, 0x7e, 0x93, 0x64, 0xea, 0x2c, 0x6d, 0x25, 0x31, 0xe5, + 0x59, 0xe5, 0xa9, 0x04, 0x8f, 0x9c, 0xc6, 0xb3, 0xa3, 0xa7, 0xf1, 0x32, 0xcc, 0xa4, 0x61, 0x2c, + 0x3b, 0xe4, 0xfc, 0x27, 0x3f, 0x3b, 0x59, 0xe6, 0x07, 0x06, 0x65, 0x79, 0x76, 0x22, 0x54, 0xd3, + 0x35, 0x7b, 0xb6, 0xf3, 0x23, 0x3d, 0x5b, 0x43, 0x27, 0x0b, 0x76, 0x8d, 0xba, 0x07, 0x0d, 0xf9, + 0xb3, 0xcb, 0xfc, 0x0b, 0x99, 0xf0, 0x82, 0x04, 0xbd, 0xf4, 0x2f, 0x8c, 0x7c, 0x08, 0xac, 0x7c, + 0x68, 0x17, 0xe0, 0x9c, 0xd2, 0xae, 0x95, 0xfa, 0xd6, 0xcf, 0x29, 0x15, 0x41, 0x97, 0x27, 0x46, + 0x67, 0x7e, 0x7c, 0xd5, 0xc5, 0x8a, 0xb3, 0x29, 0xc4, 0xe1, 0x80, 0xcf, 0x79, 0xd5, 0x79, 0x0f, + 0x9a, 0x38, 0xa8, 0x64, 0x5a, 0x14, 0x1a, 0xe5, 0xb0, 0xa3, 0xa2, 0x76, 0x46, 0x94, 0x5e, 0xc8, + 0x86, 0xed, 0xa5, 0x62, 0xfe, 0x71, 0xc8, 0x86, 0x7a, 0x3e, 0xea, 0x2c, 0x1b, 0xb6, 0x5b, 0xc5, + 0xfc, 0x63, 0x01, 0xe2, 0xe2, 0xe5, 0x37, 0xe1, 0x39, 0x15, 0x57, 0xec, 0xcb, 0xf2, 0xd1, 0x09, + 0x87, 0x1c, 0x27, 0x01, 0xd6, 0x01, 0x37, 0x61, 0x66, 0x94, 0x22, 0x2b, 0xa2, 0x60, 0xe1, 0x40, + 0xe5, 0x1a, 0xee, 0x3b, 0xb0, 0xac, 0xdc, 0xc5, 0x7c, 0x50, 0x95, 0xd1, 0x7c, 0x10, 0x31, 0xf5, + 0xa0, 0x4a, 0x7c, 0x3d, 0xfe, 0xf7, 0xbb, 0xb0, 0x74, 0x92, 0x08, 0x07, 0x7d, 0xc9, 0xed, 0x92, + 0x91, 0xe7, 0x30, 0x2f, 0x5f, 0x09, 0x91, 0x8d, 0xd2, 0xb3, 0x21, 0xf4, 0xba, 0xce, 0xe6, 0x98, + 0xe7, 0x44, 0xee, 0xea, 0xaf, 0xfe, 0xf5, 0x3f, 0xfe, 0xa2, 0xb6, 0x48, 0x1a, 0x87, 0xd7, 0x1f, + 0x1c, 0x5e, 0x50, 0x16, 0x72, 0x2a, 0x97, 0xb0, 0x68, 0xbd, 0x86, 0x21, 0x3b, 0xd6, 0x8b, 0x96, + 0x91, 0x47, 0x32, 0x9d, 0xdd, 0x89, 0xef, 0x5d, 0xdc, 0x0e, 0xb2, 0x58, 0x23, 0x44, 0xb2, 0xc8, + 0x11, 0x45, 0x10, 0xfe, 0x0a, 0x5a, 0x4f, 0xb1, 0x0f, 0xa0, 0xa9, 0x92, 0xbd, 0x82, 0x5a, 0xe5, + 0x2b, 0x9f, 0xce, 0xfe, 0x78, 0x04, 0xc9, 0x71, 0x1b, 0x39, 0xae, 0x93, 0x55, 0xce, 0x51, 0xf4, + 0x19, 0xf4, 0xeb, 0x1a, 0x92, 0xc3, 0xb2, 0x7c, 0x37, 0xf0, 0xad, 0xf2, 0xdc, 0x41, 0x9e, 0x1b, + 0x64, 0x8d, 0xf3, 0x0c, 0x04, 0x83, 0x82, 0x69, 0x82, 0x65, 0x8c, 0xf9, 0xce, 0x85, 0xdc, 0x1d, + 0xfb, 0x00, 0x46, 0xb0, 0xdc, 0xbb, 0xe5, 0x81, 0x8c, 0xbd, 0xca, 0x0b, 0xca, 0x71, 0xf5, 0x1b, + 0x19, 0xd2, 0x83, 0xa6, 0xf9, 0x8c, 0x84, 0x6c, 0x57, 0xbc, 0xd1, 0xd0, 0xac, 0x76, 0xaa, 0x07, + 0x25, 0x9f, 0x36, 0xf2, 0x21, 0x64, 0x59, 0xf2, 0xd1, 0xaf, 0x4e, 0xc8, 0x2f, 0xa1, 0x35, 0xf2, + 0x04, 0x83, 0xb8, 0x23, 0x8a, 0xaa, 0x78, 0x4e, 0xd3, 0x79, 0x6b, 0x22, 0x8e, 0xe4, 0x7a, 0x17, + 0xb9, 0xb6, 0xdd, 0x55, 0x43, 0x9f, 0x8a, 0xf3, 0x0f, 0x9c, 0x77, 0x48, 0x8e, 0x1a, 0x35, 0xdf, + 0x71, 0x4c, 0xc5, 0x7b, 0xaf, 0x62, 0xa9, 0xd6, 0x86, 0x18, 0xd5, 0xaa, 0xe2, 0x89, 0x1b, 0x23, + 0xc7, 0x5b, 0x62, 0xe3, 0x8d, 0x0b, 0xee, 0xf1, 0x69, 0xf8, 0xee, 0x56, 0xbf, 0x91, 0x91, 0xcf, + 0x74, 0x4a, 0x7b, 0x44, 0x71, 0x4d, 0x58, 0x4a, 0x72, 0xeb, 0x09, 0x91, 0x64, 0x6a, 0xfb, 0x4f, + 0xc5, 0x23, 0x9e, 0xca, 0x95, 0x9a, 0xaf, 0x72, 0xc6, 0xae, 0x34, 0x61, 0x69, 0x4e, 0x5e, 0xc3, + 0x92, 0xd8, 0x98, 0xdf, 0xbe, 0x65, 0x77, 0x91, 0xef, 0xa6, 0x4b, 0x8a, 0xdd, 0x69, 0x1a, 0xf6, + 0x0b, 0xa8, 0xeb, 0x9b, 0x78, 0xd2, 0x36, 0x16, 0x61, 0xbd, 0xf9, 0xe8, 0x8c, 0xb9, 0xd1, 0x57, + 0xde, 0xea, 0x2e, 0xca, 0x55, 0x89, 0xfb, 0x79, 0x4e, 0xf8, 0x17, 0x00, 0xc5, 0x15, 0x3f, 0xd9, + 0x2a, 0x51, 0xd6, 0x9a, 0xeb, 0x54, 0x0d, 0xa9, 0x87, 0x82, 0x48, 0x7e, 0x99, 0x2c, 0x59, 0xe4, + 0xd5, 0x7e, 0xd3, 0x37, 0xb1, 0xd6, 0x7e, 0x1b, 0x7d, 0x14, 0xd0, 0x19, 0x7f, 0x1b, 0xac, 0x8c, + 0xe2, 0xaa, 0xcd, 0xa6, 0x6b, 0x0c, 0xbe, 0x82, 0x0b, 0x8c, 0xcb, 0xc6, 0x35, 0xf4, 0x4e, 0x15, + 0x97, 0xca, 0xb8, 0x5c, 0xbe, 0x53, 0x76, 0xb7, 0x90, 0xd5, 0x2a, 0x59, 0x19, 0x65, 0x95, 0x93, + 0x2b, 0x7c, 0xf3, 0x6b, 0xdc, 0xa2, 0x12, 0x93, 0x56, 0xf9, 0x4a, 0xb9, 0x73, 0x77, 0xdc, 0xf0, + 0x98, 0x33, 0x40, 0xa6, 0x21, 0xb8, 0xa9, 0x84, 0xc1, 0xc5, 0xdd, 0xa9, 0x65, 0x70, 0xeb, 0x8a, + 0xb5, 0xb3, 0x55, 0x31, 0x22, 0xa9, 0xaf, 0x23, 0xf5, 0x16, 0x51, 0x36, 0xef, 0x09, 0x5a, 0xc2, + 0x26, 0xba, 0xa9, 0x6d, 0xd9, 0x64, 0xf4, 0xe6, 0xd3, 0x8a, 0x81, 0xa5, 0xfb, 0xcf, 0x52, 0x0c, + 0xd4, 0x37, 0x9c, 0xe4, 0x4f, 0xec, 0x8b, 0x54, 0x75, 0xb1, 0xe3, 0x4e, 0xbc, 0x89, 0x29, 0xed, + 0x96, 0xb1, 0xb7, 0x35, 0xee, 0x1e, 0x72, 0xde, 0x22, 0x9b, 0xa3, 0x9c, 0xe5, 0xcd, 0x0f, 0xf9, + 0x95, 0x03, 0xab, 0x15, 0xf7, 0x0a, 0x85, 0x04, 0xe3, 0x6f, 0x41, 0x0a, 0x09, 0x26, 0x5d, 0x4c, + 0xb8, 0x28, 0xc1, 0x8e, 0x8b, 0x12, 0xf8, 0x41, 0xa0, 0x25, 0x90, 0x59, 0x15, 0xf7, 0xcc, 0x3f, + 0x73, 0x60, 0xa3, 0xfa, 0x0e, 0x81, 0xdc, 0xd7, 0x4f, 0x2f, 0x27, 0xdd, 0x6e, 0x74, 0x1e, 0xdc, + 0x86, 0x26, 0xa5, 0xb9, 0x8f, 0xd2, 0xec, 0xb9, 0x1d, 0x2e, 0x4d, 0x86, 0xb8, 0x55, 0x02, 0xdd, + 0x60, 0x29, 0x6e, 0x77, 0xe9, 0x89, 0x71, 0x8a, 0x57, 0x5f, 0x66, 0x74, 0xee, 0x4d, 0xc0, 0xb0, + 0xc3, 0x17, 0x59, 0x97, 0x06, 0xc1, 0xd6, 0xb6, 0x6e, 0xf7, 0xcb, 0x3d, 0x5a, 0x74, 0xc1, 0xad, + 0x3d, 0x5a, 0x6a, 0xec, 0x5b, 0x7b, 0xb4, 0xdc, 0x6b, 0x2f, 0xed, 0x51, 0x64, 0x86, 0x7d, 0x77, + 0xf2, 0x25, 0x6e, 0x1b, 0xd9, 0x07, 0x6a, 0x8f, 0x6e, 0xf5, 0xbc, 0x6a, 0xdb, 0xd8, 0x9d, 0x9e, + 0x52, 0xa8, 0x14, 0xed, 0x25, 0xae, 0x3d, 0x0f, 0x16, 0x14, 0x3a, 0xd9, 0x1c, 0x25, 0xa0, 0x28, + 0x57, 0x36, 0x6e, 0xdd, 0x4d, 0x24, 0xba, 0xe2, 0x36, 0x4d, 0xa2, 0x9c, 0xe6, 0x19, 0x34, 0x8c, + 0x26, 0x25, 0xd1, 0x41, 0xb6, 0xdc, 0x93, 0xed, 0x6c, 0x57, 0x8e, 0xd9, 0xa1, 0xc4, 0x6d, 0x71, + 0x06, 0x39, 0x22, 0x98, 0x3c, 0x8c, 0x16, 0x5e, 0xc1, 0xa3, 0xdc, 0xc7, 0x2c, 0x78, 0x54, 0xf5, + 0xfc, 0x2c, 0x1e, 0x3d, 0x44, 0xd0, 0x3c, 0x32, 0x68, 0x8d, 0xb4, 0xce, 0x8a, 0xa3, 0xb8, 0xba, + 0x51, 0x58, 0x1c, 0xc5, 0x63, 0x7a, 0x6e, 0x76, 0xb2, 0x23, 0xf8, 0xf9, 0x51, 0x54, 0xd8, 0x43, + 0x84, 0x48, 0xd1, 0x58, 0xb2, 0x6c, 0x6d, 0x75, 0xd0, 0x2c, 0x5b, 0xdb, 0x5d, 0xa8, 0x52, 0x88, + 0xa4, 0x82, 0xd6, 0x2b, 0x58, 0x50, 0x1d, 0x8d, 0xc2, 0xd0, 0x23, 0xbd, 0x9c, 0x4e, 0xbb, 0x3c, + 0x20, 0xa9, 0x5a, 0xc6, 0xf6, 0x83, 0x00, 0xa9, 0x4a, 0x43, 0x18, 0xfd, 0x8d, 0xc2, 0x10, 0xe5, + 0xd6, 0x48, 0x61, 0x88, 0xaa, 0x86, 0x88, 0x65, 0x08, 0xb1, 0xdb, 0x35, 0x8f, 0xbf, 0x77, 0xe0, + 0xde, 0xad, 0xed, 0x09, 0xf2, 0xbd, 0x37, 0xe8, 0x64, 0x08, 0x81, 0x3e, 0x78, 0xe3, 0xde, 0x87, + 0xfb, 0x10, 0xc5, 0x74, 0xdd, 0x5d, 0x75, 0x00, 0xe1, 0xb4, 0x40, 0xa0, 0xeb, 0x46, 0x08, 0x17, + 0xfa, 0x6f, 0x1d, 0xf1, 0x57, 0x10, 0x13, 0xe8, 0x92, 0x83, 0x29, 0x05, 0x50, 0x02, 0x1f, 0x4e, + 0x8d, 0x2f, 0xc5, 0x7d, 0x80, 0xe2, 0xee, 0xbb, 0xdb, 0x13, 0xc4, 0xe5, 0xc2, 0xfe, 0x31, 0x6c, + 0xeb, 0x36, 0x86, 0x45, 0xf7, 0xb3, 0x41, 0x1c, 0xe4, 0x45, 0xd5, 0x34, 0xa6, 0xd7, 0x51, 0x38, + 0xce, 0x68, 0x75, 0x6b, 0x9f, 0x29, 0x37, 0x72, 0x54, 0x88, 0x71, 0xce, 0x69, 0x73, 0xee, 0x29, + 0xac, 0xa8, 0x79, 0x9f, 0x85, 0x3e, 0xfb, 0xc6, 0x3c, 0xf7, 0x91, 0x67, 0xc7, 0x5d, 0x37, 0x79, + 0x9e, 0x87, 0x3e, 0x53, 0x1c, 0xcf, 0xe6, 0xf0, 0x2f, 0x9e, 0x3e, 0xfc, 0xff, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xe9, 0x97, 0xa2, 0x2a, 0x24, 0x35, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -4124,6 +4496,10 @@ const _ = grpc.SupportPackageIsVersion4 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type GoCryptoTraderClient interface { GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) + GetSubsystems(ctx context.Context, in *GetSubsystemsRequest, opts ...grpc.CallOption) (*GetSusbsytemsResponse, error) + EnableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericSubsystemResponse, error) + DisableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericSubsystemResponse, error) + GetRPCEndpoints(ctx context.Context, in *GetRPCEndpointsRequest, opts ...grpc.CallOption) (*GetRPCEndpointsResponse, error) GetExchanges(ctx context.Context, in *GetExchangesRequest, opts ...grpc.CallOption) (*GetExchangesResponse, error) DisableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) GetExchangeInfo(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeInfoResponse, error) @@ -4173,6 +4549,42 @@ func (c *goCryptoTraderClient) GetInfo(ctx context.Context, in *GetInfoRequest, return out, nil } +func (c *goCryptoTraderClient) GetSubsystems(ctx context.Context, in *GetSubsystemsRequest, opts ...grpc.CallOption) (*GetSusbsytemsResponse, error) { + out := new(GetSusbsytemsResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetSubsystems", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) EnableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericSubsystemResponse, error) { + out := new(GenericSubsystemResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/EnableSubsystem", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) DisableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericSubsystemResponse, error) { + out := new(GenericSubsystemResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/DisableSubsystem", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetRPCEndpoints(ctx context.Context, in *GetRPCEndpointsRequest, opts ...grpc.CallOption) (*GetRPCEndpointsResponse, error) { + out := new(GetRPCEndpointsResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetRPCEndpoints", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *goCryptoTraderClient) GetExchanges(ctx context.Context, in *GetExchangesRequest, opts ...grpc.CallOption) (*GetExchangesResponse, error) { out := new(GetExchangesResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetExchanges", in, out, opts...) @@ -4446,6 +4858,10 @@ func (c *goCryptoTraderClient) WithdrawFiatFunds(ctx context.Context, in *Withdr // GoCryptoTraderServer is the server API for GoCryptoTrader service. type GoCryptoTraderServer interface { GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) + GetSubsystems(context.Context, *GetSubsystemsRequest) (*GetSusbsytemsResponse, error) + EnableSubsystem(context.Context, *GenericSubsystemRequest) (*GenericSubsystemResponse, error) + DisableSubsystem(context.Context, *GenericSubsystemRequest) (*GenericSubsystemResponse, error) + GetRPCEndpoints(context.Context, *GetRPCEndpointsRequest) (*GetRPCEndpointsResponse, error) GetExchanges(context.Context, *GetExchangesRequest) (*GetExchangesResponse, error) DisableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) GetExchangeInfo(context.Context, *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) @@ -4500,6 +4916,78 @@ func _GoCryptoTrader_GetInfo_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _GoCryptoTrader_GetSubsystems_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSubsystemsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetSubsystems(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetSubsystems", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetSubsystems(ctx, req.(*GetSubsystemsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_EnableSubsystem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GenericSubsystemRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).EnableSubsystem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/EnableSubsystem", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).EnableSubsystem(ctx, req.(*GenericSubsystemRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_DisableSubsystem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GenericSubsystemRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).DisableSubsystem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/DisableSubsystem", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).DisableSubsystem(ctx, req.(*GenericSubsystemRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetRPCEndpoints_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRPCEndpointsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetRPCEndpoints(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetRPCEndpoints", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetRPCEndpoints(ctx, req.(*GetRPCEndpointsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _GoCryptoTrader_GetExchanges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetExchangesRequest) if err := dec(in); err != nil { @@ -5048,6 +5536,22 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ MethodName: "GetInfo", Handler: _GoCryptoTrader_GetInfo_Handler, }, + { + MethodName: "GetSubsystems", + Handler: _GoCryptoTrader_GetSubsystems_Handler, + }, + { + MethodName: "EnableSubsystem", + Handler: _GoCryptoTrader_EnableSubsystem_Handler, + }, + { + MethodName: "DisableSubsystem", + Handler: _GoCryptoTrader_DisableSubsystem_Handler, + }, + { + MethodName: "GetRPCEndpoints", + Handler: _GoCryptoTrader_GetRPCEndpoints_Handler, + }, { MethodName: "GetExchanges", Handler: _GoCryptoTrader_GetExchanges_Handler, diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 7844c2e3..18bf8616 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -37,6 +37,58 @@ func request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Mar } +func request_GoCryptoTrader_GetSubsystems_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetSubsystemsRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetSubsystems(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_EnableSubsystem_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericSubsystemRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_EnableSubsystem_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.EnableSubsystem(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_DisableSubsystem_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericSubsystemRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_DisableSubsystem_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DisableSubsystem(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetRPCEndpointsRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetRPCEndpoints(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetExchanges_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -533,6 +585,86 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_GetSubsystems_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetSubsystems_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetSubsystems_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_EnableSubsystem_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_EnableSubsystem_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_EnableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_DisableSubsystem_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_DisableSubsystem_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_DisableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetRPCEndpoints_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetRPCEndpoints_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetRPCEndpoints_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_GoCryptoTrader_GetExchanges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1139,6 +1271,14 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve var ( pattern_GoCryptoTrader_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, "")) + pattern_GoCryptoTrader_GetSubsystems_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getsusbsystems"}, "")) + + pattern_GoCryptoTrader_EnableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enablesubsystem"}, "")) + + pattern_GoCryptoTrader_DisableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disablesubsystem"}, "")) + + pattern_GoCryptoTrader_GetRPCEndpoints_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getrpcendpoints"}, "")) + pattern_GoCryptoTrader_GetExchanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchanges"}, "")) pattern_GoCryptoTrader_DisableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchange"}, "")) @@ -1203,6 +1343,14 @@ var ( var ( forward_GoCryptoTrader_GetInfo_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_GetSubsystems_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_EnableSubsystem_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_DisableSubsystem_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetRPCEndpoints_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_GetExchanges_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_DisableExchange_0 = runtime.ForwardResponseMessage diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 28552d80..58a3271d 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -12,6 +12,35 @@ message GetInfoResponse { int64 enabled_exchanges = 3; string default_forex_provider = 4; string default_fiat_currency = 5; + map subsystem_status = 6; + map rpc_endpoints = 7; +} + +// TO-DO comms APIs +message GetCommunicationRelayersRequest {} +message GetCommunicationRelayersResponse {} + +message GenericSubsystemRequest { + string subsystem = 1; +} + +message GenericSubsystemResponse {} + +message GetSubsystemsRequest {} + +message GetSusbsytemsResponse { + map subsystems_status = 1; +} + +message GetRPCEndpointsRequest{} + +message RPCEndpoint { + bool started = 1; + string listen_address = 2; +} + +message GetRPCEndpointsResponse { + map endpoints = 1; } message GenericExchangeNameRequest { @@ -403,6 +432,30 @@ service GoCryptoTrader { }; } + rpc GetSubsystems (GetSubsystemsRequest) returns (GetSusbsytemsResponse) { + option (google.api.http) = { + get: "/v1/getsusbsystems" + }; + } + + rpc EnableSubsystem (GenericSubsystemRequest) returns (GenericSubsystemResponse) { + option (google.api.http) = { + get: "/v1/enablesubsystem" + }; + } + + rpc DisableSubsystem (GenericSubsystemRequest) returns (GenericSubsystemResponse) { + option (google.api.http) = { + get: "/v1/disablesubsystem" + }; + } + + rpc GetRPCEndpoints (GetRPCEndpointsRequest) returns (GetRPCEndpointsResponse) { + option (google.api.http) = { + get: "/v1/getrpcendpoints" + }; + } + rpc GetExchanges (GetExchangesRequest) returns (GetExchangesResponse) { option (google.api.http) = { get: "/v1/getexchanges" diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index b4fa444f..7a6cedd9 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -145,6 +145,30 @@ ] } }, + "/v1/disablesubsystem": { + "get": { + "operationId": "DisableSubsystem", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericSubsystemResponse" + } + } + }, + "parameters": [ + { + "name": "subsystem", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/enableexchange": { "post": { "operationId": "EnableExchange", @@ -171,6 +195,30 @@ ] } }, + "/v1/enablesubsystem": { + "get": { + "operationId": "EnableSubsystem", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericSubsystemResponse" + } + } + }, + "parameters": [ + { + "name": "subsystem", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getaccountinfo": { "get": { "operationId": "GetAccountInfo", @@ -542,6 +590,38 @@ ] } }, + "/v1/getrpcendpoints": { + "get": { + "operationId": "GetRPCEndpoints", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetRPCEndpointsResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/getsusbsystems": { + "get": { + "operationId": "GetSubsystems", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetSusbsytemsResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getticker": { "post": { "operationId": "GetTicker", @@ -978,6 +1058,9 @@ "gctrpcGenericExchangeNameResponse": { "type": "object" }, + "gctrpcGenericSubsystemResponse": { + "type": "object" + }, "gctrpcGetAccountInfoResponse": { "type": "object", "properties": { @@ -1180,6 +1263,19 @@ }, "default_fiat_currency": { "type": "string" + }, + "subsystem_status": { + "type": "object", + "additionalProperties": { + "type": "boolean", + "format": "boolean" + } + }, + "rpc_endpoints": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/gctrpcRPCEndpoint" + } } } }, @@ -1290,6 +1386,29 @@ } } }, + "gctrpcGetRPCEndpointsResponse": { + "type": "object", + "properties": { + "endpoints": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/gctrpcRPCEndpoint" + } + } + } + }, + "gctrpcGetSusbsytemsResponse": { + "type": "object", + "properties": { + "subsystems_status": { + "type": "object", + "additionalProperties": { + "type": "boolean", + "format": "boolean" + } + } + } + }, "gctrpcGetTickerRequest": { "type": "object", "properties": { @@ -1490,6 +1609,18 @@ } } }, + "gctrpcRPCEndpoint": { + "type": "object", + "properties": { + "started": { + "type": "boolean", + "format": "boolean" + }, + "listen_address": { + "type": "string" + } + } + }, "gctrpcRemoveEventRequest": { "type": "object", "properties": { From b901c4b670a973a3510b09db158caf02b4551c7f Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 14 Jun 2019 18:00:42 +1000 Subject: [PATCH 12/71] Engine improvements Add back events tests Fill out SMTP comms handler Add getcommunicationrelayers gRPC command --- cmd/gctcli/commands.go | 26 + cmd/gctcli/main.go | 1 + communications/base/base.go | 6 + communications/base/base_interface.go | 14 + communications/smtpservice/smtpservice.go | 47 +- config/config_types.go | 1 + engine/comms_relayer.go | 7 + engine/events.go | 31 +- engine/events_test.go | 723 ++++++++++----------- engine/rpcserver.go | 18 + gctrpc/rpc.pb.go | 755 ++++++++++++---------- gctrpc/rpc.pb.gw.go | 33 + gctrpc/rpc.proto | 17 +- gctrpc/rpc.swagger.json | 40 ++ 14 files changed, 978 insertions(+), 741 deletions(-) diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index 5ce218e2..5dc0c4eb 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -183,6 +183,32 @@ func getRPCEndpoints(_ *cli.Context) error { return nil } +var getCommunicationRelayersCommand = cli.Command{ + Name: "getcommsrelayers", + Usage: "gets GoCryptoTrader communication relayers", + Action: getCommunicationRelayers, +} + +func getCommunicationRelayers(_ *cli.Context) error { + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetCommunicationRelayers(context.Background(), + &gctrpc.GetCommunicationRelayersRequest{}, + ) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + var getExchangesCommand = cli.Command{ Name: "getexchanges", Usage: "gets a list of enabled or available exchanges", diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index 92c4d77d..8267fa67 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -89,6 +89,7 @@ func main() { enableSubsystemCommand, disableSubsystemCommand, getRPCEndpointsCommand, + getCommunicationRelayersCommand, getExchangesCommand, enableExchangeCommand, disableExchangeCommand, diff --git a/communications/base/base.go b/communications/base/base.go index a4ee6b09..4090eaa1 100644 --- a/communications/base/base.go +++ b/communications/base/base.go @@ -24,6 +24,12 @@ type Event struct { Message string } +// CommsStatus stores the status of a comms relayer +type CommsStatus struct { + Enabled bool `json:"enabled"` + Connected bool `json:"connected"` +} + // IsEnabled returns if the comms package has been enabled in the configuration func (b *Base) IsEnabled() bool { return b.Enabled diff --git a/communications/base/base_interface.go b/communications/base/base_interface.go index a45f486f..cb5635c2 100644 --- a/communications/base/base_interface.go +++ b/communications/base/base_interface.go @@ -30,7 +30,9 @@ func (c IComm) Setup() { err := c[i].Connect() if err != nil { log.Errorf("Communications: %s failed to connect. Err: %s", c[i].GetName(), err) + continue } + log.Debugf("Communications: %v is enabled and online.", c[i].GetName()) } } } @@ -48,6 +50,18 @@ func (c IComm) PushEvent(event Event) { } } +// GetStatus returns the status of the comms relayers +func (c IComm) GetStatus() map[string]CommsStatus { + result := make(map[string]CommsStatus) + for x := range c { + result[c[x].GetName()] = CommsStatus{ + Enabled: c[x].IsEnabled(), + Connected: c[x].IsConnected(), + } + } + return result +} + // GetEnabledCommunicationMediums prints out enabled and connected communication // packages // (#debug output only) diff --git a/communications/smtpservice/smtpservice.go b/communications/smtpservice/smtpservice.go index 042ae6f8..90fe0fd4 100644 --- a/communications/smtpservice/smtpservice.go +++ b/communications/smtpservice/smtpservice.go @@ -6,9 +6,9 @@ import ( "net/smtp" "strings" - "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/communications/base" "github.com/thrasher-/gocryptotrader/config" + log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -23,6 +23,7 @@ type SMTPservice struct { Port string AccountName string AccountPassword string + From string RecipientList string } @@ -36,7 +37,9 @@ func (s *SMTPservice) Setup(cfg *config.CommunicationsConfig) { s.Port = cfg.SMTPConfig.Port s.AccountName = cfg.SMTPConfig.AccountName s.AccountPassword = cfg.SMTPConfig.AccountPassword + s.From = cfg.SMTPConfig.From s.RecipientList = cfg.SMTPConfig.RecipientList + log.Debugf("SMTP: Setup - From: %v. To: %s. Server: %s.", s.From, s.RecipientList, s.Host) } // IsConnected returns whether or not the connection is connected @@ -51,36 +54,34 @@ func (s *SMTPservice) Connect() error { } // PushEvent sends an event to supplied recipient list via SMTP -func (s *SMTPservice) PushEvent(base.Event) error { - return common.ErrNotYetImplemented +func (s *SMTPservice) PushEvent(e base.Event) error { + return s.Send(e.Type, e.Message) } // Send sends an email template to the recipient list via your SMTP host when // an internal event is triggered by GoCryptoTrader -func (s *SMTPservice) Send(subject, alert string) error { - if subject == "" || alert == "" { +func (s *SMTPservice) Send(subject, msg string) error { + if subject == "" || msg == "" { return errors.New("STMPservice Send() please add subject and alert") } - list := strings.Split(s.RecipientList, ",") + log.Debugf("SMTP: Sending email to %v. Subject: %s Message: %s [From: %s]", s.RecipientList, + subject, msg, s.From) + messageToSend := fmt.Sprintf( + msgSMTP, + s.RecipientList, + subject, + mime, + msg) - for i := range list { - messageToSend := fmt.Sprintf( - msgSMTP, - list[i], - subject, - mime, - alert) - - err := smtp.SendMail( - s.Host+":"+s.Port, - smtp.PlainAuth("", s.AccountName, s.AccountPassword, s.Host), - s.AccountName, - []string{list[i]}, - []byte(messageToSend)) - if err != nil { - return err - } + err := smtp.SendMail( + s.Host+":"+s.Port, + smtp.PlainAuth("", s.AccountName, s.AccountPassword, s.Host), + s.From, + strings.Split(s.RecipientList, ","), + []byte(messageToSend)) + if err != nil { + return err } return nil } diff --git a/config/config_types.go b/config/config_types.go index 1a535839..4f506a23 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -251,6 +251,7 @@ type SMTPConfig struct { Port string `json:"port"` AccountName string `json:"accountName"` AccountPassword string `json:"accountPassword"` + From string `json:"from"` RecipientList string `json:"recipientList"` } diff --git a/engine/comms_relayer.go b/engine/comms_relayer.go index d1a842a1..d1a94309 100644 --- a/engine/comms_relayer.go +++ b/engine/comms_relayer.go @@ -47,6 +47,13 @@ func (c *commsManager) Start() (err error) { return nil } +func (c *commsManager) GetStatus() (map[string]base.CommsStatus, error) { + if !c.Started() { + return nil, errors.New("communications manager not started") + } + return c.comms.GetStatus(), nil +} + func (c *commsManager) Stop() error { if atomic.LoadInt32(&c.started) == 0 { return errors.New("communications manager not started") diff --git a/engine/events.go b/engine/events.go index b2fa5d76..d8d225b1 100644 --- a/engine/events.go +++ b/engine/events.go @@ -143,14 +143,12 @@ func (e *Event) ExecuteAction() bool { // String turns the structure event into a string func (e *Event) String() string { return fmt.Sprintf( - "If the %s%s [%s] %s on %s meets the following %v then %s.", e.Pair.Base.String(), - e.Pair.Quote.String(), e.Asset, e.Item, e.Exchange, e.Condition, e.Action, + "If the %s [%s] %s on %s meets the following %v then %s.", e.Pair.String(), + strings.ToUpper(e.Asset.String()), e.Item, e.Exchange, e.Condition, e.Action, ) } func (e *Event) processTicker() bool { - targetPrice := e.Condition.Price - t, err := ticker.GetTicker(e.Exchange, e.Pair, e.Asset) if err != nil { if Bot.Settings.Verbose { @@ -159,16 +157,13 @@ func (e *Event) processTicker() bool { return false } - lastPrice := t.Last - - if lastPrice == 0 { + if t.Last == 0 { if Bot.Settings.Verbose { log.Debugln("Events: ticker last price is 0") } return false } - - return e.processCondition(lastPrice, targetPrice) + return e.processCondition(t.Last, e.Condition.Price) } func (e *Event) processCondition(actual, threshold float64) bool { @@ -259,14 +254,14 @@ func IsValidEvent(exchange, item string, condition EventConditionParams, action } if item == ItemPrice { - if condition.Price == 0 { + if condition.Price <= 0 { return errInvalidCondition } } if item == ItemOrderbook { - if condition.OrderbookAmount == 0 { - return errInvalidAction + if condition.OrderbookAmount <= 0 { + return errInvalidCondition } } @@ -276,10 +271,6 @@ func IsValidEvent(exchange, item string, condition EventConditionParams, action if a[0] != ActionSMSNotify { return errInvalidAction } - - if a[1] != "ALL" { - Bot.CommsManager.PushEvent(base.Event{Type: a[1]}) - } } else if action != ActionConsolePrint && action != ActionTest { return errInvalidAction } @@ -302,10 +293,12 @@ func EventManger() { } success := event.CheckEventCondition() if success { - log.Debugf( - "Events: ID: %d triggered on %s successfully.\n", event.ID, - event.Exchange, + msg := fmt.Sprintf( + "Events: ID: %d triggered on %s successfully [%v]\n", event.ID, + event.Exchange, event.String(), ) + log.Info(msg) + Bot.CommsManager.PushEvent(base.Event{Type: "event", Message: msg}) event.Executed = true } } diff --git a/engine/events_test.go b/engine/events_test.go index 966fd2c4..661e2e87 100644 --- a/engine/events_test.go +++ b/engine/events_test.go @@ -1,369 +1,358 @@ package engine -// -// 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 := currency.NewPairFromStrings("BTC", "USD") -// eventID, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) -// if err != nil && eventID != 0 { -// t.Errorf("Test Failed. AddEvent: Error, %s", err) -// } -// eventID, err = AddEvent("ANXX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) -// if err == nil && eventID == 0 { -// t.Error("Test Failed. AddEvent: Error, error not captured in Exchange") -// } -// eventID, err = AddEvent("ANX", "prices", ">,==", pair, assets.AssetTypeSpot, actionTest) -// if err == nil && eventID == 0 { -// t.Error("Test Failed. AddEvent: Error, error not captured in Item") -// } -// eventID, err = AddEvent("ANX", "price", "3===D", pair, assets.AssetTypeSpot, actionTest) -// if err == nil && eventID == 0 { -// t.Error("Test Failed. AddEvent: Error, error not captured in Condition") -// } -// eventID, err = AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, "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) { -// testSetup(t) -// -// pair := currency.NewPairFromStrings("BTC", "USD") -// eventID, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) -// if err != nil && eventID != 0 { -// t.Errorf("Test Failed. RemoveEvent: Error, %s", err) -// } -// if !RemoveEvent(eventID) { -// t.Error("Test Failed. RemoveEvent: Error, error removing event") -// } -// if RemoveEvent(1234) { -// t.Error("Test Failed. RemoveEvent: Error, error removing event") -// } -// } -// -// func TestGetEventCounter(t *testing.T) { -// testSetup(t) -// -// pair := currency.NewPairFromStrings("BTC", "USD") -// one, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) -// if err != nil { -// t.Errorf("Test Failed. GetEventCounter: Error, %s", err) -// } -// two, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) -// if err != nil { -// t.Errorf("Test Failed. GetEventCounter: Error, %s", err) -// } -// three, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) -// if err != nil { -// t.Errorf("Test Failed. GetEventCounter: Error, %s", err) -// } -// -// Events[three-1].Executed = true -// -// total, _ := GetEventCounter() -// if total <= 0 { -// t.Errorf("Test Failed. GetEventCounter: Total = %d", total) -// } -// if !RemoveEvent(one) { -// t.Error("Test Failed. GetEventCounter: Error, error removing event") -// } -// if !RemoveEvent(two) { -// t.Error("Test Failed. GetEventCounter: Error, error removing event") -// } -// if !RemoveEvent(three) { -// t.Error("Test Failed. GetEventCounter: Error, error removing event") -// } -// -// total2, _ := GetEventCounter() -// if total2 != 0 { -// t.Errorf("Test Failed. GetEventCounter: Total = %d", total2) -// } -// } -// -// func TestExecuteAction(t *testing.T) { -// testSetup(t) -// -// pair := currency.NewPairFromStrings("BTC", "USD") -// one, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) -// 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 + "," + "ALL" -// one, err = AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, 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, assets.AssetTypeSpot, 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) { -// testSetup(t) -// -// pair := currency.NewPairFromStrings("BTC", "USD") -// one, err := AddEvent("ANX", "price", ">,==", pair, assets.AssetTypeSpot, actionTest) -// if err != nil { -// t.Errorf("Test Failed. EventToString: Error, %s", err) -// } -// -// 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") -// } -// -// if !RemoveEvent(one) { -// t.Error("Test Failed. EventToString: Error, error removing event") -// } -// } -// -// func TestCheckCondition(t *testing.T) { -// testSetup(t) -// -// // Test invalid currency pair -// newPair := currency.NewPairFromStrings("A", "B") -// one, err := AddEvent("ANX", "price", ">=,10", newPair, assets.AssetTypeSpot, actionTest) -// if err != nil { -// t.Errorf("Test Failed. CheckCondition: Error, %s", err) -// } -// conditionBool := Events[one].CheckCondition() -// if conditionBool { -// t.Error("Test Failed. CheckCondition: Error, wrong conditional.") -// } -// -// // Test last price == 0 -// var tickerNew ticker.Price -// tickerNew.Last = 0 -// newPair = currency.NewPairFromStrings("BTC", "USD") -// ticker.ProcessTicker("ANX", newPair, tickerNew, exchange.AssetTypeSpot) -// 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, exchange.AssetTypeSpot) -// 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. CheckCondition: Error, error removing event") -// } -// } -// -// func TestIsValidEvent(t *testing.T) { -// testSetup(t) -// -// err := IsValidEvent("ANX", "price", ">,==", actionTest) -// if err != nil { -// t.Errorf("Test Failed. IsValidEvent: %s", err) -// } -// err = IsValidEvent("ANX", "price", ">,", actionTest) -// if err == nil { -// t.Errorf("Test Failed. IsValidEvent: %s", err) -// } -// err = IsValidEvent("ANX", "Testy", ">,==", actionTest) -// if err == nil { -// t.Errorf("Test Failed. IsValidEvent: %s", err) -// } -// err = IsValidEvent("Testys", "price", ">,==", actionTest) -// if err == nil { -// 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) { -// testSetup(t) -// -// pair := currency.NewPairFromStrings("BTC", "USD") -// _, err := AddEvent("ANX", "price", ">=,10", pair, assets.AssetTypeSpot, actionTest) -// if err != nil { -// t.Fatal("Test failed. TestChcheckEvents add event") -// } -// -// go CheckEvents() -// } -// -// func TestIsValidExchange(t *testing.T) { -// testSetup(t) -// -// boolean := IsValidExchange("ANX") -// if !boolean { -// t.Error("Test Failed. IsValidExchange: Error, incorrect Exchange") -// } -// 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") -// } -// boolean = IsValidCondition(">=") -// if !boolean { -// t.Error("Test Failed. IsValidCondition: Error, incorrect Condition") -// } -// boolean = IsValidCondition("<") -// if !boolean { -// t.Error("Test Failed. IsValidCondition: Error, incorrect Condition") -// } -// boolean = IsValidCondition("<=") -// if !boolean { -// t.Error("Test Failed. IsValidCondition: Error, incorrect Condition") -// } -// boolean = IsValidCondition("==") -// if !boolean { -// t.Error("Test Failed. IsValidCondition: Error, incorrect Condition") -// } -// boolean = IsValidCondition("**********") -// if boolean { -// t.Error("Test Failed. IsValidCondition: Error, incorrect return") -// } -// } -// -// func TestIsValidAction(t *testing.T) { -// testSetup(t) -// -// boolean := IsValidAction("sms") -// if !boolean { -// t.Error("Test Failed. IsValidAction: Error, incorrect Action") -// } -// boolean = IsValidAction(actionTest) -// if !boolean { -// t.Error("Test Failed. IsValidAction: Error, incorrect Action") -// } -// boolean = IsValidAction("randomstring") -// if boolean { -// t.Error("Test Failed. IsValidAction: Error, incorrect return") -// } -// } -// -// func TestIsValidItem(t *testing.T) { -// testSetup(t) -// -// boolean := IsValidItem("price") -// if !boolean { -// t.Error("Test Failed. IsValidItem: Error, incorrect Item") -// } -// boolean = IsValidItem("obtuse") -// if boolean { -// t.Error("Test Failed. IsValidItem: Error, incorrect return") -// } -// } +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +const ( + testExchange = "Bitstamp" +) + +var ( + configLoaded = false +) + +func addValidEvent() (int64, error) { + return Add(testExchange, + ItemPrice, + EventConditionParams{Condition: ConditionGreaterThan, Price: 1}, + currency.NewPair(currency.BTC, currency.USD), + assets.AssetTypeSpot, + "SMS,test") +} + +func TestAdd(t *testing.T) { + if !configLoaded { + loadConfig(t) + } + + _, err := Add("", "", EventConditionParams{}, currency.Pair{}, "", "") + if err == nil { + t.Error("should err on invalid params") + } + + _, err = addValidEvent() + if err != nil { + t.Error("unexpected result", err) + } + + _, err = addValidEvent() + if err != nil { + t.Error("unexpected result", err) + } + + if len(Events) != 2 { + t.Error("2 events should be stored") + } +} + +func TestRemove(t *testing.T) { + if !configLoaded { + loadConfig(t) + } + + id, err := addValidEvent() + if err != nil { + t.Error("unexpected result", err) + } + + if s := Remove(id); !s { + t.Error("unexpected result") + } + + if s := Remove(id); s { + t.Error("unexpected result") + } +} + +func TestGetEventCounter(t *testing.T) { + if !configLoaded { + loadConfig(t) + } + + _, err := addValidEvent() + if err != nil { + t.Error("unexpected result", err) + } + + n, e := GetEventCounter() + if n == 0 || e > 0 { + t.Error("unexpected result") + } + + Events[0].Executed = true + n, e = GetEventCounter() + if n == 0 || e == 0 { + t.Error("unexpected result") + } +} + +func TestExecuteAction(t *testing.T) { + t.Parallel() + if Bot == nil { + Bot = new(Engine) + } + + var e Event + if r := e.ExecuteAction(); !r { + t.Error("unexpected result") + } + + e.Action = "SMS,test" + if r := e.ExecuteAction(); !r { + t.Error("unexpected result") + } + + e.Action = "SMS,ALL" + if r := e.ExecuteAction(); !r { + t.Error("unexpected result") + } +} + +func TestString(t *testing.T) { + t.Parallel() + e := Event{ + Exchange: testExchange, + Item: ItemPrice, + Condition: EventConditionParams{ + Condition: ConditionGreaterThan, + Price: 1, + }, + Pair: currency.NewPair(currency.BTC, currency.USD), + Asset: assets.AssetTypeSpot, + Action: "SMS,ALL", + } + + if r := e.String(); r != "If the BTCUSD [SPOT] PRICE on Bitstamp meets the following {> 1 false false 0} then SMS,ALL." { + t.Error("unexpected result") + } +} + +func TestProcessTicker(t *testing.T) { + if Bot == nil { + Bot = new(Engine) + } + Bot.Settings.Verbose = true + + e := Event{ + Exchange: testExchange, + Pair: currency.NewPair(currency.BTC, currency.USD), + Asset: assets.AssetTypeSpot, + Condition: EventConditionParams{ + Condition: ConditionGreaterThan, + Price: 1, + }, + } + + // this will throw an err with an unpopulated ticker + ticker.Tickers = nil + if r := e.processTicker(); r { + t.Error("unexpected result") + } + + // now populate it with a 0 entry + tick := ticker.Price{ + Pair: currency.NewPair(currency.BTC, currency.USD), + Last: 0, + } + if err := ticker.ProcessTicker(e.Exchange, &tick, e.Asset); err != nil { + t.Fatal("unexpected result:", err) + } + if r := e.processTicker(); r { + t.Error("unexpected result") + } + + // now populate it with a number > 0 + tick.Last = 1337 + if err := ticker.ProcessTicker(e.Exchange, &tick, e.Asset); err != nil { + t.Fatal("unexpected result:", err) + } + if r := e.processTicker(); !r { + t.Error("unexpected result") + } +} + +func TestProcessCondition(t *testing.T) { + t.Parallel() + var e Event + tester := []struct { + Condition string + Actual float64 + Threshold float64 + ExpectedResult bool + }{ + {ConditionGreaterThan, 1, 2, false}, + {ConditionGreaterThan, 2, 1, true}, + {ConditionGreaterThanOrEqual, 1, 2, false}, + {ConditionGreaterThanOrEqual, 2, 1, true}, + {ConditionIsEqual, 1, 1, true}, + {ConditionIsEqual, 1, 2, false}, + {ConditionLessThan, 1, 2, true}, + {ConditionLessThan, 2, 1, false}, + {ConditionLessThanOrEqual, 1, 2, true}, + {ConditionLessThanOrEqual, 2, 1, false}, + } + for x := range tester { + e.Condition.Condition = tester[x].Condition + if r := e.processCondition(tester[x].Actual, tester[x].Threshold); r != tester[x].ExpectedResult { + t.Error("unexpected result") + } + } +} + +func TestProcessOrderbook(t *testing.T) { + if Bot == nil { + Bot = new(Engine) + } + Bot.Settings.Verbose = true + + e := Event{ + Exchange: testExchange, + Pair: currency.NewPair(currency.BTC, currency.USD), + Asset: assets.AssetTypeSpot, + Condition: EventConditionParams{ + Condition: ConditionGreaterThan, + CheckBidsAndAsks: true, + OrderbookAmount: 100, + }, + } + + // this will throw an err with an unpopulated orderbook + orderbook.Orderbooks = nil + if r := e.processOrderbook(); r { + t.Error("unexpected result") + } + + // now populate it with a 0 entry + o := orderbook.Base{ + Pair: currency.NewPair(currency.BTC, currency.USD), + Bids: []orderbook.Item{{Amount: 24, Price: 23}}, + Asks: []orderbook.Item{{Amount: 24, Price: 23}}, + ExchangeName: e.Exchange, + AssetType: e.Asset, + } + if err := o.Process(); err != nil { + t.Fatal("unexpected result:", err) + } + + if r := e.processOrderbook(); !r { + t.Error("unexpected result") + } +} + +func TestCheckEventCondition(t *testing.T) { + t.Parallel() + if Bot == nil { + Bot = new(Engine) + } + Bot.Settings.Verbose = true + + e := Event{ + Item: ItemPrice, + } + if r := e.CheckEventCondition(); r { + t.Error("unexpected result") + } + + e.Item = ItemOrderbook + if r := e.CheckEventCondition(); r { + t.Error("unexpected result") + } +} + +func TestIsValidEvent(t *testing.T) { + if !configLoaded { + loadConfig(t) + } + + // invalid exchange name + if err := IsValidEvent("meow", "", EventConditionParams{}, ""); err != errExchangeDisabled { + t.Error("unexpected result:", err) + } + + // invalid item + if err := IsValidEvent(testExchange, "", EventConditionParams{}, ""); err != errInvalidItem { + t.Error("unexpected result:", err) + } + + // invalid condition + if err := IsValidEvent(testExchange, ItemPrice, EventConditionParams{}, ""); err != errInvalidCondition { + t.Error("unexpected result:", err) + } + + // valid condition but empty price which will still throw an errInvalidCondition + c := EventConditionParams{ + Condition: ConditionGreaterThan, + } + if err := IsValidEvent(testExchange, ItemPrice, c, ""); err != errInvalidCondition { + t.Error("unexpected result:", err) + } + + // valid condition but empty orderbook amount will still still throw an errInvalidCondition + if err := IsValidEvent(testExchange, ItemOrderbook, c, ""); err != errInvalidCondition { + t.Error("unexpected result:", err) + } + + // test action splitting, but invalid + c.OrderbookAmount = 1337 + if err := IsValidEvent(testExchange, ItemOrderbook, c, "a,meow"); err != errInvalidAction { + t.Error("unexpected result:", err) + } + + // check for invalid action without splitting + if err := IsValidEvent(testExchange, ItemOrderbook, c, "hi"); err != errInvalidAction { + t.Error("unexpected result:", err) + } + + // valid event + if err := IsValidEvent(testExchange, ItemOrderbook, c, "SMS,test"); err != nil { + t.Error("unexpected result:", err) + } +} + +func TestIsValidExchange(t *testing.T) { + t.Parallel() + if s := IsValidExchange("invalidexchangerino"); s { + t.Error("unexpected result") + } + + loadConfig(t) + if s := IsValidExchange(testExchange); !s { + t.Error("unexpected result") + } +} + +func TestIsValidCondition(t *testing.T) { + t.Parallel() + if s := IsValidCondition("invalidconditionerino"); s { + t.Error("unexpected result") + } + if s := IsValidCondition(ConditionGreaterThan); !s { + t.Error("unexpected result") + } +} + +func TestIsValidAction(t *testing.T) { + t.Parallel() + if s := IsValidAction("invalidactionerino"); s { + t.Error("unexpected result") + } + if s := IsValidAction(ActionSMSNotify); !s { + t.Error("unexpected result") + } +} + +func TestIsValidItem(t *testing.T) { + t.Parallel() + if s := IsValidItem("invaliditemerino"); s { + t.Error("unexpected result") + } + if s := IsValidItem(ItemPrice); !s { + t.Error("unexpected result") + } +} diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 958e2194..8f5237a5 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -195,6 +195,24 @@ func (s *RPCServer) GetRPCEndpoints(ctx context.Context, r *gctrpc.GetRPCEndpoin return &resp, nil } +// GetCommunicationRelayers returns the status of the engines communication relayers +func (s *RPCServer) GetCommunicationRelayers(ctx context.Context, r *gctrpc.GetCommunicationRelayersRequest) (*gctrpc.GetCommunicationRelayersResponse, error) { + relayers, err := Bot.CommsManager.GetStatus() + if err != nil { + return nil, err + } + + var resp gctrpc.GetCommunicationRelayersResponse + resp.CommunicationRelayers = make(map[string]*gctrpc.CommunicationRelayer) + for k, v := range relayers { + resp.CommunicationRelayers[k] = &gctrpc.CommunicationRelayer{ + Enabled: v.Enabled, + Connected: v.Connected, + } + } + return &resp, nil +} + // GetExchanges returns a list of exchanges // Param is whether or not you wish to list enabled exchanges func (s *RPCServer) GetExchanges(ctx context.Context, r *gctrpc.GetExchangesRequest) (*gctrpc.GetExchangesResponse, error) { diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index 64af67fa..a889663e 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -141,7 +141,6 @@ func (m *GetInfoResponse) GetRpcEndpoints() map[string]*RPCEndpoint { return nil } -// TO-DO comms APIs type GetCommunicationRelayersRequest struct { XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -173,17 +172,65 @@ func (m *GetCommunicationRelayersRequest) XXX_DiscardUnknown() { var xxx_messageInfo_GetCommunicationRelayersRequest proto.InternalMessageInfo -type GetCommunicationRelayersResponse struct { +type CommunicationRelayer struct { + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + Connected bool `protobuf:"varint,2,opt,name=connected,proto3" json:"connected,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } +func (m *CommunicationRelayer) Reset() { *m = CommunicationRelayer{} } +func (m *CommunicationRelayer) String() string { return proto.CompactTextString(m) } +func (*CommunicationRelayer) ProtoMessage() {} +func (*CommunicationRelayer) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{3} +} + +func (m *CommunicationRelayer) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CommunicationRelayer.Unmarshal(m, b) +} +func (m *CommunicationRelayer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CommunicationRelayer.Marshal(b, m, deterministic) +} +func (m *CommunicationRelayer) XXX_Merge(src proto.Message) { + xxx_messageInfo_CommunicationRelayer.Merge(m, src) +} +func (m *CommunicationRelayer) XXX_Size() int { + return xxx_messageInfo_CommunicationRelayer.Size(m) +} +func (m *CommunicationRelayer) XXX_DiscardUnknown() { + xxx_messageInfo_CommunicationRelayer.DiscardUnknown(m) +} + +var xxx_messageInfo_CommunicationRelayer proto.InternalMessageInfo + +func (m *CommunicationRelayer) GetEnabled() bool { + if m != nil { + return m.Enabled + } + return false +} + +func (m *CommunicationRelayer) GetConnected() bool { + if m != nil { + return m.Connected + } + return false +} + +type GetCommunicationRelayersResponse struct { + CommunicationRelayers map[string]*CommunicationRelayer `protobuf:"bytes,1,rep,name=communication_relayers,json=communicationRelayers,proto3" json:"communication_relayers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + func (m *GetCommunicationRelayersResponse) Reset() { *m = GetCommunicationRelayersResponse{} } func (m *GetCommunicationRelayersResponse) String() string { return proto.CompactTextString(m) } func (*GetCommunicationRelayersResponse) ProtoMessage() {} func (*GetCommunicationRelayersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{3} + return fileDescriptor_77a6da22d6a3feb1, []int{4} } func (m *GetCommunicationRelayersResponse) XXX_Unmarshal(b []byte) error { @@ -204,6 +251,13 @@ func (m *GetCommunicationRelayersResponse) XXX_DiscardUnknown() { var xxx_messageInfo_GetCommunicationRelayersResponse proto.InternalMessageInfo +func (m *GetCommunicationRelayersResponse) GetCommunicationRelayers() map[string]*CommunicationRelayer { + if m != nil { + return m.CommunicationRelayers + } + return nil +} + type GenericSubsystemRequest struct { Subsystem string `protobuf:"bytes,1,opt,name=subsystem,proto3" json:"subsystem,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -215,7 +269,7 @@ func (m *GenericSubsystemRequest) Reset() { *m = GenericSubsystemRequest func (m *GenericSubsystemRequest) String() string { return proto.CompactTextString(m) } func (*GenericSubsystemRequest) ProtoMessage() {} func (*GenericSubsystemRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{4} + return fileDescriptor_77a6da22d6a3feb1, []int{5} } func (m *GenericSubsystemRequest) XXX_Unmarshal(b []byte) error { @@ -253,7 +307,7 @@ func (m *GenericSubsystemResponse) Reset() { *m = GenericSubsystemRespon func (m *GenericSubsystemResponse) String() string { return proto.CompactTextString(m) } func (*GenericSubsystemResponse) ProtoMessage() {} func (*GenericSubsystemResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{5} + return fileDescriptor_77a6da22d6a3feb1, []int{6} } func (m *GenericSubsystemResponse) XXX_Unmarshal(b []byte) error { @@ -284,7 +338,7 @@ func (m *GetSubsystemsRequest) Reset() { *m = GetSubsystemsRequest{} } func (m *GetSubsystemsRequest) String() string { return proto.CompactTextString(m) } func (*GetSubsystemsRequest) ProtoMessage() {} func (*GetSubsystemsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{6} + return fileDescriptor_77a6da22d6a3feb1, []int{7} } func (m *GetSubsystemsRequest) XXX_Unmarshal(b []byte) error { @@ -316,7 +370,7 @@ func (m *GetSusbsytemsResponse) Reset() { *m = GetSusbsytemsResponse{} } func (m *GetSusbsytemsResponse) String() string { return proto.CompactTextString(m) } func (*GetSusbsytemsResponse) ProtoMessage() {} func (*GetSusbsytemsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{7} + return fileDescriptor_77a6da22d6a3feb1, []int{8} } func (m *GetSusbsytemsResponse) XXX_Unmarshal(b []byte) error { @@ -354,7 +408,7 @@ func (m *GetRPCEndpointsRequest) Reset() { *m = GetRPCEndpointsRequest{} func (m *GetRPCEndpointsRequest) String() string { return proto.CompactTextString(m) } func (*GetRPCEndpointsRequest) ProtoMessage() {} func (*GetRPCEndpointsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{8} + return fileDescriptor_77a6da22d6a3feb1, []int{9} } func (m *GetRPCEndpointsRequest) XXX_Unmarshal(b []byte) error { @@ -387,7 +441,7 @@ func (m *RPCEndpoint) Reset() { *m = RPCEndpoint{} } func (m *RPCEndpoint) String() string { return proto.CompactTextString(m) } func (*RPCEndpoint) ProtoMessage() {} func (*RPCEndpoint) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{9} + return fileDescriptor_77a6da22d6a3feb1, []int{10} } func (m *RPCEndpoint) XXX_Unmarshal(b []byte) error { @@ -433,7 +487,7 @@ func (m *GetRPCEndpointsResponse) Reset() { *m = GetRPCEndpointsResponse func (m *GetRPCEndpointsResponse) String() string { return proto.CompactTextString(m) } func (*GetRPCEndpointsResponse) ProtoMessage() {} func (*GetRPCEndpointsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{10} + return fileDescriptor_77a6da22d6a3feb1, []int{11} } func (m *GetRPCEndpointsResponse) XXX_Unmarshal(b []byte) error { @@ -472,7 +526,7 @@ func (m *GenericExchangeNameRequest) Reset() { *m = GenericExchangeNameR func (m *GenericExchangeNameRequest) String() string { return proto.CompactTextString(m) } func (*GenericExchangeNameRequest) ProtoMessage() {} func (*GenericExchangeNameRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{11} + return fileDescriptor_77a6da22d6a3feb1, []int{12} } func (m *GenericExchangeNameRequest) XXX_Unmarshal(b []byte) error { @@ -510,7 +564,7 @@ func (m *GenericExchangeNameResponse) Reset() { *m = GenericExchangeName func (m *GenericExchangeNameResponse) String() string { return proto.CompactTextString(m) } func (*GenericExchangeNameResponse) ProtoMessage() {} func (*GenericExchangeNameResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{12} + return fileDescriptor_77a6da22d6a3feb1, []int{13} } func (m *GenericExchangeNameResponse) XXX_Unmarshal(b []byte) error { @@ -542,7 +596,7 @@ func (m *GetExchangesRequest) Reset() { *m = GetExchangesRequest{} } func (m *GetExchangesRequest) String() string { return proto.CompactTextString(m) } func (*GetExchangesRequest) ProtoMessage() {} func (*GetExchangesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{13} + return fileDescriptor_77a6da22d6a3feb1, []int{14} } func (m *GetExchangesRequest) XXX_Unmarshal(b []byte) error { @@ -581,7 +635,7 @@ func (m *GetExchangesResponse) Reset() { *m = GetExchangesResponse{} } func (m *GetExchangesResponse) String() string { return proto.CompactTextString(m) } func (*GetExchangesResponse) ProtoMessage() {} func (*GetExchangesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{14} + return fileDescriptor_77a6da22d6a3feb1, []int{15} } func (m *GetExchangesResponse) XXX_Unmarshal(b []byte) error { @@ -620,7 +674,7 @@ func (m *GetExchangeOTPReponse) Reset() { *m = GetExchangeOTPReponse{} } func (m *GetExchangeOTPReponse) String() string { return proto.CompactTextString(m) } func (*GetExchangeOTPReponse) ProtoMessage() {} func (*GetExchangeOTPReponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{15} + return fileDescriptor_77a6da22d6a3feb1, []int{16} } func (m *GetExchangeOTPReponse) XXX_Unmarshal(b []byte) error { @@ -658,7 +712,7 @@ func (m *GetExchangeOTPsRequest) Reset() { *m = GetExchangeOTPsRequest{} func (m *GetExchangeOTPsRequest) String() string { return proto.CompactTextString(m) } func (*GetExchangeOTPsRequest) ProtoMessage() {} func (*GetExchangeOTPsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{16} + return fileDescriptor_77a6da22d6a3feb1, []int{17} } func (m *GetExchangeOTPsRequest) XXX_Unmarshal(b []byte) error { @@ -690,7 +744,7 @@ func (m *GetExchangeOTPsResponse) Reset() { *m = GetExchangeOTPsResponse func (m *GetExchangeOTPsResponse) String() string { return proto.CompactTextString(m) } func (*GetExchangeOTPsResponse) ProtoMessage() {} func (*GetExchangeOTPsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{17} + return fileDescriptor_77a6da22d6a3feb1, []int{18} } func (m *GetExchangeOTPsResponse) XXX_Unmarshal(b []byte) error { @@ -729,7 +783,7 @@ func (m *DisableExchangeRequest) Reset() { *m = DisableExchangeRequest{} func (m *DisableExchangeRequest) String() string { return proto.CompactTextString(m) } func (*DisableExchangeRequest) ProtoMessage() {} func (*DisableExchangeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{18} + return fileDescriptor_77a6da22d6a3feb1, []int{19} } func (m *DisableExchangeRequest) XXX_Unmarshal(b []byte) error { @@ -779,7 +833,7 @@ func (m *GetExchangeInfoResponse) Reset() { *m = GetExchangeInfoResponse func (m *GetExchangeInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetExchangeInfoResponse) ProtoMessage() {} func (*GetExchangeInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{19} + return fileDescriptor_77a6da22d6a3feb1, []int{20} } func (m *GetExchangeInfoResponse) XXX_Unmarshal(b []byte) error { @@ -897,7 +951,7 @@ func (m *GetTickerRequest) Reset() { *m = GetTickerRequest{} } func (m *GetTickerRequest) String() string { return proto.CompactTextString(m) } func (*GetTickerRequest) ProtoMessage() {} func (*GetTickerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{20} + return fileDescriptor_77a6da22d6a3feb1, []int{21} } func (m *GetTickerRequest) XXX_Unmarshal(b []byte) error { @@ -952,7 +1006,7 @@ func (m *CurrencyPair) Reset() { *m = CurrencyPair{} } func (m *CurrencyPair) String() string { return proto.CompactTextString(m) } func (*CurrencyPair) ProtoMessage() {} func (*CurrencyPair) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{21} + return fileDescriptor_77a6da22d6a3feb1, []int{22} } func (m *CurrencyPair) XXX_Unmarshal(b []byte) error { @@ -1014,7 +1068,7 @@ func (m *TickerResponse) Reset() { *m = TickerResponse{} } func (m *TickerResponse) String() string { return proto.CompactTextString(m) } func (*TickerResponse) ProtoMessage() {} func (*TickerResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{22} + return fileDescriptor_77a6da22d6a3feb1, []int{23} } func (m *TickerResponse) XXX_Unmarshal(b []byte) error { @@ -1115,7 +1169,7 @@ func (m *GetTickersRequest) Reset() { *m = GetTickersRequest{} } func (m *GetTickersRequest) String() string { return proto.CompactTextString(m) } func (*GetTickersRequest) ProtoMessage() {} func (*GetTickersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{23} + return fileDescriptor_77a6da22d6a3feb1, []int{24} } func (m *GetTickersRequest) XXX_Unmarshal(b []byte) error { @@ -1148,7 +1202,7 @@ func (m *Tickers) Reset() { *m = Tickers{} } func (m *Tickers) String() string { return proto.CompactTextString(m) } func (*Tickers) ProtoMessage() {} func (*Tickers) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{24} + return fileDescriptor_77a6da22d6a3feb1, []int{25} } func (m *Tickers) XXX_Unmarshal(b []byte) error { @@ -1194,7 +1248,7 @@ func (m *GetTickersResponse) Reset() { *m = GetTickersResponse{} } func (m *GetTickersResponse) String() string { return proto.CompactTextString(m) } func (*GetTickersResponse) ProtoMessage() {} func (*GetTickersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{25} + return fileDescriptor_77a6da22d6a3feb1, []int{26} } func (m *GetTickersResponse) XXX_Unmarshal(b []byte) error { @@ -1235,7 +1289,7 @@ func (m *GetOrderbookRequest) Reset() { *m = GetOrderbookRequest{} } func (m *GetOrderbookRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderbookRequest) ProtoMessage() {} func (*GetOrderbookRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{26} + return fileDescriptor_77a6da22d6a3feb1, []int{27} } func (m *GetOrderbookRequest) XXX_Unmarshal(b []byte) error { @@ -1290,7 +1344,7 @@ func (m *OrderbookItem) Reset() { *m = OrderbookItem{} } func (m *OrderbookItem) String() string { return proto.CompactTextString(m) } func (*OrderbookItem) ProtoMessage() {} func (*OrderbookItem) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{27} + return fileDescriptor_77a6da22d6a3feb1, []int{28} } func (m *OrderbookItem) XXX_Unmarshal(b []byte) error { @@ -1348,7 +1402,7 @@ func (m *OrderbookResponse) Reset() { *m = OrderbookResponse{} } func (m *OrderbookResponse) String() string { return proto.CompactTextString(m) } func (*OrderbookResponse) ProtoMessage() {} func (*OrderbookResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{28} + return fileDescriptor_77a6da22d6a3feb1, []int{29} } func (m *OrderbookResponse) XXX_Unmarshal(b []byte) error { @@ -1421,7 +1475,7 @@ func (m *GetOrderbooksRequest) Reset() { *m = GetOrderbooksRequest{} } func (m *GetOrderbooksRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderbooksRequest) ProtoMessage() {} func (*GetOrderbooksRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{29} + return fileDescriptor_77a6da22d6a3feb1, []int{30} } func (m *GetOrderbooksRequest) XXX_Unmarshal(b []byte) error { @@ -1454,7 +1508,7 @@ func (m *Orderbooks) Reset() { *m = Orderbooks{} } func (m *Orderbooks) String() string { return proto.CompactTextString(m) } func (*Orderbooks) ProtoMessage() {} func (*Orderbooks) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{30} + return fileDescriptor_77a6da22d6a3feb1, []int{31} } func (m *Orderbooks) XXX_Unmarshal(b []byte) error { @@ -1500,7 +1554,7 @@ func (m *GetOrderbooksResponse) Reset() { *m = GetOrderbooksResponse{} } func (m *GetOrderbooksResponse) String() string { return proto.CompactTextString(m) } func (*GetOrderbooksResponse) ProtoMessage() {} func (*GetOrderbooksResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{31} + return fileDescriptor_77a6da22d6a3feb1, []int{32} } func (m *GetOrderbooksResponse) XXX_Unmarshal(b []byte) error { @@ -1539,7 +1593,7 @@ func (m *GetAccountInfoRequest) Reset() { *m = GetAccountInfoRequest{} } func (m *GetAccountInfoRequest) String() string { return proto.CompactTextString(m) } func (*GetAccountInfoRequest) ProtoMessage() {} func (*GetAccountInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{32} + return fileDescriptor_77a6da22d6a3feb1, []int{33} } func (m *GetAccountInfoRequest) XXX_Unmarshal(b []byte) error { @@ -1579,7 +1633,7 @@ func (m *Account) Reset() { *m = Account{} } func (m *Account) String() string { return proto.CompactTextString(m) } func (*Account) ProtoMessage() {} func (*Account) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{33} + return fileDescriptor_77a6da22d6a3feb1, []int{34} } func (m *Account) XXX_Unmarshal(b []byte) error { @@ -1627,7 +1681,7 @@ func (m *AccountCurrencyInfo) Reset() { *m = AccountCurrencyInfo{} } func (m *AccountCurrencyInfo) String() string { return proto.CompactTextString(m) } func (*AccountCurrencyInfo) ProtoMessage() {} func (*AccountCurrencyInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{34} + return fileDescriptor_77a6da22d6a3feb1, []int{35} } func (m *AccountCurrencyInfo) XXX_Unmarshal(b []byte) error { @@ -1681,7 +1735,7 @@ func (m *GetAccountInfoResponse) Reset() { *m = GetAccountInfoResponse{} func (m *GetAccountInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetAccountInfoResponse) ProtoMessage() {} func (*GetAccountInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{35} + return fileDescriptor_77a6da22d6a3feb1, []int{36} } func (m *GetAccountInfoResponse) XXX_Unmarshal(b []byte) error { @@ -1726,7 +1780,7 @@ func (m *GetConfigRequest) Reset() { *m = GetConfigRequest{} } func (m *GetConfigRequest) String() string { return proto.CompactTextString(m) } func (*GetConfigRequest) ProtoMessage() {} func (*GetConfigRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{36} + return fileDescriptor_77a6da22d6a3feb1, []int{37} } func (m *GetConfigRequest) XXX_Unmarshal(b []byte) error { @@ -1758,7 +1812,7 @@ func (m *GetConfigResponse) Reset() { *m = GetConfigResponse{} } func (m *GetConfigResponse) String() string { return proto.CompactTextString(m) } func (*GetConfigResponse) ProtoMessage() {} func (*GetConfigResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{37} + return fileDescriptor_77a6da22d6a3feb1, []int{38} } func (m *GetConfigResponse) XXX_Unmarshal(b []byte) error { @@ -1800,7 +1854,7 @@ func (m *PortfolioAddress) Reset() { *m = PortfolioAddress{} } func (m *PortfolioAddress) String() string { return proto.CompactTextString(m) } func (*PortfolioAddress) ProtoMessage() {} func (*PortfolioAddress) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{38} + return fileDescriptor_77a6da22d6a3feb1, []int{39} } func (m *PortfolioAddress) XXX_Unmarshal(b []byte) error { @@ -1859,7 +1913,7 @@ func (m *GetPortfolioRequest) Reset() { *m = GetPortfolioRequest{} } func (m *GetPortfolioRequest) String() string { return proto.CompactTextString(m) } func (*GetPortfolioRequest) ProtoMessage() {} func (*GetPortfolioRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{39} + return fileDescriptor_77a6da22d6a3feb1, []int{40} } func (m *GetPortfolioRequest) XXX_Unmarshal(b []byte) error { @@ -1891,7 +1945,7 @@ func (m *GetPortfolioResponse) Reset() { *m = GetPortfolioResponse{} } func (m *GetPortfolioResponse) String() string { return proto.CompactTextString(m) } func (*GetPortfolioResponse) ProtoMessage() {} func (*GetPortfolioResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{40} + return fileDescriptor_77a6da22d6a3feb1, []int{41} } func (m *GetPortfolioResponse) XXX_Unmarshal(b []byte) error { @@ -1929,7 +1983,7 @@ func (m *GetPortfolioSummaryRequest) Reset() { *m = GetPortfolioSummaryR func (m *GetPortfolioSummaryRequest) String() string { return proto.CompactTextString(m) } func (*GetPortfolioSummaryRequest) ProtoMessage() {} func (*GetPortfolioSummaryRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{41} + return fileDescriptor_77a6da22d6a3feb1, []int{42} } func (m *GetPortfolioSummaryRequest) XXX_Unmarshal(b []byte) error { @@ -1964,7 +2018,7 @@ func (m *Coin) Reset() { *m = Coin{} } func (m *Coin) String() string { return proto.CompactTextString(m) } func (*Coin) ProtoMessage() {} func (*Coin) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{42} + return fileDescriptor_77a6da22d6a3feb1, []int{43} } func (m *Coin) XXX_Unmarshal(b []byte) error { @@ -2026,7 +2080,7 @@ func (m *OfflineCoinSummary) Reset() { *m = OfflineCoinSummary{} } func (m *OfflineCoinSummary) String() string { return proto.CompactTextString(m) } func (*OfflineCoinSummary) ProtoMessage() {} func (*OfflineCoinSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{43} + return fileDescriptor_77a6da22d6a3feb1, []int{44} } func (m *OfflineCoinSummary) XXX_Unmarshal(b []byte) error { @@ -2080,7 +2134,7 @@ func (m *OnlineCoinSummary) Reset() { *m = OnlineCoinSummary{} } func (m *OnlineCoinSummary) String() string { return proto.CompactTextString(m) } func (*OnlineCoinSummary) ProtoMessage() {} func (*OnlineCoinSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{44} + return fileDescriptor_77a6da22d6a3feb1, []int{45} } func (m *OnlineCoinSummary) XXX_Unmarshal(b []byte) error { @@ -2126,7 +2180,7 @@ func (m *OfflineCoins) Reset() { *m = OfflineCoins{} } func (m *OfflineCoins) String() string { return proto.CompactTextString(m) } func (*OfflineCoins) ProtoMessage() {} func (*OfflineCoins) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{45} + return fileDescriptor_77a6da22d6a3feb1, []int{46} } func (m *OfflineCoins) XXX_Unmarshal(b []byte) error { @@ -2165,7 +2219,7 @@ func (m *OnlineCoins) Reset() { *m = OnlineCoins{} } func (m *OnlineCoins) String() string { return proto.CompactTextString(m) } func (*OnlineCoins) ProtoMessage() {} func (*OnlineCoins) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{46} + return fileDescriptor_77a6da22d6a3feb1, []int{47} } func (m *OnlineCoins) XXX_Unmarshal(b []byte) error { @@ -2208,7 +2262,7 @@ func (m *GetPortfolioSummaryResponse) Reset() { *m = GetPortfolioSummary func (m *GetPortfolioSummaryResponse) String() string { return proto.CompactTextString(m) } func (*GetPortfolioSummaryResponse) ProtoMessage() {} func (*GetPortfolioSummaryResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{47} + return fileDescriptor_77a6da22d6a3feb1, []int{48} } func (m *GetPortfolioSummaryResponse) XXX_Unmarshal(b []byte) error { @@ -2278,7 +2332,7 @@ func (m *AddPortfolioAddressRequest) Reset() { *m = AddPortfolioAddressR func (m *AddPortfolioAddressRequest) String() string { return proto.CompactTextString(m) } func (*AddPortfolioAddressRequest) ProtoMessage() {} func (*AddPortfolioAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{48} + return fileDescriptor_77a6da22d6a3feb1, []int{49} } func (m *AddPortfolioAddressRequest) XXX_Unmarshal(b []byte) error { @@ -2337,7 +2391,7 @@ func (m *AddPortfolioAddressResponse) Reset() { *m = AddPortfolioAddress func (m *AddPortfolioAddressResponse) String() string { return proto.CompactTextString(m) } func (*AddPortfolioAddressResponse) ProtoMessage() {} func (*AddPortfolioAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{49} + return fileDescriptor_77a6da22d6a3feb1, []int{50} } func (m *AddPortfolioAddressResponse) XXX_Unmarshal(b []byte) error { @@ -2371,7 +2425,7 @@ func (m *RemovePortfolioAddressRequest) Reset() { *m = RemovePortfolioAd func (m *RemovePortfolioAddressRequest) String() string { return proto.CompactTextString(m) } func (*RemovePortfolioAddressRequest) ProtoMessage() {} func (*RemovePortfolioAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{50} + return fileDescriptor_77a6da22d6a3feb1, []int{51} } func (m *RemovePortfolioAddressRequest) XXX_Unmarshal(b []byte) error { @@ -2423,7 +2477,7 @@ func (m *RemovePortfolioAddressResponse) Reset() { *m = RemovePortfolioA func (m *RemovePortfolioAddressResponse) String() string { return proto.CompactTextString(m) } func (*RemovePortfolioAddressResponse) ProtoMessage() {} func (*RemovePortfolioAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{51} + return fileDescriptor_77a6da22d6a3feb1, []int{52} } func (m *RemovePortfolioAddressResponse) XXX_Unmarshal(b []byte) error { @@ -2454,7 +2508,7 @@ func (m *GetForexProvidersRequest) Reset() { *m = GetForexProvidersReque func (m *GetForexProvidersRequest) String() string { return proto.CompactTextString(m) } func (*GetForexProvidersRequest) ProtoMessage() {} func (*GetForexProvidersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{52} + return fileDescriptor_77a6da22d6a3feb1, []int{53} } func (m *GetForexProvidersRequest) XXX_Unmarshal(b []byte) error { @@ -2492,7 +2546,7 @@ func (m *ForexProvider) Reset() { *m = ForexProvider{} } func (m *ForexProvider) String() string { return proto.CompactTextString(m) } func (*ForexProvider) ProtoMessage() {} func (*ForexProvider) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{53} + return fileDescriptor_77a6da22d6a3feb1, []int{54} } func (m *ForexProvider) XXX_Unmarshal(b []byte) error { @@ -2573,7 +2627,7 @@ func (m *GetForexProvidersResponse) Reset() { *m = GetForexProvidersResp func (m *GetForexProvidersResponse) String() string { return proto.CompactTextString(m) } func (*GetForexProvidersResponse) ProtoMessage() {} func (*GetForexProvidersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{54} + return fileDescriptor_77a6da22d6a3feb1, []int{55} } func (m *GetForexProvidersResponse) XXX_Unmarshal(b []byte) error { @@ -2611,7 +2665,7 @@ func (m *GetForexRatesRequest) Reset() { *m = GetForexRatesRequest{} } func (m *GetForexRatesRequest) String() string { return proto.CompactTextString(m) } func (*GetForexRatesRequest) ProtoMessage() {} func (*GetForexRatesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{55} + return fileDescriptor_77a6da22d6a3feb1, []int{56} } func (m *GetForexRatesRequest) XXX_Unmarshal(b []byte) error { @@ -2646,7 +2700,7 @@ func (m *ForexRatesConversion) Reset() { *m = ForexRatesConversion{} } func (m *ForexRatesConversion) String() string { return proto.CompactTextString(m) } func (*ForexRatesConversion) ProtoMessage() {} func (*ForexRatesConversion) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{56} + return fileDescriptor_77a6da22d6a3feb1, []int{57} } func (m *ForexRatesConversion) XXX_Unmarshal(b []byte) error { @@ -2706,7 +2760,7 @@ func (m *GetForexRatesResponse) Reset() { *m = GetForexRatesResponse{} } func (m *GetForexRatesResponse) String() string { return proto.CompactTextString(m) } func (*GetForexRatesResponse) ProtoMessage() {} func (*GetForexRatesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{57} + return fileDescriptor_77a6da22d6a3feb1, []int{58} } func (m *GetForexRatesResponse) XXX_Unmarshal(b []byte) error { @@ -2756,7 +2810,7 @@ func (m *OrderDetails) Reset() { *m = OrderDetails{} } func (m *OrderDetails) String() string { return proto.CompactTextString(m) } func (*OrderDetails) ProtoMessage() {} func (*OrderDetails) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{58} + return fileDescriptor_77a6da22d6a3feb1, []int{59} } func (m *OrderDetails) XXX_Unmarshal(b []byte) error { @@ -2874,7 +2928,7 @@ func (m *GetOrdersRequest) Reset() { *m = GetOrdersRequest{} } func (m *GetOrdersRequest) String() string { return proto.CompactTextString(m) } func (*GetOrdersRequest) ProtoMessage() {} func (*GetOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{59} + return fileDescriptor_77a6da22d6a3feb1, []int{60} } func (m *GetOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -2927,7 +2981,7 @@ func (m *GetOrdersResponse) Reset() { *m = GetOrdersResponse{} } func (m *GetOrdersResponse) String() string { return proto.CompactTextString(m) } func (*GetOrdersResponse) ProtoMessage() {} func (*GetOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{60} + return fileDescriptor_77a6da22d6a3feb1, []int{61} } func (m *GetOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -2967,7 +3021,7 @@ func (m *GetOrderRequest) Reset() { *m = GetOrderRequest{} } func (m *GetOrderRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderRequest) ProtoMessage() {} func (*GetOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{61} + return fileDescriptor_77a6da22d6a3feb1, []int{62} } func (m *GetOrderRequest) XXX_Unmarshal(b []byte) error { @@ -3019,7 +3073,7 @@ func (m *SubmitOrderRequest) Reset() { *m = SubmitOrderRequest{} } func (m *SubmitOrderRequest) String() string { return proto.CompactTextString(m) } func (*SubmitOrderRequest) ProtoMessage() {} func (*SubmitOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{62} + return fileDescriptor_77a6da22d6a3feb1, []int{63} } func (m *SubmitOrderRequest) XXX_Unmarshal(b []byte) error { @@ -3101,7 +3155,7 @@ func (m *SubmitOrderResponse) Reset() { *m = SubmitOrderResponse{} } func (m *SubmitOrderResponse) String() string { return proto.CompactTextString(m) } func (*SubmitOrderResponse) ProtoMessage() {} func (*SubmitOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{63} + return fileDescriptor_77a6da22d6a3feb1, []int{64} } func (m *SubmitOrderResponse) XXX_Unmarshal(b []byte) error { @@ -3153,7 +3207,7 @@ func (m *CancelOrderRequest) Reset() { *m = CancelOrderRequest{} } func (m *CancelOrderRequest) String() string { return proto.CompactTextString(m) } func (*CancelOrderRequest) ProtoMessage() {} func (*CancelOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{64} + return fileDescriptor_77a6da22d6a3feb1, []int{65} } func (m *CancelOrderRequest) XXX_Unmarshal(b []byte) error { @@ -3233,7 +3287,7 @@ func (m *CancelOrderResponse) Reset() { *m = CancelOrderResponse{} } func (m *CancelOrderResponse) String() string { return proto.CompactTextString(m) } func (*CancelOrderResponse) ProtoMessage() {} func (*CancelOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{65} + return fileDescriptor_77a6da22d6a3feb1, []int{66} } func (m *CancelOrderResponse) XXX_Unmarshal(b []byte) error { @@ -3265,7 +3319,7 @@ func (m *CancelAllOrdersRequest) Reset() { *m = CancelAllOrdersRequest{} func (m *CancelAllOrdersRequest) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersRequest) ProtoMessage() {} func (*CancelAllOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{66} + return fileDescriptor_77a6da22d6a3feb1, []int{67} } func (m *CancelAllOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -3304,7 +3358,7 @@ func (m *CancelAllOrdersResponse) Reset() { *m = CancelAllOrdersResponse func (m *CancelAllOrdersResponse) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse) ProtoMessage() {} func (*CancelAllOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{67} + return fileDescriptor_77a6da22d6a3feb1, []int{68} } func (m *CancelAllOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -3344,7 +3398,7 @@ func (m *CancelAllOrdersResponse_Orders) Reset() { *m = CancelAllOrdersR func (m *CancelAllOrdersResponse_Orders) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse_Orders) ProtoMessage() {} func (*CancelAllOrdersResponse_Orders) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{67, 0} + return fileDescriptor_77a6da22d6a3feb1, []int{68, 0} } func (m *CancelAllOrdersResponse_Orders) XXX_Unmarshal(b []byte) error { @@ -3389,7 +3443,7 @@ func (m *GetEventsRequest) Reset() { *m = GetEventsRequest{} } func (m *GetEventsRequest) String() string { return proto.CompactTextString(m) } func (*GetEventsRequest) ProtoMessage() {} func (*GetEventsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{68} + return fileDescriptor_77a6da22d6a3feb1, []int{69} } func (m *GetEventsRequest) XXX_Unmarshal(b []byte) error { @@ -3425,7 +3479,7 @@ func (m *ConditionParams) Reset() { *m = ConditionParams{} } func (m *ConditionParams) String() string { return proto.CompactTextString(m) } func (*ConditionParams) ProtoMessage() {} func (*ConditionParams) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{69} + return fileDescriptor_77a6da22d6a3feb1, []int{70} } func (m *ConditionParams) XXX_Unmarshal(b []byte) error { @@ -3498,7 +3552,7 @@ func (m *GetEventsResponse) Reset() { *m = GetEventsResponse{} } func (m *GetEventsResponse) String() string { return proto.CompactTextString(m) } func (*GetEventsResponse) ProtoMessage() {} func (*GetEventsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{70} + return fileDescriptor_77a6da22d6a3feb1, []int{71} } func (m *GetEventsResponse) XXX_Unmarshal(b []byte) error { @@ -3584,7 +3638,7 @@ func (m *AddEventRequest) Reset() { *m = AddEventRequest{} } func (m *AddEventRequest) String() string { return proto.CompactTextString(m) } func (*AddEventRequest) ProtoMessage() {} func (*AddEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{71} + return fileDescriptor_77a6da22d6a3feb1, []int{72} } func (m *AddEventRequest) XXX_Unmarshal(b []byte) error { @@ -3658,7 +3712,7 @@ func (m *AddEventResponse) Reset() { *m = AddEventResponse{} } func (m *AddEventResponse) String() string { return proto.CompactTextString(m) } func (*AddEventResponse) ProtoMessage() {} func (*AddEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{72} + return fileDescriptor_77a6da22d6a3feb1, []int{73} } func (m *AddEventResponse) XXX_Unmarshal(b []byte) error { @@ -3697,7 +3751,7 @@ func (m *RemoveEventRequest) Reset() { *m = RemoveEventRequest{} } func (m *RemoveEventRequest) String() string { return proto.CompactTextString(m) } func (*RemoveEventRequest) ProtoMessage() {} func (*RemoveEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{73} + return fileDescriptor_77a6da22d6a3feb1, []int{74} } func (m *RemoveEventRequest) XXX_Unmarshal(b []byte) error { @@ -3735,7 +3789,7 @@ func (m *RemoveEventResponse) Reset() { *m = RemoveEventResponse{} } func (m *RemoveEventResponse) String() string { return proto.CompactTextString(m) } func (*RemoveEventResponse) ProtoMessage() {} func (*RemoveEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{74} + return fileDescriptor_77a6da22d6a3feb1, []int{75} } func (m *RemoveEventResponse) XXX_Unmarshal(b []byte) error { @@ -3769,7 +3823,7 @@ func (m *GetCryptocurrencyDepositAddressesRequest) Reset() { func (m *GetCryptocurrencyDepositAddressesRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{75} + return fileDescriptor_77a6da22d6a3feb1, []int{76} } func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Unmarshal(b []byte) error { @@ -3810,7 +3864,7 @@ func (m *GetCryptocurrencyDepositAddressesResponse) Reset() { func (m *GetCryptocurrencyDepositAddressesResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{76} + return fileDescriptor_77a6da22d6a3feb1, []int{77} } func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Unmarshal(b []byte) error { @@ -3852,7 +3906,7 @@ func (m *GetCryptocurrencyDepositAddressRequest) Reset() { func (m *GetCryptocurrencyDepositAddressRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{77} + return fileDescriptor_77a6da22d6a3feb1, []int{78} } func (m *GetCryptocurrencyDepositAddressRequest) XXX_Unmarshal(b []byte) error { @@ -3900,7 +3954,7 @@ func (m *GetCryptocurrencyDepositAddressResponse) Reset() { func (m *GetCryptocurrencyDepositAddressResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{78} + return fileDescriptor_77a6da22d6a3feb1, []int{79} } func (m *GetCryptocurrencyDepositAddressResponse) XXX_Unmarshal(b []byte) error { @@ -3955,7 +4009,7 @@ func (m *WithdrawCurrencyRequest) Reset() { *m = WithdrawCurrencyRequest func (m *WithdrawCurrencyRequest) String() string { return proto.CompactTextString(m) } func (*WithdrawCurrencyRequest) ProtoMessage() {} func (*WithdrawCurrencyRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{79} + return fileDescriptor_77a6da22d6a3feb1, []int{80} } func (m *WithdrawCurrencyRequest) XXX_Unmarshal(b []byte) error { @@ -4106,7 +4160,7 @@ func (m *WithdrawResponse) Reset() { *m = WithdrawResponse{} } func (m *WithdrawResponse) String() string { return proto.CompactTextString(m) } func (*WithdrawResponse) ProtoMessage() {} func (*WithdrawResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{80} + return fileDescriptor_77a6da22d6a3feb1, []int{81} } func (m *WithdrawResponse) XXX_Unmarshal(b []byte) error { @@ -4140,7 +4194,9 @@ func init() { proto.RegisterMapType((map[string]*RPCEndpoint)(nil), "gctrpc.GetInfoResponse.RpcEndpointsEntry") proto.RegisterMapType((map[string]bool)(nil), "gctrpc.GetInfoResponse.SubsystemStatusEntry") proto.RegisterType((*GetCommunicationRelayersRequest)(nil), "gctrpc.GetCommunicationRelayersRequest") + proto.RegisterType((*CommunicationRelayer)(nil), "gctrpc.CommunicationRelayer") proto.RegisterType((*GetCommunicationRelayersResponse)(nil), "gctrpc.GetCommunicationRelayersResponse") + proto.RegisterMapType((map[string]*CommunicationRelayer)(nil), "gctrpc.GetCommunicationRelayersResponse.CommunicationRelayersEntry") proto.RegisterType((*GenericSubsystemRequest)(nil), "gctrpc.GenericSubsystemRequest") proto.RegisterType((*GenericSubsystemResponse)(nil), "gctrpc.GenericSubsystemResponse") proto.RegisterType((*GetSubsystemsRequest)(nil), "gctrpc.GetSubsystemsRequest") @@ -4232,255 +4288,261 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 3962 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x4b, 0x6c, 0x24, 0x49, - 0x56, 0xca, 0xb2, 0xdb, 0x76, 0xbd, 0x2a, 0xbb, 0xec, 0xf0, 0xaf, 0x5c, 0xb6, 0xdb, 0xee, 0x1c, - 0xba, 0xa7, 0x7b, 0x3e, 0xf6, 0x4e, 0x4f, 0x8b, 0x1d, 0x76, 0x96, 0x05, 0x8f, 0xa7, 0xc7, 0xdb, - 0xec, 0xee, 0xb4, 0xc9, 0xee, 0xed, 0x91, 0x66, 0x11, 0x45, 0xba, 0x32, 0x6c, 0xa7, 0x9c, 0x95, - 0x99, 0x93, 0x19, 0x65, 0x77, 0xad, 0x90, 0x90, 0x56, 0xe2, 0x0a, 0x07, 0x84, 0xc4, 0x81, 0x13, - 0x47, 0x24, 0x2e, 0x88, 0x13, 0x87, 0x11, 0x57, 0xc4, 0x91, 0x0b, 0xdc, 0x11, 0x37, 0xe0, 0xc4, - 0x85, 0x13, 0x8a, 0x17, 0x9f, 0x8c, 0xa8, 0xcc, 0x2a, 0x57, 0xef, 0x8c, 0xe6, 0xd2, 0x5d, 0xf9, - 0xe2, 0xc5, 0x7b, 0x2f, 0xde, 0x7b, 0xf1, 0xe2, 0xbd, 0x17, 0x61, 0xa8, 0x67, 0x69, 0xef, 0x20, - 0xcd, 0x12, 0x96, 0x90, 0xb9, 0x8b, 0x1e, 0xcb, 0xd2, 0x5e, 0x67, 0xe7, 0x22, 0x49, 0x2e, 0x22, - 0x7a, 0xe8, 0xa7, 0xe1, 0xa1, 0x1f, 0xc7, 0x09, 0xf3, 0x59, 0x98, 0xc4, 0xb9, 0xc0, 0x72, 0x97, - 0x61, 0xe9, 0x84, 0xb2, 0x67, 0xf1, 0x79, 0xe2, 0xd1, 0xaf, 0x06, 0x34, 0x67, 0xee, 0x3f, 0xcc, - 0x42, 0x4b, 0x83, 0xf2, 0x34, 0x89, 0x73, 0x4a, 0x36, 0x60, 0x6e, 0x90, 0xb2, 0xb0, 0x4f, 0xdb, - 0xce, 0xbe, 0xf3, 0xb0, 0xee, 0xc9, 0x2f, 0x72, 0x08, 0xab, 0xfe, 0xb5, 0x1f, 0x46, 0xfe, 0x59, - 0x44, 0xbb, 0xf4, 0x75, 0xef, 0xd2, 0x8f, 0x2f, 0x68, 0xde, 0xae, 0xed, 0x3b, 0x0f, 0x67, 0x3c, - 0xa2, 0x87, 0x9e, 0xaa, 0x11, 0xf2, 0x2e, 0xac, 0xd0, 0x98, 0x83, 0x02, 0x03, 0x7d, 0x06, 0xd1, - 0x97, 0xe5, 0x40, 0x81, 0xfc, 0x04, 0x36, 0x02, 0x7a, 0xee, 0x0f, 0x22, 0xd6, 0x3d, 0x4f, 0x32, - 0xfa, 0xba, 0x9b, 0x66, 0xc9, 0x75, 0x18, 0xd0, 0xac, 0x3d, 0x8b, 0x52, 0xac, 0xc9, 0xd1, 0xcf, - 0xf8, 0xe0, 0xa9, 0x1c, 0x23, 0x8f, 0x61, 0x5d, 0xcf, 0x0a, 0x7d, 0xd6, 0xed, 0x0d, 0xb2, 0x8c, - 0xc6, 0xbd, 0x61, 0xfb, 0x0e, 0x4e, 0x5a, 0x55, 0x93, 0x42, 0x9f, 0x1d, 0xcb, 0x21, 0xf2, 0x05, - 0x2c, 0xe7, 0x83, 0xb3, 0x7c, 0x98, 0x33, 0xda, 0xef, 0xe6, 0xcc, 0x67, 0x83, 0xbc, 0x3d, 0xb7, - 0x3f, 0xf3, 0xb0, 0xf1, 0xf8, 0xbd, 0x03, 0xa1, 0xc6, 0x83, 0x11, 0x95, 0x1c, 0xbc, 0x50, 0xf8, - 0x2f, 0x10, 0xfd, 0x69, 0xcc, 0xb2, 0xa1, 0xd7, 0xca, 0x6d, 0x28, 0xf9, 0x1c, 0x16, 0xb3, 0xb4, - 0xd7, 0xa5, 0x71, 0x90, 0x26, 0x61, 0xcc, 0xf2, 0xf6, 0x3c, 0x52, 0x7d, 0x34, 0x8e, 0xaa, 0x97, - 0xf6, 0x9e, 0x2a, 0x5c, 0x41, 0xb2, 0x99, 0x19, 0xa0, 0xce, 0x27, 0xb0, 0x56, 0xc5, 0x98, 0x2c, - 0xc3, 0xcc, 0x15, 0x1d, 0x4a, 0xeb, 0xf0, 0x9f, 0x64, 0x0d, 0xee, 0x5c, 0xfb, 0xd1, 0x80, 0xa2, - 0x31, 0x16, 0x3c, 0xf1, 0xf1, 0x83, 0xda, 0x47, 0x4e, 0xe7, 0x25, 0xac, 0x94, 0xd8, 0x54, 0x10, - 0x78, 0x64, 0x12, 0x68, 0x3c, 0x5e, 0x55, 0x22, 0x7b, 0xa7, 0xc7, 0x6a, 0xae, 0x41, 0xd5, 0xbd, - 0x07, 0x7b, 0x27, 0x94, 0x1d, 0x27, 0xfd, 0xfe, 0x20, 0x0e, 0x7b, 0xe8, 0x63, 0x1e, 0x8d, 0xfc, - 0x21, 0xcd, 0x72, 0xe5, 0x59, 0x2e, 0xec, 0x8f, 0x47, 0x11, 0x0a, 0x70, 0xbf, 0x0f, 0x9b, 0x27, - 0x34, 0xa6, 0x59, 0xd8, 0xd3, 0xeb, 0x94, 0xd3, 0xc9, 0x0e, 0xd4, 0xb5, 0x7a, 0xa5, 0xa0, 0x05, - 0xc0, 0xed, 0x40, 0xbb, 0x3c, 0x51, 0x12, 0xdd, 0x80, 0xb5, 0x13, 0xca, 0x34, 0x5c, 0x0b, 0xf4, - 0xb5, 0x03, 0xeb, 0x38, 0x90, 0x9f, 0xe5, 0x43, 0x31, 0x20, 0x1d, 0xfe, 0x8f, 0x60, 0x45, 0x93, - 0xce, 0x95, 0x47, 0x38, 0x68, 0xbb, 0x0f, 0x0d, 0xdb, 0x95, 0x67, 0x16, 0x7e, 0x91, 0x9b, 0x8e, - 0x51, 0xb8, 0x97, 0x04, 0x77, 0x8e, 0x61, 0xbd, 0x12, 0xf5, 0x4d, 0x4c, 0xe9, 0xb6, 0x61, 0xe3, - 0x84, 0x32, 0xc3, 0x22, 0x7a, 0x69, 0x9f, 0x43, 0xc3, 0x00, 0x93, 0x36, 0xcc, 0xe7, 0xcc, 0xcf, - 0x18, 0x0d, 0x90, 0xf0, 0x82, 0xa7, 0x3e, 0xc9, 0x7d, 0x58, 0x8a, 0xc2, 0x9c, 0xd1, 0xb8, 0xeb, - 0x07, 0x41, 0x46, 0x73, 0xb1, 0x7b, 0xeb, 0xde, 0xa2, 0x80, 0x1e, 0x09, 0xa0, 0xfb, 0x8f, 0x0e, - 0x37, 0xcc, 0x08, 0x2b, 0xa9, 0xac, 0x9f, 0x42, 0xbd, 0x70, 0x70, 0xa1, 0xa4, 0x03, 0x43, 0x49, - 0x55, 0x73, 0x0e, 0x46, 0xbc, 0xbc, 0x20, 0xd0, 0xf9, 0x7d, 0x58, 0xfa, 0xb6, 0x7d, 0xf3, 0x23, - 0xe8, 0x48, 0xdf, 0x50, 0xc1, 0xe5, 0x73, 0xbf, 0x4f, 0x95, 0x5f, 0x75, 0x60, 0x41, 0xc5, 0x22, - 0xc9, 0x43, 0x7f, 0xbb, 0xbb, 0xb0, 0x5d, 0x39, 0x53, 0x3a, 0xd6, 0x21, 0xac, 0x9e, 0x50, 0xa6, - 0x23, 0x96, 0xa2, 0xd8, 0x86, 0x79, 0x19, 0xcc, 0x94, 0xb6, 0xe5, 0xa7, 0xfb, 0x04, 0x3d, 0xd1, - 0x98, 0x20, 0x55, 0xb8, 0x03, 0xf5, 0x22, 0x1e, 0x4a, 0xdf, 0xd6, 0x00, 0xf7, 0x31, 0xba, 0xa9, - 0x9a, 0xf5, 0xfc, 0xe5, 0xa9, 0x47, 0xc5, 0xb4, 0x2d, 0x58, 0x48, 0x58, 0xda, 0xed, 0x25, 0x81, - 0x12, 0x7d, 0x3e, 0x61, 0xe9, 0x71, 0x12, 0x50, 0xe9, 0x1a, 0xc6, 0x1c, 0xed, 0x1a, 0x7f, 0x23, - 0x4c, 0x69, 0x0f, 0x49, 0x39, 0x7e, 0x0f, 0xea, 0x8a, 0xa0, 0x32, 0xe5, 0xfb, 0x86, 0x29, 0xab, - 0xe6, 0x1c, 0x3c, 0x17, 0x1c, 0xa5, 0x25, 0x17, 0xa4, 0x00, 0x79, 0xe7, 0x63, 0x58, 0xb4, 0x86, - 0x6e, 0xf3, 0xec, 0xba, 0x69, 0xb2, 0x27, 0xb0, 0xf1, 0x69, 0x98, 0x9b, 0x87, 0xc7, 0x34, 0xe6, - 0xfa, 0x7a, 0xc6, 0x5a, 0x9a, 0x75, 0x86, 0x11, 0x98, 0x8d, 0x7d, 0x7d, 0x82, 0xe1, 0x6f, 0xd3, - 0x50, 0x35, 0xcb, 0x50, 0x7c, 0xe4, 0x9a, 0x66, 0x67, 0x49, 0x4e, 0xf1, 0x78, 0x5a, 0xf0, 0xd4, - 0x27, 0x79, 0x0b, 0x16, 0x07, 0x79, 0x18, 0x5f, 0x74, 0x73, 0x3f, 0x0e, 0xce, 0x92, 0xd7, 0x78, - 0x18, 0x2d, 0x78, 0x4d, 0x04, 0xbe, 0x10, 0x30, 0x72, 0x0f, 0x9a, 0x97, 0x8c, 0xa5, 0x5d, 0x7e, - 0x4a, 0x26, 0x03, 0x26, 0xcf, 0x9e, 0x06, 0x87, 0xbd, 0x14, 0x20, 0xbe, 0xf1, 0x10, 0x65, 0x90, - 0xd3, 0xcc, 0xbf, 0xa0, 0x31, 0x6b, 0xcf, 0x89, 0x8d, 0xc7, 0xa1, 0x3f, 0x57, 0x40, 0xb2, 0x0b, - 0x80, 0x68, 0x69, 0x96, 0xbc, 0x1e, 0xb6, 0xe7, 0x85, 0x6b, 0x70, 0xc8, 0x29, 0x07, 0x90, 0xb7, - 0xa1, 0x75, 0xe6, 0xe7, 0x54, 0x9d, 0x72, 0x21, 0xcd, 0xdb, 0x0b, 0x88, 0xb3, 0xc4, 0xc1, 0xc7, - 0x1a, 0x4a, 0x1e, 0xf1, 0x23, 0x2e, 0x4d, 0x13, 0xbe, 0xe9, 0xbb, 0x7e, 0x9e, 0x53, 0x96, 0xb7, - 0xeb, 0x88, 0xd9, 0xd2, 0xf0, 0x23, 0x04, 0xf3, 0x15, 0xaa, 0x43, 0x3a, 0xf5, 0xc3, 0x2c, 0x6f, - 0x03, 0xe2, 0x35, 0x25, 0xf0, 0x94, 0xc3, 0x38, 0xe3, 0xe2, 0xe8, 0x17, 0x68, 0x0d, 0xc1, 0x58, - 0x83, 0x05, 0xe2, 0xbb, 0xb0, 0xe2, 0x0f, 0xd8, 0x25, 0x8d, 0x19, 0x8f, 0xf9, 0x9c, 0x79, 0x1a, - 0xb6, 0x9b, 0xa8, 0xb3, 0x65, 0x6b, 0xe0, 0x28, 0x0d, 0xdd, 0x1b, 0x58, 0x3e, 0xa1, 0xec, 0x65, - 0xd8, 0xbb, 0xa2, 0xd9, 0x14, 0x06, 0x27, 0x0f, 0x61, 0x96, 0xf3, 0x96, 0x71, 0x60, 0x4d, 0xb9, - 0xaa, 0x3a, 0xd8, 0xb9, 0x04, 0x1e, 0x62, 0x70, 0x3d, 0xe2, 0xaa, 0xbb, 0x6c, 0x98, 0x0a, 0x9b, - 0xd6, 0xbd, 0x3a, 0x42, 0x5e, 0x0e, 0x53, 0xea, 0xbe, 0x82, 0xa6, 0x39, 0x89, 0x6f, 0xc8, 0x80, - 0x46, 0x61, 0x3f, 0x64, 0x34, 0x53, 0x1b, 0x52, 0x03, 0xb8, 0x2f, 0x71, 0xf5, 0x4a, 0xb7, 0xc5, - 0xdf, 0xdc, 0x97, 0xbf, 0x1a, 0x24, 0x4c, 0xd1, 0x16, 0x1f, 0xee, 0x5f, 0xd6, 0x60, 0x49, 0x2d, - 0x47, 0x3a, 0xa2, 0x92, 0xd9, 0xb9, 0x55, 0xe6, 0x7b, 0xd0, 0x8c, 0xfc, 0x9c, 0x75, 0x07, 0x69, - 0xc0, 0x15, 0x24, 0xf3, 0xaa, 0x06, 0x87, 0xfd, 0x5c, 0x80, 0xb8, 0xad, 0x54, 0x82, 0x83, 0x56, - 0x90, 0xdc, 0x9b, 0x3d, 0x73, 0x31, 0x04, 0x66, 0xf9, 0x1c, 0xf4, 0x54, 0xc7, 0xc3, 0xdf, 0x1c, - 0x76, 0x19, 0x5e, 0x5c, 0xa2, 0x67, 0x3a, 0x1e, 0xfe, 0xe6, 0x1b, 0x34, 0x4a, 0x6e, 0xd0, 0x0f, - 0x1d, 0x8f, 0xff, 0xe4, 0x90, 0xb3, 0x30, 0x40, 0xb7, 0x73, 0x3c, 0xfe, 0x93, 0x43, 0xfc, 0xfc, - 0x0a, 0x9d, 0xcc, 0xf1, 0xf8, 0x4f, 0x9e, 0x1c, 0x5e, 0x27, 0xd1, 0xa0, 0x4f, 0xd1, 0x9f, 0x1c, - 0x4f, 0x7e, 0x91, 0x6d, 0xa8, 0xa7, 0x59, 0xd8, 0xa3, 0x5d, 0x9f, 0x5d, 0xa2, 0x0b, 0x39, 0xde, - 0x02, 0x02, 0x8e, 0xd8, 0xa5, 0xbb, 0x0a, 0x2b, 0xda, 0xd0, 0x3a, 0x32, 0x7d, 0x01, 0xf3, 0x12, - 0x32, 0xd1, 0xe8, 0xdf, 0x83, 0x79, 0x26, 0xd0, 0xda, 0x35, 0x0c, 0x51, 0x1b, 0x4a, 0x87, 0xb6, - 0xa6, 0x3d, 0x85, 0xe6, 0xfe, 0x0e, 0x10, 0x93, 0x9b, 0x34, 0xc4, 0xa3, 0x82, 0x8e, 0x08, 0x75, - 0x2d, 0x9b, 0x4e, 0x5e, 0x10, 0xf8, 0x25, 0x06, 0xfa, 0xe7, 0x59, 0xc0, 0x83, 0x40, 0x72, 0xf5, - 0x9d, 0xba, 0xe6, 0xcf, 0x60, 0x51, 0x33, 0x7e, 0xc6, 0x68, 0x9f, 0x2b, 0xdc, 0xef, 0x27, 0x83, - 0x98, 0x21, 0x4f, 0xc7, 0x93, 0x5f, 0xdc, 0x03, 0x51, 0xbf, 0xc8, 0xd2, 0xf1, 0xc4, 0x07, 0x59, - 0x82, 0x5a, 0x18, 0xc8, 0x1c, 0xbb, 0x16, 0x06, 0xee, 0xff, 0x39, 0xb0, 0x62, 0x2c, 0xe4, 0x8d, - 0x9d, 0xb2, 0xe4, 0x71, 0xb5, 0x0a, 0x8f, 0x7b, 0x04, 0xb3, 0x67, 0x61, 0xc0, 0x53, 0x7b, 0xae, - 0xd7, 0x75, 0x45, 0xce, 0x5a, 0x87, 0x87, 0x28, 0x1c, 0xd5, 0xcf, 0xaf, 0xf2, 0xf6, 0xec, 0x44, - 0x54, 0x8e, 0x52, 0xda, 0x0f, 0x77, 0xca, 0xfb, 0xc1, 0xd6, 0xe5, 0xdc, 0xa8, 0x2e, 0x45, 0x26, - 0xa8, 0x69, 0x6b, 0xcf, 0xeb, 0x01, 0x14, 0xc0, 0x89, 0x66, 0xfd, 0x2d, 0x80, 0x44, 0x63, 0x4a, - 0xff, 0xdb, 0x2a, 0x09, 0xad, 0x5d, 0xd0, 0x40, 0x76, 0x7f, 0x82, 0xc7, 0xb8, 0xc9, 0x5c, 0x2a, - 0xff, 0xb1, 0x45, 0x53, 0xf8, 0x22, 0x29, 0xd1, 0xcc, 0x2d, 0x62, 0x1f, 0x22, 0xb1, 0xa3, 0x5e, - 0x8f, 0x9b, 0xde, 0xa8, 0xdf, 0x26, 0x9e, 0x8f, 0xaf, 0x60, 0x5e, 0xce, 0x90, 0x6e, 0x21, 0x10, - 0x6a, 0x61, 0x40, 0x3e, 0x06, 0x30, 0xce, 0x10, 0xb1, 0xae, 0x6d, 0x25, 0x83, 0x9c, 0xa4, 0xbc, - 0x01, 0xd9, 0x19, 0xe8, 0xee, 0x39, 0xac, 0x56, 0xa0, 0x70, 0x51, 0x74, 0xf5, 0x25, 0x45, 0x51, - 0xdf, 0x64, 0x0f, 0x1a, 0x2c, 0x61, 0x7e, 0xd4, 0x2d, 0x12, 0x00, 0xc7, 0x03, 0x04, 0xbd, 0xe2, - 0x10, 0x0c, 0x50, 0x49, 0x24, 0x3c, 0x97, 0x07, 0xa8, 0x24, 0x0a, 0x5c, 0x1f, 0x93, 0x1a, 0x6b, - 0xd1, 0x52, 0x85, 0x93, 0x4c, 0xf6, 0x2e, 0x2c, 0xf8, 0x62, 0x8a, 0x5a, 0x58, 0x6b, 0x64, 0x61, - 0x9e, 0x46, 0x70, 0x09, 0x9e, 0x40, 0xc7, 0x49, 0x7c, 0x1e, 0x5e, 0x28, 0xef, 0x78, 0x1b, 0x83, - 0x95, 0x82, 0x15, 0xf9, 0x44, 0xe0, 0x33, 0x1f, 0xb9, 0x35, 0x3d, 0xfc, 0xed, 0xfe, 0xa9, 0x03, - 0xcb, 0xa7, 0x49, 0xc6, 0xce, 0x93, 0x28, 0x4c, 0x64, 0xea, 0xcc, 0x53, 0x09, 0x95, 0x5a, 0xcb, - 0x1c, 0x4d, 0x7e, 0xf2, 0x08, 0xd9, 0x4b, 0xc2, 0x58, 0xf8, 0x6a, 0x4d, 0x2a, 0x28, 0x09, 0x63, - 0xee, 0xaa, 0x64, 0x1f, 0x1a, 0x01, 0xcd, 0x7b, 0x59, 0x98, 0xf2, 0x42, 0x49, 0x86, 0x05, 0x13, - 0xc4, 0x09, 0x9f, 0xf9, 0x91, 0x1f, 0xf7, 0xa8, 0x8c, 0xec, 0xea, 0xd3, 0x5d, 0xc7, 0x70, 0xa5, - 0x25, 0x29, 0x8a, 0x82, 0x35, 0x1b, 0x2c, 0x97, 0xf2, 0x9b, 0x50, 0x4f, 0x15, 0x50, 0xba, 0x5f, - 0x5b, 0x69, 0x68, 0x74, 0x39, 0x5e, 0x81, 0xea, 0xee, 0xf0, 0xbc, 0xba, 0xa0, 0xf7, 0x62, 0xd0, - 0xef, 0xfb, 0xd9, 0x50, 0x71, 0x8b, 0x61, 0xf6, 0x38, 0x09, 0x63, 0xae, 0x28, 0xbe, 0x28, 0x95, - 0x78, 0xf1, 0xdf, 0xa6, 0xe8, 0x35, 0x4b, 0x74, 0x53, 0x5b, 0x33, 0xb6, 0xb6, 0xee, 0x02, 0xa4, - 0x34, 0xeb, 0xd1, 0x98, 0xf9, 0x17, 0x6a, 0xc5, 0x06, 0xc4, 0xbd, 0x04, 0xf2, 0xfc, 0xfc, 0x3c, - 0x0a, 0x63, 0xca, 0xd9, 0x4a, 0x61, 0x26, 0x68, 0x7f, 0xbc, 0x0c, 0x36, 0xa7, 0x99, 0x12, 0xa7, - 0x9f, 0xc1, 0xca, 0xf3, 0xb8, 0x82, 0x91, 0x22, 0xe7, 0x4c, 0x22, 0x57, 0x2b, 0x91, 0xfb, 0x31, - 0x34, 0x0d, 0xc1, 0x73, 0xf2, 0x11, 0xd4, 0xa5, 0x8c, 0x3a, 0x09, 0xef, 0xe8, 0x68, 0x50, 0x5a, - 0xa1, 0x57, 0x20, 0xbb, 0x7f, 0xe5, 0x40, 0xa3, 0x90, 0x2c, 0x27, 0x4f, 0xe0, 0x0e, 0x57, 0xb7, - 0xa2, 0x72, 0x57, 0x53, 0x29, 0x70, 0x0e, 0xf0, 0x5f, 0x91, 0xbb, 0x0b, 0xe4, 0xce, 0x0b, 0x80, - 0x02, 0x58, 0x91, 0xb5, 0x1f, 0xda, 0xd5, 0xd7, 0x56, 0x99, 0xaa, 0x12, 0xcd, 0x48, 0xe8, 0xff, - 0x65, 0x96, 0x97, 0x52, 0x15, 0xce, 0x22, 0x7d, 0xf0, 0x7d, 0x68, 0x88, 0xbd, 0xc0, 0x23, 0x80, - 0x12, 0xb8, 0xa9, 0xcf, 0xa1, 0x24, 0x8c, 0x3d, 0xc0, 0xbd, 0x81, 0xe3, 0xe4, 0x03, 0x58, 0x44, - 0x61, 0xbb, 0x89, 0x50, 0x88, 0xdc, 0xd8, 0xf6, 0x84, 0x26, 0xa2, 0x48, 0x95, 0x91, 0x14, 0xd6, - 0xad, 0x29, 0xdd, 0x5c, 0x88, 0x20, 0x0f, 0xa9, 0x1f, 0x1a, 0x75, 0xce, 0x38, 0x29, 0x85, 0xb2, - 0x24, 0x41, 0x39, 0x26, 0x54, 0xb7, 0xda, 0x2b, 0x8f, 0x90, 0x43, 0x68, 0x4a, 0x8e, 0xa8, 0x19, - 0x79, 0xc4, 0xd9, 0x32, 0x36, 0xc4, 0x44, 0x44, 0x20, 0x7d, 0x58, 0x33, 0x27, 0x68, 0x09, 0xef, - 0xe0, 0xc4, 0x8f, 0xa7, 0x97, 0x30, 0x2e, 0x09, 0x48, 0x7a, 0xa5, 0x81, 0xce, 0x1f, 0x40, 0x7b, - 0xdc, 0x82, 0x2a, 0xcc, 0xfe, 0x8e, 0x6d, 0xf6, 0xb5, 0x0a, 0x97, 0xcc, 0xcd, 0x3e, 0xd3, 0x97, - 0xb0, 0x39, 0x46, 0x98, 0x37, 0xa8, 0xe8, 0x0d, 0x4f, 0x35, 0xbd, 0xe9, 0xcf, 0x1d, 0xe8, 0x1c, - 0x05, 0x41, 0x29, 0x38, 0x15, 0x05, 0xf8, 0x77, 0x1d, 0x72, 0x77, 0x61, 0xbb, 0x52, 0x20, 0xd9, - 0x29, 0x78, 0x0d, 0xbb, 0x1e, 0xed, 0x27, 0xd7, 0xf4, 0xbb, 0x16, 0xd9, 0xdd, 0x87, 0xbb, 0xe3, - 0x38, 0x4b, 0xd9, 0xb0, 0x75, 0x66, 0x77, 0x51, 0x75, 0x62, 0xf4, 0x5f, 0x0e, 0x2c, 0xda, 0xfd, - 0xd5, 0x6f, 0xab, 0x8e, 0x7e, 0x0f, 0x48, 0x46, 0x73, 0xd6, 0xcd, 0x92, 0x28, 0xe2, 0xe5, 0x74, - 0x40, 0x23, 0x7f, 0x28, 0x3b, 0xbb, 0xcb, 0x7c, 0xc4, 0x13, 0x03, 0x9f, 0x72, 0x38, 0xd9, 0x84, - 0x79, 0x3f, 0x0d, 0xbb, 0xdc, 0x6b, 0x44, 0x2d, 0x3d, 0xe7, 0xa7, 0xe1, 0x4f, 0xe8, 0x90, 0xb8, - 0xb0, 0x28, 0x07, 0xba, 0x11, 0xbd, 0xa6, 0x11, 0xe6, 0x7c, 0x33, 0x5e, 0x43, 0x0c, 0xff, 0x94, - 0x83, 0x78, 0xed, 0x9b, 0x66, 0x21, 0x77, 0xbf, 0xa2, 0x85, 0x3c, 0x8f, 0xd2, 0xb4, 0x24, 0x5c, - 0xad, 0xce, 0xfd, 0x05, 0x6c, 0x55, 0xe8, 0x42, 0xc6, 0xa8, 0x1f, 0x41, 0xcb, 0x6e, 0x44, 0xab, - 0x38, 0xa5, 0xb3, 0x56, 0x6b, 0xa2, 0xb7, 0x74, 0x6e, 0xd1, 0x91, 0xd9, 0x27, 0xe2, 0x78, 0x3e, - 0xd3, 0xfd, 0x22, 0xf7, 0x2b, 0x58, 0x2b, 0x80, 0xc7, 0x49, 0x7c, 0x4d, 0xb3, 0x9c, 0x7b, 0x1b, - 0x81, 0xd9, 0xf3, 0x2c, 0x51, 0xcd, 0x4e, 0xfc, 0xcd, 0xf3, 0x36, 0x96, 0x48, 0x37, 0xa8, 0xb1, - 0x84, 0xe3, 0x64, 0x3e, 0x53, 0xa7, 0x14, 0xfe, 0xe6, 0x79, 0x72, 0x88, 0x44, 0x68, 0x17, 0xc7, - 0x84, 0xab, 0x36, 0x24, 0x8c, 0x73, 0x71, 0x5f, 0x61, 0xfa, 0x68, 0x8a, 0x22, 0xd7, 0xf8, 0xdb, - 0xd0, 0x10, 0x6b, 0xe4, 0x33, 0xd5, 0xfa, 0x76, 0xac, 0xf5, 0x8d, 0x88, 0xe9, 0xc1, 0xb9, 0x86, - 0xba, 0xff, 0x53, 0x83, 0x26, 0x66, 0xac, 0x9f, 0x52, 0xe6, 0x87, 0xd1, 0xe4, 0x5c, 0x5a, 0xe4, - 0xa0, 0x35, 0x9d, 0x83, 0xbe, 0x05, 0x8b, 0x66, 0x33, 0x63, 0xa8, 0x8a, 0x59, 0xa3, 0x95, 0x31, - 0x24, 0xf7, 0x61, 0x09, 0x4b, 0xeb, 0x02, 0x4b, 0xf8, 0xcc, 0x22, 0x42, 0x35, 0x9a, 0x5d, 0x08, - 0xdc, 0x19, 0x29, 0x04, 0xf8, 0x30, 0x26, 0xd3, 0xdd, 0x3c, 0x0c, 0x74, 0x9d, 0x80, 0x90, 0x17, - 0x61, 0x60, 0x0c, 0xe3, 0xec, 0x79, 0x63, 0x18, 0x67, 0xf3, 0x1a, 0x28, 0xa3, 0xd8, 0xc1, 0xc6, - 0x16, 0x0f, 0x96, 0xc3, 0x33, 0x5e, 0x53, 0x01, 0x5f, 0x86, 0x7d, 0xbc, 0x34, 0x91, 0x8d, 0x63, - 0xd1, 0x67, 0x91, 0x5f, 0x45, 0x99, 0x06, 0x66, 0x99, 0x56, 0x14, 0x75, 0x0d, 0xab, 0xa8, 0xdb, - 0x83, 0x46, 0x92, 0xd2, 0xb8, 0x2b, 0x4b, 0xec, 0xa6, 0xc8, 0x1e, 0x38, 0xe8, 0x15, 0x42, 0x64, - 0xcb, 0x04, 0x75, 0x9e, 0x4f, 0x53, 0x97, 0xda, 0x8a, 0xa9, 0x8d, 0x2a, 0x46, 0x15, 0x82, 0x33, - 0xb7, 0x15, 0x82, 0xee, 0x11, 0x66, 0xc5, 0x8a, 0xb1, 0x74, 0x9f, 0xf7, 0x60, 0x0e, 0xd5, 0xa4, - 0x3c, 0x67, 0xcd, 0x2a, 0x63, 0xa4, 0x53, 0x78, 0x12, 0xc7, 0xfd, 0x31, 0x5e, 0x35, 0xe1, 0xd0, - 0x34, 0xa2, 0x6f, 0xc1, 0x82, 0xb0, 0x8a, 0xf6, 0x9a, 0x79, 0xfc, 0x7e, 0x16, 0xb8, 0xff, 0xe6, - 0x00, 0x79, 0x31, 0x38, 0xeb, 0x87, 0xd3, 0x53, 0x9b, 0xbe, 0x40, 0x27, 0x30, 0x8b, 0x6e, 0x22, - 0xdc, 0x11, 0x7f, 0x8f, 0x78, 0xc8, 0xec, 0xa8, 0x87, 0x14, 0xe6, 0xbc, 0x53, 0x5d, 0xa3, 0xcf, - 0x99, 0xc6, 0xe7, 0x21, 0x3e, 0x0a, 0x69, 0xcc, 0xba, 0xb2, 0xd9, 0xc2, 0x43, 0x3c, 0x02, 0x9e, - 0x05, 0xee, 0x0b, 0x58, 0xb5, 0x56, 0x26, 0x35, 0x7d, 0x0f, 0x9a, 0x42, 0x80, 0x34, 0xf2, 0x7b, - 0xba, 0xd3, 0xdc, 0x40, 0xd8, 0x29, 0x82, 0x26, 0xe9, 0xeb, 0xbf, 0x1d, 0x20, 0xc7, 0xfc, 0xe0, - 0x8a, 0xa6, 0xd6, 0x17, 0x77, 0x1c, 0x51, 0x25, 0x15, 0xf4, 0xea, 0x12, 0xf2, 0xcc, 0x66, 0x36, - 0x63, 0x31, 0xd3, 0x9a, 0x9e, 0x7d, 0xc3, 0x56, 0x48, 0x69, 0xd7, 0xde, 0x87, 0xa5, 0x1b, 0x3f, - 0x8a, 0x28, 0xd3, 0x97, 0x15, 0xb2, 0x67, 0x2a, 0xa0, 0xaa, 0xe2, 0x52, 0xf6, 0x9a, 0x2f, 0xec, - 0xc5, 0x4b, 0x22, 0x6b, 0xbd, 0xf2, 0xec, 0x7b, 0x02, 0x1b, 0x02, 0x7c, 0x14, 0x45, 0x53, 0xef, - 0x21, 0xf7, 0xaf, 0x6b, 0xb0, 0x59, 0x9a, 0xa6, 0x0f, 0x09, 0x7b, 0x07, 0x3c, 0xd0, 0xcb, 0xad, - 0x9e, 0x70, 0x20, 0x3f, 0xe5, 0xac, 0xce, 0x3f, 0x39, 0x30, 0x27, 0x40, 0x13, 0xad, 0xf1, 0xa5, - 0x32, 0xbf, 0x8c, 0x31, 0x22, 0xff, 0xfd, 0xfe, 0x74, 0xcc, 0xc4, 0x7f, 0xe6, 0x05, 0x95, 0xf0, - 0x1b, 0x79, 0x37, 0xf5, 0x23, 0x58, 0x1e, 0x45, 0x78, 0xa3, 0xe6, 0xbd, 0xa8, 0xa1, 0x9f, 0x5e, - 0x53, 0xe3, 0x42, 0xea, 0x6b, 0x07, 0x5a, 0xc7, 0x49, 0x1c, 0x84, 0x3c, 0x3e, 0x9e, 0xfa, 0x99, - 0xdf, 0xcf, 0xc9, 0x0e, 0xcf, 0x6c, 0x24, 0x48, 0x35, 0x59, 0x35, 0x60, 0x4c, 0x3b, 0x6b, 0x17, - 0xa0, 0x77, 0x49, 0x7b, 0x57, 0x5d, 0xd9, 0x5f, 0xe2, 0x4e, 0x5f, 0x47, 0xc8, 0x27, 0x61, 0x90, - 0x93, 0xf7, 0x61, 0xb5, 0x18, 0xee, 0xfa, 0x71, 0xd0, 0x95, 0xcd, 0x25, 0xec, 0x37, 0x6b, 0xbc, - 0xa3, 0x38, 0x38, 0xca, 0xaf, 0xb0, 0x2b, 0xae, 0x7b, 0x2a, 0x5d, 0x6b, 0xc3, 0xb6, 0x34, 0xfc, - 0x08, 0xc1, 0xee, 0xff, 0x3a, 0x18, 0xef, 0xd4, 0xaa, 0xa4, 0xb5, 0x8b, 0x36, 0x0a, 0x76, 0xd7, - 0x2c, 0x93, 0xd5, 0x46, 0x4c, 0x46, 0x60, 0x36, 0x64, 0xb4, 0xaf, 0xc2, 0x08, 0xff, 0x4d, 0x3e, - 0x81, 0x65, 0xbd, 0xe2, 0x6e, 0x8a, 0x6a, 0x91, 0xdb, 0x64, 0xb3, 0x28, 0x13, 0x2c, 0xad, 0x79, - 0xad, 0xde, 0x88, 0x1a, 0xd5, 0xf6, 0xba, 0x73, 0xeb, 0xf6, 0xe2, 0x51, 0xa9, 0x87, 0xda, 0x9e, - 0x93, 0x49, 0x14, 0x7e, 0x09, 0xa9, 0x69, 0x6f, 0xc0, 0x68, 0x20, 0x13, 0x23, 0xfd, 0xed, 0xfe, - 0xa7, 0x03, 0xad, 0xa3, 0x20, 0xc0, 0x75, 0x4f, 0x13, 0x26, 0xd4, 0x2a, 0x6b, 0xb7, 0xac, 0x72, - 0xe6, 0xd7, 0x5c, 0xe5, 0x37, 0x0e, 0x22, 0x63, 0x94, 0xe0, 0xba, 0xb0, 0x5c, 0xac, 0xb3, 0xda, - 0xbc, 0xee, 0x6f, 0x00, 0x11, 0xc9, 0xb4, 0xa5, 0x8e, 0x51, 0xac, 0x75, 0x58, 0xb5, 0xb0, 0x64, - 0xac, 0xf9, 0x0c, 0x1e, 0x9e, 0x50, 0x76, 0x9c, 0x0d, 0x53, 0x96, 0xa8, 0xe4, 0xe5, 0x53, 0x9a, - 0x26, 0x79, 0xa8, 0x22, 0x17, 0x9d, 0x2a, 0xfa, 0xfc, 0xb3, 0x03, 0x8f, 0xa6, 0x20, 0x24, 0x97, - 0xf0, 0x87, 0xe5, 0x6e, 0xc2, 0xef, 0x1a, 0x85, 0xe4, 0x74, 0x54, 0x0e, 0x34, 0x44, 0xde, 0xd7, - 0x6a, 0x92, 0x9d, 0x1f, 0xc2, 0x92, 0x3d, 0xf8, 0x46, 0xa1, 0x22, 0x82, 0x07, 0xb7, 0x08, 0x31, - 0x8d, 0xcf, 0x3d, 0x80, 0xa5, 0x9e, 0x45, 0x42, 0x32, 0x1a, 0x81, 0xba, 0xc7, 0xf0, 0xf6, 0xad, - 0xdc, 0xa4, 0xda, 0xc6, 0xd6, 0x63, 0xee, 0xdf, 0xcd, 0xc2, 0xe6, 0x17, 0x21, 0xbb, 0x0c, 0x32, - 0xff, 0x46, 0x79, 0xdf, 0x34, 0x42, 0x8e, 0x94, 0x6a, 0xb5, 0x72, 0x75, 0xf9, 0x0e, 0xac, 0x24, - 0x31, 0xc5, 0x8c, 0xb2, 0x9b, 0xfa, 0x79, 0x7e, 0x93, 0x64, 0xea, 0x2c, 0x6d, 0x25, 0x31, 0xe5, - 0x59, 0xe5, 0xa9, 0x04, 0x8f, 0x9c, 0xc6, 0xb3, 0xa3, 0xa7, 0xf1, 0x32, 0xcc, 0xa4, 0x61, 0x2c, - 0x3b, 0xe4, 0xfc, 0x27, 0x3f, 0x3b, 0x59, 0xe6, 0x07, 0x06, 0x65, 0x79, 0x76, 0x22, 0x54, 0xd3, - 0x35, 0x7b, 0xb6, 0xf3, 0x23, 0x3d, 0x5b, 0x43, 0x27, 0x0b, 0x76, 0x8d, 0xba, 0x07, 0x0d, 0xf9, - 0xb3, 0xcb, 0xfc, 0x0b, 0x99, 0xf0, 0x82, 0x04, 0xbd, 0xf4, 0x2f, 0x8c, 0x7c, 0x08, 0xac, 0x7c, - 0x68, 0x17, 0xe0, 0x9c, 0xd2, 0xae, 0x95, 0xfa, 0xd6, 0xcf, 0x29, 0x15, 0x41, 0x97, 0x27, 0x46, - 0x67, 0x7e, 0x7c, 0xd5, 0xc5, 0x8a, 0xb3, 0x29, 0xc4, 0xe1, 0x80, 0xcf, 0x79, 0xd5, 0x79, 0x0f, - 0x9a, 0x38, 0xa8, 0x64, 0x5a, 0x14, 0x1a, 0xe5, 0xb0, 0xa3, 0xa2, 0x76, 0x46, 0x94, 0x5e, 0xc8, - 0x86, 0xed, 0xa5, 0x62, 0xfe, 0x71, 0xc8, 0x86, 0x7a, 0x3e, 0xea, 0x2c, 0x1b, 0xb6, 0x5b, 0xc5, - 0xfc, 0x63, 0x01, 0xe2, 0xe2, 0xe5, 0x37, 0xe1, 0x39, 0x15, 0x57, 0xec, 0xcb, 0xf2, 0xd1, 0x09, - 0x87, 0x1c, 0x27, 0x01, 0xd6, 0x01, 0x37, 0x61, 0x66, 0x94, 0x22, 0x2b, 0xa2, 0x60, 0xe1, 0x40, - 0xe5, 0x1a, 0xee, 0x3b, 0xb0, 0xac, 0xdc, 0xc5, 0x7c, 0x50, 0x95, 0xd1, 0x7c, 0x10, 0x31, 0xf5, - 0xa0, 0x4a, 0x7c, 0x3d, 0xfe, 0xf7, 0xbb, 0xb0, 0x74, 0x92, 0x08, 0x07, 0x7d, 0xc9, 0xed, 0x92, - 0x91, 0xe7, 0x30, 0x2f, 0x5f, 0x09, 0x91, 0x8d, 0xd2, 0xb3, 0x21, 0xf4, 0xba, 0xce, 0xe6, 0x98, - 0xe7, 0x44, 0xee, 0xea, 0xaf, 0xfe, 0xf5, 0x3f, 0xfe, 0xa2, 0xb6, 0x48, 0x1a, 0x87, 0xd7, 0x1f, - 0x1c, 0x5e, 0x50, 0x16, 0x72, 0x2a, 0x97, 0xb0, 0x68, 0xbd, 0x86, 0x21, 0x3b, 0xd6, 0x8b, 0x96, - 0x91, 0x47, 0x32, 0x9d, 0xdd, 0x89, 0xef, 0x5d, 0xdc, 0x0e, 0xb2, 0x58, 0x23, 0x44, 0xb2, 0xc8, - 0x11, 0x45, 0x10, 0xfe, 0x0a, 0x5a, 0x4f, 0xb1, 0x0f, 0xa0, 0xa9, 0x92, 0xbd, 0x82, 0x5a, 0xe5, - 0x2b, 0x9f, 0xce, 0xfe, 0x78, 0x04, 0xc9, 0x71, 0x1b, 0x39, 0xae, 0x93, 0x55, 0xce, 0x51, 0xf4, - 0x19, 0xf4, 0xeb, 0x1a, 0x92, 0xc3, 0xb2, 0x7c, 0x37, 0xf0, 0xad, 0xf2, 0xdc, 0x41, 0x9e, 0x1b, - 0x64, 0x8d, 0xf3, 0x0c, 0x04, 0x83, 0x82, 0x69, 0x82, 0x65, 0x8c, 0xf9, 0xce, 0x85, 0xdc, 0x1d, - 0xfb, 0x00, 0x46, 0xb0, 0xdc, 0xbb, 0xe5, 0x81, 0x8c, 0xbd, 0xca, 0x0b, 0xca, 0x71, 0xf5, 0x1b, - 0x19, 0xd2, 0x83, 0xa6, 0xf9, 0x8c, 0x84, 0x6c, 0x57, 0xbc, 0xd1, 0xd0, 0xac, 0x76, 0xaa, 0x07, - 0x25, 0x9f, 0x36, 0xf2, 0x21, 0x64, 0x59, 0xf2, 0xd1, 0xaf, 0x4e, 0xc8, 0x2f, 0xa1, 0x35, 0xf2, - 0x04, 0x83, 0xb8, 0x23, 0x8a, 0xaa, 0x78, 0x4e, 0xd3, 0x79, 0x6b, 0x22, 0x8e, 0xe4, 0x7a, 0x17, - 0xb9, 0xb6, 0xdd, 0x55, 0x43, 0x9f, 0x8a, 0xf3, 0x0f, 0x9c, 0x77, 0x48, 0x8e, 0x1a, 0x35, 0xdf, - 0x71, 0x4c, 0xc5, 0x7b, 0xaf, 0x62, 0xa9, 0xd6, 0x86, 0x18, 0xd5, 0xaa, 0xe2, 0x89, 0x1b, 0x23, - 0xc7, 0x5b, 0x62, 0xe3, 0x8d, 0x0b, 0xee, 0xf1, 0x69, 0xf8, 0xee, 0x56, 0xbf, 0x91, 0x91, 0xcf, - 0x74, 0x4a, 0x7b, 0x44, 0x71, 0x4d, 0x58, 0x4a, 0x72, 0xeb, 0x09, 0x91, 0x64, 0x6a, 0xfb, 0x4f, - 0xc5, 0x23, 0x9e, 0xca, 0x95, 0x9a, 0xaf, 0x72, 0xc6, 0xae, 0x34, 0x61, 0x69, 0x4e, 0x5e, 0xc3, - 0x92, 0xd8, 0x98, 0xdf, 0xbe, 0x65, 0x77, 0x91, 0xef, 0xa6, 0x4b, 0x8a, 0xdd, 0x69, 0x1a, 0xf6, - 0x0b, 0xa8, 0xeb, 0x9b, 0x78, 0xd2, 0x36, 0x16, 0x61, 0xbd, 0xf9, 0xe8, 0x8c, 0xb9, 0xd1, 0x57, - 0xde, 0xea, 0x2e, 0xca, 0x55, 0x89, 0xfb, 0x79, 0x4e, 0xf8, 0x17, 0x00, 0xc5, 0x15, 0x3f, 0xd9, - 0x2a, 0x51, 0xd6, 0x9a, 0xeb, 0x54, 0x0d, 0xa9, 0x87, 0x82, 0x48, 0x7e, 0x99, 0x2c, 0x59, 0xe4, - 0xd5, 0x7e, 0xd3, 0x37, 0xb1, 0xd6, 0x7e, 0x1b, 0x7d, 0x14, 0xd0, 0x19, 0x7f, 0x1b, 0xac, 0x8c, - 0xe2, 0xaa, 0xcd, 0xa6, 0x6b, 0x0c, 0xbe, 0x82, 0x0b, 0x8c, 0xcb, 0xc6, 0x35, 0xf4, 0x4e, 0x15, - 0x97, 0xca, 0xb8, 0x5c, 0xbe, 0x53, 0x76, 0xb7, 0x90, 0xd5, 0x2a, 0x59, 0x19, 0x65, 0x95, 0x93, - 0x2b, 0x7c, 0xf3, 0x6b, 0xdc, 0xa2, 0x12, 0x93, 0x56, 0xf9, 0x4a, 0xb9, 0x73, 0x77, 0xdc, 0xf0, - 0x98, 0x33, 0x40, 0xa6, 0x21, 0xb8, 0xa9, 0x84, 0xc1, 0xc5, 0xdd, 0xa9, 0x65, 0x70, 0xeb, 0x8a, - 0xb5, 0xb3, 0x55, 0x31, 0x22, 0xa9, 0xaf, 0x23, 0xf5, 0x16, 0x51, 0x36, 0xef, 0x09, 0x5a, 0xc2, - 0x26, 0xba, 0xa9, 0x6d, 0xd9, 0x64, 0xf4, 0xe6, 0xd3, 0x8a, 0x81, 0xa5, 0xfb, 0xcf, 0x52, 0x0c, - 0xd4, 0x37, 0x9c, 0xe4, 0x4f, 0xec, 0x8b, 0x54, 0x75, 0xb1, 0xe3, 0x4e, 0xbc, 0x89, 0x29, 0xed, - 0x96, 0xb1, 0xb7, 0x35, 0xee, 0x1e, 0x72, 0xde, 0x22, 0x9b, 0xa3, 0x9c, 0xe5, 0xcd, 0x0f, 0xf9, - 0x95, 0x03, 0xab, 0x15, 0xf7, 0x0a, 0x85, 0x04, 0xe3, 0x6f, 0x41, 0x0a, 0x09, 0x26, 0x5d, 0x4c, - 0xb8, 0x28, 0xc1, 0x8e, 0x8b, 0x12, 0xf8, 0x41, 0xa0, 0x25, 0x90, 0x59, 0x15, 0xf7, 0xcc, 0x3f, - 0x73, 0x60, 0xa3, 0xfa, 0x0e, 0x81, 0xdc, 0xd7, 0x4f, 0x2f, 0x27, 0xdd, 0x6e, 0x74, 0x1e, 0xdc, - 0x86, 0x26, 0xa5, 0xb9, 0x8f, 0xd2, 0xec, 0xb9, 0x1d, 0x2e, 0x4d, 0x86, 0xb8, 0x55, 0x02, 0xdd, - 0x60, 0x29, 0x6e, 0x77, 0xe9, 0x89, 0x71, 0x8a, 0x57, 0x5f, 0x66, 0x74, 0xee, 0x4d, 0xc0, 0xb0, - 0xc3, 0x17, 0x59, 0x97, 0x06, 0xc1, 0xd6, 0xb6, 0x6e, 0xf7, 0xcb, 0x3d, 0x5a, 0x74, 0xc1, 0xad, - 0x3d, 0x5a, 0x6a, 0xec, 0x5b, 0x7b, 0xb4, 0xdc, 0x6b, 0x2f, 0xed, 0x51, 0x64, 0x86, 0x7d, 0x77, - 0xf2, 0x25, 0x6e, 0x1b, 0xd9, 0x07, 0x6a, 0x8f, 0x6e, 0xf5, 0xbc, 0x6a, 0xdb, 0xd8, 0x9d, 0x9e, - 0x52, 0xa8, 0x14, 0xed, 0x25, 0xae, 0x3d, 0x0f, 0x16, 0x14, 0x3a, 0xd9, 0x1c, 0x25, 0xa0, 0x28, - 0x57, 0x36, 0x6e, 0xdd, 0x4d, 0x24, 0xba, 0xe2, 0x36, 0x4d, 0xa2, 0x9c, 0xe6, 0x19, 0x34, 0x8c, - 0x26, 0x25, 0xd1, 0x41, 0xb6, 0xdc, 0x93, 0xed, 0x6c, 0x57, 0x8e, 0xd9, 0xa1, 0xc4, 0x6d, 0x71, - 0x06, 0x39, 0x22, 0x98, 0x3c, 0x8c, 0x16, 0x5e, 0xc1, 0xa3, 0xdc, 0xc7, 0x2c, 0x78, 0x54, 0xf5, - 0xfc, 0x2c, 0x1e, 0x3d, 0x44, 0xd0, 0x3c, 0x32, 0x68, 0x8d, 0xb4, 0xce, 0x8a, 0xa3, 0xb8, 0xba, - 0x51, 0x58, 0x1c, 0xc5, 0x63, 0x7a, 0x6e, 0x76, 0xb2, 0x23, 0xf8, 0xf9, 0x51, 0x54, 0xd8, 0x43, - 0x84, 0x48, 0xd1, 0x58, 0xb2, 0x6c, 0x6d, 0x75, 0xd0, 0x2c, 0x5b, 0xdb, 0x5d, 0xa8, 0x52, 0x88, - 0xa4, 0x82, 0xd6, 0x2b, 0x58, 0x50, 0x1d, 0x8d, 0xc2, 0xd0, 0x23, 0xbd, 0x9c, 0x4e, 0xbb, 0x3c, - 0x20, 0xa9, 0x5a, 0xc6, 0xf6, 0x83, 0x00, 0xa9, 0x4a, 0x43, 0x18, 0xfd, 0x8d, 0xc2, 0x10, 0xe5, - 0xd6, 0x48, 0x61, 0x88, 0xaa, 0x86, 0x88, 0x65, 0x08, 0xb1, 0xdb, 0x35, 0x8f, 0xbf, 0x77, 0xe0, - 0xde, 0xad, 0xed, 0x09, 0xf2, 0xbd, 0x37, 0xe8, 0x64, 0x08, 0x81, 0x3e, 0x78, 0xe3, 0xde, 0x87, - 0xfb, 0x10, 0xc5, 0x74, 0xdd, 0x5d, 0x75, 0x00, 0xe1, 0xb4, 0x40, 0xa0, 0xeb, 0x46, 0x08, 0x17, - 0xfa, 0x6f, 0x1d, 0xf1, 0x57, 0x10, 0x13, 0xe8, 0x92, 0x83, 0x29, 0x05, 0x50, 0x02, 0x1f, 0x4e, - 0x8d, 0x2f, 0xc5, 0x7d, 0x80, 0xe2, 0xee, 0xbb, 0xdb, 0x13, 0xc4, 0xe5, 0xc2, 0xfe, 0x31, 0x6c, - 0xeb, 0x36, 0x86, 0x45, 0xf7, 0xb3, 0x41, 0x1c, 0xe4, 0x45, 0xd5, 0x34, 0xa6, 0xd7, 0x51, 0x38, - 0xce, 0x68, 0x75, 0x6b, 0x9f, 0x29, 0x37, 0x72, 0x54, 0x88, 0x71, 0xce, 0x69, 0x73, 0xee, 0x29, - 0xac, 0xa8, 0x79, 0x9f, 0x85, 0x3e, 0xfb, 0xc6, 0x3c, 0xf7, 0x91, 0x67, 0xc7, 0x5d, 0x37, 0x79, - 0x9e, 0x87, 0x3e, 0x53, 0x1c, 0xcf, 0xe6, 0xf0, 0x2f, 0x9e, 0x3e, 0xfc, 0xff, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xe9, 0x97, 0xa2, 0x2a, 0x24, 0x35, 0x00, 0x00, + // 4062 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x4d, 0x6f, 0x24, 0x49, + 0x56, 0xca, 0xb2, 0xdb, 0x1f, 0xaf, 0xca, 0x2e, 0x3b, 0xfc, 0x55, 0x2e, 0xdb, 0x6d, 0x77, 0xee, + 0x76, 0x4f, 0xf7, 0x7c, 0xd8, 0x3b, 0x3d, 0x2d, 0x76, 0xd8, 0x59, 0x16, 0x3c, 0x9e, 0x1e, 0x6f, + 0xb3, 0xbb, 0xd3, 0x26, 0xbb, 0xb7, 0x47, 0x9a, 0x45, 0x14, 0xe9, 0xcc, 0xb0, 0x9d, 0x72, 0x3a, + 0x33, 0x27, 0x33, 0xca, 0xee, 0x1a, 0x21, 0x21, 0xad, 0x04, 0x47, 0x38, 0xac, 0x90, 0x38, 0x70, + 0xe2, 0x88, 0xc4, 0x05, 0x71, 0xe2, 0x30, 0xe2, 0x8a, 0x38, 0x72, 0xe1, 0x07, 0x20, 0x6e, 0xb0, + 0x27, 0x2e, 0x9c, 0x50, 0xbc, 0xf8, 0xc8, 0x88, 0xca, 0xac, 0x72, 0x35, 0x3b, 0x9a, 0x4b, 0x77, + 0xe5, 0x8b, 0x17, 0xef, 0xbd, 0x78, 0xef, 0xc5, 0x8b, 0xf7, 0x5e, 0x84, 0x61, 0x3e, 0xcf, 0x82, + 0xfd, 0x2c, 0x4f, 0x59, 0x4a, 0x66, 0xce, 0x03, 0x96, 0x67, 0x41, 0x77, 0xfb, 0x3c, 0x4d, 0xcf, + 0x63, 0x7a, 0xe0, 0x67, 0xd1, 0x81, 0x9f, 0x24, 0x29, 0xf3, 0x59, 0x94, 0x26, 0x85, 0xc0, 0x72, + 0x97, 0x60, 0xf1, 0x98, 0xb2, 0x67, 0xc9, 0x59, 0xea, 0xd1, 0x2f, 0xfb, 0xb4, 0x60, 0xee, 0x3f, + 0x4e, 0x43, 0x5b, 0x83, 0x8a, 0x2c, 0x4d, 0x0a, 0x4a, 0xd6, 0x61, 0xa6, 0x9f, 0xb1, 0xe8, 0x8a, + 0x76, 0x9c, 0x3d, 0xe7, 0xe1, 0xbc, 0x27, 0xbf, 0xc8, 0x01, 0xac, 0xf8, 0xd7, 0x7e, 0x14, 0xfb, + 0xa7, 0x31, 0xed, 0xd1, 0xd7, 0xc1, 0x85, 0x9f, 0x9c, 0xd3, 0xa2, 0xd3, 0xd8, 0x73, 0x1e, 0x4e, + 0x79, 0x44, 0x0f, 0x3d, 0x55, 0x23, 0xe4, 0x1d, 0x58, 0xa6, 0x09, 0x07, 0x85, 0x06, 0xfa, 0x14, + 0xa2, 0x2f, 0xc9, 0x81, 0x12, 0xf9, 0x09, 0xac, 0x87, 0xf4, 0xcc, 0xef, 0xc7, 0xac, 0x77, 0x96, + 0xe6, 0xf4, 0x75, 0x2f, 0xcb, 0xd3, 0xeb, 0x28, 0xa4, 0x79, 0x67, 0x1a, 0xa5, 0x58, 0x95, 0xa3, + 0x9f, 0xf2, 0xc1, 0x13, 0x39, 0x46, 0x1e, 0xc3, 0x9a, 0x9e, 0x15, 0xf9, 0xac, 0x17, 0xf4, 0xf3, + 0x9c, 0x26, 0xc1, 0xa0, 0x73, 0x07, 0x27, 0xad, 0xa8, 0x49, 0x91, 0xcf, 0x8e, 0xe4, 0x10, 0xf9, + 0x1c, 0x96, 0x8a, 0xfe, 0x69, 0x31, 0x28, 0x18, 0xbd, 0xea, 0x15, 0xcc, 0x67, 0xfd, 0xa2, 0x33, + 0xb3, 0x37, 0xf5, 0xb0, 0xf9, 0xf8, 0xdd, 0x7d, 0xa1, 0xc6, 0xfd, 0x21, 0x95, 0xec, 0xbf, 0x50, + 0xf8, 0x2f, 0x10, 0xfd, 0x69, 0xc2, 0xf2, 0x81, 0xd7, 0x2e, 0x6c, 0x28, 0xf9, 0x0c, 0x16, 0xf2, + 0x2c, 0xe8, 0xd1, 0x24, 0xcc, 0xd2, 0x28, 0x61, 0x45, 0x67, 0x16, 0xa9, 0x3e, 0x1a, 0x45, 0xd5, + 0xcb, 0x82, 0xa7, 0x0a, 0x57, 0x90, 0x6c, 0xe5, 0x06, 0xa8, 0xfb, 0x31, 0xac, 0xd6, 0x31, 0x26, + 0x4b, 0x30, 0x75, 0x49, 0x07, 0xd2, 0x3a, 0xfc, 0x27, 0x59, 0x85, 0x3b, 0xd7, 0x7e, 0xdc, 0xa7, + 0x68, 0x8c, 0x39, 0x4f, 0x7c, 0xfc, 0xa0, 0xf1, 0xa1, 0xd3, 0x7d, 0x09, 0xcb, 0x15, 0x36, 0x35, + 0x04, 0x1e, 0x99, 0x04, 0x9a, 0x8f, 0x57, 0x94, 0xc8, 0xde, 0xc9, 0x91, 0x9a, 0x6b, 0x50, 0x75, + 0xef, 0xc1, 0xee, 0x31, 0x65, 0x47, 0xe9, 0xd5, 0x55, 0x3f, 0x89, 0x02, 0xf4, 0x31, 0x8f, 0xc6, + 0xfe, 0x80, 0xe6, 0x85, 0xf2, 0xac, 0xcf, 0x60, 0xb5, 0x6e, 0x9c, 0x74, 0x60, 0x56, 0xda, 0x1e, + 0xf9, 0xcf, 0x79, 0xea, 0x93, 0x6c, 0xc3, 0x7c, 0x90, 0x26, 0x09, 0x0d, 0x18, 0x0d, 0xe5, 0x42, + 0x4a, 0x80, 0xfb, 0xe7, 0x0d, 0xd8, 0x1b, 0xcd, 0x53, 0xba, 0xee, 0x57, 0xb0, 0x1e, 0x98, 0x08, + 0xbd, 0x5c, 0x62, 0x74, 0x1c, 0x34, 0xc5, 0x91, 0x61, 0x8a, 0xb1, 0x94, 0xf6, 0x6b, 0x47, 0x85, + 0x91, 0xd6, 0x82, 0xba, 0xb1, 0xee, 0x19, 0x74, 0x47, 0x4f, 0xaa, 0x51, 0xf9, 0x63, 0x5b, 0xe5, + 0xdb, 0x4a, 0xb4, 0x3a, 0x22, 0xa6, 0xee, 0xbf, 0x0f, 0x1b, 0xc7, 0x34, 0xa1, 0x79, 0x14, 0x68, + 0xe7, 0x90, 0x3a, 0xe7, 0x1a, 0xd4, 0x3e, 0x29, 0x59, 0x95, 0x00, 0xb7, 0x0b, 0x9d, 0xea, 0x44, + 0xb1, 0x5c, 0x77, 0x1d, 0x56, 0x8f, 0x29, 0xd3, 0x70, 0x6d, 0xc5, 0xaf, 0x1d, 0x58, 0xc3, 0x81, + 0xe2, 0xb4, 0x18, 0x88, 0x01, 0xa9, 0xea, 0x3f, 0x86, 0x65, 0x4d, 0xba, 0x50, 0xdb, 0x48, 0x68, + 0xf9, 0x03, 0x43, 0xcb, 0xd5, 0x99, 0xe5, 0x66, 0x2a, 0xcc, 0xdd, 0x54, 0xee, 0x49, 0x09, 0xee, + 0x1e, 0xc1, 0x5a, 0x2d, 0xea, 0x9b, 0xf8, 0xbf, 0xdb, 0x81, 0xf5, 0x63, 0xca, 0x0c, 0x37, 0x36, + 0x1c, 0xb4, 0x69, 0x80, 0xb9, 0x5f, 0x16, 0xcc, 0xcf, 0x59, 0xe9, 0x97, 0xf2, 0x93, 0xdc, 0x87, + 0xc5, 0x38, 0x2a, 0x18, 0x4d, 0x7a, 0x7e, 0x18, 0xe6, 0xb4, 0x10, 0x21, 0x6f, 0xde, 0x5b, 0x10, + 0xd0, 0x43, 0x01, 0x74, 0xff, 0xc9, 0xe1, 0x86, 0x19, 0x62, 0x25, 0x95, 0xf5, 0x53, 0x98, 0x2f, + 0xa3, 0x82, 0x50, 0xd2, 0xbe, 0xa1, 0xa4, 0xba, 0x39, 0xfb, 0x43, 0xa1, 0xa1, 0x24, 0xd0, 0xfd, + 0x03, 0x58, 0xfc, 0xa6, 0x37, 0xf4, 0x87, 0xd0, 0x95, 0xbe, 0xa1, 0x22, 0xf2, 0x67, 0xfe, 0x15, + 0x55, 0x7e, 0xd5, 0x85, 0x39, 0x15, 0xc0, 0x25, 0x0f, 0xfd, 0xed, 0xee, 0xc0, 0x56, 0xed, 0x4c, + 0xe9, 0x58, 0x07, 0xb0, 0x72, 0x4c, 0x99, 0x0e, 0xf3, 0x8a, 0xe2, 0xc8, 0x28, 0xe0, 0x3e, 0x41, + 0x4f, 0x34, 0x26, 0x48, 0x15, 0x6e, 0xc3, 0x7c, 0x79, 0x88, 0x48, 0xdf, 0xd6, 0x00, 0xf7, 0x31, + 0xba, 0xa9, 0x9a, 0xf5, 0xfc, 0xe5, 0x89, 0x47, 0xc5, 0xb4, 0x4d, 0x98, 0x4b, 0x59, 0xd6, 0x0b, + 0xd2, 0x50, 0x89, 0x3e, 0x9b, 0xb2, 0xec, 0x28, 0x0d, 0xa9, 0x74, 0x0d, 0x63, 0x8e, 0x76, 0x8d, + 0xbf, 0x15, 0xa6, 0xb4, 0x87, 0xa4, 0x1c, 0xbf, 0x0f, 0xf3, 0x8a, 0xa0, 0x32, 0xe5, 0x7b, 0x86, + 0x29, 0xeb, 0xe6, 0xec, 0x3f, 0x17, 0x1c, 0xa5, 0x25, 0xe7, 0xa4, 0x00, 0x45, 0xf7, 0x23, 0x58, + 0xb0, 0x86, 0x6e, 0xf3, 0xec, 0x79, 0xd3, 0x64, 0x4f, 0x60, 0xfd, 0x93, 0xa8, 0x30, 0x4f, 0xdc, + 0x49, 0xcc, 0xf5, 0xf5, 0x94, 0xb5, 0x34, 0xeb, 0xe0, 0x27, 0x30, 0x9d, 0xf8, 0xfa, 0xd8, 0xc7, + 0xdf, 0xa6, 0xa1, 0x1a, 0x76, 0xb8, 0xee, 0xc0, 0xec, 0x35, 0xcd, 0x4f, 0xd3, 0x82, 0xe2, 0x99, + 0x3e, 0xe7, 0xa9, 0x4f, 0xf2, 0x1d, 0x58, 0xe8, 0x17, 0x51, 0x72, 0xde, 0x2b, 0xfc, 0x24, 0x3c, + 0x4d, 0x5f, 0xe3, 0x09, 0x3e, 0xe7, 0xb5, 0x10, 0xf8, 0x42, 0xc0, 0xc8, 0x3d, 0x68, 0x5d, 0x30, + 0x96, 0xf5, 0x78, 0x6a, 0x91, 0xf6, 0x99, 0x3c, 0xb0, 0x9b, 0x1c, 0xf6, 0x52, 0x80, 0xf8, 0xc6, + 0x43, 0x94, 0x7e, 0x41, 0x73, 0xff, 0x9c, 0x26, 0xac, 0x33, 0x23, 0x36, 0x1e, 0x87, 0xfe, 0x5c, + 0x01, 0xc9, 0x0e, 0x00, 0xa2, 0x65, 0x79, 0xfa, 0x7a, 0xd0, 0x99, 0x15, 0xae, 0xc1, 0x21, 0x27, + 0x1c, 0x40, 0xde, 0x82, 0xf6, 0xa9, 0x5f, 0x50, 0x95, 0x1a, 0x44, 0xb4, 0xe8, 0xcc, 0x21, 0xce, + 0x22, 0x07, 0x1f, 0x69, 0x28, 0x79, 0xc4, 0xf3, 0x82, 0x2c, 0x4b, 0xf9, 0xa6, 0xef, 0xf9, 0x45, + 0x41, 0x59, 0xd1, 0x99, 0x47, 0xcc, 0xb6, 0x86, 0x1f, 0x22, 0x98, 0xaf, 0x50, 0x65, 0x36, 0x99, + 0x1f, 0xe5, 0x45, 0x07, 0x10, 0xaf, 0x25, 0x81, 0x27, 0x1c, 0xc6, 0x19, 0x97, 0xf9, 0x92, 0x40, + 0x6b, 0x0a, 0xc6, 0x1a, 0x2c, 0x10, 0xdf, 0x81, 0x65, 0xbf, 0xcf, 0x2e, 0x68, 0xc2, 0x78, 0xd4, + 0xe7, 0xcc, 0xb3, 0xa8, 0xd3, 0x42, 0x9d, 0x2d, 0x59, 0x03, 0x87, 0x59, 0xe4, 0xde, 0xc0, 0xd2, + 0x31, 0x65, 0x2f, 0xa3, 0xe0, 0x92, 0xe6, 0x13, 0x18, 0x9c, 0x3c, 0x84, 0x69, 0xce, 0x5b, 0xc6, + 0x81, 0x55, 0x7d, 0xca, 0xc8, 0x6c, 0x88, 0x4b, 0xe0, 0x21, 0x06, 0xd7, 0x23, 0xae, 0xba, 0xc7, + 0x06, 0x99, 0xb0, 0xe9, 0xbc, 0x37, 0x8f, 0x90, 0x97, 0x83, 0x8c, 0xba, 0xaf, 0xa0, 0x65, 0x4e, + 0xe2, 0x1b, 0x32, 0xa4, 0x71, 0x74, 0x15, 0x31, 0x9a, 0xab, 0x0d, 0xa9, 0x01, 0xdc, 0x97, 0xb8, + 0x7a, 0xa5, 0xdb, 0xe2, 0x6f, 0xee, 0xcb, 0x5f, 0xf6, 0x53, 0xa6, 0x68, 0x8b, 0x0f, 0xf7, 0xaf, + 0x1a, 0xb0, 0xa8, 0x96, 0x23, 0x1d, 0x51, 0xc9, 0xec, 0xdc, 0x2a, 0xf3, 0x3d, 0x68, 0xc5, 0x7e, + 0xc1, 0x7a, 0xfd, 0x2c, 0xf4, 0x55, 0xda, 0x30, 0xe5, 0x35, 0x39, 0xec, 0xe7, 0x02, 0xc4, 0x6d, + 0xa5, 0xb2, 0x42, 0xb4, 0x82, 0xe4, 0xde, 0x0a, 0xcc, 0xc5, 0x10, 0x98, 0xe6, 0x73, 0xd0, 0x53, + 0x1d, 0x0f, 0x7f, 0x73, 0xd8, 0x45, 0x74, 0x7e, 0x81, 0x9e, 0xe9, 0x78, 0xf8, 0x9b, 0x6f, 0xd0, + 0x38, 0xbd, 0x41, 0x3f, 0x74, 0x3c, 0xfe, 0x93, 0x43, 0x4e, 0xa3, 0x10, 0xdd, 0xce, 0xf1, 0xf8, + 0x4f, 0x0e, 0xf1, 0x8b, 0x4b, 0x74, 0x32, 0xc7, 0xe3, 0x3f, 0x79, 0x46, 0x7d, 0x9d, 0xc6, 0xfd, + 0x2b, 0x8a, 0xfe, 0xe4, 0x78, 0xf2, 0x8b, 0x6c, 0xc1, 0x7c, 0x96, 0x47, 0x01, 0xed, 0xf9, 0xec, + 0x02, 0x5d, 0xc8, 0xf1, 0xe6, 0x10, 0x70, 0xc8, 0x2e, 0xdc, 0x15, 0x58, 0xd6, 0x86, 0xd6, 0x91, + 0xe9, 0x73, 0x98, 0x95, 0x90, 0xb1, 0x46, 0xff, 0x1e, 0xcc, 0x32, 0x81, 0xd6, 0x69, 0x60, 0x88, + 0x5a, 0x57, 0x3a, 0xb4, 0x35, 0xed, 0x29, 0x34, 0xf7, 0x77, 0x81, 0x98, 0xdc, 0xa4, 0x21, 0x1e, + 0x95, 0x74, 0x44, 0xa8, 0x6b, 0xdb, 0x74, 0x8a, 0x92, 0xc0, 0x57, 0x18, 0xe8, 0x9f, 0xe7, 0x21, + 0x0f, 0x02, 0xe9, 0xe5, 0xb7, 0xea, 0x9a, 0x3f, 0x83, 0x05, 0xcd, 0xf8, 0x19, 0xa3, 0x57, 0x5c, + 0xe1, 0xfe, 0x55, 0xda, 0x4f, 0x18, 0xf2, 0x74, 0x3c, 0xf9, 0xc5, 0x3d, 0x10, 0xf5, 0x8b, 0x2c, + 0x1d, 0x4f, 0x7c, 0x90, 0x45, 0x68, 0x44, 0xa1, 0x2c, 0x4c, 0x1a, 0x51, 0xe8, 0xfe, 0xaf, 0x03, + 0xcb, 0xc6, 0x42, 0xde, 0xd8, 0x29, 0x2b, 0x1e, 0xd7, 0xa8, 0xf1, 0xb8, 0x47, 0x30, 0x7d, 0x1a, + 0x85, 0xbc, 0x1e, 0xe2, 0x7a, 0x5d, 0x53, 0xe4, 0xac, 0x75, 0x78, 0x88, 0xc2, 0x51, 0xfd, 0xe2, + 0xb2, 0xe8, 0x4c, 0x8f, 0x45, 0xe5, 0x28, 0x95, 0xfd, 0x70, 0xa7, 0xba, 0x1f, 0x6c, 0x5d, 0xce, + 0x0c, 0xeb, 0x52, 0x64, 0x82, 0x9a, 0xb6, 0xf6, 0xbc, 0x00, 0xa0, 0x04, 0x8e, 0x35, 0xeb, 0x6f, + 0x03, 0xa4, 0x1a, 0x53, 0xfa, 0xdf, 0x66, 0x45, 0x68, 0xed, 0x82, 0x06, 0xb2, 0xfb, 0x13, 0x3c, + 0xc6, 0x4d, 0xe6, 0x52, 0xf9, 0x8f, 0x2d, 0x9a, 0xc2, 0x17, 0x49, 0x85, 0x66, 0x61, 0x11, 0xfb, + 0x00, 0x89, 0x1d, 0x06, 0x01, 0x37, 0xbd, 0x51, 0xf4, 0x8e, 0x3d, 0x1f, 0x5f, 0xc1, 0xac, 0x9c, + 0x21, 0xdd, 0x42, 0x20, 0x34, 0xa2, 0x90, 0x7c, 0x04, 0x60, 0x9c, 0x21, 0x62, 0x5d, 0x5b, 0x4a, + 0x06, 0x39, 0x49, 0x79, 0x03, 0xb2, 0x33, 0xd0, 0xdd, 0x33, 0x58, 0xa9, 0x41, 0xe1, 0xa2, 0xe8, + 0x92, 0x55, 0x8a, 0xa2, 0xbe, 0xc9, 0x2e, 0x34, 0x59, 0xca, 0xfc, 0xb8, 0x57, 0x26, 0x00, 0x8e, + 0x07, 0x08, 0x7a, 0xc5, 0x21, 0x18, 0xa0, 0xd2, 0x58, 0x78, 0x2e, 0x0f, 0x50, 0x69, 0x1c, 0xba, + 0x3e, 0x26, 0x35, 0xd6, 0xa2, 0xa5, 0x0a, 0xc7, 0x99, 0xec, 0x1d, 0x98, 0xf3, 0xc5, 0x14, 0xb5, + 0xb0, 0xf6, 0xd0, 0xc2, 0x3c, 0x8d, 0xe0, 0x12, 0x3c, 0x81, 0x8e, 0xd2, 0xe4, 0x2c, 0x3a, 0x57, + 0xde, 0xf1, 0x16, 0x06, 0x2b, 0x05, 0x2b, 0xf3, 0x89, 0xd0, 0x67, 0x3e, 0x72, 0x6b, 0x79, 0xf8, + 0xdb, 0xfd, 0x33, 0x07, 0x96, 0x4e, 0xd2, 0x9c, 0x9d, 0xa5, 0x71, 0x94, 0xca, 0xd4, 0x99, 0xa7, + 0x12, 0x2a, 0xb5, 0x96, 0x39, 0x9a, 0xfc, 0xe4, 0x11, 0x32, 0x48, 0xa3, 0x44, 0xf8, 0x6a, 0x43, + 0x2a, 0x28, 0x8d, 0x12, 0xee, 0xaa, 0x64, 0x0f, 0x9a, 0x21, 0x2d, 0x82, 0x3c, 0xca, 0x78, 0xa9, + 0x24, 0xc3, 0x82, 0x09, 0xe2, 0x84, 0x4f, 0xfd, 0xd8, 0x4f, 0x02, 0x2a, 0x23, 0xbb, 0xfa, 0x74, + 0xd7, 0x30, 0x5c, 0x69, 0x49, 0x8c, 0xaa, 0xd5, 0x06, 0xcb, 0xa5, 0xfc, 0x16, 0xcc, 0x67, 0x0a, + 0x28, 0xdd, 0xaf, 0xa3, 0x34, 0x34, 0xbc, 0x1c, 0xaf, 0x44, 0x75, 0xb7, 0x79, 0x5e, 0x5d, 0xd2, + 0x7b, 0xd1, 0xbf, 0xba, 0xf2, 0xf3, 0x81, 0xe2, 0x96, 0xc0, 0xf4, 0x51, 0x1a, 0x25, 0x5c, 0x51, + 0x7c, 0x51, 0x2a, 0xf1, 0xe2, 0xbf, 0x4d, 0xd1, 0x1b, 0x96, 0xe8, 0xa6, 0xb6, 0xa6, 0x6c, 0x6d, + 0xdd, 0x05, 0xc8, 0x68, 0x1e, 0xd0, 0x84, 0xf9, 0xe7, 0x6a, 0xc5, 0x06, 0xc4, 0xbd, 0x00, 0xf2, + 0xfc, 0xec, 0x2c, 0x8e, 0x12, 0xca, 0xd9, 0x4a, 0x61, 0xc6, 0x68, 0x7f, 0xb4, 0x0c, 0x36, 0xa7, + 0xa9, 0x0a, 0xa7, 0x9f, 0xc1, 0xf2, 0xf3, 0xa4, 0x86, 0x91, 0x22, 0xe7, 0x8c, 0x23, 0xd7, 0xa8, + 0x90, 0xfb, 0x31, 0xb4, 0x0c, 0xc1, 0x0b, 0xf2, 0x21, 0xcc, 0x4b, 0x19, 0x75, 0x12, 0xde, 0xd5, + 0xd1, 0xa0, 0xb2, 0x42, 0xaf, 0x44, 0x76, 0xff, 0xda, 0x81, 0x66, 0x29, 0x59, 0x41, 0x9e, 0xc0, + 0x1d, 0xae, 0x6e, 0x45, 0xe5, 0xae, 0xa6, 0x52, 0xe2, 0xec, 0xe3, 0xbf, 0x22, 0x77, 0x17, 0xc8, + 0xdd, 0x17, 0x00, 0x25, 0xb0, 0x26, 0x6b, 0x3f, 0xb0, 0xab, 0xaf, 0xcd, 0x2a, 0x55, 0x25, 0x9a, + 0x91, 0xd0, 0xff, 0xeb, 0x34, 0x2f, 0xa5, 0x6a, 0x9c, 0x45, 0xfa, 0xe0, 0x7b, 0xd0, 0x14, 0x7b, + 0x81, 0x47, 0x00, 0x25, 0x70, 0xab, 0x6c, 0x1b, 0x44, 0x89, 0x07, 0xb8, 0x37, 0x70, 0x9c, 0xbc, + 0x0f, 0x0b, 0x28, 0x6c, 0x2f, 0x15, 0x0a, 0x91, 0x1b, 0xdb, 0x9e, 0xd0, 0x42, 0x14, 0xa9, 0x32, + 0x92, 0xc1, 0x9a, 0x35, 0xa5, 0x57, 0x08, 0x11, 0xe4, 0x21, 0xf5, 0x43, 0xa3, 0xce, 0x19, 0x25, + 0xa5, 0x50, 0x96, 0x24, 0x28, 0xc7, 0x84, 0xea, 0x56, 0x82, 0xea, 0x08, 0x39, 0x80, 0x96, 0xe4, + 0x88, 0x9a, 0x91, 0x47, 0x9c, 0x2d, 0x63, 0x53, 0x4c, 0x44, 0x04, 0x72, 0x05, 0xab, 0xe6, 0x04, + 0x2d, 0xe1, 0x1d, 0x9c, 0xf8, 0xd1, 0xe4, 0x12, 0x26, 0x15, 0x01, 0x49, 0x50, 0x19, 0xe8, 0xfe, + 0x21, 0x74, 0x46, 0x2d, 0xa8, 0xc6, 0xec, 0x6f, 0xdb, 0x66, 0x5f, 0xad, 0x71, 0xc9, 0xc2, 0x6c, + 0xce, 0x7d, 0x01, 0x1b, 0x23, 0x84, 0x79, 0x83, 0x8a, 0xde, 0xf0, 0x54, 0xd3, 0x9b, 0xfe, 0xd2, + 0x81, 0xee, 0x61, 0x18, 0x56, 0x82, 0x53, 0x59, 0x80, 0x7f, 0xdb, 0x21, 0x77, 0x07, 0xb6, 0x6a, + 0x05, 0x92, 0x9d, 0x82, 0xd7, 0xb0, 0xe3, 0xd1, 0xab, 0xf4, 0x9a, 0x7e, 0xdb, 0x22, 0xbb, 0x7b, + 0x70, 0x77, 0x14, 0x67, 0x29, 0x1b, 0xb6, 0xce, 0xec, 0xd6, 0xb3, 0x4e, 0x8c, 0xfe, 0xcb, 0x81, + 0x05, 0xbb, 0x29, 0xfd, 0x4d, 0xd5, 0xd1, 0xef, 0x02, 0xc9, 0x69, 0xc1, 0x7a, 0x79, 0x1a, 0xc7, + 0xbc, 0x9c, 0x0e, 0x69, 0xec, 0x0f, 0x64, 0x3b, 0x7c, 0x89, 0x8f, 0x78, 0x62, 0xe0, 0x13, 0x0e, + 0x27, 0x1b, 0x30, 0xeb, 0x67, 0x51, 0x8f, 0x7b, 0x8d, 0xa8, 0xa5, 0x67, 0xfc, 0x2c, 0xfa, 0x09, + 0x1d, 0x10, 0x17, 0x16, 0xe4, 0x40, 0x2f, 0xa6, 0xd7, 0x34, 0xc6, 0x9c, 0x6f, 0xca, 0x6b, 0x8a, + 0xe1, 0x9f, 0x72, 0x10, 0xaf, 0x7d, 0xb3, 0x3c, 0xe2, 0xee, 0x57, 0xf6, 0xdd, 0x67, 0x51, 0x9a, + 0xb6, 0x84, 0xab, 0xd5, 0xb9, 0xbf, 0x80, 0xcd, 0x1a, 0x5d, 0xc8, 0x18, 0xf5, 0x23, 0x68, 0xdb, + 0xdd, 0x7b, 0x15, 0xa7, 0x74, 0xd6, 0x6a, 0x4d, 0xf4, 0x16, 0xcf, 0x2c, 0x3a, 0x32, 0xfb, 0x44, + 0x1c, 0xcf, 0x67, 0xba, 0x5f, 0xe4, 0x7e, 0x09, 0xab, 0x25, 0xf0, 0x28, 0x4d, 0xae, 0x69, 0x5e, + 0x70, 0x6f, 0x23, 0x30, 0x7d, 0x96, 0xa7, 0xaa, 0xd9, 0x89, 0xbf, 0x79, 0xde, 0xc6, 0x52, 0xe9, + 0x06, 0x0d, 0x96, 0x72, 0x9c, 0xdc, 0x67, 0xea, 0x94, 0xc2, 0xdf, 0x3c, 0x4f, 0x8e, 0x90, 0x08, + 0xed, 0xe1, 0x98, 0x70, 0xd5, 0xa6, 0x84, 0x71, 0x2e, 0xee, 0x2b, 0x4c, 0x1f, 0x4d, 0x51, 0xe4, + 0x1a, 0x7f, 0x07, 0x9a, 0x62, 0x8d, 0x7c, 0xa6, 0x5a, 0xdf, 0xb6, 0xb5, 0xbe, 0x21, 0x31, 0x3d, + 0x38, 0xd3, 0x50, 0xf7, 0xd7, 0x0d, 0x68, 0x61, 0xc6, 0xfa, 0x09, 0x65, 0x7e, 0x14, 0x8f, 0xcf, + 0xa5, 0x45, 0x0e, 0xda, 0xd0, 0x39, 0xe8, 0x77, 0x60, 0xc1, 0x6c, 0x66, 0x0c, 0x54, 0x31, 0x6b, + 0xb4, 0x32, 0x06, 0xe4, 0x3e, 0x2c, 0x62, 0x69, 0x5d, 0x62, 0x09, 0x9f, 0x59, 0x40, 0xa8, 0x46, + 0xb3, 0x0b, 0x81, 0x3b, 0x43, 0x85, 0x00, 0x1f, 0xc6, 0x64, 0xba, 0x57, 0x44, 0xa1, 0xae, 0x13, + 0x10, 0xf2, 0x22, 0x0a, 0x8d, 0x61, 0x9c, 0x3d, 0x6b, 0x0c, 0xe3, 0x6c, 0x5e, 0x03, 0xe5, 0x54, + 0x34, 0xe1, 0xf1, 0x2e, 0x69, 0x0e, 0x9d, 0xae, 0xa5, 0x80, 0x2f, 0xa3, 0x2b, 0xbc, 0x69, 0x92, + 0x8d, 0x63, 0xd1, 0x67, 0x91, 0x5f, 0x65, 0x99, 0x06, 0x66, 0x99, 0x56, 0x16, 0x75, 0x4d, 0xab, + 0xa8, 0xdb, 0x85, 0x66, 0x9a, 0xd1, 0xa4, 0x27, 0x4b, 0xec, 0x96, 0xc8, 0x1e, 0x38, 0xe8, 0x15, + 0x42, 0x64, 0xcb, 0x04, 0x75, 0x5e, 0x4c, 0x52, 0x97, 0xda, 0x8a, 0x69, 0x0c, 0x2b, 0x46, 0x15, + 0x82, 0x53, 0xb7, 0x15, 0x82, 0xee, 0x21, 0x66, 0xc5, 0x8a, 0xb1, 0x74, 0x9f, 0x77, 0x61, 0x06, + 0xd5, 0xa4, 0x3c, 0x67, 0xd5, 0x2a, 0x63, 0xa4, 0x53, 0x78, 0x12, 0xc7, 0xfd, 0x31, 0xde, 0xcf, + 0xe1, 0xd0, 0x24, 0xa2, 0x6f, 0xc2, 0x9c, 0xb0, 0x8a, 0xf6, 0x9a, 0x59, 0xfc, 0x7e, 0x16, 0xba, + 0xff, 0xee, 0x00, 0x79, 0xd1, 0x3f, 0xbd, 0x8a, 0x26, 0xa7, 0x36, 0x79, 0x81, 0x4e, 0x60, 0x1a, + 0xdd, 0x44, 0xb8, 0x23, 0xfe, 0x1e, 0xf2, 0x90, 0xe9, 0x61, 0x0f, 0x29, 0xcd, 0x79, 0xa7, 0xbe, + 0x46, 0x9f, 0x31, 0x8d, 0xcf, 0x43, 0x7c, 0x1c, 0xd1, 0x84, 0xf5, 0x64, 0xb3, 0x85, 0x87, 0x78, + 0x04, 0x3c, 0x0b, 0xdd, 0x17, 0xb0, 0x62, 0xad, 0x4c, 0x6a, 0xfa, 0x1e, 0xb4, 0x84, 0x00, 0x59, + 0xec, 0x07, 0xba, 0xd3, 0xdc, 0x44, 0xd8, 0x09, 0x82, 0xc6, 0xe9, 0xeb, 0xbf, 0x1d, 0x20, 0x47, + 0xfc, 0xe0, 0x8a, 0x27, 0xd6, 0x17, 0x77, 0x1c, 0x51, 0x25, 0x95, 0xf4, 0xe6, 0x25, 0xe4, 0x99, + 0xcd, 0x6c, 0xca, 0x62, 0xa6, 0x35, 0x3d, 0xfd, 0x86, 0xad, 0x90, 0xca, 0xae, 0xbd, 0x0f, 0x8b, + 0x37, 0x7e, 0x1c, 0x53, 0xa6, 0x2f, 0x2b, 0x64, 0xcf, 0x54, 0x40, 0x55, 0xc5, 0xa5, 0xec, 0x35, + 0x5b, 0xda, 0x8b, 0x97, 0x44, 0xd6, 0x7a, 0xe5, 0xd9, 0xf7, 0x04, 0xd6, 0x05, 0xf8, 0x30, 0x8e, + 0x27, 0xde, 0x43, 0xee, 0xdf, 0x34, 0x60, 0xa3, 0x32, 0x4d, 0x1f, 0x12, 0xf6, 0x0e, 0x78, 0xa0, + 0x97, 0x5b, 0x3f, 0x61, 0x5f, 0x7e, 0xca, 0x59, 0xdd, 0x7f, 0x76, 0x60, 0x46, 0x80, 0xc6, 0x5a, + 0xe3, 0x0b, 0x65, 0x7e, 0x19, 0x63, 0x44, 0xfe, 0xfb, 0xfd, 0xc9, 0x98, 0x89, 0xff, 0xcc, 0x0b, + 0x2a, 0xe1, 0x37, 0xf2, 0x6e, 0xea, 0x47, 0xb0, 0x34, 0x8c, 0xf0, 0x46, 0xcd, 0x7b, 0x51, 0x43, + 0x3f, 0xbd, 0xa6, 0xc6, 0x85, 0xd4, 0xd7, 0x0e, 0xb4, 0x8f, 0xd2, 0x24, 0x8c, 0x78, 0x7c, 0x3c, + 0xf1, 0x73, 0xff, 0xaa, 0x90, 0x77, 0xa2, 0x02, 0xa4, 0x9a, 0xac, 0x1a, 0x30, 0xa2, 0x9d, 0xb5, + 0x03, 0x10, 0x5c, 0xd0, 0xe0, 0xb2, 0x27, 0xfb, 0x4b, 0xe2, 0x22, 0x95, 0x43, 0x3e, 0x8e, 0xc2, + 0x82, 0xbc, 0x07, 0x2b, 0xe5, 0x70, 0xcf, 0x4f, 0xc2, 0x9e, 0x6c, 0x2e, 0x61, 0xbf, 0x59, 0xe3, + 0x1d, 0x26, 0xe1, 0x61, 0x71, 0x89, 0x5d, 0x71, 0xdd, 0x53, 0xe9, 0x59, 0x1b, 0xb6, 0xad, 0xe1, + 0x87, 0x08, 0x76, 0xff, 0xc7, 0xc1, 0x78, 0xa7, 0x56, 0x25, 0xad, 0x5d, 0xb6, 0x51, 0xb0, 0xbb, + 0x66, 0x99, 0xac, 0x31, 0x64, 0x32, 0x02, 0xd3, 0x11, 0xa3, 0x57, 0x2a, 0x8c, 0xf0, 0xdf, 0xe4, + 0x63, 0x58, 0xd2, 0x2b, 0xee, 0x65, 0xa8, 0x16, 0xb9, 0x4d, 0x36, 0xca, 0x32, 0xc1, 0xd2, 0x9a, + 0xd7, 0x0e, 0x86, 0xd4, 0xa8, 0xb6, 0xd7, 0x9d, 0x5b, 0xb7, 0x17, 0x8f, 0x4a, 0x01, 0x6a, 0x7b, + 0x46, 0x26, 0x51, 0xf8, 0x25, 0xa4, 0xa6, 0x41, 0x9f, 0xd1, 0x50, 0x26, 0x46, 0xfa, 0xdb, 0xfd, + 0x4f, 0x07, 0xda, 0x87, 0x61, 0x88, 0xeb, 0x9e, 0x24, 0x4c, 0xa8, 0x55, 0x36, 0x6e, 0x59, 0xe5, + 0xd4, 0xff, 0x73, 0x95, 0xbf, 0x71, 0x10, 0x19, 0xa1, 0x04, 0xd7, 0x85, 0xa5, 0x72, 0x9d, 0xf5, + 0xe6, 0x75, 0xbf, 0x0b, 0x44, 0x24, 0xd3, 0x96, 0x3a, 0x86, 0xb1, 0xd6, 0x60, 0xc5, 0xc2, 0x92, + 0xb1, 0xe6, 0x53, 0x78, 0x78, 0x4c, 0xd9, 0x51, 0x3e, 0xc8, 0x58, 0xaa, 0x92, 0x97, 0x4f, 0x68, + 0x96, 0x16, 0x91, 0x8a, 0x5c, 0x74, 0xa2, 0xe8, 0xf3, 0x2f, 0x0e, 0x3c, 0x9a, 0x80, 0x90, 0x5c, + 0xc2, 0x1f, 0x55, 0xbb, 0x09, 0xbf, 0x67, 0x3e, 0x14, 0x98, 0x88, 0xca, 0xbe, 0x86, 0xc8, 0xfb, + 0x5a, 0x4d, 0xb2, 0xfb, 0x43, 0x58, 0xb4, 0x07, 0xdf, 0x28, 0x54, 0xc4, 0xf0, 0xe0, 0x16, 0x21, + 0x26, 0xf1, 0xb9, 0x07, 0xb0, 0x18, 0x58, 0x24, 0x24, 0xa3, 0x21, 0xa8, 0x7b, 0x04, 0x6f, 0xdd, + 0xca, 0x4d, 0xaa, 0x6d, 0x64, 0x3d, 0xe6, 0xfe, 0xfd, 0x34, 0x6c, 0x7c, 0x1e, 0xb1, 0x8b, 0x30, + 0xf7, 0x6f, 0x94, 0xf7, 0x4d, 0x22, 0xe4, 0x50, 0xa9, 0xd6, 0xa8, 0x56, 0x97, 0x6f, 0xc3, 0x72, + 0x9a, 0x50, 0xcc, 0x28, 0x7b, 0x99, 0x5f, 0x14, 0x37, 0x69, 0xae, 0xce, 0xd2, 0x76, 0x9a, 0x50, + 0x9e, 0x55, 0x9e, 0x48, 0xf0, 0xd0, 0x69, 0x3c, 0x3d, 0x7c, 0x1a, 0x2f, 0xc1, 0x54, 0x16, 0x25, + 0xb2, 0x43, 0xce, 0x7f, 0xf2, 0xb3, 0x93, 0xe5, 0x7e, 0x68, 0x50, 0x96, 0x67, 0x27, 0x42, 0x35, + 0x5d, 0xb3, 0x67, 0x3b, 0x3b, 0xd4, 0xb3, 0x35, 0x74, 0x32, 0x67, 0xd7, 0xa8, 0xbb, 0xd0, 0x94, + 0x3f, 0x7b, 0xcc, 0x3f, 0x97, 0x09, 0x2f, 0x48, 0xd0, 0x4b, 0xff, 0xdc, 0xc8, 0x87, 0xc0, 0xca, + 0x87, 0x76, 0x00, 0xce, 0x28, 0xed, 0x59, 0xa9, 0xef, 0xfc, 0x19, 0xa5, 0x22, 0xe8, 0xf2, 0xc4, + 0xe8, 0xd4, 0x4f, 0x2e, 0x7b, 0x58, 0x71, 0xb6, 0x84, 0x38, 0x1c, 0xf0, 0x19, 0xaf, 0x3a, 0xef, + 0x41, 0x0b, 0x07, 0x95, 0x4c, 0x0b, 0x42, 0xa3, 0x1c, 0x76, 0x58, 0xd6, 0xce, 0x88, 0x12, 0x44, + 0x6c, 0xd0, 0x59, 0x2c, 0xe7, 0x1f, 0x45, 0x6c, 0xa0, 0xe7, 0xa3, 0xce, 0xf2, 0x41, 0xa7, 0x5d, + 0xce, 0x3f, 0x12, 0x20, 0x2e, 0x5e, 0x71, 0x13, 0x9d, 0x51, 0x71, 0xc5, 0xbe, 0x24, 0x1f, 0x9d, + 0x70, 0xc8, 0x51, 0x1a, 0x62, 0x1d, 0x70, 0x13, 0xe5, 0x46, 0x29, 0xb2, 0x2c, 0x0a, 0x16, 0x0e, + 0x54, 0xae, 0xe1, 0xbe, 0x0d, 0x4b, 0xca, 0x5d, 0xcc, 0x57, 0x68, 0x39, 0x2d, 0xfa, 0x31, 0x53, + 0xaf, 0xd0, 0xc4, 0xd7, 0xe3, 0x5f, 0xef, 0xc2, 0xe2, 0x71, 0x2a, 0x1c, 0xf4, 0x25, 0xb7, 0x4b, + 0x4e, 0x9e, 0xc3, 0xac, 0x7c, 0x5a, 0x45, 0xd6, 0x2b, 0x6f, 0xad, 0xd0, 0xeb, 0xba, 0x1b, 0x23, + 0xde, 0x60, 0xb9, 0x2b, 0xbf, 0xfc, 0xb7, 0xff, 0xf8, 0x55, 0x63, 0x81, 0x34, 0x0f, 0xae, 0xdf, + 0x3f, 0x38, 0xa7, 0x2c, 0xe2, 0x54, 0x2e, 0x60, 0xc1, 0x7a, 0x0d, 0x43, 0xb6, 0xad, 0x17, 0x2d, + 0x43, 0x8f, 0x64, 0xba, 0x3b, 0x63, 0xdf, 0xbb, 0xb8, 0x5d, 0x64, 0xb1, 0x4a, 0x88, 0x64, 0x51, + 0x20, 0x8a, 0x20, 0xfc, 0x25, 0xb4, 0x9f, 0x62, 0x1f, 0x40, 0x53, 0x25, 0xbb, 0x25, 0xb5, 0xda, + 0x57, 0x3e, 0xdd, 0xbd, 0xd1, 0x08, 0x92, 0xe3, 0x16, 0x72, 0x5c, 0x23, 0x2b, 0x9c, 0xa3, 0xe8, + 0x33, 0xe8, 0xd7, 0x35, 0xa4, 0x80, 0x25, 0xf9, 0x6e, 0xe0, 0x1b, 0xe5, 0xb9, 0x8d, 0x3c, 0xd7, + 0xc9, 0x2a, 0xe7, 0x19, 0x0a, 0x06, 0x25, 0xd3, 0x14, 0xcb, 0x18, 0xf3, 0x9d, 0x0b, 0xb9, 0x3b, + 0xf2, 0x01, 0x8c, 0x60, 0xb9, 0x7b, 0xcb, 0x03, 0x19, 0x7b, 0x95, 0xe7, 0x94, 0xe3, 0xea, 0x37, + 0x32, 0xe4, 0x57, 0x0e, 0xb6, 0x6c, 0x6a, 0x5f, 0x64, 0x91, 0xb7, 0x6e, 0x7f, 0x06, 0x26, 0x64, + 0x78, 0x38, 0xe9, 0x7b, 0x31, 0xf7, 0xbb, 0x28, 0xcc, 0x5d, 0xb2, 0x2d, 0x85, 0xb1, 0xde, 0x88, + 0xa9, 0x57, 0x68, 0x24, 0x80, 0x96, 0xf9, 0xb8, 0x85, 0x6c, 0xd5, 0xbc, 0x1c, 0xd1, 0xcc, 0xb7, + 0xeb, 0x07, 0x25, 0xc3, 0x0e, 0x32, 0x24, 0x64, 0x49, 0x32, 0xd4, 0x6f, 0x61, 0xc8, 0x57, 0xd0, + 0x1e, 0x7a, 0x18, 0x42, 0xdc, 0x21, 0xf3, 0xd5, 0x3c, 0xf2, 0xe9, 0x7e, 0x67, 0x2c, 0x8e, 0xe4, + 0x7a, 0x17, 0xb9, 0x76, 0xdc, 0x15, 0xc3, 0xca, 0x8a, 0xf3, 0x0f, 0x9c, 0xb7, 0x49, 0x81, 0x76, + 0x36, 0x5f, 0x97, 0x4c, 0xc4, 0x7b, 0xb7, 0x66, 0xa9, 0xd6, 0x36, 0x1d, 0xb6, 0xb5, 0xe2, 0x89, + 0xdb, 0xb5, 0xc0, 0xbb, 0x6b, 0xe3, 0xe5, 0x0d, 0x46, 0x9e, 0x49, 0xf8, 0xee, 0xd4, 0xbf, 0xdc, + 0x91, 0x8f, 0x87, 0x2a, 0x3b, 0x57, 0x71, 0x4d, 0x59, 0x46, 0x0a, 0xeb, 0x61, 0x93, 0x64, 0x6a, + 0x7b, 0x75, 0xcd, 0xd3, 0xa2, 0xda, 0x95, 0x9a, 0x6f, 0x85, 0x46, 0xae, 0x34, 0x65, 0x59, 0x41, + 0x5e, 0xc3, 0xa2, 0x08, 0x17, 0xdf, 0xbc, 0x65, 0x77, 0x90, 0xef, 0x86, 0x4b, 0xca, 0x98, 0x61, + 0x1a, 0xf6, 0x73, 0x98, 0xd7, 0xef, 0x03, 0x48, 0xc7, 0x58, 0x84, 0xf5, 0x12, 0xa5, 0x3b, 0xe2, + 0x9d, 0x81, 0xf2, 0x56, 0x77, 0x41, 0xae, 0x4a, 0xbc, 0x1a, 0xe0, 0x84, 0x7f, 0x01, 0x50, 0x3e, + 0x3c, 0x20, 0x9b, 0x15, 0xca, 0x5a, 0x73, 0xdd, 0xba, 0x21, 0xf5, 0x7c, 0x11, 0xc9, 0x2f, 0x91, + 0x45, 0x8b, 0xbc, 0xda, 0x6f, 0xfa, 0x7e, 0xd8, 0xda, 0x6f, 0xc3, 0x4f, 0x15, 0xba, 0xa3, 0xef, + 0xa8, 0x95, 0x51, 0x5c, 0xb5, 0xd9, 0x74, 0xe5, 0xc3, 0x57, 0x70, 0x8e, 0xa7, 0x85, 0x71, 0x39, + 0xbe, 0x5d, 0xc7, 0xa5, 0xf6, 0xb4, 0xa8, 0xde, 0x74, 0xbb, 0x9b, 0xc8, 0x6a, 0x85, 0x2c, 0x0f, + 0xb3, 0x2a, 0xc8, 0x25, 0x3e, 0xdf, 0x36, 0xee, 0x76, 0x89, 0x49, 0xab, 0x7a, 0xd1, 0xdd, 0xbd, + 0x3b, 0x6a, 0x78, 0xc4, 0xc9, 0x24, 0x93, 0x23, 0xdc, 0x54, 0xc2, 0xe0, 0xe2, 0x46, 0xd7, 0x32, + 0xb8, 0x75, 0xf1, 0xdb, 0xdd, 0xac, 0x19, 0x91, 0xd4, 0xd7, 0x90, 0x7a, 0x9b, 0x2c, 0xe8, 0x90, + 0x88, 0xb4, 0x84, 0x4d, 0x74, 0xab, 0xdd, 0xb2, 0xc9, 0xf0, 0x7d, 0xac, 0x15, 0x03, 0x2b, 0xb7, + 0xb2, 0x95, 0x18, 0xa8, 0xef, 0x5d, 0xc9, 0x9f, 0xda, 0xd7, 0xbb, 0xea, 0xba, 0xc9, 0x1d, 0x7b, + 0x3f, 0x54, 0xd9, 0x2d, 0x23, 0xef, 0x90, 0xdc, 0x5d, 0xe4, 0xbc, 0x49, 0x36, 0x86, 0x39, 0xcb, + 0xfb, 0x28, 0xf2, 0x4b, 0x07, 0x56, 0x6a, 0x6e, 0x3b, 0x4a, 0x09, 0x46, 0xdf, 0xcd, 0x94, 0x12, + 0x8c, 0xbb, 0x2e, 0x71, 0x51, 0x82, 0x6d, 0x17, 0x25, 0xf0, 0xc3, 0x50, 0x4b, 0x20, 0x73, 0x3d, + 0xee, 0x99, 0x7f, 0xe1, 0xc0, 0x7a, 0xfd, 0xcd, 0x06, 0xb9, 0xaf, 0x1f, 0x84, 0x8e, 0xbb, 0x73, + 0xe9, 0x3e, 0xb8, 0x0d, 0x4d, 0x4a, 0x73, 0x1f, 0xa5, 0xd9, 0x75, 0xbb, 0x5c, 0x9a, 0x1c, 0x71, + 0xeb, 0x04, 0xba, 0xc1, 0x06, 0x81, 0x7d, 0x77, 0x40, 0x8c, 0xdc, 0xa2, 0xfe, 0x8a, 0xa5, 0x7b, + 0x6f, 0x0c, 0x86, 0x1d, 0xbe, 0xc8, 0x9a, 0x34, 0x08, 0x36, 0xdc, 0xf5, 0x25, 0x84, 0xdc, 0xa3, + 0x65, 0x6f, 0xde, 0xda, 0xa3, 0x95, 0xeb, 0x06, 0x6b, 0x8f, 0x56, 0x6f, 0x00, 0x2a, 0x7b, 0x14, + 0x99, 0xe1, 0x6d, 0x00, 0xf9, 0x02, 0xb7, 0x8d, 0xec, 0x4e, 0x75, 0x86, 0xb7, 0x7a, 0x51, 0xb7, + 0x6d, 0xec, 0xfe, 0x53, 0x25, 0x54, 0x8a, 0xa6, 0x17, 0xd7, 0x9e, 0x07, 0x73, 0x0a, 0x9d, 0x6c, + 0x0c, 0x13, 0x50, 0x94, 0x6b, 0xdb, 0xc9, 0xee, 0x06, 0x12, 0x5d, 0x76, 0x5b, 0x26, 0x51, 0x4e, + 0xf3, 0x14, 0x9a, 0x46, 0xeb, 0x94, 0xe8, 0x20, 0x5b, 0xed, 0x14, 0x77, 0xb7, 0x6a, 0xc7, 0xec, + 0x50, 0xe2, 0xb6, 0x39, 0x83, 0x02, 0x11, 0x4c, 0x1e, 0x46, 0x63, 0xb1, 0xe4, 0x51, 0xed, 0xae, + 0x96, 0x3c, 0xea, 0x3a, 0x91, 0x16, 0x8f, 0x00, 0x11, 0x34, 0x8f, 0x1c, 0xda, 0x43, 0x0d, 0xbd, + 0xf2, 0x28, 0xae, 0x6f, 0x5f, 0x96, 0x47, 0xf1, 0x88, 0x4e, 0xa0, 0x9d, 0xec, 0x08, 0x7e, 0x7e, + 0x1c, 0x97, 0xf6, 0x10, 0x21, 0x52, 0xb4, 0xbb, 0x2c, 0x5b, 0x5b, 0x7d, 0x3d, 0xcb, 0xd6, 0x76, + 0x6f, 0xac, 0x12, 0x22, 0xa9, 0xa0, 0xf5, 0x0a, 0xe6, 0x54, 0x9f, 0xa5, 0x34, 0xf4, 0x50, 0x87, + 0xa9, 0xdb, 0xa9, 0x0e, 0x48, 0xaa, 0x96, 0xb1, 0xfd, 0x30, 0x44, 0xaa, 0xd2, 0x10, 0x46, 0xd7, + 0xa5, 0x34, 0x44, 0xb5, 0x61, 0x53, 0x1a, 0xa2, 0xae, 0x4d, 0x63, 0x19, 0x42, 0xec, 0x76, 0xcd, + 0xe3, 0x1f, 0x1c, 0xb8, 0x77, 0x6b, 0xd3, 0x84, 0x7c, 0xef, 0x0d, 0xfa, 0x2b, 0x42, 0xa0, 0xf7, + 0xdf, 0xb8, 0x23, 0xe3, 0x3e, 0x44, 0x31, 0x5d, 0x77, 0x47, 0x1d, 0x40, 0x38, 0x2d, 0x14, 0xe8, + 0xba, 0x3d, 0xc3, 0x85, 0xfe, 0x3b, 0x47, 0xfc, 0x41, 0xcb, 0x18, 0xba, 0x64, 0x7f, 0x42, 0x01, + 0x94, 0xc0, 0x07, 0x13, 0xe3, 0x4b, 0x71, 0x1f, 0xa0, 0xb8, 0x7b, 0xee, 0xd6, 0x18, 0x71, 0xb9, + 0xb0, 0x7f, 0x02, 0x5b, 0xba, 0xb9, 0x62, 0xd1, 0xfd, 0xb4, 0x9f, 0x84, 0x45, 0x59, 0xcb, 0x8d, + 0xe8, 0xc0, 0x94, 0x8e, 0x33, 0x5c, 0x73, 0xdb, 0x67, 0xca, 0x8d, 0x1c, 0x15, 0x62, 0x9c, 0x71, + 0xda, 0x9c, 0x7b, 0x06, 0xcb, 0x6a, 0xde, 0xa7, 0x91, 0xcf, 0x7e, 0x63, 0x9e, 0x7b, 0xc8, 0xb3, + 0xeb, 0xae, 0x99, 0x3c, 0xcf, 0x22, 0x9f, 0x29, 0x8e, 0xa7, 0x33, 0xf8, 0xc7, 0x6b, 0x1f, 0xfc, + 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4b, 0x08, 0xf6, 0x2c, 0xef, 0x36, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -4500,6 +4562,7 @@ type GoCryptoTraderClient interface { EnableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericSubsystemResponse, error) DisableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericSubsystemResponse, error) GetRPCEndpoints(ctx context.Context, in *GetRPCEndpointsRequest, opts ...grpc.CallOption) (*GetRPCEndpointsResponse, error) + GetCommunicationRelayers(ctx context.Context, in *GetCommunicationRelayersRequest, opts ...grpc.CallOption) (*GetCommunicationRelayersResponse, error) GetExchanges(ctx context.Context, in *GetExchangesRequest, opts ...grpc.CallOption) (*GetExchangesResponse, error) DisableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) GetExchangeInfo(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeInfoResponse, error) @@ -4585,6 +4648,15 @@ func (c *goCryptoTraderClient) GetRPCEndpoints(ctx context.Context, in *GetRPCEn return out, nil } +func (c *goCryptoTraderClient) GetCommunicationRelayers(ctx context.Context, in *GetCommunicationRelayersRequest, opts ...grpc.CallOption) (*GetCommunicationRelayersResponse, error) { + out := new(GetCommunicationRelayersResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetCommunicationRelayers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *goCryptoTraderClient) GetExchanges(ctx context.Context, in *GetExchangesRequest, opts ...grpc.CallOption) (*GetExchangesResponse, error) { out := new(GetExchangesResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetExchanges", in, out, opts...) @@ -4862,6 +4934,7 @@ type GoCryptoTraderServer interface { EnableSubsystem(context.Context, *GenericSubsystemRequest) (*GenericSubsystemResponse, error) DisableSubsystem(context.Context, *GenericSubsystemRequest) (*GenericSubsystemResponse, error) GetRPCEndpoints(context.Context, *GetRPCEndpointsRequest) (*GetRPCEndpointsResponse, error) + GetCommunicationRelayers(context.Context, *GetCommunicationRelayersRequest) (*GetCommunicationRelayersResponse, error) GetExchanges(context.Context, *GetExchangesRequest) (*GetExchangesResponse, error) DisableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) GetExchangeInfo(context.Context, *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) @@ -4988,6 +5061,24 @@ func _GoCryptoTrader_GetRPCEndpoints_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _GoCryptoTrader_GetCommunicationRelayers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCommunicationRelayersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetCommunicationRelayers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetCommunicationRelayers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetCommunicationRelayers(ctx, req.(*GetCommunicationRelayersRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _GoCryptoTrader_GetExchanges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetExchangesRequest) if err := dec(in); err != nil { @@ -5552,6 +5643,10 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ MethodName: "GetRPCEndpoints", Handler: _GoCryptoTrader_GetRPCEndpoints_Handler, }, + { + MethodName: "GetCommunicationRelayers", + Handler: _GoCryptoTrader_GetCommunicationRelayers_Handler, + }, { MethodName: "GetExchanges", Handler: _GoCryptoTrader_GetExchanges_Handler, diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 18bf8616..b76ff411 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -89,6 +89,15 @@ func request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler run } +func request_GoCryptoTrader_GetCommunicationRelayers_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCommunicationRelayersRequest + var metadata runtime.ServerMetadata + + msg, err := client.GetCommunicationRelayers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetExchanges_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -665,6 +674,26 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_GetCommunicationRelayers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetCommunicationRelayers_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetCommunicationRelayers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_GoCryptoTrader_GetExchanges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1279,6 +1308,8 @@ var ( pattern_GoCryptoTrader_GetRPCEndpoints_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getrpcendpoints"}, "")) + pattern_GoCryptoTrader_GetCommunicationRelayers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcommunicationrelayers"}, "")) + pattern_GoCryptoTrader_GetExchanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchanges"}, "")) pattern_GoCryptoTrader_DisableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchange"}, "")) @@ -1351,6 +1382,8 @@ var ( forward_GoCryptoTrader_GetRPCEndpoints_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_GetCommunicationRelayers_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_GetExchanges_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_DisableExchange_0 = runtime.ForwardResponseMessage diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 58a3271d..7c2d88b3 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -16,9 +16,16 @@ message GetInfoResponse { map rpc_endpoints = 7; } -// TO-DO comms APIs message GetCommunicationRelayersRequest {} -message GetCommunicationRelayersResponse {} + +message CommunicationRelayer { + bool enabled = 1; + bool connected = 2; +} + +message GetCommunicationRelayersResponse { + map communication_relayers = 1; +} message GenericSubsystemRequest { string subsystem = 1; @@ -456,6 +463,12 @@ service GoCryptoTrader { }; } + rpc GetCommunicationRelayers (GetCommunicationRelayersRequest) returns (GetCommunicationRelayersResponse) { + option (google.api.http) = { + get: "/v1/getcommunicationrelayers" + }; + } + rpc GetExchanges (GetExchangesRequest) returns (GetExchangesResponse) { option (google.api.http) = { get: "/v1/getexchanges" diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index 7a6cedd9..a19f07a0 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -243,6 +243,22 @@ ] } }, + "/v1/getcommunicationrelayers": { + "get": { + "operationId": "GetCommunicationRelayers", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetCommunicationRelayersResponse" + } + } + }, + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getconfig": { "get": { "operationId": "GetConfig", @@ -960,6 +976,19 @@ } } }, + "gctrpcCommunicationRelayer": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "format": "boolean" + }, + "connected": { + "type": "boolean", + "format": "boolean" + } + } + }, "gctrpcConditionParams": { "type": "object", "properties": { @@ -1075,6 +1104,17 @@ } } }, + "gctrpcGetCommunicationRelayersResponse": { + "type": "object", + "properties": { + "communication_relayers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/gctrpcCommunicationRelayer" + } + } + } + }, "gctrpcGetConfigResponse": { "type": "object", "properties": { From 20c24601fb0a604f13f626f470c57394d79e79c2 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Mon, 17 Jun 2019 09:02:07 +1000 Subject: [PATCH 13/71] Asset update to fix minor stutter (#316) --- cmd/exchange_template/exchange_template.go | 6 +- cmd/exchange_template/main_file.tmpl | 2 +- cmd/exchange_template/wrapper_file.tmpl | 10 +- cmd/exchange_wrapper_coverage/main.go | 6 +- cmd/websocket_client/main.go | 6 +- config/config.go | 34 +++--- config/config_test.go | 12 +- currency/manager.go | 20 ++-- currency/manager_test.go | 42 +++---- currency/manager_types.go | 14 +-- engine/events.go | 6 +- engine/events_test.go | 10 +- engine/helpers.go | 22 ++-- engine/helpers_test.go | 62 ++++++---- engine/routines.go | 10 +- engine/rpcserver.go | 10 +- engine/syncer.go | 12 +- engine/syncer_types.go | 4 +- engine/websocket.go | 6 +- exchanges/alphapoint/alphapoint_wrapper.go | 18 +-- exchanges/anx/anx_test.go | 4 +- exchanges/anx/anx_wrapper.go | 32 ++--- exchanges/asset/asset.go | 112 ++++++++++++++++++ .../assets_test.go => asset/asset_test.go} | 16 +-- exchanges/assets/assets.go | 112 ------------------ exchanges/binance/binance.go | 4 +- exchanges/binance/binance_test.go | 4 +- exchanges/binance/binance_websocket.go | 20 ++-- exchanges/binance/binance_wrapper.go | 32 ++--- exchanges/bitfinex/bitfinex_websocket.go | 16 +-- exchanges/bitfinex/bitfinex_wrapper.go | 24 ++-- exchanges/bitflyer/bitflyer_test.go | 6 +- exchanges/bitflyer/bitflyer_wrapper.go | 24 ++-- exchanges/bithumb/bithumb_wrapper.go | 28 ++--- exchanges/bitmex/bitmex_websocket.go | 10 +- exchanges/bitmex/bitmex_wrapper.go | 46 +++---- exchanges/bitstamp/bitstamp_websocket.go | 16 +-- exchanges/bitstamp/bitstamp_wrapper.go | 24 ++-- exchanges/bittrex/bittrex_wrapper.go | 32 ++--- exchanges/btcmarkets/btcmarkets_wrapper.go | 32 ++--- exchanges/btse/btse_websocket.go | 10 +- exchanges/btse/btse_wrapper.go | 32 ++--- .../coinbasepro/coinbasepro_websocket.go | 14 +-- exchanges/coinbasepro/coinbasepro_wrapper.go | 30 ++--- exchanges/coinut/coinut_websocket.go | 18 +-- exchanges/coinut/coinut_wrapper.go | 24 ++-- exchanges/exchange.go | 22 ++-- exchanges/exchange_test.go | 78 ++++++------ exchanges/exmo/exmo_wrapper.go | 24 ++-- exchanges/gateio/gateio_websocket.go | 16 +-- exchanges/gateio/gateio_wrapper.go | 28 ++--- exchanges/gemini/gemini_websocket.go | 16 +-- exchanges/gemini/gemini_wrapper.go | 26 ++-- exchanges/hitbtc/hitbtc_websocket.go | 14 +-- exchanges/hitbtc/hitbtc_wrapper.go | 32 ++--- exchanges/huobi/huobi_websocket.go | 10 +- exchanges/huobi/huobi_wrapper.go | 32 ++--- exchanges/huobihadax/huobihadax_test.go | 10 +- exchanges/huobihadax/huobihadax_websocket.go | 10 +- exchanges/huobihadax/huobihadax_wrapper.go | 30 ++--- exchanges/interfaces.go | 22 ++-- exchanges/itbit/itbit_wrapper.go | 22 ++-- exchanges/kraken/kraken_websocket.go | 4 +- exchanges/kraken/kraken_wrapper.go | 32 ++--- exchanges/lakebtc/lakebtc_test.go | 4 +- exchanges/lakebtc/lakebtc_wrapper.go | 26 ++-- .../localbitcoins/localbitcoins_wrapper.go | 26 ++-- exchanges/okcoin/okcoin_wrapper.go | 14 +-- exchanges/okex/okex_wrapper.go | 24 ++-- exchanges/okgroup/okgroup_websocket.go | 8 +- exchanges/okgroup/okgroup_wrapper.go | 24 ++-- exchanges/order_types.go | 4 +- exchanges/orderbook/orderbook.go | 32 ++--- exchanges/orderbook/orderbook_test.go | 32 ++--- exchanges/poloniex/poloniex_websocket.go | 16 +-- exchanges/poloniex/poloniex_wrapper.go | 28 ++--- exchanges/stats/stats.go | 14 +-- exchanges/stats/stats_test.go | 34 +++--- exchanges/ticker/ticker.go | 10 +- exchanges/ticker/ticker_test.go | 72 +++++------ exchanges/websocket.go | 6 +- exchanges/websocket_test.go | 10 +- exchanges/websocket_types.go | 12 +- exchanges/yobit/yobit_test.go | 4 +- exchanges/yobit/yobit_wrapper.go | 34 +++--- exchanges/zb/zb_websocket.go | 12 +- exchanges/zb/zb_wrapper.go | 34 +++--- 87 files changed, 976 insertions(+), 966 deletions(-) create mode 100644 exchanges/asset/asset.go rename exchanges/{assets/assets_test.go => asset/asset_test.go} (85%) delete mode 100644 exchanges/assets/assets.go diff --git a/cmd/exchange_template/exchange_template.go b/cmd/exchange_template/exchange_template.go index d7e44f7f..40c3fe7c 100644 --- a/cmd/exchange_template/exchange_template.go +++ b/cmd/exchange_template/exchange_template.go @@ -13,7 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) const ( @@ -121,8 +121,8 @@ func main() { newExchConfig.API.Credentials.Secret = "Secret" newExchConfig.CurrencyPairs = ¤cy.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, } diff --git a/cmd/exchange_template/main_file.tmpl b/cmd/exchange_template/main_file.tmpl index 242b633c..0434e971 100644 --- a/cmd/exchange_template/main_file.tmpl +++ b/cmd/exchange_template/main_file.tmpl @@ -36,7 +36,7 @@ func ({{.Variable}} *{{.CapitalName}}) SetDefaults() { {{.Variable}}.RequestCurrencyPairFormat.Uppercase = true {{.Variable}}.ConfigCurrencyPairFormat.Delimiter = "" {{.Variable}}.ConfigCurrencyPairFormat.Uppercase = true - {{.Variable}}.AssetTypes = assets.AssetTypes{assets.AssetTypeSpot} + {{.Variable}}.AssetTypes = asset.Items{asset.Spot} {{.Variable}}.SupportsAutoPairUpdating = false {{.Variable}}.SupportsRESTTickerBatching = false {{.Variable}}.Requester = request.New({{.Variable}}.Name, diff --git a/cmd/exchange_template/wrapper_file.tmpl b/cmd/exchange_template/wrapper_file.tmpl index 2f1f0726..351096d6 100644 --- a/cmd/exchange_template/wrapper_file.tmpl +++ b/cmd/exchange_template/wrapper_file.tmpl @@ -31,7 +31,7 @@ func ({{.Variable}} *{{.CapitalName}}) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price // NOTE EXAMPLE FOR GETTING TICKER PRICE //tick, err := {{.Variable}}.GetTickers() @@ -59,7 +59,7 @@ func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(p currency.Pair, assetType a } // FetchTicker returns the ticker for a currency pair -func ({{.Variable}} *{{.CapitalName}}) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func ({{.Variable}} *{{.CapitalName}}) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker({{.Variable}}.GetName(), p, assetType) if err != nil { return {{.Variable}}.UpdateTicker(p, assetType) @@ -68,7 +68,7 @@ func ({{.Variable}} *{{.CapitalName}}) FetchTicker(p currency.Pair, assetType as } // FetchOrderbook returns orderbook base on the currency pair -func ({{.Variable}} *{{.CapitalName}}) FetchOrderbook(currency currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func ({{.Variable}} *{{.CapitalName}}) FetchOrderbook(currency currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get({{.Variable}}.GetName(), currency, assetType) if err != nil { return {{.Variable}}.UpdateOrderbook(currency, assetType) @@ -77,7 +77,7 @@ func ({{.Variable}} *{{.CapitalName}}) FetchOrderbook(currency currency.Pair, as } // UpdateOrderbook updates and returns the orderbook for a currency pair -func ({{.Variable}} *{{.CapitalName}}) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func ({{.Variable}} *{{.CapitalName}}) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base //NOTE UPDATE ORDERBOOK EXAMPLE //orderbookNew, err := {{.Variable}}.GetOrderBook(exchange.FormatExchangeCurrency({{.Variable}}.Name, p).String(), 1000) @@ -111,7 +111,7 @@ func ({{.Variable}} *{{.CapitalName}}) GetFundingHistory() ([]exchange.FundHisto } // GetExchangeHistory returns historic trade data since exchange opening. -func ({{.Variable}} *{{.CapitalName}}) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func ({{.Variable}} *{{.CapitalName}}) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } diff --git a/cmd/exchange_wrapper_coverage/main.go b/cmd/exchange_wrapper_coverage/main.go index c8953421..58e0b408 100644 --- a/cmd/exchange_wrapper_coverage/main.go +++ b/cmd/exchange_wrapper_coverage/main.go @@ -8,7 +8,7 @@ import ( "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/engine" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) const ( @@ -68,7 +68,7 @@ func main() { func testWrappers(e exchange.IBotExchange) []string { p := currency.NewPair(currency.BTC, currency.USD) - assetType := assets.AssetTypeSpot + assetType := asset.Spot var funcs []string _, err := e.FetchTicker(p, assetType) @@ -91,7 +91,7 @@ func testWrappers(e exchange.IBotExchange) []string { funcs = append(funcs, "UpdateOrderbook") } - _, err = e.FetchTradablePairs(assets.AssetTypeSpot) + _, err = e.FetchTradablePairs(asset.Spot) if err == common.ErrNotYetImplemented { funcs = append(funcs, "FetchTradablePairs") } diff --git a/cmd/websocket_client/main.go b/cmd/websocket_client/main.go index 8115dea4..d3522c9b 100644 --- a/cmd/websocket_client/main.go +++ b/cmd/websocket_client/main.go @@ -10,7 +10,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/config" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // Vars for the websocket client @@ -44,7 +44,7 @@ type WebsocketEventResponse struct { type WebsocketOrderbookTickerRequest struct { Exchange string `json:"exchangeName"` Currency string `json:"currency"` - AssetType assets.AssetType `json:"assetType"` + AssetType asset.Item `json:"assetType"` } // SendWebsocketEvent sends a websocket event message @@ -157,7 +157,7 @@ func main() { dataReq := WebsocketOrderbookTickerRequest{ Exchange: "Bitfinex", Currency: "BTCUSD", - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, } err = SendWebsocketEvent("GetTicker", dataReq, &wsResp) diff --git a/config/config.go b/config/config.go index 6b8c4eac..9adfdbb2 100644 --- a/config/config.go +++ b/config/config.go @@ -22,7 +22,7 @@ import ( "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/forexprovider" "github.com/thrasher-/gocryptotrader/currency/forexprovider/base" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -402,21 +402,21 @@ func (c *Config) CheckCommunicationsConfig() { } // GetExchangeAssetTypes returns the exchanges supported asset types -func (c *Config) GetExchangeAssetTypes(exchName string) (assets.AssetTypes, error) { +func (c *Config) GetExchangeAssetTypes(exchName string) (asset.Items, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { - return assets.AssetTypes{}, err + return nil, err } if exchCfg.CurrencyPairs == nil { - return assets.AssetTypes{}, fmt.Errorf("exchange %s currency pairs is nil", exchName) + return nil, fmt.Errorf("exchange %s currency pairs is nil", exchName) } return exchCfg.CurrencyPairs.AssetTypes, nil } // SupportsExchangeAssetType returns whether or not the exchange supports the supplied asset type -func (c *Config) SupportsExchangeAssetType(exchName string, assetType assets.AssetType) (bool, error) { +func (c *Config) SupportsExchangeAssetType(exchName string, assetType asset.Item) (bool, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return false, err @@ -426,7 +426,7 @@ func (c *Config) SupportsExchangeAssetType(exchName string, assetType assets.Ass return false, fmt.Errorf("exchange %s currency pairs is nil", exchName) } - if !assets.IsValid(assetType) { + if !asset.IsValid(assetType) { return false, fmt.Errorf("exchange %s invalid asset types", exchName) } @@ -460,7 +460,7 @@ func (c *Config) CheckExchangeAssetsConsistency(exchName string) { } // SetPairs sets the exchanges currency pairs -func (c *Config) SetPairs(exchName string, assetType assets.AssetType, enabled bool, pairs currency.Pairs) error { +func (c *Config) SetPairs(exchName string, assetType asset.Item, enabled bool, pairs currency.Pairs) error { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return err @@ -480,7 +480,7 @@ func (c *Config) SetPairs(exchName string, assetType assets.AssetType, enabled b } // GetCurrencyPairConfig returns currency pair config for the desired exchange and asset type -func (c *Config) GetCurrencyPairConfig(exchName string, assetType assets.AssetType) (*currency.PairStore, error) { +func (c *Config) GetCurrencyPairConfig(exchName string, assetType asset.Item) (*currency.PairStore, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err @@ -636,7 +636,7 @@ func (c *Config) CheckPairConsistency(exchName string) error { // SupportsPair returns true or not whether the exchange supports the supplied // pair -func (c *Config) SupportsPair(exchName string, p currency.Pair, assetType assets.AssetType) (bool, error) { +func (c *Config) SupportsPair(exchName string, p currency.Pair, assetType asset.Item) (bool, error) { pairs, err := c.GetAvailablePairs(exchName, assetType) if err != nil { return false, err @@ -645,7 +645,7 @@ func (c *Config) SupportsPair(exchName string, p currency.Pair, assetType assets } // GetPairFormat returns the exchanges pair config storage format -func (c *Config) GetPairFormat(exchName string, assetType assets.AssetType) (currency.PairFormat, error) { +func (c *Config) GetPairFormat(exchName string, assetType asset.Item) (currency.PairFormat, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return currency.PairFormat{}, err @@ -663,7 +663,7 @@ func (c *Config) GetPairFormat(exchName string, assetType assets.AssetType) (cur } // GetAvailablePairs returns a list of currency pairs for a specifc exchange -func (c *Config) GetAvailablePairs(exchName string, assetType assets.AssetType) (currency.Pairs, error) { +func (c *Config) GetAvailablePairs(exchName string, assetType asset.Item) (currency.Pairs, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err @@ -684,7 +684,7 @@ func (c *Config) GetAvailablePairs(exchName string, assetType assets.AssetType) } // GetEnabledPairs returns a list of currency pairs for a specifc exchange -func (c *Config) GetEnabledPairs(exchName string, assetType assets.AssetType) ([]currency.Pair, error) { +func (c *Config) GetEnabledPairs(exchName string, assetType asset.Item) ([]currency.Pair, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err @@ -909,7 +909,7 @@ func (c *Config) CheckExchangeConfigValues() error { // Check if see if the new currency pairs format is empty and flesh it out if so if c.Exchanges[i].CurrencyPairs == nil { c.Exchanges[i].CurrencyPairs = new(currency.PairsManager) - c.Exchanges[i].CurrencyPairs.Pairs = make(map[assets.AssetType]*currency.PairStore) + c.Exchanges[i].CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) if c.Exchanges[i].PairsLastUpdated != nil { c.Exchanges[i].CurrencyPairs.LastUpdated = *c.Exchanges[i].PairsLastUpdated @@ -917,9 +917,9 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].CurrencyPairs.ConfigFormat = c.Exchanges[i].ConfigCurrencyPairFormat c.Exchanges[i].CurrencyPairs.RequestFormat = c.Exchanges[i].RequestCurrencyPairFormat - c.Exchanges[i].CurrencyPairs.AssetTypes = assets.New(strings.ToLower(*c.Exchanges[i].AssetTypes)) + c.Exchanges[i].CurrencyPairs.AssetTypes = asset.New(strings.ToLower(*c.Exchanges[i].AssetTypes)) c.Exchanges[i].CurrencyPairs.UseGlobalFormat = true - c.Exchanges[i].CurrencyPairs.Store(assets.AssetTypeSpot, + c.Exchanges[i].CurrencyPairs.Store(asset.Spot, currency.PairStore{ Available: *c.Exchanges[i].AvailablePairs, Enabled: *c.Exchanges[i].EnabledPairs, @@ -1197,9 +1197,9 @@ func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool) error { var pairs []currency.Pair var err error if !c.Exchanges[x].Enabled && enabledOnly { - pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name, assets.AssetTypeSpot) + pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name, asset.Spot) } else { - pairs, err = c.GetAvailablePairs(c.Exchanges[x].Name, assets.AssetTypeSpot) + pairs, err = c.GetAvailablePairs(c.Exchanges[x].Name, asset.Spot) } if err != nil { diff --git a/config/config_test.go b/config/config_test.go index cf881fa2..92c3cf48 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -6,7 +6,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" log "github.com/thrasher-/gocryptotrader/logger" "github.com/thrasher-/gocryptotrader/ntpclient" ) @@ -285,7 +285,7 @@ func TestCheckPairConsistency(t *testing.T) { Uppercase: true, }, } - pairsMan.Store(assets.AssetTypeSpot, currency.PairStore{ + pairsMan.Store(asset.Spot, currency.PairStore{ Available: currency.NewPairsFromStrings([]string{"DOGE_USD,DOGE_AUD"}), Enabled: currency.NewPairsFromStrings([]string{"DOGE_USD,DOGE_AUD,DOGE_BTC"}), }) @@ -311,7 +311,7 @@ func TestCheckPairConsistency(t *testing.T) { t.Error("Test failed. CheckPairConsistency error:", err) } - tec.CurrencyPairs.StorePairs(assets.AssetTypeSpot, currency.NewPairsFromStrings([]string{"DOGE_LTC,BTC_LTC"}), false) + tec.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"DOGE_LTC,BTC_LTC"}), false) err = cfg.UpdateExchangeConfig(tec) if err != nil { t.Error("Test failed. CheckPairConsistency Update config failed, error:", err) @@ -332,7 +332,7 @@ func TestSupportsPair(t *testing.T) { ) } - assetType := assets.AssetTypeSpot + assetType := asset.Spot _, err = cfg.SupportsPair("asdf", currency.NewPair(currency.BTC, currency.USD), assetType) if err == nil { @@ -358,7 +358,7 @@ func TestGetAvailablePairs(t *testing.T) { "Test failed. TestGetAvailablePairs. LoadConfig Error: %s", err.Error()) } - assetType := assets.AssetTypeSpot + assetType := asset.Spot _, err = cfg.GetAvailablePairs("asdf", assetType) if err == nil { t.Error( @@ -380,7 +380,7 @@ func TestGetEnabledPairs(t *testing.T) { "Test failed. TestGetEnabledPairs. LoadConfig Error: %s", err.Error()) } - assetType := assets.AssetTypeSpot + assetType := asset.Spot _, err = cfg.GetEnabledPairs("asdf", assetType) if err == nil { t.Error( diff --git a/currency/manager.go b/currency/manager.go index e6570efe..4f895ba3 100644 --- a/currency/manager.go +++ b/currency/manager.go @@ -1,14 +1,14 @@ package currency import ( - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // GetAssetTypes returns a list of stored asset types -func (p *PairsManager) GetAssetTypes() assets.AssetTypes { +func (p *PairsManager) GetAssetTypes() asset.Items { p.m.Lock() defer p.m.Unlock() - var assetTypes assets.AssetTypes + var assetTypes asset.Items for k := range p.Pairs { assetTypes = append(assetTypes, k) } @@ -16,7 +16,7 @@ func (p *PairsManager) GetAssetTypes() assets.AssetTypes { } // Get gets the currency pair config based on the asset type -func (p *PairsManager) Get(a assets.AssetType) *PairStore { +func (p *PairsManager) Get(a asset.Item) *PairStore { p.m.Lock() defer p.m.Unlock() c, ok := p.Pairs[a] @@ -27,11 +27,11 @@ func (p *PairsManager) Get(a assets.AssetType) *PairStore { } // Store stores a new currency pair config based on its asset type -func (p *PairsManager) Store(a assets.AssetType, ps PairStore) { +func (p *PairsManager) Store(a asset.Item, ps PairStore) { p.m.Lock() if p.Pairs == nil { - p.Pairs = make(map[assets.AssetType]*PairStore) + p.Pairs = make(map[asset.Item]*PairStore) } if !p.AssetTypes.Contains(a) { @@ -43,7 +43,7 @@ func (p *PairsManager) Store(a assets.AssetType, ps PairStore) { } // Delete deletes a map entry based on the supplied asset type -func (p *PairsManager) Delete(a assets.AssetType) { +func (p *PairsManager) Delete(a asset.Item) { p.m.Lock() defer p.m.Unlock() if p.Pairs == nil { @@ -60,7 +60,7 @@ func (p *PairsManager) Delete(a assets.AssetType) { // GetPairs gets a list of stored pairs based on the asset type and whether // they're enabled or not -func (p *PairsManager) GetPairs(a assets.AssetType, enabled bool) Pairs { +func (p *PairsManager) GetPairs(a asset.Item, enabled bool) Pairs { p.m.Lock() defer p.m.Unlock() if p.Pairs == nil { @@ -84,12 +84,12 @@ func (p *PairsManager) GetPairs(a assets.AssetType, enabled bool) Pairs { // StorePairs stores a list of pairs based on the asset type and whether // they're enabled or not -func (p *PairsManager) StorePairs(a assets.AssetType, pairs Pairs, enabled bool) { +func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) { p.m.Lock() defer p.m.Unlock() if p.Pairs == nil { - p.Pairs = make(map[assets.AssetType]*PairStore) + p.Pairs = make(map[asset.Item]*PairStore) } c, ok := p.Pairs[a] diff --git a/currency/manager_test.go b/currency/manager_test.go index 7994ddf4..edc29741 100644 --- a/currency/manager_test.go +++ b/currency/manager_test.go @@ -3,13 +3,13 @@ package currency import ( "testing" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) var p PairsManager func initTest() { - p.Store(assets.AssetTypeSpot, + p.Store(asset.Spot, PairStore{ Available: NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}), Enabled: NewPairsFromStrings([]string{"BTC-USD"}), @@ -32,7 +32,7 @@ func TestGetAssetTypes(t *testing.T) { t.Errorf("Test failed. GetAssetTypes shouldn't be nil") } - if !a.Contains(assets.AssetTypeSpot) { + if !a.Contains(asset.Spot) { t.Errorf("Test failed. AssetTypeSpot should be in the assets list") } } @@ -40,17 +40,17 @@ func TestGetAssetTypes(t *testing.T) { func TestGet(t *testing.T) { initTest() - if p.Get(assets.AssetTypeSpot) == nil { + if p.Get(asset.Spot) == nil { t.Error("Test failed. Spot assets shouldn't be nil") } - if p.Get(assets.AssetTypeFutures) != nil { + if p.Get(asset.Futures) != nil { t.Error("Test Failed. Futures should be nil") } } func TestStore(t *testing.T) { - p.Store(assets.AssetTypeFutures, + p.Store(asset.Futures, PairStore{ Available: NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}), Enabled: NewPairsFromStrings([]string{"BTC-USD"}), @@ -64,27 +64,27 @@ func TestStore(t *testing.T) { }, ) - if p.Get(assets.AssetTypeFutures) == nil { + if p.Get(asset.Futures) == nil { t.Error("Test failed. Futures assets shouldn't be nil") } } func TestDelete(t *testing.T) { p.Pairs = nil - p.Delete(assets.AssetTypeSpot) + p.Delete(asset.Spot) - p.Store(assets.AssetTypeSpot, + p.Store(asset.Spot, PairStore{ Available: NewPairsFromStrings([]string{"BTC-USD"}), }, ) - p.Delete(assets.AssetTypeUpsideProfitContract) - if p.Get(assets.AssetTypeSpot) == nil { + p.Delete(asset.UpsideProfitContract) + if p.Get(asset.Spot) == nil { t.Error("Test failed. AssetTypeSpot should exist") } - p.Delete(assets.AssetTypeSpot) - if p.Get(assets.AssetTypeSpot) != nil { + p.Delete(asset.Spot) + if p.Get(asset.Spot) != nil { t.Error("Test failed. Delete should have deleted AssetTypeSpot") } @@ -92,13 +92,13 @@ func TestDelete(t *testing.T) { func TestGetPairs(t *testing.T) { p.Pairs = nil - pairs := p.GetPairs(assets.AssetTypeSpot, true) + pairs := p.GetPairs(asset.Spot, true) if pairs != nil { t.Fatal("pairs shouldn't be populated") } initTest() - pairs = p.GetPairs(assets.AssetTypeSpot, true) + pairs = p.GetPairs(asset.Spot, true) if pairs == nil { t.Fatal("pairs should be populated") } @@ -111,15 +111,15 @@ func TestGetPairs(t *testing.T) { func TestStorePairs(t *testing.T) { p.Pairs = nil - p.StorePairs(assets.AssetTypeSpot, NewPairsFromStrings([]string{"ETH-USD"}), false) - pairs := p.GetPairs(assets.AssetTypeSpot, false) + p.StorePairs(asset.Spot, NewPairsFromStrings([]string{"ETH-USD"}), false) + pairs := p.GetPairs(asset.Spot, false) if !pairs.Contains(NewPairFromString("ETH-USD"), true) { t.Errorf("TestStorePairs failed, unexpected result") } initTest() - p.StorePairs(assets.AssetTypeSpot, NewPairsFromStrings([]string{"ETH-USD"}), false) - pairs = p.GetPairs(assets.AssetTypeSpot, false) + p.StorePairs(asset.Spot, NewPairsFromStrings([]string{"ETH-USD"}), false) + pairs = p.GetPairs(asset.Spot, false) if pairs == nil { t.Errorf("pairs should be populated") } @@ -128,8 +128,8 @@ func TestStorePairs(t *testing.T) { t.Errorf("TestStorePairs failed, unexpected result") } - p.StorePairs(assets.AssetTypeFutures, NewPairsFromStrings([]string{"ETH-KRW"}), true) - pairs = p.GetPairs(assets.AssetTypeFutures, true) + p.StorePairs(asset.Futures, NewPairsFromStrings([]string{"ETH-KRW"}), true) + pairs = p.GetPairs(asset.Futures, true) if pairs == nil { t.Errorf("pairs futures should be populated") } diff --git a/currency/manager_types.go b/currency/manager_types.go index 374d1179..9abf570a 100644 --- a/currency/manager_types.go +++ b/currency/manager_types.go @@ -3,17 +3,17 @@ package currency import ( "sync" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // PairsManager manages asset pairs type PairsManager struct { - RequestFormat *PairFormat `json:"requestFormat,omitempty"` - ConfigFormat *PairFormat `json:"configFormat,omitempty"` - UseGlobalFormat bool `json:"useGlobalFormat,omitempty"` - LastUpdated int64 `json:"lastUpdated,omitempty"` - AssetTypes assets.AssetTypes `json:"assetTypes"` - Pairs map[assets.AssetType]*PairStore `json:"pairs"` + RequestFormat *PairFormat `json:"requestFormat,omitempty"` + ConfigFormat *PairFormat `json:"configFormat,omitempty"` + UseGlobalFormat bool `json:"useGlobalFormat,omitempty"` + LastUpdated int64 `json:"lastUpdated,omitempty"` + AssetTypes asset.Items `json:"assetTypes"` + Pairs map[asset.Item]*PairStore `json:"pairs"` m sync.Mutex } diff --git a/engine/events.go b/engine/events.go index d8d225b1..41b2ae83 100644 --- a/engine/events.go +++ b/engine/events.go @@ -9,7 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/communications/base" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" @@ -61,7 +61,7 @@ type Event struct { Item string Condition EventConditionParams Pair currency.Pair - Asset assets.AssetType + Asset asset.Item Action string Executed bool } @@ -72,7 +72,7 @@ var Events []*Event // Add adds an event to the Events chain and returns an index/eventID // and an error -func Add(exchange, item string, condition EventConditionParams, currencyPair currency.Pair, asset assets.AssetType, action string) (int64, error) { +func Add(exchange, item string, condition EventConditionParams, currencyPair currency.Pair, asset asset.Item, action string) (int64, error) { err := IsValidEvent(exchange, item, condition, action) if err != nil { return 0, err diff --git a/engine/events_test.go b/engine/events_test.go index 661e2e87..63718d38 100644 --- a/engine/events_test.go +++ b/engine/events_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -22,7 +22,7 @@ func addValidEvent() (int64, error) { ItemPrice, EventConditionParams{Condition: ConditionGreaterThan, Price: 1}, currency.NewPair(currency.BTC, currency.USD), - assets.AssetTypeSpot, + asset.Spot, "SMS,test") } @@ -124,7 +124,7 @@ func TestString(t *testing.T) { Price: 1, }, Pair: currency.NewPair(currency.BTC, currency.USD), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Action: "SMS,ALL", } @@ -142,7 +142,7 @@ func TestProcessTicker(t *testing.T) { e := Event{ Exchange: testExchange, Pair: currency.NewPair(currency.BTC, currency.USD), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Condition: EventConditionParams{ Condition: ConditionGreaterThan, Price: 1, @@ -214,7 +214,7 @@ func TestProcessOrderbook(t *testing.T) { e := Event{ Exchange: testExchange, Pair: currency.NewPair(currency.BTC, currency.USD), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Condition: EventConditionParams{ Condition: ConditionGreaterThan, CheckBidsAndAsks: true, diff --git a/engine/helpers.go b/engine/helpers.go index eb33fd9d..6408f041 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -20,7 +20,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -179,7 +179,7 @@ func GetAvailableExchanges() []string { // GetAllAvailablePairs returns a list of all available pairs on either enabled // or disabled exchanges -func GetAllAvailablePairs(enabledExchangesOnly bool, assetType assets.AssetType) currency.Pairs { +func GetAllAvailablePairs(enabledExchangesOnly bool, assetType asset.Item) currency.Pairs { var pairList currency.Pairs for x := range Bot.Config.Exchanges { if enabledExchangesOnly && !Bot.Config.Exchanges[x].Enabled { @@ -204,7 +204,7 @@ func GetAllAvailablePairs(enabledExchangesOnly bool, assetType assets.AssetType) // GetSpecificAvailablePairs returns a list of supported pairs based on specific // parameters -func GetSpecificAvailablePairs(enabledExchangesOnly, fiatPairs, includeUSDT, cryptoPairs bool, assetType assets.AssetType) currency.Pairs { +func GetSpecificAvailablePairs(enabledExchangesOnly, fiatPairs, includeUSDT, cryptoPairs bool, assetType asset.Item) currency.Pairs { var pairList currency.Pairs supportedPairs := GetAllAvailablePairs(enabledExchangesOnly, assetType) @@ -251,7 +251,7 @@ func IsRelatablePairs(p1, p2 currency.Pair, includeUSDT bool) bool { // MapCurrenciesByExchange returns a list of currency pairs mapped to an // exchange -func MapCurrenciesByExchange(p currency.Pairs, enabledExchangesOnly bool, assetType assets.AssetType) map[string]currency.Pairs { +func MapCurrenciesByExchange(p currency.Pairs, enabledExchangesOnly bool, assetType asset.Item) map[string]currency.Pairs { currencyExchange := make(map[string]currency.Pairs) for x := range p { for y := range Bot.Config.Exchanges { @@ -283,7 +283,7 @@ func MapCurrenciesByExchange(p currency.Pairs, enabledExchangesOnly bool, assetT // GetExchangeNamesByCurrency returns a list of exchanges supporting // a currency pair based on whether the exchange is enabled or not -func GetExchangeNamesByCurrency(p currency.Pair, enabled bool, assetType assets.AssetType) []string { +func GetExchangeNamesByCurrency(p currency.Pair, enabled bool, assetType asset.Item) []string { var exchanges []string for x := range Bot.Config.Exchanges { if enabled != Bot.Config.Exchanges[x].Enabled { @@ -400,7 +400,7 @@ func GetRelatableCurrencies(p currency.Pair, incOrig, incUSDT bool) currency.Pai // GetSpecificOrderbook returns a specific orderbook given the currency, // exchangeName and assetType -func GetSpecificOrderbook(p currency.Pair, exchangeName string, assetType assets.AssetType) (orderbook.Base, error) { +func GetSpecificOrderbook(p currency.Pair, exchangeName string, assetType asset.Item) (orderbook.Base, error) { var specificOrderbook orderbook.Base var err error for x := range Bot.Exchanges { @@ -419,7 +419,7 @@ func GetSpecificOrderbook(p currency.Pair, exchangeName string, assetType assets // GetSpecificTicker returns a specific ticker given the currency, // exchangeName and assetType -func GetSpecificTicker(p currency.Pair, exchangeName string, assetType assets.AssetType) (ticker.Price, error) { +func GetSpecificTicker(p currency.Pair, exchangeName string, assetType asset.Item) (ticker.Price, error) { var specificTicker ticker.Price var err error for x := range Bot.Exchanges { @@ -475,7 +475,7 @@ func GetAccountCurrencyInfoByExchangeName(accounts []exchange.AccountInfo, excha // GetExchangeHighestPriceByCurrencyPair returns the exchange with the highest // price for a given currency pair and asset type -func GetExchangeHighestPriceByCurrencyPair(p currency.Pair, assetType assets.AssetType) (string, error) { +func GetExchangeHighestPriceByCurrencyPair(p currency.Pair, assetType asset.Item) (string, error) { result := stats.SortExchangesByPrice(p, assetType, true) if len(result) == 0 { return "", fmt.Errorf("no stats for supplied currency pair and asset type") @@ -486,7 +486,7 @@ func GetExchangeHighestPriceByCurrencyPair(p currency.Pair, assetType assets.Ass // GetExchangeLowestPriceByCurrencyPair returns the exchange with the lowest // price for a given currency pair and asset type -func GetExchangeLowestPriceByCurrencyPair(p currency.Pair, assetType assets.AssetType) (string, error) { +func GetExchangeLowestPriceByCurrencyPair(p currency.Pair, assetType asset.Item) (string, error) { result := stats.SortExchangesByPrice(p, assetType, false) if len(result) == 0 { return "", fmt.Errorf("no stats for supplied currency pair and asset type") @@ -586,7 +586,7 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) { } // GetCryptocurrenciesByExchange returns a list of cryptocurrencies the exchange supports -func GetCryptocurrenciesByExchange(exchangeName string, enabledExchangesOnly, enabledPairs bool, assetType assets.AssetType) ([]string, error) { +func GetCryptocurrenciesByExchange(exchangeName string, enabledExchangesOnly, enabledPairs bool, assetType asset.Item) ([]string, error) { var cryptocurrencies []string for x := range Bot.Config.Exchanges { if Bot.Config.Exchanges[x].Name != exchangeName { @@ -656,7 +656,7 @@ func GetExchangeCryptocurrencyDepositAddresses() map[string]map[string]string { continue } - cryptoCurrencies, err := GetCryptocurrenciesByExchange(exchName, true, true, assets.AssetTypeSpot) + cryptoCurrencies, err := GetCryptocurrenciesByExchange(exchName, true, true, asset.Spot) if err != nil { log.Debugf("%s failed to get cryptocurrency deposit addresses. Err: %s", exchName, err) continue diff --git a/engine/helpers_test.go b/engine/helpers_test.go index 77d5fde2..74598d9a 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -8,7 +8,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -151,7 +151,7 @@ func TestGetAvailableExchanges(t *testing.T) { func TestGetSpecificAvailablePairs(t *testing.T) { SetupTestHelpers(t) - assetType := assets.AssetTypeSpot + assetType := asset.Spot result := GetSpecificAvailablePairs(true, true, true, false, assetType) if !result.Contains(currency.NewPairFromStrings("BTC", "USD"), true) { @@ -341,7 +341,7 @@ func TestMapCurrenciesByExchange(t *testing.T) { currency.NewPair(currency.BTC, currency.EUR), } - result := MapCurrenciesByExchange(pairs, true, assets.AssetTypeSpot) + result := MapCurrenciesByExchange(pairs, true, asset.Spot) pairs, ok := result["Bitstamp"] if !ok { t.Fatal("Unexpected result") @@ -354,19 +354,25 @@ func TestMapCurrenciesByExchange(t *testing.T) { func TestGetExchangeNamesByCurrency(t *testing.T) { SetupTestHelpers(t) - assetType := assets.AssetTypeSpot + assetType := asset.Spot - result := GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "USD"), true, assetType) + result := GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "USD"), + true, + assetType) if !common.StringDataCompare(result, "Bitstamp") { t.Fatal("Unexpected result") } - result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "JPY"), true, assetType) + result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "JPY"), + true, + assetType) if !common.StringDataCompare(result, "Bitflyer") { t.Fatal("Unexpected result") } - result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("blah", "JPY"), true, assetType) + result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("blah", "JPY"), + true, + assetType) if len(result) > 0 { t.Fatal("Unexpected result") } @@ -379,13 +385,12 @@ func TestGetSpecificOrderbook(t *testing.T) { var bids []orderbook.Item bids = append(bids, orderbook.Item{Price: 1000, Amount: 1}) - asset := assets.AssetTypeSpot base := orderbook.Base{ Pair: currency.NewPair(currency.BTC, currency.USD), Bids: bids, ExchangeName: "Bitstamp", - AssetType: asset, + AssetType: asset.Spot, } err := base.Process() @@ -393,7 +398,9 @@ func TestGetSpecificOrderbook(t *testing.T) { t.Fatal("Unexpected result", err) } - ob, err := GetSpecificOrderbook(currency.NewPairFromString("BTCUSD"), "Bitstamp", assets.AssetTypeSpot) + ob, err := GetSpecificOrderbook(currency.NewPairFromString("BTCUSD"), + "Bitstamp", + asset.Spot) if err != nil { t.Fatal(err) } @@ -402,7 +409,9 @@ func TestGetSpecificOrderbook(t *testing.T) { t.Fatal("Unexpected result") } - ob, err = GetSpecificOrderbook(currency.NewPairFromStrings("ETH", "LTC"), "Bitstamp", asset) + ob, err = GetSpecificOrderbook(currency.NewPairFromStrings("ETH", "LTC"), + "Bitstamp", + asset.Spot) if err == nil { t.Fatal("Unexpected result") } @@ -415,15 +424,15 @@ func TestGetSpecificTicker(t *testing.T) { LoadExchange("Bitstamp", false, nil) p := currency.NewPairFromStrings("BTC", "USD") - asset := assets.AssetTypeSpot err := ticker.ProcessTicker("Bitstamp", &ticker.Price{Pair: p, Last: 1000}, - assets.AssetTypeSpot) + asset.Spot) if err != nil { t.Fatal("Test failed. ProcessTicker error", err) } - tick, err := GetSpecificTicker(currency.NewPairFromStrings("BTC", "USD"), "Bitstamp", asset) + tick, err := GetSpecificTicker(currency.NewPairFromStrings("BTC", "USD"), "Bitstamp", + asset.Spot) if err != nil { t.Fatal(err) } @@ -432,7 +441,8 @@ func TestGetSpecificTicker(t *testing.T) { t.Fatal("Unexpected result") } - tick, err = GetSpecificTicker(currency.NewPairFromStrings("ETH", "LTC"), "Bitstamp", asset) + tick, err = GetSpecificTicker(currency.NewPairFromStrings("ETH", "LTC"), "Bitstamp", + asset.Spot) if err == nil { t.Fatal("Unexpected result") } @@ -532,10 +542,9 @@ func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) { SetupTestHelpers(t) p := currency.NewPairFromStrings("BTC", "USD") - asset := assets.AssetTypeSpot - stats.Add("Bitfinex", p, assets.AssetTypeSpot, 1000, 10000) - stats.Add("Bitstamp", p, assets.AssetTypeSpot, 1337, 10000) - exchangeName, err := GetExchangeHighestPriceByCurrencyPair(p, asset) + stats.Add("Bitfinex", p, asset.Spot, 1000, 10000) + stats.Add("Bitstamp", p, asset.Spot, 1337, 10000) + exchangeName, err := GetExchangeHighestPriceByCurrencyPair(p, asset.Spot) if err != nil { t.Error(err) } @@ -544,7 +553,8 @@ func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) { t.Error("Unexpected result") } - _, err = GetExchangeHighestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"), asset) + _, err = GetExchangeHighestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"), + asset.Spot) if err == nil { t.Error("Unexpected result") } @@ -554,10 +564,9 @@ func TestGetExchangeLowestPriceByCurrencyPair(t *testing.T) { SetupTestHelpers(t) p := currency.NewPairFromStrings("BTC", "USD") - asset := assets.AssetTypeSpot - stats.Add("Bitfinex", p, assets.AssetTypeSpot, 1000, 10000) - stats.Add("Bitstamp", p, assets.AssetTypeSpot, 1337, 10000) - exchangeName, err := GetExchangeLowestPriceByCurrencyPair(p, asset) + stats.Add("Bitfinex", p, asset.Spot, 1000, 10000) + stats.Add("Bitstamp", p, asset.Spot, 1337, 10000) + exchangeName, err := GetExchangeLowestPriceByCurrencyPair(p, asset.Spot) if err != nil { t.Error(err) } @@ -566,7 +575,8 @@ func TestGetExchangeLowestPriceByCurrencyPair(t *testing.T) { t.Error("Unexpected result") } - _, err = GetExchangeLowestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"), asset) + _, err = GetExchangeLowestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"), + asset.Spot) if err == nil { t.Error("Unexpected reuslt") } @@ -575,7 +585,7 @@ func TestGetExchangeLowestPriceByCurrencyPair(t *testing.T) { func TestGetCryptocurrenciesByExchange(t *testing.T) { SetupTestHelpers(t) - _, err := GetCryptocurrenciesByExchange("Bitfinex", false, false, assets.AssetTypeSpot) + _, err := GetCryptocurrenciesByExchange("Bitfinex", false, false, asset.Spot) if err != nil { t.Fatalf("Test failed. Err %s", err) } diff --git a/engine/routines.go b/engine/routines.go index 1a553114..e687a08f 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -10,7 +10,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -57,7 +57,7 @@ func printConvertCurrencyFormat(origCurrency currency.Code, origPrice float64) s ) } -func printTickerSummary(result *ticker.Price, p currency.Pair, assetType assets.AssetType, exchangeName string, err error) { +func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.Item, exchangeName string, err error) { if err != nil { log.Errorf("Failed to get %s %s ticker. Error: %s", p.String(), @@ -108,7 +108,7 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType assets. } } -func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType assets.AssetType, exchangeName string, err error) { +func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType asset.Item, exchangeName string, err error) { if err != nil { log.Errorf("Failed to get %s %s orderbook of type %s. Error: %s", p, @@ -204,7 +204,7 @@ func TickerUpdaterRoutine() { supportsBatching := Bot.Exchanges[x].SupportsRESTTickerBatchUpdates() assetTypes := Bot.Exchanges[x].GetAssetTypes() - processTicker := func(exch exchange.IBotExchange, update bool, c currency.Pair, assetType assets.AssetType) { + processTicker := func(exch exchange.IBotExchange, update bool, c currency.Pair, assetType asset.Item) { var result ticker.Price var err error if update { @@ -256,7 +256,7 @@ func OrderbookUpdaterRoutine() { exchangeName := Bot.Exchanges[x].GetName() assetTypes := Bot.Exchanges[x].GetAssetTypes() - processOrderbook := func(exch exchange.IBotExchange, c currency.Pair, assetType assets.AssetType) { + processOrderbook := func(exch exchange.IBotExchange, c currency.Pair, assetType asset.Item) { result, err := exch.UpdateOrderbook(c, assetType) printOrderbookSummary(&result, c, assetType, exchangeName, err) if err == nil { diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 8f5237a5..473f5007 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -16,7 +16,7 @@ import ( "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/gctrpc" "github.com/thrasher-/gocryptotrader/gctrpc/auth" log "github.com/thrasher-/gocryptotrader/logger" @@ -281,7 +281,7 @@ func (s *RPCServer) GetTicker(ctx context.Context, r *gctrpc.GetTickerRequest) ( Quote: currency.NewCode(r.Pair.Quote), }, r.Exchange, - assets.AssetType(r.AssetType), + asset.Item(r.AssetType), ) if err != nil { return nil, err @@ -345,7 +345,7 @@ func (s *RPCServer) GetOrderbook(ctx context.Context, r *gctrpc.GetOrderbookRequ Quote: currency.NewCode(r.Pair.Quote), }, r.Exchange, - assets.AssetType(r.AssetType), + asset.Item(r.AssetType), ) if err != nil { return nil, err @@ -622,7 +622,7 @@ func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) ( Id: resp[x].ID, BaseCurrency: resp[x].CurrencyPair.Base.String(), QuoteCurrency: resp[x].CurrencyPair.Quote.String(), - AssetType: assets.AssetTypeSpot.String(), + AssetType: asset.Spot.String(), OrderType: resp[x].OrderType.ToString(), OrderSide: resp[x].OrderSide.ToString(), CreationTime: resp[x].OrderDate.Unix(), @@ -705,7 +705,7 @@ func (s *RPCServer) AddEvent(ctx context.Context, r *gctrpc.AddEventRequest) (*g p := currency.NewPairWithDelimiter(r.Pair.Base, r.Pair.Quote, r.Pair.Delimiter) - id, err := Add(r.Exchange, r.Item, evtCondition, p, assets.AssetType(r.AssetType), r.Action) + id, err := Add(r.Exchange, r.Item, evtCondition, p, asset.Item(r.AssetType), r.Action) if err != nil { return nil, err } diff --git a/engine/syncer.go b/engine/syncer.go index 0c3bf9e5..7c164b31 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -6,7 +6,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -58,7 +58,7 @@ func NewCurrencyPairSyncer(c CurrencyPairSyncerConfig) (*ExchangeCurrencyPairSyn return &s, nil } -func (e *ExchangeCurrencyPairSyncer) get(exchangeName string, p currency.Pair, a assets.AssetType) (*CurrencyPairSyncAgent, error) { +func (e *ExchangeCurrencyPairSyncer) get(exchangeName string, p currency.Pair, a asset.Item) (*CurrencyPairSyncAgent, error) { e.mux.Lock() defer e.mux.Unlock() @@ -73,7 +73,7 @@ func (e *ExchangeCurrencyPairSyncer) get(exchangeName string, p currency.Pair, a return nil, errors.New("exchange currency pair syncer not found") } -func (e *ExchangeCurrencyPairSyncer) exists(exchangeName string, p currency.Pair, a assets.AssetType) bool { +func (e *ExchangeCurrencyPairSyncer) exists(exchangeName string, p currency.Pair, a asset.Item) bool { e.mux.Lock() defer e.mux.Unlock() @@ -136,7 +136,7 @@ func (e *ExchangeCurrencyPairSyncer) remove(c *CurrencyPairSyncAgent) { } } -func (e *ExchangeCurrencyPairSyncer) isProcessing(exchangeName string, p currency.Pair, a assets.AssetType, syncType int) bool { +func (e *ExchangeCurrencyPairSyncer) isProcessing(exchangeName string, p currency.Pair, a asset.Item, syncType int) bool { e.mux.Lock() defer e.mux.Unlock() @@ -158,7 +158,7 @@ func (e *ExchangeCurrencyPairSyncer) isProcessing(exchangeName string, p currenc return false } -func (e *ExchangeCurrencyPairSyncer) setProcessing(exchangeName string, p currency.Pair, a assets.AssetType, syncType int, processing bool) { +func (e *ExchangeCurrencyPairSyncer) setProcessing(exchangeName string, p currency.Pair, a asset.Item, syncType int, processing bool) { e.mux.Lock() defer e.mux.Unlock() @@ -178,7 +178,7 @@ func (e *ExchangeCurrencyPairSyncer) setProcessing(exchangeName string, p curren } } -func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair, a assets.AssetType, syncType int, err error) { +func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair, a asset.Item, syncType int, err error) { if atomic.LoadInt32(&e.initSyncStarted) != 1 { return } diff --git a/engine/syncer_types.go b/engine/syncer_types.go index cd181447..8e10690a 100644 --- a/engine/syncer_types.go +++ b/engine/syncer_types.go @@ -5,7 +5,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // CurrencyPairSyncerConfig stores the currency pair config @@ -53,7 +53,7 @@ type SyncBase struct { type CurrencyPairSyncAgent struct { Created time.Time Exchange string - AssetType assets.AssetType + AssetType asset.Item Pair currency.Pair Ticker SyncBase Orderbook SyncBase diff --git a/engine/websocket.go b/engine/websocket.go index bcb8016e..af8b2210 100644 --- a/engine/websocket.go +++ b/engine/websocket.go @@ -10,7 +10,7 @@ import ( "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -347,7 +347,7 @@ func wsGetTicker(client *WebsocketClient, data interface{}) error { } result, err := GetSpecificTicker(currency.NewPairFromString(tickerReq.Currency), - tickerReq.Exchange, assets.AssetType(tickerReq.AssetType)) + tickerReq.Exchange, asset.Item(tickerReq.AssetType)) if err != nil { wsResp.Error = err.Error() @@ -379,7 +379,7 @@ func wsGetOrderbook(client *WebsocketClient, data interface{}) error { } result, err := GetSpecificOrderbook(currency.NewPairFromString(orderbookReq.Currency), - orderbookReq.Exchange, assets.AssetType(orderbookReq.AssetType)) + orderbookReq.Exchange, asset.Item(orderbookReq.AssetType)) if err != nil { wsResp.Error = err.Error() diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 81d24eb1..b46f4d62 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -10,7 +10,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -31,8 +31,8 @@ func (a *Alphapoint) SetDefaults() { a.API.CredentialsValidator.RequiresKey = true a.API.CredentialsValidator.RequiresSecret = true - a.CurrencyPairs.AssetTypes = assets.AssetTypes{ - assets.AssetTypeSpot, + a.CurrencyPairs.AssetTypes = asset.Items{ + asset.Spot, } a.Features = exchange.Features{ @@ -62,7 +62,7 @@ func (a *Alphapoint) SetDefaults() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (a *Alphapoint) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (a *Alphapoint) FetchTradablePairs(asset asset.Item) ([]string, error) { return nil, common.ErrFunctionNotSupported } @@ -100,7 +100,7 @@ func (a *Alphapoint) GetAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (a *Alphapoint) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (a *Alphapoint) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := a.GetTicker(p.String()) if err != nil { @@ -124,7 +124,7 @@ func (a *Alphapoint) UpdateTicker(p currency.Pair, assetType assets.AssetType) ( } // FetchTicker returns the ticker for a currency pair -func (a *Alphapoint) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (a *Alphapoint) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tick, err := ticker.GetTicker(a.GetName(), p, assetType) if err != nil { return a.UpdateTicker(p, assetType) @@ -133,7 +133,7 @@ func (a *Alphapoint) FetchTicker(p currency.Pair, assetType assets.AssetType) (t } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (a *Alphapoint) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (a *Alphapoint) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := a.GetOrderbook(p.String()) if err != nil { @@ -165,7 +165,7 @@ func (a *Alphapoint) UpdateOrderbook(p currency.Pair, assetType assets.AssetType } // FetchOrderbook returns the orderbook for a currency pair -func (a *Alphapoint) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (a *Alphapoint) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(a.GetName(), p, assetType) if err != nil { return a.UpdateOrderbook(p, assetType) @@ -182,7 +182,7 @@ func (a *Alphapoint) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (a *Alphapoint) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (a *Alphapoint) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index fc81f78d..455e9235 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -7,7 +7,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // Please supply your own keys here for due diligence testing @@ -65,7 +65,7 @@ func TestGetCurrencies(t *testing.T) { } func TestFetchTradablePairs(t *testing.T) { - _, err := a.FetchTradablePairs(assets.AssetTypeSpot) + _, err := a.FetchTradablePairs(asset.Spot) if err != nil { t.Fatalf("Test failed. TestGetTradablePairs failed. Err: %s", err) } diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 896fca2e..d3e1610b 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -11,7 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -61,8 +61,8 @@ func (a *ANX) SetDefaults() { a.API.CredentialsValidator.RequiresSecret = true a.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -127,13 +127,13 @@ func (a *ANX) Run() { } forceUpdate := false - if !common.StringDataContains(a.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "_") || - !common.StringDataContains(a.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "_") { + if !common.StringDataContains(a.GetEnabledPairs(asset.Spot).Strings(), "_") || + !common.StringDataContains(a.GetAvailablePairs(asset.Spot).Strings(), "_") { enabledPairs := currency.NewPairsFromStrings([]string{"BTC_USD,BTC_HKD,BTC_EUR,BTC_CAD,BTC_AUD,BTC_SGD,BTC_JPY,BTC_GBP,BTC_NZD,LTC_BTC,DOG_EBTC,STR_BTC,XRP_BTC"}) log.Warn("WARNING: Enabled pairs for ANX reset due to config upgrade, please enable the ones you would like again.") forceUpdate = true - err := a.UpdatePairs(enabledPairs, assets.AssetTypeSpot, true, true) + err := a.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { log.Errorf("%s failed to update currencies.\n", a.GetName()) return @@ -153,16 +153,16 @@ func (a *ANX) Run() { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (a *ANX) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := a.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := a.FetchTradablePairs(asset.Spot) if err != nil { return err } - return a.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return a.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (a *ANX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (a *ANX) FetchTradablePairs(asset asset.Item) ([]string, error) { result, err := a.GetCurrencies() if err != nil { return nil, err @@ -177,7 +177,7 @@ func (a *ANX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (a *ANX) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (a *ANX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := a.GetTicker(a.FormatExchangeCurrency(p, assetType).String()) if err != nil { @@ -249,7 +249,7 @@ func (a *ANX) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker. } // FetchTicker returns the ticker for a currency pair -func (a *ANX) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (a *ANX) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(a.GetName(), p, assetType) if err != nil { return a.UpdateTicker(p, assetType) @@ -258,7 +258,7 @@ func (a *ANX) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.P } // FetchOrderbook returns the orderbook for a currency pair -func (a *ANX) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (a *ANX) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(a.GetName(), p, assetType) if err != nil { return a.UpdateOrderbook(p, assetType) @@ -267,7 +267,7 @@ func (a *ANX) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (order } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (a *ANX) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (a *ANX) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := a.GetDepth(a.FormatExchangeCurrency(p, assetType).String()) if err != nil { @@ -334,7 +334,7 @@ func (a *ANX) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (a *ANX) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (a *ANX) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -485,7 +485,7 @@ func (a *ANX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]ex Amount: resp[i].TradedCurrencyAmount, CurrencyPair: currency.NewPairWithDelimiter(resp[i].TradedCurrency, resp[i].SettlementCurrency, - a.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + a.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), OrderDate: orderDate, Exchange: a.Name, ID: resp[i].OrderID, @@ -527,7 +527,7 @@ func (a *ANX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]ex Status: resp[i].OrderStatus, CurrencyPair: currency.NewPairWithDelimiter(resp[i].TradedCurrency, resp[i].SettlementCurrency, - a.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + a.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), } orders = append(orders, orderDetail) diff --git a/exchanges/asset/asset.go b/exchanges/asset/asset.go new file mode 100644 index 00000000..289eb9fb --- /dev/null +++ b/exchanges/asset/asset.go @@ -0,0 +1,112 @@ +package asset + +import ( + "strings" +) + +// Item stores the asset type +type Item string + +// Items stores a list of assets types +type Items []Item + +// Const vars for asset package +const ( + Spot = Item("spot") + Margin = Item("margin") + Index = Item("index") + Binary = Item("binary") + PerpetualContract = Item("perpetualcontract") + PerpetualSwap = Item("perpetualswap") + Futures = Item("futures") + UpsideProfitContract = Item("upsideprofitcontract") + DownsideProfitContract = Item("downsideprofitcontract") +) + +// Supported returns a list of supported asset types +func Supported() Items { + var a Items + a = append(a, + Spot, + Margin, + Index, + Binary, + PerpetualContract, + PerpetualSwap, + Futures, + UpsideProfitContract, + DownsideProfitContract, + ) + return a +} + +// returns an Item to string +func (a Item) String() string { + return string(a) +} + +// Strings converts an asset type array to a string array +func (a Items) Strings() []string { + var assets []string + for x := range a { + assets = append(assets, string(a[x])) + } + return assets +} + +// Contains returns whether or not the supplied asset exists +// in the list of Items +func (a Items) Contains(asset Item) bool { + if !IsValid(asset) { + return false + } + + for x := range a { + if a[x] == asset { + return true + } + } + + return false +} + +// JoinToString joins an asset type array and converts it to a string +// with the supplied separator +func (a Items) JoinToString(separator string) string { + return strings.Join(a.Strings(), separator) +} + +// IsValid returns whether or not the supplied asset type is valid or +// not +func IsValid(input Item) bool { + a := Supported() + for x := range a { + if strings.EqualFold(a[x].String(), input.String()) { + return true + } + } + return false +} + +// New takes an input of asset types as string and returns an Items +// array +func New(input string) Items { + if !strings.Contains(input, ",") { + if IsValid(Item(input)) { + return Items{ + Item(input), + } + } + return nil + } + + assets := strings.Split(input, ",") + var result Items + for x := range assets { + if !IsValid(Item(assets[x])) { + return nil + } + result = append(result, Item(assets[x])) + } + return result +} diff --git a/exchanges/assets/assets_test.go b/exchanges/asset/asset_test.go similarity index 85% rename from exchanges/assets/assets_test.go rename to exchanges/asset/asset_test.go index b7963ee5..a97199fe 100644 --- a/exchanges/assets/assets_test.go +++ b/exchanges/asset/asset_test.go @@ -1,4 +1,4 @@ -package assets +package asset import ( "testing" @@ -7,14 +7,14 @@ import ( ) func TestString(t *testing.T) { - a := AssetTypeSpot + a := Spot if a.String() != "spot" { t.Fatal("Test failed - TestString returned an unexpected result") } } func TestToStringArray(t *testing.T) { - a := AssetTypes{AssetTypeSpot, AssetTypeFutures} + a := Items{Spot, Futures} result := a.Strings() for x := range a { if !common.StringDataCompare(result, a[x].String()) { @@ -24,22 +24,22 @@ func TestToStringArray(t *testing.T) { } func TestContains(t *testing.T) { - a := AssetTypes{AssetTypeSpot, AssetTypeFutures} + a := Items{Spot, Futures} if a.Contains("meow") { t.Fatal("Test failed - TestContains returned an unexpected result") } - if !a.Contains(AssetTypeSpot) { + if !a.Contains(Spot) { t.Fatal("Test failed - TestContains returned an unexpected result") } - if a.Contains(AssetTypeBinary) { + if a.Contains(Binary) { t.Fatal("Test failed - TestContains returned an unexpected result") } } func TestJoinToString(t *testing.T) { - a := AssetTypes{AssetTypeSpot, AssetTypeFutures} + a := Items{Spot, Futures} if a.JoinToString(",") != "spot,futures" { t.Fatal("Test failed - TestJoinToString returned an unexpected result") } @@ -50,7 +50,7 @@ func TestIsValid(t *testing.T) { t.Fatal("Test failed - TestIsValid returned an unexpected result") } - if !IsValid(AssetTypeSpot) { + if !IsValid(Spot) { t.Fatal("Test failed - TestIsValid returned an unexpected result") } } diff --git a/exchanges/assets/assets.go b/exchanges/assets/assets.go deleted file mode 100644 index 57e50ba3..00000000 --- a/exchanges/assets/assets.go +++ /dev/null @@ -1,112 +0,0 @@ -package assets - -import ( - "strings" -) - -// AssetType stores the asset type -type AssetType string - -// AssetTypes stores a list of assets -type AssetTypes []AssetType - -// Const vars for asset package -const ( - AssetTypeSpot = AssetType("spot") - AssetTypeMargin = AssetType("margin") - AssetTypeIndex = AssetType("index") - AssetTypeBinary = AssetType("binary") - AssetTypePerpetualContract = AssetType("perpetualcontract") - AssetTypePerpetualSwap = AssetType("perpetualswap") - AssetTypeFutures = AssetType("futures") - AssetTypeUpsideProfitContract = AssetType("upsideprofitcontract") - AssetTypeDownsideProfitContract = AssetType("downsideprofitcontract") -) - -// Supported returns a list of supported asset types -func Supported() AssetTypes { - var a AssetTypes - a = append(a, - AssetTypeSpot, - AssetTypeMargin, - AssetTypeIndex, - AssetTypeBinary, - AssetTypePerpetualContract, - AssetTypePerpetualSwap, - AssetTypeFutures, - AssetTypeUpsideProfitContract, - AssetTypeDownsideProfitContract, - ) - return a -} - -// returns an AssetType to string -func (a AssetType) String() string { - return string(a) -} - -// Strings converts an asset type array to a string array -func (a AssetTypes) Strings() []string { - var assets []string - for x := range a { - assets = append(assets, string(a[x])) - } - return assets -} - -// Contains returns whether or not the supplied asset exists -// in the list of AssetTypes -func (a AssetTypes) Contains(asset AssetType) bool { - if !IsValid(asset) { - return false - } - - for x := range a { - if a[x] == asset { - return true - } - } - - return false -} - -// JoinToString joins an asset type array and converts it to a string -// with the supplied separator -func (a AssetTypes) JoinToString(separator string) string { - return strings.Join(a.Strings(), separator) -} - -// IsValid returns whether or not the supplied asset type is valid or -// not -func IsValid(input AssetType) bool { - a := Supported() - for x := range a { - if strings.EqualFold(a[x].String(), input.String()) { - return true - } - } - return false -} - -// New takes an input of asset types as string and returns an AssetTypes -// array -func New(input string) AssetTypes { - if !strings.Contains(input, ",") { - if IsValid(AssetType(input)) { - return AssetTypes{ - AssetType(input), - } - } - return nil - } - - assets := strings.Split(input, ",") - var result AssetTypes - for x := range assets { - if !IsValid(AssetType(assets[x])) { - return nil - } - result = append(result, AssetType(assets[x])) - } - return result -} diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 31b90935..8c753b60 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -17,7 +17,7 @@ import ( "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -537,7 +537,7 @@ func (b *Binance) CheckLimit(limit int) error { } // CheckSymbol checks value against a variable list -func (b *Binance) CheckSymbol(symbol string, assetType assets.AssetType) error { +func (b *Binance) CheckSymbol(symbol string, assetType asset.Item) error { enPairs := b.GetAvailablePairs(assetType) for x := range enPairs { if b.FormatExchangeCurrency(enPairs[x], assetType).String() == symbol { diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 077d91b0..f1e8f64b 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -7,7 +7,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // Please supply your own keys here for due diligence testing @@ -39,7 +39,7 @@ func TestSetup(t *testing.T) { func TestFetchTradablePairs(t *testing.T) { t.Parallel() - _, err := b.FetchTradablePairs(assets.AssetTypeSpot) + _, err := b.FetchTradablePairs(asset.Spot) if err != nil { t.Error("Test Failed - Binance FetchTradablePairs(asset asets.AssetType) error", err) } diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index 2bb61d0d..4a31aacf 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -14,7 +14,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" ) @@ -29,7 +29,7 @@ var m sync.Mutex func (b *Binance) SeedLocalCache(p currency.Pair) error { var newOrderBook orderbook.Base - formattedPair := b.FormatExchangeCurrency(p, assets.AssetTypeSpot) + formattedPair := b.FormatExchangeCurrency(p, asset.Spot) orderbookNew, err := b.GetOrderBook( OrderBookDataRequestParams{ @@ -59,7 +59,7 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error { } newOrderBook.Pair = currency.NewPairFromString(formattedPair.String()) - newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.AssetType = asset.Spot return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false) } @@ -117,7 +117,7 @@ func (b *Binance) UpdateLocalCache(ob *WebsocketDepthStream) error { currencyPair, updatedTime, b.GetName(), - assets.AssetTypeSpot) + asset.Spot) } // WSConnect intiates a websocket connection @@ -129,7 +129,7 @@ func (b *Binance) WSConnect() error { var Dialer websocket.Dialer var err error - pairs := b.GetEnabledPairs(assets.AssetTypeSpot).Strings() + pairs := b.GetEnabledPairs(asset.Spot).Strings() tick := strings.ToLower( strings.Replace( strings.Join(pairs, "@ticker/"), "-", "", -1)) + "@ticker" @@ -164,7 +164,7 @@ func (b *Binance) WSConnect() error { Dialer.Proxy = http.ProxyURL(u) } - for _, ePair := range b.GetEnabledPairs(assets.AssetTypeSpot) { + for _, ePair := range b.GetEnabledPairs(asset.Spot) { err = b.SeedLocalCache(ePair) if err != nil { return err @@ -254,7 +254,7 @@ func (b *Binance) WsHandleData() { Price: price, Amount: amount, Exchange: b.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Side: trade.EventType, } continue @@ -272,7 +272,7 @@ func (b *Binance) WsHandleData() { wsTicker.Timestamp = time.Unix(t.EventTime/1000, 0) wsTicker.Pair = currency.NewPairFromString(t.Symbol) - wsTicker.AssetType = assets.AssetTypeSpot + wsTicker.AssetType = asset.Spot wsTicker.Exchange = b.GetName() wsTicker.ClosePrice, _ = strconv.ParseFloat(t.CurrDayClose, 64) wsTicker.Quantity, _ = strconv.ParseFloat(t.TotalTradedVolume, 64) @@ -297,7 +297,7 @@ func (b *Binance) WsHandleData() { wsKline.Timestamp = time.Unix(0, kline.EventTime) wsKline.Pair = currency.NewPairFromString(kline.Symbol) - wsKline.AssetType = assets.AssetTypeSpot + wsKline.AssetType = asset.Spot wsKline.Exchange = b.GetName() wsKline.StartTime = time.Unix(0, kline.Kline.StartTime) wsKline.CloseTime = time.Unix(0, kline.Kline.CloseTime) @@ -331,7 +331,7 @@ func (b *Binance) WsHandleData() { b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: currencyPair, - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Exchange: b.GetName(), } continue diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index ac66d9d4..fdf1b815 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -52,8 +52,8 @@ func (b *Binance) SetDefaults() { b.SetValues() b.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, @@ -136,13 +136,13 @@ func (b *Binance) Run() { } forceUpdate := false - if !common.StringDataContains(b.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "-") || - !common.StringDataContains(b.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "-") { + if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), "-") || + !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), "-") { enabledPairs := currency.NewPairsFromStrings([]string{"BTC-USDT"}) log.Warn("WARNING: Available pairs for Binance reset due to config upgrade, please enable the ones you would like again") forceUpdate = true - err := b.UpdatePairs(enabledPairs, assets.AssetTypeSpot, true, true) + err := b.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { log.Errorf("%s failed to update currencies. Err: %s\n", b.Name, err) } @@ -159,7 +159,7 @@ func (b *Binance) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *Binance) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (b *Binance) FetchTradablePairs(asset asset.Item) ([]string, error) { var validCurrencyPairs []string info, err := b.GetExchangeInfo() @@ -179,16 +179,16 @@ func (b *Binance) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *Binance) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := b.FetchTradablePairs(asset.Spot) if err != nil { return err } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Binance) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Binance) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := b.GetTickers() if err != nil { @@ -215,7 +215,7 @@ func (b *Binance) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tic } // FetchTicker returns the ticker for a currency pair -func (b *Binance) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Binance) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -224,7 +224,7 @@ func (b *Binance) FetchTicker(p currency.Pair, assetType assets.AssetType) (tick } // FetchOrderbook returns orderbook base on the currency pair -func (b *Binance) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Binance) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -233,7 +233,7 @@ func (b *Binance) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (o } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Binance) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Binance) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := b.GetOrderBook(OrderBookDataRequestParams{Symbol: b.FormatExchangeCurrency(p, assetType).String(), Limit: 1000}) @@ -307,7 +307,7 @@ func (b *Binance) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Binance) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (b *Binance) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -456,7 +456,7 @@ func (b *Binance) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( var orders []exchange.OrderDetail for _, c := range getOrdersRequest.Currencies { resp, err := b.OpenOrders(b.FormatExchangeCurrency(c, - assets.AssetTypeSpot).String()) + asset.Spot).String()) if err != nil { return nil, err } @@ -496,7 +496,7 @@ func (b *Binance) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( var orders []exchange.OrderDetail for _, c := range getOrdersRequest.Currencies { resp, err := b.AllOrders(b.FormatExchangeCurrency(c, - assets.AssetTypeSpot).String(), "", "1000") + asset.Spot).String(), "", "1000") if err != nil { return nil, err } diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 1ab0a44d..8f264efd 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -14,7 +14,7 @@ import ( "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -282,7 +282,7 @@ func (b *Bitfinex) WsDataHandler() { if len(newOrderbook) > 1 { err := b.WsInsertSnapshot(currency.NewPairFromString(chanInfo.Pair), - assets.AssetTypeSpot, + asset.Spot, newOrderbook) if err != nil { @@ -293,7 +293,7 @@ func (b *Bitfinex) WsDataHandler() { } err := b.WsUpdateOrderbook(currency.NewPairFromString(chanInfo.Pair), - assets.AssetTypeSpot, + asset.Spot, newOrderbook[0]) if err != nil { @@ -309,7 +309,7 @@ func (b *Bitfinex) WsDataHandler() { LowPrice: chanData[10].(float64), Pair: currency.NewPairFromString(chanInfo.Pair), Exchange: b.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, } case "account": @@ -465,7 +465,7 @@ func (b *Bitfinex) WsDataHandler() { Price: trades[0].Price, Amount: newAmount, Exchange: b.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Side: side, } } @@ -478,7 +478,7 @@ func (b *Bitfinex) WsDataHandler() { // WsInsertSnapshot add the initial orderbook snapshot when subscribed to a // channel -func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType assets.AssetType, books []WebsocketBook) error { +func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books []WebsocketBook) error { if len(books) == 0 { return errors.New("bitfinex.go error - no orderbooks submitted") } @@ -515,7 +515,7 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType assets.AssetType, // WsUpdateOrderbook updates the orderbook list, removing and adding to the // orderbook sides -func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType assets.AssetType, book WebsocketBook) error { +func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book WebsocketBook) error { if book.Count > 0 { if book.Amount > 0 { @@ -605,7 +605,7 @@ func (b *Bitfinex) GenerateDefaultSubscriptions() { var channels = []string{"book", "trades", "ticker"} subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { - enabledPairs := b.GetEnabledPairs(assets.AssetTypeSpot) + enabledPairs := b.GetEnabledPairs(asset.Spot) for j := range enabledPairs { params := make(map[string]interface{}) if channels[i] == "book" { diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index eff51be5..72e98e78 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -13,7 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -53,8 +53,8 @@ func (b *Bitfinex) SetDefaults() { b.API.CredentialsValidator.RequiresSecret = true b.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -145,23 +145,23 @@ func (b *Bitfinex) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *Bitfinex) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (b *Bitfinex) FetchTradablePairs(asset asset.Item) ([]string, error) { return b.GetSymbols() } // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *Bitfinex) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := b.FetchTradablePairs(asset.Spot) if err != nil { return err } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price enabledPairs := b.GetEnabledPairs(assetType) @@ -197,8 +197,8 @@ func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ti } // FetchTicker returns the ticker for a currency pair -func (b *Bitfinex) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { - tick, err := ticker.GetTicker(b.GetName(), p, assets.AssetTypeSpot) +func (b *Bitfinex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { + tick, err := ticker.GetTicker(b.GetName(), p, asset.Spot) if err != nil { return b.UpdateTicker(p, assetType) } @@ -206,7 +206,7 @@ func (b *Bitfinex) FetchTicker(p currency.Pair, assetType assets.AssetType) (tic } // FetchOrderbook returns the orderbook for a currency pair -func (b *Bitfinex) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bitfinex) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -215,7 +215,7 @@ func (b *Bitfinex) FetchOrderbook(p currency.Pair, assetType assets.AssetType) ( } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base urlVals := url.Values{} urlVals.Set("limit_bids", "100") @@ -290,7 +290,7 @@ func (b *Bitfinex) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index 7e712f31..fb669997 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -7,7 +7,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -135,7 +135,7 @@ func TestFetchTicker(t *testing.T) { t.Parallel() var p currency.Pair - currencies := b.GetAvailablePairs(assets.AssetTypeSpot) + currencies := b.GetAvailablePairs(asset.Spot) for _, pair := range currencies { if pair.String() == "FXBTC_JPY" { p = pair @@ -143,7 +143,7 @@ func TestFetchTicker(t *testing.T) { } } - _, err := b.FetchTicker(p, assets.AssetTypeSpot) + _, err := b.FetchTicker(p, asset.Spot) if err != nil { t.Error("test failed - Bitflyer - FetchTicker() error", err) } diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index dc502b2e..0a5f485f 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -10,7 +10,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -49,9 +49,9 @@ func (b *Bitflyer) SetDefaults() { b.API.CredentialsValidator.RequiresSecret = true b.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, - assets.AssetTypeFutures, + AssetTypes: asset.Items{ + asset.Spot, + asset.Futures, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -127,7 +127,7 @@ func (b *Bitflyer) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *Bitflyer) FetchTradablePairs(assetType assets.AssetType) ([]string, error) { +func (b *Bitflyer) FetchTradablePairs(assetType asset.Item) ([]string, error) { pairs, err := b.GetMarkets() if err != nil { return nil, err @@ -135,9 +135,9 @@ func (b *Bitflyer) FetchTradablePairs(assetType assets.AssetType) ([]string, err var products []string for _, info := range pairs { - if info.Alias != "" && assetType == assets.AssetTypeFutures { + if info.Alias != "" && assetType == asset.Futures { products = append(products, info.Alias) - } else if info.Alias == "" && assetType == assets.AssetTypeSpot && strings.Contains(info.ProductCode, "_") { + } else if info.Alias == "" && assetType == asset.Spot && strings.Contains(info.ProductCode, "_") { products = append(products, info.ProductCode) } } @@ -163,7 +163,7 @@ func (b *Bitflyer) UpdateTradablePairs(forceUpdate bool) error { } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price p = b.CheckFXString(p) @@ -189,7 +189,7 @@ func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ti } // FetchTicker returns the ticker for a currency pair -func (b *Bitflyer) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bitflyer) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tick, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -207,7 +207,7 @@ func (b *Bitflyer) CheckFXString(p currency.Pair) currency.Pair { } // FetchOrderbook returns the orderbook for a currency pair -func (b *Bitflyer) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bitflyer) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -216,7 +216,7 @@ func (b *Bitflyer) FetchOrderbook(p currency.Pair, assetType assets.AssetType) ( } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bitflyer) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bitflyer) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base p = b.CheckFXString(p) @@ -272,7 +272,7 @@ func (b *Bitflyer) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bitflyer) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (b *Bitflyer) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 3ac1b575..333b968b 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -13,7 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -52,8 +52,8 @@ func (b *Bithumb) SetDefaults() { b.API.CredentialsValidator.RequiresSecret = true b.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, @@ -126,7 +126,7 @@ func (b *Bithumb) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *Bithumb) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (b *Bithumb) FetchTradablePairs(asset asset.Item) ([]string, error) { currencies, err := b.GetTradablePairs() if err != nil { return nil, err @@ -142,16 +142,16 @@ func (b *Bithumb) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *Bithumb) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := b.FetchTradablePairs(asset.Spot) if err != nil { return err } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bithumb) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bithumb) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tickers, err := b.GetAllTickers() @@ -179,7 +179,7 @@ func (b *Bithumb) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tic } // FetchTicker returns the ticker for a currency pair -func (b *Bithumb) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bithumb) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -188,7 +188,7 @@ func (b *Bithumb) FetchTicker(p currency.Pair, assetType assets.AssetType) (tick } // FetchOrderbook returns orderbook base on the currency pair -func (b *Bithumb) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bithumb) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -197,7 +197,7 @@ func (b *Bithumb) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (o } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bithumb) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bithumb) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base currency := p.Base.String() @@ -266,7 +266,7 @@ func (b *Bithumb) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bithumb) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (b *Bithumb) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -334,7 +334,7 @@ func (b *Bithumb) CancelAllOrders(orderCancellation *exchange.OrderCancellation) } var allOrders []OrderData - for _, currency := range b.GetEnabledPairs(assets.AssetTypeSpot) { + for _, currency := range b.GetEnabledPairs(asset.Spot) { orders, err := b.GetOrders("", orderCancellation.Side.ToString(), "100", @@ -447,7 +447,7 @@ func (b *Bithumb) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( Status: string(exchange.ActiveOrderStatus), CurrencyPair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency, resp.Data[i].PaymentCurrency, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), } if resp.Data[i].Type == "bid" { @@ -490,7 +490,7 @@ func (b *Bithumb) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( RemainingAmount: resp.Data[i].UnitsRemaining, CurrencyPair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency, resp.Data[i].PaymentCurrency, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), } if resp.Data[i].Type == "bid" { diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 9c240510..e8f3be7a 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -14,7 +14,7 @@ import ( "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -289,17 +289,17 @@ func (b *Bitmex) wsHandleIncomingData() { } } -var snapshotloaded = make(map[currency.Pair]map[assets.AssetType]bool) +var snapshotloaded = make(map[currency.Pair]map[asset.Item]bool) // ProcessOrderbook processes orderbook updates -func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPair currency.Pair, assetType assets.AssetType) error { // nolint: unparam +func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPair currency.Pair, assetType asset.Item) error { // nolint: unparam if len(data) < 1 { return errors.New("bitmex_websocket.go error - no orderbook data") } _, ok := snapshotloaded[currencyPair] if !ok { - snapshotloaded[currencyPair] = make(map[assets.AssetType]bool) + snapshotloaded[currencyPair] = make(map[asset.Item]bool) } _, ok = snapshotloaded[currencyPair][assetType] @@ -389,7 +389,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (b *Bitmex) GenerateDefaultSubscriptions() { - contracts := b.GetEnabledPairs(assets.AssetTypePerpetualContract) + contracts := b.GetEnabledPairs(asset.PerpetualContract) channels := []string{bitmexWSOrderbookL2, bitmexWSTrade} subscriptions := []exchange.WebsocketChannelSubscription{ { diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 7368ed03..5cd59837 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -51,11 +51,11 @@ func (b *Bitmex) SetDefaults() { b.API.CredentialsValidator.RequiresSecret = true b.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypePerpetualContract, - assets.AssetTypeFutures, - assets.AssetTypeDownsideProfitContract, - assets.AssetTypeUpsideProfitContract, + AssetTypes: asset.Items{ + asset.PerpetualContract, + asset.Futures, + asset.DownsideProfitContract, + asset.UpsideProfitContract, }, UseGlobalFormat: false, } @@ -69,8 +69,8 @@ func (b *Bitmex) SetDefaults() { Uppercase: true, }, } - b.CurrencyPairs.Store(assets.AssetTypePerpetualContract, fmt1) - b.CurrencyPairs.Store(assets.AssetTypeFutures, fmt1) + b.CurrencyPairs.Store(asset.PerpetualContract, fmt1) + b.CurrencyPairs.Store(asset.Futures, fmt1) // Upside and Downside profit contracts use the same format fmt2 := currency.PairStore{ @@ -83,8 +83,8 @@ func (b *Bitmex) SetDefaults() { Uppercase: true, }, } - b.CurrencyPairs.Store(assets.AssetTypeDownsideProfitContract, fmt2) - b.CurrencyPairs.Store(assets.AssetTypeUpsideProfitContract, fmt2) + b.CurrencyPairs.Store(asset.DownsideProfitContract, fmt2) + b.CurrencyPairs.Store(asset.UpsideProfitContract, fmt2) b.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ @@ -167,7 +167,7 @@ func (b *Bitmex) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *Bitmex) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (b *Bitmex) FetchTradablePairs(asset asset.Item) ([]string, error) { marketInfo, err := b.GetActiveInstruments(&GenericRequestParams{}) if err != nil { return nil, err @@ -184,7 +184,7 @@ func (b *Bitmex) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *Bitmex) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := b.FetchTradablePairs(asset.Spot) if err != nil { return err } @@ -192,25 +192,25 @@ func (b *Bitmex) UpdateTradablePairs(forceUpdate bool) error { var assetPairs []string for x := range b.CurrencyPairs.AssetTypes { switch b.CurrencyPairs.AssetTypes[x] { - case assets.AssetTypePerpetualContract: + case asset.PerpetualContract: for y := range pairs { if strings.Contains(pairs[y], "USD") { assetPairs = append(assetPairs, pairs[y]) } } - case assets.AssetTypeFutures: + case asset.Futures: for y := range pairs { if strings.Contains(pairs[y], "19") { assetPairs = append(assetPairs, pairs[y]) } } - case assets.AssetTypeDownsideProfitContract: + case asset.DownsideProfitContract: for y := range pairs { if strings.Contains(pairs[y], "_D") { assetPairs = append(assetPairs, pairs[y]) } } - case assets.AssetTypeUpsideProfitContract: + case asset.UpsideProfitContract: for y := range pairs { if strings.Contains(pairs[y], "_U") { assetPairs = append(assetPairs, pairs[y]) @@ -228,7 +228,7 @@ func (b *Bitmex) UpdateTradablePairs(forceUpdate bool) error { } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bitmex) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bitmex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price currency := b.FormatExchangeCurrency(p, assetType) @@ -251,7 +251,7 @@ func (b *Bitmex) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tick } // FetchTicker returns the ticker for a currency pair -func (b *Bitmex) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bitmex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -260,7 +260,7 @@ func (b *Bitmex) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticke } // FetchOrderbook returns orderbook base on the currency pair -func (b *Bitmex) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bitmex) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -269,7 +269,7 @@ func (b *Bitmex) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (or } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bitmex) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bitmex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(OrderBookGetL2Params{ @@ -338,7 +338,7 @@ func (b *Bitmex) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bitmex) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (b *Bitmex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -519,7 +519,7 @@ func (b *Bitmex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ Status: resp[i].OrdStatus, CurrencyPair: currency.NewPairWithDelimiter(resp[i].Symbol, resp[i].SettlCurrency, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), } orders = append(orders, orderDetail) @@ -561,7 +561,7 @@ func (b *Bitmex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ Status: resp[i].OrdStatus, CurrencyPair: currency.NewPairWithDelimiter(resp[i].Symbol, resp[i].SettlCurrency, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), } orders = append(orders, orderDetail) diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index aeb61fbd..4dff9dcc 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -13,7 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -121,7 +121,7 @@ func (b *Bitstamp) WsHandleData() { currencyPair := strings.Split(wsResponse.Channel, "_") p := currency.NewPairFromString(strings.ToUpper(currencyPair[3])) - err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, assets.AssetTypeSpot) + err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, asset.Spot) if err != nil { b.Websocket.DataHandler <- err continue @@ -144,7 +144,7 @@ func (b *Bitstamp) WsHandleData() { Amount: wsTradeTemp.Data.Amount, CurrencyPair: p, Exchange: b.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, } } } @@ -153,7 +153,7 @@ func (b *Bitstamp) WsHandleData() { func (b *Bitstamp) generateDefaultSubscriptions() { var channels = []string{"live_trades_", "diff_order_book_"} - enabledCurrencies := b.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := b.GetEnabledPairs(asset.Spot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { for j := range enabledCurrencies { @@ -193,7 +193,7 @@ func (b *Bitstamp) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubsc return b.WebsocketConn.WriteJSON(req) } -func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, assetType assets.AssetType) error { +func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, assetType asset.Item) error { if len(ob.Asks) == 0 && len(ob.Bids) == 0 { return errors.New("bitstamp_websocket.go error - no orderbook data") } @@ -251,7 +251,7 @@ func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, ass } func (b *Bitstamp) seedOrderBook() error { - p := b.GetEnabledPairs(assets.AssetTypeSpot) + p := b.GetEnabledPairs(asset.Spot) for x := range p { orderbookSeed, err := b.GetOrderbook(p[x].String()) if err != nil { @@ -278,7 +278,7 @@ func (b *Bitstamp) seedOrderBook() error { newOrderBook.Asks = asks newOrderBook.Bids = bids newOrderBook.Pair = p[x] - newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.AssetType = asset.Spot err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false) if err != nil { @@ -287,7 +287,7 @@ func (b *Bitstamp) seedOrderBook() error { b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p[x], - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Exchange: b.GetName(), } } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index a28841ab..33a090b9 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -52,8 +52,8 @@ func (b *Bitstamp) SetDefaults() { b.API.CredentialsValidator.RequiresClientID = true b.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -144,7 +144,7 @@ func (b *Bitstamp) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *Bitstamp) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (b *Bitstamp) FetchTradablePairs(asset asset.Item) ([]string, error) { pairs, err := b.GetTradingPairs() if err != nil { return nil, err @@ -166,16 +166,16 @@ func (b *Bitstamp) FetchTradablePairs(asset assets.AssetType) ([]string, error) // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *Bitstamp) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := b.FetchTradablePairs(asset.Spot) if err != nil { return err } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := b.GetTicker(p.String(), false) if err != nil { @@ -199,7 +199,7 @@ func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ti } // FetchTicker returns the ticker for a currency pair -func (b *Bitstamp) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bitstamp) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tick, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -218,7 +218,7 @@ func (b *Bitstamp) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error } // FetchOrderbook returns the orderbook for a currency pair -func (b *Bitstamp) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bitstamp) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -227,7 +227,7 @@ func (b *Bitstamp) FetchOrderbook(p currency.Pair, assetType assets.AssetType) ( } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bitstamp) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bitstamp) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(p.String()) if err != nil { @@ -303,7 +303,7 @@ func (b *Bitstamp) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bitstamp) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (b *Bitstamp) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -505,7 +505,7 @@ func (b *Bitstamp) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) if quoteCurrency.String() != "" && baseCurrency.String() != "" { currPair = currency.NewPairWithDelimiter(baseCurrency.String(), quoteCurrency.String(), - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) } orderDate := time.Unix(order.Date, 0) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 9d418cf5..77129ac4 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -11,7 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -50,8 +50,8 @@ func (b *Bittrex) SetDefaults() { b.API.CredentialsValidator.RequiresSecret = true b.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -115,13 +115,13 @@ func (b *Bittrex) Run() { } forceUpdate := false - if !common.StringDataContains(b.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "-") || - !common.StringDataContains(b.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "-") { + if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), "-") || + !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), "-") { forceUpdate = true enabledPairs := []string{"USDT-BTC"} log.Warn("WARNING: Available pairs for Bittrex reset due to config upgrade, please enable the ones you would like again") - err := b.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), assets.AssetTypeSpot, true, true) + err := b.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), asset.Spot, true, true) if err != nil { log.Errorf("%s failed to update currencies. Err: %s\n", b.Name, err) } @@ -138,7 +138,7 @@ func (b *Bittrex) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *Bittrex) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (b *Bittrex) FetchTradablePairs(asset asset.Item) ([]string, error) { markets, err := b.GetMarkets() if err != nil { return nil, err @@ -158,12 +158,12 @@ func (b *Bittrex) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *Bittrex) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := b.FetchTradablePairs(asset.Spot) if err != nil { return err } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // GetAccountInfo Retrieves balances for all enabled currencies for the @@ -193,7 +193,7 @@ func (b *Bittrex) GetAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bittrex) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bittrex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := b.GetMarketSummaries() if err != nil { @@ -220,7 +220,7 @@ func (b *Bittrex) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tic } // FetchTicker returns the ticker for a currency pair -func (b *Bittrex) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *Bittrex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tick, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -229,7 +229,7 @@ func (b *Bittrex) FetchTicker(p currency.Pair, assetType assets.AssetType) (tick } // FetchOrderbook returns the orderbook for a currency pair -func (b *Bittrex) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bittrex) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -238,7 +238,7 @@ func (b *Bittrex) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (o } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bittrex) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *Bittrex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(b.FormatExchangeCurrency(p, assetType).String()) if err != nil { @@ -283,7 +283,7 @@ func (b *Bittrex) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *Bittrex) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (b *Bittrex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -429,7 +429,7 @@ func (b *Bittrex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( } pair := currency.NewPairDelimiter(resp.Result[i].Exchange, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderType := exchange.OrderType(strings.ToUpper(resp.Result[i].Type)) orders = append(orders, exchange.OrderDetail{ @@ -473,7 +473,7 @@ func (b *Bittrex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( } pair := currency.NewPairDelimiter(resp.Result[i].Exchange, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderType := exchange.OrderType(strings.ToUpper(resp.Result[i].Type)) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 79d2f6a0..7921b0af 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -54,8 +54,8 @@ func (b *BTCMarkets) SetDefaults() { b.API.Endpoints.URL = b.API.Endpoints.URLDefault b.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -115,13 +115,13 @@ func (b *BTCMarkets) Run() { } forceUpdate := false - if !common.StringDataContains(b.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "-") || - !common.StringDataContains(b.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "-") { + if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), "-") || + !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), "-") { enabledPairs := []string{"BTC-AUD"} log.Println("WARNING: Available pairs for BTC Makrets reset due to config upgrade, please enable the pairs you would like again.") forceUpdate = true - err := b.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), assets.AssetTypeSpot, true, true) + err := b.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), asset.Spot, true, true) if err != nil { log.Errorf("%s failed to update currencies. Err: %s", b.Name, err) } @@ -138,7 +138,7 @@ func (b *BTCMarkets) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *BTCMarkets) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (b *BTCMarkets) FetchTradablePairs(asset asset.Item) ([]string, error) { markets, err := b.GetMarkets() if err != nil { return nil, err @@ -155,16 +155,16 @@ func (b *BTCMarkets) FetchTradablePairs(asset assets.AssetType) ([]string, error // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *BTCMarkets) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := b.FetchTradablePairs(asset.Spot) if err != nil { return err } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := b.GetTicker(p.Base.String(), p.Quote.String()) if err != nil { @@ -184,7 +184,7 @@ func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType assets.AssetType) ( } // FetchTicker returns the ticker for a currency pair -func (b *BTCMarkets) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *BTCMarkets) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -193,7 +193,7 @@ func (b *BTCMarkets) FetchTicker(p currency.Pair, assetType assets.AssetType) (t } // FetchOrderbook returns orderbook base on the currency pair -func (b *BTCMarkets) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *BTCMarkets) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -202,7 +202,7 @@ func (b *BTCMarkets) FetchOrderbook(p currency.Pair, assetType assets.AssetType) } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(p.Base.String(), p.Quote.String()) @@ -268,7 +268,7 @@ func (b *BTCMarkets) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *BTCMarkets) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (b *BTCMarkets) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -399,7 +399,7 @@ func (b *BTCMarkets) GetOrderInfo(orderID string) (exchange.OrderDetail, error) OrderDetail.Status = orders[i].Status OrderDetail.CurrencyPair = currency.NewPairWithDelimiter(orders[i].Instrument, orders[i].Currency, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) } return OrderDetail, nil @@ -542,7 +542,7 @@ func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest Status: respOrders[i].Status, CurrencyPair: currency.NewPairWithDelimiter(respOrders[i].Instrument, respOrders[i].Currency, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), } for j := range respOrders[i].Trades { diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index 245cb2f2..f7c0a42b 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" @@ -123,7 +123,7 @@ func (b *BTSE) WsHandleData() { b.Websocket.DataHandler <- exchange.TickerData{ Timestamp: time.Now(), Pair: currency.NewPairDelimiter(t.ProductID, "-"), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Exchange: b.GetName(), ClosePrice: price, Quantity: t.LastSize, @@ -184,7 +184,7 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error { } p := currency.NewPairDelimiter(snapshot.ProductID, "-") - base.AssetType = assets.AssetTypeSpot + base.AssetType = asset.Spot base.Pair = p base.LastUpdated = time.Now() base.ExchangeName = b.Name @@ -196,7 +196,7 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error { b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p, - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Exchange: b.GetName(), } @@ -206,7 +206,7 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (b *BTSE) GenerateDefaultSubscriptions() { var channels = []string{"snapshot", "ticker"} - enabledCurrencies := b.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := b.GetEnabledPairs(asset.Spot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { for j := range enabledCurrencies { diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index d9665df8..9ee5fff9 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -11,7 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -50,8 +50,8 @@ func (b *BTSE) SetDefaults() { b.API.CredentialsValidator.RequiresSecret = true b.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -140,7 +140,7 @@ func (b *BTSE) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *BTSE) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (b *BTSE) FetchTradablePairs(asset asset.Item) ([]string, error) { markets, err := b.GetMarkets() if err != nil { return nil, err @@ -157,16 +157,16 @@ func (b *BTSE) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *BTSE) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := b.FetchTradablePairs(asset.Spot) if err != nil { return err } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (b *BTSE) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *BTSE) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price t, err := b.GetTicker(b.FormatExchangeCurrency(p, @@ -198,7 +198,7 @@ func (b *BTSE) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker } // FetchTicker returns the ticker for a currency pair -func (b *BTSE) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (b *BTSE) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { return b.UpdateTicker(p, assetType) @@ -207,7 +207,7 @@ func (b *BTSE) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker. } // FetchOrderbook returns orderbook base on the currency pair -func (b *BTSE) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *BTSE) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -216,7 +216,7 @@ func (b *BTSE) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orde } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *BTSE) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (b *BTSE) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { return orderbook.Base{}, common.ErrFunctionNotSupported } @@ -255,7 +255,7 @@ func (b *BTSE) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -272,7 +272,7 @@ func (b *BTSE) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrde r, err := b.CreateOrder(order.Amount, order.Price, order.OrderSide.ToString(), order.OrderType.ToString(), b.FormatExchangeCurrency(order.Pair, - assets.AssetTypeSpot).String(), "GTC", order.ClientID) + asset.Spot).String(), "GTC", order.ClientID) if err != nil { return resp, err } @@ -295,7 +295,7 @@ func (b *BTSE) ModifyOrder(action *exchange.ModifyOrder) (string, error) { func (b *BTSE) CancelOrder(order *exchange.OrderCancellation) error { r, err := b.CancelExistingOrder(order.OrderID, b.FormatExchangeCurrency(order.CurrencyPair, - assets.AssetTypeSpot).String()) + asset.Spot).String()) if err != nil { return err } @@ -316,7 +316,7 @@ func (b *BTSE) CancelOrder(order *exchange.OrderCancellation) error { func (b *BTSE) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { var resp exchange.CancelAllOrdersResponse r, err := b.CancelOrders(b.FormatExchangeCurrency( - orderCancellation.CurrencyPair, assets.AssetTypeSpot).String()) + orderCancellation.CurrencyPair, asset.Spot).String()) if err != nil { return resp, err } @@ -355,7 +355,7 @@ func (b *BTSE) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { } od.CurrencyPair = currency.NewPairDelimiter(o.ProductID, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) od.Exchange = b.Name od.Amount = o.Amount od.ID = o.ID @@ -432,7 +432,7 @@ func (b *BTSE) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]e openOrder := exchange.OrderDetail{ CurrencyPair: currency.NewPairDelimiter(order.ProductID, - b.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), Exchange: b.Name, Amount: order.Amount, ID: order.ID, diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 6cec637a..02cdc693 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -113,7 +113,7 @@ func (c *CoinbasePro) WsHandleData() { c.Websocket.DataHandler <- exchange.TickerData{ Timestamp: ticker.Time, Pair: currency.NewPairFromString(ticker.ProductID), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Exchange: c.GetName(), OpenPrice: ticker.Open24H, HighPrice: ticker.High24H, @@ -188,7 +188,7 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro } p := currency.NewPairFromString(snapshot.ProductID) - base.AssetType = assets.AssetTypeSpot + base.AssetType = asset.Spot base.Pair = p base.LastUpdated = time.Now() @@ -199,7 +199,7 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p, - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Exchange: c.GetName(), } @@ -227,14 +227,14 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { p := currency.NewPairFromString(update.ProductID) - err := c.Websocket.Orderbook.Update(Bids, Asks, p, time.Now(), c.GetName(), assets.AssetTypeSpot) + err := c.Websocket.Orderbook.Update(Bids, Asks, p, time.Now(), c.GetName(), asset.Spot) if err != nil { return err } c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p, - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Exchange: c.GetName(), } @@ -244,7 +244,7 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (c *CoinbasePro) GenerateDefaultSubscriptions() { var channels = []string{"heartbeat", "level2", "ticker"} - enabledCurrencies := c.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := c.GetEnabledPairs(asset.Spot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { for j := range enabledCurrencies { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 5325b53e..06c12a73 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -11,7 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -52,8 +52,8 @@ func (c *CoinbasePro) SetDefaults() { c.API.CredentialsValidator.RequiresBase64DecodeSecret = true c.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -144,7 +144,7 @@ func (c *CoinbasePro) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (c *CoinbasePro) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (c *CoinbasePro) FetchTradablePairs(asset asset.Item) ([]string, error) { pairs, err := c.GetProducts() if err != nil { return nil, err @@ -161,12 +161,12 @@ func (c *CoinbasePro) FetchTradablePairs(asset assets.AssetType) ([]string, erro // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (c *CoinbasePro) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := c.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := c.FetchTradablePairs(asset.Spot) if err != nil { return err } - return c.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return c.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // GetAccountInfo retrieves balances for all enabled currencies for the @@ -197,7 +197,7 @@ func (c *CoinbasePro) GetAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := c.GetTicker(c.FormatExchangeCurrency(p, assetType).String()) if err != nil { @@ -225,7 +225,7 @@ func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType assets.AssetType) } // FetchTicker returns the ticker for a currency pair -func (c *CoinbasePro) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (c *CoinbasePro) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(c.GetName(), p, assetType) if err != nil { return c.UpdateTicker(p, assetType) @@ -234,7 +234,7 @@ func (c *CoinbasePro) FetchTicker(p currency.Pair, assetType assets.AssetType) ( } // FetchOrderbook returns orderbook base on the currency pair -func (c *CoinbasePro) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (c *CoinbasePro) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(c.GetName(), p, assetType) if err != nil { return c.UpdateOrderbook(p, assetType) @@ -243,7 +243,7 @@ func (c *CoinbasePro) FetchOrderbook(p currency.Pair, assetType assets.AssetType } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (c *CoinbasePro) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (c *CoinbasePro) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := c.GetOrderbook(c.FormatExchangeCurrency(p, assetType).String(), 2) if err != nil { @@ -280,7 +280,7 @@ func (c *CoinbasePro) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (c *CoinbasePro) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (c *CoinbasePro) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -418,7 +418,7 @@ func (c *CoinbasePro) GetActiveOrders(getOrdersRequest *exchange.GetOrdersReques var respOrders []GeneralizedOrderResponse for i := range getOrdersRequest.Currencies { resp, err := c.GetOrders([]string{"open", "pending", "active"}, - c.FormatExchangeCurrency(getOrdersRequest.Currencies[i], assets.AssetTypeSpot).String()) + c.FormatExchangeCurrency(getOrdersRequest.Currencies[i], asset.Spot).String()) if err != nil { return nil, err } @@ -428,7 +428,7 @@ func (c *CoinbasePro) GetActiveOrders(getOrdersRequest *exchange.GetOrdersReques var orders []exchange.OrderDetail for i := range respOrders { currency := currency.NewPairDelimiter(respOrders[i].ProductID, - c.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + c.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderSide := exchange.OrderSide(strings.ToUpper(respOrders[i].Side)) orderType := exchange.OrderType(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) @@ -461,7 +461,7 @@ func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersReques var respOrders []GeneralizedOrderResponse for _, currency := range getOrdersRequest.Currencies { resp, err := c.GetOrders([]string{"done", "settled"}, - c.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String()) + c.FormatExchangeCurrency(currency, asset.Spot).String()) if err != nil { return nil, err } @@ -471,7 +471,7 @@ func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersReques var orders []exchange.OrderDetail for i := range respOrders { currency := currency.NewPairDelimiter(respOrders[i].ProductID, - c.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + c.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderSide := exchange.OrderSide(strings.ToUpper(respOrders[i].Side)) orderType := exchange.OrderType(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index a70481eb..bd0ef301 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -10,7 +10,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -83,7 +83,7 @@ func (c *COINUT) WsHandleData() { Timestamp: time.Unix(0, ticker.Timestamp), Pair: currency.NewPairFromString(currencyPair), Exchange: c.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, HighPrice: ticker.HighestBuy, LowPrice: ticker.LowestSell, ClosePrice: ticker.Last, @@ -108,7 +108,7 @@ func (c *COINUT) WsHandleData() { c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: c.GetName(), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Pair: currency.NewPairFromString(currencyPair), } @@ -130,7 +130,7 @@ func (c *COINUT) WsHandleData() { c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: c.GetName(), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Pair: currency.NewPairFromString(currencyPair), } @@ -155,7 +155,7 @@ func (c *COINUT) WsHandleData() { c.Websocket.DataHandler <- exchange.TradeData{ Timestamp: time.Unix(tradeUpdate.Timestamp, 0), CurrencyPair: currency.NewPairFromString(currencyPair), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Exchange: c.GetName(), Price: tradeUpdate.Price, Side: tradeUpdate.Side, @@ -281,7 +281,7 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { newOrderBook.Asks = asks newOrderBook.Bids = bids newOrderBook.Pair = currency.NewPairFromString(instrumentListByCode[ob.InstID]) - newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.AssetType = asset.Spot newOrderBook.LastUpdated = time.Now() return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook, c.GetName(), false) @@ -298,7 +298,7 @@ func (c *COINUT) WsProcessOrderbookUpdate(ob *WsOrderbookUpdate) error { p, time.Now(), c.GetName(), - assets.AssetTypeSpot) + asset.Spot) } return c.Websocket.Orderbook.Update([]orderbook.Item{ @@ -307,14 +307,14 @@ func (c *COINUT) WsProcessOrderbookUpdate(ob *WsOrderbookUpdate) error { p, time.Now(), c.GetName(), - assets.AssetTypeSpot) + asset.Spot) } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (c *COINUT) GenerateDefaultSubscriptions() { var channels = []string{"inst_tick", "inst_order_book"} subscriptions := []exchange.WebsocketChannelSubscription{} - enabledCurrencies := c.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := c.GetEnabledPairs(asset.Spot) for i := range channels { for j := range enabledCurrencies { subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{ diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index a1958a6e..6bedf032 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -11,7 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -50,8 +50,8 @@ func (c *COINUT) SetDefaults() { c.API.CredentialsValidator.RequiresClientID = true c.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -142,7 +142,7 @@ func (c *COINUT) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (c *COINUT) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (c *COINUT) FetchTradablePairs(asset asset.Item) ([]string, error) { i, err := c.GetInstruments() if err != nil { return nil, err @@ -161,12 +161,12 @@ func (c *COINUT) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (c *COINUT) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := c.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := c.FetchTradablePairs(asset.Spot) if err != nil { return err } - return c.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return c.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // GetAccountInfo retrieves balances for all enabled currencies for the @@ -245,7 +245,7 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (c *COINUT) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := c.GetInstrumentTicker(c.InstrumentMap[p.String()]) if err != nil { @@ -268,7 +268,7 @@ func (c *COINUT) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tick } // FetchTicker returns the ticker for a currency pair -func (c *COINUT) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (c *COINUT) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(c.GetName(), p, assetType) if err != nil { return c.UpdateTicker(p, assetType) @@ -277,7 +277,7 @@ func (c *COINUT) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticke } // FetchOrderbook returns orderbook base on the currency pair -func (c *COINUT) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (c *COINUT) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(c.GetName(), p, assetType) if err != nil { return c.UpdateOrderbook(p, assetType) @@ -286,7 +286,7 @@ func (c *COINUT) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (or } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := c.GetInstrumentOrderbook(c.InstrumentMap[p.String()], 200) if err != nil { @@ -322,7 +322,7 @@ func (c *COINUT) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (c *COINUT) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (c *COINUT) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -521,7 +521,7 @@ func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ for _, currency := range getOrdersRequest.Currencies { currStr := fmt.Sprintf("%v%v%v", currency.Base.String(), - c.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter, + c.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter, currency.Quote.String()) if strings.EqualFold(currStr, instrument) { openOrders, err := c.GetOpenOrders(instrumentData.InstID) diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 6d43e09f..2752c8a2 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -11,7 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/request" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -221,7 +221,7 @@ func (e *Base) SetAssetTypes() { } // GetAssetTypes returns the available asset types for an individual exchange -func (e *Base) GetAssetTypes() assets.AssetTypes { +func (e *Base) GetAssetTypes() asset.Items { return e.CurrencyPairs.AssetTypes } @@ -303,7 +303,7 @@ func (e *Base) GetSupportedFeatures() FeaturesSupported { // GetPairFormat returns the pair format based on the exchange and // asset type -func (e *Base) GetPairFormat(assetType assets.AssetType, requestFormat bool) currency.PairFormat { +func (e *Base) GetPairFormat(assetType asset.Item, requestFormat bool) currency.PairFormat { if e.CurrencyPairs.UseGlobalFormat { if requestFormat { return *e.CurrencyPairs.RequestFormat @@ -319,7 +319,7 @@ func (e *Base) GetPairFormat(assetType assets.AssetType, requestFormat bool) cur // GetEnabledPairs is a method that returns the enabled currency pairs of // the exchange by asset type -func (e *Base) GetEnabledPairs(assetType assets.AssetType) currency.Pairs { +func (e *Base) GetEnabledPairs(assetType asset.Item) currency.Pairs { format := e.GetPairFormat(assetType, false) pairs := e.CurrencyPairs.GetPairs(assetType, true) return pairs.Format(format.Delimiter, format.Index, format.Uppercase) @@ -327,7 +327,7 @@ func (e *Base) GetEnabledPairs(assetType assets.AssetType) currency.Pairs { // GetAvailablePairs is a method that returns the available currency pairs // of the exchange by asset type -func (e *Base) GetAvailablePairs(assetType assets.AssetType) currency.Pairs { +func (e *Base) GetAvailablePairs(assetType asset.Item) currency.Pairs { format := e.GetPairFormat(assetType, false) pairs := e.CurrencyPairs.GetPairs(assetType, false) return pairs.Format(format.Delimiter, format.Index, format.Uppercase) @@ -335,7 +335,7 @@ func (e *Base) GetAvailablePairs(assetType assets.AssetType) currency.Pairs { // SupportsPair returns true or not whether a currency pair exists in the // exchange available currencies or not -func (e *Base) SupportsPair(p currency.Pair, enabledPairs bool, assetType assets.AssetType) bool { +func (e *Base) SupportsPair(p currency.Pair, enabledPairs bool, assetType asset.Item) bool { if enabledPairs { return e.GetEnabledPairs(assetType).Contains(p, false) } @@ -344,7 +344,7 @@ func (e *Base) SupportsPair(p currency.Pair, enabledPairs bool, assetType assets // FormatExchangeCurrencies returns a string containing // the exchanges formatted currency pairs -func (e *Base) FormatExchangeCurrencies(pairs []currency.Pair, assetType assets.AssetType) (string, error) { +func (e *Base) FormatExchangeCurrencies(pairs []currency.Pair, assetType asset.Item) (string, error) { var currencyItems string pairFmt := e.GetPairFormat(assetType, true) @@ -364,7 +364,7 @@ func (e *Base) FormatExchangeCurrencies(pairs []currency.Pair, assetType assets. // FormatExchangeCurrency is a method that formats and returns a currency pair // based on the user currency display preferences -func (e *Base) FormatExchangeCurrency(p currency.Pair, assetType assets.AssetType) currency.Pair { +func (e *Base) FormatExchangeCurrency(p currency.Pair, assetType asset.Item) currency.Pair { pairFmt := e.GetPairFormat(assetType, true) return p.Format(pairFmt.Delimiter, pairFmt.Uppercase) } @@ -520,7 +520,7 @@ func (e *Base) ValidateAPICredentials() bool { // SetPairs sets the exchange currency pairs for either enabledPairs or // availablePairs -func (e *Base) SetPairs(pairs currency.Pairs, assetType assets.AssetType, enabled bool) error { +func (e *Base) SetPairs(pairs currency.Pairs, assetType asset.Item, enabled bool) error { if len(pairs) == 0 { return fmt.Errorf("%s SetPairs error - pairs is empty", e.Name) } @@ -539,7 +539,7 @@ func (e *Base) SetPairs(pairs currency.Pairs, assetType assets.AssetType, enable // UpdatePairs updates the exchange currency pairs for either enabledPairs or // availablePairs -func (e *Base) UpdatePairs(exchangeProducts currency.Pairs, assetType assets.AssetType, enabled, force bool) error { +func (e *Base) UpdatePairs(exchangeProducts currency.Pairs, assetType asset.Item, enabled, force bool) error { if len(exchangeProducts) == 0 { return fmt.Errorf("%s UpdatePairs error - exchangeProducts is empty", e.Name) } @@ -710,7 +710,7 @@ func (e *Base) FormatWithdrawPermissions() string { // IsAssetTypeSupported whether or not the supplied asset is supported // by the exchange -func (e *Base) IsAssetTypeSupported(asset assets.AssetType) bool { +func (e *Base) IsAssetTypeSupported(asset asset.Item) bool { return e.CurrencyPairs.AssetTypes.Contains(asset) } diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 2a65bc9a..32819ec8 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -9,7 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/request" ) @@ -172,13 +172,13 @@ func TestSetAssetTypes(t *testing.T) { } b.Name = "ANX" - b.CurrencyPairs.AssetTypes = assets.AssetTypes{assets.AssetTypeSpot} + b.CurrencyPairs.AssetTypes = asset.Items{asset.Spot} exch, err := cfg.GetExchangeConfig(b.Name) if err != nil { t.Fatalf("Test failed. TestSetAssetTypes load config failed. Error %s", err) } - exch.CurrencyPairs.AssetTypes = assets.New("") + exch.CurrencyPairs.AssetTypes = asset.New("") err = cfg.UpdateExchangeConfig(exch) if err != nil { t.Fatalf("Test failed. TestSetAssetTypes update config failed. Error %s", err) @@ -195,7 +195,7 @@ func TestSetAssetTypes(t *testing.T) { } b.SetAssetTypes() - if !common.StringDataCompare(b.CurrencyPairs.AssetTypes.Strings(), assets.AssetTypeSpot.String()) { + if !common.StringDataCompare(b.CurrencyPairs.AssetTypes.Strings(), asset.Spot.String()) { t.Fatal("Test failed. TestSetAssetTypes assetTypes is not set") } } @@ -203,10 +203,10 @@ func TestSetAssetTypes(t *testing.T) { func TestGetAssetTypes(t *testing.T) { testExchange := Base{ CurrencyPairs: currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, - assets.AssetTypeBinary, - assets.AssetTypeFutures, + AssetTypes: asset.Items{ + asset.Spot, + asset.Binary, + asset.Futures, }, }, } @@ -237,7 +237,7 @@ func TestSetCurrencyPairFormat(t *testing.T) { } b.Config = exch b.SetCurrencyPairFormat() - if exch.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter != "-" && !exch.CurrencyPairs.ConfigFormat.Uppercase { + if exch.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter != "-" && !exch.CurrencyPairs.ConfigFormat.Uppercase { t.Fatal("Test failed. TestSetCurrencyPairFormat exch values are not nil") } @@ -258,7 +258,7 @@ func TestSetCurrencyPairFormat(t *testing.T) { } b.SetCurrencyPairFormat() - if exch.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter != "-" && !exch.CurrencyPairs.ConfigFormat.Uppercase { + if exch.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter != "-" && !exch.CurrencyPairs.ConfigFormat.Uppercase { t.Fatal("Test failed. TestSetCurrencyPairFormat exch values are not nil") } } @@ -286,7 +286,7 @@ func TestGetEnabledPairs(t *testing.T) { Name: "TESTNAME", } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTC-USD"}), true) format := currency.PairFormat{ Delimiter: "-", @@ -294,7 +294,7 @@ func TestGetEnabledPairs(t *testing.T) { Uppercase: true, } - assetType := assets.AssetTypeSpot + assetType := asset.Spot b.CurrencyPairs.UseGlobalFormat = true b.CurrencyPairs.RequestFormat = &format b.CurrencyPairs.ConfigFormat = &format @@ -318,7 +318,7 @@ func TestGetEnabledPairs(t *testing.T) { t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTCDOGE"}), true) format.Index = currency.BTC.String() b.CurrencyPairs.ConfigFormat = &format @@ -327,7 +327,7 @@ func TestGetEnabledPairs(t *testing.T) { t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTC_USD"}), true) b.CurrencyPairs.RequestFormat.Delimiter = "" b.CurrencyPairs.ConfigFormat.Delimiter = "_" @@ -336,7 +336,7 @@ func TestGetEnabledPairs(t *testing.T) { t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTCDOGE"}), true) b.CurrencyPairs.RequestFormat.Delimiter = "" b.CurrencyPairs.ConfigFormat.Delimiter = "" @@ -346,7 +346,7 @@ func TestGetEnabledPairs(t *testing.T) { t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTCUSD"}), true) b.CurrencyPairs.ConfigFormat.Index = "" c = b.GetEnabledPairs(assetType) @@ -360,7 +360,7 @@ func TestGetAvailablePairs(t *testing.T) { Name: "TESTNAME", } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}), false) format := currency.PairFormat{ Delimiter: "-", @@ -368,7 +368,7 @@ func TestGetAvailablePairs(t *testing.T) { Uppercase: true, } - assetType := assets.AssetTypeSpot + assetType := asset.Spot b.CurrencyPairs.UseGlobalFormat = true b.CurrencyPairs.RequestFormat = &format b.CurrencyPairs.ConfigFormat = &format @@ -392,7 +392,7 @@ func TestGetAvailablePairs(t *testing.T) { t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTCDOGE"}), false) format.Index = currency.BTC.String() b.CurrencyPairs.ConfigFormat = &format @@ -401,7 +401,7 @@ func TestGetAvailablePairs(t *testing.T) { t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTC_USD"}), false) b.CurrencyPairs.RequestFormat.Delimiter = "" b.CurrencyPairs.ConfigFormat.Delimiter = "_" @@ -410,7 +410,7 @@ func TestGetAvailablePairs(t *testing.T) { t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTCDOGE"}), false) b.CurrencyPairs.RequestFormat.Delimiter = "" b.CurrencyPairs.ConfigFormat.Delimiter = "_" @@ -420,7 +420,7 @@ func TestGetAvailablePairs(t *testing.T) { t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTCUSD"}), false) b.CurrencyPairs.ConfigFormat.Index = "" c = b.GetAvailablePairs(assetType) @@ -434,10 +434,10 @@ func TestSupportsPair(t *testing.T) { Name: "TESTNAME", } - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{ defaultTestCurrencyPair, "ETH-USD"}), false) - b.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + b.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}), true) format := ¤cy.PairFormat{ @@ -448,7 +448,7 @@ func TestSupportsPair(t *testing.T) { b.CurrencyPairs.UseGlobalFormat = true b.CurrencyPairs.RequestFormat = format b.CurrencyPairs.ConfigFormat = format - assetType := assets.AssetTypeSpot + assetType := asset.Spot if !b.SupportsPair(currency.NewPair(currency.BTC, currency.USD), true, assetType) { t.Error("Test Failed - Exchange SupportsPair() incorrect value") @@ -486,7 +486,7 @@ func TestFormatExchangeCurrencies(t *testing.T) { currency.NewPairDelimiter("LTC_BTC", "_"), } - actual, err := e.FormatExchangeCurrencies(pairs, assets.AssetTypeSpot) + actual, err := e.FormatExchangeCurrencies(pairs, asset.Spot) if err != nil { t.Errorf("Test failed - Exchange TestFormatExchangeCurrencies error %s", err) } @@ -508,7 +508,7 @@ func TestFormatExchangeCurrency(t *testing.T) { p := currency.NewPair(currency.BTC, currency.USD) expected := defaultTestCurrencyPair - actual := b.FormatExchangeCurrency(p, assets.AssetTypeSpot) + actual := b.FormatExchangeCurrency(p, asset.Spot) if actual.String() != expected { t.Errorf("Test failed - Exchange TestFormatExchangeCurrency %s != %s", @@ -569,7 +569,7 @@ func TestSetPairs(t *testing.T) { } newPair := currency.NewPairDelimiter("ETH_USDT", "_") - assetType := assets.AssetTypeSpot + assetType := asset.Spot var UAC Base UAC.Name = "ANX" @@ -579,7 +579,7 @@ func TestSetPairs(t *testing.T) { t.Fatalf("Test failed. TestSetPairs unable to set defaults: %s", err) } - err = UAC.SetPairs([]currency.Pair{newPair}, assets.AssetTypeSpot, true) + err = UAC.SetPairs([]currency.Pair{newPair}, asset.Spot, true) if err != nil { t.Fatalf("Test failed. TestSetPairs failed to set currencies: %s", err) } @@ -588,12 +588,12 @@ func TestSetPairs(t *testing.T) { t.Fatal("Test failed. TestSetPairs failed to set currencies") } - UAC.SetPairs([]currency.Pair{newPair}, assets.AssetTypeSpot, false) + UAC.SetPairs([]currency.Pair{newPair}, asset.Spot, false) if !UAC.GetAvailablePairs(assetType).Contains(newPair, true) { t.Fatal("Test failed. TestSetPairs failed to set currencies") } - err = UAC.SetPairs(nil, assets.AssetTypeSpot, false) + err = UAC.SetPairs(nil, asset.Spot, false) if err == nil { t.Fatal("Test failed. TestSetPairs should return an error when attempting to set an empty pairs array") } @@ -614,20 +614,20 @@ func TestUpdatePairs(t *testing.T) { UAC := Base{Name: "ANX"} UAC.Config = anxCfg exchangeProducts := currency.NewPairsFromStrings([]string{"ltc", "btc", "usd", "aud", ""}) - err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, true, false) + err = UAC.UpdatePairs(exchangeProducts, asset.Spot, true, false) if err != nil { t.Errorf("Test Failed - TestUpdatePairs error: %s", err) } // Test updating the same new products, diff should be 0 - err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, true, false) + err = UAC.UpdatePairs(exchangeProducts, asset.Spot, true, false) if err != nil { t.Errorf("Test Failed - TestUpdatePairs error: %s", err) } // Test force updating to only one product exchangeProducts = currency.NewPairsFromStrings([]string{"btc"}) - err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, true, true) + err = UAC.UpdatePairs(exchangeProducts, asset.Spot, true, true) if err != nil { t.Errorf("Test Failed - TestUpdatePairs error: %s", err) } @@ -635,34 +635,34 @@ func TestUpdatePairs(t *testing.T) { // Test updating exchange products exchangeProducts = currency.NewPairsFromStrings([]string{"ltc", "btc", "usd", "aud"}) UAC.Name = "ANX" - err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, false, false) + err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err != nil { t.Errorf("Test Failed - Exchange UpdatePairs() error: %s", err) } // Test updating the same new products, diff should be 0 - err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, false, false) + err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err != nil { t.Errorf("Test Failed - Exchange UpdatePairs() error: %s", err) } // Test force updating to only one product exchangeProducts = currency.NewPairsFromStrings([]string{"btc"}) - err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, false, true) + err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, true) if err != nil { t.Errorf("Test Failed - Forced Exchange UpdatePairs() error: %s", err) } // Test update currency pairs with btc excluded exchangeProducts = currency.NewPairsFromStrings([]string{"ltc", "eth"}) - err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, false, false) + err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err != nil { t.Errorf("Test Failed - Forced Exchange UpdatePairs() error: %s", err) } // Test that empty exchange products should return an error exchangeProducts = nil - err = UAC.UpdatePairs(exchangeProducts, assets.AssetTypeSpot, false, false) + err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err == nil { t.Errorf("Test failed - empty available pairs should return an error") } diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index 0703dca5..aef67d24 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -51,8 +51,8 @@ func (e *EXMO) SetDefaults() { e.API.CredentialsValidator.RequiresSecret = true e.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -127,7 +127,7 @@ func (e *EXMO) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (e *EXMO) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (e *EXMO) FetchTradablePairs(asset asset.Item) ([]string, error) { pairs, err := e.GetPairSettings() if err != nil { return nil, err @@ -144,16 +144,16 @@ func (e *EXMO) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (e *EXMO) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := e.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := e.FetchTradablePairs(asset.Spot) if err != nil { return err } - return e.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return e.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (e *EXMO) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (e *EXMO) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price pairsCollated, err := e.FormatExchangeCurrencies(e.GetEnabledPairs(assetType), assetType) if err != nil { @@ -186,7 +186,7 @@ func (e *EXMO) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker } // FetchTicker returns the ticker for a currency pair -func (e *EXMO) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (e *EXMO) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tick, err := ticker.GetTicker(e.GetName(), p, assetType) if err != nil { return e.UpdateTicker(p, assetType) @@ -195,7 +195,7 @@ func (e *EXMO) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker. } // FetchOrderbook returns the orderbook for a currency pair -func (e *EXMO) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (e *EXMO) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(e.GetName(), p, assetType) if err != nil { return e.UpdateOrderbook(p, assetType) @@ -204,7 +204,7 @@ func (e *EXMO) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orde } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base pairsCollated, err := e.FormatExchangeCurrencies(e.GetEnabledPairs(assetType), assetType) if err != nil { @@ -293,7 +293,7 @@ func (e *EXMO) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (e *EXMO) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (e *EXMO) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -465,7 +465,7 @@ func (e *EXMO) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]e var allTrades []UserTrades for _, currency := range getOrdersRequest.Currencies { - resp, err := e.GetUserTrades(e.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String(), "", "10000") + resp, err := e.GetUserTrades(e.FormatExchangeCurrency(currency, asset.Spot).String(), "", "10000") if err != nil { return nil, err } diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 80aeb672..51f4ce6e 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -15,7 +15,7 @@ import ( "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -188,7 +188,7 @@ func (g *Gateio) WsHandleData() { g.Websocket.DataHandler <- exchange.TickerData{ Timestamp: time.Now(), Pair: currency.NewPairFromString(c), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Exchange: g.GetName(), ClosePrice: ticker.Close, Quantity: ticker.BaseVolume, @@ -216,7 +216,7 @@ func (g *Gateio) WsHandleData() { g.Websocket.DataHandler <- exchange.TradeData{ Timestamp: time.Now(), CurrencyPair: currency.NewPairFromString(c), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Exchange: g.GetName(), Price: trade.Price, Amount: trade.Amount, @@ -284,7 +284,7 @@ func (g *Gateio) WsHandleData() { var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.AssetType = asset.Spot newOrderBook.LastUpdated = time.Now() newOrderBook.Pair = currency.NewPairFromString(c) @@ -300,7 +300,7 @@ func (g *Gateio) WsHandleData() { currency.NewPairFromString(c), time.Now(), g.GetName(), - assets.AssetTypeSpot) + asset.Spot) if err != nil { g.Websocket.DataHandler <- err } @@ -308,7 +308,7 @@ func (g *Gateio) WsHandleData() { g.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: currency.NewPairFromString(c), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Exchange: g.GetName(), } @@ -329,7 +329,7 @@ func (g *Gateio) WsHandleData() { g.Websocket.DataHandler <- exchange.KlineData{ Timestamp: time.Now(), Pair: currency.NewPairFromString(data[7].(string)), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Exchange: g.GetName(), OpenPrice: open, ClosePrice: closePrice, @@ -350,7 +350,7 @@ func (g *Gateio) GenerateDefaultSubscriptions() { } subscriptions := []exchange.WebsocketChannelSubscription{} - enabledCurrencies := g.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := g.GetEnabledPairs(asset.Spot) for i := range channels { for j := range enabledCurrencies { params := make(map[string]interface{}) diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index a9aa82c2..2cfdd3e2 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -51,8 +51,8 @@ func (g *Gateio) SetDefaults() { g.API.CredentialsValidator.RequiresSecret = true g.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -147,23 +147,23 @@ func (g *Gateio) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (g *Gateio) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (g *Gateio) FetchTradablePairs(asset asset.Item) ([]string, error) { return g.GetSymbols() } // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (g *Gateio) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := g.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := g.FetchTradablePairs(asset.Spot) if err != nil { return err } - return g.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return g.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (g *Gateio) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (g *Gateio) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price result, err := g.GetTickers() if err != nil { @@ -190,7 +190,7 @@ func (g *Gateio) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tick } // FetchTicker returns the ticker for a currency pair -func (g *Gateio) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (g *Gateio) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType) if err != nil { return g.UpdateTicker(p, assetType) @@ -199,7 +199,7 @@ func (g *Gateio) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticke } // FetchOrderbook returns orderbook base on the currency pair -func (g *Gateio) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (g *Gateio) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(g.GetName(), p, assetType) if err != nil { return g.UpdateOrderbook(p, assetType) @@ -208,7 +208,7 @@ func (g *Gateio) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (or } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base currency := g.FormatExchangeCurrency(p, assetType).String() @@ -312,7 +312,7 @@ func (g *Gateio) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (g *Gateio) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (g *Gateio) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -419,7 +419,7 @@ func (g *Gateio) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { orderDetail.Status = orders.Orders[x].Status orderDetail.Price = orders.Orders[x].Rate orderDetail.CurrencyPair = currency.NewPairDelimiter(orders.Orders[x].CurrencyPair, - g.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + g.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) if strings.EqualFold(orders.Orders[x].Type, exchange.AskOrderSide.ToString()) { orderDetail.OrderSide = exchange.AskOrderSide } else if strings.EqualFold(orders.Orders[x].Type, exchange.BidOrderSide.ToString()) { @@ -505,7 +505,7 @@ func (g *Gateio) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ } symbol := currency.NewPairDelimiter(resp.Orders[i].CurrencyPair, - g.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + g.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(resp.Orders[i].Type)) orderDate := time.Unix(resp.Orders[i].Timestamp, 0) @@ -542,7 +542,7 @@ func (g *Gateio) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for _, trade := range trades { symbol := currency.NewPairDelimiter(trade.Pair, - g.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + g.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(trade.Type)) orderDate := time.Unix(trade.TimeUnix, 0) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 264f37c5..c3e21e4b 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -13,7 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" ) @@ -49,7 +49,7 @@ func (g *Gemini) WsConnect() error { // WsSubscribe subscribes to the full websocket suite on gemini exchange func (g *Gemini) WsSubscribe(dialer *websocket.Dialer) error { - enabledCurrencies := g.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := g.GetEnabledPairs(asset.Spot) for i, c := range enabledCurrencies { val := url.Values{} val.Set("heartbeat", "true") @@ -157,7 +157,7 @@ func (g *Gemini) WsHandleData() { var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.AssetType = asset.Spot newOrderBook.LastUpdated = time.Now() newOrderBook.Pair = resp.Currency @@ -170,7 +170,7 @@ func (g *Gemini) WsHandleData() { } g.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: resp.Currency, - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Exchange: g.GetName()} } else { @@ -179,7 +179,7 @@ func (g *Gemini) WsHandleData() { g.Websocket.DataHandler <- exchange.TradeData{ Timestamp: time.Now(), CurrencyPair: resp.Currency, - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Exchange: g.GetName(), EventTime: result.Timestamp, Price: event.Price, @@ -197,7 +197,7 @@ func (g *Gemini) WsHandleData() { resp.Currency, time.Now(), g.GetName(), - assets.AssetTypeSpot) + asset.Spot) if err != nil { g.Websocket.DataHandler <- err } @@ -207,7 +207,7 @@ func (g *Gemini) WsHandleData() { resp.Currency, time.Now(), g.GetName(), - assets.AssetTypeSpot) + asset.Spot) if err != nil { g.Websocket.DataHandler <- err } @@ -216,7 +216,7 @@ func (g *Gemini) WsHandleData() { } g.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{Pair: resp.Currency, - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Exchange: g.GetName()} } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index ad5968b7..acb371d4 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -13,7 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -52,8 +52,8 @@ func (g *Gemini) SetDefaults() { g.API.CredentialsValidator.RequiresSecret = true g.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -145,7 +145,7 @@ func (g *Gemini) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (g *Gemini) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (g *Gemini) FetchTradablePairs(asset asset.Item) ([]string, error) { return g.GetSymbols() } @@ -157,7 +157,7 @@ func (g *Gemini) UpdateTradablePairs(forceUpdate bool) error { return err } - return g.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return g.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // GetAccountInfo Retrieves balances for all enabled currencies for the @@ -187,7 +187,7 @@ func (g *Gemini) GetAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (g *Gemini) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (g *Gemini) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := g.GetTicker(p.String()) if err != nil { @@ -208,7 +208,7 @@ func (g *Gemini) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tick } // FetchTicker returns the ticker for a currency pair -func (g *Gemini) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (g *Gemini) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType) if err != nil { return g.UpdateTicker(p, assetType) @@ -217,7 +217,7 @@ func (g *Gemini) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticke } // FetchOrderbook returns orderbook base on the currency pair -func (g *Gemini) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (g *Gemini) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(g.GetName(), p, assetType) if err != nil { return g.UpdateOrderbook(p, assetType) @@ -226,7 +226,7 @@ func (g *Gemini) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (or } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (g *Gemini) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (g *Gemini) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := g.GetOrderbook(p.String(), url.Values{}) if err != nil { @@ -261,7 +261,7 @@ func (g *Gemini) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (g *Gemini) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (g *Gemini) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -389,7 +389,7 @@ func (g *Gemini) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp { symbol := currency.NewPairDelimiter(resp[i].Symbol, - g.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + g.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) var orderType exchange.OrderType if resp[i].Type == "exchange limit" { orderType = exchange.LimitOrderType @@ -432,7 +432,7 @@ func (g *Gemini) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var trades []TradeHistory for _, currency := range getOrdersRequest.Currencies { resp, err := g.GetTradeHistory(g.FormatExchangeCurrency(currency, - assets.AssetTypeSpot).String(), getOrdersRequest.StartTicks.Unix()) + asset.Spot).String(), getOrdersRequest.StartTicks.Unix()) if err != nil { return nil, err } @@ -459,7 +459,7 @@ func (g *Gemini) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ Price: trades[i].Price, CurrencyPair: currency.NewPairWithDelimiter(trades[i].BaseCurrency, trades[i].QuoteCurrency, - g.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + g.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), }) } diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index 025380c6..af28bfce 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -117,7 +117,7 @@ func (h *HitBTC) WsHandleData() { h.Websocket.DataHandler <- exchange.TickerData{ Exchange: h.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Pair: currency.NewPairFromString(ticker.Params.Symbol), Quantity: ticker.Params.Volume, Timestamp: ts, @@ -191,7 +191,7 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.AssetType = asset.Spot newOrderBook.LastUpdated = time.Now() newOrderBook.Pair = p @@ -202,7 +202,7 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: h.GetName(), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Pair: p, } @@ -226,14 +226,14 @@ func (h *HitBTC) WsProcessOrderbookUpdate(ob WsOrderbook) error { p := currency.NewPairFromString(ob.Params.Symbol) - err := h.Websocket.Orderbook.Update(bids, asks, p, time.Now(), h.GetName(), assets.AssetTypeSpot) + err := h.Websocket.Orderbook.Update(bids, asks, p, time.Now(), h.GetName(), asset.Spot) if err != nil { return err } h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: h.GetName(), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Pair: p, } return nil @@ -243,7 +243,7 @@ func (h *HitBTC) WsProcessOrderbookUpdate(ob WsOrderbook) error { func (h *HitBTC) GenerateDefaultSubscriptions() { var channels = []string{"subscribeTicker", "subscribeOrderbook", "subscribeTrades", "subscribeCandles"} subscriptions := []exchange.WebsocketChannelSubscription{} - enabledCurrencies := h.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := h.GetEnabledPairs(asset.Spot) for i := range channels { for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "" diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index f60982d9..5506c5d2 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -51,8 +51,8 @@ func (h *HitBTC) SetDefaults() { h.API.CredentialsValidator.RequiresSecret = true h.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -131,13 +131,13 @@ func (h *HitBTC) Run() { } forceUpdate := false - if !common.StringDataContains(h.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "-") || - !common.StringDataContains(h.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "-") { + if !common.StringDataContains(h.GetEnabledPairs(asset.Spot).Strings(), "-") || + !common.StringDataContains(h.GetAvailablePairs(asset.Spot).Strings(), "-") { enabledPairs := []string{"BTC-USD"} log.Warn("WARNING: Available pairs for HitBTC reset due to config upgrade, please enable the ones you would like again.") forceUpdate = true - err := h.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), assets.AssetTypeSpot, true, true) + err := h.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), asset.Spot, true, true) if err != nil { log.Errorf("%s failed to update enabled currencies.\n", h.GetName()) } @@ -154,7 +154,7 @@ func (h *HitBTC) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (h *HitBTC) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (h *HitBTC) FetchTradablePairs(asset asset.Item) ([]string, error) { symbols, err := h.GetSymbolsDetailed() if err != nil { return nil, err @@ -170,16 +170,16 @@ func (h *HitBTC) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (h *HitBTC) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := h.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := h.FetchTradablePairs(asset.Spot) if err != nil { return err } - return h.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return h.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType asset.Item) (ticker.Price, error) { tick, err := h.GetTicker("") if err != nil { return ticker.Price{}, err @@ -205,7 +205,7 @@ func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType assets.Asset } // FetchTicker returns the ticker for a currency pair -func (h *HitBTC) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (h *HitBTC) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType) if err != nil { return h.UpdateTicker(p, assetType) @@ -214,7 +214,7 @@ func (h *HitBTC) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticke } // FetchOrderbook returns orderbook base on the currency pair -func (h *HitBTC) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (h *HitBTC) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(h.GetName(), p, assetType) if err != nil { return h.UpdateOrderbook(p, assetType) @@ -223,7 +223,7 @@ func (h *HitBTC) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (or } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := h.GetOrderbook(h.FormatExchangeCurrency(currencyPair, assetType).String(), 1000) if err != nil { @@ -286,7 +286,7 @@ func (h *HitBTC) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (h *HitBTC) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (h *HitBTC) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -425,7 +425,7 @@ func (h *HitBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + h.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[i].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[i].CreatedAt) if err != nil { @@ -469,7 +469,7 @@ func (h *HitBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + h.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[i].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[i].CreatedAt) if err != nil { diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index 886b1fed..bc7f9286 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -15,7 +15,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -152,7 +152,7 @@ func (h *HUOBI) WsHandleData() { h.Websocket.DataHandler <- exchange.KlineData{ Timestamp: time.Unix(0, kline.Timestamp), Exchange: h.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Pair: currency.NewPairFromString(data[1]), OpenPrice: kline.Tick.Open, ClosePrice: kline.Tick.Close, @@ -173,7 +173,7 @@ func (h *HUOBI) WsHandleData() { h.Websocket.DataHandler <- exchange.TradeData{ Exchange: h.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, CurrencyPair: currency.NewPairFromString(data[1]), Timestamp: time.Unix(0, trade.Tick.Timestamp), } @@ -213,7 +213,7 @@ func (h *HUOBI) WsProcessOrderbook(ob *WsDepth, symbol string) error { h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p, Exchange: h.GetName(), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, } return nil @@ -222,7 +222,7 @@ func (h *HUOBI) WsProcessOrderbook(ob *WsDepth, symbol string) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (h *HUOBI) GenerateDefaultSubscriptions() { var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade} - enabledCurrencies := h.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := h.GetEnabledPairs(asset.Spot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { for j := range enabledCurrencies { diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index b503dfb0..a09a9ea2 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -51,8 +51,8 @@ func (h *HUOBI) SetDefaults() { h.API.CredentialsValidator.RequiresSecret = true h.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, @@ -138,8 +138,8 @@ func (h *HUOBI) Run() { } var forceUpdate bool - if common.StringDataContains(h.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "CNY") || - common.StringDataContains(h.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "CNY") { + if common.StringDataContains(h.GetEnabledPairs(asset.Spot).Strings(), "CNY") || + common.StringDataContains(h.GetAvailablePairs(asset.Spot).Strings(), "CNY") { forceUpdate = true } @@ -163,7 +163,7 @@ func (h *HUOBI) Run() { } log.Warn("WARNING: Available and enabled pairs for Huobi reset due to config upgrade, please enable the ones you would like again") - err := h.UpdatePairs(enabledPairs, assets.AssetTypeSpot, true, true) + err := h.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { log.Errorf("%s Failed to update enabled currencies.\n", h.GetName()) } @@ -180,7 +180,7 @@ func (h *HUOBI) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (h *HUOBI) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (h *HUOBI) FetchTradablePairs(asset asset.Item) ([]string, error) { symbols, err := h.GetSymbols() if err != nil { return nil, err @@ -197,16 +197,16 @@ func (h *HUOBI) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (h *HUOBI) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := h.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := h.FetchTradablePairs(asset.Spot) if err != nil { return err } - return h.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return h.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (h *HUOBI) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (h *HUOBI) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := h.GetMarketDetailMerged(h.FormatExchangeCurrency(p, assetType).String()) if err != nil { @@ -236,7 +236,7 @@ func (h *HUOBI) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticke } // FetchTicker returns the ticker for a currency pair -func (h *HUOBI) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (h *HUOBI) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType) if err != nil { return h.UpdateTicker(p, assetType) @@ -245,7 +245,7 @@ func (h *HUOBI) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker } // FetchOrderbook returns orderbook base on the currency pair -func (h *HUOBI) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (h *HUOBI) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(h.GetName(), p, assetType) if err != nil { return h.UpdateOrderbook(p, assetType) @@ -254,7 +254,7 @@ func (h *HUOBI) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (ord } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (h *HUOBI) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (h *HUOBI) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := h.GetDepth(OrderBookDataRequestParams{ Symbol: h.FormatExchangeCurrency(p, assetType).String(), @@ -374,7 +374,7 @@ func (h *HUOBI) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (h *HUOBI) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (h *HUOBI) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -448,9 +448,9 @@ func (h *HUOBI) CancelOrder(order *exchange.OrderCancellation) error { // CancelAllOrders cancels all orders associated with a currency pair func (h *HUOBI) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { var cancelAllOrdersResponse exchange.CancelAllOrdersResponse - for _, currency := range h.GetEnabledPairs(assets.AssetTypeSpot) { + for _, currency := range h.GetEnabledPairs(asset.Spot) { resp, err := h.CancelOpenOrdersBatch(orderCancellation.AccountID, - h.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String()) + h.FormatExchangeCurrency(currency, asset.Spot).String()) if err != nil { return cancelAllOrdersResponse, err } diff --git a/exchanges/huobihadax/huobihadax_test.go b/exchanges/huobihadax/huobihadax_test.go index 4e4ea485..be5c4524 100644 --- a/exchanges/huobihadax/huobihadax_test.go +++ b/exchanges/huobihadax/huobihadax_test.go @@ -9,7 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // Please supply your own APIKEYS here for due diligence testing @@ -51,8 +51,8 @@ func getDefaultConfig() config.ExchangeConfig { HTTPTimeout: 15000000000, CurrencyPairs: ¤cy.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, ConfigFormat: ¤cy.PairFormat{ @@ -67,10 +67,10 @@ func getDefaultConfig() config.ExchangeConfig { BaseCurrencies: currency.NewCurrenciesFromStringArray([]string{"USD"}), } - exchCfg.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + exchCfg.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTC-USDT", "BCH-USDT"}), false) - exchCfg.CurrencyPairs.StorePairs(assets.AssetTypeSpot, + exchCfg.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTC-USDT"}), true) diff --git a/exchanges/huobihadax/huobihadax_websocket.go b/exchanges/huobihadax/huobihadax_websocket.go index 7ef4ca59..81822531 100644 --- a/exchanges/huobihadax/huobihadax_websocket.go +++ b/exchanges/huobihadax/huobihadax_websocket.go @@ -15,7 +15,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -155,7 +155,7 @@ func (h *HUOBIHADAX) WsHandleData() { h.Websocket.DataHandler <- exchange.KlineData{ Timestamp: time.Unix(0, kline.Timestamp), Exchange: h.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Pair: currency.NewPairFromString(data[1]), OpenPrice: kline.Tick.Open, ClosePrice: kline.Tick.Close, @@ -176,7 +176,7 @@ func (h *HUOBIHADAX) WsHandleData() { h.Websocket.DataHandler <- exchange.TradeData{ Exchange: h.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, CurrencyPair: currency.NewPairFromString(data[1]), Timestamp: time.Unix(0, trade.Tick.Timestamp), } @@ -216,7 +216,7 @@ func (h *HUOBIHADAX) WsProcessOrderbook(ob *WsDepth, symbol string) error { h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: p, Exchange: h.GetName(), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, } return nil @@ -225,7 +225,7 @@ func (h *HUOBIHADAX) WsProcessOrderbook(ob *WsDepth, symbol string) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (h *HUOBIHADAX) GenerateDefaultSubscriptions() { var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade} - enabledCurrencies := h.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := h.GetEnabledPairs(asset.Spot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range channels { for j := range enabledCurrencies { diff --git a/exchanges/huobihadax/huobihadax_wrapper.go b/exchanges/huobihadax/huobihadax_wrapper.go index f2f456e9..ee30a61c 100644 --- a/exchanges/huobihadax/huobihadax_wrapper.go +++ b/exchanges/huobihadax/huobihadax_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -51,8 +51,8 @@ func (h *HUOBIHADAX) SetDefaults() { h.API.CredentialsValidator.RequiresSecret = true h.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, @@ -144,7 +144,7 @@ func (h *HUOBIHADAX) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (h *HUOBIHADAX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (h *HUOBIHADAX) FetchTradablePairs(asset asset.Item) ([]string, error) { symbols, err := h.GetSymbols() if err != nil { return nil, err @@ -161,16 +161,16 @@ func (h *HUOBIHADAX) FetchTradablePairs(asset assets.AssetType) ([]string, error // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (h *HUOBIHADAX) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := h.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := h.FetchTradablePairs(asset.Spot) if err != nil { return err } - return h.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return h.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (h *HUOBIHADAX) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (h *HUOBIHADAX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := h.GetMarketDetailMerged(h.FormatExchangeCurrency(p, assetType).String()) if err != nil { @@ -200,7 +200,7 @@ func (h *HUOBIHADAX) UpdateTicker(p currency.Pair, assetType assets.AssetType) ( } // FetchTicker returns the ticker for a currency pair -func (h *HUOBIHADAX) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (h *HUOBIHADAX) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType) if err != nil { return h.UpdateTicker(p, assetType) @@ -209,7 +209,7 @@ func (h *HUOBIHADAX) FetchTicker(p currency.Pair, assetType assets.AssetType) (t } // FetchOrderbook returns orderbook base on the currency pair -func (h *HUOBIHADAX) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (h *HUOBIHADAX) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(h.GetName(), p, assetType) if err != nil { return h.UpdateOrderbook(p, assetType) @@ -218,7 +218,7 @@ func (h *HUOBIHADAX) FetchOrderbook(p currency.Pair, assetType assets.AssetType) } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (h *HUOBIHADAX) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (h *HUOBIHADAX) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := h.GetDepth(h.FormatExchangeCurrency(p, assetType).String(), "step1") if err != nil { @@ -335,7 +335,7 @@ func (h *HUOBIHADAX) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (h *HUOBIHADAX) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (h *HUOBIHADAX) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -409,9 +409,9 @@ func (h *HUOBIHADAX) CancelOrder(order *exchange.OrderCancellation) error { // CancelAllOrders cancels all orders associated with a currency pair func (h *HUOBIHADAX) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { var cancelAllOrdersResponse exchange.CancelAllOrdersResponse - for _, currency := range h.GetEnabledPairs(assets.AssetTypeSpot) { + for _, currency := range h.GetEnabledPairs(asset.Spot) { resp, err := h.CancelOpenOrdersBatch(orderCancellation.AccountID, - h.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String()) + h.FormatExchangeCurrency(currency, asset.Spot).String()) if err != nil { return cancelAllOrdersResponse, err } @@ -500,7 +500,7 @@ func (h *HUOBIHADAX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + h.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderDate := time.Unix(0, allOrders[i].CreatedAt*int64(time.Millisecond)) orders = append(orders, exchange.OrderDetail{ @@ -546,7 +546,7 @@ func (h *HUOBIHADAX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + h.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderDate := time.Unix(0, allOrders[i].CreatedAt*int64(time.Millisecond)) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go index e5cf96f0..a55fe432 100644 --- a/exchanges/interfaces.go +++ b/exchanges/interfaces.go @@ -5,7 +5,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -19,19 +19,19 @@ type IBotExchange interface { GetName() string IsEnabled() bool SetEnabled(bool) - FetchTicker(currency currency.Pair, assetType assets.AssetType) (ticker.Price, error) - UpdateTicker(currency currency.Pair, assetType assets.AssetType) (ticker.Price, error) - FetchOrderbook(currency currency.Pair, assetType assets.AssetType) (orderbook.Base, error) - UpdateOrderbook(currency currency.Pair, assetType assets.AssetType) (orderbook.Base, error) - FetchTradablePairs(assetType assets.AssetType) ([]string, error) + FetchTicker(currency currency.Pair, assetType asset.Item) (ticker.Price, error) + UpdateTicker(currency currency.Pair, assetType asset.Item) (ticker.Price, error) + FetchOrderbook(currency currency.Pair, assetType asset.Item) (orderbook.Base, error) + UpdateOrderbook(currency currency.Pair, assetType asset.Item) (orderbook.Base, error) + FetchTradablePairs(assetType asset.Item) ([]string, error) UpdateTradablePairs(forceUpdate bool) error - GetEnabledPairs(assetType assets.AssetType) currency.Pairs - GetAvailablePairs(assetType assets.AssetType) currency.Pairs + GetEnabledPairs(assetType asset.Item) currency.Pairs + GetAvailablePairs(assetType asset.Item) currency.Pairs GetAccountInfo() (AccountInfo, error) GetAuthenticatedAPISupport() bool - SetPairs(pairs currency.Pairs, assetType assets.AssetType, enabled bool) error - GetAssetTypes() assets.AssetTypes - GetExchangeHistory(currencyPair currency.Pair, assetType assets.AssetType) ([]TradeHistory, error) + SetPairs(pairs currency.Pairs, assetType asset.Item, enabled bool) error + GetAssetTypes() asset.Items + GetExchangeHistory(currencyPair currency.Pair, assetType asset.Item) ([]TradeHistory, error) SupportsAutoPairUpdates() bool SupportsRESTTickerBatchUpdates() bool GetFeeByType(feeBuilder *FeeBuilder) (float64, error) diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index a5397099..1df2ca02 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -51,8 +51,8 @@ func (i *ItBit) SetDefaults() { i.API.CredentialsValidator.RequiresSecret = true i.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -115,7 +115,7 @@ func (i *ItBit) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (i *ItBit) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (i *ItBit) FetchTradablePairs(asset asset.Item) ([]string, error) { return nil, common.ErrFunctionNotSupported } @@ -126,7 +126,7 @@ func (i *ItBit) UpdateTradablePairs(forceUpdate bool) error { } // UpdateTicker updates and returns the ticker for a currency pair -func (i *ItBit) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (i *ItBit) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := i.GetTicker(i.FormatExchangeCurrency(p, assetType).String()) if err != nil { @@ -150,7 +150,7 @@ func (i *ItBit) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticke } // FetchTicker returns the ticker for a currency pair -func (i *ItBit) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (i *ItBit) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(i.GetName(), p, assetType) if err != nil { return i.UpdateTicker(p, assetType) @@ -159,7 +159,7 @@ func (i *ItBit) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker } // FetchOrderbook returns orderbook base on the currency pair -func (i *ItBit) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (i *ItBit) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(i.GetName(), p, assetType) if err != nil { return i.UpdateOrderbook(p, assetType) @@ -168,7 +168,7 @@ func (i *ItBit) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (ord } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := i.GetOrderbook(i.FormatExchangeCurrency(p, assetType).String()) if err != nil { @@ -268,7 +268,7 @@ func (i *ItBit) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (i *ItBit) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (i *ItBit) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -420,7 +420,7 @@ func (i *ItBit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] var orders []exchange.OrderDetail for j := range allOrders { symbol := currency.NewPairDelimiter(allOrders[j].Instrument, - i.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + i.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { @@ -471,7 +471,7 @@ func (i *ItBit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] } symbol := currency.NewPairDelimiter(allOrders[j].Instrument, - i.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + i.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index 448e3ea4..6d0150d4 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -18,7 +18,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -751,7 +751,7 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data interf // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (k *Kraken) GenerateDefaultSubscriptions() { - enabledCurrencies := k.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := k.GetEnabledPairs(asset.Spot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range defaultSubscribedChannels { for j := range enabledCurrencies { diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 297ef16c..e57108b6 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -11,7 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -51,8 +51,8 @@ func (k *Kraken) SetDefaults() { k.API.CredentialsValidator.RequiresBase64DecodeSecret = true k.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, @@ -140,13 +140,13 @@ func (k *Kraken) Run() { } forceUpdate := false - if !common.StringDataContains(k.GetEnabledPairs(assets.AssetTypeSpot).Strings(), "-") || - !common.StringDataContains(k.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "-") { + if !common.StringDataContains(k.GetEnabledPairs(asset.Spot).Strings(), "-") || + !common.StringDataContains(k.GetAvailablePairs(asset.Spot).Strings(), "-") { enabledPairs := currency.NewPairsFromStrings([]string{"XBT-USD"}) log.Warn("WARNING: Available pairs for Kraken reset due to config upgrade, please enable the ones you would like again") forceUpdate = true - err := k.UpdatePairs(enabledPairs, assets.AssetTypeSpot, true, true) + err := k.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { log.Errorf("%s failed to update currencies. Err: %s\n", k.Name, err) } @@ -163,7 +163,7 @@ func (k *Kraken) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (k *Kraken) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (k *Kraken) FetchTradablePairs(asset asset.Item) ([]string, error) { pairs, err := k.GetAssetPairs() if err != nil { return nil, err @@ -191,16 +191,16 @@ func (k *Kraken) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (k *Kraken) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := k.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := k.FetchTradablePairs(asset.Spot) if err != nil { return err } - return k.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return k.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (k *Kraken) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (k *Kraken) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price pairs := k.GetEnabledPairs(assetType) pairsCollated, err := k.FormatExchangeCurrencies(pairs, assetType) @@ -233,7 +233,7 @@ func (k *Kraken) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tick } // FetchTicker returns the ticker for a currency pair -func (k *Kraken) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (k *Kraken) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(k.GetName(), p, assetType) if err != nil { return k.UpdateTicker(p, assetType) @@ -242,7 +242,7 @@ func (k *Kraken) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticke } // FetchOrderbook returns orderbook base on the currency pair -func (k *Kraken) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (k *Kraken) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(k.GetName(), p, assetType) if err != nil { return k.UpdateOrderbook(p, assetType) @@ -251,7 +251,7 @@ func (k *Kraken) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (or } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (k *Kraken) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (k *Kraken) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := k.GetDepth(k.FormatExchangeCurrency(p, assetType).String()) @@ -313,7 +313,7 @@ func (k *Kraken) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (k *Kraken) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (k *Kraken) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -447,7 +447,7 @@ func (k *Kraken) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp.Open { symbol := currency.NewPairDelimiter(resp.Open[i].Descr.Pair, - k.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + k.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(resp.Open[i].StartTm), 0) side := exchange.OrderSide(strings.ToUpper(resp.Open[i].Descr.Type)) @@ -490,7 +490,7 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp.Closed { symbol := currency.NewPairDelimiter(resp.Closed[i].Descr.Pair, - k.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + k.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(resp.Closed[i].StartTm), 0) side := exchange.OrderSide(strings.ToUpper(resp.Closed[i].Descr.Type)) diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index a6e0ce57..6126b815 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -7,7 +7,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) var l LakeBTC @@ -39,7 +39,7 @@ func TestSetup(t *testing.T) { func TestFetchTradablePairs(t *testing.T) { t.Parallel() - _, err := l.FetchTradablePairs(assets.AssetTypeSpot) + _, err := l.FetchTradablePairs(asset.Spot) if err != nil { t.Fatalf("Test failed. GetTradablePairs err: %s", err) } diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index d49333b9..f222727f 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -51,8 +51,8 @@ func (l *LakeBTC) SetDefaults() { l.API.CredentialsValidator.RequiresSecret = true l.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, @@ -125,7 +125,7 @@ func (l *LakeBTC) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (l *LakeBTC) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (l *LakeBTC) FetchTradablePairs(asset asset.Item) ([]string, error) { result, err := l.GetTicker() if err != nil { return nil, err @@ -142,16 +142,16 @@ func (l *LakeBTC) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (l *LakeBTC) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := l.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := l.FetchTradablePairs(asset.Spot) if err != nil { return err } - return l.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return l.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tick, err := l.GetTicker() if err != nil { return ticker.Price{}, err @@ -177,7 +177,7 @@ func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tic } // FetchTicker returns the ticker for a currency pair -func (l *LakeBTC) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (l *LakeBTC) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType) if err != nil { return l.UpdateTicker(p, assetType) @@ -186,7 +186,7 @@ func (l *LakeBTC) FetchTicker(p currency.Pair, assetType assets.AssetType) (tick } // FetchOrderbook returns orderbook base on the currency pair -func (l *LakeBTC) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (l *LakeBTC) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(l.GetName(), p, assetType) if err != nil { return l.UpdateOrderbook(p, assetType) @@ -195,7 +195,7 @@ func (l *LakeBTC) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (o } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (l *LakeBTC) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (l *LakeBTC) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := l.GetOrderBook(p.String()) if err != nil { @@ -261,7 +261,7 @@ func (l *LakeBTC) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (l *LakeBTC) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (l *LakeBTC) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -394,7 +394,7 @@ func (l *LakeBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( var orders []exchange.OrderDetail for _, order := range resp { - symbol := currency.NewPairDelimiter(order.Symbol, l.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + symbol := currency.NewPairDelimiter(order.Symbol, l.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderDate := time.Unix(order.At, 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) @@ -431,7 +431,7 @@ func (l *LakeBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( } symbol := currency.NewPairDelimiter(order.Symbol, - l.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + l.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderDate := time.Unix(order.At, 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index caa93459..9193be10 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -13,7 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -52,8 +52,8 @@ func (l *LocalBitcoins) SetDefaults() { l.API.CredentialsValidator.RequiresSecret = true l.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, @@ -126,7 +126,7 @@ func (l *LocalBitcoins) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (l *LocalBitcoins) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (l *LocalBitcoins) FetchTradablePairs(asset asset.Item) ([]string, error) { currencies, err := l.GetTradableCurrencies() if err != nil { return nil, err @@ -143,16 +143,16 @@ func (l *LocalBitcoins) FetchTradablePairs(asset assets.AssetType) ([]string, er // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (l *LocalBitcoins) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := l.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := l.FetchTradablePairs(asset.Spot) if err != nil { return err } - return l.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return l.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (l *LocalBitcoins) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (l *LocalBitcoins) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := l.GetTicker() if err != nil { @@ -176,7 +176,7 @@ func (l *LocalBitcoins) UpdateTicker(p currency.Pair, assetType assets.AssetType } // FetchTicker returns the ticker for a currency pair -func (l *LocalBitcoins) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (l *LocalBitcoins) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType) if err != nil { return l.UpdateTicker(p, assetType) @@ -185,7 +185,7 @@ func (l *LocalBitcoins) FetchTicker(p currency.Pair, assetType assets.AssetType) } // FetchOrderbook returns orderbook base on the currency pair -func (l *LocalBitcoins) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (l *LocalBitcoins) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(l.GetName(), p, assetType) if err != nil { return l.UpdateOrderbook(p, assetType) @@ -194,7 +194,7 @@ func (l *LocalBitcoins) FetchOrderbook(p currency.Pair, assetType assets.AssetTy } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (l *LocalBitcoins) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (l *LocalBitcoins) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := l.GetOrderbook(p.Quote.String()) if err != nil { @@ -250,7 +250,7 @@ func (l *LocalBitcoins) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (l *LocalBitcoins) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (l *LocalBitcoins) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -443,7 +443,7 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequ OrderSide: side, CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), resp[i].Data.Currency, - l.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + l.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), Exchange: l.Name, }) } @@ -516,7 +516,7 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequ Status: status, CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), allTrades[i].Data.Currency, - l.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + l.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), Exchange: l.Name, }) } diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 68397c3f..0c94dc04 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -8,7 +8,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/request" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -49,9 +49,9 @@ func (o *OKCoin) SetDefaults() { o.API.CredentialsValidator.RequiresClientID = true o.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, - assets.AssetTypeMargin, + AssetTypes: asset.Items{ + asset.Spot, + asset.Margin, }, UseGlobalFormat: true, @@ -127,7 +127,7 @@ func (o *OKCoin) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (o *OKCoin) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (o *OKCoin) FetchTradablePairs(asset asset.Item) ([]string, error) { prods, err := o.GetSpotTokenPairDetails() if err != nil { return nil, err @@ -144,11 +144,11 @@ func (o *OKCoin) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (o *OKCoin) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := o.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := o.FetchTradablePairs(asset.Spot) if err != nil { return err } return o.UpdatePairs(currency.NewPairsFromStrings(pairs), - assets.AssetTypeSpot, false, forceUpdate) + asset.Spot, false, forceUpdate) } diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go index 24105113..92a9cbb1 100644 --- a/exchanges/okex/okex_wrapper.go +++ b/exchanges/okex/okex_wrapper.go @@ -9,7 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/request" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -49,11 +49,11 @@ func (o *OKEX) SetDefaults() { o.API.CredentialsValidator.RequiresClientID = true o.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, - assets.AssetTypeFutures, - assets.AssetTypePerpetualSwap, - assets.AssetTypeIndex, + AssetTypes: asset.Items{ + asset.Spot, + asset.Futures, + asset.PerpetualSwap, + asset.Index, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -127,10 +127,10 @@ func (o *OKEX) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (o *OKEX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { var pairs []string - switch asset { - case assets.AssetTypeSpot: + switch i { + case asset.Spot: prods, err := o.GetSpotTokenPairDetails() if err != nil { return nil, err @@ -140,7 +140,7 @@ func (o *OKEX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency) } return pairs, nil - case assets.AssetTypeFutures: + case asset.Futures: prods, err := o.GetFuturesContractInformation() if err != nil { return nil, err @@ -152,7 +152,7 @@ func (o *OKEX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { } return pairs, nil - case assets.AssetTypePerpetualSwap: + case asset.PerpetualSwap: prods, err := o.GetSwapContractInformation() if err != nil { return nil, err @@ -163,7 +163,7 @@ func (o *OKEX) FetchTradablePairs(asset assets.AssetType) ([]string, error) { pairs = append(pairs, prods[x].UnderlyingIndex+"_"+prods[x].QuoteCurrency+"_SWAP") } return pairs, nil - case assets.AssetTypeIndex: + case asset.Index: return []string{"BTC_USD"}, nil } diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index 445de1c3..9aebb51c 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -16,7 +16,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common/crypto" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/currency" @@ -363,9 +363,9 @@ func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string { // GetAssetTypeFromTableName gets the asset type from the table name // eg "spot/ticker:BTCUSD" results in "SPOT" -func (o *OKGroup) GetAssetTypeFromTableName(table string) assets.AssetType { +func (o *OKGroup) GetAssetTypeFromTableName(table string) asset.Item { assetIndex := strings.Index(table, "/") - return assets.AssetType(strings.ToUpper(table[:assetIndex])) + return asset.Item(strings.ToUpper(table[:assetIndex])) } // WsHandleDataResponse classifies the WS response and sends to appropriate handler @@ -683,7 +683,7 @@ func (o *OKGroup) CalculateUpdateOrderbookChecksum(orderbookData *orderbook.Base // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (o *OKGroup) GenerateDefaultSubscriptions() { - enabledCurrencies := o.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := o.GetEnabledPairs(asset.Spot) subscriptions := []exchange.WebsocketChannelSubscription{} for i := range defaultSubscribedChannels { for j := range enabledCurrencies { diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index f4367e2c..9e839b56 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -9,7 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/ticker" log "github.com/thrasher-/gocryptotrader/logger" @@ -42,7 +42,7 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) error { } // UpdateTicker updates and returns the ticker for a currency pair -func (o *OKGroup) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tickerData ticker.Price, err error) { +func (o *OKGroup) UpdateTicker(p currency.Pair, assetType asset.Item) (tickerData ticker.Price, err error) { resp, err := o.GetSpotAllTokenPairsInformationForCurrency(o.FormatExchangeCurrency(p, assetType).String()) if err != nil { return @@ -63,7 +63,7 @@ func (o *OKGroup) UpdateTicker(p currency.Pair, assetType assets.AssetType) (tic } // FetchTicker returns the ticker for a currency pair -func (o *OKGroup) FetchTicker(p currency.Pair, assetType assets.AssetType) (tickerData ticker.Price, err error) { +func (o *OKGroup) FetchTicker(p currency.Pair, assetType asset.Item) (tickerData ticker.Price, err error) { tickerData, err = ticker.GetTicker(o.GetName(), p, assetType) if err != nil { return o.UpdateTicker(p, assetType) @@ -72,7 +72,7 @@ func (o *OKGroup) FetchTicker(p currency.Pair, assetType assets.AssetType) (tick } // FetchOrderbook returns orderbook base on the currency pair -func (o *OKGroup) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (resp orderbook.Base, err error) { +func (o *OKGroup) FetchOrderbook(p currency.Pair, assetType asset.Item) (resp orderbook.Base, err error) { ob, err := orderbook.Get(o.GetName(), p, assetType) if err != nil { return o.UpdateOrderbook(p, assetType) @@ -81,7 +81,7 @@ func (o *OKGroup) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (r } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (o *OKGroup) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (resp orderbook.Base, err error) { +func (o *OKGroup) UpdateOrderbook(p currency.Pair, assetType asset.Item) (resp orderbook.Base, err error) { orderbookNew, err := o.GetSpotOrderBook(GetSpotOrderBookRequest{ InstrumentID: o.FormatExchangeCurrency(p, assetType).String(), }) @@ -201,7 +201,7 @@ func (o *OKGroup) GetFundingHistory() (resp []exchange.FundHistory, err error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (o *OKGroup) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (o *OKGroup) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -218,7 +218,7 @@ func (o *OKGroup) SubmitOrder(order *exchange.OrderSubmission) (resp exchange.Su request := PlaceSpotOrderRequest{ ClientOID: order.ClientID, - InstrumentID: o.FormatExchangeCurrency(order.Pair, assets.AssetTypeSpot).String(), + InstrumentID: o.FormatExchangeCurrency(order.Pair, asset.Spot).String(), Side: strings.ToLower(order.OrderSide.ToString()), Type: strings.ToLower(order.OrderType.ToString()), Size: strconv.FormatFloat(order.Amount, 'f', -1, 64), @@ -251,7 +251,7 @@ func (o *OKGroup) CancelOrder(orderCancellation *exchange.OrderCancellation) (er } orderCancellationResponse, err := o.CancelSpotOrder(CancelSpotOrderRequest{ InstrumentID: o.FormatExchangeCurrency(orderCancellation.CurrencyPair, - assets.AssetTypeSpot).String(), + asset.Spot).String(), OrderID: orderID, }) if !orderCancellationResponse.Result { @@ -277,7 +277,7 @@ func (o *OKGroup) CancelAllOrders(orderCancellation *exchange.OrderCancellation) cancelOrdersResponse, err := o.CancelMultipleSpotOrders(CancelMultipleSpotOrdersRequest{ InstrumentID: o.FormatExchangeCurrency(orderCancellation.CurrencyPair, - assets.AssetTypeSpot).String(), + asset.Spot).String(), OrderIDs: orderIDNumbers, }) if err != nil { @@ -302,7 +302,7 @@ func (o *OKGroup) GetOrderInfo(orderID string) (resp exchange.OrderDetail, err e resp = exchange.OrderDetail{ Amount: order.Size, CurrencyPair: currency.NewPairDelimiter(order.InstrumentID, - o.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter), + o.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), Exchange: o.Name, OrderDate: order.Timestamp, ExecutedAmount: order.FilledSize, @@ -359,7 +359,7 @@ func (o *OKGroup) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( for _, currency := range getOrdersRequest.Currencies { spotOpenOrders, err := o.GetSpotOpenOrders(GetSpotOpenOrdersRequest{ InstrumentID: o.FormatExchangeCurrency(currency, - assets.AssetTypeSpot).String(), + asset.Spot).String(), }) if err != nil { return resp, err @@ -390,7 +390,7 @@ func (o *OKGroup) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( spotOpenOrders, err := o.GetSpotOrders(GetSpotOrdersRequest{ Status: strings.Join([]string{"filled", "cancelled", "failure"}, "|"), InstrumentID: o.FormatExchangeCurrency(currency, - assets.AssetTypeSpot).String(), + asset.Spot).String(), }) if err != nil { return resp, err diff --git a/exchanges/order_types.go b/exchanges/order_types.go index ebfe59f5..ae5375c1 100644 --- a/exchanges/order_types.go +++ b/exchanges/order_types.go @@ -8,7 +8,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // vars related to orders @@ -153,7 +153,7 @@ type OrderCancellation struct { AccountID string OrderID string CurrencyPair currency.Pair - AssetType assets.AssetType + AssetType asset.Item WalletAddress string Side OrderSide } diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index f5267ad3..d0d20d68 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -6,7 +6,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // Const values for orderbook package @@ -31,17 +31,17 @@ type Item struct { // Base holds the fields for the orderbook base type Base struct { - Pair currency.Pair `json:"pair"` - Bids []Item `json:"bids"` - Asks []Item `json:"asks"` - LastUpdated time.Time `json:"lastUpdated"` - AssetType assets.AssetType `json:"assetType"` - ExchangeName string `json:"exchangeName"` + Pair currency.Pair `json:"pair"` + Bids []Item `json:"bids"` + Asks []Item `json:"asks"` + LastUpdated time.Time `json:"lastUpdated"` + AssetType asset.Item `json:"assetType"` + ExchangeName string `json:"exchangeName"` } // Orderbook holds the orderbook information for a currency pair and type type Orderbook struct { - Orderbook map[*currency.Item]map[*currency.Item]map[assets.AssetType]Base + Orderbook map[*currency.Item]map[*currency.Item]map[asset.Item]Base ExchangeName string } @@ -74,7 +74,7 @@ func (o *Base) Update(bids, asks []Item) { // Get checks and returns the orderbook given an exchange name and currency pair // if it exists -func Get(exchange string, p currency.Pair, orderbookType assets.AssetType) (Base, error) { +func Get(exchange string, p currency.Pair, orderbookType asset.Item) (Base, error) { orderbook, err := GetByExchange(exchange) if err != nil { return Base{}, err @@ -136,14 +136,14 @@ func QuoteCurrencyExists(exchange string, p currency.Pair) bool { } // CreateNewOrderbook creates a new orderbook -func CreateNewOrderbook(exchangeName string, orderbookNew *Base, orderbookType assets.AssetType) *Orderbook { +func CreateNewOrderbook(exchangeName string, orderbookNew *Base, orderbookType asset.Item) *Orderbook { m.Lock() defer m.Unlock() orderbook := Orderbook{} orderbook.ExchangeName = exchangeName - orderbook.Orderbook = make(map[*currency.Item]map[*currency.Item]map[assets.AssetType]Base) - a := make(map[*currency.Item]map[assets.AssetType]Base) - b := make(map[assets.AssetType]Base) + orderbook.Orderbook = make(map[*currency.Item]map[*currency.Item]map[asset.Item]Base) + a := make(map[*currency.Item]map[asset.Item]Base) + b := make(map[asset.Item]Base) b[orderbookType] = *orderbookNew a[orderbookNew.Pair.Quote.Item] = b orderbook.Orderbook[orderbookNew.Pair.Base.Item] = a @@ -174,7 +174,7 @@ func (o *Base) Process() error { if BaseCurrencyExists(o.ExchangeName, o.Pair.Base) { m.Lock() - a := make(map[assets.AssetType]Base) + a := make(map[asset.Item]Base) a[o.AssetType] = *o orderbook.Orderbook[o.Pair.Base.Item][o.Pair.Quote.Item] = a m.Unlock() @@ -182,8 +182,8 @@ func (o *Base) Process() error { } m.Lock() - a := make(map[*currency.Item]map[assets.AssetType]Base) - b := make(map[assets.AssetType]Base) + a := make(map[*currency.Item]map[asset.Item]Base) + b := make(map[asset.Item]Base) b[o.AssetType] = *o a[o.Pair.Quote.Item] = b orderbook.Orderbook[o.Pair.Base.Item] = a diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index cea7c580..3944ee03 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -80,9 +80,9 @@ func TestGetOrderbook(t *testing.T) { Bids: []Item{{Price: 200, Amount: 10}}, } - CreateNewOrderbook("Exchange", &base, assets.AssetTypeSpot) + CreateNewOrderbook("Exchange", &base, asset.Spot) - result, err := Get("Exchange", c, assets.AssetTypeSpot) + result, err := Get("Exchange", c, asset.Spot) if err != nil { t.Fatalf("Test failed. TestGetOrderbook failed to get orderbook. Error %s", err) @@ -91,19 +91,19 @@ func TestGetOrderbook(t *testing.T) { t.Fatal("Test failed. TestGetOrderbook failed. Mismatched pairs") } - _, err = Get("nonexistent", c, assets.AssetTypeSpot) + _, err = Get("nonexistent", c, asset.Spot) if err == nil { t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook") } c.Base = currency.NewCode("blah") - _, err = Get("Exchange", c, assets.AssetTypeSpot) + _, err = Get("Exchange", c, asset.Spot) if err == nil { t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook using invalid first currency") } newCurrency := currency.NewPairFromStrings("BTC", "AUD") - _, err = Get("Exchange", newCurrency, assets.AssetTypeSpot) + _, err = Get("Exchange", newCurrency, asset.Spot) if err == nil { t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook using invalid second currency") } @@ -117,7 +117,7 @@ func TestGetOrderbookByExchange(t *testing.T) { Bids: []Item{{Price: 200, Amount: 10}}, } - CreateNewOrderbook("Exchange", &base, assets.AssetTypeSpot) + CreateNewOrderbook("Exchange", &base, asset.Spot) _, err := GetByExchange("Exchange") if err != nil { @@ -139,7 +139,7 @@ func TestFirstCurrencyExists(t *testing.T) { Bids: []Item{{Price: 200, Amount: 10}}, } - CreateNewOrderbook("Exchange", &base, assets.AssetTypeSpot) + CreateNewOrderbook("Exchange", &base, asset.Spot) if !BaseCurrencyExists("Exchange", c.Base) { t.Fatal("Test failed. TestFirstCurrencyExists expected first currency doesn't exist") @@ -159,7 +159,7 @@ func TestSecondCurrencyExists(t *testing.T) { Bids: []Item{{Price: 200, Amount: 10}}, } - CreateNewOrderbook("Exchange", &base, assets.AssetTypeSpot) + CreateNewOrderbook("Exchange", &base, asset.Spot) if !QuoteCurrencyExists("Exchange", c) { t.Fatal("Test failed. TestSecondCurrencyExists expected first currency doesn't exist") @@ -179,9 +179,9 @@ func TestCreateNewOrderbook(t *testing.T) { Bids: []Item{{Price: 200, Amount: 10}}, } - CreateNewOrderbook("Exchange", &base, assets.AssetTypeSpot) + CreateNewOrderbook("Exchange", &base, asset.Spot) - result, err := Get("Exchange", c, assets.AssetTypeSpot) + result, err := Get("Exchange", c, asset.Spot) if err != nil { t.Fatal("Test failed. TestCreateNewOrderbook failed to create new orderbook") } @@ -209,7 +209,7 @@ func TestProcessOrderbook(t *testing.T) { Asks: []Item{{Price: 100, Amount: 10}}, Bids: []Item{{Price: 200, Amount: 10}}, ExchangeName: "Exchange", - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, } err := base.Process() @@ -217,7 +217,7 @@ func TestProcessOrderbook(t *testing.T) { t.Error("Test Failed - Process() error", err) } - result, err := Get("Exchange", c, assets.AssetTypeSpot) + result, err := Get("Exchange", c, asset.Spot) if err != nil { t.Fatal("Test failed. TestProcessOrderbook failed to create new orderbook") } @@ -234,7 +234,7 @@ func TestProcessOrderbook(t *testing.T) { t.Error("Test Failed - Process() error", err) } - result, err = Get("Exchange", c, assets.AssetTypeSpot) + result, err = Get("Exchange", c, asset.Spot) if err != nil { t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") } @@ -311,7 +311,7 @@ func TestProcessOrderbook(t *testing.T) { Asks: asks, Bids: bids, ExchangeName: newName, - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, } m.Lock() @@ -338,7 +338,7 @@ func TestProcessOrderbook(t *testing.T) { wg.Add(1) fatalErr := false go func(test quick) { - result, err := Get(test.Name, test.P, assets.AssetTypeSpot) + result, err := Get(test.Name, test.P, asset.Spot) if err != nil { fatalErr = true return diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index ed43d954..a4f91ff0 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -13,7 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -163,7 +163,7 @@ func (p *Poloniex) WsHandleData() { Timestamp: time.Now(), Pair: currency.NewPairDelimiter(currencyPair, "_"), Exchange: p.GetName(), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, ClosePrice: t.LastPrice, LowPrice: t.LowestAsk, HighPrice: t.HighestBid, @@ -201,7 +201,7 @@ func (p *Poloniex) WsHandleData() { p.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: p.GetName(), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Pair: currency.NewPairFromString(currencyPair), } case "o": @@ -214,7 +214,7 @@ func (p *Poloniex) WsHandleData() { p.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: p.GetName(), - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Pair: currency.NewPairFromString(currencyPair), } case "t": @@ -292,7 +292,7 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.AssetType = asset.Spot newOrderBook.LastUpdated = time.Now() newOrderBook.Pair = currency.NewPairFromString(symbol) @@ -321,7 +321,7 @@ func (p *Poloniex) WsProcessOrderbookUpdate(target []interface{}, symbol string) cP, time.Now(), p.GetName(), - assets.AssetTypeSpot) + asset.Spot) } return p.Websocket.Orderbook.Update([]orderbook.Item{{Price: price, Amount: volume}}, @@ -329,7 +329,7 @@ func (p *Poloniex) WsProcessOrderbookUpdate(target []interface{}, symbol string) cP, time.Now(), p.GetName(), - assets.AssetTypeSpot) + asset.Spot) } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() @@ -340,7 +340,7 @@ func (p *Poloniex) GenerateDefaultSubscriptions() { Channel: fmt.Sprintf("%v", wsTickerDataID), }) - enabledCurrencies := p.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := p.GetEnabledPairs(asset.Spot) for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "_" subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{ diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 341de2fc..2f9ea6ca 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -11,7 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -50,8 +50,8 @@ func (p *Poloniex) SetDefaults() { p.API.CredentialsValidator.RequiresSecret = true p.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -134,7 +134,7 @@ func (p *Poloniex) Run() { } forceUpdate := false - if common.StringDataCompare(p.GetAvailablePairs(assets.AssetTypeSpot).Strings(), "BTC_USDT") { + if common.StringDataCompare(p.GetAvailablePairs(asset.Spot).Strings(), "BTC_USDT") { log.Warnf("%s contains invalid pair, forcing upgrade of available currencies.\n", p.Name) forceUpdate = true @@ -151,7 +151,7 @@ func (p *Poloniex) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (p *Poloniex) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (p *Poloniex) FetchTradablePairs(asset asset.Item) ([]string, error) { resp, err := p.GetTicker() if err != nil { return nil, err @@ -168,16 +168,16 @@ func (p *Poloniex) FetchTradablePairs(asset assets.AssetType) ([]string, error) // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (p *Poloniex) UpdateTradablePairs(forceUpgrade bool) error { - pairs, err := p.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := p.FetchTradablePairs(asset.Spot) if err != nil { return err } - return p.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpgrade) + return p.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpgrade) } // UpdateTicker updates and returns the ticker for a currency pair -func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := p.GetTicker() if err != nil { @@ -204,7 +204,7 @@ func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType assets.Ass } // FetchTicker returns the ticker for a currency pair -func (p *Poloniex) FetchTicker(currencyPair currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (p *Poloniex) FetchTicker(currencyPair currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair, assetType) if err != nil { return p.UpdateTicker(currencyPair, assetType) @@ -213,7 +213,7 @@ func (p *Poloniex) FetchTicker(currencyPair currency.Pair, assetType assets.Asse } // FetchOrderbook returns orderbook base on the currency pair -func (p *Poloniex) FetchOrderbook(currencyPair currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (p *Poloniex) FetchOrderbook(currencyPair currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(p.GetName(), currencyPair, assetType) if err != nil { return p.UpdateOrderbook(currencyPair, assetType) @@ -222,7 +222,7 @@ func (p *Poloniex) FetchOrderbook(currencyPair currency.Pair, assetType assets.A } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := p.GetOrderbook("", 1000) if err != nil { @@ -297,7 +297,7 @@ func (p *Poloniex) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (p *Poloniex) GetExchangeHistory(currencyPair currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (p *Poloniex) GetExchangeHistory(currencyPair currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -446,7 +446,7 @@ func (p *Poloniex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) var orders []exchange.OrderDetail for currencyPair, openOrders := range resp.Data { symbol := currency.NewPairDelimiter(currencyPair, - p.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + p.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) for _, order := range openOrders { orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) @@ -488,7 +488,7 @@ func (p *Poloniex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) var orders []exchange.OrderDetail for currencyPair, historicOrders := range resp.Data { symbol := currency.NewPairDelimiter(currencyPair, - p.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + p.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) for _, order := range historicOrders { orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) diff --git a/exchanges/stats/stats.go b/exchanges/stats/stats.go index d9470159..698548f0 100644 --- a/exchanges/stats/stats.go +++ b/exchanges/stats/stats.go @@ -4,14 +4,14 @@ import ( "sort" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // Item holds various fields for storing currency pair stats type Item struct { Exchange string Pair currency.Pair - AssetType assets.AssetType + AssetType asset.Item Price float64 Volume float64 } @@ -50,7 +50,7 @@ func (b ByVolume) Swap(i, j int) { } // Add adds or updates the item stats -func Add(exchange string, p currency.Pair, assetType assets.AssetType, price, volume float64) { +func Add(exchange string, p currency.Pair, assetType asset.Item, price, volume float64) { if exchange == "" || assetType == "" || price == 0 || @@ -75,7 +75,7 @@ func Add(exchange string, p currency.Pair, assetType assets.AssetType, price, vo // Append adds or updates the item stats for a specific // currency pair and asset type -func Append(exchange string, p currency.Pair, assetType assets.AssetType, price, volume float64) { +func Append(exchange string, p currency.Pair, assetType asset.Item, price, volume float64) { if AlreadyExists(exchange, p, assetType, price, volume) { return } @@ -93,7 +93,7 @@ func Append(exchange string, p currency.Pair, assetType assets.AssetType, price, // AlreadyExists checks to see if item info already exists // for a specific currency pair and asset type -func AlreadyExists(exchange string, p currency.Pair, assetType assets.AssetType, price, volume float64) bool { +func AlreadyExists(exchange string, p currency.Pair, assetType asset.Item, price, volume float64) bool { for i := range Items { if Items[i].Exchange == exchange && Items[i].Pair.EqualIncludeReciprocal(p) && @@ -108,7 +108,7 @@ func AlreadyExists(exchange string, p currency.Pair, assetType assets.AssetType, // 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 currency.Pair, assetType assets.AssetType, reverse bool) []Item { +func SortExchangesByVolume(p currency.Pair, assetType asset.Item, reverse bool) []Item { var result []Item for x := range Items { if Items[x].Pair.EqualIncludeReciprocal(p) && @@ -128,7 +128,7 @@ func SortExchangesByVolume(p currency.Pair, assetType assets.AssetType, reverse // 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 currency.Pair, assetType assets.AssetType, reverse bool) []Item { +func SortExchangesByPrice(p currency.Pair, assetType asset.Item, reverse bool) []Item { var result []Item for x := range Items { if Items[x].Pair.EqualIncludeReciprocal(p) && diff --git a/exchanges/stats/stats_test.go b/exchanges/stats/stats_test.go index 54449cb5..bcb54ac3 100644 --- a/exchanges/stats/stats_test.go +++ b/exchanges/stats/stats_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) func TestLenByPrice(t *testing.T) { @@ -13,7 +13,7 @@ func TestLenByPrice(t *testing.T) { { Exchange: "ANX", Pair: p, - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Price: 1200, Volume: 5, }, @@ -31,14 +31,14 @@ func TestLessByPrice(t *testing.T) { { Exchange: "alphapoint", Pair: p, - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Price: 1200, Volume: 5, }, { Exchange: "bitfinex", Pair: p, - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Price: 1198, Volume: 20, }, @@ -59,14 +59,14 @@ func TestSwapByPrice(t *testing.T) { { Exchange: "bitstamp", Pair: p, - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Price: 1324, Volume: 5, }, { Exchange: "bitfinex", Pair: p, - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Price: 7863, Volume: 20, }, @@ -104,7 +104,7 @@ func TestSwapByVolume(t *testing.T) { func TestAdd(t *testing.T) { Items = Items[:0] p := currency.NewPairFromStrings("BTC", "USD") - Add("ANX", p, assets.AssetTypeSpot, 1200, 42) + Add("ANX", p, asset.Spot, 1200, 42) if len(Items) < 1 { t.Error("Test Failed - stats Add did not add exchange info.") @@ -117,14 +117,14 @@ func TestAdd(t *testing.T) { } p.Base = currency.XBT - Add("ANX", p, assets.AssetTypeSpot, 1201, 43) + Add("ANX", p, asset.Spot, 1201, 43) if Items[1].Pair.String() != "XBTUSD" { t.Fatal("Test failed. stats Add did not add exchange info.") } p = currency.NewPairFromStrings("ETH", "USDT") - Add("ANX", p, assets.AssetTypeSpot, 300, 1000) + Add("ANX", p, asset.Spot, 300, 1000) if Items[2].Pair.String() != "ETHUSD" { t.Fatal("Test failed. stats Add did not add exchange info.") @@ -133,12 +133,12 @@ func TestAdd(t *testing.T) { func TestAppend(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") - Append("sillyexchange", p, assets.AssetTypeSpot, 1234, 45) + Append("sillyexchange", p, asset.Spot, 1234, 45) if len(Items) < 2 { t.Error("Test Failed - stats Append did not add exchange values.") } - Append("sillyexchange", p, assets.AssetTypeSpot, 1234, 45) + Append("sillyexchange", p, asset.Spot, 1234, 45) if len(Items) == 3 { t.Error("Test Failed - stats Append added exchange values") } @@ -146,23 +146,23 @@ func TestAppend(t *testing.T) { func TestAlreadyExists(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") - if !AlreadyExists("ANX", p, assets.AssetTypeSpot, 1200, 42) { + if !AlreadyExists("ANX", p, asset.Spot, 1200, 42) { t.Error("Test Failed - stats AlreadyExists exchange does not exist.") } p.Base = currency.NewCode("dii") - if AlreadyExists("bla", p, assets.AssetTypeSpot, 1234, 123) { + if AlreadyExists("bla", p, asset.Spot, 1234, 123) { t.Error("Test Failed - stats AlreadyExists found incorrect exchange.") } } func TestSortExchangesByVolume(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") - topVolume := SortExchangesByVolume(p, assets.AssetTypeSpot, true) + topVolume := SortExchangesByVolume(p, asset.Spot, true) if topVolume[0].Exchange != "sillyexchange" { t.Error("Test Failed - stats SortExchangesByVolume incorrectly sorted values.") } - topVolume = SortExchangesByVolume(p, assets.AssetTypeSpot, false) + topVolume = SortExchangesByVolume(p, asset.Spot, false) if topVolume[0].Exchange != "ANX" { t.Error("Test Failed - stats SortExchangesByVolume incorrectly sorted values.") } @@ -170,12 +170,12 @@ func TestSortExchangesByVolume(t *testing.T) { func TestSortExchangesByPrice(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") - topPrice := SortExchangesByPrice(p, assets.AssetTypeSpot, true) + topPrice := SortExchangesByPrice(p, asset.Spot, true) if topPrice[0].Exchange != "sillyexchange" { t.Error("Test Failed - stats SortExchangesByPrice incorrectly sorted values.") } - topPrice = SortExchangesByPrice(p, assets.AssetTypeSpot, false) + topPrice = SortExchangesByPrice(p, asset.Spot, false) if topPrice[0].Exchange != "ANX" { t.Error("Test Failed - stats SortExchangesByPrice incorrectly sorted values.") } diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index e30bcaf7..c0745f01 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -8,7 +8,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) // Const values for the ticker package @@ -44,7 +44,7 @@ type Ticker struct { } // PriceToString returns the string version of a stored price field -func (t *Ticker) PriceToString(p currency.Pair, priceType string, tickerType assets.AssetType) string { +func (t *Ticker) PriceToString(p currency.Pair, priceType string, tickerType asset.Item) string { priceType = strings.ToLower(priceType) switch priceType { @@ -68,7 +68,7 @@ func (t *Ticker) PriceToString(p currency.Pair, priceType string, tickerType ass } // GetTicker checks and returns a requested ticker if it exists -func GetTicker(exchange string, p currency.Pair, tickerType assets.AssetType) (Price, error) { +func GetTicker(exchange string, p currency.Pair, tickerType asset.Item) (Price, error) { ticker, err := GetTickerByExchange(exchange) if err != nil { return Price{}, err @@ -130,7 +130,7 @@ func SecondCurrencyExists(exchange string, p currency.Pair) bool { } // CreateNewTicker creates a new Ticker -func CreateNewTicker(exchangeName string, tickerNew *Price, tickerType assets.AssetType) Ticker { +func CreateNewTicker(exchangeName string, tickerNew *Price, tickerType asset.Item) Ticker { m.Lock() defer m.Unlock() ticker := Ticker{} @@ -147,7 +147,7 @@ func CreateNewTicker(exchangeName string, tickerNew *Price, tickerType assets.As // ProcessTicker processes incoming tickers, creating or updating the Tickers // list -func ProcessTicker(exchangeName string, tickerNew *Price, tickerType assets.AssetType) error { +func ProcessTicker(exchangeName string, tickerNew *Price, tickerType asset.Item) error { if tickerNew.Pair.String() == "" { return errors.New("") } diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index 038f6a73..4d357566 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -26,30 +26,30 @@ func TestPriceToString(t *testing.T) { PriceATH: 1337, } - newTicker := CreateNewTicker("ANX", &priceStruct, assets.AssetTypeSpot) + newTicker := CreateNewTicker("ANX", &priceStruct, asset.Spot) - if newTicker.PriceToString(newPair, "last", assets.AssetTypeSpot) != "1200" { + if newTicker.PriceToString(newPair, "last", asset.Spot) != "1200" { t.Error("Test Failed - ticker PriceToString last value is incorrect") } - if newTicker.PriceToString(newPair, "high", assets.AssetTypeSpot) != "1298" { + if newTicker.PriceToString(newPair, "high", asset.Spot) != "1298" { t.Error("Test Failed - ticker PriceToString high value is incorrect") } - if newTicker.PriceToString(newPair, "low", assets.AssetTypeSpot) != "1148" { + if newTicker.PriceToString(newPair, "low", asset.Spot) != "1148" { t.Error("Test Failed - ticker PriceToString low value is incorrect") } - if newTicker.PriceToString(newPair, "bid", assets.AssetTypeSpot) != "1195" { + if newTicker.PriceToString(newPair, "bid", asset.Spot) != "1195" { t.Error("Test Failed - ticker PriceToString bid value is incorrect") } - if newTicker.PriceToString(newPair, "ask", assets.AssetTypeSpot) != "1220" { + if newTicker.PriceToString(newPair, "ask", asset.Spot) != "1220" { t.Error("Test Failed - ticker PriceToString ask value is incorrect") } - if newTicker.PriceToString(newPair, "volume", assets.AssetTypeSpot) != "5" { + if newTicker.PriceToString(newPair, "volume", asset.Spot) != "5" { t.Error("Test Failed - ticker PriceToString volume value is incorrect") } - if newTicker.PriceToString(newPair, "ath", assets.AssetTypeSpot) != "1337" { + if newTicker.PriceToString(newPair, "ath", asset.Spot) != "1337" { t.Error("Test Failed - ticker PriceToString ath value is incorrect") } - if newTicker.PriceToString(newPair, "obtuse", assets.AssetTypeSpot) != "" { + if newTicker.PriceToString(newPair, "obtuse", asset.Spot) != "" { t.Error("Test Failed - ticker PriceToString obtuse value is incorrect") } } @@ -67,12 +67,12 @@ func TestGetTicker(t *testing.T) { PriceATH: 1337, } - err := ProcessTicker("bitfinex", &priceStruct, assets.AssetTypeSpot) + err := ProcessTicker("bitfinex", &priceStruct, asset.Spot) if err != nil { t.Fatal("Test failed. ProcessTicker error", err) } - tickerPrice, err := GetTicker("bitfinex", newPair, assets.AssetTypeSpot) + tickerPrice, err := GetTicker("bitfinex", newPair, asset.Spot) if err != nil { t.Errorf("Test Failed - Ticker GetTicker init error: %s", err) } @@ -80,19 +80,19 @@ func TestGetTicker(t *testing.T) { t.Error("Test Failed - ticker tickerPrice.CurrencyPair value is incorrect") } - _, err = GetTicker("blah", newPair, assets.AssetTypeSpot) + _, err = GetTicker("blah", newPair, asset.Spot) if err == nil { t.Fatal("Test Failed. TestGetTicker returned nil error on invalid exchange") } newPair.Base = currency.ETH - _, err = GetTicker("bitfinex", newPair, assets.AssetTypeSpot) + _, err = GetTicker("bitfinex", newPair, asset.Spot) if err == nil { t.Fatal("Test Failed. TestGetTicker returned ticker for invalid first currency") } btcltcPair := currency.NewPairFromStrings("BTC", "LTC") - _, err = GetTicker("bitfinex", btcltcPair, assets.AssetTypeSpot) + _, err = GetTicker("bitfinex", btcltcPair, asset.Spot) if err == nil { t.Fatal("Test Failed. TestGetTicker returned ticker for invalid second currency") } @@ -127,7 +127,7 @@ func TestGetTickerByExchange(t *testing.T) { PriceATH: 1337, } - anxTicker := CreateNewTicker("ANX", &priceStruct, assets.AssetTypeSpot) + anxTicker := CreateNewTicker("ANX", &priceStruct, asset.Spot) Tickers = append(Tickers, anxTicker) tickerPtr, err := GetTickerByExchange("ANX") @@ -152,7 +152,7 @@ func TestFirstCurrencyExists(t *testing.T) { PriceATH: 1337, } - alphaTicker := CreateNewTicker("alphapoint", &priceStruct, assets.AssetTypeSpot) + alphaTicker := CreateNewTicker("alphapoint", &priceStruct, asset.Spot) Tickers = append(Tickers, alphaTicker) if !FirstCurrencyExists("alphapoint", currency.BTC) { @@ -178,7 +178,7 @@ func TestSecondCurrencyExists(t *testing.T) { PriceATH: 1337, } - bitstampTicker := CreateNewTicker("bitstamp", &priceStruct, assets.AssetTypeSpot) + bitstampTicker := CreateNewTicker("bitstamp", &priceStruct, asset.Spot) Tickers = append(Tickers, bitstampTicker) if !SecondCurrencyExists("bitstamp", newPair) { @@ -205,7 +205,7 @@ func TestCreateNewTicker(t *testing.T) { PriceATH: 1337, } - newTicker := CreateNewTicker("ANX", &priceStruct, assets.AssetTypeSpot) + newTicker := CreateNewTicker("ANX", &priceStruct, asset.Spot) if reflect.ValueOf(newTicker).NumField() != 2 { t.Error("Test Failed - ticker CreateNewTicker struct change/or updated") @@ -217,31 +217,31 @@ func TestCreateNewTicker(t *testing.T) { t.Error("Test Failed - ticker CreateNewTicker.ExchangeName value is not ANX") } - if newTicker.Price[currency.BTC.Upper().String()][currency.USD.Upper().String()][assets.AssetTypeSpot.String()].Pair.String() != "BTCUSD" { + if newTicker.Price[currency.BTC.Upper().String()][currency.USD.Upper().String()][asset.Spot.String()].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"][assets.AssetTypeSpot.String()].Ask).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Ask).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Ask value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Bid).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Bid).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Bid value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Pair).String() != "currency.Pair" { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Pair).String() != "currency.Pair" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].CurrencyPair value is not a currency.Pair") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].High).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].High).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].High value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Last).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Last).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Last value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Low).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Low).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Low value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].PriceATH).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].PriceATH).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].PriceATH value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][assets.AssetTypeSpot.String()].Volume).String() != float64Type { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Volume).String() != float64Type { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Volume value is not a float64") } } @@ -261,17 +261,17 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers PriceATH: 1337, } - err := ProcessTicker(exchName, &Price{}, assets.AssetTypeSpot) + err := ProcessTicker(exchName, &Price{}, asset.Spot) if err == nil { t.Fatal("Test failed. ProcessTicker error cannot be nil") } - err = ProcessTicker(exchName, &priceStruct, assets.AssetTypeSpot) + err = ProcessTicker(exchName, &priceStruct, asset.Spot) if err != nil { t.Fatal("Test failed. ProcessTicker error", err) } - result, err := GetTicker(exchName, newPair, assets.AssetTypeSpot) + result, err := GetTicker(exchName, newPair, asset.Spot) if err != nil { t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker") } @@ -282,17 +282,17 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers secondPair := currency.NewPairFromStrings("BTC", "AUD") priceStruct.Pair = secondPair - err = ProcessTicker(exchName, &priceStruct, assets.AssetTypeSpot) + err = ProcessTicker(exchName, &priceStruct, asset.Spot) if err != nil { t.Fatal("Test failed. ProcessTicker error", err) } - result, err = GetTicker(exchName, secondPair, assets.AssetTypeSpot) + result, err = GetTicker(exchName, secondPair, asset.Spot) if err != nil { t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker") } - result, err = GetTicker(exchName, newPair, assets.AssetTypeSpot) + result, err = GetTicker(exchName, newPair, asset.Spot) if err != nil { t.Fatal("Test failed. TestProcessTicker failed to return an existing ticker") } @@ -328,7 +328,7 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers } sm.Lock() - err = ProcessTicker(newName, &tp, assets.AssetTypeSpot) + err = ProcessTicker(newName, &tp, asset.Spot) if err != nil { log.Error(err) catastrophicFailure = true @@ -351,7 +351,7 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers wg.Add(1) fatalErr := false go func(test quick) { - result, err := GetTicker(test.Name, test.P, assets.AssetTypeSpot) + result, err := GetTicker(test.Name, test.P, asset.Spot) if err != nil { fatalErr = true return diff --git a/exchanges/websocket.go b/exchanges/websocket.go index 00540a16..42e38471 100644 --- a/exchanges/websocket.go +++ b/exchanges/websocket.go @@ -9,7 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -429,7 +429,7 @@ func (w *Websocket) GetName() string { func (w *WebsocketOrderbookLocal) Update(bidTargets, askTargets []orderbook.Item, p currency.Pair, updated time.Time, - exchName string, assetType assets.AssetType) error { + exchName string, assetType asset.Item) error { if bidTargets == nil && askTargets == nil { return errors.New("exchange.go websocket orderbook cache Update() error - cannot have bids and ask targets both nil") } @@ -556,7 +556,7 @@ func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base, exc // UpdateUsingID updates orderbooks using specified ID func (w *WebsocketOrderbookLocal) UpdateUsingID(bidTargets, askTargets []orderbook.Item, p currency.Pair, - exchName string, assetType assets.AssetType, action string) error { + exchName string, assetType asset.Item, action string) error { w.m.Lock() defer w.m.Unlock() diff --git a/exchanges/websocket_test.go b/exchanges/websocket_test.go index 8917beef..ab182537 100644 --- a/exchanges/websocket_test.go +++ b/exchanges/websocket_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" ) @@ -161,7 +161,7 @@ func TestInsertingSnapShots(t *testing.T) { snapShot1.Asks = asks snapShot1.Bids = bids - snapShot1.AssetType = assets.AssetTypeSpot + snapShot1.AssetType = asset.Spot snapShot1.Pair = currency.NewPairFromString("BTCUSD") wsTest.Websocket.Orderbook.LoadSnapshot(&snapShot1, "ExchangeTest", false) @@ -197,7 +197,7 @@ func TestInsertingSnapShots(t *testing.T) { snapShot2.Asks = asks snapShot2.Bids = bids - snapShot2.AssetType = assets.AssetTypeSpot + snapShot2.AssetType = asset.Spot snapShot2.Pair = currency.NewPairFromString("LTCUSD") wsTest.Websocket.Orderbook.LoadSnapshot(&snapShot2, "ExchangeTest", false) @@ -265,7 +265,7 @@ func TestUpdate(t *testing.T) { LTCUSDPAIR, time.Now(), "ExchangeTest", - assets.AssetTypeSpot) + asset.Spot) if err != nil { t.Error("test failed - OrderbookUpdate error", err) @@ -301,7 +301,7 @@ func TestUpdate(t *testing.T) { BTCUSDPAIR, time.Now(), "ExchangeTest", - assets.AssetTypeSpot) + asset.Spot) if err != nil { t.Error("test failed - OrderbookUpdate error", err) diff --git a/exchanges/websocket_types.go b/exchanges/websocket_types.go index b81bfb71..c67f8e95 100644 --- a/exchanges/websocket_types.go +++ b/exchanges/websocket_types.go @@ -5,7 +5,7 @@ import ( "time" "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" ) @@ -118,7 +118,7 @@ type WebsocketResponse struct { // has been updated in the orderbook package type WebsocketOrderbookUpdate struct { Pair currency.Pair - Asset assets.AssetType + Asset asset.Item Exchange string } @@ -126,7 +126,7 @@ type WebsocketOrderbookUpdate struct { type TradeData struct { Timestamp time.Time CurrencyPair currency.Pair - AssetType assets.AssetType + AssetType asset.Item Exchange string EventType string EventTime int64 @@ -139,7 +139,7 @@ type TradeData struct { type TickerData struct { Timestamp time.Time Pair currency.Pair - AssetType assets.AssetType + AssetType asset.Item Exchange string ClosePrice float64 Quantity float64 @@ -152,7 +152,7 @@ type TickerData struct { type KlineData struct { Timestamp time.Time Pair currency.Pair - AssetType assets.AssetType + AssetType asset.Item Exchange string StartTime time.Time CloseTime time.Time @@ -168,6 +168,6 @@ type KlineData struct { type WebsocketPositionUpdated struct { Timestamp time.Time Pair currency.Pair - AssetType assets.AssetType + AssetType asset.Item Exchange string } diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index 7c9f15dc..35d1ed17 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -9,7 +9,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" ) var y Yobit @@ -41,7 +41,7 @@ func TestSetup(t *testing.T) { func TestFetchTradablePairs(t *testing.T) { t.Parallel() - _, err := y.FetchTradablePairs(assets.AssetTypeSpot) + _, err := y.FetchTradablePairs(asset.Spot) if err != nil { t.Errorf("Test failed. FetchTradablePairs err: %s", err) } diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index e224587c..a9121ec6 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -13,7 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -52,8 +52,8 @@ func (y *Yobit) SetDefaults() { y.API.CredentialsValidator.RequiresSecret = true y.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ @@ -130,7 +130,7 @@ func (y *Yobit) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (y *Yobit) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (y *Yobit) FetchTradablePairs(asset asset.Item) ([]string, error) { info, err := y.GetInfo() if err != nil { return nil, err @@ -147,16 +147,16 @@ func (y *Yobit) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (y *Yobit) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := y.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := y.FetchTradablePairs(asset.Spot) if err != nil { return err } - return y.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return y.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (y *Yobit) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (y *Yobit) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price pairsCollated, err := y.FormatExchangeCurrencies(y.GetEnabledPairs(assetType), assetType) if err != nil { @@ -188,7 +188,7 @@ func (y *Yobit) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticke } // FetchTicker returns the ticker for a currency pair -func (y *Yobit) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (y *Yobit) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tick, err := ticker.GetTicker(y.GetName(), p, assetType) if err != nil { return y.UpdateTicker(p, assetType) @@ -197,7 +197,7 @@ func (y *Yobit) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker } // FetchOrderbook returns the orderbook for a currency pair -func (y *Yobit) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (y *Yobit) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(y.GetName(), p, assetType) if err != nil { return y.UpdateOrderbook(p, assetType) @@ -206,7 +206,7 @@ func (y *Yobit) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (ord } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (y *Yobit) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (y *Yobit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base orderbookNew, err := y.GetDepth(y.FormatExchangeCurrency(p, assetType).String()) if err != nil { @@ -275,7 +275,7 @@ func (y *Yobit) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (y *Yobit) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (y *Yobit) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -330,9 +330,9 @@ func (y *Yobit) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelA } var allActiveOrders []map[string]ActiveOrders - for _, pair := range y.GetEnabledPairs(assets.AssetTypeSpot) { + for _, pair := range y.GetEnabledPairs(asset.Spot) { activeOrdersForPair, err := y.GetOpenOrders(y.FormatExchangeCurrency(pair, - assets.AssetTypeSpot).String()) + asset.Spot).String()) if err != nil { return cancelAllOrdersResponse, err } @@ -418,14 +418,14 @@ func (y *Yobit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] var orders []exchange.OrderDetail for _, c := range getOrdersRequest.Currencies { resp, err := y.GetOpenOrders(y.FormatExchangeCurrency(c, - assets.AssetTypeSpot).String()) + asset.Spot).String()) if err != nil { return nil, err } for ID, order := range resp { symbol := currency.NewPairDelimiter(order.Pair, - y.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + y.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(order.TimestampCreated), 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) orders = append(orders, exchange.OrderDetail{ @@ -457,7 +457,7 @@ func (y *Yobit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] getOrdersRequest.StartTicks.Unix(), getOrdersRequest.EndTicks.Unix(), "DESC", - y.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String()) + y.FormatExchangeCurrency(currency, asset.Spot).String()) if err != nil { return nil, err } @@ -470,7 +470,7 @@ func (y *Yobit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] var orders []exchange.OrderDetail for _, order := range allOrders { symbol := currency.NewPairDelimiter(order.Pair, - y.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + y.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(order.Timestamp), 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 6e44beef..26422087 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" log "github.com/thrasher-/gocryptotrader/logger" ) @@ -117,7 +117,7 @@ func (z *ZB) WsHandleData() { z.Websocket.DataHandler <- exchange.TickerData{ Timestamp: time.Unix(0, ticker.Date), Pair: currency.NewPairFromString(cPair[0]), - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Exchange: z.GetName(), ClosePrice: ticker.Data.Last, HighPrice: ticker.Data.High, @@ -156,7 +156,7 @@ func (z *ZB) WsHandleData() { var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.AssetType = assets.AssetTypeSpot + newOrderBook.AssetType = asset.Spot newOrderBook.Pair = cPair err = z.Websocket.Orderbook.LoadSnapshot(&newOrderBook, @@ -169,7 +169,7 @@ func (z *ZB) WsHandleData() { z.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Pair: cPair, - Asset: assets.AssetTypeSpot, + Asset: asset.Spot, Exchange: z.GetName(), } @@ -190,7 +190,7 @@ func (z *ZB) WsHandleData() { z.Websocket.DataHandler <- exchange.TradeData{ Timestamp: time.Unix(0, t.Date), CurrencyPair: cPair, - AssetType: assets.AssetTypeSpot, + AssetType: asset.Spot, Exchange: z.GetName(), EventTime: t.Date, Price: t.Price, @@ -248,7 +248,7 @@ func (z *ZB) GenerateDefaultSubscriptions() { Channel: "markets", }) channels := []string{"%s_ticker", "%s_depth", "%s_trades"} - enabledCurrencies := z.GetEnabledPairs(assets.AssetTypeSpot) + enabledCurrencies := z.GetEnabledPairs(asset.Spot) for i := range channels { for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "" diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 43ea8243..03b0f5fe 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -12,7 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/assets" + "github.com/thrasher-/gocryptotrader/exchanges/asset" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -51,8 +51,8 @@ func (z *ZB) SetDefaults() { z.API.CredentialsValidator.RequiresSecret = true z.CurrencyPairs = currency.PairsManager{ - AssetTypes: assets.AssetTypes{ - assets.AssetTypeSpot, + AssetTypes: asset.Items{ + asset.Spot, }, UseGlobalFormat: true, @@ -145,7 +145,7 @@ func (z *ZB) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (z *ZB) FetchTradablePairs(asset assets.AssetType) ([]string, error) { +func (z *ZB) FetchTradablePairs(asset asset.Item) ([]string, error) { markets, err := z.GetMarkets() if err != nil { return nil, err @@ -162,16 +162,16 @@ func (z *ZB) FetchTradablePairs(asset assets.AssetType) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (z *ZB) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := z.FetchTradablePairs(assets.AssetTypeSpot) + pairs, err := z.FetchTradablePairs(asset.Spot) if err != nil { return nil } - return z.UpdatePairs(currency.NewPairsFromStrings(pairs), assets.AssetTypeSpot, false, forceUpdate) + return z.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (z *ZB) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (z *ZB) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price result, err := z.GetTickers() @@ -202,7 +202,7 @@ func (z *ZB) UpdateTicker(p currency.Pair, assetType assets.AssetType) (ticker.P } // FetchTicker returns the ticker for a currency pair -func (z *ZB) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Price, error) { +func (z *ZB) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(z.GetName(), p, assetType) if err != nil { return z.UpdateTicker(p, assetType) @@ -211,7 +211,7 @@ func (z *ZB) FetchTicker(p currency.Pair, assetType assets.AssetType) (ticker.Pr } // FetchOrderbook returns orderbook base on the currency pair -func (z *ZB) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (z *ZB) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { ob, err := orderbook.Get(z.GetName(), p, assetType) if err != nil { return z.UpdateOrderbook(p, assetType) @@ -220,7 +220,7 @@ func (z *ZB) FetchOrderbook(p currency.Pair, assetType assets.AssetType) (orderb } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (z *ZB) UpdateOrderbook(p currency.Pair, assetType assets.AssetType) (orderbook.Base, error) { +func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base currency := z.FormatExchangeCurrency(p, assetType).String() @@ -295,7 +295,7 @@ func (z *ZB) GetFundingHistory() ([]exchange.FundHistory, error) { } // GetExchangeHistory returns historic trade data since exchange opening. -func (z *ZB) GetExchangeHistory(p currency.Pair, assetType assets.AssetType) ([]exchange.TradeHistory, error) { +func (z *ZB) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { return nil, common.ErrNotYetImplemented } @@ -357,10 +357,10 @@ func (z *ZB) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllO OrderStatus: make(map[string]string), } var allOpenOrders []Order - for _, currency := range z.GetEnabledPairs(assets.AssetTypeSpot) { + for _, currency := range z.GetEnabledPairs(asset.Spot) { // Limiting to 10 pages for i := 0; i < 10; i++ { - openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(z.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String(), 1, 10) + openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(z.FormatExchangeCurrency(currency, asset.Spot).String(), 1, 10) if err != nil { return cancelAllOrdersResponse, err } @@ -439,7 +439,7 @@ func (z *ZB) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exc var pageNumber int64 // Limiting to 10 pages for i := 0; i < 10; i++ { - resp, err := z.GetUnfinishedOrdersIgnoreTradeType(z.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String(), pageNumber, 10) + resp, err := z.GetUnfinishedOrdersIgnoreTradeType(z.FormatExchangeCurrency(currency, asset.Spot).String(), pageNumber, 10) if err != nil { return nil, err } @@ -455,7 +455,7 @@ func (z *ZB) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exc var orders []exchange.OrderDetail for _, order := range allOrders { symbol := currency.NewPairDelimiter(order.Currency, - z.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + z.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(order.TradeDate), 0) orderSide := orderSideMap[order.Type] orders = append(orders, exchange.OrderDetail{ @@ -494,7 +494,7 @@ func (z *ZB) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exc var pageNumber int64 // Limiting to 10 pages for i := 0; i < 10; i++ { - resp, err := z.GetOrders(z.FormatExchangeCurrency(currency, assets.AssetTypeSpot).String(), pageNumber, side) + resp, err := z.GetOrders(z.FormatExchangeCurrency(currency, asset.Spot).String(), pageNumber, side) if err != nil { return nil, err } @@ -511,7 +511,7 @@ func (z *ZB) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exc var orders []exchange.OrderDetail for _, order := range allOrders { symbol := currency.NewPairDelimiter(order.Currency, - z.CurrencyPairs.Get(assets.AssetTypeSpot).ConfigFormat.Delimiter) + z.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) orderDate := time.Unix(int64(order.TradeDate), 0) orderSide := orderSideMap[order.Type] orders = append(orders, exchange.OrderDetail{ From 897bcfd9a41ad0f84d74045633a324fa176f0ffb Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Fri, 21 Jun 2019 12:38:35 +1000 Subject: [PATCH 14/71] Documentation Update (#318) * Initial update * update * Fix linter issues * Add new documentation template and fix --- CONTRIBUTORS | 6 +- README.md | 17 +- cmd/documentation/README.md | 98 +++ .../cmd_templates/documentation.tmpl | 63 ++ cmd/documentation/documentation.go | 635 +++++++++--------- .../{CONTRIBUTORS => CONTRIBUTORS.tmpl} | 4 +- .../root_templates/{LICENSE => LICENSE.tmpl} | 0 .../sub_templates/contributors.tmpl | 5 +- .../testdata_templates/testdata_readme.tmpl | 2 +- communications/slack/README.md | 32 - 10 files changed, 496 insertions(+), 366 deletions(-) create mode 100644 cmd/documentation/README.md create mode 100644 cmd/documentation/cmd_templates/documentation.tmpl rename cmd/documentation/root_templates/{CONTRIBUTORS => CONTRIBUTORS.tmpl} (69%) rename cmd/documentation/root_templates/{LICENSE => LICENSE.tmpl} (100%) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 0de25e74..74691599 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3,12 +3,13 @@ Thanks to the following contributors: thrasher- | https://github.com/thrasher- shazbert | https://github.com/shazbert gloriousCode | https://github.com/gloriousCode -ermalguni | https://github.com/ermalguni xtda | https://github.com/xtda +ermalguni | https://github.com/ermalguni +vadimzhukck | https://github.com/vadimzhukck 140am | https://github.com/140am marcofranssen | https://github.com/marcofranssen -vadimzhukck | https://github.com/vadimzhukck cranktakular | https://github.com/cranktakular +leilaes | https://github.com/leilaes crackcomm | https://github.com/crackcomm MadCozBadd | https://github.com/MadCozBadd andreygrehov | https://github.com/andreygrehov @@ -29,5 +30,4 @@ starit | https://github.com/starit Jimexist | https://github.com/Jimexist lookfirst | https://github.com/lookfirst zeldrinn | https://github.com/zeldrinn -mattkanwisher | https://github.com/mattkanwisher diff --git a/README.md b/README.md index bed3517c..27808101 100644 --- a/README.md +++ b/README.md @@ -129,16 +129,16 @@ Binaries will be published once the codebase reaches a stable condition. ### A very special thank you to all who have contributed to this program: |User|Github|Contribution Amount| -|--|--|--| -| thrasher- | https://github.com/thrasher- | 526 | -| shazbert | https://github.com/shazbert | 166 | -| gloriousCode | https://github.com/gloriousCode | 146 | +|--|--|--|| thrasher- | https://github.com/thrasher- | 540 | +| shazbert | https://github.com/shazbert | 173 | +| gloriousCode | https://github.com/gloriousCode | 150 | +| xtda | https://github.com/xtda | 17 | | ermalguni | https://github.com/ermalguni | 14 | -| xtda | https://github.com/xtda | 11 | +| vadimzhukck | https://github.com/vadimzhukck | 10 | | 140am | https://github.com/140am | 8 | | marcofranssen | https://github.com/marcofranssen | 8 | -| vadimzhukck | https://github.com/vadimzhukck | 8 | | cranktakular | https://github.com/cranktakular | 5 | +| leilaes | https://github.com/leilaes | 3 | | crackcomm | https://github.com/crackcomm | 3 | | MadCozBadd | https://github.com/MadCozBadd | 2 | | andreygrehov | https://github.com/andreygrehov | 2 | @@ -159,10 +159,5 @@ Binaries will be published once the codebase reaches a stable condition. | Jimexist | https://github.com/Jimexist | 1 | | lookfirst | https://github.com/lookfirst | 1 | | zeldrinn | https://github.com/zeldrinn | 1 | -| mattkanwisher | https://github.com/mattkanwisher | 1 | -| mKurrels | https://github.com/mKurrels | 1 | -| m1kola | https://github.com/m1kola | 1 | -| cavapoo2 | https://github.com/cavapoo2 | 1 | - diff --git a/cmd/documentation/README.md b/cmd/documentation/README.md new file mode 100644 index 00000000..252a4faa --- /dev/null +++ b/cmd/documentation/README.md @@ -0,0 +1,98 @@ +# GoCryptoTrader package Documentation + + + + +[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader/cmd/documentation) +[![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader) + + +This documentation package is part of the GoCryptoTrader codebase. + +## This is still in active development + +You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY) + +## Current Features for documentation + +#### This tool allows for the generation of new documentation through templating + +From the `gocryptotrader/cmd/documentation/` folder, using the go command: **go run documentation.go** this will auto-generate and regenerate documentation across the **GoCryptoTrader** code base. +>Using the -v command will, ie **go run documentation.go -v** put the tool into verbose mode allowing you to see what is happening with a little more depth. + +Be aware, this tool will: +- Works off a configuration JSON file located at ``gocryptotrader/cmd/documentation/`` for future use with multiple repositories. +- Automatically find the directory and file tree for the GoCryptoTrader source code and alert you of undocumented file systems which **need** to be updated. +- Automatically find the template folder tree. +- Fetch an updated contributor list and rank on pull request commit amount +- Sets up core folder docs for the root directory tree including **LICENSE** and **CONTRIBUTORS** + +### config.json example + +```json +{ +"githubRepo": "https://api.github.com/repos/thrasher-/gocryptotrader", This is your current repo +"exclusionList": { This allows for excluded directories and files +"Files": null, +"Directories": [ +"_templates", +".git", +"web" +] +}, +"rootReadmeActive": true, allows a root directory README.md +"licenseFileActive": true, allows for a license file to be generated +"contributorFileActive": true, fetches a new contributor list +"referencePathToRepo": "../../" +} +``` +### Template example +>place a new template **example_file.tmpl** located in the current gocryptotrader/cmd/documentation/ folder; when the documentation tool finishes it will give you the define template associated name e.g. ``Template not found for path ../../cmd/documentation create new template with \{\{define "cmd documentation" -\}\} TEMPLATE HERE \{\{end}}`` so you can replace the below example with ``\{\{define "cmd documentation" -}}`` + +``` +\{\{\define "example_definition_created_by_documentation_tool" -}} +\{\{\template "header" .}} +## Current Features for documentation + +#### A concise blurb about the package or tool system + ++ Coding examples +import "github.com/thrasher-/gocryptotrader/something" +testString := "aAaAa" +upper := strings.ToUpper(testString) +// upper == "AAAAA" + +{\{\template "contributions"}} +{\{\template "donations"}} +{\{\end}} +``` + +### ALL NEW UPDATES AND FILE SYSTEM ADDITIONS NEED A DOCUMENTATION UPDATE USING THIS TOOL OR PR MERGE REQUEST MAY BE POSTPONED. + + +### Please click GoDocs chevron above to view current GoDoc information for this package + +## Contribution + +Please feel free to submit any pull requests or suggest any desired features to be added. + +When submitting a PR, please abide by our coding guidelines: + ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. + +## Donations + + + +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: + +***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** + diff --git a/cmd/documentation/cmd_templates/documentation.tmpl b/cmd/documentation/cmd_templates/documentation.tmpl new file mode 100644 index 00000000..7ec9a94d --- /dev/null +++ b/cmd/documentation/cmd_templates/documentation.tmpl @@ -0,0 +1,63 @@ +{{define "cmd documentation" -}} +{{template "header" .}} +## Current Features for {{.Name}} + +#### This tool allows for the generation of new documentation through templating + +From the `gocryptotrader/cmd/documentation/` folder, using the go command: **go run documentation.go** this will auto-generate and regenerate documentation across the **GoCryptoTrader** code base. +>Using the -v command will, ie **go run documentation.go -v** put the tool into verbose mode allowing you to see what is happening with a little more depth. + +Be aware, this tool will: +- Works off a configuration JSON file located at ``gocryptotrader/cmd/documentation/`` for future use with multiple repositories. +- Automatically find the directory and file tree for the GoCryptoTrader source code and alert you of undocumented file systems which **need** to be updated. +- Automatically find the template folder tree. +- Fetch an updated contributor list and rank on pull request commit amount +- Sets up core folder docs for the root directory tree including **LICENSE** and **CONTRIBUTORS** + +### config.json example + +```json +{ +"githubRepo": "https://api.github.com/repos/thrasher-/gocryptotrader", This is your current repo +"exclusionList": { This allows for excluded directories and files +"Files": null, +"Directories": [ +"_templates", +".git", +"web" +] +}, +"rootReadmeActive": true, allows a root directory README.md +"licenseFileActive": true, allows for a license file to be generated +"contributorFileActive": true, fetches a new contributor list +"referencePathToRepo": "../../" +} +``` +### Template example +>place a new template **example_file.tmpl** located in the current gocryptotrader/cmd/documentation/ folder; when the documentation tool finishes it will give you the define template associated name e.g. ``Template not found for path ../../cmd/documentation create new template with \{\{define "cmd documentation" -\}\} TEMPLATE HERE \{\{end}}`` so you can replace the below example with ``\{\{define "cmd documentation" -}}`` + +``` +\{\{\define "example_definition_created_by_documentation_tool" -}} +\{\{\template "header" .}} +## Current Features for {{.Name}} + +#### A concise blurb about the package or tool system + ++ Coding examples +import "github.com/thrasher-/gocryptotrader/something" +testString := "aAaAa" +upper := strings.ToUpper(testString) +// upper == "AAAAA" + +{\{\template "contributions"}} +{\{\template "donations"}} +{\{\end}} +``` + +### ALL NEW UPDATES AND FILE SYSTEM ADDITIONS NEED A DOCUMENTATION UPDATE USING THIS TOOL OR PR MERGE REQUEST MAY BE POSTPONED. + + +### Please click GoDocs chevron above to view current GoDoc information for this package +{{template "contributions"}} +{{template "donations"}} +{{end}} \ No newline at end of file diff --git a/cmd/documentation/documentation.go b/cmd/documentation/documentation.go index b5d348d3..2200d1f9 100644 --- a/cmd/documentation/documentation.go +++ b/cmd/documentation/documentation.go @@ -1,390 +1,397 @@ package main import ( + "encoding/json" + "errors" "flag" "fmt" "html/template" + "io/ioutil" "log" "os" - "runtime" + "path/filepath" "strings" "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/core" ) const ( - commonPath = "..%s..%scommon%s" - communicationsPath = "..%s..%scommunications%s" - communicationsBasePath = "..%s..%scommunications%sbase%s" - communicationsSlackPath = "..%s..%scommunications%sslack%s" - communicationsSmsglobalPath = "..%s..%scommunications%ssmsglobal%s" - communicationsSMTPPath = "..%s..%scommunications%ssmtpservice%s" - communicationsTelegramPath = "..%s..%scommunications%stelegram%s" - configPath = "..%s..%sconfig%s" - currencyPath = "..%s..%scurrency%s" - currencyFXPath = "..%s..%scurrency%sforexprovider%s" - currencyFXBasePath = "..%s..%scurrency%sforexprovider%sbase%s" - currencyFXCurrencyConverterPath = "..%s..%scurrency%sforexprovider%scurrencyconverterapi%s" - currencyFXCurrencylayerPath = "..%s..%scurrency%sforexprovider%scurrencylayer%s" - currencyFXFixerPath = "..%s..%scurrency%sforexprovider%sfixer.io%s" - currencyFXOpenExchangeRatesPath = "..%s..%scurrency%sforexprovider%sopenexchangerates%s" - currencyPairPath = "..%s..%scurrency%spair%s" - currencySymbolPath = "..%s..%scurrency%ssymbol%s" - currencyTranslationPath = "..%s..%scurrency%stranslation%s" - eventsPath = "..%s..%sevents%s" - exchangesPath = "..%s..%sexchanges%s" - exchangesNoncePath = "..%s..%sexchanges%snonce%s" - exchangesOrderbookPath = "..%s..%sexchanges%sorderbook%s" - exchangesStatsPath = "..%s..%sexchanges%sstats%s" - exchangesTickerPath = "..%s..%sexchanges%sticker%s" - exchangesOrdersPath = "..%s..%sexchanges%sorders%s" - exchangesRequestPath = "..%s..%sexchanges%srequest%s" - portfolioPath = "..%s..%sportfolio%s" - testdataPath = "..%s..%stestdata%s" - toolsPath = "..%s..%stools%s" - webPath = "..%s..%sweb%s" - rootPath = "..%s..%s" + // DefaultRepo is the main example repository + DefaultRepo = "https://api.github.com/repos/[REPO ADDRESS HERE]" - // exchange packages - alphapoint = "..%s..%sexchanges%salphapoint%s" - anx = "..%s..%sexchanges%sanx%s" - binance = "..%s..%sexchanges%sbinance%s" - bitfinex = "..%s..%sexchanges%sbitfinex%s" - bitflyer = "..%s..%sexchanges%sbitflyer%s" - bithumb = "..%s..%sexchanges%sbithumb%s" - bitmex = "..%s..%sexchanges%sbitmex%s" - bitstamp = "..%s..%sexchanges%sbitstamp%s" - bittrex = "..%s..%sexchanges%sbittrex%s" - btcmarkets = "..%s..%sexchanges%sbtcmarkets%s" - coinbasepro = "..%s..%sexchanges%scoinbasepro%s" - coinut = "..%s..%sexchanges%scoinut%s" - exmo = "..%s..%sexchanges%sexmo%s" - gateio = "..%s..%sexchanges%sgateio%s" - gemini = "..%s..%sexchanges%sgemini%s" - hitbtc = "..%s..%sexchanges%shitbtc%s" - huobi = "..%s..%sexchanges%shuobi%s" - huobihadax = "..%s..%sexchanges%shuobihadax%s" - itbit = "..%s..%sexchanges%sitbit%s" - kraken = "..%s..%sexchanges%skraken%s" - lakebtc = "..%s..%sexchanges%slakebtc%s" - localbitcoins = "..%s..%sexchanges%slocalbitcoins%s" - okcoin = "..%s..%sexchanges%sokcoin%s" - okex = "..%s..%sexchanges%sokex%s" - poloniex = "..%s..%sexchanges%spoloniex%s" - yobit = "..%s..%sexchanges%syobit%s" - zb = "..%s..%sexchanges%szb%s" + // GithubAPIEndpoint allows the program to query your repository + // contributor list + GithubAPIEndpoint = "/contributors" - contributorsList = "https://api.github.com/repos/thrasher-/gocryptotrader/contributors" + // LicenseFile defines a license file + LicenseFile = "LICENSE" - licenseName = "LICENSE" - contributorName = "CONTRIBUTORS" + // ContributorFile defines contributor file + ContributorFile = "CONTRIBUTORS" ) -var ( - verbose, replace bool - codebasePaths map[string]string - codebaseTemplatePath map[string]string - codebaseReadme map[string]readme - tmpl *template.Template - path string - contributors []contributor -) - -type readme struct { - Name string - Contributors []contributor - NameURL string - Year int - CapitalName string -} - -type contributor struct { +// Contributor defines an account associated with this code base by doing +// contributions +type Contributor struct { Login string `json:"login"` URL string `json:"html_url"` Contributions int `json:"contributions"` } +// Config defines the running config to deploy documentation across a github +// repository including exclusion lists for files and directories +type Config struct { + GithubRepo string `json:"githubRepo"` + Exclusions Exclusions `json:"exclusionList"` + RootReadme bool `json:"rootReadmeActive"` + LicenseFile bool `json:"licenseFileActive"` + ContributorFile bool `json:"contributorFileActive"` + ReferencePathToRepo string `json:"referencePathToRepo"` +} + +// Exclusions defines the exclusion list so documents are not generated +type Exclusions struct { + Files []string `json:"Files"` + Directories []string `json:"Directories"` +} + +// DocumentationDetails defines parameters to update documentation +type DocumentationDetails struct { + Directories []string + Tmpl *template.Template + Contributors []Contributor + Verbose bool + Config *Config +} + +// Attributes defines specific documentation attributes when a template is +// executed +type Attributes struct { + Name string + Contributors []Contributor + NameURL string + Year int + CapitalName string +} + func main() { - flag.BoolVar(&verbose, "v", false, "-v Verbose flag prints more information to the std output") - flag.BoolVar(&replace, "r", false, "-r Replace flag generates and replaces all documentation across the code base") + verbose := flag.Bool("v", false, "Verbose output") + flag.Parse() - fmt.Println(` - GoCryptoTrader: Exchange documentation tool + fmt.Println(core.Banner) + fmt.Println("This will update and regenerate documentation for the different packages in your repo.") + fmt.Println() - This will update and regenerate documentation for the different packages - in GoCryptoTrader.`) - - codebasePaths = make(map[string]string) - codebaseTemplatePath = make(map[string]string) - codebaseReadme = make(map[string]readme) - path = getOSPathSlash() - - if err := getContributorList(); err != nil { - log.Fatal("GoCryptoTrader: Exchange documentation tool GET error ", err) + if *verbose { + fmt.Println("Fetching configuration...") } - fmt.Println("Contributor list fetched") - - if err := addTemplates(); err != nil { - log.Fatal("GoCryptoTrader: Exchange documentation tool add template error ", err) + config, err := GetConfiguration() + if err != nil { + log.Fatalf("Documentation Generation Tool - GetConfiguration error %s", + err) } - fmt.Println("Templates parsed") - - if err := updateReadme(); err != nil { - log.Fatal("GoCryptoTrader: Exchange documentation tool update readme error ", err) + if *verbose { + fmt.Println("Fetching project directory tree...") } - fmt.Println("\nTool finished") -} - -// getOSPathSlash returns the slash used by the operating systems -// file system -// TO-DO: Change all paths to not use this -func getOSPathSlash() string { - if runtime.GOOS == "windows" { - return "\\" + dirList, err := GetProjectDirectoryTree(&config, *verbose) + if err != nil { + log.Fatalf("Documentation Generation Tool - GetProjectDirectoryTree error %s", + err) } - return "/" -} -// updateReadme iterates through codebase paths to check for readme files and either adds -// or replaces with new readme files. -func updateReadme() error { - addPaths() + var contributors []Contributor + if config.ContributorFile { + if *verbose { + fmt.Println("Fetching repository contributor list...") + } + contributors, err = GetContributorList(config.GithubRepo) + if err != nil { + log.Fatalf("Documentation Generation Tool - GetContributorList error %s", + err) + } - for packageName := range codebasePaths { - addReadmeData(packageName) - - if !checkReadme(packageName) { - if verbose { - fmt.Printf("* %s Readme file FOUND.\n", packageName) + if *verbose { + fmt.Println("Contributor List Fetched") + for i := range contributors { + fmt.Println(contributors[i].Login) } - if replace { + } + } else { + fmt.Println("Contributor list file disabled skipping fetching details") + } + + if *verbose { + fmt.Println("Fetching template files...") + } + + tmpl, err := GetTemplateFiles() + if err != nil { + log.Fatalf("Documentation Generation Tool - GetTemplateFiles error %s", + err) + } + + if *verbose { + fmt.Println("All core systems fetched, updating documentation...") + } + + err = UpdateDocumentation(DocumentationDetails{ + dirList, + tmpl, + contributors, + *verbose, + &config}) + if err != nil { + log.Fatalf("Documentation Generation Tool - UpdateDocumentation error %s", + err) + } + + fmt.Println("\nDocumentation Generation Tool - Finished") +} + +// GetConfiguration retrieves the documentation configuration +func GetConfiguration() (Config, error) { + var c Config + file, err := os.OpenFile("config.json", os.O_RDWR, os.ModePerm) + if err != nil { + fmt.Println("Creating configuration file, please add github repository path and preferences") + + file, err = os.Create("config.json") + if err != nil { + return c, err + } + + // Set default params for configuration + c.GithubRepo = DefaultRepo + c.ContributorFile = true + c.LicenseFile = true + c.RootReadme = true + c.ReferencePathToRepo = "../../" + c.Exclusions.Directories = []string{".github"} + + data, mErr := json.MarshalIndent(c, "", " ") + if mErr != nil { + return c, mErr + } + + _, err = file.WriteAt(data, 0) + if err != nil { + return c, err + } + } + + defer file.Close() + + config, err := ioutil.ReadAll(file) + if err != nil { + return c, err + } + + err = json.Unmarshal(config, &c) + if err != nil { + return c, err + } + + if c.GithubRepo == "" || c.GithubRepo == DefaultRepo { + return c, errors.New("repository not set in config.json file, please change") + } + + if c.ReferencePathToRepo == "" { + return c, errors.New("reference path not set in the config.json file, please set") + } + + return c, nil +} + +// IsExcluded returns if the file path is included in the exclusion list +func IsExcluded(path string, exclusion []string) bool { + for _, data := range exclusion { + if strings.Contains(path, data) { + return true + } + } + return false +} + +// GetProjectDirectoryTree uses filepath walk functions to get each individual +// directory name and path to match templates with +func GetProjectDirectoryTree(c *Config, verbose bool) ([]string, error) { + var directoryData []string + if c.RootReadme { // Projects root README.md + directoryData = append(directoryData, c.ReferencePathToRepo) + } + + if c.LicenseFile { // Standard license file + directoryData = append(directoryData, c.ReferencePathToRepo+LicenseFile) + } + + if c.ContributorFile { // Standard contributor file + directoryData = append(directoryData, c.ReferencePathToRepo+ContributorFile) + } + + walkfn := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + // Bypass what is contained in config.json directory exclusion + if IsExcluded(info.Name(), c.Exclusions.Directories) { if verbose { - fmt.Println("file replacement") + fmt.Println("Excluding Directory:", info.Name()) } - if err := replaceReadme(packageName); err != nil { - return err - } - continue + return filepath.SkipDir } - continue - } - if verbose { - fmt.Printf("* %s Readme file NOT FOUND.\n", packageName) - } - if replace { - if verbose { - log.Println("file creation") + // Don't append parent directory + if strings.EqualFold(info.Name(), "..") { + return nil } - if err := createReadme(packageName); err != nil { - return err - } - continue + directoryData = append(directoryData, path) } + return nil } - return nil + + return directoryData, filepath.Walk(c.ReferencePathToRepo, walkfn) } -// addPaths adds paths to different potential README.md files in the codebase -func addPaths() { - codebasePaths["common"] = fmt.Sprintf(commonPath, path, path, path) +// GetTemplateFiles parses and returns all template files in the documentation +// tree +func GetTemplateFiles() (*template.Template, error) { + tmpl := template.New("") - codebasePaths["communications comms"] = fmt.Sprintf(communicationsPath, path, path, path) - codebasePaths["communications base"] = fmt.Sprintf(communicationsBasePath, path, path, path, path) - codebasePaths["communications slack"] = fmt.Sprintf(communicationsSlackPath, path, path, path, path) - codebasePaths["communications smsglobal"] = fmt.Sprintf(communicationsSmsglobalPath, path, path, path, path) - codebasePaths["communications smtp"] = fmt.Sprintf(communicationsSMTPPath, path, path, path, path) - codebasePaths["communications telegram"] = fmt.Sprintf(communicationsTelegramPath, path, path, path, path) + walkfn := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + if path == "." || path == ".." { + return nil + } - codebasePaths["config"] = fmt.Sprintf(configPath, path, path, path) + var parseError error + tmpl, parseError = tmpl.ParseGlob(filepath.Join(path, "*.tmpl")) + if parseError != nil { + return parseError + } + return filepath.SkipDir + } + return nil + } - codebasePaths["currency"] = fmt.Sprintf(currencyPath, path, path, path) - codebasePaths["currency forexprovider"] = fmt.Sprintf(currencyFXPath, path, path, path, path) - codebasePaths["currency forexprovider base"] = fmt.Sprintf(currencyFXBasePath, path, path, path, path, path) - codebasePaths["currency forexprovider currencyconverter"] = fmt.Sprintf(currencyFXCurrencyConverterPath, path, path, path, path, path) - codebasePaths["currency forexprovider currencylayer"] = fmt.Sprintf(currencyFXCurrencylayerPath, path, path, path, path, path) - codebasePaths["currency forexprovider fixer"] = fmt.Sprintf(currencyFXFixerPath, path, path, path, path, path) - codebasePaths["currency forexprovider openexchangerates"] = fmt.Sprintf(currencyFXOpenExchangeRatesPath, path, path, path, path, path) - codebasePaths["currency pair"] = fmt.Sprintf(currencyPairPath, path, path, path, path) - codebasePaths["currency symbol"] = fmt.Sprintf(currencySymbolPath, path, path, path, path) - codebasePaths["currency translation"] = fmt.Sprintf(currencyTranslationPath, path, path, path, path) - - codebasePaths["events"] = fmt.Sprintf(eventsPath, path, path, path) - - codebasePaths["portfolio"] = fmt.Sprintf(portfolioPath, path, path, path) - codebasePaths["testdata"] = fmt.Sprintf(testdataPath, path, path, path) - codebasePaths["tools"] = fmt.Sprintf(toolsPath, path, path, path) - codebasePaths["web"] = fmt.Sprintf(webPath, path, path, path) - codebasePaths["root"] = fmt.Sprintf(rootPath, path, path) - - codebasePaths["exchanges"] = fmt.Sprintf(exchangesPath, path, path, path) - codebasePaths["exchanges nonce"] = fmt.Sprintf(exchangesNoncePath, path, path, path, path) - codebasePaths["exchanges orderbook"] = fmt.Sprintf(exchangesOrderbookPath, path, path, path, path) - codebasePaths["exchanges stats"] = fmt.Sprintf(exchangesStatsPath, path, path, path, path) - codebasePaths["exchanges ticker"] = fmt.Sprintf(exchangesTickerPath, path, path, path, path) - codebasePaths["exchanges orders"] = fmt.Sprintf(exchangesOrdersPath, path, path, path, path) - codebasePaths["exchanges request"] = fmt.Sprintf(exchangesRequestPath, path, path, path, path) - - codebasePaths["exchanges alphapoint"] = fmt.Sprintf(alphapoint, path, path, path, path) - codebasePaths["exchanges anx"] = fmt.Sprintf(anx, path, path, path, path) - codebasePaths["exchanges binance"] = fmt.Sprintf(binance, path, path, path, path) - codebasePaths["exchanges bitfinex"] = fmt.Sprintf(bitfinex, path, path, path, path) - codebasePaths["exchanges bitflyer"] = fmt.Sprintf(bitflyer, path, path, path, path) - codebasePaths["exchanges bithumb"] = fmt.Sprintf(bithumb, path, path, path, path) - codebasePaths["exchanges bitmex"] = fmt.Sprintf(bitmex, path, path, path, path) - codebasePaths["exchanges bitstamp"] = fmt.Sprintf(bitstamp, path, path, path, path) - codebasePaths["exchanges bittrex"] = fmt.Sprintf(bittrex, path, path, path, path) - codebasePaths["exchanges btcmarkets"] = fmt.Sprintf(btcmarkets, path, path, path, path) - codebasePaths["exchanges coinut"] = fmt.Sprintf(coinut, path, path, path, path) - codebasePaths["exchanges exmo"] = fmt.Sprintf(exmo, path, path, path, path) - codebasePaths["exchanges coinbasepro"] = fmt.Sprintf(coinbasepro, path, path, path, path) - codebasePaths["exchanges gateio"] = fmt.Sprintf(gateio, path, path, path, path) - codebasePaths["exchanges gemini"] = fmt.Sprintf(gemini, path, path, path, path) - codebasePaths["exchanges hitbtc"] = fmt.Sprintf(hitbtc, path, path, path, path) - codebasePaths["exchanges huobi"] = fmt.Sprintf(huobi, path, path, path, path) - codebasePaths["exchanges huobihadax"] = fmt.Sprintf(huobihadax, path, path, path, path) - codebasePaths["exchanges itbit"] = fmt.Sprintf(itbit, path, path, path, path) - codebasePaths["exchanges kraken"] = fmt.Sprintf(kraken, path, path, path, path) - codebasePaths["exchanges lakebtc"] = fmt.Sprintf(lakebtc, path, path, path, path) - codebasePaths["exchanges localbitcoins"] = fmt.Sprintf(localbitcoins, path, path, path, path) - codebasePaths["exchanges okcoin"] = fmt.Sprintf(okcoin, path, path, path, path) - codebasePaths["exchanges okex"] = fmt.Sprintf(okex, path, path, path, path) - codebasePaths["exchanges poloniex"] = fmt.Sprintf(poloniex, path, path, path, path) - codebasePaths["exchanges yobit"] = fmt.Sprintf(yobit, path, path, path, path) - codebasePaths["exchanges zb"] = fmt.Sprintf(zb, path, path, path, path) - - codebasePaths["CONTRIBUTORS"] = fmt.Sprintf(rootPath, path, path) - codebasePaths["LICENSE"] = fmt.Sprintf(rootPath, path, path) + return tmpl, filepath.Walk(".", walkfn) } -func addReadmeData(packageName string) { - readmeInfo := readme{ - Name: getName(packageName, false), +// GetContributorList fetches a list of contributors from the github api +// endpoint +func GetContributorList(repo string) ([]Contributor, error) { + var resp []Contributor + return resp, common.SendHTTPGetRequest(repo+GithubAPIEndpoint, true, false, &resp) +} + +// GetDocumentationAttributes returns specific attributes for a file template +func GetDocumentationAttributes(packageName string, contributors []Contributor) Attributes { + return Attributes{ + Name: GetPackageName(packageName, false), Contributors: contributors, - NameURL: getslashFromName(packageName), + NameURL: GetGoDocURL(packageName), Year: time.Now().Year(), - CapitalName: getName(packageName, true), + CapitalName: GetPackageName(packageName, true), } - codebaseReadme[packageName] = readmeInfo } -func getName(name string, capital bool) string { +// GetPackageName returns the package name after cleaning path as a string +func GetPackageName(name string, capital bool) string { newStrings := strings.Split(name, " ") + var i int if len(newStrings) > 1 { - if capital { - return getCapital(newStrings[1]) - } - return newStrings[1] + i = 1 } if capital { - return getCapital(name) + return strings.Title(newStrings[i]) + } + return newStrings[i] +} + +// GetGoDocURL returns a string for godoc package names +func GetGoDocURL(name string) string { + if strings.Contains(name, " ") { + return strings.Join(strings.Split(name, " "), "/") + } + if name == "testdata" || + name == "tools" || + name == ContributorFile || + name == LicenseFile { + return "" } return name } -func getCapital(name string) string { - capLetter := strings.ToUpper(string(name[0])) - last := name[1:] +// UpdateDocumentation generates or updates readme/documentation files across +// the codebase +func UpdateDocumentation(details DocumentationDetails) error { + for _, path := range details.Directories { + data := strings.Split(path, "/") + var temp []string + for _, d := range data { + if d == ".." { + continue + } + if d == "" { + break + } - return capLetter + last -} + temp = append(temp, d) + } -// getslashFromName returns a string for godoc package names -func getslashFromName(packageName string) string { - if strings.Contains(packageName, " ") { - s := strings.Split(packageName, " ") - return strings.Join(s, "/") - } - if packageName == "testdata" || packageName == "tools" || packageName == contributorName || packageName == licenseName { - return "" - } - return packageName -} + var name string + if len(temp) == 0 { + name = "root" + } else { + name = strings.Join(temp, " ") + } -var globS = []string{ - fmt.Sprintf("common_templates%s*", getOSPathSlash()), - fmt.Sprintf("communications_templates%s*", getOSPathSlash()), - fmt.Sprintf("config_templates%s*", getOSPathSlash()), - fmt.Sprintf("currency_templates%s*", getOSPathSlash()), - fmt.Sprintf("events_templates%s*", getOSPathSlash()), - fmt.Sprintf("exchanges_templates%s*", getOSPathSlash()), - fmt.Sprintf("portfolio_templates%s*", getOSPathSlash()), - fmt.Sprintf("root_templates%s*", getOSPathSlash()), - fmt.Sprintf("sub_templates%s*", getOSPathSlash()), - fmt.Sprintf("testdata_templates%s*", getOSPathSlash()), - fmt.Sprintf("tools_templates%s*", getOSPathSlash()), - fmt.Sprintf("web_templates%s*", getOSPathSlash()), -} + if IsExcluded(name, details.Config.Exclusions.Files) { + if details.Verbose { + fmt.Println("Excluding file:", name) + } + continue + } -// addTemplates adds all the template files -func addTemplates() error { - tmpl = template.New("") + if details.Tmpl.Lookup(name) == nil { + fmt.Printf("Template not found for path %s create new template with {{define \"%s\" -}} TEMPLATE HERE {{end}}\n", + path, + name) + continue + } - for _, s := range globS { - _, err := tmpl.ParseGlob(s) + var mainPath string + if name == LicenseFile || name == ContributorFile { + mainPath = path + } else { + mainPath = filepath.Join(path, "README.md") + } + + err := os.Remove(mainPath) + if err != nil && !strings.Contains(err.Error(), "no such file or directory") { + return err + } + + file, err := os.Create(mainPath) + if err != nil { + return err + } + defer file.Close() + + attr := GetDocumentationAttributes(name, details.Contributors) + + err = details.Tmpl.ExecuteTemplate(file, name, attr) if err != nil { return err } } return nil } - -// checkReadme checks to see if the file exists -func checkReadme(packageName string) bool { - if packageName == licenseName || packageName == contributorName { - _, err := os.Stat(codebasePaths[packageName] + packageName) - return os.IsNotExist(err) - } - _, err := os.Stat(codebasePaths[packageName] + "README.md") - return os.IsNotExist(err) -} - -// replaceReadme replaces readme file -func replaceReadme(packageName string) error { - if packageName == licenseName || packageName == contributorName { - if err := deleteFile(codebasePaths[packageName] + packageName); err != nil { - return err - } - return createReadme(packageName) - } - if err := deleteFile(codebasePaths[packageName] + "README.md"); err != nil { - return err - } - return createReadme(packageName) -} - -// createReadme creates new readme file and executes template -func createReadme(packageName string) error { - if packageName == licenseName || packageName == contributorName { - file, err := os.Create(codebasePaths[packageName] + packageName) - if err != nil { - return err - } - defer file.Close() - if verbose { - fmt.Println("File done") - } - return tmpl.ExecuteTemplate(file, packageName, codebaseReadme[packageName]) - } - file, err := os.Create(codebasePaths[packageName] + "README.md") - if err != nil { - return err - } - defer file.Close() - if verbose { - fmt.Println("File done") - } - return tmpl.ExecuteTemplate(file, packageName, codebaseReadme[packageName]) -} - -func deleteFile(path string) error { - return os.Remove(path) -} - -func getContributorList() error { - return common.SendHTTPGetRequest(contributorsList, true, false, &contributors) -} diff --git a/cmd/documentation/root_templates/CONTRIBUTORS b/cmd/documentation/root_templates/CONTRIBUTORS.tmpl similarity index 69% rename from cmd/documentation/root_templates/CONTRIBUTORS rename to cmd/documentation/root_templates/CONTRIBUTORS.tmpl index eda72237..fa2ba817 100644 --- a/cmd/documentation/root_templates/CONTRIBUTORS +++ b/cmd/documentation/root_templates/CONTRIBUTORS.tmpl @@ -1,6 +1,6 @@ {{define "CONTRIBUTORS"}} Thanks to the following contributors: -{{ range $contributor := .Contributors -}} +{{range $contributor := .Contributors -}} {{$contributor.Login}} | {{$contributor.URL}} -{{ end }} +{{end}} {{end}} diff --git a/cmd/documentation/root_templates/LICENSE b/cmd/documentation/root_templates/LICENSE.tmpl similarity index 100% rename from cmd/documentation/root_templates/LICENSE rename to cmd/documentation/root_templates/LICENSE.tmpl diff --git a/cmd/documentation/sub_templates/contributors.tmpl b/cmd/documentation/sub_templates/contributors.tmpl index 0d124405..e08f5609 100644 --- a/cmd/documentation/sub_templates/contributors.tmpl +++ b/cmd/documentation/sub_templates/contributors.tmpl @@ -5,8 +5,7 @@ |User|Github|Contribution Amount| |--|--|--| -{{ range $contributor := .Contributors -}} +{{- range $contributor := .Contributors -}} | {{$contributor.Login}} | {{$contributor.URL}} | {{$contributor.Contributions}} | -{{ end }} - +{{end}} {{end}} diff --git a/cmd/documentation/testdata_templates/testdata_readme.tmpl b/cmd/documentation/testdata_templates/testdata_readme.tmpl index 15a6e5d9..68a08abf 100644 --- a/cmd/documentation/testdata_templates/testdata_readme.tmpl +++ b/cmd/documentation/testdata_templates/testdata_readme.tmpl @@ -6,5 +6,5 @@ This folder contains a configuration test file for non-deployement test params. It also has the code coverage test files that allow us to monitor our entire codebase, click this link for more information [https://codecov.io/](https://codecov.io/). {{template "contributions"}} -{{template "donations"}} +{{template "donations" -}} {{end}} diff --git a/communications/slack/README.md b/communications/slack/README.md index 96639baf..b56e0617 100644 --- a/communications/slack/README.md +++ b/communications/slack/README.md @@ -63,38 +63,6 @@ err := s.Connect Once the bot has started you can interact with the bot using these commands via Slack: -main.go -```go -var b exchange.IBotExchange - -for i := range bot.Exchanges { - if bot.Exchanges[i].GetName() == "Bitfinex" { - b = bot.Exchanges[i] - } -} - -// Public calls - wrapper functions - -// Fetches current ticker information -tick, err := b.FetchTicker() -if err != nil { - // Handle error -} - -// Fetches current orderbook information -ob, err := b.FetchOrderbook() -if err != nil { - // Handle error -} - -// Private calls - wrapper functions - make sure your APIKEY and APISECRET are -// set and AuthenticatedAPISupport is set to true - -// Fetches current account information -accountInfo, err := b.GetAccountInfo() -if err != nil { - // Handle error -} ``` !status - Displays current working status of bot !help - Displays help text From 1daaa66830b9d3b967d4f2e6c3f220622502bde7 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 24 Jun 2019 17:34:07 +1000 Subject: [PATCH 15/71] Engine changes Add addr helpers (will be split off into own package) Engine status updates (log and data dir display) Use GetPairFormat for various exchanges instead of calling the config QA fixes Implement GCTRPC exchange deposit address handling --- engine/addr_helpers.go | 100 ++++++++++++++++++ engine/addr_helpers_test.go | 64 +++++++++++ engine/engine.go | 52 ++++----- engine/engine_types.go | 1 + engine/helpers.go | 58 +++++----- engine/orders.go | 2 - engine/rpcserver.go | 5 +- exchanges/anx/anx_wrapper.go | 4 +- exchanges/bithumb/bithumb_wrapper.go | 4 +- exchanges/bitmex/bitmex_wrapper.go | 4 +- exchanges/bitstamp/bitstamp.go | 19 ++-- exchanges/btcmarkets/btcmarkets_wrapper.go | 5 +- exchanges/gemini/gemini_wrapper.go | 2 +- .../localbitcoins/localbitcoins_wrapper.go | 4 +- main.go | 1 + 15 files changed, 247 insertions(+), 78 deletions(-) create mode 100644 engine/addr_helpers.go create mode 100644 engine/addr_helpers_test.go diff --git a/engine/addr_helpers.go b/engine/addr_helpers.go new file mode 100644 index 00000000..e78ab109 --- /dev/null +++ b/engine/addr_helpers.go @@ -0,0 +1,100 @@ +package engine + +import ( + "errors" + "strings" + "sync" + + "github.com/thrasher-/gocryptotrader/currency" +) + +// DepositAddressStore stores a list of exchange deposit addresses +type DepositAddressStore struct { + m sync.Mutex + Store map[string]map[string]string +} + +// DepositAddressManager manages the exchange deposit address store +type DepositAddressManager struct { + Store DepositAddressStore +} + +// vars related to the deposit address helpers +var ( + ErrDepositAddressStoreIsNil = errors.New("deposit address store is nil") + ErrDepositAddressNotFound = errors.New("deposit address does not exist") +) + +// Seed seeds the deposit address store +func (d *DepositAddressStore) Seed(coinData map[string]map[string]string) { + d.m.Lock() + defer d.m.Unlock() + if d.Store == nil { + d.Store = make(map[string]map[string]string) + } + + for k, v := range coinData { + r := make(map[string]string) + for w, x := range v { + r[strings.ToUpper(w)] = x + } + d.Store[strings.ToUpper(k)] = r + } +} + +// GetDepositAddress returns a deposit address based on the specified item +func (d *DepositAddressStore) GetDepositAddress(exchName string, item currency.Code) (string, error) { + d.m.Lock() + defer d.m.Unlock() + + if len(d.Store) == 0 { + return "", ErrDepositAddressStoreIsNil + } + + r, ok := d.Store[strings.ToUpper(exchName)] + if !ok { + return "", ErrExchangeNotFound + } + + addr, ok := r[strings.ToUpper(item.String())] + if !ok { + return "", ErrDepositAddressNotFound + } + + return addr, nil +} + +// GetDepositAddresses returns a list of stored deposit addresses +func (d *DepositAddressStore) GetDepositAddresses(exchName string) (map[string]string, error) { + d.m.Lock() + defer d.m.Unlock() + + if len(d.Store) == 0 { + return nil, ErrDepositAddressStoreIsNil + } + + r, ok := d.Store[strings.ToUpper(exchName)] + if !ok { + return nil, ErrDepositAddressNotFound + } + + return r, nil +} + +// GetDepositAddressByExchange returns a deposit address for the specified exchange and cryptocurrency +// if it exists +func (d *DepositAddressManager) GetDepositAddressByExchange(exchName string, currencyItem currency.Code) (string, error) { + return d.Store.GetDepositAddress(exchName, currencyItem) +} + +// GetDepositAddressesByExchange returns a list of cryptocurrency addresses for the specified +// exchange if they exist +func (d *DepositAddressManager) GetDepositAddressesByExchange(exchName string) (map[string]string, error) { + return d.Store.GetDepositAddresses(exchName) +} + +// Sync synchronises all deposit addresses +func (d *DepositAddressManager) Sync() { + result := GetExchangeCryptocurrencyDepositAddresses() + d.Store.Seed(result) +} diff --git a/engine/addr_helpers_test.go b/engine/addr_helpers_test.go new file mode 100644 index 00000000..34f29a25 --- /dev/null +++ b/engine/addr_helpers_test.go @@ -0,0 +1,64 @@ +package engine + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/currency" +) + +const ( + testBTCAddress = "1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" +) + +func TestSeed(t *testing.T) { + var d DepositAddressStore + u := map[string]map[string]string{ + "BITSTAMP": map[string]string{ + "BTC": testBTCAddress, + }, + } + + d.Seed(u) + r, err := d.GetDepositAddress("BITSTAMP", currency.BTC) + if err != nil { + t.Error("unexpected result") + } + + if r != testBTCAddress { + t.Error("unexpected result") + } +} + +func TestGetDepositAddress(t *testing.T) { + var d DepositAddressStore + _, err := d.GetDepositAddress("", currency.BTC) + if err != ErrDepositAddressStoreIsNil { + t.Error("non-error on non-existent exchange") + } + + d.Store = map[string]map[string]string{ + "BITSTAMP": map[string]string{ + "BTC": testBTCAddress, + }, + } + + _, err = d.GetDepositAddress("", currency.BTC) + if err != ErrExchangeNotFound { + t.Error("non-error on non-existent exchange") + } + + var r string + r, err = d.GetDepositAddress("BiTStAmP", currency.NewCode("bTC")) + if err != nil { + t.Error("unexpected err: ", err) + } + + if r != testBTCAddress { + t.Error("unexpected BTC address: ", r) + } + + _, err = d.GetDepositAddress("BiTStAmP", currency.LTC) + if err != ErrDepositAddressNotFound { + t.Error("unexpected err: ", err) + } +} diff --git a/engine/engine.go b/engine/engine.go index 15f803c4..8c3d654e 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "os/signal" - "path" "sync" "syscall" "time" @@ -25,20 +24,20 @@ import ( // Engine contains configuration, portfolio, exchange & ticker data and is the // overarching type across this code base. type Engine struct { - Config *config.Config - Portfolio *portfolio.Base - Exchanges []exchange.IBotExchange - ExchangeCurrencyPairManager *ExchangeCurrencyPairSyncer - NTPManager ntpManager - ConnectionManager connectionManager - OrderManager orderManager - PortfolioManager portfolioManager - CommsManager commsManager - Shutdown chan struct{} - Settings Settings - CryptocurrencyDepositAddresses map[string]map[string]string - Uptime time.Time - ServicesWG sync.WaitGroup + Config *config.Config + Portfolio *portfolio.Base + Exchanges []exchange.IBotExchange + ExchangeCurrencyPairManager *ExchangeCurrencyPairSyncer + NTPManager ntpManager + ConnectionManager connectionManager + OrderManager orderManager + PortfolioManager portfolioManager + CommsManager commsManager + DepositAddressManager *DepositAddressManager + Shutdown chan struct{} + Settings Settings + Uptime time.Time + ServicesWG sync.WaitGroup } // Vars for engine @@ -62,8 +61,6 @@ func New() (*Engine, error) { return nil, fmt.Errorf("failed to load config. Err: %s", err) } - b.CryptocurrencyDepositAddresses = make(map[string]map[string]string) - return &b, nil } @@ -75,7 +72,7 @@ func NewFromSettings(settings *Settings) (*Engine, error) { var b Engine b.Config = &config.Cfg - log.Debugf("Loading config file %s..\n", settings.ConfigFile) + log.Debugf("Loading config file %s...\n", settings.ConfigFile) err := b.Config.LoadConfig(settings.ConfigFile) if err != nil { return nil, fmt.Errorf("failed to load config. Err: %s", err) @@ -93,8 +90,11 @@ func NewFromSettings(settings *Settings) (*Engine, error) { b.Settings.ConfigFile = settings.ConfigFile b.Settings.DataDir = settings.DataDir - b.Settings.LogFile = path.Join(log.LogPath, log.Logger.File) - b.CryptocurrencyDepositAddresses = make(map[string]map[string]string) + + if *log.Logger.Enabled { + b.Settings.LogFile = log.LogPath + log.Debugf("Using log file: %s.\n", log.LogPath) + } err = utils.AdjustGoMaxProcs(settings.GoMaxProcs) if err != nil { @@ -102,9 +102,7 @@ func NewFromSettings(settings *Settings) (*Engine, error) { } b.handleInterrupt() - ValidateSettings(&b, settings) - return &b, nil } @@ -157,6 +155,7 @@ func ValidateSettings(b *Engine, s *Settings) { b.Settings.EnableNTPClient = s.EnableNTPClient b.Settings.EnableOrderManager = s.EnableOrderManager b.Settings.EnableExchangeSyncManager = s.EnableExchangeSyncManager + b.Settings.EnableDepositAddressManager = s.EnableDepositAddressManager b.Settings.EnableTickerSyncing = s.EnableTickerSyncing b.Settings.EnableOrderbookSyncing = s.EnableOrderbookSyncing b.Settings.EnableExchangeAutoPairUpdates = s.EnableExchangeAutoPairUpdates @@ -228,6 +227,7 @@ func PrintSettings(s *Settings) { log.Debugf("\t Event manager sleep delay: %v", s.EventManagerDelay) log.Debugf("\t Enable order manager: %v", s.EnableOrderManager) log.Debugf("\t Enable exchange sync manager: %v", s.EnableExchangeSyncManager) + log.Debugf("\t Enable deposit address manager: %v\n", s.EnableDepositAddressManager) log.Debugf("\t Enable ticker syncing: %v", s.EnableTickerSyncing) log.Debugf("\t Enable orderbook syncing: %v", s.EnableOrderbookSyncing) log.Debugf("\t Enable websocket routine: %v\n", s.EnableWebsocketRoutine) @@ -277,6 +277,7 @@ func (e *Engine) Start() { e.Uptime = time.Now() log.Debugf("Bot '%s' started.\n", e.Config.Name) + log.Debugf("Using data dir: %s\n", e.Settings.DataDir) enabledExchanges := e.Config.CountEnabledExchanges() if e.Settings.EnableAllExchanges { @@ -331,8 +332,6 @@ func (e *Engine) Start() { log.Warn("currency updater system failed to start", err) } - e.CryptocurrencyDepositAddresses = GetExchangeCryptocurrencyDepositAddresses() - if e.Settings.EnableGRPC { go StartRPCServer() } @@ -352,6 +351,11 @@ func (e *Engine) Start() { } } + if e.Settings.EnableDepositAddressManager { + e.DepositAddressManager = new(DepositAddressManager) + e.DepositAddressManager.Sync() + } + if e.Settings.EnableOrderManager { if err = e.OrderManager.Start(); err != nil { log.Errorf("Order manager unable to start: %v", err) diff --git a/engine/engine_types.go b/engine/engine_types.go index 32728c93..accad577 100644 --- a/engine/engine_types.go +++ b/engine/engine_types.go @@ -21,6 +21,7 @@ type Settings struct { EnableDeprecatedRPC bool EnableCommsRelayer bool EnableExchangeSyncManager bool + EnableDepositAddressManager bool EnableTickerSyncing bool EnableOrderbookSyncing bool EnableEventManager bool diff --git a/engine/helpers.go b/engine/helpers.go index c97122ab..a1f98ad1 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -629,15 +629,34 @@ func GetCryptocurrenciesByExchange(exchangeName string, enabledExchangesOnly, en return cryptocurrencies, nil } +// GetCryptocurrencyDepositAddressesByExchange returns the cryptocurrency deposit addresses for a particular exchange +func GetCryptocurrencyDepositAddressesByExchange(exchName string) (map[string]string, error) { + if Bot.DepositAddressManager != nil { + return Bot.DepositAddressManager.GetDepositAddressesByExchange(exchName) + } + + result := GetExchangeCryptocurrencyDepositAddresses() + r, ok := result[exchName] + if !ok { + return nil, ErrExchangeNotFound + } + + return r, nil +} + // GetExchangeCryptocurrencyDepositAddress returns the cryptocurrency deposit address for a particular // exchange -func GetExchangeCryptocurrencyDepositAddress(exchName string, item currency.Code) (string, error) { +func GetExchangeCryptocurrencyDepositAddress(exchName, accountID string, item currency.Code) (string, error) { + if Bot.DepositAddressManager != nil { + return Bot.DepositAddressManager.GetDepositAddressByExchange(exchName, item) + } + exch := GetExchangeByName(exchName) if exch == nil { return "", ErrExchangeNotFound } - return exch.GetDepositAddress(item, "") + return exch.GetDepositAddress(item, accountID) } // GetExchangeCryptocurrencyDepositAddresses obtains an exchanges deposit cryptocurrency list @@ -649,7 +668,6 @@ func GetExchangeCryptocurrencyDepositAddresses() map[string]map[string]string { continue } exchName := Bot.Exchanges[x].GetName() - if !Bot.Exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) { if Bot.Settings.Verbose { log.Debugf("GetExchangeCryptocurrencyDepositAddresses: Skippping %s due to disabled authenticated API support.", exchName) @@ -679,40 +697,18 @@ func GetExchangeCryptocurrencyDepositAddresses() map[string]map[string]string { return result } -// GetDepositAddressByExchange returns a deposit address for the specified exchange and cryptocurrency -// if it exists -func GetDepositAddressByExchange(exchName string, currencyItem currency.Code) string { - for x, y := range Bot.CryptocurrencyDepositAddresses { - if exchName == x { - addr, ok := y[currencyItem.String()] - if ok { - return addr - } - } - } - return "" -} - -// GetDepositAddressesByExchange returns a list of cryptocurrency addresses for the specified -// exchange if they exist -func GetDepositAddressesByExchange(exchName string) map[string]string { - for x, y := range Bot.CryptocurrencyDepositAddresses { - if exchName == x { - return y - } - } - return nil -} - // WithdrawCryptocurrencyFundsByExchange withdraws the desired cryptocurrency and amount to a desired cryptocurrency address -func WithdrawCryptocurrencyFundsByExchange(exchName string) (string, error) { +func WithdrawCryptocurrencyFundsByExchange(exchName string, req *exchange.CryptoWithdrawRequest) (string, error) { + if req == nil { + return "", errors.New("crypto withdraw request param is nil") + } + exch := GetExchangeByName(exchName) if exch == nil { return "", ErrExchangeNotFound } - // TO-DO: FILL - return exch.WithdrawCryptocurrencyFunds(&exchange.CryptoWithdrawRequest{}) + return exch.WithdrawCryptocurrencyFunds(req) } // FormatCurrency is a method that formats and returns a currency pair diff --git a/engine/orders.go b/engine/orders.go index 07aa93b1..9f6a8177 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -64,8 +64,6 @@ func (o *orderManager) Start() error { log.Debugln("Order manager starting...") - // test param - o.cfg.CancelOrdersOnShutdown = true o.shutdown = make(chan struct{}) o.orderStore.Orders = make(map[string][]exchange.OrderDetail) go o.run() diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 473f5007..6ea39dc9 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -727,7 +727,8 @@ func (s *RPCServer) GetCryptocurrencyDepositAddresses(ctx context.Context, r *gc return nil, errors.New("exchange is not loaded/doesn't exist") } - return &gctrpc.GetCryptocurrencyDepositAddressesResponse{}, common.ErrNotYetImplemented + result, err := GetCryptocurrencyDepositAddressesByExchange(r.Exchange) + return &gctrpc.GetCryptocurrencyDepositAddressesResponse{Addresses: result}, err } // GetCryptocurrencyDepositAddress returns a cryptocurrency deposit address @@ -738,7 +739,7 @@ func (s *RPCServer) GetCryptocurrencyDepositAddress(ctx context.Context, r *gctr return nil, errors.New("exchange is not loaded/doesn't exist") } - addr, err := exch.GetDepositAddress(currency.NewCode(r.Cryptocurrency), "") + addr, err := GetExchangeCryptocurrencyDepositAddress(r.Exchange, "", currency.NewCode(r.Cryptocurrency)) return &gctrpc.GetCryptocurrencyDepositAddressResponse{Address: addr}, err } diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 70705718..e0472ab8 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -485,7 +485,7 @@ func (a *ANX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]ex Amount: resp[i].TradedCurrencyAmount, CurrencyPair: currency.NewPairWithDelimiter(resp[i].TradedCurrency, resp[i].SettlementCurrency, - a.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + a.GetPairFormat(asset.Spot, false).Delimiter), OrderDate: orderDate, Exchange: a.Name, ID: resp[i].OrderID, @@ -527,7 +527,7 @@ func (a *ANX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]ex Status: resp[i].OrderStatus, CurrencyPair: currency.NewPairWithDelimiter(resp[i].TradedCurrency, resp[i].SettlementCurrency, - a.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + a.GetPairFormat(asset.Spot, false).Delimiter), } orders = append(orders, orderDetail) diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 6836b8ea..e84c4cd2 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -447,7 +447,7 @@ func (b *Bithumb) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( Status: string(exchange.ActiveOrderStatus), CurrencyPair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency, resp.Data[i].PaymentCurrency, - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + b.GetPairFormat(asset.Spot, false).Delimiter), } if resp.Data[i].Type == "bid" { @@ -490,7 +490,7 @@ func (b *Bithumb) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( RemainingAmount: resp.Data[i].UnitsRemaining, CurrencyPair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency, resp.Data[i].PaymentCurrency, - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + b.GetPairFormat(asset.Spot, false).Delimiter), } if resp.Data[i].Type == "bid" { diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index f62a4335..a86b9a92 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -519,7 +519,7 @@ func (b *Bitmex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ Status: resp[i].OrdStatus, CurrencyPair: currency.NewPairWithDelimiter(resp[i].Symbol, resp[i].SettlCurrency, - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + b.GetPairFormat(asset.PerpetualContract, false).Delimiter), } orders = append(orders, orderDetail) @@ -561,7 +561,7 @@ func (b *Bitmex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ Status: resp[i].OrdStatus, CurrencyPair: currency.NewPairWithDelimiter(resp[i].Symbol, resp[i].SettlCurrency, - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + b.GetPairFormat(asset.PerpetualContract, false).Delimiter), } orders = append(orders, orderDetail) diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 91dc3a0f..6787ff52 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -505,6 +505,9 @@ func (b *Bitstamp) OpenInternationalBankWithdrawal(amount float64, currency, // crypto - example "btc", "ltc", "eth", "xrp" or "bch" func (b *Bitstamp) GetCryptoDepositAddress(crypto currency.Code) (string, error) { var resp string + v2Resp := struct { + Address string `json:"address"` + }{} switch crypto { case currency.BTC: @@ -512,20 +515,20 @@ func (b *Bitstamp) GetCryptoDepositAddress(crypto currency.Code) (string, error) b.SendAuthenticatedHTTPRequest(bitstampAPIBitcoinDeposit, false, nil, &resp) case currency.LTC: - return resp, - b.SendAuthenticatedHTTPRequest(bitstampAPILitecoinDeposit, true, nil, &resp) + return v2Resp.Address, + b.SendAuthenticatedHTTPRequest(bitstampAPILitecoinDeposit, true, nil, &v2Resp) case currency.ETH: - return resp, - b.SendAuthenticatedHTTPRequest(bitstampAPIEthereumDeposit, true, nil, &resp) + return v2Resp.Address, + b.SendAuthenticatedHTTPRequest(bitstampAPIEthereumDeposit, true, nil, &v2Resp) case currency.XRP: - return resp, - b.SendAuthenticatedHTTPRequest(bitstampAPIXrpDeposit, true, nil, &resp) + return v2Resp.Address, + b.SendAuthenticatedHTTPRequest(bitstampAPIXrpDeposit, true, nil, &v2Resp) case currency.BCH: - return resp, - b.SendAuthenticatedHTTPRequest(bitstampAPIBitcoinCashDeposit, true, nil, &resp) + return v2Resp.Address, + b.SendAuthenticatedHTTPRequest(bitstampAPIBitcoinCashDeposit, true, nil, &v2Resp) default: return resp, fmt.Errorf("unsupported cryptocurrency string %s", crypto) diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 51626680..75231916 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -473,7 +473,8 @@ func (b *BTCMarkets) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest Price: resp[i].Price, Status: resp[i].Status, CurrencyPair: currency.NewPairWithDelimiter(resp[i].Instrument, - resp[i].Currency, "-"), + resp[i].Currency, + b.GetPairFormat(asset.Spot, false).Delimiter), } for j := range resp[i].Trades { @@ -542,7 +543,7 @@ func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest Status: respOrders[i].Status, CurrencyPair: currency.NewPairWithDelimiter(respOrders[i].Instrument, respOrders[i].Currency, - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + b.GetPairFormat(asset.Spot, false).Delimiter), } for j := range respOrders[i].Trades { diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index eb765659..11bb0c2c 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -459,7 +459,7 @@ func (g *Gemini) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ Price: trades[i].Price, CurrencyPair: currency.NewPairWithDelimiter(trades[i].BaseCurrency, trades[i].QuoteCurrency, - g.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + g.GetPairFormat(asset.Spot, false).Delimiter), }) } diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 9ca6b872..b977c30a 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -443,7 +443,7 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequ OrderSide: side, CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), resp[i].Data.Currency, - l.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + l.GetPairFormat(asset.Spot, false).Delimiter), Exchange: l.Name, }) } @@ -516,7 +516,7 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequ Status: status, CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), allTrades[i].Data.Currency, - l.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + l.GetPairFormat(asset.Spot, false).Delimiter), Exchange: l.Name, }) } diff --git a/main.go b/main.go index 1ce4b8fd..5ffba2af 100644 --- a/main.go +++ b/main.go @@ -46,6 +46,7 @@ func main() { flag.BoolVar(&settings.EnableCoinmarketcapAnalysis, "coinmarketcap", false, "overrides config and runs currency analysis") flag.BoolVar(&settings.EnableEventManager, "eventmanager", true, "enables the event manager") flag.BoolVar(&settings.EnableOrderManager, "ordermanager", true, "enables the order manager") + flag.BoolVar(&settings.EnableDepositAddressManager, "depositaddressmanager", true, "enables the deposit address manager") flag.BoolVar(&settings.EnableConnectivityMonitor, "connectivitymonitor", true, "enables the connectivity monitor") flag.DurationVar(&settings.EventManagerDelay, "eventmanagerdelay", time.Duration(0), "sets the event managers sleep delay between event checking") flag.BoolVar(&settings.EnableNTPClient, "ntpclient", true, "enables the NTP client to check system clock drift") From 6de0606d559eb0b29cac95ffb5608d7585dc8149 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 25 Jun 2019 17:44:46 +1000 Subject: [PATCH 16/71] Add order validation test code and use GetPairFormat where necessary --- exchanges/bitstamp/bitstamp_wrapper.go | 2 +- exchanges/bittrex/bittrex_wrapper.go | 4 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 2 +- exchanges/btse/btse_wrapper.go | 4 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 4 +- exchanges/gateio/gateio_wrapper.go | 6 +- exchanges/gemini/gemini_wrapper.go | 2 +- exchanges/hitbtc/hitbtc_wrapper.go | 4 +- exchanges/huobihadax/huobihadax_wrapper.go | 4 +- exchanges/itbit/itbit_wrapper.go | 4 +- exchanges/kraken/kraken.go | 6 +- exchanges/kraken/kraken_wrapper.go | 14 ++-- exchanges/lakebtc/lakebtc_wrapper.go | 5 +- exchanges/okgroup/okgroup_wrapper.go | 2 +- exchanges/order_test.go | 81 ++++++++++++++++++++ exchanges/order_types.go | 21 +++-- exchanges/poloniex/poloniex_wrapper.go | 4 +- exchanges/yobit/yobit_wrapper.go | 4 +- exchanges/zb/zb_wrapper.go | 4 +- 19 files changed, 134 insertions(+), 43 deletions(-) create mode 100644 exchanges/order_test.go diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 9e1d4b22..686e778e 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -505,7 +505,7 @@ func (b *Bitstamp) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) if quoteCurrency.String() != "" && baseCurrency.String() != "" { currPair = currency.NewPairWithDelimiter(baseCurrency.String(), quoteCurrency.String(), - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + b.GetPairFormat(asset.Spot, false).Delimiter) } orderDate := time.Unix(order.Date, 0) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index ba6ef544..579fa626 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -429,7 +429,7 @@ func (b *Bittrex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( } pair := currency.NewPairDelimiter(resp.Result[i].Exchange, - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + b.GetPairFormat(asset.Spot, false).Delimiter) orderType := exchange.OrderType(strings.ToUpper(resp.Result[i].Type)) orders = append(orders, exchange.OrderDetail{ @@ -473,7 +473,7 @@ func (b *Bittrex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( } pair := currency.NewPairDelimiter(resp.Result[i].Exchange, - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + b.GetPairFormat(asset.Spot, false).Delimiter) orderType := exchange.OrderType(strings.ToUpper(resp.Result[i].Type)) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 75231916..ab76f7ba 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -399,7 +399,7 @@ func (b *BTCMarkets) GetOrderInfo(orderID string) (exchange.OrderDetail, error) OrderDetail.Status = orders[i].Status OrderDetail.CurrencyPair = currency.NewPairWithDelimiter(orders[i].Instrument, orders[i].Currency, - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + b.GetPairFormat(asset.Spot, false).Delimiter) } return OrderDetail, nil diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 4f591a10..081740b5 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -355,7 +355,7 @@ func (b *BTSE) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { } od.CurrencyPair = currency.NewPairDelimiter(o.ProductID, - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + b.GetPairFormat(asset.Spot, false).Delimiter) od.Exchange = b.Name od.Amount = o.Amount od.ID = o.ID @@ -432,7 +432,7 @@ func (b *BTSE) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]e openOrder := exchange.OrderDetail{ CurrencyPair: currency.NewPairDelimiter(order.ProductID, - b.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + b.GetPairFormat(asset.Spot, false).Delimiter), Exchange: b.Name, Amount: order.Amount, ID: order.ID, diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 9a71c5e1..79b16add 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -428,7 +428,7 @@ func (c *CoinbasePro) GetActiveOrders(getOrdersRequest *exchange.GetOrdersReques var orders []exchange.OrderDetail for i := range respOrders { currency := currency.NewPairDelimiter(respOrders[i].ProductID, - c.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + c.GetPairFormat(asset.Spot, false).Delimiter) orderSide := exchange.OrderSide(strings.ToUpper(respOrders[i].Side)) orderType := exchange.OrderType(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) @@ -471,7 +471,7 @@ func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersReques var orders []exchange.OrderDetail for i := range respOrders { currency := currency.NewPairDelimiter(respOrders[i].ProductID, - c.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + c.GetPairFormat(asset.Spot, false).Delimiter) orderSide := exchange.OrderSide(strings.ToUpper(respOrders[i].Side)) orderType := exchange.OrderType(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 9de26b67..ae9940f7 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -419,7 +419,7 @@ func (g *Gateio) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { orderDetail.Status = orders.Orders[x].Status orderDetail.Price = orders.Orders[x].Rate orderDetail.CurrencyPair = currency.NewPairDelimiter(orders.Orders[x].CurrencyPair, - g.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + g.GetPairFormat(asset.Spot, false).Delimiter) if strings.EqualFold(orders.Orders[x].Type, exchange.AskOrderSide.ToString()) { orderDetail.OrderSide = exchange.AskOrderSide } else if strings.EqualFold(orders.Orders[x].Type, exchange.BidOrderSide.ToString()) { @@ -505,7 +505,7 @@ func (g *Gateio) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ } symbol := currency.NewPairDelimiter(resp.Orders[i].CurrencyPair, - g.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + g.GetPairFormat(asset.Spot, false).Delimiter) side := exchange.OrderSide(strings.ToUpper(resp.Orders[i].Type)) orderDate := time.Unix(resp.Orders[i].Timestamp, 0) @@ -542,7 +542,7 @@ func (g *Gateio) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for _, trade := range trades { symbol := currency.NewPairDelimiter(trade.Pair, - g.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + g.GetPairFormat(asset.Spot, false).Delimiter) side := exchange.OrderSide(strings.ToUpper(trade.Type)) orderDate := time.Unix(trade.TimeUnix, 0) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 11bb0c2c..05ff5f9c 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -389,7 +389,7 @@ func (g *Gemini) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp { symbol := currency.NewPairDelimiter(resp[i].Symbol, - g.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + g.GetPairFormat(asset.Spot, false).Delimiter) var orderType exchange.OrderType if resp[i].Type == "exchange limit" { orderType = exchange.LimitOrderType diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 99734a78..e7c7aab8 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -425,7 +425,7 @@ func (h *HitBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + h.GetPairFormat(asset.Spot, false).Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[i].Side)) orders = append(orders, exchange.OrderDetail{ ID: allOrders[i].ID, @@ -463,7 +463,7 @@ func (h *HitBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + h.GetPairFormat(asset.Spot, false).Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[i].Side)) orders = append(orders, exchange.OrderDetail{ ID: allOrders[i].ID, diff --git a/exchanges/huobihadax/huobihadax_wrapper.go b/exchanges/huobihadax/huobihadax_wrapper.go index fc50405a..d39637c3 100644 --- a/exchanges/huobihadax/huobihadax_wrapper.go +++ b/exchanges/huobihadax/huobihadax_wrapper.go @@ -500,7 +500,7 @@ func (h *HUOBIHADAX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + h.GetPairFormat(asset.Spot, false).Delimiter) orderDate := time.Unix(0, allOrders[i].CreatedAt*int64(time.Millisecond)) orders = append(orders, exchange.OrderDetail{ @@ -546,7 +546,7 @@ func (h *HUOBIHADAX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest var orders []exchange.OrderDetail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + h.GetPairFormat(asset.Spot, false).Delimiter) orderDate := time.Unix(0, allOrders[i].CreatedAt*int64(time.Millisecond)) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 07963c36..d59f5dac 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -420,7 +420,7 @@ func (i *ItBit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] var orders []exchange.OrderDetail for j := range allOrders { symbol := currency.NewPairDelimiter(allOrders[j].Instrument, - i.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + i.GetPairFormat(asset.Spot, false).Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { @@ -471,7 +471,7 @@ func (i *ItBit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] } symbol := currency.NewPairDelimiter(allOrders[j].Instrument, - i.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + i.GetPairFormat(asset.Spot, false).Delimiter) side := exchange.OrderSide(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index be73a5ac..153ec417 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -767,15 +767,15 @@ func (k *Kraken) AddOrder(symbol, side, orderType string, volume, price, price2, params.Set("leverage", strconv.FormatFloat(leverage, 'f', -1, 64)) } - if args.Oflags == "" { + if args.Oflags != "" { params.Set("oflags", args.Oflags) } - if args.StartTm == "" { + if args.StartTm != "" { params.Set("starttm", args.StartTm) } - if args.ExpireTm == "" { + if args.ExpireTm != "" { params.Set("expiretm", args.ExpireTm) } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 8ee63788..3fa3f282 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -446,10 +446,10 @@ func (k *Kraken) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp.Open { - symbol := currency.NewPairDelimiter(resp.Open[i].Descr.Pair, - k.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + symbol := currency.NewPairFromString(resp.Open[i].Descr.Pair) orderDate := time.Unix(int64(resp.Open[i].StartTm), 0) side := exchange.OrderSide(strings.ToUpper(resp.Open[i].Descr.Type)) + orderType := exchange.OrderType(strings.ToUpper(resp.Open[i].Descr.OrderType)) orders = append(orders, exchange.OrderDetail{ ID: i, @@ -458,8 +458,9 @@ func (k *Kraken) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ ExecutedAmount: resp.Open[i].VolExec, Exchange: k.Name, OrderDate: orderDate, - Price: resp.Open[i].Price, + Price: resp.Open[i].Descr.Price, OrderSide: side, + OrderType: orderType, CurrencyPair: symbol, }) } @@ -489,10 +490,10 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp.Closed { - symbol := currency.NewPairDelimiter(resp.Closed[i].Descr.Pair, - k.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + symbol := currency.NewPairFromString(resp.Closed[i].Descr.Pair) orderDate := time.Unix(int64(resp.Closed[i].StartTm), 0) side := exchange.OrderSide(strings.ToUpper(resp.Closed[i].Descr.Type)) + orderType := exchange.OrderType(strings.ToUpper(resp.Closed[i].Descr.OrderType)) orders = append(orders, exchange.OrderDetail{ ID: i, @@ -501,8 +502,9 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ ExecutedAmount: resp.Closed[i].VolExec, Exchange: k.Name, OrderDate: orderDate, - Price: resp.Closed[i].Price, + Price: resp.Closed[i].Descr.Price, OrderSide: side, + OrderType: orderType, CurrencyPair: symbol, }) } diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 87ff7b58..0101c816 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -394,7 +394,8 @@ func (l *LakeBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( var orders []exchange.OrderDetail for _, order := range resp { - symbol := currency.NewPairDelimiter(order.Symbol, l.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + symbol := currency.NewPairDelimiter(order.Symbol, + l.GetPairFormat(asset.Spot, false).Delimiter) orderDate := time.Unix(order.At, 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) @@ -431,7 +432,7 @@ func (l *LakeBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( } symbol := currency.NewPairDelimiter(order.Symbol, - l.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + l.GetPairFormat(asset.Spot, false).Delimiter) orderDate := time.Unix(order.At, 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index 572ae026..616b62fe 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -302,7 +302,7 @@ func (o *OKGroup) GetOrderInfo(orderID string) (resp exchange.OrderDetail, err e resp = exchange.OrderDetail{ Amount: order.Size, CurrencyPair: currency.NewPairDelimiter(order.InstrumentID, - o.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter), + o.GetPairFormat(asset.Spot, false).Delimiter), Exchange: o.Name, OrderDate: order.Timestamp, ExecutedAmount: order.FilledSize, diff --git a/exchanges/order_test.go b/exchanges/order_test.go new file mode 100644 index 00000000..94f2bc02 --- /dev/null +++ b/exchanges/order_test.go @@ -0,0 +1,81 @@ +package exchange + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/currency" +) + +func TestValidate(t *testing.T) { + testPair := currency.NewPair(currency.BTC, currency.LTC) + tester := []struct { + Pair currency.Pair + Side OrderSide + Type OrderType + Amount float64 + Price float64 + ExpectedErr error + }{ + { + ExpectedErr: ErrOrderPairIsEmpty, + }, // empty pair + { + Pair: testPair, + ExpectedErr: ErrOrderSideIsInvalid, + }, // valid pair but invalid order side + { + Pair: testPair, + Side: BuyOrderSide, + ExpectedErr: ErrOrderTypeIsInvalid, + }, // valid pair and order side but invalid order type + { + Pair: testPair, + Side: SellOrderSide, + ExpectedErr: ErrOrderTypeIsInvalid, + }, // valid pair and order side but invalid order type + { + Pair: testPair, + Side: BidOrderSide, + ExpectedErr: ErrOrderTypeIsInvalid, + }, // valid pair and order side but invalid order type + { + Pair: testPair, + Side: AskOrderSide, + ExpectedErr: ErrOrderTypeIsInvalid, + }, // valid pair and order side but invalid order type + { + Pair: testPair, + Side: AskOrderSide, + Type: MarketOrderType, + ExpectedErr: ErrOrderAmountIsInvalid, + }, // valid pair, order side, type but invalid amount + { + Pair: testPair, + Side: AskOrderSide, + Type: LimitOrderType, + Amount: 1, + ExpectedErr: ErrOrderPriceMustBeSetIfLimitOrder, + }, // valid pair, order side, type, amount but invalid price + { + Pair: testPair, + Side: AskOrderSide, + Type: LimitOrderType, + Amount: 1, + Price: 1000, + ExpectedErr: nil, + }, // valid order! + } + + for x := range tester { + s := OrderSubmission{ + Pair: tester[x].Pair, + OrderSide: tester[x].Side, + OrderType: tester[x].Type, + Amount: tester[x].Amount, + Price: tester[x].Price, + } + if err := s.Validate(); err != tester[x].ExpectedErr { + t.Errorf("Unexpected result. Got: %s, want: %s", err, tester[x].ExpectedErr) + } + } +} diff --git a/exchanges/order_types.go b/exchanges/order_types.go index ae5375c1..7f2b9523 100644 --- a/exchanges/order_types.go +++ b/exchanges/order_types.go @@ -13,7 +13,12 @@ import ( // vars related to orders var ( - ErrOrderSubmissionIsNil = errors.New("order submission is nil") + ErrOrderSubmissionIsNil = errors.New("order submission is nil") + ErrOrderPairIsEmpty = errors.New("order pair is empty") + ErrOrderSideIsInvalid = errors.New("order side is invalid") + ErrOrderTypeIsInvalid = errors.New("order type is invalid") + ErrOrderAmountIsInvalid = errors.New("order amount is invalid") + ErrOrderPriceMustBeSetIfLimitOrder = errors.New("order price must be set if limit order type is desired") ) // OrderSubmission contains the order submission data @@ -29,24 +34,26 @@ type OrderSubmission struct { // Validate checks the supplied data and returns whether or not its valid func (o *OrderSubmission) Validate() error { if o.Pair.IsEmpty() { - return errors.New("order pair is empty") + return ErrOrderPairIsEmpty } - if o.OrderSide != BuyOrderSide && o.OrderSide != SellOrderSide || + o.OrderSide = OrderSide(strings.ToUpper(o.OrderSide.ToString())) + if o.OrderSide != BuyOrderSide && o.OrderSide != SellOrderSide && o.OrderSide != BidOrderSide && o.OrderSide != AskOrderSide { - return errors.New("order side is invalid") + return ErrOrderSideIsInvalid } + o.OrderType = OrderType(strings.ToUpper(o.OrderType.ToString())) if o.OrderType != MarketOrderType && o.OrderType != LimitOrderType { - return errors.New("order type is invalid") + return ErrOrderTypeIsInvalid } if o.Amount <= 0 { - return errors.New("order amount is invalid") + return ErrOrderAmountIsInvalid } if o.OrderType == LimitOrderType && o.Price <= 0 { - return errors.New("order price must be set if limit order type is desired") + return ErrOrderPriceMustBeSetIfLimitOrder } return nil diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index f0ab0cc3..f1f945de 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -446,7 +446,7 @@ func (p *Poloniex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) var orders []exchange.OrderDetail for currencyPair, openOrders := range resp.Data { symbol := currency.NewPairDelimiter(currencyPair, - p.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + p.GetPairFormat(asset.Spot, false).Delimiter) for _, order := range openOrders { orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) @@ -488,7 +488,7 @@ func (p *Poloniex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) var orders []exchange.OrderDetail for currencyPair, historicOrders := range resp.Data { symbol := currency.NewPairDelimiter(currencyPair, - p.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + p.GetPairFormat(asset.Spot, false).Delimiter) for _, order := range historicOrders { orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 2d5ac159..65b0898d 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -425,7 +425,7 @@ func (y *Yobit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] for ID, order := range resp { symbol := currency.NewPairDelimiter(order.Pair, - y.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + y.GetPairFormat(asset.Spot, false).Delimiter) orderDate := time.Unix(int64(order.TimestampCreated), 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) orders = append(orders, exchange.OrderDetail{ @@ -470,7 +470,7 @@ func (y *Yobit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] var orders []exchange.OrderDetail for _, order := range allOrders { symbol := currency.NewPairDelimiter(order.Pair, - y.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + y.GetPairFormat(asset.Spot, false).Delimiter) orderDate := time.Unix(int64(order.Timestamp), 0) side := exchange.OrderSide(strings.ToUpper(order.Type)) orders = append(orders, exchange.OrderDetail{ diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 9996c924..c4b48c2a 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -455,7 +455,7 @@ func (z *ZB) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exc var orders []exchange.OrderDetail for _, order := range allOrders { symbol := currency.NewPairDelimiter(order.Currency, - z.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + z.GetPairFormat(asset.Spot, false).Delimiter) orderDate := time.Unix(int64(order.TradeDate), 0) orderSide := orderSideMap[order.Type] orders = append(orders, exchange.OrderDetail{ @@ -511,7 +511,7 @@ func (z *ZB) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exc var orders []exchange.OrderDetail for _, order := range allOrders { symbol := currency.NewPairDelimiter(order.Currency, - z.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter) + z.GetPairFormat(asset.Spot, false).Delimiter) orderDate := time.Unix(int64(order.TradeDate), 0) orderSide := orderSideMap[order.Type] orders = append(orders, exchange.OrderDetail{ From 7dbfcb311c767aaf736069661de5998bd4a8194d Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 27 Jun 2019 13:58:12 +1000 Subject: [PATCH 17/71] Config: Check asset type when obtaining pair format --- config/config.go | 10 ++++++++++ engine/helpers.go | 10 ++++------ exchanges/bitfinex/bitfinex_wrapper.go | 1 + exchanges/bitmex/bitmex_wrapper.go | 1 + exchanges/coinbasepro/coinbasepro_wrapper.go | 1 + exchanges/coinut/coinut_wrapper.go | 1 + exchanges/hitbtc/hitbtc_wrapper.go | 1 + exchanges/huobi/huobi_wrapper.go | 1 + exchanges/poloniex/poloniex_wrapper.go | 1 + exchanges/zb/zb_wrapper.go | 1 + 10 files changed, 22 insertions(+), 6 deletions(-) diff --git a/config/config.go b/config/config.go index a0ac2a35..6bfbb9b1 100644 --- a/config/config.go +++ b/config/config.go @@ -652,6 +652,16 @@ func (c *Config) GetPairFormat(exchName string, assetType asset.Item) (currency. return currency.PairFormat{}, err } + supports, err := c.SupportsExchangeAssetType(exchName, assetType) + if err != nil { + return currency.PairFormat{}, err + } + + if !supports { + return currency.PairFormat{}, + fmt.Errorf("exchange %s does not support asset type %v", exchName, assetType) + } + if exchCfg.CurrencyPairs == nil { return currency.PairFormat{}, errors.New("exchange currency pairs type is nil") } diff --git a/engine/helpers.go b/engine/helpers.go index a1f98ad1..ebe5ad3d 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -590,24 +590,22 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) { func GetCryptocurrenciesByExchange(exchangeName string, enabledExchangesOnly, enabledPairs bool, assetType asset.Item) ([]string, error) { var cryptocurrencies []string for x := range Bot.Config.Exchanges { - if Bot.Config.Exchanges[x].Name != exchangeName { + if !strings.EqualFold(Bot.Config.Exchanges[x].Name, exchangeName) { continue } if enabledExchangesOnly && !Bot.Config.Exchanges[x].Enabled { continue } - exchName := Bot.Config.Exchanges[x].Name - var pairs []currency.Pair var err error - + var pairs []currency.Pair if enabledPairs { - pairs, err = Bot.Config.GetEnabledPairs(exchName, assetType) + pairs, err = Bot.Config.GetEnabledPairs(exchangeName, assetType) if err != nil { return nil, err } } else { - pairs, err = Bot.Config.GetAvailablePairs(exchName, assetType) + pairs, err = Bot.Config.GetAvailablePairs(exchangeName, assetType) if err != nil { return nil, err } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index b10e8aa2..c1cb0e4c 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -88,6 +88,7 @@ func (b *Bitfinex) SetDefaults() { b.API.Endpoints.URLDefault = bitfinexAPIURLBase b.API.Endpoints.URL = b.API.Endpoints.URLDefault + b.API.Endpoints.WebsocketURL = bitfinexWebsocket b.WebsocketInit() b.Websocket.Functionality = exchange.WebsocketTickerSupported | exchange.WebsocketTradeDataSupported | diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index a86b9a92..4e1daabe 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -111,6 +111,7 @@ func (b *Bitmex) SetDefaults() { b.API.Endpoints.URLDefault = bitmexAPIURL b.API.Endpoints.URL = b.API.Endpoints.URLDefault + b.API.Endpoints.WebsocketURL = bitmexWSURL b.WebsocketInit() b.Websocket.Functionality = exchange.WebsocketTradeDataSupported | exchange.WebsocketOrderbookSupported | diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 79b16add..893ab333 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -88,6 +88,7 @@ func (c *CoinbasePro) SetDefaults() { c.API.Endpoints.URLDefault = coinbaseproAPIURL c.API.Endpoints.URL = c.API.Endpoints.URLDefault + c.API.Endpoints.WebsocketURL = coinbaseproWebsocketURL c.WebsocketInit() c.Websocket.Functionality = exchange.WebsocketTickerSupported | exchange.WebsocketOrderbookSupported | diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 78b8d807..cf971dc8 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -85,6 +85,7 @@ func (c *COINUT) SetDefaults() { c.API.Endpoints.URLDefault = coinutAPIURL c.API.Endpoints.URL = c.API.Endpoints.URLDefault + c.API.Endpoints.WebsocketURL = coinutWebsocketURL c.WebsocketInit() c.Websocket.Functionality = exchange.WebsocketTickerSupported | exchange.WebsocketOrderbookSupported | diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index e7c7aab8..0cbf9af8 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -87,6 +87,7 @@ func (h *HitBTC) SetDefaults() { h.API.Endpoints.URLDefault = apiURL h.API.Endpoints.URL = h.API.Endpoints.URLDefault + h.API.Endpoints.WebsocketURL = hitbtcWebsocketAddress h.WebsocketInit() h.Websocket.Functionality = exchange.WebsocketTickerSupported | exchange.WebsocketOrderbookSupported diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 137d1cd3..571b2b9f 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -88,6 +88,7 @@ func (h *HUOBI) SetDefaults() { h.API.Endpoints.URLDefault = huobiAPIURL h.API.Endpoints.URL = h.API.Endpoints.URLDefault + h.API.Endpoints.WebsocketURL = wsMarketURL h.WebsocketInit() h.Websocket.Functionality = exchange.WebsocketKlineSupported | exchange.WebsocketOrderbookSupported | diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index f1f945de..92e86861 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -87,6 +87,7 @@ func (p *Poloniex) SetDefaults() { p.API.Endpoints.URLDefault = poloniexAPIURL p.API.Endpoints.URL = p.API.Endpoints.URLDefault + p.API.Endpoints.WebsocketURL = poloniexWebsocketAddress p.WebsocketInit() p.Websocket.Functionality = exchange.WebsocketTradeDataSupported | exchange.WebsocketOrderbookSupported | diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index c4b48c2a..78418169 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -90,6 +90,7 @@ func (z *ZB) SetDefaults() { z.API.Endpoints.URL = z.API.Endpoints.URLDefault z.API.Endpoints.URLSecondaryDefault = zbMarketURL z.API.Endpoints.URLSecondary = z.API.Endpoints.URLSecondaryDefault + z.API.Endpoints.WebsocketURL = zbWebsocketAPI z.WebsocketInit() z.Websocket.Functionality = exchange.WebsocketTickerSupported | exchange.WebsocketOrderbookSupported | From 7112a8949141cb85e7c0b95ab5e7be28c3d91a27 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Sun, 30 Jun 2019 22:09:19 +1000 Subject: [PATCH 18/71] Add orderbook calculator and verify func --- cmd/gctcli/commands.go | 180 ++++ cmd/gctcli/main.go | 2 + engine/rpcserver.go | 74 ++ exchanges/exchange.go | 7 +- exchanges/interfaces.go | 1 + exchanges/orderbook/calculator.go | 227 +++++ exchanges/orderbook/calculator_test.go | 79 ++ exchanges/orderbook/orderbook.go | 42 + exchanges/orderbook/orderbook_test.go | 22 + .../orderbook/simulator/simulator_test.go | 24 + gctrpc/rpc.pb.go | 832 ++++++++++++------ gctrpc/rpc.pb.gw.go | 82 ++ gctrpc/rpc.proto | 37 + gctrpc/rpc.swagger.json | 118 +++ 14 files changed, 1452 insertions(+), 275 deletions(-) create mode 100644 exchanges/orderbook/calculator.go create mode 100644 exchanges/orderbook/calculator_test.go create mode 100644 exchanges/orderbook/simulator/simulator_test.go diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index 5dc0c4eb..1cae5f76 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -1243,6 +1243,186 @@ func submitOrder(c *cli.Context) error { return nil } +var simulateOrderCommand = cli.Command{ + Name: "simulateorder", + Usage: "simulate order simulates an exchange order", + ArgsUsage: " ", + Action: simulateOrder, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to simulate the order for", + }, + cli.StringFlag{ + Name: "currency_pair", + Usage: "the currency pair", + }, + cli.StringFlag{ + Name: "side", + Usage: "the order side to use (BUY OR SELL)", + }, + cli.Float64Flag{ + Name: "amount", + Usage: "the amount for the order", + }, + }, +} + +func simulateOrder(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "simulateorder") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + var currencyPair string + var orderSide string + var amount float64 + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if c.IsSet("currency_pair") { + currencyPair = c.String("currency_pair") + } else { + currencyPair = c.Args().Get(1) + } + + if c.IsSet("side") { + orderSide = c.String("side") + } else { + orderSide = c.Args().Get(2) + } + + if c.IsSet("amount") { + amount = c.Float64("amount") + } else { + amount, _ = strconv.ParseFloat(c.Args().Get(3), 64) + } + + if !validPair(currencyPair) { + return errInvalidPair + } + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.SimulateOrder(context.Background(), &gctrpc.SimulateOrderRequest{ + Exchange: exchangeName, + Pair: &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }, + Side: orderSide, + Amount: amount, + }) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +var whaleBombCommand = cli.Command{ + Name: "whalebomb", + Usage: "whale bomb finds the amount required to reach a price target", + ArgsUsage: " ", + Action: whaleBomb, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to whale bomb", + }, + cli.StringFlag{ + Name: "currency_pair", + Usage: "the currency pair", + }, + cli.StringFlag{ + Name: "side", + Usage: "the order side to use (BUY OR SELL)", + }, + cli.Float64Flag{ + Name: "price", + Usage: "the price target", + }, + }, +} + +func whaleBomb(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "whalebomb") + return nil + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + var exchangeName string + var currencyPair string + var orderSide string + var price float64 + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if c.IsSet("currency_pair") { + currencyPair = c.String("currency_pair") + } else { + currencyPair = c.Args().Get(1) + } + + if c.IsSet("side") { + orderSide = c.String("side") + } else { + orderSide = c.Args().Get(2) + } + + if c.IsSet("price") { + price = c.Float64("price") + } else { + price, _ = strconv.ParseFloat(c.Args().Get(3), 64) + } + + if !validPair(currencyPair) { + return errInvalidPair + } + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.WhaleBomb(context.Background(), &gctrpc.WhaleBombRequest{ + Exchange: exchangeName, + Pair: &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }, + Side: orderSide, + PriceTarget: price, + }) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + var cancelOrderCommand = cli.Command{ Name: "cancelorder", Usage: "cancel order cancels an exchange order", diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index 8267fa67..2ba53d45 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -111,6 +111,8 @@ func main() { getOrdersCommand, getOrderCommand, submitOrderCommand, + simulateOrderCommand, + whaleBombCommand, cancelOrderCommand, cancelAllOrdersCommand, getEventsCommand, diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 6ea39dc9..ea0ed348 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -664,6 +664,80 @@ func (s *RPCServer) SubmitOrder(ctx context.Context, r *gctrpc.SubmitOrderReques }, err } +// SimulateOrder simulates an order specified by exchange, currency pair and asset +// type +func (s *RPCServer) SimulateOrder(ctx context.Context, r *gctrpc.SimulateOrderRequest) (*gctrpc.SimulateOrderResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errors.New("exchange is not loaded/doesn't exist") + } + + p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + o, err := exch.FetchOrderbook(p, asset.Spot) + if err != nil { + return nil, err + } + + var buy = true + if !strings.EqualFold(r.Side, exchange.BuyOrderSide.ToString()) && + !strings.EqualFold(r.Side, exchange.BidOrderSide.ToString()) { + buy = false + } + + result := o.SimulateOrder(r.Amount, buy) + var resp gctrpc.SimulateOrderResponse + for x := range result.Orders { + resp.Orders = append(resp.Orders, &gctrpc.OrderbookItem{ + Price: result.Orders[x].Price, + Amount: result.Orders[x].Amount, + }) + } + + resp.Amount = result.Amount + resp.MaximumPrice = result.MaximumPrice + resp.MinimumPrice = result.MinimumPrice + resp.PercentageGainLoss = result.PercentageGainOrLoss + resp.Status = result.Status + return &resp, nil +} + +// WhaleBomb finds the amount required to reach a specific price target for a given exchange, pair +// and asset type +func (s *RPCServer) WhaleBomb(ctx context.Context, r *gctrpc.WhaleBombRequest) (*gctrpc.SimulateOrderResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errors.New("exchange is not loaded/doesn't exist") + } + + p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + o, err := exch.FetchOrderbook(p, asset.Spot) + if err != nil { + return nil, err + } + + var buy = true + if !strings.EqualFold(r.Side, exchange.BuyOrderSide.ToString()) && + !strings.EqualFold(r.Side, exchange.BidOrderSide.ToString()) { + buy = false + } + + result, err := o.WhaleBomb(r.PriceTarget, buy) + var resp gctrpc.SimulateOrderResponse + for x := range result.Orders { + resp.Orders = append(resp.Orders, &gctrpc.OrderbookItem{ + Price: result.Orders[x].Price, + Amount: result.Orders[x].Amount, + }) + } + + resp.Amount = result.Amount + resp.MaximumPrice = result.MaximumPrice + resp.MinimumPrice = result.MinimumPrice + resp.PercentageGainLoss = result.PercentageGainOrLoss + resp.Status = result.Status + return &resp, err +} + // CancelOrder cancels an order specified by exchange, currency pair and asset // type func (s *RPCServer) CancelOrder(ctx context.Context, r *gctrpc.CancelOrderRequest) (*gctrpc.CancelOrderResponse, error) { diff --git a/exchanges/exchange.go b/exchanges/exchange.go index dd49aa89..f050beb3 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -725,7 +725,10 @@ func (e *Base) IsAssetTypeSupported(asset asset.Item) bool { // PrintEnabledPairs prints the exchanges enabled asset pairs func (e *Base) PrintEnabledPairs() { for k, v := range e.CurrencyPairs.Pairs { - log.Infof("Asset type %v:", k) - log.Infof("\t Enabled pairs: %v", v.Enabled) + log.Infof("%s Asset type %v:\n\t Enabled pairs: %v", + e.Name, strings.ToUpper(k.String()), v.Enabled) } } + +// GetBase returns the exchange base +func (e *Base) GetBase() *Base { return e } diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go index 6727fd61..3a5a438a 100644 --- a/exchanges/interfaces.go +++ b/exchanges/interfaces.go @@ -63,4 +63,5 @@ type IBotExchange interface { GetDefaultConfig() (*config.ExchangeConfig, error) GetSubscriptions() ([]WebsocketChannelSubscription, error) AuthenticateWebsocket() error + GetBase() *Base } diff --git a/exchanges/orderbook/calculator.go b/exchanges/orderbook/calculator.go new file mode 100644 index 00000000..dbe0099a --- /dev/null +++ b/exchanges/orderbook/calculator.go @@ -0,0 +1,227 @@ +package orderbook + +import ( + "errors" + "fmt" + "sort" + + math "github.com/thrasher-/gocryptotrader/common/math" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// WhaleBombResult returns the whale bomb result +type WhaleBombResult struct { + Amount float64 + MinimumPrice float64 + MaximumPrice float64 + PercentageGainOrLoss float64 + Orders orderSummary + Status string +} + +// WhaleBomb finds the amount required to target a price +func (o *Base) WhaleBomb(priceTarget float64, buy bool) (*WhaleBombResult, error) { + if priceTarget < 0 { + return nil, errors.New("price target is invalid") + } + if buy { + a, orders := o.findAmount(priceTarget, true) + min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) + var err error + if max < priceTarget { + err = errors.New("unable to hit price target due to insufficient orderbook items") + } + status := fmt.Sprintf("Buying %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", + a, o.Pair.Quote.String(), o.Pair.Base.String(), min, max, + math.CalculatePercentageGainOrLoss(max, min), len(orders)) + return &WhaleBombResult{ + Amount: a, + Orders: orders, + MinimumPrice: min, + MaximumPrice: max, + Status: status, + }, err + } + + a, orders := o.findAmount(priceTarget, false) + min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) + var err error + if min > priceTarget { + err = errors.New("unable to hit price target due to insufficient orderbook items") + } + status := fmt.Sprintf("Selling %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", + a, o.Pair.Base.String(), o.Pair.Quote.String(), max, min, + math.CalculatePercentageGainOrLoss(min, max), len(orders)) + return &WhaleBombResult{ + Amount: a, + Orders: orders, + MinimumPrice: min, + MaximumPrice: max, + Status: status, + }, err +} + +// OrderSimulationResult returns the order simulation result +type OrderSimulationResult WhaleBombResult + +// SimulateOrder simulates an order +func (o *Base) SimulateOrder(amount float64, buy bool) *OrderSimulationResult { + if buy { + orders, amt := o.buy(amount) + min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) + pct := math.CalculatePercentageGainOrLoss(max, min) + status := fmt.Sprintf("Buying %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", + amount, o.Pair.Quote.String(), o.Pair.Base.String(), min, max, + pct, len(orders)) + return &OrderSimulationResult{ + Orders: orders, + Amount: amt, + MinimumPrice: min, + MaximumPrice: max, + PercentageGainOrLoss: pct, + Status: status, + } + } + orders, amt := o.sell(amount) + min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) + pct := math.CalculatePercentageGainOrLoss(min, max) + status := fmt.Sprintf("Selling %f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", + amount, o.Pair.Base.String(), o.Pair.Quote.String(), max, min, + pct, len(orders)) + return &OrderSimulationResult{ + Orders: orders, + Amount: amt, + MinimumPrice: min, + MaximumPrice: max, + PercentageGainOrLoss: pct, + Status: status, + } +} + +type orderSummary []Item + +func (o orderSummary) Print() { + for x := range o { + log.Debugf("Order: Price: %f Amount: %f", o[x].Price, o[x].Amount) + } +} + +func (o orderSummary) MinimumPrice(reverse bool) float64 { + if len(o) != 0 { + sortOrdersByPrice(&o, reverse) + return o[0].Price + } + return 0 +} + +func (o orderSummary) MaximumPrice(reverse bool) float64 { + if len(o) != 0 { + sortOrdersByPrice(&o, reverse) + return o[0].Price + } + return 0 +} + +// ByPrice used for sorting orders by order date +type ByPrice orderSummary + +func (b ByPrice) Len() int { return len(b) } +func (b ByPrice) Less(i, j int) bool { return b[i].Price < b[j].Price } +func (b ByPrice) Swap(i, j int) { b[i], b[j] = b[j], b[i] } + +// sortOrdersByPrice the caller function to sort orders +func sortOrdersByPrice(o *orderSummary, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByPrice(*o))) + } else { + sort.Sort(ByPrice(*o)) + } +} + +func (o *Base) findAmount(price float64, buy bool) (float64, orderSummary) { + var orders orderSummary + var amt float64 + + if buy { + asks := o.Asks + for x := range asks { + if asks[x].Price >= price { + amt += asks[x].Price * asks[x].Amount + orders = append(orders, Item{ + Price: asks[x].Price, + Amount: asks[x].Amount, + }) + return amt, orders + } + orders = append(orders, Item{ + Price: asks[x].Price, + Amount: asks[x].Amount, + }) + amt += asks[x].Price * asks[x].Amount + } + return amt, orders + } + + for x := range o.Bids { + if o.Bids[x].Price <= price { + amt += o.Bids[x].Amount + orders = append(orders, Item{ + Price: o.Bids[x].Price, + Amount: o.Bids[x].Amount, + }) + break + } + orders = append(orders, Item{ + Price: o.Bids[x].Price, + Amount: o.Bids[x].Amount, + }) + amt += o.Bids[x].Amount + } + return amt, orders +} + +func (o *Base) buy(amount float64) (orders orderSummary, baseAmount float64) { + var processedAmt float64 + for x := range o.Asks { + subtotal := o.Asks[x].Price * o.Asks[x].Amount + if processedAmt+subtotal >= amount { + diff := amount - processedAmt + subAmt := diff / o.Asks[x].Price + orders = append(orders, Item{ + Price: o.Asks[x].Price, + Amount: subAmt, + }) + baseAmount += subAmt + break + } + processedAmt += subtotal + baseAmount += o.Asks[x].Amount + orders = append(orders, Item{ + Price: o.Asks[x].Price, + Amount: o.Asks[x].Amount, + }) + } + return +} + +func (o *Base) sell(amount float64) (orders orderSummary, quoteAmount float64) { + var processedAmt float64 + for x := range o.Bids { + if processedAmt+o.Bids[x].Amount >= amount { + diff := amount - processedAmt + orders = append(orders, Item{ + Price: o.Bids[x].Price, + Amount: diff, + }) + quoteAmount += diff * o.Bids[x].Price + break + } + processedAmt += o.Bids[x].Amount + quoteAmount += o.Bids[x].Amount * o.Bids[x].Price + orders = append(orders, Item{ + Price: o.Bids[x].Price, + Amount: o.Bids[x].Amount, + }) + } + return +} diff --git a/exchanges/orderbook/calculator_test.go b/exchanges/orderbook/calculator_test.go new file mode 100644 index 00000000..484fe838 --- /dev/null +++ b/exchanges/orderbook/calculator_test.go @@ -0,0 +1,79 @@ +package orderbook + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/currency" +) + +func testSetup() Base { + return Base{ + ExchangeName: "a", + Pair: currency.NewPair(currency.BTC, currency.USD), + Asks: []Item{ + {Price: 7000, Amount: 1}, + {Price: 7001, Amount: 2}, + }, + Bids: []Item{ + {Price: 6999, Amount: 1}, + {Price: 6998, Amount: 2}, + }, + } +} + +func TestWhaleBomb(t *testing.T) { + t.Parallel() + b := testSetup() + + // invalid price amout + _, err := b.WhaleBomb(-1, true) + if err == nil { + t.Error("unexpected result") + } + + // valid + b.WhaleBomb(7001, true) + // invalid + b.WhaleBomb(7002, true) + + // valid + b.WhaleBomb(6998, false) + // invalid + b.WhaleBomb(6997, false) +} + +func TestSimulateOrder(t *testing.T) { + t.Parallel() + b := testSetup() + b.SimulateOrder(8000, true) + b.SimulateOrder(1.5, false) +} + +func TestOrderSummary(t *testing.T) { + var o orderSummary + if p := o.MaximumPrice(false); p != 0 { + t.Error("unexpected result") + } + if p := o.MinimumPrice(false); p != 0 { + t.Error("unexpected result") + } + + o = orderSummary{ + {Price: 1337, Amount: 1}, + {Price: 9001, Amount: 1}, + } + if p := o.MaximumPrice(false); p != 1337 { + t.Error("unexpected result") + } + if p := o.MaximumPrice(true); p != 9001 { + t.Error("unexpected result") + } + if p := o.MinimumPrice(false); p != 1337 { + t.Error("unexpected result") + } + if p := o.MinimumPrice(true); p != 9001 { + t.Error("unexpected result") + } + + o.Print() +} diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index cd827e3c..340db4a1 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -2,6 +2,7 @@ package orderbook import ( "errors" + "sort" "sync" "time" @@ -47,6 +48,45 @@ type Orderbook struct { ExchangeName string } +type byOBPrice []Item + +func (a byOBPrice) Len() int { return len(a) } +func (a byOBPrice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byOBPrice) Less(i, j int) bool { return a[i].Price < a[j].Price } + +// Verify ensures that the orderbook items are correctly sorted +// Bids should always go from a high price to a low price and +// asks should always go from a low price to a higher price +func (o *Base) Verify() { + var lastPrice float64 + var sortBids, sortAsks bool + for x := range o.Bids { + if lastPrice != 0 && o.Bids[x].Price >= lastPrice { + sortBids = true + break + } + lastPrice = o.Bids[x].Price + } + + lastPrice = 0 + for x := range o.Asks { + if lastPrice != 0 && o.Asks[x].Price <= lastPrice { + sortAsks = true + break + } + lastPrice = o.Asks[x].Price + } + + if sortBids { + sort.Sort(sort.Reverse(byOBPrice(o.Bids))) + } + + if sortAsks { + sort.Sort((byOBPrice(o.Asks))) + } + +} + // TotalBidsAmount returns the total amount of bids and the total orderbook // bids value func (o *Base) TotalBidsAmount() (amountCollated, total float64) { @@ -168,6 +208,8 @@ func (o *Base) Process() error { o.LastUpdated = time.Now() } + o.Verify() + orderbook, err := GetByExchange(o.ExchangeName) if err != nil { CreateNewOrderbook(o.ExchangeName, o, o.AssetType) diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index bf281d57..72c16f3e 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -12,6 +12,28 @@ import ( log "github.com/thrasher-/gocryptotrader/logger" ) +func TestVerify(t *testing.T) { + t.Parallel() + b := Base{ + ExchangeName: "TestExchange", + Pair: currency.NewPair(currency.BTC, currency.USD), + Bids: []Item{ + {Price: 100}, {Price: 101}, {Price: 99}, + }, + Asks: []Item{ + {Price: 100}, {Price: 99}, {Price: 101}, + }, + } + + b.Verify() + if r := b.Bids[1].Price; r != 100 { + t.Error("unexpected result") + } + if r := b.Asks[1].Price; r != 100 { + t.Error("unexpected result") + } +} + func TestCalculateTotalBids(t *testing.T) { t.Parallel() currency := currency.NewPairFromStrings("BTC", "USD") diff --git a/exchanges/orderbook/simulator/simulator_test.go b/exchanges/orderbook/simulator/simulator_test.go new file mode 100644 index 00000000..7751a8a7 --- /dev/null +++ b/exchanges/orderbook/simulator/simulator_test.go @@ -0,0 +1,24 @@ +package simulator + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/asset" + "github.com/thrasher-/gocryptotrader/exchanges/bitstamp" +) + +func TestSimulate(t *testing.T) { + b := bitstamp.Bitstamp{} + b.SetDefaults() + b.Verbose = false + o, err := b.FetchOrderbook(currency.NewPair(currency.BTC, currency.USD), asset.Spot) + if err != nil { + t.Error(err) + } + + r := o.SimulateOrder(10000000, true) + t.Log(r.Status) + r = o.SimulateOrder(2171, false) + t.Log(r.Status) +} diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index a889663e..33603502 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -3190,6 +3190,211 @@ func (m *SubmitOrderResponse) GetOrderId() string { return "" } +type SimulateOrderRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + Amount float64 `protobuf:"fixed64,3,opt,name=amount,proto3" json:"amount,omitempty"` + Side string `protobuf:"bytes,4,opt,name=side,proto3" json:"side,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SimulateOrderRequest) Reset() { *m = SimulateOrderRequest{} } +func (m *SimulateOrderRequest) String() string { return proto.CompactTextString(m) } +func (*SimulateOrderRequest) ProtoMessage() {} +func (*SimulateOrderRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{65} +} + +func (m *SimulateOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SimulateOrderRequest.Unmarshal(m, b) +} +func (m *SimulateOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SimulateOrderRequest.Marshal(b, m, deterministic) +} +func (m *SimulateOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SimulateOrderRequest.Merge(m, src) +} +func (m *SimulateOrderRequest) XXX_Size() int { + return xxx_messageInfo_SimulateOrderRequest.Size(m) +} +func (m *SimulateOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SimulateOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SimulateOrderRequest proto.InternalMessageInfo + +func (m *SimulateOrderRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *SimulateOrderRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *SimulateOrderRequest) GetAmount() float64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *SimulateOrderRequest) GetSide() string { + if m != nil { + return m.Side + } + return "" +} + +type SimulateOrderResponse struct { + Orders []*OrderbookItem `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + Amount float64 `protobuf:"fixed64,2,opt,name=amount,proto3" json:"amount,omitempty"` + MinimumPrice float64 `protobuf:"fixed64,3,opt,name=minimum_price,json=minimumPrice,proto3" json:"minimum_price,omitempty"` + MaximumPrice float64 `protobuf:"fixed64,4,opt,name=maximum_price,json=maximumPrice,proto3" json:"maximum_price,omitempty"` + PercentageGainLoss float64 `protobuf:"fixed64,5,opt,name=percentage_gain_loss,json=percentageGainLoss,proto3" json:"percentage_gain_loss,omitempty"` + Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SimulateOrderResponse) Reset() { *m = SimulateOrderResponse{} } +func (m *SimulateOrderResponse) String() string { return proto.CompactTextString(m) } +func (*SimulateOrderResponse) ProtoMessage() {} +func (*SimulateOrderResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{66} +} + +func (m *SimulateOrderResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SimulateOrderResponse.Unmarshal(m, b) +} +func (m *SimulateOrderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SimulateOrderResponse.Marshal(b, m, deterministic) +} +func (m *SimulateOrderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_SimulateOrderResponse.Merge(m, src) +} +func (m *SimulateOrderResponse) XXX_Size() int { + return xxx_messageInfo_SimulateOrderResponse.Size(m) +} +func (m *SimulateOrderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_SimulateOrderResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_SimulateOrderResponse proto.InternalMessageInfo + +func (m *SimulateOrderResponse) GetOrders() []*OrderbookItem { + if m != nil { + return m.Orders + } + return nil +} + +func (m *SimulateOrderResponse) GetAmount() float64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *SimulateOrderResponse) GetMinimumPrice() float64 { + if m != nil { + return m.MinimumPrice + } + return 0 +} + +func (m *SimulateOrderResponse) GetMaximumPrice() float64 { + if m != nil { + return m.MaximumPrice + } + return 0 +} + +func (m *SimulateOrderResponse) GetPercentageGainLoss() float64 { + if m != nil { + return m.PercentageGainLoss + } + return 0 +} + +func (m *SimulateOrderResponse) GetStatus() string { + if m != nil { + return m.Status + } + return "" +} + +type WhaleBombRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + PriceTarget float64 `protobuf:"fixed64,3,opt,name=price_target,json=priceTarget,proto3" json:"price_target,omitempty"` + Side string `protobuf:"bytes,4,opt,name=side,proto3" json:"side,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WhaleBombRequest) Reset() { *m = WhaleBombRequest{} } +func (m *WhaleBombRequest) String() string { return proto.CompactTextString(m) } +func (*WhaleBombRequest) ProtoMessage() {} +func (*WhaleBombRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{67} +} + +func (m *WhaleBombRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WhaleBombRequest.Unmarshal(m, b) +} +func (m *WhaleBombRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WhaleBombRequest.Marshal(b, m, deterministic) +} +func (m *WhaleBombRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WhaleBombRequest.Merge(m, src) +} +func (m *WhaleBombRequest) XXX_Size() int { + return xxx_messageInfo_WhaleBombRequest.Size(m) +} +func (m *WhaleBombRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WhaleBombRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WhaleBombRequest proto.InternalMessageInfo + +func (m *WhaleBombRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *WhaleBombRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *WhaleBombRequest) GetPriceTarget() float64 { + if m != nil { + return m.PriceTarget + } + return 0 +} + +func (m *WhaleBombRequest) GetSide() string { + if m != nil { + return m.Side + } + return "" +} + type CancelOrderRequest struct { Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` AccountId string `protobuf:"bytes,2,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` @@ -3207,7 +3412,7 @@ func (m *CancelOrderRequest) Reset() { *m = CancelOrderRequest{} } func (m *CancelOrderRequest) String() string { return proto.CompactTextString(m) } func (*CancelOrderRequest) ProtoMessage() {} func (*CancelOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{65} + return fileDescriptor_77a6da22d6a3feb1, []int{68} } func (m *CancelOrderRequest) XXX_Unmarshal(b []byte) error { @@ -3287,7 +3492,7 @@ func (m *CancelOrderResponse) Reset() { *m = CancelOrderResponse{} } func (m *CancelOrderResponse) String() string { return proto.CompactTextString(m) } func (*CancelOrderResponse) ProtoMessage() {} func (*CancelOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{66} + return fileDescriptor_77a6da22d6a3feb1, []int{69} } func (m *CancelOrderResponse) XXX_Unmarshal(b []byte) error { @@ -3319,7 +3524,7 @@ func (m *CancelAllOrdersRequest) Reset() { *m = CancelAllOrdersRequest{} func (m *CancelAllOrdersRequest) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersRequest) ProtoMessage() {} func (*CancelAllOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{67} + return fileDescriptor_77a6da22d6a3feb1, []int{70} } func (m *CancelAllOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -3358,7 +3563,7 @@ func (m *CancelAllOrdersResponse) Reset() { *m = CancelAllOrdersResponse func (m *CancelAllOrdersResponse) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse) ProtoMessage() {} func (*CancelAllOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{68} + return fileDescriptor_77a6da22d6a3feb1, []int{71} } func (m *CancelAllOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -3398,7 +3603,7 @@ func (m *CancelAllOrdersResponse_Orders) Reset() { *m = CancelAllOrdersR func (m *CancelAllOrdersResponse_Orders) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse_Orders) ProtoMessage() {} func (*CancelAllOrdersResponse_Orders) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{68, 0} + return fileDescriptor_77a6da22d6a3feb1, []int{71, 0} } func (m *CancelAllOrdersResponse_Orders) XXX_Unmarshal(b []byte) error { @@ -3443,7 +3648,7 @@ func (m *GetEventsRequest) Reset() { *m = GetEventsRequest{} } func (m *GetEventsRequest) String() string { return proto.CompactTextString(m) } func (*GetEventsRequest) ProtoMessage() {} func (*GetEventsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{69} + return fileDescriptor_77a6da22d6a3feb1, []int{72} } func (m *GetEventsRequest) XXX_Unmarshal(b []byte) error { @@ -3479,7 +3684,7 @@ func (m *ConditionParams) Reset() { *m = ConditionParams{} } func (m *ConditionParams) String() string { return proto.CompactTextString(m) } func (*ConditionParams) ProtoMessage() {} func (*ConditionParams) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{70} + return fileDescriptor_77a6da22d6a3feb1, []int{73} } func (m *ConditionParams) XXX_Unmarshal(b []byte) error { @@ -3552,7 +3757,7 @@ func (m *GetEventsResponse) Reset() { *m = GetEventsResponse{} } func (m *GetEventsResponse) String() string { return proto.CompactTextString(m) } func (*GetEventsResponse) ProtoMessage() {} func (*GetEventsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{71} + return fileDescriptor_77a6da22d6a3feb1, []int{74} } func (m *GetEventsResponse) XXX_Unmarshal(b []byte) error { @@ -3638,7 +3843,7 @@ func (m *AddEventRequest) Reset() { *m = AddEventRequest{} } func (m *AddEventRequest) String() string { return proto.CompactTextString(m) } func (*AddEventRequest) ProtoMessage() {} func (*AddEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{72} + return fileDescriptor_77a6da22d6a3feb1, []int{75} } func (m *AddEventRequest) XXX_Unmarshal(b []byte) error { @@ -3712,7 +3917,7 @@ func (m *AddEventResponse) Reset() { *m = AddEventResponse{} } func (m *AddEventResponse) String() string { return proto.CompactTextString(m) } func (*AddEventResponse) ProtoMessage() {} func (*AddEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{73} + return fileDescriptor_77a6da22d6a3feb1, []int{76} } func (m *AddEventResponse) XXX_Unmarshal(b []byte) error { @@ -3751,7 +3956,7 @@ func (m *RemoveEventRequest) Reset() { *m = RemoveEventRequest{} } func (m *RemoveEventRequest) String() string { return proto.CompactTextString(m) } func (*RemoveEventRequest) ProtoMessage() {} func (*RemoveEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{74} + return fileDescriptor_77a6da22d6a3feb1, []int{77} } func (m *RemoveEventRequest) XXX_Unmarshal(b []byte) error { @@ -3789,7 +3994,7 @@ func (m *RemoveEventResponse) Reset() { *m = RemoveEventResponse{} } func (m *RemoveEventResponse) String() string { return proto.CompactTextString(m) } func (*RemoveEventResponse) ProtoMessage() {} func (*RemoveEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{75} + return fileDescriptor_77a6da22d6a3feb1, []int{78} } func (m *RemoveEventResponse) XXX_Unmarshal(b []byte) error { @@ -3823,7 +4028,7 @@ func (m *GetCryptocurrencyDepositAddressesRequest) Reset() { func (m *GetCryptocurrencyDepositAddressesRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{76} + return fileDescriptor_77a6da22d6a3feb1, []int{79} } func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Unmarshal(b []byte) error { @@ -3864,7 +4069,7 @@ func (m *GetCryptocurrencyDepositAddressesResponse) Reset() { func (m *GetCryptocurrencyDepositAddressesResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{77} + return fileDescriptor_77a6da22d6a3feb1, []int{80} } func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Unmarshal(b []byte) error { @@ -3906,7 +4111,7 @@ func (m *GetCryptocurrencyDepositAddressRequest) Reset() { func (m *GetCryptocurrencyDepositAddressRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{78} + return fileDescriptor_77a6da22d6a3feb1, []int{81} } func (m *GetCryptocurrencyDepositAddressRequest) XXX_Unmarshal(b []byte) error { @@ -3954,7 +4159,7 @@ func (m *GetCryptocurrencyDepositAddressResponse) Reset() { func (m *GetCryptocurrencyDepositAddressResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{79} + return fileDescriptor_77a6da22d6a3feb1, []int{82} } func (m *GetCryptocurrencyDepositAddressResponse) XXX_Unmarshal(b []byte) error { @@ -4009,7 +4214,7 @@ func (m *WithdrawCurrencyRequest) Reset() { *m = WithdrawCurrencyRequest func (m *WithdrawCurrencyRequest) String() string { return proto.CompactTextString(m) } func (*WithdrawCurrencyRequest) ProtoMessage() {} func (*WithdrawCurrencyRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{80} + return fileDescriptor_77a6da22d6a3feb1, []int{83} } func (m *WithdrawCurrencyRequest) XXX_Unmarshal(b []byte) error { @@ -4160,7 +4365,7 @@ func (m *WithdrawResponse) Reset() { *m = WithdrawResponse{} } func (m *WithdrawResponse) String() string { return proto.CompactTextString(m) } func (*WithdrawResponse) ProtoMessage() {} func (*WithdrawResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{81} + return fileDescriptor_77a6da22d6a3feb1, []int{84} } func (m *WithdrawResponse) XXX_Unmarshal(b []byte) error { @@ -4263,6 +4468,9 @@ func init() { proto.RegisterType((*GetOrderRequest)(nil), "gctrpc.GetOrderRequest") proto.RegisterType((*SubmitOrderRequest)(nil), "gctrpc.SubmitOrderRequest") proto.RegisterType((*SubmitOrderResponse)(nil), "gctrpc.SubmitOrderResponse") + proto.RegisterType((*SimulateOrderRequest)(nil), "gctrpc.SimulateOrderRequest") + proto.RegisterType((*SimulateOrderResponse)(nil), "gctrpc.SimulateOrderResponse") + proto.RegisterType((*WhaleBombRequest)(nil), "gctrpc.WhaleBombRequest") proto.RegisterType((*CancelOrderRequest)(nil), "gctrpc.CancelOrderRequest") proto.RegisterType((*CancelOrderResponse)(nil), "gctrpc.CancelOrderResponse") proto.RegisterType((*CancelAllOrdersRequest)(nil), "gctrpc.CancelAllOrdersRequest") @@ -4288,261 +4496,273 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 4062 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x4d, 0x6f, 0x24, 0x49, - 0x56, 0xca, 0xb2, 0xdb, 0x1f, 0xaf, 0xca, 0x2e, 0x3b, 0xfc, 0x55, 0x2e, 0xdb, 0x6d, 0x77, 0xee, - 0x76, 0x4f, 0xf7, 0x7c, 0xd8, 0x3b, 0x3d, 0x2d, 0x76, 0xd8, 0x59, 0x16, 0x3c, 0x9e, 0x1e, 0x6f, - 0xb3, 0xbb, 0xd3, 0x26, 0xbb, 0xb7, 0x47, 0x9a, 0x45, 0x14, 0xe9, 0xcc, 0xb0, 0x9d, 0x72, 0x3a, - 0x33, 0x27, 0x33, 0xca, 0xee, 0x1a, 0x21, 0x21, 0xad, 0x04, 0x47, 0x38, 0xac, 0x90, 0x38, 0x70, - 0xe2, 0x88, 0xc4, 0x05, 0x71, 0xe2, 0x30, 0xe2, 0x8a, 0x38, 0x72, 0xe1, 0x07, 0x20, 0x6e, 0xb0, - 0x27, 0x2e, 0x9c, 0x50, 0xbc, 0xf8, 0xc8, 0x88, 0xca, 0xac, 0x72, 0x35, 0x3b, 0x9a, 0x4b, 0x77, - 0xe5, 0x8b, 0x17, 0xef, 0xbd, 0x78, 0xef, 0xc5, 0x8b, 0xf7, 0x5e, 0x84, 0x61, 0x3e, 0xcf, 0x82, - 0xfd, 0x2c, 0x4f, 0x59, 0x4a, 0x66, 0xce, 0x03, 0x96, 0x67, 0x41, 0x77, 0xfb, 0x3c, 0x4d, 0xcf, - 0x63, 0x7a, 0xe0, 0x67, 0xd1, 0x81, 0x9f, 0x24, 0x29, 0xf3, 0x59, 0x94, 0x26, 0x85, 0xc0, 0x72, - 0x97, 0x60, 0xf1, 0x98, 0xb2, 0x67, 0xc9, 0x59, 0xea, 0xd1, 0x2f, 0xfb, 0xb4, 0x60, 0xee, 0x3f, - 0x4e, 0x43, 0x5b, 0x83, 0x8a, 0x2c, 0x4d, 0x0a, 0x4a, 0xd6, 0x61, 0xa6, 0x9f, 0xb1, 0xe8, 0x8a, - 0x76, 0x9c, 0x3d, 0xe7, 0xe1, 0xbc, 0x27, 0xbf, 0xc8, 0x01, 0xac, 0xf8, 0xd7, 0x7e, 0x14, 0xfb, - 0xa7, 0x31, 0xed, 0xd1, 0xd7, 0xc1, 0x85, 0x9f, 0x9c, 0xd3, 0xa2, 0xd3, 0xd8, 0x73, 0x1e, 0x4e, - 0x79, 0x44, 0x0f, 0x3d, 0x55, 0x23, 0xe4, 0x1d, 0x58, 0xa6, 0x09, 0x07, 0x85, 0x06, 0xfa, 0x14, - 0xa2, 0x2f, 0xc9, 0x81, 0x12, 0xf9, 0x09, 0xac, 0x87, 0xf4, 0xcc, 0xef, 0xc7, 0xac, 0x77, 0x96, - 0xe6, 0xf4, 0x75, 0x2f, 0xcb, 0xd3, 0xeb, 0x28, 0xa4, 0x79, 0x67, 0x1a, 0xa5, 0x58, 0x95, 0xa3, - 0x9f, 0xf2, 0xc1, 0x13, 0x39, 0x46, 0x1e, 0xc3, 0x9a, 0x9e, 0x15, 0xf9, 0xac, 0x17, 0xf4, 0xf3, - 0x9c, 0x26, 0xc1, 0xa0, 0x73, 0x07, 0x27, 0xad, 0xa8, 0x49, 0x91, 0xcf, 0x8e, 0xe4, 0x10, 0xf9, - 0x1c, 0x96, 0x8a, 0xfe, 0x69, 0x31, 0x28, 0x18, 0xbd, 0xea, 0x15, 0xcc, 0x67, 0xfd, 0xa2, 0x33, - 0xb3, 0x37, 0xf5, 0xb0, 0xf9, 0xf8, 0xdd, 0x7d, 0xa1, 0xc6, 0xfd, 0x21, 0x95, 0xec, 0xbf, 0x50, - 0xf8, 0x2f, 0x10, 0xfd, 0x69, 0xc2, 0xf2, 0x81, 0xd7, 0x2e, 0x6c, 0x28, 0xf9, 0x0c, 0x16, 0xf2, - 0x2c, 0xe8, 0xd1, 0x24, 0xcc, 0xd2, 0x28, 0x61, 0x45, 0x67, 0x16, 0xa9, 0x3e, 0x1a, 0x45, 0xd5, - 0xcb, 0x82, 0xa7, 0x0a, 0x57, 0x90, 0x6c, 0xe5, 0x06, 0xa8, 0xfb, 0x31, 0xac, 0xd6, 0x31, 0x26, - 0x4b, 0x30, 0x75, 0x49, 0x07, 0xd2, 0x3a, 0xfc, 0x27, 0x59, 0x85, 0x3b, 0xd7, 0x7e, 0xdc, 0xa7, - 0x68, 0x8c, 0x39, 0x4f, 0x7c, 0xfc, 0xa0, 0xf1, 0xa1, 0xd3, 0x7d, 0x09, 0xcb, 0x15, 0x36, 0x35, - 0x04, 0x1e, 0x99, 0x04, 0x9a, 0x8f, 0x57, 0x94, 0xc8, 0xde, 0xc9, 0x91, 0x9a, 0x6b, 0x50, 0x75, - 0xef, 0xc1, 0xee, 0x31, 0x65, 0x47, 0xe9, 0xd5, 0x55, 0x3f, 0x89, 0x02, 0xf4, 0x31, 0x8f, 0xc6, - 0xfe, 0x80, 0xe6, 0x85, 0xf2, 0xac, 0xcf, 0x60, 0xb5, 0x6e, 0x9c, 0x74, 0x60, 0x56, 0xda, 0x1e, - 0xf9, 0xcf, 0x79, 0xea, 0x93, 0x6c, 0xc3, 0x7c, 0x90, 0x26, 0x09, 0x0d, 0x18, 0x0d, 0xe5, 0x42, - 0x4a, 0x80, 0xfb, 0xe7, 0x0d, 0xd8, 0x1b, 0xcd, 0x53, 0xba, 0xee, 0x57, 0xb0, 0x1e, 0x98, 0x08, - 0xbd, 0x5c, 0x62, 0x74, 0x1c, 0x34, 0xc5, 0x91, 0x61, 0x8a, 0xb1, 0x94, 0xf6, 0x6b, 0x47, 0x85, - 0x91, 0xd6, 0x82, 0xba, 0xb1, 0xee, 0x19, 0x74, 0x47, 0x4f, 0xaa, 0x51, 0xf9, 0x63, 0x5b, 0xe5, - 0xdb, 0x4a, 0xb4, 0x3a, 0x22, 0xa6, 0xee, 0xbf, 0x0f, 0x1b, 0xc7, 0x34, 0xa1, 0x79, 0x14, 0x68, - 0xe7, 0x90, 0x3a, 0xe7, 0x1a, 0xd4, 0x3e, 0x29, 0x59, 0x95, 0x00, 0xb7, 0x0b, 0x9d, 0xea, 0x44, - 0xb1, 0x5c, 0x77, 0x1d, 0x56, 0x8f, 0x29, 0xd3, 0x70, 0x6d, 0xc5, 0xaf, 0x1d, 0x58, 0xc3, 0x81, - 0xe2, 0xb4, 0x18, 0x88, 0x01, 0xa9, 0xea, 0x3f, 0x86, 0x65, 0x4d, 0xba, 0x50, 0xdb, 0x48, 0x68, - 0xf9, 0x03, 0x43, 0xcb, 0xd5, 0x99, 0xe5, 0x66, 0x2a, 0xcc, 0xdd, 0x54, 0xee, 0x49, 0x09, 0xee, - 0x1e, 0xc1, 0x5a, 0x2d, 0xea, 0x9b, 0xf8, 0xbf, 0xdb, 0x81, 0xf5, 0x63, 0xca, 0x0c, 0x37, 0x36, - 0x1c, 0xb4, 0x69, 0x80, 0xb9, 0x5f, 0x16, 0xcc, 0xcf, 0x59, 0xe9, 0x97, 0xf2, 0x93, 0xdc, 0x87, - 0xc5, 0x38, 0x2a, 0x18, 0x4d, 0x7a, 0x7e, 0x18, 0xe6, 0xb4, 0x10, 0x21, 0x6f, 0xde, 0x5b, 0x10, - 0xd0, 0x43, 0x01, 0x74, 0xff, 0xc9, 0xe1, 0x86, 0x19, 0x62, 0x25, 0x95, 0xf5, 0x53, 0x98, 0x2f, - 0xa3, 0x82, 0x50, 0xd2, 0xbe, 0xa1, 0xa4, 0xba, 0x39, 0xfb, 0x43, 0xa1, 0xa1, 0x24, 0xd0, 0xfd, - 0x03, 0x58, 0xfc, 0xa6, 0x37, 0xf4, 0x87, 0xd0, 0x95, 0xbe, 0xa1, 0x22, 0xf2, 0x67, 0xfe, 0x15, - 0x55, 0x7e, 0xd5, 0x85, 0x39, 0x15, 0xc0, 0x25, 0x0f, 0xfd, 0xed, 0xee, 0xc0, 0x56, 0xed, 0x4c, - 0xe9, 0x58, 0x07, 0xb0, 0x72, 0x4c, 0x99, 0x0e, 0xf3, 0x8a, 0xe2, 0xc8, 0x28, 0xe0, 0x3e, 0x41, - 0x4f, 0x34, 0x26, 0x48, 0x15, 0x6e, 0xc3, 0x7c, 0x79, 0x88, 0x48, 0xdf, 0xd6, 0x00, 0xf7, 0x31, - 0xba, 0xa9, 0x9a, 0xf5, 0xfc, 0xe5, 0x89, 0x47, 0xc5, 0xb4, 0x4d, 0x98, 0x4b, 0x59, 0xd6, 0x0b, - 0xd2, 0x50, 0x89, 0x3e, 0x9b, 0xb2, 0xec, 0x28, 0x0d, 0xa9, 0x74, 0x0d, 0x63, 0x8e, 0x76, 0x8d, - 0xbf, 0x15, 0xa6, 0xb4, 0x87, 0xa4, 0x1c, 0xbf, 0x0f, 0xf3, 0x8a, 0xa0, 0x32, 0xe5, 0x7b, 0x86, - 0x29, 0xeb, 0xe6, 0xec, 0x3f, 0x17, 0x1c, 0xa5, 0x25, 0xe7, 0xa4, 0x00, 0x45, 0xf7, 0x23, 0x58, - 0xb0, 0x86, 0x6e, 0xf3, 0xec, 0x79, 0xd3, 0x64, 0x4f, 0x60, 0xfd, 0x93, 0xa8, 0x30, 0x4f, 0xdc, - 0x49, 0xcc, 0xf5, 0xf5, 0x94, 0xb5, 0x34, 0xeb, 0xe0, 0x27, 0x30, 0x9d, 0xf8, 0xfa, 0xd8, 0xc7, - 0xdf, 0xa6, 0xa1, 0x1a, 0x76, 0xb8, 0xee, 0xc0, 0xec, 0x35, 0xcd, 0x4f, 0xd3, 0x82, 0xe2, 0x99, - 0x3e, 0xe7, 0xa9, 0x4f, 0xf2, 0x1d, 0x58, 0xe8, 0x17, 0x51, 0x72, 0xde, 0x2b, 0xfc, 0x24, 0x3c, - 0x4d, 0x5f, 0xe3, 0x09, 0x3e, 0xe7, 0xb5, 0x10, 0xf8, 0x42, 0xc0, 0xc8, 0x3d, 0x68, 0x5d, 0x30, - 0x96, 0xf5, 0x78, 0x6a, 0x91, 0xf6, 0x99, 0x3c, 0xb0, 0x9b, 0x1c, 0xf6, 0x52, 0x80, 0xf8, 0xc6, - 0x43, 0x94, 0x7e, 0x41, 0x73, 0xff, 0x9c, 0x26, 0xac, 0x33, 0x23, 0x36, 0x1e, 0x87, 0xfe, 0x5c, - 0x01, 0xc9, 0x0e, 0x00, 0xa2, 0x65, 0x79, 0xfa, 0x7a, 0xd0, 0x99, 0x15, 0xae, 0xc1, 0x21, 0x27, - 0x1c, 0x40, 0xde, 0x82, 0xf6, 0xa9, 0x5f, 0x50, 0x95, 0x1a, 0x44, 0xb4, 0xe8, 0xcc, 0x21, 0xce, - 0x22, 0x07, 0x1f, 0x69, 0x28, 0x79, 0xc4, 0xf3, 0x82, 0x2c, 0x4b, 0xf9, 0xa6, 0xef, 0xf9, 0x45, - 0x41, 0x59, 0xd1, 0x99, 0x47, 0xcc, 0xb6, 0x86, 0x1f, 0x22, 0x98, 0xaf, 0x50, 0x65, 0x36, 0x99, - 0x1f, 0xe5, 0x45, 0x07, 0x10, 0xaf, 0x25, 0x81, 0x27, 0x1c, 0xc6, 0x19, 0x97, 0xf9, 0x92, 0x40, - 0x6b, 0x0a, 0xc6, 0x1a, 0x2c, 0x10, 0xdf, 0x81, 0x65, 0xbf, 0xcf, 0x2e, 0x68, 0xc2, 0x78, 0xd4, - 0xe7, 0xcc, 0xb3, 0xa8, 0xd3, 0x42, 0x9d, 0x2d, 0x59, 0x03, 0x87, 0x59, 0xe4, 0xde, 0xc0, 0xd2, - 0x31, 0x65, 0x2f, 0xa3, 0xe0, 0x92, 0xe6, 0x13, 0x18, 0x9c, 0x3c, 0x84, 0x69, 0xce, 0x5b, 0xc6, - 0x81, 0x55, 0x7d, 0xca, 0xc8, 0x6c, 0x88, 0x4b, 0xe0, 0x21, 0x06, 0xd7, 0x23, 0xae, 0xba, 0xc7, - 0x06, 0x99, 0xb0, 0xe9, 0xbc, 0x37, 0x8f, 0x90, 0x97, 0x83, 0x8c, 0xba, 0xaf, 0xa0, 0x65, 0x4e, - 0xe2, 0x1b, 0x32, 0xa4, 0x71, 0x74, 0x15, 0x31, 0x9a, 0xab, 0x0d, 0xa9, 0x01, 0xdc, 0x97, 0xb8, - 0x7a, 0xa5, 0xdb, 0xe2, 0x6f, 0xee, 0xcb, 0x5f, 0xf6, 0x53, 0xa6, 0x68, 0x8b, 0x0f, 0xf7, 0xaf, - 0x1a, 0xb0, 0xa8, 0x96, 0x23, 0x1d, 0x51, 0xc9, 0xec, 0xdc, 0x2a, 0xf3, 0x3d, 0x68, 0xc5, 0x7e, - 0xc1, 0x7a, 0xfd, 0x2c, 0xf4, 0x55, 0xda, 0x30, 0xe5, 0x35, 0x39, 0xec, 0xe7, 0x02, 0xc4, 0x6d, - 0xa5, 0xb2, 0x42, 0xb4, 0x82, 0xe4, 0xde, 0x0a, 0xcc, 0xc5, 0x10, 0x98, 0xe6, 0x73, 0xd0, 0x53, - 0x1d, 0x0f, 0x7f, 0x73, 0xd8, 0x45, 0x74, 0x7e, 0x81, 0x9e, 0xe9, 0x78, 0xf8, 0x9b, 0x6f, 0xd0, - 0x38, 0xbd, 0x41, 0x3f, 0x74, 0x3c, 0xfe, 0x93, 0x43, 0x4e, 0xa3, 0x10, 0xdd, 0xce, 0xf1, 0xf8, - 0x4f, 0x0e, 0xf1, 0x8b, 0x4b, 0x74, 0x32, 0xc7, 0xe3, 0x3f, 0x79, 0x46, 0x7d, 0x9d, 0xc6, 0xfd, - 0x2b, 0x8a, 0xfe, 0xe4, 0x78, 0xf2, 0x8b, 0x6c, 0xc1, 0x7c, 0x96, 0x47, 0x01, 0xed, 0xf9, 0xec, - 0x02, 0x5d, 0xc8, 0xf1, 0xe6, 0x10, 0x70, 0xc8, 0x2e, 0xdc, 0x15, 0x58, 0xd6, 0x86, 0xd6, 0x91, - 0xe9, 0x73, 0x98, 0x95, 0x90, 0xb1, 0x46, 0xff, 0x1e, 0xcc, 0x32, 0x81, 0xd6, 0x69, 0x60, 0x88, - 0x5a, 0x57, 0x3a, 0xb4, 0x35, 0xed, 0x29, 0x34, 0xf7, 0x77, 0x81, 0x98, 0xdc, 0xa4, 0x21, 0x1e, - 0x95, 0x74, 0x44, 0xa8, 0x6b, 0xdb, 0x74, 0x8a, 0x92, 0xc0, 0x57, 0x18, 0xe8, 0x9f, 0xe7, 0x21, - 0x0f, 0x02, 0xe9, 0xe5, 0xb7, 0xea, 0x9a, 0x3f, 0x83, 0x05, 0xcd, 0xf8, 0x19, 0xa3, 0x57, 0x5c, - 0xe1, 0xfe, 0x55, 0xda, 0x4f, 0x18, 0xf2, 0x74, 0x3c, 0xf9, 0xc5, 0x3d, 0x10, 0xf5, 0x8b, 0x2c, - 0x1d, 0x4f, 0x7c, 0x90, 0x45, 0x68, 0x44, 0xa1, 0x2c, 0x4c, 0x1a, 0x51, 0xe8, 0xfe, 0xaf, 0x03, - 0xcb, 0xc6, 0x42, 0xde, 0xd8, 0x29, 0x2b, 0x1e, 0xd7, 0xa8, 0xf1, 0xb8, 0x47, 0x30, 0x7d, 0x1a, - 0x85, 0xbc, 0x1e, 0xe2, 0x7a, 0x5d, 0x53, 0xe4, 0xac, 0x75, 0x78, 0x88, 0xc2, 0x51, 0xfd, 0xe2, - 0xb2, 0xe8, 0x4c, 0x8f, 0x45, 0xe5, 0x28, 0x95, 0xfd, 0x70, 0xa7, 0xba, 0x1f, 0x6c, 0x5d, 0xce, - 0x0c, 0xeb, 0x52, 0x64, 0x82, 0x9a, 0xb6, 0xf6, 0xbc, 0x00, 0xa0, 0x04, 0x8e, 0x35, 0xeb, 0x6f, - 0x03, 0xa4, 0x1a, 0x53, 0xfa, 0xdf, 0x66, 0x45, 0x68, 0xed, 0x82, 0x06, 0xb2, 0xfb, 0x13, 0x3c, - 0xc6, 0x4d, 0xe6, 0x52, 0xf9, 0x8f, 0x2d, 0x9a, 0xc2, 0x17, 0x49, 0x85, 0x66, 0x61, 0x11, 0xfb, - 0x00, 0x89, 0x1d, 0x06, 0x01, 0x37, 0xbd, 0x51, 0xf4, 0x8e, 0x3d, 0x1f, 0x5f, 0xc1, 0xac, 0x9c, - 0x21, 0xdd, 0x42, 0x20, 0x34, 0xa2, 0x90, 0x7c, 0x04, 0x60, 0x9c, 0x21, 0x62, 0x5d, 0x5b, 0x4a, - 0x06, 0x39, 0x49, 0x79, 0x03, 0xb2, 0x33, 0xd0, 0xdd, 0x33, 0x58, 0xa9, 0x41, 0xe1, 0xa2, 0xe8, - 0x92, 0x55, 0x8a, 0xa2, 0xbe, 0xc9, 0x2e, 0x34, 0x59, 0xca, 0xfc, 0xb8, 0x57, 0x26, 0x00, 0x8e, - 0x07, 0x08, 0x7a, 0xc5, 0x21, 0x18, 0xa0, 0xd2, 0x58, 0x78, 0x2e, 0x0f, 0x50, 0x69, 0x1c, 0xba, - 0x3e, 0x26, 0x35, 0xd6, 0xa2, 0xa5, 0x0a, 0xc7, 0x99, 0xec, 0x1d, 0x98, 0xf3, 0xc5, 0x14, 0xb5, - 0xb0, 0xf6, 0xd0, 0xc2, 0x3c, 0x8d, 0xe0, 0x12, 0x3c, 0x81, 0x8e, 0xd2, 0xe4, 0x2c, 0x3a, 0x57, - 0xde, 0xf1, 0x16, 0x06, 0x2b, 0x05, 0x2b, 0xf3, 0x89, 0xd0, 0x67, 0x3e, 0x72, 0x6b, 0x79, 0xf8, - 0xdb, 0xfd, 0x33, 0x07, 0x96, 0x4e, 0xd2, 0x9c, 0x9d, 0xa5, 0x71, 0x94, 0xca, 0xd4, 0x99, 0xa7, - 0x12, 0x2a, 0xb5, 0x96, 0x39, 0x9a, 0xfc, 0xe4, 0x11, 0x32, 0x48, 0xa3, 0x44, 0xf8, 0x6a, 0x43, - 0x2a, 0x28, 0x8d, 0x12, 0xee, 0xaa, 0x64, 0x0f, 0x9a, 0x21, 0x2d, 0x82, 0x3c, 0xca, 0x78, 0xa9, - 0x24, 0xc3, 0x82, 0x09, 0xe2, 0x84, 0x4f, 0xfd, 0xd8, 0x4f, 0x02, 0x2a, 0x23, 0xbb, 0xfa, 0x74, - 0xd7, 0x30, 0x5c, 0x69, 0x49, 0x8c, 0xaa, 0xd5, 0x06, 0xcb, 0xa5, 0xfc, 0x16, 0xcc, 0x67, 0x0a, - 0x28, 0xdd, 0xaf, 0xa3, 0x34, 0x34, 0xbc, 0x1c, 0xaf, 0x44, 0x75, 0xb7, 0x79, 0x5e, 0x5d, 0xd2, - 0x7b, 0xd1, 0xbf, 0xba, 0xf2, 0xf3, 0x81, 0xe2, 0x96, 0xc0, 0xf4, 0x51, 0x1a, 0x25, 0x5c, 0x51, - 0x7c, 0x51, 0x2a, 0xf1, 0xe2, 0xbf, 0x4d, 0xd1, 0x1b, 0x96, 0xe8, 0xa6, 0xb6, 0xa6, 0x6c, 0x6d, - 0xdd, 0x05, 0xc8, 0x68, 0x1e, 0xd0, 0x84, 0xf9, 0xe7, 0x6a, 0xc5, 0x06, 0xc4, 0xbd, 0x00, 0xf2, - 0xfc, 0xec, 0x2c, 0x8e, 0x12, 0xca, 0xd9, 0x4a, 0x61, 0xc6, 0x68, 0x7f, 0xb4, 0x0c, 0x36, 0xa7, - 0xa9, 0x0a, 0xa7, 0x9f, 0xc1, 0xf2, 0xf3, 0xa4, 0x86, 0x91, 0x22, 0xe7, 0x8c, 0x23, 0xd7, 0xa8, - 0x90, 0xfb, 0x31, 0xb4, 0x0c, 0xc1, 0x0b, 0xf2, 0x21, 0xcc, 0x4b, 0x19, 0x75, 0x12, 0xde, 0xd5, - 0xd1, 0xa0, 0xb2, 0x42, 0xaf, 0x44, 0x76, 0xff, 0xda, 0x81, 0x66, 0x29, 0x59, 0x41, 0x9e, 0xc0, - 0x1d, 0xae, 0x6e, 0x45, 0xe5, 0xae, 0xa6, 0x52, 0xe2, 0xec, 0xe3, 0xbf, 0x22, 0x77, 0x17, 0xc8, - 0xdd, 0x17, 0x00, 0x25, 0xb0, 0x26, 0x6b, 0x3f, 0xb0, 0xab, 0xaf, 0xcd, 0x2a, 0x55, 0x25, 0x9a, - 0x91, 0xd0, 0xff, 0xeb, 0x34, 0x2f, 0xa5, 0x6a, 0x9c, 0x45, 0xfa, 0xe0, 0x7b, 0xd0, 0x14, 0x7b, - 0x81, 0x47, 0x00, 0x25, 0x70, 0xab, 0x6c, 0x1b, 0x44, 0x89, 0x07, 0xb8, 0x37, 0x70, 0x9c, 0xbc, - 0x0f, 0x0b, 0x28, 0x6c, 0x2f, 0x15, 0x0a, 0x91, 0x1b, 0xdb, 0x9e, 0xd0, 0x42, 0x14, 0xa9, 0x32, - 0x92, 0xc1, 0x9a, 0x35, 0xa5, 0x57, 0x08, 0x11, 0xe4, 0x21, 0xf5, 0x43, 0xa3, 0xce, 0x19, 0x25, - 0xa5, 0x50, 0x96, 0x24, 0x28, 0xc7, 0x84, 0xea, 0x56, 0x82, 0xea, 0x08, 0x39, 0x80, 0x96, 0xe4, - 0x88, 0x9a, 0x91, 0x47, 0x9c, 0x2d, 0x63, 0x53, 0x4c, 0x44, 0x04, 0x72, 0x05, 0xab, 0xe6, 0x04, - 0x2d, 0xe1, 0x1d, 0x9c, 0xf8, 0xd1, 0xe4, 0x12, 0x26, 0x15, 0x01, 0x49, 0x50, 0x19, 0xe8, 0xfe, - 0x21, 0x74, 0x46, 0x2d, 0xa8, 0xc6, 0xec, 0x6f, 0xdb, 0x66, 0x5f, 0xad, 0x71, 0xc9, 0xc2, 0x6c, - 0xce, 0x7d, 0x01, 0x1b, 0x23, 0x84, 0x79, 0x83, 0x8a, 0xde, 0xf0, 0x54, 0xd3, 0x9b, 0xfe, 0xd2, - 0x81, 0xee, 0x61, 0x18, 0x56, 0x82, 0x53, 0x59, 0x80, 0x7f, 0xdb, 0x21, 0x77, 0x07, 0xb6, 0x6a, - 0x05, 0x92, 0x9d, 0x82, 0xd7, 0xb0, 0xe3, 0xd1, 0xab, 0xf4, 0x9a, 0x7e, 0xdb, 0x22, 0xbb, 0x7b, - 0x70, 0x77, 0x14, 0x67, 0x29, 0x1b, 0xb6, 0xce, 0xec, 0xd6, 0xb3, 0x4e, 0x8c, 0xfe, 0xcb, 0x81, - 0x05, 0xbb, 0x29, 0xfd, 0x4d, 0xd5, 0xd1, 0xef, 0x02, 0xc9, 0x69, 0xc1, 0x7a, 0x79, 0x1a, 0xc7, - 0xbc, 0x9c, 0x0e, 0x69, 0xec, 0x0f, 0x64, 0x3b, 0x7c, 0x89, 0x8f, 0x78, 0x62, 0xe0, 0x13, 0x0e, - 0x27, 0x1b, 0x30, 0xeb, 0x67, 0x51, 0x8f, 0x7b, 0x8d, 0xa8, 0xa5, 0x67, 0xfc, 0x2c, 0xfa, 0x09, - 0x1d, 0x10, 0x17, 0x16, 0xe4, 0x40, 0x2f, 0xa6, 0xd7, 0x34, 0xc6, 0x9c, 0x6f, 0xca, 0x6b, 0x8a, - 0xe1, 0x9f, 0x72, 0x10, 0xaf, 0x7d, 0xb3, 0x3c, 0xe2, 0xee, 0x57, 0xf6, 0xdd, 0x67, 0x51, 0x9a, - 0xb6, 0x84, 0xab, 0xd5, 0xb9, 0xbf, 0x80, 0xcd, 0x1a, 0x5d, 0xc8, 0x18, 0xf5, 0x23, 0x68, 0xdb, - 0xdd, 0x7b, 0x15, 0xa7, 0x74, 0xd6, 0x6a, 0x4d, 0xf4, 0x16, 0xcf, 0x2c, 0x3a, 0x32, 0xfb, 0x44, - 0x1c, 0xcf, 0x67, 0xba, 0x5f, 0xe4, 0x7e, 0x09, 0xab, 0x25, 0xf0, 0x28, 0x4d, 0xae, 0x69, 0x5e, - 0x70, 0x6f, 0x23, 0x30, 0x7d, 0x96, 0xa7, 0xaa, 0xd9, 0x89, 0xbf, 0x79, 0xde, 0xc6, 0x52, 0xe9, - 0x06, 0x0d, 0x96, 0x72, 0x9c, 0xdc, 0x67, 0xea, 0x94, 0xc2, 0xdf, 0x3c, 0x4f, 0x8e, 0x90, 0x08, - 0xed, 0xe1, 0x98, 0x70, 0xd5, 0xa6, 0x84, 0x71, 0x2e, 0xee, 0x2b, 0x4c, 0x1f, 0x4d, 0x51, 0xe4, - 0x1a, 0x7f, 0x07, 0x9a, 0x62, 0x8d, 0x7c, 0xa6, 0x5a, 0xdf, 0xb6, 0xb5, 0xbe, 0x21, 0x31, 0x3d, - 0x38, 0xd3, 0x50, 0xf7, 0xd7, 0x0d, 0x68, 0x61, 0xc6, 0xfa, 0x09, 0x65, 0x7e, 0x14, 0x8f, 0xcf, - 0xa5, 0x45, 0x0e, 0xda, 0xd0, 0x39, 0xe8, 0x77, 0x60, 0xc1, 0x6c, 0x66, 0x0c, 0x54, 0x31, 0x6b, - 0xb4, 0x32, 0x06, 0xe4, 0x3e, 0x2c, 0x62, 0x69, 0x5d, 0x62, 0x09, 0x9f, 0x59, 0x40, 0xa8, 0x46, - 0xb3, 0x0b, 0x81, 0x3b, 0x43, 0x85, 0x00, 0x1f, 0xc6, 0x64, 0xba, 0x57, 0x44, 0xa1, 0xae, 0x13, - 0x10, 0xf2, 0x22, 0x0a, 0x8d, 0x61, 0x9c, 0x3d, 0x6b, 0x0c, 0xe3, 0x6c, 0x5e, 0x03, 0xe5, 0x54, - 0x34, 0xe1, 0xf1, 0x2e, 0x69, 0x0e, 0x9d, 0xae, 0xa5, 0x80, 0x2f, 0xa3, 0x2b, 0xbc, 0x69, 0x92, - 0x8d, 0x63, 0xd1, 0x67, 0x91, 0x5f, 0x65, 0x99, 0x06, 0x66, 0x99, 0x56, 0x16, 0x75, 0x4d, 0xab, - 0xa8, 0xdb, 0x85, 0x66, 0x9a, 0xd1, 0xa4, 0x27, 0x4b, 0xec, 0x96, 0xc8, 0x1e, 0x38, 0xe8, 0x15, - 0x42, 0x64, 0xcb, 0x04, 0x75, 0x5e, 0x4c, 0x52, 0x97, 0xda, 0x8a, 0x69, 0x0c, 0x2b, 0x46, 0x15, - 0x82, 0x53, 0xb7, 0x15, 0x82, 0xee, 0x21, 0x66, 0xc5, 0x8a, 0xb1, 0x74, 0x9f, 0x77, 0x61, 0x06, - 0xd5, 0xa4, 0x3c, 0x67, 0xd5, 0x2a, 0x63, 0xa4, 0x53, 0x78, 0x12, 0xc7, 0xfd, 0x31, 0xde, 0xcf, - 0xe1, 0xd0, 0x24, 0xa2, 0x6f, 0xc2, 0x9c, 0xb0, 0x8a, 0xf6, 0x9a, 0x59, 0xfc, 0x7e, 0x16, 0xba, - 0xff, 0xee, 0x00, 0x79, 0xd1, 0x3f, 0xbd, 0x8a, 0x26, 0xa7, 0x36, 0x79, 0x81, 0x4e, 0x60, 0x1a, - 0xdd, 0x44, 0xb8, 0x23, 0xfe, 0x1e, 0xf2, 0x90, 0xe9, 0x61, 0x0f, 0x29, 0xcd, 0x79, 0xa7, 0xbe, - 0x46, 0x9f, 0x31, 0x8d, 0xcf, 0x43, 0x7c, 0x1c, 0xd1, 0x84, 0xf5, 0x64, 0xb3, 0x85, 0x87, 0x78, - 0x04, 0x3c, 0x0b, 0xdd, 0x17, 0xb0, 0x62, 0xad, 0x4c, 0x6a, 0xfa, 0x1e, 0xb4, 0x84, 0x00, 0x59, - 0xec, 0x07, 0xba, 0xd3, 0xdc, 0x44, 0xd8, 0x09, 0x82, 0xc6, 0xe9, 0xeb, 0xbf, 0x1d, 0x20, 0x47, - 0xfc, 0xe0, 0x8a, 0x27, 0xd6, 0x17, 0x77, 0x1c, 0x51, 0x25, 0x95, 0xf4, 0xe6, 0x25, 0xe4, 0x99, - 0xcd, 0x6c, 0xca, 0x62, 0xa6, 0x35, 0x3d, 0xfd, 0x86, 0xad, 0x90, 0xca, 0xae, 0xbd, 0x0f, 0x8b, - 0x37, 0x7e, 0x1c, 0x53, 0xa6, 0x2f, 0x2b, 0x64, 0xcf, 0x54, 0x40, 0x55, 0xc5, 0xa5, 0xec, 0x35, - 0x5b, 0xda, 0x8b, 0x97, 0x44, 0xd6, 0x7a, 0xe5, 0xd9, 0xf7, 0x04, 0xd6, 0x05, 0xf8, 0x30, 0x8e, - 0x27, 0xde, 0x43, 0xee, 0xdf, 0x34, 0x60, 0xa3, 0x32, 0x4d, 0x1f, 0x12, 0xf6, 0x0e, 0x78, 0xa0, - 0x97, 0x5b, 0x3f, 0x61, 0x5f, 0x7e, 0xca, 0x59, 0xdd, 0x7f, 0x76, 0x60, 0x46, 0x80, 0xc6, 0x5a, - 0xe3, 0x0b, 0x65, 0x7e, 0x19, 0x63, 0x44, 0xfe, 0xfb, 0xfd, 0xc9, 0x98, 0x89, 0xff, 0xcc, 0x0b, - 0x2a, 0xe1, 0x37, 0xf2, 0x6e, 0xea, 0x47, 0xb0, 0x34, 0x8c, 0xf0, 0x46, 0xcd, 0x7b, 0x51, 0x43, - 0x3f, 0xbd, 0xa6, 0xc6, 0x85, 0xd4, 0xd7, 0x0e, 0xb4, 0x8f, 0xd2, 0x24, 0x8c, 0x78, 0x7c, 0x3c, - 0xf1, 0x73, 0xff, 0xaa, 0x90, 0x77, 0xa2, 0x02, 0xa4, 0x9a, 0xac, 0x1a, 0x30, 0xa2, 0x9d, 0xb5, - 0x03, 0x10, 0x5c, 0xd0, 0xe0, 0xb2, 0x27, 0xfb, 0x4b, 0xe2, 0x22, 0x95, 0x43, 0x3e, 0x8e, 0xc2, - 0x82, 0xbc, 0x07, 0x2b, 0xe5, 0x70, 0xcf, 0x4f, 0xc2, 0x9e, 0x6c, 0x2e, 0x61, 0xbf, 0x59, 0xe3, - 0x1d, 0x26, 0xe1, 0x61, 0x71, 0x89, 0x5d, 0x71, 0xdd, 0x53, 0xe9, 0x59, 0x1b, 0xb6, 0xad, 0xe1, - 0x87, 0x08, 0x76, 0xff, 0xc7, 0xc1, 0x78, 0xa7, 0x56, 0x25, 0xad, 0x5d, 0xb6, 0x51, 0xb0, 0xbb, - 0x66, 0x99, 0xac, 0x31, 0x64, 0x32, 0x02, 0xd3, 0x11, 0xa3, 0x57, 0x2a, 0x8c, 0xf0, 0xdf, 0xe4, - 0x63, 0x58, 0xd2, 0x2b, 0xee, 0x65, 0xa8, 0x16, 0xb9, 0x4d, 0x36, 0xca, 0x32, 0xc1, 0xd2, 0x9a, - 0xd7, 0x0e, 0x86, 0xd4, 0xa8, 0xb6, 0xd7, 0x9d, 0x5b, 0xb7, 0x17, 0x8f, 0x4a, 0x01, 0x6a, 0x7b, - 0x46, 0x26, 0x51, 0xf8, 0x25, 0xa4, 0xa6, 0x41, 0x9f, 0xd1, 0x50, 0x26, 0x46, 0xfa, 0xdb, 0xfd, - 0x4f, 0x07, 0xda, 0x87, 0x61, 0x88, 0xeb, 0x9e, 0x24, 0x4c, 0xa8, 0x55, 0x36, 0x6e, 0x59, 0xe5, - 0xd4, 0xff, 0x73, 0x95, 0xbf, 0x71, 0x10, 0x19, 0xa1, 0x04, 0xd7, 0x85, 0xa5, 0x72, 0x9d, 0xf5, - 0xe6, 0x75, 0xbf, 0x0b, 0x44, 0x24, 0xd3, 0x96, 0x3a, 0x86, 0xb1, 0xd6, 0x60, 0xc5, 0xc2, 0x92, - 0xb1, 0xe6, 0x53, 0x78, 0x78, 0x4c, 0xd9, 0x51, 0x3e, 0xc8, 0x58, 0xaa, 0x92, 0x97, 0x4f, 0x68, - 0x96, 0x16, 0x91, 0x8a, 0x5c, 0x74, 0xa2, 0xe8, 0xf3, 0x2f, 0x0e, 0x3c, 0x9a, 0x80, 0x90, 0x5c, - 0xc2, 0x1f, 0x55, 0xbb, 0x09, 0xbf, 0x67, 0x3e, 0x14, 0x98, 0x88, 0xca, 0xbe, 0x86, 0xc8, 0xfb, - 0x5a, 0x4d, 0xb2, 0xfb, 0x43, 0x58, 0xb4, 0x07, 0xdf, 0x28, 0x54, 0xc4, 0xf0, 0xe0, 0x16, 0x21, - 0x26, 0xf1, 0xb9, 0x07, 0xb0, 0x18, 0x58, 0x24, 0x24, 0xa3, 0x21, 0xa8, 0x7b, 0x04, 0x6f, 0xdd, - 0xca, 0x4d, 0xaa, 0x6d, 0x64, 0x3d, 0xe6, 0xfe, 0xfd, 0x34, 0x6c, 0x7c, 0x1e, 0xb1, 0x8b, 0x30, - 0xf7, 0x6f, 0x94, 0xf7, 0x4d, 0x22, 0xe4, 0x50, 0xa9, 0xd6, 0xa8, 0x56, 0x97, 0x6f, 0xc3, 0x72, - 0x9a, 0x50, 0xcc, 0x28, 0x7b, 0x99, 0x5f, 0x14, 0x37, 0x69, 0xae, 0xce, 0xd2, 0x76, 0x9a, 0x50, - 0x9e, 0x55, 0x9e, 0x48, 0xf0, 0xd0, 0x69, 0x3c, 0x3d, 0x7c, 0x1a, 0x2f, 0xc1, 0x54, 0x16, 0x25, - 0xb2, 0x43, 0xce, 0x7f, 0xf2, 0xb3, 0x93, 0xe5, 0x7e, 0x68, 0x50, 0x96, 0x67, 0x27, 0x42, 0x35, - 0x5d, 0xb3, 0x67, 0x3b, 0x3b, 0xd4, 0xb3, 0x35, 0x74, 0x32, 0x67, 0xd7, 0xa8, 0xbb, 0xd0, 0x94, - 0x3f, 0x7b, 0xcc, 0x3f, 0x97, 0x09, 0x2f, 0x48, 0xd0, 0x4b, 0xff, 0xdc, 0xc8, 0x87, 0xc0, 0xca, - 0x87, 0x76, 0x00, 0xce, 0x28, 0xed, 0x59, 0xa9, 0xef, 0xfc, 0x19, 0xa5, 0x22, 0xe8, 0xf2, 0xc4, - 0xe8, 0xd4, 0x4f, 0x2e, 0x7b, 0x58, 0x71, 0xb6, 0x84, 0x38, 0x1c, 0xf0, 0x19, 0xaf, 0x3a, 0xef, - 0x41, 0x0b, 0x07, 0x95, 0x4c, 0x0b, 0x42, 0xa3, 0x1c, 0x76, 0x58, 0xd6, 0xce, 0x88, 0x12, 0x44, - 0x6c, 0xd0, 0x59, 0x2c, 0xe7, 0x1f, 0x45, 0x6c, 0xa0, 0xe7, 0xa3, 0xce, 0xf2, 0x41, 0xa7, 0x5d, - 0xce, 0x3f, 0x12, 0x20, 0x2e, 0x5e, 0x71, 0x13, 0x9d, 0x51, 0x71, 0xc5, 0xbe, 0x24, 0x1f, 0x9d, - 0x70, 0xc8, 0x51, 0x1a, 0x62, 0x1d, 0x70, 0x13, 0xe5, 0x46, 0x29, 0xb2, 0x2c, 0x0a, 0x16, 0x0e, - 0x54, 0xae, 0xe1, 0xbe, 0x0d, 0x4b, 0xca, 0x5d, 0xcc, 0x57, 0x68, 0x39, 0x2d, 0xfa, 0x31, 0x53, - 0xaf, 0xd0, 0xc4, 0xd7, 0xe3, 0x5f, 0xef, 0xc2, 0xe2, 0x71, 0x2a, 0x1c, 0xf4, 0x25, 0xb7, 0x4b, - 0x4e, 0x9e, 0xc3, 0xac, 0x7c, 0x5a, 0x45, 0xd6, 0x2b, 0x6f, 0xad, 0xd0, 0xeb, 0xba, 0x1b, 0x23, - 0xde, 0x60, 0xb9, 0x2b, 0xbf, 0xfc, 0xb7, 0xff, 0xf8, 0x55, 0x63, 0x81, 0x34, 0x0f, 0xae, 0xdf, - 0x3f, 0x38, 0xa7, 0x2c, 0xe2, 0x54, 0x2e, 0x60, 0xc1, 0x7a, 0x0d, 0x43, 0xb6, 0xad, 0x17, 0x2d, - 0x43, 0x8f, 0x64, 0xba, 0x3b, 0x63, 0xdf, 0xbb, 0xb8, 0x5d, 0x64, 0xb1, 0x4a, 0x88, 0x64, 0x51, - 0x20, 0x8a, 0x20, 0xfc, 0x25, 0xb4, 0x9f, 0x62, 0x1f, 0x40, 0x53, 0x25, 0xbb, 0x25, 0xb5, 0xda, - 0x57, 0x3e, 0xdd, 0xbd, 0xd1, 0x08, 0x92, 0xe3, 0x16, 0x72, 0x5c, 0x23, 0x2b, 0x9c, 0xa3, 0xe8, - 0x33, 0xe8, 0xd7, 0x35, 0xa4, 0x80, 0x25, 0xf9, 0x6e, 0xe0, 0x1b, 0xe5, 0xb9, 0x8d, 0x3c, 0xd7, - 0xc9, 0x2a, 0xe7, 0x19, 0x0a, 0x06, 0x25, 0xd3, 0x14, 0xcb, 0x18, 0xf3, 0x9d, 0x0b, 0xb9, 0x3b, - 0xf2, 0x01, 0x8c, 0x60, 0xb9, 0x7b, 0xcb, 0x03, 0x19, 0x7b, 0x95, 0xe7, 0x94, 0xe3, 0xea, 0x37, - 0x32, 0xe4, 0x57, 0x0e, 0xb6, 0x6c, 0x6a, 0x5f, 0x64, 0x91, 0xb7, 0x6e, 0x7f, 0x06, 0x26, 0x64, - 0x78, 0x38, 0xe9, 0x7b, 0x31, 0xf7, 0xbb, 0x28, 0xcc, 0x5d, 0xb2, 0x2d, 0x85, 0xb1, 0xde, 0x88, - 0xa9, 0x57, 0x68, 0x24, 0x80, 0x96, 0xf9, 0xb8, 0x85, 0x6c, 0xd5, 0xbc, 0x1c, 0xd1, 0xcc, 0xb7, - 0xeb, 0x07, 0x25, 0xc3, 0x0e, 0x32, 0x24, 0x64, 0x49, 0x32, 0xd4, 0x6f, 0x61, 0xc8, 0x57, 0xd0, - 0x1e, 0x7a, 0x18, 0x42, 0xdc, 0x21, 0xf3, 0xd5, 0x3c, 0xf2, 0xe9, 0x7e, 0x67, 0x2c, 0x8e, 0xe4, - 0x7a, 0x17, 0xb9, 0x76, 0xdc, 0x15, 0xc3, 0xca, 0x8a, 0xf3, 0x0f, 0x9c, 0xb7, 0x49, 0x81, 0x76, - 0x36, 0x5f, 0x97, 0x4c, 0xc4, 0x7b, 0xb7, 0x66, 0xa9, 0xd6, 0x36, 0x1d, 0xb6, 0xb5, 0xe2, 0x89, - 0xdb, 0xb5, 0xc0, 0xbb, 0x6b, 0xe3, 0xe5, 0x0d, 0x46, 0x9e, 0x49, 0xf8, 0xee, 0xd4, 0xbf, 0xdc, - 0x91, 0x8f, 0x87, 0x2a, 0x3b, 0x57, 0x71, 0x4d, 0x59, 0x46, 0x0a, 0xeb, 0x61, 0x93, 0x64, 0x6a, - 0x7b, 0x75, 0xcd, 0xd3, 0xa2, 0xda, 0x95, 0x9a, 0x6f, 0x85, 0x46, 0xae, 0x34, 0x65, 0x59, 0x41, - 0x5e, 0xc3, 0xa2, 0x08, 0x17, 0xdf, 0xbc, 0x65, 0x77, 0x90, 0xef, 0x86, 0x4b, 0xca, 0x98, 0x61, - 0x1a, 0xf6, 0x73, 0x98, 0xd7, 0xef, 0x03, 0x48, 0xc7, 0x58, 0x84, 0xf5, 0x12, 0xa5, 0x3b, 0xe2, - 0x9d, 0x81, 0xf2, 0x56, 0x77, 0x41, 0xae, 0x4a, 0xbc, 0x1a, 0xe0, 0x84, 0x7f, 0x01, 0x50, 0x3e, - 0x3c, 0x20, 0x9b, 0x15, 0xca, 0x5a, 0x73, 0xdd, 0xba, 0x21, 0xf5, 0x7c, 0x11, 0xc9, 0x2f, 0x91, - 0x45, 0x8b, 0xbc, 0xda, 0x6f, 0xfa, 0x7e, 0xd8, 0xda, 0x6f, 0xc3, 0x4f, 0x15, 0xba, 0xa3, 0xef, - 0xa8, 0x95, 0x51, 0x5c, 0xb5, 0xd9, 0x74, 0xe5, 0xc3, 0x57, 0x70, 0x8e, 0xa7, 0x85, 0x71, 0x39, - 0xbe, 0x5d, 0xc7, 0xa5, 0xf6, 0xb4, 0xa8, 0xde, 0x74, 0xbb, 0x9b, 0xc8, 0x6a, 0x85, 0x2c, 0x0f, - 0xb3, 0x2a, 0xc8, 0x25, 0x3e, 0xdf, 0x36, 0xee, 0x76, 0x89, 0x49, 0xab, 0x7a, 0xd1, 0xdd, 0xbd, - 0x3b, 0x6a, 0x78, 0xc4, 0xc9, 0x24, 0x93, 0x23, 0xdc, 0x54, 0xc2, 0xe0, 0xe2, 0x46, 0xd7, 0x32, - 0xb8, 0x75, 0xf1, 0xdb, 0xdd, 0xac, 0x19, 0x91, 0xd4, 0xd7, 0x90, 0x7a, 0x9b, 0x2c, 0xe8, 0x90, - 0x88, 0xb4, 0x84, 0x4d, 0x74, 0xab, 0xdd, 0xb2, 0xc9, 0xf0, 0x7d, 0xac, 0x15, 0x03, 0x2b, 0xb7, - 0xb2, 0x95, 0x18, 0xa8, 0xef, 0x5d, 0xc9, 0x9f, 0xda, 0xd7, 0xbb, 0xea, 0xba, 0xc9, 0x1d, 0x7b, - 0x3f, 0x54, 0xd9, 0x2d, 0x23, 0xef, 0x90, 0xdc, 0x5d, 0xe4, 0xbc, 0x49, 0x36, 0x86, 0x39, 0xcb, - 0xfb, 0x28, 0xf2, 0x4b, 0x07, 0x56, 0x6a, 0x6e, 0x3b, 0x4a, 0x09, 0x46, 0xdf, 0xcd, 0x94, 0x12, - 0x8c, 0xbb, 0x2e, 0x71, 0x51, 0x82, 0x6d, 0x17, 0x25, 0xf0, 0xc3, 0x50, 0x4b, 0x20, 0x73, 0x3d, - 0xee, 0x99, 0x7f, 0xe1, 0xc0, 0x7a, 0xfd, 0xcd, 0x06, 0xb9, 0xaf, 0x1f, 0x84, 0x8e, 0xbb, 0x73, - 0xe9, 0x3e, 0xb8, 0x0d, 0x4d, 0x4a, 0x73, 0x1f, 0xa5, 0xd9, 0x75, 0xbb, 0x5c, 0x9a, 0x1c, 0x71, - 0xeb, 0x04, 0xba, 0xc1, 0x06, 0x81, 0x7d, 0x77, 0x40, 0x8c, 0xdc, 0xa2, 0xfe, 0x8a, 0xa5, 0x7b, - 0x6f, 0x0c, 0x86, 0x1d, 0xbe, 0xc8, 0x9a, 0x34, 0x08, 0x36, 0xdc, 0xf5, 0x25, 0x84, 0xdc, 0xa3, - 0x65, 0x6f, 0xde, 0xda, 0xa3, 0x95, 0xeb, 0x06, 0x6b, 0x8f, 0x56, 0x6f, 0x00, 0x2a, 0x7b, 0x14, - 0x99, 0xe1, 0x6d, 0x00, 0xf9, 0x02, 0xb7, 0x8d, 0xec, 0x4e, 0x75, 0x86, 0xb7, 0x7a, 0x51, 0xb7, - 0x6d, 0xec, 0xfe, 0x53, 0x25, 0x54, 0x8a, 0xa6, 0x17, 0xd7, 0x9e, 0x07, 0x73, 0x0a, 0x9d, 0x6c, - 0x0c, 0x13, 0x50, 0x94, 0x6b, 0xdb, 0xc9, 0xee, 0x06, 0x12, 0x5d, 0x76, 0x5b, 0x26, 0x51, 0x4e, - 0xf3, 0x14, 0x9a, 0x46, 0xeb, 0x94, 0xe8, 0x20, 0x5b, 0xed, 0x14, 0x77, 0xb7, 0x6a, 0xc7, 0xec, - 0x50, 0xe2, 0xb6, 0x39, 0x83, 0x02, 0x11, 0x4c, 0x1e, 0x46, 0x63, 0xb1, 0xe4, 0x51, 0xed, 0xae, - 0x96, 0x3c, 0xea, 0x3a, 0x91, 0x16, 0x8f, 0x00, 0x11, 0x34, 0x8f, 0x1c, 0xda, 0x43, 0x0d, 0xbd, - 0xf2, 0x28, 0xae, 0x6f, 0x5f, 0x96, 0x47, 0xf1, 0x88, 0x4e, 0xa0, 0x9d, 0xec, 0x08, 0x7e, 0x7e, - 0x1c, 0x97, 0xf6, 0x10, 0x21, 0x52, 0xb4, 0xbb, 0x2c, 0x5b, 0x5b, 0x7d, 0x3d, 0xcb, 0xd6, 0x76, - 0x6f, 0xac, 0x12, 0x22, 0xa9, 0xa0, 0xf5, 0x0a, 0xe6, 0x54, 0x9f, 0xa5, 0x34, 0xf4, 0x50, 0x87, - 0xa9, 0xdb, 0xa9, 0x0e, 0x48, 0xaa, 0x96, 0xb1, 0xfd, 0x30, 0x44, 0xaa, 0xd2, 0x10, 0x46, 0xd7, - 0xa5, 0x34, 0x44, 0xb5, 0x61, 0x53, 0x1a, 0xa2, 0xae, 0x4d, 0x63, 0x19, 0x42, 0xec, 0x76, 0xcd, - 0xe3, 0x1f, 0x1c, 0xb8, 0x77, 0x6b, 0xd3, 0x84, 0x7c, 0xef, 0x0d, 0xfa, 0x2b, 0x42, 0xa0, 0xf7, - 0xdf, 0xb8, 0x23, 0xe3, 0x3e, 0x44, 0x31, 0x5d, 0x77, 0x47, 0x1d, 0x40, 0x38, 0x2d, 0x14, 0xe8, - 0xba, 0x3d, 0xc3, 0x85, 0xfe, 0x3b, 0x47, 0xfc, 0x41, 0xcb, 0x18, 0xba, 0x64, 0x7f, 0x42, 0x01, - 0x94, 0xc0, 0x07, 0x13, 0xe3, 0x4b, 0x71, 0x1f, 0xa0, 0xb8, 0x7b, 0xee, 0xd6, 0x18, 0x71, 0xb9, - 0xb0, 0x7f, 0x02, 0x5b, 0xba, 0xb9, 0x62, 0xd1, 0xfd, 0xb4, 0x9f, 0x84, 0x45, 0x59, 0xcb, 0x8d, - 0xe8, 0xc0, 0x94, 0x8e, 0x33, 0x5c, 0x73, 0xdb, 0x67, 0xca, 0x8d, 0x1c, 0x15, 0x62, 0x9c, 0x71, - 0xda, 0x9c, 0x7b, 0x06, 0xcb, 0x6a, 0xde, 0xa7, 0x91, 0xcf, 0x7e, 0x63, 0x9e, 0x7b, 0xc8, 0xb3, - 0xeb, 0xae, 0x99, 0x3c, 0xcf, 0x22, 0x9f, 0x29, 0x8e, 0xa7, 0x33, 0xf8, 0xc7, 0x6b, 0x1f, 0xfc, - 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4b, 0x08, 0xf6, 0x2c, 0xef, 0x36, 0x00, 0x00, + // 4250 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x4d, 0x6f, 0x25, 0x49, + 0x52, 0xaa, 0x67, 0xb7, 0x3f, 0xe2, 0x3d, 0xfb, 0xd9, 0xe9, 0xaf, 0xd7, 0xaf, 0xed, 0xfe, 0xa8, + 0xd9, 0xe9, 0xe9, 0x9e, 0x0f, 0xf7, 0x4c, 0x4f, 0x8b, 0x1d, 0x76, 0x96, 0x05, 0x8f, 0xa7, 0xc7, + 0xdb, 0xec, 0xec, 0xb4, 0xa9, 0xee, 0xed, 0x96, 0x66, 0xd1, 0x16, 0xe5, 0xaa, 0xb4, 0x5d, 0x74, + 0xbd, 0xaa, 0x9a, 0xaa, 0x7c, 0x76, 0x7b, 0x84, 0x84, 0xb4, 0x12, 0x88, 0x13, 0x1c, 0x56, 0x48, + 0x1c, 0x38, 0x71, 0x44, 0xe2, 0x82, 0x38, 0x81, 0x34, 0xe2, 0x8a, 0x38, 0x72, 0xe1, 0x07, 0x20, + 0x6e, 0x80, 0x84, 0xc4, 0x85, 0x13, 0xca, 0xc8, 0x8f, 0xca, 0x7c, 0x55, 0xef, 0xf9, 0x35, 0xdb, + 0x3b, 0x97, 0xee, 0x57, 0x91, 0x91, 0x11, 0x91, 0x11, 0x91, 0x91, 0x91, 0x91, 0x61, 0x58, 0x2c, + 0xf2, 0x70, 0x37, 0x2f, 0x32, 0x96, 0x91, 0xb9, 0x93, 0x90, 0x15, 0x79, 0xd8, 0xdf, 0x3e, 0xc9, + 0xb2, 0x93, 0x84, 0xde, 0x0b, 0xf2, 0xf8, 0x5e, 0x90, 0xa6, 0x19, 0x0b, 0x58, 0x9c, 0xa5, 0xa5, + 0xc0, 0x72, 0x57, 0x60, 0xf9, 0x80, 0xb2, 0x47, 0xe9, 0x71, 0xe6, 0xd1, 0xaf, 0x86, 0xb4, 0x64, + 0xee, 0xdf, 0xcd, 0x42, 0x57, 0x83, 0xca, 0x3c, 0x4b, 0x4b, 0x4a, 0x36, 0x61, 0x6e, 0x98, 0xb3, + 0x78, 0x40, 0x7b, 0xce, 0x4d, 0xe7, 0xce, 0xa2, 0x27, 0xbf, 0xc8, 0x3d, 0x58, 0x0b, 0xce, 0x82, + 0x38, 0x09, 0x8e, 0x12, 0xea, 0xd3, 0x97, 0xe1, 0x69, 0x90, 0x9e, 0xd0, 0xb2, 0xd7, 0xba, 0xe9, + 0xdc, 0x99, 0xf1, 0x88, 0x1e, 0x7a, 0xa8, 0x46, 0xc8, 0x3b, 0xb0, 0x4a, 0x53, 0x0e, 0x8a, 0x0c, + 0xf4, 0x19, 0x44, 0x5f, 0x91, 0x03, 0x15, 0xf2, 0x03, 0xd8, 0x8c, 0xe8, 0x71, 0x30, 0x4c, 0x98, + 0x7f, 0x9c, 0x15, 0xf4, 0xa5, 0x9f, 0x17, 0xd9, 0x59, 0x1c, 0xd1, 0xa2, 0x37, 0x8b, 0x52, 0xac, + 0xcb, 0xd1, 0xcf, 0xf8, 0xe0, 0xa1, 0x1c, 0x23, 0xf7, 0x61, 0x43, 0xcf, 0x8a, 0x03, 0xe6, 0x87, + 0xc3, 0xa2, 0xa0, 0x69, 0x78, 0xd1, 0xbb, 0x82, 0x93, 0xd6, 0xd4, 0xa4, 0x38, 0x60, 0xfb, 0x72, + 0x88, 0x3c, 0x87, 0x95, 0x72, 0x78, 0x54, 0x5e, 0x94, 0x8c, 0x0e, 0xfc, 0x92, 0x05, 0x6c, 0x58, + 0xf6, 0xe6, 0x6e, 0xce, 0xdc, 0x69, 0xdf, 0x7f, 0x77, 0x57, 0xa8, 0x71, 0x77, 0x44, 0x25, 0xbb, + 0x4f, 0x14, 0xfe, 0x13, 0x44, 0x7f, 0x98, 0xb2, 0xe2, 0xc2, 0xeb, 0x96, 0x36, 0x94, 0x7c, 0x01, + 0x4b, 0x45, 0x1e, 0xfa, 0x34, 0x8d, 0xf2, 0x2c, 0x4e, 0x59, 0xd9, 0x9b, 0x47, 0xaa, 0x77, 0xc7, + 0x51, 0xf5, 0xf2, 0xf0, 0xa1, 0xc2, 0x15, 0x24, 0x3b, 0x85, 0x01, 0xea, 0x7f, 0x02, 0xeb, 0x4d, + 0x8c, 0xc9, 0x0a, 0xcc, 0xbc, 0xa0, 0x17, 0xd2, 0x3a, 0xfc, 0x27, 0x59, 0x87, 0x2b, 0x67, 0x41, + 0x32, 0xa4, 0x68, 0x8c, 0x05, 0x4f, 0x7c, 0x7c, 0xaf, 0xf5, 0x91, 0xd3, 0x7f, 0x0a, 0xab, 0x35, + 0x36, 0x0d, 0x04, 0xee, 0x9a, 0x04, 0xda, 0xf7, 0xd7, 0x94, 0xc8, 0xde, 0xe1, 0xbe, 0x9a, 0x6b, + 0x50, 0x75, 0x6f, 0xc1, 0x8d, 0x03, 0xca, 0xf6, 0xb3, 0xc1, 0x60, 0x98, 0xc6, 0x21, 0xfa, 0x98, + 0x47, 0x93, 0xe0, 0x82, 0x16, 0xa5, 0xf2, 0xac, 0x2f, 0x60, 0xbd, 0x69, 0x9c, 0xf4, 0x60, 0x5e, + 0xda, 0x1e, 0xf9, 0x2f, 0x78, 0xea, 0x93, 0x6c, 0xc3, 0x62, 0x98, 0xa5, 0x29, 0x0d, 0x19, 0x8d, + 0xe4, 0x42, 0x2a, 0x80, 0xfb, 0xc7, 0x2d, 0xb8, 0x39, 0x9e, 0xa7, 0x74, 0xdd, 0xaf, 0x61, 0x33, + 0x34, 0x11, 0xfc, 0x42, 0x62, 0xf4, 0x1c, 0x34, 0xc5, 0xbe, 0x61, 0x8a, 0x89, 0x94, 0x76, 0x1b, + 0x47, 0x85, 0x91, 0x36, 0xc2, 0xa6, 0xb1, 0xfe, 0x31, 0xf4, 0xc7, 0x4f, 0x6a, 0x50, 0xf9, 0x7d, + 0x5b, 0xe5, 0xdb, 0x4a, 0xb4, 0x26, 0x22, 0xa6, 0xee, 0xbf, 0x0b, 0x5b, 0x07, 0x34, 0xa5, 0x45, + 0x1c, 0x6a, 0xe7, 0x90, 0x3a, 0xe7, 0x1a, 0xd4, 0x3e, 0x29, 0x59, 0x55, 0x00, 0xb7, 0x0f, 0xbd, + 0xfa, 0x44, 0xb1, 0x5c, 0x77, 0x13, 0xd6, 0x0f, 0x28, 0xd3, 0x70, 0x6d, 0xc5, 0x6f, 0x1c, 0xd8, + 0xc0, 0x81, 0xf2, 0xa8, 0xbc, 0x10, 0x03, 0x52, 0xd5, 0xbf, 0x07, 0xab, 0x9a, 0x74, 0xa9, 0xb6, + 0x91, 0xd0, 0xf2, 0x87, 0x86, 0x96, 0xeb, 0x33, 0xab, 0xcd, 0x54, 0x9a, 0xbb, 0xa9, 0xda, 0x93, + 0x12, 0xdc, 0xdf, 0x87, 0x8d, 0x46, 0xd4, 0x57, 0xf1, 0x7f, 0xb7, 0x07, 0x9b, 0x07, 0x94, 0x19, + 0x6e, 0x6c, 0x38, 0x68, 0xdb, 0x00, 0x73, 0xbf, 0x2c, 0x59, 0x50, 0xb0, 0xca, 0x2f, 0xe5, 0x27, + 0x79, 0x13, 0x96, 0x93, 0xb8, 0x64, 0x34, 0xf5, 0x83, 0x28, 0x2a, 0x68, 0x29, 0x42, 0xde, 0xa2, + 0xb7, 0x24, 0xa0, 0x7b, 0x02, 0xe8, 0xfe, 0xbd, 0xc3, 0x0d, 0x33, 0xc2, 0x4a, 0x2a, 0xeb, 0x73, + 0x58, 0xac, 0xa2, 0x82, 0x50, 0xd2, 0xae, 0xa1, 0xa4, 0xa6, 0x39, 0xbb, 0x23, 0xa1, 0xa1, 0x22, + 0xd0, 0xff, 0x1d, 0x58, 0x7e, 0xdd, 0x1b, 0xfa, 0x23, 0xe8, 0x4b, 0xdf, 0x50, 0x11, 0xf9, 0x8b, + 0x60, 0x40, 0x95, 0x5f, 0xf5, 0x61, 0x41, 0x05, 0x70, 0xc9, 0x43, 0x7f, 0xbb, 0x3b, 0x70, 0xad, + 0x71, 0xa6, 0x74, 0xac, 0x7b, 0xb0, 0x76, 0x40, 0x99, 0x0e, 0xf3, 0x8a, 0xe2, 0xd8, 0x28, 0xe0, + 0x3e, 0x40, 0x4f, 0x34, 0x26, 0x48, 0x15, 0x6e, 0xc3, 0x62, 0x75, 0x88, 0x48, 0xdf, 0xd6, 0x00, + 0xf7, 0x3e, 0xba, 0xa9, 0x9a, 0xf5, 0xf8, 0xe9, 0xa1, 0x47, 0xc5, 0xb4, 0xab, 0xb0, 0x90, 0xb1, + 0xdc, 0x0f, 0xb3, 0x48, 0x89, 0x3e, 0x9f, 0xb1, 0x7c, 0x3f, 0x8b, 0xa8, 0x74, 0x0d, 0x63, 0x8e, + 0x76, 0x8d, 0xbf, 0x12, 0xa6, 0xb4, 0x87, 0xa4, 0x1c, 0xbf, 0x0d, 0x8b, 0x8a, 0xa0, 0x32, 0xe5, + 0x7b, 0x86, 0x29, 0x9b, 0xe6, 0xec, 0x3e, 0x16, 0x1c, 0xa5, 0x25, 0x17, 0xa4, 0x00, 0x65, 0xff, + 0x63, 0x58, 0xb2, 0x86, 0x2e, 0xf3, 0xec, 0x45, 0xd3, 0x64, 0x0f, 0x60, 0xf3, 0xd3, 0xb8, 0x34, + 0x4f, 0xdc, 0x69, 0xcc, 0xf5, 0xcd, 0x8c, 0xb5, 0x34, 0xeb, 0xe0, 0x27, 0x30, 0x9b, 0x06, 0xfa, + 0xd8, 0xc7, 0xdf, 0xa6, 0xa1, 0x5a, 0x76, 0xb8, 0xee, 0xc1, 0xfc, 0x19, 0x2d, 0x8e, 0xb2, 0x92, + 0xe2, 0x99, 0xbe, 0xe0, 0xa9, 0x4f, 0xf2, 0x06, 0x2c, 0x0d, 0xcb, 0x38, 0x3d, 0xf1, 0xcb, 0x20, + 0x8d, 0x8e, 0xb2, 0x97, 0x78, 0x82, 0x2f, 0x78, 0x1d, 0x04, 0x3e, 0x11, 0x30, 0x72, 0x0b, 0x3a, + 0xa7, 0x8c, 0xe5, 0x3e, 0x4f, 0x2d, 0xb2, 0x21, 0x93, 0x07, 0x76, 0x9b, 0xc3, 0x9e, 0x0a, 0x10, + 0xdf, 0x78, 0x88, 0x32, 0x2c, 0x69, 0x11, 0x9c, 0xd0, 0x94, 0xf5, 0xe6, 0xc4, 0xc6, 0xe3, 0xd0, + 0x9f, 0x28, 0x20, 0xd9, 0x01, 0x40, 0xb4, 0xbc, 0xc8, 0x5e, 0x5e, 0xf4, 0xe6, 0x85, 0x6b, 0x70, + 0xc8, 0x21, 0x07, 0x90, 0xb7, 0xa0, 0x7b, 0x14, 0x94, 0x54, 0xa5, 0x06, 0x31, 0x2d, 0x7b, 0x0b, + 0x88, 0xb3, 0xcc, 0xc1, 0xfb, 0x1a, 0x4a, 0xee, 0xf2, 0xbc, 0x20, 0xcf, 0x33, 0xbe, 0xe9, 0xfd, + 0xa0, 0x2c, 0x29, 0x2b, 0x7b, 0x8b, 0x88, 0xd9, 0xd5, 0xf0, 0x3d, 0x04, 0xf3, 0x15, 0xaa, 0xcc, + 0x26, 0x0f, 0xe2, 0xa2, 0xec, 0x01, 0xe2, 0x75, 0x24, 0xf0, 0x90, 0xc3, 0x38, 0xe3, 0x2a, 0x5f, + 0x12, 0x68, 0x6d, 0xc1, 0x58, 0x83, 0x05, 0xe2, 0x3b, 0xb0, 0x1a, 0x0c, 0xd9, 0x29, 0x4d, 0x19, + 0x8f, 0xfa, 0x9c, 0x79, 0x1e, 0xf7, 0x3a, 0xa8, 0xb3, 0x15, 0x6b, 0x60, 0x2f, 0x8f, 0xdd, 0x73, + 0x58, 0x39, 0xa0, 0xec, 0x69, 0x1c, 0xbe, 0xa0, 0xc5, 0x14, 0x06, 0x27, 0x77, 0x60, 0x96, 0xf3, + 0x96, 0x71, 0x60, 0x5d, 0x9f, 0x32, 0x32, 0x1b, 0xe2, 0x12, 0x78, 0x88, 0xc1, 0xf5, 0x88, 0xab, + 0xf6, 0xd9, 0x45, 0x2e, 0x6c, 0xba, 0xe8, 0x2d, 0x22, 0xe4, 0xe9, 0x45, 0x4e, 0xdd, 0x67, 0xd0, + 0x31, 0x27, 0xf1, 0x0d, 0x19, 0xd1, 0x24, 0x1e, 0xc4, 0x8c, 0x16, 0x6a, 0x43, 0x6a, 0x00, 0xf7, + 0x25, 0xae, 0x5e, 0xe9, 0xb6, 0xf8, 0x9b, 0xfb, 0xf2, 0x57, 0xc3, 0x8c, 0x29, 0xda, 0xe2, 0xc3, + 0xfd, 0xf3, 0x16, 0x2c, 0xab, 0xe5, 0x48, 0x47, 0x54, 0x32, 0x3b, 0x97, 0xca, 0x7c, 0x0b, 0x3a, + 0x49, 0x50, 0x32, 0x7f, 0x98, 0x47, 0x81, 0x4a, 0x1b, 0x66, 0xbc, 0x36, 0x87, 0xfd, 0x44, 0x80, + 0xb8, 0xad, 0x54, 0x56, 0x88, 0x56, 0x90, 0xdc, 0x3b, 0xa1, 0xb9, 0x18, 0x02, 0xb3, 0x7c, 0x0e, + 0x7a, 0xaa, 0xe3, 0xe1, 0x6f, 0x0e, 0x3b, 0x8d, 0x4f, 0x4e, 0xd1, 0x33, 0x1d, 0x0f, 0x7f, 0xf3, + 0x0d, 0x9a, 0x64, 0xe7, 0xe8, 0x87, 0x8e, 0xc7, 0x7f, 0x72, 0xc8, 0x51, 0x1c, 0xa1, 0xdb, 0x39, + 0x1e, 0xff, 0xc9, 0x21, 0x41, 0xf9, 0x02, 0x9d, 0xcc, 0xf1, 0xf8, 0x4f, 0x9e, 0x51, 0x9f, 0x65, + 0xc9, 0x70, 0x40, 0xd1, 0x9f, 0x1c, 0x4f, 0x7e, 0x91, 0x6b, 0xb0, 0x98, 0x17, 0x71, 0x48, 0xfd, + 0x80, 0x9d, 0xa2, 0x0b, 0x39, 0xde, 0x02, 0x02, 0xf6, 0xd8, 0xa9, 0xbb, 0x06, 0xab, 0xda, 0xd0, + 0x3a, 0x32, 0x3d, 0x87, 0x79, 0x09, 0x99, 0x68, 0xf4, 0xf7, 0x61, 0x9e, 0x09, 0xb4, 0x5e, 0x0b, + 0x43, 0xd4, 0xa6, 0xd2, 0xa1, 0xad, 0x69, 0x4f, 0xa1, 0xb9, 0xbf, 0x09, 0xc4, 0xe4, 0x26, 0x0d, + 0x71, 0xb7, 0xa2, 0x23, 0x42, 0x5d, 0xd7, 0xa6, 0x53, 0x56, 0x04, 0xbe, 0xc6, 0x40, 0xff, 0xb8, + 0x88, 0x78, 0x10, 0xc8, 0x5e, 0x7c, 0xab, 0xae, 0xf9, 0x63, 0x58, 0xd2, 0x8c, 0x1f, 0x31, 0x3a, + 0xe0, 0x0a, 0x0f, 0x06, 0xd9, 0x30, 0x65, 0xc8, 0xd3, 0xf1, 0xe4, 0x17, 0xf7, 0x40, 0xd4, 0x2f, + 0xb2, 0x74, 0x3c, 0xf1, 0x41, 0x96, 0xa1, 0x15, 0x47, 0xf2, 0x62, 0xd2, 0x8a, 0x23, 0xf7, 0x7f, + 0x1d, 0x58, 0x35, 0x16, 0xf2, 0xca, 0x4e, 0x59, 0xf3, 0xb8, 0x56, 0x83, 0xc7, 0xdd, 0x85, 0xd9, + 0xa3, 0x38, 0xe2, 0xf7, 0x21, 0xae, 0xd7, 0x0d, 0x45, 0xce, 0x5a, 0x87, 0x87, 0x28, 0x1c, 0x35, + 0x28, 0x5f, 0x94, 0xbd, 0xd9, 0x89, 0xa8, 0x1c, 0xa5, 0xb6, 0x1f, 0xae, 0xd4, 0xf7, 0x83, 0xad, + 0xcb, 0xb9, 0x51, 0x5d, 0x8a, 0x4c, 0x50, 0xd3, 0xd6, 0x9e, 0x17, 0x02, 0x54, 0xc0, 0x89, 0x66, + 0xfd, 0x75, 0x80, 0x4c, 0x63, 0x4a, 0xff, 0xbb, 0x5a, 0x13, 0x5a, 0xbb, 0xa0, 0x81, 0xec, 0xfe, + 0x08, 0x8f, 0x71, 0x93, 0xb9, 0x54, 0xfe, 0x7d, 0x8b, 0xa6, 0xf0, 0x45, 0x52, 0xa3, 0x59, 0x5a, + 0xc4, 0x3e, 0x44, 0x62, 0x7b, 0x61, 0xc8, 0x4d, 0x6f, 0x5c, 0x7a, 0x27, 0x9e, 0x8f, 0xcf, 0x60, + 0x5e, 0xce, 0x90, 0x6e, 0x21, 0x10, 0x5a, 0x71, 0x44, 0x3e, 0x06, 0x30, 0xce, 0x10, 0xb1, 0xae, + 0x6b, 0x4a, 0x06, 0x39, 0x49, 0x79, 0x03, 0xb2, 0x33, 0xd0, 0xdd, 0x63, 0x58, 0x6b, 0x40, 0xe1, + 0xa2, 0xe8, 0x2b, 0xab, 0x14, 0x45, 0x7d, 0x93, 0x1b, 0xd0, 0x66, 0x19, 0x0b, 0x12, 0xbf, 0x4a, + 0x00, 0x1c, 0x0f, 0x10, 0xf4, 0x8c, 0x43, 0x30, 0x40, 0x65, 0x89, 0xf0, 0x5c, 0x1e, 0xa0, 0xb2, + 0x24, 0x72, 0x03, 0x4c, 0x6a, 0xac, 0x45, 0x4b, 0x15, 0x4e, 0x32, 0xd9, 0x3b, 0xb0, 0x10, 0x88, + 0x29, 0x6a, 0x61, 0xdd, 0x91, 0x85, 0x79, 0x1a, 0xc1, 0x25, 0x78, 0x02, 0xed, 0x67, 0xe9, 0x71, + 0x7c, 0xa2, 0xbc, 0xe3, 0x2d, 0x0c, 0x56, 0x0a, 0x56, 0xe5, 0x13, 0x51, 0xc0, 0x02, 0xe4, 0xd6, + 0xf1, 0xf0, 0xb7, 0xfb, 0x47, 0x0e, 0xac, 0x1c, 0x66, 0x05, 0x3b, 0xce, 0x92, 0x38, 0x93, 0xa9, + 0x33, 0x4f, 0x25, 0x54, 0x6a, 0x2d, 0x73, 0x34, 0xf9, 0xc9, 0x23, 0x64, 0x98, 0xc5, 0xa9, 0xf0, + 0xd5, 0x96, 0x54, 0x50, 0x16, 0xa7, 0xdc, 0x55, 0xc9, 0x4d, 0x68, 0x47, 0xb4, 0x0c, 0x8b, 0x38, + 0xe7, 0x57, 0x25, 0x19, 0x16, 0x4c, 0x10, 0x27, 0x7c, 0x14, 0x24, 0x41, 0x1a, 0x52, 0x19, 0xd9, + 0xd5, 0xa7, 0xbb, 0x81, 0xe1, 0x4a, 0x4b, 0x62, 0xdc, 0x5a, 0x6d, 0xb0, 0x5c, 0xca, 0xaf, 0xc1, + 0x62, 0xae, 0x80, 0xd2, 0xfd, 0x7a, 0x4a, 0x43, 0xa3, 0xcb, 0xf1, 0x2a, 0x54, 0x77, 0x9b, 0xe7, + 0xd5, 0x15, 0xbd, 0x27, 0xc3, 0xc1, 0x20, 0x28, 0x2e, 0x14, 0xb7, 0x14, 0x66, 0xf7, 0xb3, 0x38, + 0xe5, 0x8a, 0xe2, 0x8b, 0x52, 0x89, 0x17, 0xff, 0x6d, 0x8a, 0xde, 0xb2, 0x44, 0x37, 0xb5, 0x35, + 0x63, 0x6b, 0xeb, 0x3a, 0x40, 0x4e, 0x8b, 0x90, 0xa6, 0x2c, 0x38, 0x51, 0x2b, 0x36, 0x20, 0xee, + 0x29, 0x90, 0xc7, 0xc7, 0xc7, 0x49, 0x9c, 0x52, 0xce, 0x56, 0x0a, 0x33, 0x41, 0xfb, 0xe3, 0x65, + 0xb0, 0x39, 0xcd, 0xd4, 0x38, 0xfd, 0x18, 0x56, 0x1f, 0xa7, 0x0d, 0x8c, 0x14, 0x39, 0x67, 0x12, + 0xb9, 0x56, 0x8d, 0xdc, 0x0f, 0xa1, 0x63, 0x08, 0x5e, 0x92, 0x8f, 0x60, 0x51, 0xca, 0xa8, 0x93, + 0xf0, 0xbe, 0x8e, 0x06, 0xb5, 0x15, 0x7a, 0x15, 0xb2, 0xfb, 0x17, 0x0e, 0xb4, 0x2b, 0xc9, 0x4a, + 0xf2, 0x00, 0xae, 0x70, 0x75, 0x2b, 0x2a, 0xd7, 0x35, 0x95, 0x0a, 0x67, 0x17, 0xff, 0x15, 0xb9, + 0xbb, 0x40, 0xee, 0x3f, 0x01, 0xa8, 0x80, 0x0d, 0x59, 0xfb, 0x3d, 0xfb, 0xf6, 0x75, 0xb5, 0x4e, + 0x55, 0x89, 0x66, 0x24, 0xf4, 0xff, 0x3c, 0xcb, 0xaf, 0x52, 0x0d, 0xce, 0x22, 0x7d, 0xf0, 0x3d, + 0x68, 0x8b, 0xbd, 0xc0, 0x23, 0x80, 0x12, 0xb8, 0x53, 0x95, 0x0d, 0xe2, 0xd4, 0x03, 0xdc, 0x1b, + 0x38, 0x4e, 0x3e, 0x80, 0x25, 0x14, 0xd6, 0xcf, 0x84, 0x42, 0xe4, 0xc6, 0xb6, 0x27, 0x74, 0x10, + 0x45, 0xaa, 0x8c, 0xe4, 0xb0, 0x61, 0x4d, 0xf1, 0x4b, 0x21, 0x82, 0x3c, 0xa4, 0xbe, 0x6f, 0xdc, + 0x73, 0xc6, 0x49, 0x29, 0x94, 0x25, 0x09, 0xca, 0x31, 0xa1, 0xba, 0xb5, 0xb0, 0x3e, 0x42, 0xee, + 0x41, 0x47, 0x72, 0x44, 0xcd, 0xc8, 0x23, 0xce, 0x96, 0xb1, 0x2d, 0x26, 0x22, 0x02, 0x19, 0xc0, + 0xba, 0x39, 0x41, 0x4b, 0x78, 0x05, 0x27, 0x7e, 0x3c, 0xbd, 0x84, 0x69, 0x4d, 0x40, 0x12, 0xd6, + 0x06, 0xfa, 0xbf, 0x0b, 0xbd, 0x71, 0x0b, 0x6a, 0x30, 0xfb, 0xdb, 0xb6, 0xd9, 0xd7, 0x1b, 0x5c, + 0xb2, 0x34, 0x8b, 0x73, 0x5f, 0xc2, 0xd6, 0x18, 0x61, 0x5e, 0xe1, 0x46, 0x6f, 0x78, 0xaa, 0xe9, + 0x4d, 0x7f, 0xe6, 0x40, 0x7f, 0x2f, 0x8a, 0x6a, 0xc1, 0xa9, 0xba, 0x80, 0x7f, 0xdb, 0x21, 0x77, + 0x07, 0xae, 0x35, 0x0a, 0x24, 0x2b, 0x05, 0x2f, 0x61, 0xc7, 0xa3, 0x83, 0xec, 0x8c, 0x7e, 0xdb, + 0x22, 0xbb, 0x37, 0xe1, 0xfa, 0x38, 0xce, 0x52, 0x36, 0x2c, 0x9d, 0xd9, 0xa5, 0x67, 0x9d, 0x18, + 0xfd, 0x87, 0x03, 0x4b, 0x76, 0x51, 0xfa, 0x75, 0xdd, 0xa3, 0xdf, 0x05, 0x52, 0xd0, 0x92, 0xf9, + 0x45, 0x96, 0x24, 0xfc, 0x3a, 0x1d, 0xd1, 0x24, 0xb8, 0x90, 0xe5, 0xf0, 0x15, 0x3e, 0xe2, 0x89, + 0x81, 0x4f, 0x39, 0x9c, 0x6c, 0xc1, 0x7c, 0x90, 0xc7, 0x3e, 0xf7, 0x1a, 0x71, 0x97, 0x9e, 0x0b, + 0xf2, 0xf8, 0x47, 0xf4, 0x82, 0xb8, 0xb0, 0x24, 0x07, 0xfc, 0x84, 0x9e, 0xd1, 0x04, 0x73, 0xbe, + 0x19, 0xaf, 0x2d, 0x86, 0x3f, 0xe7, 0x20, 0x7e, 0xf7, 0xcd, 0x8b, 0x98, 0xbb, 0x5f, 0x55, 0x77, + 0x9f, 0x47, 0x69, 0xba, 0x12, 0xae, 0x56, 0xe7, 0xfe, 0x14, 0xae, 0x36, 0xe8, 0x42, 0xc6, 0xa8, + 0x1f, 0x40, 0xd7, 0xae, 0xde, 0xab, 0x38, 0xa5, 0xb3, 0x56, 0x6b, 0xa2, 0xb7, 0x7c, 0x6c, 0xd1, + 0x91, 0xd9, 0x27, 0xe2, 0x78, 0x01, 0xd3, 0xf5, 0x22, 0xf7, 0x2b, 0x58, 0xaf, 0x80, 0xfb, 0x59, + 0x7a, 0x46, 0x8b, 0x92, 0x7b, 0x1b, 0x81, 0xd9, 0xe3, 0x22, 0x53, 0xc5, 0x4e, 0xfc, 0xcd, 0xf3, + 0x36, 0x96, 0x49, 0x37, 0x68, 0xb1, 0x8c, 0xe3, 0x14, 0x01, 0x53, 0xa7, 0x14, 0xfe, 0xe6, 0x79, + 0x72, 0x8c, 0x44, 0xa8, 0x8f, 0x63, 0xc2, 0x55, 0xdb, 0x12, 0xc6, 0xb9, 0xb8, 0xcf, 0x30, 0x7d, + 0x34, 0x45, 0x91, 0x6b, 0xfc, 0x0d, 0x68, 0x8b, 0x35, 0xf2, 0x99, 0x6a, 0x7d, 0xdb, 0xd6, 0xfa, + 0x46, 0xc4, 0xf4, 0xe0, 0x58, 0x43, 0xdd, 0xff, 0x6a, 0x41, 0x07, 0x33, 0xd6, 0x4f, 0x29, 0x0b, + 0xe2, 0x64, 0x72, 0x2e, 0x2d, 0x72, 0xd0, 0x96, 0xce, 0x41, 0xdf, 0x80, 0x25, 0xb3, 0x98, 0x71, + 0xa1, 0x2e, 0xb3, 0x46, 0x29, 0xe3, 0x82, 0xbc, 0x09, 0xcb, 0x78, 0xb5, 0xae, 0xb0, 0x84, 0xcf, + 0x2c, 0x21, 0x54, 0xa3, 0xd9, 0x17, 0x81, 0x2b, 0x23, 0x17, 0x01, 0x3e, 0x8c, 0xc9, 0xb4, 0x5f, + 0xc6, 0x91, 0xbe, 0x27, 0x20, 0xe4, 0x49, 0x1c, 0x19, 0xc3, 0x38, 0x7b, 0xde, 0x18, 0xc6, 0xd9, + 0xfc, 0x0e, 0x54, 0x50, 0x51, 0x84, 0xc7, 0xb7, 0xa4, 0x05, 0x74, 0xba, 0x8e, 0x02, 0x3e, 0x8d, + 0x07, 0xf8, 0xd2, 0x24, 0x0b, 0xc7, 0xa2, 0xce, 0x22, 0xbf, 0xaa, 0x6b, 0x1a, 0x98, 0xd7, 0xb4, + 0xea, 0x52, 0xd7, 0xb6, 0x2e, 0x75, 0x37, 0xa0, 0x9d, 0xe5, 0x34, 0xf5, 0xe5, 0x15, 0xbb, 0x23, + 0xb2, 0x07, 0x0e, 0x7a, 0x86, 0x10, 0x59, 0x32, 0x41, 0x9d, 0x97, 0xd3, 0xdc, 0x4b, 0x6d, 0xc5, + 0xb4, 0x46, 0x15, 0xa3, 0x2e, 0x82, 0x33, 0x97, 0x5d, 0x04, 0xdd, 0x3d, 0xcc, 0x8a, 0x15, 0x63, + 0xe9, 0x3e, 0xef, 0xc2, 0x1c, 0xaa, 0x49, 0x79, 0xce, 0xba, 0x75, 0x8d, 0x91, 0x4e, 0xe1, 0x49, + 0x1c, 0xf7, 0x87, 0xf8, 0x3e, 0x87, 0x43, 0xd3, 0x88, 0x7e, 0x15, 0x16, 0x84, 0x55, 0xb4, 0xd7, + 0xcc, 0xe3, 0xf7, 0xa3, 0xc8, 0xfd, 0x57, 0x07, 0xc8, 0x93, 0xe1, 0xd1, 0x20, 0x9e, 0x9e, 0xda, + 0xf4, 0x17, 0x74, 0x02, 0xb3, 0xe8, 0x26, 0xc2, 0x1d, 0xf1, 0xf7, 0x88, 0x87, 0xcc, 0x8e, 0x7a, + 0x48, 0x65, 0xce, 0x2b, 0xcd, 0x77, 0xf4, 0x39, 0xd3, 0xf8, 0x3c, 0xc4, 0x27, 0x31, 0x4d, 0x99, + 0x2f, 0x8b, 0x2d, 0x3c, 0xc4, 0x23, 0xe0, 0x51, 0xe4, 0x3e, 0x81, 0x35, 0x6b, 0x65, 0x52, 0xd3, + 0xb7, 0xa0, 0x23, 0x04, 0xc8, 0x93, 0x20, 0xd4, 0x95, 0xe6, 0x36, 0xc2, 0x0e, 0x11, 0x34, 0x49, + 0x5f, 0x7f, 0xe2, 0xc0, 0xfa, 0x93, 0x78, 0x30, 0x4c, 0x02, 0x46, 0x7f, 0x05, 0x1a, 0xab, 0x96, + 0x3f, 0x63, 0x2d, 0x5f, 0x69, 0x72, 0xb6, 0xd2, 0xa4, 0xfb, 0xdf, 0x0e, 0x6c, 0x8c, 0x88, 0xa2, + 0x73, 0x42, 0xdb, 0x99, 0xc6, 0x14, 0x07, 0x24, 0x92, 0xc1, 0xb4, 0x65, 0x31, 0x7d, 0x03, 0x96, + 0x06, 0x71, 0x1a, 0x0f, 0x86, 0x03, 0x5f, 0xe8, 0x5e, 0xc8, 0xd4, 0x91, 0xc0, 0x43, 0x34, 0x01, + 0x47, 0x0a, 0x5e, 0x1a, 0x48, 0xb3, 0x12, 0x49, 0x00, 0x05, 0xd2, 0xfb, 0xb0, 0x5e, 0xe5, 0xed, + 0xfe, 0x49, 0x10, 0xa7, 0x7e, 0x92, 0x95, 0xa5, 0xb4, 0x31, 0xa9, 0xc6, 0x0e, 0x82, 0x38, 0xfd, + 0x3c, 0x2b, 0x4b, 0x23, 0x08, 0xcc, 0x99, 0x41, 0x80, 0x27, 0x30, 0x2b, 0xcf, 0x4f, 0x83, 0x84, + 0x7e, 0x92, 0x0d, 0x8e, 0x5e, 0xaf, 0xee, 0x6f, 0x41, 0x47, 0xd4, 0xdd, 0x58, 0x50, 0x9c, 0x50, + 0x65, 0x81, 0x36, 0xc2, 0x9e, 0x22, 0xa8, 0xd1, 0x0c, 0xff, 0xe9, 0x00, 0xd9, 0xe7, 0xa9, 0x4c, + 0x32, 0xb5, 0x3f, 0xf0, 0x50, 0x22, 0xee, 0xcd, 0x95, 0x87, 0x2d, 0x4a, 0xc8, 0x23, 0xdb, 0xfd, + 0x66, 0x2c, 0xf7, 0xd3, 0xab, 0x99, 0x7d, 0xc5, 0xe2, 0x58, 0x2d, 0x8e, 0xbf, 0x09, 0xcb, 0xe7, + 0x41, 0x92, 0x50, 0xa6, 0x9f, 0xaf, 0x64, 0x15, 0x5d, 0x40, 0xd5, 0x1d, 0x5c, 0x2d, 0x78, 0xde, + 0x58, 0xf0, 0x06, 0xac, 0x59, 0xeb, 0x95, 0xd9, 0xd0, 0x03, 0xd8, 0x14, 0xe0, 0xbd, 0x24, 0x99, + 0x3a, 0xaa, 0xba, 0x7f, 0xd9, 0x82, 0xad, 0xda, 0x34, 0x9d, 0x36, 0xd8, 0x6e, 0x7c, 0x5b, 0x2f, + 0xb7, 0x79, 0xc2, 0xae, 0xfc, 0x94, 0xb3, 0xfa, 0xff, 0xe8, 0xc0, 0x9c, 0x00, 0x4d, 0xb4, 0xc6, + 0x97, 0x2a, 0x20, 0x48, 0x87, 0x13, 0x37, 0xa2, 0xef, 0x4e, 0xc7, 0x4c, 0xfc, 0x67, 0x3e, 0x59, + 0x8a, 0x48, 0x22, 0x5f, 0x2b, 0x7f, 0x00, 0x2b, 0xa3, 0x08, 0xaf, 0xf4, 0x9c, 0x23, 0xaa, 0x2a, + 0x0f, 0xcf, 0xa8, 0xf1, 0x44, 0xf9, 0x8d, 0x03, 0xdd, 0xfd, 0x2c, 0x8d, 0x62, 0x7e, 0x62, 0x1e, + 0x06, 0x45, 0x30, 0x28, 0xe5, 0x2b, 0xb9, 0x00, 0xa9, 0xb2, 0xbb, 0x06, 0x8c, 0x29, 0x70, 0xee, + 0x00, 0x84, 0xa7, 0x34, 0x7c, 0xe1, 0xcb, 0x8a, 0xa3, 0x78, 0x5a, 0xe7, 0x90, 0x4f, 0xe2, 0xa8, + 0x24, 0xef, 0xc1, 0x5a, 0x35, 0xec, 0x07, 0x69, 0xe4, 0xcb, 0x72, 0x23, 0xbe, 0x40, 0x68, 0xbc, + 0xbd, 0x34, 0xda, 0x2b, 0x5f, 0xe0, 0x3b, 0x89, 0xae, 0xb2, 0xf9, 0x56, 0x08, 0xef, 0x6a, 0xf8, + 0x1e, 0x82, 0xdd, 0xff, 0x71, 0xf0, 0x04, 0x54, 0xab, 0x92, 0xd6, 0xae, 0x0a, 0x6b, 0x58, 0x6f, + 0xb5, 0x4c, 0xd6, 0x1a, 0x31, 0x19, 0x81, 0xd9, 0x98, 0xd1, 0x81, 0x3a, 0x58, 0xf8, 0x6f, 0xf2, + 0x09, 0xac, 0xe8, 0x15, 0xfb, 0x39, 0xaa, 0x45, 0x6e, 0x93, 0xad, 0xea, 0xe2, 0x68, 0x69, 0xcd, + 0xeb, 0x86, 0x23, 0x6a, 0x54, 0xdb, 0xeb, 0xca, 0x54, 0x81, 0x3a, 0x44, 0x6d, 0xcb, 0xf8, 0x24, + 0xbe, 0x84, 0xd4, 0x34, 0x1c, 0x32, 0x1a, 0xc9, 0x54, 0x59, 0x7f, 0xbb, 0xff, 0xee, 0x40, 0x77, + 0x2f, 0x8a, 0x70, 0xdd, 0xd3, 0x84, 0x09, 0xb5, 0xca, 0xd6, 0x25, 0xab, 0x9c, 0xf9, 0x7f, 0xae, + 0xf2, 0x97, 0x0e, 0x22, 0x63, 0x94, 0xe0, 0xba, 0xb0, 0x52, 0xad, 0xb3, 0xd9, 0xbc, 0xee, 0x77, + 0x80, 0x88, 0xeb, 0x95, 0xa5, 0x8e, 0x51, 0xac, 0x0d, 0x58, 0xb3, 0xb0, 0x64, 0xac, 0xf9, 0x0c, + 0xee, 0x1c, 0x50, 0xb6, 0x5f, 0x5c, 0xe4, 0x2c, 0x53, 0xe9, 0xec, 0xa7, 0x34, 0xcf, 0xca, 0x58, + 0x45, 0x2e, 0x3a, 0x55, 0xf4, 0xf9, 0x27, 0x07, 0xee, 0x4e, 0x41, 0x48, 0x2e, 0xe1, 0x67, 0xf5, + 0xfa, 0xd2, 0x6f, 0x99, 0xad, 0x23, 0x53, 0x51, 0xd9, 0xd5, 0x10, 0xf9, 0x82, 0xaf, 0x49, 0xf6, + 0xbf, 0x0f, 0xcb, 0xf6, 0xe0, 0x2b, 0x85, 0x8a, 0x04, 0x6e, 0x5f, 0x22, 0xc4, 0x34, 0x3e, 0x77, + 0x1b, 0x96, 0x43, 0x8b, 0x84, 0x64, 0x34, 0x02, 0x75, 0xf7, 0xe1, 0xad, 0x4b, 0xb9, 0x49, 0xb5, + 0x8d, 0xbd, 0xa1, 0xbb, 0x7f, 0x33, 0x0b, 0x5b, 0xcf, 0x63, 0x76, 0x1a, 0x15, 0xc1, 0xb9, 0xf2, + 0xbe, 0x69, 0x84, 0x1c, 0xb9, 0xbc, 0xb7, 0xea, 0xf5, 0x86, 0xb7, 0x61, 0x35, 0x4b, 0x29, 0xde, + 0x31, 0xfc, 0x3c, 0x28, 0xcb, 0xf3, 0xac, 0x50, 0x67, 0x69, 0x37, 0x4b, 0x29, 0xbf, 0x67, 0x1c, + 0x4a, 0xf0, 0xc8, 0x69, 0x3c, 0x3b, 0x7a, 0x1a, 0xaf, 0xc0, 0x4c, 0x1e, 0xa7, 0xf2, 0xcd, 0x84, + 0xff, 0xe4, 0x67, 0x27, 0x2b, 0x82, 0xc8, 0xa0, 0x2c, 0xcf, 0x4e, 0x84, 0x6a, 0xba, 0x66, 0x15, + 0x7f, 0x7e, 0xa4, 0x8a, 0x6f, 0xe8, 0x64, 0xc1, 0xae, 0x5a, 0xdc, 0x80, 0xb6, 0xfc, 0xe9, 0xb3, + 0xe0, 0x44, 0x5e, 0x81, 0x40, 0x82, 0x9e, 0x06, 0x27, 0x46, 0xb6, 0x06, 0x56, 0xb6, 0xb6, 0x03, + 0x70, 0x4c, 0xa9, 0x6f, 0x5d, 0x86, 0x16, 0x8f, 0x29, 0x15, 0x41, 0x97, 0xa7, 0xca, 0x47, 0x41, + 0xfa, 0xc2, 0xc7, 0x1a, 0x44, 0x47, 0x88, 0xc3, 0x01, 0x5f, 0x04, 0x03, 0xcc, 0x89, 0x71, 0x50, + 0xc9, 0xb4, 0x24, 0x34, 0xca, 0x61, 0x7b, 0x55, 0x35, 0x05, 0x51, 0xc2, 0x98, 0x5d, 0xf4, 0x96, + 0xab, 0xf9, 0xfb, 0x31, 0xbb, 0xd0, 0xf3, 0x51, 0x67, 0xc5, 0x45, 0xaf, 0x5b, 0xcd, 0xdf, 0x17, + 0x20, 0x2e, 0x5e, 0x79, 0x1e, 0x1f, 0x53, 0xd1, 0x74, 0xb1, 0x22, 0xdb, 0x90, 0x38, 0x64, 0x3f, + 0x8b, 0x30, 0x8d, 0x3c, 0x8f, 0x0b, 0xe3, 0x72, 0xba, 0x2a, 0xae, 0xb0, 0x1c, 0xa8, 0x5c, 0xc3, + 0x7d, 0x1b, 0x56, 0x94, 0xbb, 0x98, 0x7d, 0x89, 0x05, 0x2d, 0x87, 0x09, 0x53, 0x7d, 0x89, 0xe2, + 0xeb, 0xfe, 0x3f, 0xdc, 0x82, 0xe5, 0x83, 0x4c, 0x38, 0xe8, 0x53, 0x6e, 0x97, 0x82, 0x3c, 0x86, + 0x79, 0xd9, 0x6c, 0x47, 0x36, 0x6b, 0xdd, 0x77, 0xe8, 0x75, 0xfd, 0xad, 0x31, 0x5d, 0x79, 0xee, + 0xda, 0xcf, 0xff, 0xe5, 0xdf, 0x7e, 0xd1, 0x5a, 0x22, 0xed, 0x7b, 0x67, 0x1f, 0xdc, 0x3b, 0xa1, + 0x2c, 0xe6, 0x54, 0x4e, 0x61, 0xc9, 0xea, 0x8f, 0x22, 0xdb, 0x56, 0x8f, 0xd3, 0x48, 0xdb, 0x54, + 0x7f, 0x67, 0x62, 0x07, 0x94, 0xdb, 0x47, 0x16, 0xeb, 0x84, 0x48, 0x16, 0x25, 0xa2, 0x08, 0xc2, + 0x5f, 0x41, 0xf7, 0x21, 0x56, 0x86, 0x34, 0x55, 0x72, 0xa3, 0xa2, 0xd6, 0xd8, 0xf7, 0xd5, 0xbf, + 0x39, 0x1e, 0x41, 0x72, 0xbc, 0x86, 0x1c, 0x37, 0xc8, 0x1a, 0xe7, 0x28, 0x2a, 0x4f, 0xba, 0xdf, + 0x8a, 0x94, 0xb0, 0x22, 0x3b, 0x49, 0x5e, 0x2b, 0xcf, 0x6d, 0xe4, 0xb9, 0x49, 0xd6, 0x39, 0xcf, + 0x48, 0x30, 0xa8, 0x98, 0x66, 0x78, 0xb1, 0x35, 0x3b, 0x9f, 0xc8, 0xf5, 0xb1, 0x2d, 0x51, 0x82, + 0xe5, 0x8d, 0x4b, 0x5a, 0xa6, 0xec, 0x55, 0x9e, 0x50, 0x8e, 0xab, 0xbb, 0xa6, 0xc8, 0x2f, 0x1c, + 0x2c, 0xe2, 0x35, 0xf6, 0xe8, 0x91, 0xb7, 0x2e, 0x6f, 0x0c, 0x14, 0x32, 0xdc, 0x99, 0xb6, 0x83, + 0xd0, 0xfd, 0x0e, 0x0a, 0x73, 0x9d, 0x6c, 0x4b, 0x61, 0xac, 0xae, 0x41, 0xd5, 0x97, 0x48, 0x42, + 0xe8, 0x98, 0xed, 0x4e, 0xe4, 0x5a, 0x43, 0x2f, 0x91, 0x66, 0xbe, 0xdd, 0x3c, 0x28, 0x19, 0xf6, + 0x90, 0x21, 0x21, 0x2b, 0x92, 0xa1, 0xee, 0x8e, 0x22, 0x5f, 0x43, 0x77, 0xa4, 0x55, 0x88, 0xb8, + 0x23, 0xe6, 0x6b, 0x68, 0xfb, 0xea, 0xbf, 0x31, 0x11, 0x47, 0x72, 0xbd, 0x8e, 0x5c, 0x7b, 0xee, + 0x9a, 0x61, 0x65, 0xc5, 0xf9, 0x7b, 0xce, 0xdb, 0xa4, 0x44, 0x3b, 0x9b, 0xfd, 0x46, 0x53, 0xf1, + 0xbe, 0xd1, 0xb0, 0x54, 0x6b, 0x9b, 0x8e, 0xda, 0x5a, 0xf1, 0xc4, 0xed, 0x5a, 0x62, 0x37, 0x83, + 0xd1, 0x8b, 0x85, 0x91, 0x67, 0x1a, 0xbe, 0x3b, 0xcd, 0xbd, 0x5c, 0xb2, 0x9d, 0xac, 0xb6, 0x73, + 0x15, 0xd7, 0x8c, 0xe5, 0xa4, 0xb4, 0x5a, 0xdd, 0x24, 0x53, 0xdb, 0xab, 0x1b, 0x9a, 0xcd, 0x1a, + 0x57, 0x6a, 0x76, 0x8f, 0x8d, 0x5d, 0x69, 0xc6, 0xf2, 0x92, 0xbc, 0x84, 0x65, 0x11, 0x2e, 0x5e, + 0xbf, 0x65, 0x77, 0x90, 0xef, 0x96, 0x4b, 0xaa, 0x98, 0x61, 0x1a, 0xf6, 0x39, 0x2c, 0xea, 0x8e, + 0x11, 0xd2, 0x33, 0x16, 0x61, 0xf5, 0x26, 0xf5, 0xc7, 0x74, 0x9e, 0x28, 0x6f, 0x75, 0x97, 0xe4, + 0xaa, 0x44, 0x1f, 0x09, 0x27, 0xfc, 0x53, 0x80, 0xaa, 0x15, 0x85, 0x5c, 0xad, 0x51, 0xd6, 0x9a, + 0xeb, 0x37, 0x0d, 0xa9, 0x86, 0x56, 0x24, 0xbf, 0x42, 0x96, 0x2d, 0xf2, 0x6a, 0xbf, 0xe9, 0xea, + 0x88, 0xb5, 0xdf, 0x46, 0x9b, 0x57, 0xfa, 0xe3, 0xbb, 0x16, 0x94, 0x51, 0x5c, 0xb5, 0xd9, 0xf4, + 0xcd, 0x87, 0xaf, 0xe0, 0x04, 0x4f, 0x0b, 0xa3, 0x5d, 0x62, 0xbb, 0x89, 0x4b, 0xe3, 0x69, 0x51, + 0xef, 0x7d, 0x70, 0xaf, 0x22, 0xab, 0x35, 0xb2, 0x3a, 0xca, 0xaa, 0x24, 0x2f, 0xb0, 0xa1, 0xdf, + 0x78, 0xed, 0x27, 0x26, 0xad, 0x7a, 0xeb, 0x43, 0xff, 0xfa, 0xb8, 0xe1, 0x31, 0x27, 0x93, 0x4c, + 0x8e, 0x70, 0x53, 0x09, 0x83, 0x8b, 0x37, 0x7e, 0xcb, 0xe0, 0x56, 0x2b, 0x40, 0xff, 0x6a, 0xc3, + 0x88, 0xa4, 0xbe, 0x81, 0xd4, 0xbb, 0x64, 0x49, 0x87, 0x44, 0xa4, 0x25, 0x6c, 0xa2, 0x1f, 0x5f, + 0x2c, 0x9b, 0x8c, 0xbe, 0xd0, 0x5b, 0x31, 0xb0, 0xf6, 0x4e, 0x5f, 0x8b, 0x81, 0xfa, 0x25, 0x9e, + 0xfc, 0xa1, 0xfd, 0xe0, 0xaf, 0x1e, 0x20, 0xdd, 0x89, 0x2f, 0x86, 0xb5, 0xdd, 0x32, 0xf6, 0x55, + 0xd1, 0xbd, 0x81, 0x9c, 0xaf, 0x92, 0xad, 0x51, 0xce, 0xf2, 0x85, 0x92, 0xfc, 0xdc, 0x81, 0xb5, + 0x86, 0xf7, 0xaf, 0x4a, 0x82, 0xf1, 0xaf, 0x75, 0x95, 0x04, 0x93, 0x1e, 0xd0, 0x5c, 0x94, 0x60, + 0xdb, 0x45, 0x09, 0x82, 0x28, 0xd2, 0x12, 0xc8, 0x5c, 0x8f, 0x7b, 0xe6, 0x9f, 0x3a, 0xb0, 0xd9, + 0xfc, 0xd6, 0x45, 0xde, 0xd4, 0x2d, 0xc2, 0x93, 0x5e, 0xe1, 0xfa, 0xb7, 0x2f, 0x43, 0x93, 0xd2, + 0xbc, 0x89, 0xd2, 0xdc, 0x70, 0xfb, 0x5c, 0x9a, 0x02, 0x71, 0x9b, 0x04, 0x3a, 0xc7, 0x02, 0x81, + 0xfd, 0x9a, 0x44, 0x8c, 0xdc, 0xa2, 0xf9, 0xd1, 0xad, 0x7f, 0x6b, 0x02, 0x86, 0x1d, 0xbe, 0xc8, + 0x86, 0x34, 0x08, 0x3e, 0xc1, 0xe8, 0x67, 0x29, 0xb9, 0x47, 0xab, 0xd7, 0x1a, 0x6b, 0x8f, 0xd6, + 0x1e, 0xa0, 0xac, 0x3d, 0x5a, 0x7f, 0x13, 0xaa, 0xed, 0x51, 0x64, 0x86, 0xef, 0x43, 0xe4, 0x4b, + 0xdc, 0x36, 0xb2, 0x3a, 0xd5, 0x1b, 0xdd, 0xea, 0x65, 0xd3, 0xb6, 0xb1, 0xeb, 0x4f, 0xb5, 0x50, + 0x29, 0x8a, 0x5e, 0x5c, 0x7b, 0x1e, 0x2c, 0x28, 0x74, 0xb2, 0x35, 0x4a, 0x40, 0x51, 0x6e, 0x7c, + 0x60, 0x70, 0xb7, 0x90, 0xe8, 0xaa, 0xdb, 0x31, 0x89, 0x72, 0x9a, 0x47, 0xd0, 0x36, 0x8a, 0xe9, + 0x44, 0x07, 0xd9, 0xfa, 0xdb, 0x41, 0xff, 0x5a, 0xe3, 0x98, 0x1d, 0x4a, 0xdc, 0x2e, 0x67, 0x50, + 0x22, 0x82, 0xe6, 0xf1, 0xfb, 0xb0, 0x64, 0xd5, 0xb3, 0x2b, 0xe5, 0x37, 0x55, 0xdc, 0x2b, 0xe5, + 0x37, 0x16, 0xc1, 0x55, 0xa2, 0xe9, 0xa2, 0xf2, 0x4b, 0x89, 0xa2, 0x79, 0xfd, 0x0c, 0x16, 0x75, + 0x19, 0xb9, 0xd2, 0xff, 0x68, 0x65, 0xf9, 0x32, 0x1e, 0x96, 0x0d, 0xce, 0xf9, 0xe4, 0xa3, 0x6c, + 0x70, 0x24, 0xf5, 0x65, 0x14, 0x49, 0x2b, 0x7d, 0xd5, 0x2b, 0xc5, 0x95, 0xbe, 0x9a, 0xaa, 0xaa, + 0x96, 0xbe, 0x42, 0x44, 0xd0, 0x6b, 0x28, 0xa0, 0x3b, 0x52, 0x9c, 0xac, 0xd2, 0x8a, 0xe6, 0x52, + 0x6c, 0x95, 0x56, 0x8c, 0xa9, 0x6a, 0xda, 0x89, 0x9b, 0xe0, 0x17, 0x24, 0x49, 0xe5, 0x5b, 0x22, + 0xdc, 0x8b, 0xd2, 0x9d, 0xe5, 0xb7, 0x56, 0x8d, 0xd2, 0xf2, 0x5b, 0xbb, 0xce, 0x57, 0x0b, 0xf7, + 0x54, 0xd0, 0x7a, 0x06, 0x0b, 0xaa, 0x66, 0x54, 0x39, 0xed, 0x48, 0xb5, 0xac, 0xdf, 0xab, 0x0f, + 0x48, 0xaa, 0x96, 0xe3, 0x06, 0x51, 0x84, 0x54, 0xa5, 0x21, 0x8c, 0x0a, 0x52, 0x65, 0x88, 0x7a, + 0xf1, 0xa9, 0x32, 0x44, 0x53, 0xc9, 0xc9, 0x32, 0x84, 0x88, 0x5c, 0x9a, 0xc7, 0xdf, 0x3a, 0x70, + 0xeb, 0xd2, 0x02, 0x10, 0x79, 0xff, 0x15, 0x6a, 0x45, 0x42, 0xa0, 0x0f, 0x5e, 0xb9, 0xba, 0xe4, + 0xde, 0x41, 0x31, 0x5d, 0x77, 0x47, 0x1d, 0xa6, 0x38, 0x2d, 0x12, 0xe8, 0xba, 0xd4, 0xc4, 0x85, + 0xfe, 0x6b, 0x47, 0xfc, 0xb9, 0xd6, 0x04, 0xba, 0x64, 0x77, 0x4a, 0x01, 0x94, 0xc0, 0xf7, 0xa6, + 0xc6, 0x97, 0xe2, 0xde, 0x46, 0x71, 0x6f, 0xba, 0xd7, 0x26, 0x88, 0xcb, 0x85, 0xfd, 0x03, 0xb8, + 0xa6, 0x0b, 0x45, 0x16, 0xdd, 0xcf, 0x86, 0x69, 0x54, 0x56, 0xf7, 0xd2, 0x31, 0xd5, 0xa4, 0xca, + 0x71, 0x46, 0xeb, 0x07, 0xf6, 0xf9, 0x78, 0x2e, 0x47, 0x85, 0x18, 0xc7, 0x9c, 0x36, 0xe7, 0x9e, + 0xc3, 0xaa, 0x9a, 0xf7, 0x59, 0x1c, 0xb0, 0x5f, 0x9a, 0xe7, 0x4d, 0xe4, 0xd9, 0x77, 0x37, 0x4c, + 0x9e, 0xc7, 0x71, 0xc0, 0x14, 0xc7, 0xa3, 0x39, 0xfc, 0xd3, 0xcc, 0x0f, 0xff, 0x2f, 0x00, 0x00, + 0xff, 0xff, 0x26, 0x18, 0xb3, 0x56, 0xcd, 0x39, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -4584,6 +4804,8 @@ type GoCryptoTraderClient interface { GetOrders(ctx context.Context, in *GetOrdersRequest, opts ...grpc.CallOption) (*GetOrdersResponse, error) GetOrder(ctx context.Context, in *GetOrderRequest, opts ...grpc.CallOption) (*OrderDetails, error) SubmitOrder(ctx context.Context, in *SubmitOrderRequest, opts ...grpc.CallOption) (*SubmitOrderResponse, error) + SimulateOrder(ctx context.Context, in *SimulateOrderRequest, opts ...grpc.CallOption) (*SimulateOrderResponse, error) + WhaleBomb(ctx context.Context, in *WhaleBombRequest, opts ...grpc.CallOption) (*SimulateOrderResponse, error) CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*CancelOrderResponse, error) CancelAllOrders(ctx context.Context, in *CancelAllOrdersRequest, opts ...grpc.CallOption) (*CancelAllOrdersResponse, error) GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error) @@ -4846,6 +5068,24 @@ func (c *goCryptoTraderClient) SubmitOrder(ctx context.Context, in *SubmitOrderR return out, nil } +func (c *goCryptoTraderClient) SimulateOrder(ctx context.Context, in *SimulateOrderRequest, opts ...grpc.CallOption) (*SimulateOrderResponse, error) { + out := new(SimulateOrderResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/SimulateOrder", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) WhaleBomb(ctx context.Context, in *WhaleBombRequest, opts ...grpc.CallOption) (*SimulateOrderResponse, error) { + out := new(SimulateOrderResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/WhaleBomb", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *goCryptoTraderClient) CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*CancelOrderResponse, error) { out := new(CancelOrderResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/CancelOrder", in, out, opts...) @@ -4956,6 +5196,8 @@ type GoCryptoTraderServer interface { GetOrders(context.Context, *GetOrdersRequest) (*GetOrdersResponse, error) GetOrder(context.Context, *GetOrderRequest) (*OrderDetails, error) SubmitOrder(context.Context, *SubmitOrderRequest) (*SubmitOrderResponse, error) + SimulateOrder(context.Context, *SimulateOrderRequest) (*SimulateOrderResponse, error) + WhaleBomb(context.Context, *WhaleBombRequest) (*SimulateOrderResponse, error) CancelOrder(context.Context, *CancelOrderRequest) (*CancelOrderResponse, error) CancelAllOrders(context.Context, *CancelAllOrdersRequest) (*CancelAllOrdersResponse, error) GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error) @@ -5457,6 +5699,42 @@ func _GoCryptoTrader_SubmitOrder_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _GoCryptoTrader_SimulateOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SimulateOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).SimulateOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/SimulateOrder", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).SimulateOrder(ctx, req.(*SimulateOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_WhaleBomb_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WhaleBombRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).WhaleBomb(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/WhaleBomb", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).WhaleBomb(ctx, req.(*WhaleBombRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _GoCryptoTrader_CancelOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CancelOrderRequest) if err := dec(in); err != nil { @@ -5731,6 +6009,14 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ MethodName: "SubmitOrder", Handler: _GoCryptoTrader_SubmitOrder_Handler, }, + { + MethodName: "SimulateOrder", + Handler: _GoCryptoTrader_SimulateOrder_Handler, + }, + { + MethodName: "WhaleBomb", + Handler: _GoCryptoTrader_WhaleBomb_Handler, + }, { MethodName: "CancelOrder", Handler: _GoCryptoTrader_CancelOrder_Handler, diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index b76ff411..8a90ca9f 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -391,6 +391,40 @@ func request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime } +func request_GoCryptoTrader_SimulateOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SimulateOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.SimulateOrder(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_WhaleBomb_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WhaleBombRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.WhaleBomb(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + func request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq CancelOrderRequest var metadata runtime.ServerMetadata @@ -1114,6 +1148,46 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("POST", pattern_GoCryptoTrader_SimulateOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_SimulateOrder_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SimulateOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_WhaleBomb_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_WhaleBomb_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WhaleBomb_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_GoCryptoTrader_CancelOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1352,6 +1426,10 @@ var ( pattern_GoCryptoTrader_SubmitOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "submitorder"}, "")) + pattern_GoCryptoTrader_SimulateOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "simulateorder"}, "")) + + pattern_GoCryptoTrader_WhaleBomb_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "whalebomb"}, "")) + pattern_GoCryptoTrader_CancelOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelorder"}, "")) pattern_GoCryptoTrader_CancelAllOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelallorders"}, "")) @@ -1426,6 +1504,10 @@ var ( forward_GoCryptoTrader_SubmitOrder_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_SimulateOrder_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_WhaleBomb_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_CancelOrder_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_CancelAllOrders_0 = runtime.ForwardResponseMessage diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 7c2d88b3..644a3bb0 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -327,6 +327,29 @@ message SubmitOrderResponse { string order_id = 2; } +message SimulateOrderRequest { + string exchange = 1; + CurrencyPair pair = 2; + double amount = 3; + string side = 4; +} + +message SimulateOrderResponse { + repeated OrderbookItem orders = 1; + double amount = 2; + double minimum_price = 3; + double maximum_price = 4; + double percentage_gain_loss = 5; + string status = 6; +} + +message WhaleBombRequest { + string exchange = 1; + CurrencyPair pair = 2; + double price_target = 3; + string side = 4; +} + message CancelOrderRequest { string exchange = 1; string account_id = 2; @@ -605,6 +628,20 @@ service GoCryptoTrader { }; } + rpc SimulateOrder (SimulateOrderRequest) returns (SimulateOrderResponse) { + option (google.api.http) = { + post: "/v1/simulateorder" + body: "*" + }; + } + + rpc WhaleBomb (WhaleBombRequest) returns (SimulateOrderResponse) { + option (google.api.http) = { + post: "/v1/whalebomb" + body: "*" + }; + } + rpc CancelOrder (CancelOrderRequest) returns (CancelOrderResponse) { option (google.api.http) = { post: "/v1/cancelorder" diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index a19f07a0..032da9db 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -732,6 +732,32 @@ ] } }, + "/v1/simulateorder": { + "post": { + "operationId": "SimulateOrder", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcSimulateOrderResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcSimulateOrderRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/submitorder": { "post": { "operationId": "SubmitOrder", @@ -758,6 +784,32 @@ ] } }, + "/v1/whalebomb": { + "post": { + "operationId": "WhaleBomb", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcSimulateOrderResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcWhaleBombRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/withdrawcryptofunds": { "post": { "operationId": "WithdrawCryptocurrencyFunds", @@ -1690,6 +1742,54 @@ "gctrpcRemovePortfolioAddressResponse": { "type": "object" }, + "gctrpcSimulateOrderRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + }, + "amount": { + "type": "number", + "format": "double" + }, + "side": { + "type": "string" + } + } + }, + "gctrpcSimulateOrderResponse": { + "type": "object", + "properties": { + "orders": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcOrderbookItem" + } + }, + "amount": { + "type": "number", + "format": "double" + }, + "minimum_price": { + "type": "number", + "format": "double" + }, + "maximum_price": { + "type": "number", + "format": "double" + }, + "percentage_gain_loss": { + "type": "number", + "format": "double" + }, + "status": { + "type": "string" + } + } + }, "gctrpcSubmitOrderRequest": { "type": "object", "properties": { @@ -1787,6 +1887,24 @@ } } }, + "gctrpcWhaleBombRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + }, + "price_target": { + "type": "number", + "format": "double" + }, + "side": { + "type": "string" + } + } + }, "gctrpcWithdrawCurrencyRequest": { "type": "object", "properties": { From 3de1d94e5f151fdfc67ab5a6ae16b5af2835099b Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 7 Jul 2019 05:20:31 +1000 Subject: [PATCH 19/71] New logging system (#319) * First pass at adding new logging system * NewLogger * NewLogger * WIP * silly bug fix * :D removed files * removed old logging interface * added tests * added tests * Started to add new lines to all f calls * Added subsystem log types * Logger improvements * Further performance improvements * changes to logger and sublogger creation * Renamed Logging types * removed old print statement * changes based on feedback * moved sublogger types to own file * :) * added console as output type * added get level command * added get/set log level via grpc command * added check for output being empty for migration support * first pass at log rotation * added log rotation * :D derp fixed * added tests * changes based on feedback * changed log type * comments * renamed file -> fileSettings * typo fix * changes based on feedback * gofmt ran on additional files * gofmt ran on additional files --- cmd/exchange_template/wrapper_file.tmpl | 6 +- cmd/gctcli/commands.go | 101 +++ cmd/gctcli/main.go | 2 + cmd/portfolio/portfolio.go | 14 +- cmd/websocket_client/main.go | 4 +- common/common.go | 8 +- communications/base/base_interface.go | 8 +- communications/slack/slack.go | 30 +- communications/smsglobal/smsglobal.go | 4 +- communications/smtpservice/smtpservice.go | 4 +- communications/telegram/telegram.go | 14 +- config/config.go | 132 +-- config/config_encryption.go | 9 +- config/config_test.go | 2 +- config/config_types.go | 2 +- config_example.json | 24 +- connchecker/connchecker.go | 10 +- currency/coinmarketcap/coinmarketcap.go | 2 +- currency/coinmarketcap/coinmarketcap_test.go | 2 +- currency/conversion.go | 11 +- .../currencyconverterapi.go | 2 +- .../currencylayer/currencylayer.go | 3 +- .../exchangeratesapi.io/exchangeratesapi.go | 3 +- currency/forexprovider/fixer.io/fixer.go | 3 +- .../openexchangerates/openexchangerates.go | 3 +- currency/pairs.go | 3 +- currency/storage.go | 30 +- engine/addr_helpers_test.go | 128 +-- engine/comms_relayer.go | 8 +- engine/connection.go | 10 +- engine/engine.go | 163 ++-- engine/events.go | 22 +- engine/exchange.go | 12 +- engine/helpers.go | 24 +- engine/orders.go | 28 +- engine/portfolio.go | 10 +- engine/restful_router.go | 18 +- engine/restful_server.go | 5 +- engine/routines.go | 63 +- engine/rpcserver.go | 52 +- engine/syncer.go | 62 +- engine/syncer_test.go | 3 +- engine/timekeeper.go | 14 +- engine/websocket.go | 45 +- exchanges/alphapoint/alphapoint_websocket.go | 14 +- exchanges/anx/anx.go | 16 +- exchanges/anx/anx_wrapper.go | 7 +- exchanges/binance/binance.go | 2 +- exchanges/binance/binance_wrapper.go | 10 +- exchanges/bitfinex/bitfinex.go | 2 +- exchanges/bitfinex/bitfinex_websocket.go | 10 +- exchanges/bitfinex/bitfinex_wrapper.go | 10 +- exchanges/bitflyer/bitflyer_test.go | 2 +- exchanges/bitflyer/bitflyer_wrapper.go | 2 +- exchanges/bithumb/bithumb_wrapper.go | 2 +- exchanges/bitmex/bitmex_websocket.go | 10 +- exchanges/bitmex/bitmex_wrapper.go | 6 +- exchanges/bitstamp/bitstamp.go | 10 +- exchanges/bitstamp/bitstamp_websocket.go | 6 +- exchanges/bitstamp/bitstamp_wrapper.go | 8 +- exchanges/bittrex/bittrex_wrapper.go | 10 +- exchanges/btcmarkets/btcmarkets.go | 2 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 6 +- exchanges/btse/btse.go | 2 +- exchanges/btse/btse_websocket.go | 4 +- exchanges/btse/btse_wrapper.go | 4 +- exchanges/coinbasepro/coinbasepro.go | 2 +- .../coinbasepro/coinbasepro_websocket.go | 2 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 8 +- exchanges/coinut/coinut.go | 2 +- exchanges/coinut/coinut_websocket.go | 2 +- exchanges/coinut/coinut_wrapper.go | 4 +- exchanges/exchange.go | 28 +- exchanges/exmo/exmo.go | 2 +- exchanges/exmo/exmo_wrapper.go | 2 +- exchanges/gateio/gateio_websocket.go | 4 +- exchanges/gateio/gateio_wrapper.go | 2 +- exchanges/gemini/gemini.go | 2 +- exchanges/gemini/gemini_websocket.go | 2 +- exchanges/gemini/gemini_wrapper.go | 2 +- exchanges/hitbtc/hitbtc_websocket.go | 4 +- exchanges/hitbtc/hitbtc_wrapper.go | 8 +- exchanges/huobi/huobi_websocket.go | 16 +- exchanges/huobi/huobi_wrapper.go | 10 +- exchanges/huobihadax/huobihadax.go | 3 +- exchanges/huobihadax/huobihadax_test.go | 3 +- exchanges/huobihadax/huobihadax_websocket.go | 16 +- exchanges/huobihadax/huobihadax_wrapper.go | 2 +- exchanges/itbit/itbit.go | 2 +- exchanges/itbit/itbit_wrapper.go | 4 +- exchanges/kraken/kraken.go | 4 +- exchanges/kraken/kraken_websocket.go | 62 +- exchanges/kraken/kraken_wrapper.go | 6 +- exchanges/lakebtc/lakebtc.go | 10 +- exchanges/lakebtc/lakebtc_wrapper.go | 2 +- exchanges/localbitcoins/localbitcoins.go | 10 +- .../localbitcoins/localbitcoins_wrapper.go | 6 +- exchanges/okcoin/okcoin_wrapper.go | 4 +- exchanges/okex/okex_wrapper.go | 4 +- exchanges/okgroup/okgroup.go | 6 +- exchanges/okgroup/okgroup_websocket.go | 46 +- exchanges/okgroup/okgroup_wrapper.go | 12 +- exchanges/orderbook/calculator.go | 454 +++++------ exchanges/orderbook/calculator_test.go | 158 ++-- exchanges/orderbook/orderbook_test.go | 3 +- .../orderbook/simulator/simulator_test.go | 48 +- exchanges/poloniex/poloniex_websocket.go | 4 +- exchanges/poloniex/poloniex_wrapper.go | 10 +- exchanges/request/request.go | 39 +- exchanges/ticker/ticker_test.go | 3 +- exchanges/websocket.go | 42 +- exchanges/yobit/yobit.go | 2 +- exchanges/yobit/yobit_wrapper.go | 2 +- exchanges/zb/zb_test.go | 2 +- exchanges/zb/zb_websocket.go | 4 +- exchanges/zb/zb_wrapper.go | 2 +- gctrpc/gen_pb_linux.sh | 0 gctrpc/rpc.pb.go | 760 ++++++++++++------ gctrpc/rpc.pb.gw.go | 193 +++-- gctrpc/rpc.proto | 37 +- gctrpc/rpc.swagger.json | 82 ++ go.mod | 2 +- go.sum | 2 + logger/logger.go | 166 ++-- logger/logger_levels.go | 33 - logger/logger_multiwriter.go | 75 ++ logger/logger_rotate.go | 132 +++ logger/logger_rotate_types.go | 22 + logger/logger_setup.go | 156 ++++ logger/logger_test.go | 271 +++++-- logger/logger_types.go | 98 ++- logger/loggers.go | 179 +++-- logger/sublogger_types.go | 23 + main.go | 6 +- ntpclient/ntpclient.go | 2 +- portfolio/portfolio.go | 4 +- testdata/configtest.json | 2 +- 137 files changed, 2920 insertions(+), 1650 deletions(-) mode change 100644 => 100755 gctrpc/gen_pb_linux.sh delete mode 100644 logger/logger_levels.go create mode 100644 logger/logger_multiwriter.go create mode 100644 logger/logger_rotate.go create mode 100644 logger/logger_rotate_types.go create mode 100644 logger/logger_setup.go create mode 100644 logger/sublogger_types.go diff --git a/cmd/exchange_template/wrapper_file.tmpl b/cmd/exchange_template/wrapper_file.tmpl index 672ad855..eb6836b4 100644 --- a/cmd/exchange_template/wrapper_file.tmpl +++ b/cmd/exchange_template/wrapper_file.tmpl @@ -24,9 +24,9 @@ func ({{.Variable}} *{{.CapitalName}}) Start(wg *sync.WaitGroup) { // Run implements the {{.CapitalName}} wrapper func ({{.Variable}} *{{.CapitalName}}) Run() { if {{.Variable}}.Verbose { -{{if .WS}} log.Debugf("%s Websocket: %s. (url: %s).\n", {{.Variable}}.GetName(), common.IsEnabled({{.Variable}}.Websocket.IsEnabled()), {{.Variable}}.Websocket.GetWebsocketURL()) {{end}} - log.Debugf("%s polling delay: %ds.\n", {{.Variable}}.GetName(), {{.Variable}}.RESTPollingDelay) - log.Debugf("%s %d currencies enabled: %s.\n", {{.Variable}}.GetName(), len({{.Variable}}.EnabledPairs), {{.Variable}}.EnabledPairs) +{{if .WS}} log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", {{.Variable}}.GetName(), common.IsEnabled({{.Variable}}.Websocket.IsEnabled()), {{.Variable}}.Websocket.GetWebsocketURL()) {{end}} + log.Debugf(log.ExchangeSys, "%s polling delay: %ds.\n", {{.Variable}}.GetName(), {{.Variable}}.RESTPollingDelay) + log.Debugf(log.ExchangeSys, "%s %d currencies enabled: %s.\n", {{.Variable}}.GetName(), len({{.Variable}}.EnabledPairs), {{.Variable}}.EnabledPairs) } } diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index 1cae5f76..a7ab413f 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -1948,3 +1948,104 @@ var withdrawFiatFundsCommand = cli.Command{ func withdrawFiatFunds(_ *cli.Context) error { return common.ErrNotYetImplemented } + +var getLoggerDetailsCommand = cli.Command{ + Name: "getloggerdetails", + Action: getLoggerDetails, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "logger", + Usage: "logger to get level details of", + }, + }, +} + +func getLoggerDetails(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getloggerdetails") + return nil + } + + var logger string + if c.IsSet("logger") { + logger = c.String("logger") + } else { + logger = c.Args().First() + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + + result, err := client.GetLoggerDetails(context.Background(), + &gctrpc.GetLoggerDetailsRequest{ + Logger: logger, + }, + ) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +var setLoggerDetailsCommand = cli.Command{ + Name: "setloggerdetails", + Action: setLoggerDetails, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "logger", + Usage: "logger to get level details of", + }, + cli.StringFlag{ + Name: "flags", + Usage: "pipe separated value of loggers e.g INFO|WARN", + }, + }, +} + +func setLoggerDetails(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "setloggerdetails") + return nil + } + + var logger string + var level string + + if c.IsSet("logger") { + logger = c.String("logger") + } else { + logger = c.Args().First() + } + + if c.IsSet("level") { + level = c.String("level") + } else { + level = c.Args().Get(1) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + + result, err := client.SetLoggerDetails(context.Background(), + &gctrpc.SetLoggerDetailsRequest{ + Logger: logger, + Level: level, + }, + ) + if err != nil { + return err + } + jsonOutput(result) + return nil +} diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index 2ba53d45..11e43506 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -122,6 +122,8 @@ func main() { getCryptocurrencyDepositAddressCommand, withdrawCryptocurrencyFundsCommand, withdrawFiatFundsCommand, + getLoggerDetailsCommand, + setLoggerDetailsCommand, } err := app.Run(os.Args) diff --git a/cmd/portfolio/portfolio.go b/cmd/portfolio/portfolio.go index 23e8e3b7..2477c441 100644 --- a/cmd/portfolio/portfolio.go +++ b/cmd/portfolio/portfolio.go @@ -3,11 +3,12 @@ package main import ( "flag" "fmt" + "log" + "os" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/exchanges/bitfinex" - log "github.com/thrasher-/gocryptotrader/logger" "github.com/thrasher-/gocryptotrader/portfolio" ) @@ -25,7 +26,7 @@ func printSummary(msg string, amount float64) { currency.USD, displayCurrency) if err != nil { - log.Error(err) + log.Println(err) } else { symb, err := currency.GetSymbolByCurrencyName(displayCurrency) if err != nil { @@ -64,7 +65,8 @@ func main() { defaultCfg, err := config.GetFilePath("") if err != nil { - log.Fatal(err) + log.Println(err) + os.Exit(1) } flag.StringVar(&inFile, "infile", defaultCfg, "The config input file to process.") @@ -76,7 +78,8 @@ func main() { var cfg config.Config err = cfg.LoadConfig(inFile) if err != nil { - log.Fatal(err) + log.Println(err) + os.Exit(1) } log.Println("Loaded config file.") @@ -105,7 +108,8 @@ func main() { } err = currency.SeedForeignExchangeData(fiatCurrencies) if err != nil { - log.Fatal(err) + log.Println(err) + os.Exit(1) } log.Println("Fetched currency data.") diff --git a/cmd/websocket_client/main.go b/cmd/websocket_client/main.go index d3522c9b..ac3ff3bf 100644 --- a/cmd/websocket_client/main.go +++ b/cmd/websocket_client/main.go @@ -42,8 +42,8 @@ type WebsocketEventResponse struct { // WebsocketOrderbookTickerRequest is a struct used for ticker and orderbook // requests type WebsocketOrderbookTickerRequest struct { - Exchange string `json:"exchangeName"` - Currency string `json:"currency"` + Exchange string `json:"exchangeName"` + Currency string `json:"currency"` AssetType asset.Item `json:"assetType"` } diff --git a/common/common.go b/common/common.go index 67e2e05c..a11cd71d 100644 --- a/common/common.go +++ b/common/common.go @@ -201,7 +201,7 @@ func SendHTTPRequest(method, urlPath string, headers map[string]string, body io. // on failure. func SendHTTPGetRequest(urlPath string, jsonDecode, isVerbose bool, result interface{}) error { if isVerbose { - log.Debugf("Raw URL: %s", urlPath) + log.Debugf(log.Global, "Raw URL: %s\n", urlPath) } initialiseHTTPClient() @@ -221,7 +221,7 @@ func SendHTTPGetRequest(urlPath string, jsonDecode, isVerbose bool, result inter } if isVerbose { - log.Debugf("Raw Resp: %s", string(contents)) + log.Debugf(log.Global, "Raw Resp: %s\n", string(contents)) } defer res.Body.Close() @@ -336,7 +336,7 @@ func GetDefaultDataDir(env string) string { dir, err := os.UserHomeDir() if err != nil { - log.Warn("Environment variable unset, defaulting to current directory") + log.Warnln(log.Global, "Environment variable unset, defaulting to current directory") dir = "." } return filepath.Join(dir, ".gocryptotrader") @@ -349,7 +349,7 @@ func CreateDir(dir string) error { return nil } - log.Warnf("Directory %s does not exist.. creating.", dir) + log.Warnf(log.Global, "Directory %s does not exist.. creating.\n", dir) return os.MkdirAll(dir, 0770) } diff --git a/communications/base/base_interface.go b/communications/base/base_interface.go index cb5635c2..465f630b 100644 --- a/communications/base/base_interface.go +++ b/communications/base/base_interface.go @@ -29,10 +29,10 @@ func (c IComm) Setup() { if c[i].IsEnabled() && !c[i].IsConnected() { err := c[i].Connect() if err != nil { - log.Errorf("Communications: %s failed to connect. Err: %s", c[i].GetName(), err) + log.Errorf(log.CommunicationMgr, "Communications: %s failed to connect. Err: %s", c[i].GetName(), err) continue } - log.Debugf("Communications: %v is enabled and online.", c[i].GetName()) + log.Debugf(log.CommunicationMgr, "Communications: %v is enabled and online.", c[i].GetName()) } } } @@ -43,7 +43,7 @@ func (c IComm) PushEvent(event Event) { if c[i].IsEnabled() && c[i].IsConnected() { err := c[i].PushEvent(event) if err != nil { - log.Errorf("Communications error - PushEvent() in package %s with %v. Err %s", + log.Errorf(log.CommunicationMgr, "Communications error - PushEvent() in package %s with %v. Err %s", c[i].GetName(), event, err) } } @@ -69,7 +69,7 @@ func (c IComm) GetEnabledCommunicationMediums() error { var count int for i := range c { if c[i].IsEnabled() && c[i].IsConnected() { - log.Debugf("Communications: Medium %s is enabled.", c[i].GetName()) + log.Debugf(log.CommunicationMgr, "Communications: Medium %s is enabled.", c[i].GetName()) count++ } } diff --git a/communications/slack/slack.go b/communications/slack/slack.go index dad5a45e..e1f83802 100644 --- a/communications/slack/slack.go +++ b/communications/slack/slack.go @@ -162,13 +162,13 @@ func (s *Slack) NewConnection() error { } if s.Verbose { - log.Debugf("Slack: %s [%s] connected to %s [%s] \nWebsocket URL: %s.\n", + log.Debugf(log.CommunicationMgr, "Slack: %s [%s] connected to %s [%s] \nWebsocket URL: %s.\n", s.Details.Self.Name, s.Details.Self.ID, s.Details.Team.Domain, s.Details.Team.ID, s.Details.URL) - log.Debugf("Slack: Public channels: %s", s.GetChannelsString()) + log.Debugf(log.CommunicationMgr, "Slack: Public channels: %s\n", s.GetChannelsString()) } s.TargetChannelID, err = s.GetIDByName(s.TargetChannel) @@ -176,7 +176,7 @@ func (s *Slack) NewConnection() error { return err } - log.Debugf("Slack: Target channel ID: %v [#%v]", s.TargetChannelID, + log.Debugf(log.CommunicationMgr, "Slack: Target channel ID: %v [#%v]\n", s.TargetChannelID, s.TargetChannel) return s.WebsocketConnect() } @@ -208,13 +208,13 @@ func (s *Slack) WebsocketReader() { for { _, resp, err := s.WebsocketConn.ReadMessage() if err != nil { - log.Error(err) + log.Errorln(log.CommunicationMgr, err) } var data WebsocketResponse err = common.JSONDecode(resp, &data) if err != nil { - log.Error(err) + log.Errorln(log.CommunicationMgr, err) continue } @@ -249,10 +249,10 @@ func (s *Slack) WebsocketReader() { case "pong": if s.Verbose { - log.Debugf("Slack: Pong received from server") + log.Debugln(log.CommunicationMgr, "Slack: Pong received from server") } default: - log.Debugf(string(resp)) + log.Debugln(log.CommunicationMgr, string(resp)) } } } @@ -264,7 +264,7 @@ func (s *Slack) handlePresenceChange(resp []byte) error { return err } if s.Verbose { - log.Debugf("Slack: Presence change. User %s [%s] changed status to %s\n", + log.Debugf(log.CommunicationMgr, "Slack: Presence change. User %s [%s] changed status to %s\n", s.GetUsernameByID(pres.User), pres.User, pres.Presence) } @@ -281,7 +281,7 @@ func (s *Slack) handleMessageResponse(resp []byte, data WebsocketResponse) error return err } if s.Verbose { - log.Debugf("Slack: Message received by %s [%s] with text: %s\n", + log.Debugf(log.CommunicationMgr, "Slack: Message received by %s [%s] with text: %s\n", s.GetUsernameByID(msg.User), msg.User, msg.Text) } @@ -293,7 +293,7 @@ func (s *Slack) handleMessageResponse(resp []byte, data WebsocketResponse) error func (s *Slack) handleErrorResponse(data WebsocketResponse) error { if data.Error.Msg == "Socket URL has expired" { if s.Verbose { - log.Debugf("Slack websocket URL has expired.. Reconnecting") + log.Debugln(log.CommunicationMgr, "Slack websocket URL has expired.. Reconnecting") } if s.WebsocketConn == nil { @@ -301,7 +301,7 @@ func (s *Slack) handleErrorResponse(data WebsocketResponse) error { } if err := s.WebsocketConn.Close(); err != nil { - log.Error(err) + log.Errorln(log.CommunicationMgr, err) } s.ReconnectURL = "" @@ -313,7 +313,7 @@ func (s *Slack) handleErrorResponse(data WebsocketResponse) error { func (s *Slack) handleHelloResponse() { if s.Verbose { - log.Debugln("Slack: Websocket connected successfully.") + log.Debugln(log.CommunicationMgr, "Slack: Websocket connected successfully.") } s.Connected = true go s.WebsocketKeepAlive() @@ -330,7 +330,7 @@ func (s *Slack) handleReconnectResponse(resp []byte) error { } s.ReconnectURL = recURL.URL if s.Verbose { - log.Debugf("Slack: Reconnect URL set to %s\n", s.ReconnectURL) + log.Debugf(log.CommunicationMgr, "Slack: Reconnect URL set to %s\n", s.ReconnectURL) } return nil } @@ -343,7 +343,7 @@ func (s *Slack) WebsocketKeepAlive() { for { <-ticker.C if err := s.WebsocketSend("ping", ""); err != nil { - log.Debugf("Slack: WebsocketKeepAlive() error %s", err) + log.Errorf(log.CommunicationMgr, "Slack: WebsocketKeepAlive() error %s\n", err) } } } @@ -364,7 +364,7 @@ func (s *Slack) WebsocketSend(eventType, text string) error { } if s.Verbose { - log.Debugf("Slack: Sending websocket message: %s", string(data)) + log.Debugf(log.CommunicationMgr, "Slack: Sending websocket message: %s\n", string(data)) } if s.WebsocketConn == nil { diff --git a/communications/smsglobal/smsglobal.go b/communications/smsglobal/smsglobal.go index 7f21bf61..3b75ea87 100644 --- a/communications/smsglobal/smsglobal.go +++ b/communications/smsglobal/smsglobal.go @@ -51,7 +51,7 @@ func (s *SMSGlobal) Setup(cfg *config.CommunicationsConfig) { Enabled: cfg.SMSGlobalConfig.Contacts[x].Enabled, }, ) - log.Debugf("SMSGlobal: SMS Contact: %s. Number: %s. Enabled: %v", + log.Debugf(log.CommunicationMgr, "SMSGlobal: SMS Contact: %s. Number: %s. Enabled: %v\n", cfg.SMSGlobalConfig.Contacts[x].Name, cfg.SMSGlobalConfig.Contacts[x].Number, cfg.SMSGlobalConfig.Contacts[x].Enabled) @@ -151,7 +151,7 @@ func (s *SMSGlobal) SendMessageToAll(message string) error { for x := range s.Contacts { if s.Contacts[x].Enabled { if s.Verbose { - log.Debugf("SMSGlobal: Sending SMS to %s. Number: %s. Message: %s [From: %s]", + log.Debugf(log.CommunicationMgr, "SMSGlobal: Sending SMS to %s. Number: %s. Message: %s [From: %s]\n", s.Contacts[x].Name, s.Contacts[x].Number, message, s.SendFrom) } err := s.SendMessage(s.Contacts[x].Number, message) diff --git a/communications/smtpservice/smtpservice.go b/communications/smtpservice/smtpservice.go index 90fe0fd4..b67b145e 100644 --- a/communications/smtpservice/smtpservice.go +++ b/communications/smtpservice/smtpservice.go @@ -39,7 +39,7 @@ func (s *SMTPservice) Setup(cfg *config.CommunicationsConfig) { s.AccountPassword = cfg.SMTPConfig.AccountPassword s.From = cfg.SMTPConfig.From s.RecipientList = cfg.SMTPConfig.RecipientList - log.Debugf("SMTP: Setup - From: %v. To: %s. Server: %s.", s.From, s.RecipientList, s.Host) + log.Debugf(log.CommunicationMgr, "SMTP: Setup - From: %v. To: %s. Server: %s.\n", s.From, s.RecipientList, s.Host) } // IsConnected returns whether or not the connection is connected @@ -65,7 +65,7 @@ func (s *SMTPservice) Send(subject, msg string) error { return errors.New("STMPservice Send() please add subject and alert") } - log.Debugf("SMTP: Sending email to %v. Subject: %s Message: %s [From: %s]", s.RecipientList, + log.Debugf(log.CommunicationMgr, "SMTP: Sending email to %v. Subject: %s Message: %s [From: %s]\n", s.RecipientList, subject, msg, s.From) messageToSend := fmt.Sprintf( msgSMTP, diff --git a/communications/telegram/telegram.go b/communications/telegram/telegram.go index 11a200f0..b12eb33e 100644 --- a/communications/telegram/telegram.go +++ b/communications/telegram/telegram.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "os" "strings" "github.com/thrasher-/gocryptotrader/common" @@ -86,7 +87,7 @@ func (t *Telegram) PollerStart() { for { resp, err := t.GetUpdates() if err != nil { - log.Error(err) + log.Errorln(log.CommunicationMgr, err) } for i := range resp.Result { @@ -94,7 +95,7 @@ func (t *Telegram) PollerStart() { if string(resp.Result[i].Message.Text[0]) == "/" { err = t.HandleMessages(resp.Result[i].Message.Text, resp.Result[i].Message.From.ID) if err != nil { - log.Error(err) + log.Errorln(log.CommunicationMgr, err) } } t.Offset = resp.Result[i].UpdateID @@ -108,11 +109,13 @@ func (t *Telegram) PollerStart() { func (t *Telegram) InitialConnect() { resp, err := t.GetUpdates() if err != nil { - log.Fatal(err) + log.Errorln(log.CommunicationMgr, err) + os.Exit(1) } if !resp.Ok { - log.Fatal(resp.Description) + log.Errorln(log.CommunicationMgr, resp.Description) + os.Exit(1) } warmWelcomeList := make(map[string]int64) @@ -125,7 +128,8 @@ func (t *Telegram) InitialConnect() { for userName, ID := range warmWelcomeList { err = t.SendMessage(fmt.Sprintf("GoCryptoTrader bot has connected: Hello, %s!", userName), ID) if err != nil { - log.Fatal(err) + log.Errorln(log.CommunicationMgr, err) + os.Exit(1) } } if len(resp.Result) == 0 { diff --git a/config/config.go b/config/config.go index 6bfbb9b1..1b70e5b6 100644 --- a/config/config.go +++ b/config/config.go @@ -184,20 +184,20 @@ func (c *Config) CheckClientBankAccounts() { if c.BankAccounts[i].Enabled { if c.BankAccounts[i].BankName == "" || c.BankAccounts[i].BankAddress == "" { c.BankAccounts[i].Enabled = false - log.Warnf("banking details for %s is enabled but variables not set correctly", + log.Warnf(log.ConfigMgr, "banking details for %s is enabled but variables not set correctly\n", c.BankAccounts[i].BankName) continue } if c.BankAccounts[i].AccountName == "" || c.BankAccounts[i].AccountNumber == "" { c.BankAccounts[i].Enabled = false - log.Warnf("banking account details for %s variables not set correctly", + log.Warnf(log.ConfigMgr, "banking account details for %s variables not set correctly\n", c.BankAccounts[i].BankName) continue } if c.BankAccounts[i].IBAN == "" && c.BankAccounts[i].SWIFTCode == "" && c.BankAccounts[i].BSBNumber == "" { c.BankAccounts[i].Enabled = false - log.Warnf("critical banking numbers not set for %s in %s account", + log.Warnf(log.ConfigMgr, "critical banking numbers not set for %s in %s account\n", c.BankAccounts[i].BankName, c.BankAccounts[i].AccountName) continue @@ -335,7 +335,7 @@ func (c *Config) CheckCommunicationsConfig() { } if len(c.Communications.SMSGlobalConfig.From) > 11 { - log.Warnf("SMSGlobal config supplied from name exceeds 11 characters, trimming.") + log.Warnf(log.ConfigMgr, "SMSGlobal config supplied from name exceeds 11 characters, trimming.\n") c.Communications.SMSGlobalConfig.From = c.Communications.SMSGlobalConfig.From[:11] } @@ -367,14 +367,14 @@ func (c *Config) CheckCommunicationsConfig() { c.Communications.SMSGlobalConfig.Name != "SMSGlobal" || c.Communications.SMTPConfig.Name != "SMTP" || c.Communications.TelegramConfig.Name != "Telegram" { - log.Warn("Communications config name/s not set correctly") + log.Warnln(log.ConfigMgr, "Communications config name/s not set correctly") } if c.Communications.SlackConfig.Enabled { if c.Communications.SlackConfig.TargetChannel == "" || c.Communications.SlackConfig.VerificationToken == "" || c.Communications.SlackConfig.VerificationToken == "testtest" { c.Communications.SlackConfig.Enabled = false - log.Warn("Slack enabled in config but variable data not set, disabling.") + log.Warnln(log.ConfigMgr, "Slack enabled in config but variable data not set, disabling.") } } if c.Communications.SMSGlobalConfig.Enabled { @@ -382,7 +382,7 @@ func (c *Config) CheckCommunicationsConfig() { c.Communications.SMSGlobalConfig.Password == "" || len(c.Communications.SMSGlobalConfig.Contacts) == 0 { c.Communications.SMSGlobalConfig.Enabled = false - log.Warn("SMSGlobal enabled in config but variable data not set, disabling.") + log.Warnln(log.ConfigMgr, "SMSGlobal enabled in config but variable data not set, disabling.") } } if c.Communications.SMTPConfig.Enabled { @@ -391,13 +391,13 @@ func (c *Config) CheckCommunicationsConfig() { c.Communications.SMTPConfig.AccountName == "" || c.Communications.SMTPConfig.AccountPassword == "" { c.Communications.SMTPConfig.Enabled = false - log.Warn("SMTP enabled in config but variable data not set, disabling.") + log.Warnln(log.ConfigMgr, "SMTP enabled in config but variable data not set, disabling.") } } if c.Communications.TelegramConfig.Enabled { if c.Communications.TelegramConfig.VerificationToken == "" { c.Communications.TelegramConfig.Enabled = false - log.Warn("Telegram enabled in config but variable data not set, disabling.") + log.Warnln(log.ConfigMgr, "Telegram enabled in config but variable data not set, disabling.") } } } @@ -454,7 +454,7 @@ func (c *Config) CheckExchangeAssetsConsistency(exchName string) { storedAssetTypes := exchCfg.CurrencyPairs.GetAssetTypes() for x := range storedAssetTypes { if !exchangeAssetTypes.Contains(storedAssetTypes[x]) { - log.Warnf("%s has non-needed stored asset type %v. Removing..", exchName, storedAssetTypes[x]) + log.Warnf(log.ConfigMgr, "%s has non-needed stored asset type %v. Removing..\n", exchName, storedAssetTypes[x]) exchCfg.CurrencyPairs.Delete(storedAssetTypes[x]) } } @@ -620,7 +620,7 @@ func (c *Config) CheckPairConsistency(exchName string) error { if err != nil { return fmt.Errorf("exchange %s failed to set pairs: %v", exchName, err) } - log.Warnf("Exchange %s: [%v] No enabled pairs found in available pairs, randomly added %v pair.\n", + log.Warnf(log.ExchangeSys, "Exchange %s: [%v] No enabled pairs found in available pairs, randomly added %v pair.\n", exchName, assetTypes[x], newPair) continue } else { @@ -629,7 +629,7 @@ func (c *Config) CheckPairConsistency(exchName string) error { return fmt.Errorf("exchange %s failed to set pairs: %v", exchName, err) } } - log.Warnf("Exchange %s: [%v] Removing enabled pair(s) %v from enabled pairs as it isn't an available pair.", + log.Warnf(log.ExchangeSys, "Exchange %s: [%v] Removing enabled pair(s) %v from enabled pairs as it isn't an available pair.\n", exchName, assetTypes[x], pairsRemoved.Strings()) } return nil @@ -952,7 +952,7 @@ func (c *Config) CheckExchangeConfigValues() error { if c.Exchanges[i].Enabled { if c.Exchanges[i].Name == "" { - log.Error(ErrExchangeNameEmpty, i) + log.Error(log.ConfigMgr, ErrExchangeNameEmpty, i) c.Exchanges[i].Enabled = false continue } @@ -973,46 +973,46 @@ func (c *Config) CheckExchangeConfigValues() error { if failed { c.Exchanges[i].API.AuthenticatedSupport = false c.Exchanges[i].API.AuthenticatedWebsocketSupport = false - log.Warnf(WarningExchangeAuthAPIDefaultOrEmptyValues, c.Exchanges[i].Name) + log.Warnf(log.ExchangeSys, WarningExchangeAuthAPIDefaultOrEmptyValues, c.Exchanges[i].Name) } } if !c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates && !c.Exchanges[i].Features.Supports.WebsocketCapabilities.AutoPairUpdates { lastUpdated := convert.UnixTimestampToTime(c.Exchanges[i].CurrencyPairs.LastUpdated) lastUpdated = lastUpdated.AddDate(0, 0, configPairsLastUpdatedWarningThreshold) if lastUpdated.Unix() <= time.Now().Unix() { - log.Warnf(WarningPairsLastUpdatedThresholdExceeded, c.Exchanges[i].Name, configPairsLastUpdatedWarningThreshold) + log.Warnf(log.ExchangeSys, WarningPairsLastUpdatedThresholdExceeded, c.Exchanges[i].Name, configPairsLastUpdatedWarningThreshold) } } if c.Exchanges[i].HTTPTimeout <= 0 { - log.Warnf("Exchange %s HTTP Timeout value not set, defaulting to %v.", c.Exchanges[i].Name, configDefaultHTTPTimeout) + log.Warnf(log.ExchangeSys, "Exchange %s HTTP Timeout value not set, defaulting to %v.\n", c.Exchanges[i].Name, configDefaultHTTPTimeout) c.Exchanges[i].HTTPTimeout = configDefaultHTTPTimeout } if c.Exchanges[i].HTTPRateLimiter != nil { if c.Exchanges[i].HTTPRateLimiter.Authenticated.Duration < 0 { - log.Warnf("Exchange %s HTTP Rate Limiter authenticated duration set to negative value, defaulting to 0", c.Exchanges[i].Name) + log.Warnf(log.ExchangeSys, "Exchange %s HTTP Rate Limiter authenticated duration set to negative value, defaulting to 0\n", c.Exchanges[i].Name) c.Exchanges[i].HTTPRateLimiter.Authenticated.Duration = 0 } if c.Exchanges[i].HTTPRateLimiter.Authenticated.Rate < 0 { - log.Warnf("Exchange %s HTTP Rate Limiter authenticated rate set to negative value, defaulting to 0", c.Exchanges[i].Name) + log.Warnf(log.ExchangeSys, "Exchange %s HTTP Rate Limiter authenticated rate set to negative value, defaulting to 0\n", c.Exchanges[i].Name) c.Exchanges[i].HTTPRateLimiter.Authenticated.Rate = 0 } if c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Duration < 0 { - log.Warnf("Exchange %s HTTP Rate Limiter unauthenticated duration set to negative value, defaulting to 0", c.Exchanges[i].Name) + log.Warnf(log.ExchangeSys, "Exchange %s HTTP Rate Limiter unauthenticated duration set to negative value, defaulting to 0\n", c.Exchanges[i].Name) c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Duration = 0 } if c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Rate < 0 { - log.Warnf("Exchange %s HTTP Rate Limiter unauthenticated rate set to negative value, defaulting to 0", c.Exchanges[i].Name) + log.Warnf(log.ExchangeSys, "Exchange %s HTTP Rate Limiter unauthenticated rate set to negative value, defaulting to 0\n", c.Exchanges[i].Name) c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Rate = 0 } } err := c.CheckPairConsistency(c.Exchanges[i].Name) if err != nil { - log.Errorf("Exchange %s: CheckPairConsistency error: %s", c.Exchanges[i].Name, err) + log.Errorf(log.ExchangeSys, "Exchange %s: CheckPairConsistency error: %s\n", c.Exchanges[i].Name, err) c.Exchanges[i].Enabled = false continue } @@ -1026,26 +1026,26 @@ func (c *Config) CheckExchangeConfigValues() error { } bankError := false if c.Exchanges[i].BankAccounts[x].BankName == "" || c.Exchanges[i].BankAccounts[x].BankAddress == "" { - log.Warnf("banking details for %s is enabled but variables not set", + log.Warnf(log.ExchangeSys, "banking details for %s is enabled but variables not set\n", c.Exchanges[i].Name) bankError = true } if c.Exchanges[i].BankAccounts[x].AccountName == "" || c.Exchanges[i].BankAccounts[x].AccountNumber == "" { - log.Warnf("banking account details for %s variables not set", + log.Warnf(log.ExchangeSys, "banking account details for %s variables not set\n", c.Exchanges[i].Name) bankError = true } if c.Exchanges[i].BankAccounts[x].SupportedCurrencies == "" { - log.Warnf("banking account details for %s acceptable funding currencies not set", + log.Warnf(log.ExchangeSys, "banking account details for %s acceptable funding currencies not set\n", c.Exchanges[i].Name) bankError = true } if c.Exchanges[i].BankAccounts[x].BSBNumber == "" && c.Exchanges[i].BankAccounts[x].IBAN == "" && c.Exchanges[i].BankAccounts[x].SWIFTCode == "" { - log.Warnf("banking account details for %s critical banking numbers not set", + log.Warnf(log.ExchangeSys, "banking account details for %s critical banking numbers not set\n", c.Exchanges[i].Name) bankError = true } @@ -1075,7 +1075,7 @@ func (c *Config) CheckCurrencyConfigValues() error { for x := range fxProviders { _, err := c.GetForexProviderConfig(fxProviders[x]) if err != nil { - log.Warnf("%s forex provider not found, adding to config..", fxProviders[x]) + log.Warnf(log.Global, "%s forex provider not found, adding to config..\n", fxProviders[x]) c.Currency.ForexProviders = append(c.Currency.ForexProviders, base.Settings{ Name: fxProviders[x], RESTPollingDelay: 600, @@ -1090,7 +1090,7 @@ func (c *Config) CheckCurrencyConfigValues() error { for i := range c.Currency.ForexProviders { if c.Currency.ForexProviders[i].Enabled { if c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey && c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI { - log.Warnf("%s enabled forex provider API key not set. Please set this in your config.json file", c.Currency.ForexProviders[i].Name) + log.Warnf(log.Global, "%s enabled forex provider API key not set. Please set this in your config.json file\n", c.Currency.ForexProviders[i].Name) c.Currency.ForexProviders[i].Enabled = false c.Currency.ForexProviders[i].PrimaryProvider = false continue @@ -1101,7 +1101,7 @@ func (c *Config) CheckCurrencyConfigValues() error { c.Currency.ForexProviders[i].PrimaryProvider && (c.Currency.ForexProviders[i].APIKey == "" || c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey) { - log.Warnf("CurrencyConverter forex provider no longer supports unset API key requests. Switching to ExchangeRates FX provider..") + log.Warnln(log.Global, "CurrencyConverter forex provider no longer supports unset API key requests. Switching to ExchangeRates FX provider..") c.Currency.ForexProviders[i].Enabled = false c.Currency.ForexProviders[i].PrimaryProvider = false c.Currency.ForexProviders[i].APIKey = DefaultUnsetAPIKey @@ -1111,7 +1111,7 @@ func (c *Config) CheckCurrencyConfigValues() error { } if c.Currency.ForexProviders[i].APIKeyLvl == -1 && c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI { - log.Warnf("%s APIKey Level not set, functions limited. Please set this in your config.json file", + log.Warnf(log.Global, "%s APIKey Level not set, functions limited. Please set this in your config.json file\n", c.Currency.ForexProviders[i].Name) } count++ @@ -1123,7 +1123,7 @@ func (c *Config) CheckCurrencyConfigValues() error { if c.Currency.ForexProviders[x].Name == DefaultForexProviderExchangeRatesAPI { c.Currency.ForexProviders[x].Enabled = true c.Currency.ForexProviders[x].PrimaryProvider = true - log.Warn("Using ExchangeRatesAPI for default forex provider.") + log.Warnln(log.ConfigMgr, "Using ExchangeRatesAPI for default forex provider.") } } } @@ -1139,11 +1139,11 @@ func (c *Config) CheckCurrencyConfigValues() error { if c.Currency.CryptocurrencyProvider.Enabled { if c.Currency.CryptocurrencyProvider.APIkey == "" || c.Currency.CryptocurrencyProvider.APIkey == DefaultUnsetAPIKey { - log.Warnf("CryptocurrencyProvider enabled but api key is unset please set this in your config.json file") + log.Warnln(log.ConfigMgr, "CryptocurrencyProvider enabled but api key is unset please set this in your config.json file") } if c.Currency.CryptocurrencyProvider.AccountPlan == "" || c.Currency.CryptocurrencyProvider.AccountPlan == DefaultUnsetAccountPlan { - log.Warnf("CryptocurrencyProvider enabled but account plan is unset please set this in your config.json file") + log.Warnln(log.ConfigMgr, "CryptocurrencyProvider enabled but account plan is unset please set this in your config.json file") } } else { if c.Currency.CryptocurrencyProvider.APIkey == "" { @@ -1248,34 +1248,34 @@ func (c *Config) CheckLoggerConfig() error { m.Lock() defer m.Unlock() - // check if enabled is nil or level is a blank string - if c.Logging.Enabled == nil || c.Logging.Level == "" { - // Creates a new pointer to bool and sets it as true - t := func(t bool) *bool { return &t }(true) - - log.Warn("Missing or invalid config settings using safe defaults") - - // Set logger to safe defaults - c.Logging = log.Logging{ - Enabled: t, - Level: "DEBUG|INFO|WARN|ERROR|FATAL", - ColourOutput: false, - File: "debug.txt", - Rotate: false, - } - log.Logger = &c.Logging - } else { - log.Logger = &c.Logging + if c.Logging.Enabled == nil || c.Logging.Output == "" { + c.Logging = log.GenDefaultSettings() } - if len(c.Logging.File) > 0 { - logPath := filepath.Join(common.GetDefaultDataDir(runtime.GOOS), "logs") - err := common.CreateDir(logPath) - if err != nil { - return err + f := func(f bool) *bool { return &f }(false) + + if c.Logging.LoggerFileConfig != nil { + if c.Logging.LoggerFileConfig.FileName == "" { + c.Logging.LoggerFileConfig.FileName = "log.txt" } - log.LogPath = logPath + if c.Logging.LoggerFileConfig.Rotate == nil { + c.Logging.LoggerFileConfig.Rotate = f + } + if c.Logging.LoggerFileConfig.MaxSize < 0 { + c.Logging.LoggerFileConfig.MaxSize = 100 + } + log.FileLoggingConfiguredCorrectly = true } + + log.GlobalLogConfig = &c.Logging + + logPath := filepath.Join(common.GetDefaultDataDir(runtime.GOOS), "logs") + err := common.CreateDir(logPath) + if err != nil { + return err + } + log.LogPath = logPath + return nil } @@ -1295,7 +1295,7 @@ func (c *Config) CheckNTPConfig() { } if len(c.NTPClient.Pool) < 1 { - log.Warn("NTPClient enabled with no servers configured, enabling default pool.") + log.Warnln(log.ConfigMgr, "NTPClient enabled with no servers configured, enabling default pool.") c.NTPClient.Pool = []string{"pool.ntp.org:123"} } } @@ -1306,8 +1306,8 @@ func (c *Config) DisableNTPCheck(input io.Reader) (string, error) { defer m.Unlock() reader := bufio.NewReader(input) - log.Warn("Your system time is out of sync, this may cause issues with trading.") - log.Warn("How would you like to show future notifications? (a)lert / (w)arn / (d)isable. \n") + log.Warnln(log.ConfigMgr, "Your system time is out of sync, this may cause issues with trading.") + log.Warnln(log.ConfigMgr, "How would you like to show future notifications? (a)lert / (w)arn / (d)isable.") var answered = false for ok := true; ok; ok = (!answered) { @@ -1395,13 +1395,13 @@ func GetFilePath(file string) (string, error) { if err != nil { return "", err } - log.Debugf("Renamed old config file %s to %s", oldDirs[x], newDirs[0]) + log.Debugf(log.ConfigMgr, "Renamed old config file %s to %s\n", oldDirs[x], newDirs[0]) } else { err = os.Rename(oldDirs[x], newDirs[1]) if err != nil { return "", err } - log.Debugf("Renamed old config file %s to %s", oldDirs[x], newDirs[1]) + log.Debugf(log.ConfigMgr, "Renamed old config file %s to %s\n", oldDirs[x], newDirs[1]) } } @@ -1484,7 +1484,7 @@ func (c *Config) ReadConfig(configPath string) error { } key, err := PromptForConfigKey(IsInitialSetup) if err != nil { - log.Errorf("PromptForConfigKey err: %s", err) + log.Errorf(log.ConfigMgr, "PromptForConfigKey err: %s", err) errCounter++ continue } @@ -1493,7 +1493,7 @@ func (c *Config) ReadConfig(configPath string) error { f = append(f, file...) data, err := DecryptConfigFile(f, key) if err != nil { - log.Errorf("DecryptConfigFile err: %s", err) + log.Errorf(log.ConfigMgr, "DecryptConfigFile err: %s", err) errCounter++ continue } @@ -1501,7 +1501,7 @@ func (c *Config) ReadConfig(configPath string) error { err = ConfirmConfigJSON(data, &c) if err != nil { if errCounter < configMaxAuthFailures { - log.Errorf("Invalid password.") + log.Error(log.ConfigMgr, "Invalid password.") } errCounter++ continue @@ -1591,7 +1591,7 @@ func (c *Config) CheckRemoteControlConfig() { func (c *Config) CheckConfig() error { err := c.CheckLoggerConfig() if err != nil { - log.Errorf("Failed to configure logger. Err: %s", err) + log.Errorf(log.ConfigMgr, "Failed to configure logger, some logging features unavailable: %s\n", err) } err = c.CheckExchangeConfigValues() @@ -1610,7 +1610,7 @@ func (c *Config) CheckConfig() error { } if c.GlobalHTTPTimeout <= 0 { - log.Warnf("Global HTTP Timeout value not set, defaulting to %v.", configDefaultHTTPTimeout) + log.Warnf(log.ConfigMgr, "Global HTTP Timeout value not set, defaulting to %v.\n", configDefaultHTTPTimeout) c.GlobalHTTPTimeout = configDefaultHTTPTimeout } diff --git a/config/config_encryption.go b/config/config_encryption.go index b0ac465f..416a8ee1 100644 --- a/config/config_encryption.go +++ b/config/config_encryption.go @@ -11,7 +11,6 @@ import ( "github.com/thrasher-/gocryptotrader/common" gctcrypto "github.com/thrasher-/gocryptotrader/common/crypto" - log "github.com/thrasher-/gocryptotrader/logger" "golang.org/x/crypto/scrypt" ) @@ -34,7 +33,7 @@ var ( // PromptForConfigEncryption asks for encryption key func (c *Config) PromptForConfigEncryption() bool { - log.Infof("Would you like to encrypt your config file (y/n)?") + fmt.Println("Would you like to encrypt your config file (y/n)?") input := "" _, err := fmt.Scanln(&input) @@ -55,7 +54,7 @@ func PromptForConfigKey(initialSetup bool) ([]byte, error) { var cryptoKey []byte for { - log.Println("Please enter in your password: ") + fmt.Println("Please enter in your password: ") pwPrompt := func(i *[]byte) error { _, err := fmt.Scanln(i) return err @@ -73,7 +72,7 @@ func PromptForConfigKey(initialSetup bool) ([]byte, error) { } var p2 []byte - log.Println("Please re-enter your password: ") + fmt.Println("Please re-enter your password: ") err = pwPrompt(&p2) if err != nil { return nil, err @@ -83,7 +82,7 @@ func PromptForConfigKey(initialSetup bool) ([]byte, error) { cryptoKey = p1 break } - log.Printf("Passwords did not match, please try again.") + fmt.Printf("Passwords did not match, please try again.") } return cryptoKey, nil } diff --git a/config/config_test.go b/config/config_test.go index c4917dd7..88600fb6 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -855,7 +855,7 @@ func TestCheckLoggerConfig(t *testing.T) { if err != nil { t.Fatal(err) } - c.Logging = log.Logging{} + c.Logging = log.Config{} err = c.CheckLoggerConfig() if err != nil { t.Errorf("Failed to create default logger reason: %v", err) diff --git a/config/config_types.go b/config/config_types.go index b4e82549..9df0c36c 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -16,7 +16,7 @@ type Config struct { Name string `json:"name"` EncryptConfig int `json:"encryptConfig"` GlobalHTTPTimeout time.Duration `json:"globalHTTPTimeout"` - Logging log.Logging `json:"logging"` + Logging log.Config `json:"logging"` ConnectionMonitor ConnectionMonitorConfig `json:"connectionMonitor"` Profiler ProfilerConfig `json:"profiler"` NTPClient NTPClientConfig `json:"ntpclient"` diff --git a/config_example.json b/config_example.json index 621dd928..f04556a5 100644 --- a/config_example.json +++ b/config_example.json @@ -4,10 +4,24 @@ "globalHTTPTimeout": 15000000000, "logging": { "enabled": true, - "file": "debug.txt", - "colour": false, - "level": "DEBUG|WARN|INFO|ERROR|FATAL", - "rotate": false + "level": "INFO|WARN|DEBUG|ERROR", + "output": "console", + "fileSettings": { + "filename": "log.txt", + "rotate": true, + "maxsize": 250 + }, + "advancedSettings": { + "spacer": " | ", + "timeStampFormat": "02/01/2006 15:04:05", + "headers": { + "info": "[INFO] ", + "warn": "[WARN] ", + "debug": "[DEBUG]", + "error": "[ERROR]" + } + }, + "subloggers": [] }, "profiler": { "enabled": false @@ -1322,4 +1336,4 @@ "checkInterval": 1000000000 }, "fiatDispayCurrency": "" -} \ No newline at end of file +} diff --git a/connchecker/connchecker.go b/connchecker/connchecker.go index 6c9c4f99..cb523e8a 100644 --- a/connchecker/connchecker.go +++ b/connchecker/connchecker.go @@ -53,9 +53,9 @@ func New(dnsList, domainList []string, checkInterval time.Duration) (*Checker, e } if c.connected { - log.Debug(ConnFound) + log.Debugln(log.Global, ConnFound) } else { - log.Warnf(ConnNotFound) + log.Warnln(log.Global, ConnNotFound) } c.shutdown = make(chan struct{}, 1) @@ -137,7 +137,7 @@ func (c *Checker) connectionTest() { if err == nil { c.Lock() if !c.connected { - log.Debug(ConnRe) + log.Debugln(log.Global, ConnRe) c.connected = true } c.Unlock() @@ -150,7 +150,7 @@ func (c *Checker) connectionTest() { if err == nil { c.Lock() if !c.connected { - log.Debug(ConnRe) + log.Debugln(log.Global, ConnRe) c.connected = true } c.Unlock() @@ -160,7 +160,7 @@ func (c *Checker) connectionTest() { c.Lock() if c.connected { - log.Warn(ConnLost) + log.Warnln(log.Global, ConnLost) c.connected = false } c.Unlock() diff --git a/currency/coinmarketcap/coinmarketcap.go b/currency/coinmarketcap/coinmarketcap.go index d57a68af..b22fe52d 100644 --- a/currency/coinmarketcap/coinmarketcap.go +++ b/currency/coinmarketcap/coinmarketcap.go @@ -86,7 +86,7 @@ func (c *Coinmarketcap) Setup(conf Settings) { } else { err := c.SetAccountPlan(conf.AccountPlan) if err != nil { - log.Errorf("CoinMarketCap enabled but SetAccountPlan failed. Err: %s", err) + log.Errorf(log.Global, "CoinMarketCap enabled but SetAccountPlan failed. Err: %s\n", err) return } c.Enabled = true diff --git a/currency/coinmarketcap/coinmarketcap_test.go b/currency/coinmarketcap/coinmarketcap_test.go index 336f8512..cd6492a3 100644 --- a/currency/coinmarketcap/coinmarketcap_test.go +++ b/currency/coinmarketcap/coinmarketcap_test.go @@ -20,7 +20,7 @@ const ( func areAPICredtionalsSet(minAllowable uint8) bool { if apiAccountPlanLevel != "" && apikey != "" { if err := c.CheckAccountPlan(minAllowable); err != nil { - log.Warn("coinmarketpcap test suite - account plan not allowed for function, please review or upgrade plan to test") + log.Warn(log.Global, "coinmarketpcap test suite - account plan not allowed for function, please review or upgrade plan to test") return false } return true diff --git a/currency/conversion.go b/currency/conversion.go index 5743b4f7..abc0316b 100644 --- a/currency/conversion.go +++ b/currency/conversion.go @@ -76,13 +76,15 @@ func (c *ConversionRates) Register(from, to Code) (Conversion, error) { p, ok := c.m[from.Item][to.Item] if !ok { - log.Errorf("currency conversion rate not found from %s to %s", from, to) + log.Errorf(log.Global, + "currency conversion rate not found from %s to %s\n", from, to) return Conversion{}, errors.New("no rate found") } i, ok := c.m[to.Item][from.Item] if !ok { - log.Errorf("currency conversion inversion rate not found from %s to %s", + log.Errorf(log.Global, + "currency conversion inversion rate not found from %s to %s\n", to, from) return Conversion{}, errors.New("no rate found") @@ -100,7 +102,7 @@ func (c *ConversionRates) Update(m map[string]float64) error { } if storage.IsVerbose() { - log.Debug("Conversion rates are being updated.") + log.Debugln(log.Global, "Conversion rates are being updated.") } solidvalues := make(map[Code]map[Code]float64) @@ -197,7 +199,8 @@ func (c *ConversionRates) Update(m map[string]float64) error { crossRate = 1 / v } if storage.IsVerbose() { - log.Debugf("Conversion from %s to %s deriving cross rate value %f", + log.Debugf(log.Global, + "Conversion from %s to %s deriving cross rate value %f\n", base, term, crossRate) diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go index d63082db..9ed79b8f 100644 --- a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go @@ -76,7 +76,7 @@ func (c *CurrencyConverter) GetRates(baseCurrency, symbols string) (map[string]f batch := completedStrings[i : i+2] result, err := c.ConvertMany(batch) if err != nil { - log.Errorf("Failed to get batch err: %s", err) + log.Errorf(log.Global, "Failed to get batch err: %s\n", err) continue } for k, v := range result { diff --git a/currency/forexprovider/currencylayer/currencylayer.go b/currency/forexprovider/currencylayer/currencylayer.go index 45f33592..570ebcb2 100644 --- a/currency/forexprovider/currencylayer/currencylayer.go +++ b/currency/forexprovider/currencylayer/currencylayer.go @@ -57,7 +57,8 @@ type CurrencyLayer struct { // Setup sets appropriate values for CurrencyLayer func (c *CurrencyLayer) Setup(config base.Settings) error { if config.APIKeyLvl < 0 || config.APIKeyLvl > 3 { - log.Errorf("apikey incorrectly set in config.json for %s, please set appropriate account levels", + log.Errorf(log.Global, + "apikey incorrectly set in config.json for %s, please set appropriate account levels\n", config.Name) return errors.New("apikey set failure") } diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go index 5a5289a6..076abb09 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go @@ -67,7 +67,8 @@ func cleanCurrencies(baseCurrency, symbols string) string { // remove and warn about any unsupported currencies if !strings.Contains(exchangeRatesSupportedCurrencies, x) { // nolint:gocritic - log.Warnf("Forex provider ExchangeRatesAPI does not support currency %s, removing from forex rates query.", x) + log.Warnf(log.Global, + "Forex provider ExchangeRatesAPI does not support currency %s, removing from forex rates query.\n", x) continue } cleanedCurrencies = append(cleanedCurrencies, x) diff --git a/currency/forexprovider/fixer.io/fixer.go b/currency/forexprovider/fixer.io/fixer.go index 85848cce..bcfda588 100644 --- a/currency/forexprovider/fixer.io/fixer.go +++ b/currency/forexprovider/fixer.io/fixer.go @@ -51,7 +51,8 @@ type Fixer struct { // Setup sets appropriate values for fixer object func (f *Fixer) Setup(config base.Settings) error { if config.APIKeyLvl < 0 || config.APIKeyLvl > 4 { - log.Errorf("apikey incorrectly set in config.json for %s, please set appropriate account levels", + log.Errorf(log.Global, + "apikey incorrectly set in config.json for %s, please set appropriate account levels\n", config.Name) return errors.New("apikey set failure") } diff --git a/currency/forexprovider/openexchangerates/openexchangerates.go b/currency/forexprovider/openexchangerates/openexchangerates.go index 6ac7cfcf..2d4c0b2a 100644 --- a/currency/forexprovider/openexchangerates/openexchangerates.go +++ b/currency/forexprovider/openexchangerates/openexchangerates.go @@ -65,7 +65,8 @@ type OXR struct { // Setup sets values for the OXR object func (o *OXR) Setup(config base.Settings) error { if config.APIKeyLvl < 0 || config.APIKeyLvl > 2 { - log.Errorf("apikey incorrectly set in config.json for %s, please set appropriate account levels", + log.Errorf(log.Global, + "apikey incorrectly set in config.json for %s, please set appropriate account levels\n", config.Name) return errors.New("apikey set failure") } diff --git a/currency/pairs.go b/currency/pairs.go index c6882e66..536faf03 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -51,7 +51,8 @@ func (p Pairs) Format(delimiter, index string, uppercase bool) Pairs { if index != "" { newP, err := NewPairFromIndex(p[i].String(), index) if err != nil { - log.Errorf("failed to create NewPairFromIndex. Err: %s", err) + log.Errorf(log.Global, + "failed to create NewPairFromIndex. Err: %s\n", err) continue } formattedPair.Base = newP.Base diff --git a/currency/storage.go b/currency/storage.go index 87b1a5f1..e4145798 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -113,10 +113,13 @@ func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration return errors.New("currency storage error, no fiat display currency set in config") } s.baseCurrency = settings.FiatDisplayCurrency - log.Debugf("Fiat display currency: %s.", s.baseCurrency) + log.Debugf(log.Global, + "Fiat display currency: %s.\n", s.baseCurrency) if settings.CryptocurrencyProvider.Enabled { - log.Debugf("Setting up currency analysis system with Coinmarketcap...") + log.Debugln( + log.Global, + "Setting up currency analysis system with Coinmarketcap...") c := &coinmarketcap.Coinmarketcap{} c.SetDefaults() c.Setup(coinmarketcap.Settings{ @@ -200,11 +203,13 @@ func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration return err } - log.Debugf("Primary foreign exchange conversion provider %s enabled", + log.Debugf(log.Global, + "Primary foreign exchange conversion provider %s enabled\n", s.fiatExchangeMarkets.Primary.Provider.GetName()) for i := range s.fiatExchangeMarkets.Support { - log.Debugf("Support forex conversion provider %s enabled", + log.Debugf(log.Global, + "Support forex conversion provider %s enabled\n", s.fiatExchangeMarkets.Support[i].Provider.GetName()) } @@ -212,7 +217,8 @@ func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration // until this system initially updates go s.ForeignExchangeUpdater() } else { - log.Warnf("No foreign exchange providers enabled in config.json") + log.Warnln(log.Global, + "No foreign exchange providers enabled in config.json") s.mtx.Unlock() } @@ -258,19 +264,20 @@ func (s *Storage) SetupForexProviders(setting ...base.Settings) error { // ForeignExchangeUpdater is a routine that seeds foreign exchange rate and keeps // updated as fast as possible func (s *Storage) ForeignExchangeUpdater() { - log.Debugf("Foreign exchange updater started, seeding FX rate list..") + log.Debugln(log.Global, + "Foreign exchange updater started, seeding FX rate list..") s.wg.Add(1) defer s.wg.Done() err := s.SeedCurrencyAnalysisData() if err != nil { - log.Error(err) + log.Errorln(log.Global, err) } err = s.SeedForeignExchangeRates() if err != nil { - log.Error(err) + log.Errorln(log.Global, err) } // Unlock main rate retrieval mutex so all routines waiting can get access @@ -291,13 +298,13 @@ func (s *Storage) ForeignExchangeUpdater() { case <-SeedForeignExchangeTick.C: err := s.SeedForeignExchangeRates() if err != nil { - log.Error(err) + log.Errorln(log.Global, err) } case <-SeedCurrencyAnalysisTick.C: err := s.SeedCurrencyAnalysisData() if err != nil { - log.Error(err) + log.Errorln(log.Global, err) } } } @@ -344,7 +351,8 @@ func (s *Storage) SeedCurrencyAnalysisData() error { // loads it into memory func (s *Storage) FetchCurrencyAnalysisData() error { if s.currencyAnalysis == nil { - log.Warn("Currency analysis system offline, please set api keys for coinmarketcap if you wish to use this feature.") + log.Warnln(log.Global, + "Currency analysis system offline, please set api keys for coinmarketcap if you wish to use this feature.") return errors.New("currency analysis system offline") } diff --git a/engine/addr_helpers_test.go b/engine/addr_helpers_test.go index 34f29a25..e4053a1c 100644 --- a/engine/addr_helpers_test.go +++ b/engine/addr_helpers_test.go @@ -1,64 +1,64 @@ -package engine - -import ( - "testing" - - "github.com/thrasher-/gocryptotrader/currency" -) - -const ( - testBTCAddress = "1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" -) - -func TestSeed(t *testing.T) { - var d DepositAddressStore - u := map[string]map[string]string{ - "BITSTAMP": map[string]string{ - "BTC": testBTCAddress, - }, - } - - d.Seed(u) - r, err := d.GetDepositAddress("BITSTAMP", currency.BTC) - if err != nil { - t.Error("unexpected result") - } - - if r != testBTCAddress { - t.Error("unexpected result") - } -} - -func TestGetDepositAddress(t *testing.T) { - var d DepositAddressStore - _, err := d.GetDepositAddress("", currency.BTC) - if err != ErrDepositAddressStoreIsNil { - t.Error("non-error on non-existent exchange") - } - - d.Store = map[string]map[string]string{ - "BITSTAMP": map[string]string{ - "BTC": testBTCAddress, - }, - } - - _, err = d.GetDepositAddress("", currency.BTC) - if err != ErrExchangeNotFound { - t.Error("non-error on non-existent exchange") - } - - var r string - r, err = d.GetDepositAddress("BiTStAmP", currency.NewCode("bTC")) - if err != nil { - t.Error("unexpected err: ", err) - } - - if r != testBTCAddress { - t.Error("unexpected BTC address: ", r) - } - - _, err = d.GetDepositAddress("BiTStAmP", currency.LTC) - if err != ErrDepositAddressNotFound { - t.Error("unexpected err: ", err) - } -} +package engine + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/currency" +) + +const ( + testBTCAddress = "1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" +) + +func TestSeed(t *testing.T) { + var d DepositAddressStore + u := map[string]map[string]string{ + "BITSTAMP": { + "BTC": testBTCAddress, + }, + } + + d.Seed(u) + r, err := d.GetDepositAddress("BITSTAMP", currency.BTC) + if err != nil { + t.Error("unexpected result") + } + + if r != testBTCAddress { + t.Error("unexpected result") + } +} + +func TestGetDepositAddress(t *testing.T) { + var d DepositAddressStore + _, err := d.GetDepositAddress("", currency.BTC) + if err != ErrDepositAddressStoreIsNil { + t.Error("non-error on non-existent exchange") + } + + d.Store = map[string]map[string]string{ + "BITSTAMP": { + "BTC": testBTCAddress, + }, + } + + _, err = d.GetDepositAddress("", currency.BTC) + if err != ErrExchangeNotFound { + t.Error("non-error on non-existent exchange") + } + + var r string + r, err = d.GetDepositAddress("BiTStAmP", currency.NewCode("bTC")) + if err != nil { + t.Error("unexpected err: ", err) + } + + if r != testBTCAddress { + t.Error("unexpected BTC address: ", r) + } + + _, err = d.GetDepositAddress("BiTStAmP", currency.LTC) + if err != ErrDepositAddressNotFound { + t.Error("unexpected err: ", err) + } +} diff --git a/engine/comms_relayer.go b/engine/comms_relayer.go index d1a94309..e1a647f4 100644 --- a/engine/comms_relayer.go +++ b/engine/comms_relayer.go @@ -33,7 +33,7 @@ func (c *commsManager) Start() (err error) { } }() - log.Debugln("Communications manager starting...") + log.Debugln(log.CommunicationMgr, "Communications manager starting...") commsCfg := Bot.Config.GetCommunicationsConfig() c.comms, err = communications.NewComm(&commsCfg) if err != nil { @@ -43,7 +43,7 @@ func (c *commsManager) Start() (err error) { c.shutdown = make(chan struct{}) c.relayMsg = make(chan base.Event) go c.run() - log.Debugln("Communications manager started.") + log.Debugln(log.CommunicationMgr, "Communications manager started.") return nil } @@ -64,7 +64,7 @@ func (c *commsManager) Stop() error { } close(c.shutdown) - log.Debugln("Communications manager shutting down...") + log.Debugln(log.CommunicationMgr, "Communications manager shutting down...") return nil } @@ -80,7 +80,7 @@ func (c *commsManager) run() { // TO-DO shutdown comms connections for connected services (Slack etc) atomic.CompareAndSwapInt32(&c.stopped, 1, 0) atomic.CompareAndSwapInt32(&c.started, 1, 0) - log.Debugln("Communications manager shutdown.") + log.Debugln(log.CommunicationMgr, "Communications manager shutdown.") }() for { diff --git a/engine/connection.go b/engine/connection.go index 0b16c8e3..1f931deb 100644 --- a/engine/connection.go +++ b/engine/connection.go @@ -24,7 +24,7 @@ func (c *connectionManager) Start() error { return errors.New("connection manager already started") } - log.Debugln("Connection manager starting...") + log.Debugln(log.ConnectionMgr, "Connection manager starting...") var err error c.conn, err = connchecker.New(Bot.Config.ConnectionMonitor.DNSList, Bot.Config.ConnectionMonitor.PublicDomainList, @@ -34,7 +34,7 @@ func (c *connectionManager) Start() error { return err } - log.Debugln("Connection manager started.") + log.Debugln(log.ConnectionMgr, "Connection manager started.") return nil } @@ -47,17 +47,17 @@ func (c *connectionManager) Stop() error { return errors.New("connection manager is already stopped") } - log.Debugln("Connection manager shutting down...") + log.Debugln(log.ConnectionMgr, "Connection manager shutting down...") c.conn.Shutdown() atomic.CompareAndSwapInt32(&c.stopped, 1, 0) atomic.CompareAndSwapInt32(&c.started, 1, 0) - log.Debugln("Connection manager stopped.") + log.Debugln(log.ConnectionMgr, "Connection manager stopped.") return nil } func (c *connectionManager) IsOnline() bool { if c.conn == nil { - log.Warnf("Connection manager: IsOnline called but conn is nil") + log.Warnln(log.ConnectionMgr, "Connection manager: IsOnline called but conn is nil") return false } diff --git a/engine/engine.go b/engine/engine.go index 8c3d654e..0604aa9d 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -72,7 +72,8 @@ func NewFromSettings(settings *Settings) (*Engine, error) { var b Engine b.Config = &config.Cfg - log.Debugf("Loading config file %s...\n", settings.ConfigFile) + + log.Debugf(log.Global, "Loading config file %s..\n", settings.ConfigFile) err := b.Config.LoadConfig(settings.ConfigFile) if err != nil { return nil, fmt.Errorf("failed to load config. Err: %s", err) @@ -83,19 +84,14 @@ func NewFromSettings(settings *Settings) (*Engine, error) { return nil, fmt.Errorf("failed to open/create data directory: %s. Err: %s", settings.DataDir, err) } - err = log.SetupLogger() - if err != nil { - log.Errorf("Failed to setup logger. Err: %s", err) + if *b.Config.Logging.Enabled { + log.SetupGlobalLogger() + log.SetupSubLoggers(b.Config.Logging.SubLoggers) } b.Settings.ConfigFile = settings.ConfigFile b.Settings.DataDir = settings.DataDir - if *log.Logger.Enabled { - b.Settings.LogFile = log.LogPath - log.Debugf("Using log file: %s.\n", log.LogPath) - } - err = utils.AdjustGoMaxProcs(settings.GoMaxProcs) if err != nil { return nil, fmt.Errorf("unable to adjust runtime GOMAXPROCS value. Err: %s", err) @@ -209,100 +205,101 @@ func ValidateSettings(b *Engine, s *Settings) { // PrintSettings returns the engine settings func PrintSettings(s *Settings) { - log.Debugln() - log.Debugf("ENGINE SETTINGS") - log.Debugf("- CORE SETTINGS:") - log.Debugf("\t Verbose mode: %v", s.Verbose) - log.Debugf("\t Enable dry run mode: %v", s.EnableDryRun) - log.Debugf("\t Enable all exchanges: %v", s.EnableAllExchanges) - log.Debugf("\t Enable all pairs: %v", s.EnableAllPairs) - log.Debugf("\t Enable coinmarketcap analaysis: %v", s.EnableCoinmarketcapAnalysis) - log.Debugf("\t Enable portfolio manager: %v", s.EnablePortfolioManager) - log.Debugf("\t Enable gPRC: %v", s.EnableGRPC) - log.Debugf("\t Enable gRPC Proxy: %v", s.EnableGRPCProxy) - log.Debugf("\t Enable websocket RPC: %v", s.EnableWebsocketRPC) - log.Debugf("\t Enable deprecated RPC: %v", s.EnableDeprecatedRPC) - log.Debugf("\t Enable comms relayer: %v", s.EnableCommsRelayer) - log.Debugf("\t Enable event manager: %v", s.EnableEventManager) - log.Debugf("\t Event manager sleep delay: %v", s.EventManagerDelay) - log.Debugf("\t Enable order manager: %v", s.EnableOrderManager) - log.Debugf("\t Enable exchange sync manager: %v", s.EnableExchangeSyncManager) - log.Debugf("\t Enable deposit address manager: %v\n", s.EnableDepositAddressManager) - log.Debugf("\t Enable ticker syncing: %v", s.EnableTickerSyncing) - log.Debugf("\t Enable orderbook syncing: %v", s.EnableOrderbookSyncing) - log.Debugf("\t Enable websocket routine: %v\n", s.EnableWebsocketRoutine) - log.Debugf("\t Enable NTP client: %v", s.EnableNTPClient) - log.Debugf("- FOREX SETTINGS:") - log.Debugf("\t Enable currency conveter: %v", s.EnableCurrencyConverter) - log.Debugf("\t Enable currency layer: %v", s.EnableCurrencyLayer) - log.Debugf("\t Enable fixer: %v", s.EnableFixer) - log.Debugf("\t Enable OpenExchangeRates: %v", s.EnableOpenExchangeRates) - log.Debugf("- EXCHANGE SETTINGS:") - log.Debugf("\t Enable exchange auto pair updates: %v", s.EnableExchangeAutoPairUpdates) - log.Debugf("\t Disable all exchange auto pair updates: %v", s.DisableExchangeAutoPairUpdates) - log.Debugf("\t Enable exchange websocket support: %v", s.EnableExchangeWebsocketSupport) - log.Debugf("\t Enable exchange verbose mode: %v", s.EnableExchangeVerbose) - log.Debugf("\t Enable exchange HTTP rate limiter: %v", s.EnableExchangeHTTPRateLimiter) - log.Debugf("\t Enable exchange HTTP debugging: %v", s.EnableExchangeHTTPDebugging) - log.Debugf("\t Exchange max HTTP request jobs: %v", s.MaxHTTPRequestJobsLimit) - log.Debugf("\t Exchange HTTP request timeout retry amount: %v", s.RequestTimeoutRetryAttempts) - log.Debugf("\t Exchange HTTP timeout: %v", s.ExchangeHTTPTimeout) - log.Debugf("\t Exchange HTTP user agent: %v", s.ExchangeHTTPUserAgent) - log.Debugf("\t Exchange HTTP proxy: %v\n", s.ExchangeHTTPProxy) - log.Debugf("- COMMON SETTINGS:") - log.Debugf("\t Global HTTP timeout: %v", s.GlobalHTTPTimeout) - log.Debugf("\t Global HTTP user agent: %v", s.GlobalHTTPUserAgent) - log.Debugf("\t Global HTTP proxy: %v", s.ExchangeHTTPProxy) - log.Debugln() + log.Debugln(log.Global) + log.Debugf(log.Global, "ENGINE SETTINGS") + log.Debugf(log.Global, "- CORE SETTINGS:") + log.Debugf(log.Global, "\t Verbose mode: %v", s.Verbose) + log.Debugf(log.Global, "\t Enable dry run mode: %v", s.EnableDryRun) + log.Debugf(log.Global, "\t Enable all exchanges: %v", s.EnableAllExchanges) + log.Debugf(log.Global, "\t Enable all pairs: %v", s.EnableAllPairs) + log.Debugf(log.Global, "\t Enable coinmarketcap analaysis: %v", s.EnableCoinmarketcapAnalysis) + log.Debugf(log.Global, "\t Enable portfolio manager: %v", s.EnablePortfolioManager) + log.Debugf(log.Global, "\t Enable gPRC: %v", s.EnableGRPC) + log.Debugf(log.Global, "\t Enable gRPC Proxy: %v", s.EnableGRPCProxy) + log.Debugf(log.Global, "\t Enable websocket RPC: %v", s.EnableWebsocketRPC) + log.Debugf(log.Global, "\t Enable deprecated RPC: %v", s.EnableDeprecatedRPC) + log.Debugf(log.Global, "\t Enable comms relayer: %v", s.EnableCommsRelayer) + log.Debugf(log.Global, "\t Enable event manager: %v", s.EnableEventManager) + log.Debugf(log.Global, "\t Event manager sleep delay: %v", s.EventManagerDelay) + log.Debugf(log.Global, "\t Enable order manager: %v", s.EnableOrderManager) + log.Debugf(log.Global, "\t Enable exchange sync manager: %v", s.EnableExchangeSyncManager) + log.Debugf(log.Global, "\t Enable deposit address manager: %v\n", s.EnableDepositAddressManager) + log.Debugf(log.Global, "\t Enable ticker syncing: %v", s.EnableTickerSyncing) + log.Debugf(log.Global, "\t Enable orderbook syncing: %v", s.EnableOrderbookSyncing) + log.Debugf(log.Global, "\t Enable websocket routine: %v\n", s.EnableWebsocketRoutine) + log.Debugf(log.Global, "\t Enable NTP client: %v", s.EnableNTPClient) + log.Debugf(log.Global, "- FOREX SETTINGS:") + log.Debugf(log.Global, "\t Enable currency conveter: %v", s.EnableCurrencyConverter) + log.Debugf(log.Global, "\t Enable currency layer: %v", s.EnableCurrencyLayer) + log.Debugf(log.Global, "\t Enable fixer: %v", s.EnableFixer) + log.Debugf(log.Global, "\t Enable OpenExchangeRates: %v", s.EnableOpenExchangeRates) + log.Debugf(log.Global, "- EXCHANGE SETTINGS:") + log.Debugf(log.Global, "\t Enable exchange auto pair updates: %v", s.EnableExchangeAutoPairUpdates) + log.Debugf(log.Global, "\t Disable all exchange auto pair updates: %v", s.DisableExchangeAutoPairUpdates) + log.Debugf(log.Global, "\t Enable exchange websocket support: %v", s.EnableExchangeWebsocketSupport) + log.Debugf(log.Global, "\t Enable exchange verbose mode: %v", s.EnableExchangeVerbose) + log.Debugf(log.Global, "\t Enable exchange HTTP rate limiter: %v", s.EnableExchangeHTTPRateLimiter) + log.Debugf(log.Global, "\t Enable exchange HTTP debugging: %v", s.EnableExchangeHTTPDebugging) + log.Debugf(log.Global, "\t Exchange max HTTP request jobs: %v", s.MaxHTTPRequestJobsLimit) + log.Debugf(log.Global, "\t Exchange HTTP request timeout retry amount: %v", s.RequestTimeoutRetryAttempts) + log.Debugf(log.Global, "\t Exchange HTTP timeout: %v", s.ExchangeHTTPTimeout) + log.Debugf(log.Global, "\t Exchange HTTP user agent: %v", s.ExchangeHTTPUserAgent) + log.Debugf(log.Global, "\t Exchange HTTP proxy: %v\n", s.ExchangeHTTPProxy) + log.Debugf(log.Global, "- COMMON SETTINGS:") + log.Debugf(log.Global, "\t Global HTTP timeout: %v", s.GlobalHTTPTimeout) + log.Debugf(log.Global, "\t Global HTTP user agent: %v", s.GlobalHTTPUserAgent) + log.Debugf(log.Global, "\t Global HTTP proxy: %v", s.ExchangeHTTPProxy) + log.Debugln(log.Global) } // Start starts the engine func (e *Engine) Start() { if e == nil { - log.Fatal("Engine instance is nil") + log.Errorln(log.Global, "Engine instance is nil") + os.Exit(1) } // Sets up internet connectivity monitor if e.Settings.EnableConnectivityMonitor { if err := e.ConnectionManager.Start(); err != nil { - log.Errorf("Connection manager unable to start: %v", err) + log.Errorf(log.Global, "Connection manager unable to start: %v", err) } } if e.Settings.EnableNTPClient { if err := e.NTPManager.Start(); err != nil { - log.Errorf("NTP manager unable to start: %v", err) + log.Errorf(log.Global, "NTP manager unable to start: %v", err) } } e.Uptime = time.Now() - log.Debugf("Bot '%s' started.\n", e.Config.Name) - log.Debugf("Using data dir: %s\n", e.Settings.DataDir) + log.Debugf(log.Global, "Bot '%s' started.\n", e.Config.Name) + log.Debugf(log.Global, "Using data dir: %s\n", e.Settings.DataDir) enabledExchanges := e.Config.CountEnabledExchanges() if e.Settings.EnableAllExchanges { enabledExchanges = len(e.Config.Exchanges) } - log.Debugln() - log.Debugln("EXCHANGE COVERAGE") - log.Debugf("\t Available Exchanges: %d. Enabled Exchanges: %d.\n", + log.Debugln(log.Global, "EXCHANGE COVERAGE") + log.Debugf(log.Global, "\t Available Exchanges: %d. Enabled Exchanges: %d.\n", len(e.Config.Exchanges), enabledExchanges) if e.Settings.ExchangePurgeCredentials { - log.Debugln("Purging exchange API credentials.") + log.Debugln(log.Global, "Purging exchange API credentials.") e.Config.PurgeExchangeAPICredentials() } - log.Debugln("Setting up exchanges..") + log.Debugln(log.Global, "Setting up exchanges..") SetupExchanges() if len(e.Exchanges) == 0 { - log.Fatalf("No exchanges were able to be loaded. Exiting") + log.Errorln(log.Global, "No exchanges were able to be loaded. Exiting") + os.Exit(1) } if e.Settings.EnableCommsRelayer { if err := e.CommsManager.Start(); err != nil { - log.Errorf("Communications manager unable to start: %v", err) + log.Errorf(log.Global, "Communications manager unable to start: %v\n", err) } } @@ -329,7 +326,7 @@ func (e *Engine) Start() { e.Settings.DataDir, e.Settings.Verbose) if err != nil { - log.Warn("currency updater system failed to start", err) + log.Errorf(log.Global, "currency updater system failed to start %v", err) } if e.Settings.EnableGRPC { @@ -347,7 +344,7 @@ func (e *Engine) Start() { if e.Settings.EnablePortfolioManager { if err = e.PortfolioManager.Start(); err != nil { - log.Errorf("Fund manager unable to start: %v", err) + log.Errorf(log.Global, "Fund manager unable to start: %v", err) } } @@ -358,7 +355,7 @@ func (e *Engine) Start() { if e.Settings.EnableOrderManager { if err = e.OrderManager.Start(); err != nil { - log.Errorf("Order manager unable to start: %v", err) + log.Errorf(log.Global, "Order manager unable to start: %v", err) } } @@ -372,7 +369,7 @@ func (e *Engine) Start() { e.ExchangeCurrencyPairManager, err = NewCurrencyPairSyncer(exchangeSyncCfg) if err != nil { - log.Warnf("Unable to initialise exchange currency pair syncer. Err: %s", err) + log.Warnf(log.Global, "Unable to initialise exchange currency pair syncer. Err: %s", err) } else { go e.ExchangeCurrencyPairManager.Start() } @@ -388,7 +385,7 @@ func (e *Engine) Start() { // Stop correctly shuts down engine saving configuration files func (e *Engine) Stop() { - log.Debugln("Engine shutting down..") + log.Debugln(log.Global, "Engine shutting down..") if len(portfolio.Portfolio.Addresses) != 0 { e.Config.Portfolio = portfolio.Portfolio @@ -396,46 +393,50 @@ func (e *Engine) Stop() { if e.OrderManager.Started() { if err := e.OrderManager.Stop(); err != nil { - log.Errorf("Order manager unable to stop. Error: %v", err) + log.Errorf(log.Global, "Order manager unable to stop. Error: %v", err) } } if e.NTPManager.Started() { if err := e.NTPManager.Stop(); err != nil { - log.Errorf("NTP manager unable to stop. Error: %v", err) + log.Errorf(log.Global, "NTP manager unable to stop. Error: %v", err) } } if e.CommsManager.Started() { if err := e.CommsManager.Stop(); err != nil { - log.Errorf("Communication manager unable to stop. Error: %v", err) + log.Errorf(log.Global, "Communication manager unable to stop. Error: %v", err) } } if e.PortfolioManager.Started() { if err := e.PortfolioManager.Stop(); err != nil { - log.Errorf("Fund manager unable to stop. Error: %v", err) + log.Errorf(log.Global, "Fund manager unable to stop. Error: %v", err) } } if e.ConnectionManager.Started() { if err := e.ConnectionManager.Stop(); err != nil { - log.Errorf("Connection manager unable to stop. Error: %v", err) + log.Errorf(log.Global, "Connection manager unable to stop. Error: %v", err) } } if !e.Settings.EnableDryRun { err := e.Config.SaveConfig(e.Settings.ConfigFile) if err != nil { - log.Error("Unable to save config.") + log.Errorln(log.Global, "Unable to save config.") } else { - log.Debugln("Config file saved successfully.") + log.Debugln(log.Global, "Config file saved successfully.") } } // Wait for services to gracefully shutdown e.ServicesWG.Wait() - log.Debugln("Exiting.") - log.CloseLogFile() + log.Debugln(log.Global, "Exiting.") + err := log.CloseLogger() + if err != nil { + fmt.Printf("Failed to close logger %v", err) + } + os.Exit(0) } @@ -447,7 +448,7 @@ func (e *Engine) handleInterrupt() { signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { sig := <-c - log.Debugf("Captured %v, shutdown requested.", sig) + log.Debugf(log.Global, "Captured %v, shutdown requested.\n", sig) close(e.Shutdown) }() } diff --git a/engine/events.go b/engine/events.go index 41b2ae83..f62d1c71 100644 --- a/engine/events.go +++ b/engine/events.go @@ -126,7 +126,7 @@ func (e *Event) ExecuteAction() bool { if strings.Contains(e.Action, ",") { action := strings.Split(e.Action, ",") if action[0] == ActionSMSNotify { - message := fmt.Sprintf("Event triggered: %s", e.String()) + message := fmt.Sprintf("Event triggered: %s\n", e.String()) if action[1] == "ALL" { Bot.CommsManager.PushEvent(base.Event{ Type: "event", @@ -135,7 +135,7 @@ func (e *Event) ExecuteAction() bool { } } } else { - log.Debugf("Event triggered: %s", e.String()) + log.Debugf(log.EventMgr, "Event triggered: %s\n", e.String()) } return true } @@ -143,7 +143,7 @@ func (e *Event) ExecuteAction() bool { // String turns the structure event into a string func (e *Event) String() string { return fmt.Sprintf( - "If the %s [%s] %s on %s meets the following %v then %s.", e.Pair.String(), + "If the %s [%s] %s on %s meets the following %v then %s.\n", e.Pair.String(), strings.ToUpper(e.Asset.String()), e.Item, e.Exchange, e.Condition, e.Action, ) } @@ -152,14 +152,14 @@ func (e *Event) processTicker() bool { t, err := ticker.GetTicker(e.Exchange, e.Pair, e.Asset) if err != nil { if Bot.Settings.Verbose { - log.Debugf("Events: failed to get ticker. Err: %s", err) + log.Debugf(log.EventMgr, "Events: failed to get ticker. Err: %s\n", err) } return false } if t.Last == 0 { if Bot.Settings.Verbose { - log.Debugln("Events: ticker last price is 0") + log.Debugln(log.EventMgr, "Events: ticker last price is 0") } return false } @@ -196,7 +196,7 @@ func (e *Event) processOrderbook() bool { ob, err := orderbook.Get(e.Exchange, e.Pair, e.Asset) if err != nil { if Bot.Settings.Verbose { - log.Debugf("Events: Failed to get orderbook. Err: %s", err) + log.Debugf(log.EventMgr, "Events: Failed to get orderbook. Err: %s\n", err) } return false } @@ -208,7 +208,7 @@ func (e *Event) processOrderbook() bool { result := e.processCondition(subtotal, e.Condition.OrderbookAmount) if result { success = true - log.Debugf("Events: Bid Amount: %f Price: %v Subtotal: %v", ob.Bids[x].Amount, ob.Bids[x].Price, subtotal) + log.Debugf(log.EventMgr, "Events: Bid Amount: %f Price: %v Subtotal: %v\n", ob.Bids[x].Amount, ob.Bids[x].Price, subtotal) } } } @@ -219,7 +219,7 @@ func (e *Event) processOrderbook() bool { result := e.processCondition(subtotal, e.Condition.OrderbookAmount) if result { success = true - log.Debugf("Events: Ask Amount: %f Price: %v Subtotal: %v", ob.Asks[x].Amount, ob.Asks[x].Price, subtotal) + log.Debugf(log.EventMgr, "Events: Ask Amount: %f Price: %v Subtotal: %v\n", ob.Asks[x].Amount, ob.Asks[x].Price, subtotal) } } } @@ -281,7 +281,7 @@ func IsValidEvent(exchange, item string, condition EventConditionParams, action // EventManger is the overarching routine that will iterate through the Events // chain func EventManger() { - log.Debugf("EventManager started. SleepDelay: %v", EventSleepDelay.String()) + log.Debugf(log.EventMgr, "EventManager started. SleepDelay: %v\n", EventSleepDelay.String()) for { total, executed := GetEventCounter() @@ -289,7 +289,7 @@ func EventManger() { for _, event := range Events { if !event.Executed { if Bot.Settings.Verbose { - log.Debugf("Events: Processing event %s.", event.String()) + log.Debugf(log.EventMgr, "Events: Processing event %s.\n", event.String()) } success := event.CheckEventCondition() if success { @@ -297,7 +297,7 @@ func EventManger() { "Events: ID: %d triggered on %s successfully [%v]\n", event.ID, event.Exchange, event.String(), ) - log.Info(msg) + log.Infoln(log.EventMgr, msg) Bot.CommsManager.PushEvent(base.Event{Type: "event", Message: msg}) event.Executed = true } diff --git a/engine/exchange.go b/engine/exchange.go index 92b7aa50..eea04ed0 100644 --- a/engine/exchange.go +++ b/engine/exchange.go @@ -83,7 +83,7 @@ func ReloadExchange(name string) error { e := GetExchangeByName(name) e.Setup(exchCfg) - log.Debugf("%s exchange reloaded successfully.\n", name) + log.Debugf(log.ExchangeSys, "%s exchange reloaded successfully.\n", name) return nil } @@ -275,13 +275,13 @@ func SetupExchanges() { if CheckExchangeExists(exch.Name) { e := GetExchangeByName(exch.Name) if e == nil { - log.Errorf("%s", ErrExchangeNotFound) + log.Errorln(log.ExchangeSys, ErrExchangeNotFound) continue } err := ReloadExchange(exch.Name) if err != nil { - log.Errorf("ReloadExchange %s failed: %s", exch.Name, err) + log.Errorf(log.ExchangeSys, "ReloadExchange %s failed: %s\n", exch.Name, err) continue } @@ -293,15 +293,15 @@ func SetupExchanges() { } if !exch.Enabled && !Bot.Settings.EnableAllExchanges { - log.Debugf("%s: Exchange support: Disabled", exch.Name) + log.Debugf(log.ExchangeSys, "%s: Exchange support: Disabled\n", exch.Name) continue } err := LoadExchange(exch.Name, true, &wg) if err != nil { - log.Errorf("LoadExchange %s failed: %s", exch.Name, err) + log.Errorf(log.ExchangeSys, "LoadExchange %s failed: %s\n", exch.Name, err) continue } - log.Debugf( + log.Debugf(log.ExchangeSys, "%s: Exchange support: Enabled (Authenticated API support: %s - Verbose mode: %s).\n", exch.Name, common.IsEnabled(exch.API.AuthenticatedSupport), diff --git a/engine/helpers.go b/engine/helpers.go index ebe5ad3d..0ac7ccce 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -119,7 +119,7 @@ func GetExchangeOTPs() (map[string]string, error) { exchName := Bot.Config.Exchanges[x].Name o, err := totp.GenerateCode(otpSecret, time.Now()) if err != nil { - log.Errorf("Unable to generate OTP code for exchange %s. Err: %s", + log.Errorf(log.Global, "Unable to generate OTP code for exchange %s. Err: %s\n", exchName, err) continue } @@ -541,7 +541,7 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) { continue } - log.Debugf("Portfolio: Adding new exchange address: %s, %s, %f, %s\n", + log.Debugf(log.PortfolioMgr, "Portfolio: Adding new exchange address: %s, %s, %f, %s\n", exchangeName, currencyName, total, @@ -556,7 +556,7 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) { } else { if total <= 0 { - log.Debugf("Portfolio: Removing %s %s entry.\n", + log.Debugf(log.PortfolioMgr, "Portfolio: Removing %s %s entry.\n", exchangeName, currencyName) @@ -571,7 +571,7 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) { } if balance != total { - log.Debugf("Portfolio: Updating %s %s entry with balance %f.\n", + log.Debugf(log.PortfolioMgr, "Portfolio: Updating %s %s entry with balance %f.\n", exchangeName, currencyName, total) @@ -668,14 +668,14 @@ func GetExchangeCryptocurrencyDepositAddresses() map[string]map[string]string { exchName := Bot.Exchanges[x].GetName() if !Bot.Exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) { if Bot.Settings.Verbose { - log.Debugf("GetExchangeCryptocurrencyDepositAddresses: Skippping %s due to disabled authenticated API support.", exchName) + log.Debugf(log.ExchangeSys, "GetExchangeCryptocurrencyDepositAddresses: Skippping %s due to disabled authenticated API support.\n", exchName) } continue } cryptoCurrencies, err := GetCryptocurrenciesByExchange(exchName, true, true, asset.Spot) if err != nil { - log.Debugf("%s failed to get cryptocurrency deposit addresses. Err: %s", exchName, err) + log.Debugf(log.ExchangeSys, "%s failed to get cryptocurrency deposit addresses. Err: %s\n", exchName, err) continue } @@ -684,7 +684,7 @@ func GetExchangeCryptocurrencyDepositAddresses() map[string]map[string]string { cryptocurrency := cryptoCurrencies[y] depositAddr, err := Bot.Exchanges[x].GetDepositAddress(currency.NewCode(cryptocurrency), "") if err != nil { - log.Debugf("%s failed to get cryptocurrency deposit addresses. Err: %s", exchName, err) + log.Errorf(log.Global, "%s failed to get cryptocurrency deposit addresses. Err: %s\n", exchName, err) continue } cryptoAddr[cryptocurrency] = depositAddr @@ -748,7 +748,7 @@ func GetAllActiveTickers() []EnabledExchangeCurrencies { for z := range currencies { tp, err := exch.FetchTicker(currencies[z], assets[y]) if err != nil { - log.Debugf("Exchange %s failed to retrieve %s ticker. Err: %s", exchName, + log.Errorf(log.ExchangeSys, "Exchange %s failed to retrieve %s ticker. Err: %s\n", exchName, currencies[z].String(), err) continue @@ -768,13 +768,13 @@ func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts { if individualBot != nil && individualBot.IsEnabled() { if !individualBot.GetAuthenticatedAPISupport(exchange.RestAuthentication) { if Bot.Settings.Verbose { - log.Debugf("GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.", individualBot.GetName()) + log.Debugf(log.ExchangeSys, "GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.\n", individualBot.GetName()) } continue } individualExchange, err := individualBot.GetAccountInfo() if err != nil { - log.Debugf("Error encountered retrieving exchange account info for %s. Error %s", + log.Errorf(log.ExchangeSys, "Error encountered retrieving exchange account info for %s. Error %s\n", individualBot.GetName(), err) continue } @@ -795,7 +795,7 @@ func checkCerts() error { return genCert(targetDir) } - log.Debugf("gRPC TLS certs directory already exists, will use them.") + log.Debugln(log.Global, "gRPC TLS certs directory already exists, will use them.") return nil } @@ -875,6 +875,6 @@ func genCert(targetDir string) error { return fmt.Errorf("failed to write cert.pem file %s", err) } - log.Debugf("TLS key.pem and cert.pem files written to %s", targetDir) + log.Debugf(log.Global, "TLS key.pem and cert.pem files written to %s\n", targetDir) return nil } diff --git a/engine/orders.go b/engine/orders.go index 9f6a8177..8feec650 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -62,7 +62,7 @@ func (o *orderManager) Start() error { return errors.New("order manager already started") } - log.Debugln("Order manager starting...") + log.Debugln(log.OrderBook, "Order manager starting...") o.shutdown = make(chan struct{}) o.orderStore.Orders = make(map[string][]exchange.OrderDetail) @@ -82,23 +82,23 @@ func (o *orderManager) Stop() error { atomic.CompareAndSwapInt32(&o.started, 1, 0) }() - log.Debugln("Order manager shutting down...") + log.Debugln(log.OrderBook, "Order manager shutting down...") close(o.shutdown) return nil } func (o *orderManager) gracefulShutdown() { if o.cfg.CancelOrdersOnShutdown { - log.Debug("Order manager: Cancelling any open orders...") + log.Debugln(log.OrderMgr, "Order manager: Cancelling any open orders...") orders := o.orderStore.Get() if orders == nil { return } for k, v := range orders { - log.Debugf("Order manager: Cancelling order(s) for exchange %s.", k) + log.Debugf(log.OrderMgr, "Order manager: Cancelling order(s) for exchange %s.\n", k) for y := range v { - log.Debugf("order manager: Cancelling order ID %v [%v]", + log.Debugf(log.OrderMgr, "order manager: Cancelling order ID %v [%v]", v[y].ID, v[y]) err := o.Cancel(k, &exchange.OrderCancellation{ OrderID: v[y].ID, @@ -106,7 +106,7 @@ func (o *orderManager) gracefulShutdown() { if err != nil { msg := fmt.Sprintf("Order manager: Exchange %s unable to cancel order ID=%v. Err: %s", k, v[y].ID, err) - log.Debugln(msg) + log.Debugln(log.OrderBook, msg) Bot.CommsManager.PushEvent(base.Event{ Type: "order", Message: msg, @@ -116,7 +116,7 @@ func (o *orderManager) gracefulShutdown() { msg := fmt.Sprintf("Order manager: Exchange %s order ID=%v cancelled.", k, v[y].ID) - log.Debugln(msg) + log.Debugln(log.OrderBook, msg) Bot.CommsManager.PushEvent(base.Event{ Type: "order", Message: msg, @@ -127,11 +127,11 @@ func (o *orderManager) gracefulShutdown() { } func (o *orderManager) run() { - log.Debugln("Order manager started.") + log.Debugln(log.OrderBook, "Order manager started.") tick := time.NewTicker(OrderManagerDelay) Bot.ServicesWG.Add(1) defer func() { - log.Debugf("Order manager shutdown.") + log.Debugln(log.OrderMgr, "Order manager shutdown.") tick.Stop() Bot.ServicesWG.Done() }() @@ -213,7 +213,7 @@ func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) id, err := common.GetV4UUID() if err != nil { - log.Warnf("Order manager: Unable to generate UUID. Err: %s", err) + log.Warnf(log.OrderMgr, "Order manager: Unable to generate UUID. Err: %s\n", err) } result, err := exch.SubmitOrder(order) @@ -227,7 +227,7 @@ func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) msg := fmt.Sprintf("Order manager: Exchange %s submitted order ID=%v [Ours: %v] pair=%v price=%v amount=%v side=%v type=%v.", exchName, result.OrderID, id.String(), order.Pair, order.Price, order.Amount, order.OrderSide, order.OrderType) - log.Debugln(msg) + log.Debugln(log.OrderMgr, msg) Bot.CommsManager.PushEvent(base.Event{ Type: "order", Message: msg, @@ -244,7 +244,7 @@ func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) func (o *orderManager) processOrders() { authExchanges := GetAuthAPISupportedExchanges() for x := range authExchanges { - log.Debugf("Order manager: Procesing orders for exchange %v.", authExchanges[x]) + log.Debugf(log.OrderMgr, "Order manager: Procesing orders for exchange %v.\n", authExchanges[x]) exch := GetExchangeByName(authExchanges[x]) req := exchange.GetOrdersRequest{ OrderSide: exchange.AnyOrderSide, @@ -252,7 +252,7 @@ func (o *orderManager) processOrders() { } result, err := exch.GetActiveOrders(&req) if err != nil { - log.Debugf("Order manager: Unable to get active orders: %s", err) + log.Warnf(log.OrderMgr, "Order manager: Unable to get active orders: %s\n", err) continue } @@ -262,7 +262,7 @@ func (o *orderManager) processOrders() { if result != ErrOrdersAlreadyExists { msg := fmt.Sprintf("Order manager: Exchange %s added order ID=%v pair=%v price=%v amount=%v side=%v type=%v.", order.Exchange, order.ID, order.CurrencyPair, order.Price, order.Amount, order.OrderSide, order.OrderType) - log.Debug(msg) + log.Debugf(log.OrderMgr, "%v\n", msg) Bot.CommsManager.PushEvent(base.Event{ Type: "order", Message: msg, diff --git a/engine/portfolio.go b/engine/portfolio.go index 3116472f..62f6989b 100644 --- a/engine/portfolio.go +++ b/engine/portfolio.go @@ -29,7 +29,7 @@ func (p *portfolioManager) Start() error { return errors.New("portfolio manager already started") } - log.Debugln("Portfolio manager starting...") + log.Debugln(log.PortfolioMgr, "Portfolio manager starting...") Bot.Portfolio = &portfolio.Portfolio Bot.Portfolio.Seed(Bot.Config.Portfolio) p.shutdown = make(chan struct{}) @@ -41,13 +41,13 @@ func (p *portfolioManager) Stop() error { return errors.New("portfolio manager is already stopped") } - log.Debugln("Portfolio manager shutting down...") + log.Debugln(log.PortfolioMgr, "Portfolio manager shutting down...") close(p.shutdown) return nil } func (p *portfolioManager) run() { - log.Debugln("Portfolio manager started.") + log.Debugln(log.PortfolioMgr, "Portfolio manager started.") Bot.ServicesWG.Add(1) tick := time.NewTicker(PortfolioSleepDelay) defer func() { @@ -55,7 +55,7 @@ func (p *portfolioManager) run() { atomic.CompareAndSwapInt32(&p.started, 1, 0) tick.Stop() Bot.ServicesWG.Done() - log.Debugf("Portfolio manager shutdown.") + log.Debugf(log.PortfolioMgr, "Portfolio manager shutdown.") }() for { @@ -75,7 +75,7 @@ func (p *portfolioManager) processPortfolio() { for key, value := range data { success := pf.UpdatePortfolio(value, key) if success { - log.Debugf( + log.Debugf(log.PortfolioMgr, "Portfolio manager: Successfully updated address balance for %s address(es) %s\n", key, value, ) diff --git a/engine/restful_router.go b/engine/restful_router.go index 3e988597..3a3b5ce1 100644 --- a/engine/restful_router.go +++ b/engine/restful_router.go @@ -19,7 +19,7 @@ func RESTLogger(inner http.Handler, name string) http.Handler { start := time.Now() inner.ServeHTTP(w, r) - log.Debugf( + log.Debugf(log.RESTSys, "%s\t%s\t%s\t%s", r.Method, r.RequestURI, @@ -32,20 +32,24 @@ func RESTLogger(inner http.Handler, name string) http.Handler { // StartRESTServer starts a REST server func StartRESTServer() { listenAddr := Bot.Config.RemoteControl.DeprecatedRPC.ListenAddress - log.Debugf("Deprecated RPC server support enabled. Listen URL: http://%s:%d\n", common.ExtractHost(listenAddr), common.ExtractPort(listenAddr)) + log.Debugf(log.RESTSys, + "Deprecated RPC server support enabled. Listen URL: http://%s:%d\n", + common.ExtractHost(listenAddr), common.ExtractPort(listenAddr)) err := http.ListenAndServe(listenAddr, newRouter(true)) if err != nil { - log.Errorf("Failed to start deprecated RPC server. Err: %s", err) + log.Errorf(log.RESTSys, "Failed to start deprecated RPC server. Err: %s", err) } } // StartWebsocketServer starts a Websocket server func StartWebsocketServer() { listenAddr := Bot.Config.RemoteControl.WebsocketRPC.ListenAddress - log.Debugf("Websocket RPC support enabled. Listen URL: ws://%s:%d/ws\n", common.ExtractHost(listenAddr), common.ExtractPort(listenAddr)) + log.Debugf(log.RESTSys, + "Websocket RPC support enabled. Listen URL: ws://%s:%d/ws\n", + common.ExtractHost(listenAddr), common.ExtractPort(listenAddr)) err := http.ListenAndServe(listenAddr, newRouter(false)) if err != nil { - log.Errorf("Failed to start websocket RPC server. Err: %s", err) + log.Errorf(log.RESTSys, "Failed to start websocket RPC server. Err: %s", err) } } @@ -81,7 +85,9 @@ func newRouter(isREST bool) *mux.Router { } if Bot.Config.Profiler.Enabled { - log.Debugf("HTTP Go performance profiler (pprof) endpoint enabled: http://%s:%d/debug", common.ExtractHost(listenAddr), + log.Debugf(log.RESTSys, + "HTTP Go performance profiler (pprof) endpoint enabled: http://%s:%d/debug\n", + common.ExtractHost(listenAddr), common.ExtractPort(listenAddr)) router.PathPrefix("/debug").Handler(http.DefaultServeMux) } diff --git a/engine/restful_server.go b/engine/restful_server.go index 6139d3c0..d1443ede 100644 --- a/engine/restful_server.go +++ b/engine/restful_server.go @@ -18,7 +18,7 @@ func RESTfulJSONResponse(w http.ResponseWriter, response interface{}) error { // RESTfulError prints the REST method and error func RESTfulError(method string, err error) { - log.Errorf("RESTful %s: server failed to send JSON response. Error %s", + log.Errorf(log.RESTSys, "RESTful %s: server failed to send JSON response. Error %s\n", method, err) } @@ -74,7 +74,8 @@ func GetAllActiveOrderbooks() []EnabledExchangeOrderbooks { for z := range currencies { ob, err := exch.FetchOrderbook(currencies[z], assets[y]) if err != nil { - log.Errorf("Exchange %s failed to retrieve %s orderbook. Err: %s", exchName, + log.Errorf(log.RESTSys, + "Exchange %s failed to retrieve %s orderbook. Err: %s\n", exchName, currencies[z].String(), err) continue diff --git a/engine/routines.go b/engine/routines.go index e687a08f..c1e9b255 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -20,7 +20,7 @@ import ( func printCurrencyFormat(price float64) string { displaySymbol, err := currency.GetSymbolByCurrencyName(Bot.Config.Currency.FiatDisplayCurrency) if err != nil { - log.Errorf("Failed to get display symbol: %s", err) + log.Errorf(log.Global, "Failed to get display symbol: %s\n", err) } return fmt.Sprintf("%s%.8f", displaySymbol, price) @@ -32,17 +32,17 @@ func printConvertCurrencyFormat(origCurrency currency.Code, origPrice float64) s origCurrency, displayCurrency) if err != nil { - log.Errorf("Failed to convert currency: %s", err) + log.Errorf(log.Global, "Failed to convert currency: %s\n", err) } displaySymbol, err := currency.GetSymbolByCurrencyName(displayCurrency) if err != nil { - log.Errorf("Failed to get display symbol: %s", err) + log.Errorf(log.Global, "Failed to get display symbol: %s\n", err) } origSymbol, err := currency.GetSymbolByCurrencyName(origCurrency) if err != nil { - log.Errorf("Failed to get original currency symbol for %s: %s", + log.Errorf(log.Global, "Failed to get original currency symbol for %s: %s\n", origCurrency, err) } @@ -59,7 +59,7 @@ func printConvertCurrencyFormat(origCurrency currency.Code, origPrice float64) s func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.Item, exchangeName string, err error) { if err != nil { - log.Errorf("Failed to get %s %s ticker. Error: %s", + log.Errorf(log.Ticker, "Failed to get %s %s ticker. Error: %s\n", p.String(), exchangeName, err) @@ -70,7 +70,7 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I if p.Quote.IsFiatCurrency() && p.Quote != Bot.Config.Currency.FiatDisplayCurrency { origCurrency := p.Quote.Upper() - log.Infof("%s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f", + log.Infof(log.Ticker, "%s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f\n", exchangeName, FormatCurrency(p).String(), assetType, @@ -83,7 +83,7 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I } else { if p.Quote.IsFiatCurrency() && p.Quote == Bot.Config.Currency.FiatDisplayCurrency { - log.Infof("%s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f", + log.Infof(log.Ticker, "%s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f\n", exchangeName, FormatCurrency(p).String(), assetType, @@ -94,7 +94,7 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I printCurrencyFormat(result.Low), result.Volume) } else { - log.Infof("%s %s %s: TICKER: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f", + log.Infof(log.Ticker, "%s %s %s: TICKER: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f\n", exchangeName, FormatCurrency(p).String(), assetType, @@ -110,7 +110,7 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType asset.Item, exchangeName string, err error) { if err != nil { - log.Errorf("Failed to get %s %s orderbook of type %s. Error: %s", + log.Errorf(log.OrderBook, "Failed to get %s %s orderbook of type %s. Error: %s\n", p, exchangeName, assetType, @@ -124,7 +124,7 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType as if p.Quote.IsFiatCurrency() && p.Quote != Bot.Config.Currency.FiatDisplayCurrency { origCurrency := p.Quote.Upper() - log.Infof("%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s", + log.Infof(log.OrderBook, "%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s\n", exchangeName, FormatCurrency(p).String(), assetType, @@ -140,7 +140,7 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType as } else { if p.Quote.IsFiatCurrency() && p.Quote == Bot.Config.Currency.FiatDisplayCurrency { - log.Infof("%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s", + log.Infof(log.OrderBook, "%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s\n", exchangeName, FormatCurrency(p).String(), assetType, @@ -154,7 +154,7 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType as printCurrencyFormat(asksValue), ) } else { - log.Infof("%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %f Asks len: %d Amount: %f %s. Total value: %f", + log.Infof(log.OrderBook, "%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %f Asks len: %d Amount: %f %s. Total value: %f\n", exchangeName, FormatCurrency(p).String(), assetType, @@ -180,7 +180,7 @@ func relayWebsocketEvent(result interface{}, event, assetType, exchangeName stri } err := BroadcastWebsocketMessage(evt) if err != nil { - log.Errorf("Failed to broadcast websocket event %v. Error: %s", + log.Errorf(log.WebsocketMgr, "Failed to broadcast websocket event %v. Error: %s\n", event, err) } } @@ -188,7 +188,7 @@ func relayWebsocketEvent(result interface{}, event, assetType, exchangeName stri // TickerUpdaterRoutine fetches and updates the ticker for all enabled // currency pairs and exchanges func TickerUpdaterRoutine() { - log.Debugf("Starting ticker updater routine.") + log.Debugln(log.Ticker, "Starting ticker updater routine.") var wg sync.WaitGroup for { wg.Add(len(Bot.Exchanges)) @@ -233,7 +233,7 @@ func TickerUpdaterRoutine() { }(x, &wg) } wg.Wait() - log.Debugln("All enabled currency tickers fetched.") + log.Debugln(log.Ticker, "All enabled currency tickers fetched.") time.Sleep(time.Second * 10) } } @@ -241,7 +241,7 @@ func TickerUpdaterRoutine() { // OrderbookUpdaterRoutine fetches and updates the orderbooks for all enabled // currency pairs and exchanges func OrderbookUpdaterRoutine() { - log.Debugln("Starting orderbook updater routine.") + log.Debugln(log.OrderBook, "Starting orderbook updater routine.") var wg sync.WaitGroup for { wg.Add(len(Bot.Exchanges)) @@ -275,7 +275,7 @@ func OrderbookUpdaterRoutine() { }(x, &wg) } wg.Wait() - log.Debugln("All enabled currency orderbooks fetched.") + log.Debugln(log.OrderBook, "All enabled currency orderbooks fetched.") time.Sleep(time.Second * 10) } } @@ -283,14 +283,14 @@ func OrderbookUpdaterRoutine() { // WebsocketRoutine Initial routine management system for websocket func WebsocketRoutine() { if Bot.Settings.Verbose { - log.Debugln("Connecting exchange websocket services...") + log.Debugln(log.WebsocketMgr, "Connecting exchange websocket services...") } for i := range Bot.Exchanges { go func(i int) { if Bot.Exchanges[i].SupportsWebsocket() { if Bot.Settings.Verbose { - log.Debugf("Exchange %s websocket support: Yes Enabled: %v", Bot.Exchanges[i].GetName(), + log.Debugf(log.WebsocketMgr, "Exchange %s websocket support: Yes Enabled: %v\n", Bot.Exchanges[i].GetName(), common.IsEnabled(Bot.Exchanges[i].IsWebsocketEnabled())) } @@ -304,11 +304,11 @@ func WebsocketRoutine() { err = ws.Connect() if err != nil { - log.Println(err) + log.Errorf(log.WebsocketMgr, "%v\n", err) } } } else if Bot.Settings.Verbose { - log.Debugf("Exchange %s websocket support: No", Bot.Exchanges[i].GetName()) + log.Debugf(log.WebsocketMgr, "Exchange %s websocket support: No\n", Bot.Exchanges[i].GetName()) } }(i) } @@ -322,7 +322,7 @@ var wg sync.WaitGroup func Websocketshutdown(ws *exchange.Websocket) error { err := ws.Shutdown() // shutdown routines on the exchange if err != nil { - log.Errorf("routines.go error - failed to shutdown %s", err) + log.Errorf(log.WebsocketMgr, "routines.go error - failed to shutdown %s\n", err) } timer := time.NewTimer(5 * time.Second) @@ -356,12 +356,12 @@ func streamDiversion(ws *exchange.Websocket) { case <-ws.Connected: if Bot.Settings.Verbose { - log.Debugf("exchange %s websocket feed connected", ws.GetName()) + log.Debugf(log.WebsocketMgr, "exchange %s websocket feed connected\n", ws.GetName()) } case <-ws.Disconnected: if Bot.Settings.Verbose { - log.Debugf("exchange %s websocket feed disconnected, switching to REST functionality", + log.Debugf(log.WebsocketMgr, "exchange %s websocket feed disconnected, switching to REST functionality\n", ws.GetName()) } } @@ -387,12 +387,12 @@ func WebsocketDataHandler(ws *exchange.Websocket) { switch d { case exchange.WebsocketNotEnabled: if Bot.Settings.Verbose { - log.Warnf("routines.go warning - exchange %s weboscket not enabled", + log.Warnf(log.WebsocketMgr, "routines.go warning - exchange %s weboscket not enabled\n", ws.GetName()) } default: - log.Infof(d) + log.Info(log.WebsocketMgr, d) } case error: @@ -401,7 +401,7 @@ func WebsocketDataHandler(ws *exchange.Websocket) { go ws.WebsocketReset() continue default: - log.Errorf("routines.go exchange %s websocket error - %s", ws.GetName(), data) + log.Errorf(log.WebsocketMgr, "routines.go exchange %s websocket error - %s", ws.GetName(), data) } case exchange.TradeData: @@ -433,7 +433,7 @@ func WebsocketDataHandler(ws *exchange.Websocket) { case exchange.KlineData: // Kline data if Bot.Settings.Verbose { - log.Infoln("Websocket Kline Updated: ", d) + log.Infof(log.WebsocketMgr, "Websocket Kline Updated: %v\n", d) } case exchange.WebsocketOrderbookUpdate: // Orderbook data @@ -443,10 +443,13 @@ func WebsocketDataHandler(ws *exchange.Websocket) { result.Pair, result.Asset, SyncItemOrderbook, nil) } // TO-DO: printOrderbookSummary - //nolint:gocritic log.Infof("Websocket %s %s orderbook updated", ws.GetName(), result.Pair.Pair().String()) + //nolint:gocritic + if Bot.Settings.Verbose { + log.Infof(log.WebsocketMgr, "Websocket %s %s orderbook updated\n", ws.GetName(), result.Pair.String()) + } default: if Bot.Settings.Verbose { - log.Warnf("Websocket Unknown type: %s", d) + log.Warnf(log.WebsocketMgr, "Websocket Unknown type: %s\n", d) } } } diff --git a/engine/rpcserver.go b/engine/rpcserver.go index ea0ed348..8de40aed 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -64,21 +64,21 @@ func authenticateClient(ctx context.Context) (context.Context, error) { func StartRPCServer() { err := checkCerts() if err != nil { - log.Errorf("gRPC checkCerts failed. err: %s", err) + log.Errorf(log.GRPCSys, "gRPC checkCerts failed. err: %s\n", err) return } - log.Debugf("gRPC server support enabled. Starting gRPC server on https://%v.", Bot.Config.RemoteControl.GRPC.ListenAddress) + log.Debugf(log.GRPCSys, "gRPC server support enabled. Starting gRPC server on https://%v.\n", Bot.Config.RemoteControl.GRPC.ListenAddress) lis, err := net.Listen("tcp", Bot.Config.RemoteControl.GRPC.ListenAddress) if err != nil { - log.Errorf("gRPC server failed to bind to port: %s", err) + log.Errorf(log.GRPCSys, "gRPC server failed to bind to port: %s", err) return } targetDir := utils.GetTLSDir(Bot.Settings.DataDir) creds, err := credentials.NewServerTLSFromFile(filepath.Join(targetDir, "cert.pem"), filepath.Join(targetDir, "key.pem")) if err != nil { - log.Errorf("gRPC server could not load TLS keys: %s", err) + log.Errorf(log.GRPCSys, "gRPC server could not load TLS keys: %s\n", err) return } @@ -92,12 +92,12 @@ func StartRPCServer() { go func() { if err := server.Serve(lis); err != nil { - log.Errorf("gRPC server failed to serve: %s", err) + log.Errorf(log.GRPCSys, "gRPC server failed to serve: %s\n", err) return } }() - log.Debugf("gRPC server started!") + log.Debugln(log.GRPCSys, "gRPC server started!") if Bot.Settings.EnableGRPCProxy { StartRPCRESTProxy() @@ -106,7 +106,7 @@ func StartRPCServer() { // StartRPCRESTProxy starts a gRPC proxy func StartRPCRESTProxy() { - log.Debugf("gRPC proxy server support enabled. Starting gRPC proxy server on http://%v.", Bot.Config.RemoteControl.GRPC.GRPCProxyListenAddress) + log.Debugf(log.GRPCSys, "gRPC proxy server support enabled. Starting gRPC proxy server on http://%v.\n", Bot.Config.RemoteControl.GRPC.GRPCProxyListenAddress) ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -114,7 +114,7 @@ func StartRPCRESTProxy() { targetDir := utils.GetTLSDir(Bot.Settings.DataDir) creds, err := credentials.NewClientTLSFromFile(filepath.Join(targetDir, "cert.pem"), "") if err != nil { - log.Errorf("Unabled to start gRPC proxy. Err: %s", err) + log.Errorf(log.GRPCSys, "Unabled to start gRPC proxy. Err: %s\n", err) return } @@ -127,17 +127,17 @@ func StartRPCRESTProxy() { } err = gctrpc.RegisterGoCryptoTraderHandlerFromEndpoint(ctx, mux, Bot.Config.RemoteControl.GRPC.ListenAddress, opts) if err != nil { - log.Errorf("Failed to register gRPC proxy. Err: %s", err) + log.Errorf(log.GRPCSys, "Failed to register gRPC proxy. Err: %s\n", err) } go func() { if err := http.ListenAndServe(Bot.Config.RemoteControl.GRPC.GRPCProxyListenAddress, mux); err != nil { - log.Errorf("gRPC proxy failed to server: %s", err) + log.Errorf(log.GRPCSys, "gRPC proxy failed to server: %s\n", err) return } }() - log.Debugf("gRPC proxy server started!") + log.Debugln(log.GRPCSys, "gRPC proxy server started!") select {} } @@ -606,7 +606,7 @@ func (s *RPCServer) GetForexRates(ctx context.Context, r *gctrpc.GetForexRatesRe func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) (*gctrpc.GetOrdersResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - log.Debugln(exch) + log.Debugln(log.GRPCSys, exch) return nil, errors.New("exchange is not loaded/doesn't exist") } @@ -827,3 +827,31 @@ func (s *RPCServer) WithdrawCryptocurrencyFunds(ctx context.Context, r *gctrpc.W func (s *RPCServer) WithdrawFiatFunds(ctx context.Context, r *gctrpc.WithdrawCurrencyRequest) (*gctrpc.WithdrawResponse, error) { return &gctrpc.WithdrawResponse{}, common.ErrNotYetImplemented } + +func (s *RPCServer) GetLoggerDetails(ctx context.Context, r *gctrpc.GetLoggerDetailsRequest) (*gctrpc.GetLoggerDetailsResponse, error) { + levels, err := log.Level(r.Logger) + if err != nil { + return nil, err + } + + return &gctrpc.GetLoggerDetailsResponse{ + Info: levels.Info, + Debug: levels.Debug, + Warn: levels.Warn, + Error: levels.Error, + }, nil +} + +func (s *RPCServer) SetLoggerDetails(ctx context.Context, r *gctrpc.SetLoggerDetailsRequest) (*gctrpc.GetLoggerDetailsResponse, error) { + levels, err := log.SetLevel(r.Logger, r.Level) + if err != nil { + return nil, err + } + + return &gctrpc.GetLoggerDetailsResponse{ + Info: levels.Info, + Debug: levels.Debug, + Warn: levels.Warn, + Error: levels.Error, + }, nil +} diff --git a/engine/syncer.go b/engine/syncer.go index 7c164b31..bebe1721 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -48,12 +48,12 @@ func NewCurrencyPairSyncer(c CurrencyPairSyncerConfig) (*ExchangeCurrencyPairSyn s.tickerBatchLastRequested = make(map[string]time.Time) - log.Debugf("Exchange currency pair syncer config:") - log.Debugf("SyncContinuously: %v", s.Cfg.SyncContinuously) - log.Debugf("SyncTicker: %v", s.Cfg.SyncTicker) - log.Debugf("SyncOrderbook: %v", s.Cfg.SyncOrderbook) - log.Debugf("SyncTrades: %v", s.Cfg.SyncTrades) - log.Debugf("NumWorkers: %v", s.Cfg.NumWorkers) + log.Debugln(log.SyncMgr, "Exchange currency pair syncer config:") + log.Debugf(log.SyncMgr, "SyncContinuously: %v\n", s.Cfg.SyncContinuously) + log.Debugf(log.SyncMgr, "SyncTicker: %v\n", s.Cfg.SyncTicker) + log.Debugf(log.SyncMgr, "SyncOrderbook: %v\n", s.Cfg.SyncOrderbook) + log.Debugf(log.SyncMgr, "SyncTrades: %v\n", s.Cfg.SyncTrades) + log.Debugf(log.SyncMgr, "NumWorkers: %v\n", s.Cfg.NumWorkers) return &s, nil } @@ -92,7 +92,7 @@ func (e *ExchangeCurrencyPairSyncer) add(c *CurrencyPairSyncAgent) { defer e.mux.Unlock() if e.Cfg.SyncTicker { - log.Debugf("%s: Added ticker sync item %v: using websocket: %v using REST: %v", c.Exchange, c.Pair.String(), + log.Debugf(log.SyncMgr, "%s: Added ticker sync item %v: using websocket: %v using REST: %v\n", c.Exchange, c.Pair.String(), c.Ticker.IsUsingWebsocket, c.Ticker.IsUsingREST) if atomic.LoadInt32(&e.initSyncCompleted) != 1 { e.initSyncWG.Add(1) @@ -101,7 +101,7 @@ func (e *ExchangeCurrencyPairSyncer) add(c *CurrencyPairSyncAgent) { } if e.Cfg.SyncOrderbook { - log.Debugf("%s: Added orderbook sync item %v: using websocket: %v using REST: %v", c.Exchange, c.Pair.String(), + log.Debugf(log.SyncMgr, "%s: Added orderbook sync item %v: using websocket: %v using REST: %v\n", c.Exchange, c.Pair.String(), c.Orderbook.IsUsingWebsocket, c.Orderbook.IsUsingREST) if atomic.LoadInt32(&e.initSyncCompleted) != 1 { e.initSyncWG.Add(1) @@ -110,7 +110,7 @@ func (e *ExchangeCurrencyPairSyncer) add(c *CurrencyPairSyncAgent) { } if e.Cfg.SyncTrades { - log.Debugf("%s: Added trade sync item %v: using websocket: %v using REST: %v", c.Exchange, c.Pair.String(), + log.Debugf(log.SyncMgr, "%s: Added trade sync item %v: using websocket: %v using REST: %v\n", c.Exchange, c.Pair.String(), c.Trade.IsUsingWebsocket, c.Trade.IsUsingREST) if atomic.LoadInt32(&e.initSyncCompleted) != 1 { e.initSyncWG.Add(1) @@ -197,7 +197,7 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair return } default: - log.Warnf("ExchangeCurrencyPairSyncer: unknown sync item %v", syncType) + log.Warnf(log.SyncMgr, "ExchangeCurrencyPairSyncer: unknown sync item %v\n", syncType) return } @@ -218,7 +218,7 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair e.CurrencyPairs[x].Ticker.HaveData = true e.CurrencyPairs[x].Ticker.IsProcessing = false if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { - log.Debugf("%s ticker sync complete %v [%d/%d].", exchangeName, p, removedCounter, createdCounter) + log.Debugf(log.SyncMgr, "%s ticker sync complete %v [%d/%d].\n", exchangeName, p, removedCounter, createdCounter) removedCounter++ e.initSyncWG.Done() } @@ -232,7 +232,7 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair e.CurrencyPairs[x].Orderbook.HaveData = true e.CurrencyPairs[x].Orderbook.IsProcessing = false if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { - log.Debugf("%s orderbook sync complete %v [%d/%d].", exchangeName, p, removedCounter, createdCounter) + log.Debugf(log.SyncMgr, "%s orderbook sync complete %v [%d/%d].\n", exchangeName, p, removedCounter, createdCounter) removedCounter++ e.initSyncWG.Done() } @@ -246,7 +246,7 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair e.CurrencyPairs[x].Trade.HaveData = true e.CurrencyPairs[x].Trade.IsProcessing = false if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { - log.Debugf("%s trade sync complete %v [%d/%d].", exchangeName, p, removedCounter, createdCounter) + log.Debugf(log.SyncMgr, "%s trade sync complete %v [%d/%d].\n", exchangeName, p, removedCounter, createdCounter) removedCounter++ e.initSyncWG.Done() } @@ -257,7 +257,7 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair func (e *ExchangeCurrencyPairSyncer) worker() { cleanup := func() { - log.Debugf("Exchange CurrencyPairSyncer worker shutting down.") + log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer worker shutting down.") } defer cleanup() @@ -277,7 +277,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { if Bot.Exchanges[x].SupportsWebsocket() && Bot.Exchanges[x].IsWebsocketEnabled() { ws, err := Bot.Exchanges[x].GetWebsocket() if err != nil { - log.Debugf("%s unable to get websocket pointer. Err: %s", exchangeName, err) + log.Errorf(log.SyncMgr, "%s unable to get websocket pointer. Err: %s\n", exchangeName, err) usingREST = true } @@ -329,7 +329,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { c, err := e.get(exchangeName, p, assetTypes[y]) if err != nil { - log.Errorf("failed to get item. Err: %s", err) + log.Errorf(log.SyncMgr, "failed to get item. Err: %s\n", err) continue } @@ -345,7 +345,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, true) c.Ticker.IsUsingWebsocket = false c.Ticker.IsUsingREST = true - log.Warnf("%s %s: No ticker update after 10 seconds, switching from websocket to rest", + log.Warnf(log.SyncMgr, "%s %s: No ticker update after 10 seconds, switching from websocket to rest\n", c.Exchange, c.Pair.String()) e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, false) } @@ -367,14 +367,14 @@ func (e *ExchangeCurrencyPairSyncer) worker() { if batchLastDone.IsZero() || time.Since(batchLastDone) > defaultSyncerTimeout { e.mux.Lock() if e.Cfg.Verbose { - log.Debugf("%s Init'ing REST ticker batching", exchangeName) + log.Debugf(log.SyncMgr, "%s Init'ing REST ticker batching\n", exchangeName) } result, err = Bot.Exchanges[x].UpdateTicker(c.Pair, c.AssetType) e.tickerBatchLastRequested[exchangeName] = time.Now() e.mux.Unlock() } else { if e.Cfg.Verbose { - log.Debugf("%s Using recent batching cache", exchangeName) + log.Debugf(log.OrderMgr, "%s Using recent batching cache\n", exchangeName) } result, err = Bot.Exchanges[x].FetchTicker(c.Pair, c.AssetType) } @@ -407,7 +407,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, true) c.Orderbook.IsUsingWebsocket = false c.Orderbook.IsUsingREST = true - log.Warnf("%s %s: No orderbook update after 15 seconds, switching from websocket to rest", + log.Warnf(log.SyncMgr, "%s %s: No orderbook update after 15 seconds, switching from websocket to rest\n", c.Exchange, c.Pair.String()) e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, false) } @@ -445,7 +445,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { // Start starts an exchange currency pair syncer func (e *ExchangeCurrencyPairSyncer) Start() { - log.Debugf("Exchange CurrencyPairSyncer started.") + log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer started.") for x := range Bot.Exchanges { if !Bot.Exchanges[x].IsEnabled() { @@ -458,7 +458,7 @@ func (e *ExchangeCurrencyPairSyncer) Start() { supportsREST := Bot.Exchanges[x].SupportsREST() if !supportsREST && !supportsWebsocket { - log.Warnf("Loaded exchange %s does not support REST or Websocket.", exchangeName) + log.Warnf(log.SyncMgr, "Loaded exchange %s does not support REST or Websocket.\n", exchangeName) continue } @@ -468,7 +468,7 @@ func (e *ExchangeCurrencyPairSyncer) Start() { if supportsWebsocket { ws, err := Bot.Exchanges[x].GetWebsocket() if err != nil { - log.Errorf("%s failed to get websocket. Err: %s", exchangeName, err) + log.Errorf(log.SyncMgr, "%s failed to get websocket. Err: %s\n", exchangeName, err) usingREST = true } @@ -481,7 +481,7 @@ func (e *ExchangeCurrencyPairSyncer) Start() { err = ws.Connect() if err != nil { - log.Errorf("%s websocket failed to connect. Err: %s", exchangeName, err) + log.Errorf(log.SyncMgr, "%s websocket failed to connect. Err: %s\n", exchangeName, err) usingREST = true } else { usingWebsocket = true @@ -529,21 +529,21 @@ func (e *ExchangeCurrencyPairSyncer) Start() { } if atomic.CompareAndSwapInt32(&e.initSyncStarted, 0, 1) { - log.Debugln("Exchange CurrencyPairSyncer initial sync started.") + log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer initial sync started.") e.initSyncStartTime = time.Now() - log.Debugln(createdCounter) - log.Debugln(removedCounter) + log.Debugln(log.SyncMgr, createdCounter) + log.Debugln(log.SyncMgr, removedCounter) } go func() { e.initSyncWG.Wait() if atomic.CompareAndSwapInt32(&e.initSyncCompleted, 0, 1) { - log.Debugf("Exchange CurrencyPairSyncer initial sync is complete.") + log.Debugf(log.SyncMgr, "Exchange CurrencyPairSyncer initial sync is complete.\n") completedTime := time.Now() - log.Debugf("Exchange CurrencyPairSyncer initiial sync took %v [%v sync items].", completedTime.Sub(e.initSyncStartTime), createdCounter) + log.Debugf(log.SyncMgr, "Exchange CurrencyPairSyncer initiial sync took %v [%v sync items].\n", completedTime.Sub(e.initSyncStartTime), createdCounter) if !e.Cfg.SyncContinuously { - log.Debugf("Exchange CurrencyPairSyncer stopping.") + log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer stopping.") e.Stop() Bot.Stop() return @@ -564,6 +564,6 @@ func (e *ExchangeCurrencyPairSyncer) Start() { func (e *ExchangeCurrencyPairSyncer) Stop() { stopped := atomic.CompareAndSwapInt32(&e.shutdown, 0, 1) if stopped { - log.Debugf("Exchange CurrencyPairSyncer stopped.") + log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer stopped.") } } diff --git a/engine/syncer_test.go b/engine/syncer_test.go index ad846564..832f0575 100644 --- a/engine/syncer_test.go +++ b/engine/syncer_test.go @@ -5,7 +5,6 @@ import ( "time" "github.com/thrasher-/gocryptotrader/config" - log "github.com/thrasher-/gocryptotrader/logger" ) func TestNewCurrencyPairSyncer(t *testing.T) { @@ -27,7 +26,7 @@ func TestNewCurrencyPairSyncer(t *testing.T) { SetupExchanges() if err != nil { - log.Printf("failed to start exchange syncer") + t.Log("failed to start exchange syncer") } Bot.ExchangeCurrencyPairManager, err = NewCurrencyPairSyncer(CurrencyPairSyncerConfig{ diff --git a/engine/timekeeper.go b/engine/timekeeper.go index b42fbd1c..3e9d1a83 100644 --- a/engine/timekeeper.go +++ b/engine/timekeeper.go @@ -42,7 +42,7 @@ func (n *ntpManager) Start() (err error) { } }() - log.Debugln("NTP manager starting...") + log.Debugln(log.TimeMgr, "NTP manager starting...") if Bot.Config.NTPClient.Level == 0 { // Initial NTP check (prompts user on how we should proceed) n.inititalCheck = true @@ -55,7 +55,7 @@ func (n *ntpManager) Start() (err error) { case nil: break case errNTPDisabled: - log.Debugf("NTP manager: User disabled NTP prompts. Exiting.") + log.Debugln(log.TimeMgr, "NTP manager: User disabled NTP prompts. Exiting.") disable = true err = nil return @@ -68,7 +68,7 @@ func (n *ntpManager) Start() (err error) { } n.shutdown = make(chan struct{}) go n.run() - log.Debugln("NTP manager started.") + log.Debugln(log.TimeMgr, "NTP manager started.") return nil } @@ -82,7 +82,7 @@ func (n *ntpManager) Stop() error { } close(n.shutdown) - log.Debugln("NTP manager shutting down...") + log.Debugln(log.TimeMgr, "NTP manager shutting down...") return nil } @@ -92,7 +92,7 @@ func (n *ntpManager) run() { t.Stop() atomic.CompareAndSwapInt32(&n.stopped, 1, 0) atomic.CompareAndSwapInt32(&n.started, 1, 0) - log.Debugln("NTP manager shutdown.") + log.Debugln(log.TimeMgr, "NTP manager shutdown.") }() for { @@ -123,14 +123,14 @@ func (n *ntpManager) processTime() error { configNTPTime := *Bot.Config.NTPClient.AllowedDifference configNTPNegativeTime := (*Bot.Config.NTPClient.AllowedNegativeDifference - (*Bot.Config.NTPClient.AllowedNegativeDifference * 2)) if NTPcurrentTimeDifference > configNTPTime || NTPcurrentTimeDifference < configNTPNegativeTime { - log.Warnf("NTP manager: Time out of sync (NTP): %v | (time.Now()): %v | (Difference): %v | (Allowed): +%v / %v", NTPTime, currentTime, NTPcurrentTimeDifference, configNTPTime, configNTPNegativeTime) + log.Warnf(log.TimeMgr, "NTP manager: Time out of sync (NTP): %v | (time.Now()): %v | (Difference): %v | (Allowed): +%v / %v\n", NTPTime, currentTime, NTPcurrentTimeDifference, configNTPTime, configNTPNegativeTime) if n.inititalCheck { n.inititalCheck = false disable, err := Bot.Config.DisableNTPCheck(os.Stdin) if err != nil { return fmt.Errorf("unable to disable NTP check: %s", err) } - log.Info(disable) + log.Infoln(log.TimeMgr, disable) if Bot.Config.NTPClient.Level == -1 { return errNTPDisabled } diff --git a/engine/websocket.go b/engine/websocket.go index af8b2210..67c62580 100644 --- a/engine/websocket.go +++ b/engine/websocket.go @@ -59,7 +59,7 @@ func (h *WebsocketHub) run() { h.Clients[client] = true case client := <-h.Unregister: if _, ok := h.Clients[client]; ok { - log.Debugln("websocket: disconnected client") + log.Debugln(log.WebsocketMgr, "websocket: disconnected client") delete(h.Clients, client) close(client.Send) } @@ -68,7 +68,7 @@ func (h *WebsocketHub) run() { select { case client.Send <- message: default: - log.Debugln("websocket: disconnected client") + log.Debugln(log.WebsocketMgr, "websocket: disconnected client") close(client.Send) delete(h.Clients, client) } @@ -81,7 +81,7 @@ func (h *WebsocketHub) run() { func (c *WebsocketClient) SendWebsocketMessage(evt interface{}) error { data, err := common.JSONEncode(evt) if err != nil { - log.Errorf("websocket: failed to send message: %s", err) + log.Errorf(log.WebsocketMgr, "websocket: failed to send message: %s\n", err) return err } @@ -99,7 +99,7 @@ func (c *WebsocketClient) read() { msgType, message, err := c.Conn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { - log.Errorf("websocket: client disconnected, err: %s", err) + log.Errorf(log.WebsocketMgr, "websocket: client disconnected, err: %s\n", err) } break } @@ -108,39 +108,39 @@ func (c *WebsocketClient) read() { var evt WebsocketEvent err := common.JSONDecode(message, &evt) if err != nil { - log.Errorf("websocket: failed to decode JSON sent from client %s", err) + log.Errorf(log.WebsocketMgr, "websocket: failed to decode JSON sent from client %s\n", err) continue } if evt.Event == "" { - log.Warnf("websocket: client sent a blank event, disconnecting") + log.Warnln(log.WebsocketMgr, "websocket: client sent a blank event, disconnecting") continue } dataJSON, err := common.JSONEncode(evt.Data) if err != nil { - log.Errorf("websocket: client sent data we couldn't JSON decode") + log.Errorln(log.WebsocketMgr, "websocket: client sent data we couldn't JSON decode") break } req := strings.ToLower(evt.Event) - log.Debugf("websocket: request received: %s", req) + log.Debugf(log.WebsocketMgr, "websocket: request received: %s\n", req) result, ok := wsHandlers[req] if !ok { - log.Debugln("websocket: unsupported event") + log.Debugln(log.WebsocketMgr, "websocket: unsupported event") continue } if result.authRequired && !c.Authenticated { - log.Warnf("Websocket: request %s failed due to unauthenticated request on an authenticated API", evt.Event) + log.Warnf(log.WebsocketMgr, "Websocket: request %s failed due to unauthenticated request on an authenticated API\n", evt.Event) c.SendWebsocketMessage(WebsocketEventResponse{Event: evt.Event, Error: "unauthorised request on authenticated API"}) continue } err = result.handler(c, dataJSON) if err != nil { - log.Errorf("websocket: request %s failed. Error %s", evt.Event, err) + log.Errorf(log.WebsocketMgr, "websocket: request %s failed. Error %s\n", evt.Event, err) continue } } @@ -156,13 +156,13 @@ func (c *WebsocketClient) write() { case message, ok := <-c.Send: if !ok { c.Conn.WriteMessage(websocket.CloseMessage, []byte{}) - log.Debugln("websocket: hub closed the channel") + log.Debugln(log.WebsocketMgr, "websocket: hub closed the channel") return } w, err := c.Conn.NextWriter(websocket.TextMessage) if err != nil { - log.Errorf("websocket: failed to create new io.writeCloser: %s", err) + log.Errorf(log.WebsocketMgr, "websocket: failed to create new io.writeCloser: %s\n", err) return } w.Write(message) @@ -174,7 +174,7 @@ func (c *WebsocketClient) write() { } if err := w.Close(); err != nil { - log.Errorf("websocket: failed to close io.WriteCloser: %s", err) + log.Errorf(log.WebsocketMgr, "websocket: failed to close io.WriteCloser: %s\n", err) return } } @@ -217,7 +217,8 @@ func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) { numClients := len(wsHub.Clients) if numClients >= connectionLimit { - log.Warnf("websocket: client rejected due to websocket client limit reached. Number of clients %d. Limit %d.", + log.Warnf(log.WebsocketMgr, + "websocket: client rejected due to websocket client limit reached. Number of clients %d. Limit %d.\n", numClients, connectionLimit) w.WriteHeader(http.StatusForbidden) return @@ -236,13 +237,14 @@ func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { - log.Error(err) + log.Error(log.WebsocketMgr, err) return } client := &WebsocketClient{Hub: wsHub, Conn: conn, Send: make(chan []byte, 1024)} client.Hub.Register <- client - log.Debugf("websocket: client connected. Connected clients: %d. Limit %d.", + log.Debugf(log.WebsocketMgr, + "websocket: client connected. Connected clients: %d. Limit %d.\n", numClients+1, connectionLimit) go client.read() @@ -266,7 +268,8 @@ func wsAuth(client *WebsocketClient, data interface{}) error { if auth.Username == Bot.Config.RemoteControl.Username && auth.Password == hashPW { client.Authenticated = true wsResp.Data = WebsocketResponseSuccess - log.Debugf("websocket: client authenticated successfully") + log.Debugln(log.WebsocketMgr, + "websocket: client authenticated successfully") return client.SendWebsocketMessage(wsResp) } @@ -274,13 +277,15 @@ func wsAuth(client *WebsocketClient, data interface{}) error { client.authFailures++ client.SendWebsocketMessage(wsResp) if client.authFailures >= Bot.Config.RemoteControl.WebsocketRPC.MaxAuthFailures { - log.Debugf("websocket: disconnecting client, maximum auth failures threshold reached (failures: %d limit: %d)", + log.Debugf(log.WebsocketMgr, + "websocket: disconnecting client, maximum auth failures threshold reached (failures: %d limit: %d)\n", client.authFailures, Bot.Config.RemoteControl.WebsocketRPC.MaxAuthFailures) wsHub.Unregister <- client return nil } - log.Debugf("websocket: client sent wrong username/password (failures: %d limit: %d)", + log.Debugf(log.WebsocketMgr, + "websocket: client sent wrong username/password (failures: %d limit: %d)\n", client.authFailures, Bot.Config.RemoteControl.WebsocketRPC.MaxAuthFailures) return nil } diff --git a/exchanges/alphapoint/alphapoint_websocket.go b/exchanges/alphapoint/alphapoint_websocket.go index 88b023e5..a75d745b 100644 --- a/exchanges/alphapoint/alphapoint_websocket.go +++ b/exchanges/alphapoint/alphapoint_websocket.go @@ -20,25 +20,25 @@ func (a *Alphapoint) WebsocketClient() { a.WebsocketConn, _, err = Dialer.Dial(a.API.Endpoints.WebsocketURL, http.Header{}) if err != nil { - log.Errorf("%s Unable to connect to Websocket. Error: %s\n", a.Name, err) + log.Errorf(log.ExchangeSys, "%s Unable to connect to Websocket. Error: %s\n", a.Name, err) continue } if a.Verbose { - log.Debugf("%s Connected to Websocket.\n", a.Name) + log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", a.Name) } err = a.WebsocketConn.WriteMessage(websocket.TextMessage, []byte(`{"messageType": "logon"}`)) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) return } for a.Enabled { msgType, resp, err := a.WebsocketConn.ReadMessage() if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) break } @@ -50,7 +50,7 @@ func (a *Alphapoint) WebsocketClient() { msgType := MsgType{} err := common.JSONDecode(resp, &msgType) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } @@ -58,13 +58,13 @@ func (a *Alphapoint) WebsocketClient() { ticker := WebsocketTicker{} err = common.JSONDecode(resp, &ticker) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } } } } a.WebsocketConn.Close() - log.Debugf("%s Websocket client disconnected.", a.Name) + log.Debugf(log.ExchangeSys, "%s Websocket client disconnected.", a.Name) } } diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 0fd32314..66939779 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -206,7 +206,7 @@ func (a *ANX) GetOrderList(isActiveOrdersOnly bool) ([]OrderResponse, error) { } if response.ResultCode != "OK" { - log.Errorf("Response code is not OK: %s\n", response.ResultCode) + log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return nil, errors.New(response.ResultCode) } @@ -232,7 +232,7 @@ func (a *ANX) OrderInfo(orderID string) (OrderResponse, error) { } if response.ResultCode != "OK" { - log.Errorf("Response code is not OK: %s\n", response.ResultCode) + log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return OrderResponse{}, errors.New(response.ResultCode) } return response.Order, nil @@ -263,7 +263,7 @@ func (a *ANX) Send(currency, address, otp, amount string) (string, error) { } if response.ResultCode != "OK" { - log.Errorf("Response code is not OK: %s\n", response.ResultCode) + log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return "", errors.New(response.ResultCode) } return response.TransactionID, nil @@ -289,7 +289,7 @@ func (a *ANX) CreateNewSubAccount(currency, name string) (string, error) { } if response.ResultCode != "OK" { - log.Errorf("Response code is not OK: %s\n", response.ResultCode) + log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return "", errors.New(response.ResultCode) } return response.SubAccount, nil @@ -323,7 +323,7 @@ func (a *ANX) GetDepositAddressByCurrency(currency, name string, newAddr bool) ( } if response.ResultCode != "OK" { - log.Errorf("Response code is not OK: %s\n", response.ResultCode) + log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return "", errors.New(response.ResultCode) } @@ -356,7 +356,7 @@ func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interf } if a.Verbose { - log.Debugf("Request JSON: %s\n", PayloadJSON) + log.Debugf(log.ExchangeSys, "Request JSON: %s\n", PayloadJSON) } hmac := crypto.GetHMAC(crypto.HashSHA512, []byte(path+string("\x00")+string(PayloadJSON)), []byte(a.API.Credentials.Secret)) @@ -430,7 +430,7 @@ func (a *ANX) GetAccountInformation() (AccountInformation, error) { } if response.ResultCode != "OK" { - log.Errorf("Response code is not OK: %s\n", response.ResultCode) + log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return response, errors.New(response.ResultCode) } return response, nil @@ -453,7 +453,7 @@ func (a *ANX) CheckAPIWithdrawPermission() (bool, error) { } if !apiAllowsWithdraw { - log.Warn("API key is missing withdrawal permissions") + log.Warn(log.ExchangeSys, "API key is missing withdrawal permissions") } return apiAllowsWithdraw, nil diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index e0472ab8..9cd16ef5 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -130,12 +130,13 @@ func (a *ANX) Run() { if !common.StringDataContains(a.GetEnabledPairs(asset.Spot).Strings(), "_") || !common.StringDataContains(a.GetAvailablePairs(asset.Spot).Strings(), "_") { enabledPairs := currency.NewPairsFromStrings([]string{"BTC_USD,BTC_HKD,BTC_EUR,BTC_CAD,BTC_AUD,BTC_SGD,BTC_JPY,BTC_GBP,BTC_NZD,LTC_BTC,DOG_EBTC,STR_BTC,XRP_BTC"}) - log.Warn("WARNING: Enabled pairs for ANX reset due to config upgrade, please enable the ones you would like again.") + log.Warn(log.ExchangeSys, + "Enabled pairs for ANX reset due to config upgrade, please enable the ones you would like again.") forceUpdate = true err := a.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf("%s failed to update currencies.\n", a.GetName()) + log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", a.GetName()) return } } @@ -146,7 +147,7 @@ func (a *ANX) Run() { err := a.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", a.GetName(), err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", a.GetName(), err) } } diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 8c753b60..574ac2ec 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -499,7 +499,7 @@ func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, re headers["X-MBX-APIKEY"] = b.API.Credentials.Key if b.Verbose { - log.Debugf("sent path: %s", path) + log.Debugf(log.ExchangeSys, "sent path: %s", path) } path = common.EncodeURLValues(path, params) diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 275f89bc..2ca56073 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -131,7 +131,8 @@ func (b *Binance) Start(wg *sync.WaitGroup) { // Run implements the Binance wrapper func (b *Binance) Run() { if b.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.Websocket.GetWebsocketURL()) + log.Debugf(log.ExchangeSys, + "%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.Websocket.GetWebsocketURL()) b.PrintEnabledPairs() } @@ -139,12 +140,13 @@ func (b *Binance) Run() { if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), "-") || !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), "-") { enabledPairs := currency.NewPairsFromStrings([]string{"BTC-USDT"}) - log.Warn("WARNING: Available pairs for Binance reset due to config upgrade, please enable the ones you would like again") + log.Warn(log.ExchangeSys, + "Available pairs for Binance reset due to config upgrade, please enable the ones you would like again") forceUpdate = true err := b.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf("%s failed to update currencies. Err: %s\n", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", b.Name, err) } } @@ -154,7 +156,7 @@ func (b *Binance) Run() { err := b.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) } } diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index e4836dad..710dc492 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -951,7 +951,7 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequest(method, path string, params map[ } if b.Verbose { - log.Debugf("Request JSON: %s\n", PayloadJSON) + log.Debugf(log.ExchangeSys, "Request JSON: %s\n", PayloadJSON) } PayloadBase64 := crypto.Base64Encode(PayloadJSON) diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index b535a63c..5a19acb7 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -73,7 +73,7 @@ func (b *Bitfinex) wsSend(data interface{}) error { return err } if b.Verbose { - log.Debugf("%v sending message to websocket %v", b.Name, data) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %v", b.Name, data) } return b.WebsocketConn.WriteMessage(websocket.TextMessage, json) } @@ -119,7 +119,7 @@ func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) { b.WebsocketSubdChannels[chanID] = chanInfo if b.Verbose { - log.Debugf("%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", + log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", b.GetName(), channel, pair, @@ -163,13 +163,13 @@ func (b *Bitfinex) WsConnect() error { err = b.WsSendAuth() if err != nil { - log.Errorf("%v - authentication failed: %v", b.Name, err) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err) } b.GenerateDefaultSubscriptions() if hs.Event == "info" { if b.Verbose { - log.Debugf("%s Connected to Websocket.\n", b.GetName()) + log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.GetName()) } } @@ -224,7 +224,7 @@ func (b *Bitfinex) WsDataHandler() { eventData := result.(map[string]interface{}) event := eventData["event"] if b.Verbose { - log.Debugf("%v Received message. Type '%v' Message: %v", b.Name, event, eventData) + log.Debugf(log.ExchangeSys, "%v Received message. Type '%v' Message: %v", b.Name, event, eventData) } switch event { case "subscribed": diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index c1cb0e4c..e464154d 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -131,7 +131,8 @@ func (b *Bitfinex) Start(wg *sync.WaitGroup) { // Run implements the Bitfinex wrapper func (b *Bitfinex) Run() { if b.Verbose { - log.Debugf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled())) + log.Debugf(log.ExchangeSys, + "%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled())) b.PrintEnabledPairs() } @@ -141,7 +142,8 @@ func (b *Bitfinex) Run() { err := b.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", b.Name, err) } } @@ -462,7 +464,7 @@ func (b *Bitfinex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) orderSide := exchange.OrderSide(strings.ToUpper(resp[i].Side)) timestamp, err := strconv.ParseInt(resp[i].Timestamp, 10, 64) if err != nil { - log.Warnf("Unable to convert timestamp '%v', leaving blank", resp[i].Timestamp) + log.Warnf(log.ExchangeSys, "Unable to convert timestamp '%v', leaving blank", resp[i].Timestamp) } orderDate := time.Unix(timestamp, 0) @@ -521,7 +523,7 @@ func (b *Bitfinex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) orderSide := exchange.OrderSide(strings.ToUpper(resp[i].Side)) timestamp, err := strconv.ParseInt(resp[i].Timestamp, 10, 64) if err != nil { - log.Warnf("Unable to convert timestamp '%v', leaving blank", resp[i].Timestamp) + log.Warnf(log.ExchangeSys, "Unable to convert timestamp '%v', leaving blank", resp[i].Timestamp) } orderDate := time.Unix(timestamp, 0) diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index fb669997..6fb47030 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -78,7 +78,7 @@ func TestGetAddressInfoCA(t *testing.T) { t.Error("test failed - Bitflyer - GetAddressInfoCA() error:", err) } if v.UnconfirmedBalance == 0 || v.ConfirmedBalance == 0 { - log.Warn("Donation wallet is empty :( - please consider donating") + log.Warn(log.ExchangeSys, "Donation wallet is empty :( - please consider donating") } } diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index 4f254ffc..73236a6c 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -122,7 +122,7 @@ func (b *Bitflyer) Run() { err := b.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) } } diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index e84c4cd2..0620587a 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -121,7 +121,7 @@ func (b *Bithumb) Run() { err := b.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) } } diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index ea4df7f3..7e4938f5 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -101,7 +101,7 @@ func (b *Bitmex) WsConnector() error { } if b.Verbose { - log.Debugf("Successfully connected to Bitmex %s at time: %s Limit: %d", + log.Debugf(log.ExchangeSys, "Successfully connected to Bitmex %s at time: %s Limit: %d", welcomeResp.Info, welcomeResp.Timestamp, welcomeResp.Limit.Remaining) @@ -112,7 +112,7 @@ func (b *Bitmex) WsConnector() error { err = b.websocketSendAuth() if err != nil { - log.Errorf("%v - authentication failed: %v", b.Name, err) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err) } b.GenerateAuthenticatedSubscriptions() return nil @@ -195,13 +195,13 @@ func (b *Bitmex) wsHandleIncomingData() { b.Websocket.DataHandler <- decodedResp if len(quickCapture) == 3 { if b.Verbose { - log.Debugf("%s websocket: Successfully subscribed to %s", + log.Debugf(log.ExchangeSys, "%s websocket: Successfully subscribed to %s", b.Name, decodedResp.Subscribe) } } else { b.Websocket.SetCanUseAuthenticatedEndpoints(true) if b.Verbose { - log.Debugf("%s websocket: Successfully authenticated websocket connection", + log.Debugf(log.ExchangeSys, "%s websocket: Successfully authenticated websocket connection", b.Name) } } @@ -561,7 +561,7 @@ func (b *Bitmex) wsSend(data interface{}) error { b.wsRequestMtx.Lock() defer b.wsRequestMtx.Unlock() if b.Verbose { - log.Debugf("%v sending message to websocket %v", b.Name, data) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %v", b.Name, data) } return b.WebsocketConn.WriteJSON(data) } diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 4e1daabe..dc74e474 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -153,7 +153,7 @@ func (b *Bitmex) Start(wg *sync.WaitGroup) { // Run implements the Bitmex wrapper func (b *Bitmex) Run() { if b.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.API.Endpoints.WebsocketURL) + log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.API.Endpoints.WebsocketURL) b.PrintEnabledPairs() } @@ -163,7 +163,7 @@ func (b *Bitmex) Run() { err := b.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) } } @@ -221,7 +221,7 @@ func (b *Bitmex) UpdateTradablePairs(forceUpdate bool) error { err = b.UpdatePairs(currency.NewPairsFromStrings(assetPairs), b.CurrencyPairs.AssetTypes[x], false, false) if err != nil { - log.Warnf("%s failed to update available pairs. Err: %v", b.Name, err) + log.Warnf(log.ExchangeSys, "%s failed to update available pairs. Err: %v", b.Name, err) } assetPairs = nil } diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 6787ff52..d5d6c437 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -192,12 +192,12 @@ func (b *Bitstamp) GetOrderbook(currency string) (Orderbook, error) { for _, x := range resp.Bids { price, err := strconv.ParseFloat(x[0], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } amount, err := strconv.ParseFloat(x[1], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } orderbook.Bids = append(orderbook.Bids, OrderbookBase{price, amount}) @@ -206,12 +206,12 @@ func (b *Bitstamp) GetOrderbook(currency string) (Orderbook, error) { for _, x := range resp.Asks { price, err := strconv.ParseFloat(x[0], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } amount, err := strconv.ParseFloat(x[1], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } orderbook.Asks = append(orderbook.Asks, OrderbookBase{price, amount}) @@ -598,7 +598,7 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url } if b.Verbose { - log.Debugf("Sending POST request to " + path) + log.Debugf(log.ExchangeSys, "Sending POST request to "+path) } headers := make(map[string]string) diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 7d97743b..f6cde5a4 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -46,7 +46,7 @@ func (b *Bitstamp) WsConnect() error { } if b.Verbose { - log.Debugf("%s Connected to Websocket.\n", b.GetName()) + log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.GetName()) } err = b.seedOrderBook() @@ -69,7 +69,7 @@ func (b *Bitstamp) WsReadData() (exchange.WebsocketResponse, error) { } if b.Verbose { - log.Debugf("%s websocket raw response: %s", b.GetName(), resp) + log.Debugf(log.ExchangeSys, "%s websocket raw response: %s", b.GetName(), resp) } b.Websocket.TrafficAlert <- struct{}{} @@ -106,7 +106,7 @@ func (b *Bitstamp) WsHandleData() { switch wsResponse.Event { case "bts:request_reconnect": if b.Verbose { - log.Debugf("%v - Websocket reconnection request received", b.GetName()) + log.Debugf(log.ExchangeSys, "%v - Websocket reconnection request received", b.GetName()) } go b.Websocket.WebsocketReset() diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 686e778e..6ad69992 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -129,7 +129,7 @@ func (b *Bitstamp) Start(wg *sync.WaitGroup) { // Run implements the Bitstamp wrapper func (b *Bitstamp) Run() { if b.Verbose { - log.Debugf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled())) + log.Debugf(log.ExchangeSys, "%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled())) b.PrintEnabledPairs() } @@ -139,7 +139,7 @@ func (b *Bitstamp) Run() { err := b.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) } } @@ -489,7 +489,7 @@ func (b *Bitstamp) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) case order.XRP > 0: baseCurrency = currency.XRP default: - log.Warnf("no base currency found for OrderID '%v'", order.OrderID) + log.Warnf(log.ExchangeSys, "no base currency found for OrderID '%v'", order.OrderID) } switch { @@ -498,7 +498,7 @@ func (b *Bitstamp) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) case order.EUR > 0: quoteCurrency = currency.EUR default: - log.Warnf("no quote currency found for orderID '%v'", order.OrderID) + log.Warnf(log.ExchangeSys, "no quote currency found for orderID '%v'", order.OrderID) } var currPair currency.Pair diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 579fa626..47b375d1 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -119,11 +119,11 @@ func (b *Bittrex) Run() { !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), "-") { forceUpdate = true enabledPairs := []string{"USDT-BTC"} - log.Warn("WARNING: Available pairs for Bittrex reset due to config upgrade, please enable the ones you would like again") + log.Warn(log.ExchangeSys, "Available pairs for Bittrex reset due to config upgrade, please enable the ones you would like again") err := b.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), asset.Spot, true, true) if err != nil { - log.Errorf("%s failed to update currencies. Err: %s\n", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", b.Name, err) } } @@ -133,7 +133,7 @@ func (b *Bittrex) Run() { err := b.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) } } @@ -424,7 +424,7 @@ func (b *Bittrex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( for i := range resp.Result { orderDate, err := time.Parse(time.RFC3339, resp.Result[i].Opened) if err != nil { - log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", b.Name, "GetActiveOrders", resp.Result[i].OrderUUID, resp.Result[i].Opened) } @@ -468,7 +468,7 @@ func (b *Bittrex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( for i := range resp.Result { orderDate, err := time.Parse(time.RFC3339, resp.Result[i].TimeStamp) if err != nil { - log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", b.Name, "GetActiveOrders", resp.Result[i].OrderUUID, resp.Result[i].Opened) } diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 84fa534a..cd196840 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -393,7 +393,7 @@ func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data, result []byte(req), []byte(b.API.Credentials.Secret)) if b.Verbose { - log.Debugf("Sending %s request to URL %s with params %s\n", + log.Debugf(log.ExchangeSys, "Sending %s request to URL %s with params %s\n", reqType, b.API.Endpoints.URL+path, req) diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index ab76f7ba..65abbea8 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -118,12 +118,12 @@ func (b *BTCMarkets) Run() { if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), "-") || !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), "-") { enabledPairs := []string{"BTC-AUD"} - log.Println("WARNING: Available pairs for BTC Makrets reset due to config upgrade, please enable the pairs you would like again.") + log.Warnln(log.ExchangeSys, "Available pairs for BTC Markets reset due to config upgrade, please enable the pairs you would like again.") forceUpdate = true err := b.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), asset.Spot, true, true) if err != nil { - log.Errorf("%s failed to update currencies. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s", b.Name, err) } } @@ -133,7 +133,7 @@ func (b *BTCMarkets) Run() { err := b.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) } } diff --git a/exchanges/btse/btse.go b/exchanges/btse/btse.go index 78f0fc4d..ca5fd25b 100644 --- a/exchanges/btse/btse.go +++ b/exchanges/btse/btse.go @@ -203,7 +203,7 @@ func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[str p := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, endpoint) if b.Verbose { - log.Debugf("Sending %s request to URL %s with params %s\n", method, p, string(payload)) + log.Debugf(log.ExchangeSys, "Sending %s request to URL %s with params %s\n", method, p, string(payload)) } return b.SendPayload(method, p, headers, strings.NewReader(string(payload)), &result, true, false, b.Verbose, b.HTTPDebugging) diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index 66c44252..6193aa09 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -93,7 +93,7 @@ func (b *BTSE) WsHandleData() { if strings.Contains(string(resp.Raw), "Welcome to BTSE") { if b.Verbose { - log.Debugf("%s websocket client successfully connected to %s", + log.Debugf(log.ExchangeSys, "%s websocket client successfully connected to %s", b.Name, b.Websocket.GetWebsocketURL()) } continue @@ -252,7 +252,7 @@ func (b *BTSE) wsSend(data interface{}) error { b.wsRequestMtx.Lock() defer b.wsRequestMtx.Unlock() if b.Verbose { - log.Debugf("%v sending message to websocket %v", b.Name, data) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %v", b.Name, data) } json, err := common.JSONEncode(data) if err != nil { diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 081740b5..cb4750b4 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -135,7 +135,7 @@ func (b *BTSE) Run() { err := b.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) } } @@ -445,7 +445,7 @@ func (b *BTSE) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]e fills, err := b.GetFills(order.ID, "", "", "", "") if err != nil { - log.Errorf("unable to get order fills for orderID %s", order.ID) + log.Errorf(log.ExchangeSys, "unable to get order fills for orderID %s", order.ID) continue } diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 569a4646..5cbb9ec1 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -731,7 +731,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(method, path string, params m } if c.Verbose { - log.Debugf("Request JSON: %s\n", payload) + log.Debugf(log.ExchangeSys, "Request JSON: %s\n", payload) } } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 212311fc..511ad307 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -354,7 +354,7 @@ func (c *CoinbasePro) wsSend(data interface{}) error { c.wsRequestMtx.Lock() defer c.wsRequestMtx.Unlock() if c.Verbose { - log.Debugf("%v sending message to websocket %v", c.Name, data) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %v", c.Name, data) } json, err := common.JSONEncode(data) if err != nil { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 893ab333..88c2a91d 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -130,7 +130,7 @@ func (c *CoinbasePro) Start(wg *sync.WaitGroup) { // Run implements the coinbasepro wrapper func (c *CoinbasePro) Run() { if c.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket.IsEnabled()), coinbaseproWebsocketURL) + log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket.IsEnabled()), coinbaseproWebsocketURL) c.PrintEnabledPairs() } @@ -140,7 +140,7 @@ func (c *CoinbasePro) Run() { err := c.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", c.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", c.Name, err) } } @@ -434,7 +434,7 @@ func (c *CoinbasePro) GetActiveOrders(getOrdersRequest *exchange.GetOrdersReques orderType := exchange.OrderType(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) if err != nil { - log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", c.Name, "GetActiveOrders", respOrders[i].ID, respOrders[i].CreatedAt) } @@ -477,7 +477,7 @@ func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersReques orderType := exchange.OrderType(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) if err != nil { - log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", c.Name, "GetActiveOrders", respOrders[i].ID, respOrders[i].CreatedAt) } diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 28f646d3..dc8741f1 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -276,7 +276,7 @@ func (c *COINUT) SendHTTPRequest(apiRequest string, params map[string]interface{ } if c.Verbose { - log.Debugf("Request JSON: %s", payload) + log.Debugf(log.ExchangeSys, "Request JSON: %s", payload) } headers := make(map[string]string) diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index 44b8f4d4..ce09a3b8 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -459,7 +459,7 @@ func (c *COINUT) wsSend(data interface{}) error { return err } if c.Verbose { - log.Debugf("%v sending message to websocket %v", c.Name, string(json)) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %v", c.Name, string(json)) } // Basic rate limiter time.Sleep(coinutWebsocketRateLimit) diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index cf971dc8..cf650a04 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -128,7 +128,7 @@ func (c *COINUT) Start(wg *sync.WaitGroup) { // Run implements the COINUT wrapper func (c *COINUT) Run() { if c.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket.IsEnabled()), coinutWebsocketURL) + log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket.IsEnabled()), coinutWebsocketURL) c.PrintEnabledPairs() } @@ -138,7 +138,7 @@ func (c *COINUT) Run() { err := c.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", c.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", c.Name, err) } } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index f050beb3..3cb31f64 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -395,7 +395,7 @@ func (e *Base) SetAPIKeys(apiKey, apiSecret, clientID string) { if err != nil { e.API.AuthenticatedSupport = false e.API.AuthenticatedWebsocketSupport = false - log.Warnf(warningBase64DecryptSecretKeyFailed, e.Name) + log.Warnf(log.ExchangeSys, warningBase64DecryptSecretKeyFailed, e.Name) } e.API.Credentials.Secret = string(result) } else { @@ -482,7 +482,8 @@ func (e *Base) ValidateAPICredentials() bool { if e.API.CredentialsValidator.RequiresKey { if e.API.Credentials.Key == "" || e.API.Credentials.Key == config.DefaultAPIKey { - log.Warnf("exchange %s requires API key but default/empty one set", + log.Warnf(log.ExchangeSys, + "exchange %s requires API key but default/empty one set", e.Name) return false } @@ -491,7 +492,8 @@ func (e *Base) ValidateAPICredentials() bool { if e.API.CredentialsValidator.RequiresSecret { if e.API.Credentials.Secret == "" || e.API.Credentials.Secret == config.DefaultAPISecret { - log.Warnf("exchange %s requires API secret but default/empty one set", + log.Warnf(log.ExchangeSys, + "exchange %s requires API secret but default/empty one set", e.Name) return false } @@ -500,7 +502,8 @@ func (e *Base) ValidateAPICredentials() bool { if e.API.CredentialsValidator.RequiresPEM { if e.API.Credentials.PEMKey == "" || strings.Contains(e.API.Credentials.PEMKey, "JUSTADUMMY") { - log.Warnf("exchange %s requires API PEM key but default/empty one set", + log.Warnf(log.ExchangeSys, + "exchange %s requires API PEM key but default/empty one set", e.Name) return false } @@ -509,7 +512,8 @@ func (e *Base) ValidateAPICredentials() bool { if e.API.CredentialsValidator.RequiresClientID { if e.API.Credentials.ClientID == "" || e.API.Credentials.ClientID == config.DefaultAPIClientID { - log.Warnf("exchange %s requires API ClientID but default/empty one set", + log.Warnf(log.ExchangeSys, + "exchange %s requires API ClientID but default/empty one set", e.Name) return false } @@ -518,7 +522,8 @@ func (e *Base) ValidateAPICredentials() bool { if e.API.CredentialsValidator.RequiresBase64DecodeSecret && !e.LoadedByConfig { _, err := crypto.Base64Decode(e.API.Credentials.Secret) if err != nil { - log.Warnf("exchange %s API secret base64 decode failed: %s", + log.Warnf(log.ExchangeSys, + "exchange %s API secret base64 decode failed: %s", e.Name, err) return false } @@ -575,15 +580,18 @@ func (e *Base) UpdatePairs(exchangeProducts currency.Pairs, assetType asset.Item if force || len(newPairs) > 0 || len(removedPairs) > 0 { if force { - log.Debugf("%s forced update of %s [%v] pairs.", e.Name, updateType, + log.Debugf(log.ExchangeSys, + "%s forced update of %s [%v] pairs.", e.Name, updateType, strings.ToUpper(assetType.String())) } else { if len(newPairs) > 0 { - log.Debugf("%s Updating pairs [%v] - New: %s.\n", e.Name, + log.Debugf(log.ExchangeSys, + "%s Updating pairs [%v] - New: %s.\n", e.Name, strings.ToUpper(assetType.String()), newPairs) } if len(removedPairs) > 0 { - log.Debugf("%s Updating pairs [%v] - Removed: %s.\n", e.Name, + log.Debugf(log.ExchangeSys, + "%s Updating pairs [%v] - Removed: %s.\n", e.Name, strings.ToUpper(assetType.String()), removedPairs) } } @@ -725,7 +733,7 @@ func (e *Base) IsAssetTypeSupported(asset asset.Item) bool { // PrintEnabledPairs prints the exchanges enabled asset pairs func (e *Base) PrintEnabledPairs() { for k, v := range e.CurrencyPairs.Pairs { - log.Infof("%s Asset type %v:\n\t Enabled pairs: %v", + log.Infof(log.ExchangeSys, "%s Asset type %v:\n\t Enabled pairs: %v", e.Name, strings.ToUpper(k.String()), v.Enabled) } } diff --git a/exchanges/exmo/exmo.go b/exchanges/exmo/exmo.go index de89512b..81002d26 100644 --- a/exchanges/exmo/exmo.go +++ b/exchanges/exmo/exmo.go @@ -321,7 +321,7 @@ func (e *EXMO) SendAuthenticatedHTTPRequest(method, endpoint string, vals url.Va []byte(e.API.Credentials.Secret)) if e.Verbose { - log.Debugf("Sending %s request to %s with params %s\n", + log.Debugf(log.ExchangeSys, "Sending %s request to %s with params %s\n", method, endpoint, payload) diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index d8731a2e..120a679a 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -122,7 +122,7 @@ func (e *EXMO) Run() { err := e.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", e.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", e.Name, err) } } diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 48d25700..665a2b26 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -52,7 +52,7 @@ func (g *Gateio) WsConnect() error { err = g.wsServerSignIn() if err != nil { - log.Errorf("%v - authentication failed: %v", g.Name, err) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", g.Name, err) } g.GenerateAuthenticatedSubscriptions() g.GenerateDefaultSubscriptions() @@ -456,7 +456,7 @@ func (g *Gateio) wsSend(data interface{}) error { g.wsRequestMtx.Lock() defer g.wsRequestMtx.Unlock() if g.Verbose { - log.Debugf("%v sending message to websocket %v", g.Name, data) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %v", g.Name, data) } // Basic rate limiter time.Sleep(gateioWebsocketRateLimit) diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index ae9940f7..afe171c8 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -142,7 +142,7 @@ func (g *Gateio) Run() { err := g.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", g.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", g.Name, err) } } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 423124ba..7800fb47 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -433,7 +433,7 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st } if g.Verbose { - log.Debugf("Request JSON: %s", PayloadJSON) + log.Debugf(log.ExchangeSys, "Request JSON: %s", PayloadJSON) } PayloadBase64 := crypto.Base64Encode(PayloadJSON) diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 661e1ef4..c545f839 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -48,7 +48,7 @@ func (g *Gemini) WsConnect() error { go g.WsHandleData() err := g.WsSecureSubscribe(&dialer, geminiWsOrderEvents) if err != nil { - log.Errorf("%v - authentication failed: %v", g.Name, err) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", g.Name, err) } return g.WsSubscribe(&dialer) } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 05ff5f9c..1bd4366c 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -140,7 +140,7 @@ func (g *Gemini) Run() { err := g.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", g.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", g.Name, err) } } diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index d4e4590a..4fca1fe2 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -52,7 +52,7 @@ func (h *HitBTC) WsConnect() error { go h.WsHandleData() err = h.wsLogin() if err != nil { - log.Errorf("%v - authentication failed: %v", h.Name, err) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err) } h.GenerateDefaultSubscriptions() @@ -391,7 +391,7 @@ func (h *HitBTC) wsSend(data interface{}) error { return err } if h.Verbose { - log.Debugf("%v sending message to websocket %v", h.Name, string(json)) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %v", h.Name, string(json)) } return h.WebsocketConn.WriteMessage(websocket.TextMessage, json) } diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 0cbf9af8..e7355350 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -127,7 +127,7 @@ func (h *HitBTC) Start(wg *sync.WaitGroup) { // Run implements the HitBTC wrapper func (h *HitBTC) Run() { if h.Verbose { - log.Debugf("%s Websocket: %s (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket.IsEnabled()), hitbtcWebsocketAddress) + log.Debugf(log.ExchangeSys, "%s Websocket: %s (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket.IsEnabled()), hitbtcWebsocketAddress) h.PrintEnabledPairs() } @@ -135,12 +135,12 @@ func (h *HitBTC) Run() { if !common.StringDataContains(h.GetEnabledPairs(asset.Spot).Strings(), "-") || !common.StringDataContains(h.GetAvailablePairs(asset.Spot).Strings(), "-") { enabledPairs := []string{"BTC-USD"} - log.Warn("WARNING: Available pairs for HitBTC reset due to config upgrade, please enable the ones you would like again.") + log.Warn(log.ExchangeSys, "Available pairs for HitBTC reset due to config upgrade, please enable the ones you would like again.") forceUpdate = true err := h.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), asset.Spot, true, true) if err != nil { - log.Errorf("%s failed to update enabled currencies.\n", h.GetName()) + log.Errorf(log.ExchangeSys, "%s failed to update enabled currencies.\n", h.GetName()) } } @@ -150,7 +150,7 @@ func (h *HitBTC) Run() { err := h.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", h.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", h.Name, err) } } diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index 702bdf44..4eff4899 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -72,11 +72,11 @@ func (h *HUOBI) WsConnect() error { } err = h.wsAuthenticatedDial(&dialer) if err != nil { - log.Errorf("%v - authenticated dial failed: %v", h.Name, err) + log.Errorf(log.ExchangeSys, "%v - authenticated dial failed: %v\n", h.Name, err) } err = h.wsLogin() if err != nil { - log.Errorf("%v - authentication failed: %v", h.Name, err) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err) } go h.WsHandleData() @@ -156,7 +156,7 @@ func (h *HUOBI) WsHandleData() { return case resp := <-comms: if h.Verbose { - log.Debugf("%v: %v: %v", h.Name, resp.URL, string(resp.Raw)) + log.Debugf(log.ExchangeSys, "%v: %v: %v", h.Name, resp.URL, string(resp.Raw)) } switch resp.URL { case wsMarketURL: @@ -189,14 +189,14 @@ func (h *HUOBI) wsHandleAuthenticatedData(resp WsMessage) { if init.Ping != 0 { err = h.WebsocketConn.WriteJSON(`{"pong":1337}`) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) } return } if init.Op == "sub" { if h.Verbose { - log.Debugf("%v: %v: Successfully subscribed to %v", h.Name, resp.URL, init.Topic) + log.Debugf(log.ExchangeSys, "%v: %v: Successfully subscribed to %v", h.Name, resp.URL, init.Topic) } return } @@ -277,7 +277,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { if init.Ping != 0 { err = h.WebsocketConn.WriteJSON(`{"pong":1337}`) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) } return } @@ -420,7 +420,7 @@ func (h *HUOBI) wsSend(data []byte) error { h.wsRequestMtx.Lock() defer h.wsRequestMtx.Unlock() if h.Verbose { - log.Debugf("%v sending message to websocket %s", h.Name, string(data)) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %s", h.Name, string(data)) } return h.WebsocketConn.WriteMessage(websocket.TextMessage, data) } @@ -456,7 +456,7 @@ func (h *HUOBI) wsAuthenticatedSend(request interface{}) error { return err } if h.Verbose { - log.Debugf("%v sending Authenticated message to websocket %s", h.Name, string(encodedRequest)) + log.Debugf(log.ExchangeSys, "%v sending Authenticated message to websocket %s", h.Name, string(encodedRequest)) } return h.AuthenticatedWebsocketConn.WriteMessage(websocket.TextMessage, encodedRequest) } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 571b2b9f..4a25341d 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -134,7 +134,7 @@ func (h *HUOBI) Start(wg *sync.WaitGroup) { // Run implements the HUOBI wrapper func (h *HUOBI) Run() { if h.Verbose { - log.Debugf("%s Websocket: %s (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket.IsEnabled()), wsMarketURL) + log.Debugf(log.ExchangeSys, "%s Websocket: %s (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket.IsEnabled()), wsMarketURL) h.PrintEnabledPairs() } @@ -148,7 +148,7 @@ func (h *HUOBI) Run() { cfg := config.GetConfig() exchCfg, err := cfg.GetExchangeConfig(h.Name) if err != nil { - log.Errorf("%s failed to get exchange config. %s\n", h.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to get exchange config. %s\n", h.Name, err) return } exchCfg.BaseCurrencies = currency.Currencies{currency.USD} @@ -162,11 +162,11 @@ func (h *HUOBI) Run() { Delimiter: "-", }, } - log.Warn("WARNING: Available and enabled pairs for Huobi reset due to config upgrade, please enable the ones you would like again") + log.Warn(log.ExchangeSys, "Available and enabled pairs for Huobi reset due to config upgrade, please enable the ones you would like again") err := h.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf("%s Failed to update enabled currencies.\n", h.GetName()) + log.Errorf(log.ExchangeSys, "%s Failed to update enabled currencies.\n", h.GetName()) } } @@ -176,7 +176,7 @@ func (h *HUOBI) Run() { err := h.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", h.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", h.Name, err) } } diff --git a/exchanges/huobihadax/huobihadax.go b/exchanges/huobihadax/huobihadax.go index 23f5a7bf..835b0070 100644 --- a/exchanges/huobihadax/huobihadax.go +++ b/exchanges/huobihadax/huobihadax.go @@ -17,6 +17,7 @@ import ( "github.com/thrasher-/gocryptotrader/common/crypto" "github.com/thrasher-/gocryptotrader/currency" exchange "github.com/thrasher-/gocryptotrader/exchanges" + log "github.com/thrasher-/gocryptotrader/logger" ) const ( @@ -330,7 +331,7 @@ func (h *HUOBIHADAX) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) bytesParams, _ := json.Marshal(vals) postBodyParams := string(bytesParams) if h.Verbose { - fmt.Println("Post params:", postBodyParams) + log.Debugf(log.ExchangeSys, "Post params: %v", postBodyParams) } var result response diff --git a/exchanges/huobihadax/huobihadax_test.go b/exchanges/huobihadax/huobihadax_test.go index b2990155..ae3294d4 100644 --- a/exchanges/huobihadax/huobihadax_test.go +++ b/exchanges/huobihadax/huobihadax_test.go @@ -1,7 +1,6 @@ package huobihadax import ( - "fmt" "strconv" "testing" "time" @@ -276,7 +275,7 @@ func TestSpotNewOrder(t *testing.T) { if err != nil { t.Errorf("Test failed - Huobi SpotNewOrder: %s", err) } else { - fmt.Println(newOrderID) + t.Log(newOrderID) } } diff --git a/exchanges/huobihadax/huobihadax_websocket.go b/exchanges/huobihadax/huobihadax_websocket.go index 2747a081..004a48da 100644 --- a/exchanges/huobihadax/huobihadax_websocket.go +++ b/exchanges/huobihadax/huobihadax_websocket.go @@ -72,11 +72,11 @@ func (h *HUOBIHADAX) WsConnect() error { } err = h.wsAuthenticatedDial(&dialer) if err != nil { - log.Errorf("%v - authenticated dial failed: %v", h.Name, err) + log.Errorf(log.ExchangeSys, "%v - authenticated dial failed: %v\n", h.Name, err) } err = h.wsLogin() if err != nil { - log.Errorf("%v - authentication failed: %v", h.Name, err) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err) } go h.WsHandleData() @@ -156,7 +156,7 @@ func (h *HUOBIHADAX) WsHandleData() { return case resp := <-comms: if h.Verbose { - log.Debugf("%v: %v: %v", h.Name, resp.URL, string(resp.Raw)) + log.Debugf(log.ExchangeSys, "%v: %v: %v", h.Name, resp.URL, string(resp.Raw)) } switch resp.URL { case HuobiHadaxSocketIOAddress: @@ -189,14 +189,14 @@ func (h *HUOBIHADAX) wsHandleAuthenticatedData(resp WsMessage) { if init.Ping != 0 { err = h.WebsocketConn.WriteJSON(`{"pong":1337}`) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) } return } if init.Op == "sub" { if h.Verbose { - log.Debugf("%v: %v: Successfully subscribed to %v", h.Name, resp.URL, init.Topic) + log.Debugf(log.ExchangeSys, "%v: %v: Successfully subscribed to %v", h.Name, resp.URL, init.Topic) } return } @@ -277,7 +277,7 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) { if init.Ping != 0 { err = h.WebsocketConn.WriteJSON(`{"pong":1337}`) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) } return } @@ -420,7 +420,7 @@ func (h *HUOBIHADAX) wsSend(data []byte) error { h.wsRequestMtx.Lock() defer h.wsRequestMtx.Unlock() if h.Verbose { - log.Debugf("%v sending message to websocket %s", h.Name, string(data)) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %s", h.Name, string(data)) } return h.WebsocketConn.WriteMessage(websocket.TextMessage, data) } @@ -456,7 +456,7 @@ func (h *HUOBIHADAX) wsAuthenticatedSend(request interface{}) error { return err } if h.Verbose { - log.Debugf("%v sending Authenticated message to websocket %s", h.Name, string(encodedRequest)) + log.Debugf(log.ExchangeSys, "%v sending Authenticated message to websocket %s", h.Name, string(encodedRequest)) } return h.AuthenticatedWebsocketConn.WriteMessage(websocket.TextMessage, encodedRequest) } diff --git a/exchanges/huobihadax/huobihadax_wrapper.go b/exchanges/huobihadax/huobihadax_wrapper.go index d39637c3..77e9529e 100644 --- a/exchanges/huobihadax/huobihadax_wrapper.go +++ b/exchanges/huobihadax/huobihadax_wrapper.go @@ -139,7 +139,7 @@ func (h *HUOBIHADAX) Run() { err := h.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", h.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", h.Name, err) } } diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index a0a5d707..7d4258f8 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -302,7 +302,7 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method, path string, params map[str } if i.Verbose { - log.Debugf("Request JSON: %s\n", PayloadJSON) + log.Debugf(log.ExchangeSys, "Request JSON: %s\n", PayloadJSON) } } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index d59f5dac..a033256f 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -424,7 +424,7 @@ func (i *ItBit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] side := exchange.OrderSide(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { - log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", i.Name, "GetActiveOrders", allOrders[j].ID, allOrders[j].CreatedTime) } @@ -475,7 +475,7 @@ func (i *ItBit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] side := exchange.OrderSide(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { - log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", i.Name, "GetActiveOrders", allOrders[j].ID, allOrders[j].CreatedTime) } diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 153ec417..854062ef 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -835,7 +835,7 @@ func GetError(apiErrors []string) error { for _, e := range apiErrors { switch e[0] { case 'W': - log.Warnf("%s API warning: %v\n", exchangeName, e[1:]) + log.Warnf(log.ExchangeSys, "%s API warning: %v\n", exchangeName, e[1:]) default: return fmt.Errorf("%s API error: %v", exchangeName, e[1:]) } @@ -865,7 +865,7 @@ func (k *Kraken) SendAuthenticatedHTTPRequest(method string, params url.Values, append([]byte(path), shasum...), []byte(k.API.Credentials.Secret))) if k.Verbose { - log.Debugf("Sending POST request to %s, path: %s, params: %s", + log.Debugf(log.ExchangeSys, "Sending POST request to %s, path: %s, params: %s", k.API.Endpoints.URL, path, encoded) diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index 662306ea..7dc4dbda 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -72,7 +72,7 @@ func (k *Kraken) writeToWebsocket(message []byte) error { k.wsRequestMtx.Lock() defer k.wsRequestMtx.Unlock() if k.Verbose { - log.Debugf("Sending message to WS: %v", + log.Debugf(log.ExchangeSys, "Sending message to WS: %v", string(message)) } // Really basic WS rate limit @@ -98,7 +98,7 @@ func (k *Kraken) WsConnect() error { var err error if k.Verbose { - log.Debugf("Attempting to connect to %v", + log.Debugf(log.ExchangeSys, "Attempting to connect to %v", k.Websocket.GetWebsocketURL()) } k.WebsocketConn, _, err = dialer.Dial(k.Websocket.GetWebsocketURL(), @@ -109,7 +109,7 @@ func (k *Kraken) WsConnect() error { err) } if k.Verbose { - log.Debugf("Successful connection to %v", + log.Debugf(log.ExchangeSys, "Successful connection to %v", k.Websocket.GetWebsocketURL()) } go k.WsHandleData() @@ -142,7 +142,7 @@ func (k *Kraken) WsReadData() (exchange.WebsocketResponse, error) { } } if k.Verbose { - log.Debugf("%v Websocket message received: %v", + log.Debugf(log.ExchangeSys, "%v Websocket message received: %v", k.Name, string(standardMessage)) } @@ -165,7 +165,7 @@ func (k *Kraken) wsPingHandler() { case <-ticker.C: pingEvent := fmt.Sprintf("{\"event\":\"%v\"}", krakenWsPing) if k.Verbose { - log.Debugf("%v sending ping", + log.Debugf(log.ExchangeSys, "%v sending ping", k.GetName()) } err := k.writeToWebsocket([]byte(pingEvent)) @@ -221,36 +221,36 @@ func (k *Kraken) WsHandleDataResponse(response WebsocketDataResponse) { switch channelData.Subscription { case krakenWsTicker: if k.Verbose { - log.Debugf("%v Websocket ticker data received", + log.Debugf(log.ExchangeSys, "%v Websocket ticker data received", k.GetName()) } k.wsProcessTickers(&channelData, response[1]) case krakenWsOHLC: if k.Verbose { - log.Debugf("%v Websocket OHLC data received", + log.Debugf(log.ExchangeSys, "%v Websocket OHLC data received", k.GetName()) } k.wsProcessCandles(&channelData, response[1]) case krakenWsOrderbook: if k.Verbose { - log.Debugf("%v Websocket Orderbook data received", + log.Debugf(log.ExchangeSys, "%v Websocket Orderbook data received", k.GetName()) } k.wsProcessOrderBook(&channelData, response[1]) case krakenWsSpread: if k.Verbose { - log.Debugf("%v Websocket Spread data received", + log.Debugf(log.ExchangeSys, "%v Websocket Spread data received", k.GetName()) } k.wsProcessSpread(&channelData, response[1]) case krakenWsTrade: if k.Verbose { - log.Debugf("%v Websocket Trade data received", + log.Debugf(log.ExchangeSys, "%v Websocket Trade data received", k.GetName()) } k.wsProcessTrades(&channelData, response[1]) default: - log.Errorf("%v Unidentified websocket data received: %v", + log.Errorf(log.ExchangeSys, "%v Unidentified websocket data received: %v", k.GetName(), response) } } @@ -260,17 +260,17 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse) { switch response.Event { case krakenWsHeartbeat: if k.Verbose { - log.Debugf("%v Websocket heartbeat data received", + log.Debugf(log.ExchangeSys, "%v Websocket heartbeat data received", k.GetName()) } case krakenWsPong: if k.Verbose { - log.Debugf("%v Websocket pong data received", + log.Debugf(log.ExchangeSys, "%v Websocket pong data received", k.GetName()) } case krakenWsSystemStatus: if k.Verbose { - log.Debugf("%v Websocket status data received", + log.Debugf(log.ExchangeSys, "%v Websocket status data received", k.GetName()) } if response.Status != "online" { @@ -278,12 +278,12 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse) { k.GetName(), response.Status) } if response.WebsocketStatusResponse.Version != krakenWSSupportedVersion { - log.Warnf("%v New version of Websocket API released. Was %v Now %v", + log.Warnf(log.ExchangeSys, "%v New version of Websocket API released. Was %v Now %v", k.GetName(), krakenWSSupportedVersion, response.WebsocketStatusResponse.Version) } case krakenWsSubscriptionStatus: if k.Verbose { - log.Debugf("%v Websocket subscription status data received", + log.Debugf(log.ExchangeSys, "%v Websocket subscription status data received", k.GetName()) } if response.Status != "subscribed" { @@ -296,7 +296,7 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse) { } addNewSubscriptionChannelData(response) default: - log.Errorf("%v Unidentified websocket data received: %v", k.GetName(), response) + log.Errorf(log.ExchangeSys, "%v Unidentified websocket data received: %v", k.GetName(), response) } } @@ -374,7 +374,7 @@ func (k *Kraken) wsProcessSpread(channelData *WebsocketChannelData, data interfa sec, dec := math.Modf(timeData) spreadTimestamp := time.Unix(int64(sec), int64(dec*(1e9))) if k.Verbose { - log.Debugf("Spread data for '%v' received. Best bid: '%v' Best ask: '%v' Time: '%v'", + log.Debugf(log.ExchangeSys, "Spread data for '%v' received. Best bid: '%v' Best ask: '%v' Time: '%v'", channelData.Pair, bestBid, bestAsk, @@ -550,7 +550,7 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD } orderbookBuffer[channelData.ChannelID] = append(orderbookBuffer[channelData.ChannelID], ob) if k.Verbose { - log.Debugf("Adding orderbook to buffer for channel %v. Lastupdated: %v. %v / %v", + log.Debugf(log.ExchangeSys, "Adding orderbook to buffer for channel %v. Lastupdated: %v. %v / %v", channelData.ChannelID, ob.LastUpdated, len(orderbookBuffer[channelData.ChannelID]), @@ -561,12 +561,12 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD // wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData) error { if k.Verbose { - log.Debugf("Current orderbook 'LastUpdated': %v", + log.Debugf(log.ExchangeSys, "Current orderbook 'LastUpdated': %v", krakenOrderBooks[channelData.ChannelID].LastUpdated) } lowestLastUpdated := orderbookBuffer[channelData.ChannelID][0].LastUpdated if k.Verbose { - log.Debugf("Sorting orderbook. Earliest 'LastUpdated' entry: %v", + log.Debugf(log.ExchangeSys, "Sorting orderbook. Earliest 'LastUpdated' entry: %v", lowestLastUpdated) } sort.Slice(orderbookBuffer[channelData.ChannelID], func(i, j int) bool { @@ -575,7 +575,7 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData) err lowestLastUpdated = orderbookBuffer[channelData.ChannelID][0].LastUpdated if k.Verbose { - log.Debugf("Sorted orderbook. Earliest 'LastUpdated' entry: %v", + log.Debugf(log.ExchangeSys, "Sorted orderbook. Earliest 'LastUpdated' entry: %v", lowestLastUpdated) } // The earliest update has to be after the previously stored orderbook @@ -590,7 +590,7 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData) err k.updateChannelOrderbookEntries(channelData) highestLastUpdate := orderbookBuffer[channelData.ChannelID][len(orderbookBuffer[channelData.ChannelID])-1].LastUpdated if k.Verbose { - log.Debugf("Saving orderbook. Lastupdated: %v", + log.Debugf(log.ExchangeSys, "Saving orderbook. Lastupdated: %v", highestLastUpdate) } @@ -627,7 +627,7 @@ func (k *Kraken) updateChannelOrderbookAsks(i, j int, channelData *WebsocketChan askFound := k.updateChannelOrderbookAsk(i, j, channelData) if !askFound { if k.Verbose { - log.Debugf("Adding Ask for channel %v. Price %v. Amount %v", + log.Debugf(log.ExchangeSys, "Adding Ask for channel %v. Price %v. Amount %v", channelData.ChannelID, orderbookBuffer[channelData.ChannelID][i].Asks[j].Price, orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount) @@ -646,7 +646,7 @@ func (k *Kraken) updateChannelOrderbookAsk(i, j int, channelData *WebsocketChann if orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount == 0 { // Remove existing entry if k.Verbose { - log.Debugf("Removing Ask for channel %v. Price %v. Old amount %v. Buffer %v", + log.Debugf(log.ExchangeSys, "Removing Ask for channel %v. Price %v. Old amount %v. Buffer %v", channelData.ChannelID, orderbookBuffer[channelData.ChannelID][i].Asks[j].Price, krakenOrderBooks[channelData.ChannelID].Asks[l].Amount, i) @@ -657,7 +657,7 @@ func (k *Kraken) updateChannelOrderbookAsk(i, j int, channelData *WebsocketChann l-- } else if krakenOrderBooks[channelData.ChannelID].Asks[l].Amount != orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount { if k.Verbose { - log.Debugf("Updating Ask for channel %v. Price %v. Old amount %v, New Amount %v", + log.Debugf(log.ExchangeSys, "Updating Ask for channel %v. Price %v. Old amount %v, New Amount %v", channelData.ChannelID, orderbookBuffer[channelData.ChannelID][i].Asks[j].Price, krakenOrderBooks[channelData.ChannelID].Asks[l].Amount, @@ -675,7 +675,7 @@ func (k *Kraken) updateChannelOrderbookBids(i, j int, channelData *WebsocketChan bidFound := k.updateChannelOrderbookBid(i, j, channelData) if !bidFound { if k.Verbose { - log.Debugf("Adding Bid for channel %v. Price %v. Amount %v", + log.Debugf(log.ExchangeSys, "Adding Bid for channel %v. Price %v. Amount %v", channelData.ChannelID, orderbookBuffer[channelData.ChannelID][i].Bids[j].Price, orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount) @@ -694,7 +694,7 @@ func (k *Kraken) updateChannelOrderbookBid(i, j int, channelData *WebsocketChann if orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount == 0 { // Remove existing entry if k.Verbose { - log.Debugf("Removing Bid for channel %v. Price %v. Old amount %v. Buffer %v", + log.Debugf(log.ExchangeSys, "Removing Bid for channel %v. Price %v. Old amount %v. Buffer %v", channelData.ChannelID, orderbookBuffer[channelData.ChannelID][i].Bids[j].Price, krakenOrderBooks[channelData.ChannelID].Bids[l].Amount, i) @@ -705,7 +705,7 @@ func (k *Kraken) updateChannelOrderbookBid(i, j int, channelData *WebsocketChann l-- } else if krakenOrderBooks[channelData.ChannelID].Bids[l].Amount != orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount { if k.Verbose { - log.Debugf("Updating Bid for channel %v. Price %v. Old amount %v, New Amount %v", + log.Debugf(log.ExchangeSys, "Updating Bid for channel %v. Price %v. Old amount %v, New Amount %v", channelData.ChannelID, orderbookBuffer[channelData.ChannelID][i].Bids[j].Price, krakenOrderBooks[channelData.ChannelID].Bids[l].Amount, @@ -777,7 +777,7 @@ func (k *Kraken) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscript json, err := common.JSONEncode(resp) if err != nil { if k.Verbose { - log.Debugf("%v subscribe error: %v", k.Name, err) + log.Errorf(log.ExchangeSys, "%v subscribe error: %v", k.Name, err) } return err } @@ -796,7 +796,7 @@ func (k *Kraken) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscri json, err := common.JSONEncode(resp) if err != nil { if k.Verbose { - log.Debugf("%v unsubscribe error: %v", k.Name, err) + log.Errorf(log.ExchangeSys, "%v unsubscribe error: %v", k.Name, err) } return err } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 3fa3f282..4302ec8d 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -143,12 +143,12 @@ func (k *Kraken) Run() { if !common.StringDataContains(k.GetEnabledPairs(asset.Spot).Strings(), "-") || !common.StringDataContains(k.GetAvailablePairs(asset.Spot).Strings(), "-") { enabledPairs := currency.NewPairsFromStrings([]string{"XBT-USD"}) - log.Warn("WARNING: Available pairs for Kraken reset due to config upgrade, please enable the ones you would like again") + log.Warn(log.ExchangeSys, "Available pairs for Kraken reset due to config upgrade, please enable the ones you would like again") forceUpdate = true err := k.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf("%s failed to update currencies. Err: %s\n", k.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", k.Name, err) } } @@ -158,7 +158,7 @@ func (k *Kraken) Run() { err := k.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", k.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", k.Name, err) } } diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 8fec64cc..6d82855d 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -93,12 +93,12 @@ func (l *LakeBTC) GetOrderBook(currency string) (Orderbook, error) { for _, x := range resp.Bids { price, err := strconv.ParseFloat(x[0], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } amount, err := strconv.ParseFloat(x[1], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } orderbook.Bids = append(orderbook.Bids, OrderbookStructure{price, amount}) @@ -107,12 +107,12 @@ func (l *LakeBTC) GetOrderBook(currency string) (Orderbook, error) { for _, x := range resp.Asks { price, err := strconv.ParseFloat(x[0], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } amount, err := strconv.ParseFloat(x[1], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } orderbook.Asks = append(orderbook.Asks, OrderbookStructure{price, amount}) @@ -265,7 +265,7 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int hmac := crypto.GetHMAC(crypto.HashSHA1, []byte(req), []byte(l.API.Credentials.Secret)) if l.Verbose { - log.Debugf("Sending POST request to %s calling method %s with params %s\n", l.API.Endpoints.URL, method, req) + log.Debugf(log.ExchangeSys, "Sending POST request to %s calling method %s with params %s\n", l.API.Endpoints.URL, method, req) } postData := make(map[string]interface{}) diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 0101c816..1b737b4b 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -120,7 +120,7 @@ func (l *LakeBTC) Run() { err := l.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", l.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", l.Name, err) } } diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index 5a34882b..54e00269 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -635,12 +635,12 @@ func (l *LocalBitcoins) GetOrderbook(currency string) (Orderbook, error) { for _, x := range resp.Bids { price, err := strconv.ParseFloat(x[0], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } amount, err := strconv.ParseFloat(x[1], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } orderbook.Bids = append(orderbook.Bids, Price{price, amount}) @@ -649,12 +649,12 @@ func (l *LocalBitcoins) GetOrderbook(currency string) (Orderbook, error) { for _, x := range resp.Asks { price, err := strconv.ParseFloat(x[0], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } amount, err := strconv.ParseFloat(x[1], 64) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) continue } orderbook.Asks = append(orderbook.Asks, Price{price, amount}) @@ -688,7 +688,7 @@ func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, params headers["Content-Type"] = "application/x-www-form-urlencoded" if l.Verbose { - log.Debugf("Sending POST request to `%s`, path: `%s`, params: `%s`.", l.API.Endpoints.URL, path, encoded) + log.Debugf(log.ExchangeSys, "Sending POST request to `%s`, path: `%s`, params: `%s`.", l.API.Endpoints.URL, path, encoded) } if method == http.MethodGet && len(encoded) > 0 { diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index b977c30a..7f2a3f4d 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -121,7 +121,7 @@ func (l *LocalBitcoins) Run() { err := l.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", l.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", l.Name, err) } } @@ -420,7 +420,7 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequ for i := range resp { orderDate, err := time.Parse(time.RFC3339, resp[i].Data.CreatedAt) if err != nil { - log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", l.Name, "GetActiveOrders", resp[i].Data.Advertisement.ID, @@ -481,7 +481,7 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequ for i := range allTrades { orderDate, err := time.Parse(time.RFC3339, allTrades[i].Data.CreatedAt) if err != nil { - log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", l.Name, "GetActiveOrders", allTrades[i].Data.Advertisement.ID, diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 0c94dc04..7cf27e72 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -113,7 +113,7 @@ func (o *OKCoin) Start(wg *sync.WaitGroup) { // Run implements the OKEX wrapper func (o *OKCoin) Run() { if o.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) + log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) } if !o.GetEnabledFeatures().AutoPairUpdates { @@ -122,7 +122,7 @@ func (o *OKCoin) Run() { err := o.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", o.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", o.Name, err) } } diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go index 92a9cbb1..c31d391e 100644 --- a/exchanges/okex/okex_wrapper.go +++ b/exchanges/okex/okex_wrapper.go @@ -113,7 +113,7 @@ func (o *OKEX) Start(wg *sync.WaitGroup) { // Run implements the OKEX wrapper func (o *OKEX) Run() { if o.Verbose { - log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.API.Endpoints.WebsocketURL) + log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.API.Endpoints.WebsocketURL) } if !o.GetEnabledFeatures().AutoPairUpdates { @@ -122,7 +122,7 @@ func (o *OKEX) Run() { err := o.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", o.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", o.Name, err) } } diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go index ee606771..9147f8c3 100644 --- a/exchanges/okgroup/okgroup.go +++ b/exchanges/okgroup/okgroup.go @@ -495,7 +495,7 @@ func (o *OKGroup) GetMarginTransactionDetails(request GetSpotTransactionDetailsR func FormatParameters(request interface{}) (parameters string) { v, err := query.Values(request) if err != nil { - log.Errorf("Could not parse %v to URL values. Check that the type has url fields", reflect.TypeOf(request).Name()) + log.Errorf(log.ExchangeSys, "Could not parse %v to URL values. Check that the type has url fields", reflect.TypeOf(request).Name()) return } urlEncodedValues := v.Encode() @@ -546,13 +546,13 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d } if o.Verbose { - log.Debugf("Request JSON: %s\n", payload) + log.Debugf(log.ExchangeSys, "Request JSON: %s\n", payload) } } path := o.API.Endpoints.URL + requestType + o.APIVersion + requestPath if o.Verbose { - log.Debugf("Sending %v request to %s \n", requestType, path) + log.Debugf(log.ExchangeSys, "Sending %v request to %s \n", requestType, path) } headers := make(map[string]string) diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index b00e849d..79efb391 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -159,7 +159,7 @@ func (o *OKGroup) writeToWebsocket(message string) error { o.wsRequestMtx.Lock() defer o.wsRequestMtx.Unlock() if o.Verbose { - log.Debugf("%v sending message to WS: %v", o.Name, message) + log.Debugf(log.ExchangeSys, "%v sending message to WS: %v", o.Name, message) } // Really basic WS rate limit time.Sleep(okGroupWsRateLimit) @@ -183,7 +183,7 @@ func (o *OKGroup) WsConnect() error { var err error if o.Verbose { - log.Debugf("Attempting to connect to %v", o.Websocket.GetWebsocketURL()) + log.Debugf(log.ExchangeSys, "Attempting to connect to %v", o.Websocket.GetWebsocketURL()) } o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(), http.Header{}) @@ -193,7 +193,7 @@ func (o *OKGroup) WsConnect() error { err) } if o.Verbose { - log.Debugf("Successful connection to %v", + log.Debugf(log.ExchangeSys, "Successful connection to %v\n", o.Websocket.GetWebsocketURL()) } wg := sync.WaitGroup{} @@ -203,7 +203,7 @@ func (o *OKGroup) WsConnect() error { if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { err = o.WsLogin() if err != nil { - log.Errorf("%v - authentication failed: %v", o.Name, err) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", o.Name, err) } } @@ -235,7 +235,7 @@ func (o *OKGroup) WsReadData() (exchange.WebsocketResponse, error) { } } if o.Verbose { - log.Debugf("%v Websocket message received: %v", o.Name, string(standardMessage)) + log.Debugf(log.ExchangeSys, "%v Websocket message received: %v", o.Name, string(standardMessage)) } return exchange.WebsocketResponse{Raw: standardMessage}, nil @@ -259,7 +259,7 @@ func (o *OKGroup) wsPingHandler(wg *sync.WaitGroup) { case <-ticker.C: err := o.writeToWebsocket("ping") if o.Verbose { - log.Debugf("%v sending ping", o.GetName()) + log.Debugf(log.ExchangeSys, "%v sending ping", o.GetName()) } if err != nil { o.Websocket.DataHandler <- err @@ -300,7 +300,7 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) { err = common.JSONDecode(resp.Raw, &errorResponse) if err == nil && errorResponse.ErrorCode > 0 { if o.Verbose { - log.Debugf("WS Error Event: %v Message: %v", errorResponse.Event, errorResponse.Message) + log.Debugf(log.ExchangeSys, "WS Error Event: %v Message: %v", errorResponse.Event, errorResponse.Message) } o.WsHandleErrorResponse(errorResponse) continue @@ -312,7 +312,7 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) { o.Websocket.SetCanUseAuthenticatedEndpoints(eventResponse.Success) } if o.Verbose { - log.Debugf("WS Event: %v on Channel: %v", eventResponse.Event, eventResponse.Channel) + log.Debugf(log.ExchangeSys, "WS Event: %v on Channel: %v", eventResponse.Event, eventResponse.Channel) } o.Websocket.DataHandler <- eventResponse continue @@ -352,7 +352,7 @@ func (o *OKGroup) WsHandleErrorResponse(event WebsocketErrorResponse) { errorMessage := fmt.Sprintf("%v error - %v message: %s ", o.GetName(), event.ErrorCode, event.Message) if o.Verbose { - log.Error(errorMessage) + log.Error(log.ExchangeSys, errorMessage) } o.Websocket.DataHandler <- fmt.Errorf(errorMessage) } @@ -389,12 +389,12 @@ func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) { okGroupWsCandle1800s, okGroupWsCandle3600s, okGroupWsCandle7200s, okGroupWsCandle14400s, okGroupWsCandle21600s, okGroupWsCandle43200s, okGroupWsCandle86400s, okGroupWsCandle604900s: if o.Verbose { - log.Debugf("%v Websocket candle data received", o.GetName()) + log.Debugf(log.ExchangeSys, "%v Websocket candle data received", o.GetName()) } o.wsProcessCandles(response) case okGroupWsDepth, okGroupWsDepth5: if o.Verbose { - log.Debugf("%v Websocket orderbook data received", o.GetName()) + log.Debugf(log.ExchangeSys, "%v Websocket orderbook data received", o.GetName()) } // Locking, orderbooks cannot be processed out of order orderbookMutex.Lock() @@ -410,12 +410,12 @@ func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) { orderbookMutex.Unlock() case okGroupWsTicker: if o.Verbose { - log.Debugf("%v Websocket ticker data received", o.GetName()) + log.Debugf(log.ExchangeSys, "%v Websocket ticker data received", o.GetName()) } o.wsProcessTickers(response) case okGroupWsTrade: if o.Verbose { - log.Debugf("%v Websocket trade data received", o.GetName()) + log.Debugf(log.ExchangeSys, "%v Websocket trade data received", o.GetName()) } o.wsProcessTrades(response) default: @@ -427,7 +427,7 @@ func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) { // where there is no websocket datahandler for it func logDataResponse(response *WebsocketDataResponse) { for i := range response.Data { - log.Errorf("Unhandled channel: '%v'. Instrument '%v' Timestamp '%v', Data '%v", + log.Errorf(log.ExchangeSys, "Unhandled channel: '%v'. Instrument '%v' Timestamp '%v', Data '%v", response.Table, response.Data[i].InstrumentID, response.Data[i].Timestamp, @@ -474,7 +474,7 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) { instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-") timeData, err := time.Parse(time.RFC3339Nano, response.Data[i].WebsocketCandleResponse.Candle[0]) if err != nil { - log.Warnf("%v Time data could not be parsed: %v", o.GetName(), response.Data[i].Candle[0]) + log.Warnf(log.ExchangeSys, "%v Time data could not be parsed: %v", o.GetName(), response.Data[i].Candle[0]) } candleIndex := strings.LastIndex(response.Table, okGroupWsCandle) @@ -535,7 +535,7 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i return fmt.Errorf("channel: %v. Orderbook partial for %v checksum invalid", tableName, instrument) } if o.Verbose { - log.Debug("Passed checksum!") + log.Debug(log.ExchangeSys, "Passed checksum!") } asks := o.AppendWsOrderbookItems(wsEventData.Asks) bids := o.AppendWsOrderbookItems(wsEventData.Bids) @@ -569,7 +569,7 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, in } if internalOrderbook.LastUpdated.After(wsEventData.Timestamp) { if o.Verbose { - log.Errorf("Orderbook update out of order. Existing: %v, Attempted: %v", internalOrderbook.LastUpdated.Unix(), wsEventData.Timestamp.Unix()) + log.Errorf(log.ExchangeSys, "Orderbook update out of order. Existing: %v, Attempted: %v", internalOrderbook.LastUpdated.Unix(), wsEventData.Timestamp.Unix()) } return errors.New("updated orderbook is older than existing") } @@ -584,16 +584,16 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, in checksum := o.CalculateUpdateOrderbookChecksum(&internalOrderbook) if checksum == wsEventData.Checksum { if o.Verbose { - log.Debug("Orderbook valid") + log.Debug(log.ExchangeSys, "Orderbook valid") } internalOrderbook.LastUpdated = wsEventData.Timestamp if o.Verbose { - log.Debug("Internalising orderbook") + log.Debug(log.ExchangeSys, "Internalising orderbook") } err := o.Websocket.Orderbook.LoadSnapshot(&internalOrderbook, o.GetName(), true) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) } o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{ Exchange: o.GetName(), @@ -602,7 +602,7 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, in } } else { if o.Verbose { - log.Debug("Orderbook invalid") + log.Warnln(log.ExchangeSys, "Orderbook invalid") } return fmt.Errorf("channel: %v. Orderbook update for %v checksum invalid. Received %v Calculated %v", tableName, instrument, wsEventData.Checksum, checksum) } @@ -727,7 +727,7 @@ func (o *OKGroup) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscrip json, err := common.JSONEncode(resp) if err != nil { if o.Verbose { - log.Debugf("%v subscribe error: %v", o.Name, err) + log.Errorf(log.ExchangeSys, "%v subscribe error: %v", o.Name, err) } return err } @@ -743,7 +743,7 @@ func (o *OKGroup) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscr json, err := common.JSONEncode(resp) if err != nil { if o.Verbose { - log.Debugf("%v unsubscribe error: %v", o.Name, err) + log.Errorf(log.ExchangeSys, "%v unsubscribe error: %v", o.Name, err) } return err } diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index 616b62fe..3dccb0b1 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -92,11 +92,11 @@ func (o *OKGroup) UpdateOrderbook(p currency.Pair, assetType asset.Item) (resp o for x := range orderbookNew.Bids { amount, convErr := strconv.ParseFloat(orderbookNew.Bids[x][1], 64) if convErr != nil { - log.Errorf("Could not convert %v to float64", orderbookNew.Bids[x][1]) + log.Errorf(log.ExchangeSys, "Could not convert %v to float64", orderbookNew.Bids[x][1]) } price, convErr := strconv.ParseFloat(orderbookNew.Bids[x][0], 64) if convErr != nil { - log.Errorf("Could not convert %v to float64", orderbookNew.Bids[x][0]) + log.Errorf(log.ExchangeSys, "Could not convert %v to float64", orderbookNew.Bids[x][0]) } resp.Bids = append(resp.Bids, orderbook.Item{ Amount: amount, @@ -107,11 +107,11 @@ func (o *OKGroup) UpdateOrderbook(p currency.Pair, assetType asset.Item) (resp o for x := range orderbookNew.Asks { amount, convErr := strconv.ParseFloat(orderbookNew.Asks[x][1], 64) if convErr != nil { - log.Errorf("Could not convert %v to float64", orderbookNew.Asks[x][1]) + log.Errorf(log.ExchangeSys, "Could not convert %v to float64", orderbookNew.Asks[x][1]) } price, convErr := strconv.ParseFloat(orderbookNew.Asks[x][0], 64) if convErr != nil { - log.Errorf("Could not convert %v to float64", orderbookNew.Asks[x][0]) + log.Errorf(log.ExchangeSys, "Could not convert %v to float64", orderbookNew.Asks[x][0]) } resp.Asks = append(resp.Asks, orderbook.Item{ Amount: amount, @@ -140,11 +140,11 @@ func (o *OKGroup) GetAccountInfo() (resp exchange.AccountInfo, err error) { for _, curr := range currencies { hold, err := strconv.ParseFloat(curr.Hold, 64) if err != nil { - log.Errorf("Could not convert %v to float64", curr.Hold) + log.Errorf(log.ExchangeSys, "Could not convert %v to float64", curr.Hold) } totalValue, err := strconv.ParseFloat(curr.Balance, 64) if err != nil { - log.Errorf("Could not convert %v to float64", curr.Balance) + log.Errorf(log.ExchangeSys, "Could not convert %v to float64", curr.Balance) } currencyAccount.Currencies = append(currencyAccount.Currencies, exchange.AccountCurrencyInfo{ CurrencyName: currency.NewCode(curr.Currency), diff --git a/exchanges/orderbook/calculator.go b/exchanges/orderbook/calculator.go index dbe0099a..88655138 100644 --- a/exchanges/orderbook/calculator.go +++ b/exchanges/orderbook/calculator.go @@ -1,227 +1,227 @@ -package orderbook - -import ( - "errors" - "fmt" - "sort" - - math "github.com/thrasher-/gocryptotrader/common/math" - log "github.com/thrasher-/gocryptotrader/logger" -) - -// WhaleBombResult returns the whale bomb result -type WhaleBombResult struct { - Amount float64 - MinimumPrice float64 - MaximumPrice float64 - PercentageGainOrLoss float64 - Orders orderSummary - Status string -} - -// WhaleBomb finds the amount required to target a price -func (o *Base) WhaleBomb(priceTarget float64, buy bool) (*WhaleBombResult, error) { - if priceTarget < 0 { - return nil, errors.New("price target is invalid") - } - if buy { - a, orders := o.findAmount(priceTarget, true) - min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) - var err error - if max < priceTarget { - err = errors.New("unable to hit price target due to insufficient orderbook items") - } - status := fmt.Sprintf("Buying %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", - a, o.Pair.Quote.String(), o.Pair.Base.String(), min, max, - math.CalculatePercentageGainOrLoss(max, min), len(orders)) - return &WhaleBombResult{ - Amount: a, - Orders: orders, - MinimumPrice: min, - MaximumPrice: max, - Status: status, - }, err - } - - a, orders := o.findAmount(priceTarget, false) - min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) - var err error - if min > priceTarget { - err = errors.New("unable to hit price target due to insufficient orderbook items") - } - status := fmt.Sprintf("Selling %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", - a, o.Pair.Base.String(), o.Pair.Quote.String(), max, min, - math.CalculatePercentageGainOrLoss(min, max), len(orders)) - return &WhaleBombResult{ - Amount: a, - Orders: orders, - MinimumPrice: min, - MaximumPrice: max, - Status: status, - }, err -} - -// OrderSimulationResult returns the order simulation result -type OrderSimulationResult WhaleBombResult - -// SimulateOrder simulates an order -func (o *Base) SimulateOrder(amount float64, buy bool) *OrderSimulationResult { - if buy { - orders, amt := o.buy(amount) - min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) - pct := math.CalculatePercentageGainOrLoss(max, min) - status := fmt.Sprintf("Buying %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", - amount, o.Pair.Quote.String(), o.Pair.Base.String(), min, max, - pct, len(orders)) - return &OrderSimulationResult{ - Orders: orders, - Amount: amt, - MinimumPrice: min, - MaximumPrice: max, - PercentageGainOrLoss: pct, - Status: status, - } - } - orders, amt := o.sell(amount) - min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) - pct := math.CalculatePercentageGainOrLoss(min, max) - status := fmt.Sprintf("Selling %f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", - amount, o.Pair.Base.String(), o.Pair.Quote.String(), max, min, - pct, len(orders)) - return &OrderSimulationResult{ - Orders: orders, - Amount: amt, - MinimumPrice: min, - MaximumPrice: max, - PercentageGainOrLoss: pct, - Status: status, - } -} - -type orderSummary []Item - -func (o orderSummary) Print() { - for x := range o { - log.Debugf("Order: Price: %f Amount: %f", o[x].Price, o[x].Amount) - } -} - -func (o orderSummary) MinimumPrice(reverse bool) float64 { - if len(o) != 0 { - sortOrdersByPrice(&o, reverse) - return o[0].Price - } - return 0 -} - -func (o orderSummary) MaximumPrice(reverse bool) float64 { - if len(o) != 0 { - sortOrdersByPrice(&o, reverse) - return o[0].Price - } - return 0 -} - -// ByPrice used for sorting orders by order date -type ByPrice orderSummary - -func (b ByPrice) Len() int { return len(b) } -func (b ByPrice) Less(i, j int) bool { return b[i].Price < b[j].Price } -func (b ByPrice) Swap(i, j int) { b[i], b[j] = b[j], b[i] } - -// sortOrdersByPrice the caller function to sort orders -func sortOrdersByPrice(o *orderSummary, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByPrice(*o))) - } else { - sort.Sort(ByPrice(*o)) - } -} - -func (o *Base) findAmount(price float64, buy bool) (float64, orderSummary) { - var orders orderSummary - var amt float64 - - if buy { - asks := o.Asks - for x := range asks { - if asks[x].Price >= price { - amt += asks[x].Price * asks[x].Amount - orders = append(orders, Item{ - Price: asks[x].Price, - Amount: asks[x].Amount, - }) - return amt, orders - } - orders = append(orders, Item{ - Price: asks[x].Price, - Amount: asks[x].Amount, - }) - amt += asks[x].Price * asks[x].Amount - } - return amt, orders - } - - for x := range o.Bids { - if o.Bids[x].Price <= price { - amt += o.Bids[x].Amount - orders = append(orders, Item{ - Price: o.Bids[x].Price, - Amount: o.Bids[x].Amount, - }) - break - } - orders = append(orders, Item{ - Price: o.Bids[x].Price, - Amount: o.Bids[x].Amount, - }) - amt += o.Bids[x].Amount - } - return amt, orders -} - -func (o *Base) buy(amount float64) (orders orderSummary, baseAmount float64) { - var processedAmt float64 - for x := range o.Asks { - subtotal := o.Asks[x].Price * o.Asks[x].Amount - if processedAmt+subtotal >= amount { - diff := amount - processedAmt - subAmt := diff / o.Asks[x].Price - orders = append(orders, Item{ - Price: o.Asks[x].Price, - Amount: subAmt, - }) - baseAmount += subAmt - break - } - processedAmt += subtotal - baseAmount += o.Asks[x].Amount - orders = append(orders, Item{ - Price: o.Asks[x].Price, - Amount: o.Asks[x].Amount, - }) - } - return -} - -func (o *Base) sell(amount float64) (orders orderSummary, quoteAmount float64) { - var processedAmt float64 - for x := range o.Bids { - if processedAmt+o.Bids[x].Amount >= amount { - diff := amount - processedAmt - orders = append(orders, Item{ - Price: o.Bids[x].Price, - Amount: diff, - }) - quoteAmount += diff * o.Bids[x].Price - break - } - processedAmt += o.Bids[x].Amount - quoteAmount += o.Bids[x].Amount * o.Bids[x].Price - orders = append(orders, Item{ - Price: o.Bids[x].Price, - Amount: o.Bids[x].Amount, - }) - } - return -} +package orderbook + +import ( + "errors" + "fmt" + "sort" + + math "github.com/thrasher-/gocryptotrader/common/math" + log "github.com/thrasher-/gocryptotrader/logger" +) + +// WhaleBombResult returns the whale bomb result +type WhaleBombResult struct { + Amount float64 + MinimumPrice float64 + MaximumPrice float64 + PercentageGainOrLoss float64 + Orders orderSummary + Status string +} + +// WhaleBomb finds the amount required to target a price +func (o *Base) WhaleBomb(priceTarget float64, buy bool) (*WhaleBombResult, error) { + if priceTarget < 0 { + return nil, errors.New("price target is invalid") + } + if buy { + a, orders := o.findAmount(priceTarget, true) + min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) + var err error + if max < priceTarget { + err = errors.New("unable to hit price target due to insufficient orderbook items") + } + status := fmt.Sprintf("Buying %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", + a, o.Pair.Quote.String(), o.Pair.Base.String(), min, max, + math.CalculatePercentageGainOrLoss(max, min), len(orders)) + return &WhaleBombResult{ + Amount: a, + Orders: orders, + MinimumPrice: min, + MaximumPrice: max, + Status: status, + }, err + } + + a, orders := o.findAmount(priceTarget, false) + min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) + var err error + if min > priceTarget { + err = errors.New("unable to hit price target due to insufficient orderbook items") + } + status := fmt.Sprintf("Selling %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", + a, o.Pair.Base.String(), o.Pair.Quote.String(), max, min, + math.CalculatePercentageGainOrLoss(min, max), len(orders)) + return &WhaleBombResult{ + Amount: a, + Orders: orders, + MinimumPrice: min, + MaximumPrice: max, + Status: status, + }, err +} + +// OrderSimulationResult returns the order simulation result +type OrderSimulationResult WhaleBombResult + +// SimulateOrder simulates an order +func (o *Base) SimulateOrder(amount float64, buy bool) *OrderSimulationResult { + if buy { + orders, amt := o.buy(amount) + min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) + pct := math.CalculatePercentageGainOrLoss(max, min) + status := fmt.Sprintf("Buying %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", + amount, o.Pair.Quote.String(), o.Pair.Base.String(), min, max, + pct, len(orders)) + return &OrderSimulationResult{ + Orders: orders, + Amount: amt, + MinimumPrice: min, + MaximumPrice: max, + PercentageGainOrLoss: pct, + Status: status, + } + } + orders, amt := o.sell(amount) + min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) + pct := math.CalculatePercentageGainOrLoss(min, max) + status := fmt.Sprintf("Selling %f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", + amount, o.Pair.Base.String(), o.Pair.Quote.String(), max, min, + pct, len(orders)) + return &OrderSimulationResult{ + Orders: orders, + Amount: amt, + MinimumPrice: min, + MaximumPrice: max, + PercentageGainOrLoss: pct, + Status: status, + } +} + +type orderSummary []Item + +func (o orderSummary) Print() { + for x := range o { + log.Debugf(log.OrderBook, "Order: Price: %f Amount: %f", o[x].Price, o[x].Amount) + } +} + +func (o orderSummary) MinimumPrice(reverse bool) float64 { + if len(o) != 0 { + sortOrdersByPrice(&o, reverse) + return o[0].Price + } + return 0 +} + +func (o orderSummary) MaximumPrice(reverse bool) float64 { + if len(o) != 0 { + sortOrdersByPrice(&o, reverse) + return o[0].Price + } + return 0 +} + +// ByPrice used for sorting orders by order date +type ByPrice orderSummary + +func (b ByPrice) Len() int { return len(b) } +func (b ByPrice) Less(i, j int) bool { return b[i].Price < b[j].Price } +func (b ByPrice) Swap(i, j int) { b[i], b[j] = b[j], b[i] } + +// sortOrdersByPrice the caller function to sort orders +func sortOrdersByPrice(o *orderSummary, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByPrice(*o))) + } else { + sort.Sort(ByPrice(*o)) + } +} + +func (o *Base) findAmount(price float64, buy bool) (float64, orderSummary) { + var orders orderSummary + var amt float64 + + if buy { + asks := o.Asks + for x := range asks { + if asks[x].Price >= price { + amt += asks[x].Price * asks[x].Amount + orders = append(orders, Item{ + Price: asks[x].Price, + Amount: asks[x].Amount, + }) + return amt, orders + } + orders = append(orders, Item{ + Price: asks[x].Price, + Amount: asks[x].Amount, + }) + amt += asks[x].Price * asks[x].Amount + } + return amt, orders + } + + for x := range o.Bids { + if o.Bids[x].Price <= price { + amt += o.Bids[x].Amount + orders = append(orders, Item{ + Price: o.Bids[x].Price, + Amount: o.Bids[x].Amount, + }) + break + } + orders = append(orders, Item{ + Price: o.Bids[x].Price, + Amount: o.Bids[x].Amount, + }) + amt += o.Bids[x].Amount + } + return amt, orders +} + +func (o *Base) buy(amount float64) (orders orderSummary, baseAmount float64) { + var processedAmt float64 + for x := range o.Asks { + subtotal := o.Asks[x].Price * o.Asks[x].Amount + if processedAmt+subtotal >= amount { + diff := amount - processedAmt + subAmt := diff / o.Asks[x].Price + orders = append(orders, Item{ + Price: o.Asks[x].Price, + Amount: subAmt, + }) + baseAmount += subAmt + break + } + processedAmt += subtotal + baseAmount += o.Asks[x].Amount + orders = append(orders, Item{ + Price: o.Asks[x].Price, + Amount: o.Asks[x].Amount, + }) + } + return +} + +func (o *Base) sell(amount float64) (orders orderSummary, quoteAmount float64) { + var processedAmt float64 + for x := range o.Bids { + if processedAmt+o.Bids[x].Amount >= amount { + diff := amount - processedAmt + orders = append(orders, Item{ + Price: o.Bids[x].Price, + Amount: diff, + }) + quoteAmount += diff * o.Bids[x].Price + break + } + processedAmt += o.Bids[x].Amount + quoteAmount += o.Bids[x].Amount * o.Bids[x].Price + orders = append(orders, Item{ + Price: o.Bids[x].Price, + Amount: o.Bids[x].Amount, + }) + } + return +} diff --git a/exchanges/orderbook/calculator_test.go b/exchanges/orderbook/calculator_test.go index 484fe838..d7373afb 100644 --- a/exchanges/orderbook/calculator_test.go +++ b/exchanges/orderbook/calculator_test.go @@ -1,79 +1,79 @@ -package orderbook - -import ( - "testing" - - "github.com/thrasher-/gocryptotrader/currency" -) - -func testSetup() Base { - return Base{ - ExchangeName: "a", - Pair: currency.NewPair(currency.BTC, currency.USD), - Asks: []Item{ - {Price: 7000, Amount: 1}, - {Price: 7001, Amount: 2}, - }, - Bids: []Item{ - {Price: 6999, Amount: 1}, - {Price: 6998, Amount: 2}, - }, - } -} - -func TestWhaleBomb(t *testing.T) { - t.Parallel() - b := testSetup() - - // invalid price amout - _, err := b.WhaleBomb(-1, true) - if err == nil { - t.Error("unexpected result") - } - - // valid - b.WhaleBomb(7001, true) - // invalid - b.WhaleBomb(7002, true) - - // valid - b.WhaleBomb(6998, false) - // invalid - b.WhaleBomb(6997, false) -} - -func TestSimulateOrder(t *testing.T) { - t.Parallel() - b := testSetup() - b.SimulateOrder(8000, true) - b.SimulateOrder(1.5, false) -} - -func TestOrderSummary(t *testing.T) { - var o orderSummary - if p := o.MaximumPrice(false); p != 0 { - t.Error("unexpected result") - } - if p := o.MinimumPrice(false); p != 0 { - t.Error("unexpected result") - } - - o = orderSummary{ - {Price: 1337, Amount: 1}, - {Price: 9001, Amount: 1}, - } - if p := o.MaximumPrice(false); p != 1337 { - t.Error("unexpected result") - } - if p := o.MaximumPrice(true); p != 9001 { - t.Error("unexpected result") - } - if p := o.MinimumPrice(false); p != 1337 { - t.Error("unexpected result") - } - if p := o.MinimumPrice(true); p != 9001 { - t.Error("unexpected result") - } - - o.Print() -} +package orderbook + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/currency" +) + +func testSetup() Base { + return Base{ + ExchangeName: "a", + Pair: currency.NewPair(currency.BTC, currency.USD), + Asks: []Item{ + {Price: 7000, Amount: 1}, + {Price: 7001, Amount: 2}, + }, + Bids: []Item{ + {Price: 6999, Amount: 1}, + {Price: 6998, Amount: 2}, + }, + } +} + +func TestWhaleBomb(t *testing.T) { + t.Parallel() + b := testSetup() + + // invalid price amout + _, err := b.WhaleBomb(-1, true) + if err == nil { + t.Error("unexpected result") + } + + // valid + b.WhaleBomb(7001, true) + // invalid + b.WhaleBomb(7002, true) + + // valid + b.WhaleBomb(6998, false) + // invalid + b.WhaleBomb(6997, false) +} + +func TestSimulateOrder(t *testing.T) { + t.Parallel() + b := testSetup() + b.SimulateOrder(8000, true) + b.SimulateOrder(1.5, false) +} + +func TestOrderSummary(t *testing.T) { + var o orderSummary + if p := o.MaximumPrice(false); p != 0 { + t.Error("unexpected result") + } + if p := o.MinimumPrice(false); p != 0 { + t.Error("unexpected result") + } + + o = orderSummary{ + {Price: 1337, Amount: 1}, + {Price: 9001, Amount: 1}, + } + if p := o.MaximumPrice(false); p != 1337 { + t.Error("unexpected result") + } + if p := o.MaximumPrice(true); p != 9001 { + t.Error("unexpected result") + } + if p := o.MinimumPrice(false); p != 1337 { + t.Error("unexpected result") + } + if p := o.MinimumPrice(true); p != 9001 { + t.Error("unexpected result") + } + + o.Print() +} diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 72c16f3e..b7e53623 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -9,7 +9,6 @@ import ( "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/exchanges/asset" - log "github.com/thrasher-/gocryptotrader/logger" ) func TestVerify(t *testing.T) { @@ -364,7 +363,7 @@ func TestProcessOrderbook(t *testing.T) { m.Lock() err = base.Process() if err != nil { - log.Error(err) + t.Error(err) catastrophicFailure = true return } diff --git a/exchanges/orderbook/simulator/simulator_test.go b/exchanges/orderbook/simulator/simulator_test.go index 7751a8a7..967e8d84 100644 --- a/exchanges/orderbook/simulator/simulator_test.go +++ b/exchanges/orderbook/simulator/simulator_test.go @@ -1,24 +1,24 @@ -package simulator - -import ( - "testing" - - "github.com/thrasher-/gocryptotrader/currency" - "github.com/thrasher-/gocryptotrader/exchanges/asset" - "github.com/thrasher-/gocryptotrader/exchanges/bitstamp" -) - -func TestSimulate(t *testing.T) { - b := bitstamp.Bitstamp{} - b.SetDefaults() - b.Verbose = false - o, err := b.FetchOrderbook(currency.NewPair(currency.BTC, currency.USD), asset.Spot) - if err != nil { - t.Error(err) - } - - r := o.SimulateOrder(10000000, true) - t.Log(r.Status) - r = o.SimulateOrder(2171, false) - t.Log(r.Status) -} +package simulator + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/exchanges/asset" + "github.com/thrasher-/gocryptotrader/exchanges/bitstamp" +) + +func TestSimulate(t *testing.T) { + b := bitstamp.Bitstamp{} + b.SetDefaults() + b.Verbose = false + o, err := b.FetchOrderbook(currency.NewPair(currency.BTC, currency.USD), asset.Spot) + if err != nil { + t.Error(err) + } + + r := o.SimulateOrder(10000000, true) + t.Log(r.Status) + r = o.SimulateOrder(2171, false) + t.Log(r.Status) +} diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index e81b05a6..023c506a 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -129,7 +129,7 @@ func (p *Poloniex) WsHandleData() { if len(data) == 2 && chanID != wsHeartbeat { if checkSubscriptionSuccess(data) { if p.Verbose { - log.Debugf("poloniex websocket subscribed to channel successfully. %d", chanID) + log.Debugf(log.ExchangeSys, "poloniex websocket subscribed to channel successfully. %d", chanID) } } else { p.Websocket.DataHandler <- fmt.Errorf("poloniex websocket subscription to channel failed. %d", chanID) @@ -456,7 +456,7 @@ func (p *Poloniex) wsSend(data interface{}) error { return err } if p.Verbose { - log.Debugf("%v sending message to websocket %v", p.Name, data) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %v", p.Name, data) } return p.WebsocketConn.WriteMessage(websocket.TextMessage, json) } diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 92e86861..7591c28c 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -130,13 +130,13 @@ func (p *Poloniex) Start(wg *sync.WaitGroup) { // Run implements the Poloniex wrapper func (p *Poloniex) Run() { if p.Verbose { - log.Debugf("%s Websocket: %s (url: %s).\n", p.GetName(), common.IsEnabled(p.Websocket.IsEnabled()), poloniexWebsocketAddress) + log.Debugf(log.ExchangeSys, "%s Websocket: %s (url: %s).\n", p.GetName(), common.IsEnabled(p.Websocket.IsEnabled()), poloniexWebsocketAddress) p.PrintEnabledPairs() } forceUpdate := false if common.StringDataCompare(p.GetAvailablePairs(asset.Spot).Strings(), "BTC_USDT") { - log.Warnf("%s contains invalid pair, forcing upgrade of available currencies.\n", + log.Warnf(log.ExchangeSys, "%s contains invalid pair, forcing upgrade of available currencies.\n", p.Name) forceUpdate = true } @@ -147,7 +147,7 @@ func (p *Poloniex) Run() { err := p.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", p.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", p.Name, err) } } @@ -453,7 +453,7 @@ func (p *Poloniex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) orderDate, err := time.Parse(poloniexDateLayout, order.Date) if err != nil { - log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", p.Name, "GetActiveOrders", order.OrderNumber, order.Date) } @@ -495,7 +495,7 @@ func (p *Poloniex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) orderDate, err := time.Parse(poloniexDateLayout, order.Date) if err != nil { - log.Warnf("Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", p.Name, "GetActiveOrders", order.OrderNumber, order.Date) } diff --git a/exchanges/request/request.go b/exchanges/request/request.go index 93c9a5f9..197c7097 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -281,11 +281,12 @@ func (r *Requester) checkRequest(method, path string, body io.Reader, headers ma // DoRequest performs a HTTP/HTTPS request with the supplied params func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, result interface{}, authRequest, verbose, httpDebug bool) error { if verbose { - log.Debugf("%s exchange request path: %s requires rate limiter: %v", r.Name, path, r.RequiresRateLimiter()) + log.Debugf(log.Global, + "%s exchange request path: %s requires rate limiter: %v", r.Name, path, r.RequiresRateLimiter()) for k, d := range req.Header { - log.Debugf("%s exchange request header [%s]: %s", r.Name, k, d) + log.Debugf(log.Global, "%s exchange request header [%s]: %s", r.Name, k, d) } - log.Debug(body) + log.Debugln(log.Global, body) } var timeoutError error @@ -294,7 +295,7 @@ func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, re if err != nil { if timeoutErr, ok := err.(net.Error); ok && timeoutErr.Timeout() { if verbose { - log.Errorf("%s request has timed-out retrying request, count %d", + log.Errorf(log.ExchangeSys, "%s request has timed-out retrying request, count %d", r.Name, i) } @@ -332,7 +333,7 @@ func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, re reader = resp.Body default: - log.Warnf("%s request response content type differs from JSON; received %v [path: %s]", + log.Warnf(log.ExchangeSys, "%s request response content type differs from JSON; received %v [path: %s]\n", r.Name, resp.Header.Get("Content-Type"), path) reader = resp.Body } @@ -356,17 +357,17 @@ func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, re if httpDebug { dump, err := httputil.DumpResponse(resp, false) if err != nil { - log.Errorf("DumpResponse invalid response: %v:", err) + log.Errorf(log.Global, "DumpResponse invalid response: %v:", err) } - log.Debugf("DumpResponse Headers (%v):\n%s", path, dump) - log.Debugf("DumpResponse Body (%v):\n %s", path, string(contents)) + log.Debugf(log.Global, "DumpResponse Headers (%v):\n%s", path, dump) + log.Debugf(log.Global, "DumpResponse Body (%v):\n %s", path, string(contents)) } resp.Body.Close() if verbose { - log.Debugf("HTTP status: %s, Code: %v", resp.Status, resp.StatusCode) + log.Debugf(log.ExchangeSys, "HTTP status: %s, Code: %v", resp.Status, resp.StatusCode) if !httpDebug { - log.Debugf("%s exchange raw response: %s", r.Name, string(contents)) + log.Debugf(log.ExchangeSys, "%s exchange raw response: %s", r.Name, string(contents)) } } @@ -395,7 +396,7 @@ func (r *Requester) worker() { limit := r.GetRateLimit(x.AuthRequest) diff := limit.GetDuration() - time.Since(r.Cycle) if x.Verbose { - log.Debugf("%s request. Rate limited! Sleeping for %v", r.Name, diff) + log.Debugf(log.ExchangeSys, "%s request. Rate limited! Sleeping for %v", r.Name, diff) } time.Sleep(diff) @@ -407,7 +408,7 @@ func (r *Requester) worker() { r.IncrementRequests(x.AuthRequest) if x.Verbose { - log.Debugf("%s request. No longer rate limited! Doing request", r.Name) + log.Debugf(log.ExchangeSys, "%s request. No longer rate limited! Doing request", r.Name) } err := r.DoRequest(x.Request, x.Path, x.Body, x.Result, x.AuthRequest, x.Verbose, x.HTTPDebugging) @@ -452,9 +453,11 @@ func (r *Requester) SendPayload(method, path string, headers map[string]string, if httpDebugging { dump, err := httputil.DumpRequestOut(req, true) if err != nil { - log.Errorf("DumpRequest invalid response %v:", err) + log.Errorf(log.Global, + "DumpRequest invalid response %v:", err) } - log.Debugf("DumpRequest:\n%s", dump) + log.Debugf(log.Global, + "DumpRequest:\n%s", dump) } if !r.RequiresRateLimiter() { @@ -491,18 +494,18 @@ func (r *Requester) SendPayload(method, path string, headers map[string]string, } if verbose { - log.Debugf("%s request. Attaching new job.", r.Name) + log.Debugf(log.ExchangeSys, "%s request. Attaching new job.", r.Name) } r.Jobs <- newJob r.unlock() if verbose { - log.Debugf("%s request. Waiting for job to complete.", r.Name) + log.Debugf(log.ExchangeSys, "%s request. Waiting for job to complete.", r.Name) } resp := <-newJob.JobResult if verbose { - log.Debugf("%s request. Job complete.", r.Name) + log.Debugf(log.ExchangeSys, "%s request. Job complete.", r.Name) } return resp.Error @@ -563,7 +566,7 @@ func (r *Requester) lock() { wg.Done() select { case <-timer.C: - log.Errorf("Unlocking due to possible error for %s", r.Name) + log.Errorf(log.ExchangeSys, "Unlocking due to possible error for %s", r.Name) r.fifoLock.Unlock() case <-r.disengage: diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index d459b677..45b0d206 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -10,7 +10,6 @@ import ( "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/exchanges/asset" - log "github.com/thrasher-/gocryptotrader/logger" ) func TestPriceToString(t *testing.T) { @@ -351,7 +350,7 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers sm.Lock() err = ProcessTicker(newName, &tp, asset.Spot) if err != nil { - log.Error(err) + t.Error(err) catastrophicFailure = true return } diff --git a/exchanges/websocket.go b/exchanges/websocket.go index 0095bb2b..6ff5671f 100644 --- a/exchanges/websocket.go +++ b/exchanges/websocket.go @@ -119,17 +119,17 @@ func (w *Websocket) wsConnectionMonitor() { w.DataHandler <- fmt.Errorf("%v WsConnectionMonitor: websocket disabled, shutting down", w.exchangeName) err := w.Shutdown() if err != nil { - log.Error(err) + log.Error(log.WebsocketMgr, err) } if w.verbose { - log.Debugf("%v WsConnectionMonitor exiting", w.exchangeName) + log.Debugf(log.WebsocketMgr, "%v WsConnectionMonitor exiting", w.exchangeName) } return } w.m.Unlock() err := w.checkConnection() if err != nil { - log.Error(err) + log.Error(log.WebsocketMgr, err) } } } @@ -138,18 +138,18 @@ func (w *Websocket) wsConnectionMonitor() { // Will reconnect on disconnect func (w *Websocket) checkConnection() error { if w.verbose { - log.Debugf("%v checking connection", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v checking connection", w.exchangeName) } switch { case !w.IsConnected() && !w.IsConnecting(): w.m.Lock() defer w.m.Unlock() if w.verbose { - log.Debugf("%v no connection. Attempt %v/%v", w.exchangeName, w.noConnectionChecks, w.noConnectionCheckLimit) + log.Debugf(log.ExchangeSys, "%v no connection. Attempt %v/%v", w.exchangeName, w.noConnectionChecks, w.noConnectionCheckLimit) } if w.noConnectionChecks >= w.noConnectionCheckLimit { if w.verbose { - log.Debugf("%v resetting connection", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v resetting connection", w.exchangeName) } w.connecting = true go w.WebsocketReset() @@ -163,7 +163,7 @@ func (w *Websocket) checkConnection() error { w.reconnectionLimit*int(connectionMonitorDelay.Seconds())) } if w.verbose { - log.Debugf("%v Busy reconnecting", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v Busy reconnecting", w.exchangeName) } w.reconnectionChecks++ default: @@ -200,7 +200,7 @@ func (w *Websocket) Shutdown() error { } if w.verbose { - log.Debugf("%v shutting down websocket channels", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v shutting down websocket channels", w.exchangeName) } timer := time.NewTimer(15 * time.Second) c := make(chan struct{}, 1) @@ -209,7 +209,7 @@ func (w *Websocket) Shutdown() error { close(w.ShutdownC) w.Wg.Wait() if w.verbose { - log.Debugf("%v completed websocket channel shutdown", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v completed websocket channel shutdown", w.exchangeName) } c <- struct{}{} }(c) @@ -229,15 +229,15 @@ func (w *Websocket) WebsocketReset() error { err := w.Shutdown() if err != nil { // does not return here to allow connection to be made if already shut down - log.Errorf("%v shutdown error: %v", w.exchangeName, err) + log.Errorf(log.ExchangeSys, "%v shutdown error: %v", w.exchangeName, err) } - log.Infof("%v reconnecting to websocket", w.exchangeName) + log.Infof(log.WebsocketMgr, "%v reconnecting to websocket", w.exchangeName) w.m.Lock() w.init = true w.m.Unlock() err = w.Connect() if err != nil { - log.Errorf("%v connection error: %v", w.exchangeName, err) + log.Errorf(log.ExchangeSys, "%v connection error: %v", w.exchangeName, err) } return err } @@ -260,7 +260,7 @@ func (w *Websocket) trafficMonitor(wg *sync.WaitGroup) { select { case <-w.ShutdownC: // Returns on shutdown channel close if w.verbose { - log.Debugf("%v trafficMonitor shutdown message received", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v trafficMonitor shutdown message received", w.exchangeName) } return case <-w.TrafficAlert: // Resets timer on traffic @@ -271,13 +271,13 @@ func (w *Websocket) trafficMonitor(wg *sync.WaitGroup) { } w.m.Unlock() if w.verbose { - log.Debugf("%v received a traffic alert", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v received a traffic alert", w.exchangeName) } trafficTimer.Reset(WebsocketTrafficLimitTime) case <-trafficTimer.C: // Falls through when timer runs out newtimer := time.NewTimer(10 * time.Second) // New secondary timer set if w.verbose { - log.Debugf("%v has not received a traffic alert in 5 seconds.", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v has not received a traffic alert in 5 seconds.", w.exchangeName) } w.m.Lock() if w.connected { @@ -296,7 +296,7 @@ func (w *Websocket) trafficMonitor(wg *sync.WaitGroup) { case <-newtimer.C: // If secondary timer runs state timeout is sent to the data handler if w.verbose { - log.Debugf("%v has not received a traffic alert in 15 seconds, exiting", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v has not received a traffic alert in 15 seconds, exiting", w.exchangeName) } w.DataHandler <- fmt.Errorf("trafficMonitor %v", WebsocketStateTimeout) return @@ -308,7 +308,7 @@ func (w *Websocket) trafficMonitor(wg *sync.WaitGroup) { // If not connected dive rt traffic from REST to websocket w.Connected <- struct{}{} if w.verbose { - log.Debugf("%v has received a traffic alert. Setting status to connected", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v has received a traffic alert. Setting status to connected", w.exchangeName) } w.connected = true } @@ -722,7 +722,7 @@ func (w *Websocket) manageSubscriptions() error { w.Wg.Add(1) defer func() { if w.verbose { - log.Debugf("%v ManageSubscriptions exiting", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v ManageSubscriptions exiting", w.exchangeName) } w.Wg.Done() }() @@ -731,13 +731,13 @@ func (w *Websocket) manageSubscriptions() error { case <-w.ShutdownC: w.subscribedChannels = []WebsocketChannelSubscription{} if w.verbose { - log.Debugf("%v shutdown manageSubscriptions", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v shutdown manageSubscriptions", w.exchangeName) } return nil default: time.Sleep(manageSubscriptionsDelay) if w.verbose { - log.Debugf("%v checking subscriptions", w.exchangeName) + log.Debugf(log.ExchangeSys, "%v checking subscriptions", w.exchangeName) } // Subscribe to channels Pending a subscription if w.SupportsFunctionality(WebsocketSubscribeSupported) { @@ -771,7 +771,7 @@ func (w *Websocket) subscribeToChannels() error { } if !channelIsSubscribed { if w.verbose { - log.Debugf("%v Subscribing to %v %v", w.exchangeName, w.channelsToSubscribe[i].Channel, w.channelsToSubscribe[i].Currency.String()) + log.Debugf(log.ExchangeSys, "%v Subscribing to %v %v", w.exchangeName, w.channelsToSubscribe[i].Channel, w.channelsToSubscribe[i].Currency.String()) } err := w.channelSubscriber(w.channelsToSubscribe[i]) if err != nil { diff --git a/exchanges/yobit/yobit.go b/exchanges/yobit/yobit.go index 0e10a69a..3562931c 100644 --- a/exchanges/yobit/yobit.go +++ b/exchanges/yobit/yobit.go @@ -286,7 +286,7 @@ func (y *Yobit) SendAuthenticatedHTTPRequest(path string, params url.Values, res hmac := crypto.GetHMAC(crypto.HashSHA512, []byte(encoded), []byte(y.API.Credentials.Secret)) if y.Verbose { - log.Debugf("Sending POST request to %s calling path %s with params %s\n", + log.Debugf(log.ExchangeSys, "Sending POST request to %s calling path %s with params %s\n", apiPrivateURL, path, encoded) diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 65b0898d..fe78f5e8 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -125,7 +125,7 @@ func (y *Yobit) Run() { err := y.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", y.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", y.Name, err) } } diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index cecc804e..35ff33cb 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -82,7 +82,7 @@ func TestSpotNewOrder(t *testing.T) { if err != nil { t.Errorf("Test failed - ZB SpotNewOrder: %s", err) } else { - fmt.Println(orderid) + t.Log(orderid) } } diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 6e3bcefa..c8aeffc2 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -364,7 +364,7 @@ func (z *ZB) wsSend(data interface{}) error { return err } if z.Verbose { - log.Debugf("%v sending message to websocket %v", z.Name, string(json)) + log.Debugf(log.ExchangeSys, "%v sending message to websocket %v", z.Name, string(json)) } return z.WebsocketConn.WriteMessage(websocket.TextMessage, json) } @@ -442,7 +442,7 @@ func (z *ZB) wsCreateSubUserKey(assetPerm, entrustPerm, leverPerm, moneyPerm boo func (z *ZB) wsGenerateSignature(request interface{}) string { jsonResponse, err := common.JSONEncode(request) if err != nil { - log.Error(err) + log.Error(log.ExchangeSys, err) } hmac := crypto.GetHMAC(crypto.HashMD5, jsonResponse, diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 78418169..93b82f79 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -141,7 +141,7 @@ func (z *ZB) Run() { err := z.UpdateTradablePairs(false) if err != nil { - log.Errorf("%s failed to update tradable pairs. Err: %s", z.Name, err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", z.Name, err) } } diff --git a/gctrpc/gen_pb_linux.sh b/gctrpc/gen_pb_linux.sh old mode 100644 new mode 100755 diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index 33603502..b80231e3 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -4393,6 +4393,155 @@ func (m *WithdrawResponse) GetResult() string { return "" } +type GetLoggerDetailsRequest struct { + Logger string `protobuf:"bytes,1,opt,name=logger,proto3" json:"logger,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetLoggerDetailsRequest) Reset() { *m = GetLoggerDetailsRequest{} } +func (m *GetLoggerDetailsRequest) String() string { return proto.CompactTextString(m) } +func (*GetLoggerDetailsRequest) ProtoMessage() {} +func (*GetLoggerDetailsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{85} +} + +func (m *GetLoggerDetailsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetLoggerDetailsRequest.Unmarshal(m, b) +} +func (m *GetLoggerDetailsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetLoggerDetailsRequest.Marshal(b, m, deterministic) +} +func (m *GetLoggerDetailsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetLoggerDetailsRequest.Merge(m, src) +} +func (m *GetLoggerDetailsRequest) XXX_Size() int { + return xxx_messageInfo_GetLoggerDetailsRequest.Size(m) +} +func (m *GetLoggerDetailsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetLoggerDetailsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetLoggerDetailsRequest proto.InternalMessageInfo + +func (m *GetLoggerDetailsRequest) GetLogger() string { + if m != nil { + return m.Logger + } + return "" +} + +type GetLoggerDetailsResponse struct { + Info bool `protobuf:"varint,1,opt,name=info,proto3" json:"info,omitempty"` + Debug bool `protobuf:"varint,2,opt,name=debug,proto3" json:"debug,omitempty"` + Warn bool `protobuf:"varint,3,opt,name=warn,proto3" json:"warn,omitempty"` + Error bool `protobuf:"varint,4,opt,name=error,proto3" json:"error,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetLoggerDetailsResponse) Reset() { *m = GetLoggerDetailsResponse{} } +func (m *GetLoggerDetailsResponse) String() string { return proto.CompactTextString(m) } +func (*GetLoggerDetailsResponse) ProtoMessage() {} +func (*GetLoggerDetailsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{86} +} + +func (m *GetLoggerDetailsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetLoggerDetailsResponse.Unmarshal(m, b) +} +func (m *GetLoggerDetailsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetLoggerDetailsResponse.Marshal(b, m, deterministic) +} +func (m *GetLoggerDetailsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetLoggerDetailsResponse.Merge(m, src) +} +func (m *GetLoggerDetailsResponse) XXX_Size() int { + return xxx_messageInfo_GetLoggerDetailsResponse.Size(m) +} +func (m *GetLoggerDetailsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetLoggerDetailsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetLoggerDetailsResponse proto.InternalMessageInfo + +func (m *GetLoggerDetailsResponse) GetInfo() bool { + if m != nil { + return m.Info + } + return false +} + +func (m *GetLoggerDetailsResponse) GetDebug() bool { + if m != nil { + return m.Debug + } + return false +} + +func (m *GetLoggerDetailsResponse) GetWarn() bool { + if m != nil { + return m.Warn + } + return false +} + +func (m *GetLoggerDetailsResponse) GetError() bool { + if m != nil { + return m.Error + } + return false +} + +type SetLoggerDetailsRequest struct { + Logger string `protobuf:"bytes,1,opt,name=logger,proto3" json:"logger,omitempty"` + Level string `protobuf:"bytes,2,opt,name=level,proto3" json:"level,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SetLoggerDetailsRequest) Reset() { *m = SetLoggerDetailsRequest{} } +func (m *SetLoggerDetailsRequest) String() string { return proto.CompactTextString(m) } +func (*SetLoggerDetailsRequest) ProtoMessage() {} +func (*SetLoggerDetailsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{87} +} + +func (m *SetLoggerDetailsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetLoggerDetailsRequest.Unmarshal(m, b) +} +func (m *SetLoggerDetailsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetLoggerDetailsRequest.Marshal(b, m, deterministic) +} +func (m *SetLoggerDetailsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetLoggerDetailsRequest.Merge(m, src) +} +func (m *SetLoggerDetailsRequest) XXX_Size() int { + return xxx_messageInfo_SetLoggerDetailsRequest.Size(m) +} +func (m *SetLoggerDetailsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SetLoggerDetailsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SetLoggerDetailsRequest proto.InternalMessageInfo + +func (m *SetLoggerDetailsRequest) GetLogger() string { + if m != nil { + return m.Logger + } + return "" +} + +func (m *SetLoggerDetailsRequest) GetLevel() string { + if m != nil { + return m.Level + } + return "" +} + func init() { proto.RegisterType((*GetInfoRequest)(nil), "gctrpc.GetInfoRequest") proto.RegisterType((*GetInfoResponse)(nil), "gctrpc.GetInfoResponse") @@ -4491,278 +4640,289 @@ func init() { proto.RegisterType((*GetCryptocurrencyDepositAddressResponse)(nil), "gctrpc.GetCryptocurrencyDepositAddressResponse") proto.RegisterType((*WithdrawCurrencyRequest)(nil), "gctrpc.WithdrawCurrencyRequest") proto.RegisterType((*WithdrawResponse)(nil), "gctrpc.WithdrawResponse") + proto.RegisterType((*GetLoggerDetailsRequest)(nil), "gctrpc.GetLoggerDetailsRequest") + proto.RegisterType((*GetLoggerDetailsResponse)(nil), "gctrpc.GetLoggerDetailsResponse") + proto.RegisterType((*SetLoggerDetailsRequest)(nil), "gctrpc.SetLoggerDetailsRequest") } func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 4250 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x4d, 0x6f, 0x25, 0x49, - 0x52, 0xaa, 0x67, 0xb7, 0x3f, 0xe2, 0x3d, 0xfb, 0xd9, 0xe9, 0xaf, 0xd7, 0xaf, 0xed, 0xfe, 0xa8, - 0xd9, 0xe9, 0xe9, 0x9e, 0x0f, 0xf7, 0x4c, 0x4f, 0x8b, 0x1d, 0x76, 0x96, 0x05, 0x8f, 0xa7, 0xc7, - 0xdb, 0xec, 0xec, 0xb4, 0xa9, 0xee, 0xed, 0x96, 0x66, 0xd1, 0x16, 0xe5, 0xaa, 0xb4, 0x5d, 0x74, - 0xbd, 0xaa, 0x9a, 0xaa, 0x7c, 0x76, 0x7b, 0x84, 0x84, 0xb4, 0x12, 0x88, 0x13, 0x1c, 0x56, 0x48, - 0x1c, 0x38, 0x71, 0x44, 0xe2, 0x82, 0x38, 0x81, 0x34, 0xe2, 0x8a, 0x38, 0x72, 0xe1, 0x07, 0x20, - 0x6e, 0x80, 0x84, 0xc4, 0x85, 0x13, 0xca, 0xc8, 0x8f, 0xca, 0x7c, 0x55, 0xef, 0xf9, 0x35, 0xdb, - 0x3b, 0x97, 0xee, 0x57, 0x91, 0x91, 0x11, 0x91, 0x11, 0x91, 0x91, 0x91, 0x91, 0x61, 0x58, 0x2c, - 0xf2, 0x70, 0x37, 0x2f, 0x32, 0x96, 0x91, 0xb9, 0x93, 0x90, 0x15, 0x79, 0xd8, 0xdf, 0x3e, 0xc9, - 0xb2, 0x93, 0x84, 0xde, 0x0b, 0xf2, 0xf8, 0x5e, 0x90, 0xa6, 0x19, 0x0b, 0x58, 0x9c, 0xa5, 0xa5, - 0xc0, 0x72, 0x57, 0x60, 0xf9, 0x80, 0xb2, 0x47, 0xe9, 0x71, 0xe6, 0xd1, 0xaf, 0x86, 0xb4, 0x64, - 0xee, 0xdf, 0xcd, 0x42, 0x57, 0x83, 0xca, 0x3c, 0x4b, 0x4b, 0x4a, 0x36, 0x61, 0x6e, 0x98, 0xb3, - 0x78, 0x40, 0x7b, 0xce, 0x4d, 0xe7, 0xce, 0xa2, 0x27, 0xbf, 0xc8, 0x3d, 0x58, 0x0b, 0xce, 0x82, - 0x38, 0x09, 0x8e, 0x12, 0xea, 0xd3, 0x97, 0xe1, 0x69, 0x90, 0x9e, 0xd0, 0xb2, 0xd7, 0xba, 0xe9, - 0xdc, 0x99, 0xf1, 0x88, 0x1e, 0x7a, 0xa8, 0x46, 0xc8, 0x3b, 0xb0, 0x4a, 0x53, 0x0e, 0x8a, 0x0c, - 0xf4, 0x19, 0x44, 0x5f, 0x91, 0x03, 0x15, 0xf2, 0x03, 0xd8, 0x8c, 0xe8, 0x71, 0x30, 0x4c, 0x98, - 0x7f, 0x9c, 0x15, 0xf4, 0xa5, 0x9f, 0x17, 0xd9, 0x59, 0x1c, 0xd1, 0xa2, 0x37, 0x8b, 0x52, 0xac, - 0xcb, 0xd1, 0xcf, 0xf8, 0xe0, 0xa1, 0x1c, 0x23, 0xf7, 0x61, 0x43, 0xcf, 0x8a, 0x03, 0xe6, 0x87, - 0xc3, 0xa2, 0xa0, 0x69, 0x78, 0xd1, 0xbb, 0x82, 0x93, 0xd6, 0xd4, 0xa4, 0x38, 0x60, 0xfb, 0x72, - 0x88, 0x3c, 0x87, 0x95, 0x72, 0x78, 0x54, 0x5e, 0x94, 0x8c, 0x0e, 0xfc, 0x92, 0x05, 0x6c, 0x58, - 0xf6, 0xe6, 0x6e, 0xce, 0xdc, 0x69, 0xdf, 0x7f, 0x77, 0x57, 0xa8, 0x71, 0x77, 0x44, 0x25, 0xbb, - 0x4f, 0x14, 0xfe, 0x13, 0x44, 0x7f, 0x98, 0xb2, 0xe2, 0xc2, 0xeb, 0x96, 0x36, 0x94, 0x7c, 0x01, - 0x4b, 0x45, 0x1e, 0xfa, 0x34, 0x8d, 0xf2, 0x2c, 0x4e, 0x59, 0xd9, 0x9b, 0x47, 0xaa, 0x77, 0xc7, - 0x51, 0xf5, 0xf2, 0xf0, 0xa1, 0xc2, 0x15, 0x24, 0x3b, 0x85, 0x01, 0xea, 0x7f, 0x02, 0xeb, 0x4d, - 0x8c, 0xc9, 0x0a, 0xcc, 0xbc, 0xa0, 0x17, 0xd2, 0x3a, 0xfc, 0x27, 0x59, 0x87, 0x2b, 0x67, 0x41, - 0x32, 0xa4, 0x68, 0x8c, 0x05, 0x4f, 0x7c, 0x7c, 0xaf, 0xf5, 0x91, 0xd3, 0x7f, 0x0a, 0xab, 0x35, - 0x36, 0x0d, 0x04, 0xee, 0x9a, 0x04, 0xda, 0xf7, 0xd7, 0x94, 0xc8, 0xde, 0xe1, 0xbe, 0x9a, 0x6b, - 0x50, 0x75, 0x6f, 0xc1, 0x8d, 0x03, 0xca, 0xf6, 0xb3, 0xc1, 0x60, 0x98, 0xc6, 0x21, 0xfa, 0x98, - 0x47, 0x93, 0xe0, 0x82, 0x16, 0xa5, 0xf2, 0xac, 0x2f, 0x60, 0xbd, 0x69, 0x9c, 0xf4, 0x60, 0x5e, - 0xda, 0x1e, 0xf9, 0x2f, 0x78, 0xea, 0x93, 0x6c, 0xc3, 0x62, 0x98, 0xa5, 0x29, 0x0d, 0x19, 0x8d, - 0xe4, 0x42, 0x2a, 0x80, 0xfb, 0xc7, 0x2d, 0xb8, 0x39, 0x9e, 0xa7, 0x74, 0xdd, 0xaf, 0x61, 0x33, - 0x34, 0x11, 0xfc, 0x42, 0x62, 0xf4, 0x1c, 0x34, 0xc5, 0xbe, 0x61, 0x8a, 0x89, 0x94, 0x76, 0x1b, - 0x47, 0x85, 0x91, 0x36, 0xc2, 0xa6, 0xb1, 0xfe, 0x31, 0xf4, 0xc7, 0x4f, 0x6a, 0x50, 0xf9, 0x7d, - 0x5b, 0xe5, 0xdb, 0x4a, 0xb4, 0x26, 0x22, 0xa6, 0xee, 0xbf, 0x0b, 0x5b, 0x07, 0x34, 0xa5, 0x45, - 0x1c, 0x6a, 0xe7, 0x90, 0x3a, 0xe7, 0x1a, 0xd4, 0x3e, 0x29, 0x59, 0x55, 0x00, 0xb7, 0x0f, 0xbd, - 0xfa, 0x44, 0xb1, 0x5c, 0x77, 0x13, 0xd6, 0x0f, 0x28, 0xd3, 0x70, 0x6d, 0xc5, 0x6f, 0x1c, 0xd8, - 0xc0, 0x81, 0xf2, 0xa8, 0xbc, 0x10, 0x03, 0x52, 0xd5, 0xbf, 0x07, 0xab, 0x9a, 0x74, 0xa9, 0xb6, - 0x91, 0xd0, 0xf2, 0x87, 0x86, 0x96, 0xeb, 0x33, 0xab, 0xcd, 0x54, 0x9a, 0xbb, 0xa9, 0xda, 0x93, - 0x12, 0xdc, 0xdf, 0x87, 0x8d, 0x46, 0xd4, 0x57, 0xf1, 0x7f, 0xb7, 0x07, 0x9b, 0x07, 0x94, 0x19, - 0x6e, 0x6c, 0x38, 0x68, 0xdb, 0x00, 0x73, 0xbf, 0x2c, 0x59, 0x50, 0xb0, 0xca, 0x2f, 0xe5, 0x27, - 0x79, 0x13, 0x96, 0x93, 0xb8, 0x64, 0x34, 0xf5, 0x83, 0x28, 0x2a, 0x68, 0x29, 0x42, 0xde, 0xa2, - 0xb7, 0x24, 0xa0, 0x7b, 0x02, 0xe8, 0xfe, 0xbd, 0xc3, 0x0d, 0x33, 0xc2, 0x4a, 0x2a, 0xeb, 0x73, - 0x58, 0xac, 0xa2, 0x82, 0x50, 0xd2, 0xae, 0xa1, 0xa4, 0xa6, 0x39, 0xbb, 0x23, 0xa1, 0xa1, 0x22, - 0xd0, 0xff, 0x1d, 0x58, 0x7e, 0xdd, 0x1b, 0xfa, 0x23, 0xe8, 0x4b, 0xdf, 0x50, 0x11, 0xf9, 0x8b, - 0x60, 0x40, 0x95, 0x5f, 0xf5, 0x61, 0x41, 0x05, 0x70, 0xc9, 0x43, 0x7f, 0xbb, 0x3b, 0x70, 0xad, - 0x71, 0xa6, 0x74, 0xac, 0x7b, 0xb0, 0x76, 0x40, 0x99, 0x0e, 0xf3, 0x8a, 0xe2, 0xd8, 0x28, 0xe0, - 0x3e, 0x40, 0x4f, 0x34, 0x26, 0x48, 0x15, 0x6e, 0xc3, 0x62, 0x75, 0x88, 0x48, 0xdf, 0xd6, 0x00, - 0xf7, 0x3e, 0xba, 0xa9, 0x9a, 0xf5, 0xf8, 0xe9, 0xa1, 0x47, 0xc5, 0xb4, 0xab, 0xb0, 0x90, 0xb1, - 0xdc, 0x0f, 0xb3, 0x48, 0x89, 0x3e, 0x9f, 0xb1, 0x7c, 0x3f, 0x8b, 0xa8, 0x74, 0x0d, 0x63, 0x8e, - 0x76, 0x8d, 0xbf, 0x12, 0xa6, 0xb4, 0x87, 0xa4, 0x1c, 0xbf, 0x0d, 0x8b, 0x8a, 0xa0, 0x32, 0xe5, - 0x7b, 0x86, 0x29, 0x9b, 0xe6, 0xec, 0x3e, 0x16, 0x1c, 0xa5, 0x25, 0x17, 0xa4, 0x00, 0x65, 0xff, - 0x63, 0x58, 0xb2, 0x86, 0x2e, 0xf3, 0xec, 0x45, 0xd3, 0x64, 0x0f, 0x60, 0xf3, 0xd3, 0xb8, 0x34, - 0x4f, 0xdc, 0x69, 0xcc, 0xf5, 0xcd, 0x8c, 0xb5, 0x34, 0xeb, 0xe0, 0x27, 0x30, 0x9b, 0x06, 0xfa, - 0xd8, 0xc7, 0xdf, 0xa6, 0xa1, 0x5a, 0x76, 0xb8, 0xee, 0xc1, 0xfc, 0x19, 0x2d, 0x8e, 0xb2, 0x92, - 0xe2, 0x99, 0xbe, 0xe0, 0xa9, 0x4f, 0xf2, 0x06, 0x2c, 0x0d, 0xcb, 0x38, 0x3d, 0xf1, 0xcb, 0x20, - 0x8d, 0x8e, 0xb2, 0x97, 0x78, 0x82, 0x2f, 0x78, 0x1d, 0x04, 0x3e, 0x11, 0x30, 0x72, 0x0b, 0x3a, - 0xa7, 0x8c, 0xe5, 0x3e, 0x4f, 0x2d, 0xb2, 0x21, 0x93, 0x07, 0x76, 0x9b, 0xc3, 0x9e, 0x0a, 0x10, - 0xdf, 0x78, 0x88, 0x32, 0x2c, 0x69, 0x11, 0x9c, 0xd0, 0x94, 0xf5, 0xe6, 0xc4, 0xc6, 0xe3, 0xd0, - 0x9f, 0x28, 0x20, 0xd9, 0x01, 0x40, 0xb4, 0xbc, 0xc8, 0x5e, 0x5e, 0xf4, 0xe6, 0x85, 0x6b, 0x70, - 0xc8, 0x21, 0x07, 0x90, 0xb7, 0xa0, 0x7b, 0x14, 0x94, 0x54, 0xa5, 0x06, 0x31, 0x2d, 0x7b, 0x0b, - 0x88, 0xb3, 0xcc, 0xc1, 0xfb, 0x1a, 0x4a, 0xee, 0xf2, 0xbc, 0x20, 0xcf, 0x33, 0xbe, 0xe9, 0xfd, - 0xa0, 0x2c, 0x29, 0x2b, 0x7b, 0x8b, 0x88, 0xd9, 0xd5, 0xf0, 0x3d, 0x04, 0xf3, 0x15, 0xaa, 0xcc, - 0x26, 0x0f, 0xe2, 0xa2, 0xec, 0x01, 0xe2, 0x75, 0x24, 0xf0, 0x90, 0xc3, 0x38, 0xe3, 0x2a, 0x5f, - 0x12, 0x68, 0x6d, 0xc1, 0x58, 0x83, 0x05, 0xe2, 0x3b, 0xb0, 0x1a, 0x0c, 0xd9, 0x29, 0x4d, 0x19, - 0x8f, 0xfa, 0x9c, 0x79, 0x1e, 0xf7, 0x3a, 0xa8, 0xb3, 0x15, 0x6b, 0x60, 0x2f, 0x8f, 0xdd, 0x73, - 0x58, 0x39, 0xa0, 0xec, 0x69, 0x1c, 0xbe, 0xa0, 0xc5, 0x14, 0x06, 0x27, 0x77, 0x60, 0x96, 0xf3, - 0x96, 0x71, 0x60, 0x5d, 0x9f, 0x32, 0x32, 0x1b, 0xe2, 0x12, 0x78, 0x88, 0xc1, 0xf5, 0x88, 0xab, - 0xf6, 0xd9, 0x45, 0x2e, 0x6c, 0xba, 0xe8, 0x2d, 0x22, 0xe4, 0xe9, 0x45, 0x4e, 0xdd, 0x67, 0xd0, - 0x31, 0x27, 0xf1, 0x0d, 0x19, 0xd1, 0x24, 0x1e, 0xc4, 0x8c, 0x16, 0x6a, 0x43, 0x6a, 0x00, 0xf7, - 0x25, 0xae, 0x5e, 0xe9, 0xb6, 0xf8, 0x9b, 0xfb, 0xf2, 0x57, 0xc3, 0x8c, 0x29, 0xda, 0xe2, 0xc3, - 0xfd, 0xf3, 0x16, 0x2c, 0xab, 0xe5, 0x48, 0x47, 0x54, 0x32, 0x3b, 0x97, 0xca, 0x7c, 0x0b, 0x3a, - 0x49, 0x50, 0x32, 0x7f, 0x98, 0x47, 0x81, 0x4a, 0x1b, 0x66, 0xbc, 0x36, 0x87, 0xfd, 0x44, 0x80, - 0xb8, 0xad, 0x54, 0x56, 0x88, 0x56, 0x90, 0xdc, 0x3b, 0xa1, 0xb9, 0x18, 0x02, 0xb3, 0x7c, 0x0e, - 0x7a, 0xaa, 0xe3, 0xe1, 0x6f, 0x0e, 0x3b, 0x8d, 0x4f, 0x4e, 0xd1, 0x33, 0x1d, 0x0f, 0x7f, 0xf3, - 0x0d, 0x9a, 0x64, 0xe7, 0xe8, 0x87, 0x8e, 0xc7, 0x7f, 0x72, 0xc8, 0x51, 0x1c, 0xa1, 0xdb, 0x39, - 0x1e, 0xff, 0xc9, 0x21, 0x41, 0xf9, 0x02, 0x9d, 0xcc, 0xf1, 0xf8, 0x4f, 0x9e, 0x51, 0x9f, 0x65, - 0xc9, 0x70, 0x40, 0xd1, 0x9f, 0x1c, 0x4f, 0x7e, 0x91, 0x6b, 0xb0, 0x98, 0x17, 0x71, 0x48, 0xfd, - 0x80, 0x9d, 0xa2, 0x0b, 0x39, 0xde, 0x02, 0x02, 0xf6, 0xd8, 0xa9, 0xbb, 0x06, 0xab, 0xda, 0xd0, - 0x3a, 0x32, 0x3d, 0x87, 0x79, 0x09, 0x99, 0x68, 0xf4, 0xf7, 0x61, 0x9e, 0x09, 0xb4, 0x5e, 0x0b, - 0x43, 0xd4, 0xa6, 0xd2, 0xa1, 0xad, 0x69, 0x4f, 0xa1, 0xb9, 0xbf, 0x09, 0xc4, 0xe4, 0x26, 0x0d, - 0x71, 0xb7, 0xa2, 0x23, 0x42, 0x5d, 0xd7, 0xa6, 0x53, 0x56, 0x04, 0xbe, 0xc6, 0x40, 0xff, 0xb8, - 0x88, 0x78, 0x10, 0xc8, 0x5e, 0x7c, 0xab, 0xae, 0xf9, 0x63, 0x58, 0xd2, 0x8c, 0x1f, 0x31, 0x3a, - 0xe0, 0x0a, 0x0f, 0x06, 0xd9, 0x30, 0x65, 0xc8, 0xd3, 0xf1, 0xe4, 0x17, 0xf7, 0x40, 0xd4, 0x2f, - 0xb2, 0x74, 0x3c, 0xf1, 0x41, 0x96, 0xa1, 0x15, 0x47, 0xf2, 0x62, 0xd2, 0x8a, 0x23, 0xf7, 0x7f, - 0x1d, 0x58, 0x35, 0x16, 0xf2, 0xca, 0x4e, 0x59, 0xf3, 0xb8, 0x56, 0x83, 0xc7, 0xdd, 0x85, 0xd9, - 0xa3, 0x38, 0xe2, 0xf7, 0x21, 0xae, 0xd7, 0x0d, 0x45, 0xce, 0x5a, 0x87, 0x87, 0x28, 0x1c, 0x35, - 0x28, 0x5f, 0x94, 0xbd, 0xd9, 0x89, 0xa8, 0x1c, 0xa5, 0xb6, 0x1f, 0xae, 0xd4, 0xf7, 0x83, 0xad, - 0xcb, 0xb9, 0x51, 0x5d, 0x8a, 0x4c, 0x50, 0xd3, 0xd6, 0x9e, 0x17, 0x02, 0x54, 0xc0, 0x89, 0x66, - 0xfd, 0x75, 0x80, 0x4c, 0x63, 0x4a, 0xff, 0xbb, 0x5a, 0x13, 0x5a, 0xbb, 0xa0, 0x81, 0xec, 0xfe, - 0x08, 0x8f, 0x71, 0x93, 0xb9, 0x54, 0xfe, 0x7d, 0x8b, 0xa6, 0xf0, 0x45, 0x52, 0xa3, 0x59, 0x5a, - 0xc4, 0x3e, 0x44, 0x62, 0x7b, 0x61, 0xc8, 0x4d, 0x6f, 0x5c, 0x7a, 0x27, 0x9e, 0x8f, 0xcf, 0x60, - 0x5e, 0xce, 0x90, 0x6e, 0x21, 0x10, 0x5a, 0x71, 0x44, 0x3e, 0x06, 0x30, 0xce, 0x10, 0xb1, 0xae, - 0x6b, 0x4a, 0x06, 0x39, 0x49, 0x79, 0x03, 0xb2, 0x33, 0xd0, 0xdd, 0x63, 0x58, 0x6b, 0x40, 0xe1, - 0xa2, 0xe8, 0x2b, 0xab, 0x14, 0x45, 0x7d, 0x93, 0x1b, 0xd0, 0x66, 0x19, 0x0b, 0x12, 0xbf, 0x4a, - 0x00, 0x1c, 0x0f, 0x10, 0xf4, 0x8c, 0x43, 0x30, 0x40, 0x65, 0x89, 0xf0, 0x5c, 0x1e, 0xa0, 0xb2, - 0x24, 0x72, 0x03, 0x4c, 0x6a, 0xac, 0x45, 0x4b, 0x15, 0x4e, 0x32, 0xd9, 0x3b, 0xb0, 0x10, 0x88, - 0x29, 0x6a, 0x61, 0xdd, 0x91, 0x85, 0x79, 0x1a, 0xc1, 0x25, 0x78, 0x02, 0xed, 0x67, 0xe9, 0x71, - 0x7c, 0xa2, 0xbc, 0xe3, 0x2d, 0x0c, 0x56, 0x0a, 0x56, 0xe5, 0x13, 0x51, 0xc0, 0x02, 0xe4, 0xd6, - 0xf1, 0xf0, 0xb7, 0xfb, 0x47, 0x0e, 0xac, 0x1c, 0x66, 0x05, 0x3b, 0xce, 0x92, 0x38, 0x93, 0xa9, - 0x33, 0x4f, 0x25, 0x54, 0x6a, 0x2d, 0x73, 0x34, 0xf9, 0xc9, 0x23, 0x64, 0x98, 0xc5, 0xa9, 0xf0, - 0xd5, 0x96, 0x54, 0x50, 0x16, 0xa7, 0xdc, 0x55, 0xc9, 0x4d, 0x68, 0x47, 0xb4, 0x0c, 0x8b, 0x38, - 0xe7, 0x57, 0x25, 0x19, 0x16, 0x4c, 0x10, 0x27, 0x7c, 0x14, 0x24, 0x41, 0x1a, 0x52, 0x19, 0xd9, - 0xd5, 0xa7, 0xbb, 0x81, 0xe1, 0x4a, 0x4b, 0x62, 0xdc, 0x5a, 0x6d, 0xb0, 0x5c, 0xca, 0xaf, 0xc1, - 0x62, 0xae, 0x80, 0xd2, 0xfd, 0x7a, 0x4a, 0x43, 0xa3, 0xcb, 0xf1, 0x2a, 0x54, 0x77, 0x9b, 0xe7, - 0xd5, 0x15, 0xbd, 0x27, 0xc3, 0xc1, 0x20, 0x28, 0x2e, 0x14, 0xb7, 0x14, 0x66, 0xf7, 0xb3, 0x38, - 0xe5, 0x8a, 0xe2, 0x8b, 0x52, 0x89, 0x17, 0xff, 0x6d, 0x8a, 0xde, 0xb2, 0x44, 0x37, 0xb5, 0x35, - 0x63, 0x6b, 0xeb, 0x3a, 0x40, 0x4e, 0x8b, 0x90, 0xa6, 0x2c, 0x38, 0x51, 0x2b, 0x36, 0x20, 0xee, - 0x29, 0x90, 0xc7, 0xc7, 0xc7, 0x49, 0x9c, 0x52, 0xce, 0x56, 0x0a, 0x33, 0x41, 0xfb, 0xe3, 0x65, - 0xb0, 0x39, 0xcd, 0xd4, 0x38, 0xfd, 0x18, 0x56, 0x1f, 0xa7, 0x0d, 0x8c, 0x14, 0x39, 0x67, 0x12, - 0xb9, 0x56, 0x8d, 0xdc, 0x0f, 0xa1, 0x63, 0x08, 0x5e, 0x92, 0x8f, 0x60, 0x51, 0xca, 0xa8, 0x93, - 0xf0, 0xbe, 0x8e, 0x06, 0xb5, 0x15, 0x7a, 0x15, 0xb2, 0xfb, 0x17, 0x0e, 0xb4, 0x2b, 0xc9, 0x4a, - 0xf2, 0x00, 0xae, 0x70, 0x75, 0x2b, 0x2a, 0xd7, 0x35, 0x95, 0x0a, 0x67, 0x17, 0xff, 0x15, 0xb9, - 0xbb, 0x40, 0xee, 0x3f, 0x01, 0xa8, 0x80, 0x0d, 0x59, 0xfb, 0x3d, 0xfb, 0xf6, 0x75, 0xb5, 0x4e, - 0x55, 0x89, 0x66, 0x24, 0xf4, 0xff, 0x3c, 0xcb, 0xaf, 0x52, 0x0d, 0xce, 0x22, 0x7d, 0xf0, 0x3d, - 0x68, 0x8b, 0xbd, 0xc0, 0x23, 0x80, 0x12, 0xb8, 0x53, 0x95, 0x0d, 0xe2, 0xd4, 0x03, 0xdc, 0x1b, - 0x38, 0x4e, 0x3e, 0x80, 0x25, 0x14, 0xd6, 0xcf, 0x84, 0x42, 0xe4, 0xc6, 0xb6, 0x27, 0x74, 0x10, - 0x45, 0xaa, 0x8c, 0xe4, 0xb0, 0x61, 0x4d, 0xf1, 0x4b, 0x21, 0x82, 0x3c, 0xa4, 0xbe, 0x6f, 0xdc, - 0x73, 0xc6, 0x49, 0x29, 0x94, 0x25, 0x09, 0xca, 0x31, 0xa1, 0xba, 0xb5, 0xb0, 0x3e, 0x42, 0xee, - 0x41, 0x47, 0x72, 0x44, 0xcd, 0xc8, 0x23, 0xce, 0x96, 0xb1, 0x2d, 0x26, 0x22, 0x02, 0x19, 0xc0, - 0xba, 0x39, 0x41, 0x4b, 0x78, 0x05, 0x27, 0x7e, 0x3c, 0xbd, 0x84, 0x69, 0x4d, 0x40, 0x12, 0xd6, - 0x06, 0xfa, 0xbf, 0x0b, 0xbd, 0x71, 0x0b, 0x6a, 0x30, 0xfb, 0xdb, 0xb6, 0xd9, 0xd7, 0x1b, 0x5c, - 0xb2, 0x34, 0x8b, 0x73, 0x5f, 0xc2, 0xd6, 0x18, 0x61, 0x5e, 0xe1, 0x46, 0x6f, 0x78, 0xaa, 0xe9, - 0x4d, 0x7f, 0xe6, 0x40, 0x7f, 0x2f, 0x8a, 0x6a, 0xc1, 0xa9, 0xba, 0x80, 0x7f, 0xdb, 0x21, 0x77, - 0x07, 0xae, 0x35, 0x0a, 0x24, 0x2b, 0x05, 0x2f, 0x61, 0xc7, 0xa3, 0x83, 0xec, 0x8c, 0x7e, 0xdb, - 0x22, 0xbb, 0x37, 0xe1, 0xfa, 0x38, 0xce, 0x52, 0x36, 0x2c, 0x9d, 0xd9, 0xa5, 0x67, 0x9d, 0x18, - 0xfd, 0x87, 0x03, 0x4b, 0x76, 0x51, 0xfa, 0x75, 0xdd, 0xa3, 0xdf, 0x05, 0x52, 0xd0, 0x92, 0xf9, - 0x45, 0x96, 0x24, 0xfc, 0x3a, 0x1d, 0xd1, 0x24, 0xb8, 0x90, 0xe5, 0xf0, 0x15, 0x3e, 0xe2, 0x89, - 0x81, 0x4f, 0x39, 0x9c, 0x6c, 0xc1, 0x7c, 0x90, 0xc7, 0x3e, 0xf7, 0x1a, 0x71, 0x97, 0x9e, 0x0b, - 0xf2, 0xf8, 0x47, 0xf4, 0x82, 0xb8, 0xb0, 0x24, 0x07, 0xfc, 0x84, 0x9e, 0xd1, 0x04, 0x73, 0xbe, - 0x19, 0xaf, 0x2d, 0x86, 0x3f, 0xe7, 0x20, 0x7e, 0xf7, 0xcd, 0x8b, 0x98, 0xbb, 0x5f, 0x55, 0x77, - 0x9f, 0x47, 0x69, 0xba, 0x12, 0xae, 0x56, 0xe7, 0xfe, 0x14, 0xae, 0x36, 0xe8, 0x42, 0xc6, 0xa8, - 0x1f, 0x40, 0xd7, 0xae, 0xde, 0xab, 0x38, 0xa5, 0xb3, 0x56, 0x6b, 0xa2, 0xb7, 0x7c, 0x6c, 0xd1, - 0x91, 0xd9, 0x27, 0xe2, 0x78, 0x01, 0xd3, 0xf5, 0x22, 0xf7, 0x2b, 0x58, 0xaf, 0x80, 0xfb, 0x59, - 0x7a, 0x46, 0x8b, 0x92, 0x7b, 0x1b, 0x81, 0xd9, 0xe3, 0x22, 0x53, 0xc5, 0x4e, 0xfc, 0xcd, 0xf3, - 0x36, 0x96, 0x49, 0x37, 0x68, 0xb1, 0x8c, 0xe3, 0x14, 0x01, 0x53, 0xa7, 0x14, 0xfe, 0xe6, 0x79, - 0x72, 0x8c, 0x44, 0xa8, 0x8f, 0x63, 0xc2, 0x55, 0xdb, 0x12, 0xc6, 0xb9, 0xb8, 0xcf, 0x30, 0x7d, - 0x34, 0x45, 0x91, 0x6b, 0xfc, 0x0d, 0x68, 0x8b, 0x35, 0xf2, 0x99, 0x6a, 0x7d, 0xdb, 0xd6, 0xfa, - 0x46, 0xc4, 0xf4, 0xe0, 0x58, 0x43, 0xdd, 0xff, 0x6a, 0x41, 0x07, 0x33, 0xd6, 0x4f, 0x29, 0x0b, - 0xe2, 0x64, 0x72, 0x2e, 0x2d, 0x72, 0xd0, 0x96, 0xce, 0x41, 0xdf, 0x80, 0x25, 0xb3, 0x98, 0x71, - 0xa1, 0x2e, 0xb3, 0x46, 0x29, 0xe3, 0x82, 0xbc, 0x09, 0xcb, 0x78, 0xb5, 0xae, 0xb0, 0x84, 0xcf, - 0x2c, 0x21, 0x54, 0xa3, 0xd9, 0x17, 0x81, 0x2b, 0x23, 0x17, 0x01, 0x3e, 0x8c, 0xc9, 0xb4, 0x5f, - 0xc6, 0x91, 0xbe, 0x27, 0x20, 0xe4, 0x49, 0x1c, 0x19, 0xc3, 0x38, 0x7b, 0xde, 0x18, 0xc6, 0xd9, - 0xfc, 0x0e, 0x54, 0x50, 0x51, 0x84, 0xc7, 0xb7, 0xa4, 0x05, 0x74, 0xba, 0x8e, 0x02, 0x3e, 0x8d, - 0x07, 0xf8, 0xd2, 0x24, 0x0b, 0xc7, 0xa2, 0xce, 0x22, 0xbf, 0xaa, 0x6b, 0x1a, 0x98, 0xd7, 0xb4, - 0xea, 0x52, 0xd7, 0xb6, 0x2e, 0x75, 0x37, 0xa0, 0x9d, 0xe5, 0x34, 0xf5, 0xe5, 0x15, 0xbb, 0x23, - 0xb2, 0x07, 0x0e, 0x7a, 0x86, 0x10, 0x59, 0x32, 0x41, 0x9d, 0x97, 0xd3, 0xdc, 0x4b, 0x6d, 0xc5, - 0xb4, 0x46, 0x15, 0xa3, 0x2e, 0x82, 0x33, 0x97, 0x5d, 0x04, 0xdd, 0x3d, 0xcc, 0x8a, 0x15, 0x63, - 0xe9, 0x3e, 0xef, 0xc2, 0x1c, 0xaa, 0x49, 0x79, 0xce, 0xba, 0x75, 0x8d, 0x91, 0x4e, 0xe1, 0x49, - 0x1c, 0xf7, 0x87, 0xf8, 0x3e, 0x87, 0x43, 0xd3, 0x88, 0x7e, 0x15, 0x16, 0x84, 0x55, 0xb4, 0xd7, - 0xcc, 0xe3, 0xf7, 0xa3, 0xc8, 0xfd, 0x57, 0x07, 0xc8, 0x93, 0xe1, 0xd1, 0x20, 0x9e, 0x9e, 0xda, - 0xf4, 0x17, 0x74, 0x02, 0xb3, 0xe8, 0x26, 0xc2, 0x1d, 0xf1, 0xf7, 0x88, 0x87, 0xcc, 0x8e, 0x7a, - 0x48, 0x65, 0xce, 0x2b, 0xcd, 0x77, 0xf4, 0x39, 0xd3, 0xf8, 0x3c, 0xc4, 0x27, 0x31, 0x4d, 0x99, - 0x2f, 0x8b, 0x2d, 0x3c, 0xc4, 0x23, 0xe0, 0x51, 0xe4, 0x3e, 0x81, 0x35, 0x6b, 0x65, 0x52, 0xd3, - 0xb7, 0xa0, 0x23, 0x04, 0xc8, 0x93, 0x20, 0xd4, 0x95, 0xe6, 0x36, 0xc2, 0x0e, 0x11, 0x34, 0x49, - 0x5f, 0x7f, 0xe2, 0xc0, 0xfa, 0x93, 0x78, 0x30, 0x4c, 0x02, 0x46, 0x7f, 0x05, 0x1a, 0xab, 0x96, - 0x3f, 0x63, 0x2d, 0x5f, 0x69, 0x72, 0xb6, 0xd2, 0xa4, 0xfb, 0xdf, 0x0e, 0x6c, 0x8c, 0x88, 0xa2, - 0x73, 0x42, 0xdb, 0x99, 0xc6, 0x14, 0x07, 0x24, 0x92, 0xc1, 0xb4, 0x65, 0x31, 0x7d, 0x03, 0x96, - 0x06, 0x71, 0x1a, 0x0f, 0x86, 0x03, 0x5f, 0xe8, 0x5e, 0xc8, 0xd4, 0x91, 0xc0, 0x43, 0x34, 0x01, - 0x47, 0x0a, 0x5e, 0x1a, 0x48, 0xb3, 0x12, 0x49, 0x00, 0x05, 0xd2, 0xfb, 0xb0, 0x5e, 0xe5, 0xed, - 0xfe, 0x49, 0x10, 0xa7, 0x7e, 0x92, 0x95, 0xa5, 0xb4, 0x31, 0xa9, 0xc6, 0x0e, 0x82, 0x38, 0xfd, - 0x3c, 0x2b, 0x4b, 0x23, 0x08, 0xcc, 0x99, 0x41, 0x80, 0x27, 0x30, 0x2b, 0xcf, 0x4f, 0x83, 0x84, - 0x7e, 0x92, 0x0d, 0x8e, 0x5e, 0xaf, 0xee, 0x6f, 0x41, 0x47, 0xd4, 0xdd, 0x58, 0x50, 0x9c, 0x50, - 0x65, 0x81, 0x36, 0xc2, 0x9e, 0x22, 0xa8, 0xd1, 0x0c, 0xff, 0xe9, 0x00, 0xd9, 0xe7, 0xa9, 0x4c, - 0x32, 0xb5, 0x3f, 0xf0, 0x50, 0x22, 0xee, 0xcd, 0x95, 0x87, 0x2d, 0x4a, 0xc8, 0x23, 0xdb, 0xfd, - 0x66, 0x2c, 0xf7, 0xd3, 0xab, 0x99, 0x7d, 0xc5, 0xe2, 0x58, 0x2d, 0x8e, 0xbf, 0x09, 0xcb, 0xe7, - 0x41, 0x92, 0x50, 0xa6, 0x9f, 0xaf, 0x64, 0x15, 0x5d, 0x40, 0xd5, 0x1d, 0x5c, 0x2d, 0x78, 0xde, - 0x58, 0xf0, 0x06, 0xac, 0x59, 0xeb, 0x95, 0xd9, 0xd0, 0x03, 0xd8, 0x14, 0xe0, 0xbd, 0x24, 0x99, - 0x3a, 0xaa, 0xba, 0x7f, 0xd9, 0x82, 0xad, 0xda, 0x34, 0x9d, 0x36, 0xd8, 0x6e, 0x7c, 0x5b, 0x2f, - 0xb7, 0x79, 0xc2, 0xae, 0xfc, 0x94, 0xb3, 0xfa, 0xff, 0xe8, 0xc0, 0x9c, 0x00, 0x4d, 0xb4, 0xc6, - 0x97, 0x2a, 0x20, 0x48, 0x87, 0x13, 0x37, 0xa2, 0xef, 0x4e, 0xc7, 0x4c, 0xfc, 0x67, 0x3e, 0x59, - 0x8a, 0x48, 0x22, 0x5f, 0x2b, 0x7f, 0x00, 0x2b, 0xa3, 0x08, 0xaf, 0xf4, 0x9c, 0x23, 0xaa, 0x2a, - 0x0f, 0xcf, 0xa8, 0xf1, 0x44, 0xf9, 0x8d, 0x03, 0xdd, 0xfd, 0x2c, 0x8d, 0x62, 0x7e, 0x62, 0x1e, - 0x06, 0x45, 0x30, 0x28, 0xe5, 0x2b, 0xb9, 0x00, 0xa9, 0xb2, 0xbb, 0x06, 0x8c, 0x29, 0x70, 0xee, - 0x00, 0x84, 0xa7, 0x34, 0x7c, 0xe1, 0xcb, 0x8a, 0xa3, 0x78, 0x5a, 0xe7, 0x90, 0x4f, 0xe2, 0xa8, - 0x24, 0xef, 0xc1, 0x5a, 0x35, 0xec, 0x07, 0x69, 0xe4, 0xcb, 0x72, 0x23, 0xbe, 0x40, 0x68, 0xbc, - 0xbd, 0x34, 0xda, 0x2b, 0x5f, 0xe0, 0x3b, 0x89, 0xae, 0xb2, 0xf9, 0x56, 0x08, 0xef, 0x6a, 0xf8, - 0x1e, 0x82, 0xdd, 0xff, 0x71, 0xf0, 0x04, 0x54, 0xab, 0x92, 0xd6, 0xae, 0x0a, 0x6b, 0x58, 0x6f, - 0xb5, 0x4c, 0xd6, 0x1a, 0x31, 0x19, 0x81, 0xd9, 0x98, 0xd1, 0x81, 0x3a, 0x58, 0xf8, 0x6f, 0xf2, - 0x09, 0xac, 0xe8, 0x15, 0xfb, 0x39, 0xaa, 0x45, 0x6e, 0x93, 0xad, 0xea, 0xe2, 0x68, 0x69, 0xcd, - 0xeb, 0x86, 0x23, 0x6a, 0x54, 0xdb, 0xeb, 0xca, 0x54, 0x81, 0x3a, 0x44, 0x6d, 0xcb, 0xf8, 0x24, - 0xbe, 0x84, 0xd4, 0x34, 0x1c, 0x32, 0x1a, 0xc9, 0x54, 0x59, 0x7f, 0xbb, 0xff, 0xee, 0x40, 0x77, - 0x2f, 0x8a, 0x70, 0xdd, 0xd3, 0x84, 0x09, 0xb5, 0xca, 0xd6, 0x25, 0xab, 0x9c, 0xf9, 0x7f, 0xae, - 0xf2, 0x97, 0x0e, 0x22, 0x63, 0x94, 0xe0, 0xba, 0xb0, 0x52, 0xad, 0xb3, 0xd9, 0xbc, 0xee, 0x77, - 0x80, 0x88, 0xeb, 0x95, 0xa5, 0x8e, 0x51, 0xac, 0x0d, 0x58, 0xb3, 0xb0, 0x64, 0xac, 0xf9, 0x0c, - 0xee, 0x1c, 0x50, 0xb6, 0x5f, 0x5c, 0xe4, 0x2c, 0x53, 0xe9, 0xec, 0xa7, 0x34, 0xcf, 0xca, 0x58, - 0x45, 0x2e, 0x3a, 0x55, 0xf4, 0xf9, 0x27, 0x07, 0xee, 0x4e, 0x41, 0x48, 0x2e, 0xe1, 0x67, 0xf5, - 0xfa, 0xd2, 0x6f, 0x99, 0xad, 0x23, 0x53, 0x51, 0xd9, 0xd5, 0x10, 0xf9, 0x82, 0xaf, 0x49, 0xf6, - 0xbf, 0x0f, 0xcb, 0xf6, 0xe0, 0x2b, 0x85, 0x8a, 0x04, 0x6e, 0x5f, 0x22, 0xc4, 0x34, 0x3e, 0x77, - 0x1b, 0x96, 0x43, 0x8b, 0x84, 0x64, 0x34, 0x02, 0x75, 0xf7, 0xe1, 0xad, 0x4b, 0xb9, 0x49, 0xb5, - 0x8d, 0xbd, 0xa1, 0xbb, 0x7f, 0x33, 0x0b, 0x5b, 0xcf, 0x63, 0x76, 0x1a, 0x15, 0xc1, 0xb9, 0xf2, - 0xbe, 0x69, 0x84, 0x1c, 0xb9, 0xbc, 0xb7, 0xea, 0xf5, 0x86, 0xb7, 0x61, 0x35, 0x4b, 0x29, 0xde, - 0x31, 0xfc, 0x3c, 0x28, 0xcb, 0xf3, 0xac, 0x50, 0x67, 0x69, 0x37, 0x4b, 0x29, 0xbf, 0x67, 0x1c, - 0x4a, 0xf0, 0xc8, 0x69, 0x3c, 0x3b, 0x7a, 0x1a, 0xaf, 0xc0, 0x4c, 0x1e, 0xa7, 0xf2, 0xcd, 0x84, - 0xff, 0xe4, 0x67, 0x27, 0x2b, 0x82, 0xc8, 0xa0, 0x2c, 0xcf, 0x4e, 0x84, 0x6a, 0xba, 0x66, 0x15, - 0x7f, 0x7e, 0xa4, 0x8a, 0x6f, 0xe8, 0x64, 0xc1, 0xae, 0x5a, 0xdc, 0x80, 0xb6, 0xfc, 0xe9, 0xb3, - 0xe0, 0x44, 0x5e, 0x81, 0x40, 0x82, 0x9e, 0x06, 0x27, 0x46, 0xb6, 0x06, 0x56, 0xb6, 0xb6, 0x03, - 0x70, 0x4c, 0xa9, 0x6f, 0x5d, 0x86, 0x16, 0x8f, 0x29, 0x15, 0x41, 0x97, 0xa7, 0xca, 0x47, 0x41, - 0xfa, 0xc2, 0xc7, 0x1a, 0x44, 0x47, 0x88, 0xc3, 0x01, 0x5f, 0x04, 0x03, 0xcc, 0x89, 0x71, 0x50, - 0xc9, 0xb4, 0x24, 0x34, 0xca, 0x61, 0x7b, 0x55, 0x35, 0x05, 0x51, 0xc2, 0x98, 0x5d, 0xf4, 0x96, - 0xab, 0xf9, 0xfb, 0x31, 0xbb, 0xd0, 0xf3, 0x51, 0x67, 0xc5, 0x45, 0xaf, 0x5b, 0xcd, 0xdf, 0x17, - 0x20, 0x2e, 0x5e, 0x79, 0x1e, 0x1f, 0x53, 0xd1, 0x74, 0xb1, 0x22, 0xdb, 0x90, 0x38, 0x64, 0x3f, - 0x8b, 0x30, 0x8d, 0x3c, 0x8f, 0x0b, 0xe3, 0x72, 0xba, 0x2a, 0xae, 0xb0, 0x1c, 0xa8, 0x5c, 0xc3, - 0x7d, 0x1b, 0x56, 0x94, 0xbb, 0x98, 0x7d, 0x89, 0x05, 0x2d, 0x87, 0x09, 0x53, 0x7d, 0x89, 0xe2, - 0xeb, 0xfe, 0x3f, 0xdc, 0x82, 0xe5, 0x83, 0x4c, 0x38, 0xe8, 0x53, 0x6e, 0x97, 0x82, 0x3c, 0x86, - 0x79, 0xd9, 0x6c, 0x47, 0x36, 0x6b, 0xdd, 0x77, 0xe8, 0x75, 0xfd, 0xad, 0x31, 0x5d, 0x79, 0xee, - 0xda, 0xcf, 0xff, 0xe5, 0xdf, 0x7e, 0xd1, 0x5a, 0x22, 0xed, 0x7b, 0x67, 0x1f, 0xdc, 0x3b, 0xa1, - 0x2c, 0xe6, 0x54, 0x4e, 0x61, 0xc9, 0xea, 0x8f, 0x22, 0xdb, 0x56, 0x8f, 0xd3, 0x48, 0xdb, 0x54, - 0x7f, 0x67, 0x62, 0x07, 0x94, 0xdb, 0x47, 0x16, 0xeb, 0x84, 0x48, 0x16, 0x25, 0xa2, 0x08, 0xc2, - 0x5f, 0x41, 0xf7, 0x21, 0x56, 0x86, 0x34, 0x55, 0x72, 0xa3, 0xa2, 0xd6, 0xd8, 0xf7, 0xd5, 0xbf, - 0x39, 0x1e, 0x41, 0x72, 0xbc, 0x86, 0x1c, 0x37, 0xc8, 0x1a, 0xe7, 0x28, 0x2a, 0x4f, 0xba, 0xdf, - 0x8a, 0x94, 0xb0, 0x22, 0x3b, 0x49, 0x5e, 0x2b, 0xcf, 0x6d, 0xe4, 0xb9, 0x49, 0xd6, 0x39, 0xcf, - 0x48, 0x30, 0xa8, 0x98, 0x66, 0x78, 0xb1, 0x35, 0x3b, 0x9f, 0xc8, 0xf5, 0xb1, 0x2d, 0x51, 0x82, - 0xe5, 0x8d, 0x4b, 0x5a, 0xa6, 0xec, 0x55, 0x9e, 0x50, 0x8e, 0xab, 0xbb, 0xa6, 0xc8, 0x2f, 0x1c, - 0x2c, 0xe2, 0x35, 0xf6, 0xe8, 0x91, 0xb7, 0x2e, 0x6f, 0x0c, 0x14, 0x32, 0xdc, 0x99, 0xb6, 0x83, - 0xd0, 0xfd, 0x0e, 0x0a, 0x73, 0x9d, 0x6c, 0x4b, 0x61, 0xac, 0xae, 0x41, 0xd5, 0x97, 0x48, 0x42, - 0xe8, 0x98, 0xed, 0x4e, 0xe4, 0x5a, 0x43, 0x2f, 0x91, 0x66, 0xbe, 0xdd, 0x3c, 0x28, 0x19, 0xf6, - 0x90, 0x21, 0x21, 0x2b, 0x92, 0xa1, 0xee, 0x8e, 0x22, 0x5f, 0x43, 0x77, 0xa4, 0x55, 0x88, 0xb8, - 0x23, 0xe6, 0x6b, 0x68, 0xfb, 0xea, 0xbf, 0x31, 0x11, 0x47, 0x72, 0xbd, 0x8e, 0x5c, 0x7b, 0xee, - 0x9a, 0x61, 0x65, 0xc5, 0xf9, 0x7b, 0xce, 0xdb, 0xa4, 0x44, 0x3b, 0x9b, 0xfd, 0x46, 0x53, 0xf1, - 0xbe, 0xd1, 0xb0, 0x54, 0x6b, 0x9b, 0x8e, 0xda, 0x5a, 0xf1, 0xc4, 0xed, 0x5a, 0x62, 0x37, 0x83, - 0xd1, 0x8b, 0x85, 0x91, 0x67, 0x1a, 0xbe, 0x3b, 0xcd, 0xbd, 0x5c, 0xb2, 0x9d, 0xac, 0xb6, 0x73, - 0x15, 0xd7, 0x8c, 0xe5, 0xa4, 0xb4, 0x5a, 0xdd, 0x24, 0x53, 0xdb, 0xab, 0x1b, 0x9a, 0xcd, 0x1a, - 0x57, 0x6a, 0x76, 0x8f, 0x8d, 0x5d, 0x69, 0xc6, 0xf2, 0x92, 0xbc, 0x84, 0x65, 0x11, 0x2e, 0x5e, - 0xbf, 0x65, 0x77, 0x90, 0xef, 0x96, 0x4b, 0xaa, 0x98, 0x61, 0x1a, 0xf6, 0x39, 0x2c, 0xea, 0x8e, - 0x11, 0xd2, 0x33, 0x16, 0x61, 0xf5, 0x26, 0xf5, 0xc7, 0x74, 0x9e, 0x28, 0x6f, 0x75, 0x97, 0xe4, - 0xaa, 0x44, 0x1f, 0x09, 0x27, 0xfc, 0x53, 0x80, 0xaa, 0x15, 0x85, 0x5c, 0xad, 0x51, 0xd6, 0x9a, - 0xeb, 0x37, 0x0d, 0xa9, 0x86, 0x56, 0x24, 0xbf, 0x42, 0x96, 0x2d, 0xf2, 0x6a, 0xbf, 0xe9, 0xea, - 0x88, 0xb5, 0xdf, 0x46, 0x9b, 0x57, 0xfa, 0xe3, 0xbb, 0x16, 0x94, 0x51, 0x5c, 0xb5, 0xd9, 0xf4, - 0xcd, 0x87, 0xaf, 0xe0, 0x04, 0x4f, 0x0b, 0xa3, 0x5d, 0x62, 0xbb, 0x89, 0x4b, 0xe3, 0x69, 0x51, - 0xef, 0x7d, 0x70, 0xaf, 0x22, 0xab, 0x35, 0xb2, 0x3a, 0xca, 0xaa, 0x24, 0x2f, 0xb0, 0xa1, 0xdf, - 0x78, 0xed, 0x27, 0x26, 0xad, 0x7a, 0xeb, 0x43, 0xff, 0xfa, 0xb8, 0xe1, 0x31, 0x27, 0x93, 0x4c, - 0x8e, 0x70, 0x53, 0x09, 0x83, 0x8b, 0x37, 0x7e, 0xcb, 0xe0, 0x56, 0x2b, 0x40, 0xff, 0x6a, 0xc3, - 0x88, 0xa4, 0xbe, 0x81, 0xd4, 0xbb, 0x64, 0x49, 0x87, 0x44, 0xa4, 0x25, 0x6c, 0xa2, 0x1f, 0x5f, - 0x2c, 0x9b, 0x8c, 0xbe, 0xd0, 0x5b, 0x31, 0xb0, 0xf6, 0x4e, 0x5f, 0x8b, 0x81, 0xfa, 0x25, 0x9e, - 0xfc, 0xa1, 0xfd, 0xe0, 0xaf, 0x1e, 0x20, 0xdd, 0x89, 0x2f, 0x86, 0xb5, 0xdd, 0x32, 0xf6, 0x55, - 0xd1, 0xbd, 0x81, 0x9c, 0xaf, 0x92, 0xad, 0x51, 0xce, 0xf2, 0x85, 0x92, 0xfc, 0xdc, 0x81, 0xb5, - 0x86, 0xf7, 0xaf, 0x4a, 0x82, 0xf1, 0xaf, 0x75, 0x95, 0x04, 0x93, 0x1e, 0xd0, 0x5c, 0x94, 0x60, - 0xdb, 0x45, 0x09, 0x82, 0x28, 0xd2, 0x12, 0xc8, 0x5c, 0x8f, 0x7b, 0xe6, 0x9f, 0x3a, 0xb0, 0xd9, - 0xfc, 0xd6, 0x45, 0xde, 0xd4, 0x2d, 0xc2, 0x93, 0x5e, 0xe1, 0xfa, 0xb7, 0x2f, 0x43, 0x93, 0xd2, - 0xbc, 0x89, 0xd2, 0xdc, 0x70, 0xfb, 0x5c, 0x9a, 0x02, 0x71, 0x9b, 0x04, 0x3a, 0xc7, 0x02, 0x81, - 0xfd, 0x9a, 0x44, 0x8c, 0xdc, 0xa2, 0xf9, 0xd1, 0xad, 0x7f, 0x6b, 0x02, 0x86, 0x1d, 0xbe, 0xc8, - 0x86, 0x34, 0x08, 0x3e, 0xc1, 0xe8, 0x67, 0x29, 0xb9, 0x47, 0xab, 0xd7, 0x1a, 0x6b, 0x8f, 0xd6, - 0x1e, 0xa0, 0xac, 0x3d, 0x5a, 0x7f, 0x13, 0xaa, 0xed, 0x51, 0x64, 0x86, 0xef, 0x43, 0xe4, 0x4b, - 0xdc, 0x36, 0xb2, 0x3a, 0xd5, 0x1b, 0xdd, 0xea, 0x65, 0xd3, 0xb6, 0xb1, 0xeb, 0x4f, 0xb5, 0x50, - 0x29, 0x8a, 0x5e, 0x5c, 0x7b, 0x1e, 0x2c, 0x28, 0x74, 0xb2, 0x35, 0x4a, 0x40, 0x51, 0x6e, 0x7c, - 0x60, 0x70, 0xb7, 0x90, 0xe8, 0xaa, 0xdb, 0x31, 0x89, 0x72, 0x9a, 0x47, 0xd0, 0x36, 0x8a, 0xe9, - 0x44, 0x07, 0xd9, 0xfa, 0xdb, 0x41, 0xff, 0x5a, 0xe3, 0x98, 0x1d, 0x4a, 0xdc, 0x2e, 0x67, 0x50, - 0x22, 0x82, 0xe6, 0xf1, 0xfb, 0xb0, 0x64, 0xd5, 0xb3, 0x2b, 0xe5, 0x37, 0x55, 0xdc, 0x2b, 0xe5, - 0x37, 0x16, 0xc1, 0x55, 0xa2, 0xe9, 0xa2, 0xf2, 0x4b, 0x89, 0xa2, 0x79, 0xfd, 0x0c, 0x16, 0x75, - 0x19, 0xb9, 0xd2, 0xff, 0x68, 0x65, 0xf9, 0x32, 0x1e, 0x96, 0x0d, 0xce, 0xf9, 0xe4, 0xa3, 0x6c, - 0x70, 0x24, 0xf5, 0x65, 0x14, 0x49, 0x2b, 0x7d, 0xd5, 0x2b, 0xc5, 0x95, 0xbe, 0x9a, 0xaa, 0xaa, - 0x96, 0xbe, 0x42, 0x44, 0xd0, 0x6b, 0x28, 0xa0, 0x3b, 0x52, 0x9c, 0xac, 0xd2, 0x8a, 0xe6, 0x52, - 0x6c, 0x95, 0x56, 0x8c, 0xa9, 0x6a, 0xda, 0x89, 0x9b, 0xe0, 0x17, 0x24, 0x49, 0xe5, 0x5b, 0x22, - 0xdc, 0x8b, 0xd2, 0x9d, 0xe5, 0xb7, 0x56, 0x8d, 0xd2, 0xf2, 0x5b, 0xbb, 0xce, 0x57, 0x0b, 0xf7, - 0x54, 0xd0, 0x7a, 0x06, 0x0b, 0xaa, 0x66, 0x54, 0x39, 0xed, 0x48, 0xb5, 0xac, 0xdf, 0xab, 0x0f, - 0x48, 0xaa, 0x96, 0xe3, 0x06, 0x51, 0x84, 0x54, 0xa5, 0x21, 0x8c, 0x0a, 0x52, 0x65, 0x88, 0x7a, - 0xf1, 0xa9, 0x32, 0x44, 0x53, 0xc9, 0xc9, 0x32, 0x84, 0x88, 0x5c, 0x9a, 0xc7, 0xdf, 0x3a, 0x70, - 0xeb, 0xd2, 0x02, 0x10, 0x79, 0xff, 0x15, 0x6a, 0x45, 0x42, 0xa0, 0x0f, 0x5e, 0xb9, 0xba, 0xe4, - 0xde, 0x41, 0x31, 0x5d, 0x77, 0x47, 0x1d, 0xa6, 0x38, 0x2d, 0x12, 0xe8, 0xba, 0xd4, 0xc4, 0x85, - 0xfe, 0x6b, 0x47, 0xfc, 0xb9, 0xd6, 0x04, 0xba, 0x64, 0x77, 0x4a, 0x01, 0x94, 0xc0, 0xf7, 0xa6, - 0xc6, 0x97, 0xe2, 0xde, 0x46, 0x71, 0x6f, 0xba, 0xd7, 0x26, 0x88, 0xcb, 0x85, 0xfd, 0x03, 0xb8, - 0xa6, 0x0b, 0x45, 0x16, 0xdd, 0xcf, 0x86, 0x69, 0x54, 0x56, 0xf7, 0xd2, 0x31, 0xd5, 0xa4, 0xca, - 0x71, 0x46, 0xeb, 0x07, 0xf6, 0xf9, 0x78, 0x2e, 0x47, 0x85, 0x18, 0xc7, 0x9c, 0x36, 0xe7, 0x9e, - 0xc3, 0xaa, 0x9a, 0xf7, 0x59, 0x1c, 0xb0, 0x5f, 0x9a, 0xe7, 0x4d, 0xe4, 0xd9, 0x77, 0x37, 0x4c, - 0x9e, 0xc7, 0x71, 0xc0, 0x14, 0xc7, 0xa3, 0x39, 0xfc, 0xd3, 0xcc, 0x0f, 0xff, 0x2f, 0x00, 0x00, - 0xff, 0xff, 0x26, 0x18, 0xb3, 0x56, 0xcd, 0x39, 0x00, 0x00, + // 4379 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x4d, 0x6f, 0x1c, 0x57, + 0x72, 0xe8, 0x21, 0xc5, 0x8f, 0x9a, 0x21, 0x67, 0xf8, 0xf8, 0x35, 0x1a, 0x91, 0xfa, 0x68, 0xad, + 0x64, 0x49, 0xb6, 0x29, 0x5b, 0x16, 0xb2, 0x8e, 0xbd, 0xd9, 0x84, 0xa6, 0x65, 0xae, 0xb2, 0x5e, + 0x8b, 0x69, 0x6a, 0x25, 0xc0, 0x1b, 0xec, 0xa4, 0xd9, 0xfd, 0x38, 0xec, 0xa8, 0xa7, 0xbb, 0xdd, + 0xdd, 0x43, 0x8a, 0x46, 0x80, 0x00, 0x0b, 0x24, 0xc8, 0x29, 0x39, 0x2c, 0x02, 0xe4, 0x90, 0x53, + 0x4e, 0x41, 0x80, 0x5c, 0x82, 0x9c, 0x72, 0x30, 0x72, 0x0d, 0x72, 0xcc, 0x25, 0x3f, 0x20, 0xc8, + 0x2d, 0x09, 0x10, 0x20, 0x97, 0x9c, 0x82, 0x57, 0xef, 0xa3, 0xdf, 0xeb, 0xee, 0x19, 0x0e, 0xd7, + 0x8a, 0x2f, 0xd2, 0x74, 0xbd, 0x7a, 0x55, 0xf5, 0xaa, 0xea, 0xd5, 0xab, 0x57, 0xaf, 0x08, 0x8b, + 0x69, 0xe2, 0xed, 0x24, 0x69, 0x9c, 0xc7, 0x64, 0x6e, 0xe0, 0xe5, 0x69, 0xe2, 0xf5, 0xb6, 0x06, + 0x71, 0x3c, 0x08, 0xe9, 0x43, 0x37, 0x09, 0x1e, 0xba, 0x51, 0x14, 0xe7, 0x6e, 0x1e, 0xc4, 0x51, + 0xc6, 0xb1, 0xec, 0x0e, 0x2c, 0xef, 0xd3, 0xfc, 0x69, 0x74, 0x1c, 0x3b, 0xf4, 0xab, 0x11, 0xcd, + 0x72, 0xfb, 0xef, 0x67, 0xa1, 0xad, 0x40, 0x59, 0x12, 0x47, 0x19, 0x25, 0x1b, 0x30, 0x37, 0x4a, + 0xf2, 0x60, 0x48, 0xbb, 0xd6, 0x4d, 0xeb, 0xde, 0xa2, 0x23, 0xbe, 0xc8, 0x43, 0x58, 0x75, 0x4f, + 0xdd, 0x20, 0x74, 0x8f, 0x42, 0xda, 0xa7, 0xaf, 0xbd, 0x13, 0x37, 0x1a, 0xd0, 0xac, 0xdb, 0xb8, + 0x69, 0xdd, 0x9b, 0x71, 0x88, 0x1a, 0x7a, 0x22, 0x47, 0xc8, 0xdb, 0xb0, 0x42, 0x23, 0x06, 0xf2, + 0x35, 0xf4, 0x19, 0x44, 0xef, 0x88, 0x81, 0x02, 0xf9, 0x31, 0x6c, 0xf8, 0xf4, 0xd8, 0x1d, 0x85, + 0x79, 0xff, 0x38, 0x4e, 0xe9, 0xeb, 0x7e, 0x92, 0xc6, 0xa7, 0x81, 0x4f, 0xd3, 0xee, 0x2c, 0x4a, + 0xb1, 0x26, 0x46, 0x3f, 0x63, 0x83, 0x07, 0x62, 0x8c, 0x3c, 0x82, 0x75, 0x35, 0x2b, 0x70, 0xf3, + 0xbe, 0x37, 0x4a, 0x53, 0x1a, 0x79, 0xe7, 0xdd, 0x2b, 0x38, 0x69, 0x55, 0x4e, 0x0a, 0xdc, 0x7c, + 0x4f, 0x0c, 0x91, 0x97, 0xd0, 0xc9, 0x46, 0x47, 0xd9, 0x79, 0x96, 0xd3, 0x61, 0x3f, 0xcb, 0xdd, + 0x7c, 0x94, 0x75, 0xe7, 0x6e, 0xce, 0xdc, 0x6b, 0x3e, 0x7a, 0x67, 0x87, 0xab, 0x71, 0xa7, 0xa4, + 0x92, 0x9d, 0x43, 0x89, 0x7f, 0x88, 0xe8, 0x4f, 0xa2, 0x3c, 0x3d, 0x77, 0xda, 0x99, 0x09, 0x25, + 0x5f, 0xc0, 0x52, 0x9a, 0x78, 0x7d, 0x1a, 0xf9, 0x49, 0x1c, 0x44, 0x79, 0xd6, 0x9d, 0x47, 0xaa, + 0xf7, 0xc7, 0x51, 0x75, 0x12, 0xef, 0x89, 0xc4, 0xe5, 0x24, 0x5b, 0xa9, 0x06, 0xea, 0x7d, 0x02, + 0x6b, 0x75, 0x8c, 0x49, 0x07, 0x66, 0x5e, 0xd1, 0x73, 0x61, 0x1d, 0xf6, 0x93, 0xac, 0xc1, 0x95, + 0x53, 0x37, 0x1c, 0x51, 0x34, 0xc6, 0x82, 0xc3, 0x3f, 0x3e, 0x6a, 0x7c, 0x68, 0xf5, 0x9e, 0xc3, + 0x4a, 0x85, 0x4d, 0x0d, 0x81, 0xfb, 0x3a, 0x81, 0xe6, 0xa3, 0x55, 0x29, 0xb2, 0x73, 0xb0, 0x27, + 0xe7, 0x6a, 0x54, 0xed, 0x5b, 0x70, 0x63, 0x9f, 0xe6, 0x7b, 0xf1, 0x70, 0x38, 0x8a, 0x02, 0x0f, + 0x7d, 0xcc, 0xa1, 0xa1, 0x7b, 0x4e, 0xd3, 0x4c, 0x7a, 0xd6, 0x17, 0xb0, 0x56, 0x37, 0x4e, 0xba, + 0x30, 0x2f, 0x6c, 0x8f, 0xfc, 0x17, 0x1c, 0xf9, 0x49, 0xb6, 0x60, 0xd1, 0x8b, 0xa3, 0x88, 0x7a, + 0x39, 0xf5, 0xc5, 0x42, 0x0a, 0x80, 0xfd, 0xc7, 0x0d, 0xb8, 0x39, 0x9e, 0xa7, 0x70, 0xdd, 0xaf, + 0x61, 0xc3, 0xd3, 0x11, 0xfa, 0xa9, 0xc0, 0xe8, 0x5a, 0x68, 0x8a, 0x3d, 0xcd, 0x14, 0x13, 0x29, + 0xed, 0xd4, 0x8e, 0x72, 0x23, 0xad, 0x7b, 0x75, 0x63, 0xbd, 0x63, 0xe8, 0x8d, 0x9f, 0x54, 0xa3, + 0xf2, 0x47, 0xa6, 0xca, 0xb7, 0xa4, 0x68, 0x75, 0x44, 0x74, 0xdd, 0x7f, 0x1f, 0x36, 0xf7, 0x69, + 0x44, 0xd3, 0xc0, 0x53, 0xce, 0x21, 0x74, 0xce, 0x34, 0xa8, 0x7c, 0x52, 0xb0, 0x2a, 0x00, 0x76, + 0x0f, 0xba, 0xd5, 0x89, 0x7c, 0xb9, 0xf6, 0x06, 0xac, 0xed, 0xd3, 0x5c, 0xc1, 0x95, 0x15, 0xbf, + 0xb1, 0x60, 0x1d, 0x07, 0xb2, 0xa3, 0xec, 0x9c, 0x0f, 0x08, 0x55, 0xff, 0x1e, 0xac, 0x28, 0xd2, + 0x99, 0xdc, 0x46, 0x5c, 0xcb, 0x1f, 0x68, 0x5a, 0xae, 0xce, 0x2c, 0x36, 0x53, 0xa6, 0xef, 0xa6, + 0x62, 0x4f, 0x0a, 0x70, 0x6f, 0x0f, 0xd6, 0x6b, 0x51, 0x2f, 0xe3, 0xff, 0x76, 0x17, 0x36, 0xf6, + 0x69, 0xae, 0xb9, 0xb1, 0xe6, 0xa0, 0x4d, 0x0d, 0xcc, 0xfc, 0x32, 0xcb, 0xdd, 0x34, 0x2f, 0xfc, + 0x52, 0x7c, 0x92, 0x3b, 0xb0, 0x1c, 0x06, 0x59, 0x4e, 0xa3, 0xbe, 0xeb, 0xfb, 0x29, 0xcd, 0x78, + 0xc8, 0x5b, 0x74, 0x96, 0x38, 0x74, 0x97, 0x03, 0xed, 0x7f, 0xb0, 0x98, 0x61, 0x4a, 0xac, 0x84, + 0xb2, 0x3e, 0x87, 0xc5, 0x22, 0x2a, 0x70, 0x25, 0xed, 0x68, 0x4a, 0xaa, 0x9b, 0xb3, 0x53, 0x0a, + 0x0d, 0x05, 0x81, 0xde, 0xef, 0xc0, 0xf2, 0x9b, 0xde, 0xd0, 0x1f, 0x42, 0x4f, 0xf8, 0x86, 0x8c, + 0xc8, 0x5f, 0xb8, 0x43, 0x2a, 0xfd, 0xaa, 0x07, 0x0b, 0x32, 0x80, 0x0b, 0x1e, 0xea, 0xdb, 0xde, + 0x86, 0x6b, 0xb5, 0x33, 0x85, 0x63, 0x3d, 0x84, 0xd5, 0x7d, 0x9a, 0xab, 0x30, 0x2f, 0x29, 0x8e, + 0x8d, 0x02, 0xf6, 0x63, 0xf4, 0x44, 0x6d, 0x82, 0x50, 0xe1, 0x16, 0x2c, 0x16, 0x87, 0x88, 0xf0, + 0x6d, 0x05, 0xb0, 0x1f, 0xa1, 0x9b, 0xca, 0x59, 0xcf, 0x9e, 0x1f, 0x38, 0x94, 0x4f, 0xbb, 0x0a, + 0x0b, 0x71, 0x9e, 0xf4, 0xbd, 0xd8, 0x97, 0xa2, 0xcf, 0xc7, 0x79, 0xb2, 0x17, 0xfb, 0x54, 0xb8, + 0x86, 0x36, 0x47, 0xb9, 0xc6, 0x5f, 0x71, 0x53, 0x9a, 0x43, 0x42, 0x8e, 0xdf, 0x86, 0x45, 0x49, + 0x50, 0x9a, 0xf2, 0x5d, 0xcd, 0x94, 0x75, 0x73, 0x76, 0x9e, 0x71, 0x8e, 0xc2, 0x92, 0x0b, 0x42, + 0x80, 0xac, 0xf7, 0x31, 0x2c, 0x19, 0x43, 0x17, 0x79, 0xf6, 0xa2, 0x6e, 0xb2, 0xc7, 0xb0, 0xf1, + 0x69, 0x90, 0xe9, 0x27, 0xee, 0x34, 0xe6, 0xfa, 0x66, 0xc6, 0x58, 0x9a, 0x71, 0xf0, 0x13, 0x98, + 0x8d, 0x5c, 0x75, 0xec, 0xe3, 0x6f, 0xdd, 0x50, 0x0d, 0x33, 0x5c, 0x77, 0x61, 0xfe, 0x94, 0xa6, + 0x47, 0x71, 0x46, 0xf1, 0x4c, 0x5f, 0x70, 0xe4, 0x27, 0xb9, 0x0d, 0x4b, 0xa3, 0x2c, 0x88, 0x06, + 0xfd, 0xcc, 0x8d, 0xfc, 0xa3, 0xf8, 0x35, 0x9e, 0xe0, 0x0b, 0x4e, 0x0b, 0x81, 0x87, 0x1c, 0x46, + 0x6e, 0x41, 0xeb, 0x24, 0xcf, 0x93, 0x3e, 0x4b, 0x2d, 0xe2, 0x51, 0x2e, 0x0e, 0xec, 0x26, 0x83, + 0x3d, 0xe7, 0x20, 0xb6, 0xf1, 0x10, 0x65, 0x94, 0xd1, 0xd4, 0x1d, 0xd0, 0x28, 0xef, 0xce, 0xf1, + 0x8d, 0xc7, 0xa0, 0x3f, 0x95, 0x40, 0xb2, 0x0d, 0x80, 0x68, 0x49, 0x1a, 0xbf, 0x3e, 0xef, 0xce, + 0x73, 0xd7, 0x60, 0x90, 0x03, 0x06, 0x20, 0x6f, 0x41, 0xfb, 0xc8, 0xcd, 0xa8, 0x4c, 0x0d, 0x02, + 0x9a, 0x75, 0x17, 0x10, 0x67, 0x99, 0x81, 0xf7, 0x14, 0x94, 0xdc, 0x67, 0x79, 0x41, 0x92, 0xc4, + 0x6c, 0xd3, 0xf7, 0xdd, 0x2c, 0xa3, 0x79, 0xd6, 0x5d, 0x44, 0xcc, 0xb6, 0x82, 0xef, 0x22, 0x98, + 0xad, 0x50, 0x66, 0x36, 0x89, 0x1b, 0xa4, 0x59, 0x17, 0x10, 0xaf, 0x25, 0x80, 0x07, 0x0c, 0xc6, + 0x18, 0x17, 0xf9, 0x12, 0x47, 0x6b, 0x72, 0xc6, 0x0a, 0xcc, 0x11, 0xdf, 0x86, 0x15, 0x77, 0x94, + 0x9f, 0xd0, 0x28, 0x67, 0x51, 0x9f, 0x31, 0x4f, 0x82, 0x6e, 0x0b, 0x75, 0xd6, 0x31, 0x06, 0x76, + 0x93, 0xc0, 0x3e, 0x83, 0xce, 0x3e, 0xcd, 0x9f, 0x07, 0xde, 0x2b, 0x9a, 0x4e, 0x61, 0x70, 0x72, + 0x0f, 0x66, 0x19, 0x6f, 0x11, 0x07, 0xd6, 0xd4, 0x29, 0x23, 0xb2, 0x21, 0x26, 0x81, 0x83, 0x18, + 0x4c, 0x8f, 0xb8, 0xea, 0x7e, 0x7e, 0x9e, 0x70, 0x9b, 0x2e, 0x3a, 0x8b, 0x08, 0x79, 0x7e, 0x9e, + 0x50, 0xfb, 0x05, 0xb4, 0xf4, 0x49, 0x6c, 0x43, 0xfa, 0x34, 0x0c, 0x86, 0x41, 0x4e, 0x53, 0xb9, + 0x21, 0x15, 0x80, 0xf9, 0x12, 0x53, 0xaf, 0x70, 0x5b, 0xfc, 0xcd, 0x7c, 0xf9, 0xab, 0x51, 0x9c, + 0x4b, 0xda, 0xfc, 0xc3, 0xfe, 0xf3, 0x06, 0x2c, 0xcb, 0xe5, 0x08, 0x47, 0x94, 0x32, 0x5b, 0x17, + 0xca, 0x7c, 0x0b, 0x5a, 0xa1, 0x9b, 0xe5, 0xfd, 0x51, 0xe2, 0xbb, 0x32, 0x6d, 0x98, 0x71, 0x9a, + 0x0c, 0xf6, 0x53, 0x0e, 0x62, 0xb6, 0x92, 0x59, 0x21, 0x5a, 0x41, 0x70, 0x6f, 0x79, 0xfa, 0x62, + 0x08, 0xcc, 0xb2, 0x39, 0xe8, 0xa9, 0x96, 0x83, 0xbf, 0x19, 0xec, 0x24, 0x18, 0x9c, 0xa0, 0x67, + 0x5a, 0x0e, 0xfe, 0x66, 0x1b, 0x34, 0x8c, 0xcf, 0xd0, 0x0f, 0x2d, 0x87, 0xfd, 0x64, 0x90, 0xa3, + 0xc0, 0x47, 0xb7, 0xb3, 0x1c, 0xf6, 0x93, 0x41, 0xdc, 0xec, 0x15, 0x3a, 0x99, 0xe5, 0xb0, 0x9f, + 0x2c, 0xa3, 0x3e, 0x8d, 0xc3, 0xd1, 0x90, 0xa2, 0x3f, 0x59, 0x8e, 0xf8, 0x22, 0xd7, 0x60, 0x31, + 0x49, 0x03, 0x8f, 0xf6, 0xdd, 0xfc, 0x04, 0x5d, 0xc8, 0x72, 0x16, 0x10, 0xb0, 0x9b, 0x9f, 0xd8, + 0xab, 0xb0, 0xa2, 0x0c, 0xad, 0x22, 0xd3, 0x4b, 0x98, 0x17, 0x90, 0x89, 0x46, 0x7f, 0x0f, 0xe6, + 0x73, 0x8e, 0xd6, 0x6d, 0x60, 0x88, 0xda, 0x90, 0x3a, 0x34, 0x35, 0xed, 0x48, 0x34, 0xfb, 0x37, + 0x81, 0xe8, 0xdc, 0x84, 0x21, 0xee, 0x17, 0x74, 0x78, 0xa8, 0x6b, 0x9b, 0x74, 0xb2, 0x82, 0xc0, + 0xd7, 0x18, 0xe8, 0x9f, 0xa5, 0x3e, 0x0b, 0x02, 0xf1, 0xab, 0xef, 0xd4, 0x35, 0x7f, 0x02, 0x4b, + 0x8a, 0xf1, 0xd3, 0x9c, 0x0e, 0x99, 0xc2, 0xdd, 0x61, 0x3c, 0x8a, 0x72, 0xe4, 0x69, 0x39, 0xe2, + 0x8b, 0x79, 0x20, 0xea, 0x17, 0x59, 0x5a, 0x0e, 0xff, 0x20, 0xcb, 0xd0, 0x08, 0x7c, 0x71, 0x31, + 0x69, 0x04, 0xbe, 0xfd, 0xbf, 0x16, 0xac, 0x68, 0x0b, 0xb9, 0xb4, 0x53, 0x56, 0x3c, 0xae, 0x51, + 0xe3, 0x71, 0xf7, 0x61, 0xf6, 0x28, 0xf0, 0xd9, 0x7d, 0x88, 0xe9, 0x75, 0x5d, 0x92, 0x33, 0xd6, + 0xe1, 0x20, 0x0a, 0x43, 0x75, 0xb3, 0x57, 0x59, 0x77, 0x76, 0x22, 0x2a, 0x43, 0xa9, 0xec, 0x87, + 0x2b, 0xd5, 0xfd, 0x60, 0xea, 0x72, 0xae, 0xac, 0x4b, 0x9e, 0x09, 0x2a, 0xda, 0xca, 0xf3, 0x3c, + 0x80, 0x02, 0x38, 0xd1, 0xac, 0xbf, 0x0e, 0x10, 0x2b, 0x4c, 0xe1, 0x7f, 0x57, 0x2b, 0x42, 0x2b, + 0x17, 0xd4, 0x90, 0xed, 0x1f, 0xe3, 0x31, 0xae, 0x33, 0x17, 0xca, 0x7f, 0x64, 0xd0, 0xe4, 0xbe, + 0x48, 0x2a, 0x34, 0x33, 0x83, 0xd8, 0x07, 0x48, 0x6c, 0xd7, 0xf3, 0x98, 0xe9, 0xb5, 0x4b, 0xef, + 0xc4, 0xf3, 0xf1, 0x05, 0xcc, 0x8b, 0x19, 0xc2, 0x2d, 0x38, 0x42, 0x23, 0xf0, 0xc9, 0xc7, 0x00, + 0xda, 0x19, 0xc2, 0xd7, 0x75, 0x4d, 0xca, 0x20, 0x26, 0x49, 0x6f, 0x40, 0x76, 0x1a, 0xba, 0x7d, + 0x0c, 0xab, 0x35, 0x28, 0x4c, 0x14, 0x75, 0x65, 0x15, 0xa2, 0xc8, 0x6f, 0x72, 0x03, 0x9a, 0x79, + 0x9c, 0xbb, 0x61, 0xbf, 0x48, 0x00, 0x2c, 0x07, 0x10, 0xf4, 0x82, 0x41, 0x30, 0x40, 0xc5, 0x21, + 0xf7, 0x5c, 0x16, 0xa0, 0xe2, 0xd0, 0xb7, 0x5d, 0x4c, 0x6a, 0x8c, 0x45, 0x0b, 0x15, 0x4e, 0x32, + 0xd9, 0xdb, 0xb0, 0xe0, 0xf2, 0x29, 0x72, 0x61, 0xed, 0xd2, 0xc2, 0x1c, 0x85, 0x60, 0x13, 0x3c, + 0x81, 0xf6, 0xe2, 0xe8, 0x38, 0x18, 0x48, 0xef, 0x78, 0x0b, 0x83, 0x95, 0x84, 0x15, 0xf9, 0x84, + 0xef, 0xe6, 0x2e, 0x72, 0x6b, 0x39, 0xf8, 0xdb, 0xfe, 0x23, 0x0b, 0x3a, 0x07, 0x71, 0x9a, 0x1f, + 0xc7, 0x61, 0x10, 0x8b, 0xd4, 0x99, 0xa5, 0x12, 0x32, 0xb5, 0x16, 0x39, 0x9a, 0xf8, 0x64, 0x11, + 0xd2, 0x8b, 0x83, 0x88, 0xfb, 0x6a, 0x43, 0x28, 0x28, 0x0e, 0x22, 0xe6, 0xaa, 0xe4, 0x26, 0x34, + 0x7d, 0x9a, 0x79, 0x69, 0x90, 0xb0, 0xab, 0x92, 0x08, 0x0b, 0x3a, 0x88, 0x11, 0x3e, 0x72, 0x43, + 0x37, 0xf2, 0xa8, 0x88, 0xec, 0xf2, 0xd3, 0x5e, 0xc7, 0x70, 0xa5, 0x24, 0xd1, 0x6e, 0xad, 0x26, + 0x58, 0x2c, 0xe5, 0xd7, 0x60, 0x31, 0x91, 0x40, 0xe1, 0x7e, 0x5d, 0xa9, 0xa1, 0xf2, 0x72, 0x9c, + 0x02, 0xd5, 0xde, 0x62, 0x79, 0x75, 0x41, 0xef, 0x70, 0x34, 0x1c, 0xba, 0xe9, 0xb9, 0xe4, 0x16, + 0xc1, 0xec, 0x5e, 0x1c, 0x44, 0x4c, 0x51, 0x6c, 0x51, 0x32, 0xf1, 0x62, 0xbf, 0x75, 0xd1, 0x1b, + 0x86, 0xe8, 0xba, 0xb6, 0x66, 0x4c, 0x6d, 0x5d, 0x07, 0x48, 0x68, 0xea, 0xd1, 0x28, 0x77, 0x07, + 0x72, 0xc5, 0x1a, 0xc4, 0x3e, 0x01, 0xf2, 0xec, 0xf8, 0x38, 0x0c, 0x22, 0xca, 0xd8, 0x0a, 0x61, + 0x26, 0x68, 0x7f, 0xbc, 0x0c, 0x26, 0xa7, 0x99, 0x0a, 0xa7, 0x9f, 0xc0, 0xca, 0xb3, 0xa8, 0x86, + 0x91, 0x24, 0x67, 0x4d, 0x22, 0xd7, 0xa8, 0x90, 0xfb, 0x11, 0xb4, 0x34, 0xc1, 0x33, 0xf2, 0x21, + 0x2c, 0x0a, 0x19, 0x55, 0x12, 0xde, 0x53, 0xd1, 0xa0, 0xb2, 0x42, 0xa7, 0x40, 0xb6, 0xff, 0xc2, + 0x82, 0x66, 0x21, 0x59, 0x46, 0x1e, 0xc3, 0x15, 0xa6, 0x6e, 0x49, 0xe5, 0xba, 0xa2, 0x52, 0xe0, + 0xec, 0xe0, 0xbf, 0x3c, 0x77, 0xe7, 0xc8, 0xbd, 0x43, 0x80, 0x02, 0x58, 0x93, 0xb5, 0x3f, 0x34, + 0x6f, 0x5f, 0x57, 0xab, 0x54, 0xa5, 0x68, 0x5a, 0x42, 0xff, 0xcf, 0xb3, 0xec, 0x2a, 0x55, 0xe3, + 0x2c, 0xc2, 0x07, 0xdf, 0x85, 0x26, 0xdf, 0x0b, 0x2c, 0x02, 0x48, 0x81, 0x5b, 0x45, 0xd9, 0x20, + 0x88, 0x1c, 0xc0, 0xbd, 0x81, 0xe3, 0xe4, 0x7d, 0x58, 0x42, 0x61, 0xfb, 0x31, 0x57, 0x88, 0xd8, + 0xd8, 0xe6, 0x84, 0x16, 0xa2, 0x08, 0x95, 0x91, 0x04, 0xd6, 0x8d, 0x29, 0xfd, 0x8c, 0x8b, 0x20, + 0x0e, 0xa9, 0x1f, 0x68, 0xf7, 0x9c, 0x71, 0x52, 0x72, 0x65, 0x09, 0x82, 0x62, 0x8c, 0xab, 0x6e, + 0xd5, 0xab, 0x8e, 0x90, 0x87, 0xd0, 0x12, 0x1c, 0x51, 0x33, 0xe2, 0x88, 0x33, 0x65, 0x6c, 0xf2, + 0x89, 0x88, 0x40, 0x86, 0xb0, 0xa6, 0x4f, 0x50, 0x12, 0x5e, 0xc1, 0x89, 0x1f, 0x4f, 0x2f, 0x61, + 0x54, 0x11, 0x90, 0x78, 0x95, 0x81, 0xde, 0xef, 0x42, 0x77, 0xdc, 0x82, 0x6a, 0xcc, 0xfe, 0xc0, + 0x34, 0xfb, 0x5a, 0x8d, 0x4b, 0x66, 0x7a, 0x71, 0xee, 0x4b, 0xd8, 0x1c, 0x23, 0xcc, 0x25, 0x6e, + 0xf4, 0x9a, 0xa7, 0xea, 0xde, 0xf4, 0x67, 0x16, 0xf4, 0x76, 0x7d, 0xbf, 0x12, 0x9c, 0x8a, 0x0b, + 0xf8, 0x77, 0x1d, 0x72, 0xb7, 0xe1, 0x5a, 0xad, 0x40, 0xa2, 0x52, 0xf0, 0x1a, 0xb6, 0x1d, 0x3a, + 0x8c, 0x4f, 0xe9, 0x77, 0x2d, 0xb2, 0x7d, 0x13, 0xae, 0x8f, 0xe3, 0x2c, 0x64, 0xc3, 0xd2, 0x99, + 0x59, 0x7a, 0x56, 0x89, 0xd1, 0x7f, 0x58, 0xb0, 0x64, 0x16, 0xa5, 0xdf, 0xd4, 0x3d, 0xfa, 0x1d, + 0x20, 0x29, 0xcd, 0xf2, 0x7e, 0x1a, 0x87, 0x21, 0xbb, 0x4e, 0xfb, 0x34, 0x74, 0xcf, 0x45, 0x39, + 0xbc, 0xc3, 0x46, 0x1c, 0x3e, 0xf0, 0x29, 0x83, 0x93, 0x4d, 0x98, 0x77, 0x93, 0xa0, 0xcf, 0xbc, + 0x86, 0xdf, 0xa5, 0xe7, 0xdc, 0x24, 0xf8, 0x31, 0x3d, 0x27, 0x36, 0x2c, 0x89, 0x81, 0x7e, 0x48, + 0x4f, 0x69, 0x88, 0x39, 0xdf, 0x8c, 0xd3, 0xe4, 0xc3, 0x9f, 0x33, 0x10, 0xbb, 0xfb, 0x26, 0x69, + 0xc0, 0xdc, 0xaf, 0xa8, 0xbb, 0xcf, 0xa3, 0x34, 0x6d, 0x01, 0x97, 0xab, 0xb3, 0x7f, 0x06, 0x57, + 0x6b, 0x74, 0x21, 0x62, 0xd4, 0x0f, 0xa1, 0x6d, 0x56, 0xef, 0x65, 0x9c, 0x52, 0x59, 0xab, 0x31, + 0xd1, 0x59, 0x3e, 0x36, 0xe8, 0x88, 0xec, 0x13, 0x71, 0x1c, 0x37, 0x57, 0xf5, 0x22, 0xfb, 0x2b, + 0x58, 0x2b, 0x80, 0x7b, 0x71, 0x74, 0x4a, 0xd3, 0x8c, 0x79, 0x1b, 0x81, 0xd9, 0xe3, 0x34, 0x96, + 0xc5, 0x4e, 0xfc, 0xcd, 0xf2, 0xb6, 0x3c, 0x16, 0x6e, 0xd0, 0xc8, 0x63, 0x86, 0x93, 0xba, 0xb9, + 0x3c, 0xa5, 0xf0, 0x37, 0xcb, 0x93, 0x03, 0x24, 0x42, 0xfb, 0x38, 0xc6, 0x5d, 0xb5, 0x29, 0x60, + 0x8c, 0x8b, 0xfd, 0x02, 0xd3, 0x47, 0x5d, 0x14, 0xb1, 0xc6, 0xdf, 0x80, 0x26, 0x5f, 0x23, 0x9b, + 0x29, 0xd7, 0xb7, 0x65, 0xac, 0xaf, 0x24, 0xa6, 0x03, 0xc7, 0x0a, 0x6a, 0xff, 0x57, 0x03, 0x5a, + 0x98, 0xb1, 0x7e, 0x4a, 0x73, 0x37, 0x08, 0x27, 0xe7, 0xd2, 0x3c, 0x07, 0x6d, 0xa8, 0x1c, 0xf4, + 0x36, 0x2c, 0xe9, 0xc5, 0x8c, 0x73, 0x79, 0x99, 0xd5, 0x4a, 0x19, 0xe7, 0xe4, 0x0e, 0x2c, 0xe3, + 0xd5, 0xba, 0xc0, 0xe2, 0x3e, 0xb3, 0x84, 0x50, 0x85, 0x66, 0x5e, 0x04, 0xae, 0x94, 0x2e, 0x02, + 0x6c, 0x18, 0x93, 0xe9, 0x7e, 0x16, 0xf8, 0xea, 0x9e, 0x80, 0x90, 0xc3, 0xc0, 0xd7, 0x86, 0x71, + 0xf6, 0xbc, 0x36, 0x8c, 0xb3, 0xd9, 0x1d, 0x28, 0xa5, 0xbc, 0x08, 0x8f, 0x6f, 0x49, 0x0b, 0xe8, + 0x74, 0x2d, 0x09, 0x7c, 0x1e, 0x0c, 0xf1, 0xa5, 0x49, 0x14, 0x8e, 0x79, 0x9d, 0x45, 0x7c, 0x15, + 0xd7, 0x34, 0xd0, 0xaf, 0x69, 0xc5, 0xa5, 0xae, 0x69, 0x5c, 0xea, 0x6e, 0x40, 0x33, 0x4e, 0x68, + 0xd4, 0x17, 0x57, 0xec, 0x16, 0xcf, 0x1e, 0x18, 0xe8, 0x05, 0x42, 0x44, 0xc9, 0x04, 0x75, 0x9e, + 0x4d, 0x73, 0x2f, 0x35, 0x15, 0xd3, 0x28, 0x2b, 0x46, 0x5e, 0x04, 0x67, 0x2e, 0xba, 0x08, 0xda, + 0xbb, 0x98, 0x15, 0x4b, 0xc6, 0xc2, 0x7d, 0xde, 0x81, 0x39, 0x54, 0x93, 0xf4, 0x9c, 0x35, 0xe3, + 0x1a, 0x23, 0x9c, 0xc2, 0x11, 0x38, 0xf6, 0x8f, 0xf0, 0x7d, 0x0e, 0x87, 0xa6, 0x11, 0xfd, 0x2a, + 0x2c, 0x70, 0xab, 0x28, 0xaf, 0x99, 0xc7, 0xef, 0xa7, 0xbe, 0xfd, 0xaf, 0x16, 0x90, 0xc3, 0xd1, + 0xd1, 0x30, 0x98, 0x9e, 0xda, 0xf4, 0x17, 0x74, 0x02, 0xb3, 0xe8, 0x26, 0xdc, 0x1d, 0xf1, 0x77, + 0xc9, 0x43, 0x66, 0xcb, 0x1e, 0x52, 0x98, 0xf3, 0x4a, 0xfd, 0x1d, 0x7d, 0x4e, 0x37, 0x3e, 0x0b, + 0xf1, 0x61, 0x40, 0xa3, 0xbc, 0x2f, 0x8a, 0x2d, 0x2c, 0xc4, 0x23, 0xe0, 0xa9, 0x6f, 0x1f, 0xc2, + 0xaa, 0xb1, 0x32, 0xa1, 0xe9, 0x5b, 0xd0, 0xe2, 0x02, 0x24, 0xa1, 0xeb, 0xa9, 0x4a, 0x73, 0x13, + 0x61, 0x07, 0x08, 0x9a, 0xa4, 0xaf, 0x3f, 0xb1, 0x60, 0xed, 0x30, 0x18, 0x8e, 0x42, 0x37, 0xa7, + 0xff, 0x0f, 0x1a, 0x2b, 0x96, 0x3f, 0x63, 0x2c, 0x5f, 0x6a, 0x72, 0xb6, 0xd0, 0xa4, 0xfd, 0xdf, + 0x16, 0xac, 0x97, 0x44, 0x51, 0x39, 0xa1, 0xe9, 0x4c, 0x63, 0x8a, 0x03, 0x02, 0x49, 0x63, 0xda, + 0x30, 0x98, 0xde, 0x86, 0xa5, 0x61, 0x10, 0x05, 0xc3, 0xd1, 0xb0, 0xcf, 0x75, 0xcf, 0x65, 0x6a, + 0x09, 0xe0, 0x01, 0x9a, 0x80, 0x21, 0xb9, 0xaf, 0x35, 0xa4, 0x59, 0x81, 0xc4, 0x81, 0x1c, 0xe9, + 0x3d, 0x58, 0x2b, 0xf2, 0xf6, 0xfe, 0xc0, 0x0d, 0xa2, 0x7e, 0x18, 0x67, 0x99, 0xb0, 0x31, 0x29, + 0xc6, 0xf6, 0xdd, 0x20, 0xfa, 0x3c, 0xce, 0x32, 0x2d, 0x08, 0xcc, 0xe9, 0x41, 0x80, 0x25, 0x30, + 0x9d, 0x97, 0x27, 0x6e, 0x48, 0x3f, 0x89, 0x87, 0x47, 0x6f, 0x56, 0xf7, 0xb7, 0xa0, 0xc5, 0xeb, + 0x6e, 0xb9, 0x9b, 0x0e, 0xa8, 0xb4, 0x40, 0x13, 0x61, 0xcf, 0x11, 0x54, 0x6b, 0x86, 0xff, 0xb4, + 0x80, 0xec, 0xb1, 0x54, 0x26, 0x9c, 0xda, 0x1f, 0x58, 0x28, 0xe1, 0xf7, 0xe6, 0xc2, 0xc3, 0x16, + 0x05, 0xe4, 0xa9, 0xe9, 0x7e, 0x33, 0x86, 0xfb, 0xa9, 0xd5, 0xcc, 0x5e, 0xb2, 0x38, 0x56, 0x89, + 0xe3, 0x77, 0x60, 0xf9, 0xcc, 0x0d, 0x43, 0x9a, 0xab, 0xe7, 0x2b, 0x51, 0x45, 0xe7, 0x50, 0x79, + 0x07, 0x97, 0x0b, 0x9e, 0xd7, 0x16, 0xbc, 0x0e, 0xab, 0xc6, 0x7a, 0x45, 0x36, 0xf4, 0x18, 0x36, + 0x38, 0x78, 0x37, 0x0c, 0xa7, 0x8e, 0xaa, 0xf6, 0x5f, 0x36, 0x60, 0xb3, 0x32, 0x4d, 0xa5, 0x0d, + 0xa6, 0x1b, 0xdf, 0x55, 0xcb, 0xad, 0x9f, 0xb0, 0x23, 0x3e, 0xc5, 0xac, 0xde, 0x3f, 0x5a, 0x30, + 0xc7, 0x41, 0x13, 0xad, 0xf1, 0xa5, 0x0c, 0x08, 0xc2, 0xe1, 0xf8, 0x8d, 0xe8, 0xfb, 0xd3, 0x31, + 0xe3, 0xff, 0xe9, 0x4f, 0x96, 0x3c, 0x92, 0x88, 0xd7, 0xca, 0x1f, 0x42, 0xa7, 0x8c, 0x70, 0xa9, + 0xe7, 0x1c, 0x5e, 0x55, 0x79, 0x72, 0x4a, 0xb5, 0x27, 0xca, 0x6f, 0x2c, 0x68, 0xef, 0xc5, 0x91, + 0x1f, 0xb0, 0x13, 0xf3, 0xc0, 0x4d, 0xdd, 0x61, 0x26, 0x5e, 0xc9, 0x39, 0x48, 0x96, 0xdd, 0x15, + 0x60, 0x4c, 0x81, 0x73, 0x1b, 0xc0, 0x3b, 0xa1, 0xde, 0xab, 0xbe, 0xa8, 0x38, 0xf2, 0xa7, 0x75, + 0x06, 0xf9, 0x24, 0xf0, 0x33, 0xf2, 0x2e, 0xac, 0x16, 0xc3, 0x7d, 0x37, 0xf2, 0xfb, 0xa2, 0xdc, + 0x88, 0x2f, 0x10, 0x0a, 0x6f, 0x37, 0xf2, 0x77, 0xb3, 0x57, 0xf8, 0x4e, 0xa2, 0xaa, 0x6c, 0x7d, + 0x23, 0x84, 0xb7, 0x15, 0x7c, 0x17, 0xc1, 0xf6, 0xff, 0x58, 0x78, 0x02, 0xca, 0x55, 0x09, 0x6b, + 0x17, 0x85, 0x35, 0xac, 0xb7, 0x1a, 0x26, 0x6b, 0x94, 0x4c, 0x46, 0x60, 0x36, 0xc8, 0xe9, 0x50, + 0x1e, 0x2c, 0xec, 0x37, 0xf9, 0x04, 0x3a, 0x6a, 0xc5, 0xfd, 0x04, 0xd5, 0x22, 0xb6, 0xc9, 0x66, + 0x71, 0x71, 0x34, 0xb4, 0xe6, 0xb4, 0xbd, 0x92, 0x1a, 0xe5, 0xf6, 0xba, 0x32, 0x55, 0xa0, 0xf6, + 0x50, 0xdb, 0x22, 0x3e, 0xf1, 0x2f, 0x2e, 0x35, 0xf5, 0x46, 0x39, 0xf5, 0x45, 0xaa, 0xac, 0xbe, + 0xed, 0x7f, 0xb7, 0xa0, 0xbd, 0xeb, 0xfb, 0xb8, 0xee, 0x69, 0xc2, 0x84, 0x5c, 0x65, 0xe3, 0x82, + 0x55, 0xce, 0xfc, 0x8a, 0xab, 0xfc, 0xd6, 0x41, 0x64, 0x8c, 0x12, 0x6c, 0x1b, 0x3a, 0xc5, 0x3a, + 0xeb, 0xcd, 0x6b, 0x7f, 0x0f, 0x08, 0xbf, 0x5e, 0x19, 0xea, 0x28, 0x63, 0xad, 0xc3, 0xaa, 0x81, + 0x25, 0x62, 0xcd, 0x67, 0x70, 0x6f, 0x9f, 0xe6, 0x7b, 0xe9, 0x79, 0x92, 0xc7, 0x32, 0x9d, 0xfd, + 0x94, 0x26, 0x71, 0x16, 0xc8, 0xc8, 0x45, 0xa7, 0x8a, 0x3e, 0xff, 0x64, 0xc1, 0xfd, 0x29, 0x08, + 0x89, 0x25, 0xfc, 0xbc, 0x5a, 0x5f, 0xfa, 0x2d, 0xbd, 0x75, 0x64, 0x2a, 0x2a, 0x3b, 0x0a, 0x22, + 0x5e, 0xf0, 0x15, 0xc9, 0xde, 0x0f, 0x60, 0xd9, 0x1c, 0xbc, 0x54, 0xa8, 0x08, 0xe1, 0xee, 0x05, + 0x42, 0x4c, 0xe3, 0x73, 0x77, 0x61, 0xd9, 0x33, 0x48, 0x08, 0x46, 0x25, 0xa8, 0xbd, 0x07, 0x6f, + 0x5d, 0xc8, 0x4d, 0xa8, 0x6d, 0xec, 0x0d, 0xdd, 0xfe, 0xdb, 0x59, 0xd8, 0x7c, 0x19, 0xe4, 0x27, + 0x7e, 0xea, 0x9e, 0x49, 0xef, 0x9b, 0x46, 0xc8, 0xd2, 0xe5, 0xbd, 0x51, 0xad, 0x37, 0x3c, 0x80, + 0x95, 0x38, 0xa2, 0x78, 0xc7, 0xe8, 0x27, 0x6e, 0x96, 0x9d, 0xc5, 0xa9, 0x3c, 0x4b, 0xdb, 0x71, + 0x44, 0xd9, 0x3d, 0xe3, 0x40, 0x80, 0x4b, 0xa7, 0xf1, 0x6c, 0xf9, 0x34, 0xee, 0xc0, 0x4c, 0x12, + 0x44, 0xe2, 0xcd, 0x84, 0xfd, 0x64, 0x67, 0x67, 0x9e, 0xba, 0xbe, 0x46, 0x59, 0x9c, 0x9d, 0x08, + 0x55, 0x74, 0xf5, 0x2a, 0xfe, 0x7c, 0xa9, 0x8a, 0xaf, 0xe9, 0x64, 0xc1, 0xac, 0x5a, 0xdc, 0x80, + 0xa6, 0xf8, 0xd9, 0xcf, 0xdd, 0x81, 0xb8, 0x02, 0x81, 0x00, 0x3d, 0x77, 0x07, 0x5a, 0xb6, 0x06, + 0x46, 0xb6, 0xb6, 0x0d, 0x70, 0x4c, 0x69, 0xdf, 0xb8, 0x0c, 0x2d, 0x1e, 0x53, 0xca, 0x83, 0x2e, + 0x4b, 0x95, 0x8f, 0xdc, 0xe8, 0x55, 0x1f, 0x6b, 0x10, 0x2d, 0x2e, 0x0e, 0x03, 0x7c, 0xe1, 0x0e, + 0x31, 0x27, 0xc6, 0x41, 0x29, 0xd3, 0x12, 0xd7, 0x28, 0x83, 0xed, 0x16, 0xd5, 0x14, 0x44, 0xf1, + 0x82, 0xfc, 0xbc, 0xbb, 0x5c, 0xcc, 0xdf, 0x0b, 0xf2, 0x73, 0x35, 0x1f, 0x75, 0x96, 0x9e, 0x77, + 0xdb, 0xc5, 0xfc, 0x3d, 0x0e, 0x62, 0xe2, 0x65, 0x67, 0xc1, 0x31, 0xe5, 0x4d, 0x17, 0x1d, 0xd1, + 0x86, 0xc4, 0x20, 0x7b, 0xb1, 0x8f, 0x69, 0xe4, 0x59, 0x90, 0x6a, 0x97, 0xd3, 0x15, 0x7e, 0x85, + 0x65, 0x40, 0xe9, 0x1a, 0xf6, 0x03, 0xe8, 0x48, 0x77, 0xd1, 0xfb, 0x12, 0x53, 0x9a, 0x8d, 0xc2, + 0x5c, 0xf6, 0x25, 0xf2, 0x2f, 0xfb, 0x7d, 0xec, 0x68, 0xf8, 0x3c, 0x1e, 0x0c, 0x8a, 0xeb, 0x93, + 0x70, 0xad, 0x0d, 0x98, 0x0b, 0x11, 0x2e, 0xa7, 0xf0, 0x2f, 0x3b, 0xc2, 0x7a, 0x4e, 0x69, 0x4a, + 0xf1, 0x6a, 0x11, 0x44, 0xc7, 0xb1, 0xb8, 0x2d, 0xe0, 0x6f, 0xb6, 0x17, 0x7d, 0x7a, 0x34, 0x1a, + 0xc8, 0xfe, 0x22, 0xfc, 0x60, 0x98, 0x67, 0x6e, 0x1a, 0x89, 0x03, 0x15, 0x7f, 0x33, 0x4c, 0x9a, + 0xa6, 0x71, 0x2a, 0x4e, 0x4f, 0xfe, 0x61, 0xef, 0xc3, 0xe6, 0xe1, 0xe5, 0x44, 0x64, 0x84, 0x78, + 0xb5, 0x46, 0x6c, 0x7f, 0xfc, 0x78, 0xf4, 0xd7, 0xb7, 0x61, 0x79, 0x3f, 0xe6, 0x9b, 0xf1, 0x39, + 0xf3, 0xc1, 0x94, 0x3c, 0x83, 0x79, 0xd1, 0x58, 0x48, 0x36, 0x2a, 0x9d, 0x86, 0xc8, 0xa3, 0xb7, + 0x39, 0xa6, 0x03, 0xd1, 0x5e, 0xfd, 0xc5, 0xbf, 0xfc, 0xdb, 0x2f, 0x1b, 0x4b, 0xa4, 0xf9, 0xf0, + 0xf4, 0xfd, 0x87, 0x03, 0x9a, 0xe3, 0x62, 0x4f, 0x60, 0xc9, 0xe8, 0x05, 0x23, 0x5b, 0x46, 0x3f, + 0x57, 0xa9, 0x45, 0xac, 0xb7, 0x3d, 0xb1, 0xdb, 0xcb, 0xee, 0x21, 0x8b, 0x35, 0x42, 0x04, 0x8b, + 0x0c, 0x51, 0x38, 0xe1, 0xaf, 0xa0, 0xfd, 0x04, 0xab, 0x60, 0x8a, 0x2a, 0xb9, 0x51, 0x50, 0xab, + 0xed, 0x71, 0xeb, 0xdd, 0x1c, 0x8f, 0x20, 0x38, 0x5e, 0x43, 0x8e, 0xeb, 0x64, 0x95, 0x71, 0xe4, + 0x55, 0x36, 0xd5, 0x5b, 0x46, 0x32, 0xe8, 0x88, 0xae, 0x99, 0x37, 0xca, 0x73, 0x0b, 0x79, 0x6e, + 0x90, 0x35, 0xc6, 0xd3, 0xe7, 0x0c, 0x0a, 0xa6, 0x31, 0x5e, 0xe2, 0xf5, 0x2e, 0x2f, 0x72, 0x7d, + 0x6c, 0xfb, 0x17, 0x67, 0x79, 0xe3, 0x82, 0xf6, 0x30, 0x73, 0x95, 0x03, 0xca, 0x70, 0x55, 0x87, + 0x18, 0xf9, 0xa5, 0x85, 0x0e, 0x5e, 0xdb, 0x8f, 0x48, 0xde, 0xba, 0xb8, 0x09, 0x92, 0xcb, 0x70, + 0x6f, 0xda, 0x6e, 0x49, 0xfb, 0x7b, 0x28, 0xcc, 0x75, 0xb2, 0x25, 0x84, 0x31, 0x3a, 0x24, 0x65, + 0x0f, 0x26, 0xf1, 0xa0, 0xa5, 0xb7, 0x76, 0x91, 0x6b, 0x35, 0x7d, 0x53, 0x8a, 0xf9, 0x56, 0xfd, + 0xa0, 0x60, 0xd8, 0x45, 0x86, 0x84, 0x74, 0x04, 0x43, 0xd5, 0x09, 0x46, 0xbe, 0x86, 0x76, 0xa9, + 0x2d, 0x8a, 0xd8, 0x25, 0xf3, 0xd5, 0xb4, 0xb8, 0xf5, 0x6e, 0x4f, 0xc4, 0x11, 0x5c, 0xaf, 0x23, + 0xd7, 0xae, 0xbd, 0xaa, 0x59, 0x59, 0x72, 0xfe, 0xc8, 0x7a, 0x40, 0x32, 0xb4, 0xb3, 0xde, 0x5b, + 0x35, 0x15, 0xef, 0x1b, 0x35, 0x4b, 0x35, 0xb6, 0x69, 0xd9, 0xd6, 0x92, 0x27, 0x6e, 0xd7, 0x0c, + 0x3b, 0x37, 0xb4, 0xbe, 0x33, 0x8c, 0xb2, 0xd3, 0xf0, 0xdd, 0xae, 0xef, 0x5b, 0x13, 0xad, 0x73, + 0x95, 0x9d, 0x2b, 0xb9, 0xc6, 0x79, 0x42, 0x32, 0xa3, 0xad, 0x4f, 0x30, 0x35, 0xbd, 0xba, 0xa6, + 0xb1, 0xae, 0x76, 0xa5, 0x7a, 0xa7, 0xdc, 0xd8, 0x95, 0xc6, 0x79, 0x92, 0x91, 0xd7, 0xb0, 0xcc, + 0xc3, 0xc5, 0x9b, 0xb7, 0xec, 0x36, 0xf2, 0xdd, 0xb4, 0x49, 0x11, 0x33, 0x74, 0xc3, 0xbe, 0x84, + 0x45, 0xd5, 0x1d, 0x43, 0xba, 0xda, 0x22, 0x8c, 0x3e, 0xac, 0xde, 0x98, 0x2e, 0x1b, 0xe9, 0xad, + 0xf6, 0x92, 0x58, 0x15, 0xef, 0x99, 0x61, 0x84, 0x7f, 0x06, 0x50, 0xb4, 0xdd, 0x90, 0xab, 0x15, + 0xca, 0x4a, 0x73, 0xbd, 0xba, 0x21, 0xd9, 0xbc, 0x8b, 0xe4, 0x3b, 0x64, 0xd9, 0x20, 0x2f, 0xf7, + 0x9b, 0xaa, 0x04, 0x19, 0xfb, 0xad, 0xdc, 0xa8, 0xd3, 0x1b, 0xdf, 0xa1, 0x21, 0x8d, 0x62, 0xcb, + 0xcd, 0xa6, 0x6e, 0x79, 0x6c, 0x05, 0x03, 0x3c, 0x2d, 0xb4, 0xd6, 0x90, 0xad, 0x3a, 0x2e, 0xb5, + 0xa7, 0x45, 0xb5, 0xcf, 0xc3, 0xbe, 0x8a, 0xac, 0x56, 0xc9, 0x4a, 0x99, 0x55, 0x46, 0x5e, 0xe1, + 0x1f, 0x2f, 0x68, 0x9d, 0x0d, 0x44, 0xa7, 0x55, 0x6d, 0xf3, 0xe8, 0x5d, 0x1f, 0x37, 0x3c, 0xe6, + 0x64, 0x12, 0x89, 0x20, 0x6e, 0x2a, 0x6e, 0x70, 0xde, 0xcf, 0x60, 0x18, 0xdc, 0x68, 0x7b, 0xe8, + 0x5d, 0xad, 0x19, 0x11, 0xd4, 0xd7, 0x91, 0x7a, 0x9b, 0x2c, 0xa9, 0x90, 0x88, 0xb4, 0xb8, 0x4d, + 0xd4, 0x43, 0x93, 0x61, 0x93, 0x72, 0x37, 0x82, 0x11, 0x03, 0x2b, 0x3d, 0x09, 0x95, 0x18, 0xa8, + 0xba, 0x0e, 0xc8, 0x1f, 0x9a, 0xcd, 0x0d, 0xf2, 0xb1, 0xd5, 0x9e, 0xf8, 0x3a, 0x5a, 0xd9, 0x2d, + 0x63, 0x5f, 0x50, 0xed, 0x1b, 0xc8, 0xf9, 0x2a, 0xd9, 0x2c, 0x73, 0x16, 0xaf, 0xb1, 0xe4, 0x17, + 0x16, 0xac, 0xd6, 0xbc, 0xf5, 0x15, 0x12, 0x8c, 0x7f, 0x99, 0x2c, 0x24, 0x98, 0xf4, 0x58, 0x68, + 0xa3, 0x04, 0x5b, 0x36, 0x4a, 0xe0, 0xfa, 0xbe, 0x92, 0x40, 0xe4, 0xb5, 0xcc, 0x33, 0xff, 0xd4, + 0x82, 0x8d, 0xfa, 0x77, 0x3d, 0x72, 0x47, 0xb5, 0x43, 0x4f, 0x7a, 0x71, 0xec, 0xdd, 0xbd, 0x08, + 0x4d, 0x48, 0x73, 0x07, 0xa5, 0xb9, 0x61, 0xf7, 0x98, 0x34, 0x29, 0xe2, 0xd6, 0x09, 0x74, 0x86, + 0xc5, 0x10, 0xf3, 0xe5, 0x8c, 0x68, 0xb9, 0x45, 0xfd, 0x03, 0x63, 0xef, 0xd6, 0x04, 0x0c, 0x33, + 0x7c, 0x91, 0x75, 0x61, 0x10, 0x7c, 0x6e, 0x52, 0x4f, 0x70, 0x62, 0x8f, 0x16, 0x2f, 0x53, 0xc6, + 0x1e, 0xad, 0x3c, 0xb6, 0x19, 0x7b, 0xb4, 0xfa, 0xfe, 0x55, 0xd9, 0xa3, 0xc8, 0x0c, 0xdf, 0xc2, + 0xc8, 0x97, 0xb8, 0x6d, 0x44, 0x25, 0xae, 0x5b, 0xde, 0xea, 0x59, 0xdd, 0xb6, 0x31, 0x6b, 0x6d, + 0x95, 0x50, 0xc9, 0x0b, 0x7c, 0x4c, 0x7b, 0x0e, 0x2c, 0x48, 0x74, 0xb2, 0x59, 0x26, 0x20, 0x29, + 0xd7, 0x3e, 0xa6, 0xd8, 0x9b, 0x48, 0x74, 0xc5, 0x6e, 0xe9, 0x44, 0x19, 0xcd, 0x23, 0x68, 0x6a, + 0x0f, 0x07, 0x44, 0x05, 0xd9, 0xea, 0x3b, 0x49, 0xef, 0x5a, 0xed, 0x98, 0x19, 0x4a, 0xec, 0x36, + 0x63, 0x90, 0x21, 0x82, 0xe2, 0xf1, 0xfb, 0xb0, 0x64, 0xd4, 0xee, 0x0b, 0xe5, 0xd7, 0xbd, 0x2e, + 0x14, 0xca, 0xaf, 0x2d, 0xf8, 0xcb, 0x44, 0xd3, 0x46, 0xe5, 0x67, 0x02, 0x45, 0xf1, 0xfa, 0x39, + 0x2c, 0xaa, 0x92, 0x79, 0xa1, 0xff, 0x72, 0x15, 0xfd, 0x22, 0x1e, 0x86, 0x0d, 0xce, 0xd8, 0xe4, + 0xa3, 0x78, 0x78, 0x24, 0xf4, 0xa5, 0x15, 0x84, 0x0b, 0x7d, 0x55, 0xab, 0xe2, 0x85, 0xbe, 0xea, + 0x2a, 0xc8, 0x86, 0xbe, 0x3c, 0x44, 0x50, 0x6b, 0x48, 0xa1, 0x5d, 0x2a, 0xc4, 0x16, 0x69, 0x45, + 0x7d, 0xd9, 0xb9, 0x48, 0x2b, 0xc6, 0x54, 0x70, 0xcd, 0xc4, 0x8d, 0xf3, 0x73, 0xc3, 0xb0, 0xf0, + 0x2d, 0x1e, 0xee, 0x79, 0x99, 0xd2, 0xf0, 0x5b, 0xa3, 0x1e, 0x6b, 0xf8, 0xad, 0x59, 0xd3, 0xac, + 0x84, 0x7b, 0xca, 0x69, 0xbd, 0x80, 0x05, 0x59, 0x1f, 0x2b, 0x9c, 0xb6, 0x54, 0x19, 0xec, 0x75, + 0xab, 0x03, 0x82, 0xaa, 0xe1, 0xb8, 0xae, 0xef, 0x23, 0x55, 0x61, 0x08, 0xad, 0x5a, 0x56, 0x18, + 0xa2, 0x5a, 0x68, 0x2b, 0x0c, 0x51, 0x57, 0x5e, 0x33, 0x0c, 0xc1, 0x23, 0x97, 0xe2, 0xf1, 0x77, + 0x16, 0xdc, 0xba, 0xb0, 0xd8, 0x45, 0xde, 0xbb, 0x44, 0x5d, 0x8c, 0x0b, 0xf4, 0xfe, 0xa5, 0x2b, + 0x69, 0xf6, 0x3d, 0x14, 0xd3, 0xb6, 0xb7, 0xe5, 0x61, 0x8a, 0xd3, 0x7c, 0x8e, 0xae, 0xca, 0x6a, + 0x4c, 0xe8, 0xbf, 0xb1, 0xf8, 0x9f, 0xa6, 0x4d, 0xa0, 0x4b, 0x76, 0xa6, 0x14, 0x40, 0x0a, 0xfc, + 0x70, 0x6a, 0x7c, 0x21, 0xee, 0x5d, 0x14, 0xf7, 0xa6, 0x7d, 0x6d, 0x82, 0xb8, 0x4c, 0xd8, 0x3f, + 0x80, 0x6b, 0xaa, 0x28, 0x66, 0xd0, 0xfd, 0x6c, 0x14, 0xf9, 0x59, 0x71, 0x2f, 0x1d, 0x53, 0x39, + 0x2b, 0x1c, 0xa7, 0x5c, 0x2b, 0x31, 0xcf, 0xc7, 0x33, 0x31, 0xca, 0xc5, 0x38, 0x66, 0xb4, 0x19, + 0xf7, 0x04, 0x56, 0xe4, 0xbc, 0xcf, 0x02, 0x37, 0xff, 0xd6, 0x3c, 0x6f, 0x22, 0xcf, 0x9e, 0xbd, + 0xae, 0xf3, 0x3c, 0x0e, 0xdc, 0x5c, 0x71, 0xcc, 0xf0, 0x8d, 0xc3, 0x28, 0x83, 0xe8, 0x97, 0xef, + 0xda, 0x02, 0x89, 0x7e, 0xf9, 0xae, 0xaf, 0xd8, 0x98, 0x97, 0xef, 0x01, 0xcd, 0x79, 0x05, 0xc5, + 0x17, 0x0c, 0x4e, 0xa1, 0x73, 0x38, 0x96, 0xe9, 0xe1, 0xaf, 0xcc, 0x54, 0xe4, 0x40, 0x36, 0x32, + 0xcd, 0x4a, 0x4c, 0x3f, 0xb2, 0x1e, 0x1c, 0xcd, 0xe1, 0xdf, 0xdc, 0x7e, 0xf0, 0x7f, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x0a, 0xe0, 0x50, 0x4a, 0xa6, 0x3b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -4815,6 +4975,8 @@ type GoCryptoTraderClient interface { GetCryptocurrencyDepositAddress(ctx context.Context, in *GetCryptocurrencyDepositAddressRequest, opts ...grpc.CallOption) (*GetCryptocurrencyDepositAddressResponse, error) WithdrawCryptocurrencyFunds(ctx context.Context, in *WithdrawCurrencyRequest, opts ...grpc.CallOption) (*WithdrawResponse, error) WithdrawFiatFunds(ctx context.Context, in *WithdrawCurrencyRequest, opts ...grpc.CallOption) (*WithdrawResponse, error) + GetLoggerDetails(ctx context.Context, in *GetLoggerDetailsRequest, opts ...grpc.CallOption) (*GetLoggerDetailsResponse, error) + SetLoggerDetails(ctx context.Context, in *SetLoggerDetailsRequest, opts ...grpc.CallOption) (*GetLoggerDetailsResponse, error) } type goCryptoTraderClient struct { @@ -5167,6 +5329,24 @@ func (c *goCryptoTraderClient) WithdrawFiatFunds(ctx context.Context, in *Withdr return out, nil } +func (c *goCryptoTraderClient) GetLoggerDetails(ctx context.Context, in *GetLoggerDetailsRequest, opts ...grpc.CallOption) (*GetLoggerDetailsResponse, error) { + out := new(GetLoggerDetailsResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetLoggerDetails", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) SetLoggerDetails(ctx context.Context, in *SetLoggerDetailsRequest, opts ...grpc.CallOption) (*GetLoggerDetailsResponse, error) { + out := new(GetLoggerDetailsResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/SetLoggerDetails", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // GoCryptoTraderServer is the server API for GoCryptoTrader service. type GoCryptoTraderServer interface { GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) @@ -5207,6 +5387,8 @@ type GoCryptoTraderServer interface { GetCryptocurrencyDepositAddress(context.Context, *GetCryptocurrencyDepositAddressRequest) (*GetCryptocurrencyDepositAddressResponse, error) WithdrawCryptocurrencyFunds(context.Context, *WithdrawCurrencyRequest) (*WithdrawResponse, error) WithdrawFiatFunds(context.Context, *WithdrawCurrencyRequest) (*WithdrawResponse, error) + GetLoggerDetails(context.Context, *GetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) + SetLoggerDetails(context.Context, *SetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) } func RegisterGoCryptoTraderServer(s *grpc.Server, srv GoCryptoTraderServer) { @@ -5897,6 +6079,42 @@ func _GoCryptoTrader_WithdrawFiatFunds_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } +func _GoCryptoTrader_GetLoggerDetails_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetLoggerDetailsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetLoggerDetails(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetLoggerDetails", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetLoggerDetails(ctx, req.(*GetLoggerDetailsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_SetLoggerDetails_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetLoggerDetailsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).SetLoggerDetails(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/SetLoggerDetails", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).SetLoggerDetails(ctx, req.(*SetLoggerDetailsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ ServiceName: "gctrpc.GoCryptoTrader", HandlerType: (*GoCryptoTraderServer)(nil), @@ -6053,6 +6271,14 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ MethodName: "WithdrawFiatFunds", Handler: _GoCryptoTrader_WithdrawFiatFunds_Handler, }, + { + MethodName: "GetLoggerDetails", + Handler: _GoCryptoTrader_GetLoggerDetails_Handler, + }, + { + MethodName: "SetLoggerDetails", + Handler: _GoCryptoTrader_SetLoggerDetails_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "rpc.proto", diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 8a90ca9f..4bd4c1e7 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -9,13 +9,13 @@ It translates gRPC into RESTful JSON APIs. package gctrpc import ( + "context" "io" "net/http" "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/grpc-ecosystem/grpc-gateway/utilities" - "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/grpclog" @@ -54,7 +54,10 @@ func request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshaler run var protoReq GenericSubsystemRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_EnableSubsystem_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_EnableSubsystem_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -71,7 +74,10 @@ func request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marshaler ru var protoReq GenericSubsystemRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_DisableSubsystem_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_DisableSubsystem_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -106,7 +112,10 @@ func request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtim var protoReq GetExchangesRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchanges_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -140,7 +149,10 @@ func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler run var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -157,7 +169,10 @@ func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -252,7 +267,10 @@ func request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runt var protoReq GetAccountInfoRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetAccountInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -570,6 +588,43 @@ func request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler r } +var ( + filter_GoCryptoTrader_GetLoggerDetails_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetLoggerDetailsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetLoggerDetails_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetLoggerDetails(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetLoggerDetailsRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.SetLoggerDetails(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + // RegisterGoCryptoTraderHandlerFromEndpoint is same as RegisterGoCryptoTraderHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterGoCryptoTraderHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { @@ -1368,85 +1423,129 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_GetLoggerDetails_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetLoggerDetails_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_SetLoggerDetails_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_SetLoggerDetails_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } var ( - pattern_GoCryptoTrader_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, "")) + pattern_GoCryptoTrader_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetSubsystems_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getsusbsystems"}, "")) + pattern_GoCryptoTrader_GetSubsystems_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getsusbsystems"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_EnableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enablesubsystem"}, "")) + pattern_GoCryptoTrader_EnableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enablesubsystem"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_DisableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disablesubsystem"}, "")) + pattern_GoCryptoTrader_DisableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disablesubsystem"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetRPCEndpoints_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getrpcendpoints"}, "")) + pattern_GoCryptoTrader_GetRPCEndpoints_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getrpcendpoints"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetCommunicationRelayers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcommunicationrelayers"}, "")) + pattern_GoCryptoTrader_GetCommunicationRelayers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcommunicationrelayers"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetExchanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchanges"}, "")) + pattern_GoCryptoTrader_GetExchanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchanges"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_DisableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchange"}, "")) + pattern_GoCryptoTrader_DisableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchange"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetExchangeInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeinfo"}, "")) + pattern_GoCryptoTrader_GetExchangeInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeinfo"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotp"}, "")) + pattern_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotp"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetExchangeOTPCodes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotps"}, "")) + pattern_GoCryptoTrader_GetExchangeOTPCodes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotps"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_EnableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchange"}, "")) + pattern_GoCryptoTrader_EnableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchange"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetTicker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getticker"}, "")) + pattern_GoCryptoTrader_GetTicker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getticker"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetTickers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "gettickers"}, "")) + pattern_GoCryptoTrader_GetTickers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "gettickers"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetOrderbook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbook"}, "")) + pattern_GoCryptoTrader_GetOrderbook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbook"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetOrderbooks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbooks"}, "")) + pattern_GoCryptoTrader_GetOrderbooks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbooks"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetAccountInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getaccountinfo"}, "")) + pattern_GoCryptoTrader_GetAccountInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getaccountinfo"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getconfig"}, "")) + pattern_GoCryptoTrader_GetConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getconfig"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetPortfolio_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfolio"}, "")) + pattern_GoCryptoTrader_GetPortfolio_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfolio"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetPortfolioSummary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfoliosummary"}, "")) + pattern_GoCryptoTrader_GetPortfolioSummary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfoliosummary"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_AddPortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addportfolioaddress"}, "")) + pattern_GoCryptoTrader_AddPortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addportfolioaddress"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_RemovePortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeportfolioaddress"}, "")) + pattern_GoCryptoTrader_RemovePortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeportfolioaddress"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetForexProviders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexproviders"}, "")) + pattern_GoCryptoTrader_GetForexProviders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexproviders"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetForexRates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexrates"}, "")) + pattern_GoCryptoTrader_GetForexRates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexrates"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorders"}, "")) + pattern_GoCryptoTrader_GetOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorders"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorder"}, "")) + pattern_GoCryptoTrader_GetOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorder"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_SubmitOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "submitorder"}, "")) + pattern_GoCryptoTrader_SubmitOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "submitorder"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_SimulateOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "simulateorder"}, "")) + pattern_GoCryptoTrader_SimulateOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "simulateorder"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_WhaleBomb_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "whalebomb"}, "")) + pattern_GoCryptoTrader_WhaleBomb_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "whalebomb"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_CancelOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelorder"}, "")) + pattern_GoCryptoTrader_CancelOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelorder"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_CancelAllOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelallorders"}, "")) + pattern_GoCryptoTrader_CancelAllOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelallorders"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getevents"}, "")) + pattern_GoCryptoTrader_GetEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getevents"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_AddEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addevent"}, "")) + pattern_GoCryptoTrader_AddEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addevent"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_RemoveEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeevent"}, "")) + pattern_GoCryptoTrader_RemoveEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeevent"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddresses"}, "")) + pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddresses"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddress"}, "")) + pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddress"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawcryptofunds"}, "")) + pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawcryptofunds"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_WithdrawFiatFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawfiatfunds"}, "")) + pattern_GoCryptoTrader_WithdrawFiatFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawfiatfunds"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_GetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getloggerdetails"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_SetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "setloggerdetails"}, "", runtime.AssumeColonVerbOpt(true))) ) var ( @@ -1525,4 +1624,8 @@ var ( forward_GoCryptoTrader_WithdrawCryptocurrencyFunds_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_WithdrawFiatFunds_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetLoggerDetails_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_SetLoggerDetails_0 = runtime.ForwardResponseMessage ) diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 644a3bb0..aa0cde00 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -68,7 +68,7 @@ message GetExchangeOTPReponse { string otp_code = 1; } -message GetExchangeOTPsRequest {} +message GetExchangeOTPsRequest {} message GetExchangeOTPsResponse { map otp_codes = 1; @@ -147,7 +147,7 @@ message OrderbookResponse { repeated OrderbookItem bids = 3; repeated OrderbookItem asks = 4; int64 last_updated = 5; - string asset_type = 6; + string asset_type = 6; } message GetOrderbooksRequest {} @@ -225,7 +225,7 @@ message OfflineCoins { } message OnlineCoins { - map coins = 1; + map coins = 1; } message GetPortfolioSummaryResponse { @@ -371,7 +371,7 @@ message CancelAllOrdersResponse { string exchange = 1; map order_status = 2; } - repeated Orders orders = 1; + repeated Orders orders = 1; } message GetEventsRequest {} @@ -455,6 +455,22 @@ message WithdrawResponse { string result = 1; } +message GetLoggerDetailsRequest { + string logger = 1; +} + +message GetLoggerDetailsResponse{ + bool info = 1; + bool debug = 2; + bool warn = 3; + bool error = 4; +} + +message SetLoggerDetailsRequest { + string logger = 1; + string level = 2; +} + service GoCryptoTrader { rpc GetInfo (GetInfoRequest) returns (GetInfoResponse) { option (google.api.http) = { @@ -703,4 +719,17 @@ service GoCryptoTrader { body: "*" }; } + + rpc GetLoggerDetails(GetLoggerDetailsRequest) returns (GetLoggerDetailsResponse) { + option (google.api.http) = { + get: "/v1/getloggerdetails" + }; + } + + rpc SetLoggerDetails(SetLoggerDetailsRequest) returns (GetLoggerDetailsResponse) { + option (google.api.http) = { + post: "/v1/setloggerdetails", + body: "*" + }; + } } diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index 032da9db..c590bbd3 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -480,6 +480,30 @@ ] } }, + "/v1/getloggerdetails": { + "get": { + "operationId": "GetLoggerDetails", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetLoggerDetailsResponse" + } + } + }, + "parameters": [ + { + "name": "logger", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getorder": { "post": { "operationId": "GetOrder", @@ -732,6 +756,32 @@ ] } }, + "/v1/setloggerdetails": { + "post": { + "operationId": "SetLoggerDetails", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetLoggerDetailsResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcSetLoggerDetailsRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/simulateorder": { "post": { "operationId": "SimulateOrder", @@ -1371,6 +1421,27 @@ } } }, + "gctrpcGetLoggerDetailsResponse": { + "type": "object", + "properties": { + "info": { + "type": "boolean", + "format": "boolean" + }, + "debug": { + "type": "boolean", + "format": "boolean" + }, + "warn": { + "type": "boolean", + "format": "boolean" + }, + "error": { + "type": "boolean", + "format": "boolean" + } + } + }, "gctrpcGetOrderRequest": { "type": "object", "properties": { @@ -1742,6 +1813,17 @@ "gctrpcRemovePortfolioAddressResponse": { "type": "object" }, + "gctrpcSetLoggerDetailsRequest": { + "type": "object", + "properties": { + "logger": { + "type": "string" + }, + "level": { + "type": "string" + } + } + }, "gctrpcSimulateOrderRequest": { "type": "object", "properties": { diff --git a/go.mod b/go.mod index 00e0d429..1c618c5a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/gorilla/mux v1.7.2 github.com/gorilla/websocket v1.4.0 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 - github.com/grpc-ecosystem/grpc-gateway v1.9.0 + github.com/grpc-ecosystem/grpc-gateway v1.9.2 github.com/pquerna/otp v1.2.0 github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b github.com/urfave/cli v1.20.0 diff --git a/go.sum b/go.sum index 8fc938f2..a84ddf41 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmo github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.2 h1:S+ef0492XaIknb8LMjcwgW2i3cNTzDYMmDrOThOJNWc= +github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= diff --git a/logger/logger.go b/logger/logger.go index 84572942..7dfdc323 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -1,120 +1,78 @@ package logger import ( + "errors" "fmt" "io" - "io/ioutil" - "log" - "os" - "path/filepath" - "runtime" "time" ) -func init() { - setDefaultOutputs() -} - -// SetupLogger configure logger instance with user provided settings -func SetupLogger() (err error) { - if *Logger.Enabled { - err = setupOutputs() - if err != nil { - return - } - logLevel() - if Logger.ColourOutput { - colourOutput() - } - } else { - clearAllLoggers() - } - return -} - -// setDefaultOutputs() this setups defaults used by the logger -// This allows it to be used without any user configuration -func setDefaultOutputs() { - debugLogger = log.New(os.Stdout, - "[DEBUG]: ", - log.Ldate|log.Ltime) - - infoLogger = log.New(os.Stdout, - "[INFO]: ", - log.Ldate|log.Ltime) - - warnLogger = log.New(os.Stdout, - "[WARN]: ", - log.Ldate|log.Ltime) - - errorLogger = log.New(os.Stdout, - "[ERROR]: ", - log.Ldate|log.Ltime) - - fatalLogger = log.New(os.Stdout, - "[FATAL]: ", - log.Ldate|log.Ltime) -} - -// colorOutput() sets the prefix of each log type to matching colour -// TODO: add windows support -func colourOutput() { - if runtime.GOOS != "windows" || Logger.ColourOutputOverride { - debugLogger.SetPrefix("\033[34m[DEBUG]\033[0m: ") - infoLogger.SetPrefix("\033[32m[INFO]\033[0m: ") - warnLogger.SetPrefix("\033[33m[WARN]\033[0m: ") - errorLogger.SetPrefix("\033[31m[ERROR]\033[0m: ") - fatalLogger.SetPrefix("\033[31m[FATAL]\033[0m: ") +func newLogger(c *Config) *Logger { + return &Logger{ + Timestamp: c.AdvancedSettings.TimeStampFormat, + Spacer: c.AdvancedSettings.Spacer, + ErrorHeader: c.AdvancedSettings.Headers.Error, + InfoHeader: c.AdvancedSettings.Headers.Info, + WarnHeader: c.AdvancedSettings.Headers.Warn, + DebugHeader: c.AdvancedSettings.Headers.Debug, } } -// clearAllLoggers() sets all logger flags to 0 and outputs to Discard -func clearAllLoggers() { - debugLogger.SetFlags(0) - infoLogger.SetFlags(0) - warnLogger.SetFlags(0) - errorLogger.SetFlags(0) - fatalLogger.SetFlags(0) - - debugLogger.SetOutput(ioutil.Discard) - infoLogger.SetOutput(ioutil.Discard) - warnLogger.SetOutput(ioutil.Discard) - errorLogger.SetOutput(ioutil.Discard) - fatalLogger.SetOutput(ioutil.Discard) -} - -// setupOutputs() sets up the io.writer to use for logging -// TODO: Fix up rotating at the moment its a quick job -func setupOutputs() (err error) { - if len(Logger.File) > 0 { - logFile := filepath.Join(LogPath, Logger.File) - if Logger.Rotate { - if _, err = os.Stat(logFile); !os.IsNotExist(err) { - currentTime := time.Now() - newName := currentTime.Format("2006-01-02 15-04-05") - newFile := newName + " " + Logger.File - err = os.Rename(logFile, filepath.Join(LogPath, newFile)) - if err != nil { - err = fmt.Errorf("failed to rename old log file %s", err) - return - } - } - } - logFileHandle, err = os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - if err != nil { - return - } - logOutput = io.MultiWriter(os.Stdout, logFileHandle) - } else { - logOutput = os.Stdout +func (l *Logger) newLogEvent(data, header string, w io.Writer) error { + if w == nil { + return errors.New("io.Writer not set") } - return + e := eventPool.Get().(*LogEvent) + e.output = w + e.data = append(e.data, []byte(header)...) + e.data = append(e.data, l.Spacer...) + if l.Timestamp != "" { + e.data = time.Now().AppendFormat(e.data, l.Timestamp) + } + e.data = append(e.data, l.Spacer...) + e.data = append(e.data, []byte(data)...) + if data == "" || data[len(data)-1] != '\n' { + e.data = append(e.data, '\n') + } + e.output.Write(e.data) + e.data = e.data[:0] + eventPool.Put(e) + + return nil } -// CloseLogFile close the handler for any open log files -func CloseLogFile() (err error) { - if logFileHandle != nil { - err = logFileHandle.Close() +// CloseLogger is called on shutdown of application +func CloseLogger() error { + err := GlobalLogFile.Close() + if err != nil { + return err } - return + return nil + +} + +func validSubLogger(s string) (bool, *subLogger) { + if v, found := subLoggers[s]; found { + return true, v + } + return false, nil +} + +func Level(s string) (*Levels, error) { + found, logger := validSubLogger(s) + if !found { + return nil, fmt.Errorf("logger %v not found", s) + } + + return &logger.Levels, nil +} + +func SetLevel(s, level string) (*Levels, error) { + found, logger := validSubLogger(s) + if !found { + return nil, fmt.Errorf("logger %v not found", s) + } + logger.Levels = splitLevel(level) + + return &logger.Levels, nil } diff --git a/logger/logger_levels.go b/logger/logger_levels.go deleted file mode 100644 index ce7f2c8c..00000000 --- a/logger/logger_levels.go +++ /dev/null @@ -1,33 +0,0 @@ -package logger - -import ( - "log" - "strings" -) - -func logLevel() { - clearAllLoggers() - enabledLevels := strings.Split(Logger.Level, "|") - - for x := range enabledLevels { - switch level := enabledLevels[x]; level { - case "DEBUG": - debugLogger.SetOutput(logOutput) - debugLogger.SetFlags(log.Ldate | log.Ltime) - case "INFO": - infoLogger.SetOutput(logOutput) - infoLogger.SetFlags(log.Ldate | log.Ltime) - case "WARN": - warnLogger.SetOutput(logOutput) - warnLogger.SetFlags(log.Ldate | log.Ltime) - case "ERROR": - errorLogger.SetOutput(logOutput) - errorLogger.SetFlags(log.Ldate | log.Ltime) - case "FATAL": - fatalLogger.SetOutput(logOutput) - fatalLogger.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) - default: - continue - } - } -} diff --git a/logger/logger_multiwriter.go b/logger/logger_multiwriter.go new file mode 100644 index 00000000..af07cfff --- /dev/null +++ b/logger/logger_multiwriter.go @@ -0,0 +1,75 @@ +package logger + +import ( + "io" +) + +// Add appends a new writer to the multiwriter slice +func (mw *multiWriter) Add(writer io.Writer) { + mw.mu.Lock() + mw.writers = append(mw.writers, writer) + mw.mu.Unlock() +} + +// Remove removes exisiting writer from multiwriter slice +func (mw *multiWriter) Remove(writer io.Writer) { + mw.mu.Lock() + + var removeIDs []int + for i := range mw.writers { + if mw.writers[i] == writer { + removeIDs = append(removeIDs, i) + } + } + + for x := range removeIDs { + mw.writers[x] = mw.writers[len(mw.writers)-1] + mw.writers[len(mw.writers)-1] = nil + mw.writers = mw.writers[:len(mw.writers)-1] + } + + mw.mu.Unlock() +} + +// Write concurrent safe Write for each writer +func (mw *multiWriter) Write(p []byte) (n int, err error) { + type data struct { + n int + err error + } + + results := make(chan data) + + for _, wr := range mw.writers { + go func(w io.Writer, p []byte, ch chan data) { + n, err = w.Write(p) + if err != nil { + ch <- data{n, err} + return + } + if n != len(p) { + ch <- data{n, io.ErrShortWrite} + return + } + select { + case ch <- data{n, nil}: + default: + } + }(wr, p, results) + } + + for range mw.writers { + d := <-results + if d.err != nil { + return d.n, d.err + } + } + return len(p), nil +} + +// MultiWriter make and return a new copy of multiWriter +func MultiWriter(writers ...io.Writer) io.Writer { + w := make([]io.Writer, len(writers)) + copy(w, writers) + return &multiWriter{writers: w} +} diff --git a/logger/logger_rotate.go b/logger/logger_rotate.go new file mode 100644 index 00000000..dc4a0f3d --- /dev/null +++ b/logger/logger_rotate.go @@ -0,0 +1,132 @@ +package logger + +import ( + "fmt" + "os" + "path/filepath" + "time" +) + +// Write implementation to satisfy io.Writer handles length check and rotation +func (r *Rotate) Write(output []byte) (n int, err error) { + r.mu.Lock() + defer r.mu.Unlock() + + outputLen := int64(len(output)) + + if outputLen > r.maxSize() { + return 0, fmt.Errorf( + "write length %v exceeds max file size %v", outputLen, r.maxSize(), + ) + } + + if r.output == nil { + err = r.openOrCreateFile(outputLen) + if err != nil { + return 0, err + } + } + + if *r.Rotate { + if r.size+outputLen > r.maxSize() { + err = r.rotateFile() + if err != nil { + return 0, err + } + } + } + + n, err = r.output.Write(output) + r.size += int64(n) + + return n, err +} + +func (r *Rotate) openOrCreateFile(n int64) error { + logFile := filepath.Join(LogPath, r.FileName) + + info, err := os.Stat(logFile) + if err != nil { + if os.IsNotExist(err) { + return r.openNew() + } + return fmt.Errorf("error opening log file info: %s", err) + } + + if *r.Rotate { + if info.Size()+n >= r.maxSize() { + return r.rotateFile() + } + } + + file, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + return r.openNew() + } + + r.output = file + r.size = info.Size() + + return nil +} + +func (r *Rotate) openNew() error { + name := filepath.Join(LogPath, r.FileName) + + t := time.Now() + timestamp := t.Format("2006-01-02T15-04-05") + newName := filepath.Join(LogPath, timestamp+"-"+r.FileName) + _, err := os.Stat(name) + + if err == nil { + err = os.Rename(name, newName) + if err != nil { + return fmt.Errorf("can't rename log file: %s", err) + } + } + + file, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("can't open new logfile: %s", err) + } + + r.output = file + r.size = 0 + + return nil +} + +func (r *Rotate) close() (err error) { + if r.output == nil { + return nil + } + err = r.output.Close() + r.output = nil + return err +} + +func (r *Rotate) Close() error { + r.mu.Lock() + defer r.mu.Unlock() + return r.close() +} + +func (r *Rotate) rotateFile() (err error) { + err = r.close() + if err != nil { + return + } + + err = r.openNew() + if err != nil { + return + } + return nil +} + +func (r *Rotate) maxSize() int64 { + if r.MaxSize == 0 { + return int64(defaultMaxSize * megabyte) + } + return r.MaxSize * int64(megabyte) +} diff --git a/logger/logger_rotate_types.go b/logger/logger_rotate_types.go new file mode 100644 index 00000000..8b298ce8 --- /dev/null +++ b/logger/logger_rotate_types.go @@ -0,0 +1,22 @@ +package logger + +import ( + "os" + "sync" +) + +const ( + defaultMaxSize = 250 + megabyte = 1024 * 1024 +) + +// Rotate struct for each instance of Rotate +type Rotate struct { + FileName string + Rotate *bool + MaxSize int64 + + size int64 + output *os.File + mu sync.Mutex +} diff --git a/logger/logger_setup.go b/logger/logger_setup.go new file mode 100644 index 00000000..b461e059 --- /dev/null +++ b/logger/logger_setup.go @@ -0,0 +1,156 @@ +package logger + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "strings" +) + +func getWriters(s *SubLoggerConfig) io.Writer { + mw := MultiWriter() + m := mw.(*multiWriter) + + outputWriters := strings.Split(s.Output, "|") + for x := range outputWriters { + switch outputWriters[x] { + case "stdout", "console": + m.Add(os.Stdout) + case "stderr": + m.Add(os.Stderr) + case "file": + if FileLoggingConfiguredCorrectly { + m.Add(GlobalLogFile) + } + default: + m.Add(ioutil.Discard) + } + } + + return m +} + +// GenDefaultSettings return struct with known sane/working logger settings +func GenDefaultSettings() (log Config) { + t := func(t bool) *bool { return &t }(true) + f := func(f bool) *bool { return &f }(false) + + log = Config{ + Enabled: t, + SubLoggerConfig: SubLoggerConfig{ + Level: "INFO|DEBUG|WARN|ERROR", + Output: "console", + }, + LoggerFileConfig: &loggerFileConfig{ + FileName: "log.txt", + Rotate: f, + MaxSize: 0, + }, + AdvancedSettings: advancedSettings{ + Spacer: " | ", + TimeStampFormat: timestampFormat, + Headers: headers{ + Info: "[INFO]", + Warn: "[WARN]", + Debug: "[DEBUG]", + Error: "[ERROR]", + }, + }, + } + return +} + +func configureSubLogger(logger, levels string, output io.Writer) error { + found, logPtr := validSubLogger(logger) + if !found { + return fmt.Errorf("logger %v not found", logger) + } + + logPtr.output = output + + logPtr.Levels = splitLevel(levels) + subLoggers[logger] = logPtr + + return nil +} + +// SetupSubLoggers configure all sub loggers with provided configuration values +func SetupSubLoggers(s []SubLoggerConfig) { + for x := range s { + output := getWriters(&s[x]) + err := configureSubLogger(s[x].Name, s[x].Level, output) + if err != nil { + continue + } + } +} + +// SetupGlobalLogger setup the global loggers with the default global config values +func SetupGlobalLogger() { + if FileLoggingConfiguredCorrectly { + GlobalLogFile = &Rotate{ + FileName: GlobalLogConfig.LoggerFileConfig.FileName, + MaxSize: GlobalLogConfig.LoggerFileConfig.MaxSize, + Rotate: GlobalLogConfig.LoggerFileConfig.Rotate, + } + } + + for x := range subLoggers { + subLoggers[x].Levels = splitLevel(GlobalLogConfig.Level) + subLoggers[x].output = getWriters(&GlobalLogConfig.SubLoggerConfig) + } + + logger = newLogger(GlobalLogConfig) +} + +func splitLevel(level string) (l Levels) { + enabledLevels := strings.Split(level, "|") + for x := range enabledLevels { + switch level := enabledLevels[x]; level { + case "DEBUG": + l.Debug = true + case "INFO": + l.Info = true + case "WARN": + l.Warn = true + case "ERROR": + l.Error = true + } + } + return +} + +func registerNewSubLogger(logger string) *subLogger { + temp := subLogger{ + name: logger, + output: os.Stdout, + } + + temp.Levels = splitLevel("INFO|WARN|DEBUG|ERROR") + subLoggers[logger] = &temp + + return &temp +} + +// register all loggers at package init() +func init() { + Global = registerNewSubLogger("log") + + ConnectionMgr = registerNewSubLogger("connection") + CommunicationMgr = registerNewSubLogger("comms") + ConfigMgr = registerNewSubLogger("config") + OrderMgr = registerNewSubLogger("order") + PortfolioMgr = registerNewSubLogger("portfolio") + SyncMgr = registerNewSubLogger("sync") + TimeMgr = registerNewSubLogger("timekeeper") + WebsocketMgr = registerNewSubLogger("websocket") + EventMgr = registerNewSubLogger("event") + + ExchangeSys = registerNewSubLogger("exchange") + GRPCSys = registerNewSubLogger("grpc") + RESTSys = registerNewSubLogger("rest") + + Ticker = registerNewSubLogger("ticker") + OrderBook = registerNewSubLogger("orderbook") +} diff --git a/logger/logger_test.go b/logger/logger_test.go index 35d0cd65..7af289e0 100644 --- a/logger/logger_test.go +++ b/logger/logger_test.go @@ -1,8 +1,9 @@ package logger import ( + "bytes" + "io/ioutil" "os" - "path/filepath" "testing" ) @@ -11,77 +12,237 @@ var ( falseptr = func(b bool) *bool { return &b }(false) ) -func TestCloseLogFile(t *testing.T) { - Logger = &Logging{ - Enabled: trueptr, - Level: "DEBUG", - ColourOutput: false, - File: "", - Rotate: false, +func SetupTest() { + logTest := Config{ + Enabled: trueptr, + SubLoggerConfig: SubLoggerConfig{ + Output: "console", + Level: "INFO|WARN|DEBUG|ERROR", + }, + AdvancedSettings: advancedSettings{ + Spacer: " | ", + TimeStampFormat: timestampFormat, + Headers: headers{ + Info: "[INFO]", + Warn: "[WARN]", + Debug: "[DEBUG]", + Error: "[ERROR]", + }, + }, + SubLoggers: []SubLoggerConfig{ + { + Name: "test", + Level: "INFO|DEBUG|WARN|ERROR", + Output: "stdout", + }}, } - SetupLogger() - err := CloseLogFile() - if err != nil { - t.Fatalf("CloseLogFile failed with %v", err) - } - os.Remove(filepath.Join(LogPath, Logger.File)) + + GlobalLogConfig = &logTest + SetupGlobalLogger() + SetupSubLoggers(logTest.SubLoggers) } -func TestSetupOutputsValidPath(t *testing.T) { - Logger.Enabled = trueptr - Logger.File = "debug.txt" - LogPath = "../testdata/" - err := setupOutputs() - if err != nil { - t.Fatalf("SetupOutputs failed expected nil got %v", err) +func SetupDisabled() { + logTest := Config{ + Enabled: falseptr, } - err = CloseLogFile() - if err != nil { - t.Fatalf("CloseLogFile failed with %v", err) - } + GlobalLogConfig = &logTest + SetupGlobalLogger() + SetupSubLoggers(logTest.SubLoggers) +} - err = os.Remove(filepath.Join(LogPath, Logger.File)) - if err != nil { - t.Fatal("Test Failed - SetupOutputsValidPath() error could not remove test file", err) +func BenchmarkInfo(b *testing.B) { + SetupTest() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + Info(Global, "Hello this is an info benchmark") } } -func TestSetupOutputsInValidPath(t *testing.T) { - Logger.Enabled = trueptr - Logger.File = "debug.txt" - LogPath = "../testdataa/" - err := setupOutputs() - if err != nil { - if !os.IsNotExist(err) { - t.Fatalf("SetupOutputs failed expected %v got %v", os.ErrNotExist, err) - } +func SetupTestDisabled(t *testing.T) { + SetupDisabled() +} + +func TestAddWriter(t *testing.T) { + mw := MultiWriter() + m := mw.(*multiWriter) + + m.Add(ioutil.Discard) + m.Add(os.Stdin) + m.Add(os.Stdout) + + total := len(m.writers) + + if total != 3 { + t.Errorf("expected m.Writers to be 3 %v", total) } - err = os.Remove(filepath.Join(LogPath, Logger.File)) +} + +func TestRemoveWriter(t *testing.T) { + mw := MultiWriter() + m := mw.(*multiWriter) + + m.Add(ioutil.Discard) + m.Add(os.Stdin) + m.Add(os.Stdout) + + total := len(m.writers) + + m.Remove(os.Stdin) + m.Remove(os.Stdout) + + if len(m.writers) != total-2 { + t.Errorf("expected m.Writers to be %v got %v", total-2, len(m.writers)) + } +} + +func TestLevel(t *testing.T) { + SetupTest() + + _, err := Level("log") + if err != nil { + t.Errorf("Failed to get log %s levels skippin", err) + } + + _, err = Level("totallyinvalidlogger") if err == nil { - t.Fatal("Test Failed - SetupOutputsInValidPath() error cannot be nil") + t.Error("expected error on invalid logger") } } -func BenchmarkDebugf(b *testing.B) { - Logger = &Logging{ - Enabled: trueptr, - Level: "DEBUG", - ColourOutput: false, - File: "", - Rotate: false, +func TestSetLevel(t *testing.T) { + SetupTest() + + newLevel, err := SetLevel("log", "ERROR") + if err != nil { + t.Skipf("Failed to get log %s levels skipping", err) } - SetupLogger() - b.ResetTimer() - for n := 0; n < b.N; n++ { - Debugf("This is a debug benchmark %d", n) + + if newLevel.Info || newLevel.Debug || newLevel.Warn { + t.Error("failed to set level correctly") + } + + if !newLevel.Error { + t.Error("failed to set level correctly") + } + + _, err = SetLevel("abc12345556665", "ERROR") + if err == nil { + t.Error("SetLevel() Should return error on invalid logger") } } -func BenchmarkDebugfLoggerDisabled(b *testing.B) { - clearAllLoggers() - b.ResetTimer() - for n := 0; n < b.N; n++ { - Debugf("this is a debug benchmark") +func TestValidSubLogger(t *testing.T) { + b, logPtr := validSubLogger("log") + + if !b { + t.Skip("validSubLogger() should return found, pointer if valid logger found") + } + if logPtr == nil { + t.Error("validSubLogger() should return a pointer and not nil ") + } +} + +func TestCloseLogger(t *testing.T) { + err := CloseLogger() + if err != nil { + t.Errorf("CloseLogger() failed %v", err) + } +} + +func TestConfigureSubLogger(t *testing.T) { + err := configureSubLogger("log", "INFO", os.Stdin) + if err != nil { + t.Skipf("configureSubLogger() returned unexpected error %v", err) + } + if (Global.Levels != Levels{ + Info: true, + Debug: false, + }) { + t.Error("configureSubLogger() incorrectly configure subLogger") + } +} + +func TestSplitLevel(t *testing.T) { + levelsInfoDebug := splitLevel("INFO|DEBUG") + + expected := Levels{ + Info: true, + Debug: true, + Warn: false, + Error: false, + } + + if levelsInfoDebug != expected { + t.Errorf("splitLevel() returned invalid data expected: %+v got: %+v", expected, levelsInfoDebug) + } +} + +func BenchmarkInfoDisabled(b *testing.B) { + SetupDisabled() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + Info(Global, "Hello this is an info benchmark") + } +} + +func BenchmarkInfof(b *testing.B) { + SetupTest() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + Infof(Global, "Hello this is an infof benchmark %v %v %v\n", n, 1, 2) + } +} + +func BenchmarkInfoln(b *testing.B) { + SetupTest() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + Infoln(Global, "Hello this is an infoln benchmark") + } +} + +func TestNewLogEvent(t *testing.T) { + w := &bytes.Buffer{} + logger.newLogEvent("out", "header", w) + + if w.String() == "" { + t.Error("newLogEvent() failed expected output got empty string") + } + + err := logger.newLogEvent("out", "header", nil) + if err == nil { + t.Error("Error expected with output is set to nil") + } +} + +func TestInfo(t *testing.T) { + w := &bytes.Buffer{} + + tempSL := subLogger{ + "testymctestalot", + splitLevel("INFO|WARN|DEBUG|ERROR"), + w, + } + + Info(&tempSL, "Hello") + + if w.String() == "" { + t.Error("expected Info() to write output to buffer") + } + + tempSL.output = nil + w.Reset() + + SetLevel("testymctestalot", "INFO") + Debug(&tempSL, "HelloHello") + + if w.String() != "" { + t.Error("Expected output buffer to be empty but Debug wrote to output") } } diff --git a/logger/logger_types.go b/logger/logger_types.go index 970d8900..5ec424da 100644 --- a/logger/logger_types.go +++ b/logger/logger_types.go @@ -2,34 +2,88 @@ package logger import ( "io" - "log" - "os" + "sync" ) -// Logging struct that holds all user configurable options for the logger -type Logging struct { - Enabled *bool `json:"enabled,omitempty"` - File string `json:"file"` - ColourOutput bool `json:"colour"` - ColourOutputOverride bool `json:"colourOverride,omitempty"` - Level string `json:"level"` - Rotate bool `json:"rotate"` +const timestampFormat = " 02/01/2006 15:04:05 " + +const spacer = "|" + +// Config holds configuration settings loaded from bot config +type Config struct { + Enabled *bool `json:"enabled"` + SubLoggerConfig + LoggerFileConfig *loggerFileConfig `json:"fileSettings,omitempty"` + AdvancedSettings advancedSettings `json:"advancedSettings"` + SubLoggers []SubLoggerConfig `json:"subloggers,omitempty"` +} + +type advancedSettings struct { + Spacer string `json:"spacer"` + TimeStampFormat string `json:"timeStampFormat"` + Headers headers `json:"headers"` +} + +type headers struct { + Info string `json:"info"` + Warn string `json:"warn"` + Debug string `json:"debug"` + Error string `json:"error"` +} + +// SubLoggerConfig holds sub logger configuration settings loaded from bot config +type SubLoggerConfig struct { + Name string `json:"name,omitempty"` + Level string `json:"level"` + Output string `json:"output"` +} + +type loggerFileConfig struct { + FileName string `json:"filename,omitempty"` + Rotate *bool `json:"rotate,omitempty"` + MaxSize int64 `json:"maxsize,omitempty"` +} + +// Logger each instance of logger settings +type Logger struct { + Timestamp string + InfoHeader, ErrorHeader, DebugHeader, WarnHeader string + Spacer string +} + +type Levels struct { + Info, Debug, Warn, Error bool +} + +type subLogger struct { + name string + Levels + output io.Writer +} + +type LogEvent struct { + data []byte + output io.Writer +} + +type multiWriter struct { + writers []io.Writer + mu sync.Mutex } var ( - debugLogger *log.Logger - infoLogger *log.Logger - warnLogger *log.Logger - errorLogger *log.Logger - fatalLogger *log.Logger + logger = &Logger{} + FileLoggingConfiguredCorrectly bool + GlobalLogConfig = &Config{} // GlobalLogConfig hold global configuration options for logger + GlobalLogFile = &Rotate{} - logFileHandle *os.File + eventPool = &sync.Pool{ + New: func() interface{} { + return &LogEvent{ + data: make([]byte, 0, 80), + } + }, + } - logOutput io.Writer - - // LogPath location to store logs in LogPath string - - // Logger create a pointer to Logging struct for holding data - Logger = &Logging{} ) diff --git a/logger/loggers.go b/logger/loggers.go index 7e6c1f4f..e8fdfb45 100644 --- a/logger/loggers.go +++ b/logger/loggers.go @@ -2,79 +2,160 @@ package logger import ( "fmt" - "log" - "os" ) -// Info handler takes any input returns unformatted output to infoLogger writer -func Info(v ...interface{}) { - infoLogger.Print(v...) +// Info takes a pointer subLogger struct and string sends to newLogEvent +func Info(sl *subLogger, data string) { + if sl == nil { + return + } + + if !sl.Info { + return + } + + logger.newLogEvent(data, logger.InfoHeader, sl.output) } -// Infof handler takes any input infoLogger returns formatted output to infoLogger writer -func Infof(data string, v ...interface{}) { - infoLogger.Printf(data, v...) +// Infoln takes a pointer subLogger struct and interface sends to newLogEvent +func Infoln(sl *subLogger, v ...interface{}) { + if sl == nil { + return + } + + if !sl.Info { + return + } + + logger.newLogEvent(fmt.Sprintln(v...), logger.InfoHeader, sl.output) } -// Infoln handler takes any input infoLogger returns formatted output to infoLogger writer -func Infoln(v ...interface{}) { - infoLogger.Println(v...) +// Infof takes a pointer subLogger struct, string & interface formats and sends to Info() +func Infof(sl *subLogger, data string, v ...interface{}) { + if sl == nil { + return + } + + if !sl.Info { + return + } + + Info(sl, fmt.Sprintf(data, v...)) } -// Print aliased to Standard log.Print -var Print = log.Print +// Debug takes a pointer subLogger struct and string sends to multiwriter +func Debug(sl *subLogger, data string) { + if sl == nil { + return + } -// Printf aliased to Standard log.Printf -var Printf = log.Printf + if !sl.Debug { + return + } -// Println aliased to Standard log.Println -var Println = log.Println - -// Debug handler takes any input returns unformatted output to infoLogger writer -func Debug(v ...interface{}) { - debugLogger.Print(v...) + logger.newLogEvent(data, logger.DebugHeader, sl.output) } -// Debugf handler takes any input infoLogger returns formatted output to infoLogger writer -func Debugf(data string, v ...interface{}) { - debugLogger.Printf(data, v...) +// Debugln takes a pointer subLogger struct, string and interface sends to newLogEvent +func Debugln(sl *subLogger, v ...interface{}) { + if sl == nil { + return + } + + if !sl.Debug { + return + } + + logger.newLogEvent(fmt.Sprintln(v...), logger.DebugHeader, sl.output) } -// Debugln handler takes any input infoLogger returns formatted output to infoLogger writer -func Debugln(v ...interface{}) { - debugLogger.Println(v...) +// Debugf takes a pointer subLogger struct, string & interface formats and sends to Info() +func Debugf(sl *subLogger, data string, v ...interface{}) { + if sl == nil { + return + } + + if !sl.Debug { + return + } + + Debug(sl, fmt.Sprintf(data, v...)) } -// Warn handler takes any input returns unformatted output to warnLogger writer -func Warn(v ...interface{}) { - warnLogger.Print(v...) +// Warn takes a pointer subLogger struct & string and sends to newLogEvent() +func Warn(sl *subLogger, data string) { + if sl == nil { + return + } + + if !sl.Warn { + return + } + + logger.newLogEvent(data, logger.WarnHeader, sl.output) } -// Warnf handler takes any input returns unformatted output to warnLogger writer -func Warnf(data string, v ...interface{}) { - warnLogger.Printf(data, v...) +// Warnln takes a pointer subLogger struct & interface formats and sends to newLogEvent() +func Warnln(sl *subLogger, v ...interface{}) { + if sl == nil { + return + } + + if !sl.Warn { + return + } + + logger.newLogEvent(fmt.Sprintln(v...), logger.WarnHeader, sl.output) } -// Error handler takes any input returns unformatted output to errorLogger writer -func Error(v ...interface{}) { - errorLogger.Print(v...) +// Warnf takes a pointer subLogger struct, string & interface formats and sends to Warn() +func Warnf(sl *subLogger, data string, v ...interface{}) { + if sl == nil { + return + } + + if !sl.Warn { + return + } + + Warn(sl, fmt.Sprintf(data, v...)) } -// Errorf handler takes any input returns unformatted output to errorLogger writer -func Errorf(data string, v ...interface{}) { - errorLogger.Printf(data, v...) +// Error takes a pointer subLogger struct & interface formats and sends to newLogEvent() +func Error(sl *subLogger, data ...interface{}) { + if sl == nil { + return + } + + if !sl.Error { + return + } + + logger.newLogEvent(fmt.Sprint(data...), logger.ErrorHeader, sl.output) } -// Fatal handler takes any input returns unformatted output to fatalLogger writer -func Fatal(v ...interface{}) { - // Send to Output instead of Fatal to allow us to increase the output depth by 1 to make sure the correct file is displayed - fatalLogger.Output(2, fmt.Sprint(v...)) - os.Exit(1) +// Errorln takes a pointer subLogger struct, string & interface formats and sends to newLogEvent() +func Errorln(sl *subLogger, v ...interface{}) { + if sl == nil { + return + } + + if !sl.Error { + return + } + + logger.newLogEvent(fmt.Sprintln(v...), logger.ErrorHeader, sl.output) } -// Fatalf handler takes any input returns unformatted output to fatalLogger writer -func Fatalf(data string, v ...interface{}) { - // Send to Output instead of Fatal to allow us to increase the output depth by 1 to make sure the correct file is displayed - fatalLogger.Output(2, fmt.Sprintf(data, v...)) - os.Exit(1) +// Errorf takes a pointer subLogger struct, string & interface formats and sends to Debug() +func Errorf(sl *subLogger, data string, v ...interface{}) { + if sl == nil { + return + } + + if !sl.Error { + return + } + + Error(sl, fmt.Sprintf(data, v...)) } diff --git a/logger/sublogger_types.go b/logger/sublogger_types.go new file mode 100644 index 00000000..f49ae474 --- /dev/null +++ b/logger/sublogger_types.go @@ -0,0 +1,23 @@ +package logger + +var ( + subLoggers = map[string]*subLogger{} + + Global *subLogger + ConnectionMgr *subLogger + CommunicationMgr *subLogger + ConfigMgr *subLogger + OrderMgr *subLogger + PortfolioMgr *subLogger + SyncMgr *subLogger + TimeMgr *subLogger + WebsocketMgr *subLogger + EventMgr *subLogger + + ExchangeSys *subLogger + GRPCSys *subLogger + RESTSys *subLogger + + Ticker *subLogger + OrderBook *subLogger +) diff --git a/main.go b/main.go index 5ffba2af..7550599e 100644 --- a/main.go +++ b/main.go @@ -18,7 +18,8 @@ import ( func main() { defaultPath, err := config.GetFilePath("") if err != nil { - log.Fatal(err) + log.Errorln(log.Global, err) + os.Exit(1) } // Handle flags @@ -89,7 +90,8 @@ func main() { engine.Bot, err = engine.NewFromSettings(&settings) if engine.Bot == nil || err != nil { - log.Fatalf("Unable to initialise bot engine. Err: %s", err) + log.Errorf(log.Global, "Unable to initialise bot engine. Err: %s\n", err) + os.Exit(1) } engine.PrintSettings(&engine.Bot.Settings) diff --git a/ntpclient/ntpclient.go b/ntpclient/ntpclient.go index 99015cbd..ec52072b 100644 --- a/ntpclient/ntpclient.go +++ b/ntpclient/ntpclient.go @@ -32,7 +32,7 @@ func NTPClient(pool []string) (time.Time, error) { for i := range pool { con, err := net.Dial("udp", pool[i]) if err != nil { - log.Warnf("Unable to connect to hosts %v attempting next", pool[i]) + log.Warnf(log.TimeMgr, "Unable to connect to hosts %v attempting next", pool[i]) continue } diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index e8862592..9e8002c3 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -416,7 +416,7 @@ func (p *Base) Seed(port Base) { // StartPortfolioWatcher observes the portfolio object func StartPortfolioWatcher() { addrCount := len(Portfolio.Addresses) - log.Debugf( + log.Debugf(log.PortfolioMgr, "PortfolioWatcher started: Have %d entries in portfolio.\n", addrCount, ) for { @@ -424,7 +424,7 @@ func StartPortfolioWatcher() { for key, value := range data { success := Portfolio.UpdatePortfolio(value, key) if success { - log.Debugf( + log.Debugf(log.PortfolioMgr, "PortfolioWatcher: Successfully updated address balance for %s address(es) %s\n", key, value, ) diff --git a/testdata/configtest.json b/testdata/configtest.json index 8ccdff17..786f0365 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -1349,4 +1349,4 @@ "checkInterval": 1000000000 }, "fiatDispayCurrency": "" -} \ No newline at end of file +} From e8c9a9a2a49a78eb7c60c8fb5ae63116f8ce6426 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 6 Aug 2019 16:08:52 +1000 Subject: [PATCH 20/71] Minor linter/test fixes after merging master --- engine/rpcserver.go | 2 ++ exchanges/huobi/huobi_test.go | 2 +- exchanges/huobihadax/huobihadax_test.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 8de40aed..07b121e8 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -828,6 +828,7 @@ func (s *RPCServer) WithdrawFiatFunds(ctx context.Context, r *gctrpc.WithdrawCur return &gctrpc.WithdrawResponse{}, common.ErrNotYetImplemented } +// GetLoggerDetails returns a loggers details func (s *RPCServer) GetLoggerDetails(ctx context.Context, r *gctrpc.GetLoggerDetailsRequest) (*gctrpc.GetLoggerDetailsResponse, error) { levels, err := log.Level(r.Logger) if err != nil { @@ -842,6 +843,7 @@ func (s *RPCServer) GetLoggerDetails(ctx context.Context, r *gctrpc.GetLoggerDet }, nil } +// SetLoggerDetails sets a loggers details func (s *RPCServer) SetLoggerDetails(ctx context.Context, r *gctrpc.SetLoggerDetailsRequest) (*gctrpc.GetLoggerDetailsResponse, error) { levels, err := log.SetLevel(r.Logger, r.Level) if err != nil { diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index d83134d7..0b2a9624 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -214,7 +214,7 @@ func TestGetAccountBalance(t *testing.T) { func TestGetAggregatedBalance(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } diff --git a/exchanges/huobihadax/huobihadax_test.go b/exchanges/huobihadax/huobihadax_test.go index d273aa4e..5ed35194 100644 --- a/exchanges/huobihadax/huobihadax_test.go +++ b/exchanges/huobihadax/huobihadax_test.go @@ -258,7 +258,7 @@ func TestGetAccountBalance(t *testing.T) { func TestGetAggregatedBalance(t *testing.T) { t.Parallel() - if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" { + if !h.ValidateAPICredentials() { t.Skip() } From b178dd2c1d3d7b5233aa5e8d3183f8e3ac8dbf95 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 8 Aug 2019 23:02:27 -0700 Subject: [PATCH 21/71] Linter fixes --- engine/helpers_test.go | 2 +- exchanges/okcoin/okcoin_test.go | 2 +- exchanges/okex/okex_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/helpers_test.go b/engine/helpers_test.go index 889384fe..838275a3 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -4,11 +4,11 @@ import ( "testing" "time" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/stats" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 1f7c8c6d..7d93ef82 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -8,11 +8,11 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/okgroup" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/wshandler" diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index f946ede8..a8dd1a80 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -9,11 +9,11 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/okgroup" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/wshandler" From 0c76789b0d84f9a2395fa1243bbf91302f7ac36c Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 20 Aug 2019 16:35:06 +1000 Subject: [PATCH 22/71] Database interface & auditing feature (#332) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added audit manager * Basic database DOA setup * Added base config file * added sqlite support and creation of schema * added basic tests and config entry * corrected issues of database is disabled * fixed path for test * WIP * Added tests fixed config checking * reverted files back to upstream * reverted go.mod files * no more test test test * removed local testing details for psql * hello * added comments * increased ping to 30 seconds * renamed database table and added additional condition around test * removed database test details * goimport ran on all files * WIP * first attempt at migration * fixes for migration system * Migration system logger interface implemented * fixes to print functions * added write pooling pass * gofmt :D * formatted imports correctly * removed old code * added creation of migration * gofmt * :D Hello * ❌ 🏎️ * maybe one day i will remember to revert go mod files * checked err return condition correctly * first changes for PR feedback * code clean up * protect Connected with RWmutex & event with mutex * : D * we can just pretend like it never happened * MOved migrations back to source directory and added README * readme formatting update * Addd command line override for datadir * use correct var when creating a migration and confirm folder is created * Check if database version is newer than latest migration and also you know make migrations work..... * uses filepath instead of manual path to use correct path seperator * Add connection message and lower timeout * Added support for sslmode for psql * no longer force Close of database instead allow driver to maage * Added closer func to test output * sslmode added to example config --- Makefile | 7 +- cmd/dbmigrate/main.go | 168 ++++++++++++++++ cmd/documentation/README.md | 10 +- .../cmd_templates/documentation.tmpl | 10 +- config/config.go | 29 +++ config/config_types.go | 2 + config_example.json | 12 ++ currency/conversion_test.go | 2 +- database/README.md | 76 ++++++++ database/db_types.go | 38 ++++ database/drivers/drivers_type.go | 11 ++ database/drivers/postgres/postgresql.go | 41 ++++ database/drivers/sqlite/sqlite.go | 28 +++ database/migration/migrate.go | 180 ++++++++++++++++++ database/migration/migrate_type.go | 37 ++++ database/migration/migration_logger.go | 25 +++ .../1565657999_create_audit_event_table.sql | 11 ++ database/models/audit.go | 8 + database/repository/audit/audit.go | 62 ++++++ database/repository/audit/postgres/audit.go | 52 +++++ database/repository/audit/sqlite/audit.go | 53 ++++++ database/tests/audit_test.go | 126 ++++++++++++ database/tests/db_test.go | 148 ++++++++++++++ engine/database.go | 144 ++++++++++++++ engine/engine.go | 15 ++ engine/engine_types.go | 10 +- exchanges/wshandler/websocket_test.go | 4 +- logger/logger_setup.go | 1 + logger/sublogger_types.go | 1 + main.go | 3 + 30 files changed, 1295 insertions(+), 19 deletions(-) create mode 100644 cmd/dbmigrate/main.go create mode 100644 database/README.md create mode 100644 database/db_types.go create mode 100644 database/drivers/drivers_type.go create mode 100644 database/drivers/postgres/postgresql.go create mode 100644 database/drivers/sqlite/sqlite.go create mode 100644 database/migration/migrate.go create mode 100644 database/migration/migrate_type.go create mode 100644 database/migration/migration_logger.go create mode 100755 database/migration/migrations/1565657999_create_audit_event_table.sql create mode 100644 database/models/audit.go create mode 100644 database/repository/audit/audit.go create mode 100644 database/repository/audit/postgres/audit.go create mode 100644 database/repository/audit/sqlite/audit.go create mode 100644 database/tests/audit_test.go create mode 100644 database/tests/db_test.go create mode 100644 engine/database.go diff --git a/Makefile b/Makefile index 6a34f44e..77f66545 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,10 @@ update_deps: .PHONY: profile_heap profile_heap: go tool pprof -http "localhost:$(GCTPROFILERLISTENPORT)" 'http://localhost:$(GCTLISTENPORT)/debug/pprof/heap' - + .PHONY: profile_cpu profile_cpu: - go tool pprof -http "localhost:$(GCTPROFILERLISTENPORT)" 'http://localhost:$(GCTLISTENPORT)/debug/pprof/profile' \ No newline at end of file + go tool pprof -http "localhost:$(GCTPROFILERLISTENPORT)" 'http://localhost:$(GCTLISTENPORT)/debug/pprof/profile' + +db_migrate: + go run ./cmd/dbmigrate diff --git a/cmd/dbmigrate/main.go b/cmd/dbmigrate/main.go new file mode 100644 index 00000000..bff65642 --- /dev/null +++ b/cmd/dbmigrate/main.go @@ -0,0 +1,168 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "runtime" + "strconv" + "time" + + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/core" + "github.com/thrasher-corp/gocryptotrader/database" + db "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres" + dbsqlite3 "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite" + mg "github.com/thrasher-corp/gocryptotrader/database/migration" +) + +var ( + dbConn *database.Database + configFile string + defaultDataDir string + createMigration string + migrationDir string +) + +var defaultMigration = []byte(`-- up +-- down +`) + +func openDbConnection(driver string) (err error) { + if driver == "postgres" { + dbConn, err = db.Connect() + if err != nil { + return fmt.Errorf("database failed to connect: %v Some features that utilise a database will be unavailable", err) + } + + dbConn.SQL.SetMaxOpenConns(2) + dbConn.SQL.SetMaxIdleConns(1) + dbConn.SQL.SetConnMaxLifetime(time.Hour) + + } else if driver == "sqlite" { + dbConn, err = dbsqlite3.Connect() + + if err != nil { + return fmt.Errorf("database failed to connect: %v Some features that utilise a database will be unavailable", err) + } + } + return nil +} + +type tmpLogger struct{} + +// Printf implantation of migration Logger interface +// Passes directly to Printf from fmt package +func (t tmpLogger) Printf(format string, v ...interface{}) { + fmt.Printf(format, v...) +} + +// Println implantation of migration Logger interface +// Passes directly to Println from fmt package +func (t tmpLogger) Println(v ...interface{}) { + fmt.Println(v...) +} + +// Errorf implantation of migration Logger interface +// Passes directly to Printf from fmt package +func (t tmpLogger) Errorf(format string, v ...interface{}) { + fmt.Printf(format, v...) +} + +func main() { + fmt.Println("GoCryptoTrader database migration tool") + fmt.Println(core.Copyright) + fmt.Println() + + defaultPath, err := config.GetFilePath("") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + flag.StringVar(&configFile, "config", defaultPath, "config file to load") + flag.StringVar(&defaultDataDir, "datadir", common.GetDefaultDataDir(runtime.GOOS), "default data directory for GoCryptoTrader files") + flag.StringVar(&createMigration, "create", "", "create a new empty migration file") + flag.StringVar(&migrationDir, "migrationdir", mg.MigrationDir, "override migration folder") + + flag.Parse() + + if createMigration != "" { + err = newMigrationFile(createMigration) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("Migration created successfully") + os.Exit(0) + } + + tempLogger := tmpLogger{} + + temp := mg.Migrator{ + Log: tempLogger, + } + + err = temp.LoadMigrations() + if err != nil { + fmt.Println(err) + os.Exit(0) + } + + conf := config.GetConfig() + + err = conf.LoadConfig(configFile) + if err != nil { + fmt.Println(err) + os.Exit(0) + } + + err = openDbConnection(conf.Database.Driver) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + fmt.Printf("Connected to: %s\n", conf.Database.Host) + + temp.Conn = dbConn + + err = temp.RunMigration() + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if dbConn.SQL != nil { + err = dbConn.SQL.Close() + if err != nil { + fmt.Println(err) + } + } +} + +func newMigrationFile(filename string) error { + curTime := strconv.FormatInt(time.Now().Unix(), 10) + path := filepath.Join(migrationDir, curTime+"_"+filename+".sql") + err := common.CreateDir(migrationDir) + if err != nil { + return err + } + fmt.Printf("Creating new empty migration: %v\n", path) + f, err := os.Create(path) + + if err != nil { + return err + } + + _, err = f.Write(defaultMigration) + + if err != nil { + return err + } + + return f.Close() +} diff --git a/cmd/documentation/README.md b/cmd/documentation/README.md index 8e3bd0c0..ea9c5e4b 100644 --- a/cmd/documentation/README.md +++ b/cmd/documentation/README.md @@ -3,10 +3,10 @@ -[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader) +[![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader) [![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) [![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/cmd/documentation) -[![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) @@ -22,7 +22,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader #### This tool allows for the generation of new documentation through templating -From the `gocryptotrader/cmd/documentation/` folder, using the go command: **go run documentation.go** this will auto-generate and regenerate documentation across the **GoCryptoTrader** code base. +From the `gocryptotrader/cmd/documentation/` folder, using the go command: **go run documentation.go** this will auto-generate and regenerate documentation across the **GoCryptoTrader** code base. >Using the -v command will, ie **go run documentation.go -v** put the tool into verbose mode allowing you to see what is happening with a little more depth. Be aware, this tool will: @@ -36,7 +36,7 @@ Be aware, this tool will: ```json { -"githubRepo": "https://api.github.com/repos/thrasher-/gocryptotrader", This is your current repo +"githubRepo": "https://api.github.com/repos/thrasher-corp/gocryptotrader", This is your current repo "exclusionList": { This allows for excluded directories and files "Files": null, "Directories": [ @@ -55,7 +55,7 @@ Be aware, this tool will: >place a new template **example_file.tmpl** located in the current gocryptotrader/cmd/documentation/ folder; when the documentation tool finishes it will give you the define template associated name e.g. ``Template not found for path ../../cmd/documentation create new template with \{\{define "cmd documentation" -\}\} TEMPLATE HERE \{\{end}}`` so you can replace the below example with ``\{\{define "cmd documentation" -}}`` ``` -\{\{\define "example_definition_created_by_documentation_tool" -}} +\{\{\define "example_definition_created_by_documentation_tool" -}} \{\{\template "header" .}} ## Current Features for documentation diff --git a/cmd/documentation/cmd_templates/documentation.tmpl b/cmd/documentation/cmd_templates/documentation.tmpl index 24c48537..1f6d4956 100644 --- a/cmd/documentation/cmd_templates/documentation.tmpl +++ b/cmd/documentation/cmd_templates/documentation.tmpl @@ -1,10 +1,10 @@ -{{define "cmd documentation" -}} +{{define "cmd documentation" -}} {{template "header" .}} ## Current Features for {{.Name}} #### This tool allows for the generation of new documentation through templating -From the `gocryptotrader/cmd/documentation/` folder, using the go command: **go run documentation.go** this will auto-generate and regenerate documentation across the **GoCryptoTrader** code base. +From the `gocryptotrader/cmd/documentation/` folder, using the go command: **go run documentation.go** this will auto-generate and regenerate documentation across the **GoCryptoTrader** code base. >Using the -v command will, ie **go run documentation.go -v** put the tool into verbose mode allowing you to see what is happening with a little more depth. Be aware, this tool will: @@ -18,7 +18,7 @@ Be aware, this tool will: ```json { -"githubRepo": "https://api.github.com/repos/thrasher-/gocryptotrader", This is your current repo +"githubRepo": "https://api.github.com/repos/thrasher-corp/gocryptotrader", This is your current repo "exclusionList": { This allows for excluded directories and files "Files": null, "Directories": [ @@ -37,7 +37,7 @@ Be aware, this tool will: >place a new template **example_file.tmpl** located in the current gocryptotrader/cmd/documentation/ folder; when the documentation tool finishes it will give you the define template associated name e.g. ``Template not found for path ../../cmd/documentation create new template with \{\{define "cmd documentation" -\}\} TEMPLATE HERE \{\{end}}`` so you can replace the below example with ``\{\{define "cmd documentation" -}}`` ``` -\{\{\define "example_definition_created_by_documentation_tool" -}} +\{\{\define "example_definition_created_by_documentation_tool" -}} \{\{\template "header" .}} ## Current Features for {{.Name}} @@ -60,4 +60,4 @@ upper := strings.ToUpper(testString) ### Please click GoDocs chevron above to view current GoDoc information for this package {{template "contributions"}} {{template "donations"}} -{{end}} \ No newline at end of file +{{end}} diff --git a/config/config.go b/config/config.go index 5e5e2618..085ddf69 100644 --- a/config/config.go +++ b/config/config.go @@ -22,6 +22,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/database" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -1293,6 +1294,29 @@ func (c *Config) CheckLoggerConfig() error { return nil } +func (c *Config) checkDatabaseConfig() error { + m.Lock() + defer m.Unlock() + + if !common.StringDataCompare(database.SupportedDrivers, c.Database.Driver) { + c.Database.Enabled = false + return fmt.Errorf("unsupported database driver %v database disabled", c.Database.Driver) + } + + if c.Database.Driver == "sqlite" { + databaseDir := filepath.Join(common.GetDefaultDataDir(runtime.GOOS), "/database") + err := common.CreateDir(databaseDir) + if err != nil { + return err + } + database.Conn.DataPath = databaseDir + } + + database.Conn.Config = &c.Database + + return nil +} + // CheckNTPConfig checks for missing or incorrectly configured NTPClient and recreates with known safe defaults func (c *Config) CheckNTPConfig() { m.Lock() @@ -1608,6 +1632,11 @@ func (c *Config) CheckConfig() error { log.Errorf(log.ConfigMgr, "Failed to configure logger, some logging features unavailable: %s\n", err) } + err = c.checkDatabaseConfig() + if err != nil { + log.Errorf(log.DatabaseMgr, "Failed to configure database: %v", err) + } + err = c.CheckExchangeConfigValues() if err != nil { return fmt.Errorf(ErrCheckingConfigValues, err) diff --git a/config/config_types.go b/config/config_types.go index 8edca253..b005d0c1 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -5,6 +5,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/database" log "github.com/thrasher-corp/gocryptotrader/logger" "github.com/thrasher-corp/gocryptotrader/portfolio" ) @@ -16,6 +17,7 @@ type Config struct { Name string `json:"name"` EncryptConfig int `json:"encryptConfig"` GlobalHTTPTimeout time.Duration `json:"globalHTTPTimeout"` + Database database.Config `json:"database"` Logging log.Config `json:"logging"` ConnectionMonitor ConnectionMonitorConfig `json:"connectionMonitor"` Profiler ProfilerConfig `json:"profiler"` diff --git a/config_example.json b/config_example.json index 2c958f2c..364690f6 100644 --- a/config_example.json +++ b/config_example.json @@ -2,6 +2,18 @@ "name": "Skynet", "encryptConfig": 0, "globalHTTPTimeout": 15000000000, + "database": { + "enabled": false, + "driver": "sqlite", + "connectionDetails": { + "Host": "", + "Port": 0, + "Username": "", + "Password": "", + "Database": "", + "SSLMode": "" + } + }, "logging": { "enabled": true, "level": "INFO|WARN|DEBUG|ERROR", diff --git a/currency/conversion_test.go b/currency/conversion_test.go index da61200b..22774a0a 100644 --- a/currency/conversion_test.go +++ b/currency/conversion_test.go @@ -146,7 +146,7 @@ func TestConversionsRatesSystem(t *testing.T) { err = SuperDuperConversionSystem.Update(nil) if err == nil { - t.Fatal("Test Failed - Update() error cannnot be nil") + t.Fatal("Test Failed - Update() error cannot be nil") } if !SuperDuperConversionSystem.HasData() { diff --git a/database/README.md b/database/README.md new file mode 100644 index 00000000..b2a74905 --- /dev/null +++ b/database/README.md @@ -0,0 +1,76 @@ +# GoCryptoTrader package Database + + + + +[![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/portfolio) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + + +This database package is part of the GoCryptoTrader codebase. + +## This is still in active development + +You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY) + +## Current Features for database package + ++ Establishes & Maintains database connection across program life cycle ++ Multiple database support via simple repository model ++ Run migration on connection to assure database is at correct version + +## How to use + +##### To Manually migrate to the latest database you can run the "dbmigrate" helper in the cmd folder + +This will parse and run all migration files in your $GoCryptoTrader/database/migrations + +_This is also run from the bot when a connection is established to the database_ + +```sh +go run ./cmd/dbmigrate +``` +A Makefile command has also been added for this +```sh +make db_migrate +``` + +##### To create a new migrate file you can also run the same command with the -create "migration name" flag + +```sh +go run ./cmd/dbmigrate -create "alter some table" +``` + +##### Adding a new model + ++ Create Model in github.com/thrasher-corp/gocryptotrader/database/models directory + +##### Adding a Repository ++ Create Repository directory in github.com/thrasher-corp/gocryptotrader/database/repository/ ++ Create a base Repository interface with any required Methods ++ Create a per driver implementation of the Repository that implement all required methods to match the interface + +## Contribution + +Please feel free to submit any pull requests or suggest any desired features to be added. + +When submitting a PR, please abide by our coding guidelines: + ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. + +## Donations + + + +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: + +***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** + diff --git a/database/db_types.go b/database/db_types.go new file mode 100644 index 00000000..596106fe --- /dev/null +++ b/database/db_types.go @@ -0,0 +1,38 @@ +package database + +import ( + "errors" + "sync" + + "github.com/jmoiron/sqlx" + "github.com/thrasher-corp/gocryptotrader/database/drivers" +) + +// Database holds a pointer to sql connection, DataPath which is used for file based databases +// and a pointer to a Config struct +type Database struct { + Config *Config + DataPath string + SQL *sqlx.DB + + Connected bool + Mu sync.RWMutex +} + +// Config holds connection information about the database what the driver type is and if its enabled or not +type Config struct { + Enabled bool `json:"enabled"` + Driver string `json:"driver"` + drivers.ConnectionDetails `json:"connectionDetails"` +} + +// Conn is a global copy of Database{} struct +var Conn = &Database{} + +var ( + // ErrNoDatabaseProvided error to display when no database is provided + ErrNoDatabaseProvided = errors.New("no database provided") + + // SupportedDrivers slice of supported database driver types + SupportedDrivers = []string{"sqlite", "postgres"} +) diff --git a/database/drivers/drivers_type.go b/database/drivers/drivers_type.go new file mode 100644 index 00000000..ec0498a6 --- /dev/null +++ b/database/drivers/drivers_type.go @@ -0,0 +1,11 @@ +package drivers + +// ConnectionDetails holds DSN information +type ConnectionDetails struct { + Host string + Port uint16 + Username string + Password string + Database string + SSLMode string +} diff --git a/database/drivers/postgres/postgresql.go b/database/drivers/postgres/postgresql.go new file mode 100644 index 00000000..3041f52d --- /dev/null +++ b/database/drivers/postgres/postgresql.go @@ -0,0 +1,41 @@ +package postgres + +import ( + "fmt" + "time" + + "github.com/jackc/pgx" + "github.com/jackc/pgx/stdlib" + "github.com/jmoiron/sqlx" + "github.com/thrasher-corp/gocryptotrader/database" +) + +// Connect establishes a connection pool to the database +func Connect() (*database.Database, error) { + configDSN := fmt.Sprintf("host=%s port=%d user=%s password=%s database=%s sslmode=%s", + database.Conn.Config.Host, + database.Conn.Config.Port, + database.Conn.Config.Username, + database.Conn.Config.Password, + database.Conn.Config.Database, + database.Conn.Config.SSLMode) + + connConfig, err := pgx.ParseDSN(configDSN) + if err != nil { + return nil, err + } + + connPool, err := pgx.NewConnPool(pgx.ConnPoolConfig{ + ConnConfig: connConfig, + AfterConnect: nil, + MaxConnections: 20, + AcquireTimeout: 30 * time.Second, + }) + if err != nil { + return nil, err + } + + sqlxDB := stdlib.OpenDBFromPool(connPool) + database.Conn.SQL = sqlx.NewDb(sqlxDB, "pgx") + return database.Conn, nil +} diff --git a/database/drivers/sqlite/sqlite.go b/database/drivers/sqlite/sqlite.go new file mode 100644 index 00000000..6dad1161 --- /dev/null +++ b/database/drivers/sqlite/sqlite.go @@ -0,0 +1,28 @@ +package sqlite + +import ( + "path/filepath" + + "github.com/jmoiron/sqlx" + // import sqlite3 driver + _ "github.com/mattn/go-sqlite3" + "github.com/thrasher-corp/gocryptotrader/database" +) + +// Connect creates a connection to the entered database +// With SQLite the database is not created until first read/write + +func Connect() (*database.Database, error) { + if database.Conn.Config.Database == "" { + return nil, database.ErrNoDatabaseProvided + } + + databaseFullLocation := filepath.Join(database.Conn.DataPath, database.Conn.Config.Database) + dbConn, err := sqlx.Open("sqlite3", databaseFullLocation) + if err != nil { + return nil, err + } + + database.Conn.SQL = dbConn + return database.Conn, nil +} diff --git a/database/migration/migrate.go b/database/migration/migrate.go new file mode 100644 index 00000000..ae47e2d8 --- /dev/null +++ b/database/migration/migrate.go @@ -0,0 +1,180 @@ +package migrations + +import ( + "bytes" + "database/sql" + "errors" + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" +) + +// LoadMigrations will load all migrations in the ./database/migration/migrations folder +func (m *Migrator) LoadMigrations() error { + flag.Visit(func(f *flag.Flag) { + if f.Name == "migrationdir" { + MigrationDir = flag.Lookup("migrationdir").Value.String() + } + }) + + m.Log.Printf("Using migration folder %s\n", MigrationDir) + + migration, err := filepath.Glob(MigrationDir + "/*.sql") + + if err != nil { + return errors.New("failed to load migrations") + } + + if len(migration) == 0 { + return errors.New("no migration files found") + } + + sort.Strings(migration) + + for x := range migration { + err = m.loadMigration(migration[x]) + if err != nil { + return err + } + } + return nil +} + +func (m *Migrator) loadMigration(migration string) error { + file, err := os.Open(migration) + if err != nil { + return err + } + + fileData := strings.Trim(file.Name(), MigrationDir) + fileSeq := strings.Split(fileData, "_") + seq, _ := strconv.Atoi(fileSeq[0]) + + b, err := ioutil.ReadAll(file) + if err != nil { + return err + } + + up := bytes.Split(b, []byte("-- up")) + + if len(up) == 1 { + return fmt.Errorf("invalid migration file %v", file.Name()) + } + + down := strings.Split(string(up[1]), "-- down") + + temp := Migration{ + Sequence: seq, + UpSQL: down[0], + DownSQL: down[1], + } + + m.Migrations = append(m.Migrations, temp) + + return nil +} + +// RunMigration attempts to run current migrations against a database +func (m *Migrator) RunMigration() (err error) { + v, err := m.getCurrentVersion() + if err != nil { + return + } + m.Log.Printf("Current database version: %v\n", v) + + latestSeq := m.Migrations[len(m.Migrations)-1].Sequence + + if v > latestSeq { + return errors.New("current database version is greater than latest migration halting further migrations") + } + + if v == latestSeq { + m.Log.Println("no migrations to be run") + return + } + + tx, err := m.Conn.SQL.Begin() + if err != nil { + return + } + + for y := 0; y < len(m.Migrations); y++ { + if m.Migrations[y].Sequence <= v { + continue + } + + err = m.txBegin(tx, m.checkConvert(m.Migrations[y].UpSQL)) + if err != nil { + return tx.Rollback() + } + + _, err = tx.Exec("update version set version=$1", m.Migrations[y].Sequence) + if err != nil { + return tx.Rollback() + } + } + + err = tx.Commit() + if err != nil { + return tx.Rollback() + } + + m.Log.Println("Migration completed") + m.Log.Printf("New database version: %v\n", latestSeq) + return nil +} + +func (m *Migrator) txBegin(tx *sql.Tx, input string) error { + _, err := tx.Exec(input) + if err != nil { + m.Log.Errorf("%v", err) + return tx.Rollback() + } + return nil +} + +func (m *Migrator) getCurrentVersion() (v int, err error) { + err = m.checkVersionTableExists() + if err != nil { + return + } + err = m.Conn.SQL.QueryRow("select version from version").Scan(&v) + return +} + +func (m *Migrator) checkVersionTableExists() error { + query := ` + CREATE TABLE IF NOT EXISTS version( + version int not null + ); + + INSERT INTO version SELECT 0 WHERE 0=(SELECT COUNT(*) from version); +` + + _, err := m.Conn.SQL.Exec(m.checkConvert(query)) + if err != nil { + return err + } + return nil +} + +func (m *Migrator) checkConvert(input string) string { + if m.Conn.Config.Driver != "sqlite" { + return input + } + + // Common PSQL -> SQLITE conversion + // TODO: Find a better way to handle this list + + r := strings.NewReplacer( + "bigserial", "integer", + "int", "integer", + "now()", "CURRENT_TIMESTAMP") + + return r.Replace(input) +} diff --git a/database/migration/migrate_type.go b/database/migration/migrate_type.go new file mode 100644 index 00000000..16a13bdf --- /dev/null +++ b/database/migration/migrate_type.go @@ -0,0 +1,37 @@ +package migrations + +import ( + "path/filepath" + + "github.com/thrasher-corp/gocryptotrader/database" +) + +var ( + // MigrationDir Default folder to look for migrations to apply + MigrationDir = filepath.Join("./database", "migration", "migrations") +) + +// Migration holds all information passes from a migration file +// Includes: Sequence(version), SQL queries to run on up & down +type Migration struct { + Sequence int + Name string + UpSQL string + DownSQL string +} + +// Migrator holds pointer to database struct slice of Migrations and logger +type Migrator struct { + Conn *database.Database + Migrations []Migration + Log Logger +} + +// Logger interface implementation +// Allows you to BYO Logging/Printing + +type Logger interface { + Printf(format string, v ...interface{}) + Println(v ...interface{}) + Errorf(format string, v ...interface{}) +} diff --git a/database/migration/migration_logger.go b/database/migration/migration_logger.go new file mode 100644 index 00000000..56b4ebd5 --- /dev/null +++ b/database/migration/migration_logger.go @@ -0,0 +1,25 @@ +package migrations + +import ( + log "github.com/thrasher-corp/gocryptotrader/logger" +) + +type MLogger struct{} + +// Printf implantation of migration Logger interface +// Passes off to log.Infof +func (t MLogger) Printf(format string, v ...interface{}) { + log.Infof(log.DatabaseMgr, format, v...) +} + +// Println implantation of migration Logger interface +// Passes off to log.Infoln +func (t MLogger) Println(v ...interface{}) { + log.Infoln(log.DatabaseMgr, v...) +} + +// Errorf implantation of migration Logger interface +// Passes off to log.Errorf +func (t MLogger) Errorf(format string, v ...interface{}) { + log.Errorf(log.DatabaseMgr, format, v...) +} diff --git a/database/migration/migrations/1565657999_create_audit_event_table.sql b/database/migration/migrations/1565657999_create_audit_event_table.sql new file mode 100755 index 00000000..1021c0e7 --- /dev/null +++ b/database/migration/migrations/1565657999_create_audit_event_table.sql @@ -0,0 +1,11 @@ +-- up +CREATE TABLE IF NOT EXISTS audit_event +( + id bigserial PRIMARY KEY NOT NULL, + Type varchar(255) NOT NULL, + Identifier varchar(255) NOT NULL, + Message text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now() +); +-- down +DROP TABLE audit_event; diff --git a/database/models/audit.go b/database/models/audit.go new file mode 100644 index 00000000..878b1feb --- /dev/null +++ b/database/models/audit.go @@ -0,0 +1,8 @@ +package models + +// AuditEvent is a model of how the data is represented in a database +type AuditEvent struct { + Type string + Identifier string + Message string +} diff --git a/database/repository/audit/audit.go b/database/repository/audit/audit.go new file mode 100644 index 00000000..e2ea17dd --- /dev/null +++ b/database/repository/audit/audit.go @@ -0,0 +1,62 @@ +package audit + +import ( + "sync" + + "github.com/thrasher-corp/gocryptotrader/database" + "github.com/thrasher-corp/gocryptotrader/database/models" + log "github.com/thrasher-corp/gocryptotrader/logger" +) + +// Repository that is required for each driver type to implement +type Repository interface { + AddEventTx(event []*models.AuditEvent) +} + +var ( + // Audit repository initialise copy of Audit Repository + Audit Repository +) + +type eventPool struct { + events []*models.AuditEvent + eventMu sync.Mutex +} + +var ep eventPool + +// Event allows you to call audit.Event() as long as the audit repository package without the need to include each driver +func Event(msgType, identifier, message string) { + if database.Conn.SQL == nil { + return + } + + if Audit == nil { + return + } + + tempEvent := models.AuditEvent{ + Type: msgType, + Identifier: identifier, + Message: message} + + ep.poolEvents(&tempEvent) +} + +func (e *eventPool) poolEvents(event *models.AuditEvent) { + e.eventMu.Lock() + defer e.eventMu.Unlock() + + e.events = append(e.events, event) + + database.Conn.Mu.RLock() + defer database.Conn.Mu.RUnlock() + + if !database.Conn.Connected { + log.Warnln(log.DatabaseMgr, "connection to database interrupted pooling database writes") + return + } + + Audit.AddEventTx(e.events) + e.events = nil +} diff --git a/database/repository/audit/postgres/audit.go b/database/repository/audit/postgres/audit.go new file mode 100644 index 00000000..98fb2d8a --- /dev/null +++ b/database/repository/audit/postgres/audit.go @@ -0,0 +1,52 @@ +package audit + +import ( + "github.com/thrasher-corp/gocryptotrader/database" + "github.com/thrasher-corp/gocryptotrader/database/models" + "github.com/thrasher-corp/gocryptotrader/database/repository/audit" + log "github.com/thrasher-corp/gocryptotrader/logger" +) + +type auditRepo struct{} + +// Audit returns a new instance of auditRepo +func Audit() audit.Repository { + return &auditRepo{} +} + +// AddEventTx writes multiple events to database +// writes are done using a transaction with a rollback on error +func (pg *auditRepo) AddEventTx(event []*models.AuditEvent) { + if pg == nil { + return + } + + tx, err := database.Conn.SQL.Begin() + if err != nil { + log.Errorf(log.Global, "Failed to create transaction: %v\n", err) + return + } + + query := `INSERT INTO audit_event (type, identifier, message) VALUES($1, $2, $3)` + + for x := range event { + _, err = tx.Exec(query, &event[x].Type, &event[x].Identifier, &event[x].Message) + + if err != nil { + err = tx.Rollback() + if err != nil { + log.Errorf(log.Global, "Tx Rollback has failed: %v", err) + } + return + } + } + + err = tx.Commit() + if err != nil { + err = tx.Rollback() + if err != nil { + log.Errorf(log.Global, "Tx Rollback has failed: %v", err) + } + return + } +} diff --git a/database/repository/audit/sqlite/audit.go b/database/repository/audit/sqlite/audit.go new file mode 100644 index 00000000..c69c4079 --- /dev/null +++ b/database/repository/audit/sqlite/audit.go @@ -0,0 +1,53 @@ +package audit + +import ( + "github.com/thrasher-corp/gocryptotrader/database" + "github.com/thrasher-corp/gocryptotrader/database/models" + "github.com/thrasher-corp/gocryptotrader/database/repository/audit" + log "github.com/thrasher-corp/gocryptotrader/logger" +) + +type auditRepo struct{} + +// Audit returns a new instance of auditRepo +func Audit() audit.Repository { + return &auditRepo{} +} + +// AddEventTx writes multiple event to database +// writes are done using a transaction with a rollback on error +func (pg *auditRepo) AddEventTx(event []*models.AuditEvent) { + if pg == nil { + return + } + + tx, err := database.Conn.SQL.Begin() + if err != nil { + log.Errorf(log.Global, "Failed to create transaction: %v\n", err) + return + } + + query := `INSERT INTO audit_event (type, identifier, message) VALUES($1, $2, $3)` + + for x := range event { + _, err = tx.Exec(query, &event[x].Type, &event[x].Identifier, &event[x].Message) + + if err != nil { + err = tx.Rollback() + if err != nil { + log.Errorf(log.Global, "Tx Rollback has failed: %v", err) + } + return + } + } + + err = tx.Commit() + if err != nil { + err = tx.Rollback() + if err != nil { + log.Errorf(log.Global, "Tx Rollback has failed: %v", err) + } + return + } + +} diff --git a/database/tests/audit_test.go b/database/tests/audit_test.go new file mode 100644 index 00000000..4d6267cf --- /dev/null +++ b/database/tests/audit_test.go @@ -0,0 +1,126 @@ +package tests + +import ( + "fmt" + "path" + "path/filepath" + "sync" + "testing" + + "github.com/thrasher-corp/gocryptotrader/database" + "github.com/thrasher-corp/gocryptotrader/database/drivers" + mg "github.com/thrasher-corp/gocryptotrader/database/migration" + "github.com/thrasher-corp/gocryptotrader/database/repository/audit" + auditPSQL "github.com/thrasher-corp/gocryptotrader/database/repository/audit/postgres" + auditSQlite "github.com/thrasher-corp/gocryptotrader/database/repository/audit/sqlite" +) + +func TestAudit(t *testing.T) { + testCases := []struct { + name string + config database.Config + audit audit.Repository + runner func(t *testing.T) + closer func(t *testing.T, dbConn *database.Database) error + output interface{} + }{ + { + "SQLite", + database.Config{ + Driver: "sqlite", + ConnectionDetails: drivers.ConnectionDetails{Database: path.Join(tempDir, "./testdb.db")}, + }, + auditSQlite.Audit(), + writeAudit, + closeDatabase, + nil, + }, + { + "Postgres", + postgresTestDatabase, + auditPSQL.Audit(), + writeAudit, + nil, + nil, + }, + } + + for _, tests := range testCases { + test := tests + + t.Run(test.name, func(t *testing.T) { + + mg.MigrationDir = filepath.Join("../migration", "migrations") + + if !checkValidConfig(t, &test.config.ConnectionDetails) { + t.Skip("database not configured skipping test") + } + + dbConn, err := connectToDatabase(t, &test.config) + + if err != nil { + t.Fatal(err) + } + + mLogger := mg.MLogger{} + migrations := mg.Migrator{ + Log: mLogger, + } + + migrations.Conn = dbConn + + err = migrations.LoadMigrations() + if err != nil { + t.Fatal(err) + } + + err = migrations.RunMigration() + if err != nil { + t.Fatal(err) + } + + if test.audit != nil { + audit.Audit = test.audit + } + + if test.runner != nil { + test.runner(t) + } + + switch v := test.output.(type) { + + case error: + if v.Error() != test.output.(error).Error() { + t.Fatal(err) + } + return + default: + break + } + + if test.closer != nil { + err = test.closer(t, dbConn) + if err != nil { + t.Log(err) + } + } + }) + } +} + +func writeAudit(t *testing.T) { + t.Helper() + var wg sync.WaitGroup + + for x := 0; x < 200; x++ { + wg.Add(1) + + go func(x int) { + defer wg.Done() + test := fmt.Sprintf("test-%v", x) + audit.Event(test, test, test) + }(x) + } + + wg.Wait() +} diff --git a/database/tests/db_test.go b/database/tests/db_test.go new file mode 100644 index 00000000..1e4e6610 --- /dev/null +++ b/database/tests/db_test.go @@ -0,0 +1,148 @@ +package tests + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "reflect" + "testing" + + "github.com/thrasher-corp/gocryptotrader/database" + "github.com/thrasher-corp/gocryptotrader/database/drivers" + dbpsql "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres" + dbsqlite "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite" +) + +var ( + tempDir string + + postgresTestDatabase = database.Config{ + Enabled: true, + Driver: "postgres", + ConnectionDetails: drivers.ConnectionDetails{ + //Host: "", + //Port: 5432, + //Username: "", + //Password: "", + //Database: "", + //SSLMode: "", + }, + } +) + +func TestMain(m *testing.M) { + var err error + tempDir, err = ioutil.TempDir("", "gct-temp") + if err != nil { + fmt.Printf("failed to create temp file: %v", err) + os.Exit(1) + } + + t := m.Run() + + err = os.RemoveAll(tempDir) + if err != nil { + fmt.Printf("Failed to remove temp db file: %v", err) + } + + os.Exit(t) +} + +func TestDatabaseConnect(t *testing.T) { + testCases := []struct { + name string + config database.Config + closer func(t *testing.T, dbConn *database.Database) error + output interface{} + }{ + { + "SQLite", + database.Config{ + Driver: "sqlite", + ConnectionDetails: drivers.ConnectionDetails{Database: path.Join(tempDir, "./testdb.db")}, + }, + closeDatabase, + nil, + }, + { + "SQliteNoDatabase", + database.Config{ + Driver: "sqlite", + ConnectionDetails: drivers.ConnectionDetails{ + Host: "localhost", + }, + }, + nil, + database.ErrNoDatabaseProvided, + }, + { + name: "Postgres", + config: postgresTestDatabase, + output: nil, + }, + } + + for _, tests := range testCases { + test := tests + t.Run(test.name, func(t *testing.T) { + if !checkValidConfig(t, &test.config.ConnectionDetails) { + t.Skip("database not configured skipping test") + } + + dbConn, err := connectToDatabase(t, &test.config) + if err != nil { + switch v := test.output.(type) { + case error: + if v.Error() != err.Error() { + t.Fatal(err) + } + return + default: + break + } + } + + if test.closer != nil { + err = test.closer(t, dbConn) + if err != nil { + t.Log(err) + } + } + }) + } +} + +func connectToDatabase(t *testing.T, conn *database.Config) (dbConn *database.Database, err error) { + t.Helper() + database.Conn.Config = conn + + if conn.Driver == "postgres" { + dbConn, err = dbpsql.Connect() + if err != nil { + return + } + } else if conn.Driver == "sqlite" { + dbConn, err = dbsqlite.Connect() + if err != nil { + return + } + } + database.Conn.Connected = true + return +} + +func closeDatabase(t *testing.T, conn *database.Database) (err error) { + t.Helper() + + if conn != nil { + return conn.SQL.Close() + } + return nil +} + +func checkValidConfig(t *testing.T, config *drivers.ConnectionDetails) bool { + t.Helper() + + return !reflect.DeepEqual(drivers.ConnectionDetails{}, *config) +} diff --git a/engine/database.go b/engine/database.go new file mode 100644 index 00000000..696ef965 --- /dev/null +++ b/engine/database.go @@ -0,0 +1,144 @@ +package engine + +import ( + "errors" + "fmt" + "sync/atomic" + "time" + + "github.com/thrasher-corp/gocryptotrader/database" + db "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres" + dbsqlite3 "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite" + mg "github.com/thrasher-corp/gocryptotrader/database/migration" + "github.com/thrasher-corp/gocryptotrader/database/repository/audit" + auditPSQL "github.com/thrasher-corp/gocryptotrader/database/repository/audit/postgres" + auditSQLite "github.com/thrasher-corp/gocryptotrader/database/repository/audit/sqlite" + log "github.com/thrasher-corp/gocryptotrader/logger" +) + +var ( + dbConn *database.Database +) + +type databaseManager struct { + running atomic.Value + shutdown chan struct{} +} + +func (a *databaseManager) Started() bool { + return a.running.Load() == true +} + +func (a *databaseManager) Start() (err error) { + if a.Started() { + return errors.New("database manager already started") + } + + log.Debugln(log.DatabaseMgr, "database manager starting...") + + a.shutdown = make(chan struct{}) + + if Bot.Config.Database.Enabled { + if Bot.Config.Database.Driver == "postgres" { + dbConn, err = db.Connect() + if err != nil { + return fmt.Errorf("database failed to connect: %v Some features that utilise a database will be unavailable", err) + } + + dbConn.SQL.SetMaxOpenConns(2) + dbConn.SQL.SetMaxIdleConns(1) + dbConn.SQL.SetConnMaxLifetime(time.Hour) + + audit.Audit = auditPSQL.Audit() + } else if Bot.Config.Database.Driver == "sqlite" { + dbConn, err = dbsqlite3.Connect() + + if err != nil { + return fmt.Errorf("database failed to connect: %v Some features that utilise a database will be unavailable", err) + } + + audit.Audit = auditSQLite.Audit() + } + dbConn.Connected = true + log.Debugf(log.DatabaseMgr, "connection established to %v using %v", dbConn.Config.Host, dbConn.Config.Driver) + + mLogger := mg.MLogger{} + migrations := mg.Migrator{ + Log: mLogger, + } + + migrations.Conn = dbConn + + err := migrations.LoadMigrations() + if err != nil { + return err + } + + err = migrations.RunMigration() + if err != nil { + return err + } + + go a.run() + return nil + } + + return errors.New("database support disabled") +} + +func (a *databaseManager) Stop() error { + if !a.Started() { + return errors.New("database manager already stopped") + } + + log.Debugln(log.DatabaseMgr, "database manager shutting down...") + err := dbConn.SQL.Close() + if err != nil { + log.Errorf(log.DatabaseMgr, "Failed to close database: %v", err) + } + close(a.shutdown) + return nil +} + +func (a *databaseManager) run() { + log.Debugln(log.DatabaseMgr, "database manager started.") + Bot.ServicesWG.Add(1) + + t := time.NewTicker(time.Second * 2) + a.running.Store(true) + + defer func() { + t.Stop() + a.running.Store(false) + + Bot.ServicesWG.Done() + + log.Debugln(log.DatabaseMgr, "database manager shutdown.") + }() + + for { + select { + case <-a.shutdown: + return + case <-t.C: + a.checkConnection() + } + } +} + +func (a *databaseManager) checkConnection() { + dbConn.Mu.Lock() + defer dbConn.Mu.Unlock() + + err := dbConn.SQL.Ping() + if err != nil { + log.Errorf(log.DatabaseMgr, "database connection error: %v", err) + dbConn.Connected = false + return + } + + if !dbConn.Connected { + log.Info(log.DatabaseMgr, "database connection reestablished") + dbConn.Connected = true + } +} diff --git a/engine/engine.go b/engine/engine.go index 2e141935..33be5800 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -30,6 +30,7 @@ type Engine struct { ExchangeCurrencyPairManager *ExchangeCurrencyPairSyncer NTPManager ntpManager ConnectionManager connectionManager + DatabaseManager databaseManager OrderManager orderManager PortfolioManager portfolioManager CommsManager commsManager @@ -110,6 +111,7 @@ func ValidateSettings(b *Engine, s *Settings) { b.Settings.EnableAllPairs = s.EnableAllPairs b.Settings.EnablePortfolioManager = s.EnablePortfolioManager b.Settings.EnableCoinmarketcapAnalysis = s.EnableCoinmarketcapAnalysis + b.Settings.EnableDatabaseManager = s.EnableDatabaseManager // TO-DO: FIXME if flag.Lookup("grpc") != nil { @@ -259,6 +261,12 @@ func (e *Engine) Start() { os.Exit(1) } + if e.Settings.EnableDatabaseManager { + if err := e.DatabaseManager.Start(); err != nil { + log.Errorf(log.Global, "Database manager unable to start: %v", err) + } + } + // Sets up internet connectivity monitor if e.Settings.EnableConnectivityMonitor { if err := e.ConnectionManager.Start(); err != nil { @@ -417,6 +425,12 @@ func (e *Engine) Stop() { } } + if e.DatabaseManager.Started() { + if err := e.DatabaseManager.Stop(); err != nil { + log.Errorf(log.Global, "Database manager unable to stop. Error: %v", err) + } + } + if !e.Settings.EnableDryRun { err := e.Config.SaveConfig(e.Settings.ConfigFile) if err != nil { @@ -425,6 +439,7 @@ func (e *Engine) Stop() { log.Debugln(log.Global, "Config file saved successfully.") } } + // Wait for services to gracefully shutdown e.ServicesWG.Wait() log.Debugln(log.Global, "Exiting.") diff --git a/engine/engine_types.go b/engine/engine_types.go index accad577..42cffb37 100644 --- a/engine/engine_types.go +++ b/engine/engine_types.go @@ -4,10 +4,11 @@ import "time" // Settings stores engine params type Settings struct { - ConfigFile string - DataDir string - LogFile string - GoMaxProcs int + ConfigFile string + DataDir string + MigrationDir string + LogFile string + GoMaxProcs int // Core Settings EnableDryRun bool @@ -27,6 +28,7 @@ type Settings struct { EnableEventManager bool EnableOrderManager bool EnableConnectivityMonitor bool + EnableDatabaseManager bool EnableNTPClient bool EnableWebsocketRoutine bool EventManagerDelay time.Duration diff --git a/exchanges/wshandler/websocket_test.go b/exchanges/wshandler/websocket_test.go index 5cf62409..b679831f 100644 --- a/exchanges/wshandler/websocket_test.go +++ b/exchanges/wshandler/websocket_test.go @@ -384,7 +384,7 @@ func TestSubscriptionWithExistingEntry(t *testing.T) { w.SetChannelSubscriber(placeholderSubscriber) w.subscribeToChannels() if len(w.subscribedChannels) != 1 { - t.Errorf("Subscription should not have occured") + t.Errorf("Subscription should not have occurred") } } @@ -405,7 +405,7 @@ func TestUnsubscriptionWithExistingEntry(t *testing.T) { w.SetChannelUnsubscriber(placeholderSubscriber) w.unsubscribeToChannels() if len(w.subscribedChannels) != 1 { - t.Errorf("Unsubscription should not have occured") + t.Errorf("Unsubscription should not have occurred") } } diff --git a/logger/logger_setup.go b/logger/logger_setup.go index b461e059..bf391b3c 100644 --- a/logger/logger_setup.go +++ b/logger/logger_setup.go @@ -140,6 +140,7 @@ func init() { ConnectionMgr = registerNewSubLogger("connection") CommunicationMgr = registerNewSubLogger("comms") ConfigMgr = registerNewSubLogger("config") + DatabaseMgr = registerNewSubLogger("database") OrderMgr = registerNewSubLogger("order") PortfolioMgr = registerNewSubLogger("portfolio") SyncMgr = registerNewSubLogger("sync") diff --git a/logger/sublogger_types.go b/logger/sublogger_types.go index f49ae474..3a406ae2 100644 --- a/logger/sublogger_types.go +++ b/logger/sublogger_types.go @@ -7,6 +7,7 @@ var ( ConnectionMgr *subLogger CommunicationMgr *subLogger ConfigMgr *subLogger + DatabaseMgr *subLogger OrderMgr *subLogger PortfolioMgr *subLogger SyncMgr *subLogger diff --git a/main.go b/main.go index f9b286b2..112737ab 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" + mg "github.com/thrasher-corp/gocryptotrader/database/migration" "github.com/thrasher-corp/gocryptotrader/engine" "github.com/thrasher-corp/gocryptotrader/exchanges/request" log "github.com/thrasher-corp/gocryptotrader/logger" @@ -29,6 +30,7 @@ func main() { // Core settings flag.StringVar(&settings.ConfigFile, "config", defaultPath, "config file to load") flag.StringVar(&settings.DataDir, "datadir", common.GetDefaultDataDir(runtime.GOOS), "default data directory for GoCryptoTrader files") + flag.StringVar(&settings.MigrationDir, "migrationdir", mg.MigrationDir, "override migration folder") flag.IntVar(&settings.GoMaxProcs, "gomaxprocs", runtime.NumCPU(), "sets the runtime GOMAXPROCS value") flag.BoolVar(&settings.EnableDryRun, "dryrun", false, "dry runs bot, doesn't save config file") flag.BoolVar(&settings.EnableAllExchanges, "enableallexchanges", false, "enables all exchanges") @@ -49,6 +51,7 @@ func main() { flag.BoolVar(&settings.EnableOrderManager, "ordermanager", true, "enables the order manager") flag.BoolVar(&settings.EnableDepositAddressManager, "depositaddressmanager", true, "enables the deposit address manager") flag.BoolVar(&settings.EnableConnectivityMonitor, "connectivitymonitor", true, "enables the connectivity monitor") + flag.BoolVar(&settings.EnableDatabaseManager, "databasemanager", true, "enables database manager") flag.DurationVar(&settings.EventManagerDelay, "eventmanagerdelay", time.Duration(0), "sets the event managers sleep delay between event checking") flag.BoolVar(&settings.EnableNTPClient, "ntpclient", true, "enables the NTP client to check system clock drift") From 2dc813b5f307d47840166e958a6d6a8d52b55ea5 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 23 Aug 2019 21:59:01 +1000 Subject: [PATCH 23/71] Merge branch 'master' into engine --- .appveyor.yml | 7 +- CONTRIBUTORS | 6 +- Makefile | 5 + README.md | 19 +- .../exchanges_templates/lbank.tmpl | 98 + .../root_templates/root_readme.tmpl | 3 +- config/README.md | 1 - config/config_test.go | 4 +- config_example.json | 42 + currency/coinmarketcap/coinmarketcap.go | 1 + .../currencyconverterapi.go | 1 + .../currencylayer/currencylayer.go | 1 + .../exchangeratesapi.io/exchangeratesapi.go | 1 + currency/forexprovider/fixer.io/fixer.go | 1 + .../openexchangerates/openexchangerates.go | 1 + engine/exchange.go | 3 + exchanges/alphapoint/alphapoint.go | 22 +- exchanges/anx/anx.go | 41 +- exchanges/anx/anx_live_test.go | 32 + exchanges/anx/anx_mock_test.go | 45 + exchanges/anx/anx_test.go | 202 +- exchanges/anx/anx_wrapper.go | 3 +- exchanges/binance/binance.go | 44 +- exchanges/binance/binance_live_test.go | 32 + exchanges/binance/binance_mock_test.go | 45 + exchanges/binance/binance_test.go | 340 +- exchanges/binance/binance_types.go | 4 +- exchanges/binance/binance_wrapper.go | 9 +- exchanges/bitfinex/bitfinex.go | 14 +- exchanges/bitflyer/bitflyer.go | 11 +- exchanges/bithumb/bithumb.go | 14 +- exchanges/bitmex/bitmex.go | 25 +- exchanges/bitstamp/bitstamp.go | 91 +- exchanges/bitstamp/bitstamp_live_test.go | 33 + exchanges/bitstamp/bitstamp_mock_test.go | 46 + exchanges/bitstamp/bitstamp_test.go | 375 +- exchanges/bitstamp/bitstamp_types.go | 12 +- exchanges/bitstamp/bitstamp_wrapper.go | 29 +- exchanges/bittrex/bittrex.go | 22 +- exchanges/btcmarkets/btcmarkets.go | 14 +- exchanges/btse/btse.go | 24 +- exchanges/coinbasepro/coinbasepro.go | 14 +- exchanges/coinut/coinut.go | 2 +- exchanges/exchange.go | 5 + exchanges/exchange_types.go | 2 + exchanges/exmo/exmo.go | 14 +- exchanges/gateio/gateio.go | 14 +- exchanges/gemini/gemini.go | 83 +- exchanges/gemini/gemini_live_test.go | 33 + exchanges/gemini/gemini_mock_test.go | 45 + exchanges/gemini/gemini_test.go | 405 +- exchanges/gemini/gemini_types.go | 23 +- exchanges/gemini/gemini_wrapper.go | 11 +- exchanges/hitbtc/hitbtc.go | 14 +- exchanges/huobi/huobi.go | 22 +- exchanges/huobihadax/huobihadax.go | 33 +- exchanges/itbit/itbit.go | 22 +- exchanges/kraken/kraken.go | 14 +- exchanges/lakebtc/lakebtc.go | 23 +- exchanges/lbank/README.md | 133 + exchanges/lbank/lbank.go | 562 + exchanges/lbank/lbank_test.go | 396 + exchanges/lbank/lbank_types.go | 270 + exchanges/lbank/lbank_wrapper.go | 668 + exchanges/localbitcoins/localbitcoins.go | 121 +- .../localbitcoins/localbitcoins_live_test.go | 32 + .../localbitcoins/localbitcoins_mock_test.go | 45 + exchanges/localbitcoins/localbitcoins_test.go | 216 +- .../localbitcoins/localbitcoins_wrapper.go | 8 +- exchanges/mock/README.md | 174 + exchanges/mock/common.go | 71 + exchanges/mock/common_test.go | 140 + exchanges/mock/recording.go | 440 + exchanges/mock/recording_test.go | 186 + exchanges/mock/server.go | 283 + exchanges/mock/server_test.go | 117 + exchanges/okgroup/okgroup.go | 10 +- exchanges/poloniex/poloniex.go | 40 +- exchanges/poloniex/poloniex_live_test.go | 32 + exchanges/poloniex/poloniex_mock_test.go | 45 + exchanges/poloniex/poloniex_test.go | 221 +- exchanges/poloniex/poloniex_wrapper.go | 2 +- exchanges/request/request.go | 32 +- exchanges/request/request_test.go | 24 +- .../sharedtestvalues/sharedtestvalues.go | 3 + exchanges/support.go | 1 + exchanges/yobit/yobit.go | 6 +- exchanges/zb/zb.go | 14 +- testdata/README.md | 1 + testdata/configtest.json | 43 + testdata/http_mock/anx/anx.json | 2404 + testdata/http_mock/binance/binance.json | 45467 ++++++++++++++++ testdata/http_mock/bitstamp/bitstamp.json | 42842 +++++++++++++++ testdata/http_mock/exclusion.json | 19 + testdata/http_mock/gemini/gemini.json | 2734 + .../localbitcoins/localbitcoins.json | 3400 ++ testdata/http_mock/poloniex/poloniex.json | 9031 +++ 97 files changed, 111512 insertions(+), 1223 deletions(-) create mode 100644 cmd/documentation/exchanges_templates/lbank.tmpl create mode 100644 exchanges/anx/anx_live_test.go create mode 100644 exchanges/anx/anx_mock_test.go create mode 100644 exchanges/binance/binance_live_test.go create mode 100644 exchanges/binance/binance_mock_test.go create mode 100644 exchanges/bitstamp/bitstamp_live_test.go create mode 100644 exchanges/bitstamp/bitstamp_mock_test.go create mode 100644 exchanges/gemini/gemini_live_test.go create mode 100644 exchanges/gemini/gemini_mock_test.go create mode 100644 exchanges/lbank/README.md create mode 100644 exchanges/lbank/lbank.go create mode 100644 exchanges/lbank/lbank_test.go create mode 100644 exchanges/lbank/lbank_types.go create mode 100644 exchanges/lbank/lbank_wrapper.go create mode 100644 exchanges/localbitcoins/localbitcoins_live_test.go create mode 100644 exchanges/localbitcoins/localbitcoins_mock_test.go create mode 100644 exchanges/mock/README.md create mode 100644 exchanges/mock/common.go create mode 100644 exchanges/mock/common_test.go create mode 100644 exchanges/mock/recording.go create mode 100644 exchanges/mock/recording_test.go create mode 100644 exchanges/mock/server.go create mode 100644 exchanges/mock/server_test.go create mode 100644 exchanges/poloniex/poloniex_live_test.go create mode 100644 exchanges/poloniex/poloniex_mock_test.go create mode 100644 testdata/http_mock/anx/anx.json create mode 100644 testdata/http_mock/binance/binance.json create mode 100644 testdata/http_mock/bitstamp/bitstamp.json create mode 100644 testdata/http_mock/exclusion.json create mode 100644 testdata/http_mock/gemini/gemini.json create mode 100644 testdata/http_mock/localbitcoins/localbitcoins.json create mode 100644 testdata/http_mock/poloniex/poloniex.json diff --git a/.appveyor.yml b/.appveyor.yml index dbf5ad25..11be377c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -30,7 +30,12 @@ test_script: # test back-end - go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.16.0 - '%GOPATH%\bin\golangci-lint.exe run --verbose' - - go test -race ./... + - ps: >- + if($env:APPVEYOR_SCHEDULED_BUILD -eq 'true') { + go test -race ./... -tags=mock_test_off + }else { + go test -race ./... + } # test front-end - node --version diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 74691599..ae80db0c 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -25,9 +25,13 @@ CodeLingoBot | https://github.com/CodeLingoBot CodeLingoTeam | https://github.com/CodeLingoTeam Daanikus | https://github.com/Daanikus daniel-cohen | https://github.com/daniel-cohen +DirectX | https://github.com/DirectX frankzougc | https://github.com/frankzougc starit | https://github.com/starit Jimexist | https://github.com/Jimexist lookfirst | https://github.com/lookfirst +mattkanwisher | https://github.com/mattkanwisher +mKurrels | https://github.com/mKurrels +m1kola | https://github.com/m1kola +cavapoo2 | https://github.com/cavapoo2 zeldrinn | https://github.com/zeldrinn - diff --git a/Makefile b/Makefile index 77f66545..41d83cf9 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ LINTPKG = github.com/golangci/golangci-lint/cmd/golangci-lint@v1.16.0 LINTBIN = $(GOPATH)/bin/golangci-lint GCTLISTENPORT=9050 GCTPROFILERLISTENPORT=8085 +CRON = $(TRAVIS_EVENT_TYPE) get: GO111MODULE=on go get $(GCTPKG) @@ -16,7 +17,11 @@ linter: check: linter test test: +ifeq ($(CRON), cron) + go test -race -tags=mock_test_off -coverprofile=coverage.txt -covermode=atomic ./... +else go test -race -coverprofile=coverage.txt -covermode=atomic ./... +endif build: GO111MODULE=on go build $(LDFLAGS) diff --git a/README.md b/README.md index 56c0fb55..2bf4da02 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | Huobi.Hadax | Yes | Yes | NA | | ItBit | Yes | NA | No | | Kraken | Yes | Yes | NA | -| LakeBTC | Yes | Yes | NA | +| Lbank | Yes | No | NA | +| LakeBTC | Yes | No | NA | | LocalBitcoins | Yes | NA | NA | | OKCoin International | Yes | Yes | No | | OKEX | Yes | Yes | No | @@ -129,10 +130,11 @@ Binaries will be published once the codebase reaches a stable condition. ### A very special thank you to all who have contributed to this program: |User|Github|Contribution Amount| -|--|--|--|| thrasher- | https://github.com/thrasher- | 540 | -| shazbert | https://github.com/shazbert | 173 | -| gloriousCode | https://github.com/gloriousCode | 150 | -| xtda | https://github.com/xtda | 17 | +|--|--|--| +| thrasher- | https://github.com/thrasher- | 543 | +| shazbert | https://github.com/shazbert | 174 | +| gloriousCode | https://github.com/gloriousCode | 154 | +| xtda | https://github.com/xtda | 18 | | ermalguni | https://github.com/ermalguni | 14 | | vadimzhukck | https://github.com/vadimzhukck | 10 | | 140am | https://github.com/140am | 8 | @@ -154,10 +156,13 @@ Binaries will be published once the codebase reaches a stable condition. | CodeLingoTeam | https://github.com/CodeLingoTeam | 1 | | Daanikus | https://github.com/Daanikus | 1 | | daniel-cohen | https://github.com/daniel-cohen | 1 | +| DirectX | https://github.com/DirectX | 1 | | frankzougc | https://github.com/frankzougc | 1 | | starit | https://github.com/starit | 1 | | Jimexist | https://github.com/Jimexist | 1 | | lookfirst | https://github.com/lookfirst | 1 | +| mattkanwisher | https://github.com/mattkanwisher | 1 | +| mKurrels | https://github.com/mKurrels | 1 | +| m1kola | https://github.com/m1kola | 1 | +| cavapoo2 | https://github.com/cavapoo2 | 1 | | zeldrinn | https://github.com/zeldrinn | 1 | - - diff --git a/cmd/documentation/exchanges_templates/lbank.tmpl b/cmd/documentation/exchanges_templates/lbank.tmpl new file mode 100644 index 00000000..45fb9441 --- /dev/null +++ b/cmd/documentation/exchanges_templates/lbank.tmpl @@ -0,0 +1,98 @@ +{{define "exchanges lbank" -}} +{{template "header" .}} +## Lbank Exchange + +### Current Features + ++ REST Support + +### How to enable + ++ [Enable via configuration](https://githul.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example) + ++ Individual package example below: + +```go + // Exchanges will be abstracted out in further updates and examples will be + // supplied then +``` + +### How to do REST public/private calls + ++ If enabled via "configuration".json file the exchange will be added to the +IBotExchange array in the ```go var bot Bot``` and you will only be able to use +the wrapper interface functions for accessing exchange data. View routines.go +for an example of integration usage with GoCryptoTrader. Rudimentary example +below: + +main.go +```go +var l exchange.IBotExchange + +for i := range bot.exchanges { + if bot.exchanges[i].GetName() == "Lbank" { + l = bot.exchanges[i] + } +} + +// Public calls - wrapper functions + +// Fetches current ticker information +tick, err := l.GetTickerPrice() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := l.GetOrderbookEx() +if err != nil { + // Handle error +} + +// Private calls - wrapper functions - make sure your APIKEY and APISECRET are +// set and AuthenticatedAPISupport is set to true + +// Fetches current account information +accountInfo, err := l.GetAccountInfo() +if err != nil { + // Handle error +} +``` + ++ If enabled via individually importing package, rudimentary example below: + +```go +// Public calls + +// Fetches current ticker information +ticker, err := l.GetTicker() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := l.GetOrderBook() +if err != nil { + // Handle error +} + +// Private calls - make sure your APIKEY and APISECRET are set and +// AuthenticatedAPISupport is set to true + +// GetUserInfo returns account info +accountInfo, err := l.GetUserInfo(...) +if err != nil { + // Handle error +} + +// Submits an order and the exchange and returns its tradeID +tradeID, err := l.Trade(...) +if err != nil { + // Handle error +} +``` + +### Please click GoDocs chevron above to view current GoDoc information for this package +{{template "contributions"}} +{{template "donations"}} +{{end}} diff --git a/cmd/documentation/root_templates/root_readme.tmpl b/cmd/documentation/root_templates/root_readme.tmpl index 7d22c325..6c2d9edb 100644 --- a/cmd/documentation/root_templates/root_readme.tmpl +++ b/cmd/documentation/root_templates/root_readme.tmpl @@ -40,7 +40,8 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | Huobi.Hadax | Yes | Yes | NA | | ItBit | Yes | NA | No | | Kraken | Yes | Yes | NA | -| LakeBTC | Yes | Yes | NA | +| Lbank | Yes | No | NA | +| LakeBTC | Yes | No | NA | | LocalBitcoins | Yes | NA | NA | | OKCoin International | Yes | Yes | No | | OKEX | Yes | Yes | No | diff --git a/config/README.md b/config/README.md index 240b3191..77ff7223 100644 --- a/config/README.md +++ b/config/README.md @@ -85,7 +85,6 @@ have multiple deposit accounts for different FIAT deposit currencies. "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, "httpTimeout": 15000000000, - "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", "AvailablePairs": "ATENC_GBP,ATENC_NZD,BTC_AUD,BTC_SGD,LTC_BTC,START_GBP,...", diff --git a/config/config_test.go b/config/config_test.go index ae2fa660..1613817a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -14,7 +14,7 @@ import ( const ( // Default number of enabled exchanges. Modify this whenever an exchange is // added or removed - defaultEnabledExchanges = 27 + defaultEnabledExchanges = 28 ) func TestGetCurrencyConfig(t *testing.T) { @@ -465,7 +465,7 @@ func TestCountEnabledExchanges(t *testing.T) { } enabledExch := GetConfigEnabledExchanges.CountEnabledExchanges() if enabledExch != defaultEnabledExchanges { - t.Error("Test failed. GetConfigEnabledExchanges is wrong") + t.Errorf("Test failed. Expected %v, Received %v", defaultEnabledExchanges, enabledExch) } } diff --git a/config_example.json b/config_example.json index 364690f6..143156ae 100644 --- a/config_example.json +++ b/config_example.json @@ -1107,6 +1107,48 @@ } ] }, + { + "name": "LBank", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "fbc_usdt,hds_usdt,galt_usdt,dxn_usdt,iog_usdt,ioex_usdt,vollar_usdt,oath_usdt,bloc_usdt,btc_lbcn,eth_lbcn,usdt_lbcn,btc_usdt,eth_usdt,eth_btc,abbc_btc,bzky_eth,onot_eth,kisc_eth,bxa_usdt,atp_usdt,mat_usdt,sky_btc,sky_lbcn,rnt_usdt,vena_usdt,grin_usdt,ida_usdt,pnt_usdt,bsv_btc,bsv_usdt,opx_usdt,tena_eth,seer_lbcn,vet_lbcn,vtho_btc,vnx_lbcn,vnx_btc,amo_eth,ubex_btc,eos_btc,ubex_usdt,tns_lbcn,tns_btc,ali_eth,sdc_eth,sait_eth,artcn_usdt,dax_btc,dax_eth,dali_usdt,vet_usdt,ten_usdt,bch_usdt,neo_usdt,qtum_usdt,zec_usdt,vet_btc,pai_btc,pnt_btc,bch_btc,ltc_btc,neo_btc,dash_btc,etc_btc,qtum_btc,zec_btc,sc_btc,bts_btc,cpx_btc,xwc_btc,fil6_btc,fil12_btc,fil36_btc,eos_usdt,ut_eth,ela_eth,vet_eth,vtho_eth,pai_eth,bfdt_eth,her_eth,ptt_eth,tac_eth,idhub_eth,ssc_eth,skm_eth,iic_eth,ply_eth,ext_eth,eos_eth,yoyow_eth,trx_eth,qtum_eth,zec_eth,bts_eth,btm_eth,mith_eth,nas_eth,man_eth,dbc_eth,bto_eth,ddd_eth,cpx_eth,cs_eth,iht_eth,tky_eth,ocn_eth,dct_eth,zpt_eth,eko_eth,mda_eth,pst_eth,xwc_eth,put_eth,pnt_eth,aac_eth,fil6_eth,fil12_eth,fil36_eth,uip_eth,seer_eth,bsb_eth,cdc_eth,grams_eth,ddmx_eth,eai_eth,inc_eth,bnb_usdt,ht_usdt,bot_eth,kbc_btc,kbc_usdt,mai_usdt,phv_usdt,hnb_usdt,gt_usdt,b91_usdt,voken_usdt,cye_usdt,brc_usdt,btc_ausd", + "enabledPairs": "btc_usdt", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, { "name": "LocalBitcoins", "enabled": true, diff --git a/currency/coinmarketcap/coinmarketcap.go b/currency/coinmarketcap/coinmarketcap.go index 8771ac5e..0304d0b8 100644 --- a/currency/coinmarketcap/coinmarketcap.go +++ b/currency/coinmarketcap/coinmarketcap.go @@ -733,6 +733,7 @@ func (c *Coinmarketcap) SendHTTPRequest(method, endpoint string, v url.Values, r false, false, c.Verbose, + false, false) } diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go index 0abf9a30..df867e21 100644 --- a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go @@ -193,6 +193,7 @@ func (c *CurrencyConverter) SendHTTPRequest(endPoint string, values url.Values, auth, false, c.Verbose, + false, false) if err != nil { return fmt.Errorf("currency converter API SendHTTPRequest error %s with path %s", diff --git a/currency/forexprovider/currencylayer/currencylayer.go b/currency/forexprovider/currencylayer/currencylayer.go index 69e14ace..e79f5469 100644 --- a/currency/forexprovider/currencylayer/currencylayer.go +++ b/currency/forexprovider/currencylayer/currencylayer.go @@ -243,5 +243,6 @@ func (c *CurrencyLayer) SendHTTPRequest(endPoint string, values url.Values, resu auth, false, c.Verbose, + false, false) } diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go index 9515d1a7..85724a55 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go @@ -179,6 +179,7 @@ func (e *ExchangeRates) SendHTTPRequest(endPoint string, values url.Values, resu false, false, e.Verbose, + false, false) if err != nil { return fmt.Errorf("exchangeRatesAPI SendHTTPRequest error %s with path %s", diff --git a/currency/forexprovider/fixer.io/fixer.go b/currency/forexprovider/fixer.io/fixer.go index 417d53e1..5f6f9201 100644 --- a/currency/forexprovider/fixer.io/fixer.go +++ b/currency/forexprovider/fixer.io/fixer.go @@ -267,5 +267,6 @@ func (f *Fixer) SendOpenHTTPRequest(endpoint string, v url.Values, result interf auth, false, f.Verbose, + false, false) } diff --git a/currency/forexprovider/openexchangerates/openexchangerates.go b/currency/forexprovider/openexchangerates/openexchangerates.go index 07b54e7f..4a02eb78 100644 --- a/currency/forexprovider/openexchangerates/openexchangerates.go +++ b/currency/forexprovider/openexchangerates/openexchangerates.go @@ -267,5 +267,6 @@ func (o *OXR) SendHTTPRequest(endpoint string, values url.Values, result interfa false, false, o.Verbose, + false, false) } diff --git a/engine/exchange.go b/engine/exchange.go index 0920c592..13b61753 100644 --- a/engine/exchange.go +++ b/engine/exchange.go @@ -29,6 +29,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/itbit" "github.com/thrasher-corp/gocryptotrader/exchanges/kraken" "github.com/thrasher-corp/gocryptotrader/exchanges/lakebtc" + "github.com/thrasher-corp/gocryptotrader/exchanges/lbank" "github.com/thrasher-corp/gocryptotrader/exchanges/localbitcoins" "github.com/thrasher-corp/gocryptotrader/exchanges/okcoin" "github.com/thrasher-corp/gocryptotrader/exchanges/okex" @@ -174,6 +175,8 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { exch = new(kraken.Kraken) case "lakebtc": exch = new(lakebtc.LakeBTC) + case "lbank": + exch = new(lbank.Lbank) case "localbitcoins": exch = new(localbitcoins.LocalBitcoins) case "okcoin international": diff --git a/exchanges/alphapoint/alphapoint.go b/exchanges/alphapoint/alphapoint.go index 2fb50c9a..6d2859b6 100644 --- a/exchanges/alphapoint/alphapoint.go +++ b/exchanges/alphapoint/alphapoint.go @@ -517,7 +517,16 @@ func (a *Alphapoint) SendHTTPRequest(method, path string, data map[string]interf return errors.New("unable to JSON request") } - return a.SendPayload(method, path, headers, bytes.NewBuffer(PayloadJSON), result, false, false, a.Verbose, a.HTTPDebugging) + return a.SendPayload(method, + path, + headers, + bytes.NewBuffer(PayloadJSON), + result, + false, + false, + a.Verbose, + a.HTTPDebugging, + a.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated request @@ -543,5 +552,14 @@ func (a *Alphapoint) SendAuthenticatedHTTPRequest(method, path string, data map[ return errors.New("unable to JSON request") } - return a.SendPayload(method, path, headers, bytes.NewBuffer(PayloadJSON), result, true, true, a.Verbose, a.HTTPDebugging) + return a.SendPayload(method, + path, + headers, + bytes.NewBuffer(PayloadJSON), + result, + true, + true, + a.Verbose, + a.HTTPDebugging, + a.HTTPRecording) } diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 36aea39d..3ad90c94 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -176,9 +176,13 @@ func (a *ANX) NewOrder(orderType string, buy bool, tradedCurrency string, traded // CancelOrderByIDs cancels orders, requires already knowing order IDs // There is no existing API call to retrieve orderIds func (a *ANX) CancelOrderByIDs(orderIds []string) (OrderCancelResponse, error) { + var response OrderCancelResponse + if len(orderIds) == 0 { + return response, errors.New("no order ids provided") + } + req := make(map[string]interface{}) req["orderIds"] = orderIds - var response OrderCancelResponse err := a.SendAuthenticatedHTTPRequest(anxOrderCancel, req, &response) if response.ResultCode != "OK" { @@ -194,7 +198,7 @@ func (a *ANX) GetOrderList(isActiveOrdersOnly bool) ([]OrderResponse, error) { req["activeOnly"] = isActiveOrdersOnly type OrderListResponse struct { - Timestamp int64 `json:"timestamp"` + Timestamp int64 `json:"timestamp,string"` ResultCode string `json:"resultCode"` Count int64 `json:"count"` OrderResponses []OrderResponse `json:"orders"` @@ -206,7 +210,6 @@ func (a *ANX) GetOrderList(isActiveOrdersOnly bool) ([]OrderResponse, error) { } if response.ResultCode != "OK" { - log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return nil, errors.New(response.ResultCode) } @@ -232,7 +235,6 @@ func (a *ANX) OrderInfo(orderID string) (OrderResponse, error) { } if response.ResultCode != "OK" { - log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return OrderResponse{}, errors.New(response.ResultCode) } return response.Order, nil @@ -252,7 +254,7 @@ func (a *ANX) Send(currency, address, otp, amount string) (string, error) { type SendResponse struct { TransactionID string `json:"transactionId"` ResultCode string `json:"resultCode"` - Timestamp int64 `json:"timestamp"` + Timestamp int64 `json:"timestamp,string"` } var response SendResponse @@ -263,7 +265,6 @@ func (a *ANX) Send(currency, address, otp, amount string) (string, error) { } if response.ResultCode != "OK" { - log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return "", errors.New(response.ResultCode) } return response.TransactionID, nil @@ -289,7 +290,6 @@ func (a *ANX) CreateNewSubAccount(currency, name string) (string, error) { } if response.ResultCode != "OK" { - log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return "", errors.New(response.ResultCode) } return response.SubAccount, nil @@ -308,7 +308,7 @@ func (a *ANX) GetDepositAddressByCurrency(currency, name string, newAddr bool) ( Address string `json:"address"` SubAccount string `json:"subAccount"` ResultCode string `json:"resultCode"` - Timestamp int64 `json:"timestamp"` + Timestamp int64 `json:"timestamp,string"` } var response AddressResponse @@ -323,7 +323,6 @@ func (a *ANX) GetDepositAddressByCurrency(currency, name string, newAddr bool) ( } if response.ResultCode != "OK" { - log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode) return "", errors.New(response.ResultCode) } @@ -332,7 +331,16 @@ func (a *ANX) GetDepositAddressByCurrency(currency, name string, newAddr bool) ( // SendHTTPRequest sends an unauthenticated HTTP request func (a *ANX) SendHTTPRequest(path string, result interface{}) error { - return a.SendPayload(http.MethodGet, path, nil, nil, result, false, false, a.Verbose, a.HTTPDebugging) + return a.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + a.Verbose, + a.HTTPDebugging, + a.HTTPRecording) } // SendAuthenticatedHTTPRequest sends a authenticated HTTP request @@ -365,9 +373,16 @@ func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interf headers["Rest-Sign"] = crypto.Base64Encode(hmac) headers["Content-Type"] = "application/json" - return a.SendPayload(http.MethodPost, a.API.Endpoints.URL+path, headers, - bytes.NewBuffer(PayloadJSON), result, true, true, a.Verbose, - a.HTTPDebugging) + return a.SendPayload(http.MethodPost, + a.API.Endpoints.URL+path, + headers, + bytes.NewBuffer(PayloadJSON), + result, + true, + true, + a.Verbose, + a.HTTPDebugging, + a.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/anx/anx_live_test.go b/exchanges/anx/anx_live_test.go new file mode 100644 index 00000000..803916eb --- /dev/null +++ b/exchanges/anx/anx_live_test.go @@ -0,0 +1,32 @@ +//+build mock_test_off + +// This will build if build tag mock_test_off is parsed and will do live testing +// using all tests in (exchange)_test.go +package anx + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +var mockTests = false + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + anxConfig, err := cfg.GetExchangeConfig("ANX") + if err != nil { + log.Fatalf("Test Failed - ANX Setup() init error: %s", err) + } + anxConfig.API.AuthenticatedSupport = true + anxConfig.API.Credentials.Key = apiKey + anxConfig.API.Credentials.Secret = apiSecret + a.SetDefaults() + a.Setup(anxConfig) + log.Printf(sharedtestvalues.LiveTesting, a.GetName(), a.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/anx/anx_mock_test.go b/exchanges/anx/anx_mock_test.go new file mode 100644 index 00000000..39d6c479 --- /dev/null +++ b/exchanges/anx/anx_mock_test.go @@ -0,0 +1,45 @@ +//+build !mock_test_off + +// This will build if build tag mock_test_off is not parsed and will try to mock +// all tests in _test.go +package anx + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/mock" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +const mockFile = "../../testdata/http_mock/anx/anx.json" + +var mockTests = true + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + anxConfig, err := cfg.GetExchangeConfig("ANX") + if err != nil { + log.Fatal("Test Failed - Mock server error", err) + } + a.SkipAuthCheck = true + anxConfig.API.AuthenticatedSupport = true + anxConfig.API.Credentials.Key = apiKey + anxConfig.API.Credentials.Secret = apiSecret + a.SetDefaults() + a.Setup(anxConfig) + + serverDetails, newClient, err := mock.NewVCRServer(mockFile) + if err != nil { + log.Fatalf("Test Failed - Mock server error %s", err) + } + + a.HTTPClient = newClient + a.API.Endpoints.URL = serverDetails + "/" + + log.Printf(sharedtestvalues.MockTesting, a.GetName(), a.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index b11547ed..a583247c 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -19,52 +18,16 @@ const ( var a ANX -func TestSetDefaults(t *testing.T) { - a.SetDefaults() - - if a.Name != "ANX" { - t.Error("Test Failed - ANX SetDefaults() incorrect values set") - } - if !a.Enabled { - t.Error("Test Failed - ANX SetDefaults() incorrect values set") - } - if !a.Verbose { - t.Error("Test Failed - ANX SetDefaults() incorrect values set") - } -} - -func TestSetup(t *testing.T) { - anxSetupConfig := config.GetConfig() - anxSetupConfig.LoadConfig("../../testdata/configtest.json") - anxConfig, err := anxSetupConfig.GetExchangeConfig("ANX") - if err != nil { - t.Error("Test Failed - ANX Setup() init error") - } - - a.Setup(anxConfig) - a.API.Credentials.Key = apiKey - a.API.Credentials.Secret = apiSecret - a.API.AuthenticatedSupport = true - - if !a.Enabled { - t.Error("Test Failed - ANX Setup() incorrect values set") - } - if a.Verbose { - t.Error("Test Failed - ANX Setup() incorrect values set") - } - if len(a.BaseCurrencies) == 0 { - t.Error("Test Failed - ANX Setup() incorrect values set") - } -} - func TestGetCurrencies(t *testing.T) { + t.Parallel() _, err := a.GetCurrencies() if err != nil { t.Fatalf("Test failed. TestGetCurrencies failed. Err: %s", err) } } -func TestFetchTradablePairs(t *testing.T) { +func TestGetTradablePairs(t *testing.T) { + t.Parallel() _, err := a.FetchTradablePairs(asset.Spot) if err != nil { t.Fatalf("Test failed. TestGetTradablePairs failed. Err: %s", err) @@ -72,6 +35,7 @@ func TestFetchTradablePairs(t *testing.T) { } func TestGetTicker(t *testing.T) { + t.Parallel() ticker, err := a.GetTicker("BTCUSD") if err != nil { t.Errorf("Test Failed - ANX GetTicker() error: %s", err) @@ -82,16 +46,18 @@ func TestGetTicker(t *testing.T) { } func TestGetDepth(t *testing.T) { - ticker, err := a.GetDepth("BTCUSD") + t.Parallel() + depth, err := a.GetDepth("BTCUSD") if err != nil { t.Errorf("Test Failed - ANX GetDepth() error: %s", err) } - if ticker.Result != "success" { + if depth.Result != "success" { t.Error("Test Failed - ANX GetDepth() unsuccessful") } } func TestGetAPIKey(t *testing.T) { + t.Parallel() apiKey, apiSecret, err := a.GetAPIKey("userName", "passWord", "", "1337") if err == nil { t.Error("Test Failed - ANX GetAPIKey() Incorrect") @@ -116,6 +82,7 @@ func setFeeBuilder() *exchange.FeeBuilder { // TestGetFeeByTypeOfflineTradeFee logic test func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { + t.Parallel() var feeBuilder = setFeeBuilder() a.GetFeeByType(feeBuilder) if apiKey == "" || apiSecret == "" { @@ -130,9 +97,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - a.SetDefaults() - TestSetup(t) - + t.Parallel() var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic @@ -202,7 +167,7 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - a.SetDefaults() + t.Parallel() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.WithdrawCryptoWithEmailText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText @@ -214,34 +179,36 @@ func TestFormatWithdrawPermissions(t *testing.T) { } func TestGetActiveOrders(t *testing.T) { - a.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, } _, err := a.GetActiveOrders(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Errorf("Could not get open orders: %s", err) } } func TestGetOrderHistory(t *testing.T) { - a.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, } _, err := a.GetOrderHistory(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - GetBalance() error", err) } } @@ -253,10 +220,8 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - a.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -267,13 +232,14 @@ func TestSubmitOrder(t *testing.T) { Quote: currency.USD, }, OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderType: exchange.MarketOrderType, Price: 1, Amount: 1, ClientID: "meowOrder", } response, err := a.SubmitOrder(orderSubmission) - if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { + if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) && !mockTests { + // TODO: QA Pass to submit order t.Errorf("Order failed to be placed: %v", err) } else if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") @@ -281,10 +247,8 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - a.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -298,18 +262,19 @@ func TestCancelExchangeOrder(t *testing.T) { } err := a.CancelOrder(orderCancellation) - if areTestAPIKeysSet() && err != nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Could not cancel order: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Errorf("Could not cancel order: %s", err) } } func TestCancelAllExchangeOrders(t *testing.T) { - a.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -323,11 +288,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { } resp, err := a.CancelAllOrders(orderCancellation) - - if areTestAPIKeysSet() && err != nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Could not cancel order: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err == nil: + t.Errorf("QA pass needs to be completed and mock needs to be updated error cannot be nil") } if len(resp.OrderStatus) > 0 { @@ -336,20 +303,20 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - if apiKey != "" || apiSecret != "" { - _, err := a.GetAccountInfo() - if err != nil { - t.Error("test failed - GetAccountInfo() error:", err) - } - } else { - _, err := a.GetAccountInfo() - if err == nil { - t.Error("test failed - GetAccountInfo() error") - } + t.Parallel() + _, err := a.GetAccountInfo() + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Error("test failed - GetAccountInfo() error:", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("test failed - GetAccountInfo() error") + case mockTests && err != nil: + t.Error("test failed - GetAccountInfo() error:", err) } } func TestModifyOrder(t *testing.T) { + t.Parallel() _, err := a.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { t.Error("Test failed - ModifyOrder() error") @@ -357,10 +324,8 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - a.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -375,55 +340,54 @@ func TestWithdraw(t *testing.T) { } _, err := a.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) - if areTestAPIKeysSet() && err != nil { + if areTestAPIKeysSet() && err != nil && !mockTests { t.Errorf("Withdraw failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { + } else if !areTestAPIKeysSet() && err == nil && mockTests { t.Error("Expecting an error when no keys are set") } } func TestWithdrawFiat(t *testing.T) { - a.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + t.Parallel() var withdrawFiatRequest = exchange.FiatWithdrawRequest{} - _, err := a.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } func TestWithdrawInternationalBank(t *testing.T) { - a.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - + t.Parallel() var withdrawFiatRequest = exchange.FiatWithdrawRequest{} - _, err := a.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } func TestGetDepositAddress(t *testing.T) { - if areTestAPIKeysSet() { - _, err := a.GetDepositAddress(currency.BTC, "") - if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) - } - } else { - _, err := a.GetDepositAddress(currency.BTC, "") - if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") - } + t.Parallel() + _, err := a.GetDepositAddress(currency.BTC, "") + if areTestAPIKeysSet() && err != nil && !mockTests { + t.Error("Test Failed - GetDepositAddress() error", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } +} + +func TestUpdateOrderbook(t *testing.T) { + t.Parallel() + q := currency.Pair{ + Delimiter: "_", + Base: currency.BTC, + Quote: currency.USD} + + _, err := a.UpdateOrderbook(q, "spot") + if err != nil { + t.Fatalf("Update for orderbook failed: %v", err) } } diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 33568cb3..0cd37dd0 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -60,6 +60,7 @@ func (a *ANX) SetDefaults() { } a.API.CredentialsValidator.RequiresKey = true a.API.CredentialsValidator.RequiresSecret = true + a.API.CredentialsValidator.RequiresBase64DecodeSecret = true a.CurrencyPairs = currency.PairsManager{ AssetTypes: asset.Items{ @@ -464,7 +465,7 @@ func (a *ANX) GetWebsocket() (*wshandler.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (a *ANX) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if !a.AllowAuthenticatedRequest() && // Todo check connection status + if (!a.AllowAuthenticatedRequest() || a.SkipAuthCheck) && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 879f74a8..dc841f73 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -159,20 +159,10 @@ func (b *Binance) GetRecentTrades(rtr RecentTradeRequestParams) ([]RecentTrade, // limit: Optional. Default 500; max 1000. // fromID: func (b *Binance) GetHistoricalTrades(symbol string, limit int, fromID int64) ([]HistoricalTrade, error) { - var resp []HistoricalTrade - - if err := b.CheckLimit(limit); err != nil { - return resp, err - } - - params := url.Values{} - params.Set("symbol", strings.ToUpper(symbol)) - params.Set("limit", strconv.Itoa(limit)) - params.Set("fromid", strconv.FormatInt(fromID, 10)) - - path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, historicalTrades, params.Encode()) - - return resp, b.SendHTTPRequest(path, &resp) + // Dropping support due to response for market data is always + // {"code":-2014,"msg":"API-key format invalid."} + // TODO: replace with newer API vs REST endpoint + return nil, common.ErrFunctionNotSupported } // GetAggregatedTrades returns aggregated trade activity @@ -476,7 +466,16 @@ func (b *Binance) GetAccount() (*Account, error) { // SendHTTPRequest sends an unauthenticated request func (b *Binance) SendHTTPRequest(path string, result interface{}) error { - return b.SendPayload(http.MethodGet, path, nil, nil, result, false, false, b.Verbose, b.HTTPDebugging) + return b.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) } // SendAuthHTTPRequest sends an authenticated HTTP request @@ -512,7 +511,16 @@ func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, re Message string `json:"msg"` }{} - err := b.SendPayload(method, path, headers, bytes.NewBuffer(nil), &interim, true, false, b.Verbose, b.HTTPDebugging) + err := b.SendPayload(method, + path, + headers, + bytes.NewBuffer(nil), + &interim, + true, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) if err != nil { return err } @@ -632,7 +640,7 @@ func getCryptocurrencyWithdrawalFee(c currency.Code) float64 { } // WithdrawCrypto sends cryptocurrency to the address of your choosing -func (b *Binance) WithdrawCrypto(asset, address, addressTag, name, amount string) (int64, error) { +func (b *Binance) WithdrawCrypto(asset, address, addressTag, name, amount string) (string, error) { var resp WithdrawResponse path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, withdraw) @@ -648,7 +656,7 @@ func (b *Binance) WithdrawCrypto(asset, address, addressTag, name, amount string } if err := b.SendAuthHTTPRequest(http.MethodPost, path, params, &resp); err != nil { - return -1, err + return "", err } if !resp.Success { diff --git a/exchanges/binance/binance_live_test.go b/exchanges/binance/binance_live_test.go new file mode 100644 index 00000000..2d70cfb3 --- /dev/null +++ b/exchanges/binance/binance_live_test.go @@ -0,0 +1,32 @@ +//+build mock_test_off + +// This will build if build tag mock_test_off is parsed and will do live testing +// using all tests in (exchange)_test.go +package binance + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +var mockTests = false + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + binanceConfig, err := cfg.GetExchangeConfig("Binance") + if err != nil { + log.Fatal("Test Failed - Binance Setup() init error", err) + } + binanceConfig.API.AuthenticatedSupport = true + binanceConfig.API.Credentials.Key = apiKey + binanceConfig.API.Credentials.Secret = apiSecret + b.SetDefaults() + b.Setup(binanceConfig) + log.Printf(sharedtestvalues.LiveTesting, b.GetName(), b.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/binance/binance_mock_test.go b/exchanges/binance/binance_mock_test.go new file mode 100644 index 00000000..b258744a --- /dev/null +++ b/exchanges/binance/binance_mock_test.go @@ -0,0 +1,45 @@ +//+build !mock_test_off + +// This will build if build tag mock_test_off is not parsed and will try to mock +// all tests in _test.go +package binance + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/mock" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +const mockfile = "../../testdata/http_mock/binance/binance.json" + +var mockTests = true + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + binanceConfig, err := cfg.GetExchangeConfig("Binance") + if err != nil { + log.Fatal("Test Failed - Binance Setup() init error", err) + } + b.SkipAuthCheck = true + binanceConfig.API.AuthenticatedSupport = true + binanceConfig.API.Credentials.Key = apiKey + binanceConfig.API.Credentials.Secret = apiSecret + b.SetDefaults() + b.Setup(binanceConfig) + + serverDetails, newClient, err := mock.NewVCRServer(mockfile) + if err != nil { + log.Fatalf("Test Failed - Mock server error %s", err) + } + + b.HTTPClient = newClient + b.API.Endpoints.URL = serverDetails + + log.Printf(sharedtestvalues.MockTesting, b.GetName(), b.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index ebf5c613..9e815e2a 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -19,26 +18,22 @@ const ( var b Binance -func TestSetDefaults(t *testing.T) { - b.SetDefaults() +func areTestAPIKeysSet() bool { + return b.ValidateAPICredentials() } -func TestSetup(t *testing.T) { - cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") - binanceConfig, err := cfg.GetExchangeConfig("Binance") - if err != nil { - t.Error("Test Failed - Binance Setup() init error") +func setFeeBuilder() *exchange.FeeBuilder { + return &exchange.FeeBuilder{ + Amount: 1, + FeeType: exchange.CryptocurrencyTradeFee, + Pair: currency.NewPair(currency.BTC, currency.LTC), + PurchasePrice: 1, } - - binanceConfig.API.AuthenticatedSupport = true - binanceConfig.API.Credentials.Key = apiKey - binanceConfig.API.Credentials.Secret = apiSecret - b.Setup(binanceConfig) } func TestFetchTradablePairs(t *testing.T) { t.Parallel() + _, err := b.FetchTradablePairs(asset.Spot) if err != nil { t.Error("Test Failed - Binance FetchTradablePairs(asset asets.AssetType) error", err) @@ -47,6 +42,7 @@ func TestFetchTradablePairs(t *testing.T) { func TestGetOrderBook(t *testing.T) { t.Parallel() + _, err := b.GetOrderBook(OrderBookDataRequestParams{ Symbol: "BTCUSDT", Limit: 10, @@ -72,14 +68,19 @@ func TestGetRecentTrades(t *testing.T) { func TestGetHistoricalTrades(t *testing.T) { t.Parallel() - _, err := b.GetHistoricalTrades("BTCUSDT", 5, 1337) - if err == nil { + + _, err := b.GetHistoricalTrades("BTCUSDT", 5, 0) + if !mockTests && err == nil { + t.Error("Test Failed - Binance GetHistoricalTrades() expecting error") + } + if mockTests && err == nil { t.Error("Test Failed - Binance GetHistoricalTrades() error", err) } } func TestGetAggregatedTrades(t *testing.T) { t.Parallel() + _, err := b.GetAggregatedTrades("BTCUSDT", 5) if err != nil { t.Error("Test Failed - Binance GetAggregatedTrades() error", err) @@ -88,6 +89,7 @@ func TestGetAggregatedTrades(t *testing.T) { func TestGetSpotKline(t *testing.T) { t.Parallel() + _, err := b.GetSpotKline(KlinesRequestParams{ Symbol: "BTCUSDT", Interval: TimeIntervalFiveMinutes, @@ -100,6 +102,7 @@ func TestGetSpotKline(t *testing.T) { func TestGetAveragePrice(t *testing.T) { t.Parallel() + _, err := b.GetAveragePrice("BTCUSDT") if err != nil { t.Error("Test Failed - Binance GetAveragePrice() error", err) @@ -108,6 +111,7 @@ func TestGetAveragePrice(t *testing.T) { func TestGetPriceChangeStats(t *testing.T) { t.Parallel() + _, err := b.GetPriceChangeStats("BTCUSDT") if err != nil { t.Error("Test Failed - Binance GetPriceChangeStats() error", err) @@ -116,6 +120,7 @@ func TestGetPriceChangeStats(t *testing.T) { func TestGetTickers(t *testing.T) { t.Parallel() + _, err := b.GetTickers() if err != nil { t.Error("Test Failed - Binance TestGetTickers error", err) @@ -124,6 +129,7 @@ func TestGetTickers(t *testing.T) { func TestGetLatestSpotPrice(t *testing.T) { t.Parallel() + _, err := b.GetLatestSpotPrice("BTCUSDT") if err != nil { t.Error("Test Failed - Binance GetLatestSpotPrice() error", err) @@ -132,117 +138,59 @@ func TestGetLatestSpotPrice(t *testing.T) { func TestGetBestPrice(t *testing.T) { t.Parallel() + _, err := b.GetBestPrice("BTCUSDT") if err != nil { t.Error("Test Failed - Binance GetBestPrice() error", err) } } -func TestNewOrder(t *testing.T) { - t.Parallel() - - if apiKey == "" || apiSecret == "" { - t.Skip() - } - _, err := b.NewOrder(&NewOrderRequest{ - Symbol: "BTCUSDT", - Side: exchange.SellOrderSide.ToString(), - TradeType: BinanceRequestParamsOrderLimit, - TimeInForce: BinanceRequestParamsTimeGTC, - Quantity: 0.01, - Price: 1536.1, - }) - - if err == nil { - t.Error("Test Failed - Binance NewOrder() error", err) - } -} - -func TestCancelExistingOrder(t *testing.T) { - t.Parallel() - - if apiKey == "" || apiSecret == "" { - t.Skip() - } - - _, err := b.CancelExistingOrder("BTCUSDT", 82584683, "") - if err != nil { - t.Error("Test Failed - Binance CancelExistingOrder() error", err) - } -} - func TestQueryOrder(t *testing.T) { t.Parallel() - if apiKey == "" || apiSecret == "" { - t.Skip() - } - _, err := b.QueryOrder("BTCUSDT", "", 1337) - if err == nil { - t.Error("Test Failed - Binance QueryOrder() error", err) + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - QueryOrder() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - QueryOrder() expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock QueryOrder() error", err) } } func TestOpenOrders(t *testing.T) { t.Parallel() - if apiKey == "" || apiSecret == "" { - t.Skip() - } - _, err := b.OpenOrders("BTCUSDT") - if err != nil { - t.Error("Test Failed - Binance OpenOrders() error", err) + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - OpenOrders() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - OpenOrders() expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock OpenOrders() error", err) } } func TestAllOrders(t *testing.T) { t.Parallel() - if apiKey == "" || apiSecret == "" { - t.Skip() - } - _, err := b.AllOrders("BTCUSDT", "", "") - if err != nil { - t.Error("Test Failed - Binance AllOrders() error", err) - } -} - -func TestGetAccount(t *testing.T) { - if apiKey == "" || apiSecret == "" { - t.Skip() - } - t.Parallel() - b.SetDefaults() - TestSetup(t) - account, err := b.GetAccount() - if err != nil { - t.Fatal("Test Failed - Binance GetAccount() error", err) - } - if account.MakerCommission <= 0 { - t.Fatalf("Test Failed. Expected > 0, Received %d", account.MakerCommission) - } - if account.TakerCommission <= 0 { - t.Fatalf("Test Failed. Expected > 0, Received %d", account.TakerCommission) - } - - t.Logf("Current makerFee: %d", account.MakerCommission) - t.Logf("Current takerFee: %d", account.TakerCommission) -} - -func setFeeBuilder() *exchange.FeeBuilder { - return &exchange.FeeBuilder{ - Amount: 1, - FeeType: exchange.CryptocurrencyTradeFee, - Pair: currency.NewPair(currency.BTC, currency.LTC), - PurchasePrice: 1, + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - AllOrders() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - AllOrders() expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock AllOrders() error", err) } } // TestGetFeeByTypeOfflineTradeFee logic test func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { + t.Parallel() + var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) if apiKey == "" || apiSecret == "" { @@ -257,12 +205,11 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() var feeBuilder = setFeeBuilder() - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() || mockTests { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.1) || err != nil { t.Error(err) @@ -332,19 +279,18 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() + t.Parallel() + expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, @@ -359,16 +305,18 @@ func TestGetActiveOrders(t *testing.T) { } _, err = b.GetActiveOrders(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - GetActiveOrders() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - GetActiveOrders() expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock GetActiveOrders() error", err) } } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, @@ -384,125 +332,133 @@ func TestGetOrderHistory(t *testing.T) { currency.BTC)} _, err = b.GetOrderHistory(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - GetOrderHistory() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - GetOrderHistory() expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock GetOrderHistory() error", err) } } // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ----------------------------------------------------------------------------------------------------------------------------- -func areTestAPIKeysSet() bool { - return b.ValidateAPICredentials() -} - func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() + + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } var orderSubmission = &exchange.OrderSubmission{ Pair: currency.Pair{ Delimiter: "_", - Base: currency.BTC, - Quote: currency.USD, + Base: currency.LTC, + Quote: currency.BTC, }, OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderType: exchange.MarketOrderType, Price: 1, - Amount: 1, + Amount: 1000000000, ClientID: "meowOrder", } - response, err := b.SubmitOrder(orderSubmission) - if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { - t.Errorf("Order failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") + _, err := b.SubmitOrder(orderSubmission) + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - SubmitOrder() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - SubmitOrder() expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock SubmitOrder() error", err) } } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() - currencyPair := currency.NewPair(currency.LTC, currency.BTC) + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } var orderCancellation = &exchange.OrderCancellation{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", - CurrencyPair: currencyPair, + CurrencyPair: currency.NewPair(currency.LTC, currency.BTC), } err := b.CancelOrder(orderCancellation) - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not cancel orders: %v", err) + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - CancelExchangeOrder() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - CancelExchangeOrder() expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock CancelExchangeOrder() error", err) } } func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() - currencyPair := currency.NewPair(currency.LTC, currency.BTC) + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } var orderCancellation = &exchange.OrderCancellation{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", - CurrencyPair: currencyPair, + CurrencyPair: currency.NewPair(currency.LTC, currency.BTC), } - resp, err := b.CancelAllOrders(orderCancellation) - - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not cancel order: %v", err) - } - - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + _, err := b.CancelAllOrders(orderCancellation) + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - CancelAllExchangeOrders() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - CancelAllExchangeOrders() expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock CancelAllExchangeOrders() error", err) } } func TestGetAccountInfo(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if apiKey == "" || apiSecret == "" { - t.Skip() - } + t.Parallel() _, err := b.GetAccountInfo() - if err != nil { - t.Error("test failed - GetAccountInfo() error", err) + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - GetAccountInfo() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - GetAccountInfo() expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock GetAccountInfo() error", err) } } func TestModifyOrder(t *testing.T) { + t.Parallel() + _, err := b.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("Test failed - ModifyOrder() error cannot be nil") } } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ - Amount: -1, + Amount: 0, Currency: currency.BTC, Description: "WITHDRAW IT ALL", }, @@ -510,24 +466,20 @@ func TestWithdraw(t *testing.T) { } _, err := b.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - Withdraw() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - Withdraw() expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock Withdraw() error", err) } } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - - var withdrawFiatRequest = exchange.FiatWithdrawRequest{} + t.Parallel() + var withdrawFiatRequest exchange.FiatWithdrawRequest _, err := b.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -535,15 +487,9 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - - var withdrawFiatRequest = exchange.FiatWithdrawRequest{} + t.Parallel() + var withdrawFiatRequest exchange.FiatWithdrawRequest _, err := b.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) @@ -551,15 +497,15 @@ func TestWithdrawInternationalBank(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { - if areTestAPIKeysSet() { - _, err := b.GetDepositAddress(currency.BTC, "") - if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) - } - } else { - _, err := b.GetDepositAddress(currency.BTC, "") - if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") - } + t.Parallel() + + _, err := b.GetDepositAddress(currency.BTC, "") + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - GetDepositAddress() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + case mockTests && err != nil: + t.Error("Test Failed - Mock GetDepositAddress() error", err) } } diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index 6280ce0e..276b4480 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -314,7 +314,7 @@ type NewOrderResponse struct { Price float64 `json:"price,string"` Qty float64 `json:"qty,string"` Commission float64 `json:"commission,string"` - CommissionAsset float64 `json:"commissionAsset,string"` + CommissionAsset string `json:"commissionAsset"` } `json:"fills"` } @@ -610,5 +610,5 @@ var WithdrawalFees = map[currency.Code]float64{ type WithdrawResponse struct { Success bool `json:"success"` Msg string `json:"msg"` - ID int64 `json:"id"` + ID string `json:"id"` } diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 169a81a1..5966e596 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -444,9 +444,10 @@ func (b *Binance) GetDepositAddress(cryptocurrency currency.Code, _ string) (str // submitted func (b *Binance) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { amountStr := strconv.FormatFloat(withdrawRequest.Amount, 'f', -1, 64) - id, err := b.WithdrawCrypto(withdrawRequest.Currency.String(), withdrawRequest.Address, withdrawRequest.AddressTag, withdrawRequest.Description, amountStr) - - return strconv.FormatInt(id, 10), err + return b.WithdrawCrypto(withdrawRequest.Currency.String(), + withdrawRequest.Address, + withdrawRequest.AddressTag, + withdrawRequest.Description, amountStr) } // WithdrawFiatFunds returns a withdrawal ID when a @@ -468,7 +469,7 @@ func (b *Binance) GetWebsocket() (*wshandler.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (b *Binance) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if !b.AllowAuthenticatedRequest() && // Todo check connection status + if (!b.AllowAuthenticatedRequest() || b.SkipAuthCheck) && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 88f0a556..a02bc5a6 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -922,7 +922,16 @@ func (b *Bitfinex) CloseMarginFunding(swapID int64) (Offer, error) { // SendHTTPRequest sends an unauthenticated request func (b *Bitfinex) SendHTTPRequest(path string, result interface{}, verbose bool) error { - return b.SendPayload(http.MethodGet, path, nil, nil, result, false, false, verbose, b.HTTPDebugging) + return b.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + verbose, + b.HTTPDebugging, + b.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an autheticated http request and json @@ -968,7 +977,8 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequest(method, path string, params map[ true, true, b.Verbose, - b.HTTPDebugging) + b.HTTPDebugging, + b.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/bitflyer/bitflyer.go b/exchanges/bitflyer/bitflyer.go index c0b52d8b..e0fb6b6a 100644 --- a/exchanges/bitflyer/bitflyer.go +++ b/exchanges/bitflyer/bitflyer.go @@ -306,7 +306,16 @@ func (b *Bitflyer) GetTradingCommission() { // SendHTTPRequest sends an unauthenticated request func (b *Bitflyer) SendHTTPRequest(path string, result interface{}) error { - return b.SendPayload(http.MethodGet, path, nil, nil, result, false, false, b.Verbose, b.HTTPDebugging) + return b.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) } // SendAuthHTTPRequest sends an authenticated HTTP request diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go index e5373956..77589394 100644 --- a/exchanges/bithumb/bithumb.go +++ b/exchanges/bithumb/bithumb.go @@ -459,7 +459,16 @@ func (b *Bithumb) MarketSellOrder(currency string, units float64) (MarketSell, e // SendHTTPRequest sends an unauthenticated HTTP request func (b *Bithumb) SendHTTPRequest(path string, result interface{}) error { - return b.SendPayload(http.MethodGet, path, nil, nil, result, false, false, b.Verbose, b.HTTPDebugging) + return b.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to bithumb @@ -503,7 +512,8 @@ func (b *Bithumb) SendAuthenticatedHTTPRequest(path string, params url.Values, r true, true, b.Verbose, - b.HTTPDebugging) + b.HTTPDebugging, + b.HTTPRecording) if err != nil { return err } diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index 8bd998d5..33f17238 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -775,14 +775,32 @@ func (b *Bitmex) SendHTTPRequest(path string, params Parameter, result interface if err != nil { return err } - err = b.SendPayload(http.MethodGet, encodedPath, nil, nil, &respCheck, false, false, b.Verbose, b.HTTPDebugging) + err = b.SendPayload(http.MethodGet, + encodedPath, + nil, + nil, + &respCheck, + false, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) if err != nil { return err } return b.CaptureError(respCheck, result) } } - err := b.SendPayload(http.MethodGet, path, nil, nil, &respCheck, false, false, b.Verbose, b.HTTPDebugging) + err := b.SendPayload(http.MethodGet, + path, + nil, + nil, + &respCheck, + false, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) if err != nil { return err } @@ -834,7 +852,8 @@ func (b *Bitmex) SendAuthenticatedHTTPRequest(verb, path string, params Paramete true, false, b.Verbose, - b.HTTPDebugging) + b.HTTPDebugging, + b.HTTPRecording) if err != nil { return err } diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 32b77c01..7a91c74b 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -1,6 +1,7 @@ package bitstamp import ( + "bytes" "encoding/json" "errors" "fmt" @@ -68,7 +69,6 @@ func (b *Bitstamp) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) { switch feeBuilder.FeeType { case exchange.CryptocurrencyTradeFee: var err error - b.Balance, err = b.GetBalance() if err != nil { return 0, err @@ -260,16 +260,15 @@ func (b *Bitstamp) GetEURUSDConversionRate() (EURUSDConversionRate, error) { // GetBalance returns full balance of currency held on the exchange func (b *Bitstamp) GetBalance() (Balances, error) { - balance := Balances{} - path := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, bitstampAPIBalance) - - return balance, b.SendHTTPRequest(path, &balance) + var balance Balances + return balance, + b.SendAuthenticatedHTTPRequest(bitstampAPIBalance, true, nil, &balance) } // GetUserTransactions returns an array of transactions func (b *Bitstamp) GetUserTransactions(currencyPair string) ([]UserTransactions, error) { type Response struct { - Date int64 `json:"datetime"` + Date string `json:"datetime"` TransID int64 `json:"id"` Type int `json:"type,string"` USD interface{} `json:"usd"` @@ -282,12 +281,18 @@ func (b *Bitstamp) GetUserTransactions(currencyPair string) ([]UserTransactions, } var response []Response - if currencyPair != "" { - if err := b.SendAuthenticatedHTTPRequest(bitstampAPIUserTransactions, true, url.Values{}, &response); err != nil { + if currencyPair == "" { + if err := b.SendAuthenticatedHTTPRequest(bitstampAPIUserTransactions, + true, + url.Values{}, + &response); err != nil { return nil, err } } else { - if err := b.SendAuthenticatedHTTPRequest(bitstampAPIUserTransactions+"/"+currencyPair, true, url.Values{}, &response); err != nil { + if err := b.SendAuthenticatedHTTPRequest(bitstampAPIUserTransactions+"/"+currencyPair, + true, + url.Values{}, + &response); err != nil { return nil, err } } @@ -383,10 +388,11 @@ func (b *Bitstamp) PlaceOrder(currencyPair string, price, amount float64, buy, m orderType = exchange.SellOrderSide.ToLower().ToString() } - path := fmt.Sprintf("%s/%s", orderType, strings.ToLower(currencyPair)) - + var path string if market { path = fmt.Sprintf("%s/%s/%s", orderType, bitstampAPIMarket, strings.ToLower(currencyPair)) + } else { + path = fmt.Sprintf("%s/%s", orderType, strings.ToLower(currencyPair)) } return response, @@ -546,28 +552,41 @@ func (b *Bitstamp) GetUnconfirmedBitcoinDeposits() ([]UnconfirmedBTCTransactions // currency - which currency to transfer // subaccount - name of account // toMain - bool either to or from account -func (b *Bitstamp) TransferAccountBalance(amount float64, currency, subAccount string, toMain bool) (bool, error) { +func (b *Bitstamp) TransferAccountBalance(amount float64, currency, subAccount string, toMain bool) error { var req = url.Values{} req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("currency", currency) + + if subAccount == "" { + return errors.New("missing subAccount parameter") + } + req.Add("subAccount", subAccount) - path := bitstampAPITransferToMain - if !toMain { + var path string + if toMain { + path = bitstampAPITransferToMain + } else { path = bitstampAPITransferFromMain } - err := b.SendAuthenticatedHTTPRequest(path, true, req, nil) - if err != nil { - return false, err - } + var resp interface{} - return true, nil + return b.SendAuthenticatedHTTPRequest(path, true, req, &resp) } // SendHTTPRequest sends an unauthenticated HTTP request func (b *Bitstamp) SendHTTPRequest(path string, result interface{}) error { - return b.SendPayload(http.MethodGet, path, nil, nil, result, false, false, b.Verbose, b.HTTPDebugging) + return b.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated request @@ -603,15 +622,26 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url headers["Content-Type"] = "application/x-www-form-urlencoded" encodedValues := values.Encode() - readerValues := strings.NewReader(encodedValues) + readerValues := bytes.NewBufferString(encodedValues) interim := json.RawMessage{} errCap := struct { - Error string `json:"error"` + Error string `json:"error"` + Status string `json:"status"` + Reason interface{} `json:"reason"` }{} - err := b.SendPayload(http.MethodPost, path, headers, readerValues, &interim, true, true, b.Verbose, b.HTTPDebugging) + err := b.SendPayload(http.MethodPost, + path, + headers, + readerValues, + &interim, + true, + true, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) if err != nil { return err } @@ -620,6 +650,21 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url if errCap.Error != "" { return errors.New(errCap.Error) } + if data, ok := errCap.Reason.(map[string][]string); ok { + var details string + for _, v := range data { + details += strings.Join(v, "") + } + return errors.New(details) + } + + if data, ok := errCap.Reason.(string); ok { + return errors.New(data) + } + + if errCap.Status != "" { + return errors.New(errCap.Status) + } } return common.JSONDecode(interim, result) diff --git a/exchanges/bitstamp/bitstamp_live_test.go b/exchanges/bitstamp/bitstamp_live_test.go new file mode 100644 index 00000000..fd5e8df2 --- /dev/null +++ b/exchanges/bitstamp/bitstamp_live_test.go @@ -0,0 +1,33 @@ +//+build mock_test_off + +// This will build if build tag mock_test_off is parsed and will do live testing +// using all tests in (exchange)_test.go +package bitstamp + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +var mockTests = false + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + bitstampConfig, err := cfg.GetExchangeConfig("Bitstamp") + if err != nil { + log.Fatal("Test Failed - Bitstamp Setup() init error", err) + } + bitstampConfig.API.AuthenticatedSupport = true + bitstampConfig.API.Credentials.Key = apiKey + bitstampConfig.API.Credentials.Secret = apiSecret + bitstampConfig.API.Credentials.ClientID = customerID + b.SetDefaults() + b.Setup(bitstampConfig) + log.Printf(sharedtestvalues.LiveTesting, b.GetName(), b.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/bitstamp/bitstamp_mock_test.go b/exchanges/bitstamp/bitstamp_mock_test.go new file mode 100644 index 00000000..6075d94d --- /dev/null +++ b/exchanges/bitstamp/bitstamp_mock_test.go @@ -0,0 +1,46 @@ +//+build !mock_test_off + +// This will build if build tag mock_test_off is not parsed and will try to mock +// all tests in _test.go +package bitstamp + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/mock" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +const mockfile = "../../testdata/http_mock/bitstamp/bitstamp.json" + +var mockTests = true + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + bitstampConfig, err := cfg.GetExchangeConfig("Bitstamp") + if err != nil { + log.Fatal("Test Failed - Bitstamp Setup() init error", err) + } + b.SkipAuthCheck = true + bitstampConfig.API.AuthenticatedSupport = true + bitstampConfig.API.Credentials.Key = apiKey + bitstampConfig.API.Credentials.Secret = apiSecret + bitstampConfig.API.Credentials.ClientID = customerID + b.SetDefaults() + b.Setup(bitstampConfig) + + serverDetails, newClient, err := mock.NewVCRServer(mockfile) + if err != nil { + log.Fatalf("Test Failed - Mock server error %s", err) + } + + b.HTTPClient = newClient + b.API.Endpoints.URL = serverDetails + "/api" + + log.Printf(sharedtestvalues.MockTesting, b.GetName(), b.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 65c4564a..78155d7d 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -4,7 +4,6 @@ import ( "net/url" "testing" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" ) @@ -19,40 +18,8 @@ const ( var b Bitstamp -func TestSetDefaults(t *testing.T) { - b.SetDefaults() - - if b.Name != "Bitstamp" { - t.Error("Test Failed - SetDefaults() error") - } - if !b.Enabled { - t.Error("Test Failed - SetDefaults() error") - } - if !b.Verbose { - t.Error("Test Failed - SetDefaults() error") - } - if b.Websocket.IsEnabled() { - t.Error("Test Failed - SetDefaults() error") - } -} - -func TestSetup(t *testing.T) { - cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") - bConfig, err := cfg.GetExchangeConfig("Bitstamp") - if err != nil { - t.Error("Test Failed - Bitstamp Setup() init error") - } - bConfig.API.Credentials.Key = apiKey - bConfig.API.Credentials.Secret = apiSecret - bConfig.API.Credentials.ClientID = customerID - - b.Setup(bConfig) - - if !b.IsEnabled() || b.API.AuthenticatedSupport || - b.Verbose || b.Websocket.IsEnabled() || len(b.BaseCurrencies) < 1 { - t.Error("Test Failed - Bitstamp Setup values not set correctly") - } +func areTestAPIKeysSet() bool { + return b.ValidateAPICredentials() } func setFeeBuilder() *exchange.FeeBuilder { @@ -66,53 +33,66 @@ func setFeeBuilder() *exchange.FeeBuilder { // TestGetFeeByTypeOfflineTradeFee logic test func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { + t.Parallel() + var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) if apiKey == "" || apiSecret == "" { if feeBuilder.FeeType != exchange.OfflineTradeFee { - t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) + t.Errorf("Expected %v, received %v", + exchange.OfflineTradeFee, + feeBuilder.FeeType) } } else { if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee { - t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) + t.Errorf("Expected %v, received %v", + exchange.CryptocurrencyTradeFee, + feeBuilder.FeeType) } } } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic - if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { + if resp, err := b.GetFee(feeBuilder); resp != float64(0) || (areTestAPIKeysSet() && err != nil) { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) } // CryptocurrencyTradeFee High quantity feeBuilder = setFeeBuilder() feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 - if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + if resp, err := b.GetFee(feeBuilder); resp != float64(0) || (areTestAPIKeysSet() && err != nil) { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } // CryptocurrencyTradeFee IsMaker feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true - if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + if resp, err := b.GetFee(feeBuilder); resp != float64(0) || (areTestAPIKeysSet() && err != nil) { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } // CryptocurrencyTradeFee Negative purchase price feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 - if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + if resp, err := b.GetFee(feeBuilder); resp != float64(0) || (areTestAPIKeysSet() && err != nil) { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } @@ -120,7 +100,9 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } @@ -128,7 +110,9 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } @@ -137,7 +121,9 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(7.5) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(7.5), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(7.5), + resp) t.Error(err) } @@ -146,15 +132,15 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(15) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(15), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(15), + resp) t.Error(err) } } func TestCalculateTradingFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) - b.Balance = Balances{} + t.Parallel() b.Balance.BTCUSDFee = 1 b.Balance.BTCEURFee = 0 @@ -176,18 +162,16 @@ func TestCalculateTradingFee(t *testing.T) { func TestGetTicker(t *testing.T) { t.Parallel() + _, err := b.GetTicker(currency.BTC.String()+currency.USD.String(), false) if err != nil { t.Error("Test Failed - GetTicker() error", err) } - _, err = b.GetTicker(currency.BTC.String()+currency.USD.String(), true) - if err != nil { - t.Error("Test Failed - GetTicker() error", err) - } } func TestGetOrderbook(t *testing.T) { t.Parallel() + _, err := b.GetOrderbook(currency.BTC.String() + currency.USD.String()) if err != nil { t.Error("Test Failed - GetOrderbook() error", err) @@ -196,6 +180,7 @@ func TestGetOrderbook(t *testing.T) { func TestGetTradingPairs(t *testing.T) { t.Parallel() + _, err := b.GetTradingPairs() if err != nil { t.Error("Test Failed - GetTradingPairs() error", err) @@ -204,6 +189,7 @@ func TestGetTradingPairs(t *testing.T) { func TestGetTransactions(t *testing.T) { t.Parallel() + value := url.Values{} value.Set("time", "hour") @@ -211,14 +197,11 @@ func TestGetTransactions(t *testing.T) { if err != nil { t.Error("Test Failed - GetTransactions() error", err) } - _, err = b.GetTransactions("wigwham", value) - if err == nil { - t.Error("Test Failed - GetTransactions() error") - } } func TestGetEURUSDConversionRate(t *testing.T) { t.Parallel() + _, err := b.GetEURUSDConversionRate() if err != nil { t.Error("Test Failed - GetEURUSDConversionRate() error", err) @@ -227,21 +210,28 @@ func TestGetEURUSDConversionRate(t *testing.T) { func TestGetBalance(t *testing.T) { t.Parallel() + _, err := b.GetBalance() - if err != nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Error("Test Failed - GetBalance() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: t.Error("Test Failed - GetBalance() error", err) } } func TestGetUserTransactions(t *testing.T) { t.Parallel() - _, err := b.GetUserTransactions("") - if err == nil { - t.Error("Test Failed - GetUserTransactions() error", err) - } - _, err = b.GetUserTransactions("btcusd") - if err == nil { + _, err := b.GetUserTransactions("btcusd") + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Error("Test Failed - GetUserTransactions() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: t.Error("Test Failed - GetUserTransactions() error", err) } } @@ -250,54 +240,27 @@ func TestGetOpenOrders(t *testing.T) { t.Parallel() _, err := b.GetOpenOrders("btcusd") - if err == nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Error("Test Failed - GetOpenOrders() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: t.Error("Test Failed - GetOpenOrders() error", err) - } - _, err = b.GetOpenOrders("wigwham") - if err == nil { - t.Error("Test Failed - GetOpenOrders() error") } } func TestGetOrderStatus(t *testing.T) { t.Parallel() - if !b.ValidateAPICredentials() { - t.Skip() - } _, err := b.GetOrderStatus(1337) - if err == nil { - t.Error("Test Failed - GetOpenOrders() error") - } -} - -func TestCancelExistingOrder(t *testing.T) { - t.Parallel() - - resp, err := b.CancelExistingOrder(1337) - if err == nil || resp { - t.Error("Test Failed - CancelExistingOrder() error") - } -} - -func TestCancelAllExistingOrders(t *testing.T) { - t.Parallel() - - _, err := b.CancelAllExistingOrders() - if err == nil { - t.Error("Test Failed - CancelAllExistingOrders() error", err) - } -} - -func TestPlaceOrder(t *testing.T) { - t.Parallel() - if !b.ValidateAPICredentials() { - t.Skip() - } - - _, err := b.PlaceOrder("btcusd", 0.01, 1, true, true) - if err == nil { - t.Error("Test Failed - PlaceOrder() error") + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Error("Test Failed - GetOrderStatus() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Expecting an error when no keys are set") + case mockTests && err == nil: + t.Error("Expecting an error until a QA pass can be completed") } } @@ -305,33 +268,13 @@ func TestGetWithdrawalRequests(t *testing.T) { t.Parallel() _, err := b.GetWithdrawalRequests(0) - if err == nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Error("Test Failed - GetWithdrawalRequests() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: t.Error("Test Failed - GetWithdrawalRequests() error", err) - } - _, err = b.GetWithdrawalRequests(-1) - if err == nil { - t.Error("Test Failed - GetWithdrawalRequests() error") - } -} - -func TestCryptoWithdrawal(t *testing.T) { - t.Parallel() - if !b.ValidateAPICredentials() { - t.Skip() - } - - _, err := b.CryptoWithdrawal(0, "bla", "btc", "", true) - if err == nil { - t.Error("Test Failed - CryptoWithdrawal() error", err) - } -} - -func TestGetBitcoinDepositAddress(t *testing.T) { - t.Parallel() - - _, err := b.GetCryptoDepositAddress(currency.BTC) - if err == nil { - t.Error("Test Failed - GetCryptoDepositAddress() error", err) } } @@ -339,81 +282,90 @@ func TestGetUnconfirmedBitcoinDeposits(t *testing.T) { t.Parallel() _, err := b.GetUnconfirmedBitcoinDeposits() - if err == nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Error("Test Failed - GetUnconfirmedBitcoinDeposits() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: t.Error("Test Failed - GetUnconfirmedBitcoinDeposits() error", err) } } func TestTransferAccountBalance(t *testing.T) { t.Parallel() - if !b.ValidateAPICredentials() { + + if !areTestAPIKeysSet() && !mockTests { t.Skip() } - _, err := b.TransferAccountBalance(1, "", "", true) - if err == nil { + err := b.TransferAccountBalance(0.01, "btc", "testAccount", true) + if !mockTests && err != nil { t.Error("Test Failed - TransferAccountBalance() error", err) } - - _, err = b.TransferAccountBalance(1, "btc", "", false) - if err == nil { - t.Error("Test Failed - TransferAccountBalance() error", err) + if mockTests && err == nil { + t.Error("Expecting an error until a QA pass can be completed") } } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() - expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.AutoWithdrawFiatText + t.Parallel() + + expectedResult := exchange.AutoWithdrawCryptoText + + " & " + + exchange.AutoWithdrawFiatText withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { - t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) + t.Errorf("Expected: %s, Received: %s", + expectedResult, + withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, } _, err := b.GetActiveOrders(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Errorf("Could not get open orders: %s", err) } } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, } _, err := b.GetOrderHistory(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Errorf("Could not get order history: %s", err) } } // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- -func areTestAPIKeysSet() bool { - return b.ValidateAPICredentials() -} func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Parallel() + + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -429,18 +381,20 @@ func TestSubmitOrder(t *testing.T) { ClientID: "meowOrder", } response, err := b.SubmitOrder(orderSubmission) - if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { + switch { + case areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) && !mockTests: t.Errorf("Order failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err == nil: + t.Error("Expecting an error until QA pass is completed") } } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -454,19 +408,20 @@ func TestCancelExchangeOrder(t *testing.T) { } err := b.CancelOrder(orderCancellation) - if !areTestAPIKeysSet() && err == nil { + switch { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Could not cancel orders: %v", err) + case mockTests && err == nil: + t.Error("Expecting an error until QA pass is completed") } } func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -480,11 +435,12 @@ func TestCancelAllExchangeOrders(t *testing.T) { } resp, err := b.CancelAllOrders(orderCancellation) - - if !areTestAPIKeysSet() && err == nil { + switch { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Errorf("Could not cancel orders: %v", err) + case mockTests && err != nil: t.Errorf("Could not cancel orders: %v", err) } @@ -494,6 +450,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + t.Parallel() + _, err := b.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { t.Error("Test failed - ModifyOrder() error") @@ -501,8 +459,11 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() + + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ @@ -513,24 +474,21 @@ func TestWithdraw(t *testing.T) { Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - _, err := b.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) - if !areTestAPIKeysSet() && err == nil { + switch { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Withdraw failed to be placed: %v", err) + case mockTests && err == nil: + t.Error("Expecting an error until QA pass is completed") } } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -555,19 +513,20 @@ func TestWithdrawFiat(t *testing.T) { } _, err := b.WithdrawFiatFunds(&withdrawFiatRequest) - if !areTestAPIKeysSet() && err == nil { + switch { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Withdraw failed to be placed: %v", err) + case mockTests && err == nil: + t.Error("Expecting an error until QA pass is completed") } } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -598,24 +557,26 @@ func TestWithdrawInternationalBank(t *testing.T) { } _, err := b.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) - if !areTestAPIKeysSet() && err == nil { + switch { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Withdraw failed to be placed: %v", err) + case mockTests && err == nil: + t.Error("Expecting an error until QA pass is completed") } } func TestGetDepositAddress(t *testing.T) { - if areTestAPIKeysSet() && customerID != "" { - _, err := b.GetDepositAddress(currency.BTC, "") - if err != nil { - t.Error("Test Failed - GetDepositAddress error", err) - } - } else { - _, err := b.GetDepositAddress(currency.BTC, "") - if err == nil { - t.Error("Test Failed - GetDepositAddress error cannot be nil") - } + t.Parallel() + + _, err := b.GetDepositAddress(currency.BTC, "") + switch { + case areTestAPIKeysSet() && customerID != "" && err != nil && !mockTests: + t.Error("Test Failed - GetDepositAddress error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - GetDepositAddress error cannot be nil") + case mockTests && err != nil: + t.Error("Test Failed - GetDepositAddress error", err) } } diff --git a/exchanges/bitstamp/bitstamp_types.go b/exchanges/bitstamp/bitstamp_types.go index 1cdc63a9..e8d8a676 100644 --- a/exchanges/bitstamp/bitstamp_types.go +++ b/exchanges/bitstamp/bitstamp_types.go @@ -77,7 +77,7 @@ type Balances struct { // UserTransactions holds user transaction information type UserTransactions struct { - Date int64 `json:"datetime"` + Date string `json:"datetime"` TransID int64 `json:"id"` Type int `json:"type,string"` USD float64 `json:"usd"` @@ -125,15 +125,15 @@ type WithdrawalRequests struct { // CryptoWithdrawalResponse response from a crypto withdrawal request type CryptoWithdrawalResponse struct { - ID string `json:"id"` - Error string `json:"error"` + ID string `json:"id"` + Error map[string][]string `json:"error"` } // FIATWithdrawalResponse response from a fiat withdrawal request type FIATWithdrawalResponse struct { - ID string `json:"id"` - Status string `json:"status"` - Reason string `json:"reason"` + ID string `json:"id"` + Status string `json:"status"` + Reason map[string][]string `json:"reason"` } // UnconfirmedBTCTransactions holds address information about unconfirmed diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index b09493ef..c07f84d5 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -235,7 +235,7 @@ func (b *Bitstamp) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pr // GetFeeByType returns an estimate of fee based on type of transaction func (b *Bitstamp) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if !b.AllowAuthenticatedRequest() && // Todo check connection status + if (!b.AllowAuthenticatedRequest() || b.SkipAuthCheck) && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } @@ -409,8 +409,12 @@ func (b *Bitstamp) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoW if err != nil { return "", err } - if resp.Error != "" { - return "", errors.New(resp.Error) + if len(resp.Error) != 0 { + var details string + for _, v := range resp.Error { + details += strings.Join(v, "") + } + return "", errors.New(details) } return resp.ID, nil @@ -427,7 +431,11 @@ func (b *Bitstamp) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawReque return "", err } if resp.Status == errStr { - return "", errors.New(resp.Reason) + var details string + for _, v := range resp.Reason { + details += strings.Join(v, "") + } + return "", errors.New(details) } return resp.ID, nil @@ -446,7 +454,11 @@ func (b *Bitstamp) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchang return "", err } if resp.Status == errStr { - return "", errors.New(resp.Reason) + var details string + for _, v := range resp.Reason { + details += strings.Join(v, "") + } + return "", errors.New(details) } return resp.ID, nil @@ -533,7 +545,12 @@ func (b *Bitstamp) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) quoteCurrency.String(), b.GetPairFormat(asset.Spot, false).Delimiter) } - orderDate := time.Unix(order.Date, 0) + + orderDate, err := time.Parse("2006-01-02 15:04:05", order.Date) + if err != nil { + return nil, err + } + orders = append(orders, exchange.OrderDetail{ ID: fmt.Sprintf("%v", order.OrderID), OrderDate: orderDate, diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index d4684dbe..6749252f 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -427,7 +427,16 @@ func (b *Bittrex) GetDepositHistory(currency string) (WithdrawalHistory, error) // SendHTTPRequest sends an unauthenticated HTTP request func (b *Bittrex) SendHTTPRequest(path string, result interface{}) error { - return b.SendPayload(http.MethodGet, path, nil, nil, result, false, false, b.Verbose, b.HTTPDebugging) + return b.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated http request to a desired @@ -448,7 +457,16 @@ func (b *Bittrex) SendAuthenticatedHTTPRequest(path string, values url.Values, r headers := make(map[string]string) headers["apisign"] = crypto.HexEncodeToString(hmac) - return b.SendPayload(http.MethodGet, rawQuery, headers, nil, result, true, true, b.Verbose, b.HTTPDebugging) + return b.SendPayload(http.MethodGet, + rawQuery, + headers, + nil, + result, + true, + true, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 1123157a..3105c5b0 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -364,7 +364,16 @@ func (b *BTCMarkets) WithdrawAUD(accountName, accountNumber, bankName, bsbNumber // SendHTTPRequest sends an unauthenticated HTTP request func (b *BTCMarkets) SendHTTPRequest(path string, result interface{}) error { - return b.SendPayload(http.MethodGet, path, nil, nil, result, false, false, b.Verbose, b.HTTPDebugging) + return b.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) } // SendAuthenticatedRequest sends an authenticated HTTP request @@ -415,7 +424,8 @@ func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data, result true, true, b.Verbose, - b.HTTPDebugging) + b.HTTPDebugging, + b.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/btse/btse.go b/exchanges/btse/btse.go index 0f47da0a..32be20ba 100644 --- a/exchanges/btse/btse.go +++ b/exchanges/btse/btse.go @@ -176,8 +176,16 @@ func (b *BTSE) GetFills(orderID, productID, before, after, limit string) (*Fille // SendHTTPRequest sends an HTTP request to the desired endpoint func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}) error { - p := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, endpoint) - return b.SendPayload(method, p, nil, nil, &result, false, false, b.Verbose, b.HTTPDebugging) + return b.SendPayload(method, + fmt.Sprintf("%s/%s", b.API.Endpoints.URL, endpoint), + nil, + nil, + &result, + false, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the desired endpoint @@ -203,8 +211,16 @@ func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[str if b.Verbose { log.Debugf(log.ExchangeSys, "Sending %s request to URL %s with params %s\n", method, p, string(payload)) } - return b.SendPayload(method, p, headers, strings.NewReader(string(payload)), - &result, true, false, b.Verbose, b.HTTPDebugging) + return b.SendPayload(method, + p, + headers, + strings.NewReader(string(payload)), + &result, + true, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index dafbfe1b..65a212a5 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -710,7 +710,16 @@ func (c *CoinbasePro) GetTrailingVolume() ([]Volume, error) { // SendHTTPRequest sends an unauthenticated HTTP request func (c *CoinbasePro) SendHTTPRequest(path string, result interface{}) error { - return c.SendPayload(http.MethodGet, path, nil, nil, result, false, false, c.Verbose, c.HTTPDebugging) + return c.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + c.Verbose, + c.HTTPDebugging, + c.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP reque @@ -751,7 +760,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(method, path string, params m true, true, c.Verbose, - c.HTTPDebugging) + c.HTTPDebugging, + c.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 1baf3025..acd52481 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -295,7 +295,7 @@ func (c *COINUT) SendHTTPRequest(apiRequest string, params map[string]interface{ true, c.Verbose, c.HTTPDebugging, - ) + c.HTTPRecording) if err != nil { return err } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index dfb585f9..f247b151 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -462,6 +462,11 @@ func (e *Base) SetupDefaults(exch *config.ExchangeConfig) error { // AllowAuthenticatedRequest checks to see if the required fields have been set before sending an authenticated // API request func (e *Base) AllowAuthenticatedRequest() bool { + // Skip auth check + if e.SkipAuthCheck { + return true + } + // Individual package usage, allow request if API credentials are valid a // and without needing to set AuthenticatedSupport to true if !e.LoadedByConfig && !e.ValidateAPICredentials() { diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index 33b36c5e..8e9ec272 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -304,12 +304,14 @@ type Base struct { Enabled bool Verbose bool LoadedByConfig bool + SkipAuthCheck bool API API BaseCurrencies currency.Currencies CurrencyPairs currency.PairsManager Features Features HTTPTimeout time.Duration HTTPUserAgent string + HTTPRecording bool HTTPDebugging bool WebsocketResponseCheckTimeout time.Duration WebsocketResponseMaxLimit time.Duration diff --git a/exchanges/exmo/exmo.go b/exchanges/exmo/exmo.go index 218dee0f..f95bdf40 100644 --- a/exchanges/exmo/exmo.go +++ b/exchanges/exmo/exmo.go @@ -302,7 +302,16 @@ func (e *EXMO) GetWalletHistory(date int64) (WalletHistory, error) { // SendHTTPRequest sends an unauthenticated HTTP request func (e *EXMO) SendHTTPRequest(path string, result interface{}) error { - return e.SendPayload(http.MethodGet, path, nil, nil, result, false, false, e.Verbose, e.HTTPDebugging) + return e.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + e.Verbose, + e.HTTPDebugging, + e.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request @@ -342,7 +351,8 @@ func (e *EXMO) SendAuthenticatedHTTPRequest(method, endpoint string, vals url.Va true, true, e.Verbose, - e.HTTPDebugging) + e.HTTPDebugging, + e.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index bfb0a8e3..fef8d55b 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -306,7 +306,16 @@ func (g *Gateio) CancelExistingOrder(orderID int64, symbol string) (bool, error) // SendHTTPRequest sends an unauthenticated HTTP request func (g *Gateio) SendHTTPRequest(path string, result interface{}) error { - return g.SendPayload(http.MethodGet, path, nil, nil, result, false, false, g.Verbose, g.HTTPDebugging) + return g.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + g.Verbose, + g.HTTPDebugging, + g.HTTPRecording) } // CancelAllExistingOrders all orders for a given symbol and side @@ -407,7 +416,8 @@ func (g *Gateio) SendAuthenticatedHTTPRequest(method, endpoint, param string, re true, false, g.Verbose, - g.HTTPDebugging) + g.HTTPDebugging, + g.HTTPRecording) if err != nil { return err } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index d86905a2..beddc304 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -53,11 +53,6 @@ const ( geminiRoleFundManager = "fundmanager" ) -var ( - // Session manager - Session map[int]*Gemini -) - // 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 new sessions to the Session map using @@ -71,31 +66,6 @@ type Gemini struct { RequiresHeartBeat bool } -// AddSession adds a new session to the gemini base -func AddSession(g *Gemini, sessionID int, apiKey, apiSecret, role string, needsHeartbeat, isSandbox bool) error { - if Session == nil { - Session = make(map[int]*Gemini) - } - - _, ok := Session[sessionID] - if ok { - return errors.New("sessionID already being used") - } - - g.API.Credentials.Key = apiKey - g.API.Credentials.Secret = apiSecret - g.Role = role - g.RequiresHeartBeat = needsHeartbeat - g.API.Endpoints.URL = geminiAPIURL - - if isSandbox { - g.API.Endpoints.URL = geminiSandboxAPIURL - } - - Session[sessionID] = g - return nil -} - // GetSymbols returns all available symbols for trading func (g *Gemini) GetSymbols() ([]string, error) { var symbols []string @@ -155,9 +125,15 @@ func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { // 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", g.API.Endpoints.URL, geminiAPIVersion, geminiOrderbook, currencyPair), params) - orderbook := Orderbook{} + path := common.EncodeURLValues( + fmt.Sprintf("%s/v%s/%s/%s", + g.API.Endpoints.URL, + geminiAPIVersion, + geminiOrderbook, + currencyPair), + params) + var orderbook Orderbook return orderbook, g.SendHTTPRequest(path, &orderbook) } @@ -202,20 +178,9 @@ func (g *Gemini) GetAuctionHistory(currencyPair string, params url.Values) ([]Au return auctionHist, g.SendHTTPRequest(path, &auctionHist) } -func (g *Gemini) isCorrectSession() error { - if g.Role != geminiRoleTrader { - 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(); err != nil { - return 0, err - } - req := make(map[string]interface{}) req["symbol"] = symbol req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) @@ -300,6 +265,7 @@ func (g *Gemini) GetOrders() ([]Order, error) { if err != nil { return nil, err } + switch r := response.(type) { case orders: return r.orders, nil @@ -317,7 +283,7 @@ func (g *Gemini) GetTradeHistory(currencyPair string, timestamp int64) ([]TradeH req := make(map[string]interface{}) req["symbol"] = currencyPair - if timestamp != 0 { + if timestamp > 0 { req["timestamp"] = timestamp } @@ -406,7 +372,16 @@ func (g *Gemini) PostHeartbeat() (string, error) { // SendHTTPRequest sends an unauthenticated request func (g *Gemini) SendHTTPRequest(path string, result interface{}) error { - return g.SendPayload(http.MethodGet, path, nil, nil, result, false, false, g.Verbose, g.HTTPDebugging) + return g.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + g.Verbose, + g.HTTPDebugging, + g.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the @@ -416,7 +391,6 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name) } - headers := make(map[string]string) req := make(map[string]interface{}) req["request"] = fmt.Sprintf("/v%s/%s", geminiAPIVersion, path) req["nonce"] = g.Requester.GetNonce(true).String() @@ -437,6 +411,7 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st PayloadBase64 := crypto.Base64Encode(PayloadJSON) hmac := crypto.GetHMAC(crypto.HashSHA512_384, []byte(PayloadBase64), []byte(g.API.Credentials.Secret)) + headers := make(map[string]string) headers["Content-Length"] = "0" headers["Content-Type"] = "text/plain" headers["X-GEMINI-APIKEY"] = g.API.Credentials.Key @@ -444,8 +419,16 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st headers["X-GEMINI-SIGNATURE"] = crypto.HexEncodeToString(hmac) headers["Cache-Control"] = "no-cache" - return g.SendPayload(method, g.API.Endpoints.URL+"/v1/"+path, headers, - strings.NewReader(""), result, true, false, g.Verbose, g.HTTPDebugging) + return g.SendPayload(method, + g.API.Endpoints.URL+"/v1/"+path, + headers, + nil, + result, + true, + false, + g.Verbose, + g.HTTPDebugging, + g.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction @@ -481,9 +464,9 @@ func getOfflineTradeFee(price, amount float64) float64 { func calculateTradingFee(notionVolume *NotionalVolume, purchasePrice, amount float64, isMaker bool) float64 { var volumeFee float64 if isMaker { - volumeFee = (float64(notionVolume.MakerFee) / 100) + volumeFee = (float64(notionVolume.APIMakerFeeBPS) / 10000) } else { - volumeFee = (float64(notionVolume.TakerFee) / 100) + volumeFee = (float64(notionVolume.APITakerFeeBPS) / 10000) } return volumeFee * amount * purchasePrice diff --git a/exchanges/gemini/gemini_live_test.go b/exchanges/gemini/gemini_live_test.go new file mode 100644 index 00000000..2e1010c3 --- /dev/null +++ b/exchanges/gemini/gemini_live_test.go @@ -0,0 +1,33 @@ +//+build mock_test_off + +// This will build if build tag mock_test_off is parsed and will do live testing +// using all tests in (exchange)_test.go +package gemini + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +var mockTests = false + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + geminiConfig, err := cfg.GetExchangeConfig("Gemini") + if err != nil { + log.Fatal("Test Failed - Gemini Setup() init error", err) + } + geminiConfig.API.AuthenticatedSupport = true + geminiConfig.API.Credentials.Key = apiKey + geminiConfig.API.Credentials.Secret = apiSecret + g.SetDefaults() + g.Setup(geminiConfig) + g.API.Endpoints.URL = geminiSandboxAPIURL + log.Printf(sharedtestvalues.LiveTesting, g.GetName(), g.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/gemini/gemini_mock_test.go b/exchanges/gemini/gemini_mock_test.go new file mode 100644 index 00000000..a334eff8 --- /dev/null +++ b/exchanges/gemini/gemini_mock_test.go @@ -0,0 +1,45 @@ +//+build !mock_test_off + +// This will build if build tag mock_test_off is not parsed and will try to mock +// all tests in _test.go +package gemini + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/mock" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +const mockFile = "../../testdata/http_mock/gemini/gemini.json" + +var mockTests = true + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + geminiConfig, err := cfg.GetExchangeConfig("Gemini") + if err != nil { + log.Fatal("Test Failed - Mock server error", err) + } + g.SkipAuthCheck = true + geminiConfig.API.AuthenticatedSupport = true + geminiConfig.API.Credentials.Key = apiKey + geminiConfig.API.Credentials.Secret = apiSecret + g.SetDefaults() + g.Setup(geminiConfig) + + serverDetails, newClient, err := mock.NewVCRServer(mockFile) + if err != nil { + log.Fatalf("Test Failed - Mock server error %s", err) + } + + g.HTTPClient = newClient + g.API.Endpoints.URL = serverDetails + + log.Printf(sharedtestvalues.MockTesting, g.GetName(), g.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index c1c20f0d..32c4b703 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -7,7 +7,6 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" @@ -15,75 +14,21 @@ import ( ) // Please enter sandbox API keys & assigned roles for better testing procedures - const ( - apiKey1 = "" - apiSecret1 = "" - apiKeyRole1 = "" - sessionHeartBeat1 = false - - apiKey2 = "" - apiSecret2 = "" - apiKeyRole2 = "" - sessionHeartBeat2 = false - + apiKey = "" + apiSecret = "" + apiKeyRole = "" + sessionHeartBeat = false canManipulateRealOrders = false ) -func TestAddSession(t *testing.T) { - var g1 Gemini - if Session[1] == nil { - err := AddSession(&g1, 1, apiKey1, apiSecret1, apiKeyRole1, true, true) - if err != nil { - t.Error("Test failed - AddSession() error", err) - } - err = AddSession(&g1, 1, apiKey1, apiSecret1, apiKeyRole1, true, true) - if err == nil { - t.Error("Test failed - AddSession() error", err) - } - } +const testCurrency = "btcusd" - if len(Session) <= 1 { - var g2 Gemini - err := AddSession(&g2, 2, apiKey2, apiSecret2, apiKeyRole2, false, true) - if err != nil { - t.Error("Test failed - AddSession() error", err) - } - } -} - -func TestSetDefaults(t *testing.T) { - Session[1].SetDefaults() - Session[2].SetDefaults() -} - -func TestSetup(t *testing.T) { - cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") - geminiConfig, err := cfg.GetExchangeConfig("Gemini") - if err != nil { - t.Error("Test Failed - Gemini Setup() init error") - } - - geminiConfig.API.AuthenticatedSupport = true - geminiConfig.API.AuthenticatedWebsocketSupport = true - - Session[1].Setup(geminiConfig) - Session[2].Setup(geminiConfig) - - Session[1].API.Credentials.Key = apiKey1 - Session[1].API.Credentials.Secret = apiSecret1 - - Session[2].API.Credentials.Key = apiKey2 - Session[2].API.Credentials.Secret = apiSecret2 - - Session[1].API.Endpoints.URL = geminiSandboxAPIURL - Session[2].API.Endpoints.URL = geminiSandboxAPIURL -} +var g Gemini func TestGetSymbols(t *testing.T) { t.Parallel() - _, err := Session[1].GetSymbols() + _, err := g.GetSymbols() if err != nil { t.Error("Test Failed - GetSymbols() error", err) } @@ -91,11 +36,11 @@ func TestGetSymbols(t *testing.T) { func TestGetTicker(t *testing.T) { t.Parallel() - _, err := Session[2].GetTicker("BTCUSD") + _, err := g.GetTicker("BTCUSD") if err != nil { t.Error("Test Failed - GetTicker() error", err) } - _, err = Session[1].GetTicker("bla") + _, err = g.GetTicker("bla") if err == nil { t.Error("Test Failed - GetTicker() error", err) } @@ -103,7 +48,7 @@ func TestGetTicker(t *testing.T) { func TestGetOrderbook(t *testing.T) { t.Parallel() - _, err := Session[1].GetOrderbook("btcusd", url.Values{}) + _, err := g.GetOrderbook(testCurrency, url.Values{}) if err != nil { t.Error("Test Failed - GetOrderbook() error", err) } @@ -111,25 +56,25 @@ func TestGetOrderbook(t *testing.T) { func TestGetTrades(t *testing.T) { t.Parallel() - _, err := Session[2].GetTrades("btcusd", url.Values{}) + _, err := g.GetTrades(testCurrency, url.Values{}) if err != nil { t.Error("Test Failed - GetTrades() error", err) } } func TestGetNotionalVolume(t *testing.T) { - if apiKey2 != "" && apiSecret2 != "" { - t.Parallel() - _, err := Session[2].GetNotionalVolume() - if err != nil { - t.Error("Test Failed - GetNotionalVolume() error", err) - } + t.Parallel() + _, err := g.GetNotionalVolume() + if err != nil && mockTests { + t.Error("Test Failed - GetNotionalVolume() error", err) + } else if err == nil && !mockTests { + t.Error("Test Failed - GetNotionalVolume() error cannot be nil") } } func TestGetAuction(t *testing.T) { t.Parallel() - _, err := Session[1].GetAuction("btcusd") + _, err := g.GetAuction(testCurrency) if err != nil { t.Error("Test Failed - GetAuction() error", err) } @@ -137,7 +82,7 @@ func TestGetAuction(t *testing.T) { func TestGetAuctionHistory(t *testing.T) { t.Parallel() - _, err := Session[2].GetAuctionHistory("btcusd", url.Values{}) + _, err := g.GetAuctionHistory(testCurrency, url.Values{}) if err != nil { t.Error("Test Failed - GetAuctionHistory() error", err) } @@ -145,81 +90,87 @@ func TestGetAuctionHistory(t *testing.T) { func TestNewOrder(t *testing.T) { t.Parallel() - _, err := Session[1].NewOrder("btcusd", 1, 4500, - exchange.BuyOrderSide.ToLower().ToString(), "exchange limit") - if err == nil { - t.Error("Test Failed - NewOrder() error", err) - } - _, err = Session[2].NewOrder("btcusd", 1, 4500, - exchange.BuyOrderSide.ToLower().ToString(), "exchange limit") - if err == nil { + _, err := g.NewOrder(testCurrency, 1, 9000, "buy", "exchange limit") + if err != nil && mockTests { t.Error("Test Failed - NewOrder() error", err) + } else if err == nil && !mockTests { + t.Error("Test Failed - NewOrder() error cannot be nil") } } func TestCancelExistingOrder(t *testing.T) { t.Parallel() - _, err := Session[1].CancelExistingOrder(1337) - if err == nil { + _, err := g.CancelExistingOrder(265555413) + if err != nil && mockTests { t.Error("Test Failed - CancelExistingOrder() error", err) + } else if err == nil && !mockTests { + t.Error("Test Failed - CancelExistingOrder() error cannot be nil") } } func TestCancelExistingOrders(t *testing.T) { t.Parallel() - _, err := Session[1].CancelExistingOrders(false) - if err == nil { - t.Error("Test Failed - CancelExistingOrders() error", err) - } - _, err = Session[2].CancelExistingOrders(true) - if err == nil { + _, err := g.CancelExistingOrders(false) + if err != nil && mockTests { t.Error("Test Failed - CancelExistingOrders() error", err) + } else if err == nil && !mockTests { + t.Error("Test Failed - CancelExistingOrders() error cannot be nil") } } func TestGetOrderStatus(t *testing.T) { t.Parallel() - _, err := Session[2].GetOrderStatus(1337) - if err == nil { + _, err := g.GetOrderStatus(265563260) + if err != nil && mockTests { t.Error("Test Failed - GetOrderStatus() error", err) + } else if err == nil && !mockTests { + t.Error("Test Failed - GetOrderStatus() error cannot be nil") } } func TestGetOrders(t *testing.T) { t.Parallel() - _, err := Session[1].GetOrders() - if err == nil { + _, err := g.GetOrders() + if err != nil && mockTests { t.Error("Test Failed - GetOrders() error", err) + } else if err == nil && !mockTests { + t.Error("Test Failed - GetOrders() error cannot be nil") } } func TestGetTradeHistory(t *testing.T) { t.Parallel() - _, err := Session[1].GetTradeHistory("btcusd", 0) - if err == nil { + _, err := g.GetTradeHistory(testCurrency, 0) + if err != nil && mockTests { t.Error("Test Failed - GetTradeHistory() error", err) + } else if err == nil && !mockTests { + t.Error("Test Failed - GetTradeHistory() error cannot be nil") } } func TestGetTradeVolume(t *testing.T) { t.Parallel() - _, err := Session[2].GetTradeVolume() - if err == nil { + _, err := g.GetTradeVolume() + if err != nil && mockTests { t.Error("Test Failed - GetTradeVolume() error", err) + } else if err == nil && !mockTests { + t.Error("Test Failed - GetTradeVolume() error cannot be nil") } } func TestGetBalances(t *testing.T) { t.Parallel() - _, err := Session[1].GetBalances() - if err == nil { + _, err := g.GetBalances() + if err != nil && mockTests { t.Error("Test Failed - GetBalances() error", err) + } else if err == nil && !mockTests { + t.Error("Test Failed - GetBalances() error cannot be nil") } } func TestGetCryptoDepositAddress(t *testing.T) { t.Parallel() - _, err := Session[1].GetCryptoDepositAddress("LOL123", "btc") + _, err := g.GetCryptoDepositAddress("LOL123", "btc") if err == nil { t.Error("Test Failed - GetCryptoDepositAddress() error", err) } @@ -227,7 +178,7 @@ func TestGetCryptoDepositAddress(t *testing.T) { func TestWithdrawCrypto(t *testing.T) { t.Parallel() - _, err := Session[1].WithdrawCrypto("LOL123", "btc", 1) + _, err := g.WithdrawCrypto("LOL123", "btc", 1) if err == nil { t.Error("Test Failed - WithdrawCrypto() error", err) } @@ -235,9 +186,11 @@ func TestWithdrawCrypto(t *testing.T) { func TestPostHeartbeat(t *testing.T) { t.Parallel() - _, err := Session[2].PostHeartbeat() - if err == nil { + _, err := g.PostHeartbeat() + if err != nil && mockTests { t.Error("Test Failed - PostHeartbeat() error", err) + } else if err == nil && !mockTests { + t.Error("Test Failed - PostHeartbeat() error cannot be nil") } } @@ -256,61 +209,75 @@ func setFeeBuilder() *exchange.FeeBuilder { // TestGetFeeByTypeOfflineTradeFee logic test func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) + t.Parallel() var feeBuilder = setFeeBuilder() - Session[1].GetFeeByType(feeBuilder) - if apiKey1 == "" || apiSecret1 == "" { + g.GetFeeByType(feeBuilder) + + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { - t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) + t.Errorf("Expected %v, received %v", + exchange.OfflineTradeFee, + feeBuilder.FeeType) } } else { if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee { - t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) + t.Errorf("Expected %v, received %v", + exchange.CryptocurrencyTradeFee, + feeBuilder.FeeType) } } } func TestGetFee(t *testing.T) { + t.Parallel() var feeBuilder = setFeeBuilder() - if apiKey1 != "" && apiSecret1 != "" { + if areTestAPIKeysSet() || mockTests { // CryptocurrencyTradeFee Basic - if resp, err := Session[1].GetFee(feeBuilder); resp != float64(0.01) || err != nil { + if resp, err := g.GetFee(feeBuilder); resp != float64(0.0035) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0.0035), + resp) t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.01), resp) } // CryptocurrencyTradeFee High quantity feeBuilder = setFeeBuilder() feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 - if resp, err := Session[1].GetFee(feeBuilder); resp != float64(100) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(100), resp) + if resp, err := g.GetFee(feeBuilder); resp != float64(3500) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(3500), + resp) t.Error(err) } // CryptocurrencyTradeFee IsMaker feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true - if resp, err := Session[1].GetFee(feeBuilder); resp != float64(0.01) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.01), resp) + if resp, err := g.GetFee(feeBuilder); resp != float64(0.001) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0.001), + resp) t.Error(err) } // CryptocurrencyTradeFee Negative purchase price feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 - if resp, err := Session[1].GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } } // CryptocurrencyWithdrawalFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee - if resp, err := Session[1].GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } @@ -318,24 +285,30 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee - if resp, err := Session[1].GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } // CyptocurrencyDepositFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee - if resp, err := Session[1].GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } // InternationalBankDepositFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee - if resp, err := Session[1].GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } @@ -343,74 +316,78 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD - if resp, err := Session[1].GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), + resp) t.Error(err) } } func TestFormatWithdrawPermissions(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) + t.Parallel() + expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + + " & " + + exchange.AutoWithdrawCryptoWithSetupText + + " & " + + exchange.WithdrawFiatViaWebsiteOnlyText - expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - - withdrawPermissions := Session[1].FormatWithdrawPermissions() + withdrawPermissions := g.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { - t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) + t.Errorf("Expected: %s, Received: %s", + expectedResult, + withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) - + t.Parallel() var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, - Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)}, + OrderType: exchange.AnyOrderType, + Currencies: []currency.Pair{ + currency.NewPair(currency.LTC, currency.BTC), + }, } - _, err := Session[1].GetActiveOrders(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { + _, err := g.GetActiveOrders(&getOrdersRequest) + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Errorf("Could not get open orders: %s", err) } } func TestGetOrderHistory(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) - + t.Parallel() var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)}, } - _, err := Session[1].GetOrderHistory(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { + _, err := g.GetOrderHistory(&getOrdersRequest) + switch { + case areTestAPIKeysSet() && err != nil: t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case err != nil && mockTests: + t.Errorf("Could not get order history: %s", err) } } // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { - return Session[1].ValidateAPICredentials() + return g.ValidateAPICredentials() } func TestSubmitOrder(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -422,51 +399,46 @@ func TestSubmitOrder(t *testing.T) { }, OrderSide: exchange.BuyOrderSide, OrderType: exchange.LimitOrderType, - Price: 1, + Price: 10, Amount: 1, - ClientID: "meowOrder", + ClientID: "1234234", } - response, err := Session[1].SubmitOrder(orderSubmission) - if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { + + response, err := g.SubmitOrder(orderSubmission) + switch { + case areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced): t.Errorf("Order failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Errorf("Order failed to be placed: %v", err) } } func TestCancelExchangeOrder(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ - OrderID: "1", - WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - AccountID: "1", - CurrencyPair: currencyPair, + OrderID: "266029865", } - err := Session[1].CancelOrder(orderCancellation) - if !areTestAPIKeysSet() && err == nil { + err := g.CancelOrder(orderCancellation) + switch { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil: + t.Errorf("Could not cancel orders: %v", err) + case err != nil && mockTests: t.Errorf("Could not cancel orders: %v", err) } } func TestCancelAllExchangeOrders(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -479,12 +451,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { CurrencyPair: currencyPair, } - resp, err := Session[1].CancelAllOrders(orderCancellation) - - if !areTestAPIKeysSet() && err == nil { + resp, err := g.CancelAllOrders(orderCancellation) + switch { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil: + t.Errorf("Could not cancel orders: %v", err) + case mockTests && err != nil: t.Errorf("Could not cancel orders: %v", err) } @@ -494,16 +467,15 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { - _, err := Session[1].ModifyOrder(&exchange.ModifyOrder{}) + t.Parallel() + _, err := g.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { t.Error("Test failed - ModifyOrder() error") } } func TestWithdraw(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) + t.Parallel() withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -513,53 +485,55 @@ func TestWithdraw(t *testing.T) { Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - _, err := Session[1].WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) + _, err := g.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } - if areTestAPIKeysSet() && err != nil { + if areTestAPIKeysSet() && err != nil && !mockTests { + t.Errorf("Withdraw failed to be placed: %v", err) + } + if areTestAPIKeysSet() && err == nil && mockTests { t.Errorf("Withdraw failed to be placed: %v", err) } } func TestWithdrawFiat(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } var withdrawFiatRequest = exchange.FiatWithdrawRequest{} - _, err := Session[1].WithdrawFiatFunds(&withdrawFiatRequest) + _, err := g.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } func TestWithdrawInternationalBank(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } var withdrawFiatRequest = exchange.FiatWithdrawRequest{} - _, err := Session[1].WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) + _, err := g.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } func TestGetDepositAddress(t *testing.T) { - _, err := Session[1].GetDepositAddress(currency.BTC, "") + t.Parallel() + _, err := g.GetDepositAddress(currency.BTC, "") if err == nil { t.Error("Test Failed - GetDepositAddress error cannot be nil") } @@ -567,13 +541,12 @@ func TestGetDepositAddress(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { - TestAddSession(t) - TestSetDefaults(t) - TestSetup(t) - g := Session[1] + t.Parallel() g.API.Endpoints.WebsocketURL = geminiWebsocketSandboxEndpoint - if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + if !g.Websocket.IsEnabled() && + !g.API.AuthenticatedWebsocketSupport || + !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } var dialer websocket.Dialer diff --git a/exchanges/gemini/gemini_types.go b/exchanges/gemini/gemini_types.go index 206c9cbe..ebe461ec 100644 --- a/exchanges/gemini/gemini_types.go +++ b/exchanges/gemini/gemini_types.go @@ -145,16 +145,23 @@ type TradeVolume struct { SellTakerCount float64 `json:"sell_taker_count"` } -// NotionalVolume api call for fees +// NotionalVolume api call for fees, all return fee amounts are in basis points type NotionalVolume struct { - MakerFee int64 `json:"maker_fee_bps"` - TakerFee int64 `json:"taker_fee_bps"` - AuctionFee int64 `json:"auction_fee_bps"` - ThirtyDayVolume float64 `json:"notional_30d_volume"` - LastedUpdated int64 `json:"last_updated_ms"` - AccountID int64 `json:"account_id"` - Date string `json:"date"` + APIAuctionFeeBPS int64 `json:"api_auction_fee_bps"` + APIMakerFeeBPS int64 `json:"api_maker_fee_bps"` + APITakerFeeBPS int64 `json:"api_taker_fee_bps"` + BlockMakerFeeBPS int64 `json:"block_maker_fee_bps"` + BlockTakerFeeBPS int64 `json:"block_taker_fee_bps"` + FixAuctionFeeBPS int64 `json:"fix_auction_fee_bps"` + FixMakerFeeBPS int64 `json:"fix_maker_fee_bps"` + FixTakerFeeBPS int64 `json:"fix_taker_fee_bps"` OneDayNotionalVolumes []OneDayNotionalVolume `json:"notional_1d_volume"` + ThirtyDayVolume float64 `json:"notional_30d_volume"` + WebAuctionFeeBPS int64 `json:"web_auction_fee_bps"` + WebMakerFeeBPS int64 `json:"web_maker_fee_bps"` + WebTakerFeeBPS int64 `json:"web_taker_fee_bps"` + LastedUpdated int64 `json:"last_updated_ms"` + Date string `json:"date"` } // OneDayNotionalVolume Contains the notioanl volume for a single day diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index bbc37d36..43f6cda0 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -305,11 +305,16 @@ func (g *Gemini) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOr return submitOrderResponse, err } - response, err := g.NewOrder(order.Pair.String(), + if order.OrderType != exchange.LimitOrderType { + return submitOrderResponse, errors.New("only limit orders are enabled through this exchange") + } + + response, err := g.NewOrder( + g.FormatExchangeCurrency(order.Pair, asset.Spot).String(), order.Amount, order.Price, order.OrderSide.ToString(), - order.OrderType.ToString()) + "exchange limit") if response > 0 { submitOrderResponse.OrderID = fmt.Sprintf("%v", response) } @@ -401,7 +406,7 @@ func (g *Gemini) GetWebsocket() (*wshandler.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (g *Gemini) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if !g.AllowAuthenticatedRequest() && // Todo check connection status + if (!g.AllowAuthenticatedRequest() || g.SkipAuthCheck) && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index f799396f..e187209f 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -508,7 +508,16 @@ func (h *HitBTC) TransferBalance(currency, from, to string, amount float64) (boo // SendHTTPRequest sends an unauthenticated HTTP request func (h *HitBTC) SendHTTPRequest(path string, result interface{}) error { - return h.SendPayload(http.MethodGet, path, nil, nil, result, false, false, h.Verbose, h.HTTPDebugging) + return h.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + h.Verbose, + h.HTTPDebugging, + h.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated http request @@ -530,7 +539,8 @@ func (h *HitBTC) SendAuthenticatedHTTPRequest(method, endpoint string, values ur true, false, h.Verbose, - h.HTTPDebugging) + h.HTTPDebugging, + h.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index 005261be..826fe3a7 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -776,7 +776,16 @@ func (h *HUOBI) CancelWithdraw(withdrawID int64) (int64, error) { // SendHTTPRequest sends an unauthenticated HTTP request func (h *HUOBI) SendHTTPRequest(path string, result interface{}) error { - return h.SendPayload(http.MethodGet, path, nil, nil, result, false, false, h.Verbose, h.HTTPDebugging) + return h.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + h.Verbose, + h.HTTPDebugging, + h.HTTPRecording) } // SendAuthenticatedHTTPRequest sends authenticated requests to the HUOBI API @@ -852,7 +861,16 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url body = encoded } - return h.SendPayload(method, urlPath, headers, bytes.NewReader(body), result, true, false, h.Verbose, h.HTTPDebugging) + return h.SendPayload(method, + urlPath, + headers, + bytes.NewReader(body), + result, + true, + false, + h.Verbose, + h.HTTPDebugging, + h.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/huobihadax/huobihadax.go b/exchanges/huobihadax/huobihadax.go index 5442d907..bb2ccd5d 100644 --- a/exchanges/huobihadax/huobihadax.go +++ b/exchanges/huobihadax/huobihadax.go @@ -773,7 +773,16 @@ func (h *HUOBIHADAX) CancelWithdraw(withdrawID int64) (int64, error) { // SendHTTPRequest sends an unauthenticated HTTP request func (h *HUOBIHADAX) SendHTTPRequest(path string, result interface{}) error { - return h.SendPayload(http.MethodGet, path, nil, nil, result, false, false, h.Verbose, h.HTTPDebugging) + return h.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + h.Verbose, + h.HTTPDebugging, + h.HTTPRecording) } // SendAuthenticatedHTTPPostRequest sends authenticated requests to the HUOBI API @@ -801,7 +810,16 @@ func (h *HUOBIHADAX) SendAuthenticatedHTTPPostRequest(method, endpoint, postBody urlPath := common.EncodeURLValues(fmt.Sprintf("%s%s", h.API.Endpoints.URL, endpoint), signatureParams) - return h.SendPayload(method, urlPath, headers, bytes.NewBufferString(postBodyValues), result, true, false, h.Verbose, h.HTTPDebugging) + return h.SendPayload(method, + urlPath, + headers, + bytes.NewBufferString(postBodyValues), + result, + true, + false, + h.Verbose, + h.HTTPDebugging, + h.HTTPRecording) } // SendAuthenticatedHTTPRequest sends authenticated requests to the HUOBI API @@ -827,7 +845,16 @@ func (h *HUOBIHADAX) SendAuthenticatedHTTPRequest(method, endpoint string, value urlPath := common.EncodeURLValues(fmt.Sprintf("%s%s", h.API.Endpoints.URL, endpoint), values) - return h.SendPayload(method, urlPath, headers, bytes.NewBufferString(""), result, true, false, h.Verbose, h.HTTPDebugging) + return h.SendPayload(method, + urlPath, + headers, + bytes.NewBufferString(""), + result, + true, + false, + h.Verbose, + h.HTTPDebugging, + h.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index 0d87bb2f..9a4a6958 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -276,7 +276,16 @@ func (i *ItBit) WalletTransfer(walletID, sourceWallet, destWallet string, amount // SendHTTPRequest sends an unauthenticated HTTP request func (i *ItBit) SendHTTPRequest(path string, result interface{}) error { - return i.SendPayload(http.MethodGet, path, nil, nil, result, false, false, i.Verbose, i.HTTPDebugging) + return i.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + i.Verbose, + i.HTTPDebugging, + i.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated request to itBit @@ -331,7 +340,16 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method, path string, params map[str RequestID string `json:"requestId"` }{} - err = i.SendPayload(method, urlPath, headers, bytes.NewBuffer(PayloadJSON), &intermediary, true, true, i.Verbose, i.HTTPDebugging) + err = i.SendPayload(method, + urlPath, + headers, + bytes.NewBuffer(PayloadJSON), + &intermediary, + true, + true, + i.Verbose, + i.HTTPDebugging, + i.HTTPRecording) if err != nil { return err } diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index ad2b69df..e301d3f7 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -846,7 +846,16 @@ func GetError(apiErrors []string) error { // SendHTTPRequest sends an unauthenticated HTTP requests func (k *Kraken) SendHTTPRequest(path string, result interface{}) error { - return k.SendPayload(http.MethodGet, path, nil, nil, result, false, false, k.Verbose, k.HTTPDebugging) + return k.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + k.Verbose, + k.HTTPDebugging, + k.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request @@ -883,7 +892,8 @@ func (k *Kraken) SendAuthenticatedHTTPRequest(method string, params url.Values, true, true, k.Verbose, - k.HTTPDebugging) + k.HTTPDebugging, + k.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 0a071f50..12d09c24 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -251,7 +251,16 @@ func (l *LakeBTC) CreateWithdraw(amount float64, accountID string) (Withdraw, er // SendHTTPRequest sends an unauthenticated http request func (l *LakeBTC) SendHTTPRequest(path string, result interface{}) error { - return l.SendPayload(http.MethodGet, path, nil, nil, result, false, false, l.Verbose, l.HTTPDebugging) + return l.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + l.Verbose, + l.HTTPDebugging, + l.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an autheticated HTTP request to a LakeBTC @@ -284,8 +293,16 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int headers["Authorization"] = "Basic " + crypto.Base64Encode([]byte(l.API.Credentials.Key+":"+crypto.HexEncodeToString(hmac))) headers["Content-Type"] = "application/json-rpc" - return l.SendPayload(http.MethodPost, l.API.Endpoints.URL, headers, - strings.NewReader(string(data)), result, true, true, l.Verbose, l.HTTPDebugging) + return l.SendPayload(http.MethodPost, + l.API.Endpoints.URL, + headers, + strings.NewReader(string(data)), + result, + true, + true, + l.Verbose, + l.HTTPDebugging, + l.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/lbank/README.md b/exchanges/lbank/README.md new file mode 100644 index 00000000..5325ce2b --- /dev/null +++ b/exchanges/lbank/README.md @@ -0,0 +1,133 @@ +# GoCryptoTrader package Lbank + + + + +[![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/lbank) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + + +This lbank package is part of the GoCryptoTrader codebase. + +## This is still in active development + +You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY) + +## Lbank Exchange + +### Current Features + ++ REST Support + +### How to enable + ++ [Enable via configuration](https://githul.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example) + ++ Individual package example below: + +```go + // Exchanges will be abstracted out in further updates and examples will be + // supplied then +``` + +### How to do REST public/private calls + ++ If enabled via "configuration".json file the exchange will be added to the +IBotExchange array in the ```go var bot Bot``` and you will only be able to use +the wrapper interface functions for accessing exchange data. View routines.go +for an example of integration usage with GoCryptoTrader. Rudimentary example +below: + +main.go +```go +var l exchange.IBotExchange + +for i := range bot.exchanges { + if bot.exchanges[i].GetName() == "Lbank" { + l = bot.exchanges[i] + } +} + +// Public calls - wrapper functions + +// Fetches current ticker information +tick, err := l.GetTickerPrice() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := l.GetOrderbookEx() +if err != nil { + // Handle error +} + +// Private calls - wrapper functions - make sure your APIKEY and APISECRET are +// set and AuthenticatedAPISupport is set to true + +// Fetches current account information +accountInfo, err := l.GetAccountInfo() +if err != nil { + // Handle error +} +``` + ++ If enabled via individually importing package, rudimentary example below: + +```go +// Public calls + +// Fetches current ticker information +ticker, err := l.GetTicker() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := l.GetOrderBook() +if err != nil { + // Handle error +} + +// Private calls - make sure your APIKEY and APISECRET are set and +// AuthenticatedAPISupport is set to true + +// GetUserInfo returns account info +accountInfo, err := l.GetUserInfo(...) +if err != nil { + // Handle error +} + +// Submits an order and the exchange and returns its tradeID +tradeID, err := l.Trade(...) +if err != nil { + // Handle error +} +``` + +### Please click GoDocs chevron above to view current GoDoc information for this package + +## Contribution + +Please feel free to submit any pull requests or suggest any desired features to be added. + +When submitting a PR, please abide by our coding guidelines: + ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. + +## Donations + + + +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: + +***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** + diff --git a/exchanges/lbank/lbank.go b/exchanges/lbank/lbank.go new file mode 100644 index 00000000..72130ff0 --- /dev/null +++ b/exchanges/lbank/lbank.go @@ -0,0 +1,562 @@ +package lbank + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + gctcrypto "github.com/thrasher-corp/gocryptotrader/common/crypto" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" +) + +// Lbank is the overarching type across this package +type Lbank struct { + exchange.Base + privateKey *rsa.PrivateKey + WebsocketConn *wshandler.WebsocketConnection +} + +const ( + lbankAPIURL = "https://api.lbkex.com" + lbankAPIVersion = "1" + lbankAuthRateLimit = 0 + lbankUnAuthRateLimit = 0 + lbankFeeNotFound = 0.0 + + // Public endpoints + lbankTicker = "ticker.do" + lbankCurrencyPairs = "currencyPairs.do" + lbankMarketDepths = "depth.do" + lbankTrades = "trades.do" + lbankKlines = "kline.do" + lbankPairInfo = "accuracy.do" + lbankUSD2CNYRate = "usdToCny.do" + lbankWithdrawConfig = "withdrawConfigs.do" + + // Authenticated endpoints + lbankUserInfo = "user_info.do" + lbankPlaceOrder = "create_order.do" + lbankCancelOrder = "cancel_order.do" + lbankQueryOrder = "orders_info.do" + lbankQueryHistoryOrder = "orders_info_history.do" + lbankOrderTransactionDetails = "order_transaction_detail.do" + lbankPastTransactions = "transaction_history.do" + lbankOpeningOrders = "orders_info_no_deal.do" + lbankWithdrawalRecords = "withdraws.do" + lbankWithdraw = "withdraw.do" + lbankRevokeWithdraw = "withdrawCancel.do" +) + +// GetTicker returns a ticker for the specified symbol +// symbol: eth_btc +func (l *Lbank) GetTicker(symbol string) (TickerResponse, error) { + var t TickerResponse + params := url.Values{} + params.Set("symbol", symbol) + path := fmt.Sprintf("%s/v%s/%s?%s", l.API.Endpoints.URL, lbankAPIVersion, lbankTicker, params.Encode()) + return t, l.SendHTTPRequest(path, &t) +} + +// GetCurrencyPairs returns a list of supported currency pairs by the exchange +func (l *Lbank) GetCurrencyPairs() ([]string, error) { + path := fmt.Sprintf("%s/v%s/%s", l.API.Endpoints.URL, lbankAPIVersion, + lbankCurrencyPairs) + var result []string + return result, l.SendHTTPRequest(path, &result) +} + +// GetMarketDepths returns arrays of asks, bids and timestamp +func (l *Lbank) GetMarketDepths(symbol, size, merge string) (MarketDepthResponse, error) { + var m MarketDepthResponse + params := url.Values{} + params.Set("symbol", symbol) + params.Set("size", size) + params.Set("merge", merge) + path := fmt.Sprintf("%s/v%s/%s?%s", l.API.Endpoints.URL, lbankAPIVersion, lbankMarketDepths, params.Encode()) + return m, l.SendHTTPRequest(path, &m) +} + +// GetTrades returns an array of available trades regarding a particular exchange +func (l *Lbank) GetTrades(symbol, size, time string) ([]TradeResponse, error) { + var g []TradeResponse + params := url.Values{} + params.Set("symbol", symbol) + params.Set("size", size) + params.Set("time", time) + path := fmt.Sprintf("%s/v%s/%s?%s", l.API.Endpoints.URL, lbankAPIVersion, lbankTrades, params.Encode()) + return g, l.SendHTTPRequest(path, &g) +} + +// GetKlines returns kline data +func (l *Lbank) GetKlines(symbol, size, klineType, time string) ([]KlineResponse, error) { + var klineTemp interface{} + var k []KlineResponse + params := url.Values{} + params.Set("symbol", symbol) + params.Set("size", size) + params.Set("type", klineType) + params.Set("time", time) + path := fmt.Sprintf("%s/v%s/%s?%s", l.API.Endpoints.URL, lbankAPIVersion, lbankKlines, params.Encode()) + err := l.SendHTTPRequest(path, &klineTemp) + if err != nil { + return k, err + } + + resp, ok := klineTemp.([]interface{}) + if !ok { + return nil, errors.New("response received is invalid") + } + + for i := range resp { + resp2, ok := resp[i].([]interface{}) + if !ok { + return nil, errors.New("response received is invalid") + } + var tempResp KlineResponse + for x := range resp2 { + switch x { + case 0: + tempResp.TimeStamp = int64(resp2[x].(float64)) + case 1: + if val, ok := resp2[x].(int64); ok { + tempResp.OpenPrice = float64(val) + } else { + tempResp.OpenPrice = resp2[x].(float64) + } + case 2: + if val, ok := resp2[x].(int64); ok { + tempResp.HigestPrice = float64(val) + } else { + tempResp.HigestPrice = resp2[x].(float64) + } + case 3: + if val, ok := resp2[x].(int64); ok { + tempResp.LowestPrice = float64(val) + } else { + tempResp.LowestPrice = resp2[x].(float64) + } + + case 4: + if val, ok := resp2[x].(int64); ok { + tempResp.ClosePrice = float64(val) + } else { + tempResp.ClosePrice = resp2[x].(float64) + } + + case 5: + if val, ok := resp2[x].(int64); ok { + tempResp.TradingVolume = float64(val) + } else { + tempResp.TradingVolume = resp2[x].(float64) + } + + } + } + k = append(k, tempResp) + } + return k, nil +} + +// GetUserInfo gets users account info +func (l *Lbank) GetUserInfo() (InfoFinalResponse, error) { + var resp InfoFinalResponse + path := fmt.Sprintf("%s/v%s/%s?", l.API.Endpoints.URL, lbankAPIVersion, lbankUserInfo) + err := l.SendAuthHTTPRequest(http.MethodPost, path, nil, &resp) + if err != nil { + return resp, err + } + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// CreateOrder creates an order +func (l *Lbank) CreateOrder(pair, side string, amount, price float64) (CreateOrderResponse, error) { + var resp CreateOrderResponse + if !strings.EqualFold(side, "buy") && !strings.EqualFold(side, "sell") { + return resp, errors.New("side type invalid can only be 'buy' or 'sell'") + } + if amount <= 0 { + return resp, errors.New("amount can't be smaller than or equal to 0") + } + if price <= 0 { + return resp, errors.New("price can't be smaller than or equal to 0") + } + params := url.Values{} + + params.Set("symbol", pair) + params.Set("type", strings.ToLower(side)) + params.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) + params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + path := fmt.Sprintf("%s/v%s/%s?", l.API.Endpoints.URL, lbankAPIVersion, lbankPlaceOrder) + err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp) + if err != nil { + return resp, err + } + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// RemoveOrder cancels a given order +func (l *Lbank) RemoveOrder(pair, orderID string) (RemoveOrderResponse, error) { + var resp RemoveOrderResponse + params := url.Values{} + params.Set("symbol", pair) + params.Set("order_id", orderID) + path := fmt.Sprintf("%s/v%s/%s", l.API.Endpoints.URL, lbankAPIVersion, lbankCancelOrder) + err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp) + if err != nil { + return resp, err + } + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// QueryOrder finds out information about orders (can pass up to 3 comma separated values to this) +// Lbank returns an empty string as their []OrderResponse instead of returning an empty array, so when len(tempResp.Orders) > 2 its not empty and should be unmarshalled separately +func (l *Lbank) QueryOrder(pair, orderIDs string) (QueryOrderFinalResponse, error) { + var resp QueryOrderFinalResponse + var tempResp QueryOrderResponse + params := url.Values{} + params.Set("symbol", pair) + params.Set("order_id", orderIDs) + path := fmt.Sprintf("%s/v%s/%s?", l.API.Endpoints.URL, lbankAPIVersion, lbankQueryOrder) + err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &tempResp) + if err != nil { + return resp, err + } + + var totalOrders []OrderResponse + if len(tempResp.Orders) > 2 { + err = json.Unmarshal(tempResp.Orders, &totalOrders) + if err != nil { + return resp, err + } + } + resp.ErrCapture = tempResp.ErrCapture + resp.Orders = totalOrders + + if err != nil { + return resp, err + } + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// QueryOrderHistory finds order info in the past 2 days +// Lbank returns an empty string as their []OrderResponse instead of returning an empty array, so when len(tempResp.Orders) > 2 its not empty and should be unmarshalled separately +func (l *Lbank) QueryOrderHistory(pair, pageNumber, pageLength string) (OrderHistoryFinalResponse, error) { + var resp OrderHistoryFinalResponse + var tempResp OrderHistoryResponse + params := url.Values{} + params.Set("symbol", pair) + params.Set("current_page", pageNumber) + params.Set("page_length", pageLength) + path := fmt.Sprintf("%s/v%s/%s?", l.API.Endpoints.URL, lbankAPIVersion, lbankQueryHistoryOrder) + err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &tempResp) + if err != nil { + return resp, err + } + + var totalOrders []OrderResponse + if len(tempResp.Orders) > 2 { + err = json.Unmarshal(tempResp.Orders, &totalOrders) + if err != nil { + return resp, err + } + } + resp.ErrCapture = tempResp.ErrCapture + resp.PageLength = tempResp.PageLength + resp.Orders = totalOrders + resp.CurrentPage = tempResp.CurrentPage + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// GetPairInfo finds information about all trading pairs +func (l *Lbank) GetPairInfo() ([]PairInfoResponse, error) { + var resp []PairInfoResponse + path := fmt.Sprintf("%s/v%s/%s?", l.API.Endpoints.URL, lbankAPIVersion, lbankPairInfo) + return resp, l.SendHTTPRequest(path, &resp) +} + +// OrderTransactionDetails gets info about transactions +func (l *Lbank) OrderTransactionDetails(symbol, orderID string) (TransactionHistoryResp, error) { + var resp TransactionHistoryResp + params := url.Values{} + params.Set("symbol", symbol) + params.Set("order_id", orderID) + path := fmt.Sprintf("%s/v%s/%s?", l.API.Endpoints.URL, lbankAPIVersion, lbankOrderTransactionDetails) + err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp) + if err != nil { + return resp, err + } + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// TransactionHistory stores info about transactions +func (l *Lbank) TransactionHistory(symbol, transactionType, startDate, endDate, from, direct, size string) (TransactionHistoryResp, error) { + var resp TransactionHistoryResp + params := url.Values{} + params.Set("symbol", symbol) + params.Set("type", transactionType) + params.Set("start_date", startDate) + params.Set("end_date", endDate) + params.Set("from", from) + params.Set("direct", direct) + params.Set("size", size) + path := fmt.Sprintf("%s/v%s/%s?", l.API.Endpoints.URL, lbankAPIVersion, lbankPastTransactions) + err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp) + if err != nil { + return resp, err + } + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// GetOpenOrders gets opening orders +// Lbank returns an empty string as their []OrderResponse instead of returning an empty array, so when len(tempResp.Orders) > 2 its not empty and should be unmarshalled separately +func (l *Lbank) GetOpenOrders(pair, pageNumber, pageLength string) (OpenOrderFinalResponse, error) { + var resp OpenOrderFinalResponse + var tempResp OpenOrderResponse + params := url.Values{} + params.Set("symbol", pair) + params.Set("current_page", pageNumber) + params.Set("page_length", pageLength) + path := fmt.Sprintf("%s/v%s/%s", l.API.Endpoints.URL, lbankAPIVersion, lbankOpeningOrders) + err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &tempResp) + if err != nil { + return resp, err + } + + var totalOrders []OrderResponse + if len(tempResp.Orders) > 2 { + err = json.Unmarshal(tempResp.Orders, &totalOrders) + if err != nil { + return resp, err + } + } + resp.ErrCapture = tempResp.ErrCapture + resp.PageLength = tempResp.PageLength + resp.PageNumber = tempResp.PageNumber + resp.Orders = totalOrders + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// USD2RMBRate finds USD-CNY Rate +func (l *Lbank) USD2RMBRate() (ExchangeRateResponse, error) { + var resp ExchangeRateResponse + path := fmt.Sprintf("%s/v%s/%s", l.API.Endpoints.URL, lbankAPIVersion, lbankUSD2CNYRate) + return resp, l.SendHTTPRequest(path, &resp) +} + +// GetWithdrawConfig gets information about withdrawals +func (l *Lbank) GetWithdrawConfig(assetCode string) ([]WithdrawConfigResponse, error) { + var resp []WithdrawConfigResponse + params := url.Values{} + params.Set("assetCode", assetCode) + path := fmt.Sprintf("%s/v%s/%s?%s", l.API.Endpoints.URL, lbankAPIVersion, lbankWithdrawConfig, params.Encode()) + return resp, l.SendHTTPRequest(path, &resp) +} + +// Withdraw sends a withdrawal request +func (l *Lbank) Withdraw(account, assetCode, amount, memo, mark string) (WithdrawResponse, error) { + var resp WithdrawResponse + params := url.Values{} + params.Set("account", account) + params.Set("assetCode", assetCode) + params.Set("amount", amount) + if memo != "" { + params.Set("memo", memo) + } + if mark != "" { + params.Set("mark", mark) + } + path := fmt.Sprintf("%s/v%s/%s", l.API.Endpoints.URL, lbankAPIVersion, lbankWithdraw) + err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp) + if err != nil { + return resp, err + } + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// RevokeWithdraw cancels the withdrawal given the withdrawalID +func (l *Lbank) RevokeWithdraw(withdrawID string) (RevokeWithdrawResponse, error) { + var resp RevokeWithdrawResponse + params := url.Values{} + if withdrawID != "" { + params.Set("withdrawId", withdrawID) + } + path := fmt.Sprintf("%s/v%s/%s?", l.API.Endpoints.URL, lbankAPIVersion, lbankRevokeWithdraw) + err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp) + if err != nil { + return resp, err + } + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// GetWithdrawalRecords gets withdrawal records +func (l *Lbank) GetWithdrawalRecords(assetCode, status, pageNo, pageSize string) (WithdrawalResponse, error) { + var resp WithdrawalResponse + params := url.Values{} + params.Set("assetCode", assetCode) + params.Set("status", status) + params.Set("pageNo", pageNo) + params.Set("pageSize", pageSize) + path := fmt.Sprintf("%s/v%s/%s", l.API.Endpoints.URL, lbankAPIVersion, lbankWithdrawalRecords) + err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp) + if err != nil { + return resp, err + } + + if resp.Error != 0 { + return resp, ErrorCapture(resp.Error) + } + + return resp, nil +} + +// ErrorCapture captures errors +func ErrorCapture(code int64) error { + msg, ok := errorCodes[code] + if !ok { + return fmt.Errorf("undefined code please check api docs for error code definition: %v", code) + } + return errors.New(msg) +} + +// SendHTTPRequest sends an unauthenticated HTTP request +func (l *Lbank) SendHTTPRequest(path string, result interface{}) error { + return l.SendPayload(http.MethodGet, + path, + nil, + nil, + &result, + false, + false, + l.Verbose, + l.HTTPDebugging, + l.HTTPRecording) +} + +func (l *Lbank) loadPrivKey() error { + key := strings.Join([]string{ + "-----BEGIN RSA PRIVATE KEY-----", + l.API.Credentials.Secret, + "-----END RSA PRIVATE KEY-----", + }, "\n") + + block, _ := pem.Decode([]byte(key)) + if block == nil { + return errors.New("pem block is nil") + } + + p, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return fmt.Errorf("unable to decode priv key: %s", err) + } + + var ok bool + l.privateKey, ok = p.(*rsa.PrivateKey) + if !ok { + return errors.New("unable to parse RSA private key") + } + return nil +} + +func (l *Lbank) sign(data string) (string, error) { + if l.privateKey == nil { + return "", errors.New("private key not loaded") + } + md5hash := gctcrypto.GetMD5([]byte(data)) + m := strings.ToUpper(gctcrypto.HexEncodeToString(md5hash)) + s := gctcrypto.GetSHA256([]byte(m)) + r, err := rsa.SignPKCS1v15(rand.Reader, l.privateKey, crypto.SHA256, s) + if err != nil { + return "", err + } + return gctcrypto.Base64Encode(r), nil +} + +// SendAuthHTTPRequest sends an authenticated request +func (l *Lbank) SendAuthHTTPRequest(method, endpoint string, vals url.Values, result interface{}) error { + if vals == nil { + vals = url.Values{} + } + + vals.Set("api_key", l.API.Credentials.Key) + sig, err := l.sign(vals.Encode()) + if err != nil { + return err + } + + vals.Set("sign", sig) + payload := vals.Encode() + headers := make(map[string]string) + headers["Content-Type"] = "application/x-www-form-urlencoded" + + return l.SendPayload(method, + endpoint, + headers, + bytes.NewBufferString(payload), + &result, + true, + false, + l.Verbose, + l.HTTPDebugging, + l.HTTPRecording) +} diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go new file mode 100644 index 00000000..e9067980 --- /dev/null +++ b/exchanges/lbank/lbank_test.go @@ -0,0 +1,396 @@ +package lbank + +import ( + "fmt" + "sync" + "testing" + "time" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/currency" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" +) + +// Please supply your own keys here for due diligence testing +const ( + testAPIKey = "" + testAPISecret = "" + canManipulateRealOrders = false +) + +var l Lbank +var setupRan bool +var m sync.Mutex + +func TestSetup(t *testing.T) { + t.Parallel() + m.Lock() + defer m.Unlock() + + if setupRan { + return + } + l.SetDefaults() + cfg := config.GetConfig() + err := cfg.LoadConfig("../../testdata/configtest.json") + if err != nil { + t.Errorf("Test Failed - Lbank Setup() init error:, %v", err) + } + lbankConfig, err := cfg.GetExchangeConfig("Lbank") + if err != nil { + t.Errorf("Test Failed - Lbank Setup() init error: %v", err) + } + lbankConfig.API.AuthenticatedSupport = true + lbankConfig.API.Credentials.Secret = testAPISecret + lbankConfig.API.Credentials.Key = testAPIKey + l.Setup(lbankConfig) + setupRan = true +} + +func areTestAPIKeysSet() bool { + return l.AllowAuthenticatedRequest() +} + +func TestGetTicker(t *testing.T) { + TestSetup(t) + _, err := l.GetTicker("btc_usdt") + if err != nil { + t.Errorf("test failed: %v", err) + } +} + +func TestGetCurrencyPairs(t *testing.T) { + TestSetup(t) + _, err := l.GetCurrencyPairs() + if err != nil { + t.Errorf("test failed: %v", err) + } +} + +func TestGetMarketDepths(t *testing.T) { + TestSetup(t) + _, err := l.GetMarketDepths("btc_usdt", "60", "1") + if err != nil { + t.Errorf("GetMarketDepth failed: %v", err) + } + a, _ := l.GetMarketDepths("btc_usdt", "60", "0") + if len(a.Asks) != 60 { + t.Errorf("length requested doesnt match the output") + } +} + +func TestGetTrades(t *testing.T) { + TestSetup(t) + _, err := l.GetTrades("btc_usdt", "600", fmt.Sprintf("%v", time.Now().Unix())) + if err != nil { + t.Errorf("test failed: %v", err) + } + a, err := l.GetTrades("btc_usdt", "600", "0") + if len(a) != 600 && err != nil { + t.Errorf("test failed: %v", err) + } +} + +func TestGetKlines(t *testing.T) { + TestSetup(t) + _, err := l.GetKlines("btc_usdt", "600", "minute1", fmt.Sprintf("%v", time.Now().Unix())) + if err != nil { + t.Errorf("test failed: %v", err) + } +} + +func TestUpdateOrderbook(t *testing.T) { + TestSetup(t) + p := currency.Pair{ + Delimiter: "_", + Base: currency.ETH, + Quote: currency.BTC} + + _, err := l.UpdateOrderbook(p.Lower(), "spot") + if err != nil { + t.Errorf("Update for orderbook failed: %v", err) + } +} + +func TestGetUserInfo(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := l.GetUserInfo() + if err != nil { + t.Errorf("invalid key or sign: %v", err) + } +} + +func TestCreateOrder(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") + } + cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_") + _, err := l.CreateOrder(cp.Lower().String(), "what", 1231, 12314) + if err == nil { + t.Error("Test Failed - CreateOrder error cannot be nil") + } + _, err = l.CreateOrder(cp.Lower().String(), "buy", 0, 0) + if err == nil { + t.Error("Test Failed - CreateOrder error cannot be nil") + } + _, err = l.CreateOrder(cp.Lower().String(), "sell", 1231, 0) + if err == nil { + t.Error("Test Failed - CreateOrder error cannot be nil") + } + _, err = l.CreateOrder(cp.Lower().String(), "buy", 58, 681) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestRemoveOrder(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") + } + cp := currency.NewPairWithDelimiter(currency.ETH.String(), currency.BTC.String(), "_") + _, err := l.RemoveOrder(cp.Lower().String(), "24f7ce27-af1d-4dca-a8c1-ef1cbeec1b23") + if err != nil { + t.Errorf("unable to remove order: %v", err) + } +} + +func TestQueryOrder(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_") + _, err := l.QueryOrder(cp.Lower().String(), "1") + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestQueryOrderHistory(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_") + _, err := l.QueryOrderHistory(cp.Lower().String(), "1", "100") + if err != nil { + t.Errorf("test failed: %v", err) + } +} + +func TestGetPairInfo(t *testing.T) { + TestSetup(t) + _, err := l.GetPairInfo() + if err != nil { + t.Errorf("couldnt get pair info: %v", err) + } +} + +func TestOrderTransactionDetails(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := l.OrderTransactionDetails("eth_btc", "24f7ce27-af1d-4dca-a8c1-ef1cbeec1b23") + if err != nil { + t.Errorf("couldnt get transaction details: %v", err) + } +} + +func TestTransactionHistory(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := l.TransactionHistory("btc_usdt", "", "", "", "", "", "") + if err != nil { + t.Errorf("couldnt get transaction history: %v", err) + } +} + +func TestGetOpenOrders(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_") + _, err := l.GetOpenOrders(cp.Lower().String(), "1", "50") + if err != nil { + t.Error("unexpected error", err) + } +} + +func TestUSD2RMBRate(t *testing.T) { + TestSetup(t) + _, err := l.USD2RMBRate() + if err != nil { + t.Error("unable to acquire the rate") + } +} + +func TestGetWithdrawConfig(t *testing.T) { + TestSetup(t) + _, err := l.GetWithdrawConfig("eth") + if err != nil { + t.Errorf("unable to get withdraw config: %v", err) + } +} + +func TestWithdraw(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") + } + _, err := l.Withdraw("", "", "", "", "") + if err != nil { + t.Errorf("unable to withdraw: %v", err) + } +} + +func TestGetWithdrawRecords(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := l.GetWithdrawalRecords("eth", "0", "1", "20") + if err != nil { + t.Errorf("unable to get withdrawal records: %v", err) + } +} + +func TestLoadPrivKey(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + err := l.loadPrivKey() + if err != nil { + t.Error(err) + } + l.API.Credentials.Secret = "errortest" + err = l.loadPrivKey() + if err == nil { + t.Errorf("expected error due to pemblock nil, got err: %v", err) + } +} + +func TestSign(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + l.API.Credentials.Secret = testAPISecret + l.loadPrivKey() + _, err := l.sign("hello123") + if err != nil { + t.Errorf("test failed: %v", err) + } +} + +func TestSubmitOrder(t *testing.T) { + TestSetup(t) + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } + + var orderSubmission = &exchange.OrderSubmission{ + Pair: currency.Pair{ + Base: currency.BTC, + Quote: currency.USDT, + Delimiter: "_", + }, + OrderSide: exchange.BuyOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1, + Amount: 1, + ClientID: "meowOrder", + } + response, err := l.SubmitOrder(orderSubmission) + if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { + t.Errorf("Order failed to be placed: %v", err) + } else if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting an error when no keys are set") + } +} + +func TestCancelOrder(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") + } + cp := currency.NewPairWithDelimiter(currency.ETH.String(), currency.BTC.String(), "_") + var a exchange.OrderCancellation + a.CurrencyPair = cp + a.OrderID = "24f7ce27-af1d-4dca-a8c1-ef1cbeec1b23" + err := l.CancelOrder(&a) + if err != nil { + t.Errorf("test failed: %v", err) + } +} + +func TestGetOrderInfo(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := l.GetOrderInfo("9ead39f5-701a-400b-b635-d7349eb0f6b") + if err != nil { + t.Errorf("test failed: %v", err) + } +} + +func TestGetAllOpenOrderID(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := l.getAllOpenOrderID() + if err != nil { + t.Errorf("test failed: %v", err) + } +} + +func TestGetFeeByType(t *testing.T) { + TestSetup(t) + cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_") + var input exchange.FeeBuilder + input.Amount = 2 + input.FeeType = exchange.CryptocurrencyWithdrawalFee + input.Pair = cp + a, err := l.GetFeeByType(&input) + if err != nil { + t.Errorf("test failed. couldnt get fee: %v", err) + } + if a != 0.0005 { + t.Errorf("testGetFeeByType failed. Expected: 0.0005, Received: %v", a) + } +} + +func TestGetAccountInfo(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := l.GetAccountInfo() + if err != nil { + t.Error(err) + } +} + +func TestGetOrderHistory(t *testing.T) { + TestSetup(t) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + var input exchange.GetOrdersRequest + input.OrderSide = exchange.BuyOrderSide + _, err := l.GetOrderHistory(&input) + if err != nil { + t.Error(err) + } +} diff --git a/exchanges/lbank/lbank_types.go b/exchanges/lbank/lbank_types.go new file mode 100644 index 00000000..cb6653ac --- /dev/null +++ b/exchanges/lbank/lbank_types.go @@ -0,0 +1,270 @@ +package lbank + +import ( + "encoding/json" +) + +// Ticker stores the ticker price data for a currency pair +type Ticker struct { + Change float64 `json:"change"` + High float64 `json:"high"` + Latest float64 `json:"latest"` + Low float64 `json:"low"` + Turnover float64 `json:"turnover"` + Volume float64 `json:"vol"` +} + +// TickerResponse stores the ticker price data and timestamp for a currency pair +type TickerResponse struct { + Symbol string `json:"symbol"` + Timestamp int64 `json:"timestamp"` + Ticker Ticker `json:"ticker"` +} + +// MarketDepthResponse stores arrays for asks, bids and a timestamp for a currecy pair +type MarketDepthResponse struct { + ErrCapture `json:",omitempty"` + Asks [][]float64 `json:"asks"` + Bids [][]float64 `json:"bids"` + Timestamp int64 `json:"timestamp"` +} + +// TradeResponse stores date_ms, amount, price, type, tid for a currency pair +type TradeResponse struct { + DateMS int64 `json:"date_ms"` + Amount float64 `json:"amount"` + Price float64 `json:"price"` + Type string `json:"type"` + TID string `json:"tid"` +} + +// KlineResponse stores kline info for given currency exchange +type KlineResponse struct { + TimeStamp int64 `json:"timestamp"` + OpenPrice float64 `json:"openprice"` + HigestPrice float64 `json:"highestprice"` + LowestPrice float64 `json:"lowestprice"` + ClosePrice float64 `json:"closeprice"` + TradingVolume float64 `json:"tradingvolume"` +} + +// InfoResponse stores info +type InfoResponse struct { + Freeze map[string]string `json:"freeze"` + Asset map[string]string `json:"asset"` + Free map[string]string `json:"Free"` +} + +// InfoFinalResponse stores info +type InfoFinalResponse struct { + ErrCapture `json:",omitempty"` + Info InfoResponse `json:"info"` +} + +// CreateOrderResponse stores the result of the Order and +type CreateOrderResponse struct { + ErrCapture `json:",omitempty"` + OrderID string `json:"order_id"` +} + +// RemoveOrderResponse stores the result when an order is cancelled +type RemoveOrderResponse struct { + ErrCapture `json:",omitempty"` + Err string `json:"error"` + OrderID string `json:"order_id"` + Success string `json:"success"` +} + +// OrderResponse stores the data related to the given OrderIDs +type OrderResponse struct { + Symbol string `json:"symbol"` + Amount float64 `json:"amount"` + CreateTime int64 `json:"created_time"` + Price float64 `json:"price"` + AvgPrice float64 `json:"avg_price"` + Type string `json:"type"` + OrderID string `json:"order_id"` + DealAmount float64 `json:"deal_amount"` + Status int64 `json:"status"` +} + +// QueryOrderResponse stores the data from queries +type QueryOrderResponse struct { + ErrCapture `json:",omitempty"` + Orders json.RawMessage `json:"orders"` +} + +// QueryOrderFinalResponse stores data from queries +type QueryOrderFinalResponse struct { + ErrCapture + Orders []OrderResponse +} + +// OrderHistory stores data for past orders +type OrderHistory struct { + Result bool `json:"result,string"` + Total string `json:"total"` + PageLength uint8 `json:"page_length"` + Orders json.RawMessage `json:"orders"` + CurrentPage uint8 `json:"current_page"` + ErrorCode int64 `json:"error_code"` +} + +// OrderHistoryResponse stores past orders +type OrderHistoryResponse struct { + ErrCapture `json:",omitempty"` + PageLength uint8 `json:"page_length"` + Orders json.RawMessage `json:"orders"` + CurrentPage uint8 `json:"current_page"` +} + +// OrderHistoryFinalResponse stores past orders +type OrderHistoryFinalResponse struct { + ErrCapture + PageLength uint8 + Orders []OrderResponse + CurrentPage uint8 +} + +// PairInfoResponse stores information about trading pairs +type PairInfoResponse struct { + MinimumQuantity string `json:"minTranQua"` + PriceAccuracy string `json:"priceAccuracy"` + QuantityAccuracy string `json:"quantityAccuracy"` + Symbol string `json:"symbol"` +} + +// TransactionTemp stores details about transactions +type TransactionTemp struct { + TxUUID string `json:"txUuid"` + OrderUUID string `json:"orderUuid"` + TradeType string `json:"tradeType"` + DealTime int64 `json:"dealTime"` + DealPrice float64 `json:"dealPrice"` + DealQuantity float64 `json:"dealQuantity"` + DealVolPrice float64 `json:"dealVolumePrice"` + TradeFee float64 `json:"tradeFee"` + TradeFeeRate float64 `json:"tradeFeeRate"` +} + +// TransactionHistoryResp stores details about past transactions +type TransactionHistoryResp struct { + ErrCapture `json:",omitempty"` + Transaction []TransactionTemp `json:"transaction"` +} + +// OpenOrderResponse stores information about the opening orders +type OpenOrderResponse struct { + ErrCapture `json:",omitempty"` + PageLength uint8 `json:"page_length"` + PageNumber uint8 `json:"page_number"` + Total string `json:"total"` + Orders json.RawMessage `json:"orders"` +} + +// OpenOrderFinalResponse stores the unmarshalled value of OpenOrderResponse +type OpenOrderFinalResponse struct { + ErrCapture + PageLength uint8 + PageNumber uint8 + Total string + Orders []OrderResponse +} + +// ExchangeRateResponse stores information about USD-RMB rate +type ExchangeRateResponse struct { + USD2CNY string `json:"USD2CNY"` +} + +// WithdrawConfigResponse stores info about withdrawal configurations +type WithdrawConfigResponse struct { + AssetCode string `json:"assetCode"` + Minimum string `json:"min"` + CanWithDraw bool `json:"canWithDraw"` + Fee string `json:"fee"` +} + +// WithdrawResponse stores info about the withdrawal +type WithdrawResponse struct { + ErrCapture `json:",omitempty"` + WithdrawID string `json:"withdrawId"` + Fee float64 `json:"fee"` +} + +// RevokeWithdrawResponse stores info about the revoked withdrawal +type RevokeWithdrawResponse struct { + ErrCapture `json:",omitempty"` + WithdrawID string `json:"string"` +} + +// ListDataResponse contains some of withdrawal data +type ListDataResponse struct { + ErrCapture `json:",omitempty"` + Amount float64 `json:"amount"` + AssetCode string `json:"assetCode"` + Address string `json:"address"` + Fee float64 `json:"fee"` + ID int64 `json:"id"` + Time int64 `json:"time"` + TXHash string `json:"txhash"` + Status string `json:"status"` +} + +// WithdrawalResponse stores data for withdrawals +type WithdrawalResponse struct { + ErrCapture `json:",omitempty"` + TotalPages int64 `json:"totalPages"` + PageSize int64 `json:"pageSize"` + PageNo int64 `json:"pageNo"` + List []ListDataResponse `json:"list"` +} + +// ErrCapture helps with error info +type ErrCapture struct { + Error int64 `json:"error_code"` + Result bool `json:"result,string"` +} + +// GetAllOpenIDResp stores orderIds and currency pairs for open orders +type GetAllOpenIDResp struct { + CurrencyPair string + OrderID string +} + +var errorCodes = map[int64]string{ + 10000: "Internal error", + 10001: "The required parameters can not be empty", + 10002: "Validation Failed", + 10003: "Invalid parameter", + 10004: "Request too frequent", + 10005: "Secret key does not exist", + 10006: "User does not exist", + 10007: "Invalid signature", + 10008: "Invalid Trading Pair", + 10009: "Price and/or Amount are required for limit order", + 10010: "Price and/or Amount must be more than 0", + 10013: "The amount is too small", + 10014: "Insufficient amount of money in account", + 10015: "Invalid order type", + 10016: "Insufficient account balance", + 10017: "Server Error", + 10018: "Page size should be between 1 and 50", + 10019: "Cancel NO more than 3 orders in one request", + 10020: "Volume < 0.001", + 10021: "Price < 0.01", + 10022: "Access denied", + 10023: "Market Order is not supported yet.", + 10024: "User cannot trade on this pair", + 10025: "Order has been filled", + 10026: "Order has been cancelld", + 10027: "Order is cancelling", + 10028: "Wrong query time", + 10029: "'from' is not in the query time", + 10030: "'from' does not match the transaction type of inqury", + 10100: "Has no privilege to withdraw", + 10101: "Invalid fee rate to withdraw", + 10102: "Too little to withdraw", + 10103: "Exceed daily limitation of withdraw", + 10104: "Cancel was rejected", + 10105: "Request has been cancelled", +} diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go new file mode 100644 index 00000000..b69e1965 --- /dev/null +++ b/exchanges/lbank/lbank_wrapper.go @@ -0,0 +1,668 @@ +package lbank + +import ( + "fmt" + "strconv" + "strings" + "sync" + "time" + + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/currency" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" + "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + log "github.com/thrasher-corp/gocryptotrader/logger" +) + +// GetDefaultConfig returns a default exchange config +func (l *Lbank) GetDefaultConfig() (*config.ExchangeConfig, error) { + l.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = l.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = l.BaseCurrencies + + err := l.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if l.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = l.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + + return exchCfg, nil +} + +// SetDefaults sets the basic defaults for Lbank +func (l *Lbank) SetDefaults() { + l.Name = "Lbank" + l.Enabled = true + l.Verbose = true + l.API.CredentialsValidator.RequiresKey = true + l.API.CredentialsValidator.RequiresSecret = true + + l.CurrencyPairs = currency.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + }, + + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Delimiter: "_", + }, + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "_", + }, + } + + l.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + REST: true, + RESTCapabilities: exchange.ProtocolFeatures{ + AutoPairUpdates: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | + exchange.NoFiatWithdrawals, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + + l.Requester = request.New(l.Name, + request.NewRateLimit(time.Second, lbankAuthRateLimit), + request.NewRateLimit(time.Second, lbankUnAuthRateLimit), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + l.API.Endpoints.URLDefault = lbankAPIURL + l.API.Endpoints.URL = l.API.Endpoints.URLDefault +} + +// Setup sets exchange configuration profile +func (l *Lbank) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + l.SetEnabled(false) + return nil + } + + err := l.SetupDefaults(exch) + if err != nil { + return err + } + + if l.API.AuthenticatedSupport { + err = l.loadPrivKey() + if err != nil { + l.API.AuthenticatedSupport = false + log.Errorf(log.ExchangeSys, "%s couldn't load private key, setting authenticated support to false", l.Name) + } + } + return nil +} + +// Start starts the LakeBTC go routine +func (l *Lbank) Start(wg *sync.WaitGroup) { + wg.Add(1) + go func() { + l.Run() + wg.Done() + }() +} + +// Run implements the Lbank wrapper +func (l *Lbank) Run() { + if l.Verbose { + l.PrintEnabledPairs() + } + + if !l.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := l.UpdateTradablePairs(false) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", l.Name, err) + } +} + +// FetchTradablePairs returns a list of the exchanges tradable pairs +func (l *Lbank) FetchTradablePairs(asset asset.Item) ([]string, error) { + currencies, err := l.GetCurrencyPairs() + if err != nil { + return nil, err + } + return currencies, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func (l *Lbank) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := l.FetchTradablePairs(asset.Spot) + if err != nil { + return err + } + + return l.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) +} + +// UpdateTicker updates and returns the ticker for a currency pair +func (l *Lbank) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { + var tickerPrice ticker.Price + tickerInfo, err := l.GetTicker(l.FormatExchangeCurrency(p, assetType).String()) + if err != nil { + return tickerPrice, err + } + tickerPrice.Pair = p + tickerPrice.Last = tickerInfo.Ticker.Latest + tickerPrice.High = tickerInfo.Ticker.High + tickerPrice.Volume = tickerInfo.Ticker.Volume + tickerPrice.Low = tickerInfo.Ticker.Low + + err = ticker.ProcessTicker(l.GetName(), &tickerPrice, assetType) + if err != nil { + return tickerPrice, err + } + + return ticker.GetTicker(l.Name, p, assetType) +} + +// FetchTicker returns the ticker for a currency pair +func (l *Lbank) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(l.GetName(), + l.FormatExchangeCurrency(p, assetType), assetType) + if err != nil { + return l.UpdateTicker(p, assetType) + } + return tickerNew, nil +} + +// FetchOrderbook returns orderbook base on the currency pair +func (l *Lbank) FetchOrderbook(currency currency.Pair, assetType asset.Item) (orderbook.Base, error) { + ob, err := orderbook.Get(l.GetName(), currency, assetType) + if err != nil { + return l.UpdateOrderbook(currency, assetType) + } + return ob, nil +} + +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (l *Lbank) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { + var orderBook orderbook.Base + a, err := l.GetMarketDepths(l.FormatExchangeCurrency(p, assetType).String(), "60", "1") + if err != nil { + return orderBook, err + } + for i := range a.Asks { + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Price: a.Asks[i][0], + Amount: a.Asks[i][1]}) + } + for i := range a.Bids { + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Price: a.Bids[i][0], + Amount: a.Bids[i][1]}) + } + orderBook.Pair = p + orderBook.ExchangeName = l.GetName() + orderBook.AssetType = assetType + err = orderBook.Process() + if err != nil { + return orderBook, err + } + + return orderbook.Get(l.Name, p, assetType) +} + +// GetAccountInfo retrieves balances for all enabled currencies for the +// Lbank exchange +func (l *Lbank) GetAccountInfo() (exchange.AccountInfo, error) { + var info exchange.AccountInfo + data, err := l.GetUserInfo() + if err != nil { + return info, err + } + var account exchange.Account + for key, val := range data.Info.Asset { + c := currency.NewCode(key) + hold, ok := data.Info.Freeze[key] + if !ok { + return info, fmt.Errorf("hold data not found with %s", key) + } + totalVal, err := strconv.ParseFloat(val, 64) + if err != nil { + return info, err + } + totalHold, err := strconv.ParseFloat(hold, 64) + if err != nil { + return info, err + } + account.Currencies = append(account.Currencies, + exchange.AccountCurrencyInfo{CurrencyName: c, + TotalValue: totalVal, + Hold: totalHold}) + } + + info.Accounts = append(info.Accounts, account) + info.Exchange = l.GetName() + return info, nil +} + +// GetFundingHistory returns funding history, deposits and +// withdrawals +func (l *Lbank) GetFundingHistory() ([]exchange.FundHistory, error) { + return nil, common.ErrFunctionNotSupported +} + +// GetExchangeHistory returns historic trade data since exchange opening. +func (l *Lbank) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) { + return nil, common.ErrFunctionNotSupported +} + +// SubmitOrder submits a new order +func (l *Lbank) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { + var resp exchange.SubmitOrderResponse + if order == nil { + return resp, exchange.ErrOrderSubmissionIsNil + } + + if err := order.Validate(); err != nil { + return resp, err + } + + if order.OrderSide != exchange.BuyOrderSide && + order.OrderSide != exchange.SellOrderSide { + return resp, + fmt.Errorf("%s order side is not supported by the exchange", + order.OrderSide) + } + tempResp, err := l.CreateOrder( + l.FormatExchangeCurrency(order.Pair, asset.Spot).String(), + order.OrderSide.ToString(), + order.Amount, + order.Price) + if err != nil { + return resp, err + } + resp.IsOrderPlaced = true + resp.OrderID = tempResp.OrderID + return resp, nil +} + +// ModifyOrder will allow of changing orderbook placement and limit to +// market conversion +func (l *Lbank) ModifyOrder(action *exchange.ModifyOrder) (string, error) { + return "", common.ErrFunctionNotSupported +} + +// CancelOrder cancels an order by its corresponding ID number +func (l *Lbank) CancelOrder(order *exchange.OrderCancellation) error { + _, err := l.RemoveOrder(l.FormatExchangeCurrency(order.CurrencyPair, + order.AssetType).String(), order.OrderID) + return err +} + +// CancelAllOrders cancels all orders associated with a currency pair +func (l *Lbank) CancelAllOrders(orders *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { + var resp exchange.CancelAllOrdersResponse + orderIDs, err := l.getAllOpenOrderID() + if err != nil { + return resp, nil + } + + for key := range orderIDs { + if key != orders.CurrencyPair.String() { + continue + } + var x, y = 0, 0 + var input string + var tempSlice []string + for x <= len(orderIDs[key]) { + x++ + for y != x { + tempSlice = append(tempSlice, orderIDs[key][y]) + if y%3 == 0 { + input = strings.Join(tempSlice, ",") + CancelResponse, err2 := l.RemoveOrder(key, input) + if err2 != nil { + return resp, err2 + } + tempStringSuccess := strings.Split(CancelResponse.Success, ",") + for k := range tempStringSuccess { + resp.OrderStatus[tempStringSuccess[k]] = "Cancelled" + } + tempStringError := strings.Split(CancelResponse.Err, ",") + for l := range tempStringError { + resp.OrderStatus[tempStringError[l]] = "Failed" + } + tempSlice = tempSlice[:0] + y++ + } + y++ + } + input = strings.Join(tempSlice, ",") + CancelResponse, err2 := l.RemoveOrder(key, input) + if err2 != nil { + return resp, err2 + } + tempStringSuccess := strings.Split(CancelResponse.Success, ",") + for k := range tempStringSuccess { + resp.OrderStatus[tempStringSuccess[k]] = "Cancelled" + } + tempStringError := strings.Split(CancelResponse.Err, ",") + for l := range tempStringError { + resp.OrderStatus[tempStringError[l]] = "Failed" + } + tempSlice = tempSlice[:0] + } + } + return resp, nil +} + +// GetOrderInfo returns information on a current open order +func (l *Lbank) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { + var resp exchange.OrderDetail + orderIDs, err := l.getAllOpenOrderID() + if err != nil { + return resp, err + } + + for key, val := range orderIDs { + for i := range val { + if val[i] != orderID { + continue + } + tempResp, err := l.QueryOrder(key, orderID) + if err != nil { + return resp, err + } + resp.Exchange = l.GetName() + resp.CurrencyPair = currency.NewPairFromString(key) + if strings.EqualFold(tempResp.Orders[0].Type, "buy") { + resp.OrderSide = exchange.BuyOrderSide + } else { + resp.OrderSide = exchange.SellOrderSide + } + z := tempResp.Orders[0].Status + switch { + case z == -1: + resp.Status = "cancelled" + case z == 0: + resp.Status = "on trading" + case z == 1: + resp.Status = "filled partially" + case z == 2: + resp.Status = "Filled totally" + case z == 4: + resp.Status = "Cancelling" + default: + resp.Status = "Invalid Order Status" + } + resp.Price = tempResp.Orders[0].Price + resp.Amount = tempResp.Orders[0].Amount + resp.ExecutedAmount = tempResp.Orders[0].DealAmount + resp.RemainingAmount = tempResp.Orders[0].Amount - tempResp.Orders[0].DealAmount + resp.Fee, err = l.GetFeeByType(&exchange.FeeBuilder{ + FeeType: exchange.CryptocurrencyTradeFee, + Amount: tempResp.Orders[0].Amount, + PurchasePrice: tempResp.Orders[0].Price}) + if err != nil { + resp.Fee = lbankFeeNotFound + } + } + } + return resp, nil +} + +// GetDepositAddress returns a deposit address for a specified currency +func (l *Lbank) GetDepositAddress(cryptocurrency currency.Code, accountID string) (string, error) { + return "", common.ErrFunctionNotSupported +} + +// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is +// submitted +func (l *Lbank) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { + resp, err := l.Withdraw(withdrawRequest.Address, withdrawRequest.Currency.String(), strconv.FormatFloat(withdrawRequest.Amount, 'f', -1, 64), "", withdrawRequest.Description) + return resp.WithdrawID, err +} + +// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is +// submitted +func (l *Lbank) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { + return "", common.ErrFunctionNotSupported +} + +// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is +// submitted +func (l *Lbank) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { + return "", common.ErrFunctionNotSupported +} + +// GetWebsocket returns a pointer to the exchange websocket +func (l *Lbank) GetWebsocket() (*wshandler.Websocket, error) { + return nil, common.ErrNotYetImplemented +} + +// GetActiveOrders retrieves any orders that are active/open +func (l *Lbank) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var finalResp []exchange.OrderDetail + var resp exchange.OrderDetail + tempData, err := l.getAllOpenOrderID() + if err != nil { + return finalResp, err + } + + for key, val := range tempData { + for x := range val { + tempResp, err := l.QueryOrder(key, val[x]) + if err != nil { + return finalResp, err + } + resp.Exchange = l.GetName() + resp.CurrencyPair = currency.NewPairFromString(key) + if strings.EqualFold(tempResp.Orders[0].Type, "buy") { + resp.OrderSide = exchange.BuyOrderSide + } else { + resp.OrderSide = exchange.SellOrderSide + } + z := tempResp.Orders[0].Status + switch { + case z == -1: + resp.Status = "cancelled" + case z == 1: + resp.Status = "on trading" + case z == 2: + resp.Status = "filled partially" + case z == 3: + resp.Status = "Filled totally" + case z == 4: + resp.Status = "Cancelling" + default: + resp.Status = "Invalid Order Status" + } + resp.Price = tempResp.Orders[0].Price + resp.Amount = tempResp.Orders[0].Amount + resp.OrderDate = time.Unix(tempResp.Orders[0].CreateTime, 9) + resp.ExecutedAmount = tempResp.Orders[0].DealAmount + resp.RemainingAmount = tempResp.Orders[0].Amount - tempResp.Orders[0].DealAmount + resp.Fee, err = l.GetFeeByType(&exchange.FeeBuilder{ + FeeType: exchange.CryptocurrencyTradeFee, + Amount: tempResp.Orders[0].Amount, + PurchasePrice: tempResp.Orders[0].Price}) + if err != nil { + resp.Fee = lbankFeeNotFound + } + for y := int(0); y < len(getOrdersRequest.Currencies); y++ { + if getOrdersRequest.Currencies[y].String() != key { + continue + } + if getOrdersRequest.OrderSide == "ANY" { + finalResp = append(finalResp, resp) + continue + } + if strings.EqualFold(getOrdersRequest.OrderSide.ToString(), tempResp.Orders[0].Type) { + finalResp = append(finalResp, resp) + } + } + } + } + return finalResp, nil +} + +// GetOrderHistory retrieves account order information * +// Can Limit response to specific order status +func (l *Lbank) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { + var finalResp []exchange.OrderDetail + var resp exchange.OrderDetail + var tempCurr currency.Pairs + var x int + if len(getOrdersRequest.Currencies) == 0 { + tempCurr = l.GetEnabledPairs(asset.Spot) + } else { + for x < len(getOrdersRequest.Currencies) { + tempCurr = getOrdersRequest.Currencies + } + } + for a := range tempCurr { + p := l.FormatExchangeCurrency(tempCurr[a], asset.Spot).String() + b := int64(1) + tempResp, err := l.QueryOrderHistory(p, strconv.FormatInt(b, 10), "200") + if err != nil { + return finalResp, err + } + for len(tempResp.Orders) != 0 { + tempResp, err = l.QueryOrderHistory(p, strconv.FormatInt(b, 10), "200") + if err != nil { + return finalResp, err + } + for x := 0; x < len(tempResp.Orders); x++ { + resp.Exchange = l.GetName() + resp.CurrencyPair = currency.NewPairFromString(tempResp.Orders[x].Symbol) + if strings.EqualFold(tempResp.Orders[x].Type, "buy") { + resp.OrderSide = exchange.BuyOrderSide + } else { + resp.OrderSide = exchange.SellOrderSide + } + z := tempResp.Orders[x].Status + switch { + case z == -1: + resp.Status = "cancelled" + case z == 1: + resp.Status = "on trading" + case z == 2: + resp.Status = "filled partially" + case z == 3: + resp.Status = "Filled totally" + case z == 4: + resp.Status = "Cancelling" + default: + resp.Status = "Invalid Order Status" + } + resp.Price = tempResp.Orders[x].Price + resp.Amount = tempResp.Orders[x].Amount + resp.OrderDate = time.Unix(tempResp.Orders[x].CreateTime, 9) + resp.ExecutedAmount = tempResp.Orders[x].DealAmount + resp.RemainingAmount = tempResp.Orders[x].Price - tempResp.Orders[x].DealAmount + resp.Fee, err = l.GetFeeByType(&exchange.FeeBuilder{ + FeeType: exchange.CryptocurrencyTradeFee, + Amount: tempResp.Orders[x].Amount, + PurchasePrice: tempResp.Orders[x].Price}) + if err != nil { + resp.Fee = lbankFeeNotFound + } + finalResp = append(finalResp, resp) + b++ + } + } + } + return finalResp, nil +} + +// GetFeeByType returns an estimate of fee based on the type of transaction * +func (l *Lbank) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { + var resp float64 + if feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { + return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.002, nil + } + if feeBuilder.FeeType == exchange.CryptocurrencyWithdrawalFee { + withdrawalFee, err := l.GetWithdrawConfig(feeBuilder.Pair.Base.Lower().String()) + if err != nil { + return resp, err + } + var tempFee string + temp := strings.Split(withdrawalFee[0].Fee, ":\"") + if len(temp) > 1 { + tempFee = strings.TrimRight(temp[1], ",\"type") + } else { + tempFee = temp[0] + } + resp, err = strconv.ParseFloat(tempFee, 64) + if err != nil { + return resp, err + } + } + return resp, nil +} + +// GetAllOpenOrderID returns all open orders by currency pairs +func (l *Lbank) getAllOpenOrderID() (map[string][]string, error) { + allPairs := l.GetEnabledPairs(asset.Spot) + resp := make(map[string][]string) + for a := range allPairs { + p := l.FormatExchangeCurrency(allPairs[a], asset.Spot).String() + b := int64(1) + tempResp, err := l.GetOpenOrders(p, strconv.FormatInt(b, 10), "200") + if err != nil { + return resp, err + } + tempData := len(tempResp.Orders) + for tempData != 0 { + tempResp, err = l.GetOpenOrders(p, strconv.FormatInt(b, 10), "200") + if err != nil { + return resp, err + } + + if len(tempResp.Orders) == 0 { + return resp, nil + } + + for c := 0; c < tempData; c++ { + resp[p] = append(resp[p], tempResp.Orders[c].OrderID) + + } + tempData = len(tempResp.Orders) + b++ + } + } + return resp, nil +} + +// SubscribeToWebsocketChannels appends to ChannelsToSubscribe +// which lets websocket.manageSubscriptions handle subscribing +func (l *Lbank) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { + return common.ErrNotYetImplemented +} + +// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe +// which lets websocket.manageSubscriptions handle unsubscribing +func (l *Lbank) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { + return common.ErrNotYetImplemented +} + +// AuthenticateWebsocket authenticates it +func (l *Lbank) AuthenticateWebsocket() error { + return common.ErrNotYetImplemented +} + +// GetSubscriptions gets subscriptions +func (l *Lbank) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { + return nil, common.ErrNotYetImplemented +} diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index b7563f41..dd37c207 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -1,6 +1,7 @@ package localbitcoins import ( + "bytes" "errors" "fmt" "net/http" @@ -142,16 +143,36 @@ func (l *LocalBitcoins) GetAccountInformation(username string, self bool) (Accou // adID - [optional] string if omitted returns all ads func (l *LocalBitcoins) Getads(args ...string) (AdData, error) { var resp struct { - Data AdData `json:"data"` + Data AdData `json:"data"` + Error struct { + Message string `json:"message"` + Code int `json:"error_code"` + } `json:"error"` } + var err error if len(args) == 0 { - return resp.Data, l.SendAuthenticatedHTTPRequest(http.MethodGet, localbitcoinsAPIAds, nil, &resp) + err = l.SendAuthenticatedHTTPRequest(http.MethodGet, + localbitcoinsAPIAds, + nil, + &resp) + } else { + params := url.Values{"ads": {strings.Join(args, ",")}} + + err = l.SendAuthenticatedHTTPRequest(http.MethodGet, + localbitcoinsAPIAdGet, + params, + &resp) } - params := url.Values{"ads": {strings.Join(args, ",")}} + if err != nil { + return resp.Data, err + } - return resp.Data, l.SendAuthenticatedHTTPRequest(http.MethodGet, localbitcoinsAPIAdGet, params, &resp) + if resp.Error.Message != "" { + return resp.Data, errors.New(resp.Error.Message) + } + return resp.Data, nil } // EditAd updates set advertisements @@ -160,12 +181,27 @@ func (l *LocalBitcoins) Getads(args ...string) (AdData, error) { // adID - string for the ad you already created // TODO func (l *LocalBitcoins) EditAd(_ *AdEdit, adID string) error { - type response struct { - Data AdData `json:"data"` + resp := struct { + Data AdData `json:"data"` + Error struct { + Message string `json:"message"` + Code int `json:"error_code"` + } + }{} + + err := l.SendAuthenticatedHTTPRequest(http.MethodPost, + localbitcoinsAPIAdEdit+adID+"/", + nil, + &resp) + if err != nil { + return err } - resp := response{} - return l.SendAuthenticatedHTTPRequest(http.MethodPost, localbitcoinsAPIAdEdit+adID+"/", nil, &resp) + if resp.Error.Message != "" { + return errors.New(resp.Error.Message) + } + + return nil } // CreateAd creates a new advertisement @@ -192,7 +228,26 @@ func (l *LocalBitcoins) UpdatePriceEquation(adID string) error { // adID - string of specific ad identification // TODO func (l *LocalBitcoins) DeleteAd(adID string) error { - return l.SendAuthenticatedHTTPRequest(http.MethodPost, localbitcoinsAPIDeleteAd+adID, nil, nil) + resp := struct { + Error struct { + Message string `json:"message"` + Code int `json:"error_code"` + } `json:"error"` + }{} + + err := l.SendAuthenticatedHTTPRequest(http.MethodPost, + localbitcoinsAPIDeleteAd+adID+"/", + nil, + &resp) + if err != nil { + return err + } + + if resp.Error.Message != "" { + return errors.New(resp.Error.Message) + } + + return nil } // ReleaseFunds releases Bitcoin trades specified by ID {contact_id}. If the @@ -517,7 +572,7 @@ func (l *LocalBitcoins) GetWalletBalance() (WalletBalanceInfo, error) { // On success, the response returns a message indicating success. It is highly // recommended to minimize the lifetime of access tokens with the money // permission. Use Logout() to make the current token expire instantly. -func (l *LocalBitcoins) WalletSend(address string, amount float64, pin int64) (bool, error) { +func (l *LocalBitcoins) WalletSend(address string, amount float64, pin int64) error { values := url.Values{} values.Set("address", address) values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) @@ -528,23 +583,34 @@ func (l *LocalBitcoins) WalletSend(address string, amount float64, pin int64) (b path = localbitcoinsAPIWalletSendPin } - type response struct { + resp := struct { + Error struct { + Message string `json:"message"` + Errors map[string]string `json:"errors"` + Code int `json:"error_code"` + } `json:"error"` Data struct { Message string `json:"message"` } `json:"data"` - } + }{} - resp := response{} err := l.SendAuthenticatedHTTPRequest(http.MethodPost, path, values, &resp) if err != nil { - return false, err + return err } if resp.Data.Message != "Money is being sent" { - return false, errors.New("unable to send Bitcoins") + if len(resp.Error.Errors) != 0 { + var details string + for _, val := range resp.Error.Errors { + details += val + } + return errors.New(details) + } + return errors.New(resp.Data.Message) } - return true, nil + return nil } // GetWalletAddress returns an unused receiving address from the token owner's @@ -665,7 +731,16 @@ func (l *LocalBitcoins) GetOrderbook(currency string) (Orderbook, error) { // SendHTTPRequest sends an unauthenticated HTTP request func (l *LocalBitcoins) SendHTTPRequest(path string, result interface{}) error { - return l.SendPayload(http.MethodGet, path, nil, nil, result, false, false, l.Verbose, l.HTTPDebugging) + return l.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + l.Verbose, + l.HTTPDebugging, + l.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to @@ -695,8 +770,16 @@ func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, params path += "?" + encoded } - return l.SendPayload(method, l.API.Endpoints.URL+path, headers, - strings.NewReader(encoded), result, true, true, l.Verbose, l.HTTPDebugging) + return l.SendPayload(method, + l.API.Endpoints.URL+path, + headers, + bytes.NewBufferString(encoded), + result, + true, + true, + l.Verbose, + l.HTTPDebugging, + l.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/localbitcoins/localbitcoins_live_test.go b/exchanges/localbitcoins/localbitcoins_live_test.go new file mode 100644 index 00000000..ac50b975 --- /dev/null +++ b/exchanges/localbitcoins/localbitcoins_live_test.go @@ -0,0 +1,32 @@ +//+build mock_test_off + +// This will build if build tag mock_test_off is parsed and will do live testing +// using all tests in (exchange)_test.go +package localbitcoins + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +var mockTests = false + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + localbitcoinsConfig, err := cfg.GetExchangeConfig("LocalBitcoins") + if err != nil { + log.Fatal("Test Failed - LocalBitcoins Setup() init error", err) + } + localbitcoinsConfig.API.AuthenticatedSupport = true + localbitcoinsConfig.API.Credentials.Key = apiKey + localbitcoinsConfig.API.Credentials.Secret = apiSecret + l.SetDefaults() + l.Setup(localbitcoinsConfig) + log.Printf(sharedtestvalues.LiveTesting, l.GetName(), l.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/localbitcoins/localbitcoins_mock_test.go b/exchanges/localbitcoins/localbitcoins_mock_test.go new file mode 100644 index 00000000..996dd56e --- /dev/null +++ b/exchanges/localbitcoins/localbitcoins_mock_test.go @@ -0,0 +1,45 @@ +//+build !mock_test_off + +// This will build if build tag mock_test_off is not parsed and will try to mock +// all tests in _test.go +package localbitcoins + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/mock" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +const mockfile = "../../testdata/http_mock/localbitcoins/localbitcoins.json" + +var mockTests = true + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + localbitcoinsConfig, err := cfg.GetExchangeConfig("LocalBitcoins") + if err != nil { + log.Fatal("Test Failed - Localbitcoins Setup() init error", err) + } + l.SkipAuthCheck = true + localbitcoinsConfig.API.AuthenticatedSupport = true + localbitcoinsConfig.API.Credentials.Key = apiKey + localbitcoinsConfig.API.Credentials.Secret = apiSecret + l.SetDefaults() + l.Setup(localbitcoinsConfig) + + serverDetails, newClient, err := mock.NewVCRServer(mockfile) + if err != nil { + log.Fatalf("Test Failed - Mock server error %s", err) + } + + l.HTTPClient = newClient + l.API.Endpoints.URL = serverDetails + + log.Printf(sharedtestvalues.MockTesting, l.GetName(), l.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/localbitcoins/localbitcoins_test.go b/exchanges/localbitcoins/localbitcoins_test.go index 3474ff8e..d99d9a0e 100644 --- a/exchanges/localbitcoins/localbitcoins_test.go +++ b/exchanges/localbitcoins/localbitcoins_test.go @@ -4,13 +4,10 @@ import ( "testing" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" ) -var l LocalBitcoins - // Please supply your own APIKEYS here for due diligence testing const ( @@ -19,26 +16,11 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { - l.SetDefaults() -} - -func TestSetup(t *testing.T) { - cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") - localbitcoinsConfig, err := cfg.GetExchangeConfig("LocalBitcoins") - if err != nil { - t.Error("Test Failed - LakeBTC Setup() init error") - } - localbitcoinsConfig.API.AuthenticatedSupport = true - localbitcoinsConfig.API.Credentials.Key = apiKey - localbitcoinsConfig.API.Credentials.Secret = apiSecret - - l.Setup(localbitcoinsConfig) -} +var l LocalBitcoins func TestGetTicker(t *testing.T) { t.Parallel() + _, err := l.GetTicker() if err != nil { t.Errorf("Test failed - GetTicker() returned: %s", err) @@ -47,6 +29,7 @@ func TestGetTicker(t *testing.T) { func TestGetTradableCurrencies(t *testing.T) { t.Parallel() + _, err := l.GetTradableCurrencies() if err != nil { t.Errorf("Test failed - GetTradableCurrencies() returned: %s", err) @@ -55,43 +38,42 @@ func TestGetTradableCurrencies(t *testing.T) { func TestGetAccountInfo(t *testing.T) { t.Parallel() - if !l.ValidateAPICredentials() { - t.Skip() - } _, err := l.GetAccountInformation("", true) - if err == nil { - t.Error("Test Failed - GetAccountInformation() error", err) - } - _, err = l.GetAccountInformation("bitcoinbaron", false) - if err != nil { - t.Error("Test Failed - GetAccountInformation() error", err) + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Errorf("Could not get AccountInformation: %s", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Errorf("Could not get AccountInformation: %s", err) } } func TestGetads(t *testing.T) { t.Parallel() - if !l.ValidateAPICredentials() { - t.Skip() - } _, err := l.Getads("") - if err == nil { - t.Error("Test Failed - Getads() - Full list, error", err) - } - _, err = l.Getads("1337") - if err == nil { - t.Error("Test Failed - Getads() error", err) + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Errorf("Could not get ads: %s", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Expecting an error when no keys are set") + case mockTests && err == nil: + t.Error("Expecting error until QA pass") } } func TestEditAd(t *testing.T) { t.Parallel() - if !l.ValidateAPICredentials() { - t.Skip() - } - edit := AdEdit{} + + var edit AdEdit err := l.EditAd(&edit, "1337") - if err == nil { - t.Error("Test Failed - EditAd() error", err) + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Errorf("Could not edit order: %s", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Expecting an error when no keys are set") + case mockTests && err == nil: + t.Error("Expecting error until QA pass") } } @@ -110,6 +92,7 @@ func setFeeBuilder() *exchange.FeeBuilder { // TestGetFeeByTypeOfflineTradeFee logic test func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { + t.Parallel() var feeBuilder = setFeeBuilder() l.GetFeeByType(feeBuilder) if apiKey == "" || apiSecret == "" { @@ -124,7 +107,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - l.SetDefaults() + t.Parallel() var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { @@ -200,45 +183,53 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - l.SetDefaults() - expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText + t.Parallel() + + expectedResult := exchange.AutoWithdrawCryptoText + + " & " + + exchange.WithdrawFiatViaWebsiteOnlyText withdrawPermissions := l.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { - t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) + t.Errorf("Expected: %s, Received: %s", + expectedResult, + withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - l.SetDefaults() - TestSetup(t) + t.Parallel() var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, } _, err := l.GetActiveOrders(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Errorf("Could not get active orders: %s", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Errorf("Could not get active orders: %s", err) } } func TestGetOrderHistory(t *testing.T) { - l.SetDefaults() - TestSetup(t) + t.Parallel() var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, } _, err := l.GetOrderHistory(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Errorf("Could not get order history: %s", err) } } @@ -249,10 +240,9 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - l.SetDefaults() - TestSetup(t) + t.Parallel() - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -268,62 +258,62 @@ func TestSubmitOrder(t *testing.T) { ClientID: "meowOrder", } response, err := l.SubmitOrder(orderSubmission) - if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { + switch { + case areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) && !mockTests: t.Errorf("Order failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err == nil: + t.Error("Expecting an error until QA pass") } } func TestCancelExchangeOrder(t *testing.T) { - l.SetDefaults() - TestSetup(t) + t.Parallel() - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", - CurrencyPair: currencyPair, + CurrencyPair: currency.NewPair(currency.LTC, currency.BTC), } err := l.CancelOrder(orderCancellation) - if !areTestAPIKeysSet() && err == nil { + switch { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Could not cancel orders: %v", err) + case mockTests && err == nil: + t.Error("Expecting an error until QA pass") } } func TestCancelAllExchangeOrders(t *testing.T) { - l.SetDefaults() - TestSetup(t) + t.Parallel() - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", - CurrencyPair: currencyPair, + CurrencyPair: currency.NewPair(currency.LTC, currency.BTC), } resp, err := l.CancelAllOrders(orderCancellation) - - if !areTestAPIKeysSet() && err == nil { + switch { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Errorf("Could not cancel orders: %v", err) + case mockTests && err != nil: t.Errorf("Could not cancel orders: %v", err) } @@ -333,15 +323,17 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + t.Parallel() + _, err := l.ModifyOrder(&exchange.ModifyOrder{}) - if err == nil { - t.Error("Test failed - ModifyOrder() error") + if err != common.ErrFunctionNotSupported { + t.Error("Test failed - ModifyOrder() error", err) } } func TestWithdraw(t *testing.T) { - l.SetDefaults() - TestSetup(t) + t.Parallel() + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -351,59 +343,55 @@ func TestWithdraw(t *testing.T) { Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } _, err := l.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) - if !areTestAPIKeysSet() && err == nil { + switch { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil && !mockTests: t.Errorf("Withdraw failed to be placed: %v", err) + case mockTests && err == nil: + t.Error("Expecting an error until QA pass") } } func TestWithdrawFiat(t *testing.T) { - l.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } + t.Parallel() var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := l.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } func TestWithdrawInternationalBank(t *testing.T) { - l.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } + t.Parallel() var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := l.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } func TestGetDepositAddress(t *testing.T) { - if apiKey != "" || apiSecret != "" { - _, err := l.GetDepositAddress(currency.BTC, "") - if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) - } - } else { - _, err := l.GetDepositAddress(currency.BTC, "") - if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") - } + t.Parallel() + + _, err := l.GetDepositAddress(currency.BTC, "") + switch { + case areTestAPIKeysSet() && err != nil && !mockTests: + t.Error("Test Failed - GetDepositAddress() error", err) + case !areTestAPIKeysSet() && err == nil && !mockTests: + t.Error("Test Failed - GetDepositAddress() expecting an error when no APIKeys are set") + case mockTests && err != nil: + t.Error("Test Failed - GetDepositAddress() error", err) } } diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 455b8f74..4c63780b 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -380,8 +380,10 @@ func (l *LocalBitcoins) GetDepositAddress(cryptocurrency currency.Code, _ string // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted func (l *LocalBitcoins) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { - _, err := l.WalletSend(withdrawRequest.Address, withdrawRequest.Amount, withdrawRequest.PIN) - return "", err + return "", + l.WalletSend(withdrawRequest.Address, + withdrawRequest.Amount, + withdrawRequest.PIN) } // WithdrawFiatFunds returns a withdrawal ID when a @@ -403,7 +405,7 @@ func (l *LocalBitcoins) GetWebsocket() (*wshandler.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (l *LocalBitcoins) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if !l.AllowAuthenticatedRequest() && // Todo check connection status + if (!l.AllowAuthenticatedRequest() || l.SkipAuthCheck) && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } diff --git a/exchanges/mock/README.md b/exchanges/mock/README.md new file mode 100644 index 00000000..45e2c732 --- /dev/null +++ b/exchanges/mock/README.md @@ -0,0 +1,174 @@ +# GoCryptoTrader package Mock + + + + +[![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/mock) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + + +This Mock package is part of the GoCryptoTrader codebase. + +## This is still in active development + +You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY) + +## Mock Testing Suite + +### Current Features + ++ REST recording service ++ REST mock response server + +### How to enable + ++ Mock testing is enabled by default in some exchanges; to disable and run live endpoint testing parse -tags=mock_test_off as a go test param. + ++ To record a live endpoint create two files for an exchange. + +### file one - your_current_exchange_name_live_test.go + +```go +//+build mock_test_off + +// This will build if build tag mock_test_off is parsed and will do live testing +// using all tests in (exchange)_test.go +package your_current_exchange_name + +import ( + "os" + "testing" + "log" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +var mockTests = false + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + your_current_exchange_nameConfig, err := cfg.GetExchangeConfig("your_current_exchange_name") + if err != nil { + log.Fatal("Test Failed - your_current_exchange_name Setup() init error", err) + } + your_current_exchange_nameConfig.AuthenticatedAPISupport = true + your_current_exchange_nameConfig.APIKey = apiKey + your_current_exchange_nameConfig.APISecret = apiSecret + l.SetDefaults() + l.Setup(&your_current_exchange_nameConfig) + log.Printf(sharedtestvalues.LiveTesting, l.GetName(), l.APIUrl) + os.Exit(m.Run()) +} +``` + +### file two - your_current_exchange_name_mock_test.go + +```go +//+build !mock_test_off + +// This will build if build tag mock_test_off is not parsed and will try to mock +// all tests in _test.go +package your_current_exchange_name + +import ( + "os" + "testing" + "log" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/mock" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +const mockfile = "../../testdata/http_mock/your_current_exchange_name/your_current_exchange_name.json" + +var mockTests = true + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + your_current_exchange_nameConfig, err := cfg.GetExchangeConfig("your_current_exchange_name") + if err != nil { + log.Fatal("Test Failed - your_current_exchange_name Setup() init error", err) + } + your_current_exchange_nameConfig.AuthenticatedAPISupport = true + your_current_exchange_nameConfig.APIKey = apiKey + your_current_exchange_nameConfig.APISecret = apiSecret + l.SetDefaults() + l.Setup(&your_current_exchange_nameConfig) + + serverDetails, newClient, err := mock.NewVCRServer(mockfile) + if err != nil { + log.Fatalf("Test Failed - Mock server error %s", err) + } + + g.HTTPClient = newClient + g.APIUrl = serverDetails + + log.Printf(sharedtestvalues.MockTesting, l.GetName(), l.APIUrl) + os.Exit(m.Run()) +} + +``` + ++ Once those files are completed go through each invidual test function and add + +```go +var s SomeExchange + +func TestDummyTest(t *testing.T) { + s.APIURL = exchangeDefaultURL // This will overwrite the current mock url at localhost + s.Verbose = true // This will show you some fancy debug output + s.HTTPRecording = true // This will record the request and response payloads + + err := s.SomeExchangeEndpointFunction() + // check error +} +``` + ++ After this is completed it should populate a new mocktest.json file for you with the relavent payloads in testdata ++ To check if the recording was successful, comment out recording and apiurl changes, then re-run test. + +```go +var s SomeExchange + +func TestDummyTest(t *testing.T) { + // s.APIURL = exchangeDefaultURL // This will overwrite the current mock url at localhost + s.Verbose = true // This will show you some fancy debug output + // s.HTTPRecording = true // This will record the request and response payloads + + err := s.SomeExchangeEndpointFunction() + // check error +} +``` + ++ The payload should be the same. + +### Please click GoDocs chevron above to view current GoDoc information for this package + +## Contribution + +Please feel free to submit any pull requests or suggest any desired features to be added. + +When submitting a PR, please abide by our coding guidelines: + ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. + +## Donations + + + +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: + +***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** + diff --git a/exchanges/mock/common.go b/exchanges/mock/common.go new file mode 100644 index 00000000..e738bf8c --- /dev/null +++ b/exchanges/mock/common.go @@ -0,0 +1,71 @@ +package mock + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "net/url" + "reflect" + "strconv" + "strings" +) + +// MatchURLVals matches url.Value query strings +func MatchURLVals(v1, v2 url.Values) bool { + if len(v1) != len(v2) { + return false + } + + if len(v1) == 0 && len(v2) == 0 { + return true + } + + for key, val := range v1 { + if key == "nonce" || key == "signature" || key == "timestamp" || key == "tonce" || key == "key" { // delta values + if _, ok := v2[key]; !ok { + return false + } + continue + } + + if val2, ok := v2[key]; ok { + if strings.Join(val2, "") == strings.Join(val, "") { + continue + } + } + return false + } + return true +} + +// DeriveURLValsFromJSONMap gets url vals from a map[string]string encoded JSON body +func DeriveURLValsFromJSONMap(payload []byte) (url.Values, error) { + var vals = url.Values{} + if string(payload) == "" { + return vals, nil + } + intermediary := make(map[string]interface{}) + err := json.Unmarshal(payload, &intermediary) + if err != nil { + return vals, err + } + + for k, v := range intermediary { + switch val := v.(type) { + case string: + vals.Add(k, val) + case bool: + vals.Add(k, strconv.FormatBool(val)) + case float64: + vals.Add(k, strconv.FormatFloat(val, 'f', -1, 64)) + case map[string]interface{}, []interface{}, nil: + vals.Add(k, fmt.Sprintf("%v", val)) + default: + log.Println(reflect.TypeOf(val)) + return vals, errors.New("unhandled conversion type, please add as needed") + } + } + + return vals, nil +} diff --git a/exchanges/mock/common_test.go b/exchanges/mock/common_test.go new file mode 100644 index 00000000..d06269dc --- /dev/null +++ b/exchanges/mock/common_test.go @@ -0,0 +1,140 @@ +package mock + +import ( + "encoding/json" + "net/url" + "testing" +) + +func TestMatchURLVals(t *testing.T) { + testVal, testVal2, testVal3, emptyVal := url.Values{}, url.Values{}, url.Values{}, url.Values{} + testVal.Add("test", "test") + testVal2.Add("test2", "test2") + testVal3.Add("test", "diferentValString") + + nonceVal1, nonceVal2 := url.Values{}, url.Values{} + nonceVal1.Add("nonce", "012349723587") + nonceVal2.Add("nonce", "9327373874") + + var expected = false + received := MatchURLVals(testVal, emptyVal) + if received != expected { + t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + expected, + received) + } + + received = MatchURLVals(emptyVal, testVal) + if received != expected { + t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + expected, + received) + } + + received = MatchURLVals(testVal, testVal2) + if received != expected { + t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + expected, + received) + } + + received = MatchURLVals(testVal2, testVal) + if received != expected { + t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + expected, + received) + } + + received = MatchURLVals(testVal, testVal3) + if received != expected { + t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + expected, + received) + } + + received = MatchURLVals(nonceVal1, testVal2) + if received != expected { + t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + expected, + received) + } + + expected = true + received = MatchURLVals(emptyVal, emptyVal) + if received != expected { + t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + expected, + received) + } + + received = MatchURLVals(testVal, testVal) + if received != expected { + t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + expected, + received) + } + + received = MatchURLVals(nonceVal1, nonceVal2) + if received != expected { + t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + expected, + received) + } +} + +func TestDeriveURLValsFromJSON(t *testing.T) { + test1 := struct { + Things []string `json:"things"` + Data struct { + Numbers []int `json:"numbers"` + Number float64 `json:"number"` + SomeString string `json:"somestring"` + } `json:"data"` + }{ + Things: []string{"hello", "world"}, + Data: struct { + Numbers []int `json:"numbers"` + Number float64 `json:"number"` + SomeString string `json:"somestring"` + }{ + Numbers: []int{1, 3, 3, 7}, + Number: 3.14, + SomeString: "hello, peoples", + }, + } + + payload, err := json.Marshal(test1) + if err != nil { + t.Error("Test Failed - marshal error", err) + } + + _, err = DeriveURLValsFromJSONMap(payload) + if err != nil { + t.Error("Test Failed - DeriveURLValsFromJSON error", err) + } + + test2 := map[string]string{ + "val": "1", + "val2": "2", + "val3": "3", + "val4": "4", + "val5": "5", + "val6": "6", + "val7": "7", + } + + payload, err = json.Marshal(test2) + if err != nil { + t.Error("Test Failed - marshal error", err) + } + + vals, err := DeriveURLValsFromJSONMap(payload) + if err != nil { + t.Error("Test Failed - DeriveURLValsFromJSON error", err) + } + + if vals["val"][0] != "1" { + t.Error("Test Failed - DeriveURLValsFromJSON unexpected value", + vals["val"][0]) + } +} diff --git a/exchanges/mock/recording.go b/exchanges/mock/recording.go new file mode 100644 index 00000000..2deeaff5 --- /dev/null +++ b/exchanges/mock/recording.go @@ -0,0 +1,440 @@ +package mock + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "path/filepath" + "reflect" + "strings" + "sync" + + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/crypto" +) + +// HTTPResponse defines expected response from the end point including request +// data for pathing on the VCR server +type HTTPResponse struct { + Data json.RawMessage `json:"data"` + QueryString string `json:"queryString"` + BodyParams string `json:"bodyParams"` + Headers map[string][]string `json:"headers"` +} + +// HTTPRecord will record the request and response to a default JSON file for +// mocking purposes +func HTTPRecord(res *http.Response, service string, respContents []byte) error { + if res == nil { + return errors.New("http.Response cannot be nil") + } + + if res.Request == nil { + return errors.New("http.Request cannot be nil") + } + + if res.Request.Method == "" { + return errors.New("request method not supplied") + } + + if service == "" { + return errors.New("service not supplied cannot access correct mock file") + } + service = strings.ToLower(service) + + fileout := filepath.Join(DefaultDirectory, service, service+".json") + + contents, err := ioutil.ReadFile(fileout) + if err != nil { + return err + } + + var m VCRMock + err = json.Unmarshal(contents, &m) + if err != nil { + return err + } + + if m.Routes == nil { + m.Routes = make(map[string]map[string][]HTTPResponse) + } + + var httpResponse HTTPResponse + cleanedContents, err := CheckResponsePayload(respContents) + if err != nil { + return err + } + + err = json.Unmarshal(cleanedContents, &httpResponse.Data) + if err != nil { + return err + } + + var body string + if res.Request.GetBody != nil { + bodycopy, bodyErr := res.Request.GetBody() + if bodyErr != nil { + return bodyErr + } + payload, bodyErr := ioutil.ReadAll(bodycopy) + if bodyErr != nil { + return bodyErr + } + body = string(payload) + } + + switch res.Request.Header.Get(contentType) { + case applicationURLEncoded: + vals, urlErr := url.ParseQuery(body) + if urlErr != nil { + return urlErr + } + + httpResponse.BodyParams, urlErr = GetFilteredURLVals(vals) + if urlErr != nil { + return urlErr + } + + case textPlain: + payload := res.Request.Header.Get("X-Gemini-Payload") + j, dErr := crypto.Base64Decode(payload) + if dErr != nil { + return dErr + } + + httpResponse.BodyParams = string(j) + + default: + httpResponse.BodyParams = body + } + + httpResponse.Headers, err = GetFilteredHeader(res) + if err != nil { + return err + } + + httpResponse.QueryString, err = GetFilteredURLVals(res.Request.URL.Query()) + if err != nil { + return err + } + + _, ok := m.Routes[res.Request.URL.Path] + if !ok { + m.Routes[res.Request.URL.Path] = make(map[string][]HTTPResponse) + m.Routes[res.Request.URL.Path][res.Request.Method] = []HTTPResponse{httpResponse} + } else { + mockResponses, ok := m.Routes[res.Request.URL.Path][res.Request.Method] + if !ok { + m.Routes[res.Request.URL.Path][res.Request.Method] = []HTTPResponse{httpResponse} + } else { + switch res.Request.Method { // Based off method - check add or replace + case http.MethodGet: + for i := range mockResponses { + mockQuery, urlErr := url.ParseQuery(mockResponses[i].QueryString) + if urlErr != nil { + return urlErr + } + + if MatchURLVals(mockQuery, res.Request.URL.Query()) { + mockResponses = append(mockResponses[:i], mockResponses[i+1:]...) // Delete Old + break + } + } + + case http.MethodPost: + for i := range mockResponses { + cType, ok := mockResponses[i].Headers[contentType] + if !ok { + return errors.New("cannot find content type within mock responses") + } + + jCType := strings.Join(cType, "") + var found bool + switch jCType { + case applicationURLEncoded: + respQueryVals, urlErr := url.ParseQuery(body) + if urlErr != nil { + return urlErr + } + + mockRespVals, urlErr := url.ParseQuery(mockResponses[i].BodyParams) + if urlErr != nil { + log.Fatal(urlErr) + } + + if MatchURLVals(respQueryVals, mockRespVals) { + // if found will delete instance and overwrite with new + // data + mockResponses = append(mockResponses[:i], mockResponses[i+1:]...) + found = true + } + + case applicationJSON, textPlain: + reqVals, jErr := DeriveURLValsFromJSONMap([]byte(body)) + if jErr != nil { + return jErr + } + + mockVals, jErr := DeriveURLValsFromJSONMap([]byte(mockResponses[i].BodyParams)) + if jErr != nil { + return jErr + } + + if MatchURLVals(reqVals, mockVals) { + // if found will delete instance and overwrite with new + // data + mockResponses = append(mockResponses[:i], mockResponses[i+1:]...) + found = true + } + + default: + return fmt.Errorf("unhandled content type %s", jCType) + } + if found { + break + } + } + + default: + return fmt.Errorf("unhandled request method %s", res.Request.Method) + } + + m.Routes[res.Request.URL.Path][res.Request.Method] = append(mockResponses, httpResponse) + } + } + + payload, err := json.MarshalIndent(m, "", " ") + if err != nil { + return err + } + + return common.WriteFile(fileout, payload) +} + +// GetFilteredHeader filters excluded http headers for insertion into a mock +// test file +func GetFilteredHeader(res *http.Response) (http.Header, error) { + items, err := GetExcludedItems() + if err != nil { + return res.Header, err + } + + for i := range items.Headers { + if res.Request.Header.Get(items.Headers[i]) != "" { + res.Request.Header.Set(items.Headers[i], "") + } + } + + return res.Request.Header, nil +} + +// GetFilteredURLVals filters excluded url value variables for insertion into a +// mock test file +func GetFilteredURLVals(vals url.Values) (string, error) { + items, err := GetExcludedItems() + if err != nil { + return "", err + } + + for key, val := range vals { + for i := range items.Variables { + if strings.EqualFold(items.Variables[i], val[0]) { + vals.Set(key, "") + } + } + } + return vals.Encode(), nil +} + +// CheckResponsePayload checks to see if there are any response body variables +// that should not be there. +func CheckResponsePayload(data []byte) ([]byte, error) { + items, err := GetExcludedItems() + if err != nil { + return nil, err + } + + var intermediary interface{} + err = json.Unmarshal(data, &intermediary) + if err != nil { + return nil, err + } + + payload, err := CheckJSON(intermediary, &items) + if err != nil { + return nil, err + } + + return json.MarshalIndent(payload, "", " ") +} + +// Reflection consts +const ( + Int64 = "int64" + Float64 = "float64" + Slice = "slice" + String = "string" + Bool = "bool" + Invalid = "invalid" +) + +// CheckJSON recursively parses json data to retract keywords, quite intensive. +func CheckJSON(data interface{}, excluded *Exclusion) (interface{}, error) { + var context map[string]interface{} + if reflect.TypeOf(data).String() == "[]interface {}" { + var sData []interface{} + for i := range data.([]interface{}) { + checkedData, err := CheckJSON(data.([]interface{})[i], excluded) + if err != nil { + return nil, err + } + + sData = append(sData, checkedData) + } + return sData, nil + } + + conv, err := json.Marshal(data) + if err != nil { + return nil, err + } + + err = json.Unmarshal(conv, &context) + if err != nil { + return nil, err + } + + if len(context) == 0 { + // Nil for some reason, should error out before in json.Unmarshal + return nil, nil + } + + for key, val := range context { + switch reflect.ValueOf(val).Kind().String() { + case String: + if IsExcluded(key, excluded.Variables) { + context[key] = "" // Zero val string + } + case Int64: + if IsExcluded(key, excluded.Variables) { + context[key] = 0 // Zero val int + } + case Float64: + if IsExcluded(key, excluded.Variables) { + context[key] = 0.0 // Zero val float + } + case Slice: + slice := val.([]interface{}) + if len(slice) < 1 { + // Empty slice found + context[key] = slice + } else { + if _, ok := slice[0].(map[string]interface{}); ok { + var cleanSlice []interface{} + for i := range slice { + cleanMap, sErr := CheckJSON(slice[i], excluded) + if sErr != nil { + return nil, sErr + } + cleanSlice = append(cleanSlice, cleanMap) + } + context[key] = cleanSlice + } else if IsExcluded(key, excluded.Variables) { + context[key] = nil // Zero val slice + } + } + + case Bool, Invalid: // Skip these bad boys for now + default: + // Recursively check map data + contextValue, err := CheckJSON(val, excluded) + if err != nil { + return nil, err + } + context[key] = contextValue + } + } + + return context, nil +} + +// IsExcluded cross references the key with the excluded variables +func IsExcluded(key string, excludedVars []string) bool { + for i := range excludedVars { + if strings.EqualFold(key, excludedVars[i]) { + return true + } + } + return false +} + +var excludedList Exclusion +var m sync.Mutex +var set bool +var exclusionFile = DefaultDirectory + "exclusion.json" + +var defaultExcludedHeaders = []string{"Key", + "X-Mbx-Apikey", + "Rest-Key", + "Apiauth-Key"} +var defaultExcludedVariables = []string{"bsb", + "user", + "name", + "real_name", + "receiver_name", + "account_number", + "username"} + +// Exclusion defines a list of items to be excluded from the main mock output +// this attempts a catch all approach and needs to be updated per exchange basis +type Exclusion struct { + Headers []string `json:"headers"` + Variables []string `json:"variables"` +} + +// GetExcludedItems checks to see if the variable is in the exclusion list as to +// not display secure items in mock file generator output +func GetExcludedItems() (Exclusion, error) { + m.Lock() + defer m.Unlock() + if !set { + file, err := ioutil.ReadFile(exclusionFile) + if err != nil { + if !strings.Contains(err.Error(), "no such file or directory") { + return excludedList, err + } + + excludedList.Headers = defaultExcludedHeaders + excludedList.Variables = defaultExcludedVariables + + data, mErr := json.MarshalIndent(excludedList, "", " ") + if mErr != nil { + return excludedList, mErr + } + + mErr = ioutil.WriteFile(exclusionFile, data, os.ModePerm) + if mErr != nil { + return excludedList, mErr + } + + } else { + err = json.Unmarshal(file, &excludedList) + if err != nil { + return excludedList, err + } + + if len(excludedList.Headers) == 0 || len(excludedList.Variables) == 0 { + return excludedList, errors.New("exclusion list does not have names") + } + } + + set = true + } + + return excludedList, nil +} diff --git a/exchanges/mock/recording_test.go b/exchanges/mock/recording_test.go new file mode 100644 index 00000000..978c0c2b --- /dev/null +++ b/exchanges/mock/recording_test.go @@ -0,0 +1,186 @@ +package mock + +import ( + "encoding/json" + "net/http" + "net/url" + "strings" + "testing" +) + +func TestGetFilteredHeader(t *testing.T) { + resp := http.Response{} + resp.Request = &http.Request{} + resp.Request.Header = http.Header{} + resp.Request.Header.Set("Key", "RiskyVals") + fMap, err := GetFilteredHeader(&resp) + if err != nil { + t.Error(err) + } + + if fMap.Get("Key") != "" { + t.Error("Test Failed - risky vals where not replaced correctly") + } +} + +func TestGetFilteredURLVals(t *testing.T) { + superSecretData := "Dr Seuss" + shadyVals := url.Values{} + shadyVals.Set("real_name", superSecretData) + cleanVals, err := GetFilteredURLVals(shadyVals) + if err != nil { + t.Error("Test Failed - GetFilteredURLVals error", err) + } + + if strings.Contains(cleanVals, superSecretData) { + t.Error("Test Failed - Super secret data found") + } +} + +func TestCheckResponsePayload(t *testing.T) { + testbody := struct { + SomeJSON string `json:"stuff"` + }{ + SomeJSON: "REAAAAHHHHH", + } + + payload, err := json.Marshal(testbody) + if err != nil { + t.Fatal("Test Failed - json marshal error", err) + } + + data, err := CheckResponsePayload(payload) + if err != nil { + t.Error("Test Failed - CheckBody error", err) + } + + expected := `{ + "stuff": "REAAAAHHHHH" +}` + + if string(data) != expected { + t.Error("unexpected returned data") + } +} + +type TestStructLevel0 struct { + StringVal string `json:"stringVal"` + FloatVal float64 `json:"floatVal"` + IntVal int64 `json:"intVal"` + StructVal TestStructLevel1 `json:"structVal"` +} + +type TestStructLevel1 struct { + OkayVal string `json:"okayVal"` + OkayVal2 float64 `json:"okayVal2"` + BadVal string `json:"user"` + BadVal2 int `json:"bsb"` + OtherData TestStructLevel2 `json:"otherVals"` +} + +type TestStructLevel2 struct { + OkayVal string `json:"okayVal"` + OkayVal2 float64 `json:"okayVal2"` + BadVal float32 `json:"name"` + BadVal2 int32 `json:"real_name"` + OtherData TestStructLevel3 `json:"moreOtherVals"` +} + +type TestStructLevel3 struct { + OkayVal string `json:"okayVal"` + OkayVal2 float64 `json:"okayVal2"` + BadVal int64 `json:"receiver_name"` + BadVal2 string `json:"account_number"` +} + +func TestCheckJSON(t *testing.T) { + level3 := TestStructLevel3{ + OkayVal: "stuff", + OkayVal2: 129219, + BadVal: 1337, + BadVal2: "Super Secret Password", + } + + level2 := TestStructLevel2{ + OkayVal: "stuff", + OkayVal2: 129219, + BadVal: 0.222, + BadVal2: 1337888888, + OtherData: level3, + } + + level1 := TestStructLevel1{ + OkayVal: "stuff", + OkayVal2: 120938, + BadVal: "CritcalBankingStuff", + BadVal2: 1337, + OtherData: level2, + } + + testVal := TestStructLevel0{ + StringVal: "somestringstuff", + FloatVal: 3.14, + IntVal: 1337, + StructVal: level1, + } + + exclusionList, err := GetExcludedItems() + if err != nil { + t.Error("Test Failed - GetExcludedItems error", err) + } + + vals, err := CheckJSON(testVal, &exclusionList) + if err != nil { + t.Error("Test Failed - Check JSON error", err) + } + + payload, err := json.Marshal(vals) + if err != nil { + t.Fatal("Test Failed - json marshal error", err) + } + + newStruct := TestStructLevel0{} + err = json.Unmarshal(payload, &newStruct) + if err != nil { + t.Fatal("Test Failed - Umarshal error", err) + } + + if newStruct.StructVal.BadVal != "" { + t.Error("Value not wiped correctly") + } + + if newStruct.StructVal.BadVal2 != 0 { + t.Error("Value not wiped correctly") + } + + if newStruct.StructVal.OtherData.BadVal != 0 { + t.Error("Value not wiped correctly") + } + + if newStruct.StructVal.OtherData.BadVal2 != 0 { + t.Error("Value not wiped correctly") + } + + if newStruct.StructVal.OtherData.OtherData.BadVal != 0 { + t.Error("Value not wiped correctly") + } + + if newStruct.StructVal.OtherData.OtherData.BadVal2 != "" { + t.Error("Value not wiped correctly") + } +} + +func TestGetExcludedItems(t *testing.T) { + exclusionList, err := GetExcludedItems() + if err != nil { + t.Error("Test Failed - GetExcludedItems error", err) + } + + if len(exclusionList.Headers) == 0 { + t.Error("Test Failed - Header exclusion list not popoulated") + } + + if len(exclusionList.Variables) == 0 { + t.Error("Test Failed - Variable exclusion list not popoulated") + } +} diff --git a/exchanges/mock/server.go b/exchanges/mock/server.go new file mode 100644 index 00000000..c04dbd3a --- /dev/null +++ b/exchanges/mock/server.go @@ -0,0 +1,283 @@ +package mock + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "strconv" + "strings" + + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/crypto" +) + +// DefaultDirectory defines the main mock directory +const DefaultDirectory = "../../testdata/http_mock/" + +const ( + contentType = "Content-Type" + applicationURLEncoded = "application/x-www-form-urlencoded" + applicationJSON = "application/json" + textPlain = "text/plain" +) + +// VCRMock defines the main mock JSON file and attributes +type VCRMock struct { + Routes map[string]map[string][]HTTPResponse `json:"routes"` +} + +// NewVCRServer starts a new VCR server for replaying HTTP requests for testing +// purposes and returns the server connection details +func NewVCRServer(path string) (string, *http.Client, error) { + if path == "" { + return "", nil, errors.New("no path to json mock file found") + } + + var mockFile VCRMock + + contents, err := ioutil.ReadFile(path) + if err != nil { + pathing := strings.Split(path, "/") + dirPathing := pathing[:len(pathing)-1] + dir := strings.Join(dirPathing, "/") + err = common.CreateDir(dir) + if err != nil { + return "", nil, err + } + + data, jErr := json.MarshalIndent(mockFile, "", " ") + if jErr != nil { + return "", nil, jErr + } + + err = common.WriteFile(path, data) + if err != nil { + return "", nil, err + } + contents = data + } + + if !json.Valid(contents) { + return "", + nil, + fmt.Errorf("contents of file %s are not valid JSON", path) + } + + // Get mocking data for the specific service + err = json.Unmarshal(contents, &mockFile) + if err != nil { + return "", nil, err + } + + newMux := http.NewServeMux() + // Range over routes and assign responses to explicit paths and http + // methods + if len(mockFile.Routes) != 0 { + for pattern, mockResponses := range mockFile.Routes { + RegisterHandler(pattern, mockResponses, newMux) + } + } else { + newMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + err := json.NewEncoder(w).Encode("There is no mock data available in file please record a new HTTP response. Please follow README.md in the mock package.") + if err != nil { + panic(err) + } + }) + } + tlsServer := httptest.NewTLSServer(newMux) + + return tlsServer.URL, tlsServer.Client(), nil +} + +// RegisterHandler registers a generalised mock response logic for specific +// routes +func RegisterHandler(pattern string, mock map[string][]HTTPResponse, mux *http.ServeMux) { + mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { + httpResponses, ok := mock[r.Method] + if !ok { + log.Fatalf("Mock Test Failure - Method %s not present in mock file", + r.Method) + } + + switch r.Method { + case http.MethodGet: + vals, err := url.ParseRequestURI(r.RequestURI) + if err != nil { + log.Fatal("Mock Test Failure - Parse request URI error", err) + } + + payload, err := MatchAndGetResponse(httpResponses, vals.Query(), true) + if err != nil { + log.Fatalf("Mock Test Failure - MatchAndGetResponse error %s for %s", + err, r.RequestURI) + } + + MessageWriteJSON(w, http.StatusOK, payload) + return + + case http.MethodPost: + switch r.Header.Get(contentType) { + case applicationURLEncoded: + readBody, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Fatal("Mock Test Failure - ReadAll error", err) + } + + vals, err := url.ParseQuery(string(readBody)) + if err != nil { + log.Fatal("Mock Test Failure - parse query error", err) + } + + payload, err := MatchAndGetResponse(httpResponses, vals, false) + if err != nil { + log.Fatal("Mock Test Failure - MatchAndGetResponse error ", err) + } + + MessageWriteJSON(w, http.StatusOK, payload) + return + + case "": + payload, err := MatchAndGetResponse(httpResponses, r.URL.Query(), true) + if err != nil { + log.Fatal("Mock Test Failure - MatchAndGetResponse error ", err) + } + + MessageWriteJSON(w, http.StatusOK, payload) + return + + case applicationJSON: + readBody, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Fatalf("Mock Test Failure - %v", err) + } + + reqVals, err := DeriveURLValsFromJSONMap(readBody) + if err != nil { + log.Fatalf("Mock Test Failure - %v", err) + } + + payload, err := MatchAndGetResponse(httpResponses, reqVals, false) + if err != nil { + log.Fatal("Mock Test Failure - MatchAndGetResponse error ", err) + } + + MessageWriteJSON(w, http.StatusOK, payload) + return + + case textPlain: + headerData, ok := r.Header["X-Gemini-Payload"] + if !ok { + log.Fatal("Mock Test Failure - Cannot find header in request") + } + + base64data := strings.Join(headerData, "") + + jsonThings, err := crypto.Base64Decode(base64data) + if err != nil { + log.Fatal("Mock Test Failure - ", err) + } + + reqVals, err := DeriveURLValsFromJSONMap(jsonThings) + if err != nil { + log.Fatalf("Mock Test Failure - %v", err) + } + + payload, err := MatchAndGetResponse(httpResponses, reqVals, false) + if err != nil { + log.Fatal("Mock Test Failure - MatchAndGetResponse error ", err) + } + + MessageWriteJSON(w, http.StatusOK, payload) + return + + default: + log.Fatalf("Mock Test Failure - Unhandled content type %v", + r.Header.Get(contentType)) + } + + case http.MethodDelete: + payload, err := MatchAndGetResponse(httpResponses, r.URL.Query(), true) + if err != nil { + log.Println(r.URL.Query()) + log.Fatal("Mock Test Failure - MatchAndGetResponse error ", err) + } + + MessageWriteJSON(w, http.StatusOK, payload) + return + + default: + log.Fatal("Mock Test Failure - Unhandled HTTP method:", + r.Header.Get(contentType)) + } + }) +} + +// MessageWriteJSON writes JSON to a connection +func MessageWriteJSON(w http.ResponseWriter, status int, data interface{}) { + w.Header().Set(contentType, applicationJSON) + w.WriteHeader(status) + if data != nil { + err := json.NewEncoder(w).Encode(data) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + log.Fatal("Mock Test Failure - JSON encode error", err) + } + } +} + +// MatchAndGetResponse matches incoming request values with mockdata response +// values and returns the payload +func MatchAndGetResponse(mockData []HTTPResponse, requestVals url.Values, isQueryData bool) (json.RawMessage, error) { + for i := range mockData { + var data string + if isQueryData { + data = mockData[i].QueryString + } else { + data = mockData[i].BodyParams + } + + var mockVals = url.Values{} + var err error + if json.Valid([]byte(data)) { + something := make(map[string]interface{}) + err = json.Unmarshal([]byte(data), &something) + if err != nil { + return nil, err + } + + for k, v := range something { + switch val := v.(type) { + case string: + mockVals.Add(k, val) + case bool: + mockVals.Add(k, strconv.FormatBool(val)) + case float64: + mockVals.Add(k, strconv.FormatFloat(val, 'f', -1, 64)) + case map[string]interface{}, []interface{}, nil: + mockVals.Add(k, fmt.Sprintf("%v", val)) + default: + log.Println(reflect.TypeOf(val)) + log.Fatal("unhandled type please add as needed") + } + } + } else { + mockVals, err = url.ParseQuery(data) + if err != nil { + return nil, err + } + } + + if MatchURLVals(mockVals, requestVals) { + return mockData[i].Data, nil + } + } + return nil, errors.New("no data could be matched") +} diff --git a/exchanges/mock/server_test.go b/exchanges/mock/server_test.go new file mode 100644 index 00000000..370d1a31 --- /dev/null +++ b/exchanges/mock/server_test.go @@ -0,0 +1,117 @@ +package mock + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "os" + "strings" + "testing" + + "github.com/thrasher-corp/gocryptotrader/common" +) + +type responsePayload struct { + Price float64 `json:"price"` + Amount float64 `json:"amount"` + Currency string `json:"currency"` +} + +const queryString = "currency=btc&command=getprice" +const testFile = "test.json" + +func TestNewVCRServer(t *testing.T) { + _, _, err := NewVCRServer("") + if err == nil { + t.Error("Test Failed - NewVCRServer error cannot be nil") + } + + // Set up mock data + test1 := VCRMock{} + test1.Routes = make(map[string]map[string][]HTTPResponse) + test1.Routes["/test"] = make(map[string][]HTTPResponse) + + rp, err := json.Marshal(responsePayload{Price: 8000.0, + Amount: 1, + Currency: "bitcoin"}) + if err != nil { + t.Fatal("Test Failed - marshal error", err) + } + + testValue := HTTPResponse{Data: rp, QueryString: queryString, BodyParams: queryString} + test1.Routes["/test"][http.MethodGet] = []HTTPResponse{testValue} + + payload, err := json.Marshal(test1) + if err != nil { + t.Fatal("Test Failed - marshal error", err) + } + + err = ioutil.WriteFile(testFile, payload, os.ModePerm) + if err != nil { + t.Fatal("Test Failed - marshal error", err) + } + + deets, client, err := NewVCRServer(testFile) + if err != nil { + t.Error("Test Failed - NewVCRServer error", err) + } + + common.HTTPClient = client // Set common package global HTTP Client + + _, err = common.SendHTTPRequest(http.MethodGet, + "http://localhost:300/somethingElse?"+queryString, + nil, + bytes.NewBufferString("")) + if err == nil { + t.Error("Test Failed - Sending http request expected an error") + } + + // Expected good outcome + r, err := common.SendHTTPRequest(http.MethodGet, + deets, + nil, + bytes.NewBufferString("")) + if err != nil { + t.Error("Test Failed - Sending http request error", err) + } + + if !strings.Contains(r, "404 page not found") { + t.Error("Test Failed - Was not expecting any value returned:", r) + } + + r, err = common.SendHTTPRequest(http.MethodGet, + deets+"/test?"+queryString, + nil, + bytes.NewBufferString("")) + if err != nil { + t.Error("Test Failed - Sending http request error", err) + } + + var res responsePayload + err = json.Unmarshal([]byte(r), &res) + if err != nil { + t.Error("Test Failed - unmarshal error", err) + } + + if res.Price != 8000 { + t.Error("Test Failed - response error expected 8000 but received:", + res.Price) + } + + if res.Amount != 1 { + t.Error("Test Failed - response error expected 1 but received:", + res.Amount) + } + + if res.Currency != "bitcoin" { + t.Error("Test Failed - response error expected \"bitcoin\" but received:", + res.Currency) + } + + // clean up test.json file + err = os.Remove(testFile) + if err != nil { + t.Fatal("Test Failed - Remove error", err) + } +} diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go index ba1e32a5..478139bd 100644 --- a/exchanges/okgroup/okgroup.go +++ b/exchanges/okgroup/okgroup.go @@ -576,7 +576,15 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d errCap := errCapFormat{} errCap.Result = true - err = o.SendPayload(strings.ToUpper(httpMethod), path, headers, bytes.NewBuffer(payload), &intermediary, authenticated, false, o.Verbose, o.HTTPDebugging) + err = o.SendPayload(strings.ToUpper(httpMethod), + path, headers, + bytes.NewBuffer(payload), + &intermediary, + authenticated, + false, + o.Verbose, + o.HTTPDebugging, + o.HTTPRecording) if err != nil { return err } diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 812ea383..72e7639b 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -224,8 +224,8 @@ func (p *Poloniex) GetLoanOrders(currency string) (LoanOrders, error) { // GetBalances returns balances for your account. func (p *Poloniex) GetBalances() (Balance, error) { var result interface{} - err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexBalances, url.Values{}, &result) + err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexBalances, url.Values{}, &result) if err != nil { return Balance{}, err } @@ -244,8 +244,8 @@ func (p *Poloniex) GetBalances() (Balance, error) { // GetCompleteBalances returns complete balances from your account. func (p *Poloniex) GetCompleteBalances() (CompleteBalances, error) { var result interface{} - err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexBalancesComplete, url.Values{}, &result) + err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexBalancesComplete, url.Values{}, &result) if err != nil { return CompleteBalances{}, err } @@ -270,14 +270,18 @@ func (p *Poloniex) GetCompleteBalances() (CompleteBalances, error) { func (p *Poloniex) GetDepositAddresses() (DepositAddresses, error) { var result interface{} addresses := DepositAddresses{} - err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexDepositAddresses, url.Values{}, &result) + err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexDepositAddresses, url.Values{}, &result) if err != nil { return addresses, err } addresses.Addresses = make(map[string]string) - data := result.(map[string]interface{}) + data, ok := result.(map[string]interface{}) + if !ok { + return addresses, errors.New("return val not map[string]interface{}") + } + for x, y := range data { addresses.Addresses[x] = y.(string) } @@ -297,7 +301,6 @@ func (p *Poloniex) GenerateNewAddress(currency string) (string, error) { values.Set("currency", currency) err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexGenerateNewAddress, values, &resp) - if err != nil { return "", err } @@ -498,7 +501,6 @@ func (p *Poloniex) Withdraw(currency, address string, amount float64) (bool, err values.Set("address", address) err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexWithdraw, values, &result) - if err != nil { return false, err } @@ -525,7 +527,6 @@ func (p *Poloniex) GetTradableBalances() (map[string]map[string]float64, error) result := Response{} err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexTradableBalances, url.Values{}, &result.Data) - if err != nil { return nil, err } @@ -553,7 +554,6 @@ func (p *Poloniex) TransferBalance(currency, from, to string, amount float64) (b values.Set("toAccount", to) err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexTransferBalance, values, &result) - if err != nil { return false, err } @@ -619,7 +619,6 @@ func (p *Poloniex) CloseMarginPosition(currency string) (bool, error) { result := GenericResponse{} err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexMarginPositionClose, values, &result) - if err != nil { return false, err } @@ -655,7 +654,6 @@ func (p *Poloniex) CreateLoanOffer(currency string, amount, rate float64, durati result := Response{} err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexCreateLoanOffer, values, &result) - if err != nil { return 0, err } @@ -674,7 +672,6 @@ func (p *Poloniex) CancelLoanOffer(orderNumber int64) (bool, error) { values.Set("orderID", strconv.FormatInt(orderNumber, 10)) err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexCancelLoanOffer, values, &result) - if err != nil { return false, err } @@ -694,7 +691,6 @@ func (p *Poloniex) GetOpenLoanOffers() (map[string][]LoanOffer, error) { result := Response{} err := p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexOpenLoanOffers, url.Values{}, &result.Data) - if err != nil { return nil, err } @@ -725,15 +721,10 @@ func (p *Poloniex) GetLendingHistory(start, end string) ([]LendingHistory, error } var resp []LendingHistory - err := p.SendAuthenticatedHTTPRequest(http.MethodPost, + return resp, p.SendAuthenticatedHTTPRequest(http.MethodPost, poloniexLendingHistory, vals, &resp) - - if err != nil { - return nil, err - } - return resp, nil } // ToggleAutoRenew allows for the autorenew of a contract @@ -746,7 +737,6 @@ func (p *Poloniex) ToggleAutoRenew(orderNumber int64) (bool, error) { poloniexAutoRenew, values, &result) - if err != nil { return false, err } @@ -768,7 +758,8 @@ func (p *Poloniex) SendHTTPRequest(path string, result interface{}) error { false, false, p.Verbose, - p.HTTPDebugging) + p.HTTPDebugging, + p.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request @@ -777,13 +768,11 @@ func (p *Poloniex) SendAuthenticatedHTTPRequest(method, endpoint string, values return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, p.Name) } + headers := make(map[string]string) headers["Content-Type"] = "application/x-www-form-urlencoded" headers["Key"] = p.API.Credentials.Key - - n := p.Requester.GetNonce(true).String() - - values.Set("nonce", n) + values.Set("nonce", p.Requester.GetNonce(true).String()) values.Set("command", endpoint) hmac := crypto.GetHMAC(crypto.HashSHA512, @@ -802,7 +791,8 @@ func (p *Poloniex) SendAuthenticatedHTTPRequest(method, endpoint string, values true, true, p.Verbose, - p.HTTPDebugging) + p.HTTPDebugging, + p.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/poloniex/poloniex_live_test.go b/exchanges/poloniex/poloniex_live_test.go new file mode 100644 index 00000000..915f708c --- /dev/null +++ b/exchanges/poloniex/poloniex_live_test.go @@ -0,0 +1,32 @@ +//+build mock_test_off + +// This will build if build tag mock_test_off is parsed and will do live testing +// using all tests in (exchange)_test.go +package poloniex + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +var mockTests = false + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + poloniexConfig, err := cfg.GetExchangeConfig("Poloniex") + if err != nil { + log.Fatal("Test Failed - Poloniex Setup() init error", err) + } + poloniexConfig.API.AuthenticatedSupport = true + poloniexConfig.API.Credentials.Key = apiKey + poloniexConfig.API.Credentials.Secret = apiSecret + p.SetDefaults() + p.Setup(poloniexConfig) + log.Printf(sharedtestvalues.LiveTesting, p.GetName(), p.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/poloniex/poloniex_mock_test.go b/exchanges/poloniex/poloniex_mock_test.go new file mode 100644 index 00000000..46c77719 --- /dev/null +++ b/exchanges/poloniex/poloniex_mock_test.go @@ -0,0 +1,45 @@ +//+build !mock_test_off + +// This will build if build tag mock_test_off is not parsed and will try to mock +// all tests in _test.go +package poloniex + +import ( + "log" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/mock" + "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" +) + +const mockfile = "../../testdata/http_mock/poloniex/poloniex.json" + +var mockTests = true + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + poloniexConfig, err := cfg.GetExchangeConfig("Poloniex") + if err != nil { + log.Fatal("Test Failed - Poloniex Setup() init error", err) + } + p.SkipAuthCheck = true + poloniexConfig.API.AuthenticatedSupport = true + poloniexConfig.API.Credentials.Key = apiKey + poloniexConfig.API.Credentials.Secret = apiSecret + p.SetDefaults() + p.Setup(poloniexConfig) + + serverDetails, newClient, err := mock.NewVCRServer(mockfile) + if err != nil { + log.Fatalf("Test Failed - Mock server error %s", err) + } + + p.HTTPClient = newClient + p.API.Endpoints.URL = serverDetails + + log.Printf(sharedtestvalues.MockTesting, p.GetName(), p.API.Endpoints.URL) + os.Exit(m.Run()) +} diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index cd0768d0..bed54367 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -7,15 +7,12 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) -var p Poloniex - // Please supply your own APIKEYS here for due diligence testing const ( apiKey = "" @@ -23,31 +20,17 @@ const ( canManipulateRealOrders = false ) -var isSetup bool +var p Poloniex -func TestSetup(t *testing.T) { - if !isSetup { - cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") - poloniexConfig, err := cfg.GetExchangeConfig("Poloniex") - if err != nil { - t.Error("Test Failed - Poloniex Setup() init error") - } - poloniexConfig.API.AuthenticatedSupport = true - poloniexConfig.API.AuthenticatedWebsocketSupport = true - poloniexConfig.API.Credentials.Key = apiKey - poloniexConfig.API.Credentials.Secret = apiSecret - p.SetDefaults() - p.Setup(poloniexConfig) - isSetup = true - } +func areTestAPIKeysSet() bool { + return p.ValidateAPICredentials() } func TestGetTicker(t *testing.T) { t.Parallel() _, err := p.GetTicker() if err != nil { - t.Error("Test faild - Poloniex GetTicker() error") + t.Error("Test Failed - Poloniex GetTicker() error", err) } } @@ -115,37 +98,43 @@ func setFeeBuilder() *exchange.FeeBuilder { // TestGetFeeByTypeOfflineTradeFee logic test func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { t.Parallel() + var feeBuilder = setFeeBuilder() p.GetFeeByType(feeBuilder) if apiKey == "" || apiSecret == "" { if feeBuilder.FeeType != exchange.OfflineTradeFee { - t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) + t.Errorf("Expected %v, received %v", + exchange.OfflineTradeFee, + feeBuilder.FeeType) } } else { if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee { - t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) + t.Errorf("Expected %v, received %v", + exchange.CryptocurrencyTradeFee, + feeBuilder.FeeType) } } } func TestGetFee(t *testing.T) { t.Parallel() - TestSetup(t) var feeBuilder = setFeeBuilder() - if areTestAPIKeysSet() { + if areTestAPIKeysSet() || mockTests { // CryptocurrencyTradeFee Basic - if resp, err := p.GetFee(feeBuilder); resp != float64(0.002) || err != nil { + if resp, err := p.GetFee(feeBuilder); resp != float64(0.0025) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0.0025), resp) } // CryptocurrencyTradeFee High quantity feeBuilder = setFeeBuilder() feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 - if resp, err := p.GetFee(feeBuilder); resp != float64(2000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2000), resp) + if resp, err := p.GetFee(feeBuilder); resp != float64(2500) || err != nil { + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(2500), resp) t.Error(err) } @@ -153,7 +142,8 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := p.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), resp) t.Error(err) } } @@ -161,7 +151,8 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := p.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0.001), resp) t.Error(err) } @@ -170,7 +161,8 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := p.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), resp) t.Error(err) } @@ -178,7 +170,8 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := p.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), resp) t.Error(err) } @@ -186,7 +179,8 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := p.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), resp) t.Error(err) } @@ -195,66 +189,67 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := p.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + float64(0), resp) t.Error(err) } } func TestFormatWithdrawPermissions(t *testing.T) { t.Parallel() - TestSetup(t) - expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.NoFiatWithdrawalsText + expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + + " & " + + exchange.NoFiatWithdrawalsText withdrawPermissions := p.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { - t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) + t.Errorf("Expected: %s, Received: %s", + expectedResult, + withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { t.Parallel() - TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, } _, err := p.GetActiveOrders(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - GetActiveOrders() error", err) + case !areTestAPIKeysSet() && !mockTests && err == nil: + t.Error("Test Failed - Expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock GetActiveOrders() err", err) } } func TestGetOrderHistory(t *testing.T) { t.Parallel() - TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ OrderType: exchange.AnyOrderType, } _, err := p.GetOrderHistory(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { + switch { + case areTestAPIKeysSet() && err != nil: t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Errorf("Could not mock get order history: %s", err) } } // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- -func areTestAPIKeysSet() bool { - return p.ValidateAPICredentials() -} func TestSubmitOrder(t *testing.T) { t.Parallel() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -265,51 +260,50 @@ func TestSubmitOrder(t *testing.T) { Quote: currency.LTC, }, OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, - Price: 1, - Amount: 1, - ClientID: "meowOrder", + OrderType: exchange.MarketOrderType, + Price: 10, + Amount: 10000000, + ClientID: "hi", } + response, err := p.SubmitOrder(orderSubmission) - if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { + switch { + case areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced): t.Errorf("Order failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { + case !areTestAPIKeysSet() && !mockTests && err == nil: t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock SubmitOrder() err", err) } } func TestCancelExchangeOrder(t *testing.T) { t.Parallel() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", - CurrencyPair: currencyPair, + CurrencyPair: currency.NewPair(currency.LTC, currency.BTC), } err := p.CancelOrder(orderCancellation) - - if !areTestAPIKeysSet() && err == nil { + switch { + case !areTestAPIKeysSet() && !mockTests && err == nil: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil: t.Errorf("Could not cancel orders: %v", err) + case mockTests && err != nil: + t.Error("Test Failed - Mock CancelExchangeOrder() err", err) } } func TestCancelAllExchangeOrders(t *testing.T) { t.Parallel() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -323,14 +317,14 @@ func TestCancelAllExchangeOrders(t *testing.T) { } resp, err := p.CancelAllOrders(orderCancellation) - - if !areTestAPIKeysSet() && err == nil { + switch { + case !areTestAPIKeysSet() && !mockTests && err == nil: t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + case areTestAPIKeysSet() && err != nil: t.Errorf("Could not cancel orders: %v", err) + case mockTests && err != nil: + t.Error("Test Failed - Mock CancelAllExchangeOrders() err", err) } - if len(resp.OrderStatus) > 0 { t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) } @@ -338,87 +332,90 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestModifyOrder(t *testing.T) { t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } + _, err := p.ModifyOrder(&exchange.ModifyOrder{OrderID: "1337", Price: 1337}) - if err == nil { - t.Error("Test Failed - ModifyOrder() error") + switch { + case areTestAPIKeysSet() && err != nil && mockTests: + t.Error("Test Failed - ModifyOrder() error", err) + case !areTestAPIKeysSet() && !mockTests && err == nil: + t.Error("Test Failed - ModifyOrder() error cannot be nil") + case mockTests && err != nil: + t.Error("Test Failed - Mock ModifyOrder() err", err) } } func TestWithdraw(t *testing.T) { t.Parallel() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ - Amount: -1, - Currency: currency.BTC, + Amount: 0, + Currency: currency.LTC, Description: "WITHDRAW IT ALL", }, Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } _, err := p.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { + switch { + case areTestAPIKeysSet() && err != nil: t.Errorf("Withdraw failed to be placed: %v", err) + case !areTestAPIKeysSet() && !mockTests && err == nil: + t.Error("Expecting an error when no keys are set") + case mockTests && err != nil: + t.Error("Test Failed - Mock Withdraw() err", err) } } func TestWithdrawFiat(t *testing.T) { t.Parallel() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.FiatWithdrawRequest{} + var withdrawFiatRequest exchange.FiatWithdrawRequest _, err := p.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, err) } } func TestWithdrawInternationalBank(t *testing.T) { t.Parallel() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { + if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var withdrawFiatRequest = exchange.FiatWithdrawRequest{} + var withdrawFiatRequest exchange.FiatWithdrawRequest _, err := p.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, err) } } func TestGetDepositAddress(t *testing.T) { t.Parallel() - TestSetup(t) - - if areTestAPIKeysSet() { - _, err := p.GetDepositAddress(currency.DASH, "") - if err != nil { - t.Error("Test Failed - GetDepositAddress()", err) - } - } else { - _, err := p.GetDepositAddress(currency.DASH, "") - if err == nil { - t.Error("Test Failed - GetDepositAddress()") - } + _, err := p.GetDepositAddress(currency.DASH, "") + switch { + case areTestAPIKeysSet() && err != nil: + t.Error("Test Failed - GetDepositAddress()", err) + case !areTestAPIKeysSet() && !mockTests && err == nil: + t.Error("Test Failed - GetDepositAddress() cannot be nil") + case mockTests && err != nil: + t.Error("Test Failed - Mock GetDepositAddress() err", err) } } func TestWsHandleAccountData(t *testing.T) { t.Parallel() - TestSetup(t) p.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() jsons := []string{ `[["n",225,807230187,0,"1000.00000000","0.10000000","2018-11-07 16:42:42"],["b",267,"e","-0.10000000"]]`, @@ -438,7 +435,7 @@ func TestWsHandleAccountData(t *testing.T) { // TestWsAuth dials websocket, sends login request. // Will receive a message only on failure func TestWsAuth(t *testing.T) { - TestSetup(t) + t.Parallel() if !p.Websocket.IsEnabled() && !p.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index d28a9b51..3bcf3d64 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -457,7 +457,7 @@ func (p *Poloniex) GetWebsocket() (*wshandler.Websocket, error) { // GetFeeByType returns an estimate of fee based on type of transaction func (p *Poloniex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - if !p.AllowAuthenticatedRequest() && // Todo check connection status + if (!p.AllowAuthenticatedRequest() || p.SkipAuthCheck) && // Todo check connection status feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } diff --git a/exchanges/request/request.go b/exchanges/request/request.go index 7e4612fb..61290a93 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -15,6 +15,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/exchanges/mock" "github.com/thrasher-corp/gocryptotrader/exchanges/nonce" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -81,6 +82,7 @@ type Job struct { AuthRequest bool Verbose bool HTTPDebugging bool + Record bool } // NewRateLimit creates a new RateLimit @@ -279,14 +281,21 @@ func (r *Requester) checkRequest(method, path string, body io.Reader, headers ma } // DoRequest performs a HTTP/HTTPS request with the supplied params -func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, result interface{}, authRequest, verbose, httpDebug bool) error { +func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, result interface{}, authRequest, verbose, httpDebug, httpRecord bool) error { if verbose { log.Debugf(log.Global, - "%s exchange request path: %s requires rate limiter: %v", r.Name, path, r.RequiresRateLimiter()) + "%s exchange request path: %s requires rate limiter: %v", + r.Name, + path, + r.RequiresRateLimiter()) + for k, d := range req.Header { log.Debugf(log.Global, "%s exchange request header [%s]: %s", r.Name, k, d) } - log.Debugln(log.Global, body) + log.Debugf(log.Global, + "%s exchange request type: %s", r.Name, req.Method) + log.Debugf(log.Global, + "%s exchange request body: %v", r.Name, body) } var timeoutError error @@ -344,6 +353,14 @@ func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, re return err } + if httpRecord { + // This dumps http responses for future mocking implementations + err = mock.HTTPRecord(resp, r.Name, contents) + if err != nil { + return fmt.Errorf("mock recording failure %s", err) + } + } + if resp.StatusCode != 200 && resp.StatusCode != 201 && resp.StatusCode != 202 { err = fmt.Errorf("unsuccessful HTTP status code: %d", resp.StatusCode) if verbose { @@ -387,7 +404,7 @@ func (r *Requester) worker() { if !r.IsRateLimited(x.AuthRequest) { r.IncrementRequests(x.AuthRequest) - err := r.DoRequest(x.Request, x.Path, x.Body, x.Result, x.AuthRequest, x.Verbose, x.HTTPDebugging) + err := r.DoRequest(x.Request, x.Path, x.Body, x.Result, x.AuthRequest, x.Verbose, x.HTTPDebugging, x.Record) x.JobResult <- &JobResult{ Error: err, Result: x.Result, @@ -411,7 +428,7 @@ func (r *Requester) worker() { log.Debugf(log.ExchangeSys, "%s request. No longer rate limited! Doing request", r.Name) } - err := r.DoRequest(x.Request, x.Path, x.Body, x.Result, x.AuthRequest, x.Verbose, x.HTTPDebugging) + err := r.DoRequest(x.Request, x.Path, x.Body, x.Result, x.AuthRequest, x.Verbose, x.HTTPDebugging, x.Record) x.JobResult <- &JobResult{ Error: err, Result: x.Result, @@ -424,7 +441,7 @@ func (r *Requester) worker() { } // SendPayload handles sending HTTP/HTTPS requests -func (r *Requester) SendPayload(method, path string, headers map[string]string, body io.Reader, result interface{}, authRequest, nonceEnabled, verbose, httpDebugging bool) error { +func (r *Requester) SendPayload(method, path string, headers map[string]string, body io.Reader, result interface{}, authRequest, nonceEnabled, verbose, httpDebugging, record bool) error { if !nonceEnabled { r.lock() } @@ -462,7 +479,7 @@ func (r *Requester) SendPayload(method, path string, headers map[string]string, if !r.RequiresRateLimiter() { r.unlock() - return r.DoRequest(req, path, body, result, authRequest, verbose, httpDebugging) + return r.DoRequest(req, path, body, result, authRequest, verbose, httpDebugging, record) } if len(r.Jobs) == MaxRequestJobs { @@ -491,6 +508,7 @@ func (r *Requester) SendPayload(method, path string, headers map[string]string, AuthRequest: authRequest, Verbose: verbose, HTTPDebugging: httpDebugging, + Record: record, } if verbose { diff --git a/exchanges/request/request_test.go b/exchanges/request/request_test.go index 6517965a..aa5c36df 100644 --- a/exchanges/request/request_test.go +++ b/exchanges/request/request_test.go @@ -200,7 +200,7 @@ func TestCheckRequest(t *testing.T) { func TestDoRequest(t *testing.T) { var test = new(Requester) - err := test.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false) + err := test.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false, false) if err == nil { t.Fatal("not iniitalised") } @@ -211,17 +211,17 @@ func TestDoRequest(t *testing.T) { } r.Name = "bitfinex" - err = r.SendPayload("BLAH", "https://www.google.com", nil, nil, nil, false, false, true, false) + err = r.SendPayload("BLAH", "https://www.google.com", nil, nil, nil, false, false, true, false, false) if err == nil { t.Fatal("unexpected values") } - err = r.SendPayload(http.MethodGet, "", nil, nil, nil, false, false, true, false) + err = r.SendPayload(http.MethodGet, "", nil, nil, nil, false, false, true, false, false) if err == nil { t.Fatal("unexpected values") } - err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false) + err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false, false) if err != nil { t.Fatal("unexpected values") } @@ -233,7 +233,7 @@ func TestDoRequest(t *testing.T) { r.SetRateLimit(false, time.Second, 0) r.SetRateLimit(true, time.Second, 0) - err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false) + err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false, false) if err != nil { t.Fatal("unexpected values") } @@ -250,7 +250,7 @@ func TestDoRequest(t *testing.T) { t.Fatal("unexepcted values") } - err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false) + err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false, false) if err != nil { t.Fatal("unexpected values") } @@ -261,27 +261,27 @@ func TestDoRequest(t *testing.T) { t.Fatal("unexepcted values") } - err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, true, false, true, false) + err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, true, false, true, false, false) if err != nil { t.Fatal("unexpected values") } var result interface{} - err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, result, false, false, true, false) + err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, result, false, false, true, false, false) if err != nil { t.Fatal(err) } headers := make(map[string]string) headers["content-type"] = "content/text" - err = r.SendPayload(http.MethodPost, "https://bitfinex.com", headers, nil, result, false, false, true, false) + err = r.SendPayload(http.MethodPost, "https://bitfinex.com", headers, nil, result, false, false, true, false, false) if err != nil { t.Fatal(err) } r.StartCycle() r.UnauthLimit.SetRequests(100) - err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, result, false, false, false, false) + err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, result, false, false, false, false, false) if err != nil { t.Fatal("unexpected values") } @@ -297,7 +297,7 @@ func TestDoRequest(t *testing.T) { } r.HTTPClient.Timeout = 1 * time.Second - err = r.SendPayload(http.MethodPost, "https://httpstat.us/200?sleep=20000", nil, nil, nil, false, false, true, false) + err = r.SendPayload(http.MethodPost, "https://httpstat.us/200?sleep=20000", nil, nil, nil, false, false, true, false, false) if err == nil { t.Fatal(err) } @@ -327,6 +327,6 @@ func BenchmarkRequestLockMech(b *testing.B) { var r = new(Requester) var meep interface{} for n := 0; n < b.N; n++ { - r.SendPayload(http.MethodGet, "127.0.0.1", nil, nil, &meep, false, false, false, false) + r.SendPayload(http.MethodGet, "127.0.0.1", nil, nil, &meep, false, false, false, false, false) } } diff --git a/exchanges/sharedtestvalues/sharedtestvalues.go b/exchanges/sharedtestvalues/sharedtestvalues.go index 3e9c92c8..22732338 100644 --- a/exchanges/sharedtestvalues/sharedtestvalues.go +++ b/exchanges/sharedtestvalues/sharedtestvalues.go @@ -13,6 +13,9 @@ const ( // WebsocketChannelOverrideCapacity used in websocket testing // Defines channel capacity as defaults size can block tests WebsocketChannelOverrideCapacity = 20 + + MockTesting = "Mock testing framework in use for %s exchange @ %s on REST endpoints only" + LiveTesting = "Mock testing bypassed; live testing of REST endpoints in use for %s exchange @ %s" ) // GetWebsocketInterfaceChannelOverride returns a new interface based channel diff --git a/exchanges/support.go b/exchanges/support.go index 10912d1f..010fa626 100644 --- a/exchanges/support.go +++ b/exchanges/support.go @@ -23,6 +23,7 @@ var Exchanges = []string{ "itbit", "kraken", "lakebtc", + "lbank", "localbitcoins", "okcoin international", "okex", diff --git a/exchanges/yobit/yobit.go b/exchanges/yobit/yobit.go index 16008c76..392736f9 100644 --- a/exchanges/yobit/yobit.go +++ b/exchanges/yobit/yobit.go @@ -264,7 +264,8 @@ func (y *Yobit) SendHTTPRequest(path string, result interface{}) error { false, false, y.Verbose, - y.HTTPDebugging) + y.HTTPDebugging, + y.HTTPRecording) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to Yobit @@ -305,7 +306,8 @@ func (y *Yobit) SendAuthenticatedHTTPRequest(path string, params url.Values, res true, true, y.Verbose, - y.HTTPDebugging) + y.HTTPDebugging, + y.HTTPRecording) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index 70c7b6e6..63fe8428 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -286,7 +286,16 @@ func (z *ZB) GetCryptoAddress(currency currency.Code) (UserAddress, error) { // SendHTTPRequest sends an unauthenticated HTTP request func (z *ZB) SendHTTPRequest(path string, result interface{}) error { - return z.SendPayload(http.MethodGet, path, nil, nil, result, false, false, z.Verbose, z.HTTPDebugging) + return z.SendPayload(http.MethodGet, + path, + nil, + nil, + result, + false, + false, + z.Verbose, + z.HTTPDebugging, + z.HTTPRecording) } // SendAuthenticatedHTTPRequest sends authenticated requests to the zb API @@ -324,7 +333,8 @@ func (z *ZB) SendAuthenticatedHTTPRequest(httpMethod string, params url.Values, true, false, z.Verbose, - z.HTTPDebugging) + z.HTTPDebugging, + z.HTTPRecording) if err != nil { return err } diff --git a/testdata/README.md b/testdata/README.md index 64ded58c..f178096f 100644 --- a/testdata/README.md +++ b/testdata/README.md @@ -42,3 +42,4 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** + diff --git a/testdata/configtest.json b/testdata/configtest.json index 340f280b..d455fad6 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -443,6 +443,49 @@ } ] }, + { + "name": "LBank", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "fbc_usdt,hds_usdt,galt_usdt,dxn_usdt,iog_usdt,ioex_usdt,vollar_usdt,oath_usdt,bloc_usdt,btc_lbcn,eth_lbcn,usdt_lbcn,btc_usdt,eth_usdt,eth_btc,abbc_btc,bzky_eth,onot_eth,kisc_eth,bxa_usdt,atp_usdt,mat_usdt,sky_btc,sky_lbcn,rnt_usdt,vena_usdt,grin_usdt,ida_usdt,pnt_usdt,bsv_btc,bsv_usdt,opx_usdt,tena_eth,seer_lbcn,vet_lbcn,vtho_btc,vnx_lbcn,vnx_btc,amo_eth,ubex_btc,eos_btc,ubex_usdt,tns_lbcn,tns_btc,ali_eth,sdc_eth,sait_eth,artcn_usdt,dax_btc,dax_eth,dali_usdt,vet_usdt,ten_usdt,bch_usdt,neo_usdt,qtum_usdt,zec_usdt,vet_btc,pai_btc,pnt_btc,bch_btc,ltc_btc,neo_btc,dash_btc,etc_btc,qtum_btc,zec_btc,sc_btc,bts_btc,cpx_btc,xwc_btc,fil6_btc,fil12_btc,fil36_btc,eos_usdt,ut_eth,ela_eth,vet_eth,vtho_eth,pai_eth,bfdt_eth,her_eth,ptt_eth,tac_eth,idhub_eth,ssc_eth,skm_eth,iic_eth,ply_eth,ext_eth,eos_eth,yoyow_eth,trx_eth,qtum_eth,zec_eth,bts_eth,btm_eth,mith_eth,nas_eth,man_eth,dbc_eth,bto_eth,ddd_eth,cpx_eth,cs_eth,iht_eth,tky_eth,ocn_eth,dct_eth,zpt_eth,eko_eth,mda_eth,pst_eth,xwc_eth,put_eth,pnt_eth,aac_eth,fil6_eth,fil12_eth,fil36_eth,uip_eth,seer_eth,bsb_eth,cdc_eth,grams_eth,ddmx_eth,eai_eth,inc_eth,bnb_usdt,ht_usdt,bot_eth,kbc_btc,kbc_usdt,mai_usdt,phv_usdt,hnb_usdt,gt_usdt,b91_usdt,voken_usdt,cye_usdt,brc_usdt,btc_ausd", + "enabledPairs": "eth_btc", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, { "name": "Bittrex", "enabled": true, diff --git a/testdata/http_mock/anx/anx.json b/testdata/http_mock/anx/anx.json new file mode 100644 index 00000000..fcf8ca46 --- /dev/null +++ b/testdata/http_mock/anx/anx.json @@ -0,0 +1,2404 @@ +{ + "routes": { + "/api/2/BTCUSD/money/depth/full": { + "GET": [ + { + "data": { + "data": { + "asks": [], + "bids": [], + "dataUpdateTime": "1565223784172000", + "now": "1565223792073000" + }, + "result": "success" + }, + "queryString": "", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/2/BTCUSD/money/ticker": { + "GET": [ + { + "data": { + "data": { + "avg": { + "currency": "USD", + "display": "7,009.18974 USD", + "display_short": "7,009.19 USD", + "value": "7009.18974", + "value_int": "700918974" + }, + "buy": { + "currency": "USD", + "display": " USD", + "display_short": " USD", + "value": "", + "value_int": "" + }, + "dataUpdateTime": "1565221354182000", + "high": { + "currency": "USD", + "display": "7,018.37948 USD", + "display_short": "7,018.38 USD", + "value": "7018.37948", + "value_int": "701837948" + }, + "last": { + "currency": "USD", + "display": "7,000.00000 USD", + "display_short": "7,000.00 USD", + "value": "7000.00000", + "value_int": "700000000" + }, + "low": { + "currency": "USD", + "display": "7,000.00000 USD", + "display_short": "7,000.00 USD", + "value": "7000.00000", + "value_int": "700000000" + }, + "now": "1565221384172000", + "sell": { + "currency": "USD", + "display": " USD", + "display_short": " USD", + "value": "", + "value_int": "" + }, + "vol": { + "currency": "BTC", + "display": "0.79840000 BTC", + "display_short": "0.80 BTC", + "value": "0.79840000", + "value_int": "79840000" + }, + "vwap": { + "currency": "USD", + "display": "1,696.89683 USD", + "display_short": "1,696.90 USD", + "value": "1696.89683", + "value_int": "169689683" + } + }, + "result": "success" + }, + "queryString": "", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/3/account": { + "POST": [ + { + "data": { + "data": { + "Created": "2017-04-18 11:21:21", + "Language": "en_US", + "Login": "", + "Rights": [ + "trade", + "withdraw", + "get_info" + ], + "Trade_Fee": "0.2000", + "Wallets": { + "ANX": { + "Available_Balance": { + "currency": "ANX", + "display": "0.00000000 ANX", + "displayShort": "0.00 ANX", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "ANX", + "display": "0.00000000 ANX", + "displayShort": "0.00 ANX", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "ANX", + "display": "50,000.00000000 ANX", + "displayShort": "50,000.00 ANX", + "value": "50000.00000000", + "valueInt": "5000000000000" + }, + "Max_Withdraw": { + "currency": "ANX", + "display": "50,000.00000000 ANX", + "displayShort": "50,000.00 ANX", + "value": "50000.00000000", + "valueInt": "5000000000000" + } + }, + "ATENC": { + "Available_Balance": { + "currency": "ATENC", + "display": "0.00000000 ATENC", + "displayShort": "0.00 ATENC", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "ATENC", + "display": "0.00000000 ATENC", + "displayShort": "0.00 ATENC", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "ATENC", + "display": "100.00000000 ATENC", + "displayShort": "100.00 ATENC", + "value": "100.00000000", + "valueInt": "10000000000" + }, + "Max_Withdraw": { + "currency": "ATENC", + "display": "100.00000000 ATENC", + "displayShort": "100.00 ATENC", + "value": "100.00000000", + "valueInt": "10000000000" + } + }, + "AUD": { + "Available_Balance": { + "currency": "AUD", + "display": "0.00000 AUD", + "displayShort": "0.00 AUD", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "AUD", + "display": "0.00000 AUD", + "displayShort": "0.00 AUD", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "AUD", + "display": "0.00000 AUD", + "displayShort": "0.00 AUD", + "value": "0.00000", + "valueInt": "0" + }, + "Max_Withdraw": { + "currency": "AUD", + "display": "0.00000 AUD", + "displayShort": "0.00 AUD", + "value": "0.00000", + "valueInt": "0" + } + }, + "BTC": { + "Available_Balance": { + "currency": "BTC", + "display": "0.00000000 BTC", + "displayShort": "0.00 BTC", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "BTC", + "display": "0.00000000 BTC", + "displayShort": "0.00 BTC", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "BTC", + "display": "2.00000000 BTC", + "displayShort": "2.00 BTC", + "value": "2.00000000", + "valueInt": "200000000" + }, + "Max_Withdraw": { + "currency": "BTC", + "display": "2.00000000 BTC", + "displayShort": "2.00 BTC", + "value": "2.00000000", + "valueInt": "200000000" + } + }, + "CAD": { + "Available_Balance": { + "currency": "CAD", + "display": "0.00000 CAD", + "displayShort": "0.00 CAD", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "CAD", + "display": "0.00000 CAD", + "displayShort": "0.00 CAD", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "CAD", + "display": "10,000.00000 CAD", + "displayShort": "10,000.00 CAD", + "value": "10000.00000", + "valueInt": "1000000000" + }, + "Max_Withdraw": { + "currency": "CAD", + "display": "10,000.00000 CAD", + "displayShort": "10,000.00 CAD", + "value": "10000.00000", + "valueInt": "1000000000" + } + }, + "CHF": { + "Available_Balance": { + "currency": "CHF", + "display": "0.00000 CHF", + "displayShort": "0.00 CHF", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "CHF", + "display": "0.00000 CHF", + "displayShort": "0.00 CHF", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "CHF", + "display": "10,000.00000 CHF", + "displayShort": "10,000.00 CHF", + "value": "10000.00000", + "valueInt": "1000000000" + }, + "Max_Withdraw": { + "currency": "CHF", + "display": "10,000.00000 CHF", + "displayShort": "10,000.00 CHF", + "value": "10000.00000", + "valueInt": "1000000000" + } + }, + "CNY": { + "Available_Balance": { + "currency": "CNY", + "display": "0.00000 CNY", + "displayShort": "0.00 CNY", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "CNY", + "display": "0.00000 CNY", + "displayShort": "0.00 CNY", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "CNY", + "display": "0.00000 CNY", + "displayShort": "0.00 CNY", + "value": "0.00000", + "valueInt": "0" + }, + "Max_Withdraw": { + "currency": "CNY", + "display": "0.00000 CNY", + "displayShort": "0.00 CNY", + "value": "0.00000", + "valueInt": "0" + } + }, + "DOGE": { + "Available_Balance": { + "currency": "DOGE", + "display": "0.00000000 DOGE", + "displayShort": "0.00 DOGE", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "DOGE", + "display": "0.00000000 DOGE", + "displayShort": "0.00 DOGE", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "DOGE", + "display": "20,000,000.00000000 DOGE", + "displayShort": "20,000,000.00 DOGE", + "value": "20000000.00000000", + "valueInt": "2000000000000000" + }, + "Max_Withdraw": { + "currency": "DOGE", + "display": "20,000,000.00000000 DOGE", + "displayShort": "20,000,000.00 DOGE", + "value": "20000000.00000000", + "valueInt": "2000000000000000" + } + }, + "EGD": { + "Available_Balance": { + "currency": "EGD", + "display": "0.00000000 EGD", + "displayShort": "0.00 EGD", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "EGD", + "display": "0.00000000 EGD", + "displayShort": "0.00 EGD", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "EGD", + "display": "80.00000000 EGD", + "displayShort": "80.00 EGD", + "value": "80.00000000", + "valueInt": "8000000000" + }, + "Max_Withdraw": { + "currency": "EGD", + "display": "80.00000000 EGD", + "displayShort": "80.00 EGD", + "value": "80.00000000", + "valueInt": "8000000000" + } + }, + "ETH": { + "Available_Balance": { + "currency": "ETH", + "display": "0.00000000 ETH", + "displayShort": "0.00 ETH", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "ETH", + "display": "0.00000000 ETH", + "displayShort": "0.00 ETH", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "ETH", + "display": "200.00000000 ETH", + "displayShort": "200.00 ETH", + "value": "200.00000000", + "valueInt": "20000000000" + }, + "Max_Withdraw": { + "currency": "ETH", + "display": "200.00000000 ETH", + "displayShort": "200.00 ETH", + "value": "200.00000000", + "valueInt": "20000000000" + } + }, + "EUR": { + "Available_Balance": { + "currency": "EUR", + "display": "0.00000 EUR", + "displayShort": "0.00 EUR", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "EUR", + "display": "0.00000 EUR", + "displayShort": "0.00 EUR", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "EUR", + "display": "0.00000 EUR", + "displayShort": "0.00 EUR", + "value": "0.00000", + "valueInt": "0" + }, + "Max_Withdraw": { + "currency": "EUR", + "display": "0.00000 EUR", + "displayShort": "0.00 EUR", + "value": "0.00000", + "valueInt": "0" + } + }, + "GBP": { + "Available_Balance": { + "currency": "GBP", + "display": "0.00000 GBP", + "displayShort": "0.00 GBP", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "GBP", + "display": "0.00000 GBP", + "displayShort": "0.00 GBP", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "GBP", + "display": "0.00000 GBP", + "displayShort": "0.00 GBP", + "value": "0.00000", + "valueInt": "0" + }, + "Max_Withdraw": { + "currency": "GBP", + "display": "0.00000 GBP", + "displayShort": "0.00 GBP", + "value": "0.00000", + "valueInt": "0" + } + }, + "GNT": { + "Available_Balance": { + "currency": "GNT", + "display": "0.00000000 GNT", + "displayShort": "0.00 GNT", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "GNT", + "display": "0.00000000 GNT", + "displayShort": "0.00 GNT", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "GNT", + "display": "180,000.00000000 GNT", + "displayShort": "180,000.00 GNT", + "value": "180000.00000000", + "valueInt": "18000000000000" + }, + "Max_Withdraw": { + "currency": "GNT", + "display": "180,000.00000000 GNT", + "displayShort": "180,000.00 GNT", + "value": "180000.00000000", + "valueInt": "18000000000000" + } + }, + "HKD": { + "Available_Balance": { + "currency": "HKD", + "display": "0.00000 HKD", + "displayShort": "0.00 HKD", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "HKD", + "display": "0.00000 HKD", + "displayShort": "0.00 HKD", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "HKD", + "display": "0.00000 HKD", + "displayShort": "0.00 HKD", + "value": "0.00000", + "valueInt": "0" + }, + "Max_Withdraw": { + "currency": "HKD", + "display": "0.00000 HKD", + "displayShort": "0.00 HKD", + "value": "0.00000", + "valueInt": "0" + } + }, + "JPY": { + "Available_Balance": { + "currency": "JPY", + "display": "0.00000 JPY", + "displayShort": "0.00 JPY", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "JPY", + "display": "0.00000 JPY", + "displayShort": "0.00 JPY", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "JPY", + "display": "100,000.00000 JPY", + "displayShort": "100,000.00 JPY", + "value": "100000.00000", + "valueInt": "10000000000" + }, + "Max_Withdraw": { + "currency": "JPY", + "display": "100,000.00000 JPY", + "displayShort": "100,000.00 JPY", + "value": "100000.00000", + "valueInt": "10000000000" + } + }, + "LTC": { + "Available_Balance": { + "currency": "LTC", + "display": "0.00000000 LTC", + "displayShort": "0.00 LTC", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "LTC", + "display": "0.00000000 LTC", + "displayShort": "0.00 LTC", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "LTC", + "display": "500.00000000 LTC", + "displayShort": "500.00 LTC", + "value": "500.00000000", + "valueInt": "50000000000" + }, + "Max_Withdraw": { + "currency": "LTC", + "display": "500.00000000 LTC", + "displayShort": "500.00 LTC", + "value": "500.00000000", + "valueInt": "50000000000" + } + }, + "NMC": { + "Available_Balance": { + "currency": "NMC", + "display": "0.00000000 NMC", + "displayShort": "0.00 NMC", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "NMC", + "display": "0.00000000 NMC", + "displayShort": "0.00 NMC", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "NMC", + "display": "10,000.00000000 NMC", + "displayShort": "10,000.00 NMC", + "value": "10000.00000000", + "valueInt": "1000000000000" + }, + "Max_Withdraw": { + "currency": "NMC", + "display": "10,000.00000000 NMC", + "displayShort": "10,000.00 NMC", + "value": "10000.00000000", + "valueInt": "1000000000000" + } + }, + "NZD": { + "Available_Balance": { + "currency": "NZD", + "display": "0.00000 NZD", + "displayShort": "0.00 NZD", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "NZD", + "display": "0.00000 NZD", + "displayShort": "0.00 NZD", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "NZD", + "display": "10,000.00000 NZD", + "displayShort": "10,000.00 NZD", + "value": "10000.00000", + "valueInt": "1000000000" + }, + "Max_Withdraw": { + "currency": "NZD", + "display": "10,000.00000 NZD", + "displayShort": "10,000.00 NZD", + "value": "10000.00000", + "valueInt": "1000000000" + } + }, + "OAX": { + "Available_Balance": { + "currency": "OAX", + "display": "0.00000000 OAX", + "displayShort": "0.00 OAX", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "OAX", + "display": "0.00000000 OAX", + "displayShort": "0.00 OAX", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "OAX", + "display": "100,000.00000000 OAX", + "displayShort": "100,000.00 OAX", + "value": "100000.00000000", + "valueInt": "10000000000000" + }, + "Max_Withdraw": { + "currency": "OAX", + "display": "100,000.00000000 OAX", + "displayShort": "100,000.00 OAX", + "value": "100000.00000000", + "valueInt": "10000000000000" + } + }, + "PPC": { + "Available_Balance": { + "currency": "PPC", + "display": "0.00000000 PPC", + "displayShort": "0.00 PPC", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "PPC", + "display": "0.00000000 PPC", + "displayShort": "0.00 PPC", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "PPC", + "display": "10,000.00000000 PPC", + "displayShort": "10,000.00 PPC", + "value": "10000.00000000", + "valueInt": "1000000000000" + }, + "Max_Withdraw": { + "currency": "PPC", + "display": "10,000.00000000 PPC", + "displayShort": "10,000.00 PPC", + "value": "10000.00000000", + "valueInt": "1000000000000" + } + }, + "SGD": { + "Available_Balance": { + "currency": "SGD", + "display": "0.00000 SGD", + "displayShort": "0.00 SGD", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "SGD", + "display": "0.00000 SGD", + "displayShort": "0.00 SGD", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "SGD", + "display": "10,000.00000 SGD", + "displayShort": "10,000.00 SGD", + "value": "10000.00000", + "valueInt": "1000000000" + }, + "Max_Withdraw": { + "currency": "SGD", + "display": "10,000.00000 SGD", + "displayShort": "10,000.00 SGD", + "value": "10000.00000", + "valueInt": "1000000000" + } + }, + "START": { + "Available_Balance": { + "currency": "START", + "display": "0.00000000 START", + "displayShort": "0.00 START", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "START", + "display": "0.00000000 START", + "displayShort": "0.00 START", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "START", + "display": "50,000.00000000 START", + "displayShort": "50,000.00 START", + "value": "50000.00000000", + "valueInt": "5000000000000" + }, + "Max_Withdraw": { + "currency": "START", + "display": "50,000.00000000 START", + "displayShort": "50,000.00 START", + "value": "50000.00000000", + "valueInt": "5000000000000" + } + }, + "STR": { + "Available_Balance": { + "currency": "STR", + "display": "0.00000000 STR", + "displayShort": "0.00 STR", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "STR", + "display": "0.00000000 STR", + "displayShort": "0.00 STR", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "STR", + "display": "2,000,000.00000000 STR", + "displayShort": "2,000,000.00 STR", + "value": "2000000.00000000", + "valueInt": "200000000000000" + }, + "Max_Withdraw": { + "currency": "STR", + "display": "2,000,000.00000000 STR", + "displayShort": "2,000,000.00 STR", + "value": "2000000.00000000", + "valueInt": "200000000000000" + } + }, + "USD": { + "Available_Balance": { + "currency": "USD", + "display": "0.00000 USD", + "displayShort": "0.00 USD", + "value": "0.00000", + "valueInt": "0" + }, + "Balance": { + "currency": "USD", + "display": "0.00000 USD", + "displayShort": "0.00 USD", + "value": "0.00000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "USD", + "display": "0.00000 USD", + "displayShort": "0.00 USD", + "value": "0.00000", + "valueInt": "0" + }, + "Max_Withdraw": { + "currency": "USD", + "display": "0.00000 USD", + "displayShort": "0.00 USD", + "value": "0.00000", + "valueInt": "0" + } + }, + "XRP": { + "Available_Balance": { + "currency": "XRP", + "display": "0.00000000 XRP", + "displayShort": "0.00 XRP", + "value": "0.00000000", + "valueInt": "0" + }, + "Balance": { + "currency": "XRP", + "display": "0.00000000 XRP", + "displayShort": "0.00 XRP", + "value": "0.00000000", + "valueInt": "0" + }, + "Daily_Withdrawal_Limit": { + "currency": "XRP", + "display": "50,000.00000000 XRP", + "displayShort": "50,000.00 XRP", + "value": "50000.00000000", + "valueInt": "5000000000000" + }, + "Max_Withdraw": { + "currency": "XRP", + "display": "50,000.00000000 XRP", + "displayShort": "50,000.00 XRP", + "value": "50000.00000000", + "valueInt": "5000000000000" + } + } + } + }, + "resultCode": "OK", + "timestamp": "1565243672136", + "userUuid": "e1de6d35-5f5c-4f99-b693-861ecb1fcada" + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565243671495\"}", + "headers": { + "Content-Type": [ + "application/json" + ], + "Rest-Key": [ + "" + ], + "Rest-Sign": [ + "WBsh289+Kjgzv7szgHNkspJ8tIJLM+iGH20feDOyg4WhIAemJv9N0TJBYSWpjpntsqm8zDAQmuDi9R9EzrVHPA==" + ] + } + } + ] + }, + "/api/3/apiKey": { + "POST": [ + { + "data": { + "resultCode": "INVALID_PARAMETERS", + "timestamp": "1565224367747" + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Content-Type": [ + "application/json" + ], + "Rest-Key": [ + "" + ], + "Rest-Sign": [ + "7rVm+8HCtd7aiTir+uHTAU9sr9GvviKT11CLQ2u02RqXAWLn6Za0BJRuuAcSTPZSz6eqcT7CU47dBQX48kitgA==" + ] + } + }, + { + "data": { + "resultCode": "INVALID_PARAMETERS", + "timestamp": "1565244853045" + }, + "queryString": "", + "bodyParams": "{\"deviceId\":\"1337\",\"nonce\":\"1565244852428\",\"password\":\"passWord\",\"username\":\"userName\"}", + "headers": { + "Content-Type": [ + "application/json" + ], + "Rest-Key": [ + "" + ], + "Rest-Sign": [ + "0K81dGp9rFxbPHtobirx9UxfrhcZdeVAtcyt++L7Jk7mqc+OPkeUjrRJZGQSTakhY0EoObDw8713neDcMb1dlQ==" + ] + } + } + ] + }, + "/api/3/currencyStatic": { + "GET": [ + { + "data": { + "currencyStatic": { + "currencies": { + "ANX": { + "assetDivisibility": 2, + "assetIcon": "/images/currencies/crypto/ANX.svg", + "assetIssueQuantity": "1000", + "assetName": "ANX", + "confirmationThresholds": [ + { + "confosRequired": 6 + } + ], + "decimals": 2, + "digitalCurrencyType": "MULTICHAIN_NATIVE", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": false, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 1000000000, + "maxMarketOrderValue": 10000000000, + "maxOrderSize": 1000000000, + "maxOrderValue": 10000000000, + "minOrderSize": 0.01, + "minOrderValue": 1, + "networkFee": 0.1, + "summaryDecimals": 2, + "type": "CRYPTO" + }, + "ATENC": { + "assetDivisibility": 0, + "confirmationThresholds": [ + { + "confosRequired": 6 + } + ], + "decimals": 8, + "displayDenominator": 1000, + "displayUnit": "kATENC", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": false, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 1000, + "maxMarketOrderValue": 1000, + "maxOrderSize": 100000, + "maxOrderValue": 100000000, + "minOrderSize": 0.01, + "minOrderValue": 1e-7, + "networkFee": 0.1, + "summaryDecimals": 0, + "symbol": "b", + "type": "CRYPTO" + }, + "AUD": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "AUD", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 6000, + "maxMarketOrderValue": 6000, + "maxOrderSize": 1000000000, + "maxOrderValue": 1000000000, + "minOrderSize": 0.1, + "minOrderValue": 0.1, + "summaryDecimals": 0, + "symbol": "$", + "type": "FIAT" + }, + "BTC": { + "assetDivisibility": 0, + "assetIcon": "/images/currencies/crypto/BTC.svg", + "confirmationThresholds": [ + { + "confosRequired": 4, + "threshold": 0.25 + }, + { + "confosRequired": 7, + "threshold": 30 + }, + { + "confosRequired": 8 + } + ], + "decimals": 8, + "digitalCurrencyType": "SATOSHI", + "displayDenominator": 0.001, + "displayUnit": "mBTC", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": true + }, + "maxMarketOrderSize": 10, + "maxMarketOrderValue": 10, + "maxOrderSize": 100000, + "maxOrderValue": 100000000, + "minOrderSize": 0.01, + "minOrderValue": 1e-7, + "networkFee": 0.002, + "summaryDecimals": 2, + "symbol": "฿", + "type": "CRYPTO", + "urlPrefix": "bitcoin:" + }, + "CAD": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "CAD", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 6000, + "maxMarketOrderValue": 6000, + "maxOrderSize": 1000000000, + "maxOrderValue": 1000000000, + "minOrderSize": 0.1, + "minOrderValue": 0.1, + "summaryDecimals": 0, + "symbol": "$", + "type": "FIAT" + }, + "CHF": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "CHF", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": false, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 4500, + "maxMarketOrderValue": 4500, + "maxOrderSize": 1000000000, + "maxOrderValue": 1000000000, + "minOrderSize": 0.1, + "minOrderValue": 0.1, + "summaryDecimals": 0, + "symbol": "Fr.", + "type": "FIAT" + }, + "CNY": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "CNY", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 33000, + "maxMarketOrderValue": 33000, + "maxOrderSize": 10000000000, + "maxOrderValue": 10000000000, + "minOrderSize": 1, + "minOrderValue": 1, + "summaryDecimals": 0, + "symbol": "¥", + "type": "FIAT" + }, + "DOGE": { + "assetDivisibility": 0, + "assetIcon": "/images/currencies/crypto/DOGE.svg", + "confirmationThresholds": [ + { + "confosRequired": 6 + } + ], + "decimals": 8, + "digitalCurrencyType": "SATOSHI", + "displayDenominator": 1000, + "displayUnit": "kDOGE", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": true + }, + "maxMarketOrderSize": 25000000, + "maxMarketOrderValue": 25000000, + "maxOrderSize": 1000000000, + "maxOrderValue": 10000000000, + "minOrderSize": 5000, + "minOrderValue": 1, + "networkFee": 0.1, + "summaryDecimals": 0, + "symbol": "Ɖ", + "type": "CRYPTO", + "urlPrefix": "dogecoin:" + }, + "ETH": { + "assetDivisibility": 0, + "assetIcon": "/images/currencies/crypto/ETH.svg", + "confirmationThresholds": [ + { + "confosRequired": 30, + "threshold": 0.5 + }, + { + "confosRequired": 45, + "threshold": 10 + }, + { + "confosRequired": 70 + } + ], + "decimals": 8, + "digitalCurrencyType": "ETHEREUM", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": true + }, + "maxMarketOrderSize": 1000000000, + "maxMarketOrderValue": 10000000000, + "maxOrderSize": 1000000000, + "maxOrderValue": 10000000000, + "minOrderSize": 0.0001, + "minOrderValue": 0.0001, + "networkFee": 0.005, + "type": "CRYPTO" + }, + "EUR": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "EUR", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 4000, + "maxMarketOrderValue": 4000, + "maxOrderSize": 1000000000, + "maxOrderValue": 1000000000, + "minOrderSize": 0.1, + "minOrderValue": 0.1, + "summaryDecimals": 0, + "symbol": "€", + "type": "FIAT" + }, + "GBP": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "GBP", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 3000, + "maxMarketOrderValue": 3000, + "maxOrderSize": 1000000000, + "maxOrderValue": 1000000000, + "minOrderSize": 0.1, + "minOrderValue": 0.1, + "summaryDecimals": 0, + "symbol": "£", + "type": "FIAT" + }, + "GNT": { + "assetDivisibility": 8, + "assetIcon": "/images/currencies/crypto/GNT.svg", + "assetName": "Golem", + "confirmationThresholds": [ + { + "confosRequired": 30, + "threshold": 10 + }, + { + "confosRequired": 45, + "threshold": 1000 + }, + { + "confosRequired": 60 + } + ], + "decimals": 8, + "digitalCurrencyType": "ETHEREUM_TOKEN", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": true + }, + "maxMarketOrderSize": 1000000000, + "maxMarketOrderValue": 10000000000, + "maxOrderSize": 1000000000, + "maxOrderValue": 10000000000, + "minOrderSize": 1, + "minOrderValue": 1, + "networkFee": 0.001, + "summaryDecimals": 8, + "type": "CRYPTO" + }, + "HKD": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "HKD", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": true + }, + "maxMarketOrderSize": 36000, + "maxMarketOrderValue": 36000, + "maxOrderSize": 10000000000, + "maxOrderValue": 10000000000, + "minOrderSize": 1, + "minOrderValue": 1, + "summaryDecimals": 0, + "symbol": "$", + "type": "FIAT" + }, + "JPY": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "JPY", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 550000, + "maxMarketOrderValue": 550000, + "maxOrderSize": 10000000000, + "maxOrderValue": 1000000000, + "minOrderSize": 10, + "minOrderValue": 10, + "summaryDecimals": 0, + "symbol": "¥", + "type": "FIAT" + }, + "LTC": { + "assetDivisibility": 0, + "assetIcon": "/images/currencies/crypto/LTC.svg", + "confirmationThresholds": [ + { + "confosRequired": 6 + } + ], + "decimals": 8, + "digitalCurrencyType": "SATOSHI", + "displayDenominator": 1, + "displayUnit": "LTC", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": true + }, + "maxMarketOrderSize": 1200, + "maxMarketOrderValue": 1200, + "maxOrderSize": 10000000, + "maxOrderValue": 100000000, + "minOrderSize": 0.1, + "minOrderValue": 0.01, + "networkFee": 0.02, + "summaryDecimals": 1, + "symbol": "Ł", + "type": "CRYPTO", + "urlPrefix": "litecoin:" + }, + "NZD": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "NZD", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 6500, + "maxMarketOrderValue": 6500, + "maxOrderSize": 1000000000, + "maxOrderValue": 1000000000, + "minOrderSize": 0.1, + "minOrderValue": 0.1, + "summaryDecimals": 0, + "symbol": "$", + "type": "FIAT" + }, + "OAX": { + "assetDivisibility": 8, + "assetIcon": "/images/currencies/crypto/OAX.svg", + "assetIssueQuantity": "100000000", + "assetName": "openANX Token", + "confirmationThresholds": [ + { + "confosRequired": 30, + "threshold": 10 + }, + { + "confosRequired": 45, + "threshold": 1000 + }, + { + "confosRequired": 60 + } + ], + "decimals": 8, + "digitalCurrencyType": "ETHEREUM_TOKEN", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": true + }, + "maxMarketOrderSize": 30000000, + "maxMarketOrderValue": 30000000, + "maxOrderSize": 30000000, + "maxOrderValue": 30000000, + "minOrderSize": 0.01, + "minOrderValue": 0.01, + "networkFee": 0.001, + "summaryDecimals": 8, + "type": "CRYPTO" + }, + "SGD": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "SGD", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 6200, + "maxMarketOrderValue": 6200, + "maxOrderSize": 1000000000, + "maxOrderValue": 1000000000, + "minOrderSize": 0.1, + "minOrderValue": 0.1, + "summaryDecimals": 0, + "symbol": "$", + "type": "FIAT" + }, + "START": { + "assetDivisibility": 8, + "confirmationThresholds": [ + { + "confosRequired": 6 + } + ], + "decimals": 8, + "digitalCurrencyType": "SATOSHI", + "displayDenominator": 1000, + "displayUnit": "kSTART", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": false, + "mobileAccessEnabled": true, + "withdrawalsEnabled": true + }, + "maxMarketOrderSize": 100000, + "maxMarketOrderValue": 100000, + "maxOrderSize": 100000, + "maxOrderValue": 100000, + "minOrderSize": 0.01, + "minOrderValue": 0.01, + "networkFee": 0.1, + "summaryDecimals": 8, + "symbol": "s", + "type": "CRYPTO" + }, + "STR": { + "assetDivisibility": 0, + "assetIcon": "/images/currencies/crypto/STR.svg", + "confirmationThresholds": [ + { + "confosRequired": 1, + "threshold": 0.5 + }, + { + "confosRequired": 1, + "threshold": 10 + }, + { + "confosRequired": 1 + } + ], + "decimals": 8, + "digitalCurrencyType": "RIPPLE", + "displayDenominator": 1000, + "displayUnit": "kSTR", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": false, + "mobileAccessEnabled": true, + "withdrawalsEnabled": true + }, + "maxMarketOrderSize": 10000, + "maxMarketOrderValue": 10000, + "maxOrderSize": 1000000, + "maxOrderValue": 10, + "minOrderSize": 500, + "minOrderValue": 0.0001, + "networkFee": 1, + "summaryDecimals": 0, + "symbol": "S", + "type": "CRYPTO" + }, + "USD": { + "assetDivisibility": 0, + "decimals": 2, + "displayDenominator": 1, + "displayUnit": "USD", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": false + }, + "maxMarketOrderSize": 4500, + "maxMarketOrderValue": 4500, + "maxOrderSize": 1000000000, + "maxOrderValue": 1000000000, + "minOrderSize": 0.1, + "minOrderValue": 0.1, + "summaryDecimals": 0, + "symbol": "$", + "type": "FIAT" + }, + "XRP": { + "assetDivisibility": 0, + "assetIcon": "/images/currencies/crypto/XRP.svg", + "confirmationThresholds": [ + { + "confosRequired": 1, + "threshold": 0.5 + }, + { + "confosRequired": 1, + "threshold": 10 + }, + { + "confosRequired": 1 + } + ], + "decimals": 8, + "digitalCurrencyType": "RIPPLE", + "displayDenominator": 1000, + "displayUnit": "kXRP", + "engineSettings": { + "depositsEnabled": false, + "displayEnabled": true, + "mobileAccessEnabled": true, + "withdrawalsEnabled": true + }, + "maxMarketOrderSize": 620000, + "maxMarketOrderValue": 620000, + "maxOrderSize": 1000000, + "maxOrderValue": 100000000, + "minOrderSize": 100, + "minOrderValue": 0.01, + "networkFee": 1, + "summaryDecimals": 0, + "symbol": "X", + "type": "CRYPTO" + } + }, + "currencyPairs": { + "ATENCAUD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000000, + "minOrderRate": 1, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "AUD", + "simpleTradeEnabled": false, + "tradedCcy": "ATENC" + }, + "ATENCCAD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000000, + "minOrderRate": 1, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "CAD", + "simpleTradeEnabled": false, + "tradedCcy": "ATENC" + }, + "ATENCEUR": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000000, + "minOrderRate": 1, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "EUR", + "simpleTradeEnabled": false, + "tradedCcy": "ATENC" + }, + "ATENCGBP": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000000, + "minOrderRate": 1, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "GBP", + "simpleTradeEnabled": false, + "tradedCcy": "ATENC" + }, + "ATENCHKD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000000, + "minOrderRate": 1, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "HKD", + "simpleTradeEnabled": false, + "tradedCcy": "ATENC" + }, + "ATENCJPY": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000000, + "minOrderRate": 1, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "JPY", + "simpleTradeEnabled": false, + "tradedCcy": "ATENC" + }, + "ATENCNZD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000000, + "minOrderRate": 1, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "NZD", + "simpleTradeEnabled": false, + "tradedCcy": "ATENC" + }, + "ATENCSGD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000000, + "minOrderRate": 1, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "SGD", + "simpleTradeEnabled": false, + "tradedCcy": "ATENC" + }, + "ATENCUSD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000000, + "minOrderRate": 1, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "USD", + "simpleTradeEnabled": false, + "tradedCcy": "ATENC" + }, + "BTCAUD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 40000, + "minOrderRate": 10, + "preferredMarket": "BITFINEX", + "priceDecimals": 5, + "settlementCcy": "AUD", + "simpleTradeEnabled": false, + "tradedCcy": "BTC" + }, + "BTCCAD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 40000, + "minOrderRate": 10, + "preferredMarket": "BITFINEX", + "priceDecimals": 5, + "settlementCcy": "CAD", + "simpleTradeEnabled": false, + "tradedCcy": "BTC" + }, + "BTCEUR": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 40000, + "minOrderRate": 10, + "preferredMarket": "BITFINEX", + "priceDecimals": 5, + "settlementCcy": "EUR", + "simpleTradeEnabled": false, + "tradedCcy": "BTC" + }, + "BTCGBP": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 40000, + "minOrderRate": 10, + "preferredMarket": "BITFINEX", + "priceDecimals": 5, + "settlementCcy": "GBP", + "simpleTradeEnabled": false, + "tradedCcy": "BTC" + }, + "BTCHKD": { + "chartEnabled": true, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 4200000, + "minOrderRate": 100, + "preferredMarket": "BITFINEX", + "priceDecimals": 5, + "settlementCcy": "HKD", + "simpleTradeEnabled": false, + "tradedCcy": "BTC" + }, + "BTCJPY": { + "chartEnabled": true, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 4000000, + "minOrderRate": 1000, + "preferredMarket": "BITFINEX", + "priceDecimals": 5, + "settlementCcy": "JPY", + "simpleTradeEnabled": false, + "tradedCcy": "BTC" + }, + "BTCNZD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 40000, + "minOrderRate": 10, + "preferredMarket": "BITFINEX", + "priceDecimals": 5, + "settlementCcy": "NZD", + "simpleTradeEnabled": false, + "tradedCcy": "BTC" + }, + "BTCSGD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 40000, + "minOrderRate": 10, + "preferredMarket": "BITFINEX", + "priceDecimals": 5, + "settlementCcy": "SGD", + "simpleTradeEnabled": false, + "tradedCcy": "BTC" + }, + "BTCUSD": { + "chartEnabled": true, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 40000, + "minOrderRate": 10, + "preferredMarket": "BITFINEX", + "priceDecimals": 5, + "settlementCcy": "USD", + "simpleTradeEnabled": false, + "tradedCcy": "BTC" + }, + "DOGEBTC": { + "chartEnabled": false, + "displayPriceDecimals": 8, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": false + }, + "maxOrderRate": 0.001, + "minOrderRate": 2e-8, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "BTC", + "simpleTradeEnabled": false, + "tradedCcy": "DOGE" + }, + "ETHBTC": { + "chartEnabled": true, + "displayPriceDecimals": 8, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": false + }, + "maxOrderRate": 10, + "minOrderRate": 2e-8, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "BTC", + "simpleTradeEnabled": false, + "tradedCcy": "ETH" + }, + "ETHHKD": { + "chartEnabled": true, + "displayPriceDecimals": 5, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": false + }, + "maxOrderRate": 100000, + "minOrderRate": 100, + "preferredMarket": "ANX", + "priceDecimals": 5, + "settlementCcy": "HKD", + "simpleTradeEnabled": false, + "tradedCcy": "ETH" + }, + "ETHUSD": { + "chartEnabled": true, + "displayPriceDecimals": 5, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": false + }, + "maxOrderRate": 10000, + "minOrderRate": 10, + "preferredMarket": "ANX", + "priceDecimals": 5, + "settlementCcy": "USD", + "simpleTradeEnabled": false, + "tradedCcy": "ETH" + }, + "GNTETH": { + "chartEnabled": false, + "displayPriceDecimals": 8, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": false + }, + "maxOrderRate": 0.01, + "minOrderRate": 2e-8, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "ETH", + "simpleTradeEnabled": false, + "tradedCcy": "GNT" + }, + "LTCBTC": { + "chartEnabled": true, + "displayPriceDecimals": 8, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": false + }, + "maxOrderRate": 1, + "minOrderRate": 0.001, + "preferredMarket": "BITFINEX", + "priceDecimals": 5, + "settlementCcy": "BTC", + "simpleTradeEnabled": false, + "tradedCcy": "LTC" + }, + "OAXETH": { + "chartEnabled": false, + "displayPriceDecimals": 8, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": false + }, + "maxOrderRate": 10, + "minOrderRate": 2e-8, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "ETH", + "simpleTradeEnabled": false, + "tradedCcy": "OAX" + }, + "STARTAUD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000, + "minOrderRate": 0.00002, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "AUD", + "simpleTradeEnabled": false, + "tradedCcy": "START" + }, + "STARTBTC": { + "chartEnabled": false, + "displayPriceDecimals": 8, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": false + }, + "maxOrderRate": 10, + "minOrderRate": 2e-8, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "BTC", + "simpleTradeEnabled": false, + "tradedCcy": "START" + }, + "STARTCAD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000, + "minOrderRate": 0.00002, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "CAD", + "simpleTradeEnabled": false, + "tradedCcy": "START" + }, + "STARTEUR": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000, + "minOrderRate": 0.00002, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "EUR", + "simpleTradeEnabled": false, + "tradedCcy": "START" + }, + "STARTGBP": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000, + "minOrderRate": 0.00002, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "GBP", + "simpleTradeEnabled": false, + "tradedCcy": "START" + }, + "STARTHKD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 10000, + "minOrderRate": 0.0002, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "HKD", + "simpleTradeEnabled": false, + "tradedCcy": "START" + }, + "STARTJPY": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 100000, + "minOrderRate": 0.002, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "JPY", + "simpleTradeEnabled": false, + "tradedCcy": "START" + }, + "STARTNZD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000, + "minOrderRate": 0.00002, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "NZD", + "simpleTradeEnabled": false, + "tradedCcy": "START" + }, + "STARTSGD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000, + "minOrderRate": 0.00002, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "SGD", + "simpleTradeEnabled": false, + "tradedCcy": "START" + }, + "STARTUSD": { + "chartEnabled": false, + "displayPriceDecimals": 2, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": true, + "restrictedSell": true, + "tradingEnabled": false, + "verifyRequired": true + }, + "maxOrderRate": 1000, + "minOrderRate": 0.00002, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "USD", + "simpleTradeEnabled": false, + "tradedCcy": "START" + }, + "STRBTC": { + "chartEnabled": false, + "displayPriceDecimals": 8, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": false, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": false + }, + "maxOrderRate": 10, + "minOrderRate": 2e-8, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "BTC", + "simpleTradeEnabled": false, + "tradedCcy": "STR" + }, + "XRPBTC": { + "chartEnabled": true, + "displayPriceDecimals": 8, + "engineSettings": { + "cancelOnly": true, + "displayEnabled": true, + "restrictedBuy": false, + "restrictedSell": false, + "tradingEnabled": false, + "verifyRequired": false + }, + "maxOrderRate": 10, + "minOrderRate": 2e-8, + "preferredMarket": "ANX", + "priceDecimals": 8, + "settlementCcy": "BTC", + "simpleTradeEnabled": false, + "tradedCcy": "XRP" + } + } + }, + "resultCode": "OK", + "timestamp": "1565221059387" + }, + "queryString": "", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/3/order/cancel": { + "POST": [ + { + "data": { + "resultCode": "OK", + "resultCodeList": [ + { + "errorCode": "ORDER_NOT_FOUND", + "uuid": "1" + } + ], + "timestamp": "1565239523039" + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565239522519\",\"orderIds\":[\"1\"]}", + "headers": { + "Content-Type": [ + "application/json" + ], + "Rest-Key": [ + "" + ], + "Rest-Sign": [ + "YFhH1Xb4r+FcD95UX/WDlBBzdsBUUc0NOEeYm2SqeV6EbX8lY0ZpWpGYySLOeRitJnqmL8iDzqaSkK6YbHYrFw==" + ] + } + }, + { + "data": { + "resultCode": "OK", + "resultCodeList": [ + { + "errorCode": "ORDER_NOT_FOUND", + "uuid": "1" + } + ], + "timestamp": "1565239523039" + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565239522519\",\"orderIds\":[\"\u003cnil\u003e\"]}", + "headers": { + "Content-Type": [ + "application/json" + ], + "Rest-Key": [ + "" + ], + "Rest-Sign": [ + "YFhH1Xb4r+FcD95UX/WDlBBzdsBUUc0NOEeYm2SqeV6EbX8lY0ZpWpGYySLOeRitJnqmL8iDzqaSkK6YbHYrFw==" + ] + } + } + ] + }, + "/api/3/order/list": { + "POST": [ + { + "data": { + "count": 0, + "orders": [], + "resultCode": "OK", + "timestamp": "1565237720206" + }, + "queryString": "", + "bodyParams": "{\"activeOnly\":false,\"nonce\":\"1565237719696\"}", + "headers": { + "Content-Type": [ + "application/json" + ], + "Rest-Key": [ + "" + ], + "Rest-Sign": [ + "vNiddyUn2Rp/iZibLwivUMt+EImNBgxFl2QnOzuZChO6hAK0Y8kycOO5I9NCL/F3GlXn55o6fWX6t6QV4Vvxig==" + ] + } + }, + { + "data": { + "count": 0, + "orders": [], + "resultCode": "OK", + "timestamp": "1565245118448" + }, + "queryString": "", + "bodyParams": "{\"activeOnly\":true,\"nonce\":\"1565245118213\"}", + "headers": { + "Content-Type": [ + "application/json" + ], + "Rest-Key": [ + "" + ], + "Rest-Sign": [ + "Z5v3uegtw7zJDHmPtQhMGQBfLNzD9iHFJ1dhcwMBVQEjMutuBL9TimZJ2N7WlJlRF8qN2lYHmNBV5ju/Us3dDA==" + ] + } + } + ] + }, + "/api/3/order/new": { + "POST": [ + { + "data": { + "resultCode": "UNAUTHORISED", + "timestamp": "1565238020536" + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565238020075\",\"order\":{\"orderType\":\"MARKET\",\"buyTradedCurrency\":true,\"tradedCurrency\":\"BTC\",\"settlementCurrency\":\"USD\",\"tradedCurrencyAmount\":\"1\",\"settlementCurrencyAmount\":\"0\",\"limitPriceInSettlementCurrency\":\"0\",\"replaceExistingOrderUuid\":\"\",\"replaceOnlyIfActive\":false}}", + "headers": { + "Content-Type": [ + "application/json" + ], + "Rest-Key": [ + "" + ], + "Rest-Sign": [ + "/H1O2+hjiuRWBkVnadizhfU/BzH77/t4iVDmnV7CqUi8/b0biDYxXdITxPA08sSguiqMd+mn7pJx+eXR9TNxuw==" + ] + } + } + ] + }, + "/api/3/receive": { + "POST": [ + { + "data": { + "resultCode": "ADDRESS_NOT_AVAILABLE", + "timestamp": "1565245771661" + }, + "queryString": "", + "bodyParams": "{\"ccy\":\"BTC\",\"nonce\":\"1565245771117\"}", + "headers": { + "Content-Type": [ + "application/json" + ], + "Rest-Key": [ + "" + ], + "Rest-Sign": [ + "6LMb99SQINMKMr2nbmgxcN/WQ1iH6ehBIkSEJbIOAanDVjypa3MnTsQILxtGKdCFnqlNqiIE80iaBztb+rv1+g==" + ] + } + } + ] + }, + "/api/3/send": { + "POST": [ + { + "data": { + "resultCode": "INSUFFICIENT_LIMIT", + "timestamp": "1565243984807" + }, + "queryString": "", + "bodyParams": "{\"address\":\"1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB\",\"amount\":\"-1\",\"ccy\":\"BTC\",\"nonce\":\"1565243984355\"}", + "headers": { + "Content-Type": [ + "application/json" + ], + "Rest-Key": [ + "" + ], + "Rest-Sign": [ + "veKau9XDbYJFgXFFgLrjpzmU6r7CghBTv5s4wIbQ0bhUHSl6SarBMQhTOY2639G46sAnkJmqyq480JDetVp+kw==" + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/testdata/http_mock/binance/binance.json b/testdata/http_mock/binance/binance.json new file mode 100644 index 00000000..0094c820 --- /dev/null +++ b/testdata/http_mock/binance/binance.json @@ -0,0 +1,45467 @@ +{ + "routes": { + "/api/v1/aggTrades": { + "GET": [ + { + "data": [ + { + "a": 122553794, + "p": "7878.29000000", + "q": "0.03564200", + "f": 134314157, + "l": 134314157, + "T": 1560296988887, + "m": true, + "M": true + }, + { + "a": 122553795, + "p": "7878.29000000", + "q": "0.07211900", + "f": 134314158, + "l": 134314158, + "T": 1560296989005, + "m": true, + "M": true + }, + { + "a": 122553796, + "p": "7878.29000000", + "q": "0.03317700", + "f": 134314159, + "l": 134314159, + "T": 1560296989099, + "m": true, + "M": true + }, + { + "a": 122553797, + "p": "7878.29000000", + "q": "0.11212700", + "f": 134314160, + "l": 134314160, + "T": 1560296989198, + "m": true, + "M": true + }, + { + "a": 122553798, + "p": "7878.29000000", + "q": "0.03598700", + "f": 134314161, + "l": 134314161, + "T": 1560296989319, + "m": true, + "M": true + } + ], + "queryString": "limit=5\u0026symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v1/depth": { + "GET": [ + { + "data": { + "lastUpdateId": 697211255, + "bids": [ + [ + "7886.01000000", + "0.00484600" + ], + [ + "7885.98000000", + "0.49716800" + ], + [ + "7885.96000000", + "1.00000000" + ], + [ + "7885.74000000", + "0.01720900" + ], + [ + "7885.55000000", + "0.02237700" + ], + [ + "7885.11000000", + "0.04452700" + ], + [ + "7885.09000000", + "0.50000000" + ], + [ + "7885.01000000", + "0.06131600" + ], + [ + "7884.55000000", + "0.50000000" + ], + [ + "7884.28000000", + "0.36721300" + ] + ], + "asks": [ + [ + "7888.94000000", + "3.60000000" + ], + [ + "7888.95000000", + "0.11707400" + ], + [ + "7889.05000000", + "0.03931600" + ], + [ + "7889.62000000", + "0.03659000" + ], + [ + "7890.08000000", + "0.04696200" + ], + [ + "7890.10000000", + "0.90702600" + ], + [ + "7890.76000000", + "0.35580100" + ], + [ + "7890.85000000", + "0.14271800" + ], + [ + "7891.06000000", + "0.45315700" + ], + [ + "7891.64000000", + "0.10609900" + ] + ] + }, + "queryString": "limit=10\u0026symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v1/exchangeInfo": { + "GET": [ + { + "data": { + "timezone": "UTC", + "serverTime": 1560297862726, + "rateLimits": [ + { + "rateLimitType": "REQUEST_WEIGHT", + "interval": "MINUTE", + "intervalNum": 1, + "limit": 1200 + }, + { + "rateLimitType": "ORDERS", + "interval": "SECOND", + "intervalNum": 1, + "limit": 10 + }, + { + "rateLimitType": "ORDERS", + "interval": "DAY", + "intervalNum": 1, + "limit": 100000 + } + ], + "exchangeFilters": [], + "symbols": [ + { + "symbol": "ETHBTC", + "status": "TRADING", + "baseAsset": "ETH", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": true, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "100000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "100000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "63100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LTCBTC", + "status": "TRADING", + "baseAsset": "LTC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "100000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "72500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BNBBTC", + "status": "TRADING", + "baseAsset": "BNB", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": true, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1769700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NEOBTC", + "status": "TRADING", + "baseAsset": "NEO", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "100000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "365600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QTUMETH", + "status": "TRADING", + "baseAsset": "QTUM", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "146600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EOSETH", + "status": "TRADING", + "baseAsset": "EOS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "286200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SNTETH", + "status": "TRADING", + "baseAsset": "SNT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7648700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BNTETH", + "status": "TRADING", + "baseAsset": "BNT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "356100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCCBTC", + "status": "BREAK", + "baseAsset": "BCC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "100000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "100000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GASBTC", + "status": "TRADING", + "baseAsset": "GAS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "100000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "111700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BNBETH", + "status": "TRADING", + "baseAsset": "BNB", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "68100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTCUSDT", + "status": "TRADING", + "baseAsset": "BTC", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": true, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00000100", + "maxQty": "10000000.00000000", + "stepSize": "0.00000100" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETHUSDT", + "status": "TRADING", + "baseAsset": "ETH", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": true, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "52400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "HSRBTC", + "status": "BREAK", + "baseAsset": "HSR", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "OAXETH", + "status": "TRADING", + "baseAsset": "OAX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "822600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DNTETH", + "status": "TRADING", + "baseAsset": "DNT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7750800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MCOETH", + "status": "TRADING", + "baseAsset": "MCO", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "29700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ICNETH", + "status": "BREAK", + "baseAsset": "ICN", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MCOBTC", + "status": "TRADING", + "baseAsset": "MCO", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "226300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WTCBTC", + "status": "TRADING", + "baseAsset": "WTC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1489800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WTCETH", + "status": "TRADING", + "baseAsset": "WTC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "120000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LRCBTC", + "status": "TRADING", + "baseAsset": "LRC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8481800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LRCETH", + "status": "TRADING", + "baseAsset": "LRC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3072100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QTUMBTC", + "status": "TRADING", + "baseAsset": "QTUM", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "554400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "YOYOBTC", + "status": "TRADING", + "baseAsset": "YOYO", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "41680800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "OMGBTC", + "status": "TRADING", + "baseAsset": "OMG", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "371100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "OMGETH", + "status": "TRADING", + "baseAsset": "OMG", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "372000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZRXBTC", + "status": "TRADING", + "baseAsset": "ZRX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4510800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZRXETH", + "status": "TRADING", + "baseAsset": "ZRX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1617600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "STRATBTC", + "status": "TRADING", + "baseAsset": "STRAT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1022300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "STRATETH", + "status": "TRADING", + "baseAsset": "STRAT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "247900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SNGLSBTC", + "status": "TRADING", + "baseAsset": "SNGLS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "23413000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SNGLSETH", + "status": "TRADING", + "baseAsset": "SNGLS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6821000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BQXBTC", + "status": "TRADING", + "baseAsset": "BQX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "12797800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BQXETH", + "status": "TRADING", + "baseAsset": "BQX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1303500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "KNCBTC", + "status": "TRADING", + "baseAsset": "KNC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "5147500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "KNCETH", + "status": "TRADING", + "baseAsset": "KNC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3642600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FUNBTC", + "status": "TRADING", + "baseAsset": "FUN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "51853200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FUNETH", + "status": "TRADING", + "baseAsset": "FUN", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "62536900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SNMBTC", + "status": "TRADING", + "baseAsset": "SNM", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8975000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SNMETH", + "status": "TRADING", + "baseAsset": "SNM", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4705100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NEOETH", + "status": "TRADING", + "baseAsset": "NEO", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "130000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "IOTABTC", + "status": "TRADING", + "baseAsset": "IOTA", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "12315300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "IOTAETH", + "status": "TRADING", + "baseAsset": "IOTA", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2518700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LINKBTC", + "status": "TRADING", + "baseAsset": "LINK", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2976000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LINKETH", + "status": "TRADING", + "baseAsset": "LINK", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "385500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XVGBTC", + "status": "TRADING", + "baseAsset": "XVG", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "76202500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XVGETH", + "status": "TRADING", + "baseAsset": "XVG", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "47534300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SALTBTC", + "status": "BREAK", + "baseAsset": "SALT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SALTETH", + "status": "BREAK", + "baseAsset": "SALT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MDABTC", + "status": "TRADING", + "baseAsset": "MDA", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1092600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MDAETH", + "status": "TRADING", + "baseAsset": "MDA", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "178800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MTLBTC", + "status": "TRADING", + "baseAsset": "MTL", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "840000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MTLETH", + "status": "TRADING", + "baseAsset": "MTL", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "214800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SUBBTC", + "status": "BREAK", + "baseAsset": "SUB", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SUBETH", + "status": "BREAK", + "baseAsset": "SUB", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EOSBTC", + "status": "TRADING", + "baseAsset": "EOS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1684200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SNTBTC", + "status": "TRADING", + "baseAsset": "SNT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "12415800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETCETH", + "status": "TRADING", + "baseAsset": "ETC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "37600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETCBTC", + "status": "TRADING", + "baseAsset": "ETC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "343000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MTHBTC", + "status": "TRADING", + "baseAsset": "MTH", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "26826400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MTHETH", + "status": "TRADING", + "baseAsset": "MTH", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "10684900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ENGBTC", + "status": "TRADING", + "baseAsset": "ENG", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2283700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ENGETH", + "status": "TRADING", + "baseAsset": "ENG", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "844200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DNTBTC", + "status": "TRADING", + "baseAsset": "DNT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "26246600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZECBTC", + "status": "TRADING", + "baseAsset": "ZEC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "27800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZECETH", + "status": "TRADING", + "baseAsset": "ZEC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BNTBTC", + "status": "TRADING", + "baseAsset": "BNT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "911500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ASTBTC", + "status": "TRADING", + "baseAsset": "AST", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "16333300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ASTETH", + "status": "TRADING", + "baseAsset": "AST", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3134700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DASHBTC", + "status": "TRADING", + "baseAsset": "DASH", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "12600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DASHETH", + "status": "TRADING", + "baseAsset": "DASH", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "OAXBTC", + "status": "TRADING", + "baseAsset": "OAX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3892200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ICNBTC", + "status": "BREAK", + "baseAsset": "ICN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTGBTC", + "status": "TRADING", + "baseAsset": "BTG", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "83200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTGETH", + "status": "TRADING", + "baseAsset": "BTG", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EVXBTC", + "status": "TRADING", + "baseAsset": "EVX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2424900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EVXETH", + "status": "TRADING", + "baseAsset": "EVX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "196100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "REQBTC", + "status": "TRADING", + "baseAsset": "REQ", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "17712100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "REQETH", + "status": "TRADING", + "baseAsset": "REQ", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "18357800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VIBBTC", + "status": "TRADING", + "baseAsset": "VIB", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "9370400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VIBETH", + "status": "TRADING", + "baseAsset": "VIB", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2852000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "HSRETH", + "status": "BREAK", + "baseAsset": "HSR", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRXBTC", + "status": "TRADING", + "baseAsset": "TRX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": true, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "177000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRXETH", + "status": "TRADING", + "baseAsset": "TRX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "60203600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "POWRBTC", + "status": "TRADING", + "baseAsset": "POWR", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4829000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "POWRETH", + "status": "TRADING", + "baseAsset": "POWR", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1218300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ARKBTC", + "status": "TRADING", + "baseAsset": "ARK", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1608500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ARKETH", + "status": "TRADING", + "baseAsset": "ARK", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "171500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "YOYOETH", + "status": "TRADING", + "baseAsset": "YOYO", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7287900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XRPBTC", + "status": "TRADING", + "baseAsset": "XRP", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": true, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "42853300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XRPETH", + "status": "TRADING", + "baseAsset": "XRP", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4202500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MODBTC", + "status": "BREAK", + "baseAsset": "MOD", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MODETH", + "status": "BREAK", + "baseAsset": "MOD", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ENJBTC", + "status": "TRADING", + "baseAsset": "ENJ", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "19300900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ENJETH", + "status": "TRADING", + "baseAsset": "ENJ", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3616900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "STORJBTC", + "status": "TRADING", + "baseAsset": "STORJ", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3562900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "STORJETH", + "status": "TRADING", + "baseAsset": "STORJ", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "294100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BNBUSDT", + "status": "TRADING", + "baseAsset": "BNB", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": true, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "393000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VENBNB", + "status": "BREAK", + "baseAsset": "VEN", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "YOYOBNB", + "status": "TRADING", + "baseAsset": "YOYO", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "915300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "POWRBNB", + "status": "TRADING", + "baseAsset": "POWR", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "420100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VENBTC", + "status": "BREAK", + "baseAsset": "VEN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VENETH", + "status": "BREAK", + "baseAsset": "VEN", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "KMDBTC", + "status": "TRADING", + "baseAsset": "KMD", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1014400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "KMDETH", + "status": "TRADING", + "baseAsset": "KMD", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "70200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NULSBNB", + "status": "TRADING", + "baseAsset": "NULS", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "94800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RCNBTC", + "status": "TRADING", + "baseAsset": "RCN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "14574700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RCNETH", + "status": "TRADING", + "baseAsset": "RCN", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3707200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RCNBNB", + "status": "TRADING", + "baseAsset": "RCN", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1674200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NULSBTC", + "status": "TRADING", + "baseAsset": "NULS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "962300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NULSETH", + "status": "TRADING", + "baseAsset": "NULS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "124200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RDNBTC", + "status": "TRADING", + "baseAsset": "RDN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2625600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RDNETH", + "status": "TRADING", + "baseAsset": "RDN", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1288500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RDNBNB", + "status": "TRADING", + "baseAsset": "RDN", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "87100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XMRBTC", + "status": "TRADING", + "baseAsset": "XMR", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "41700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XMRETH", + "status": "TRADING", + "baseAsset": "XMR", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DLTBNB", + "status": "TRADING", + "baseAsset": "DLT", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "718800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WTCBNB", + "status": "TRADING", + "baseAsset": "WTC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "38400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DLTBTC", + "status": "TRADING", + "baseAsset": "DLT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "10422400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DLTETH", + "status": "TRADING", + "baseAsset": "DLT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2510300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AMBBTC", + "status": "TRADING", + "baseAsset": "AMB", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "16127800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AMBETH", + "status": "TRADING", + "baseAsset": "AMB", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4658100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AMBBNB", + "status": "TRADING", + "baseAsset": "AMB", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "668000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCCETH", + "status": "BREAK", + "baseAsset": "BCC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCCUSDT", + "status": "BREAK", + "baseAsset": "BCC", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCCBNB", + "status": "BREAK", + "baseAsset": "BCC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "100000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BATBTC", + "status": "TRADING", + "baseAsset": "BAT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6664500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BATETH", + "status": "TRADING", + "baseAsset": "BAT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1040500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BATBNB", + "status": "TRADING", + "baseAsset": "BAT", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "754800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCPTBTC", + "status": "TRADING", + "baseAsset": "BCPT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "15465800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCPTETH", + "status": "TRADING", + "baseAsset": "BCPT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2395500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCPTBNB", + "status": "TRADING", + "baseAsset": "BCPT", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "914300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ARNBTC", + "status": "TRADING", + "baseAsset": "ARN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2868900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ARNETH", + "status": "TRADING", + "baseAsset": "ARN", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "308200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GVTBTC", + "status": "TRADING", + "baseAsset": "GVT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "381100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GVTETH", + "status": "TRADING", + "baseAsset": "GVT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "68500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CDTBTC", + "status": "TRADING", + "baseAsset": "CDT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "38110800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CDTETH", + "status": "TRADING", + "baseAsset": "CDT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "12174400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GXSBTC", + "status": "TRADING", + "baseAsset": "GXS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "497300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GXSETH", + "status": "TRADING", + "baseAsset": "GXS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "109600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NEOUSDT", + "status": "TRADING", + "baseAsset": "NEO", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "236900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NEOBNB", + "status": "TRADING", + "baseAsset": "NEO", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "24600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "POEBTC", + "status": "TRADING", + "baseAsset": "POE", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "69079600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "POEETH", + "status": "TRADING", + "baseAsset": "POE", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "36484600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QSPBTC", + "status": "TRADING", + "baseAsset": "QSP", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "9955800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QSPETH", + "status": "TRADING", + "baseAsset": "QSP", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "5732800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QSPBNB", + "status": "TRADING", + "baseAsset": "QSP", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1515800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTSBTC", + "status": "TRADING", + "baseAsset": "BTS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "13082700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTSETH", + "status": "TRADING", + "baseAsset": "BTS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1691500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTSBNB", + "status": "TRADING", + "baseAsset": "BTS", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "803100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XZCBTC", + "status": "TRADING", + "baseAsset": "XZC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "62700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XZCETH", + "status": "TRADING", + "baseAsset": "XZC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "10000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XZCBNB", + "status": "TRADING", + "baseAsset": "XZC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LSKBTC", + "status": "TRADING", + "baseAsset": "LSK", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "596600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LSKETH", + "status": "TRADING", + "baseAsset": "LSK", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "115400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LSKBNB", + "status": "TRADING", + "baseAsset": "LSK", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "20100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TNTBTC", + "status": "TRADING", + "baseAsset": "TNT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "15082300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TNTETH", + "status": "TRADING", + "baseAsset": "TNT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6646900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FUELBTC", + "status": "TRADING", + "baseAsset": "FUEL", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "60427700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FUELETH", + "status": "TRADING", + "baseAsset": "FUEL", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "24424800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MANABTC", + "status": "TRADING", + "baseAsset": "MANA", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "19397300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MANAETH", + "status": "TRADING", + "baseAsset": "MANA", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6197900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCDBTC", + "status": "TRADING", + "baseAsset": "BCD", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "266700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCDETH", + "status": "TRADING", + "baseAsset": "BCD", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "118100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DGDBTC", + "status": "TRADING", + "baseAsset": "DGD", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "30900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DGDETH", + "status": "TRADING", + "baseAsset": "DGD", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "9000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "IOTABNB", + "status": "TRADING", + "baseAsset": "IOTA", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "417500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ADXBTC", + "status": "TRADING", + "baseAsset": "ADX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "5864300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ADXETH", + "status": "TRADING", + "baseAsset": "ADX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "524300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ADXBNB", + "status": "TRADING", + "baseAsset": "ADX", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "218000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ADABTC", + "status": "TRADING", + "baseAsset": "ADA", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "62929600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ADAETH", + "status": "TRADING", + "baseAsset": "ADA", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "13623000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PPTBTC", + "status": "TRADING", + "baseAsset": "PPT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "503700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PPTETH", + "status": "TRADING", + "baseAsset": "PPT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "110400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CMTBTC", + "status": "TRADING", + "baseAsset": "CMT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "25294300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CMTETH", + "status": "TRADING", + "baseAsset": "CMT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7780500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CMTBNB", + "status": "TRADING", + "baseAsset": "CMT", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "661400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XLMBTC", + "status": "TRADING", + "baseAsset": "XLM", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "15465700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XLMETH", + "status": "TRADING", + "baseAsset": "XLM", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "10947200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XLMBNB", + "status": "TRADING", + "baseAsset": "XLM", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1214300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CNDBTC", + "status": "TRADING", + "baseAsset": "CND", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "27678400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CNDETH", + "status": "TRADING", + "baseAsset": "CND", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7973800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CNDBNB", + "status": "TRADING", + "baseAsset": "CND", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2682800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LENDBTC", + "status": "TRADING", + "baseAsset": "LEND", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "66709800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LENDETH", + "status": "TRADING", + "baseAsset": "LEND", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "15463700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WABIBTC", + "status": "TRADING", + "baseAsset": "WABI", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3858000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WABIETH", + "status": "TRADING", + "baseAsset": "WABI", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "647200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WABIBNB", + "status": "TRADING", + "baseAsset": "WABI", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "280800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LTCETH", + "status": "TRADING", + "baseAsset": "LTC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "5200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LTCUSDT", + "status": "TRADING", + "baseAsset": "LTC", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "55700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LTCBNB", + "status": "TRADING", + "baseAsset": "LTC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "100000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "18600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TNBBTC", + "status": "TRADING", + "baseAsset": "TNB", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "61109700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TNBETH", + "status": "TRADING", + "baseAsset": "TNB", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "38128500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WAVESBTC", + "status": "TRADING", + "baseAsset": "WAVES", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "772500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WAVESETH", + "status": "TRADING", + "baseAsset": "WAVES", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "82200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WAVESBNB", + "status": "TRADING", + "baseAsset": "WAVES", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "22400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GTOBTC", + "status": "TRADING", + "baseAsset": "GTO", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "9729600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GTOETH", + "status": "TRADING", + "baseAsset": "GTO", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7988100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GTOBNB", + "status": "TRADING", + "baseAsset": "GTO", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2202000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ICXBTC", + "status": "TRADING", + "baseAsset": "ICX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2926800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ICXETH", + "status": "TRADING", + "baseAsset": "ICX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1520800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ICXBNB", + "status": "TRADING", + "baseAsset": "ICX", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "172100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "OSTBTC", + "status": "TRADING", + "baseAsset": "OST", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "16300200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "OSTETH", + "status": "TRADING", + "baseAsset": "OST", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "10247300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "OSTBNB", + "status": "TRADING", + "baseAsset": "OST", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "993700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ELFBTC", + "status": "TRADING", + "baseAsset": "ELF", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4822300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ELFETH", + "status": "TRADING", + "baseAsset": "ELF", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2012400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AIONBTC", + "status": "TRADING", + "baseAsset": "AION", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2889700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AIONETH", + "status": "TRADING", + "baseAsset": "AION", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2146600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AIONBNB", + "status": "TRADING", + "baseAsset": "AION", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "191400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NEBLBTC", + "status": "TRADING", + "baseAsset": "NEBL", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "377100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NEBLETH", + "status": "TRADING", + "baseAsset": "NEBL", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "71000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NEBLBNB", + "status": "TRADING", + "baseAsset": "NEBL", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "36800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BRDBTC", + "status": "TRADING", + "baseAsset": "BRD", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2201500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BRDETH", + "status": "TRADING", + "baseAsset": "BRD", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "237500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BRDBNB", + "status": "TRADING", + "baseAsset": "BRD", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "73200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MCOBNB", + "status": "TRADING", + "baseAsset": "MCO", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "12300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EDOBTC", + "status": "TRADING", + "baseAsset": "EDO", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1514800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EDOETH", + "status": "TRADING", + "baseAsset": "EDO", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "178900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WINGSBTC", + "status": "BREAK", + "baseAsset": "WINGS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WINGSETH", + "status": "BREAK", + "baseAsset": "WINGS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NAVBTC", + "status": "TRADING", + "baseAsset": "NAV", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2144400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NAVETH", + "status": "TRADING", + "baseAsset": "NAV", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "250300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NAVBNB", + "status": "TRADING", + "baseAsset": "NAV", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "43600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LUNBTC", + "status": "TRADING", + "baseAsset": "LUN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "279800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LUNETH", + "status": "TRADING", + "baseAsset": "LUN", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "45500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRIGBTC", + "status": "BREAK", + "baseAsset": "TRIG", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRIGETH", + "status": "BREAK", + "baseAsset": "TRIG", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRIGBNB", + "status": "BREAK", + "baseAsset": "TRIG", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "APPCBTC", + "status": "TRADING", + "baseAsset": "APPC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6279600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "APPCETH", + "status": "TRADING", + "baseAsset": "APPC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2519600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "APPCBNB", + "status": "TRADING", + "baseAsset": "APPC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "556300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VIBEBTC", + "status": "TRADING", + "baseAsset": "VIBE", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "5029300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VIBEETH", + "status": "TRADING", + "baseAsset": "VIBE", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2290000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RLCBTC", + "status": "TRADING", + "baseAsset": "RLC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "785100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RLCETH", + "status": "TRADING", + "baseAsset": "RLC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "176700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RLCBNB", + "status": "TRADING", + "baseAsset": "RLC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "33100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "INSBTC", + "status": "TRADING", + "baseAsset": "INS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3027300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "INSETH", + "status": "TRADING", + "baseAsset": "INS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "397000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PIVXBTC", + "status": "TRADING", + "baseAsset": "PIVX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "889800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PIVXETH", + "status": "TRADING", + "baseAsset": "PIVX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "222100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PIVXBNB", + "status": "TRADING", + "baseAsset": "PIVX", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "28100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "IOSTBTC", + "status": "TRADING", + "baseAsset": "IOST", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "49930700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "IOSTETH", + "status": "TRADING", + "baseAsset": "IOST", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "17963200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CHATBTC", + "status": "BREAK", + "baseAsset": "CHAT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CHATETH", + "status": "BREAK", + "baseAsset": "CHAT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "STEEMBTC", + "status": "TRADING", + "baseAsset": "STEEM", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1199600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "STEEMETH", + "status": "TRADING", + "baseAsset": "STEEM", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "312100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "STEEMBNB", + "status": "TRADING", + "baseAsset": "STEEM", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "75600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NANOBTC", + "status": "TRADING", + "baseAsset": "NANO", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2262900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NANOETH", + "status": "TRADING", + "baseAsset": "NANO", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "294900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NANOBNB", + "status": "TRADING", + "baseAsset": "NANO", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "89000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VIABTC", + "status": "TRADING", + "baseAsset": "VIA", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "623500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VIAETH", + "status": "TRADING", + "baseAsset": "VIA", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "93600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VIABNB", + "status": "TRADING", + "baseAsset": "VIA", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "81000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BLZBTC", + "status": "TRADING", + "baseAsset": "BLZ", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "12656600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BLZETH", + "status": "TRADING", + "baseAsset": "BLZ", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2188000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BLZBNB", + "status": "TRADING", + "baseAsset": "BLZ", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "468900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AEBTC", + "status": "TRADING", + "baseAsset": "AE", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "597400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AEETH", + "status": "TRADING", + "baseAsset": "AE", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "554600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AEBNB", + "status": "TRADING", + "baseAsset": "AE", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "123100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RPXBTC", + "status": "BREAK", + "baseAsset": "RPX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RPXETH", + "status": "BREAK", + "baseAsset": "RPX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RPXBNB", + "status": "BREAK", + "baseAsset": "RPX", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NCASHBTC", + "status": "TRADING", + "baseAsset": "NCASH", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "153000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NCASHETH", + "status": "TRADING", + "baseAsset": "NCASH", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "67411900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NCASHBNB", + "status": "TRADING", + "baseAsset": "NCASH", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "1000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "21332000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "POABTC", + "status": "TRADING", + "baseAsset": "POA", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8586800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "POAETH", + "status": "TRADING", + "baseAsset": "POA", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4744600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "POABNB", + "status": "TRADING", + "baseAsset": "POA", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2311900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZILBTC", + "status": "TRADING", + "baseAsset": "ZIL", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "44522800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZILETH", + "status": "TRADING", + "baseAsset": "ZIL", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "19905900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZILBNB", + "status": "TRADING", + "baseAsset": "ZIL", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "9231200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONTBTC", + "status": "TRADING", + "baseAsset": "ONT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "672900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONTETH", + "status": "TRADING", + "baseAsset": "ONT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "248200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONTBNB", + "status": "TRADING", + "baseAsset": "ONT", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "126500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "STORMBTC", + "status": "TRADING", + "baseAsset": "STORM", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "82130100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "STORMETH", + "status": "TRADING", + "baseAsset": "STORM", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "24609300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "STORMBNB", + "status": "TRADING", + "baseAsset": "STORM", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6673100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QTUMBNB", + "status": "TRADING", + "baseAsset": "QTUM", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "24800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QTUMUSDT", + "status": "TRADING", + "baseAsset": "QTUM", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "171500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XEMBTC", + "status": "TRADING", + "baseAsset": "XEM", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8118400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XEMETH", + "status": "TRADING", + "baseAsset": "XEM", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2766300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XEMBNB", + "status": "TRADING", + "baseAsset": "XEM", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "551800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WANBTC", + "status": "TRADING", + "baseAsset": "WAN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1116700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WANETH", + "status": "TRADING", + "baseAsset": "WAN", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1177500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WANBNB", + "status": "TRADING", + "baseAsset": "WAN", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "101300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WPRBTC", + "status": "TRADING", + "baseAsset": "WPR", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "12499800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WPRETH", + "status": "TRADING", + "baseAsset": "WPR", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "11275100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QLCBTC", + "status": "TRADING", + "baseAsset": "QLC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "21208000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QLCETH", + "status": "TRADING", + "baseAsset": "QLC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3120800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SYSBTC", + "status": "TRADING", + "baseAsset": "SYS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "10148300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SYSETH", + "status": "TRADING", + "baseAsset": "SYS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "699000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SYSBNB", + "status": "TRADING", + "baseAsset": "SYS", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "453500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QLCBNB", + "status": "TRADING", + "baseAsset": "QLC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1132700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GRSBTC", + "status": "TRADING", + "baseAsset": "GRS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2609800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GRSETH", + "status": "TRADING", + "baseAsset": "GRS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "194900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ADAUSDT", + "status": "TRADING", + "baseAsset": "ADA", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "44681800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ADABNB", + "status": "TRADING", + "baseAsset": "ADA", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4526300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CLOAKBTC", + "status": "BREAK", + "baseAsset": "CLOAK", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CLOAKETH", + "status": "BREAK", + "baseAsset": "CLOAK", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GNTBTC", + "status": "TRADING", + "baseAsset": "GNT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7038900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GNTETH", + "status": "TRADING", + "baseAsset": "GNT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2165800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GNTBNB", + "status": "TRADING", + "baseAsset": "GNT", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "442900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LOOMBTC", + "status": "TRADING", + "baseAsset": "LOOM", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4695700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LOOMETH", + "status": "TRADING", + "baseAsset": "LOOM", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4655200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LOOMBNB", + "status": "TRADING", + "baseAsset": "LOOM", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "588900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XRPUSDT", + "status": "TRADING", + "baseAsset": "XRP", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": true, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "21663800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCNBTC", + "status": "BREAK", + "baseAsset": "BCN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCNETH", + "status": "BREAK", + "baseAsset": "BCN", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCNBNB", + "status": "BREAK", + "baseAsset": "BCN", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "REPBTC", + "status": "TRADING", + "baseAsset": "REP", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "39600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "REPETH", + "status": "TRADING", + "baseAsset": "REP", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "17800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "REPBNB", + "status": "TRADING", + "baseAsset": "REP", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTCTUSD", + "status": "TRADING", + "baseAsset": "BTC", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00000100", + "maxQty": "10000000.00000000", + "stepSize": "0.00000100" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TUSDBTC", + "status": "BREAK", + "baseAsset": "TUSD", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETHTUSD", + "status": "TRADING", + "baseAsset": "ETH", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TUSDETH", + "status": "BREAK", + "baseAsset": "TUSD", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TUSDBNB", + "status": "BREAK", + "baseAsset": "TUSD", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZENBTC", + "status": "TRADING", + "baseAsset": "ZEN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "38800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZENETH", + "status": "TRADING", + "baseAsset": "ZEN", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZENBNB", + "status": "TRADING", + "baseAsset": "ZEN", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SKYBTC", + "status": "TRADING", + "baseAsset": "SKY", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "295400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SKYETH", + "status": "TRADING", + "baseAsset": "SKY", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "61200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SKYBNB", + "status": "TRADING", + "baseAsset": "SKY", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "18400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EOSUSDT", + "status": "TRADING", + "baseAsset": "EOS", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "674000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EOSBNB", + "status": "TRADING", + "baseAsset": "EOS", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "108500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CVCBTC", + "status": "TRADING", + "baseAsset": "CVC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4964200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CVCETH", + "status": "TRADING", + "baseAsset": "CVC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "793400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CVCBNB", + "status": "TRADING", + "baseAsset": "CVC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "343400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "THETABTC", + "status": "TRADING", + "baseAsset": "THETA", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "5038700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "THETAETH", + "status": "TRADING", + "baseAsset": "THETA", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1737100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "THETABNB", + "status": "TRADING", + "baseAsset": "THETA", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "755000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XRPBNB", + "status": "TRADING", + "baseAsset": "XRP", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1405600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TUSDUSDT", + "status": "TRADING", + "baseAsset": "TUSD", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1720800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "IOTAUSDT", + "status": "TRADING", + "baseAsset": "IOTA", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3767600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XLMUSDT", + "status": "TRADING", + "baseAsset": "XLM", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "11819800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "IOTXBTC", + "status": "TRADING", + "baseAsset": "IOTX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "62047600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "IOTXETH", + "status": "TRADING", + "baseAsset": "IOTX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "10494500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QKCBTC", + "status": "TRADING", + "baseAsset": "QKC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "24155000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "QKCETH", + "status": "TRADING", + "baseAsset": "QKC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6874500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AGIBTC", + "status": "TRADING", + "baseAsset": "AGI", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8524400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AGIETH", + "status": "TRADING", + "baseAsset": "AGI", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3979700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "AGIBNB", + "status": "TRADING", + "baseAsset": "AGI", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "444800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NXSBTC", + "status": "TRADING", + "baseAsset": "NXS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "820500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NXSETH", + "status": "TRADING", + "baseAsset": "NXS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "121800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NXSBNB", + "status": "TRADING", + "baseAsset": "NXS", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "66500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ENJBNB", + "status": "TRADING", + "baseAsset": "ENJ", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "963900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DATABTC", + "status": "TRADING", + "baseAsset": "DATA", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "10846500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DATAETH", + "status": "TRADING", + "baseAsset": "DATA", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3264700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONTUSDT", + "status": "TRADING", + "baseAsset": "ONT", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "840800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRXBNB", + "status": "TRADING", + "baseAsset": "TRX", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "10168100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRXUSDT", + "status": "TRADING", + "baseAsset": "TRX", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": true, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "76622600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETCUSDT", + "status": "TRADING", + "baseAsset": "ETC", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "159700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETCBNB", + "status": "TRADING", + "baseAsset": "ETC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ICXUSDT", + "status": "TRADING", + "baseAsset": "ICX", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2173500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SCBTC", + "status": "TRADING", + "baseAsset": "SC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "328000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SCETH", + "status": "TRADING", + "baseAsset": "SC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "61896000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "SCBNB", + "status": "TRADING", + "baseAsset": "SC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "15000500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NPXSBTC", + "status": "TRADING", + "baseAsset": "NPXS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4340000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NPXSETH", + "status": "TRADING", + "baseAsset": "NPXS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "476000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VENUSDT", + "status": "BREAK", + "baseAsset": "VEN", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "KEYBTC", + "status": "TRADING", + "baseAsset": "KEY", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "56242900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "KEYETH", + "status": "TRADING", + "baseAsset": "KEY", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "32748000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NASBTC", + "status": "TRADING", + "baseAsset": "NAS", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "279400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NASETH", + "status": "TRADING", + "baseAsset": "NAS", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "71800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NASBNB", + "status": "TRADING", + "baseAsset": "NAS", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "45700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MFTBTC", + "status": "TRADING", + "baseAsset": "MFT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "138000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MFTETH", + "status": "TRADING", + "baseAsset": "MFT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "35855500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MFTBNB", + "status": "TRADING", + "baseAsset": "MFT", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "9292000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DENTBTC", + "status": "TRADING", + "baseAsset": "DENT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1830000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DENTETH", + "status": "TRADING", + "baseAsset": "DENT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "125000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ARDRBTC", + "status": "TRADING", + "baseAsset": "ARDR", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3945800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ARDRETH", + "status": "TRADING", + "baseAsset": "ARDR", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "358500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ARDRBNB", + "status": "TRADING", + "baseAsset": "ARDR", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "331400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NULSUSDT", + "status": "TRADING", + "baseAsset": "NULS", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "353500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "HOTBTC", + "status": "TRADING", + "baseAsset": "HOT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1870000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "HOTETH", + "status": "TRADING", + "baseAsset": "HOT", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "565000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VETBTC", + "status": "TRADING", + "baseAsset": "VET", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "116000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VETETH", + "status": "TRADING", + "baseAsset": "VET", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "52447600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VETUSDT", + "status": "TRADING", + "baseAsset": "VET", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "78479400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "VETBNB", + "status": "TRADING", + "baseAsset": "VET", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "1000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "30626300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DOCKBTC", + "status": "TRADING", + "baseAsset": "DOCK", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "14703300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DOCKETH", + "status": "TRADING", + "baseAsset": "DOCK", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "5313300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "POLYBTC", + "status": "TRADING", + "baseAsset": "POLY", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6854100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "POLYBNB", + "status": "TRADING", + "baseAsset": "POLY", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "285600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PHXBTC", + "status": "BREAK", + "baseAsset": "PHX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "79612100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PHXETH", + "status": "BREAK", + "baseAsset": "PHX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7740200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PHXBNB", + "status": "BREAK", + "baseAsset": "PHX", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3980900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "HCBTC", + "status": "TRADING", + "baseAsset": "HC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "276700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "HCETH", + "status": "TRADING", + "baseAsset": "HC", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "66400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GOBTC", + "status": "TRADING", + "baseAsset": "GO", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7814200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "GOBNB", + "status": "TRADING", + "baseAsset": "GO", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "949900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PAXBTC", + "status": "BREAK", + "baseAsset": "PAX", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PAXBNB", + "status": "BREAK", + "baseAsset": "PAX", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PAXUSDT", + "status": "TRADING", + "baseAsset": "PAX", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2450000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PAXETH", + "status": "BREAK", + "baseAsset": "PAX", + "baseAssetPrecision": 8, + "quoteAsset": "ETH", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.01000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RVNBTC", + "status": "TRADING", + "baseAsset": "RVN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "40636700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RVNBNB", + "status": "TRADING", + "baseAsset": "RVN", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2440800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DCRBTC", + "status": "TRADING", + "baseAsset": "DCR", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "28900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DCRBNB", + "status": "TRADING", + "baseAsset": "DCR", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "USDCBNB", + "status": "BREAK", + "baseAsset": "USDC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "USDCBTC", + "status": "BREAK", + "baseAsset": "USDC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MITHBTC", + "status": "TRADING", + "baseAsset": "MITH", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "20880600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MITHBNB", + "status": "TRADING", + "baseAsset": "MITH", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "813300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCHABCBTC", + "status": "TRADING", + "baseAsset": "BCHABC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "31000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCHSVBTC", + "status": "BREAK", + "baseAsset": "BCHSV", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000000", + "maxPrice": "0.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCHABCUSDT", + "status": "TRADING", + "baseAsset": "BCHABC", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "22000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCHSVUSDT", + "status": "BREAK", + "baseAsset": "BCHSV", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BNBPAX", + "status": "TRADING", + "baseAsset": "BNB", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "57700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTCPAX", + "status": "TRADING", + "baseAsset": "BTC", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00000100", + "maxQty": "10000000.00000000", + "stepSize": "0.00000100" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETHPAX", + "status": "TRADING", + "baseAsset": "ETH", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XRPPAX", + "status": "TRADING", + "baseAsset": "XRP", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "971300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EOSPAX", + "status": "TRADING", + "baseAsset": "EOS", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "26100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XLMPAX", + "status": "TRADING", + "baseAsset": "XLM", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "187400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RENBTC", + "status": "TRADING", + "baseAsset": "REN", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8259300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "RENBNB", + "status": "TRADING", + "baseAsset": "REN", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1865400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BNBTUSD", + "status": "TRADING", + "baseAsset": "BNB", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "35400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XRPTUSD", + "status": "TRADING", + "baseAsset": "XRP", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "946000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EOSTUSD", + "status": "TRADING", + "baseAsset": "EOS", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "26500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XLMTUSD", + "status": "TRADING", + "baseAsset": "XLM", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "389700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BNBUSDC", + "status": "TRADING", + "baseAsset": "BNB", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "27700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTCUSDC", + "status": "TRADING", + "baseAsset": "BTC", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00000100", + "maxQty": "10000000.00000000", + "stepSize": "0.00000100" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETHUSDC", + "status": "TRADING", + "baseAsset": "ETH", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XRPUSDC", + "status": "TRADING", + "baseAsset": "XRP", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1492000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "EOSUSDC", + "status": "TRADING", + "baseAsset": "EOS", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "34600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XLMUSDC", + "status": "TRADING", + "baseAsset": "XLM", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1401500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "USDCUSDT", + "status": "TRADING", + "baseAsset": "USDC", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1523100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ADATUSD", + "status": "TRADING", + "baseAsset": "ADA", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2231500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRXTUSD", + "status": "TRADING", + "baseAsset": "TRX", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7502800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NEOTUSD", + "status": "TRADING", + "baseAsset": "NEO", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "13500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRXXRP", + "status": "TRADING", + "baseAsset": "TRX", + "baseAssetPrecision": 8, + "quoteAsset": "XRP", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "14698800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XZCXRP", + "status": "TRADING", + "baseAsset": "XZC", + "baseAssetPrecision": 8, + "quoteAsset": "XRP", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "20400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PAXTUSD", + "status": "TRADING", + "baseAsset": "PAX", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "554400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "USDCTUSD", + "status": "TRADING", + "baseAsset": "USDC", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "974900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "USDCPAX", + "status": "TRADING", + "baseAsset": "USDC", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "460200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LINKUSDT", + "status": "TRADING", + "baseAsset": "LINK", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1275600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LINKTUSD", + "status": "TRADING", + "baseAsset": "LINK", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "186400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LINKPAX", + "status": "TRADING", + "baseAsset": "LINK", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "120400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LINKUSDC", + "status": "TRADING", + "baseAsset": "LINK", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "79700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WAVESUSDT", + "status": "TRADING", + "baseAsset": "WAVES", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "126400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WAVESTUSD", + "status": "TRADING", + "baseAsset": "WAVES", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "37300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WAVESPAX", + "status": "TRADING", + "baseAsset": "WAVES", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "WAVESUSDC", + "status": "TRADING", + "baseAsset": "WAVES", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "26900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCHABCTUSD", + "status": "TRADING", + "baseAsset": "BCHABC", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCHABCPAX", + "status": "TRADING", + "baseAsset": "BCHABC", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCHABCUSDC", + "status": "TRADING", + "baseAsset": "BCHABC", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCHSVTUSD", + "status": "BREAK", + "baseAsset": "BCHSV", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCHSVPAX", + "status": "BREAK", + "baseAsset": "BCHSV", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BCHSVUSDC", + "status": "BREAK", + "baseAsset": "BCHSV", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "1.3", + "multiplierDown": "0.7", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LTCTUSD", + "status": "TRADING", + "baseAsset": "LTC", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LTCPAX", + "status": "TRADING", + "baseAsset": "LTC", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "5300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "LTCUSDC", + "status": "TRADING", + "baseAsset": "LTC", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRXPAX", + "status": "TRADING", + "baseAsset": "TRX", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4068900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TRXUSDC", + "status": "TRADING", + "baseAsset": "TRX", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4984400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTTBTC", + "status": "TRADING", + "baseAsset": "BTT", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8020000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTTBNB", + "status": "TRADING", + "baseAsset": "BTT", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "1000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "385000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTTUSDT", + "status": "TRADING", + "baseAsset": "BTT", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "1000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1060000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BNBUSDS", + "status": "TRADING", + "baseAsset": "BNB", + "baseAssetPrecision": 8, + "quoteAsset": "USDS", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTCUSDS", + "status": "TRADING", + "baseAsset": "BTC", + "baseAssetPrecision": 8, + "quoteAsset": "USDS", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00000100", + "maxQty": "10000000.00000000", + "stepSize": "0.00000100" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "USDSUSDT", + "status": "TRADING", + "baseAsset": "USDS", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "153500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "USDSPAX", + "status": "TRADING", + "baseAsset": "USDS", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "156600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "USDSTUSD", + "status": "TRADING", + "baseAsset": "USDS", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "74500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "USDSUSDC", + "status": "TRADING", + "baseAsset": "USDS", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "232600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTTPAX", + "status": "TRADING", + "baseAsset": "BTT", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "1000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "95111900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTTTUSD", + "status": "TRADING", + "baseAsset": "BTT", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "1000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "78001500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BTTUSDC", + "status": "TRADING", + "baseAsset": "BTT", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "1000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "125000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONGBNB", + "status": "TRADING", + "baseAsset": "ONG", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "51900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONGBTC", + "status": "TRADING", + "baseAsset": "ONG", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1217900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONGUSDT", + "status": "TRADING", + "baseAsset": "ONG", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "656200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "HOTBNB", + "status": "TRADING", + "baseAsset": "HOT", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "1000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "71608100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "HOTUSDT", + "status": "TRADING", + "baseAsset": "HOT", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "1000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "241000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZILUSDT", + "status": "TRADING", + "baseAsset": "ZIL", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "29107600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZRXBNB", + "status": "TRADING", + "baseAsset": "ZRX", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "88200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZRXUSDT", + "status": "TRADING", + "baseAsset": "ZRX", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "412400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FETBNB", + "status": "TRADING", + "baseAsset": "FET", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1354700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FETBTC", + "status": "TRADING", + "baseAsset": "FET", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1669300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FETUSDT", + "status": "TRADING", + "baseAsset": "FET", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3243500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BATUSDT", + "status": "TRADING", + "baseAsset": "BAT", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2120100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XMRBNB", + "status": "TRADING", + "baseAsset": "XMR", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "XMRUSDT", + "status": "TRADING", + "baseAsset": "XMR", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZECBNB", + "status": "TRADING", + "baseAsset": "ZEC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZECUSDT", + "status": "TRADING", + "baseAsset": "ZEC", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "6700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZECPAX", + "status": "TRADING", + "baseAsset": "ZEC", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZECTUSD", + "status": "TRADING", + "baseAsset": "ZEC", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "600.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ZECUSDC", + "status": "TRADING", + "baseAsset": "ZEC", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "IOSTBNB", + "status": "TRADING", + "baseAsset": "IOST", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "1000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "5060500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "IOSTUSDT", + "status": "TRADING", + "baseAsset": "IOST", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "41402800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CELRBNB", + "status": "TRADING", + "baseAsset": "CELR", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "14310800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CELRBTC", + "status": "TRADING", + "baseAsset": "CELR", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "45282700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "CELRUSDT", + "status": "TRADING", + "baseAsset": "CELR", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "36144200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ADAPAX", + "status": "TRADING", + "baseAsset": "ADA", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "857400.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ADAUSDC", + "status": "TRADING", + "baseAsset": "ADA", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3042200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NEOPAX", + "status": "TRADING", + "baseAsset": "NEO", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "8800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NEOUSDC", + "status": "TRADING", + "baseAsset": "NEO", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "20500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DASHBNB", + "status": "TRADING", + "baseAsset": "DASH", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "DASHUSDT", + "status": "TRADING", + "baseAsset": "DASH", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.01000000", + "maxPrice": "10000000.00000000", + "tickSize": "0.01000000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00001000", + "maxQty": "10000000.00000000", + "stepSize": "0.00001000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "NANOUSDT", + "status": "TRADING", + "baseAsset": "NANO", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "279300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "OMGBNB", + "status": "TRADING", + "baseAsset": "OMG", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "10000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "40300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "OMGUSDT", + "status": "TRADING", + "baseAsset": "OMG", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "193200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "THETAUSDT", + "status": "TRADING", + "baseAsset": "THETA", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1618800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ENJUSDT", + "status": "TRADING", + "baseAsset": "ENJ", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "2366900.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MITHUSDT", + "status": "TRADING", + "baseAsset": "MITH", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "7316200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MATICBNB", + "status": "TRADING", + "baseAsset": "MATIC", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "37288200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MATICBTC", + "status": "TRADING", + "baseAsset": "MATIC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "349000000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "MATICUSDT", + "status": "TRADING", + "baseAsset": "MATIC", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "78345200.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ATOMBNB", + "status": "TRADING", + "baseAsset": "ATOM", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "33700.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ATOMBTC", + "status": "TRADING", + "baseAsset": "ATOM", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000010", + "maxPrice": "100000.00000000", + "tickSize": "0.00000010" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "90000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "588300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ATOMUSDT", + "status": "TRADING", + "baseAsset": "ATOM", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "208000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ATOMUSDC", + "status": "TRADING", + "baseAsset": "ATOM", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "3800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ATOMPAX", + "status": "TRADING", + "baseAsset": "ATOM", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "15300.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ATOMTUSD", + "status": "TRADING", + "baseAsset": "ATOM", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "15000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETCUSDC", + "status": "TRADING", + "baseAsset": "ETC", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "9800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETCPAX", + "status": "TRADING", + "baseAsset": "ETC", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "1000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ETCTUSD", + "status": "TRADING", + "baseAsset": "ETC", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00100000", + "maxPrice": "10000000.00000000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "10000000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "9100.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BATUSDC", + "status": "TRADING", + "baseAsset": "BAT", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "302000.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BATPAX", + "status": "TRADING", + "baseAsset": "BAT", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "186800.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "BATTUSD", + "status": "TRADING", + "baseAsset": "BAT", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00010000", + "maxPrice": "100000.00000000", + "tickSize": "0.00010000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "10000000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "85500.00000000", + "stepSize": "0.00000000" + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PHBBNB", + "status": "TRADING", + "baseAsset": "PHB", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PHBBTC", + "status": "TRADING", + "baseAsset": "PHB", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PHBUSDC", + "status": "TRADING", + "baseAsset": "PHB", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PHBTUSD", + "status": "TRADING", + "baseAsset": "PHB", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "PHBPAX", + "status": "TRADING", + "baseAsset": "PHB", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TFUELBNB", + "status": "TRADING", + "baseAsset": "TFUEL", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TFUELBTC", + "status": "TRADING", + "baseAsset": "TFUEL", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TFUELUSDT", + "status": "TRADING", + "baseAsset": "TFUEL", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TFUELUSDC", + "status": "TRADING", + "baseAsset": "TFUEL", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TFUELTUSD", + "status": "TRADING", + "baseAsset": "TFUEL", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "TFUELPAX", + "status": "TRADING", + "baseAsset": "TFUEL", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONEBNB", + "status": "TRADING", + "baseAsset": "ONE", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONEBTC", + "status": "TRADING", + "baseAsset": "ONE", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONEUSDT", + "status": "TRADING", + "baseAsset": "ONE", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONETUSD", + "status": "TRADING", + "baseAsset": "ONE", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONEPAX", + "status": "TRADING", + "baseAsset": "ONE", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "ONEUSDC", + "status": "TRADING", + "baseAsset": "ONE", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FTMBNB", + "status": "TRADING", + "baseAsset": "FTM", + "baseAssetPrecision": 8, + "quoteAsset": "BNB", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "10000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.10000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FTMBTC", + "status": "TRADING", + "baseAsset": "FTM", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000001", + "maxPrice": "100000.00000000", + "tickSize": "0.00000001" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "1.00000000", + "maxQty": "90000000.00000000", + "stepSize": "1.00000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FTMUSDT", + "status": "TRADING", + "baseAsset": "FTM", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FTMTUSD", + "status": "TRADING", + "baseAsset": "FTM", + "baseAssetPrecision": 8, + "quoteAsset": "TUSD", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FTMPAX", + "status": "TRADING", + "baseAsset": "FTM", + "baseAssetPrecision": 8, + "quoteAsset": "PAX", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + }, + { + "symbol": "FTMUSDC", + "status": "TRADING", + "baseAsset": "FTM", + "baseAssetPrecision": 8, + "quoteAsset": "USDC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "isSpotTradingAllowed": true, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00001000", + "maxPrice": "100000.00000000", + "tickSize": "0.00001000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.10000000", + "maxQty": "90000000.00000000", + "stepSize": "0.10000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { + "filterType": "ICEBERG_PARTS", + "limit": 10 + }, + { + "filterType": "MAX_NUM_ALGO_ORDERS", + "maxNumAlgoOrders": 5 + } + ] + } + ] + }, + "queryString": "", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v1/historicalTrades": { + "GET": [ + { + "data": { + "code": -2014, + "msg": "API-key format invalid." + }, + "queryString": "fromid=1337\u0026limit=5\u0026symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + }, + { + "data": { + "code": -2014, + "msg": "API-key format invalid." + }, + "queryString": "fromid=0\u0026limit=5\u0026symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + }, + { + "data": { + "code": -2014, + "msg": "API-key format invalid." + }, + "queryString": "limit=5\u0026symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v1/klines": { + "GET": [ + { + "data": [ + [ + 1560289800000, + "7881.84000000", + "7893.07000000", + "7875.00000000", + "7883.54000000", + "48.48529700", + 1560290099999, + "382349.29249228", + 834, + "26.54171500", + "209356.25914881", + "0" + ], + [ + 1560290100000, + "7885.23000000", + "7888.87000000", + "7878.02000000", + "7885.00000000", + "67.35000200", + 1560290399999, + "530915.10829860", + 842, + "41.30834600", + "325645.54788180", + "0" + ], + [ + 1560290400000, + "7883.90000000", + "7890.85000000", + "7874.59000000", + "7875.96000000", + "38.78600600", + 1560290699999, + "305845.05705121", + 738, + "12.07722800", + "95228.17170730", + "0" + ], + [ + 1560290700000, + "7874.61000000", + "7880.22000000", + "7873.00000000", + "7878.10000000", + "32.01931100", + 1560290999999, + "252191.69888947", + 634, + "19.45375600", + "153223.94584195", + "0" + ], + [ + 1560291000000, + "7876.46000000", + "7897.80000000", + "7875.00000000", + "7895.15000000", + "57.09996400", + 1560291299999, + "450163.99985188", + 1072, + "32.52785000", + "256489.35917382", + "0" + ], + [ + 1560291300000, + "7897.02000000", + "7900.00000000", + "7881.26000000", + "7892.98000000", + "36.86890600", + 1560291599999, + "290969.83727260", + 793, + "21.04945500", + "166142.18520686", + "0" + ], + [ + 1560291600000, + "7892.02000000", + "7905.04000000", + "7889.28000000", + "7905.00000000", + "35.37265200", + 1560291899999, + "279242.32341654", + 793, + "20.33734800", + "160562.95336440", + "0" + ], + [ + 1560291900000, + "7905.00000000", + "7909.29000000", + "7894.18000000", + "7900.96000000", + "51.66462800", + 1560292199999, + "408277.84852476", + 830, + "29.64769400", + "234290.68021458", + "0" + ], + [ + 1560292200000, + "7900.77000000", + "7906.52000000", + "7889.61000000", + "7896.68000000", + "45.26993800", + 1560292499999, + "357641.67054969", + 800, + "26.03810900", + "205699.79402493", + "0" + ], + [ + 1560292500000, + "7896.56000000", + "7903.00000000", + "7894.00000000", + "7902.66000000", + "44.63988800", + 1560292799999, + "352595.79732206", + 831, + "22.42822900", + "177163.31409709", + "0" + ], + [ + 1560292800000, + "7901.68000000", + "7905.72000000", + "7879.90000000", + "7886.98000000", + "116.18438300", + 1560293099999, + "916943.64246997", + 1282, + "51.26161500", + "404408.36174471", + "0" + ], + [ + 1560293100000, + "7885.45000000", + "7889.43000000", + "7877.31000000", + "7879.75000000", + "41.70249800", + 1560293399999, + "328738.15323699", + 753, + "22.28951500", + "175709.46569603", + "0" + ], + [ + 1560293400000, + "7879.77000000", + "7886.96000000", + "7869.37000000", + "7870.00000000", + "85.97570200", + 1560293699999, + "677186.81101133", + 927, + "55.44460500", + "436658.83891601", + "0" + ], + [ + 1560293700000, + "7870.00000000", + "7870.00000000", + "7863.49000000", + "7869.55000000", + "72.24212400", + 1560293999999, + "568442.90824311", + 764, + "53.28308100", + "419285.91751837", + "0" + ], + [ + 1560294000000, + "7867.93000000", + "7929.78000000", + "7867.20000000", + "7896.14000000", + "282.87952800", + 1560294299999, + "2229697.40509594", + 1487, + "244.70839500", + "1928487.97570286", + "0" + ], + [ + 1560294300000, + "7898.81000000", + "7902.13000000", + "7885.58000000", + "7888.42000000", + "65.27782600", + 1560294599999, + "515260.60356970", + 743, + "37.86963800", + "298905.24330721", + "0" + ], + [ + 1560294600000, + "7891.60000000", + "7894.72000000", + "7880.64000000", + "7883.61000000", + "57.53969200", + 1560294899999, + "453829.17707837", + 669, + "26.13476400", + "206142.58590609", + "0" + ], + [ + 1560294900000, + "7884.22000000", + "7900.10000000", + "7882.81000000", + "7891.07000000", + "85.75378900", + 1560295199999, + "676735.70630014", + 908, + "52.85499000", + "417113.35460680", + "0" + ], + [ + 1560295200000, + "7892.17000000", + "7905.97000000", + "7887.98000000", + "7905.97000000", + "124.03521600", + 1560295499999, + "979680.45514714", + 892, + "40.74581600", + "321816.22842545", + "0" + ], + [ + 1560295500000, + "7904.91000000", + "7906.85000000", + "7894.12000000", + "7904.00000000", + "49.88850200", + 1560295799999, + "394182.50097019", + 658, + "24.09410100", + "190373.54848526", + "0" + ], + [ + 1560295800000, + "7904.00000000", + "7908.20000000", + "7895.66000000", + "7897.35000000", + "58.66883700", + 1560296099999, + "463609.99079062", + 778, + "24.77859000", + "195812.97883943", + "0" + ], + [ + 1560296100000, + "7898.12000000", + "7904.32000000", + "7886.80000000", + "7891.37000000", + "45.37032000", + 1560296399999, + "358173.04332748", + 654, + "26.56629600", + "209713.84022121", + "0" + ], + [ + 1560296400000, + "7891.48000000", + "7894.97000000", + "7881.31000000", + "7882.90000000", + "36.15788200", + 1560296699999, + "285239.28461983", + 553, + "19.52315300", + "154024.35607127", + "0" + ], + [ + 1560296700000, + "7883.57000000", + "7895.01000000", + "7883.57000000", + "7885.63000000", + "64.60177300", + 1560296999999, + "509686.52481889", + 548, + "42.80302800", + "337717.93005312", + "0" + ] + ], + "queryString": "interval=5m\u0026limit=24\u0026symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v1/ticker/24hr": { + "GET": [ + { + "data": [ + { + "symbol": "ETHBTC", + "priceChange": "0.00015200", + "priceChangePercent": "0.492", + "weightedAvgPrice": "0.03090567", + "prevClosePrice": "0.03089900", + "lastPrice": "0.03105300", + "lastQty": "0.03900000", + "bidPrice": "0.03104200", + "bidQty": "1.61700000", + "askPrice": "0.03105300", + "askQty": "8.39600000", + "openPrice": "0.03090100", + "highPrice": "0.03124000", + "lowPrice": "0.03069600", + "volume": "129150.93400000", + "quoteVolume": "3991.49565303", + "openTime": 1560210242200, + "closeTime": 1560296642200, + "firstId": 127054257, + "lastId": 127184979, + "count": 130723 + }, + { + "symbol": "LTCBTC", + "priceChange": "0.00104700", + "priceChangePercent": "6.475", + "weightedAvgPrice": "0.01671812", + "prevClosePrice": "0.01617000", + "lastPrice": "0.01721700", + "lastQty": "0.12000000", + "bidPrice": "0.01721300", + "bidQty": "0.58000000", + "askPrice": "0.01721700", + "askQty": "1.91000000", + "openPrice": "0.01617000", + "highPrice": "0.01752700", + "lowPrice": "0.01594300", + "volume": "514301.53000000", + "quoteVolume": "8598.15583782", + "openTime": 1560210241807, + "closeTime": 1560296641807, + "firstId": 27757801, + "lastId": 27905612, + "count": 147812 + }, + { + "symbol": "BNBBTC", + "priceChange": "0.00003740", + "priceChangePercent": "0.933", + "weightedAvgPrice": "0.00400785", + "prevClosePrice": "0.00401100", + "lastPrice": "0.00404770", + "lastQty": "0.31000000", + "bidPrice": "0.00404700", + "bidQty": "11.09000000", + "askPrice": "0.00404770", + "askQty": "11.29000000", + "openPrice": "0.00401030", + "highPrice": "0.00404780", + "lowPrice": "0.00396120", + "volume": "2500696.35000000", + "quoteVolume": "10022.42483221", + "openTime": 1560210241963, + "closeTime": 1560296641963, + "firstId": 49608008, + "lastId": 49690881, + "count": 82874 + }, + { + "symbol": "NEOBTC", + "priceChange": "0.00001800", + "priceChangePercent": "1.171", + "weightedAvgPrice": "0.00153873", + "prevClosePrice": "0.00153700", + "lastPrice": "0.00155500", + "lastQty": "54.36000000", + "bidPrice": "0.00155500", + "bidQty": "44.70000000", + "askPrice": "0.00155600", + "askQty": "297.12000000", + "openPrice": "0.00153700", + "highPrice": "0.00157500", + "lowPrice": "0.00151300", + "volume": "349009.48000000", + "quoteVolume": "537.03121117", + "openTime": 1560210242365, + "closeTime": 1560296642365, + "firstId": 26947014, + "lastId": 26964071, + "count": 17058 + }, + { + "symbol": "QTUMETH", + "priceChange": "-0.00016400", + "priceChangePercent": "-1.263", + "weightedAvgPrice": "0.01292534", + "prevClosePrice": "0.01298000", + "lastPrice": "0.01282000", + "lastQty": "1.00000000", + "bidPrice": "0.01277300", + "bidQty": "3.95000000", + "askPrice": "0.01282000", + "askQty": "16.44000000", + "openPrice": "0.01298400", + "highPrice": "0.01336000", + "lowPrice": "0.01269100", + "volume": "64460.16000000", + "quoteVolume": "833.16968650", + "openTime": 1560210242001, + "closeTime": 1560296642001, + "firstId": 3591805, + "lastId": 3593885, + "count": 2081 + }, + { + "symbol": "EOSETH", + "priceChange": "-0.00021200", + "priceChangePercent": "-0.815", + "weightedAvgPrice": "0.02589703", + "prevClosePrice": "0.02599100", + "lastPrice": "0.02579800", + "lastQty": "10.95000000", + "bidPrice": "0.02580000", + "bidQty": "0.77000000", + "askPrice": "0.02580300", + "askQty": "30.88000000", + "openPrice": "0.02601000", + "highPrice": "0.02624600", + "lowPrice": "0.02566800", + "volume": "189008.84000000", + "quoteVolume": "4894.76829344", + "openTime": 1560210241817, + "closeTime": 1560296641817, + "firstId": 15155696, + "lastId": 15163777, + "count": 8082 + }, + { + "symbol": "SNTETH", + "priceChange": "0.00000075", + "priceChangePercent": "0.621", + "weightedAvgPrice": "0.00012203", + "prevClosePrice": "0.00012087", + "lastPrice": "0.00012161", + "lastQty": "3506.00000000", + "bidPrice": "0.00012129", + "bidQty": "8230.00000000", + "askPrice": "0.00012177", + "askQty": "7413.00000000", + "openPrice": "0.00012086", + "highPrice": "0.00012576", + "lowPrice": "0.00011935", + "volume": "1619318.00000000", + "quoteVolume": "197.60326762", + "openTime": 1560210241181, + "closeTime": 1560296641181, + "firstId": 2179270, + "lastId": 2180436, + "count": 1167 + }, + { + "symbol": "BNTETH", + "priceChange": "0.00002800", + "priceChangePercent": "0.981", + "weightedAvgPrice": "0.00284127", + "prevClosePrice": "0.00284800", + "lastPrice": "0.00288200", + "lastQty": "92.00000000", + "bidPrice": "0.00287800", + "bidQty": "428.00000000", + "askPrice": "0.00288200", + "askQty": "1987.00000000", + "openPrice": "0.00285400", + "highPrice": "0.00288300", + "lowPrice": "0.00280800", + "volume": "243660.13000000", + "quoteVolume": "692.30447894", + "openTime": 1560210241719, + "closeTime": 1560296641719, + "firstId": 921359, + "lastId": 922549, + "count": 1191 + }, + { + "symbol": "BCCBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.07908100", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998596356, + "closeTime": 1558084996356, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "GASBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00039292", + "prevClosePrice": "0.00039600", + "lastPrice": "0.00039600", + "lastQty": "477.44000000", + "bidPrice": "0.00039500", + "bidQty": "1671.60000000", + "askPrice": "0.00039700", + "askQty": "206.04000000", + "openPrice": "0.00039600", + "highPrice": "0.00040100", + "lowPrice": "0.00038500", + "volume": "151310.29000000", + "quoteVolume": "59.45228817", + "openTime": 1560210242353, + "closeTime": 1560296642353, + "firstId": 5210558, + "lastId": 5214567, + "count": 4010 + }, + { + "symbol": "BNBETH", + "priceChange": "0.00063900", + "priceChangePercent": "0.492", + "weightedAvgPrice": "0.12963651", + "prevClosePrice": "0.12969200", + "lastPrice": "0.13040000", + "lastQty": "0.04000000", + "bidPrice": "0.13026500", + "bidQty": "6.87000000", + "askPrice": "0.13040000", + "askQty": "16.84000000", + "openPrice": "0.12976100", + "highPrice": "0.13125000", + "lowPrice": "0.12841600", + "volume": "71426.40000000", + "quoteVolume": "9259.46897511", + "openTime": 1560210239869, + "closeTime": 1560296639869, + "firstId": 13474298, + "lastId": 13490901, + "count": 16604 + }, + { + "symbol": "BTCUSDT", + "priceChange": "-80.09000000", + "priceChangePercent": "-1.005", + "weightedAvgPrice": "7847.79388595", + "prevClosePrice": "7964.64000000", + "lastPrice": "7886.47000000", + "lastQty": "0.18337000", + "bidPrice": "7885.11000000", + "bidQty": "0.08648600", + "askPrice": "7887.47000000", + "askQty": "0.09149000", + "openPrice": "7966.56000000", + "highPrice": "8010.00000000", + "lowPrice": "7692.23000000", + "volume": "30439.44903900", + "quoteVolume": "238882522.05987985", + "openTime": 1560210242340, + "closeTime": 1560296642340, + "firstId": 134002942, + "lastId": 134313369, + "count": 310428 + }, + { + "symbol": "ETHUSDT", + "priceChange": "-1.25000000", + "priceChangePercent": "-0.508", + "weightedAvgPrice": "242.34131800", + "prevClosePrice": "246.13000000", + "lastPrice": "244.86000000", + "lastQty": "0.04524000", + "bidPrice": "244.76000000", + "bidQty": "4.45732000", + "askPrice": "244.88000000", + "askQty": "0.45741000", + "openPrice": "246.11000000", + "highPrice": "247.80000000", + "lowPrice": "236.49000000", + "volume": "238959.65780000", + "quoteVolume": "57909798.42086450", + "openTime": 1560210242308, + "closeTime": 1560296642308, + "firstId": 79293778, + "lastId": 79427408, + "count": 133631 + }, + { + "symbol": "HSRBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00041400", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998596357, + "closeTime": 1558084996357, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "OAXETH", + "priceChange": "0.00003860", + "priceChangePercent": "4.412", + "weightedAvgPrice": "0.00093744", + "prevClosePrice": "0.00087100", + "lastPrice": "0.00091340", + "lastQty": "345.00000000", + "bidPrice": "0.00090880", + "bidQty": "53.00000000", + "askPrice": "0.00091450", + "askQty": "314.00000000", + "openPrice": "0.00087480", + "highPrice": "0.00102220", + "lowPrice": "0.00082690", + "volume": "819183.00000000", + "quoteVolume": "767.93268970", + "openTime": 1560210242345, + "closeTime": 1560296642345, + "firstId": 1230567, + "lastId": 1233261, + "count": 2695 + }, + { + "symbol": "DNTETH", + "priceChange": "0.00000118", + "priceChangePercent": "1.613", + "weightedAvgPrice": "0.00007304", + "prevClosePrice": "0.00007286", + "lastPrice": "0.00007433", + "lastQty": "136.00000000", + "bidPrice": "0.00007428", + "bidQty": "60907.00000000", + "askPrice": "0.00007498", + "askQty": "6886.00000000", + "openPrice": "0.00007315", + "highPrice": "0.00007593", + "lowPrice": "0.00007091", + "volume": "1873284.00000000", + "quoteVolume": "136.82393602", + "openTime": 1560210234786, + "closeTime": 1560296634786, + "firstId": 1482380, + "lastId": 1482908, + "count": 529 + }, + { + "symbol": "MCOETH", + "priceChange": "0.00212900", + "priceChangePercent": "8.516", + "weightedAvgPrice": "0.02645715", + "prevClosePrice": "0.02505000", + "lastPrice": "0.02712900", + "lastQty": "22.85000000", + "bidPrice": "0.02697800", + "bidQty": "9.66000000", + "askPrice": "0.02712000", + "askQty": "1.28000000", + "openPrice": "0.02500000", + "highPrice": "0.02807100", + "lowPrice": "0.02498500", + "volume": "44966.87000000", + "quoteVolume": "1189.69509856", + "openTime": 1560210242110, + "closeTime": 1560296642110, + "firstId": 1667413, + "lastId": 1671898, + "count": 4486 + }, + { + "symbol": "ICNETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00166300", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998596358, + "closeTime": 1558084996358, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "MCOBTC", + "priceChange": "0.00006900", + "priceChangePercent": "8.949", + "weightedAvgPrice": "0.00082091", + "prevClosePrice": "0.00077300", + "lastPrice": "0.00084000", + "lastQty": "5.00000000", + "bidPrice": "0.00083700", + "bidQty": "138.28000000", + "askPrice": "0.00084000", + "askQty": "4.42000000", + "openPrice": "0.00077100", + "highPrice": "0.00086700", + "lowPrice": "0.00077000", + "volume": "340047.00000000", + "quoteVolume": "279.14906756", + "openTime": 1560210242297, + "closeTime": 1560296642297, + "firstId": 7832972, + "lastId": 7846921, + "count": 13950 + }, + { + "symbol": "WTCBTC", + "priceChange": "0.00000520", + "priceChangePercent": "1.899", + "weightedAvgPrice": "0.00027704", + "prevClosePrice": "0.00027400", + "lastPrice": "0.00027910", + "lastQty": "88.93000000", + "bidPrice": "0.00027890", + "bidQty": "69.87000000", + "askPrice": "0.00027920", + "askQty": "76.19000000", + "openPrice": "0.00027390", + "highPrice": "0.00028500", + "lowPrice": "0.00027180", + "volume": "1107228.23000000", + "quoteVolume": "306.74562803", + "openTime": 1560210241733, + "closeTime": 1560296641733, + "firstId": 9902375, + "lastId": 9911179, + "count": 8805 + }, + { + "symbol": "WTCETH", + "priceChange": "0.00013400", + "priceChangePercent": "1.511", + "weightedAvgPrice": "0.00895546", + "prevClosePrice": "0.00887000", + "lastPrice": "0.00900400", + "lastQty": "1.36000000", + "bidPrice": "0.00897000", + "bidQty": "2.46000000", + "askPrice": "0.00900300", + "askQty": "5.85000000", + "openPrice": "0.00887000", + "highPrice": "0.00922100", + "lowPrice": "0.00883600", + "volume": "48612.76000000", + "quoteVolume": "435.34981892", + "openTime": 1560210242194, + "closeTime": 1560296642194, + "firstId": 2679645, + "lastId": 2680798, + "count": 1154 + }, + { + "symbol": "LRCBTC", + "priceChange": "-0.00000029", + "priceChangePercent": "-3.341", + "weightedAvgPrice": "0.00000829", + "prevClosePrice": "0.00000868", + "lastPrice": "0.00000839", + "lastQty": "341.00000000", + "bidPrice": "0.00000839", + "bidQty": "3419.00000000", + "askPrice": "0.00000840", + "askQty": "31956.00000000", + "openPrice": "0.00000868", + "highPrice": "0.00000873", + "lowPrice": "0.00000808", + "volume": "7560191.00000000", + "quoteVolume": "62.69600662", + "openTime": 1560210230584, + "closeTime": 1560296630584, + "firstId": 4590594, + "lastId": 4594678, + "count": 4085 + }, + { + "symbol": "LRCETH", + "priceChange": "-0.00001001", + "priceChangePercent": "-3.552", + "weightedAvgPrice": "0.00026811", + "prevClosePrice": "0.00028072", + "lastPrice": "0.00027183", + "lastQty": "343.00000000", + "bidPrice": "0.00026992", + "bidQty": "838.00000000", + "askPrice": "0.00027211", + "askQty": "1101.00000000", + "openPrice": "0.00028184", + "highPrice": "0.00028246", + "lowPrice": "0.00026172", + "volume": "650259.00000000", + "quoteVolume": "174.34401360", + "openTime": 1560210240757, + "closeTime": 1560296640757, + "firstId": 1642858, + "lastId": 1643965, + "count": 1108 + }, + { + "symbol": "QTUMBTC", + "priceChange": "-0.00000200", + "priceChangePercent": "-0.500", + "weightedAvgPrice": "0.00039913", + "prevClosePrice": "0.00040000", + "lastPrice": "0.00039800", + "lastQty": "1901.77000000", + "bidPrice": "0.00039700", + "bidQty": "213.92000000", + "askPrice": "0.00039800", + "askQty": "1446.60000000", + "openPrice": "0.00040000", + "highPrice": "0.00041300", + "lowPrice": "0.00039300", + "volume": "423019.61000000", + "quoteVolume": "168.83809369", + "openTime": 1560210242039, + "closeTime": 1560296642039, + "firstId": 9862161, + "lastId": 9868847, + "count": 6687 + }, + { + "symbol": "YOYOBTC", + "priceChange": "0.00000013", + "priceChangePercent": "4.050", + "weightedAvgPrice": "0.00000329", + "prevClosePrice": "0.00000323", + "lastPrice": "0.00000334", + "lastQty": "2928.00000000", + "bidPrice": "0.00000332", + "bidQty": "2882.00000000", + "askPrice": "0.00000333", + "askQty": "9354.00000000", + "openPrice": "0.00000321", + "highPrice": "0.00000345", + "lowPrice": "0.00000318", + "volume": "20563763.00000000", + "quoteVolume": "67.73176743", + "openTime": 1560210236448, + "closeTime": 1560296636448, + "firstId": 4366438, + "lastId": 4370171, + "count": 3734 + }, + { + "symbol": "OMGBTC", + "priceChange": "-0.00000200", + "priceChangePercent": "-0.791", + "weightedAvgPrice": "0.00025000", + "prevClosePrice": "0.00025300", + "lastPrice": "0.00025100", + "lastQty": "12.44000000", + "bidPrice": "0.00025000", + "bidQty": "2747.29000000", + "askPrice": "0.00025100", + "askQty": "4986.18000000", + "openPrice": "0.00025300", + "highPrice": "0.00025400", + "lowPrice": "0.00024500", + "volume": "599909.58000000", + "quoteVolume": "149.97758385", + "openTime": 1560210240810, + "closeTime": 1560296640810, + "firstId": 9755698, + "lastId": 9760139, + "count": 4442 + }, + { + "symbol": "OMGETH", + "priceChange": "-0.00016200", + "priceChangePercent": "-1.972", + "weightedAvgPrice": "0.00806463", + "prevClosePrice": "0.00821700", + "lastPrice": "0.00805500", + "lastQty": "3.60000000", + "bidPrice": "0.00804800", + "bidQty": "2.93000000", + "askPrice": "0.00807200", + "askQty": "3.60000000", + "openPrice": "0.00821700", + "highPrice": "0.00823000", + "lowPrice": "0.00794700", + "volume": "143961.83000000", + "quoteVolume": "1160.99857784", + "openTime": 1560210241816, + "closeTime": 1560296641816, + "firstId": 3348268, + "lastId": 3350350, + "count": 2083 + }, + { + "symbol": "ZRXBTC", + "priceChange": "-0.00000013", + "priceChangePercent": "-0.315", + "weightedAvgPrice": "0.00004095", + "prevClosePrice": "0.00004118", + "lastPrice": "0.00004113", + "lastQty": "1181.00000000", + "bidPrice": "0.00004112", + "bidQty": "1141.00000000", + "askPrice": "0.00004113", + "askQty": "450.00000000", + "openPrice": "0.00004126", + "highPrice": "0.00004135", + "lowPrice": "0.00004055", + "volume": "3902287.00000000", + "quoteVolume": "159.79234638", + "openTime": 1560210241770, + "closeTime": 1560296641770, + "firstId": 11914440, + "lastId": 11920946, + "count": 6507 + }, + { + "symbol": "ZRXETH", + "priceChange": "-0.00000756", + "priceChangePercent": "-0.567", + "weightedAvgPrice": "0.00132220", + "prevClosePrice": "0.00133228", + "lastPrice": "0.00132632", + "lastQty": "8.00000000", + "bidPrice": "0.00132400", + "bidQty": "6095.00000000", + "askPrice": "0.00132636", + "askQty": "1861.00000000", + "openPrice": "0.00133388", + "highPrice": "0.00136000", + "lowPrice": "0.00130925", + "volume": "491642.00000000", + "quoteVolume": "650.04899345", + "openTime": 1560210240537, + "closeTime": 1560296640537, + "firstId": 3507829, + "lastId": 3509417, + "count": 1589 + }, + { + "symbol": "STRATBTC", + "priceChange": "-0.00000040", + "priceChangePercent": "-0.333", + "weightedAvgPrice": "0.00011976", + "prevClosePrice": "0.00012040", + "lastPrice": "0.00011970", + "lastQty": "49.99000000", + "bidPrice": "0.00011960", + "bidQty": "248.55000000", + "askPrice": "0.00011970", + "askQty": "58.70000000", + "openPrice": "0.00012010", + "highPrice": "0.00012190", + "lowPrice": "0.00011770", + "volume": "720978.13000000", + "quoteVolume": "86.34414101", + "openTime": 1560210240546, + "closeTime": 1560296640546, + "firstId": 8033229, + "lastId": 8037702, + "count": 4474 + }, + { + "symbol": "STRATETH", + "priceChange": "-0.00004700", + "priceChangePercent": "-1.208", + "weightedAvgPrice": "0.00387753", + "prevClosePrice": "0.00388500", + "lastPrice": "0.00384300", + "lastQty": "36.23000000", + "bidPrice": "0.00384500", + "bidQty": "251.47000000", + "askPrice": "0.00386500", + "askQty": "234.00000000", + "openPrice": "0.00389000", + "highPrice": "0.00394800", + "lowPrice": "0.00382500", + "volume": "41218.38000000", + "quoteVolume": "159.82561490", + "openTime": 1560210241809, + "closeTime": 1560296641809, + "firstId": 1238235, + "lastId": 1238975, + "count": 741 + }, + { + "symbol": "SNGLSBTC", + "priceChange": "0.00000008", + "priceChangePercent": "3.509", + "weightedAvgPrice": "0.00000238", + "prevClosePrice": "0.00000227", + "lastPrice": "0.00000236", + "lastQty": "159037.00000000", + "bidPrice": "0.00000236", + "bidQty": "148725.00000000", + "askPrice": "0.00000238", + "askQty": "32797.00000000", + "openPrice": "0.00000228", + "highPrice": "0.00000249", + "lowPrice": "0.00000225", + "volume": "25483511.00000000", + "quoteVolume": "60.59798108", + "openTime": 1560210240941, + "closeTime": 1560296640941, + "firstId": 4325983, + "lastId": 4329865, + "count": 3883 + }, + { + "symbol": "SNGLSETH", + "priceChange": "0.00000337", + "priceChangePercent": "4.583", + "weightedAvgPrice": "0.00007736", + "prevClosePrice": "0.00007364", + "lastPrice": "0.00007691", + "lastQty": "179.00000000", + "bidPrice": "0.00007599", + "bidQty": "1621.00000000", + "askPrice": "0.00007684", + "askQty": "459.00000000", + "openPrice": "0.00007354", + "highPrice": "0.00008054", + "lowPrice": "0.00007310", + "volume": "2111144.00000000", + "quoteVolume": "163.31190790", + "openTime": 1560210239190, + "closeTime": 1560296639190, + "firstId": 1163775, + "lastId": 1164550, + "count": 776 + }, + { + "symbol": "BQXBTC", + "priceChange": "0.00000050", + "priceChangePercent": "2.894", + "weightedAvgPrice": "0.00001791", + "prevClosePrice": "0.00001727", + "lastPrice": "0.00001778", + "lastQty": "159.00000000", + "bidPrice": "0.00001774", + "bidQty": "113.00000000", + "askPrice": "0.00001778", + "askQty": "66.00000000", + "openPrice": "0.00001728", + "highPrice": "0.00001868", + "lowPrice": "0.00001700", + "volume": "9571328.00000000", + "quoteVolume": "171.44754189", + "openTime": 1560210242324, + "closeTime": 1560296642324, + "firstId": 6001241, + "lastId": 6010667, + "count": 9427 + }, + { + "symbol": "BQXETH", + "priceChange": "0.00001010", + "priceChangePercent": "1.803", + "weightedAvgPrice": "0.00057860", + "prevClosePrice": "0.00056050", + "lastPrice": "0.00057020", + "lastQty": "1458.00000000", + "bidPrice": "0.00057030", + "bidQty": "113.00000000", + "askPrice": "0.00057380", + "askQty": "394.00000000", + "openPrice": "0.00056010", + "highPrice": "0.00060530", + "lowPrice": "0.00055280", + "volume": "322112.00000000", + "quoteVolume": "186.37477570", + "openTime": 1560210240832, + "closeTime": 1560296640832, + "firstId": 1261881, + "lastId": 1262992, + "count": 1112 + }, + { + "symbol": "KNCBTC", + "priceChange": "0.00000188", + "priceChangePercent": "5.587", + "weightedAvgPrice": "0.00003455", + "prevClosePrice": "0.00003366", + "lastPrice": "0.00003553", + "lastQty": "534.00000000", + "bidPrice": "0.00003550", + "bidQty": "187.00000000", + "askPrice": "0.00003553", + "askQty": "464.00000000", + "openPrice": "0.00003365", + "highPrice": "0.00003609", + "lowPrice": "0.00003329", + "volume": "2797127.00000000", + "quoteVolume": "96.62843017", + "openTime": 1560210241405, + "closeTime": 1560296641405, + "firstId": 5095530, + "lastId": 5101712, + "count": 6183 + }, + { + "symbol": "KNCETH", + "priceChange": "0.00005400", + "priceChangePercent": "4.963", + "weightedAvgPrice": "0.00111681", + "prevClosePrice": "0.00108910", + "lastPrice": "0.00114210", + "lastQty": "975.00000000", + "bidPrice": "0.00114150", + "bidQty": "1712.00000000", + "askPrice": "0.00114480", + "askQty": "120.00000000", + "openPrice": "0.00108810", + "highPrice": "0.00116080", + "lowPrice": "0.00108190", + "volume": "264204.00000000", + "quoteVolume": "295.06698640", + "openTime": 1560210241059, + "closeTime": 1560296641059, + "firstId": 2154131, + "lastId": 2155333, + "count": 1203 + }, + { + "symbol": "FUNBTC", + "priceChange": "-0.00000004", + "priceChangePercent": "-4.878", + "weightedAvgPrice": "0.00000080", + "prevClosePrice": "0.00000082", + "lastPrice": "0.00000078", + "lastQty": "214233.00000000", + "bidPrice": "0.00000077", + "bidQty": "2417010.00000000", + "askPrice": "0.00000078", + "askQty": "322941.00000000", + "openPrice": "0.00000082", + "highPrice": "0.00000083", + "lowPrice": "0.00000076", + "volume": "54646905.00000000", + "quoteVolume": "43.48782924", + "openTime": 1560210230586, + "closeTime": 1560296630586, + "firstId": 5586495, + "lastId": 5588481, + "count": 1987 + }, + { + "symbol": "FUNETH", + "priceChange": "-0.00000152", + "priceChangePercent": "-5.736", + "weightedAvgPrice": "0.00002549", + "prevClosePrice": "0.00002616", + "lastPrice": "0.00002498", + "lastQty": "562.00000000", + "bidPrice": "0.00002479", + "bidQty": "10513.00000000", + "askPrice": "0.00002498", + "askQty": "8089.00000000", + "openPrice": "0.00002650", + "highPrice": "0.00002669", + "lowPrice": "0.00002434", + "volume": "37047505.00000000", + "quoteVolume": "944.19988859", + "openTime": 1560210239801, + "closeTime": 1560296639801, + "firstId": 1659933, + "lastId": 1661347, + "count": 1415 + }, + { + "symbol": "SNMBTC", + "priceChange": "0.00000012", + "priceChangePercent": "3.380", + "weightedAvgPrice": "0.00000357", + "prevClosePrice": "0.00000355", + "lastPrice": "0.00000367", + "lastQty": "4182.00000000", + "bidPrice": "0.00000367", + "bidQty": "8133.00000000", + "askPrice": "0.00000368", + "askQty": "2254.00000000", + "openPrice": "0.00000355", + "highPrice": "0.00000372", + "lowPrice": "0.00000339", + "volume": "22586348.00000000", + "quoteVolume": "80.59334586", + "openTime": 1560210241131, + "closeTime": 1560296641131, + "firstId": 3523254, + "lastId": 3528715, + "count": 5462 + }, + { + "symbol": "SNMETH", + "priceChange": "0.00000289", + "priceChangePercent": "2.510", + "weightedAvgPrice": "0.00011544", + "prevClosePrice": "0.00011508", + "lastPrice": "0.00011804", + "lastQty": "86.00000000", + "bidPrice": "0.00011791", + "bidQty": "8133.00000000", + "askPrice": "0.00011867", + "askQty": "215.00000000", + "openPrice": "0.00011515", + "highPrice": "0.00012045", + "lowPrice": "0.00011034", + "volume": "2537020.00000000", + "quoteVolume": "292.88486052", + "openTime": 1560210242288, + "closeTime": 1560296642288, + "firstId": 969963, + "lastId": 971920, + "count": 1958 + }, + { + "symbol": "NEOETH", + "priceChange": "0.00029700", + "priceChangePercent": "0.596", + "weightedAvgPrice": "0.04981716", + "prevClosePrice": "0.04981100", + "lastPrice": "0.05010100", + "lastQty": "7.97000000", + "bidPrice": "0.05006000", + "bidQty": "0.39000000", + "askPrice": "0.05018000", + "askQty": "164.28000000", + "openPrice": "0.04980400", + "highPrice": "0.05094900", + "lowPrice": "0.04896100", + "volume": "28677.23000000", + "quoteVolume": "1428.61819190", + "openTime": 1560210242365, + "closeTime": 1560296642365, + "firstId": 7656571, + "lastId": 7659639, + "count": 3069 + }, + { + "symbol": "IOTABTC", + "priceChange": "-0.00000026", + "priceChangePercent": "-0.484", + "weightedAvgPrice": "0.00005315", + "prevClosePrice": "0.00005387", + "lastPrice": "0.00005350", + "lastQty": "337.00000000", + "bidPrice": "0.00005347", + "bidQty": "2250.00000000", + "askPrice": "0.00005350", + "askQty": "1562.00000000", + "openPrice": "0.00005376", + "highPrice": "0.00005396", + "lowPrice": "0.00005250", + "volume": "5150101.00000000", + "quoteVolume": "273.73349618", + "openTime": 1560210242327, + "closeTime": 1560296642327, + "firstId": 18580088, + "lastId": 18590852, + "count": 10765 + }, + { + "symbol": "IOTAETH", + "priceChange": "-0.00001957", + "priceChangePercent": "-1.123", + "weightedAvgPrice": "0.00171979", + "prevClosePrice": "0.00173606", + "lastPrice": "0.00172314", + "lastQty": "7.00000000", + "bidPrice": "0.00172083", + "bidQty": "1133.00000000", + "askPrice": "0.00172575", + "askQty": "144.00000000", + "openPrice": "0.00174271", + "highPrice": "0.00174313", + "lowPrice": "0.00169500", + "volume": "371813.00000000", + "quoteVolume": "639.44166338", + "openTime": 1560210241005, + "closeTime": 1560296641005, + "firstId": 5468943, + "lastId": 5470707, + "count": 1765 + }, + { + "symbol": "LINKBTC", + "priceChange": "-0.00000593", + "priceChangePercent": "-3.991", + "weightedAvgPrice": "0.00014535", + "prevClosePrice": "0.00014871", + "lastPrice": "0.00014266", + "lastQty": "41.00000000", + "bidPrice": "0.00014260", + "bidQty": "268.00000000", + "askPrice": "0.00014267", + "askQty": "661.00000000", + "openPrice": "0.00014859", + "highPrice": "0.00015200", + "lowPrice": "0.00014057", + "volume": "6349418.00000000", + "quoteVolume": "922.86768905", + "openTime": 1560210240936, + "closeTime": 1560296640936, + "firstId": 11348226, + "lastId": 11381826, + "count": 33601 + }, + { + "symbol": "LINKETH", + "priceChange": "-0.00023228", + "priceChangePercent": "-4.815", + "weightedAvgPrice": "0.00467012", + "prevClosePrice": "0.00482415", + "lastPrice": "0.00459200", + "lastQty": "40.00000000", + "bidPrice": "0.00459201", + "bidQty": "2266.00000000", + "askPrice": "0.00460213", + "askQty": "1.00000000", + "openPrice": "0.00482428", + "highPrice": "0.00492263", + "lowPrice": "0.00454546", + "volume": "1030757.00000000", + "quoteVolume": "4813.76122754", + "openTime": 1560210242231, + "closeTime": 1560296642231, + "firstId": 2862059, + "lastId": 2869630, + "count": 7572 + }, + { + "symbol": "XVGBTC", + "priceChange": "-0.00000002", + "priceChangePercent": "-1.739", + "weightedAvgPrice": "0.00000115", + "prevClosePrice": "0.00000114", + "lastPrice": "0.00000113", + "lastQty": "54711.00000000", + "bidPrice": "0.00000113", + "bidQty": "4408637.00000000", + "askPrice": "0.00000114", + "askQty": "344267.00000000", + "openPrice": "0.00000115", + "highPrice": "0.00000117", + "lowPrice": "0.00000113", + "volume": "136811687.00000000", + "quoteVolume": "156.88872673", + "openTime": 1560210235980, + "closeTime": 1560296635980, + "firstId": 20129264, + "lastId": 20132344, + "count": 3081 + }, + { + "symbol": "XVGETH", + "priceChange": "-0.00000032", + "priceChangePercent": "-0.862", + "weightedAvgPrice": "0.00003718", + "prevClosePrice": "0.00003708", + "lastPrice": "0.00003680", + "lastQty": "4849.00000000", + "bidPrice": "0.00003663", + "bidQty": "578.00000000", + "askPrice": "0.00003680", + "askQty": "6535.00000000", + "openPrice": "0.00003712", + "highPrice": "0.00003755", + "lowPrice": "0.00003657", + "volume": "8673507.00000000", + "quoteVolume": "322.44859371", + "openTime": 1560210241135, + "closeTime": 1560296641135, + "firstId": 6117073, + "lastId": 6118193, + "count": 1121 + }, + { + "symbol": "SALTBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00004250", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998596365, + "closeTime": 1558084996365, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "SALTETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00113800", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998596365, + "closeTime": 1558084996365, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "MDABTC", + "priceChange": "-0.00000266", + "priceChangePercent": "-1.965", + "weightedAvgPrice": "0.00013338", + "prevClosePrice": "0.00013533", + "lastPrice": "0.00013268", + "lastQty": "45.00000000", + "bidPrice": "0.00013255", + "bidQty": "40.00000000", + "askPrice": "0.00013298", + "askQty": "17.00000000", + "openPrice": "0.00013534", + "highPrice": "0.00013693", + "lowPrice": "0.00013077", + "volume": "938136.00000000", + "quoteVolume": "125.13010446", + "openTime": 1560210241557, + "closeTime": 1560296641557, + "firstId": 8780864, + "lastId": 8788201, + "count": 7338 + }, + { + "symbol": "MDAETH", + "priceChange": "-0.00009910", + "priceChangePercent": "-2.255", + "weightedAvgPrice": "0.00431183", + "prevClosePrice": "0.00437570", + "lastPrice": "0.00429610", + "lastQty": "50.00000000", + "bidPrice": "0.00426730", + "bidQty": "3.00000000", + "askPrice": "0.00428670", + "askQty": "707.00000000", + "openPrice": "0.00439520", + "highPrice": "0.00444460", + "lowPrice": "0.00422650", + "volume": "56379.00000000", + "quoteVolume": "243.09655230", + "openTime": 1560210238623, + "closeTime": 1560296638623, + "firstId": 1730058, + "lastId": 1730984, + "count": 927 + }, + { + "symbol": "MTLBTC", + "priceChange": "0.00001100", + "priceChangePercent": "16.616", + "weightedAvgPrice": "0.00007875", + "prevClosePrice": "0.00006640", + "lastPrice": "0.00007720", + "lastQty": "4807.39000000", + "bidPrice": "0.00007730", + "bidQty": "6697.11000000", + "askPrice": "0.00007760", + "askQty": "5157.34000000", + "openPrice": "0.00006620", + "highPrice": "0.00008680", + "lowPrice": "0.00006440", + "volume": "18780379.34000000", + "quoteVolume": "1479.01354461", + "openTime": 1560210242227, + "closeTime": 1560296642227, + "firstId": 7084732, + "lastId": 7121519, + "count": 36788 + }, + { + "symbol": "MTLETH", + "priceChange": "0.00035300", + "priceChangePercent": "16.465", + "weightedAvgPrice": "0.00253959", + "prevClosePrice": "0.00214400", + "lastPrice": "0.00249700", + "lastQty": "527.98000000", + "bidPrice": "0.00248800", + "bidQty": "227.91000000", + "askPrice": "0.00249700", + "askQty": "1562.94000000", + "openPrice": "0.00214400", + "highPrice": "0.00280000", + "lowPrice": "0.00208900", + "volume": "1171712.55000000", + "quoteVolume": "2975.66422113", + "openTime": 1560210242207, + "closeTime": 1560296642207, + "firstId": 952831, + "lastId": 958349, + "count": 5519 + }, + { + "symbol": "SUBBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00000457", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998596366, + "closeTime": 1558084996366, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "SUBETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00012334", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998596366, + "closeTime": 1558084996366, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "EOSBTC", + "priceChange": "-0.00000250", + "priceChangePercent": "-0.311", + "weightedAvgPrice": "0.00080084", + "prevClosePrice": "0.00080340", + "lastPrice": "0.00080100", + "lastQty": "2.98000000", + "bidPrice": "0.00080070", + "bidQty": "2.49000000", + "askPrice": "0.00080090", + "askQty": "7.47000000", + "openPrice": "0.00080350", + "highPrice": "0.00080890", + "lowPrice": "0.00079300", + "volume": "1858358.48000000", + "quoteVolume": "1488.24572639", + "openTime": 1560210242368, + "closeTime": 1560296642368, + "firstId": 38418626, + "lastId": 38462380, + "count": 43755 + }, + { + "symbol": "SNTBTC", + "priceChange": "0.00000004", + "priceChangePercent": "1.070", + "weightedAvgPrice": "0.00000377", + "prevClosePrice": "0.00000374", + "lastPrice": "0.00000378", + "lastQty": "29825.00000000", + "bidPrice": "0.00000377", + "bidQty": "192935.00000000", + "askPrice": "0.00000378", + "askQty": "67025.00000000", + "openPrice": "0.00000374", + "highPrice": "0.00000390", + "lowPrice": "0.00000368", + "volume": "13159718.00000000", + "quoteVolume": "49.58038702", + "openTime": 1560210240136, + "closeTime": 1560296640136, + "firstId": 5740006, + "lastId": 5742829, + "count": 2824 + }, + { + "symbol": "ETCETH", + "priceChange": "-0.00008200", + "priceChangePercent": "-0.244", + "weightedAvgPrice": "0.03355313", + "prevClosePrice": "0.03363200", + "lastPrice": "0.03359100", + "lastQty": "0.43000000", + "bidPrice": "0.03355100", + "bidQty": "107.49000000", + "askPrice": "0.03362200", + "askQty": "0.40000000", + "openPrice": "0.03367300", + "highPrice": "0.03417000", + "lowPrice": "0.03319800", + "volume": "8005.46000000", + "quoteVolume": "268.60822303", + "openTime": 1560210240280, + "closeTime": 1560296640280, + "firstId": 3873566, + "lastId": 3875015, + "count": 1450 + }, + { + "symbol": "ETCBTC", + "priceChange": "0.00000300", + "priceChangePercent": "0.288", + "weightedAvgPrice": "0.00103873", + "prevClosePrice": "0.00104000", + "lastPrice": "0.00104300", + "lastQty": "22.44000000", + "bidPrice": "0.00104200", + "bidQty": "182.80000000", + "askPrice": "0.00104400", + "askQty": "191.64000000", + "openPrice": "0.00104000", + "highPrice": "0.00105500", + "lowPrice": "0.00102800", + "volume": "163116.19000000", + "quoteVolume": "169.43360276", + "openTime": 1560210242350, + "closeTime": 1560296642350, + "firstId": 15627778, + "lastId": 15635061, + "count": 7284 + }, + { + "symbol": "MTHBTC", + "priceChange": "0.00000017", + "priceChangePercent": "5.629", + "weightedAvgPrice": "0.00000316", + "prevClosePrice": "0.00000302", + "lastPrice": "0.00000319", + "lastQty": "4775.00000000", + "bidPrice": "0.00000318", + "bidQty": "6848.00000000", + "askPrice": "0.00000319", + "askQty": "2349.00000000", + "openPrice": "0.00000302", + "highPrice": "0.00000328", + "lowPrice": "0.00000293", + "volume": "42896418.00000000", + "quoteVolume": "135.49103561", + "openTime": 1560210240728, + "closeTime": 1560296640728, + "firstId": 4255083, + "lastId": 4260995, + "count": 5913 + }, + { + "symbol": "MTHETH", + "priceChange": "0.00000441", + "priceChangePercent": "4.510", + "weightedAvgPrice": "0.00010240", + "prevClosePrice": "0.00009865", + "lastPrice": "0.00010220", + "lastQty": "319.00000000", + "bidPrice": "0.00010226", + "bidQty": "6848.00000000", + "askPrice": "0.00010289", + "askQty": "1244.00000000", + "openPrice": "0.00009779", + "highPrice": "0.00010624", + "lowPrice": "0.00009525", + "volume": "1752326.00000000", + "quoteVolume": "179.44533951", + "openTime": 1560210238835, + "closeTime": 1560296638835, + "firstId": 943061, + "lastId": 943913, + "count": 853 + }, + { + "symbol": "ENGBTC", + "priceChange": "0.00000827", + "priceChangePercent": "12.741", + "weightedAvgPrice": "0.00007091", + "prevClosePrice": "0.00006516", + "lastPrice": "0.00007318", + "lastQty": "5.00000000", + "bidPrice": "0.00007273", + "bidQty": "344.00000000", + "askPrice": "0.00007318", + "askQty": "211.00000000", + "openPrice": "0.00006491", + "highPrice": "0.00007757", + "lowPrice": "0.00006346", + "volume": "3059932.00000000", + "quoteVolume": "216.99438061", + "openTime": 1560210242165, + "closeTime": 1560296642165, + "firstId": 5630092, + "lastId": 5644229, + "count": 14138 + }, + { + "symbol": "ENGETH", + "priceChange": "0.00025750", + "priceChangePercent": "12.261", + "weightedAvgPrice": "0.00231274", + "prevClosePrice": "0.00211420", + "lastPrice": "0.00235760", + "lastQty": "5.00000000", + "bidPrice": "0.00233800", + "bidQty": "527.00000000", + "askPrice": "0.00235760", + "askQty": "378.00000000", + "openPrice": "0.00210010", + "highPrice": "0.00260000", + "lowPrice": "0.00205690", + "volume": "443175.00000000", + "quoteVolume": "1024.95058690", + "openTime": 1560210239817, + "closeTime": 1560296639817, + "firstId": 1453262, + "lastId": 1455901, + "count": 2640 + }, + { + "symbol": "DNTBTC", + "priceChange": "0.00000008", + "priceChangePercent": "3.556", + "weightedAvgPrice": "0.00000227", + "prevClosePrice": "0.00000225", + "lastPrice": "0.00000233", + "lastQty": "480.00000000", + "bidPrice": "0.00000231", + "bidQty": "54543.00000000", + "askPrice": "0.00000233", + "askQty": "11203.00000000", + "openPrice": "0.00000225", + "highPrice": "0.00000236", + "lowPrice": "0.00000221", + "volume": "21250598.00000000", + "quoteVolume": "48.31214098", + "openTime": 1560210241240, + "closeTime": 1560296641240, + "firstId": 5013016, + "lastId": 5015881, + "count": 2866 + }, + { + "symbol": "ZECBTC", + "priceChange": "0.00009000", + "priceChangePercent": "0.899", + "weightedAvgPrice": "0.01001830", + "prevClosePrice": "0.01000400", + "lastPrice": "0.01010300", + "lastQty": "2.56400000", + "bidPrice": "0.01010000", + "bidQty": "17.87400000", + "askPrice": "0.01010400", + "askQty": "2.00000000", + "openPrice": "0.01001300", + "highPrice": "0.01017200", + "lowPrice": "0.00985400", + "volume": "28655.84300000", + "quoteVolume": "287.08275248", + "openTime": 1560210238612, + "closeTime": 1560296638612, + "firstId": 8065285, + "lastId": 8074828, + "count": 9544 + }, + { + "symbol": "ZECETH", + "priceChange": "0.00155000", + "priceChangePercent": "0.479", + "weightedAvgPrice": "0.32341918", + "prevClosePrice": "0.32422000", + "lastPrice": "0.32522000", + "lastQty": "6.78800000", + "bidPrice": "0.32490000", + "bidQty": "8.09600000", + "askPrice": "0.32599000", + "askQty": "25.08400000", + "openPrice": "0.32367000", + "highPrice": "0.32927000", + "lowPrice": "0.31854000", + "volume": "2092.86900000", + "quoteVolume": "676.87398032", + "openTime": 1560210242116, + "closeTime": 1560296642116, + "firstId": 1749003, + "lastId": 1750023, + "count": 1021 + }, + { + "symbol": "BNTBTC", + "priceChange": "0.00000139", + "priceChangePercent": "1.580", + "weightedAvgPrice": "0.00008786", + "prevClosePrice": "0.00008825", + "lastPrice": "0.00008939", + "lastQty": "224.00000000", + "bidPrice": "0.00008939", + "bidQty": "62082.00000000", + "askPrice": "0.00008949", + "askQty": "42.00000000", + "openPrice": "0.00008800", + "highPrice": "0.00008944", + "lowPrice": "0.00008670", + "volume": "661758.00000000", + "quoteVolume": "58.14323040", + "openTime": 1560210242087, + "closeTime": 1560296642087, + "firstId": 1784458, + "lastId": 1787000, + "count": 2543 + }, + { + "symbol": "ASTBTC", + "priceChange": "0.00000015", + "priceChangePercent": "2.479", + "weightedAvgPrice": "0.00000614", + "prevClosePrice": "0.00000604", + "lastPrice": "0.00000620", + "lastQty": "151.00000000", + "bidPrice": "0.00000618", + "bidQty": "3874.00000000", + "askPrice": "0.00000620", + "askQty": "3391.00000000", + "openPrice": "0.00000605", + "highPrice": "0.00000638", + "lowPrice": "0.00000585", + "volume": "17930397.00000000", + "quoteVolume": "110.10547200", + "openTime": 1560210242239, + "closeTime": 1560296642239, + "firstId": 5137032, + "lastId": 5142854, + "count": 5823 + }, + { + "symbol": "ASTETH", + "priceChange": "0.00000380", + "priceChangePercent": "1.940", + "weightedAvgPrice": "0.00019926", + "prevClosePrice": "0.00019660", + "lastPrice": "0.00019970", + "lastQty": "17804.00000000", + "bidPrice": "0.00019870", + "bidQty": "3874.00000000", + "askPrice": "0.00019980", + "askQty": "1251.00000000", + "openPrice": "0.00019590", + "highPrice": "0.00020680", + "lowPrice": "0.00018970", + "volume": "873370.00000000", + "quoteVolume": "174.02669020", + "openTime": 1560210241593, + "closeTime": 1560296641593, + "firstId": 1358586, + "lastId": 1359368, + "count": 783 + }, + { + "symbol": "DASHBTC", + "priceChange": "-0.00029900", + "priceChangePercent": "-1.576", + "weightedAvgPrice": "0.01864979", + "prevClosePrice": "0.01895700", + "lastPrice": "0.01867300", + "lastQty": "0.41500000", + "bidPrice": "0.01867100", + "bidQty": "0.36900000", + "askPrice": "0.01867300", + "askQty": "9.02000000", + "openPrice": "0.01897200", + "highPrice": "0.01897200", + "lowPrice": "0.01842200", + "volume": "10466.58400000", + "quoteVolume": "195.19960267", + "openTime": 1560210241862, + "closeTime": 1560296641862, + "firstId": 8639146, + "lastId": 8647555, + "count": 8410 + }, + { + "symbol": "DASHETH", + "priceChange": "-0.01072000", + "priceChangePercent": "-1.752", + "weightedAvgPrice": "0.60252279", + "prevClosePrice": "0.61228000", + "lastPrice": "0.60109000", + "lastQty": "0.35700000", + "bidPrice": "0.60109000", + "bidQty": "0.07600000", + "askPrice": "0.60192000", + "askQty": "0.35700000", + "openPrice": "0.61181000", + "highPrice": "0.61250000", + "lowPrice": "0.59708000", + "volume": "930.59900000", + "quoteVolume": "560.70710767", + "openTime": 1560210242238, + "closeTime": 1560296642238, + "firstId": 1714660, + "lastId": 1716007, + "count": 1348 + }, + { + "symbol": "OAXBTC", + "priceChange": "0.00000141", + "priceChangePercent": "5.226", + "weightedAvgPrice": "0.00002873", + "prevClosePrice": "0.00002697", + "lastPrice": "0.00002839", + "lastQty": "53.00000000", + "bidPrice": "0.00002833", + "bidQty": "3109.00000000", + "askPrice": "0.00002839", + "askQty": "2012.00000000", + "openPrice": "0.00002698", + "highPrice": "0.00003150", + "lowPrice": "0.00002565", + "volume": "17054216.00000000", + "quoteVolume": "489.97286248", + "openTime": 1560210242047, + "closeTime": 1560296642047, + "firstId": 4552949, + "lastId": 4577482, + "count": 24534 + }, + { + "symbol": "ICNBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00005742", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998596370, + "closeTime": 1558084996370, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "BTGBTC", + "priceChange": "-0.00002200", + "priceChangePercent": "-0.682", + "weightedAvgPrice": "0.00322480", + "prevClosePrice": "0.00322900", + "lastPrice": "0.00320500", + "lastQty": "3.58000000", + "bidPrice": "0.00320100", + "bidQty": "29.19000000", + "askPrice": "0.00320500", + "askQty": "2.22000000", + "openPrice": "0.00322700", + "highPrice": "0.00334800", + "lowPrice": "0.00317900", + "volume": "26729.06000000", + "quoteVolume": "86.19585656", + "openTime": 1560210236082, + "closeTime": 1560296636082, + "firstId": 5839399, + "lastId": 5844843, + "count": 5445 + }, + { + "symbol": "BTGETH", + "priceChange": "-0.00069000", + "priceChangePercent": "-0.662", + "weightedAvgPrice": "0.10425666", + "prevClosePrice": "0.10473400", + "lastPrice": "0.10349500", + "lastQty": "0.28000000", + "bidPrice": "0.10299500", + "bidQty": "2.15000000", + "askPrice": "0.10354200", + "askQty": "2.26000000", + "openPrice": "0.10418500", + "highPrice": "0.10764400", + "lowPrice": "0.10250200", + "volume": "1197.30000000", + "quoteVolume": "124.82650177", + "openTime": 1560210241450, + "closeTime": 1560296641450, + "firstId": 928738, + "lastId": 929450, + "count": 713 + }, + { + "symbol": "EVXBTC", + "priceChange": "0.00000060", + "priceChangePercent": "0.559", + "weightedAvgPrice": "0.00010671", + "prevClosePrice": "0.00010785", + "lastPrice": "0.00010788", + "lastQty": "81.00000000", + "bidPrice": "0.00010741", + "bidQty": "404.00000000", + "askPrice": "0.00010794", + "askQty": "12.00000000", + "openPrice": "0.00010728", + "highPrice": "0.00011039", + "lowPrice": "0.00010389", + "volume": "1645260.00000000", + "quoteVolume": "175.55784899", + "openTime": 1560210242353, + "closeTime": 1560296642353, + "firstId": 6014545, + "lastId": 6022719, + "count": 8175 + }, + { + "symbol": "EVXETH", + "priceChange": "-0.00001500", + "priceChangePercent": "-0.430", + "weightedAvgPrice": "0.00344513", + "prevClosePrice": "0.00348530", + "lastPrice": "0.00347030", + "lastQty": "999.00000000", + "bidPrice": "0.00345920", + "bidQty": "1156.00000000", + "askPrice": "0.00349280", + "askQty": "35.00000000", + "openPrice": "0.00348530", + "highPrice": "0.00358370", + "lowPrice": "0.00335030", + "volume": "186518.00000000", + "quoteVolume": "642.57880230", + "openTime": 1560210241963, + "closeTime": 1560296641963, + "firstId": 1249684, + "lastId": 1251257, + "count": 1574 + }, + { + "symbol": "REQBTC", + "priceChange": "0.00000001", + "priceChangePercent": "0.319", + "weightedAvgPrice": "0.00000316", + "prevClosePrice": "0.00000313", + "lastPrice": "0.00000314", + "lastQty": "561.00000000", + "bidPrice": "0.00000314", + "bidQty": "17217.00000000", + "askPrice": "0.00000315", + "askQty": "5503.00000000", + "openPrice": "0.00000313", + "highPrice": "0.00000326", + "lowPrice": "0.00000303", + "volume": "14854514.00000000", + "quoteVolume": "46.88217170", + "openTime": 1560210215960, + "closeTime": 1560296615960, + "firstId": 4318970, + "lastId": 4321880, + "count": 2911 + }, + { + "symbol": "REQETH", + "priceChange": "-0.00000041", + "priceChangePercent": "-0.402", + "weightedAvgPrice": "0.00010219", + "prevClosePrice": "0.00010175", + "lastPrice": "0.00010147", + "lastQty": "100.00000000", + "bidPrice": "0.00010097", + "bidQty": "17217.00000000", + "askPrice": "0.00010159", + "askQty": "551.00000000", + "openPrice": "0.00010188", + "highPrice": "0.00010523", + "lowPrice": "0.00009898", + "volume": "1333467.00000000", + "quoteVolume": "136.27241780", + "openTime": 1560210238801, + "closeTime": 1560296638801, + "firstId": 2042439, + "lastId": 2043040, + "count": 602 + }, + { + "symbol": "VIBBTC", + "priceChange": "0.00000010", + "priceChangePercent": "1.645", + "weightedAvgPrice": "0.00000619", + "prevClosePrice": "0.00000606", + "lastPrice": "0.00000618", + "lastQty": "268.00000000", + "bidPrice": "0.00000615", + "bidQty": "5243.00000000", + "askPrice": "0.00000618", + "askQty": "98984.00000000", + "openPrice": "0.00000608", + "highPrice": "0.00000636", + "lowPrice": "0.00000603", + "volume": "19303398.00000000", + "quoteVolume": "119.56552058", + "openTime": 1560210236313, + "closeTime": 1560296636313, + "firstId": 4801338, + "lastId": 4806520, + "count": 5183 + }, + { + "symbol": "VIBETH", + "priceChange": "0.00000194", + "priceChangePercent": "0.989", + "weightedAvgPrice": "0.00019996", + "prevClosePrice": "0.00019656", + "lastPrice": "0.00019819", + "lastQty": "501.00000000", + "bidPrice": "0.00019751", + "bidQty": "16552.00000000", + "askPrice": "0.00019993", + "askQty": "76910.00000000", + "openPrice": "0.00019625", + "highPrice": "0.00020499", + "lowPrice": "0.00019592", + "volume": "2247030.00000000", + "quoteVolume": "449.31674219", + "openTime": 1560210242356, + "closeTime": 1560296642356, + "firstId": 1174773, + "lastId": 1175567, + "count": 795 + }, + { + "symbol": "HSRETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.01247400", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998596373, + "closeTime": 1558084996373, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "TRXBTC", + "priceChange": "-0.00000001", + "priceChangePercent": "-0.253", + "weightedAvgPrice": "0.00000390", + "prevClosePrice": "0.00000394", + "lastPrice": "0.00000394", + "lastQty": "4972.00000000", + "bidPrice": "0.00000394", + "bidQty": "2521027.00000000", + "askPrice": "0.00000395", + "askQty": "561866.00000000", + "openPrice": "0.00000395", + "highPrice": "0.00000401", + "lowPrice": "0.00000383", + "volume": "263078543.00000000", + "quoteVolume": "1026.60259312", + "openTime": 1560210241078, + "closeTime": 1560296641078, + "firstId": 42882235, + "lastId": 42902745, + "count": 20511 + }, + { + "symbol": "TRXETH", + "priceChange": "-0.00000081", + "priceChangePercent": "-0.634", + "weightedAvgPrice": "0.00012616", + "prevClosePrice": "0.00012780", + "lastPrice": "0.00012703", + "lastQty": "5659.00000000", + "bidPrice": "0.00012703", + "bidQty": "5659.00000000", + "askPrice": "0.00012725", + "askQty": "16371.00000000", + "openPrice": "0.00012784", + "highPrice": "0.00012953", + "lowPrice": "0.00012385", + "volume": "34987685.00000000", + "quoteVolume": "4413.96662798", + "openTime": 1560210240540, + "closeTime": 1560296640540, + "firstId": 17894967, + "lastId": 17901611, + "count": 6645 + }, + { + "symbol": "POWRBTC", + "priceChange": "0.00000013", + "priceChangePercent": "0.852", + "weightedAvgPrice": "0.00001525", + "prevClosePrice": "0.00001529", + "lastPrice": "0.00001539", + "lastQty": "525.00000000", + "bidPrice": "0.00001539", + "bidQty": "536.00000000", + "askPrice": "0.00001540", + "askQty": "2677.00000000", + "openPrice": "0.00001526", + "highPrice": "0.00001553", + "lowPrice": "0.00001490", + "volume": "3746091.00000000", + "quoteVolume": "57.13839353", + "openTime": 1560210240100, + "closeTime": 1560296640100, + "firstId": 6182324, + "lastId": 6185266, + "count": 2943 + }, + { + "symbol": "POWRETH", + "priceChange": "0.00000263", + "priceChangePercent": "0.532", + "weightedAvgPrice": "0.00049383", + "prevClosePrice": "0.00049600", + "lastPrice": "0.00049663", + "lastQty": "50.00000000", + "bidPrice": "0.00049404", + "bidQty": "536.00000000", + "askPrice": "0.00049711", + "askQty": "22.00000000", + "openPrice": "0.00049400", + "highPrice": "0.00050137", + "lowPrice": "0.00048522", + "volume": "354456.00000000", + "quoteVolume": "175.04242672", + "openTime": 1560210239439, + "closeTime": 1560296639439, + "firstId": 1476512, + "lastId": 1477175, + "count": 664 + }, + { + "symbol": "ARKBTC", + "priceChange": "0.00000320", + "priceChangePercent": "4.205", + "weightedAvgPrice": "0.00007744", + "prevClosePrice": "0.00007610", + "lastPrice": "0.00007930", + "lastQty": "478.81000000", + "bidPrice": "0.00007900", + "bidQty": "827.43000000", + "askPrice": "0.00007920", + "askQty": "1314.14000000", + "openPrice": "0.00007610", + "highPrice": "0.00008230", + "lowPrice": "0.00007470", + "volume": "1265879.48000000", + "quoteVolume": "98.02832177", + "openTime": 1560210241793, + "closeTime": 1560296641793, + "firstId": 4685760, + "lastId": 4690985, + "count": 5226 + }, + { + "symbol": "ARKETH", + "priceChange": "0.00007200", + "priceChangePercent": "2.921", + "weightedAvgPrice": "0.00251155", + "prevClosePrice": "0.00246500", + "lastPrice": "0.00253700", + "lastQty": "4.00000000", + "bidPrice": "0.00253900", + "bidQty": "827.43000000", + "askPrice": "0.00255200", + "askQty": "427.84000000", + "openPrice": "0.00246500", + "highPrice": "0.00261000", + "lowPrice": "0.00242600", + "volume": "51368.41000000", + "quoteVolume": "129.01409567", + "openTime": 1560210239029, + "closeTime": 1560296639029, + "firstId": 1123673, + "lastId": 1125117, + "count": 1445 + }, + { + "symbol": "YOYOETH", + "priceChange": "0.00000440", + "priceChangePercent": "4.258", + "weightedAvgPrice": "0.00010727", + "prevClosePrice": "0.00010457", + "lastPrice": "0.00010774", + "lastQty": "105.00000000", + "bidPrice": "0.00010673", + "bidQty": "2882.00000000", + "askPrice": "0.00010782", + "askQty": "1367.00000000", + "openPrice": "0.00010334", + "highPrice": "0.00011196", + "lowPrice": "0.00010300", + "volume": "688507.00000000", + "quoteVolume": "73.85433245", + "openTime": 1560210241949, + "closeTime": 1560296641949, + "firstId": 1087652, + "lastId": 1088099, + "count": 448 + }, + { + "symbol": "XRPBTC", + "priceChange": "-0.00000032", + "priceChangePercent": "-0.640", + "weightedAvgPrice": "0.00004971", + "prevClosePrice": "0.00005002", + "lastPrice": "0.00004970", + "lastQty": "11783.00000000", + "bidPrice": "0.00004968", + "bidQty": "744.00000000", + "askPrice": "0.00004971", + "askQty": "10398.00000000", + "openPrice": "0.00005002", + "highPrice": "0.00005020", + "lowPrice": "0.00004924", + "volume": "49405902.00000000", + "quoteVolume": "2456.13112196", + "openTime": 1560210241740, + "closeTime": 1560296641740, + "firstId": 50136865, + "lastId": 50207595, + "count": 70731 + }, + { + "symbol": "XRPETH", + "priceChange": "-0.00002224", + "priceChangePercent": "-1.372", + "weightedAvgPrice": "0.00160898", + "prevClosePrice": "0.00161902", + "lastPrice": "0.00159861", + "lastQty": "381.00000000", + "bidPrice": "0.00159986", + "bidQty": "87.00000000", + "askPrice": "0.00160128", + "askQty": "74.00000000", + "openPrice": "0.00162085", + "highPrice": "0.00162613", + "lowPrice": "0.00158911", + "volume": "1766647.00000000", + "quoteVolume": "2842.49467935", + "openTime": 1560210242327, + "closeTime": 1560296642327, + "firstId": 14142362, + "lastId": 14149962, + "count": 7601 + }, + { + "symbol": "MODBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00004280", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998597363, + "closeTime": 1558084997363, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "MODETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00116700", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998597363, + "closeTime": 1558084997363, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "ENJBTC", + "priceChange": "0.00000107", + "priceChangePercent": "5.617", + "weightedAvgPrice": "0.00001983", + "prevClosePrice": "0.00001904", + "lastPrice": "0.00002012", + "lastQty": "1977.00000000", + "bidPrice": "0.00002010", + "bidQty": "39635.00000000", + "askPrice": "0.00002012", + "askQty": "392.00000000", + "openPrice": "0.00001905", + "highPrice": "0.00002077", + "lowPrice": "0.00001890", + "volume": "30238611.00000000", + "quoteVolume": "599.65868796", + "openTime": 1560210242358, + "closeTime": 1560296642358, + "firstId": 10783571, + "lastId": 10807151, + "count": 23581 + }, + { + "symbol": "ENJETH", + "priceChange": "0.00003518", + "priceChangePercent": "5.707", + "weightedAvgPrice": "0.00063987", + "prevClosePrice": "0.00061974", + "lastPrice": "0.00065160", + "lastQty": "18.00000000", + "bidPrice": "0.00064710", + "bidQty": "106.00000000", + "askPrice": "0.00065056", + "askQty": "1463.00000000", + "openPrice": "0.00061642", + "highPrice": "0.00066969", + "lowPrice": "0.00061307", + "volume": "2706776.00000000", + "quoteVolume": "1731.98387383", + "openTime": 1560210241883, + "closeTime": 1560296641883, + "firstId": 3090249, + "lastId": 3094577, + "count": 4329 + }, + { + "symbol": "STORJBTC", + "priceChange": "0.00000620", + "priceChangePercent": "17.499", + "weightedAvgPrice": "0.00004550", + "prevClosePrice": "0.00003533", + "lastPrice": "0.00004163", + "lastQty": "235.00000000", + "bidPrice": "0.00004154", + "bidQty": "151.00000000", + "askPrice": "0.00004164", + "askQty": "71.00000000", + "openPrice": "0.00003543", + "highPrice": "0.00006100", + "lowPrice": "0.00003470", + "volume": "24031456.00000000", + "quoteVolume": "1093.51850034", + "openTime": 1560210242042, + "closeTime": 1560296642042, + "firstId": 4337221, + "lastId": 4381768, + "count": 44548 + }, + { + "symbol": "STORJETH", + "priceChange": "0.00019190", + "priceChangePercent": "16.729", + "weightedAvgPrice": "0.00147553", + "prevClosePrice": "0.00114690", + "lastPrice": "0.00133900", + "lastQty": "151.00000000", + "bidPrice": "0.00133910", + "bidQty": "10.00000000", + "askPrice": "0.00134610", + "askQty": "255.00000000", + "openPrice": "0.00114710", + "highPrice": "0.00198890", + "lowPrice": "0.00112170", + "volume": "1285867.00000000", + "quoteVolume": "1897.33970080", + "openTime": 1560210239859, + "closeTime": 1560296639859, + "firstId": 814540, + "lastId": 820344, + "count": 5805 + }, + { + "symbol": "BNBUSDT", + "priceChange": "-0.02830000", + "priceChangePercent": "-0.089", + "weightedAvgPrice": "31.52359766", + "prevClosePrice": "31.95490000", + "lastPrice": "31.92130000", + "lastQty": "226.81000000", + "bidPrice": "31.91380000", + "bidQty": "146.59000000", + "askPrice": "31.92480000", + "askQty": "100.00000000", + "openPrice": "31.94960000", + "highPrice": "32.08210000", + "lowPrice": "30.93120000", + "volume": "2730428.18000000", + "quoteVolume": "86072919.39840800", + "openTime": 1560210242352, + "closeTime": 1560296642352, + "firstId": 30651623, + "lastId": 30725904, + "count": 74282 + }, + { + "symbol": "VENBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.14920000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998597393, + "closeTime": 1558084997393, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "YOYOBNB", + "priceChange": "0.00002000", + "priceChangePercent": "2.494", + "weightedAvgPrice": "0.00081916", + "prevClosePrice": "0.00080500", + "lastPrice": "0.00082200", + "lastQty": "2786.00000000", + "bidPrice": "0.00081900", + "bidQty": "3013.00000000", + "askPrice": "0.00083000", + "askQty": "507.00000000", + "openPrice": "0.00080200", + "highPrice": "0.00085900", + "lowPrice": "0.00079500", + "volume": "496144.00000000", + "quoteVolume": "406.42118400", + "openTime": 1560210236890, + "closeTime": 1560296636890, + "firstId": 719874, + "lastId": 720102, + "count": 229 + }, + { + "symbol": "POWRBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00380255", + "prevClosePrice": "0.00380000", + "lastPrice": "0.00383000", + "lastQty": "41.70000000", + "bidPrice": "0.00379000", + "bidQty": "2403.70000000", + "askPrice": "0.00383000", + "askQty": "9159.50000000", + "openPrice": "0.00383000", + "highPrice": "0.00386000", + "lowPrice": "0.00375000", + "volume": "91786.00000000", + "quoteVolume": "349.02108200", + "openTime": 1560210227441, + "closeTime": 1560296627441, + "firstId": 451261, + "lastId": 451486, + "count": 226 + }, + { + "symbol": "VENBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00013928", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998597397, + "closeTime": 1558084997397, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "VENETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00325194", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998597399, + "closeTime": 1558084997399, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "KMDBTC", + "priceChange": "-0.00000390", + "priceChangePercent": "-1.868", + "weightedAvgPrice": "0.00020512", + "prevClosePrice": "0.00020900", + "lastPrice": "0.00020490", + "lastQty": "29.99000000", + "bidPrice": "0.00020460", + "bidQty": "30.00000000", + "askPrice": "0.00020500", + "askQty": "453.47000000", + "openPrice": "0.00020880", + "highPrice": "0.00020930", + "lowPrice": "0.00020270", + "volume": "217452.58000000", + "quoteVolume": "44.60377784", + "openTime": 1560210241731, + "closeTime": 1560296641731, + "firstId": 5284540, + "lastId": 5287842, + "count": 3303 + }, + { + "symbol": "KMDETH", + "priceChange": "-0.00020200", + "priceChangePercent": "-2.987", + "weightedAvgPrice": "0.00664017", + "prevClosePrice": "0.00677400", + "lastPrice": "0.00656000", + "lastQty": "20.20000000", + "bidPrice": "0.00658200", + "bidQty": "152.00000000", + "askPrice": "0.00660700", + "askQty": "137.00000000", + "openPrice": "0.00676200", + "highPrice": "0.00677900", + "lowPrice": "0.00652600", + "volume": "13681.06000000", + "quoteVolume": "90.84460652", + "openTime": 1560210241612, + "closeTime": 1560296641612, + "firstId": 945461, + "lastId": 945846, + "count": 386 + }, + { + "symbol": "NULSBNB", + "priceChange": "-0.00083000", + "priceChangePercent": "-2.770", + "weightedAvgPrice": "0.02966464", + "prevClosePrice": "0.02999000", + "lastPrice": "0.02913000", + "lastQty": "10.80000000", + "bidPrice": "0.02904000", + "bidQty": "122.80000000", + "askPrice": "0.02923000", + "askQty": "49.00000000", + "openPrice": "0.02996000", + "highPrice": "0.03153000", + "lowPrice": "0.02840000", + "volume": "135537.50000000", + "quoteVolume": "4020.67167500", + "openTime": 1560210238569, + "closeTime": 1560296638569, + "firstId": 506039, + "lastId": 507621, + "count": 1583 + }, + { + "symbol": "RCNBTC", + "priceChange": "0.00000006", + "priceChangePercent": "1.474", + "weightedAvgPrice": "0.00000411", + "prevClosePrice": "0.00000407", + "lastPrice": "0.00000413", + "lastQty": "8799.00000000", + "bidPrice": "0.00000413", + "bidQty": "809.00000000", + "askPrice": "0.00000414", + "askQty": "4257.00000000", + "openPrice": "0.00000407", + "highPrice": "0.00000425", + "lowPrice": "0.00000393", + "volume": "14682714.00000000", + "quoteVolume": "60.30640815", + "openTime": 1560210240946, + "closeTime": 1560296640946, + "firstId": 5058891, + "lastId": 5062837, + "count": 3947 + }, + { + "symbol": "RCNETH", + "priceChange": "0.00000233", + "priceChangePercent": "1.775", + "weightedAvgPrice": "0.00013381", + "prevClosePrice": "0.00013126", + "lastPrice": "0.00013359", + "lastQty": "98.00000000", + "bidPrice": "0.00013265", + "bidQty": "809.00000000", + "askPrice": "0.00013381", + "askQty": "21569.00000000", + "openPrice": "0.00013126", + "highPrice": "0.00013793", + "lowPrice": "0.00012768", + "volume": "894798.00000000", + "quoteVolume": "119.73456894", + "openTime": 1560210242365, + "closeTime": 1560296642365, + "firstId": 950385, + "lastId": 950797, + "count": 413 + }, + { + "symbol": "RCNBNB", + "priceChange": "0.00001900", + "priceChangePercent": "1.881", + "weightedAvgPrice": "0.00103115", + "prevClosePrice": "0.00101200", + "lastPrice": "0.00102900", + "lastQty": "6673.00000000", + "bidPrice": "0.00101900", + "bidQty": "998.00000000", + "askPrice": "0.00102700", + "askQty": "1756.00000000", + "openPrice": "0.00101000", + "highPrice": "0.00105800", + "lowPrice": "0.00097900", + "volume": "877011.00000000", + "quoteVolume": "904.33393600", + "openTime": 1560210236791, + "closeTime": 1560296636791, + "firstId": 453983, + "lastId": 454384, + "count": 402 + }, + { + "symbol": "NULSBTC", + "priceChange": "-0.00000258", + "priceChangePercent": "-2.140", + "weightedAvgPrice": "0.00011926", + "prevClosePrice": "0.00012062", + "lastPrice": "0.00011798", + "lastQty": "214.00000000", + "bidPrice": "0.00011745", + "bidQty": "168.00000000", + "askPrice": "0.00011802", + "askQty": "225.00000000", + "openPrice": "0.00012056", + "highPrice": "0.00012647", + "lowPrice": "0.00011426", + "volume": "7507206.00000000", + "quoteVolume": "895.28635919", + "openTime": 1560210242217, + "closeTime": 1560296642217, + "firstId": 6073457, + "lastId": 6102894, + "count": 29438 + }, + { + "symbol": "NULSETH", + "priceChange": "-0.00009543", + "priceChangePercent": "-2.442", + "weightedAvgPrice": "0.00381871", + "prevClosePrice": "0.00390493", + "lastPrice": "0.00381235", + "lastQty": "3.00000000", + "bidPrice": "0.00378219", + "bidQty": "225.00000000", + "askPrice": "0.00380761", + "askQty": "75.00000000", + "openPrice": "0.00390778", + "highPrice": "0.00410076", + "lowPrice": "0.00369718", + "volume": "158007.00000000", + "quoteVolume": "603.38355733", + "openTime": 1560210241715, + "closeTime": 1560296641715, + "firstId": 1115786, + "lastId": 1118819, + "count": 3034 + }, + { + "symbol": "RDNBTC", + "priceChange": "0.00000031", + "priceChangePercent": "0.714", + "weightedAvgPrice": "0.00004406", + "prevClosePrice": "0.00004353", + "lastPrice": "0.00004374", + "lastQty": "25.00000000", + "bidPrice": "0.00004375", + "bidQty": "107.00000000", + "askPrice": "0.00004382", + "askQty": "28.00000000", + "openPrice": "0.00004343", + "highPrice": "0.00004546", + "lowPrice": "0.00004299", + "volume": "899882.00000000", + "quoteVolume": "39.65037678", + "openTime": 1560210241896, + "closeTime": 1560296641896, + "firstId": 3087856, + "lastId": 3090646, + "count": 2791 + }, + { + "symbol": "RDNETH", + "priceChange": "0.00000460", + "priceChangePercent": "0.327", + "weightedAvgPrice": "0.00142790", + "prevClosePrice": "0.00141110", + "lastPrice": "0.00141110", + "lastQty": "90.00000000", + "bidPrice": "0.00140580", + "bidQty": "3685.00000000", + "askPrice": "0.00141110", + "askQty": "410.00000000", + "openPrice": "0.00140650", + "highPrice": "0.00148000", + "lowPrice": "0.00138970", + "volume": "93495.00000000", + "quoteVolume": "133.50149370", + "openTime": 1560210239020, + "closeTime": 1560296639020, + "firstId": 1181422, + "lastId": 1182078, + "count": 657 + }, + { + "symbol": "RDNBNB", + "priceChange": "0.00005000", + "priceChangePercent": "0.464", + "weightedAvgPrice": "0.01102214", + "prevClosePrice": "0.01094000", + "lastPrice": "0.01082000", + "lastQty": "1304.00000000", + "bidPrice": "0.01082000", + "bidQty": "5555.70000000", + "askPrice": "0.01087000", + "askQty": "32053.10000000", + "openPrice": "0.01077000", + "highPrice": "0.01143000", + "lowPrice": "0.01069000", + "volume": "66428.60000000", + "quoteVolume": "732.18512900", + "openTime": 1560210241610, + "closeTime": 1560296641610, + "firstId": 242875, + "lastId": 243299, + "count": 425 + }, + { + "symbol": "XMRBTC", + "priceChange": "0.00010300", + "priceChangePercent": "0.948", + "weightedAvgPrice": "0.01099919", + "prevClosePrice": "0.01086400", + "lastPrice": "0.01096700", + "lastQty": "3.84300000", + "bidPrice": "0.01096800", + "bidQty": "0.73700000", + "askPrice": "0.01098300", + "askQty": "1.50000000", + "openPrice": "0.01086400", + "highPrice": "0.01111600", + "lowPrice": "0.01083100", + "volume": "34685.44300000", + "quoteVolume": "381.51166925", + "openTime": 1560210242348, + "closeTime": 1560296642348, + "firstId": 11359555, + "lastId": 11373233, + "count": 13679 + }, + { + "symbol": "XMRETH", + "priceChange": "0.00183000", + "priceChangePercent": "0.520", + "weightedAvgPrice": "0.35624748", + "prevClosePrice": "0.35176000", + "lastPrice": "0.35359000", + "lastQty": "0.07500000", + "bidPrice": "0.35288000", + "bidQty": "0.84800000", + "askPrice": "0.35381000", + "askQty": "0.70100000", + "openPrice": "0.35176000", + "highPrice": "0.36129000", + "lowPrice": "0.35080000", + "volume": "1897.26300000", + "quoteVolume": "675.89516498", + "openTime": 1560210240283, + "closeTime": 1560296640283, + "firstId": 2168587, + "lastId": 2170241, + "count": 1655 + }, + { + "symbol": "DLTBNB", + "priceChange": "0.00009000", + "priceChangePercent": "2.368", + "weightedAvgPrice": "0.00382176", + "prevClosePrice": "0.00381000", + "lastPrice": "0.00389000", + "lastQty": "29.70000000", + "bidPrice": "0.00387000", + "bidQty": "17066.00000000", + "askPrice": "0.00391000", + "askQty": "2083.00000000", + "openPrice": "0.00380000", + "highPrice": "0.00405000", + "lowPrice": "0.00366000", + "volume": "543880.60000000", + "quoteVolume": "2078.58080900", + "openTime": 1560210234620, + "closeTime": 1560296634620, + "firstId": 389614, + "lastId": 390345, + "count": 732 + }, + { + "symbol": "WTCBNB", + "priceChange": "0.00050000", + "priceChangePercent": "0.728", + "weightedAvgPrice": "0.06931776", + "prevClosePrice": "0.06830000", + "lastPrice": "0.06920000", + "lastQty": "77.00000000", + "bidPrice": "0.06890000", + "bidQty": "2.83000000", + "askPrice": "0.06920000", + "askQty": "117.00000000", + "openPrice": "0.06870000", + "highPrice": "0.07090000", + "lowPrice": "0.06830000", + "volume": "11660.04000000", + "quoteVolume": "808.24781600", + "openTime": 1560210235985, + "closeTime": 1560296635985, + "firstId": 461689, + "lastId": 461995, + "count": 307 + }, + { + "symbol": "DLTBTC", + "priceChange": "0.00000038", + "priceChangePercent": "2.480", + "weightedAvgPrice": "0.00001540", + "prevClosePrice": "0.00001536", + "lastPrice": "0.00001570", + "lastQty": "66.00000000", + "bidPrice": "0.00001571", + "bidQty": "662.00000000", + "askPrice": "0.00001573", + "askQty": "30463.00000000", + "openPrice": "0.00001532", + "highPrice": "0.00001620", + "lowPrice": "0.00001464", + "volume": "11501395.00000000", + "quoteVolume": "177.13842835", + "openTime": 1560210241967, + "closeTime": 1560296641967, + "firstId": 5699669, + "lastId": 5707628, + "count": 7960 + }, + { + "symbol": "DLTETH", + "priceChange": "0.00000826", + "priceChangePercent": "1.653", + "weightedAvgPrice": "0.00049157", + "prevClosePrice": "0.00049929", + "lastPrice": "0.00050804", + "lastQty": "27.00000000", + "bidPrice": "0.00050509", + "bidQty": "662.00000000", + "askPrice": "0.00051257", + "askQty": "1879.00000000", + "openPrice": "0.00049978", + "highPrice": "0.00052497", + "lowPrice": "0.00047283", + "volume": "519195.00000000", + "quoteVolume": "255.22035012", + "openTime": 1560210241374, + "closeTime": 1560296641374, + "firstId": 1267553, + "lastId": 1268664, + "count": 1112 + }, + { + "symbol": "AMBBTC", + "priceChange": "0.00000003", + "priceChangePercent": "0.515", + "weightedAvgPrice": "0.00000583", + "prevClosePrice": "0.00000583", + "lastPrice": "0.00000586", + "lastQty": "4853.00000000", + "bidPrice": "0.00000586", + "bidQty": "10201.00000000", + "askPrice": "0.00000587", + "askQty": "1622.00000000", + "openPrice": "0.00000583", + "highPrice": "0.00000591", + "lowPrice": "0.00000575", + "volume": "7669268.00000000", + "quoteVolume": "44.71236193", + "openTime": 1560210204263, + "closeTime": 1560296604263, + "firstId": 4565725, + "lastId": 4567662, + "count": 1938 + }, + { + "symbol": "AMBETH", + "priceChange": "-0.00000064", + "priceChangePercent": "-0.339", + "weightedAvgPrice": "0.00018931", + "prevClosePrice": "0.00018896", + "lastPrice": "0.00018838", + "lastQty": "54.00000000", + "bidPrice": "0.00018854", + "bidQty": "1200.00000000", + "askPrice": "0.00018968", + "askQty": "312.00000000", + "openPrice": "0.00018902", + "highPrice": "0.00019255", + "lowPrice": "0.00018599", + "volume": "656943.00000000", + "quoteVolume": "124.36862293", + "openTime": 1560210231423, + "closeTime": 1560296631423, + "firstId": 1152721, + "lastId": 1153157, + "count": 437 + }, + { + "symbol": "AMBBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00145689", + "prevClosePrice": "0.00146000", + "lastPrice": "0.00145000", + "lastQty": "21610.00000000", + "bidPrice": "0.00144000", + "bidQty": "29761.20000000", + "askPrice": "0.00145000", + "askQty": "35310.00000000", + "openPrice": "0.00145000", + "highPrice": "0.00148000", + "lowPrice": "0.00143000", + "volume": "173160.50000000", + "quoteVolume": "252.27513200", + "openTime": 1560210240442, + "closeTime": 1560296640442, + "firstId": 368401, + "lastId": 368544, + "count": 144 + }, + { + "symbol": "BCCETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "2.47246000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998597445, + "closeTime": 1558084997445, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "BCCUSDT", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "448.70000000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998597452, + "closeTime": 1558084997452, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "BCCBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "54.29000000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998597457, + "closeTime": 1558084997457, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "BATBTC", + "priceChange": "-0.00000062", + "priceChangePercent": "-1.486", + "weightedAvgPrice": "0.00004124", + "prevClosePrice": "0.00004170", + "lastPrice": "0.00004109", + "lastQty": "125.00000000", + "bidPrice": "0.00004106", + "bidQty": "375.00000000", + "askPrice": "0.00004110", + "askQty": "588.00000000", + "openPrice": "0.00004171", + "highPrice": "0.00004174", + "lowPrice": "0.00004081", + "volume": "6055925.00000000", + "quoteVolume": "249.76239151", + "openTime": 1560210241542, + "closeTime": 1560296641542, + "firstId": 11688347, + "lastId": 11700560, + "count": 12214 + }, + { + "symbol": "BATETH", + "priceChange": "-0.00002582", + "priceChangePercent": "-1.913", + "weightedAvgPrice": "0.00133679", + "prevClosePrice": "0.00134956", + "lastPrice": "0.00132378", + "lastQty": "253.00000000", + "bidPrice": "0.00132069", + "bidQty": "184.00000000", + "askPrice": "0.00132565", + "askQty": "123.00000000", + "openPrice": "0.00134960", + "highPrice": "0.00135400", + "lowPrice": "0.00132010", + "volume": "649017.00000000", + "quoteVolume": "867.60063383", + "openTime": 1560210239281, + "closeTime": 1560296639281, + "firstId": 3103446, + "lastId": 3106237, + "count": 2792 + }, + { + "symbol": "BATBNB", + "priceChange": "-0.00024000", + "priceChangePercent": "-2.312", + "weightedAvgPrice": "0.01029064", + "prevClosePrice": "0.01041000", + "lastPrice": "0.01014000", + "lastQty": "386.50000000", + "bidPrice": "0.01014000", + "bidQty": "2558.60000000", + "askPrice": "0.01017000", + "askQty": "1322.10000000", + "openPrice": "0.01038000", + "highPrice": "0.01042000", + "lowPrice": "0.01014000", + "volume": "250421.60000000", + "quoteVolume": "2576.99801800", + "openTime": 1560210235103, + "closeTime": 1560296635103, + "firstId": 824117, + "lastId": 825016, + "count": 900 + }, + { + "symbol": "BCPTBTC", + "priceChange": "-0.00000006", + "priceChangePercent": "-0.779", + "weightedAvgPrice": "0.00000760", + "prevClosePrice": "0.00000770", + "lastPrice": "0.00000764", + "lastQty": "3256.00000000", + "bidPrice": "0.00000764", + "bidQty": "4577.00000000", + "askPrice": "0.00000765", + "askQty": "132.00000000", + "openPrice": "0.00000770", + "highPrice": "0.00000779", + "lowPrice": "0.00000744", + "volume": "10863972.00000000", + "quoteVolume": "82.53020301", + "openTime": 1560210235092, + "closeTime": 1560296635092, + "firstId": 7168287, + "lastId": 7172651, + "count": 4365 + }, + { + "symbol": "BCPTETH", + "priceChange": "-0.00000417", + "priceChangePercent": "-1.661", + "weightedAvgPrice": "0.00024664", + "prevClosePrice": "0.00024914", + "lastPrice": "0.00024692", + "lastQty": "107.00000000", + "bidPrice": "0.00024545", + "bidQty": "4577.00000000", + "askPrice": "0.00024730", + "askQty": "908.00000000", + "openPrice": "0.00025109", + "highPrice": "0.00025243", + "lowPrice": "0.00024100", + "volume": "544840.00000000", + "quoteVolume": "134.37784875", + "openTime": 1560210238867, + "closeTime": 1560296638867, + "firstId": 1401950, + "lastId": 1402486, + "count": 537 + }, + { + "symbol": "BCPTBNB", + "priceChange": "-0.00005000", + "priceChangePercent": "-2.591", + "weightedAvgPrice": "0.00189467", + "prevClosePrice": "0.00194000", + "lastPrice": "0.00188000", + "lastQty": "224.90000000", + "bidPrice": "0.00188000", + "bidQty": "5015.90000000", + "askPrice": "0.00190000", + "askQty": "5391.20000000", + "openPrice": "0.00193000", + "highPrice": "0.00196000", + "lowPrice": "0.00186000", + "volume": "918395.90000000", + "quoteVolume": "1740.05855900", + "openTime": 1560210225652, + "closeTime": 1560296625652, + "firstId": 434120, + "lastId": 434499, + "count": 380 + }, + { + "symbol": "ARNBTC", + "priceChange": "-0.00000063", + "priceChangePercent": "-1.020", + "weightedAvgPrice": "0.00006161", + "prevClosePrice": "0.00006175", + "lastPrice": "0.00006112", + "lastQty": "2153.00000000", + "bidPrice": "0.00006106", + "bidQty": "2000.00000000", + "askPrice": "0.00006124", + "askQty": "132.00000000", + "openPrice": "0.00006175", + "highPrice": "0.00006325", + "lowPrice": "0.00006070", + "volume": "1897758.00000000", + "quoteVolume": "116.91851829", + "openTime": 1560210241233, + "closeTime": 1560296641233, + "firstId": 12882845, + "lastId": 12887958, + "count": 5114 + }, + { + "symbol": "ARNETH", + "priceChange": "-0.00002913", + "priceChangePercent": "-1.459", + "weightedAvgPrice": "0.00199587", + "prevClosePrice": "0.00199769", + "lastPrice": "0.00196702", + "lastQty": "10.00000000", + "bidPrice": "0.00196370", + "bidQty": "508.00000000", + "askPrice": "0.00197444", + "askQty": "146.00000000", + "openPrice": "0.00199615", + "highPrice": "0.00204300", + "lowPrice": "0.00195602", + "volume": "188365.00000000", + "quoteVolume": "375.95179014", + "openTime": 1560210240421, + "closeTime": 1560296640421, + "firstId": 4281415, + "lastId": 4282333, + "count": 919 + }, + { + "symbol": "GVTBTC", + "priceChange": "-0.00000750", + "priceChangePercent": "-1.634", + "weightedAvgPrice": "0.00045413", + "prevClosePrice": "0.00045790", + "lastPrice": "0.00045140", + "lastQty": "13.25000000", + "bidPrice": "0.00045170", + "bidQty": "47.65000000", + "askPrice": "0.00045290", + "askQty": "13.25000000", + "openPrice": "0.00045890", + "highPrice": "0.00046790", + "lowPrice": "0.00044800", + "volume": "96628.17000000", + "quoteVolume": "43.88187023", + "openTime": 1560210241809, + "closeTime": 1560296641809, + "firstId": 7069285, + "lastId": 7071895, + "count": 2611 + }, + { + "symbol": "GVTETH", + "priceChange": "-0.00021700", + "priceChangePercent": "-1.463", + "weightedAvgPrice": "0.01468622", + "prevClosePrice": "0.01486200", + "lastPrice": "0.01461500", + "lastQty": "6.46000000", + "bidPrice": "0.01452600", + "bidQty": "69.00000000", + "askPrice": "0.01461700", + "askQty": "20.42000000", + "openPrice": "0.01483200", + "highPrice": "0.01504500", + "lowPrice": "0.01446000", + "volume": "6478.14000000", + "quoteVolume": "95.13935858", + "openTime": 1560210217399, + "closeTime": 1560296617399, + "firstId": 1409021, + "lastId": 1409573, + "count": 553 + }, + { + "symbol": "CDTBTC", + "priceChange": "0.00000004", + "priceChangePercent": "3.008", + "weightedAvgPrice": "0.00000136", + "prevClosePrice": "0.00000132", + "lastPrice": "0.00000137", + "lastQty": "7761.00000000", + "bidPrice": "0.00000137", + "bidQty": "49773.00000000", + "askPrice": "0.00000138", + "askQty": "40467.00000000", + "openPrice": "0.00000133", + "highPrice": "0.00000140", + "lowPrice": "0.00000130", + "volume": "37827845.00000000", + "quoteVolume": "51.31227442", + "openTime": 1560210239344, + "closeTime": 1560296639344, + "firstId": 4238131, + "lastId": 4240680, + "count": 2550 + }, + { + "symbol": "CDTETH", + "priceChange": "0.00000134", + "priceChangePercent": "3.139", + "weightedAvgPrice": "0.00004455", + "prevClosePrice": "0.00004269", + "lastPrice": "0.00004403", + "lastQty": "600.00000000", + "bidPrice": "0.00004402", + "bidQty": "49773.00000000", + "askPrice": "0.00004424", + "askQty": "857.00000000", + "openPrice": "0.00004269", + "highPrice": "0.00004550", + "lowPrice": "0.00004240", + "volume": "2710915.00000000", + "quoteVolume": "120.76179468", + "openTime": 1560210241981, + "closeTime": 1560296641981, + "firstId": 1835497, + "lastId": 1836032, + "count": 536 + }, + { + "symbol": "GXSBTC", + "priceChange": "0.00000340", + "priceChangePercent": "1.162", + "weightedAvgPrice": "0.00029066", + "prevClosePrice": "0.00029280", + "lastPrice": "0.00029610", + "lastQty": "84.93000000", + "bidPrice": "0.00029580", + "bidQty": "157.75000000", + "askPrice": "0.00029640", + "askQty": "101.38000000", + "openPrice": "0.00029270", + "highPrice": "0.00032200", + "lowPrice": "0.00025900", + "volume": "3149184.66000000", + "quoteVolume": "915.34318858", + "openTime": 1560210242322, + "closeTime": 1560296642322, + "firstId": 3586935, + "lastId": 3622172, + "count": 35238 + }, + { + "symbol": "GXSETH", + "priceChange": "0.00004900", + "priceChangePercent": "0.516", + "weightedAvgPrice": "0.00940418", + "prevClosePrice": "0.00948900", + "lastPrice": "0.00954000", + "lastQty": "18.87000000", + "bidPrice": "0.00951500", + "bidQty": "157.75000000", + "askPrice": "0.00955500", + "askQty": "16.65000000", + "openPrice": "0.00949100", + "highPrice": "0.01041100", + "lowPrice": "0.00838700", + "volume": "243866.40000000", + "quoteVolume": "2293.36358499", + "openTime": 1560210242051, + "closeTime": 1560296642051, + "firstId": 1238669, + "lastId": 1245114, + "count": 6446 + }, + { + "symbol": "NEOUSDT", + "priceChange": "0.02200000", + "priceChangePercent": "0.180", + "weightedAvgPrice": "12.08073978", + "prevClosePrice": "12.24700000", + "lastPrice": "12.26700000", + "lastQty": "70.00000000", + "bidPrice": "12.26700000", + "bidQty": "1.17000000", + "askPrice": "12.26800000", + "askQty": "4.27300000", + "openPrice": "12.24500000", + "highPrice": "12.42000000", + "lowPrice": "11.68800000", + "volume": "558424.34900000", + "quoteVolume": "6746179.24927800", + "openTime": 1560210237792, + "closeTime": 1560296637792, + "firstId": 18155404, + "lastId": 18176825, + "count": 21422 + }, + { + "symbol": "NEOBNB", + "priceChange": "0.00200000", + "priceChangePercent": "0.522", + "weightedAvgPrice": "0.38337832", + "prevClosePrice": "0.38400000", + "lastPrice": "0.38500000", + "lastQty": "146.48000000", + "bidPrice": "0.38400000", + "bidQty": "27.94300000", + "askPrice": "0.38500000", + "askQty": "115.57700000", + "openPrice": "0.38300000", + "highPrice": "0.39200000", + "lowPrice": "0.37800000", + "volume": "19920.56500000", + "quoteVolume": "7637.11281000", + "openTime": 1560210237381, + "closeTime": 1560296637381, + "firstId": 2038710, + "lastId": 2042095, + "count": 3386 + }, + { + "symbol": "POEBTC", + "priceChange": "-0.00000001", + "priceChangePercent": "-1.538", + "weightedAvgPrice": "0.00000065", + "prevClosePrice": "0.00000065", + "lastPrice": "0.00000064", + "lastQty": "235277.00000000", + "bidPrice": "0.00000064", + "bidQty": "3222870.00000000", + "askPrice": "0.00000065", + "askQty": "312602.00000000", + "openPrice": "0.00000065", + "highPrice": "0.00000067", + "lowPrice": "0.00000064", + "volume": "70540139.00000000", + "quoteVolume": "45.74407070", + "openTime": 1560210239171, + "closeTime": 1560296639171, + "firstId": 7566317, + "lastId": 7567421, + "count": 1105 + }, + { + "symbol": "POEETH", + "priceChange": "-0.00000049", + "priceChangePercent": "-2.300", + "weightedAvgPrice": "0.00002105", + "prevClosePrice": "0.00002132", + "lastPrice": "0.00002081", + "lastQty": "1554.00000000", + "bidPrice": "0.00002078", + "bidQty": "968.00000000", + "askPrice": "0.00002095", + "askQty": "4784.00000000", + "openPrice": "0.00002130", + "highPrice": "0.00002156", + "lowPrice": "0.00002068", + "volume": "3379172.00000000", + "quoteVolume": "71.12596409", + "openTime": 1560210194782, + "closeTime": 1560296594782, + "firstId": 2488105, + "lastId": 2488377, + "count": 273 + }, + { + "symbol": "QSPBTC", + "priceChange": "-0.00000002", + "priceChangePercent": "-0.592", + "weightedAvgPrice": "0.00000335", + "prevClosePrice": "0.00000339", + "lastPrice": "0.00000336", + "lastQty": "1016.00000000", + "bidPrice": "0.00000335", + "bidQty": "212420.00000000", + "askPrice": "0.00000336", + "askQty": "115244.00000000", + "openPrice": "0.00000338", + "highPrice": "0.00000342", + "lowPrice": "0.00000327", + "volume": "10066422.00000000", + "quoteVolume": "33.76750494", + "openTime": 1560210241979, + "closeTime": 1560296641979, + "firstId": 4840090, + "lastId": 4841711, + "count": 1622 + }, + { + "symbol": "QSPETH", + "priceChange": "-0.00000122", + "priceChangePercent": "-1.110", + "weightedAvgPrice": "0.00010943", + "prevClosePrice": "0.00010951", + "lastPrice": "0.00010873", + "lastQty": "127.00000000", + "bidPrice": "0.00010781", + "bidQty": "108.00000000", + "askPrice": "0.00010850", + "askQty": "8321.00000000", + "openPrice": "0.00010995", + "highPrice": "0.00011545", + "lowPrice": "0.00010615", + "volume": "379490.00000000", + "quoteVolume": "41.52813557", + "openTime": 1560210242240, + "closeTime": 1560296642240, + "firstId": 1564258, + "lastId": 1564685, + "count": 428 + }, + { + "symbol": "QSPBNB", + "priceChange": "-0.00001100", + "priceChangePercent": "-1.306", + "weightedAvgPrice": "0.00084182", + "prevClosePrice": "0.00084200", + "lastPrice": "0.00083100", + "lastQty": "1108.00000000", + "bidPrice": "0.00082900", + "bidQty": "262.00000000", + "askPrice": "0.00083200", + "askQty": "10769.00000000", + "openPrice": "0.00084200", + "highPrice": "0.00085600", + "lowPrice": "0.00082200", + "volume": "816912.00000000", + "quoteVolume": "687.69429200", + "openTime": 1560210242143, + "closeTime": 1560296642143, + "firstId": 601000, + "lastId": 601296, + "count": 297 + }, + { + "symbol": "BTSBTC", + "priceChange": "0.00000004", + "priceChangePercent": "0.516", + "weightedAvgPrice": "0.00000775", + "prevClosePrice": "0.00000775", + "lastPrice": "0.00000779", + "lastQty": "1326.00000000", + "bidPrice": "0.00000777", + "bidQty": "12870.00000000", + "askPrice": "0.00000779", + "askQty": "3978.00000000", + "openPrice": "0.00000775", + "highPrice": "0.00000782", + "lowPrice": "0.00000765", + "volume": "2969793.00000000", + "quoteVolume": "23.02004965", + "openTime": 1560210239025, + "closeTime": 1560296639025, + "firstId": 5347398, + "lastId": 5349854, + "count": 2457 + }, + { + "symbol": "BTSETH", + "priceChange": "-0.00000079", + "priceChangePercent": "-0.315", + "weightedAvgPrice": "0.00025104", + "prevClosePrice": "0.00025060", + "lastPrice": "0.00024981", + "lastQty": "75.00000000", + "bidPrice": "0.00025011", + "bidQty": "481.00000000", + "askPrice": "0.00025162", + "askQty": "560.00000000", + "openPrice": "0.00025060", + "highPrice": "0.00025341", + "lowPrice": "0.00024788", + "volume": "226591.00000000", + "quoteVolume": "56.88231642", + "openTime": 1560210232049, + "closeTime": 1560296632049, + "firstId": 1586243, + "lastId": 1586484, + "count": 242 + }, + { + "symbol": "BTSBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00193092", + "prevClosePrice": "0.00194000", + "lastPrice": "0.00194000", + "lastQty": "82.00000000", + "bidPrice": "0.00192000", + "bidQty": "5887.30000000", + "askPrice": "0.00194000", + "askQty": "25392.60000000", + "openPrice": "0.00194000", + "highPrice": "0.00195000", + "lowPrice": "0.00190000", + "volume": "55264.80000000", + "quoteVolume": "106.71173700", + "openTime": 1560210234586, + "closeTime": 1560296634586, + "firstId": 344266, + "lastId": 344336, + "count": 71 + }, + { + "symbol": "XZCBTC", + "priceChange": "0.00004000", + "priceChangePercent": "3.265", + "weightedAvgPrice": "0.00121648", + "prevClosePrice": "0.00122600", + "lastPrice": "0.00126500", + "lastQty": "2.60000000", + "bidPrice": "0.00126500", + "bidQty": "77.50000000", + "askPrice": "0.00126600", + "askQty": "3.16000000", + "openPrice": "0.00122500", + "highPrice": "0.00131000", + "lowPrice": "0.00116000", + "volume": "190665.49000000", + "quoteVolume": "231.94161761", + "openTime": 1560210242123, + "closeTime": 1560296642123, + "firstId": 3055623, + "lastId": 3068630, + "count": 13008 + }, + { + "symbol": "XZCETH", + "priceChange": "0.00124600", + "priceChangePercent": "3.148", + "weightedAvgPrice": "0.03937281", + "prevClosePrice": "0.03996600", + "lastPrice": "0.04082500", + "lastQty": "0.87000000", + "bidPrice": "0.04065700", + "bidQty": "77.50000000", + "askPrice": "0.04115100", + "askQty": "1.03000000", + "openPrice": "0.03957900", + "highPrice": "0.04158000", + "lowPrice": "0.03796800", + "volume": "9518.75000000", + "quoteVolume": "374.77996142", + "openTime": 1560210242338, + "closeTime": 1560296642338, + "firstId": 602091, + "lastId": 603636, + "count": 1546 + }, + { + "symbol": "XZCBNB", + "priceChange": "0.00700000", + "priceChangePercent": "2.288", + "weightedAvgPrice": "0.30573753", + "prevClosePrice": "0.30800000", + "lastPrice": "0.31300000", + "lastQty": "0.80000000", + "bidPrice": "0.31200000", + "bidQty": "1.53100000", + "askPrice": "0.31600000", + "askQty": "29.15800000", + "openPrice": "0.30600000", + "highPrice": "0.32400000", + "lowPrice": "0.29200000", + "volume": "7162.17000000", + "quoteVolume": "2189.74416100", + "openTime": 1560210241793, + "closeTime": 1560296641793, + "firstId": 189846, + "lastId": 190720, + "count": 875 + }, + { + "symbol": "LSKBTC", + "priceChange": "0.00000440", + "priceChangePercent": "1.711", + "weightedAvgPrice": "0.00025927", + "prevClosePrice": "0.00025720", + "lastPrice": "0.00026150", + "lastQty": "32.57000000", + "bidPrice": "0.00026130", + "bidQty": "4.59000000", + "askPrice": "0.00026190", + "askQty": "30.90000000", + "openPrice": "0.00025710", + "highPrice": "0.00026610", + "lowPrice": "0.00025300", + "volume": "330389.31000000", + "quoteVolume": "85.65874582", + "openTime": 1560210241547, + "closeTime": 1560296641547, + "firstId": 6393123, + "lastId": 6397948, + "count": 4826 + }, + { + "symbol": "LSKETH", + "priceChange": "0.00010100", + "priceChangePercent": "1.211", + "weightedAvgPrice": "0.00839188", + "prevClosePrice": "0.00834300", + "lastPrice": "0.00844400", + "lastQty": "107.00000000", + "bidPrice": "0.00839100", + "bidQty": "9.44000000", + "askPrice": "0.00847400", + "askQty": "19.86000000", + "openPrice": "0.00834300", + "highPrice": "0.00860600", + "lowPrice": "0.00819900", + "volume": "17398.19000000", + "quoteVolume": "146.00345830", + "openTime": 1560210239221, + "closeTime": 1560296639221, + "firstId": 1573772, + "lastId": 1574447, + "count": 676 + }, + { + "symbol": "LSKBNB", + "priceChange": "0.00060000", + "priceChangePercent": "0.936", + "weightedAvgPrice": "0.06458953", + "prevClosePrice": "0.06410000", + "lastPrice": "0.06470000", + "lastQty": "46.36000000", + "bidPrice": "0.06440000", + "bidQty": "4.59000000", + "askPrice": "0.06490000", + "askQty": "17.81000000", + "openPrice": "0.06410000", + "highPrice": "0.06630000", + "lowPrice": "0.06300000", + "volume": "15686.06000000", + "quoteVolume": "1013.15522400", + "openTime": 1560210239225, + "closeTime": 1560296639225, + "firstId": 366952, + "lastId": 367311, + "count": 360 + }, + { + "symbol": "TNTBTC", + "priceChange": "0.00000011", + "priceChangePercent": "2.619", + "weightedAvgPrice": "0.00000429", + "prevClosePrice": "0.00000420", + "lastPrice": "0.00000431", + "lastQty": "159.00000000", + "bidPrice": "0.00000429", + "bidQty": "5537.00000000", + "askPrice": "0.00000431", + "askQty": "68079.00000000", + "openPrice": "0.00000420", + "highPrice": "0.00000448", + "lowPrice": "0.00000410", + "volume": "42295389.00000000", + "quoteVolume": "181.54155203", + "openTime": 1560210239553, + "closeTime": 1560296639553, + "firstId": 4797056, + "lastId": 4804445, + "count": 7390 + }, + { + "symbol": "TNTETH", + "priceChange": "0.00000290", + "priceChangePercent": "2.132", + "weightedAvgPrice": "0.00013902", + "prevClosePrice": "0.00013600", + "lastPrice": "0.00013891", + "lastQty": "87.00000000", + "bidPrice": "0.00013796", + "bidQty": "1848.00000000", + "askPrice": "0.00013891", + "askQty": "1762.00000000", + "openPrice": "0.00013601", + "highPrice": "0.00014520", + "lowPrice": "0.00013266", + "volume": "2405837.00000000", + "quoteVolume": "334.46647343", + "openTime": 1560210242074, + "closeTime": 1560296642074, + "firstId": 1229562, + "lastId": 1231269, + "count": 1708 + }, + { + "symbol": "FUELBTC", + "priceChange": "0.00000003", + "priceChangePercent": "2.632", + "weightedAvgPrice": "0.00000115", + "prevClosePrice": "0.00000114", + "lastPrice": "0.00000117", + "lastQty": "108434.00000000", + "bidPrice": "0.00000116", + "bidQty": "623780.00000000", + "askPrice": "0.00000117", + "askQty": "143431.00000000", + "openPrice": "0.00000114", + "highPrice": "0.00000119", + "lowPrice": "0.00000110", + "volume": "130322645.00000000", + "quoteVolume": "149.77708954", + "openTime": 1560210241549, + "closeTime": 1560296641549, + "firstId": 5171023, + "lastId": 5175128, + "count": 4106 + }, + { + "symbol": "FUELETH", + "priceChange": "0.00000056", + "priceChangePercent": "1.513", + "weightedAvgPrice": "0.00003773", + "prevClosePrice": "0.00003702", + "lastPrice": "0.00003757", + "lastQty": "10587.00000000", + "bidPrice": "0.00003754", + "bidQty": "6600.00000000", + "askPrice": "0.00003758", + "askQty": "434.00000000", + "openPrice": "0.00003701", + "highPrice": "0.00003835", + "lowPrice": "0.00003592", + "volume": "16293732.00000000", + "quoteVolume": "614.73045781", + "openTime": 1560210241436, + "closeTime": 1560296641436, + "firstId": 2048997, + "lastId": 2050957, + "count": 1961 + }, + { + "symbol": "MANABTC", + "priceChange": "-0.00000001", + "priceChangePercent": "-0.133", + "weightedAvgPrice": "0.00000746", + "prevClosePrice": "0.00000752", + "lastPrice": "0.00000752", + "lastQty": "2567.00000000", + "bidPrice": "0.00000750", + "bidQty": "7607.00000000", + "askPrice": "0.00000753", + "askQty": "3854.00000000", + "openPrice": "0.00000753", + "highPrice": "0.00000769", + "lowPrice": "0.00000732", + "volume": "11159576.00000000", + "quoteVolume": "83.24482390", + "openTime": 1560210239108, + "closeTime": 1560296639108, + "firstId": 6994197, + "lastId": 6998811, + "count": 4615 + }, + { + "symbol": "MANAETH", + "priceChange": "-0.00000287", + "priceChangePercent": "-1.172", + "weightedAvgPrice": "0.00024140", + "prevClosePrice": "0.00024419", + "lastPrice": "0.00024195", + "lastQty": "4588.00000000", + "bidPrice": "0.00024126", + "bidQty": "698.00000000", + "askPrice": "0.00024297", + "askQty": "1434.00000000", + "openPrice": "0.00024482", + "highPrice": "0.00024893", + "lowPrice": "0.00023749", + "volume": "1134408.00000000", + "quoteVolume": "273.85075075", + "openTime": 1560210241907, + "closeTime": 1560296641907, + "firstId": 2420496, + "lastId": 2421410, + "count": 915 + }, + { + "symbol": "BCDBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00015141", + "prevClosePrice": "0.00015200", + "lastPrice": "0.00015200", + "lastQty": "49.95000000", + "bidPrice": "0.00015200", + "bidQty": "209.59500000", + "askPrice": "0.00015300", + "askQty": "2009.10400000", + "openPrice": "0.00015200", + "highPrice": "0.00015400", + "lowPrice": "0.00014800", + "volume": "384708.63100000", + "quoteVolume": "58.24957044", + "openTime": 1560210241845, + "closeTime": 1560296641845, + "firstId": 6011931, + "lastId": 6014426, + "count": 2496 + }, + { + "symbol": "BCDETH", + "priceChange": "-0.00002000", + "priceChangePercent": "-0.406", + "weightedAvgPrice": "0.00490731", + "prevClosePrice": "0.00491000", + "lastPrice": "0.00491000", + "lastQty": "39.87000000", + "bidPrice": "0.00488000", + "bidQty": "226.63600000", + "askPrice": "0.00492000", + "askQty": "51.99100000", + "openPrice": "0.00493000", + "highPrice": "0.00496000", + "lowPrice": "0.00481000", + "volume": "32175.21400000", + "quoteVolume": "157.89366369", + "openTime": 1560210241867, + "closeTime": 1560296641867, + "firstId": 1777866, + "lastId": 1779008, + "count": 1143 + }, + { + "symbol": "DGDBTC", + "priceChange": "-0.00005800", + "priceChangePercent": "-1.412", + "weightedAvgPrice": "0.00406719", + "prevClosePrice": "0.00410100", + "lastPrice": "0.00405100", + "lastQty": "5.44900000", + "bidPrice": "0.00404400", + "bidQty": "1.19000000", + "askPrice": "0.00405500", + "askQty": "0.81000000", + "openPrice": "0.00410900", + "highPrice": "0.00414000", + "lowPrice": "0.00400000", + "volume": "9401.70400000", + "quoteVolume": "38.23849952", + "openTime": 1560210237619, + "closeTime": 1560296637619, + "firstId": 8082266, + "lastId": 8084561, + "count": 2296 + }, + { + "symbol": "DGDETH", + "priceChange": "-0.00374000", + "priceChangePercent": "-2.799", + "weightedAvgPrice": "0.13071898", + "prevClosePrice": "0.13287000", + "lastPrice": "0.12989000", + "lastQty": "0.08000000", + "bidPrice": "0.12994000", + "bidQty": "5.91800000", + "askPrice": "0.13062000", + "askQty": "1.99900000", + "openPrice": "0.13363000", + "highPrice": "0.13435000", + "lowPrice": "0.12942000", + "volume": "1500.93000000", + "quoteVolume": "196.20004045", + "openTime": 1560210232671, + "closeTime": 1560296632671, + "firstId": 2779736, + "lastId": 2780244, + "count": 509 + }, + { + "symbol": "IOTABNB", + "priceChange": "-0.00020000", + "priceChangePercent": "-1.494", + "weightedAvgPrice": "0.01326593", + "prevClosePrice": "0.01338000", + "lastPrice": "0.01319000", + "lastQty": "326.60000000", + "bidPrice": "0.01321000", + "bidQty": "134.60000000", + "askPrice": "0.01324000", + "askQty": "460.30000000", + "openPrice": "0.01339000", + "highPrice": "0.01345000", + "lowPrice": "0.01310000", + "volume": "274865.30000000", + "quoteVolume": "3646.34306300", + "openTime": 1560210241573, + "closeTime": 1560296641573, + "firstId": 1376244, + "lastId": 1377556, + "count": 1313 + }, + { + "symbol": "ADXBTC", + "priceChange": "0.00000044", + "priceChangePercent": "2.141", + "weightedAvgPrice": "0.00002061", + "prevClosePrice": "0.00002059", + "lastPrice": "0.00002099", + "lastQty": "96.00000000", + "bidPrice": "0.00002094", + "bidQty": "412.00000000", + "askPrice": "0.00002099", + "askQty": "736.00000000", + "openPrice": "0.00002055", + "highPrice": "0.00002137", + "lowPrice": "0.00002020", + "volume": "2651714.00000000", + "quoteVolume": "54.65445544", + "openTime": 1560210241875, + "closeTime": 1560296641875, + "firstId": 4587339, + "lastId": 4591228, + "count": 3890 + }, + { + "symbol": "ADXETH", + "priceChange": "0.00000940", + "priceChangePercent": "1.411", + "weightedAvgPrice": "0.00066672", + "prevClosePrice": "0.00066600", + "lastPrice": "0.00067540", + "lastQty": "894.00000000", + "bidPrice": "0.00067250", + "bidQty": "412.00000000", + "askPrice": "0.00067650", + "askQty": "518.00000000", + "openPrice": "0.00066600", + "highPrice": "0.00068280", + "lowPrice": "0.00065280", + "volume": "248704.00000000", + "quoteVolume": "165.81511890", + "openTime": 1560210241071, + "closeTime": 1560296641071, + "firstId": 977950, + "lastId": 978775, + "count": 826 + }, + { + "symbol": "ADXBNB", + "priceChange": "0.00007000", + "priceChangePercent": "1.370", + "weightedAvgPrice": "0.00513947", + "prevClosePrice": "0.00513000", + "lastPrice": "0.00518000", + "lastQty": "1114.30000000", + "bidPrice": "0.00516000", + "bidQty": "412.00000000", + "askPrice": "0.00519000", + "askQty": "1670.20000000", + "openPrice": "0.00511000", + "highPrice": "0.00523000", + "lowPrice": "0.00506000", + "volume": "76119.70000000", + "quoteVolume": "391.21499400", + "openTime": 1560210234114, + "closeTime": 1560296634114, + "firstId": 501616, + "lastId": 501943, + "count": 328 + }, + { + "symbol": "ADABTC", + "priceChange": "0.00000046", + "priceChangePercent": "4.315", + "weightedAvgPrice": "0.00001089", + "prevClosePrice": "0.00001066", + "lastPrice": "0.00001112", + "lastQty": "7324.00000000", + "bidPrice": "0.00001111", + "bidQty": "244930.00000000", + "askPrice": "0.00001112", + "askQty": "565.00000000", + "openPrice": "0.00001066", + "highPrice": "0.00001127", + "lowPrice": "0.00001047", + "volume": "207174872.00000000", + "quoteVolume": "2256.97846038", + "openTime": 1560210242001, + "closeTime": 1560296642001, + "firstId": 28548729, + "lastId": 28590337, + "count": 41609 + }, + { + "symbol": "ADAETH", + "priceChange": "0.00001227", + "priceChangePercent": "3.552", + "weightedAvgPrice": "0.00035140", + "prevClosePrice": "0.00034510", + "lastPrice": "0.00035770", + "lastQty": "9622.00000000", + "bidPrice": "0.00035771", + "bidQty": "4066.00000000", + "askPrice": "0.00035840", + "askQty": "53.00000000", + "openPrice": "0.00034543", + "highPrice": "0.00036523", + "lowPrice": "0.00033970", + "volume": "8759588.00000000", + "quoteVolume": "3078.12940777", + "openTime": 1560210240851, + "closeTime": 1560296640851, + "firstId": 9128113, + "lastId": 9134567, + "count": 6455 + }, + { + "symbol": "PPTBTC", + "priceChange": "0.00000580", + "priceChangePercent": "4.064", + "weightedAvgPrice": "0.00015195", + "prevClosePrice": "0.00014180", + "lastPrice": "0.00014850", + "lastQty": "102.62000000", + "bidPrice": "0.00014790", + "bidQty": "58.14000000", + "askPrice": "0.00014840", + "askQty": "108.30000000", + "openPrice": "0.00014270", + "highPrice": "0.00016160", + "lowPrice": "0.00013940", + "volume": "1575892.74000000", + "quoteVolume": "239.46305904", + "openTime": 1560210242114, + "closeTime": 1560296642114, + "firstId": 4679377, + "lastId": 4694125, + "count": 14749 + }, + { + "symbol": "PPTETH", + "priceChange": "0.00017600", + "priceChangePercent": "3.806", + "weightedAvgPrice": "0.00492030", + "prevClosePrice": "0.00462800", + "lastPrice": "0.00480000", + "lastQty": "87.00000000", + "bidPrice": "0.00475400", + "bidQty": "58.14000000", + "askPrice": "0.00479700", + "askQty": "345.44000000", + "openPrice": "0.00462400", + "highPrice": "0.00526000", + "lowPrice": "0.00451700", + "volume": "203371.98000000", + "quoteVolume": "1000.65046327", + "openTime": 1560210234489, + "closeTime": 1560296634489, + "firstId": 1395613, + "lastId": 1398988, + "count": 3376 + }, + { + "symbol": "CMTBTC", + "priceChange": "-0.00000014", + "priceChangePercent": "-2.905", + "weightedAvgPrice": "0.00000464", + "prevClosePrice": "0.00000481", + "lastPrice": "0.00000468", + "lastQty": "3546.00000000", + "bidPrice": "0.00000466", + "bidQty": "10955.00000000", + "askPrice": "0.00000468", + "askQty": "21592.00000000", + "openPrice": "0.00000482", + "highPrice": "0.00000482", + "lowPrice": "0.00000453", + "volume": "16439833.00000000", + "quoteVolume": "76.23472262", + "openTime": 1560210238991, + "closeTime": 1560296638991, + "firstId": 6004774, + "lastId": 6009219, + "count": 4446 + }, + { + "symbol": "CMTETH", + "priceChange": "-0.00000713", + "priceChangePercent": "-4.559", + "weightedAvgPrice": "0.00015002", + "prevClosePrice": "0.00015570", + "lastPrice": "0.00014927", + "lastQty": "8628.00000000", + "bidPrice": "0.00015002", + "bidQty": "28556.00000000", + "askPrice": "0.00015059", + "askQty": "10727.00000000", + "openPrice": "0.00015640", + "highPrice": "0.00015640", + "lowPrice": "0.00014738", + "volume": "778759.00000000", + "quoteVolume": "116.83297989", + "openTime": 1560210240654, + "closeTime": 1560296640654, + "firstId": 2000458, + "lastId": 2001204, + "count": 747 + }, + { + "symbol": "CMTBNB", + "priceChange": "-0.00004000", + "priceChangePercent": "-3.333", + "weightedAvgPrice": "0.00116551", + "prevClosePrice": "0.00120000", + "lastPrice": "0.00116000", + "lastQty": "6876.60000000", + "bidPrice": "0.00115000", + "bidQty": "11137.50000000", + "askPrice": "0.00116000", + "askQty": "3508.30000000", + "openPrice": "0.00120000", + "highPrice": "0.00121000", + "lowPrice": "0.00113000", + "volume": "260039.20000000", + "quoteVolume": "303.07906600", + "openTime": 1560210218019, + "closeTime": 1560296618019, + "firstId": 512977, + "lastId": 513198, + "count": 222 + }, + { + "symbol": "XLMBTC", + "priceChange": "0.00000008", + "priceChangePercent": "0.518", + "weightedAvgPrice": "0.00001541", + "prevClosePrice": "0.00001543", + "lastPrice": "0.00001551", + "lastQty": "22542.00000000", + "bidPrice": "0.00001550", + "bidQty": "30314.00000000", + "askPrice": "0.00001551", + "askQty": "29005.00000000", + "openPrice": "0.00001543", + "highPrice": "0.00001558", + "lowPrice": "0.00001529", + "volume": "19500707.00000000", + "quoteVolume": "300.42544956", + "openTime": 1560210242291, + "closeTime": 1560296642291, + "firstId": 24200410, + "lastId": 24211653, + "count": 11244 + }, + { + "symbol": "XLMETH", + "priceChange": "-0.00000064", + "priceChangePercent": "-0.128", + "weightedAvgPrice": "0.00049871", + "prevClosePrice": "0.00049885", + "lastPrice": "0.00049873", + "lastQty": "885.00000000", + "bidPrice": "0.00049872", + "bidQty": "629.00000000", + "askPrice": "0.00050032", + "askQty": "1406.00000000", + "openPrice": "0.00049937", + "highPrice": "0.00050429", + "lowPrice": "0.00049389", + "volume": "1302515.00000000", + "quoteVolume": "649.57993912", + "openTime": 1560210237948, + "closeTime": 1560296637948, + "firstId": 6764869, + "lastId": 6766357, + "count": 1489 + }, + { + "symbol": "XLMBNB", + "priceChange": "-0.00001000", + "priceChangePercent": "-0.260", + "weightedAvgPrice": "0.00383965", + "prevClosePrice": "0.00385000", + "lastPrice": "0.00384000", + "lastQty": "50.00000000", + "bidPrice": "0.00383000", + "bidQty": "3023.20000000", + "askPrice": "0.00384000", + "askQty": "742.50000000", + "openPrice": "0.00385000", + "highPrice": "0.00388000", + "lowPrice": "0.00380000", + "volume": "691538.20000000", + "quoteVolume": "2655.26581400", + "openTime": 1560210236651, + "closeTime": 1560296636651, + "firstId": 1702849, + "lastId": 1703832, + "count": 984 + }, + { + "symbol": "CNDBTC", + "priceChange": "-0.00000007", + "priceChangePercent": "-2.917", + "weightedAvgPrice": "0.00000234", + "prevClosePrice": "0.00000241", + "lastPrice": "0.00000233", + "lastQty": "3788.00000000", + "bidPrice": "0.00000232", + "bidQty": "10024.00000000", + "askPrice": "0.00000233", + "askQty": "78342.00000000", + "openPrice": "0.00000240", + "highPrice": "0.00000242", + "lowPrice": "0.00000226", + "volume": "10172405.00000000", + "quoteVolume": "23.78748630", + "openTime": 1560210241927, + "closeTime": 1560296641927, + "firstId": 5768128, + "lastId": 5769580, + "count": 1453 + }, + { + "symbol": "CNDETH", + "priceChange": "-0.00000239", + "priceChangePercent": "-3.084", + "weightedAvgPrice": "0.00007554", + "prevClosePrice": "0.00007712", + "lastPrice": "0.00007511", + "lastQty": "514.00000000", + "bidPrice": "0.00007482", + "bidQty": "353.00000000", + "askPrice": "0.00007511", + "askQty": "12513.00000000", + "openPrice": "0.00007750", + "highPrice": "0.00007820", + "lowPrice": "0.00007351", + "volume": "1200307.00000000", + "quoteVolume": "90.67116129", + "openTime": 1560210241560, + "closeTime": 1560296641560, + "firstId": 1991581, + "lastId": 1992137, + "count": 557 + }, + { + "symbol": "CNDBNB", + "priceChange": "-0.00002000", + "priceChangePercent": "-3.350", + "weightedAvgPrice": "0.00058433", + "prevClosePrice": "0.00059900", + "lastPrice": "0.00057700", + "lastQty": "16754.00000000", + "bidPrice": "0.00057300", + "bidQty": "10466.00000000", + "askPrice": "0.00057700", + "askQty": "4498.00000000", + "openPrice": "0.00059700", + "highPrice": "0.00060100", + "lowPrice": "0.00057400", + "volume": "644039.00000000", + "quoteVolume": "376.33171200", + "openTime": 1560210240388, + "closeTime": 1560296640388, + "firstId": 980442, + "lastId": 980606, + "count": 165 + }, + { + "symbol": "LENDBTC", + "priceChange": "0.00000009", + "priceChangePercent": "7.692", + "weightedAvgPrice": "0.00000128", + "prevClosePrice": "0.00000118", + "lastPrice": "0.00000126", + "lastQty": "49493.00000000", + "bidPrice": "0.00000125", + "bidQty": "112998.00000000", + "askPrice": "0.00000126", + "askQty": "118341.00000000", + "openPrice": "0.00000117", + "highPrice": "0.00000140", + "lowPrice": "0.00000114", + "volume": "273830746.00000000", + "quoteVolume": "351.27304656", + "openTime": 1560210239248, + "closeTime": 1560296639248, + "firstId": 5087959, + "lastId": 5099343, + "count": 11385 + }, + { + "symbol": "LENDETH", + "priceChange": "0.00000281", + "priceChangePercent": "7.408", + "weightedAvgPrice": "0.00004126", + "prevClosePrice": "0.00003813", + "lastPrice": "0.00004074", + "lastQty": "271.00000000", + "bidPrice": "0.00004036", + "bidQty": "722.00000000", + "askPrice": "0.00004074", + "askQty": "37842.00000000", + "openPrice": "0.00003793", + "highPrice": "0.00004526", + "lowPrice": "0.00003712", + "volume": "10121577.00000000", + "quoteVolume": "417.63617230", + "openTime": 1560210242226, + "closeTime": 1560296642226, + "firstId": 1853170, + "lastId": 1855291, + "count": 2122 + }, + { + "symbol": "WABIBTC", + "priceChange": "-0.00000086", + "priceChangePercent": "-2.072", + "weightedAvgPrice": "0.00004118", + "prevClosePrice": "0.00004150", + "lastPrice": "0.00004064", + "lastQty": "470.00000000", + "bidPrice": "0.00004063", + "bidQty": "1392.00000000", + "askPrice": "0.00004078", + "askQty": "49.00000000", + "openPrice": "0.00004150", + "highPrice": "0.00004247", + "lowPrice": "0.00003992", + "volume": "4118445.00000000", + "quoteVolume": "169.59354636", + "openTime": 1560210238799, + "closeTime": 1560296638799, + "firstId": 5961575, + "lastId": 5970450, + "count": 8876 + }, + { + "symbol": "WABIETH", + "priceChange": "-0.00002833", + "priceChangePercent": "-2.103", + "weightedAvgPrice": "0.00133710", + "prevClosePrice": "0.00134902", + "lastPrice": "0.00131909", + "lastQty": "10.00000000", + "bidPrice": "0.00131044", + "bidQty": "28.00000000", + "askPrice": "0.00131696", + "askQty": "36.00000000", + "openPrice": "0.00134742", + "highPrice": "0.00137966", + "lowPrice": "0.00129806", + "volume": "134563.00000000", + "quoteVolume": "179.92455716", + "openTime": 1560210240452, + "closeTime": 1560296640452, + "firstId": 1318310, + "lastId": 1319337, + "count": 1028 + }, + { + "symbol": "WABIBNB", + "priceChange": "-0.00027000", + "priceChangePercent": "-2.616", + "weightedAvgPrice": "0.01024498", + "prevClosePrice": "0.01038000", + "lastPrice": "0.01005000", + "lastQty": "12.00000000", + "bidPrice": "0.01005000", + "bidQty": "17.60000000", + "askPrice": "0.01008000", + "askQty": "428.00000000", + "openPrice": "0.01032000", + "highPrice": "0.01058000", + "lowPrice": "0.01005000", + "volume": "292635.20000000", + "quoteVolume": "2998.04069300", + "openTime": 1560210240459, + "closeTime": 1560296640459, + "firstId": 453632, + "lastId": 454376, + "count": 745 + }, + { + "symbol": "LTCETH", + "priceChange": "0.03159000", + "priceChangePercent": "6.038", + "weightedAvgPrice": "0.54400202", + "prevClosePrice": "0.52315000", + "lastPrice": "0.55474000", + "lastQty": "0.36200000", + "bidPrice": "0.55422000", + "bidQty": "7.55200000", + "askPrice": "0.55474000", + "askQty": "1.42100000", + "openPrice": "0.52315000", + "highPrice": "0.56870000", + "lowPrice": "0.51847000", + "volume": "23311.85100000", + "quoteVolume": "12681.69405103", + "openTime": 1560210241304, + "closeTime": 1560296641304, + "firstId": 5909053, + "lastId": 5925864, + "count": 16812 + }, + { + "symbol": "LTCUSDT", + "priceChange": "7.02000000", + "priceChangePercent": "5.452", + "weightedAvgPrice": "130.59813352", + "prevClosePrice": "128.80000000", + "lastPrice": "135.77000000", + "lastQty": "0.12000000", + "bidPrice": "135.73000000", + "bidQty": "15.10902000", + "askPrice": "135.77000000", + "askQty": "0.83679000", + "openPrice": "128.75000000", + "highPrice": "138.56000000", + "lowPrice": "124.85000000", + "volume": "876053.04634000", + "quoteVolume": "114410892.71528510", + "openTime": 1560210242213, + "closeTime": 1560296642213, + "firstId": 23711560, + "lastId": 23864373, + "count": 152814 + }, + { + "symbol": "LTCBNB", + "priceChange": "0.22000000", + "priceChangePercent": "5.459", + "weightedAvgPrice": "4.18284207", + "prevClosePrice": "4.03000000", + "lastPrice": "4.25000000", + "lastQty": "0.76848000", + "bidPrice": "4.25000000", + "bidQty": "179.81538000", + "askPrice": "4.26000000", + "askQty": "24.23826000", + "openPrice": "4.03000000", + "highPrice": "4.37000000", + "lowPrice": "4.01000000", + "volume": "34512.60212000", + "quoteVolume": "144360.76396640", + "openTime": 1560210242318, + "closeTime": 1560296642318, + "firstId": 2551019, + "lastId": 2570818, + "count": 19800 + }, + { + "symbol": "TNBBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00000062", + "prevClosePrice": "0.00000062", + "lastPrice": "0.00000062", + "lastQty": "151117.00000000", + "bidPrice": "0.00000062", + "bidQty": "954748.00000000", + "askPrice": "0.00000063", + "askQty": "4265152.00000000", + "openPrice": "0.00000062", + "highPrice": "0.00000064", + "lowPrice": "0.00000061", + "volume": "86546834.00000000", + "quoteVolume": "53.89275459", + "openTime": 1560210237598, + "closeTime": 1560296637598, + "firstId": 4756877, + "lastId": 4758181, + "count": 1305 + }, + { + "symbol": "TNBETH", + "priceChange": "-0.00000026", + "priceChangePercent": "-1.286", + "weightedAvgPrice": "0.00002017", + "prevClosePrice": "0.00002021", + "lastPrice": "0.00001995", + "lastQty": "4053.00000000", + "bidPrice": "0.00001995", + "bidQty": "3093.00000000", + "askPrice": "0.00002009", + "askQty": "8755.00000000", + "openPrice": "0.00002021", + "highPrice": "0.00002073", + "lowPrice": "0.00001966", + "volume": "4910763.00000000", + "quoteVolume": "99.06634329", + "openTime": 1560210241563, + "closeTime": 1560296641563, + "firstId": 1877351, + "lastId": 1878023, + "count": 673 + }, + { + "symbol": "WAVESBTC", + "priceChange": "0.00000290", + "priceChangePercent": "0.975", + "weightedAvgPrice": "0.00030052", + "prevClosePrice": "0.00029720", + "lastPrice": "0.00030020", + "lastQty": "183.74000000", + "bidPrice": "0.00030020", + "bidQty": "137.16000000", + "askPrice": "0.00030030", + "askQty": "0.04000000", + "openPrice": "0.00029730", + "highPrice": "0.00030370", + "lowPrice": "0.00029680", + "volume": "2356893.03000000", + "quoteVolume": "708.28957464", + "openTime": 1560210242223, + "closeTime": 1560296642223, + "firstId": 16604142, + "lastId": 16635713, + "count": 31572 + }, + { + "symbol": "WAVESETH", + "priceChange": "0.00004800", + "priceChangePercent": "0.498", + "weightedAvgPrice": "0.00972609", + "prevClosePrice": "0.00963500", + "lastPrice": "0.00968200", + "lastQty": "170.71000000", + "bidPrice": "0.00965500", + "bidQty": "105.23000000", + "askPrice": "0.00968300", + "askQty": "329.56000000", + "openPrice": "0.00963400", + "highPrice": "0.00984800", + "lowPrice": "0.00960000", + "volume": "365473.86000000", + "quoteVolume": "3554.63099067", + "openTime": 1560210242326, + "closeTime": 1560296642326, + "firstId": 2237870, + "lastId": 2245213, + "count": 7344 + }, + { + "symbol": "WAVESBNB", + "priceChange": "0.00030000", + "priceChangePercent": "0.405", + "weightedAvgPrice": "0.07504166", + "prevClosePrice": "0.07410000", + "lastPrice": "0.07430000", + "lastQty": "266.56000000", + "bidPrice": "0.07410000", + "bidQty": "121.00000000", + "askPrice": "0.07440000", + "askQty": "314.85000000", + "openPrice": "0.07400000", + "highPrice": "0.07600000", + "lowPrice": "0.07370000", + "volume": "48267.48000000", + "quoteVolume": "3622.07190400", + "openTime": 1560210236756, + "closeTime": 1560296636756, + "firstId": 464346, + "lastId": 465000, + "count": 655 + }, + { + "symbol": "GTOBTC", + "priceChange": "0.00000001", + "priceChangePercent": "0.236", + "weightedAvgPrice": "0.00000416", + "prevClosePrice": "0.00000423", + "lastPrice": "0.00000424", + "lastQty": "728.00000000", + "bidPrice": "0.00000424", + "bidQty": "1508.00000000", + "askPrice": "0.00000425", + "askQty": "83728.00000000", + "openPrice": "0.00000423", + "highPrice": "0.00000430", + "lowPrice": "0.00000402", + "volume": "35051445.00000000", + "quoteVolume": "145.69004880", + "openTime": 1560210238659, + "closeTime": 1560296638659, + "firstId": 7835247, + "lastId": 7841339, + "count": 6093 + }, + { + "symbol": "GTOETH", + "priceChange": "0.00000014", + "priceChangePercent": "0.102", + "weightedAvgPrice": "0.00013511", + "prevClosePrice": "0.00013703", + "lastPrice": "0.00013708", + "lastQty": "1608.00000000", + "bidPrice": "0.00013626", + "bidQty": "1508.00000000", + "askPrice": "0.00013709", + "askQty": "3494.00000000", + "openPrice": "0.00013694", + "highPrice": "0.00013959", + "lowPrice": "0.00013077", + "volume": "1856991.00000000", + "quoteVolume": "250.88897236", + "openTime": 1560210236177, + "closeTime": 1560296636177, + "firstId": 2703957, + "lastId": 2704859, + "count": 903 + }, + { + "symbol": "GTOBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00104286", + "prevClosePrice": "0.00106000", + "lastPrice": "0.00105000", + "lastQty": "44315.20000000", + "bidPrice": "0.00104000", + "bidQty": "43019.10000000", + "askPrice": "0.00106000", + "askQty": "45027.00000000", + "openPrice": "0.00105000", + "highPrice": "0.00108000", + "lowPrice": "0.00101000", + "volume": "2091211.80000000", + "quoteVolume": "2180.83746000", + "openTime": 1560210234060, + "closeTime": 1560296634060, + "firstId": 1071500, + "lastId": 1071957, + "count": 458 + }, + { + "symbol": "ICXBTC", + "priceChange": "0.00000060", + "priceChangePercent": "1.271", + "weightedAvgPrice": "0.00004746", + "prevClosePrice": "0.00004730", + "lastPrice": "0.00004780", + "lastQty": "21.00000000", + "bidPrice": "0.00004780", + "bidQty": "78125.42000000", + "askPrice": "0.00004800", + "askQty": "27556.80000000", + "openPrice": "0.00004720", + "highPrice": "0.00004840", + "lowPrice": "0.00004680", + "volume": "8846923.94000000", + "quoteVolume": "419.84124719", + "openTime": 1560210241552, + "closeTime": 1560296641552, + "firstId": 18306830, + "lastId": 18314605, + "count": 7776 + }, + { + "symbol": "ICXETH", + "priceChange": "0.00000500", + "priceChangePercent": "0.326", + "weightedAvgPrice": "0.00153473", + "prevClosePrice": "0.00152900", + "lastPrice": "0.00153800", + "lastQty": "50.22000000", + "bidPrice": "0.00153900", + "bidQty": "72.00000000", + "askPrice": "0.00154500", + "askQty": "656.97000000", + "openPrice": "0.00153300", + "highPrice": "0.00156000", + "lowPrice": "0.00151500", + "volume": "392267.60000000", + "quoteVolume": "602.02628154", + "openTime": 1560210229409, + "closeTime": 1560296629409, + "firstId": 6191290, + "lastId": 6192757, + "count": 1468 + }, + { + "symbol": "ICXBNB", + "priceChange": "0.00004000", + "priceChangePercent": "0.339", + "weightedAvgPrice": "0.01182259", + "prevClosePrice": "0.01181000", + "lastPrice": "0.01183000", + "lastQty": "22.20000000", + "bidPrice": "0.01183000", + "bidQty": "232.20000000", + "askPrice": "0.01189000", + "askQty": "233.30000000", + "openPrice": "0.01179000", + "highPrice": "0.01208000", + "lowPrice": "0.01162000", + "volume": "263588.90000000", + "quoteVolume": "3116.30290500", + "openTime": 1560210241591, + "closeTime": 1560296641591, + "firstId": 1072069, + "lastId": 1073003, + "count": 935 + }, + { + "symbol": "OSTBTC", + "priceChange": "0.00000016", + "priceChangePercent": "4.598", + "weightedAvgPrice": "0.00000357", + "prevClosePrice": "0.00000351", + "lastPrice": "0.00000364", + "lastQty": "20295.00000000", + "bidPrice": "0.00000363", + "bidQty": "74315.00000000", + "askPrice": "0.00000364", + "askQty": "24603.00000000", + "openPrice": "0.00000348", + "highPrice": "0.00000365", + "lowPrice": "0.00000345", + "volume": "23914199.00000000", + "quoteVolume": "85.36730912", + "openTime": 1560210242194, + "closeTime": 1560296642194, + "firstId": 4191302, + "lastId": 4195894, + "count": 4593 + }, + { + "symbol": "OSTETH", + "priceChange": "0.00000416", + "priceChangePercent": "3.688", + "weightedAvgPrice": "0.00011559", + "prevClosePrice": "0.00011329", + "lastPrice": "0.00011695", + "lastQty": "90.00000000", + "bidPrice": "0.00011661", + "bidQty": "46105.00000000", + "askPrice": "0.00011695", + "askQty": "175.00000000", + "openPrice": "0.00011279", + "highPrice": "0.00011870", + "lowPrice": "0.00011229", + "volume": "1542418.00000000", + "quoteVolume": "178.28299169", + "openTime": 1560210237443, + "closeTime": 1560296637443, + "firstId": 1187658, + "lastId": 1188464, + "count": 807 + }, + { + "symbol": "OSTBNB", + "priceChange": "0.00002700", + "priceChangePercent": "3.107", + "weightedAvgPrice": "0.00089030", + "prevClosePrice": "0.00087200", + "lastPrice": "0.00089600", + "lastQty": "2034.00000000", + "bidPrice": "0.00089600", + "bidQty": "13110.00000000", + "askPrice": "0.00090200", + "askQty": "4864.00000000", + "openPrice": "0.00086900", + "highPrice": "0.00091400", + "lowPrice": "0.00086500", + "volume": "1711154.00000000", + "quoteVolume": "1523.43867800", + "openTime": 1560210239163, + "closeTime": 1560296639163, + "firstId": 297225, + "lastId": 297872, + "count": 648 + }, + { + "symbol": "ELFBTC", + "priceChange": "-0.00000011", + "priceChangePercent": "-0.420", + "weightedAvgPrice": "0.00002599", + "prevClosePrice": "0.00002621", + "lastPrice": "0.00002610", + "lastQty": "2533.00000000", + "bidPrice": "0.00002609", + "bidQty": "1686.00000000", + "askPrice": "0.00002611", + "askQty": "250.00000000", + "openPrice": "0.00002621", + "highPrice": "0.00002683", + "lowPrice": "0.00002559", + "volume": "5243356.00000000", + "quoteVolume": "136.27505504", + "openTime": 1560210241842, + "closeTime": 1560296641842, + "firstId": 9939372, + "lastId": 9950883, + "count": 11512 + }, + { + "symbol": "ELFETH", + "priceChange": "-0.00000863", + "priceChangePercent": "-1.016", + "weightedAvgPrice": "0.00084235", + "prevClosePrice": "0.00084961", + "lastPrice": "0.00084070", + "lastQty": "318.00000000", + "bidPrice": "0.00083901", + "bidQty": "749.00000000", + "askPrice": "0.00084140", + "askQty": "75.00000000", + "openPrice": "0.00084933", + "highPrice": "0.00087077", + "lowPrice": "0.00082370", + "volume": "504565.00000000", + "quoteVolume": "425.01908037", + "openTime": 1560210241970, + "closeTime": 1560296641970, + "firstId": 3505666, + "lastId": 3507156, + "count": 1491 + }, + { + "symbol": "AIONBTC", + "priceChange": "0.00000070", + "priceChangePercent": "2.869", + "weightedAvgPrice": "0.00002472", + "prevClosePrice": "0.00002440", + "lastPrice": "0.00002510", + "lastQty": "1565.16000000", + "bidPrice": "0.00002500", + "bidQty": "135.83000000", + "askPrice": "0.00002510", + "askQty": "6906.06000000", + "openPrice": "0.00002440", + "highPrice": "0.00002520", + "lowPrice": "0.00002410", + "volume": "3189202.43000000", + "quoteVolume": "78.84927549", + "openTime": 1560210242307, + "closeTime": 1560296642307, + "firstId": 5975199, + "lastId": 5977874, + "count": 2676 + }, + { + "symbol": "AIONETH", + "priceChange": "0.00001700", + "priceChangePercent": "2.157", + "weightedAvgPrice": "0.00079942", + "prevClosePrice": "0.00078800", + "lastPrice": "0.00080500", + "lastQty": "336.41000000", + "bidPrice": "0.00080500", + "bidQty": "376.59000000", + "askPrice": "0.00080700", + "askQty": "1426.00000000", + "openPrice": "0.00078800", + "highPrice": "0.00081800", + "lowPrice": "0.00078200", + "volume": "511881.05000000", + "quoteVolume": "409.20933586", + "openTime": 1560210241380, + "closeTime": 1560296641380, + "firstId": 2054407, + "lastId": 2055874, + "count": 1468 + }, + { + "symbol": "AIONBNB", + "priceChange": "0.00012000", + "priceChangePercent": "1.977", + "weightedAvgPrice": "0.00614127", + "prevClosePrice": "0.00607000", + "lastPrice": "0.00619000", + "lastQty": "147.20000000", + "bidPrice": "0.00617000", + "bidQty": "659.50000000", + "askPrice": "0.00622000", + "askQty": "1964.50000000", + "openPrice": "0.00607000", + "highPrice": "0.00631000", + "lowPrice": "0.00603000", + "volume": "423101.20000000", + "quoteVolume": "2598.37970100", + "openTime": 1560210235396, + "closeTime": 1560296635396, + "firstId": 340342, + "lastId": 340786, + "count": 445 + }, + { + "symbol": "NEBLBTC", + "priceChange": "-0.00000110", + "priceChangePercent": "-0.625", + "weightedAvgPrice": "0.00017488", + "prevClosePrice": "0.00017620", + "lastPrice": "0.00017500", + "lastQty": "7.02000000", + "bidPrice": "0.00017440", + "bidQty": "238.71000000", + "askPrice": "0.00017520", + "askQty": "116.19000000", + "openPrice": "0.00017610", + "highPrice": "0.00018090", + "lowPrice": "0.00016950", + "volume": "480412.77000000", + "quoteVolume": "84.01470241", + "openTime": 1560210240408, + "closeTime": 1560296640408, + "firstId": 5877698, + "lastId": 5882155, + "count": 4458 + }, + { + "symbol": "NEBLETH", + "priceChange": "-0.00004300", + "priceChangePercent": "-0.754", + "weightedAvgPrice": "0.00563842", + "prevClosePrice": "0.00570000", + "lastPrice": "0.00565700", + "lastQty": "129.64000000", + "bidPrice": "0.00560700", + "bidQty": "238.71000000", + "askPrice": "0.00565100", + "askQty": "160.00000000", + "openPrice": "0.00570000", + "highPrice": "0.00583300", + "lowPrice": "0.00550300", + "volume": "38190.19000000", + "quoteVolume": "215.33250862", + "openTime": 1560210241044, + "closeTime": 1560296641044, + "firstId": 1501665, + "lastId": 1502578, + "count": 914 + }, + { + "symbol": "NEBLBNB", + "priceChange": "-0.00042000", + "priceChangePercent": "-0.956", + "weightedAvgPrice": "0.04388029", + "prevClosePrice": "0.04391000", + "lastPrice": "0.04349000", + "lastQty": "36.00000000", + "bidPrice": "0.04311000", + "bidQty": "135.60000000", + "askPrice": "0.04349000", + "askQty": "151.00000000", + "openPrice": "0.04391000", + "highPrice": "0.04525000", + "lowPrice": "0.04268000", + "volume": "24989.10000000", + "quoteVolume": "1096.52892100", + "openTime": 1560210235384, + "closeTime": 1560296635384, + "firstId": 318599, + "lastId": 319119, + "count": 521 + }, + { + "symbol": "BRDBTC", + "priceChange": "-0.00000080", + "priceChangePercent": "-1.426", + "weightedAvgPrice": "0.00005585", + "prevClosePrice": "0.00005613", + "lastPrice": "0.00005530", + "lastQty": "210.00000000", + "bidPrice": "0.00005531", + "bidQty": "1302.00000000", + "askPrice": "0.00005563", + "askQty": "1407.00000000", + "openPrice": "0.00005610", + "highPrice": "0.00005678", + "lowPrice": "0.00005523", + "volume": "282226.00000000", + "quoteVolume": "15.76364416", + "openTime": 1560210242332, + "closeTime": 1560296642332, + "firstId": 3609524, + "lastId": 3610956, + "count": 1433 + }, + { + "symbol": "BRDETH", + "priceChange": "-0.00004870", + "priceChangePercent": "-2.659", + "weightedAvgPrice": "0.00180834", + "prevClosePrice": "0.00183170", + "lastPrice": "0.00178290", + "lastQty": "7.00000000", + "bidPrice": "0.00178240", + "bidQty": "101.00000000", + "askPrice": "0.00179420", + "askQty": "1256.00000000", + "openPrice": "0.00183160", + "highPrice": "0.00183300", + "lowPrice": "0.00177950", + "volume": "17203.00000000", + "quoteVolume": "31.10895220", + "openTime": 1560210242293, + "closeTime": 1560296642293, + "firstId": 805624, + "lastId": 805826, + "count": 203 + }, + { + "symbol": "BRDBNB", + "priceChange": "-0.00038000", + "priceChangePercent": "-2.697", + "weightedAvgPrice": "0.01397083", + "prevClosePrice": "0.01404000", + "lastPrice": "0.01371000", + "lastQty": "13.00000000", + "bidPrice": "0.01368000", + "bidQty": "8.40000000", + "askPrice": "0.01377000", + "askQty": "145.00000000", + "openPrice": "0.01409000", + "highPrice": "0.01422000", + "lowPrice": "0.01371000", + "volume": "21360.50000000", + "quoteVolume": "298.42394900", + "openTime": 1560210236362, + "closeTime": 1560296636362, + "firstId": 245171, + "lastId": 245347, + "count": 177 + }, + { + "symbol": "MCOBNB", + "priceChange": "0.01573000", + "priceChangePercent": "8.148", + "weightedAvgPrice": "0.20501267", + "prevClosePrice": "0.19313000", + "lastPrice": "0.20879000", + "lastQty": "10.10000000", + "bidPrice": "0.20719000", + "bidQty": "37.70000000", + "askPrice": "0.20839000", + "askQty": "4.50000000", + "openPrice": "0.19306000", + "highPrice": "0.21500000", + "lowPrice": "0.19306000", + "volume": "19929.80000000", + "quoteVolume": "4085.86147300", + "openTime": 1560210242215, + "closeTime": 1560296642215, + "firstId": 344921, + "lastId": 346457, + "count": 1537 + }, + { + "symbol": "EDOBTC", + "priceChange": "0.00000430", + "priceChangePercent": "4.135", + "weightedAvgPrice": "0.00010733", + "prevClosePrice": "0.00010400", + "lastPrice": "0.00010830", + "lastQty": "557.40000000", + "bidPrice": "0.00010830", + "bidQty": "184.33000000", + "askPrice": "0.00010840", + "askQty": "55.01000000", + "openPrice": "0.00010400", + "highPrice": "0.00011170", + "lowPrice": "0.00010340", + "volume": "606490.16000000", + "quoteVolume": "65.09329546", + "openTime": 1560210223409, + "closeTime": 1560296623409, + "firstId": 3592227, + "lastId": 3597936, + "count": 5710 + }, + { + "symbol": "EDOETH", + "priceChange": "0.00011600", + "priceChangePercent": "3.437", + "weightedAvgPrice": "0.00346753", + "prevClosePrice": "0.00336800", + "lastPrice": "0.00349100", + "lastQty": "19.27000000", + "bidPrice": "0.00348500", + "bidQty": "61.47000000", + "askPrice": "0.00351000", + "askQty": "9.24000000", + "openPrice": "0.00337500", + "highPrice": "0.00362100", + "lowPrice": "0.00335800", + "volume": "54442.46000000", + "quoteVolume": "188.78063896", + "openTime": 1560210240311, + "closeTime": 1560296640311, + "firstId": 601819, + "lastId": 602653, + "count": 835 + }, + { + "symbol": "WINGSBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00001193", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998602344, + "closeTime": 1558085002344, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "WINGSETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00033460", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998602932, + "closeTime": 1558085002932, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "NAVBTC", + "priceChange": "0.00000100", + "priceChangePercent": "3.509", + "weightedAvgPrice": "0.00002891", + "prevClosePrice": "0.00002840", + "lastPrice": "0.00002950", + "lastQty": "1490.07000000", + "bidPrice": "0.00002930", + "bidQty": "2137.32000000", + "askPrice": "0.00002950", + "askQty": "1273.51000000", + "openPrice": "0.00002850", + "highPrice": "0.00002960", + "lowPrice": "0.00002820", + "volume": "1083484.38000000", + "quoteVolume": "31.31971411", + "openTime": 1560210233964, + "closeTime": 1560296633964, + "firstId": 3189188, + "lastId": 3190932, + "count": 1745 + }, + { + "symbol": "NAVETH", + "priceChange": "0.00002700", + "priceChangePercent": "2.941", + "weightedAvgPrice": "0.00092884", + "prevClosePrice": "0.00092200", + "lastPrice": "0.00094500", + "lastQty": "14.12000000", + "bidPrice": "0.00094200", + "bidQty": "1058.00000000", + "askPrice": "0.00095000", + "askQty": "14.12000000", + "openPrice": "0.00091800", + "highPrice": "0.00095700", + "lowPrice": "0.00091000", + "volume": "36596.39000000", + "quoteVolume": "33.99220182", + "openTime": 1560210238674, + "closeTime": 1560296638674, + "firstId": 695129, + "lastId": 695455, + "count": 327 + }, + { + "symbol": "NAVBNB", + "priceChange": "0.00015000", + "priceChangePercent": "2.116", + "weightedAvgPrice": "0.00718410", + "prevClosePrice": "0.00711000", + "lastPrice": "0.00724000", + "lastQty": "28.30000000", + "bidPrice": "0.00724000", + "bidQty": "251.60000000", + "askPrice": "0.00729000", + "askQty": "224.80000000", + "openPrice": "0.00709000", + "highPrice": "0.00734000", + "lowPrice": "0.00703000", + "volume": "61542.70000000", + "quoteVolume": "442.12867500", + "openTime": 1560210241586, + "closeTime": 1560296641586, + "firstId": 207203, + "lastId": 207532, + "count": 330 + }, + { + "symbol": "LUNBTC", + "priceChange": "0.00001000", + "priceChangePercent": "3.086", + "weightedAvgPrice": "0.00032517", + "prevClosePrice": "0.00032470", + "lastPrice": "0.00033400", + "lastQty": "15.88000000", + "bidPrice": "0.00033400", + "bidQty": "43.72000000", + "askPrice": "0.00033450", + "askQty": "30.87000000", + "openPrice": "0.00032400", + "highPrice": "0.00033600", + "lowPrice": "0.00031560", + "volume": "144362.83000000", + "quoteVolume": "46.94257390", + "openTime": 1560210240485, + "closeTime": 1560296640485, + "firstId": 5870299, + "lastId": 5873686, + "count": 3388 + }, + { + "symbol": "LUNETH", + "priceChange": "0.00031800", + "priceChangePercent": "3.040", + "weightedAvgPrice": "0.01062507", + "prevClosePrice": "0.01054900", + "lastPrice": "0.01077700", + "lastQty": "51.64000000", + "bidPrice": "0.01072500", + "bidQty": "59.27000000", + "askPrice": "0.01078300", + "askQty": "187.13000000", + "openPrice": "0.01045900", + "highPrice": "0.01096300", + "lowPrice": "0.01020600", + "volume": "9622.53000000", + "quoteVolume": "102.24000858", + "openTime": 1560210240667, + "closeTime": 1560296640667, + "firstId": 997746, + "lastId": 998160, + "count": 415 + }, + { + "symbol": "TRIGBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00001980", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998603891, + "closeTime": 1558085003891, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "TRIGETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00059400", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998604320, + "closeTime": 1558085004320, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "TRIGBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.01218000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998604366, + "closeTime": 1558085004366, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "APPCBTC", + "priceChange": "0.00000023", + "priceChangePercent": "1.879", + "weightedAvgPrice": "0.00001261", + "prevClosePrice": "0.00001224", + "lastPrice": "0.00001247", + "lastQty": "213.00000000", + "bidPrice": "0.00001246", + "bidQty": "825.00000000", + "askPrice": "0.00001247", + "askQty": "6582.00000000", + "openPrice": "0.00001224", + "highPrice": "0.00001308", + "lowPrice": "0.00001215", + "volume": "6839719.00000000", + "quoteVolume": "86.28034452", + "openTime": 1560210240343, + "closeTime": 1560296640343, + "firstId": 5618030, + "lastId": 5623762, + "count": 5733 + }, + { + "symbol": "APPCETH", + "priceChange": "0.00000140", + "priceChangePercent": "0.352", + "weightedAvgPrice": "0.00040776", + "prevClosePrice": "0.00039600", + "lastPrice": "0.00039960", + "lastQty": "20.00000000", + "bidPrice": "0.00040060", + "bidQty": "825.00000000", + "askPrice": "0.00040350", + "askQty": "532.00000000", + "openPrice": "0.00039820", + "highPrice": "0.00042380", + "lowPrice": "0.00039410", + "volume": "455456.00000000", + "quoteVolume": "185.71738710", + "openTime": 1560210235237, + "closeTime": 1560296635237, + "firstId": 1642575, + "lastId": 1643394, + "count": 820 + }, + { + "symbol": "APPCBNB", + "priceChange": "0.00007000", + "priceChangePercent": "2.303", + "weightedAvgPrice": "0.00316001", + "prevClosePrice": "0.00304000", + "lastPrice": "0.00311000", + "lastQty": "40.00000000", + "bidPrice": "0.00308000", + "bidQty": "384.90000000", + "askPrice": "0.00310000", + "askQty": "898.60000000", + "openPrice": "0.00304000", + "highPrice": "0.00329000", + "lowPrice": "0.00303000", + "volume": "211707.60000000", + "quoteVolume": "668.99863500", + "openTime": 1560210232182, + "closeTime": 1560296632182, + "firstId": 378049, + "lastId": 378457, + "count": 409 + }, + { + "symbol": "VIBEBTC", + "priceChange": "-0.00000026", + "priceChangePercent": "-4.012", + "weightedAvgPrice": "0.00000624", + "prevClosePrice": "0.00000649", + "lastPrice": "0.00000622", + "lastQty": "282.00000000", + "bidPrice": "0.00000619", + "bidQty": "37881.00000000", + "askPrice": "0.00000622", + "askQty": "23944.00000000", + "openPrice": "0.00000648", + "highPrice": "0.00000658", + "lowPrice": "0.00000601", + "volume": "51887173.00000000", + "quoteVolume": "323.77770486", + "openTime": 1560210240907, + "closeTime": 1560296640907, + "firstId": 6227627, + "lastId": 6240209, + "count": 12583 + }, + { + "symbol": "VIBEETH", + "priceChange": "-0.00000980", + "priceChangePercent": "-4.669", + "weightedAvgPrice": "0.00020264", + "prevClosePrice": "0.00020990", + "lastPrice": "0.00020010", + "lastQty": "2465.00000000", + "bidPrice": "0.00019990", + "bidQty": "2465.00000000", + "askPrice": "0.00020030", + "askQty": "3556.00000000", + "openPrice": "0.00020990", + "highPrice": "0.00021390", + "lowPrice": "0.00019430", + "volume": "3958161.00000000", + "quoteVolume": "802.07795210", + "openTime": 1560210231791, + "closeTime": 1560296631791, + "firstId": 1722168, + "lastId": 1724805, + "count": 2638 + }, + { + "symbol": "RLCBTC", + "priceChange": "0.00000060", + "priceChangePercent": "1.036", + "weightedAvgPrice": "0.00005946", + "prevClosePrice": "0.00005810", + "lastPrice": "0.00005850", + "lastQty": "48.58000000", + "bidPrice": "0.00005850", + "bidQty": "118.18000000", + "askPrice": "0.00005860", + "askQty": "313.52000000", + "openPrice": "0.00005790", + "highPrice": "0.00006150", + "lowPrice": "0.00005730", + "volume": "1959007.99000000", + "quoteVolume": "116.48232459", + "openTime": 1560210241737, + "closeTime": 1560296641737, + "firstId": 3913789, + "lastId": 3919607, + "count": 5819 + }, + { + "symbol": "RLCETH", + "priceChange": "0.00000200", + "priceChangePercent": "0.106", + "weightedAvgPrice": "0.00192565", + "prevClosePrice": "0.00187100", + "lastPrice": "0.00188500", + "lastQty": "6.34000000", + "bidPrice": "0.00188100", + "bidQty": "118.18000000", + "askPrice": "0.00188900", + "askQty": "277.01000000", + "openPrice": "0.00188300", + "highPrice": "0.00198900", + "lowPrice": "0.00185700", + "volume": "112970.54000000", + "quoteVolume": "217.54116267", + "openTime": 1560210241854, + "closeTime": 1560296641854, + "firstId": 1026466, + "lastId": 1027233, + "count": 768 + }, + { + "symbol": "RLCBNB", + "priceChange": "-0.00007000", + "priceChangePercent": "-0.481", + "weightedAvgPrice": "0.01480986", + "prevClosePrice": "0.01452000", + "lastPrice": "0.01449000", + "lastQty": "141.00000000", + "bidPrice": "0.01442000", + "bidQty": "118.10000000", + "askPrice": "0.01453000", + "askQty": "22.40000000", + "openPrice": "0.01456000", + "highPrice": "0.01535000", + "lowPrice": "0.01422000", + "volume": "160255.40000000", + "quoteVolume": "2373.35961300", + "openTime": 1560210238881, + "closeTime": 1560296638881, + "firstId": 236760, + "lastId": 237613, + "count": 854 + }, + { + "symbol": "INSBTC", + "priceChange": "0.00000270", + "priceChangePercent": "5.921", + "weightedAvgPrice": "0.00004716", + "prevClosePrice": "0.00004560", + "lastPrice": "0.00004830", + "lastQty": "1030.00000000", + "bidPrice": "0.00004830", + "bidQty": "11623.71000000", + "askPrice": "0.00004860", + "askQty": "2634.07000000", + "openPrice": "0.00004560", + "highPrice": "0.00004860", + "lowPrice": "0.00004510", + "volume": "2354884.98000000", + "quoteVolume": "111.05673720", + "openTime": 1560210242358, + "closeTime": 1560296642358, + "firstId": 3883516, + "lastId": 3888184, + "count": 4669 + }, + { + "symbol": "INSETH", + "priceChange": "0.00007800", + "priceChangePercent": "5.292", + "weightedAvgPrice": "0.00150805", + "prevClosePrice": "0.00147400", + "lastPrice": "0.00155200", + "lastQty": "29.14000000", + "bidPrice": "0.00155500", + "bidQty": "160.39000000", + "askPrice": "0.00155900", + "askQty": "7.10000000", + "openPrice": "0.00147400", + "highPrice": "0.00157100", + "lowPrice": "0.00145500", + "volume": "465930.50000000", + "quoteVolume": "702.64811567", + "openTime": 1560210239017, + "closeTime": 1560296639017, + "firstId": 951472, + "lastId": 952922, + "count": 1451 + }, + { + "symbol": "PIVXBTC", + "priceChange": "0.00000520", + "priceChangePercent": "5.771", + "weightedAvgPrice": "0.00009693", + "prevClosePrice": "0.00009010", + "lastPrice": "0.00009530", + "lastQty": "13.79000000", + "bidPrice": "0.00009510", + "bidQty": "314.24000000", + "askPrice": "0.00009520", + "askQty": "486.21000000", + "openPrice": "0.00009010", + "highPrice": "0.00010270", + "lowPrice": "0.00008870", + "volume": "2338787.40000000", + "quoteVolume": "226.69941167", + "openTime": 1560210233191, + "closeTime": 1560296633191, + "firstId": 3122076, + "lastId": 3132368, + "count": 10293 + }, + { + "symbol": "PIVXETH", + "priceChange": "0.00015100", + "priceChangePercent": "5.166", + "weightedAvgPrice": "0.00313476", + "prevClosePrice": "0.00291600", + "lastPrice": "0.00307400", + "lastQty": "4.00000000", + "bidPrice": "0.00305800", + "bidQty": "628.48000000", + "askPrice": "0.00308500", + "askQty": "293.00000000", + "openPrice": "0.00292300", + "highPrice": "0.00333700", + "lowPrice": "0.00286300", + "volume": "132144.87000000", + "quoteVolume": "414.24273701", + "openTime": 1560210234516, + "closeTime": 1560296634516, + "firstId": 614311, + "lastId": 616014, + "count": 1704 + }, + { + "symbol": "PIVXBNB", + "priceChange": "0.00110000", + "priceChangePercent": "4.911", + "weightedAvgPrice": "0.02399101", + "prevClosePrice": "0.02240000", + "lastPrice": "0.02350000", + "lastQty": "27.50000000", + "bidPrice": "0.02350000", + "bidQty": "51.00000000", + "askPrice": "0.02361000", + "askQty": "5.10000000", + "openPrice": "0.02240000", + "highPrice": "0.02577000", + "lowPrice": "0.02211000", + "volume": "61801.10000000", + "quoteVolume": "1482.67100400", + "openTime": 1560210229236, + "closeTime": 1560296629236, + "firstId": 160023, + "lastId": 160428, + "count": 406 + }, + { + "symbol": "IOSTBTC", + "priceChange": "-0.00000004", + "priceChangePercent": "-2.721", + "weightedAvgPrice": "0.00000143", + "prevClosePrice": "0.00000147", + "lastPrice": "0.00000143", + "lastQty": "41131.00000000", + "bidPrice": "0.00000143", + "bidQty": "3520480.00000000", + "askPrice": "0.00000144", + "askQty": "2481197.00000000", + "openPrice": "0.00000147", + "highPrice": "0.00000147", + "lowPrice": "0.00000141", + "volume": "204053613.00000000", + "quoteVolume": "291.89150089", + "openTime": 1560210242300, + "closeTime": 1560296642300, + "firstId": 11679184, + "lastId": 11684237, + "count": 5054 + }, + { + "symbol": "IOSTETH", + "priceChange": "-0.00000111", + "priceChangePercent": "-2.345", + "weightedAvgPrice": "0.00004631", + "prevClosePrice": "0.00004735", + "lastPrice": "0.00004622", + "lastQty": "18858.00000000", + "bidPrice": "0.00004610", + "bidQty": "70819.00000000", + "askPrice": "0.00004621", + "askQty": "1142.00000000", + "openPrice": "0.00004733", + "highPrice": "0.00004760", + "lowPrice": "0.00004567", + "volume": "17935782.00000000", + "quoteVolume": "830.61419184", + "openTime": 1560210234183, + "closeTime": 1560296634183, + "firstId": 3841279, + "lastId": 3843116, + "count": 1838 + }, + { + "symbol": "CHATBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00000195", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998608796, + "closeTime": 1558085008796, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "CHATETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00006585", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998609006, + "closeTime": 1558085009006, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "STEEMBTC", + "priceChange": "0.00000300", + "priceChangePercent": "5.848", + "weightedAvgPrice": "0.00005379", + "prevClosePrice": "0.00005150", + "lastPrice": "0.00005430", + "lastQty": "49.37000000", + "bidPrice": "0.00005420", + "bidQty": "231.37000000", + "askPrice": "0.00005440", + "askQty": "98.87000000", + "openPrice": "0.00005130", + "highPrice": "0.00005600", + "lowPrice": "0.00005080", + "volume": "2636532.56000000", + "quoteVolume": "141.81419367", + "openTime": 1560210242273, + "closeTime": 1560296642273, + "firstId": 4928910, + "lastId": 4937666, + "count": 8757 + }, + { + "symbol": "STEEMETH", + "priceChange": "0.00008800", + "priceChangePercent": "5.298", + "weightedAvgPrice": "0.00173766", + "prevClosePrice": "0.00166100", + "lastPrice": "0.00174900", + "lastQty": "134.63000000", + "bidPrice": "0.00174500", + "bidQty": "149.25000000", + "askPrice": "0.00175700", + "askQty": "1485.00000000", + "openPrice": "0.00166100", + "highPrice": "0.00181300", + "lowPrice": "0.00164100", + "volume": "208345.44000000", + "quoteVolume": "362.03356969", + "openTime": 1560210234646, + "closeTime": 1560296634646, + "firstId": 1075343, + "lastId": 1076852, + "count": 1510 + }, + { + "symbol": "STEEMBNB", + "priceChange": "0.00062000", + "priceChangePercent": "4.832", + "weightedAvgPrice": "0.01336950", + "prevClosePrice": "0.01283000", + "lastPrice": "0.01345000", + "lastQty": "23.70000000", + "bidPrice": "0.01339000", + "bidQty": "3319.10000000", + "askPrice": "0.01351000", + "askQty": "78.50000000", + "openPrice": "0.01283000", + "highPrice": "0.01393000", + "lowPrice": "0.01275000", + "volume": "159700.10000000", + "quoteVolume": "2135.11080600", + "openTime": 1560210238034, + "closeTime": 1560296638034, + "firstId": 276111, + "lastId": 277075, + "count": 965 + }, + { + "symbol": "NANOBTC", + "priceChange": "0.00000070", + "priceChangePercent": "0.354", + "weightedAvgPrice": "0.00019580", + "prevClosePrice": "0.00019800", + "lastPrice": "0.00019850", + "lastQty": "226.65000000", + "bidPrice": "0.00019840", + "bidQty": "20.25000000", + "askPrice": "0.00019880", + "askQty": "40.58000000", + "openPrice": "0.00019780", + "highPrice": "0.00020020", + "lowPrice": "0.00019240", + "volume": "1330494.93000000", + "quoteVolume": "260.50577310", + "openTime": 1560210241556, + "closeTime": 1560296641556, + "firstId": 17167460, + "lastId": 17178592, + "count": 11133 + }, + { + "symbol": "NANOETH", + "priceChange": "0.00000200", + "priceChangePercent": "0.031", + "weightedAvgPrice": "0.00634503", + "prevClosePrice": "0.00643100", + "lastPrice": "0.00640500", + "lastQty": "2.23000000", + "bidPrice": "0.00638600", + "bidQty": "259.01000000", + "askPrice": "0.00640900", + "askQty": "3.46000000", + "openPrice": "0.00640300", + "highPrice": "0.00645400", + "lowPrice": "0.00621700", + "volume": "71013.55000000", + "quoteVolume": "450.58326638", + "openTime": 1560210241318, + "closeTime": 1560296641318, + "firstId": 4481452, + "lastId": 4483058, + "count": 1607 + }, + { + "symbol": "NANOBNB", + "priceChange": "-0.00020000", + "priceChangePercent": "-0.406", + "weightedAvgPrice": "0.04873005", + "prevClosePrice": "0.04950000", + "lastPrice": "0.04910000", + "lastQty": "17.89000000", + "bidPrice": "0.04900000", + "bidQty": "20.40000000", + "askPrice": "0.04920000", + "askQty": "92.84000000", + "openPrice": "0.04930000", + "highPrice": "0.04980000", + "lowPrice": "0.04800000", + "volume": "57450.53000000", + "quoteVolume": "2799.56696700", + "openTime": 1560210225062, + "closeTime": 1560296625062, + "firstId": 923790, + "lastId": 924704, + "count": 915 + }, + { + "symbol": "VIABTC", + "priceChange": "0.00000100", + "priceChangePercent": "1.362", + "weightedAvgPrice": "0.00007256", + "prevClosePrice": "0.00007370", + "lastPrice": "0.00007440", + "lastQty": "285.00000000", + "bidPrice": "0.00007420", + "bidQty": "19.64000000", + "askPrice": "0.00007460", + "askQty": "480.55000000", + "openPrice": "0.00007340", + "highPrice": "0.00007480", + "lowPrice": "0.00007100", + "volume": "697158.15000000", + "quoteVolume": "50.58387511", + "openTime": 1560210241421, + "closeTime": 1560296641421, + "firstId": 3187376, + "lastId": 3189889, + "count": 2514 + }, + { + "symbol": "VIAETH", + "priceChange": "0.00002000", + "priceChangePercent": "0.841", + "weightedAvgPrice": "0.00234691", + "prevClosePrice": "0.00237800", + "lastPrice": "0.00239700", + "lastQty": "11.22000000", + "bidPrice": "0.00238100", + "bidQty": "109.30000000", + "askPrice": "0.00240100", + "askQty": "11.14000000", + "openPrice": "0.00237700", + "highPrice": "0.00241000", + "lowPrice": "0.00229800", + "volume": "39480.19000000", + "quoteVolume": "92.65629448", + "openTime": 1560210235854, + "closeTime": 1560296635854, + "firstId": 581747, + "lastId": 582115, + "count": 369 + }, + { + "symbol": "VIABNB", + "priceChange": "-0.00016000", + "priceChangePercent": "-0.858", + "weightedAvgPrice": "0.01826489", + "prevClosePrice": "0.01831000", + "lastPrice": "0.01849000", + "lastQty": "53.70000000", + "bidPrice": "0.01826000", + "bidQty": "489.00000000", + "askPrice": "0.01849000", + "askQty": "6.90000000", + "openPrice": "0.01865000", + "highPrice": "0.01865000", + "lowPrice": "0.01769000", + "volume": "8092.50000000", + "quoteVolume": "147.80863900", + "openTime": 1560210241313, + "closeTime": 1560296641313, + "firstId": 165166, + "lastId": 165257, + "count": 92 + }, + { + "symbol": "BLZBTC", + "priceChange": "-0.00000020", + "priceChangePercent": "-2.454", + "weightedAvgPrice": "0.00000796", + "prevClosePrice": "0.00000810", + "lastPrice": "0.00000795", + "lastQty": "172.00000000", + "bidPrice": "0.00000795", + "bidQty": "11780.00000000", + "askPrice": "0.00000797", + "askQty": "3354.00000000", + "openPrice": "0.00000815", + "highPrice": "0.00000820", + "lowPrice": "0.00000786", + "volume": "7029639.00000000", + "quoteVolume": "55.97081665", + "openTime": 1560210235228, + "closeTime": 1560296635228, + "firstId": 4515565, + "lastId": 4518645, + "count": 3081 + }, + { + "symbol": "BLZETH", + "priceChange": "-0.00000819", + "priceChangePercent": "-3.091", + "weightedAvgPrice": "0.00025882", + "prevClosePrice": "0.00026220", + "lastPrice": "0.00025674", + "lastQty": "50.00000000", + "bidPrice": "0.00025565", + "bidQty": "53.00000000", + "askPrice": "0.00025686", + "askQty": "13480.00000000", + "openPrice": "0.00026493", + "highPrice": "0.00026593", + "lowPrice": "0.00025375", + "volume": "498823.00000000", + "quoteVolume": "129.10421385", + "openTime": 1560210242243, + "closeTime": 1560296642243, + "firstId": 1742794, + "lastId": 1743487, + "count": 694 + }, + { + "symbol": "BLZBNB", + "priceChange": "-0.00006000", + "priceChangePercent": "-2.970", + "weightedAvgPrice": "0.00199165", + "prevClosePrice": "0.00202000", + "lastPrice": "0.00196000", + "lastQty": "6890.40000000", + "bidPrice": "0.00196000", + "bidQty": "2211.60000000", + "askPrice": "0.00198000", + "askQty": "4112.00000000", + "openPrice": "0.00202000", + "highPrice": "0.00206000", + "lowPrice": "0.00196000", + "volume": "175646.50000000", + "quoteVolume": "349.82593700", + "openTime": 1560210219068, + "closeTime": 1560296619068, + "firstId": 243549, + "lastId": 243768, + "count": 220 + }, + { + "symbol": "AEBTC", + "priceChange": "-0.00000140", + "priceChangePercent": "-2.124", + "weightedAvgPrice": "0.00006461", + "prevClosePrice": "0.00006570", + "lastPrice": "0.00006450", + "lastQty": "35.20000000", + "bidPrice": "0.00006440", + "bidQty": "5780.70000000", + "askPrice": "0.00006450", + "askQty": "134.79000000", + "openPrice": "0.00006590", + "highPrice": "0.00006600", + "lowPrice": "0.00006360", + "volume": "1058143.44000000", + "quoteVolume": "68.36547662", + "openTime": 1560210239965, + "closeTime": 1560296639965, + "firstId": 5415691, + "lastId": 5419658, + "count": 3968 + }, + { + "symbol": "AEETH", + "priceChange": "-0.00004300", + "priceChangePercent": "-2.021", + "weightedAvgPrice": "0.00208631", + "prevClosePrice": "0.00213900", + "lastPrice": "0.00208500", + "lastQty": "5.77000000", + "bidPrice": "0.00207100", + "bidQty": "532.93000000", + "askPrice": "0.00208300", + "askQty": "119.69000000", + "openPrice": "0.00212800", + "highPrice": "0.00214000", + "lowPrice": "0.00204800", + "volume": "103414.29000000", + "quoteVolume": "215.75401429", + "openTime": 1560210236551, + "closeTime": 1560296636551, + "firstId": 1907118, + "lastId": 1907915, + "count": 798 + }, + { + "symbol": "AEBNB", + "priceChange": "-0.00053000", + "priceChangePercent": "-3.220", + "weightedAvgPrice": "0.01631387", + "prevClosePrice": "0.01639000", + "lastPrice": "0.01593000", + "lastQty": "15.80000000", + "bidPrice": "0.01590000", + "bidQty": "563.00000000", + "askPrice": "0.01603000", + "askQty": "30.70000000", + "openPrice": "0.01646000", + "highPrice": "0.01670000", + "lowPrice": "0.01590000", + "volume": "72552.00000000", + "quoteVolume": "1183.60359600", + "openTime": 1560210230011, + "closeTime": 1560296630011, + "firstId": 259320, + "lastId": 259758, + "count": 439 + }, + { + "symbol": "RPXBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00000224", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998613312, + "closeTime": 1558085013312, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "RPXETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00005449", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998613666, + "closeTime": 1558085013666, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "RPXBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00145700", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998614011, + "closeTime": 1558085014011, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "NCASHBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00000027", + "prevClosePrice": "0.00000027", + "lastPrice": "0.00000027", + "lastQty": "2243184.00000000", + "bidPrice": "0.00000027", + "bidQty": "5757767.00000000", + "askPrice": "0.00000028", + "askQty": "57699949.00000000", + "openPrice": "0.00000027", + "highPrice": "0.00000028", + "lowPrice": "0.00000026", + "volume": "72811909.00000000", + "quoteVolume": "19.74466494", + "openTime": 1560210230359, + "closeTime": 1560296630359, + "firstId": 6550415, + "lastId": 6550920, + "count": 506 + }, + { + "symbol": "NCASHETH", + "priceChange": "-0.00000020", + "priceChangePercent": "-2.227", + "weightedAvgPrice": "0.00000880", + "prevClosePrice": "0.00000899", + "lastPrice": "0.00000878", + "lastQty": "3000.00000000", + "bidPrice": "0.00000877", + "bidQty": "47350.00000000", + "askPrice": "0.00000886", + "askQty": "28908.00000000", + "openPrice": "0.00000898", + "highPrice": "0.00000904", + "lowPrice": "0.00000876", + "volume": "27119232.00000000", + "quoteVolume": "238.77247529", + "openTime": 1560210195146, + "closeTime": 1560296595146, + "firstId": 2193896, + "lastId": 2194579, + "count": 684 + }, + { + "symbol": "NCASHBNB", + "priceChange": "-0.00000160", + "priceChangePercent": "-2.305", + "weightedAvgPrice": "0.00006772", + "prevClosePrice": "0.00006920", + "lastPrice": "0.00006780", + "lastQty": "8211.00000000", + "bidPrice": "0.00006740", + "bidQty": "10000.00000000", + "askPrice": "0.00006780", + "askQty": "14080.00000000", + "openPrice": "0.00006940", + "highPrice": "0.00006980", + "lowPrice": "0.00006670", + "volume": "17666754.00000000", + "quoteVolume": "1196.43401920", + "openTime": 1560210235434, + "closeTime": 1560296635434, + "firstId": 1233503, + "lastId": 1233803, + "count": 301 + }, + { + "symbol": "POABTC", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00000441", + "prevClosePrice": "0.00000435", + "lastPrice": "0.00000435", + "lastQty": "1628.00000000", + "bidPrice": "0.00000434", + "bidQty": "1396.00000000", + "askPrice": "0.00000435", + "askQty": "230.00000000", + "openPrice": "0.00000435", + "highPrice": "0.00000463", + "lowPrice": "0.00000427", + "volume": "15486295.00000000", + "quoteVolume": "68.30504707", + "openTime": 1560210241429, + "closeTime": 1560296641429, + "firstId": 4173600, + "lastId": 4177162, + "count": 3563 + }, + { + "symbol": "POAETH", + "priceChange": "-0.00000195", + "priceChangePercent": "-1.378", + "weightedAvgPrice": "0.00014342", + "prevClosePrice": "0.00014141", + "lastPrice": "0.00013955", + "lastQty": "78.00000000", + "bidPrice": "0.00013955", + "bidQty": "1658.00000000", + "askPrice": "0.00014005", + "askQty": "22635.00000000", + "openPrice": "0.00014150", + "highPrice": "0.00015226", + "lowPrice": "0.00013815", + "volume": "1277268.00000000", + "quoteVolume": "183.18021672", + "openTime": 1560210241269, + "closeTime": 1560296641269, + "firstId": 1421107, + "lastId": 1422191, + "count": 1085 + }, + { + "symbol": "POABNB", + "priceChange": "-0.00003000", + "priceChangePercent": "-2.727", + "weightedAvgPrice": "0.00110010", + "prevClosePrice": "0.00109000", + "lastPrice": "0.00107000", + "lastQty": "302.10000000", + "bidPrice": "0.00107000", + "bidQty": "20575.90000000", + "askPrice": "0.00109000", + "askQty": "18069.40000000", + "openPrice": "0.00110000", + "highPrice": "0.00116000", + "lowPrice": "0.00106000", + "volume": "670753.90000000", + "quoteVolume": "737.89899500", + "openTime": 1560210232885, + "closeTime": 1560296632885, + "firstId": 283835, + "lastId": 284095, + "count": 261 + }, + { + "symbol": "ZILBTC", + "priceChange": "-0.00000002", + "priceChangePercent": "-0.678", + "weightedAvgPrice": "0.00000301", + "prevClosePrice": "0.00000295", + "lastPrice": "0.00000293", + "lastQty": "204668.00000000", + "bidPrice": "0.00000293", + "bidQty": "65984.00000000", + "askPrice": "0.00000294", + "askQty": "646113.00000000", + "openPrice": "0.00000295", + "highPrice": "0.00000312", + "lowPrice": "0.00000290", + "volume": "421087779.00000000", + "quoteVolume": "1266.80419539", + "openTime": 1560210242368, + "closeTime": 1560296642368, + "firstId": 11685563, + "lastId": 11713414, + "count": 27852 + }, + { + "symbol": "ZILETH", + "priceChange": "-0.00000097", + "priceChangePercent": "-1.015", + "weightedAvgPrice": "0.00009756", + "prevClosePrice": "0.00009593", + "lastPrice": "0.00009461", + "lastQty": "151.00000000", + "bidPrice": "0.00009431", + "bidQty": "14654.00000000", + "askPrice": "0.00009471", + "askQty": "391.00000000", + "openPrice": "0.00009558", + "highPrice": "0.00010076", + "lowPrice": "0.00009379", + "volume": "26096213.00000000", + "quoteVolume": "2546.05525206", + "openTime": 1560210241967, + "closeTime": 1560296641967, + "firstId": 3637373, + "lastId": 3641916, + "count": 4544 + }, + { + "symbol": "ZILBNB", + "priceChange": "-0.00001200", + "priceChangePercent": "-1.626", + "weightedAvgPrice": "0.00074978", + "prevClosePrice": "0.00073800", + "lastPrice": "0.00072600", + "lastQty": "3353.00000000", + "bidPrice": "0.00072300", + "bidQty": "39011.00000000", + "askPrice": "0.00072700", + "askQty": "11173.00000000", + "openPrice": "0.00073800", + "highPrice": "0.00077800", + "lowPrice": "0.00072200", + "volume": "18757487.00000000", + "quoteVolume": "14063.90268200", + "openTime": 1560210234058, + "closeTime": 1560296634058, + "firstId": 712427, + "lastId": 715590, + "count": 3164 + }, + { + "symbol": "ONTBTC", + "priceChange": "0.00000450", + "priceChangePercent": "2.561", + "weightedAvgPrice": "0.00017836", + "prevClosePrice": "0.00017570", + "lastPrice": "0.00018020", + "lastQty": "419.51000000", + "bidPrice": "0.00018000", + "bidQty": "0.45000000", + "askPrice": "0.00018020", + "askQty": "476.08000000", + "openPrice": "0.00017570", + "highPrice": "0.00018400", + "lowPrice": "0.00017290", + "volume": "3030440.89000000", + "quoteVolume": "540.50386304", + "openTime": 1560210242348, + "closeTime": 1560296642348, + "firstId": 18778057, + "lastId": 18796441, + "count": 18385 + }, + { + "symbol": "ONTETH", + "priceChange": "0.00010700", + "priceChangePercent": "1.880", + "weightedAvgPrice": "0.00574767", + "prevClosePrice": "0.00569600", + "lastPrice": "0.00580000", + "lastQty": "68.28000000", + "bidPrice": "0.00579000", + "bidQty": "482.66000000", + "askPrice": "0.00580000", + "askQty": "84.66000000", + "openPrice": "0.00569300", + "highPrice": "0.00596700", + "lowPrice": "0.00560000", + "volume": "140820.33000000", + "quoteVolume": "809.38927147", + "openTime": 1560210240667, + "closeTime": 1560296640667, + "firstId": 4512361, + "lastId": 4515042, + "count": 2682 + }, + { + "symbol": "ONTBNB", + "priceChange": "0.00060000", + "priceChangePercent": "1.369", + "weightedAvgPrice": "0.04449367", + "prevClosePrice": "0.04383000", + "lastPrice": "0.04444000", + "lastQty": "185.00000000", + "bidPrice": "0.04441000", + "bidQty": "202.00000000", + "askPrice": "0.04463000", + "askQty": "250.50000000", + "openPrice": "0.04384000", + "highPrice": "0.04575000", + "lowPrice": "0.04336000", + "volume": "120419.70000000", + "quoteVolume": "5357.91380400", + "openTime": 1560210240665, + "closeTime": 1560296640665, + "firstId": 1311302, + "lastId": 1312815, + "count": 1514 + }, + { + "symbol": "STORMBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00000042", + "prevClosePrice": "0.00000043", + "lastPrice": "0.00000042", + "lastQty": "16565.00000000", + "bidPrice": "0.00000042", + "bidQty": "5066391.00000000", + "askPrice": "0.00000043", + "askQty": "24819613.00000000", + "openPrice": "0.00000042", + "highPrice": "0.00000043", + "lowPrice": "0.00000041", + "volume": "77742280.00000000", + "quoteVolume": "32.57151012", + "openTime": 1560210242231, + "closeTime": 1560296642231, + "firstId": 5976229, + "lastId": 5977231, + "count": 1003 + }, + { + "symbol": "STORMETH", + "priceChange": "-0.00000015", + "priceChangePercent": "-1.096", + "weightedAvgPrice": "0.00001361", + "prevClosePrice": "0.00001369", + "lastPrice": "0.00001354", + "lastQty": "18271.00000000", + "bidPrice": "0.00001354", + "bidQty": "876.00000000", + "askPrice": "0.00001372", + "askQty": "20321.00000000", + "openPrice": "0.00001369", + "highPrice": "0.00001385", + "lowPrice": "0.00001340", + "volume": "4056663.00000000", + "quoteVolume": "55.22138777", + "openTime": 1560210239417, + "closeTime": 1560296639417, + "firstId": 1673150, + "lastId": 1673436, + "count": 287 + }, + { + "symbol": "STORMBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00010537", + "prevClosePrice": "0.00010600", + "lastPrice": "0.00010500", + "lastQty": "450710.00000000", + "bidPrice": "0.00010400", + "bidQty": "2517688.00000000", + "askPrice": "0.00010500", + "askQty": "81301.00000000", + "openPrice": "0.00010500", + "highPrice": "0.00010800", + "lowPrice": "0.00010300", + "volume": "13766992.00000000", + "quoteVolume": "1450.59325100", + "openTime": 1560210238656, + "closeTime": 1560296638656, + "firstId": 424817, + "lastId": 425122, + "count": 306 + }, + { + "symbol": "QTUMBNB", + "priceChange": "-0.00153000", + "priceChangePercent": "-1.534", + "weightedAvgPrice": "0.09938148", + "prevClosePrice": "0.10023000", + "lastPrice": "0.09823000", + "lastQty": "4.30000000", + "bidPrice": "0.09816000", + "bidQty": "2.50000000", + "askPrice": "0.09838000", + "askQty": "37.30000000", + "openPrice": "0.09976000", + "highPrice": "0.10336000", + "lowPrice": "0.09766000", + "volume": "21468.40000000", + "quoteVolume": "2133.56145900", + "openTime": 1560210236878, + "closeTime": 1560296636878, + "firstId": 391967, + "lastId": 392527, + "count": 561 + }, + { + "symbol": "QTUMUSDT", + "priceChange": "-0.06000000", + "priceChangePercent": "-1.881", + "weightedAvgPrice": "3.13777177", + "prevClosePrice": "3.18800000", + "lastPrice": "3.12900000", + "lastQty": "9.91200000", + "bidPrice": "3.13000000", + "bidQty": "161.47000000", + "askPrice": "3.13700000", + "askQty": "200.00000000", + "openPrice": "3.18900000", + "highPrice": "3.26800000", + "lowPrice": "3.03100000", + "volume": "511960.96500000", + "quoteVolume": "1606416.66501400", + "openTime": 1560210242033, + "closeTime": 1560296642033, + "firstId": 4204886, + "lastId": 4210948, + "count": 6063 + }, + { + "symbol": "XEMBTC", + "priceChange": "-0.00000003", + "priceChangePercent": "-0.284", + "weightedAvgPrice": "0.00001055", + "prevClosePrice": "0.00001056", + "lastPrice": "0.00001053", + "lastQty": "185.00000000", + "bidPrice": "0.00001052", + "bidQty": "25852.00000000", + "askPrice": "0.00001054", + "askQty": "1000.00000000", + "openPrice": "0.00001056", + "highPrice": "0.00001080", + "lowPrice": "0.00001045", + "volume": "9582988.00000000", + "quoteVolume": "101.13021306", + "openTime": 1560210242153, + "closeTime": 1560296642153, + "firstId": 5463048, + "lastId": 5469186, + "count": 6139 + }, + { + "symbol": "XEMETH", + "priceChange": "-0.00000251", + "priceChangePercent": "-0.734", + "weightedAvgPrice": "0.00034107", + "prevClosePrice": "0.00034044", + "lastPrice": "0.00033944", + "lastQty": "33.00000000", + "bidPrice": "0.00033891", + "bidQty": "2461.00000000", + "askPrice": "0.00033995", + "askQty": "2461.00000000", + "openPrice": "0.00034195", + "highPrice": "0.00034923", + "lowPrice": "0.00033765", + "volume": "463099.00000000", + "quoteVolume": "157.95008406", + "openTime": 1560210242265, + "closeTime": 1560296642265, + "firstId": 1188235, + "lastId": 1188877, + "count": 643 + }, + { + "symbol": "XEMBNB", + "priceChange": "-0.00004000", + "priceChangePercent": "-1.515", + "weightedAvgPrice": "0.00263935", + "prevClosePrice": "0.00264000", + "lastPrice": "0.00260000", + "lastQty": "38.60000000", + "bidPrice": "0.00260000", + "bidQty": "10.20000000", + "askPrice": "0.00261000", + "askQty": "1263.20000000", + "openPrice": "0.00264000", + "highPrice": "0.00269000", + "lowPrice": "0.00259000", + "volume": "280213.60000000", + "quoteVolume": "739.58064500", + "openTime": 1560210242072, + "closeTime": 1560296642072, + "firstId": 328287, + "lastId": 328587, + "count": 301 + }, + { + "symbol": "WANBTC", + "priceChange": "-0.00000250", + "priceChangePercent": "-4.296", + "weightedAvgPrice": "0.00005665", + "prevClosePrice": "0.00005830", + "lastPrice": "0.00005570", + "lastQty": "6.69000000", + "bidPrice": "0.00005560", + "bidQty": "6897.89000000", + "askPrice": "0.00005570", + "askQty": "9656.19000000", + "openPrice": "0.00005820", + "highPrice": "0.00005900", + "lowPrice": "0.00005440", + "volume": "3497126.30000000", + "quoteVolume": "198.11148390", + "openTime": 1560210232909, + "closeTime": 1560296632909, + "firstId": 8870034, + "lastId": 8881148, + "count": 11115 + }, + { + "symbol": "WANETH", + "priceChange": "-0.00009500", + "priceChangePercent": "-5.029", + "weightedAvgPrice": "0.00184349", + "prevClosePrice": "0.00188200", + "lastPrice": "0.00179400", + "lastQty": "32.93000000", + "bidPrice": "0.00179000", + "bidQty": "145.62000000", + "askPrice": "0.00179800", + "askQty": "142.13000000", + "openPrice": "0.00188900", + "highPrice": "0.00190900", + "lowPrice": "0.00177400", + "volume": "337032.85000000", + "quoteVolume": "621.31686368", + "openTime": 1560210242039, + "closeTime": 1560296642039, + "firstId": 2469859, + "lastId": 2474642, + "count": 4784 + }, + { + "symbol": "WANBNB", + "priceChange": "-0.00077000", + "priceChangePercent": "-5.299", + "weightedAvgPrice": "0.01415863", + "prevClosePrice": "0.01453000", + "lastPrice": "0.01376000", + "lastQty": "127.10000000", + "bidPrice": "0.01376000", + "bidQty": "7261.40000000", + "askPrice": "0.01383000", + "askQty": "636.70000000", + "openPrice": "0.01453000", + "highPrice": "0.01476000", + "lowPrice": "0.01362000", + "volume": "163385.30000000", + "quoteVolume": "2313.31233400", + "openTime": 1560210235789, + "closeTime": 1560296635789, + "firstId": 360854, + "lastId": 361518, + "count": 665 + }, + { + "symbol": "WPRBTC", + "priceChange": "-0.00000001", + "priceChangePercent": "-0.552", + "weightedAvgPrice": "0.00000190", + "prevClosePrice": "0.00000181", + "lastPrice": "0.00000180", + "lastQty": "4415.00000000", + "bidPrice": "0.00000180", + "bidQty": "306243.00000000", + "askPrice": "0.00000181", + "askQty": "176705.00000000", + "openPrice": "0.00000181", + "highPrice": "0.00000206", + "lowPrice": "0.00000178", + "volume": "78348493.00000000", + "quoteVolume": "148.57341497", + "openTime": 1560210242117, + "closeTime": 1560296642117, + "firstId": 3292517, + "lastId": 3300506, + "count": 7990 + }, + { + "symbol": "WPRETH", + "priceChange": "-0.00000030", + "priceChangePercent": "-0.512", + "weightedAvgPrice": "0.00006079", + "prevClosePrice": "0.00005863", + "lastPrice": "0.00005833", + "lastQty": "400.00000000", + "bidPrice": "0.00005791", + "bidQty": "2726.00000000", + "askPrice": "0.00005833", + "askQty": "452.00000000", + "openPrice": "0.00005863", + "highPrice": "0.00006678", + "lowPrice": "0.00005716", + "volume": "7858456.00000000", + "quoteVolume": "477.75044754", + "openTime": 1560210230383, + "closeTime": 1560296630383, + "firstId": 895687, + "lastId": 897555, + "count": 1869 + }, + { + "symbol": "QLCBTC", + "priceChange": "0.00000005", + "priceChangePercent": "0.965", + "weightedAvgPrice": "0.00000517", + "prevClosePrice": "0.00000517", + "lastPrice": "0.00000523", + "lastQty": "194.00000000", + "bidPrice": "0.00000522", + "bidQty": "770.00000000", + "askPrice": "0.00000523", + "askQty": "41336.00000000", + "openPrice": "0.00000518", + "highPrice": "0.00000530", + "lowPrice": "0.00000505", + "volume": "23892809.00000000", + "quoteVolume": "123.63535486", + "openTime": 1560210234858, + "closeTime": 1560296634858, + "firstId": 4979274, + "lastId": 4984098, + "count": 4825 + }, + { + "symbol": "QLCETH", + "priceChange": "-0.00000038", + "priceChangePercent": "-0.225", + "weightedAvgPrice": "0.00016823", + "prevClosePrice": "0.00016800", + "lastPrice": "0.00016827", + "lastQty": "141.00000000", + "bidPrice": "0.00016784", + "bidQty": "770.00000000", + "askPrice": "0.00016842", + "askQty": "5368.00000000", + "openPrice": "0.00016865", + "highPrice": "0.00018000", + "lowPrice": "0.00016288", + "volume": "565236.00000000", + "quoteVolume": "95.08800343", + "openTime": 1560210222131, + "closeTime": 1560296622131, + "firstId": 835338, + "lastId": 835741, + "count": 404 + }, + { + "symbol": "SYSBTC", + "priceChange": "0.00000006", + "priceChangePercent": "0.733", + "weightedAvgPrice": "0.00000817", + "prevClosePrice": "0.00000817", + "lastPrice": "0.00000824", + "lastQty": "1928.00000000", + "bidPrice": "0.00000821", + "bidQty": "18059.00000000", + "askPrice": "0.00000824", + "askQty": "2921.00000000", + "openPrice": "0.00000818", + "highPrice": "0.00000839", + "lowPrice": "0.00000803", + "volume": "2589073.00000000", + "quoteVolume": "21.16446412", + "openTime": 1560210224942, + "closeTime": 1560296624942, + "firstId": 2809605, + "lastId": 2811314, + "count": 1710 + }, + { + "symbol": "SYSETH", + "priceChange": "-0.00000150", + "priceChangePercent": "-0.566", + "weightedAvgPrice": "0.00026454", + "prevClosePrice": "0.00026523", + "lastPrice": "0.00026373", + "lastQty": "39.00000000", + "bidPrice": "0.00026395", + "bidQty": "18059.00000000", + "askPrice": "0.00026562", + "askQty": "600.00000000", + "openPrice": "0.00026523", + "highPrice": "0.00027062", + "lowPrice": "0.00025910", + "volume": "207279.00000000", + "quoteVolume": "54.83339526", + "openTime": 1560210232213, + "closeTime": 1560296632213, + "firstId": 506719, + "lastId": 507014, + "count": 296 + }, + { + "symbol": "SYSBNB", + "priceChange": "-0.00003000", + "priceChangePercent": "-1.456", + "weightedAvgPrice": "0.00204603", + "prevClosePrice": "0.00205000", + "lastPrice": "0.00203000", + "lastQty": "1673.30000000", + "bidPrice": "0.00203000", + "bidQty": "2727.70000000", + "askPrice": "0.00206000", + "askQty": "727.10000000", + "openPrice": "0.00206000", + "highPrice": "0.00208000", + "lowPrice": "0.00201000", + "volume": "197745.40000000", + "quoteVolume": "404.59284300", + "openTime": 1560210241302, + "closeTime": 1560296641302, + "firstId": 208005, + "lastId": 208206, + "count": 202 + }, + { + "symbol": "QLCBNB", + "priceChange": "-0.00000300", + "priceChangePercent": "-0.233", + "weightedAvgPrice": "0.00128627", + "prevClosePrice": "0.00130100", + "lastPrice": "0.00128700", + "lastQty": "1136.00000000", + "bidPrice": "0.00128800", + "bidQty": "3160.00000000", + "askPrice": "0.00129500", + "askQty": "9541.00000000", + "openPrice": "0.00129000", + "highPrice": "0.00132100", + "lowPrice": "0.00126900", + "volume": "862735.00000000", + "quoteVolume": "1109.70823900", + "openTime": 1560210231504, + "closeTime": 1560296631504, + "firstId": 385645, + "lastId": 385937, + "count": 293 + }, + { + "symbol": "GRSBTC", + "priceChange": "0.00000435", + "priceChangePercent": "7.892", + "weightedAvgPrice": "0.00005689", + "prevClosePrice": "0.00005523", + "lastPrice": "0.00005947", + "lastQty": "233.00000000", + "bidPrice": "0.00005931", + "bidQty": "136.00000000", + "askPrice": "0.00005949", + "askQty": "741.00000000", + "openPrice": "0.00005512", + "highPrice": "0.00006004", + "lowPrice": "0.00005425", + "volume": "4546759.00000000", + "quoteVolume": "258.67739829", + "openTime": 1560210241949, + "closeTime": 1560296641949, + "firstId": 4384513, + "lastId": 4397646, + "count": 13134 + }, + { + "symbol": "GRSETH", + "priceChange": "0.00012899", + "priceChangePercent": "7.219", + "weightedAvgPrice": "0.00184193", + "prevClosePrice": "0.00178650", + "lastPrice": "0.00191569", + "lastQty": "15.00000000", + "bidPrice": "0.00190664", + "bidQty": "441.00000000", + "askPrice": "0.00191672", + "askQty": "472.00000000", + "openPrice": "0.00178670", + "highPrice": "0.00193000", + "lowPrice": "0.00176569", + "volume": "206880.00000000", + "quoteVolume": "381.05859456", + "openTime": 1560210241924, + "closeTime": 1560296641924, + "firstId": 638953, + "lastId": 640326, + "count": 1374 + }, + { + "symbol": "ADAUSDT", + "priceChange": "0.00274000", + "priceChangePercent": "3.226", + "weightedAvgPrice": "0.08583970", + "prevClosePrice": "0.08491000", + "lastPrice": "0.08767000", + "lastQty": "1633.00000000", + "bidPrice": "0.08767000", + "bidQty": "1570.40000000", + "askPrice": "0.08774000", + "askQty": "13375.00000000", + "openPrice": "0.08493000", + "highPrice": "0.08913000", + "lowPrice": "0.08167000", + "volume": "167342169.80000000", + "quoteVolume": "14364602.30267100", + "openTime": 1560210242334, + "closeTime": 1560296642334, + "firstId": 14601476, + "lastId": 14638049, + "count": 36574 + }, + { + "symbol": "ADABNB", + "priceChange": "0.00008000", + "priceChangePercent": "2.996", + "weightedAvgPrice": "0.00272873", + "prevClosePrice": "0.00265000", + "lastPrice": "0.00275000", + "lastQty": "21818.00000000", + "bidPrice": "0.00274000", + "bidQty": "71670.40000000", + "askPrice": "0.00276000", + "askQty": "113821.10000000", + "openPrice": "0.00267000", + "highPrice": "0.00281000", + "lowPrice": "0.00262000", + "volume": "9848406.20000000", + "quoteVolume": "26873.64540500", + "openTime": 1560210238417, + "closeTime": 1560296638417, + "firstId": 1387698, + "lastId": 1392455, + "count": 4758 + }, + { + "symbol": "CLOAKBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00015550", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998627259, + "closeTime": 1558085027259, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "CLOAKETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00414200", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998627483, + "closeTime": 1558085027483, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "GNTBTC", + "priceChange": "0.00000107", + "priceChangePercent": "8.969", + "weightedAvgPrice": "0.00001283", + "prevClosePrice": "0.00001190", + "lastPrice": "0.00001300", + "lastQty": "241.00000000", + "bidPrice": "0.00001300", + "bidQty": "1518.00000000", + "askPrice": "0.00001303", + "askQty": "1847.00000000", + "openPrice": "0.00001193", + "highPrice": "0.00001339", + "lowPrice": "0.00001192", + "volume": "11369629.00000000", + "quoteVolume": "145.83941210", + "openTime": 1560210239658, + "closeTime": 1560296639658, + "firstId": 3085565, + "lastId": 3096135, + "count": 10571 + }, + { + "symbol": "GNTETH", + "priceChange": "0.00003185", + "priceChangePercent": "8.223", + "weightedAvgPrice": "0.00041595", + "prevClosePrice": "0.00038596", + "lastPrice": "0.00041916", + "lastQty": "260.00000000", + "bidPrice": "0.00041805", + "bidQty": "1518.00000000", + "askPrice": "0.00041916", + "askQty": "8.00000000", + "openPrice": "0.00038731", + "highPrice": "0.00043398", + "lowPrice": "0.00038667", + "volume": "1343978.00000000", + "quoteVolume": "559.03229291", + "openTime": 1560210241742, + "closeTime": 1560296641742, + "firstId": 712119, + "lastId": 714210, + "count": 2092 + }, + { + "symbol": "GNTBNB", + "priceChange": "0.00024000", + "priceChangePercent": "8.081", + "weightedAvgPrice": "0.00321531", + "prevClosePrice": "0.00296000", + "lastPrice": "0.00321000", + "lastQty": "2461.20000000", + "bidPrice": "0.00321000", + "bidQty": "4610.00000000", + "askPrice": "0.00323000", + "askQty": "21744.70000000", + "openPrice": "0.00297000", + "highPrice": "0.00339000", + "lowPrice": "0.00297000", + "volume": "641074.70000000", + "quoteVolume": "2061.25648500", + "openTime": 1560210241215, + "closeTime": 1560296641215, + "firstId": 142582, + "lastId": 143391, + "count": 810 + }, + { + "symbol": "LOOMBTC", + "priceChange": "0.00000037", + "priceChangePercent": "3.565", + "weightedAvgPrice": "0.00001062", + "prevClosePrice": "0.00001038", + "lastPrice": "0.00001075", + "lastQty": "1049.00000000", + "bidPrice": "0.00001075", + "bidQty": "1050.00000000", + "askPrice": "0.00001076", + "askQty": "3409.00000000", + "openPrice": "0.00001038", + "highPrice": "0.00001094", + "lowPrice": "0.00001023", + "volume": "15967543.00000000", + "quoteVolume": "169.65126777", + "openTime": 1560210241963, + "closeTime": 1560296641963, + "firstId": 4842232, + "lastId": 4851515, + "count": 9284 + }, + { + "symbol": "LOOMETH", + "priceChange": "0.00001081", + "priceChangePercent": "3.217", + "weightedAvgPrice": "0.00034253", + "prevClosePrice": "0.00033715", + "lastPrice": "0.00034688", + "lastQty": "41.00000000", + "bidPrice": "0.00034563", + "bidQty": "1050.00000000", + "askPrice": "0.00034694", + "askQty": "2115.00000000", + "openPrice": "0.00033607", + "highPrice": "0.00035592", + "lowPrice": "0.00033239", + "volume": "2270484.00000000", + "quoteVolume": "777.71614094", + "openTime": 1560210242144, + "closeTime": 1560296642144, + "firstId": 1653242, + "lastId": 1655618, + "count": 2377 + }, + { + "symbol": "LOOMBNB", + "priceChange": "0.00006000", + "priceChangePercent": "2.308", + "weightedAvgPrice": "0.00262569", + "prevClosePrice": "0.00258000", + "lastPrice": "0.00266000", + "lastQty": "82.20000000", + "bidPrice": "0.00265000", + "bidQty": "2793.00000000", + "askPrice": "0.00267000", + "askQty": "4312.10000000", + "openPrice": "0.00260000", + "highPrice": "0.00272000", + "lowPrice": "0.00257000", + "volume": "1192249.20000000", + "quoteVolume": "3130.47339800", + "openTime": 1560210215818, + "closeTime": 1560296615818, + "firstId": 309666, + "lastId": 310274, + "count": 609 + }, + { + "symbol": "XRPUSDT", + "priceChange": "-0.00665000", + "priceChangePercent": "-1.668", + "weightedAvgPrice": "0.39065927", + "prevClosePrice": "0.39851000", + "lastPrice": "0.39202000", + "lastQty": "29.90000000", + "bidPrice": "0.39195000", + "bidQty": "0.10000000", + "askPrice": "0.39202000", + "askQty": "802.40000000", + "openPrice": "0.39867000", + "highPrice": "0.40195000", + "lowPrice": "0.38175000", + "volume": "75821809.80000000", + "quoteVolume": "29620492.89863600", + "openTime": 1560210242369, + "closeTime": 1560296642369, + "firstId": 26654672, + "lastId": 26745524, + "count": 90853 + }, + { + "symbol": "BCNBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00000022", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998630956, + "closeTime": 1558085030956, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "BCNETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00000707", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000700", + "bidQty": "3458.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998631015, + "closeTime": 1558085031015, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "BCNBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00002000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998631295, + "closeTime": 1558085031295, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "REPBTC", + "priceChange": "-0.00002000", + "priceChangePercent": "-0.844", + "weightedAvgPrice": "0.00237911", + "prevClosePrice": "0.00237200", + "lastPrice": "0.00235100", + "lastQty": "8.67800000", + "bidPrice": "0.00234600", + "bidQty": "4.99300000", + "askPrice": "0.00235300", + "askQty": "0.72700000", + "openPrice": "0.00237100", + "highPrice": "0.00249200", + "lowPrice": "0.00232800", + "volume": "36908.74500000", + "quoteVolume": "87.80986838", + "openTime": 1560210241244, + "closeTime": 1560296641244, + "firstId": 3815588, + "lastId": 3820891, + "count": 5304 + }, + { + "symbol": "REPETH", + "priceChange": "-0.00086000", + "priceChangePercent": "-1.123", + "weightedAvgPrice": "0.07715039", + "prevClosePrice": "0.07675000", + "lastPrice": "0.07573000", + "lastQty": "1.49600000", + "bidPrice": "0.07551000", + "bidQty": "0.16300000", + "askPrice": "0.07577000", + "askQty": "2.04300000", + "openPrice": "0.07659000", + "highPrice": "0.08054000", + "lowPrice": "0.07529000", + "volume": "3918.63100000", + "quoteVolume": "302.32390659", + "openTime": 1560210242320, + "closeTime": 1560296642320, + "firstId": 779068, + "lastId": 780219, + "count": 1152 + }, + { + "symbol": "REPBNB", + "priceChange": "-0.00600000", + "priceChangePercent": "-1.019", + "weightedAvgPrice": "0.60011595", + "prevClosePrice": "0.59300000", + "lastPrice": "0.58300000", + "lastQty": "5.41800000", + "bidPrice": "0.57900000", + "bidQty": "120.04000000", + "askPrice": "0.58200000", + "askQty": "6.30800000", + "openPrice": "0.58900000", + "highPrice": "0.62100000", + "lowPrice": "0.57900000", + "volume": "1156.91000000", + "quoteVolume": "694.28013900", + "openTime": 1560210240932, + "closeTime": 1560296640932, + "firstId": 132032, + "lastId": 132318, + "count": 287 + }, + { + "symbol": "BTCTUSD", + "priceChange": "-78.93000000", + "priceChangePercent": "-0.987", + "weightedAvgPrice": "7889.51752989", + "prevClosePrice": "8001.67000000", + "lastPrice": "7920.07000000", + "lastQty": "0.00671900", + "bidPrice": "7920.18000000", + "bidQty": "0.02813800", + "askPrice": "7925.36000000", + "askQty": "0.03492200", + "openPrice": "7999.00000000", + "highPrice": "8055.32000000", + "lowPrice": "7705.76000000", + "volume": "712.31513600", + "quoteVolume": "5619822.75227997", + "openTime": 1560210242155, + "closeTime": 1560296642155, + "firstId": 1283285, + "lastId": 1294852, + "count": 11568 + }, + { + "symbol": "TUSDBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00025971", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998633126, + "closeTime": 1558085033126, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "ETHTUSD", + "priceChange": "-1.03000000", + "priceChangePercent": "-0.416", + "weightedAvgPrice": "242.77437067", + "prevClosePrice": "246.85000000", + "lastPrice": "246.27000000", + "lastQty": "0.91977000", + "bidPrice": "245.67000000", + "bidQty": "0.90655000", + "askPrice": "246.20000000", + "askQty": "2.46201000", + "openPrice": "247.30000000", + "highPrice": "249.16000000", + "lowPrice": "235.04000000", + "volume": "6175.79336000", + "quoteVolume": "1499324.34633410", + "openTime": 1560210241958, + "closeTime": 1560296641958, + "firstId": 342274, + "lastId": 345162, + "count": 2889 + }, + { + "symbol": "TUSDETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00762097", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998635242, + "closeTime": 1558085035242, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "TUSDBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.06777000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998635302, + "closeTime": 1558085035302, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "ZENBTC", + "priceChange": "-0.00002100", + "priceChangePercent": "-1.587", + "weightedAvgPrice": "0.00131180", + "prevClosePrice": "0.00132200", + "lastPrice": "0.00130200", + "lastQty": "22.95300000", + "bidPrice": "0.00130200", + "bidQty": "22.21300000", + "askPrice": "0.00130500", + "askQty": "3.66800000", + "openPrice": "0.00132300", + "highPrice": "0.00133700", + "lowPrice": "0.00129100", + "volume": "18060.88800000", + "quoteVolume": "23.69229667", + "openTime": 1560210236867, + "closeTime": 1560296636867, + "firstId": 2091369, + "lastId": 2092807, + "count": 1439 + }, + { + "symbol": "ZENETH", + "priceChange": "-0.00080000", + "priceChangePercent": "-1.870", + "weightedAvgPrice": "0.04245675", + "prevClosePrice": "0.04268000", + "lastPrice": "0.04199000", + "lastQty": "5.21000000", + "bidPrice": "0.04188000", + "bidQty": "11.36600000", + "askPrice": "0.04220000", + "askQty": "0.59800000", + "openPrice": "0.04279000", + "highPrice": "0.04329000", + "lowPrice": "0.04163000", + "volume": "1121.75300000", + "quoteVolume": "47.62599151", + "openTime": 1560210242231, + "closeTime": 1560296642231, + "firstId": 442181, + "lastId": 442476, + "count": 296 + }, + { + "symbol": "ZENBNB", + "priceChange": "-0.00700000", + "priceChangePercent": "-2.128", + "weightedAvgPrice": "0.32566180", + "prevClosePrice": "0.32800000", + "lastPrice": "0.32200000", + "lastQty": "24.17600000", + "bidPrice": "0.32200000", + "bidQty": "3.41800000", + "askPrice": "0.32300000", + "askQty": "25.00000000", + "openPrice": "0.32900000", + "highPrice": "0.34100000", + "lowPrice": "0.32000000", + "volume": "1453.19100000", + "quoteVolume": "473.24880400", + "openTime": 1560210239106, + "closeTime": 1560296639106, + "firstId": 106367, + "lastId": 106512, + "count": 146 + }, + { + "symbol": "SKYBTC", + "priceChange": "-0.00000100", + "priceChangePercent": "-0.467", + "weightedAvgPrice": "0.00021178", + "prevClosePrice": "0.00021400", + "lastPrice": "0.00021300", + "lastQty": "115.21200000", + "bidPrice": "0.00021200", + "bidQty": "581.81900000", + "askPrice": "0.00021300", + "askQty": "2135.15700000", + "openPrice": "0.00021400", + "highPrice": "0.00021500", + "lowPrice": "0.00020800", + "volume": "196878.21800000", + "quoteVolume": "41.69498988", + "openTime": 1560210225489, + "closeTime": 1560296625489, + "firstId": 3425202, + "lastId": 3427350, + "count": 2149 + }, + { + "symbol": "SKYETH", + "priceChange": "-0.00006000", + "priceChangePercent": "-0.868", + "weightedAvgPrice": "0.00686913", + "prevClosePrice": "0.00694000", + "lastPrice": "0.00685000", + "lastQty": "13.20000000", + "bidPrice": "0.00682000", + "bidQty": "62.06100000", + "askPrice": "0.00687000", + "askQty": "133.18300000", + "openPrice": "0.00691000", + "highPrice": "0.00699000", + "lowPrice": "0.00668000", + "volume": "36212.46900000", + "quoteVolume": "248.74811753", + "openTime": 1560210241986, + "closeTime": 1560296641986, + "firstId": 656704, + "lastId": 660156, + "count": 3453 + }, + { + "symbol": "SKYBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.05286355", + "prevClosePrice": "0.05400000", + "lastPrice": "0.05300000", + "lastQty": "193.48600000", + "bidPrice": "0.05200000", + "bidQty": "3825.13400000", + "askPrice": "0.05300000", + "askQty": "2715.52000000", + "openPrice": "0.05300000", + "highPrice": "0.05400000", + "lowPrice": "0.05200000", + "volume": "13763.21500000", + "quoteVolume": "727.57244600", + "openTime": 1560210137610, + "closeTime": 1560296537610, + "firstId": 212386, + "lastId": 212615, + "count": 230 + }, + { + "symbol": "EOSUSDT", + "priceChange": "-0.08270000", + "priceChangePercent": "-1.292", + "weightedAvgPrice": "6.29238094", + "prevClosePrice": "6.40000000", + "lastPrice": "6.31610000", + "lastQty": "21.03000000", + "bidPrice": "6.31410000", + "bidQty": "66.51000000", + "askPrice": "6.31490000", + "askQty": "406.42000000", + "openPrice": "6.39880000", + "highPrice": "6.47990000", + "lowPrice": "6.12820000", + "volume": "5223888.55000000", + "quoteVolume": "32870696.73354400", + "openTime": 1560210242348, + "closeTime": 1560296642348, + "firstId": 24992661, + "lastId": 25059471, + "count": 66811 + }, + { + "symbol": "EOSBNB", + "priceChange": "-0.00280000", + "priceChangePercent": "-1.397", + "weightedAvgPrice": "0.20001522", + "prevClosePrice": "0.20040000", + "lastPrice": "0.19770000", + "lastQty": "10.14000000", + "bidPrice": "0.19770000", + "bidQty": "428.18000000", + "askPrice": "0.19810000", + "askQty": "30.88000000", + "openPrice": "0.20050000", + "highPrice": "0.20290000", + "lowPrice": "0.19680000", + "volume": "149109.20000000", + "quoteVolume": "29824.10900300", + "openTime": 1560210242072, + "closeTime": 1560296642072, + "firstId": 1364067, + "lastId": 1369258, + "count": 5192 + }, + { + "symbol": "CVCBTC", + "priceChange": "0.00000050", + "priceChangePercent": "4.604", + "weightedAvgPrice": "0.00001172", + "prevClosePrice": "0.00001083", + "lastPrice": "0.00001136", + "lastQty": "30.00000000", + "bidPrice": "0.00001134", + "bidQty": "2971.00000000", + "askPrice": "0.00001136", + "askQty": "59.00000000", + "openPrice": "0.00001086", + "highPrice": "0.00001271", + "lowPrice": "0.00001067", + "volume": "20436862.00000000", + "quoteVolume": "239.58455101", + "openTime": 1560210241662, + "closeTime": 1560296641662, + "firstId": 2984245, + "lastId": 2995467, + "count": 11223 + }, + { + "symbol": "CVCETH", + "priceChange": "0.00001697", + "priceChangePercent": "4.865", + "weightedAvgPrice": "0.00038136", + "prevClosePrice": "0.00034968", + "lastPrice": "0.00036580", + "lastQty": "86.00000000", + "bidPrice": "0.00036474", + "bidQty": "714.00000000", + "askPrice": "0.00036580", + "askQty": "1224.00000000", + "openPrice": "0.00034883", + "highPrice": "0.00042612", + "lowPrice": "0.00034545", + "volume": "1725400.00000000", + "quoteVolume": "658.00666494", + "openTime": 1560210235843, + "closeTime": 1560296635843, + "firstId": 294848, + "lastId": 296655, + "count": 1808 + }, + { + "symbol": "CVCBNB", + "priceChange": "0.00010000", + "priceChangePercent": "3.704", + "weightedAvgPrice": "0.00294002", + "prevClosePrice": "0.00270000", + "lastPrice": "0.00280000", + "lastQty": "4983.20000000", + "bidPrice": "0.00279000", + "bidQty": "39352.50000000", + "askPrice": "0.00281000", + "askQty": "1060.90000000", + "openPrice": "0.00270000", + "highPrice": "0.00317000", + "lowPrice": "0.00267000", + "volume": "735733.70000000", + "quoteVolume": "2163.06869400", + "openTime": 1560210241615, + "closeTime": 1560296641615, + "firstId": 72113, + "lastId": 72870, + "count": 758 + }, + { + "symbol": "THETABTC", + "priceChange": "-0.00000030", + "priceChangePercent": "-1.649", + "weightedAvgPrice": "0.00001802", + "prevClosePrice": "0.00001819", + "lastPrice": "0.00001789", + "lastQty": "5819.00000000", + "bidPrice": "0.00001788", + "bidQty": "1320.00000000", + "askPrice": "0.00001789", + "askQty": "1888.00000000", + "openPrice": "0.00001819", + "highPrice": "0.00001854", + "lowPrice": "0.00001770", + "volume": "28994982.00000000", + "quoteVolume": "522.51192869", + "openTime": 1560210241915, + "closeTime": 1560296641915, + "firstId": 6385043, + "lastId": 6403893, + "count": 18851 + }, + { + "symbol": "THETAETH", + "priceChange": "-0.00001094", + "priceChangePercent": "-1.860", + "weightedAvgPrice": "0.00058472", + "prevClosePrice": "0.00058988", + "lastPrice": "0.00057710", + "lastQty": "916.00000000", + "bidPrice": "0.00057506", + "bidQty": "3311.00000000", + "askPrice": "0.00057710", + "askQty": "646.00000000", + "openPrice": "0.00058804", + "highPrice": "0.00060007", + "lowPrice": "0.00057117", + "volume": "2490435.00000000", + "quoteVolume": "1456.19747980", + "openTime": 1560210242338, + "closeTime": 1560296642338, + "firstId": 1155595, + "lastId": 1159015, + "count": 3421 + }, + { + "symbol": "THETABNB", + "priceChange": "-0.00011000", + "priceChangePercent": "-2.423", + "weightedAvgPrice": "0.00450636", + "prevClosePrice": "0.00453000", + "lastPrice": "0.00443000", + "lastQty": "8367.40000000", + "bidPrice": "0.00441000", + "bidQty": "4990.20000000", + "askPrice": "0.00443000", + "askQty": "1832.00000000", + "openPrice": "0.00454000", + "highPrice": "0.00465000", + "lowPrice": "0.00442000", + "volume": "1161469.60000000", + "quoteVolume": "5233.99954700", + "openTime": 1560210238328, + "closeTime": 1560296638328, + "firstId": 346986, + "lastId": 348148, + "count": 1163 + }, + { + "symbol": "XRPBNB", + "priceChange": "-0.00020000", + "priceChangePercent": "-1.601", + "weightedAvgPrice": "0.01238444", + "prevClosePrice": "0.01249000", + "lastPrice": "0.01229000", + "lastQty": "3055.30000000", + "bidPrice": "0.01227000", + "bidQty": "253.20000000", + "askPrice": "0.01228000", + "askQty": "721.60000000", + "openPrice": "0.01249000", + "highPrice": "0.01254000", + "lowPrice": "0.01184000", + "volume": "3955430.60000000", + "quoteVolume": "48985.80813600", + "openTime": 1560210240786, + "closeTime": 1560296640786, + "firstId": 1789769, + "lastId": 1795979, + "count": 6211 + }, + { + "symbol": "TUSDUSDT", + "priceChange": "-0.00060000", + "priceChangePercent": "-0.060", + "weightedAvgPrice": "0.99591676", + "prevClosePrice": "0.99580000", + "lastPrice": "0.99540000", + "lastQty": "203.46000000", + "bidPrice": "0.99530000", + "bidQty": "874.27000000", + "askPrice": "0.99560000", + "askQty": "960.32000000", + "openPrice": "0.99600000", + "highPrice": "0.99800000", + "lowPrice": "0.99340000", + "volume": "9682003.96000000", + "quoteVolume": "9642470.05115000", + "openTime": 1560210242329, + "closeTime": 1560296642329, + "firstId": 5198186, + "lastId": 5223087, + "count": 24902 + }, + { + "symbol": "IOTAUSDT", + "priceChange": "-0.00590000", + "priceChangePercent": "-1.379", + "weightedAvgPrice": "0.41778772", + "prevClosePrice": "0.42810000", + "lastPrice": "0.42180000", + "lastQty": "910.80000000", + "bidPrice": "0.42150000", + "bidQty": "134.19000000", + "askPrice": "0.42180000", + "askQty": "2023.41000000", + "openPrice": "0.42770000", + "highPrice": "0.43000000", + "lowPrice": "0.40630000", + "volume": "4026746.63000000", + "quoteVolume": "1682325.29333200", + "openTime": 1560210242311, + "closeTime": 1560296642311, + "firstId": 5430745, + "lastId": 5438363, + "count": 7619 + }, + { + "symbol": "XLMUSDT", + "priceChange": "-0.00048000", + "priceChangePercent": "-0.391", + "weightedAvgPrice": "0.12092606", + "prevClosePrice": "0.12291000", + "lastPrice": "0.12237000", + "lastQty": "1082.00000000", + "bidPrice": "0.12229000", + "bidQty": "1.60000000", + "askPrice": "0.12241000", + "askQty": "1.60000000", + "openPrice": "0.12285000", + "highPrice": "0.12362000", + "lowPrice": "0.11831000", + "volume": "22219234.60000000", + "quoteVolume": "2686884.54939400", + "openTime": 1560210241701, + "closeTime": 1560296641701, + "firstId": 8876496, + "lastId": 8891925, + "count": 15430 + }, + { + "symbol": "IOTXBTC", + "priceChange": "0.00000006", + "priceChangePercent": "4.412", + "weightedAvgPrice": "0.00000138", + "prevClosePrice": "0.00000137", + "lastPrice": "0.00000142", + "lastQty": "10854.00000000", + "bidPrice": "0.00000142", + "bidQty": "88443.00000000", + "askPrice": "0.00000143", + "askQty": "273756.00000000", + "openPrice": "0.00000136", + "highPrice": "0.00000148", + "lowPrice": "0.00000132", + "volume": "51323690.00000000", + "quoteVolume": "70.95952946", + "openTime": 1560210240601, + "closeTime": 1560296640601, + "firstId": 4015561, + "lastId": 4019597, + "count": 4037 + }, + { + "symbol": "IOTXETH", + "priceChange": "0.00000157", + "priceChangePercent": "3.543", + "weightedAvgPrice": "0.00004476", + "prevClosePrice": "0.00004433", + "lastPrice": "0.00004588", + "lastQty": "302.00000000", + "bidPrice": "0.00004559", + "bidQty": "88443.00000000", + "askPrice": "0.00004588", + "askQty": "7918.00000000", + "openPrice": "0.00004431", + "highPrice": "0.00004745", + "lowPrice": "0.00004287", + "volume": "5598624.00000000", + "quoteVolume": "250.60685350", + "openTime": 1560210241951, + "closeTime": 1560296641951, + "firstId": 1332983, + "lastId": 1334319, + "count": 1337 + }, + { + "symbol": "QKCBTC", + "priceChange": "0.00000012", + "priceChangePercent": "4.013", + "weightedAvgPrice": "0.00000305", + "prevClosePrice": "0.00000300", + "lastPrice": "0.00000311", + "lastQty": "7534.00000000", + "bidPrice": "0.00000311", + "bidQty": "325152.00000000", + "askPrice": "0.00000312", + "askQty": "173501.00000000", + "openPrice": "0.00000299", + "highPrice": "0.00000316", + "lowPrice": "0.00000291", + "volume": "192323802.00000000", + "quoteVolume": "585.79109350", + "openTime": 1560210242332, + "closeTime": 1560296642332, + "firstId": 8991859, + "lastId": 9007034, + "count": 15176 + }, + { + "symbol": "QKCETH", + "priceChange": "0.00000375", + "priceChangePercent": "3.880", + "weightedAvgPrice": "0.00009855", + "prevClosePrice": "0.00009664", + "lastPrice": "0.00010039", + "lastQty": "145.00000000", + "bidPrice": "0.00010001", + "bidQty": "44054.00000000", + "askPrice": "0.00010030", + "askQty": "120.00000000", + "openPrice": "0.00009664", + "highPrice": "0.00010232", + "lowPrice": "0.00009375", + "volume": "10071052.00000000", + "quoteVolume": "992.48708586", + "openTime": 1560210242270, + "closeTime": 1560296642270, + "firstId": 1714983, + "lastId": 1717718, + "count": 2736 + }, + { + "symbol": "AGIBTC", + "priceChange": "-0.00000009", + "priceChangePercent": "-1.452", + "weightedAvgPrice": "0.00000610", + "prevClosePrice": "0.00000620", + "lastPrice": "0.00000611", + "lastQty": "983.00000000", + "bidPrice": "0.00000610", + "bidQty": "11360.00000000", + "askPrice": "0.00000611", + "askQty": "889.00000000", + "openPrice": "0.00000620", + "highPrice": "0.00000625", + "lowPrice": "0.00000601", + "volume": "14982667.00000000", + "quoteVolume": "91.34101193", + "openTime": 1560210240624, + "closeTime": 1560296640624, + "firstId": 3212981, + "lastId": 3216253, + "count": 3273 + }, + { + "symbol": "AGIETH", + "priceChange": "-0.00000371", + "priceChangePercent": "-1.849", + "weightedAvgPrice": "0.00019717", + "prevClosePrice": "0.00020037", + "lastPrice": "0.00019699", + "lastQty": "612.00000000", + "bidPrice": "0.00019620", + "bidQty": "618.00000000", + "askPrice": "0.00019768", + "askQty": "2499.00000000", + "openPrice": "0.00020070", + "highPrice": "0.00020198", + "lowPrice": "0.00019101", + "volume": "421374.00000000", + "quoteVolume": "83.08196584", + "openTime": 1560210236455, + "closeTime": 1560296636455, + "firstId": 897260, + "lastId": 897975, + "count": 716 + }, + { + "symbol": "AGIBNB", + "priceChange": "-0.00002000", + "priceChangePercent": "-1.299", + "weightedAvgPrice": "0.00152827", + "prevClosePrice": "0.00154000", + "lastPrice": "0.00152000", + "lastQty": "3020.50000000", + "bidPrice": "0.00150000", + "bidQty": "6386.00000000", + "askPrice": "0.00152000", + "askQty": "4479.00000000", + "openPrice": "0.00154000", + "highPrice": "0.00155000", + "lowPrice": "0.00150000", + "volume": "358724.30000000", + "quoteVolume": "548.22925700", + "openTime": 1560210240854, + "closeTime": 1560296640854, + "firstId": 105654, + "lastId": 105832, + "count": 179 + }, + { + "symbol": "NXSBTC", + "priceChange": "-0.00000090", + "priceChangePercent": "-1.935", + "weightedAvgPrice": "0.00004639", + "prevClosePrice": "0.00004660", + "lastPrice": "0.00004560", + "lastQty": "361.74000000", + "bidPrice": "0.00004540", + "bidQty": "1943.79000000", + "askPrice": "0.00004560", + "askQty": "321.36000000", + "openPrice": "0.00004650", + "highPrice": "0.00004790", + "lowPrice": "0.00004510", + "volume": "746198.96000000", + "quoteVolume": "34.61523818", + "openTime": 1560210241625, + "closeTime": 1560296641625, + "firstId": 2006460, + "lastId": 2008213, + "count": 1754 + }, + { + "symbol": "NXSETH", + "priceChange": "-0.00003200", + "priceChangePercent": "-2.121", + "weightedAvgPrice": "0.00149684", + "prevClosePrice": "0.00150800", + "lastPrice": "0.00147700", + "lastQty": "108.77000000", + "bidPrice": "0.00146100", + "bidQty": "110.83000000", + "askPrice": "0.00147700", + "askQty": "1182.49000000", + "openPrice": "0.00150900", + "highPrice": "0.00157200", + "lowPrice": "0.00145900", + "volume": "30600.25000000", + "quoteVolume": "45.80369707", + "openTime": 1560210241749, + "closeTime": 1560296641749, + "firstId": 226778, + "lastId": 227055, + "count": 278 + }, + { + "symbol": "NXSBNB", + "priceChange": "-0.00020000", + "priceChangePercent": "-1.724", + "weightedAvgPrice": "0.01157243", + "prevClosePrice": "0.01160000", + "lastPrice": "0.01140000", + "lastQty": "1247.71000000", + "bidPrice": "0.01120000", + "bidQty": "877.22000000", + "askPrice": "0.01130000", + "askQty": "273.16000000", + "openPrice": "0.01160000", + "highPrice": "0.01190000", + "lowPrice": "0.01130000", + "volume": "20953.95000000", + "quoteVolume": "242.48818300", + "openTime": 1560210229188, + "closeTime": 1560296629188, + "firstId": 67723, + "lastId": 67821, + "count": 99 + }, + { + "symbol": "ENJBNB", + "priceChange": "0.00024300", + "priceChangePercent": "5.114", + "weightedAvgPrice": "0.00495912", + "prevClosePrice": "0.00475600", + "lastPrice": "0.00499500", + "lastQty": "822.00000000", + "bidPrice": "0.00497200", + "bidQty": "822.00000000", + "askPrice": "0.00498900", + "askQty": "1557.00000000", + "openPrice": "0.00475200", + "highPrice": "0.00513800", + "lowPrice": "0.00474100", + "volume": "1200995.00000000", + "quoteVolume": "5955.88209300", + "openTime": 1560210238872, + "closeTime": 1560296638872, + "firstId": 473224, + "lastId": 474865, + "count": 1642 + }, + { + "symbol": "DATABTC", + "priceChange": "0.00000003", + "priceChangePercent": "0.971", + "weightedAvgPrice": "0.00000309", + "prevClosePrice": "0.00000309", + "lastPrice": "0.00000312", + "lastQty": "9969.00000000", + "bidPrice": "0.00000312", + "bidQty": "118236.00000000", + "askPrice": "0.00000313", + "askQty": "964.00000000", + "openPrice": "0.00000309", + "highPrice": "0.00000316", + "lowPrice": "0.00000303", + "volume": "7870539.00000000", + "quoteVolume": "24.35154490", + "openTime": 1560210238813, + "closeTime": 1560296638813, + "firstId": 2232909, + "lastId": 2234359, + "count": 1451 + }, + { + "symbol": "DATAETH", + "priceChange": "0.00000073", + "priceChangePercent": "0.728", + "weightedAvgPrice": "0.00009979", + "prevClosePrice": "0.00009984", + "lastPrice": "0.00010104", + "lastQty": "302.00000000", + "bidPrice": "0.00010017", + "bidQty": "52323.00000000", + "askPrice": "0.00010092", + "askQty": "1115.00000000", + "openPrice": "0.00010031", + "highPrice": "0.00010209", + "lowPrice": "0.00009782", + "volume": "277511.00000000", + "quoteVolume": "27.69404327", + "openTime": 1560210242193, + "closeTime": 1560296642193, + "firstId": 375328, + "lastId": 375722, + "count": 395 + }, + { + "symbol": "ONTUSDT", + "priceChange": "0.01930000", + "priceChangePercent": "1.379", + "weightedAvgPrice": "1.40122361", + "prevClosePrice": "1.40070000", + "lastPrice": "1.41930000", + "lastQty": "415.13000000", + "bidPrice": "1.41920000", + "bidQty": "704.62000000", + "askPrice": "1.42110000", + "askQty": "164.08000000", + "openPrice": "1.40000000", + "highPrice": "1.44000000", + "lowPrice": "1.36120000", + "volume": "3457406.39000000", + "quoteVolume": "4844599.46294600", + "openTime": 1560210241949, + "closeTime": 1560296641949, + "firstId": 8769757, + "lastId": 8786427, + "count": 16671 + }, + { + "symbol": "TRXBNB", + "priceChange": "-0.00000900", + "priceChangePercent": "-0.915", + "weightedAvgPrice": "0.00097151", + "prevClosePrice": "0.00098500", + "lastPrice": "0.00097500", + "lastQty": "1394.00000000", + "bidPrice": "0.00097400", + "bidQty": "30114.00000000", + "askPrice": "0.00097500", + "askQty": "1394.00000000", + "openPrice": "0.00098400", + "highPrice": "0.00099500", + "lowPrice": "0.00095700", + "volume": "21616168.00000000", + "quoteVolume": "21000.35266400", + "openTime": 1560210240475, + "closeTime": 1560296640475, + "firstId": 1247997, + "lastId": 1252867, + "count": 4871 + }, + { + "symbol": "TRXUSDT", + "priceChange": "-0.00031000", + "priceChangePercent": "-0.986", + "weightedAvgPrice": "0.03064353", + "prevClosePrice": "0.03146000", + "lastPrice": "0.03114000", + "lastQty": "269491.00000000", + "bidPrice": "0.03112000", + "bidQty": "24217.30000000", + "askPrice": "0.03115000", + "askQty": "10736.90000000", + "openPrice": "0.03145000", + "highPrice": "0.03158000", + "lowPrice": "0.02983000", + "volume": "595084477.60000000", + "quoteVolume": "18235491.34168100", + "openTime": 1560210241716, + "closeTime": 1560296641716, + "firstId": 13890951, + "lastId": 13930501, + "count": 39551 + }, + { + "symbol": "ETCUSDT", + "priceChange": "-0.05020000", + "priceChangePercent": "-0.607", + "weightedAvgPrice": "8.14996956", + "prevClosePrice": "8.27930000", + "lastPrice": "8.22570000", + "lastQty": "121.93000000", + "bidPrice": "8.21920000", + "bidQty": "99.26000000", + "askPrice": "8.22810000", + "askQty": "100.00000000", + "openPrice": "8.27590000", + "highPrice": "8.32960000", + "lowPrice": "8.00000000", + "volume": "471056.09000000", + "quoteVolume": "3839092.79417800", + "openTime": 1560210241851, + "closeTime": 1560296641851, + "firstId": 5554327, + "lastId": 5564722, + "count": 10396 + }, + { + "symbol": "ETCBNB", + "priceChange": "-0.00190000", + "priceChangePercent": "-0.731", + "weightedAvgPrice": "0.25878667", + "prevClosePrice": "0.25950000", + "lastPrice": "0.25790000", + "lastQty": "176.97000000", + "bidPrice": "0.25790000", + "bidQty": "99.26000000", + "askPrice": "0.25820000", + "askQty": "55.20000000", + "openPrice": "0.25980000", + "highPrice": "0.26280000", + "lowPrice": "0.25510000", + "volume": "8302.39000000", + "quoteVolume": "2148.54789000", + "openTime": 1560210238746, + "closeTime": 1560296638746, + "firstId": 352925, + "lastId": 353614, + "count": 690 + }, + { + "symbol": "ICXUSDT", + "priceChange": "0.00040000", + "priceChangePercent": "0.106", + "weightedAvgPrice": "0.37198708", + "prevClosePrice": "0.37680000", + "lastPrice": "0.37680000", + "lastQty": "1618.16000000", + "bidPrice": "0.37680000", + "bidQty": "232.20000000", + "askPrice": "0.37800000", + "askQty": "628.92000000", + "openPrice": "0.37640000", + "highPrice": "0.38200000", + "lowPrice": "0.35800000", + "volume": "4602877.26000000", + "quoteVolume": "1712210.85226100", + "openTime": 1560210234256, + "closeTime": 1560296634256, + "firstId": 4531957, + "lastId": 4538151, + "count": 6195 + }, + { + "symbol": "SCBTC", + "priceChange": "0.00000001", + "priceChangePercent": "2.564", + "weightedAvgPrice": "0.00000040", + "prevClosePrice": "0.00000039", + "lastPrice": "0.00000040", + "lastQty": "14144.00000000", + "bidPrice": "0.00000040", + "bidQty": "2976496.00000000", + "askPrice": "0.00000041", + "askQty": "59716617.00000000", + "openPrice": "0.00000039", + "highPrice": "0.00000041", + "lowPrice": "0.00000039", + "volume": "174941304.00000000", + "quoteVolume": "69.69514044", + "openTime": 1560210239732, + "closeTime": 1560296639732, + "firstId": 1596148, + "lastId": 1597304, + "count": 1157 + }, + { + "symbol": "SCETH", + "priceChange": "0.00000006", + "priceChangePercent": "0.466", + "weightedAvgPrice": "0.00001286", + "prevClosePrice": "0.00001288", + "lastPrice": "0.00001294", + "lastQty": "20620.00000000", + "bidPrice": "0.00001289", + "bidQty": "20209.00000000", + "askPrice": "0.00001295", + "askQty": "12284.00000000", + "openPrice": "0.00001288", + "highPrice": "0.00001308", + "lowPrice": "0.00001270", + "volume": "24698412.00000000", + "quoteVolume": "317.56269588", + "openTime": 1560210236511, + "closeTime": 1560296636511, + "firstId": 611374, + "lastId": 613350, + "count": 1977 + }, + { + "symbol": "SCBNB", + "priceChange": "0.00000100", + "priceChangePercent": "1.010", + "weightedAvgPrice": "0.00009888", + "prevClosePrice": "0.00009900", + "lastPrice": "0.00010000", + "lastQty": "175860.00000000", + "bidPrice": "0.00009900", + "bidQty": "1992188.00000000", + "askPrice": "0.00010000", + "askQty": "3083869.00000000", + "openPrice": "0.00009900", + "highPrice": "0.00010100", + "lowPrice": "0.00009800", + "volume": "16753304.00000000", + "quoteVolume": "1656.59912500", + "openTime": 1560210236934, + "closeTime": 1560296636934, + "firstId": 147853, + "lastId": 148268, + "count": 416 + }, + { + "symbol": "NPXSBTC", + "priceChange": "0.00000001", + "priceChangePercent": "10.000", + "weightedAvgPrice": "0.00000011", + "prevClosePrice": "0.00000011", + "lastPrice": "0.00000011", + "lastQty": "69265.00000000", + "bidPrice": "0.00000010", + "bidQty": "3515195577.00000000", + "askPrice": "0.00000011", + "askQty": "1489026330.00000000", + "openPrice": "0.00000010", + "highPrice": "0.00000011", + "lowPrice": "0.00000010", + "volume": "971255442.00000000", + "quoteVolume": "102.03278408", + "openTime": 1560210229370, + "closeTime": 1560296629370, + "firstId": 2870490, + "lastId": 2872129, + "count": 1640 + }, + { + "symbol": "NPXSETH", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00000347", + "prevClosePrice": "0.00000346", + "lastPrice": "0.00000346", + "lastQty": "511652.00000000", + "bidPrice": "0.00000346", + "bidQty": "248183.00000000", + "askPrice": "0.00000347", + "askQty": "1747992.00000000", + "openPrice": "0.00000346", + "highPrice": "0.00000353", + "lowPrice": "0.00000342", + "volume": "982405636.00000000", + "quoteVolume": "3412.95934411", + "openTime": 1560210240289, + "closeTime": 1560296640289, + "firstId": 2224356, + "lastId": 2229552, + "count": 5197 + }, + { + "symbol": "VENUSDT", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00010000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998662933, + "closeTime": 1558085062933, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "KEYBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00000043", + "prevClosePrice": "0.00000042", + "lastPrice": "0.00000042", + "lastQty": "34755.00000000", + "bidPrice": "0.00000042", + "bidQty": "19501243.00000000", + "askPrice": "0.00000043", + "askQty": "18207238.00000000", + "openPrice": "0.00000042", + "highPrice": "0.00000044", + "lowPrice": "0.00000042", + "volume": "59547093.00000000", + "quoteVolume": "25.33629843", + "openTime": 1560210212924, + "closeTime": 1560296612924, + "firstId": 4226188, + "lastId": 4227075, + "count": 888 + }, + { + "symbol": "KEYETH", + "priceChange": "-0.00000015", + "priceChangePercent": "-1.080", + "weightedAvgPrice": "0.00001377", + "prevClosePrice": "0.00001394", + "lastPrice": "0.00001374", + "lastQty": "754257.00000000", + "bidPrice": "0.00001374", + "bidQty": "420214.00000000", + "askPrice": "0.00001375", + "askQty": "801.00000000", + "openPrice": "0.00001389", + "highPrice": "0.00001414", + "lowPrice": "0.00001357", + "volume": "11225731.00000000", + "quoteVolume": "154.61654534", + "openTime": 1560210197386, + "closeTime": 1560296597386, + "firstId": 1039519, + "lastId": 1039904, + "count": 386 + }, + { + "symbol": "NASBTC", + "priceChange": "0.00000340", + "priceChangePercent": "1.563", + "weightedAvgPrice": "0.00022581", + "prevClosePrice": "0.00021750", + "lastPrice": "0.00022090", + "lastQty": "7.62000000", + "bidPrice": "0.00022020", + "bidQty": "488.33000000", + "askPrice": "0.00022100", + "askQty": "1268.30000000", + "openPrice": "0.00021750", + "highPrice": "0.00024940", + "lowPrice": "0.00021130", + "volume": "5562051.12000000", + "quoteVolume": "1255.98692235", + "openTime": 1560210240728, + "closeTime": 1560296640728, + "firstId": 2973917, + "lastId": 3006908, + "count": 32992 + }, + { + "symbol": "NASETH", + "priceChange": "0.00003500", + "priceChangePercent": "0.497", + "weightedAvgPrice": "0.00737806", + "prevClosePrice": "0.00703900", + "lastPrice": "0.00708400", + "lastQty": "21.70000000", + "bidPrice": "0.00708300", + "bidQty": "196.72000000", + "askPrice": "0.00711900", + "askQty": "21.70000000", + "openPrice": "0.00704900", + "highPrice": "0.00808700", + "lowPrice": "0.00683600", + "volume": "379292.35000000", + "quoteVolume": "2798.44211581", + "openTime": 1560210242350, + "closeTime": 1560296642350, + "firstId": 1097020, + "lastId": 1103700, + "count": 6681 + }, + { + "symbol": "NASBNB", + "priceChange": "0.00019000", + "priceChangePercent": "0.350", + "weightedAvgPrice": "0.05667784", + "prevClosePrice": "0.05477000", + "lastPrice": "0.05448000", + "lastQty": "2163.20000000", + "bidPrice": "0.05435000", + "bidQty": "165.00000000", + "askPrice": "0.05474000", + "askQty": "149.00000000", + "openPrice": "0.05429000", + "highPrice": "0.06259000", + "lowPrice": "0.05277000", + "volume": "148953.50000000", + "quoteVolume": "8442.36204600", + "openTime": 1560210229303, + "closeTime": 1560296629303, + "firstId": 70367, + "lastId": 72250, + "count": 1884 + }, + { + "symbol": "MFTBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00000044", + "prevClosePrice": "0.00000044", + "lastPrice": "0.00000044", + "lastQty": "7972219.00000000", + "bidPrice": "0.00000043", + "bidQty": "114033113.00000000", + "askPrice": "0.00000044", + "askQty": "41928472.00000000", + "openPrice": "0.00000044", + "highPrice": "0.00000046", + "lowPrice": "0.00000043", + "volume": "646733476.00000000", + "quoteVolume": "285.05461444", + "openTime": 1560210219665, + "closeTime": 1560296619665, + "firstId": 3221951, + "lastId": 3225220, + "count": 3270 + }, + { + "symbol": "MFTETH", + "priceChange": "-0.00000005", + "priceChangePercent": "-0.352", + "weightedAvgPrice": "0.00001422", + "prevClosePrice": "0.00001426", + "lastPrice": "0.00001417", + "lastQty": "9921.00000000", + "bidPrice": "0.00001413", + "bidQty": "213466.00000000", + "askPrice": "0.00001417", + "askQty": "74893.00000000", + "openPrice": "0.00001422", + "highPrice": "0.00001482", + "lowPrice": "0.00001397", + "volume": "101503345.00000000", + "quoteVolume": "1443.34802336", + "openTime": 1560210231775, + "closeTime": 1560296631775, + "firstId": 1183375, + "lastId": 1185627, + "count": 2253 + }, + { + "symbol": "MFTBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00010972", + "prevClosePrice": "0.00010900", + "lastPrice": "0.00010900", + "lastQty": "611851.00000000", + "bidPrice": "0.00010800", + "bidQty": "68527.00000000", + "askPrice": "0.00010900", + "askQty": "231495.00000000", + "openPrice": "0.00010900", + "highPrice": "0.00011400", + "lowPrice": "0.00010800", + "volume": "35296989.00000000", + "quoteVolume": "3872.63980500", + "openTime": 1560210221063, + "closeTime": 1560296621063, + "firstId": 244796, + "lastId": 245900, + "count": 1105 + }, + { + "symbol": "DENTBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00000023", + "prevClosePrice": "0.00000024", + "lastPrice": "0.00000024", + "lastQty": "1063245.00000000", + "bidPrice": "0.00000023", + "bidQty": "221629018.00000000", + "askPrice": "0.00000024", + "askQty": "168110581.00000000", + "openPrice": "0.00000024", + "highPrice": "0.00000024", + "lowPrice": "0.00000023", + "volume": "268111188.00000000", + "quoteVolume": "62.11767308", + "openTime": 1560210199668, + "closeTime": 1560296599668, + "firstId": 1807888, + "lastId": 1808763, + "count": 876 + }, + { + "symbol": "DENTETH", + "priceChange": "-0.00000004", + "priceChangePercent": "-0.531", + "weightedAvgPrice": "0.00000746", + "prevClosePrice": "0.00000754", + "lastPrice": "0.00000749", + "lastQty": "48273.00000000", + "bidPrice": "0.00000748", + "bidQty": "30400.00000000", + "askPrice": "0.00000749", + "askQty": "180500.00000000", + "openPrice": "0.00000753", + "highPrice": "0.00000757", + "lowPrice": "0.00000737", + "volume": "30823167.00000000", + "quoteVolume": "229.82589933", + "openTime": 1560210238842, + "closeTime": 1560296638842, + "firstId": 799529, + "lastId": 801261, + "count": 1733 + }, + { + "symbol": "ARDRBTC", + "priceChange": "0.00000051", + "priceChangePercent": "4.096", + "weightedAvgPrice": "0.00001276", + "prevClosePrice": "0.00001246", + "lastPrice": "0.00001296", + "lastQty": "155.00000000", + "bidPrice": "0.00001289", + "bidQty": "469.00000000", + "askPrice": "0.00001293", + "askQty": "1409.00000000", + "openPrice": "0.00001245", + "highPrice": "0.00001331", + "lowPrice": "0.00001240", + "volume": "5914463.00000000", + "quoteVolume": "75.48307569", + "openTime": 1560210242056, + "closeTime": 1560296642056, + "firstId": 1495324, + "lastId": 1500812, + "count": 5489 + }, + { + "symbol": "ARDRETH", + "priceChange": "0.00001570", + "priceChangePercent": "3.900", + "weightedAvgPrice": "0.00041127", + "prevClosePrice": "0.00040254", + "lastPrice": "0.00041824", + "lastQty": "154.00000000", + "bidPrice": "0.00041484", + "bidQty": "156.00000000", + "askPrice": "0.00041822", + "askQty": "307.00000000", + "openPrice": "0.00040254", + "highPrice": "0.00042971", + "lowPrice": "0.00040079", + "volume": "628264.00000000", + "quoteVolume": "258.38330171", + "openTime": 1560210237854, + "closeTime": 1560296637854, + "firstId": 285573, + "lastId": 286835, + "count": 1263 + }, + { + "symbol": "ARDRBNB", + "priceChange": "0.00001000", + "priceChangePercent": "0.314", + "weightedAvgPrice": "0.00319576", + "prevClosePrice": "0.00311000", + "lastPrice": "0.00319000", + "lastQty": "697.10000000", + "bidPrice": "0.00318000", + "bidQty": "3556.00000000", + "askPrice": "0.00321000", + "askQty": "233.00000000", + "openPrice": "0.00318000", + "highPrice": "0.00330000", + "lowPrice": "0.00311000", + "volume": "291033.80000000", + "quoteVolume": "930.07528400", + "openTime": 1560210230116, + "closeTime": 1560296630116, + "firstId": 65787, + "lastId": 66169, + "count": 383 + }, + { + "symbol": "NULSUSDT", + "priceChange": "-0.02800000", + "priceChangePercent": "-2.918", + "weightedAvgPrice": "0.93230148", + "prevClosePrice": "0.95780000", + "lastPrice": "0.93160000", + "lastQty": "142.99000000", + "bidPrice": "0.92770000", + "bidQty": "171.62000000", + "askPrice": "0.93160000", + "askQty": "176.27000000", + "openPrice": "0.95960000", + "highPrice": "1.00150000", + "lowPrice": "0.88510000", + "volume": "1804112.09000000", + "quoteVolume": "1681976.36555300", + "openTime": 1560210241206, + "closeTime": 1560296641206, + "firstId": 1746724, + "lastId": 1759251, + "count": 12528 + }, + { + "symbol": "HOTBTC", + "priceChange": "-0.00000001", + "priceChangePercent": "-4.167", + "weightedAvgPrice": "0.00000023", + "prevClosePrice": "0.00000024", + "lastPrice": "0.00000023", + "lastQty": "30134.00000000", + "bidPrice": "0.00000022", + "bidQty": "482614693.00000000", + "askPrice": "0.00000023", + "askQty": "313912795.00000000", + "openPrice": "0.00000024", + "highPrice": "0.00000024", + "lowPrice": "0.00000022", + "volume": "694182698.00000000", + "quoteVolume": "156.99502189", + "openTime": 1560210230625, + "closeTime": 1560296630625, + "firstId": 2207948, + "lastId": 2209796, + "count": 1849 + }, + { + "symbol": "HOTETH", + "priceChange": "-0.00000025", + "priceChangePercent": "-3.320", + "weightedAvgPrice": "0.00000735", + "prevClosePrice": "0.00000754", + "lastPrice": "0.00000728", + "lastQty": "5411.00000000", + "bidPrice": "0.00000726", + "bidQty": "14460.00000000", + "askPrice": "0.00000728", + "askQty": "990.00000000", + "openPrice": "0.00000753", + "highPrice": "0.00000754", + "lowPrice": "0.00000725", + "volume": "470333850.00000000", + "quoteVolume": "3455.13777108", + "openTime": 1560210241975, + "closeTime": 1560296641975, + "firstId": 2648754, + "lastId": 2656591, + "count": 7838 + }, + { + "symbol": "VETBTC", + "priceChange": "-0.00000003", + "priceChangePercent": "-3.093", + "weightedAvgPrice": "0.00000095", + "prevClosePrice": "0.00000096", + "lastPrice": "0.00000094", + "lastQty": "43560.00000000", + "bidPrice": "0.00000094", + "bidQty": "7933654.00000000", + "askPrice": "0.00000095", + "askQty": "11355052.00000000", + "openPrice": "0.00000097", + "highPrice": "0.00000098", + "lowPrice": "0.00000094", + "volume": "257602419.00000000", + "quoteVolume": "245.95465015", + "openTime": 1560210240660, + "closeTime": 1560296640660, + "firstId": 4082402, + "lastId": 4086106, + "count": 3705 + }, + { + "symbol": "VETETH", + "priceChange": "-0.00000069", + "priceChangePercent": "-2.209", + "weightedAvgPrice": "0.00003089", + "prevClosePrice": "0.00003122", + "lastPrice": "0.00003054", + "lastQty": "12063.00000000", + "bidPrice": "0.00003050", + "bidQty": "18164.00000000", + "askPrice": "0.00003057", + "askQty": "54891.00000000", + "openPrice": "0.00003123", + "highPrice": "0.00003146", + "lowPrice": "0.00003036", + "volume": "34303283.00000000", + "quoteVolume": "1059.53437179", + "openTime": 1560210242347, + "closeTime": 1560296642347, + "firstId": 2125913, + "lastId": 2128270, + "count": 2358 + }, + { + "symbol": "VETUSDT", + "priceChange": "-0.00023200", + "priceChangePercent": "-3.015", + "weightedAvgPrice": "0.00751102", + "prevClosePrice": "0.00769800", + "lastPrice": "0.00746400", + "lastQty": "3000.00000000", + "bidPrice": "0.00746700", + "bidQty": "25276.00000000", + "askPrice": "0.00749800", + "askQty": "3361.00000000", + "openPrice": "0.00769600", + "highPrice": "0.00771600", + "lowPrice": "0.00730000", + "volume": "279169463.00000000", + "quoteVolume": "2096846.46945900", + "openTime": 1560210240597, + "closeTime": 1560296640597, + "firstId": 4218776, + "lastId": 4225360, + "count": 6585 + }, + { + "symbol": "VETBNB", + "priceChange": "-0.00000670", + "priceChangePercent": "-2.781", + "weightedAvgPrice": "0.00023836", + "prevClosePrice": "0.00024200", + "lastPrice": "0.00023420", + "lastQty": "30227.00000000", + "bidPrice": "0.00023390", + "bidQty": "88812.00000000", + "askPrice": "0.00023440", + "askQty": "50281.00000000", + "openPrice": "0.00024090", + "highPrice": "0.00024320", + "lowPrice": "0.00023350", + "volume": "30343523.00000000", + "quoteVolume": "7232.66712280", + "openTime": 1560210241582, + "closeTime": 1560296641582, + "firstId": 545138, + "lastId": 547558, + "count": 2421 + }, + { + "symbol": "DOCKBTC", + "priceChange": "0.00000007", + "priceChangePercent": "4.046", + "weightedAvgPrice": "0.00000175", + "prevClosePrice": "0.00000174", + "lastPrice": "0.00000180", + "lastQty": "164193.00000000", + "bidPrice": "0.00000179", + "bidQty": "231486.00000000", + "askPrice": "0.00000180", + "askQty": "71582.00000000", + "openPrice": "0.00000173", + "highPrice": "0.00000183", + "lowPrice": "0.00000170", + "volume": "44146957.00000000", + "quoteVolume": "77.31624712", + "openTime": 1560210241625, + "closeTime": 1560296641625, + "firstId": 3343016, + "lastId": 3348994, + "count": 5979 + }, + { + "symbol": "DOCKETH", + "priceChange": "0.00000195", + "priceChangePercent": "3.465", + "weightedAvgPrice": "0.00005668", + "prevClosePrice": "0.00005625", + "lastPrice": "0.00005822", + "lastQty": "370.00000000", + "bidPrice": "0.00005758", + "bidQty": "17234.00000000", + "askPrice": "0.00005813", + "askQty": "3889.00000000", + "openPrice": "0.00005627", + "highPrice": "0.00005900", + "lowPrice": "0.00005484", + "volume": "1830586.00000000", + "quoteVolume": "103.76672252", + "openTime": 1560210238569, + "closeTime": 1560296638569, + "firstId": 548530, + "lastId": 549408, + "count": 879 + }, + { + "symbol": "POLYBTC", + "priceChange": "0.00000067", + "priceChangePercent": "5.386", + "weightedAvgPrice": "0.00001295", + "prevClosePrice": "0.00001244", + "lastPrice": "0.00001311", + "lastQty": "97.00000000", + "bidPrice": "0.00001312", + "bidQty": "1853.00000000", + "askPrice": "0.00001315", + "askQty": "77.00000000", + "openPrice": "0.00001244", + "highPrice": "0.00001350", + "lowPrice": "0.00001238", + "volume": "36126308.00000000", + "quoteVolume": "467.75519947", + "openTime": 1560210242102, + "closeTime": 1560296642102, + "firstId": 2904502, + "lastId": 2923096, + "count": 18595 + }, + { + "symbol": "POLYBNB", + "priceChange": "0.00014000", + "priceChangePercent": "4.516", + "weightedAvgPrice": "0.00323491", + "prevClosePrice": "0.00309000", + "lastPrice": "0.00324000", + "lastQty": "765.10000000", + "bidPrice": "0.00323000", + "bidQty": "26371.40000000", + "askPrice": "0.00325000", + "askQty": "56.00000000", + "openPrice": "0.00310000", + "highPrice": "0.00339000", + "lowPrice": "0.00308000", + "volume": "1757360.40000000", + "quoteVolume": "5684.90322400", + "openTime": 1560210241309, + "closeTime": 1560296641309, + "firstId": 149995, + "lastId": 151410, + "count": 1416 + }, + { + "symbol": "PHXBTC", + "priceChange": "-0.00000035", + "priceChangePercent": "-16.279", + "weightedAvgPrice": "0.00000186", + "prevClosePrice": "0.00000216", + "lastPrice": "0.00000180", + "lastQty": "37023.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000215", + "highPrice": "0.00000218", + "lowPrice": "0.00000171", + "volume": "619076073.00000000", + "quoteVolume": "1150.99531032", + "openTime": 1558413271188, + "closeTime": 1558499671188, + "firstId": 3778964, + "lastId": 3813995, + "count": 35032 + }, + { + "symbol": "PHXETH", + "priceChange": "-0.00001182", + "priceChangePercent": "-17.385", + "weightedAvgPrice": "0.00005821", + "prevClosePrice": "0.00006742", + "lastPrice": "0.00005617", + "lastQty": "948.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00006799", + "highPrice": "0.00006866", + "lowPrice": "0.00005273", + "volume": "22929178.00000000", + "quoteVolume": "1334.81645843", + "openTime": 1558413243081, + "closeTime": 1558499643081, + "firstId": 517841, + "lastId": 521807, + "count": 3967 + }, + { + "symbol": "PHXBNB", + "priceChange": "-0.00014400", + "priceChangePercent": "-24.000", + "weightedAvgPrice": "0.00049428", + "prevClosePrice": "0.00060500", + "lastPrice": "0.00045600", + "lastQty": "8954.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00060000", + "highPrice": "0.00061300", + "lowPrice": "0.00042900", + "volume": "38905794.00000000", + "quoteVolume": "19230.24078400", + "openTime": 1558413259444, + "closeTime": 1558499659444, + "firstId": 131885, + "lastId": 136239, + "count": 4355 + }, + { + "symbol": "HCBTC", + "priceChange": "-0.00002010", + "priceChangePercent": "-6.867", + "weightedAvgPrice": "0.00028056", + "prevClosePrice": "0.00029270", + "lastPrice": "0.00027260", + "lastQty": "4.37000000", + "bidPrice": "0.00027260", + "bidQty": "52.51000000", + "askPrice": "0.00027400", + "askQty": "85.55000000", + "openPrice": "0.00029270", + "highPrice": "0.00029830", + "lowPrice": "0.00027120", + "volume": "494426.45000000", + "quoteVolume": "138.71792410", + "openTime": 1560210242189, + "closeTime": 1560296642189, + "firstId": 2742275, + "lastId": 2753233, + "count": 10959 + }, + { + "symbol": "HCETH", + "priceChange": "-0.00072000", + "priceChangePercent": "-7.561", + "weightedAvgPrice": "0.00906047", + "prevClosePrice": "0.00948700", + "lastPrice": "0.00880200", + "lastQty": "3.69000000", + "bidPrice": "0.00878000", + "bidQty": "25.99000000", + "askPrice": "0.00886500", + "askQty": "25.16000000", + "openPrice": "0.00952200", + "highPrice": "0.00965900", + "lowPrice": "0.00872500", + "volume": "52059.79000000", + "quoteVolume": "471.68613047", + "openTime": 1560210242021, + "closeTime": 1560296642021, + "firstId": 715921, + "lastId": 718936, + "count": 3016 + }, + { + "symbol": "GOBTC", + "priceChange": "0.00000010", + "priceChangePercent": "3.300", + "weightedAvgPrice": "0.00000321", + "prevClosePrice": "0.00000304", + "lastPrice": "0.00000313", + "lastQty": "13129.00000000", + "bidPrice": "0.00000312", + "bidQty": "400.00000000", + "askPrice": "0.00000313", + "askQty": "30773.00000000", + "openPrice": "0.00000303", + "highPrice": "0.00000338", + "lowPrice": "0.00000295", + "volume": "74750116.00000000", + "quoteVolume": "240.25268721", + "openTime": 1560210242345, + "closeTime": 1560296642345, + "firstId": 3038878, + "lastId": 3048713, + "count": 9836 + }, + { + "symbol": "GOBNB", + "priceChange": "0.00001700", + "priceChangePercent": "2.246", + "weightedAvgPrice": "0.00079147", + "prevClosePrice": "0.00076000", + "lastPrice": "0.00077400", + "lastQty": "96266.00000000", + "bidPrice": "0.00077000", + "bidQty": "2020.00000000", + "askPrice": "0.00077400", + "askQty": "10968.00000000", + "openPrice": "0.00075700", + "highPrice": "0.00084400", + "lowPrice": "0.00074300", + "volume": "3405523.00000000", + "quoteVolume": "2695.37095500", + "openTime": 1560210242163, + "closeTime": 1560296642163, + "firstId": 172767, + "lastId": 173771, + "count": 1005 + }, + { + "symbol": "PAXBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00025175", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998679294, + "closeTime": 1558085079294, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "PAXBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.20121000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998679649, + "closeTime": 1558085079649, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "PAXUSDT", + "priceChange": "-0.00040000", + "priceChangePercent": "-0.040", + "weightedAvgPrice": "0.99527927", + "prevClosePrice": "0.99540000", + "lastPrice": "0.99500000", + "lastQty": "38.28000000", + "bidPrice": "0.99510000", + "bidQty": "270.49000000", + "askPrice": "0.99530000", + "askQty": "11.00000000", + "openPrice": "0.99540000", + "highPrice": "0.99750000", + "lowPrice": "0.99360000", + "volume": "6363268.50000000", + "quoteVolume": "6333229.24259200", + "openTime": 1560210241771, + "closeTime": 1560296641771, + "firstId": 5978601, + "lastId": 5998484, + "count": 19884 + }, + { + "symbol": "PAXETH", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00888047", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998681372, + "closeTime": 1558085081372, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "RVNBTC", + "priceChange": "-0.00000014", + "priceChangePercent": "-1.671", + "weightedAvgPrice": "0.00000824", + "prevClosePrice": "0.00000837", + "lastPrice": "0.00000824", + "lastQty": "47414.00000000", + "bidPrice": "0.00000824", + "bidQty": "5579.00000000", + "askPrice": "0.00000825", + "askQty": "162725.00000000", + "openPrice": "0.00000838", + "highPrice": "0.00000843", + "lowPrice": "0.00000813", + "volume": "90985579.00000000", + "quoteVolume": "749.67694862", + "openTime": 1560210238818, + "closeTime": 1560296638818, + "firstId": 8034300, + "lastId": 8048178, + "count": 13879 + }, + { + "symbol": "RVNBNB", + "priceChange": "-0.00005000", + "priceChangePercent": "-2.392", + "weightedAvgPrice": "0.00206612", + "prevClosePrice": "0.00209100", + "lastPrice": "0.00204000", + "lastQty": "1000.00000000", + "bidPrice": "0.00203400", + "bidQty": "704.00000000", + "askPrice": "0.00204100", + "askQty": "1698.00000000", + "openPrice": "0.00209000", + "highPrice": "0.00218700", + "lowPrice": "0.00202100", + "volume": "4002048.00000000", + "quoteVolume": "8268.71250500", + "openTime": 1560210234416, + "closeTime": 1560296634416, + "firstId": 621833, + "lastId": 623516, + "count": 1684 + }, + { + "symbol": "DCRBTC", + "priceChange": "0.00010900", + "priceChangePercent": "3.164", + "weightedAvgPrice": "0.00350972", + "prevClosePrice": "0.00344400", + "lastPrice": "0.00355400", + "lastQty": "0.30900000", + "bidPrice": "0.00354700", + "bidQty": "2.05700000", + "askPrice": "0.00355400", + "askQty": "0.22400000", + "openPrice": "0.00344500", + "highPrice": "0.00361000", + "lowPrice": "0.00343500", + "volume": "17960.26400000", + "quoteVolume": "63.03555774", + "openTime": 1560210242069, + "closeTime": 1560296642069, + "firstId": 1006307, + "lastId": 1009868, + "count": 3562 + }, + { + "symbol": "DCRBNB", + "priceChange": "0.01900000", + "priceChangePercent": "2.204", + "weightedAvgPrice": "0.87828717", + "prevClosePrice": "0.86100000", + "lastPrice": "0.88100000", + "lastQty": "3.73100000", + "bidPrice": "0.87500000", + "bidQty": "3.19800000", + "askPrice": "0.87900000", + "askQty": "0.11500000", + "openPrice": "0.86200000", + "highPrice": "0.89700000", + "lowPrice": "0.85900000", + "volume": "801.29500000", + "quoteVolume": "703.76711500", + "openTime": 1560210226120, + "closeTime": 1560296626120, + "firstId": 71442, + "lastId": 71729, + "count": 288 + }, + { + "symbol": "USDCBNB", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.21755000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998682639, + "closeTime": 1558085082639, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "USDCBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.00031132", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998682840, + "closeTime": 1558085082840, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "MITHBTC", + "priceChange": "-0.00000029", + "priceChangePercent": "-4.096", + "weightedAvgPrice": "0.00000708", + "prevClosePrice": "0.00000709", + "lastPrice": "0.00000679", + "lastQty": "7242.00000000", + "bidPrice": "0.00000678", + "bidQty": "4236.00000000", + "askPrice": "0.00000679", + "askQty": "35760.00000000", + "openPrice": "0.00000708", + "highPrice": "0.00000782", + "lowPrice": "0.00000655", + "volume": "188279528.00000000", + "quoteVolume": "1333.48857275", + "openTime": 1560210242345, + "closeTime": 1560296642345, + "firstId": 3233683, + "lastId": 3267757, + "count": 34075 + }, + { + "symbol": "MITHBNB", + "priceChange": "-0.00009000", + "priceChangePercent": "-5.085", + "weightedAvgPrice": "0.00174753", + "prevClosePrice": "0.00177000", + "lastPrice": "0.00168000", + "lastQty": "23629.50000000", + "bidPrice": "0.00167000", + "bidQty": "64453.10000000", + "askPrice": "0.00168000", + "askQty": "47616.20000000", + "openPrice": "0.00177000", + "highPrice": "0.00196000", + "lowPrice": "0.00163000", + "volume": "35890728.30000000", + "quoteVolume": "62720.03329100", + "openTime": 1560210240717, + "closeTime": 1560296640717, + "firstId": 138271, + "lastId": 147160, + "count": 8890 + }, + { + "symbol": "BCHABCBTC", + "priceChange": "-0.00011200", + "priceChangePercent": "-0.227", + "weightedAvgPrice": "0.04922779", + "prevClosePrice": "0.04925100", + "lastPrice": "0.04914000", + "lastQty": "0.05100000", + "bidPrice": "0.04911800", + "bidQty": "0.83700000", + "askPrice": "0.04914000", + "askQty": "1357.95800000", + "openPrice": "0.04925200", + "highPrice": "0.04993100", + "lowPrice": "0.04881400", + "volume": "25689.11900000", + "quoteVolume": "1264.61861334", + "openTime": 1560210241502, + "closeTime": 1560296641502, + "firstId": 10733078, + "lastId": 10755345, + "count": 22268 + }, + { + "symbol": "BCHSVBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "0.01117900", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998684358, + "closeTime": 1558085084358, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "BCHABCUSDT", + "priceChange": "-4.81000000", + "priceChangePercent": "-1.226", + "weightedAvgPrice": "386.77207208", + "prevClosePrice": "392.50000000", + "lastPrice": "387.61000000", + "lastQty": "0.05100000", + "bidPrice": "387.61000000", + "bidQty": "0.00056000", + "askPrice": "387.83000000", + "askQty": "14.97598000", + "openPrice": "392.42000000", + "highPrice": "396.93000000", + "lowPrice": "378.00000000", + "volume": "40442.41019000", + "quoteVolume": "15641994.78923310", + "openTime": 1560210242369, + "closeTime": 1560296642369, + "firstId": 11848693, + "lastId": 11880371, + "count": 31679 + }, + { + "symbol": "BCHSVUSDT", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "58.90000000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998684916, + "closeTime": 1558085084916, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "BNBPAX", + "priceChange": "0.02130000", + "priceChangePercent": "0.066", + "weightedAvgPrice": "31.64618956", + "prevClosePrice": "32.08090000", + "lastPrice": "32.07260000", + "lastQty": "10.00000000", + "bidPrice": "32.00750000", + "bidQty": "15.81000000", + "askPrice": "32.09040000", + "askQty": "20.57000000", + "openPrice": "32.05130000", + "highPrice": "32.30400000", + "lowPrice": "31.05380000", + "volume": "11571.17000000", + "quoteVolume": "366183.43929600", + "openTime": 1560210234701, + "closeTime": 1560296634701, + "firstId": 997855, + "lastId": 999030, + "count": 1176 + }, + { + "symbol": "BTCPAX", + "priceChange": "-76.74000000", + "priceChangePercent": "-0.959", + "weightedAvgPrice": "7896.12409026", + "prevClosePrice": "8002.71000000", + "lastPrice": "7925.89000000", + "lastQty": "0.04817000", + "bidPrice": "7922.86000000", + "bidQty": "2.80000000", + "askPrice": "7930.36000000", + "askQty": "0.07700100", + "openPrice": "8002.63000000", + "highPrice": "8060.00000000", + "lowPrice": "7710.00000000", + "volume": "1385.28008700", + "quoteVolume": "10938343.46671823", + "openTime": 1560210242293, + "closeTime": 1560296642293, + "firstId": 4560814, + "lastId": 4577400, + "count": 16587 + }, + { + "symbol": "ETHPAX", + "priceChange": "-1.05000000", + "priceChangePercent": "-0.425", + "weightedAvgPrice": "243.17044615", + "prevClosePrice": "247.25000000", + "lastPrice": "246.08000000", + "lastQty": "3.01453000", + "bidPrice": "245.91000000", + "bidQty": "0.66151000", + "askPrice": "246.08000000", + "askQty": "1.18719000", + "openPrice": "247.13000000", + "highPrice": "249.24000000", + "lowPrice": "237.58000000", + "volume": "12786.05854000", + "quoteVolume": "3109191.55971920", + "openTime": 1560210242239, + "closeTime": 1560296642239, + "firstId": 1859172, + "lastId": 1867390, + "count": 8219 + }, + { + "symbol": "XRPPAX", + "priceChange": "-0.00718000", + "priceChangePercent": "-1.791", + "weightedAvgPrice": "0.39186513", + "prevClosePrice": "0.40088000", + "lastPrice": "0.39370000", + "lastQty": "296.70000000", + "bidPrice": "0.39353000", + "bidQty": "339.60000000", + "askPrice": "0.39428000", + "askQty": "1489.80000000", + "openPrice": "0.40088000", + "highPrice": "0.40363000", + "lowPrice": "0.38384000", + "volume": "845775.90000000", + "quoteVolume": "331430.08321800", + "openTime": 1560210239414, + "closeTime": 1560296639414, + "firstId": 692511, + "lastId": 693485, + "count": 975 + }, + { + "symbol": "EOSPAX", + "priceChange": "-0.07000000", + "priceChangePercent": "-1.091", + "weightedAvgPrice": "6.33071046", + "prevClosePrice": "6.42400000", + "lastPrice": "6.34900000", + "lastQty": "138.84000000", + "bidPrice": "6.33910000", + "bidQty": "158.07000000", + "askPrice": "6.35670000", + "askQty": "105.84000000", + "openPrice": "6.41900000", + "highPrice": "6.51080000", + "lowPrice": "6.15830000", + "volume": "30581.44000000", + "quoteVolume": "193602.24204000", + "openTime": 1560210242356, + "closeTime": 1560296642356, + "firstId": 597209, + "lastId": 598236, + "count": 1028 + }, + { + "symbol": "XLMPAX", + "priceChange": "-0.00077000", + "priceChangePercent": "-0.622", + "weightedAvgPrice": "0.12150706", + "prevClosePrice": "0.12358000", + "lastPrice": "0.12299000", + "lastQty": "88.10000000", + "bidPrice": "0.12273000", + "bidQty": "253.90000000", + "askPrice": "0.12307000", + "askQty": "1424.20000000", + "openPrice": "0.12376000", + "highPrice": "0.12416000", + "lowPrice": "0.11890000", + "volume": "369027.50000000", + "quoteVolume": "44839.44587100", + "openTime": 1560210239691, + "closeTime": 1560296639691, + "firstId": 365169, + "lastId": 365695, + "count": 527 + }, + { + "symbol": "RENBTC", + "priceChange": "-0.00000036", + "priceChangePercent": "-5.835", + "weightedAvgPrice": "0.00000591", + "prevClosePrice": "0.00000617", + "lastPrice": "0.00000581", + "lastQty": "20315.00000000", + "bidPrice": "0.00000580", + "bidQty": "27117.00000000", + "askPrice": "0.00000581", + "askQty": "102582.00000000", + "openPrice": "0.00000617", + "highPrice": "0.00000621", + "lowPrice": "0.00000569", + "volume": "10556610.00000000", + "quoteVolume": "62.39022815", + "openTime": 1560210234129, + "closeTime": 1560296634129, + "firstId": 1280889, + "lastId": 1283657, + "count": 2769 + }, + { + "symbol": "RENBNB", + "priceChange": "-0.00011000", + "priceChangePercent": "-7.143", + "weightedAvgPrice": "0.00146679", + "prevClosePrice": "0.00154000", + "lastPrice": "0.00143000", + "lastQty": "13495.50000000", + "bidPrice": "0.00143000", + "bidQty": "2070.90000000", + "askPrice": "0.00144000", + "askQty": "9511.40000000", + "openPrice": "0.00154000", + "highPrice": "0.00154000", + "lowPrice": "0.00140000", + "volume": "866364.70000000", + "quoteVolume": "1270.77585900", + "openTime": 1560210230000, + "closeTime": 1560296630000, + "firstId": 64229, + "lastId": 64521, + "count": 293 + }, + { + "symbol": "BNBTUSD", + "priceChange": "-0.03340000", + "priceChangePercent": "-0.104", + "weightedAvgPrice": "31.59726559", + "prevClosePrice": "32.02480000", + "lastPrice": "32.00000000", + "lastQty": "0.54000000", + "bidPrice": "32.00030000", + "bidQty": "28.06000000", + "askPrice": "32.06070000", + "askQty": "0.41000000", + "openPrice": "32.03340000", + "highPrice": "32.22250000", + "lowPrice": "31.00000000", + "volume": "17026.80000000", + "quoteVolume": "538000.32170900", + "openTime": 1560210234591, + "closeTime": 1560296634591, + "firstId": 301068, + "lastId": 302604, + "count": 1537 + }, + { + "symbol": "XRPTUSD", + "priceChange": "-0.00606000", + "priceChangePercent": "-1.513", + "weightedAvgPrice": "0.39252803", + "prevClosePrice": "0.40014000", + "lastPrice": "0.39458000", + "lastQty": "591.00000000", + "bidPrice": "0.39342000", + "bidQty": "566.20000000", + "askPrice": "0.39401000", + "askQty": "702.60000000", + "openPrice": "0.40064000", + "highPrice": "0.40512000", + "lowPrice": "0.38180000", + "volume": "2000544.80000000", + "quoteVolume": "785269.90169100", + "openTime": 1560210240425, + "closeTime": 1560296640425, + "firstId": 414671, + "lastId": 416906, + "count": 2236 + }, + { + "symbol": "EOSTUSD", + "priceChange": "-0.08330000", + "priceChangePercent": "-1.295", + "weightedAvgPrice": "6.30595267", + "prevClosePrice": "6.44360000", + "lastPrice": "6.34680000", + "lastQty": "72.58000000", + "bidPrice": "6.33240000", + "bidQty": "139.58000000", + "askPrice": "6.35060000", + "askQty": "4094.19000000", + "openPrice": "6.43010000", + "highPrice": "6.50880000", + "lowPrice": "6.15020000", + "volume": "139920.75000000", + "quoteVolume": "882333.62722000", + "openTime": 1560210242294, + "closeTime": 1560296642294, + "firstId": 277674, + "lastId": 279092, + "count": 1419 + }, + { + "symbol": "XLMTUSD", + "priceChange": "-0.00043000", + "priceChangePercent": "-0.348", + "weightedAvgPrice": "0.12143112", + "prevClosePrice": "0.12342000", + "lastPrice": "0.12322000", + "lastQty": "1222.70000000", + "bidPrice": "0.12271000", + "bidQty": "29999.90000000", + "askPrice": "0.12307000", + "askQty": "1508.80000000", + "openPrice": "0.12365000", + "highPrice": "0.12400000", + "lowPrice": "0.11856000", + "volume": "193731.00000000", + "quoteVolume": "23524.97321800", + "openTime": 1560210239684, + "closeTime": 1560296639684, + "firstId": 199211, + "lastId": 199434, + "count": 224 + }, + { + "symbol": "BNBUSDC", + "priceChange": "0.06240000", + "priceChangePercent": "0.195", + "weightedAvgPrice": "31.62092521", + "prevClosePrice": "32.07300000", + "lastPrice": "32.07550000", + "lastQty": "1.62000000", + "bidPrice": "32.01220000", + "bidQty": "9.41000000", + "askPrice": "32.09940000", + "askQty": "0.32000000", + "openPrice": "32.01310000", + "highPrice": "32.23680000", + "lowPrice": "31.01250000", + "volume": "26510.69000000", + "quoteVolume": "838292.54574400", + "openTime": 1560210241606, + "closeTime": 1560296641606, + "firstId": 415300, + "lastId": 417344, + "count": 2045 + }, + { + "symbol": "BTCUSDC", + "priceChange": "-74.09000000", + "priceChangePercent": "-0.927", + "weightedAvgPrice": "7875.81593253", + "prevClosePrice": "7999.58000000", + "lastPrice": "7918.82000000", + "lastQty": "0.22028900", + "bidPrice": "7919.60000000", + "bidQty": "0.50000000", + "askPrice": "7924.98000000", + "askQty": "0.00888800", + "openPrice": "7992.91000000", + "highPrice": "8066.07000000", + "lowPrice": "7708.44000000", + "volume": "1775.91165800", + "quoteVolume": "13986753.33083854", + "openTime": 1560210241536, + "closeTime": 1560296641536, + "firstId": 2419636, + "lastId": 2436647, + "count": 17012 + }, + { + "symbol": "ETHUSDC", + "priceChange": "-0.86000000", + "priceChangePercent": "-0.348", + "weightedAvgPrice": "242.15394370", + "prevClosePrice": "247.11000000", + "lastPrice": "246.10000000", + "lastQty": "5.73000000", + "bidPrice": "245.54000000", + "bidQty": "5.00000000", + "askPrice": "246.12000000", + "askQty": "0.28607000", + "openPrice": "246.96000000", + "highPrice": "249.06000000", + "lowPrice": "237.39000000", + "volume": "10133.86309000", + "quoteVolume": "2453954.91218720", + "openTime": 1560210238579, + "closeTime": 1560296638579, + "firstId": 679420, + "lastId": 682802, + "count": 3383 + }, + { + "symbol": "XRPUSDC", + "priceChange": "-0.00662000", + "priceChangePercent": "-1.655", + "weightedAvgPrice": "0.39141579", + "prevClosePrice": "0.39919000", + "lastPrice": "0.39331000", + "lastQty": "1729.70000000", + "bidPrice": "0.39321000", + "bidQty": "717.20000000", + "askPrice": "0.39402000", + "askQty": "178.60000000", + "openPrice": "0.39993000", + "highPrice": "0.40333000", + "lowPrice": "0.38325000", + "volume": "1005213.30000000", + "quoteVolume": "393456.35301600", + "openTime": 1560210242202, + "closeTime": 1560296642202, + "firstId": 383776, + "lastId": 385272, + "count": 1497 + }, + { + "symbol": "EOSUSDC", + "priceChange": "-0.09170000", + "priceChangePercent": "-1.422", + "weightedAvgPrice": "6.33576723", + "prevClosePrice": "6.42500000", + "lastPrice": "6.35830000", + "lastQty": "53.67000000", + "bidPrice": "6.33560000", + "bidQty": "3.17000000", + "askPrice": "6.35400000", + "askQty": "403.04000000", + "openPrice": "6.45000000", + "highPrice": "6.50170000", + "lowPrice": "6.14190000", + "volume": "21982.02000000", + "quoteVolume": "139272.96204300", + "openTime": 1560210242348, + "closeTime": 1560296642348, + "firstId": 270365, + "lastId": 270816, + "count": 452 + }, + { + "symbol": "XLMUSDC", + "priceChange": "0.00011000", + "priceChangePercent": "0.089", + "weightedAvgPrice": "0.12132535", + "prevClosePrice": "0.12349000", + "lastPrice": "0.12332000", + "lastQty": "16858.90000000", + "bidPrice": "0.12273000", + "bidQty": "126.10000000", + "askPrice": "0.12307000", + "askQty": "1422.60000000", + "openPrice": "0.12321000", + "highPrice": "0.12400000", + "lowPrice": "0.11790000", + "volume": "835399.70000000", + "quoteVolume": "101355.15955500", + "openTime": 1560210242277, + "closeTime": 1560296642277, + "firstId": 200639, + "lastId": 201105, + "count": 467 + }, + { + "symbol": "USDCUSDT", + "priceChange": "-0.00080000", + "priceChangePercent": "-0.080", + "weightedAvgPrice": "0.99612957", + "prevClosePrice": "0.99610000", + "lastPrice": "0.99530000", + "lastQty": "15.00000000", + "bidPrice": "0.99530000", + "bidQty": "1757.68000000", + "askPrice": "0.99570000", + "askQty": "3443.21000000", + "openPrice": "0.99610000", + "highPrice": "0.99800000", + "lowPrice": "0.99420000", + "volume": "8336031.71000000", + "quoteVolume": "8303767.65774100", + "openTime": 1560210242329, + "closeTime": 1560296642329, + "firstId": 2327985, + "lastId": 2352657, + "count": 24673 + }, + { + "symbol": "ADATUSD", + "priceChange": "0.00304000", + "priceChangePercent": "3.567", + "weightedAvgPrice": "0.08626125", + "prevClosePrice": "0.08522000", + "lastPrice": "0.08827000", + "lastQty": "15097.70000000", + "bidPrice": "0.08801000", + "bidQty": "27546.10000000", + "askPrice": "0.08818000", + "askQty": "58150.90000000", + "openPrice": "0.08523000", + "highPrice": "0.08955000", + "lowPrice": "0.08221000", + "volume": "12730359.90000000", + "quoteVolume": "1098136.78718500", + "openTime": 1560210237674, + "closeTime": 1560296637674, + "firstId": 270374, + "lastId": 272888, + "count": 2515 + }, + { + "symbol": "TRXTUSD", + "priceChange": "-0.00030000", + "priceChangePercent": "-0.949", + "weightedAvgPrice": "0.03070799", + "prevClosePrice": "0.03170000", + "lastPrice": "0.03132000", + "lastQty": "4200.00000000", + "bidPrice": "0.03123000", + "bidQty": "71650.30000000", + "askPrice": "0.03132000", + "askQty": "12185.10000000", + "openPrice": "0.03162000", + "highPrice": "0.03168000", + "lowPrice": "0.02993000", + "volume": "10866687.40000000", + "quoteVolume": "333694.11605700", + "openTime": 1560210235916, + "closeTime": 1560296635916, + "firstId": 249136, + "lastId": 249998, + "count": 863 + }, + { + "symbol": "NEOTUSD", + "priceChange": "0.05500000", + "priceChangePercent": "0.447", + "weightedAvgPrice": "12.16189680", + "prevClosePrice": "12.29100000", + "lastPrice": "12.37000000", + "lastQty": "629.20300000", + "bidPrice": "12.31900000", + "bidQty": "173.06800000", + "askPrice": "12.34900000", + "askQty": "102.56200000", + "openPrice": "12.31500000", + "highPrice": "12.46500000", + "lowPrice": "11.77200000", + "volume": "83144.00300000", + "quoteVolume": "1011188.78395400", + "openTime": 1560210241236, + "closeTime": 1560296641236, + "firstId": 188148, + "lastId": 190835, + "count": 2688 + }, + { + "symbol": "TRXXRP", + "priceChange": "0.00056000", + "priceChangePercent": "0.710", + "weightedAvgPrice": "0.07854345", + "prevClosePrice": "0.07894000", + "lastPrice": "0.07943000", + "lastQty": "1579.60000000", + "bidPrice": "0.07941000", + "bidQty": "12036.30000000", + "askPrice": "0.07950000", + "askQty": "9416.00000000", + "openPrice": "0.07887000", + "highPrice": "0.08038000", + "lowPrice": "0.07754000", + "volume": "14979632.00000000", + "quoteVolume": "1176551.95522200", + "openTime": 1560210241124, + "closeTime": 1560296641124, + "firstId": 739503, + "lastId": 745555, + "count": 6053 + }, + { + "symbol": "XZCXRP", + "priceChange": "1.11100000", + "priceChangePercent": "4.531", + "weightedAvgPrice": "24.71175895", + "prevClosePrice": "24.51800000", + "lastPrice": "25.62900000", + "lastQty": "6.40100000", + "bidPrice": "25.36000000", + "bidQty": "5.35000000", + "askPrice": "25.62900000", + "askQty": "28.96100000", + "openPrice": "24.51800000", + "highPrice": "25.92500000", + "lowPrice": "23.00000000", + "volume": "4779.70900000", + "quoteVolume": "118115.01665800", + "openTime": 1560210241316, + "closeTime": 1560296641316, + "firstId": 159665, + "lastId": 160877, + "count": 1213 + }, + { + "symbol": "PAXTUSD", + "priceChange": "0.00030000", + "priceChangePercent": "0.030", + "weightedAvgPrice": "0.99960506", + "prevClosePrice": "0.99980000", + "lastPrice": "0.99980000", + "lastQty": "2998.50000000", + "bidPrice": "0.99900000", + "bidQty": "20961.16000000", + "askPrice": "1.00030000", + "askQty": "143.38000000", + "openPrice": "0.99950000", + "highPrice": "1.00070000", + "lowPrice": "0.99840000", + "volume": "334029.08000000", + "quoteVolume": "333897.15689600", + "openTime": 1560210241524, + "closeTime": 1560296641524, + "firstId": 215393, + "lastId": 216310, + "count": 918 + }, + { + "symbol": "USDCTUSD", + "priceChange": "-0.00010000", + "priceChangePercent": "-0.010", + "weightedAvgPrice": "1.00049699", + "prevClosePrice": "1.00140000", + "lastPrice": "0.99990000", + "lastQty": "20.25000000", + "bidPrice": "0.99990000", + "bidQty": "591.01000000", + "askPrice": "1.00130000", + "askQty": "24443.68000000", + "openPrice": "1.00000000", + "highPrice": "1.00150000", + "lowPrice": "0.99950000", + "volume": "461237.75000000", + "quoteVolume": "461466.98013500", + "openTime": 1560210240740, + "closeTime": 1560296640740, + "firstId": 120717, + "lastId": 121632, + "count": 916 + }, + { + "symbol": "USDCPAX", + "priceChange": "-0.00030000", + "priceChangePercent": "-0.030", + "weightedAvgPrice": "1.00079916", + "prevClosePrice": "1.00090000", + "lastPrice": "1.00060000", + "lastQty": "11.50000000", + "bidPrice": "0.99970000", + "bidQty": "43800.38000000", + "askPrice": "1.00090000", + "askQty": "11855.29000000", + "openPrice": "1.00090000", + "highPrice": "1.00200000", + "lowPrice": "0.99970000", + "volume": "959489.23000000", + "quoteVolume": "960256.01870300", + "openTime": 1560210241526, + "closeTime": 1560296641526, + "firstId": 328884, + "lastId": 333817, + "count": 4934 + }, + { + "symbol": "LINKUSDT", + "priceChange": "-0.05860000", + "priceChangePercent": "-4.947", + "weightedAvgPrice": "1.14586865", + "prevClosePrice": "1.18500000", + "lastPrice": "1.12590000", + "lastQty": "17.00000000", + "bidPrice": "1.12590000", + "bidQty": "0.70000000", + "askPrice": "1.12950000", + "askQty": "250.87000000", + "openPrice": "1.18450000", + "highPrice": "1.21040000", + "lowPrice": "1.08760000", + "volume": "3168642.53000000", + "quoteVolume": "3630848.12585300", + "openTime": 1560210237950, + "closeTime": 1560296637950, + "firstId": 1131493, + "lastId": 1144325, + "count": 12833 + }, + { + "symbol": "LINKTUSD", + "priceChange": "-0.06360000", + "priceChangePercent": "-5.331", + "weightedAvgPrice": "1.14413921", + "prevClosePrice": "1.18230000", + "lastPrice": "1.12940000", + "lastQty": "159.00000000", + "bidPrice": "1.12910000", + "bidQty": "849.26000000", + "askPrice": "1.13640000", + "askQty": "9.94000000", + "openPrice": "1.19300000", + "highPrice": "1.21990000", + "lowPrice": "1.09070000", + "volume": "100601.10000000", + "quoteVolume": "115101.66275400", + "openTime": 1560210241356, + "closeTime": 1560296641356, + "firstId": 129115, + "lastId": 129659, + "count": 545 + }, + { + "symbol": "LINKPAX", + "priceChange": "-0.06000000", + "priceChangePercent": "-5.022", + "weightedAvgPrice": "1.14641705", + "prevClosePrice": "1.18420000", + "lastPrice": "1.13480000", + "lastQty": "395.89000000", + "bidPrice": "1.12930000", + "bidQty": "118.21000000", + "askPrice": "1.13490000", + "askQty": "17.45000000", + "openPrice": "1.19480000", + "highPrice": "1.21570000", + "lowPrice": "1.09540000", + "volume": "20003.16000000", + "quoteVolume": "22931.96372600", + "openTime": 1560210240668, + "closeTime": 1560296640668, + "firstId": 34750, + "lastId": 34917, + "count": 168 + }, + { + "symbol": "LINKUSDC", + "priceChange": "-0.05990000", + "priceChangePercent": "-5.017", + "weightedAvgPrice": "1.15086170", + "prevClosePrice": "1.18100000", + "lastPrice": "1.13400000", + "lastQty": "8.91000000", + "bidPrice": "1.12910000", + "bidQty": "901.31000000", + "askPrice": "1.13400000", + "askQty": "88.47000000", + "openPrice": "1.19390000", + "highPrice": "1.21640000", + "lowPrice": "1.08720000", + "volume": "103374.92000000", + "quoteVolume": "118970.23583600", + "openTime": 1560210240924, + "closeTime": 1560296640924, + "firstId": 130174, + "lastId": 130735, + "count": 562 + }, + { + "symbol": "WAVESUSDT", + "priceChange": "0.00660000", + "priceChangePercent": "0.279", + "weightedAvgPrice": "2.35294110", + "prevClosePrice": "2.36200000", + "lastPrice": "2.37270000", + "lastQty": "123.70000000", + "bidPrice": "2.36510000", + "bidQty": "105.20000000", + "askPrice": "2.37270000", + "askQty": "6.53000000", + "openPrice": "2.36610000", + "highPrice": "2.40690000", + "lowPrice": "2.30250000", + "volume": "533967.93000000", + "quoteVolume": "1256395.08702000", + "openTime": 1560210241970, + "closeTime": 1560296641970, + "firstId": 984761, + "lastId": 990341, + "count": 5581 + }, + { + "symbol": "WAVESTUSD", + "priceChange": "0.00260000", + "priceChangePercent": "0.109", + "weightedAvgPrice": "2.36476897", + "prevClosePrice": "2.38130000", + "lastPrice": "2.38120000", + "lastQty": "251.96000000", + "bidPrice": "2.37620000", + "bidQty": "652.70000000", + "askPrice": "2.38330000", + "askQty": "1804.95000000", + "openPrice": "2.37860000", + "highPrice": "2.42380000", + "lowPrice": "2.31380000", + "volume": "124155.79000000", + "quoteVolume": "293599.75952200", + "openTime": 1560210241206, + "closeTime": 1560296641206, + "firstId": 109723, + "lastId": 110573, + "count": 851 + }, + { + "symbol": "WAVESPAX", + "priceChange": "0.00640000", + "priceChangePercent": "0.270", + "weightedAvgPrice": "2.37330236", + "prevClosePrice": "2.38060000", + "lastPrice": "2.38000000", + "lastQty": "42.01000000", + "bidPrice": "2.37460000", + "bidQty": "137.16000000", + "askPrice": "2.38670000", + "askQty": "1033.70000000", + "openPrice": "2.37360000", + "highPrice": "2.41830000", + "lowPrice": "2.30520000", + "volume": "6052.40000000", + "quoteVolume": "14364.17521900", + "openTime": 1560210240809, + "closeTime": 1560296640809, + "firstId": 17844, + "lastId": 17943, + "count": 100 + }, + { + "symbol": "WAVESUSDC", + "priceChange": "-0.01010000", + "priceChangePercent": "-0.423", + "weightedAvgPrice": "2.38219208", + "prevClosePrice": "2.37000000", + "lastPrice": "2.38000000", + "lastQty": "1666.66000000", + "bidPrice": "2.37440000", + "bidQty": "258.43000000", + "askPrice": "2.38570000", + "askQty": "1033.70000000", + "openPrice": "2.39010000", + "highPrice": "2.40780000", + "lowPrice": "2.31100000", + "volume": "6670.16000000", + "quoteVolume": "15889.60232000", + "openTime": 1560210242231, + "closeTime": 1560296642231, + "firstId": 20941, + "lastId": 21031, + "count": 91 + }, + { + "symbol": "BCHABCTUSD", + "priceChange": "-4.40000000", + "priceChangePercent": "-1.117", + "weightedAvgPrice": "388.13962092", + "prevClosePrice": "393.91000000", + "lastPrice": "389.52000000", + "lastQty": "2.82897000", + "bidPrice": "389.01000000", + "bidQty": "10.58701000", + "askPrice": "389.67000000", + "askQty": "2.04296000", + "openPrice": "393.92000000", + "highPrice": "399.01000000", + "lowPrice": "379.53000000", + "volume": "4337.69511000", + "quoteVolume": "1683631.33566330", + "openTime": 1560210236546, + "closeTime": 1560296636546, + "firstId": 287780, + "lastId": 289581, + "count": 1802 + }, + { + "symbol": "BCHABCPAX", + "priceChange": "-3.88000000", + "priceChangePercent": "-0.986", + "weightedAvgPrice": "389.15991058", + "prevClosePrice": "393.87000000", + "lastPrice": "389.78000000", + "lastQty": "1.44409000", + "bidPrice": "389.09000000", + "bidQty": "2.33917000", + "askPrice": "389.72000000", + "askQty": "1.56900000", + "openPrice": "393.66000000", + "highPrice": "399.00000000", + "lowPrice": "378.95000000", + "volume": "1421.22301000", + "quoteVolume": "553083.01948720", + "openTime": 1560210241651, + "closeTime": 1560296641651, + "firstId": 119095, + "lastId": 119916, + "count": 822 + }, + { + "symbol": "BCHABCUSDC", + "priceChange": "-4.24000000", + "priceChangePercent": "-1.076", + "weightedAvgPrice": "388.45737226", + "prevClosePrice": "393.66000000", + "lastPrice": "389.76000000", + "lastQty": "2.14426000", + "bidPrice": "388.72000000", + "bidQty": "1.67215000", + "askPrice": "389.74000000", + "askQty": "2.12599000", + "openPrice": "394.00000000", + "highPrice": "399.13000000", + "lowPrice": "379.72000000", + "volume": "575.40997000", + "quoteVolume": "223522.24491730", + "openTime": 1560210242265, + "closeTime": 1560296642265, + "firstId": 115234, + "lastId": 115680, + "count": 447 + }, + { + "symbol": "BCHSVTUSD", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "59.17000000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998721199, + "closeTime": 1558085121199, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "BCHSVPAX", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "58.18000000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998723279, + "closeTime": 1558085123279, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "BCHSVUSDC", + "priceChange": "0.00000000", + "priceChangePercent": "0", + "weightedAvgPrice": "0", + "prevClosePrice": "57.50000000", + "lastPrice": "0.00000000", + "lastQty": "0.00000000", + "bidPrice": "0.00000000", + "bidQty": "0.00000000", + "askPrice": "0.00000000", + "askQty": "0.00000000", + "openPrice": "0.00000000", + "highPrice": "0.00000000", + "lowPrice": "0.00000000", + "volume": "0.00000000", + "quoteVolume": "0.00000000", + "openTime": 1557998723487, + "closeTime": 1558085123487, + "firstId": -1, + "lastId": -1, + "count": 0 + }, + { + "symbol": "LTCTUSD", + "priceChange": "7.14000000", + "priceChangePercent": "5.521", + "weightedAvgPrice": "132.06061929", + "prevClosePrice": "129.32000000", + "lastPrice": "136.46000000", + "lastQty": "10.00000000", + "bidPrice": "136.30000000", + "bidQty": "48.48424000", + "askPrice": "136.55000000", + "askQty": "70.08578000", + "openPrice": "129.32000000", + "highPrice": "139.12000000", + "lowPrice": "125.40000000", + "volume": "41771.66053000", + "quoteVolume": "5516391.35846850", + "openTime": 1560210241259, + "closeTime": 1560296641259, + "firstId": 327810, + "lastId": 334248, + "count": 6439 + }, + { + "symbol": "LTCPAX", + "priceChange": "6.78000000", + "priceChangePercent": "5.230", + "weightedAvgPrice": "130.68947001", + "prevClosePrice": "129.23000000", + "lastPrice": "136.42000000", + "lastQty": "0.20000000", + "bidPrice": "136.35000000", + "bidQty": "50.00000000", + "askPrice": "136.61000000", + "askQty": "5.85100000", + "openPrice": "129.64000000", + "highPrice": "139.77000000", + "lowPrice": "125.38000000", + "volume": "4714.02428000", + "quoteVolume": "616073.33476830", + "openTime": 1560210241671, + "closeTime": 1560296641671, + "firstId": 132387, + "lastId": 133774, + "count": 1388 + }, + { + "symbol": "LTCUSDC", + "priceChange": "7.12000000", + "priceChangePercent": "5.502", + "weightedAvgPrice": "131.25383359", + "prevClosePrice": "129.15000000", + "lastPrice": "136.52000000", + "lastQty": "3.00000000", + "bidPrice": "136.18000000", + "bidQty": "5.01334000", + "askPrice": "136.57000000", + "askQty": "23.40400000", + "openPrice": "129.40000000", + "highPrice": "139.03000000", + "lowPrice": "125.50000000", + "volume": "22326.34651000", + "quoteVolume": "2930418.56953440", + "openTime": 1560210241041, + "closeTime": 1560296641041, + "firstId": 200351, + "lastId": 204927, + "count": 4577 + }, + { + "symbol": "TRXPAX", + "priceChange": "-0.00006000", + "priceChangePercent": "-0.192", + "weightedAvgPrice": "0.03072500", + "prevClosePrice": "0.03173000", + "lastPrice": "0.03120000", + "lastQty": "28443.30000000", + "bidPrice": "0.03121000", + "bidQty": "9612.30000000", + "askPrice": "0.03134000", + "askQty": "22376.70000000", + "openPrice": "0.03126000", + "highPrice": "0.03168000", + "lowPrice": "0.03006000", + "volume": "1879385.50000000", + "quoteVolume": "57744.11493900", + "openTime": 1560210241036, + "closeTime": 1560296641036, + "firstId": 48268, + "lastId": 48479, + "count": 212 + }, + { + "symbol": "TRXUSDC", + "priceChange": "-0.00028000", + "priceChangePercent": "-0.886", + "weightedAvgPrice": "0.03073827", + "prevClosePrice": "0.03167000", + "lastPrice": "0.03134000", + "lastQty": "1310.70000000", + "bidPrice": "0.03121000", + "bidQty": "57294.80000000", + "askPrice": "0.03131000", + "askQty": "35121.20000000", + "openPrice": "0.03162000", + "highPrice": "0.03169000", + "lowPrice": "0.03008000", + "volume": "44300096.90000000", + "quoteVolume": "1361708.14173700", + "openTime": 1560210232485, + "closeTime": 1560296632485, + "firstId": 94566, + "lastId": 97967, + "count": 3402 + }, + { + "symbol": "BTTBTC", + "priceChange": "0.00000000", + "priceChangePercent": "0.000", + "weightedAvgPrice": "0.00000015", + "prevClosePrice": "0.00000016", + "lastPrice": "0.00000016", + "lastQty": "11119.00000000", + "bidPrice": "0.00000015", + "bidQty": "1628021436.00000000", + "askPrice": "0.00000016", + "askQty": "1704596588.00000000", + "openPrice": "0.00000016", + "highPrice": "0.00000016", + "lowPrice": "0.00000015", + "volume": "624130339.00000000", + "quoteVolume": "94.88905260", + "openTime": 1560210240883, + "closeTime": 1560296640883, + "firstId": 1403772, + "lastId": 1406041, + "count": 2270 + }, + { + "symbol": "BTTBNB", + "priceChange": "-0.00000129", + "priceChangePercent": "-3.343", + "weightedAvgPrice": "0.00003776", + "prevClosePrice": "0.00003855", + "lastPrice": "0.00003730", + "lastQty": "55109.00000000", + "bidPrice": "0.00003730", + "bidQty": "55109.00000000", + "askPrice": "0.00003739", + "askQty": "297747.00000000", + "openPrice": "0.00003859", + "highPrice": "0.00003862", + "lowPrice": "0.00003722", + "volume": "509038362.00000000", + "quoteVolume": "19219.99326265", + "openTime": 1560210235760, + "closeTime": 1560296635760, + "firstId": 1384101, + "lastId": 1389782, + "count": 5682 + }, + { + "symbol": "BTTUSDT", + "priceChange": "-0.00003990", + "priceChangePercent": "-3.239", + "weightedAvgPrice": "0.00119047", + "prevClosePrice": "0.00123120", + "lastPrice": "0.00119180", + "lastQty": "220345.00000000", + "bidPrice": "0.00119150", + "bidQty": "1349338.00000000", + "askPrice": "0.00119210", + "askQty": "303329.00000000", + "openPrice": "0.00123170", + "highPrice": "0.00123200", + "lowPrice": "0.00115730", + "volume": "7664642698.00000000", + "quoteVolume": "9124511.42703080", + "openTime": 1560210240779, + "closeTime": 1560296640779, + "firstId": 7188369, + "lastId": 7211916, + "count": 23548 + }, + { + "symbol": "BNBUSDS", + "priceChange": "-0.12830000", + "priceChangePercent": "-0.399", + "weightedAvgPrice": "31.65113704", + "prevClosePrice": "32.04500000", + "lastPrice": "32.03010000", + "lastQty": "2.25000000", + "bidPrice": "32.00700000", + "bidQty": "3.40000000", + "askPrice": "32.18140000", + "askQty": "3.37000000", + "openPrice": "32.15840000", + "highPrice": "32.22000000", + "lowPrice": "31.18000000", + "volume": "2249.24000000", + "quoteVolume": "71191.00346700", + "openTime": 1560210224488, + "closeTime": 1560296624488, + "firstId": 20697, + "lastId": 20858, + "count": 162 + }, + { + "symbol": "BTCUSDS", + "priceChange": "-121.34000000", + "priceChangePercent": "-1.507", + "weightedAvgPrice": "7905.44294368", + "prevClosePrice": "7983.47000000", + "lastPrice": "7928.66000000", + "lastQty": "0.03788000", + "bidPrice": "7926.13000000", + "bidQty": "0.03789200", + "askPrice": "7942.85000000", + "askQty": "0.02458600", + "openPrice": "8050.00000000", + "highPrice": "8065.28000000", + "lowPrice": "7732.61000000", + "volume": "45.77543000", + "quoteVolume": "361875.05008762", + "openTime": 1560210240688, + "closeTime": 1560296640688, + "firstId": 103769, + "lastId": 104155, + "count": 387 + }, + { + "symbol": "USDSUSDT", + "priceChange": "-0.00020000", + "priceChangePercent": "-0.020", + "weightedAvgPrice": "0.99391112", + "prevClosePrice": "0.99270000", + "lastPrice": "0.99280000", + "lastQty": "11.05000000", + "bidPrice": "0.99290000", + "bidQty": "158.36000000", + "askPrice": "0.99580000", + "askQty": "490.73000000", + "openPrice": "0.99300000", + "highPrice": "0.99610000", + "lowPrice": "0.99200000", + "volume": "45533.28000000", + "quoteVolume": "45256.03333000", + "openTime": 1560210241660, + "closeTime": 1560296641660, + "firstId": 61032, + "lastId": 61239, + "count": 208 + }, + { + "symbol": "USDSPAX", + "priceChange": "0.00140000", + "priceChangePercent": "0.140", + "weightedAvgPrice": "0.99976042", + "prevClosePrice": "1.00000000", + "lastPrice": "0.99990000", + "lastQty": "2257.77000000", + "bidPrice": "0.99770000", + "bidQty": "4035.95000000", + "askPrice": "0.99990000", + "askQty": "2742.23000000", + "openPrice": "0.99850000", + "highPrice": "1.00010000", + "lowPrice": "0.99770000", + "volume": "77545.69000000", + "quoteVolume": "77527.11150400", + "openTime": 1560210223446, + "closeTime": 1560296623446, + "firstId": 58386, + "lastId": 58492, + "count": 107 + }, + { + "symbol": "USDSTUSD", + "priceChange": "-0.00140000", + "priceChangePercent": "-0.140", + "weightedAvgPrice": "0.99858080", + "prevClosePrice": "0.99980000", + "lastPrice": "0.99840000", + "lastQty": "1275.35000000", + "bidPrice": "0.99840000", + "bidQty": "2247.36000000", + "askPrice": "0.99940000", + "askQty": "0.16000000", + "openPrice": "0.99980000", + "highPrice": "1.00000000", + "lowPrice": "0.99790000", + "volume": "26762.65000000", + "quoteVolume": "26724.66841100", + "openTime": 1560210161526, + "closeTime": 1560296561526, + "firstId": 39313, + "lastId": 39411, + "count": 99 + }, + { + "symbol": "USDSUSDC", + "priceChange": "-0.00090000", + "priceChangePercent": "-0.090", + "weightedAvgPrice": "0.99755899", + "prevClosePrice": "0.99980000", + "lastPrice": "0.99700000", + "lastQty": "1114.65000000", + "bidPrice": "0.99700000", + "bidQty": "3576.24000000", + "askPrice": "0.99980000", + "askQty": "3079.13000000", + "openPrice": "0.99790000", + "highPrice": "0.99990000", + "lowPrice": "0.99700000", + "volume": "24625.02000000", + "quoteVolume": "24564.91008300", + "openTime": 1560209818467, + "closeTime": 1560296218467, + "firstId": 32294, + "lastId": 32338, + "count": 45 + }, + { + "symbol": "BTTPAX", + "priceChange": "-0.00001780", + "priceChangePercent": "-1.463", + "weightedAvgPrice": "0.00120386", + "prevClosePrice": "0.00123770", + "lastPrice": "0.00119890", + "lastQty": "13000.00000000", + "bidPrice": "0.00119450", + "bidQty": "411860.00000000", + "askPrice": "0.00120050", + "askQty": "357455.00000000", + "openPrice": "0.00121670", + "highPrice": "0.00122180", + "lowPrice": "0.00116260", + "volume": "19377000.00000000", + "quoteVolume": "23327.22747270", + "openTime": 1560210242081, + "closeTime": 1560296642081, + "firstId": 33168, + "lastId": 33295, + "count": 128 + }, + { + "symbol": "BTTTUSD", + "priceChange": "-0.00004130", + "priceChangePercent": "-3.348", + "weightedAvgPrice": "0.00120209", + "prevClosePrice": "0.00123720", + "lastPrice": "0.00119210", + "lastQty": "280758.00000000", + "bidPrice": "0.00119430", + "bidQty": "740081.00000000", + "askPrice": "0.00120030", + "askQty": "361454.00000000", + "openPrice": "0.00123340", + "highPrice": "0.00123400", + "lowPrice": "0.00116330", + "volume": "89406879.00000000", + "quoteVolume": "107475.30162090", + "openTime": 1560210239891, + "closeTime": 1560296639891, + "firstId": 55851, + "lastId": 56231, + "count": 381 + }, + { + "symbol": "BTTUSDC", + "priceChange": "-0.00003170", + "priceChangePercent": "-2.573", + "weightedAvgPrice": "0.00120503", + "prevClosePrice": "0.00123800", + "lastPrice": "0.00120050", + "lastQty": "73694.00000000", + "bidPrice": "0.00119420", + "bidQty": "742367.00000000", + "askPrice": "0.00120050", + "askQty": "271334.00000000", + "openPrice": "0.00123220", + "highPrice": "0.00123220", + "lowPrice": "0.00116840", + "volume": "60423471.00000000", + "quoteVolume": "72811.88543280", + "openTime": 1560210237638, + "closeTime": 1560296637638, + "firstId": 58531, + "lastId": 58798, + "count": 268 + }, + { + "symbol": "ONGBNB", + "priceChange": "0.00042000", + "priceChangePercent": "3.075", + "weightedAvgPrice": "0.01392402", + "prevClosePrice": "0.01364000", + "lastPrice": "0.01408000", + "lastQty": "155.00000000", + "bidPrice": "0.01400000", + "bidQty": "2719.50000000", + "askPrice": "0.01413000", + "askQty": "1629.90000000", + "openPrice": "0.01366000", + "highPrice": "0.01425000", + "lowPrice": "0.01353000", + "volume": "38295.10000000", + "quoteVolume": "533.22190100", + "openTime": 1560210233855, + "closeTime": 1560296633855, + "firstId": 82482, + "lastId": 82793, + "count": 312 + }, + { + "symbol": "ONGBTC", + "priceChange": "0.00000244", + "priceChangePercent": "4.471", + "weightedAvgPrice": "0.00005592", + "prevClosePrice": "0.00005431", + "lastPrice": "0.00005701", + "lastQty": "68.00000000", + "bidPrice": "0.00005679", + "bidQty": "2439.00000000", + "askPrice": "0.00005699", + "askQty": "1826.00000000", + "openPrice": "0.00005457", + "highPrice": "0.00005711", + "lowPrice": "0.00005404", + "volume": "647859.00000000", + "quoteVolume": "36.22756813", + "openTime": 1560210242061, + "closeTime": 1560296642061, + "firstId": 1843386, + "lastId": 1846585, + "count": 3200 + }, + { + "symbol": "ONGUSDT", + "priceChange": "0.01490000", + "priceChangePercent": "3.435", + "weightedAvgPrice": "0.43968192", + "prevClosePrice": "0.43390000", + "lastPrice": "0.44870000", + "lastQty": "100.23000000", + "bidPrice": "0.44870000", + "bidQty": "0.10000000", + "askPrice": "0.45060000", + "askQty": "334.55000000", + "openPrice": "0.43380000", + "highPrice": "0.45230000", + "lowPrice": "0.43010000", + "volume": "690164.44000000", + "quoteVolume": "303452.82529600", + "openTime": 1560210239103, + "closeTime": 1560296639103, + "firstId": 1000039, + "lastId": 1002931, + "count": 2893 + }, + { + "symbol": "HOTBNB", + "priceChange": "-0.00000170", + "priceChangePercent": "-2.941", + "weightedAvgPrice": "0.00005682", + "prevClosePrice": "0.00005807", + "lastPrice": "0.00005610", + "lastQty": "5411.00000000", + "bidPrice": "0.00005577", + "bidQty": "1323120.00000000", + "askPrice": "0.00005610", + "askQty": "30.00000000", + "openPrice": "0.00005780", + "highPrice": "0.00005780", + "lowPrice": "0.00005577", + "volume": "92048158.00000000", + "quoteVolume": "5230.50464565", + "openTime": 1560210240095, + "closeTime": 1560296640095, + "firstId": 195490, + "lastId": 196570, + "count": 1081 + }, + { + "symbol": "HOTUSDT", + "priceChange": "-0.00006890", + "priceChangePercent": "-3.725", + "weightedAvgPrice": "0.00178590", + "prevClosePrice": "0.00185490", + "lastPrice": "0.00178080", + "lastQty": "192770.00000000", + "bidPrice": "0.00178080", + "bidQty": "7230.00000000", + "askPrice": "0.00178580", + "askQty": "35826.00000000", + "openPrice": "0.00184970", + "highPrice": "0.00185590", + "lowPrice": "0.00175330", + "volume": "889178818.00000000", + "quoteVolume": "1587982.63279420", + "openTime": 1560210241682, + "closeTime": 1560296641682, + "firstId": 770550, + "lastId": 775405, + "count": 4856 + }, + { + "symbol": "ZILUSDT", + "priceChange": "-0.00036000", + "priceChangePercent": "-1.529", + "weightedAvgPrice": "0.02364746", + "prevClosePrice": "0.02353000", + "lastPrice": "0.02318000", + "lastQty": "435.80000000", + "bidPrice": "0.02313000", + "bidQty": "12000.00000000", + "askPrice": "0.02318000", + "askQty": "7354.80000000", + "openPrice": "0.02354000", + "highPrice": "0.02464000", + "lowPrice": "0.02270000", + "volume": "161219747.40000000", + "quoteVolume": "3812437.55673800", + "openTime": 1560210240620, + "closeTime": 1560296640620, + "firstId": 895951, + "lastId": 907741, + "count": 11791 + }, + { + "symbol": "ZRXBNB", + "priceChange": "-0.00010000", + "priceChangePercent": "-0.977", + "weightedAvgPrice": "0.01021804", + "prevClosePrice": "0.01026000", + "lastPrice": "0.01014000", + "lastQty": "280.50000000", + "bidPrice": "0.01015000", + "bidQty": "884.00000000", + "askPrice": "0.01018000", + "askQty": "559.30000000", + "openPrice": "0.01024000", + "highPrice": "0.01034000", + "lowPrice": "0.01012000", + "volume": "100036.40000000", + "quoteVolume": "1022.17556500", + "openTime": 1560210238418, + "closeTime": 1560296638418, + "firstId": 41384, + "lastId": 41649, + "count": 266 + }, + { + "symbol": "ZRXUSDT", + "priceChange": "-0.00360000", + "priceChangePercent": "-1.096", + "weightedAvgPrice": "0.32141434", + "prevClosePrice": "0.32860000", + "lastPrice": "0.32500000", + "lastQty": "0.91000000", + "bidPrice": "0.32400000", + "bidQty": "0.17000000", + "askPrice": "0.32470000", + "askQty": "846.78000000", + "openPrice": "0.32860000", + "highPrice": "0.32960000", + "lowPrice": "0.31440000", + "volume": "1038768.51000000", + "quoteVolume": "333875.09608100", + "openTime": 1560210240446, + "closeTime": 1560296640446, + "firstId": 327885, + "lastId": 329695, + "count": 1811 + }, + { + "symbol": "FETBNB", + "priceChange": "0.00018000", + "priceChangePercent": "2.350", + "weightedAvgPrice": "0.00764683", + "prevClosePrice": "0.00766000", + "lastPrice": "0.00784000", + "lastQty": "5662.30000000", + "bidPrice": "0.00782000", + "bidQty": "9164.10000000", + "askPrice": "0.00785000", + "askQty": "245.70000000", + "openPrice": "0.00766000", + "highPrice": "0.00809000", + "lowPrice": "0.00712000", + "volume": "7664494.80000000", + "quoteVolume": "58609.09915000", + "openTime": 1560210241892, + "closeTime": 1560296641892, + "firstId": 606050, + "lastId": 628420, + "count": 22371 + }, + { + "symbol": "FETBTC", + "priceChange": "0.00000110", + "priceChangePercent": "3.589", + "weightedAvgPrice": "0.00003049", + "prevClosePrice": "0.00003067", + "lastPrice": "0.00003175", + "lastQty": "9.00000000", + "bidPrice": "0.00003170", + "bidQty": "14338.00000000", + "askPrice": "0.00003175", + "askQty": "491.00000000", + "openPrice": "0.00003065", + "highPrice": "0.00003244", + "lowPrice": "0.00002841", + "volume": "101147687.00000000", + "quoteVolume": "3084.49499009", + "openTime": 1560210242326, + "closeTime": 1560296642326, + "firstId": 4324599, + "lastId": 4387300, + "count": 62702 + }, + { + "symbol": "FETUSDT", + "priceChange": "0.00570000", + "priceChangePercent": "2.331", + "weightedAvgPrice": "0.23946663", + "prevClosePrice": "0.24450000", + "lastPrice": "0.25020000", + "lastQty": "11794.87000000", + "bidPrice": "0.25010000", + "bidQty": "2146.23000000", + "askPrice": "0.25040000", + "askQty": "877.34000000", + "openPrice": "0.24450000", + "highPrice": "0.25390000", + "lowPrice": "0.22360000", + "volume": "113931192.43000000", + "quoteVolume": "27282718.30743300", + "openTime": 1560210241796, + "closeTime": 1560296641796, + "firstId": 2786073, + "lastId": 2834798, + "count": 48726 + }, + { + "symbol": "BATUSDT", + "priceChange": "-0.00760000", + "priceChangePercent": "-2.291", + "weightedAvgPrice": "0.32465977", + "prevClosePrice": "0.33260000", + "lastPrice": "0.32420000", + "lastQty": "1160.10000000", + "bidPrice": "0.32370000", + "bidQty": "130.58000000", + "askPrice": "0.32410000", + "askQty": "593.11000000", + "openPrice": "0.33180000", + "highPrice": "0.33410000", + "lowPrice": "0.31920000", + "volume": "1642233.13000000", + "quoteVolume": "533167.02764400", + "openTime": 1560210241809, + "closeTime": 1560296641809, + "firstId": 1063936, + "lastId": 1067602, + "count": 3667 + }, + { + "symbol": "XMRBNB", + "priceChange": "-0.00100000", + "priceChangePercent": "-0.037", + "weightedAvgPrice": "2.74955085", + "prevClosePrice": "2.71100000", + "lastPrice": "2.71200000", + "lastQty": "0.44500000", + "bidPrice": "2.70800000", + "bidQty": "0.44500000", + "askPrice": "2.71600000", + "askQty": "3.76400000", + "openPrice": "2.71300000", + "highPrice": "2.77300000", + "lowPrice": "2.71200000", + "volume": "650.41400000", + "quoteVolume": "1788.34636600", + "openTime": 1560210235905, + "closeTime": 1560296635905, + "firstId": 66572, + "lastId": 67180, + "count": 609 + }, + { + "symbol": "XMRUSDT", + "priceChange": "0.23000000", + "priceChangePercent": "0.266", + "weightedAvgPrice": "86.56217197", + "prevClosePrice": "86.53000000", + "lastPrice": "86.71000000", + "lastQty": "11.51277000", + "bidPrice": "86.44000000", + "bidQty": "1.63455000", + "askPrice": "86.67000000", + "askQty": "5.85053000", + "openPrice": "86.48000000", + "highPrice": "87.50000000", + "lowPrice": "85.28000000", + "volume": "6228.19914000", + "quoteVolume": "539126.44498900", + "openTime": 1560210237037, + "closeTime": 1560296637037, + "firstId": 293004, + "lastId": 295043, + "count": 2040 + }, + { + "symbol": "ZECBNB", + "priceChange": "0.00100000", + "priceChangePercent": "0.040", + "weightedAvgPrice": "2.49478327", + "prevClosePrice": "2.50100000", + "lastPrice": "2.49600000", + "lastQty": "0.99000000", + "bidPrice": "2.49400000", + "bidQty": "0.08100000", + "askPrice": "2.50400000", + "askQty": "0.91700000", + "openPrice": "2.49500000", + "highPrice": "2.53300000", + "lowPrice": "2.46600000", + "volume": "760.99900000", + "quoteVolume": "1898.52757300", + "openTime": 1560210239169, + "closeTime": 1560296639169, + "firstId": 40192, + "lastId": 40594, + "count": 403 + }, + { + "symbol": "ZECUSDT", + "priceChange": "0.07000000", + "priceChangePercent": "0.088", + "weightedAvgPrice": "78.86995708", + "prevClosePrice": "79.62000000", + "lastPrice": "79.70000000", + "lastQty": "0.90300000", + "bidPrice": "79.70000000", + "bidQty": "0.00038000", + "askPrice": "79.76000000", + "askQty": "2.57818000", + "openPrice": "79.63000000", + "highPrice": "80.18000000", + "lowPrice": "77.00000000", + "volume": "6997.80204000", + "quoteVolume": "551916.34657450", + "openTime": 1560210240761, + "closeTime": 1560296640761, + "firstId": 274377, + "lastId": 276695, + "count": 2319 + }, + { + "symbol": "ZECPAX", + "priceChange": "-0.13000000", + "priceChangePercent": "-0.162", + "weightedAvgPrice": "79.04464040", + "prevClosePrice": "80.02000000", + "lastPrice": "80.07000000", + "lastQty": "4.07300000", + "bidPrice": "79.97000000", + "bidQty": "4.05187000", + "askPrice": "80.25000000", + "askQty": "15.20201000", + "openPrice": "80.20000000", + "highPrice": "80.44000000", + "lowPrice": "78.00000000", + "volume": "223.33445000", + "quoteVolume": "17653.39128880", + "openTime": 1560210241531, + "closeTime": 1560296641531, + "firstId": 9297, + "lastId": 9364, + "count": 68 + }, + { + "symbol": "ZECTUSD", + "priceChange": "0.15000000", + "priceChangePercent": "0.187", + "weightedAvgPrice": "78.96766638", + "prevClosePrice": "80.07000000", + "lastPrice": "80.28000000", + "lastQty": "0.12992000", + "bidPrice": "79.97000000", + "bidQty": "20.66930000", + "askPrice": "80.16000000", + "askQty": "49.64772000", + "openPrice": "80.13000000", + "highPrice": "80.74000000", + "lowPrice": "77.35000000", + "volume": "2011.18022000", + "quoteVolume": "158818.20863350", + "openTime": 1560210240744, + "closeTime": 1560296640744, + "firstId": 32659, + "lastId": 33035, + "count": 377 + }, + { + "symbol": "ZECUSDC", + "priceChange": "0.65000000", + "priceChangePercent": "0.815", + "weightedAvgPrice": "78.79704313", + "prevClosePrice": "80.01000000", + "lastPrice": "80.36000000", + "lastQty": "1.90000000", + "bidPrice": "79.87000000", + "bidQty": "11.09972000", + "askPrice": "80.05000000", + "askQty": "6.96055000", + "openPrice": "79.71000000", + "highPrice": "80.36000000", + "lowPrice": "77.32000000", + "volume": "227.52895000", + "quoteVolume": "17928.60848550", + "openTime": 1560210233215, + "closeTime": 1560296633215, + "firstId": 17588, + "lastId": 17697, + "count": 110 + }, + { + "symbol": "IOSTBNB", + "priceChange": "-0.00000940", + "priceChangePercent": "-2.585", + "weightedAvgPrice": "0.00035802", + "prevClosePrice": "0.00036510", + "lastPrice": "0.00035420", + "lastQty": "36018.00000000", + "bidPrice": "0.00035360", + "bidQty": "67779.00000000", + "askPrice": "0.00035580", + "askQty": "49076.00000000", + "openPrice": "0.00036360", + "highPrice": "0.00036360", + "lowPrice": "0.00035160", + "volume": "5484716.00000000", + "quoteVolume": "1963.65445350", + "openTime": 1560210241652, + "closeTime": 1560296641652, + "firstId": 79409, + "lastId": 79833, + "count": 425 + }, + { + "symbol": "IOSTUSDT", + "priceChange": "-0.00032800", + "priceChangePercent": "-2.814", + "weightedAvgPrice": "0.01126910", + "prevClosePrice": "0.01168700", + "lastPrice": "0.01133000", + "lastQty": "24535.00000000", + "bidPrice": "0.01129700", + "bidQty": "138678.00000000", + "askPrice": "0.01132800", + "askQty": "42131.00000000", + "openPrice": "0.01165800", + "highPrice": "0.01169300", + "lowPrice": "0.01094400", + "volume": "75476276.00000000", + "quoteVolume": "850549.65212300", + "openTime": 1560210238678, + "closeTime": 1560296638678, + "firstId": 786261, + "lastId": 789666, + "count": 3406 + }, + { + "symbol": "CELRBNB", + "priceChange": "0.00003400", + "priceChangePercent": "5.475", + "weightedAvgPrice": "0.00062770", + "prevClosePrice": "0.00062200", + "lastPrice": "0.00065500", + "lastQty": "8155.00000000", + "bidPrice": "0.00065400", + "bidQty": "1201.00000000", + "askPrice": "0.00065500", + "askQty": "23164.00000000", + "openPrice": "0.00062100", + "highPrice": "0.00066000", + "lowPrice": "0.00060100", + "volume": "33856220.00000000", + "quoteVolume": "21251.55313000", + "openTime": 1560210242327, + "closeTime": 1560296642327, + "firstId": 288460, + "lastId": 295763, + "count": 7304 + }, + { + "symbol": "CELRBTC", + "priceChange": "0.00000016", + "priceChangePercent": "6.426", + "weightedAvgPrice": "0.00000251", + "prevClosePrice": "0.00000249", + "lastPrice": "0.00000265", + "lastQty": "65470.00000000", + "bidPrice": "0.00000264", + "bidQty": "641450.00000000", + "askPrice": "0.00000265", + "askQty": "2609.00000000", + "openPrice": "0.00000249", + "highPrice": "0.00000267", + "lowPrice": "0.00000239", + "volume": "646744656.00000000", + "quoteVolume": "1624.31990169", + "openTime": 1560210242115, + "closeTime": 1560296642115, + "firstId": 3064405, + "lastId": 3093743, + "count": 29339 + }, + { + "symbol": "CELRUSDT", + "priceChange": "0.00109000", + "priceChangePercent": "5.505", + "weightedAvgPrice": "0.01976011", + "prevClosePrice": "0.01984000", + "lastPrice": "0.02089000", + "lastQty": "173410.10000000", + "bidPrice": "0.02086000", + "bidQty": "257014.00000000", + "askPrice": "0.02090000", + "askQty": "0.30000000", + "openPrice": "0.01980000", + "highPrice": "0.02106000", + "lowPrice": "0.01865000", + "volume": "299507522.30000000", + "quoteVolume": "5918301.54493500", + "openTime": 1560210242307, + "closeTime": 1560296642307, + "firstId": 1844118, + "lastId": 1861039, + "count": 16922 + }, + { + "symbol": "ADAPAX", + "priceChange": "0.00250000", + "priceChangePercent": "2.925", + "weightedAvgPrice": "0.08624944", + "prevClosePrice": "0.08496000", + "lastPrice": "0.08797000", + "lastQty": "3541.10000000", + "bidPrice": "0.08795000", + "bidQty": "11588.60000000", + "askPrice": "0.08825000", + "askQty": "14446.00000000", + "openPrice": "0.08547000", + "highPrice": "0.08950000", + "lowPrice": "0.08150000", + "volume": "1718862.50000000", + "quoteVolume": "148250.93570300", + "openTime": 1560210242350, + "closeTime": 1560296642350, + "firstId": 36466, + "lastId": 36941, + "count": 476 + }, + { + "symbol": "ADAUSDC", + "priceChange": "0.00258000", + "priceChangePercent": "3.020", + "weightedAvgPrice": "0.08594246", + "prevClosePrice": "0.08536000", + "lastPrice": "0.08802000", + "lastQty": "12507.30000000", + "bidPrice": "0.08805000", + "bidQty": "3131.00000000", + "askPrice": "0.08820000", + "askQty": "311.30000000", + "openPrice": "0.08544000", + "highPrice": "0.08947000", + "lowPrice": "0.08208000", + "volume": "4458148.70000000", + "quoteVolume": "383144.27909300", + "openTime": 1560210238290, + "closeTime": 1560296638290, + "firstId": 94761, + "lastId": 95974, + "count": 1214 + }, + { + "symbol": "NEOPAX", + "priceChange": "0.04400000", + "priceChangePercent": "0.357", + "weightedAvgPrice": "12.14655288", + "prevClosePrice": "12.33800000", + "lastPrice": "12.37800000", + "lastQty": "58.92100000", + "bidPrice": "12.30500000", + "bidQty": "45.99600000", + "askPrice": "12.36200000", + "askQty": "102.56200000", + "openPrice": "12.33400000", + "highPrice": "12.47000000", + "lowPrice": "11.77100000", + "volume": "2690.11800000", + "quoteVolume": "32675.66053700", + "openTime": 1560210238101, + "closeTime": 1560296638101, + "firstId": 19572, + "lastId": 19732, + "count": 161 + }, + { + "symbol": "NEOUSDC", + "priceChange": "0.01300000", + "priceChangePercent": "0.105", + "weightedAvgPrice": "12.15370847", + "prevClosePrice": "12.35800000", + "lastPrice": "12.33700000", + "lastQty": "1.65000000", + "bidPrice": "12.30000000", + "bidQty": "5.41900000", + "askPrice": "12.33700000", + "askQty": "52.38100000", + "openPrice": "12.32400000", + "highPrice": "12.50000000", + "lowPrice": "11.76100000", + "volume": "11594.27300000", + "quoteVolume": "140913.41395900", + "openTime": 1560210241712, + "closeTime": 1560296641712, + "firstId": 34145, + "lastId": 34598, + "count": 454 + }, + { + "symbol": "DASHBNB", + "priceChange": "-0.09800000", + "priceChangePercent": "-2.075", + "weightedAvgPrice": "4.64866212", + "prevClosePrice": "4.72300000", + "lastPrice": "4.62500000", + "lastQty": "0.02200000", + "bidPrice": "4.61600000", + "bidQty": "0.03000000", + "askPrice": "4.62600000", + "askQty": "0.98900000", + "openPrice": "4.72300000", + "highPrice": "4.73700000", + "lowPrice": "4.56100000", + "volume": "243.22900000", + "quoteVolume": "1130.68943800", + "openTime": 1560210237397, + "closeTime": 1560296637397, + "firstId": 50938, + "lastId": 51349, + "count": 412 + }, + { + "symbol": "DASHUSDT", + "priceChange": "-3.47000000", + "priceChangePercent": "-2.295", + "weightedAvgPrice": "146.75301850", + "prevClosePrice": "151.15000000", + "lastPrice": "147.72000000", + "lastQty": "0.00038000", + "bidPrice": "147.16000000", + "bidQty": "3.03503000", + "askPrice": "147.56000000", + "askQty": "9.93711000", + "openPrice": "151.19000000", + "highPrice": "151.33000000", + "lowPrice": "143.21000000", + "volume": "4322.71790000", + "quoteVolume": "634371.89996950", + "openTime": 1560210242313, + "closeTime": 1560296642313, + "firstId": 276279, + "lastId": 279022, + "count": 2744 + }, + { + "symbol": "NANOUSDT", + "priceChange": "-0.01060000", + "priceChangePercent": "-0.672", + "weightedAvgPrice": "1.53950413", + "prevClosePrice": "1.57720000", + "lastPrice": "1.56720000", + "lastQty": "17.89000000", + "bidPrice": "1.56600000", + "bidQty": "20.24000000", + "askPrice": "1.57170000", + "askQty": "459.04000000", + "openPrice": "1.57780000", + "highPrice": "1.59000000", + "lowPrice": "1.49040000", + "volume": "329014.34000000", + "quoteVolume": "506518.93544700", + "openTime": 1560210241571, + "closeTime": 1560296641571, + "firstId": 423916, + "lastId": 427042, + "count": 3127 + }, + { + "symbol": "OMGBNB", + "priceChange": "-0.00127000", + "priceChangePercent": "-2.015", + "weightedAvgPrice": "0.06201349", + "prevClosePrice": "0.06298000", + "lastPrice": "0.06177000", + "lastQty": "10.80000000", + "bidPrice": "0.06172000", + "bidQty": "2.30000000", + "askPrice": "0.06207000", + "askQty": "98.00000000", + "openPrice": "0.06304000", + "highPrice": "0.06357000", + "lowPrice": "0.06121000", + "volume": "16590.40000000", + "quoteVolume": "1028.82862200", + "openTime": 1560210240321, + "closeTime": 1560296640321, + "firstId": 28115, + "lastId": 28390, + "count": 276 + }, + { + "symbol": "OMGUSDT", + "priceChange": "-0.04140000", + "priceChangePercent": "-2.053", + "weightedAvgPrice": "1.95217372", + "prevClosePrice": "2.01150000", + "lastPrice": "1.97560000", + "lastQty": "232.96000000", + "bidPrice": "1.96960000", + "bidQty": "116.19000000", + "askPrice": "1.97540000", + "askQty": "15.41000000", + "openPrice": "2.01700000", + "highPrice": "2.03500000", + "lowPrice": "1.90000000", + "volume": "229823.20000000", + "quoteVolume": "448654.81116400", + "openTime": 1560210242367, + "closeTime": 1560296642367, + "firstId": 205394, + "lastId": 207939, + "count": 2546 + }, + { + "symbol": "THETAUSDT", + "priceChange": "-0.00332000", + "priceChangePercent": "-2.292", + "weightedAvgPrice": "0.14197788", + "prevClosePrice": "0.14545000", + "lastPrice": "0.14154000", + "lastQty": "24.80000000", + "bidPrice": "0.14100000", + "bidQty": "1000.00000000", + "askPrice": "0.14154000", + "askQty": "751.20000000", + "openPrice": "0.14486000", + "highPrice": "0.14699000", + "lowPrice": "0.13716000", + "volume": "7735950.50000000", + "quoteVolume": "1098333.84784800", + "openTime": 1560210240897, + "closeTime": 1560296640897, + "firstId": 298462, + "lastId": 303579, + "count": 5118 + }, + { + "symbol": "ENJUSDT", + "priceChange": "0.00694000", + "priceChangePercent": "4.573", + "weightedAvgPrice": "0.15655550", + "prevClosePrice": "0.15187000", + "lastPrice": "0.15870000", + "lastQty": "150.00000000", + "bidPrice": "0.15857000", + "bidQty": "471.40000000", + "askPrice": "0.15870000", + "askQty": "129.20000000", + "openPrice": "0.15176000", + "highPrice": "0.16300000", + "lowPrice": "0.14850000", + "volume": "10260340.90000000", + "quoteVolume": "1606312.79674900", + "openTime": 1560210241602, + "closeTime": 1560296641602, + "firstId": 336082, + "lastId": 343710, + "count": 7629 + }, + { + "symbol": "MITHUSDT", + "priceChange": "-0.00288000", + "priceChangePercent": "-5.105", + "weightedAvgPrice": "0.05596819", + "prevClosePrice": "0.05650000", + "lastPrice": "0.05353000", + "lastQty": "23568.50000000", + "bidPrice": "0.05353000", + "bidQty": "1079.50000000", + "askPrice": "0.05354000", + "askQty": "15942.90000000", + "openPrice": "0.05641000", + "highPrice": "0.06214000", + "lowPrice": "0.05075000", + "volume": "110687296.60000000", + "quoteVolume": "6194967.77664000", + "openTime": 1560210241615, + "closeTime": 1560296641615, + "firstId": 319482, + "lastId": 341765, + "count": 22284 + }, + { + "symbol": "MATICBNB", + "priceChange": "-0.00011200", + "priceChangePercent": "-13.130", + "weightedAvgPrice": "0.00078635", + "prevClosePrice": "0.00085300", + "lastPrice": "0.00074100", + "lastQty": "29080.00000000", + "bidPrice": "0.00074100", + "bidQty": "4833.00000000", + "askPrice": "0.00074200", + "askQty": "101147.00000000", + "openPrice": "0.00085300", + "highPrice": "0.00085900", + "lowPrice": "0.00073500", + "volume": "79523708.00000000", + "quoteVolume": "62533.61614000", + "openTime": 1560210242296, + "closeTime": 1560296642296, + "firstId": 987804, + "lastId": 1011499, + "count": 23696 + }, + { + "symbol": "MATICBTC", + "priceChange": "-0.00000042", + "priceChangePercent": "-12.281", + "weightedAvgPrice": "0.00000317", + "prevClosePrice": "0.00000342", + "lastPrice": "0.00000300", + "lastQty": "120514.00000000", + "bidPrice": "0.00000299", + "bidQty": "159732.00000000", + "askPrice": "0.00000300", + "askQty": "375506.00000000", + "openPrice": "0.00000342", + "highPrice": "0.00000343", + "lowPrice": "0.00000297", + "volume": "913581362.00000000", + "quoteVolume": "2893.33960470", + "openTime": 1560210242332, + "closeTime": 1560296642332, + "firstId": 3451197, + "lastId": 3501232, + "count": 50036 + }, + { + "symbol": "MATICUSDT", + "priceChange": "-0.00355000", + "priceChangePercent": "-13.037", + "weightedAvgPrice": "0.02486993", + "prevClosePrice": "0.02723000", + "lastPrice": "0.02368000", + "lastQty": "109875.70000000", + "bidPrice": "0.02366000", + "bidQty": "0.90000000", + "askPrice": "0.02370000", + "askQty": "17690.80000000", + "openPrice": "0.02723000", + "highPrice": "0.02726000", + "lowPrice": "0.02332000", + "volume": "654070859.70000000", + "quoteVolume": "16266693.96727500", + "openTime": 1560210241820, + "closeTime": 1560296641820, + "firstId": 2601067, + "lastId": 2633532, + "count": 32466 + }, + { + "symbol": "ATOMBNB", + "priceChange": "-0.00400000", + "priceChangePercent": "-2.105", + "weightedAvgPrice": "0.18753979", + "prevClosePrice": "0.19040000", + "lastPrice": "0.18600000", + "lastQty": "50.30000000", + "bidPrice": "0.18530000", + "bidQty": "4.00000000", + "askPrice": "0.18590000", + "askQty": "43.00000000", + "openPrice": "0.19000000", + "highPrice": "0.19220000", + "lowPrice": "0.18450000", + "volume": "59417.33000000", + "quoteVolume": "11143.11384500", + "openTime": 1560210232779, + "closeTime": 1560296632779, + "firstId": 246698, + "lastId": 249966, + "count": 3269 + }, + { + "symbol": "ATOMBTC", + "priceChange": "-0.00001100", + "priceChangePercent": "-1.443", + "weightedAvgPrice": "0.00075161", + "prevClosePrice": "0.00076230", + "lastPrice": "0.00075130", + "lastQty": "1.87000000", + "bidPrice": "0.00075070", + "bidQty": "70.00000000", + "askPrice": "0.00075100", + "askQty": "22.27000000", + "openPrice": "0.00076230", + "highPrice": "0.00076900", + "lowPrice": "0.00073990", + "volume": "455102.98000000", + "quoteVolume": "342.05996020", + "openTime": 1560210242305, + "closeTime": 1560296642305, + "firstId": 1753703, + "lastId": 1765752, + "count": 12050 + }, + { + "symbol": "ATOMUSDT", + "priceChange": "-0.15100000", + "priceChangePercent": "-2.486", + "weightedAvgPrice": "5.91714574", + "prevClosePrice": "6.07300000", + "lastPrice": "5.92200000", + "lastQty": "26.04000000", + "bidPrice": "5.92000000", + "bidQty": "330.48400000", + "askPrice": "5.92600000", + "askQty": "3.74100000", + "openPrice": "6.07300000", + "highPrice": "6.17600000", + "lowPrice": "5.75300000", + "volume": "558727.88900000", + "quoteVolume": "3306074.34556900", + "openTime": 1560210242241, + "closeTime": 1560296642241, + "firstId": 1343653, + "lastId": 1359396, + "count": 15744 + }, + { + "symbol": "ATOMUSDC", + "priceChange": "-0.16300000", + "priceChangePercent": "-2.664", + "weightedAvgPrice": "5.90345727", + "prevClosePrice": "6.10000000", + "lastPrice": "5.95600000", + "lastQty": "87.21000000", + "bidPrice": "5.93700000", + "bidQty": "232.16400000", + "askPrice": "5.97400000", + "askQty": "108.32000000", + "openPrice": "6.11900000", + "highPrice": "6.17500000", + "lowPrice": "5.79100000", + "volume": "10850.30500000", + "quoteVolume": "64054.31196800", + "openTime": 1560210225978, + "closeTime": 1560296625978, + "firstId": 21404, + "lastId": 21698, + "count": 295 + }, + { + "symbol": "ATOMPAX", + "priceChange": "-0.13100000", + "priceChangePercent": "-2.149", + "weightedAvgPrice": "5.94537525", + "prevClosePrice": "6.10800000", + "lastPrice": "5.96500000", + "lastQty": "10.04800000", + "bidPrice": "5.93900000", + "bidQty": "75.20300000", + "askPrice": "5.97600000", + "askQty": "42.84500000", + "openPrice": "6.09600000", + "highPrice": "6.20800000", + "lowPrice": "5.78400000", + "volume": "1894.51000000", + "quoteVolume": "11263.57286800", + "openTime": 1560210240667, + "closeTime": 1560296640667, + "firstId": 13610, + "lastId": 13681, + "count": 72 + }, + { + "symbol": "ATOMTUSD", + "priceChange": "-0.12400000", + "priceChangePercent": "-2.031", + "weightedAvgPrice": "5.96889871", + "prevClosePrice": "6.09000000", + "lastPrice": "5.98100000", + "lastQty": "2.00700000", + "bidPrice": "5.95100000", + "bidQty": "1.68100000", + "askPrice": "5.99600000", + "askQty": "63.80500000", + "openPrice": "6.10500000", + "highPrice": "6.17400000", + "lowPrice": "5.79100000", + "volume": "7810.31100000", + "quoteVolume": "46618.95524300", + "openTime": 1560210235936, + "closeTime": 1560296635936, + "firstId": 16978, + "lastId": 17186, + "count": 209 + }, + { + "symbol": "ETCUSDC", + "priceChange": "-0.09800000", + "priceChangePercent": "-1.176", + "weightedAvgPrice": "8.18155115", + "prevClosePrice": "8.30500000", + "lastPrice": "8.23800000", + "lastQty": "10.56000000", + "bidPrice": "8.24500000", + "bidQty": "23.42100000", + "askPrice": "8.27500000", + "askQty": "40.32500000", + "openPrice": "8.33600000", + "highPrice": "8.33600000", + "lowPrice": "8.02500000", + "volume": "1758.47400000", + "quoteVolume": "14387.04497300", + "openTime": 1560210239847, + "closeTime": 1560296639847, + "firstId": 9159, + "lastId": 9245, + "count": 87 + }, + { + "symbol": "ETCPAX", + "priceChange": "-0.07500000", + "priceChangePercent": "-0.901", + "weightedAvgPrice": "8.10907059", + "prevClosePrice": "8.32500000", + "lastPrice": "8.25000000", + "lastQty": "3.28400000", + "bidPrice": "8.24700000", + "bidQty": "117.88300000", + "askPrice": "8.28500000", + "askQty": "143.19500000", + "openPrice": "8.32500000", + "highPrice": "8.32500000", + "lowPrice": "8.05000000", + "volume": "1302.25900000", + "quoteVolume": "10560.11015500", + "openTime": 1560210239570, + "closeTime": 1560296639570, + "firstId": 4888, + "lastId": 4925, + "count": 38 + }, + { + "symbol": "ETCTUSD", + "priceChange": "-0.12100000", + "priceChangePercent": "-1.452", + "weightedAvgPrice": "8.16198650", + "prevClosePrice": "8.30200000", + "lastPrice": "8.21300000", + "lastQty": "113.19400000", + "bidPrice": "8.25100000", + "bidQty": "609.02000000", + "askPrice": "8.28100000", + "askQty": "239.84100000", + "openPrice": "8.33400000", + "highPrice": "8.39200000", + "lowPrice": "8.01200000", + "volume": "1528.64400000", + "quoteVolume": "12476.77168800", + "openTime": 1560210234914, + "closeTime": 1560296634914, + "firstId": 5713, + "lastId": 5809, + "count": 97 + }, + { + "symbol": "BATUSDC", + "priceChange": "-0.00490000", + "priceChangePercent": "-1.468", + "weightedAvgPrice": "0.32530943", + "prevClosePrice": "0.33380000", + "lastPrice": "0.32890000", + "lastQty": "401.00000000", + "bidPrice": "0.32440000", + "bidQty": "178.05000000", + "askPrice": "0.32550000", + "askQty": "150.00000000", + "openPrice": "0.33380000", + "highPrice": "0.33410000", + "lowPrice": "0.31910000", + "volume": "94755.86000000", + "quoteVolume": "30824.97445800", + "openTime": 1560210191167, + "closeTime": 1560296591167, + "firstId": 27179, + "lastId": 27355, + "count": 177 + }, + { + "symbol": "BATPAX", + "priceChange": "-0.00980000", + "priceChangePercent": "-2.922", + "weightedAvgPrice": "0.32603511", + "prevClosePrice": "0.33340000", + "lastPrice": "0.32560000", + "lastQty": "47.05000000", + "bidPrice": "0.32450000", + "bidQty": "1622.76000000", + "askPrice": "0.32630000", + "askQty": "7118.60000000", + "openPrice": "0.33540000", + "highPrice": "0.33540000", + "lowPrice": "0.32060000", + "volume": "37144.86000000", + "quoteVolume": "12110.52858000", + "openTime": 1560210235579, + "closeTime": 1560296635579, + "firstId": 15416, + "lastId": 15571, + "count": 156 + }, + { + "symbol": "BATTUSD", + "priceChange": "-0.00780000", + "priceChangePercent": "-2.333", + "weightedAvgPrice": "0.32638133", + "prevClosePrice": "0.33120000", + "lastPrice": "0.32660000", + "lastQty": "76.55000000", + "bidPrice": "0.32520000", + "bidQty": "63.00000000", + "askPrice": "0.32640000", + "askQty": "7118.60000000", + "openPrice": "0.33440000", + "highPrice": "0.33500000", + "lowPrice": "0.32060000", + "volume": "68841.13000000", + "quoteVolume": "22468.45971800", + "openTime": 1560210189248, + "closeTime": 1560296589248, + "firstId": 18339, + "lastId": 18513, + "count": 175 + }, + { + "symbol": "PHBBNB", + "priceChange": "0.00000200", + "priceChangePercent": "0.319", + "weightedAvgPrice": "0.00063703", + "prevClosePrice": "0.00063100", + "lastPrice": "0.00062900", + "lastQty": "165.00000000", + "bidPrice": "0.00062300", + "bidQty": "12100.00000000", + "askPrice": "0.00062800", + "askQty": "12949.00000000", + "openPrice": "0.00062700", + "highPrice": "0.00065700", + "lowPrice": "0.00062300", + "volume": "3945778.00000000", + "quoteVolume": "2513.58541300", + "openTime": 1560210229870, + "closeTime": 1560296629870, + "firstId": 87363, + "lastId": 88046, + "count": 684 + }, + { + "symbol": "PHBBTC", + "priceChange": "0.00000001", + "priceChangePercent": "0.397", + "weightedAvgPrice": "0.00000254", + "prevClosePrice": "0.00000252", + "lastPrice": "0.00000253", + "lastQty": "493.00000000", + "bidPrice": "0.00000252", + "bidQty": "206541.00000000", + "askPrice": "0.00000253", + "askQty": "19093.00000000", + "openPrice": "0.00000252", + "highPrice": "0.00000262", + "lowPrice": "0.00000249", + "volume": "124241877.00000000", + "quoteVolume": "316.05665202", + "openTime": 1560210241470, + "closeTime": 1560296641470, + "firstId": 828292, + "lastId": 836458, + "count": 8167 + }, + { + "symbol": "PHBUSDC", + "priceChange": "-0.00003000", + "priceChangePercent": "-0.151", + "weightedAvgPrice": "0.01972287", + "prevClosePrice": "0.02020000", + "lastPrice": "0.01989000", + "lastQty": "5066.00000000", + "bidPrice": "0.01986000", + "bidQty": "44638.40000000", + "askPrice": "0.02015000", + "askQty": "1534.70000000", + "openPrice": "0.01992000", + "highPrice": "0.02098000", + "lowPrice": "0.01950000", + "volume": "3419726.50000000", + "quoteVolume": "67446.80731800", + "openTime": 1560209687161, + "closeTime": 1560296087161, + "firstId": 9662, + "lastId": 9792, + "count": 131 + }, + { + "symbol": "PHBTUSD", + "priceChange": "-0.00018000", + "priceChangePercent": "-0.896", + "weightedAvgPrice": "0.02015609", + "prevClosePrice": "0.02032000", + "lastPrice": "0.01991000", + "lastQty": "6248.80000000", + "bidPrice": "0.01982000", + "bidQty": "42740.10000000", + "askPrice": "0.02013000", + "askQty": "565.40000000", + "openPrice": "0.02009000", + "highPrice": "0.02080000", + "lowPrice": "0.01941000", + "volume": "2077313.20000000", + "quoteVolume": "41870.51056000", + "openTime": 1560210221566, + "closeTime": 1560296621566, + "firstId": 20199, + "lastId": 20360, + "count": 162 + }, + { + "symbol": "PHBPAX", + "priceChange": "-0.00029000", + "priceChangePercent": "-1.444", + "weightedAvgPrice": "0.02023682", + "prevClosePrice": "0.02012000", + "lastPrice": "0.01979000", + "lastQty": "789.50000000", + "bidPrice": "0.01990000", + "bidQty": "24721.90000000", + "askPrice": "0.02061000", + "askQty": "12694.00000000", + "openPrice": "0.02008000", + "highPrice": "0.02091000", + "lowPrice": "0.01900000", + "volume": "524483.40000000", + "quoteVolume": "10613.87669600", + "openTime": 1560210150387, + "closeTime": 1560296550387, + "firstId": 4662, + "lastId": 4707, + "count": 46 + }, + { + "symbol": "TFUELBNB", + "priceChange": "-0.00001800", + "priceChangePercent": "-4.054", + "weightedAvgPrice": "0.00043259", + "prevClosePrice": "0.00044500", + "lastPrice": "0.00042600", + "lastQty": "5868.00000000", + "bidPrice": "0.00042600", + "bidQty": "1401.00000000", + "askPrice": "0.00042700", + "askQty": "3065.00000000", + "openPrice": "0.00044400", + "highPrice": "0.00044600", + "lowPrice": "0.00041800", + "volume": "7130165.00000000", + "quoteVolume": "3084.43275600", + "openTime": 1560210238615, + "closeTime": 1560296638615, + "firstId": 82367, + "lastId": 83656, + "count": 1290 + }, + { + "symbol": "TFUELBTC", + "priceChange": "-0.00000005", + "priceChangePercent": "-2.809", + "weightedAvgPrice": "0.00000172", + "prevClosePrice": "0.00000178", + "lastPrice": "0.00000173", + "lastQty": "179950.00000000", + "bidPrice": "0.00000172", + "bidQty": "1785886.00000000", + "askPrice": "0.00000173", + "askQty": "327891.00000000", + "openPrice": "0.00000178", + "highPrice": "0.00000178", + "lowPrice": "0.00000167", + "volume": "235969134.00000000", + "quoteVolume": "406.43081984", + "openTime": 1560210241830, + "closeTime": 1560296641830, + "firstId": 675825, + "lastId": 684861, + "count": 9037 + }, + { + "symbol": "TFUELUSDT", + "priceChange": "-0.00059000", + "priceChangePercent": "-4.158", + "weightedAvgPrice": "0.01355165", + "prevClosePrice": "0.01419000", + "lastPrice": "0.01360000", + "lastQty": "4432.00000000", + "bidPrice": "0.01360000", + "bidQty": "67724.90000000", + "askPrice": "0.01365000", + "askQty": "57729.40000000", + "openPrice": "0.01419000", + "highPrice": "0.01419000", + "lowPrice": "0.01306000", + "volume": "148384159.80000000", + "quoteVolume": "2010850.68084700", + "openTime": 1560210230316, + "closeTime": 1560296630316, + "firstId": 413392, + "lastId": 419140, + "count": 5749 + }, + { + "symbol": "TFUELUSDC", + "priceChange": "-0.00059000", + "priceChangePercent": "-4.143", + "weightedAvgPrice": "0.01371500", + "prevClosePrice": "0.01424000", + "lastPrice": "0.01365000", + "lastQty": "2331.00000000", + "bidPrice": "0.01363000", + "bidQty": "71513.40000000", + "askPrice": "0.01375000", + "askQty": "38394.80000000", + "openPrice": "0.01424000", + "highPrice": "0.01425000", + "lowPrice": "0.01208000", + "volume": "1230614.30000000", + "quoteVolume": "16877.87628100", + "openTime": 1560210228567, + "closeTime": 1560296628567, + "firstId": 5908, + "lastId": 5995, + "count": 88 + }, + { + "symbol": "TFUELTUSD", + "priceChange": "-0.00029000", + "priceChangePercent": "-2.064", + "weightedAvgPrice": "0.01358160", + "prevClosePrice": "0.01418000", + "lastPrice": "0.01376000", + "lastQty": "190.70000000", + "bidPrice": "0.01364000", + "bidQty": "67724.90000000", + "askPrice": "0.01375000", + "askQty": "47272.70000000", + "openPrice": "0.01405000", + "highPrice": "0.01405000", + "lowPrice": "0.01303000", + "volume": "1897045.80000000", + "quoteVolume": "25764.91254200", + "openTime": 1560210232555, + "closeTime": 1560296632555, + "firstId": 9701, + "lastId": 9836, + "count": 136 + }, + { + "symbol": "TFUELPAX", + "priceChange": "-0.00035000", + "priceChangePercent": "-2.491", + "weightedAvgPrice": "0.01340426", + "prevClosePrice": "0.01401000", + "lastPrice": "0.01370000", + "lastQty": "730.00000000", + "bidPrice": "0.01364000", + "bidQty": "41720.80000000", + "askPrice": "0.01376000", + "askQty": "18728.50000000", + "openPrice": "0.01405000", + "highPrice": "0.01493000", + "lowPrice": "0.01315000", + "volume": "381946.10000000", + "quoteVolume": "5119.70428700", + "openTime": 1560210173853, + "closeTime": 1560296573853, + "firstId": 3215, + "lastId": 3255, + "count": 41 + }, + { + "symbol": "ONEBNB", + "priceChange": "-0.00008200", + "priceChangePercent": "-10.289", + "weightedAvgPrice": "0.00075398", + "prevClosePrice": "0.00079800", + "lastPrice": "0.00071500", + "lastQty": "51353.00000000", + "bidPrice": "0.00071400", + "bidQty": "3681.00000000", + "askPrice": "0.00071500", + "askQty": "100636.00000000", + "openPrice": "0.00079700", + "highPrice": "0.00080500", + "lowPrice": "0.00070000", + "volume": "155214517.00000000", + "quoteVolume": "117029.39136100", + "openTime": 1560210241888, + "closeTime": 1560296641888, + "firstId": 622209, + "lastId": 647025, + "count": 24817 + }, + { + "symbol": "ONEBTC", + "priceChange": "-0.00000030", + "priceChangePercent": "-9.375", + "weightedAvgPrice": "0.00000302", + "prevClosePrice": "0.00000320", + "lastPrice": "0.00000290", + "lastQty": "9910.00000000", + "bidPrice": "0.00000289", + "bidQty": "944766.00000000", + "askPrice": "0.00000290", + "askQty": "161583.00000000", + "openPrice": "0.00000320", + "highPrice": "0.00000322", + "lowPrice": "0.00000281", + "volume": "910684490.00000000", + "quoteVolume": "2750.03683503", + "openTime": 1560210238199, + "closeTime": 1560296638199, + "firstId": 1390419, + "lastId": 1435222, + "count": 44804 + }, + { + "symbol": "ONEUSDT", + "priceChange": "-0.00257000", + "priceChangePercent": "-10.102", + "weightedAvgPrice": "0.02370474", + "prevClosePrice": "0.02547000", + "lastPrice": "0.02287000", + "lastQty": "19990.00000000", + "bidPrice": "0.02281000", + "bidQty": "12657.10000000", + "askPrice": "0.02285000", + "askQty": "71369.00000000", + "openPrice": "0.02544000", + "highPrice": "0.02573000", + "lowPrice": "0.02220000", + "volume": "631028365.70000000", + "quoteVolume": "14958360.57240400", + "openTime": 1560210238458, + "closeTime": 1560296638458, + "firstId": 981615, + "lastId": 1014575, + "count": 32961 + }, + { + "symbol": "ONETUSD", + "priceChange": "-0.00253000", + "priceChangePercent": "-9.914", + "weightedAvgPrice": "0.02377527", + "prevClosePrice": "0.02550000", + "lastPrice": "0.02299000", + "lastQty": "5617.60000000", + "bidPrice": "0.02290000", + "bidQty": "16444.60000000", + "askPrice": "0.02304000", + "askQty": "34117.90000000", + "openPrice": "0.02552000", + "highPrice": "0.02573000", + "lowPrice": "0.02237000", + "volume": "9549010.30000000", + "quoteVolume": "227030.30916900", + "openTime": 1560210225069, + "closeTime": 1560296625069, + "firstId": 19776, + "lastId": 21130, + "count": 1355 + }, + { + "symbol": "ONEPAX", + "priceChange": "-0.00276000", + "priceChangePercent": "-10.718", + "weightedAvgPrice": "0.02359438", + "prevClosePrice": "0.02552000", + "lastPrice": "0.02299000", + "lastQty": "18347.20000000", + "bidPrice": "0.02284000", + "bidQty": "22156.60000000", + "askPrice": "0.02312000", + "askQty": "13535.30000000", + "openPrice": "0.02575000", + "highPrice": "0.02578000", + "lowPrice": "0.02242000", + "volume": "7045258.80000000", + "quoteVolume": "166228.50257800", + "openTime": 1560210228728, + "closeTime": 1560296628728, + "firstId": 12550, + "lastId": 13833, + "count": 1284 + }, + { + "symbol": "ONEUSDC", + "priceChange": "-0.00324000", + "priceChangePercent": "-12.366", + "weightedAvgPrice": "0.02384909", + "prevClosePrice": "0.02553000", + "lastPrice": "0.02296000", + "lastQty": "4976.60000000", + "bidPrice": "0.02287000", + "bidQty": "12657.10000000", + "askPrice": "0.02318000", + "askQty": "2010.80000000", + "openPrice": "0.02620000", + "highPrice": "0.02698000", + "lowPrice": "0.02219000", + "volume": "5513200.00000000", + "quoteVolume": "131484.79003500", + "openTime": 1560210239109, + "closeTime": 1560296639109, + "firstId": 15915, + "lastId": 16324, + "count": 410 + }, + { + "symbol": "FTMBNB", + "priceChange": "-0.00027900", + "priceChangePercent": "-21.462", + "weightedAvgPrice": "0.00118080", + "prevClosePrice": "0.00000000", + "lastPrice": "0.00102100", + "lastQty": "4178.00000000", + "bidPrice": "0.00102100", + "bidQty": "53090.00000000", + "askPrice": "0.00102800", + "askQty": "86783.00000000", + "openPrice": "0.00130000", + "highPrice": "0.00148900", + "lowPrice": "0.00091100", + "volume": "91600332.00000000", + "quoteVolume": "108162.12637800", + "openTime": 1560210242347, + "closeTime": 1560296642347, + "firstId": 0, + "lastId": 10046, + "count": 10047 + }, + { + "symbol": "FTMBTC", + "priceChange": "0.00000015", + "priceChangePercent": "3.750", + "weightedAvgPrice": "0.00000472", + "prevClosePrice": "0.00000000", + "lastPrice": "0.00000415", + "lastQty": "7571.00000000", + "bidPrice": "0.00000415", + "bidQty": "18772.00000000", + "askPrice": "0.00000416", + "askQty": "375803.00000000", + "openPrice": "0.00000400", + "highPrice": "0.00000562", + "lowPrice": "0.00000390", + "volume": "2006043884.00000000", + "quoteVolume": "9476.77885328", + "openTime": 1560210241698, + "closeTime": 1560296641698, + "firstId": 0, + "lastId": 124790, + "count": 124791 + }, + { + "symbol": "FTMUSDT", + "priceChange": "-0.00318000", + "priceChangePercent": "-8.858", + "weightedAvgPrice": "0.03686993", + "prevClosePrice": "0.00000000", + "lastPrice": "0.03272000", + "lastQty": "8648.00000000", + "bidPrice": "0.03272000", + "bidQty": "269.60000000", + "askPrice": "0.03276000", + "askQty": "228338.00000000", + "openPrice": "0.03590000", + "highPrice": "0.04500000", + "lowPrice": "0.03000000", + "volume": "763113706.40000000", + "quoteVolume": "28135948.22995100", + "openTime": 1560210242332, + "closeTime": 1560296642332, + "firstId": 0, + "lastId": 60921, + "count": 60922 + }, + { + "symbol": "FTMTUSD", + "priceChange": "-0.00701000", + "priceChangePercent": "-17.521", + "weightedAvgPrice": "0.03709871", + "prevClosePrice": "0.00000000", + "lastPrice": "0.03300000", + "lastQty": "2115.20000000", + "bidPrice": "0.03275000", + "bidQty": "31705.20000000", + "askPrice": "0.03331000", + "askQty": "9151.00000000", + "openPrice": "0.04001000", + "highPrice": "0.05000000", + "lowPrice": "0.02500000", + "volume": "5185500.90000000", + "quoteVolume": "192375.37978600", + "openTime": 1560210206035, + "closeTime": 1560296606035, + "firstId": 0, + "lastId": 1054, + "count": 1055 + }, + { + "symbol": "FTMPAX", + "priceChange": "-0.00786000", + "priceChangePercent": "-19.645", + "weightedAvgPrice": "0.03695218", + "prevClosePrice": "0.00000000", + "lastPrice": "0.03215000", + "lastQty": "1562.00000000", + "bidPrice": "0.03275000", + "bidQty": "17376.20000000", + "askPrice": "0.03366000", + "askQty": "1788.40000000", + "openPrice": "0.04001000", + "highPrice": "0.04485000", + "lowPrice": "0.03000000", + "volume": "1483649.10000000", + "quoteVolume": "54824.06712800", + "openTime": 1560210237873, + "closeTime": 1560296637873, + "firstId": 0, + "lastId": 326, + "count": 327 + }, + { + "symbol": "FTMUSDC", + "priceChange": "-0.00741000", + "priceChangePercent": "-18.525", + "weightedAvgPrice": "0.03838945", + "prevClosePrice": "0.00000000", + "lastPrice": "0.03259000", + "lastQty": "3582.70000000", + "bidPrice": "0.03273000", + "bidQty": "29780.80000000", + "askPrice": "0.03371000", + "askQty": "9359.70000000", + "openPrice": "0.04000000", + "highPrice": "0.05354000", + "lowPrice": "0.03000000", + "volume": "5006329.20000000", + "quoteVolume": "192190.22320100", + "openTime": 1560210238896, + "closeTime": 1560296638896, + "firstId": 0, + "lastId": 591, + "count": 592 + } + ], + "queryString": "", + "bodyParams": "\u003cnil\u003e", + "headers": {} + }, + { + "data": { + "symbol": "BTCUSDT", + "priceChange": "-66.29000000", + "priceChangePercent": "-0.833", + "weightedAvgPrice": "7847.66848366", + "prevClosePrice": "7954.88000000", + "lastPrice": "7887.72000000", + "lastQty": "1.05713300", + "bidPrice": "7887.45000000", + "bidQty": "0.13037900", + "askPrice": "7887.72000000", + "askQty": "7.85449900", + "openPrice": "7954.01000000", + "highPrice": "8010.00000000", + "lowPrice": "7692.23000000", + "volume": "30443.06048900", + "quoteVolume": "238907046.34575619", + "openTime": 1560210506109, + "closeTime": 1560296906109, + "firstId": 134003738, + "lastId": 134313940, + "count": 310203 + }, + "queryString": "symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v1/trades": { + "GET": [ + { + "data": [ + { + "id": 134316261, + "price": "7889.90000000", + "qty": "0.00212800", + "quoteQty": "16.78970720", + "time": 1560297806779, + "isBuyerMaker": false, + "isBestMatch": true + }, + { + "id": 134316262, + "price": "7888.66000000", + "qty": "0.02660000", + "quoteQty": "209.83835600", + "time": 1560297808410, + "isBuyerMaker": false, + "isBestMatch": true + }, + { + "id": 134316263, + "price": "7888.66000000", + "qty": "0.10928300", + "quoteQty": "862.09643078", + "time": 1560297808410, + "isBuyerMaker": false, + "isBestMatch": true + }, + { + "id": 134316264, + "price": "7888.66000000", + "qty": "0.02660000", + "quoteQty": "209.83835600", + "time": 1560297808414, + "isBuyerMaker": true, + "isBestMatch": true + }, + { + "id": 134316265, + "price": "7887.72000000", + "qty": "0.09776000", + "quoteQty": "771.10350720", + "time": 1560297808414, + "isBuyerMaker": true, + "isBestMatch": true + }, + { + "id": 134316266, + "price": "7887.72000000", + "qty": "0.01103000", + "quoteQty": "87.00155160", + "time": 1560297809203, + "isBuyerMaker": true, + "isBestMatch": true + }, + { + "id": 134316267, + "price": "7887.72000000", + "qty": "0.01557000", + "quoteQty": "122.81180040", + "time": 1560297809587, + "isBuyerMaker": true, + "isBestMatch": true + }, + { + "id": 134316268, + "price": "7887.70000000", + "qty": "0.01732700", + "quoteQty": "136.67017790", + "time": 1560297810038, + "isBuyerMaker": true, + "isBestMatch": true + }, + { + "id": 134316269, + "price": "7887.70000000", + "qty": "0.00495100", + "quoteQty": "39.05200270", + "time": 1560297810495, + "isBuyerMaker": true, + "isBestMatch": true + }, + { + "id": 134316270, + "price": "7887.38000000", + "qty": "0.06520200", + "quoteQty": "514.27295076", + "time": 1560297811181, + "isBuyerMaker": true, + "isBestMatch": true + }, + { + "id": 134316271, + "price": "7887.67000000", + "qty": "0.00148600", + "quoteQty": "11.72107762", + "time": 1560297812185, + "isBuyerMaker": false, + "isBestMatch": true + }, + { + "id": 134316272, + "price": "7887.67000000", + "qty": "0.01019300", + "quoteQty": "80.39902031", + "time": 1560297813510, + "isBuyerMaker": false, + "isBestMatch": true + }, + { + "id": 134316273, + "price": "7887.67000000", + "qty": "0.00000100", + "quoteQty": "0.00788767", + "time": 1560297813535, + "isBuyerMaker": false, + "isBestMatch": true + }, + { + "id": 134316274, + "price": "7889.05000000", + "qty": "0.13062200", + "quoteQty": "1030.48348910", + "time": 1560297814605, + "isBuyerMaker": false, + "isBestMatch": true + }, + { + "id": 134316275, + "price": "7889.72000000", + "qty": "0.00837800", + "quoteQty": "66.10007416", + "time": 1560297814605, + "isBuyerMaker": false, + "isBestMatch": true + } + ], + "queryString": "limit=15\u0026symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v3/account": { + "GET": [ + { + "data": { + "makerCommission": 10, + "takerCommission": 10, + "buyerCommission": 0, + "sellerCommission": 0, + "canTrade": true, + "canWithdraw": true, + "canDeposit": true, + "updateTime": 1517434535027, + "accountType": "MARGIN", + "balances": [ + { + "asset": "BTC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "LTC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ETH", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "NEO", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BNB", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "QTUM", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "EOS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "SNT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BNT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "GAS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BCC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "USDT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "HSR", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "OAX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "DNT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "MCO", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ICN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ZRX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "OMG", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "WTC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "YOYO", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "LRC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "TRX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "SNGLS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "STRAT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BQX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "FUN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "KNC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "CDT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "XVG", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "IOTA", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "SNM", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "LINK", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "CVC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "TNT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "REP", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "MDA", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "MTL", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "SALT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "NULS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "SUB", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "MTH", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ADX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ETC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ENG", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ZEC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "AST", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "GNT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "DGD", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BAT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "DASH", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "POWR", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BTG", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "REQ", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "XMR", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "EVX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "VIB", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ENJ", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "VEN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ARK", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "XRP", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "MOD", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "STORJ", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "KMD", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "RCN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "EDO", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "DATA", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "DLT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "MANA", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "PPT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "RDN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "GXS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "AMB", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ARN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BCPT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "CND", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "GVT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "POE", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BTS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "FUEL", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "XZC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "QSP", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "LSK", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BCD", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "TNB", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ADA", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "LEND", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "XLM", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "CMT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "WAVES", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "WABI", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "GTO", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ICX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "OST", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ELF", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "AION", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "WINGS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BRD", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "NEBL", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "NAV", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "VIBE", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "LUN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "TRIG", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "APPC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "CHAT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "RLC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "INS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "PIVX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "IOST", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "STEEM", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "NANO", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "AE", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "VIA", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BLZ", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "SYS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "RPX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "NCASH", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "POA", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ONT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ZIL", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "STORM", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "XEM", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "WAN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "WPR", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "QLC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "GRS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "CLOAK", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "LOOM", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BCN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "TUSD", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ZEN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "SKY", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "THETA", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "IOTX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "QKC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "AGI", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "NXS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "SC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "NPXS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "KEY", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "NAS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "MFT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "DENT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ARDR", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "HOT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "VET", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "DOCK", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "POLY", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ONG", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "PHX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "HC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "GO", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "PAX", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "RVN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "DCR", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "USDC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "MITH", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BCHABC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BCHSV", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "REN", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "BTT", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "USDS", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "FET", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "TFUEL", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "CELR", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "MATIC", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ATOM", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "PHB", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "ONE", + "free": "0.00000000", + "locked": "0.00000000" + }, + { + "asset": "FTM", + "free": "0.00000000", + "locked": "0.00000000" + } + ] + }, + "queryString": "recvWindow=5000\u0026signature=7cae6f0626a72d9331edf09a1905f16ee424cb0b9b884d09000b490c4142c207\u0026timestamp=1560296073000", + "bodyParams": "", + "headers": { + "X-Mbx-Apikey": [ + "" + ] + } + } + ] + }, + "/api/v3/allOrders": { + "GET": [ + { + "data": [ + { + "symbol": "LTCBTC", + "orderId": 1, + "clientOrderId": "mySuperOrderOfAwesome", + "price": "0.1", + "origQty": "1.0", + "executedQty": "0.0", + "cummulativeQuoteQty": "0.0", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": true + } + ], + "queryString": "limit=1000\u0026recvWindow=5000\u0026signature=4c63d8c6c56c8376c43df8bae65c19bc5c45677e501f8cf07227c7061d40ab28\u0026symbol=LTCBTC\u0026timestamp=1560295613000", + "bodyParams": "", + "headers": { + "X-Mbx-Apikey": [ + "" + ] + } + }, + { + "data": [ + { + "symbol": "LTCBTC", + "orderId": 1, + "clientOrderId": "myOrder1", + "price": "0.1", + "origQty": "1.0", + "executedQty": "0.0", + "cummulativeQuoteQty": "0.0", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": true + } + ], + "queryString": "recvWindow=5000\u0026signature=ffa80a00a4ac2e29493e2d7b36df11de1e4b63f4f0cc584cfeaf1709a6478dc1\u0026symbol=BTCUSDT\u0026timestamp=1560296162000", + "bodyParams": "", + "headers": { + "X-Mbx-Apikey": [ + "" + ] + } + } + ] + }, + "/api/v3/avgPrice": { + "GET": [ + { + "data": { + "mins": 5, + "price": "7889.05325623" + }, + "queryString": "symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v3/openOrders": { + "GET": [ + { + "data": [ + { + "symbol": "LTCBTC", + "orderId": 1, + "clientOrderId": "myOrder1", + "price": "0.1", + "origQty": "1.0", + "executedQty": "0.0", + "cummulativeQuoteQty": "0.0", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": true + } + ], + "queryString": "recvWindow=5000\u0026signature=0351f91c920dc08244977c888946962df9c39add182cfc400041a29dc41931ac\u0026timestamp=1560235137000", + "bodyParams": "", + "headers": { + "Key": [ + "" + ], + "X-Mbx-Apikey": [ + "" + ] + } + }, + { + "data": [ + { + "symbol": "LTCBTC", + "orderId": 1, + "clientOrderId": "superDuperOpenOrder", + "price": "0.1", + "origQty": "1.0", + "executedQty": "0.0", + "cummulativeQuoteQty": "0.0", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": true + } + ], + "queryString": "recvWindow=5000\u0026signature=7ec10254280461f4ac9abaaad60afddf3f0ffeccf7aa5f8269174faea27751da\u0026symbol=LTCBTC\u0026timestamp=1560295910000", + "bodyParams": "", + "headers": { + "X-Mbx-Apikey": [ + "" + ] + } + }, + { + "data": [ + { + "symbol": "BTCUDT", + "orderId": 1, + "clientOrderId": "meowOrder", + "price": "0.1", + "origQty": "1.0", + "executedQty": "0.0", + "cummulativeQuoteQty": "0.0", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": true + } + ], + "queryString": "recvWindow=5000\u0026signature=d202904fe292b81d5fbb334ac7322bfbdb8472074c05f5a78b946232b197cd8a\u0026symbol=BTCUSDT\u0026timestamp=1560296250000", + "bodyParams": "", + "headers": { + "X-Mbx-Apikey": [ + "" + ] + } + } + ] + }, + "/api/v3/order": { + "DELETE": [ + { + "data": { + "symbol": "LTCBTC", + "orderId": 28, + "origClientOrderId": "myOrder1", + "clientOrderId": "cancelMyOrder1", + "transactTime": 1507725176595, + "price": "1.00000000", + "origQty": "10.00000000", + "executedQty": "8.00000000", + "cummulativeQuoteQty": "8.00000000", + "status": "CANCELED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "SELL" + }, + "queryString": "orderId=1\u0026origClientOrderId=1\u0026recvWindow=5000\u0026signature=1fc17bbf196e99c41a3fdc5c333aa963d3a059b00bef547b35734d2c0bb950e4\u0026symbol=LTCBTC\u0026timestamp=1560235422000", + "bodyParams": "", + "headers": { + "Key": [ + "" + ], + "X-Mbx-Apikey": [ + "" + ] + } + }, + { + "data": { + "symbol": "LTCBTC", + "orderId": 28, + "origClientOrderId": "myOrder1", + "clientOrderId": "cancelMyOrder1", + "transactTime": 1507725176595, + "price": "1.00000000", + "origQty": "10.00000000", + "executedQty": "8.00000000", + "cummulativeQuoteQty": "8.00000000", + "status": "CANCELED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "SELL" + }, + "queryString": "orderId=1\u0026recvWindow=5000\u0026signature=1fc17bbf196e99c41a3fdc5c333aa963d3a059b00bef547b35734d2c0bb950e4\u0026symbol=LTCBTC\u0026timestamp=1560235422000", + "bodyParams": "", + "headers": { + "Key": [ + "" + ], + "X-Mbx-Apikey": [ + "" + ] + } + } + ], + "GET": [ + { + "data": { + "symbol": "BTCUSDT", + "orderId": 1337, + "clientOrderId": "eliteOrder", + "price": "0.1", + "origQty": "1.0", + "executedQty": "0.0", + "cummulativeQuoteQty": "0.0", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": true + }, + "queryString": "orderId=1337\u0026recvWindow=5000\u0026signature=0dffb798f0ecba6292e5b5fc9e86027c4d85733c6ff6edd5dbb9be2f7e48f6a6\u0026symbol=BTCUSDT\u0026timestamp=1560296399000", + "bodyParams": "", + "headers": { + "X-Mbx-Apikey": [ + "" + ] + } + } + ], + "POST": [ + { + "data": {}, + "queryString": "quantity=1000000000\u0026recvWindow=5000\u0026side=BUY\u0026signature=d122bfd46f4dcf8dda98974aed14221916aabc050a6aa30194d124e81ffe7f44\u0026symbol=LTCBTC\u0026timeInForce=GTC\u0026timestamp=1560236466000\u0026type=MARKET", + "bodyParams": "", + "headers": { + "Key": [ + "" + ], + "X-Mbx-Apikey": [ + "" + ] + } + } + ] + }, + "/api/v3/ticker/bookTicker": { + "GET": [ + { + "data": { + "symbol": "BTCUSDT", + "bidPrice": "7888.64000000", + "bidQty": "0.02534600", + "askPrice": "7890.50000000", + "askQty": "0.13300500" + }, + "queryString": "symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v3/ticker/price": { + "GET": [ + { + "data": { + "symbol": "BTCUSDT", + "price": "7888.64000000" + }, + "queryString": "symbol=BTCUSDT", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/wapi/v3/depositAddress.html": { + "GET": [ + { + "data": { + "address": "1H5qe5xK5fCcEHGhww7jsoBMZRYSzVDz9V", + "success": true, + "addressTag": "", + "asset": "BTC" + }, + "queryString": "asset=BTC\u0026recvWindow=5000\u0026signature=e94a8205dc67343a7bb482c4b4c65cb48abbca60b3009ee139e8c221f0271e6a\u0026status=true\u0026timestamp=1560233327000", + "bodyParams": "", + "headers": { + "Key": [ + "" + ], + "X-Mbx-Apikey": [ + "" + ] + } + } + ] + }, + "/wapi/v3/withdraw.html": { + "POST": [ + { + "data": { + "msg": "success", + "success": true, + "id": "7213fea8e94b4a5593d507237e5a555b" + }, + "queryString": "address=1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB\u0026amount=0\u0026asset=BTC\u0026name=WITHDRAW+IT+ALL\u0026recvWindow=5000\u0026signature=bec597908e6d2c223790cac9ca46300109216e056690a3f10a279b9eecc97e7e\u0026timestamp=1560233386000", + "bodyParams": "", + "headers": { + "Key": [ + "" + ], + "X-Mbx-Apikey": [ + "" + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/testdata/http_mock/bitstamp/bitstamp.json b/testdata/http_mock/bitstamp/bitstamp.json new file mode 100644 index 00000000..900a690e --- /dev/null +++ b/testdata/http_mock/bitstamp/bitstamp.json @@ -0,0 +1,42842 @@ +{ + "routes": { + "/api/bitcoin_deposit_address/": { + "POST": [ + { + "data": "3JKiWaRunFQhpaYSDs4cQwfdPXHK2pWXct", + "queryString": "", + "bodyParams": "key=\u0026nonce=1560479820465818675\u0026signature=71962D6BBDFE3BF42BAC455E3A9C410FA90286148A6C299BAC1B44234072DA45", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/bitcoin_withdrawal/": { + "POST": [ + { + "data": { + "error": { + "amount": [ + "You have only 0.00000000 BTC available. Check your account balance for details." + ], + "instant": [ + "Bitcoin instant withdrawals are temporarily disabled." + ] + } + }, + "queryString": "", + "bodyParams": "address=1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB\u0026amount=-1\u0026instant=1\u0026key=\u0026nonce=1560405519661130725\u0026signature=2CEEA36209A743A15CEF2C5AD724DEF2339E4FB5497DE58149507ED6CEEF98BA", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/cancel_all_orders/": { + "POST": [ + { + "data": true, + "queryString": "", + "bodyParams": "key=\u0026nonce=1560466182936874211\u0026signature=D9F943CF9728072206C123E8E6381EE6AB714504A264D9AEA091D945539FEFF5", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/eur_usd/": { + "GET": [ + { + "data": { + "sell": "1.1287", + "buy": "1.1377" + }, + "queryString": "", + "bodyParams": "\u003cnil\u003e", + "headers": { + "Referer": [ + "https://www.bitstamp.net/api/eur_usd" + ] + } + } + ] + }, + "/api/order_status/": { + "POST": [ + { + "data": { + "error": "Order not found" + }, + "queryString": "", + "bodyParams": "id=1337\u0026key=\u0026nonce=1560480481012757775\u0026signature=71E0C3A3B0CE4C96C9AC0C6519C3F4801A46C52C5C9BAAADB8642CE65F0CF385", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/unconfirmed_btc/": { + "POST": [ + { + "data": [], + "queryString": "", + "bodyParams": "key=\u0026nonce=1560479341665583985\u0026signature=8E4563D286BB5EEE45093DA708A8F78780B20CA5B1631E308E9C5CB64ACFE8D3", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/v2/balance/": { + "POST": [ + { + "data": { + "bch_available": "0.00000000", + "bch_balance": "0.00000000", + "bch_reserved": "0.00000000", + "bchbtc_fee": "0.25", + "bcheur_fee": "0.25", + "bchusd_fee": "0.25", + "btc_available": "0.00000000", + "btc_balance": "0.00000000", + "btc_reserved": "0.00000000", + "btceur_fee": "0.25", + "btcusd_fee": "0.25", + "eth_available": "0.00000000", + "eth_balance": "0.00000000", + "eth_reserved": "0.00000000", + "ethbtc_fee": "0.25", + "etheur_fee": "0.25", + "ethusd_fee": "0.25", + "eur_available": "0.00", + "eur_balance": "0.00", + "eur_reserved": "0.00", + "eurusd_fee": "0.25", + "ltc_available": "0.00000000", + "ltc_balance": "0.00000000", + "ltc_reserved": "0.00000000", + "ltcbtc_fee": "0.25", + "ltceur_fee": "0.25", + "ltcusd_fee": "0.25", + "usd_available": "0.00", + "usd_balance": "0.00", + "usd_reserved": "0.00", + "xrp_available": "0.00000000", + "xrp_balance": "0.00000000", + "xrp_reserved": "0.00000000", + "xrpbtc_fee": "0.25", + "xrpeur_fee": "0.25", + "xrpusd_fee": "0.25" + }, + "queryString": "", + "bodyParams": "key=\u0026nonce=1560481519007838128\u0026signature=C7558B2B2E75E9057994271CCC0200835887CDC63EC4103220489C52F749BFFA", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/v2/buy/market/btcusd/": { + "POST": [ + { + "data": { + "status": "error", + "reason": { + "__all__": [ + "You can only buy 0.00000000 BTC. Check your account balance for details." + ] + } + }, + "queryString": "", + "bodyParams": "amount=1\u0026key=\u0026nonce=1560469416881040806\u0026price=1\u0026signature=D683BE07779B299B730F334C94758EBC56E264FC5C96C87B64E5ECC670421D65", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/v2/cancel_order/": { + "POST": [ + { + "data": { + "error": "Order not found" + }, + "queryString": "", + "bodyParams": "id=1\u0026key=\u0026nonce=1560467884949723197\u0026signature=79D88FC2BC3AECBFDD27F70EF5DDBB485349F45129C58EE725BAEE0DF7818452", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/v2/open_orders/all/": { + "POST": [ + { + "data": [], + "queryString": "", + "bodyParams": "key=\u0026nonce=1560473214020947705\u0026signature=4530400B3F02B223F5AFE62DA3B18A09757A32882D9798B9AA78D70DE976316F", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/v2/open_orders/btcusd/": { + "POST": [ + { + "data": [], + "queryString": "", + "bodyParams": "key=\u0026nonce=1560480749904299481\u0026signature=C1DCFCFC6E3122D12ACCA57178F781658DA6690DCAE4F56EC13998D5FCE9DF5D", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/v2/order_book/btcusd/": { + "GET": [ + { + "data": { + "timestamp": "1560482001", + "bids": [ + [ + "8200.59", + "2.00000000" + ], + [ + "8196.66", + "1.90720000" + ], + [ + "8196.51", + "2.00000000" + ], + [ + "8196.00", + "0.02000000" + ], + [ + "8195.02", + "3.04580000" + ], + [ + "8194.92", + "0.06000000" + ], + [ + "8194.10", + "4.98000000" + ], + [ + "8193.82", + "1.00000000" + ], + [ + "8193.22", + "1.50000000" + ], + [ + "8192.88", + "5.00000000" + ], + [ + "8192.09", + "0.40000000" + ], + [ + "8191.98", + "2.50000000" + ], + [ + "8191.08", + "4.00000000" + ], + [ + "8188.41", + "10.00000000" + ], + [ + "8188.00", + "0.06000000" + ], + [ + "8187.91", + "4.30000000" + ], + [ + "8187.29", + "4.12935423" + ], + [ + "8186.73", + "3.62073320" + ], + [ + "8186.45", + "2.50000000" + ], + [ + "8186.29", + "2.00000000" + ], + [ + "8186.26", + "5.00000000" + ], + [ + "8185.69", + "0.31014450" + ], + [ + "8185.68", + "0.40000000" + ], + [ + "8185.16", + "0.10000000" + ], + [ + "8184.96", + "3.38967500" + ], + [ + "8184.00", + "4.90000000" + ], + [ + "8182.48", + "1.00000000" + ], + [ + "8181.69", + "0.70000000" + ], + [ + "8181.00", + "1.66108835" + ], + [ + "8180.84", + "4.01501600" + ], + [ + "8180.78", + "5.00000000" + ], + [ + "8180.37", + "5.00000000" + ], + [ + "8178.92", + "2.37600000" + ], + [ + "8177.83", + "2.75000000" + ], + [ + "8174.55", + "3.00000000" + ], + [ + "8173.79", + "0.20000000" + ], + [ + "8173.76", + "0.00096057" + ], + [ + "8172.48", + "8.08587150" + ], + [ + "8172.04", + "8.06320237" + ], + [ + "8171.53", + "0.00674998" + ], + [ + "8171.00", + "0.20000000" + ], + [ + "8170.71", + "12.55000000" + ], + [ + "8169.98", + "1.50180000" + ], + [ + "8169.50", + "0.86960531" + ], + [ + "8168.24", + "0.01480061" + ], + [ + "8165.58", + "1.45200000" + ], + [ + "8165.19", + "1.39337966" + ], + [ + "8164.80", + "1.36000000" + ], + [ + "8164.77", + "0.07000000" + ], + [ + "8164.17", + "1.43276582" + ], + [ + "8161.00", + "2.36531606" + ], + [ + "8160.61", + "0.00674998" + ], + [ + "8160.00", + "0.01400000" + ], + [ + "8159.95", + "0.00096056" + ], + [ + "8158.25", + "21.53520000" + ], + [ + "8156.61", + "0.00069500" + ], + [ + "8155.26", + "0.07000000" + ], + [ + "8150.33", + "12.55000000" + ], + [ + "8150.00", + "0.72616995" + ], + [ + "8149.69", + "0.00674998" + ], + [ + "8148.60", + "0.81000000" + ], + [ + "8147.59", + "15.18900000" + ], + [ + "8146.13", + "0.00096055" + ], + [ + "8143.71", + "0.00413412" + ], + [ + "8141.30", + "15.88668520" + ], + [ + "8140.50", + "0.86000000" + ], + [ + "8140.00", + "1.00000000" + ], + [ + "8138.77", + "0.00674998" + ], + [ + "8135.00", + "0.75605800" + ], + [ + "8132.31", + "0.00097054" + ], + [ + "8130.03", + "0.01720104" + ], + [ + "8130.01", + "12.55000000" + ], + [ + "8129.50", + "0.00689602" + ], + [ + "8128.96", + "18.20801456" + ], + [ + "8128.00", + "0.02598254" + ], + [ + "8127.85", + "0.00674998" + ], + [ + "8126.00", + "0.50000000" + ], + [ + "8125.52", + "1.00000000" + ], + [ + "8125.01", + "0.49230709" + ], + [ + "8125.00", + "2.00000000" + ], + [ + "8123.40", + "0.03654189" + ], + [ + "8123.39", + "6.04930000" + ], + [ + "8123.37", + "4.59900000" + ], + [ + "8121.97", + "0.51019519" + ], + [ + "8121.93", + "1.00000000" + ], + [ + "8120.00", + "1.00000000" + ], + [ + "8118.49", + "0.00097053" + ], + [ + "8117.77", + "6.50051395" + ], + [ + "8117.53", + "0.05000000" + ], + [ + "8116.92", + "0.00674998" + ], + [ + "8113.27", + "1.00000000" + ], + [ + "8113.19", + "1.00000000" + ], + [ + "8111.11", + "1.15219829" + ], + [ + "8110.68", + "2.00000000" + ], + [ + "8110.00", + "0.01732169" + ], + [ + "8109.74", + "12.55000000" + ], + [ + "8106.00", + "0.00674998" + ], + [ + "8104.85", + "0.98000000" + ], + [ + "8104.68", + "0.00097052" + ], + [ + "8104.00", + "0.12402915" + ], + [ + "8102.57", + "18.97881720" + ], + [ + "8102.00", + "0.15000000" + ], + [ + "8101.00", + "11.54930648" + ], + [ + "8100.00", + "3.59309698" + ], + [ + "8099.00", + "2.50000000" + ], + [ + "8098.98", + "9.99000000" + ], + [ + "8098.91", + "9.99000000" + ], + [ + "8098.88", + "9.99000000" + ], + [ + "8097.42", + "0.01470036" + ], + [ + "8096.98", + "9.99000000" + ], + [ + "8096.68", + "1.00000000" + ], + [ + "8095.08", + "0.00674998" + ], + [ + "8092.00", + "0.07742462" + ], + [ + "8090.86", + "0.00097051" + ], + [ + "8090.00", + "1.00000000" + ], + [ + "8089.51", + "12.55000000" + ], + [ + "8087.18", + "6.87000000" + ], + [ + "8086.00", + "0.01500000" + ], + [ + "8084.73", + "6.86118815" + ], + [ + "8084.16", + "0.00674998" + ], + [ + "8080.00", + "0.42569834" + ], + [ + "8077.44", + "0.30384255" + ], + [ + "8077.04", + "0.00097050" + ], + [ + "8076.47", + "0.50000000" + ], + [ + "8075.00", + "1.00000000" + ], + [ + "8074.11", + "0.33000000" + ], + [ + "8073.63", + "21.59624700" + ], + [ + "8073.24", + "0.00674998" + ], + [ + "8070.00", + "0.00710161" + ], + [ + "8069.34", + "12.55000000" + ], + [ + "8067.00", + "0.51000000" + ], + [ + "8066.00", + "0.02130092" + ], + [ + "8064.87", + "1.48830000" + ], + [ + "8063.23", + "0.00097049" + ], + [ + "8063.00", + "0.01500000" + ], + [ + "8062.32", + "0.00674998" + ], + [ + "8060.00", + "0.02598254" + ], + [ + "8058.00", + "0.10000000" + ], + [ + "8057.79", + "13.65145361" + ], + [ + "8057.00", + "0.10000000" + ], + [ + "8055.00", + "0.10000000" + ], + [ + "8053.90", + "0.50000000" + ], + [ + "8053.00", + "0.07000000" + ], + [ + "8051.40", + "0.00674998" + ], + [ + "8050.99", + "7.21600000" + ], + [ + "8050.00", + "0.32238572" + ], + [ + "8049.41", + "0.00098048" + ], + [ + "8049.22", + "8.97821409" + ], + [ + "8047.93", + "0.03000000" + ], + [ + "8047.48", + "18.20000000" + ], + [ + "8046.00", + "0.02200000" + ], + [ + "8044.00", + "0.10000000" + ], + [ + "8042.00", + "1.00000000" + ], + [ + "8041.00", + "0.05000000" + ], + [ + "8040.47", + "0.00674998" + ], + [ + "8038.04", + "0.00247324" + ], + [ + "8038.00", + "0.15000000" + ], + [ + "8037.00", + "0.10152215" + ], + [ + "8036.08", + "0.50000000" + ], + [ + "8035.59", + "0.00098047" + ], + [ + "8034.11", + "1.00000000" + ], + [ + "8032.22", + "0.10000000" + ], + [ + "8031.65", + "0.01242951" + ], + [ + "8030.75", + "15.04265167" + ], + [ + "8030.00", + "0.09876339" + ], + [ + "8029.55", + "0.00674998" + ], + [ + "8029.14", + "12.55000000" + ], + [ + "8028.00", + "0.60000000" + ], + [ + "8023.84", + "0.03000000" + ], + [ + "8023.59", + "0.01000000" + ], + [ + "8021.77", + "0.00098046" + ], + [ + "8021.00", + "0.05000000" + ], + [ + "8020.00", + "0.27000000" + ], + [ + "8019.11", + "0.01865918" + ], + [ + "8018.63", + "0.00674998" + ], + [ + "8018.47", + "15.95553644" + ], + [ + "8018.00", + "0.02500000" + ], + [ + "8016.28", + "0.00814691" + ], + [ + "8015.00", + "0.03000000" + ], + [ + "8014.79", + "13.28100000" + ], + [ + "8013.00", + "0.60000000" + ], + [ + "8012.90", + "1.00000000" + ], + [ + "8010.00", + "0.20100000" + ], + [ + "8009.32", + "0.02517442" + ], + [ + "8009.22", + "14.70848699" + ], + [ + "8009.12", + "12.55000000" + ], + [ + "8008.71", + "1.00000000" + ], + [ + "8008.45", + "0.21665000" + ], + [ + "8007.96", + "0.00098045" + ], + [ + "8007.71", + "0.00674998" + ], + [ + "8005.00", + "1.20779625" + ], + [ + "8004.51", + "0.05000000" + ], + [ + "8004.00", + "0.20000000" + ], + [ + "8001.00", + "0.87189976" + ], + [ + "8000.90", + "0.02000000" + ], + [ + "8000.00", + "3.24490281" + ], + [ + "7999.39", + "0.00070900" + ], + [ + "7996.79", + "0.00674998" + ], + [ + "7996.53", + "0.50000000" + ], + [ + "7995.97", + "0.50000000" + ], + [ + "7995.89", + "0.50000000" + ], + [ + "7994.14", + "0.00098044" + ], + [ + "7993.23", + "5.46294097" + ], + [ + "7991.00", + "0.01500000" + ], + [ + "7990.31", + "0.01744988" + ], + [ + "7990.00", + "7.05100000" + ], + [ + "7989.15", + "12.55000000" + ], + [ + "7985.87", + "0.00674998" + ], + [ + "7982.01", + "13.11068317" + ], + [ + "7980.36", + "0.02000000" + ], + [ + "7980.32", + "0.00098043" + ], + [ + "7979.00", + "0.05000000" + ], + [ + "7977.00", + "0.05000000" + ], + [ + "7974.95", + "0.00674998" + ], + [ + "7974.93", + "0.81816016" + ], + [ + "7973.00", + "1.00000000" + ], + [ + "7971.87", + "0.50000000" + ], + [ + "7971.00", + "0.05000000" + ], + [ + "7969.22", + "12.55000000" + ], + [ + "7968.00", + "0.50000000" + ], + [ + "7966.51", + "0.00099042" + ], + [ + "7966.00", + "0.13172454" + ], + [ + "7964.02", + "0.00674998" + ], + [ + "7963.00", + "0.01000000" + ], + [ + "7955.91", + "0.50000000" + ], + [ + "7953.10", + "0.00674998" + ], + [ + "7952.69", + "0.00099041" + ], + [ + "7952.51", + "0.05000000" + ], + [ + "7950.05", + "0.03081550" + ], + [ + "7950.00", + "0.33412140" + ], + [ + "7949.35", + "12.55000000" + ], + [ + "7947.22", + "0.03000000" + ], + [ + "7945.00", + "1.00000000" + ], + [ + "7942.18", + "0.00674998" + ], + [ + "7940.02", + "1.55230000" + ], + [ + "7940.00", + "0.02000000" + ], + [ + "7938.87", + "0.00099040" + ], + [ + "7937.29", + "0.03058631" + ], + [ + "7933.00", + "0.13262996" + ], + [ + "7931.26", + "0.00674998" + ], + [ + "7929.53", + "2.19914149" + ], + [ + "7928.07", + "0.23692176" + ], + [ + "7925.05", + "0.00099039" + ], + [ + "7923.99", + "0.00319990" + ], + [ + "7922.00", + "0.00100000" + ], + [ + "7920.34", + "0.00674998" + ], + [ + "7920.15", + "0.05502743" + ], + [ + "7917.27", + "0.10000000" + ], + [ + "7916.80", + "0.63000000" + ], + [ + "7913.04", + "0.03080318" + ], + [ + "7912.90", + "1.00000000" + ], + [ + "7911.24", + "0.00099038" + ], + [ + "7911.00", + "1.00000000" + ], + [ + "7910.78", + "0.50000000" + ], + [ + "7909.75", + "12.55000000" + ], + [ + "7909.42", + "0.00674998" + ], + [ + "7909.00", + "0.30000000" + ], + [ + "7907.00", + "0.10000000" + ], + [ + "7906.00", + "0.05000000" + ], + [ + "7904.00", + "0.05000000" + ], + [ + "7902.00", + "0.05000000" + ], + [ + "7901.00", + "0.22000000" + ], + [ + "7900.03", + "0.03502316" + ], + [ + "7900.00", + "3.33303042" + ], + [ + "7899.00", + "0.10000000" + ], + [ + "7898.00", + "0.05000000" + ], + [ + "7897.42", + "0.00099037" + ], + [ + "7896.32", + "8.69316277" + ], + [ + "7892.11", + "0.00831717" + ], + [ + "7892.00", + "0.50000000" + ], + [ + "7890.03", + "12.55000000" + ], + [ + "7890.00", + "0.00848289" + ], + [ + "7889.00", + "0.00430000" + ], + [ + "7888.86", + "0.00944836" + ], + [ + "7888.00", + "0.55686619" + ], + [ + "7887.00", + "0.02000000" + ], + [ + "7885.75", + "0.10000000" + ], + [ + "7884.00", + "2.00000000" + ], + [ + "7883.60", + "0.00100036" + ], + [ + "7883.58", + "0.13469514" + ], + [ + "7882.78", + "0.10000000" + ], + [ + "7879.00", + "0.50000000" + ], + [ + "7875.00", + "0.10003937" + ], + [ + "7874.00", + "0.02677673" + ], + [ + "7873.00", + "0.02000000" + ], + [ + "7870.35", + "12.55000000" + ], + [ + "7870.00", + "0.05000000" + ], + [ + "7869.78", + "0.00100035" + ], + [ + "7869.02", + "0.50000000" + ], + [ + "7868.00", + "0.01783045" + ], + [ + "7867.00", + "0.02000000" + ], + [ + "7865.00", + "1.00000000" + ], + [ + "7863.16", + "0.00448460" + ], + [ + "7861.46", + "0.02500000" + ], + [ + "7861.08", + "0.00223754" + ], + [ + "7861.00", + "0.10000000" + ], + [ + "7860.00", + "0.07336400" + ], + [ + "7859.72", + "0.50000000" + ], + [ + "7859.00", + "0.01000000" + ], + [ + "7857.81", + "0.01508244" + ], + [ + "7856.00", + "0.09667897" + ], + [ + "7855.97", + "0.00100034" + ], + [ + "7855.00", + "0.03000000" + ], + [ + "7854.00", + "0.03000000" + ], + [ + "7850.79", + "0.02000000" + ], + [ + "7850.72", + "12.55000000" + ], + [ + "7850.01", + "0.03000000" + ], + [ + "7850.00", + "1.43150655" + ], + [ + "7847.00", + "0.02000000" + ], + [ + "7842.77", + "0.01280995" + ], + [ + "7842.30", + "0.00091800" + ], + [ + "7842.15", + "0.00100033" + ], + [ + "7840.13", + "0.00112000" + ], + [ + "7840.00", + "0.40459322" + ], + [ + "7838.91", + "0.00328234" + ], + [ + "7838.59", + "0.01407625" + ], + [ + "7832.00", + "1.00000000" + ], + [ + "7831.15", + "12.55000000" + ], + [ + "7828.33", + "0.00100032" + ], + [ + "7828.11", + "0.50000000" + ], + [ + "7827.00", + "0.02000000" + ], + [ + "7826.00", + "0.11768209" + ], + [ + "7824.00", + "0.03000000" + ], + [ + "7823.55", + "0.12277035" + ], + [ + "7820.00", + "0.41870407" + ], + [ + "7816.00", + "0.01727865" + ], + [ + "7815.43", + "1.51010000" + ], + [ + "7815.00", + "0.00750479" + ], + [ + "7814.52", + "0.00100031" + ], + [ + "7812.90", + "1.00000000" + ], + [ + "7811.62", + "12.55000000" + ], + [ + "7810.00", + "0.13608067" + ], + [ + "7807.74", + "0.40000000" + ], + [ + "7807.72", + "0.01306500" + ], + [ + "7807.00", + "0.02000000" + ], + [ + "7806.11", + "1.54850000" + ], + [ + "7804.68", + "1.36235694" + ], + [ + "7802.84", + "0.00177307" + ], + [ + "7801.01", + "0.43352592" + ], + [ + "7800.70", + "0.00101030" + ], + [ + "7800.10", + "0.03811144" + ], + [ + "7800.00", + "16.16853461" + ], + [ + "7793.00", + "0.10239060" + ], + [ + "7792.14", + "12.55000000" + ], + [ + "7789.00", + "0.00350000" + ], + [ + "7788.96", + "0.50000000" + ], + [ + "7788.00", + "0.50000000" + ], + [ + "7787.00", + "0.02000000" + ], + [ + "7786.88", + "0.00101029" + ], + [ + "7785.00", + "0.92434167" + ], + [ + "7780.00", + "1.05101414" + ], + [ + "7777.70", + "2.68333055" + ], + [ + "7777.00", + "0.10000000" + ], + [ + "7773.06", + "0.00101028" + ], + [ + "7773.00", + "0.02200000" + ], + [ + "7772.71", + "12.55000000" + ], + [ + "7771.00", + "0.10000000" + ], + [ + "7770.00", + "0.20000000" + ], + [ + "7768.00", + "0.05000000" + ], + [ + "7767.00", + "0.02000000" + ], + [ + "7765.00", + "0.10000000" + ], + [ + "7761.00", + "2.00000000" + ], + [ + "7760.00", + "0.02000000" + ], + [ + "7759.25", + "0.00101027" + ], + [ + "7758.72", + "0.01527572" + ], + [ + "7758.64", + "0.40000000" + ], + [ + "7757.73", + "0.06284014" + ], + [ + "7755.00", + "0.11000000" + ], + [ + "7753.32", + "12.55000000" + ], + [ + "7751.01", + "0.43632249" + ], + [ + "7751.00", + "0.02699522" + ], + [ + "7750.01", + "0.50000000" + ], + [ + "7750.00", + "11.42995872" + ], + [ + "7749.00", + "0.00350000" + ], + [ + "7748.34", + "0.50000000" + ], + [ + "7748.00", + "0.05000000" + ], + [ + "7747.00", + "0.02000000" + ], + [ + "7741.00", + "0.03500000" + ], + [ + "7737.00", + "1.01000000" + ], + [ + "7733.99", + "12.55000000" + ], + [ + "7733.00", + "0.13505535" + ], + [ + "7731.00", + "0.05000000" + ], + [ + "7730.00", + "0.00100000" + ], + [ + "7729.87", + "0.00191000" + ], + [ + "7729.76", + "0.05274738" + ], + [ + "7727.00", + "0.02000000" + ], + [ + "7726.17", + "0.08443693" + ], + [ + "7723.00", + "0.03000000" + ], + [ + "7722.28", + "0.00245005" + ], + [ + "7719.00", + "0.00350000" + ], + [ + "7714.70", + "12.55000000" + ], + [ + "7712.90", + "2.00000000" + ], + [ + "7711.25", + "0.50000000" + ], + [ + "7710.00", + "0.04628867" + ], + [ + "7708.00", + "0.39135398" + ], + [ + "7707.49", + "0.10000000" + ], + [ + "7707.00", + "0.02000000" + ], + [ + "7706.00", + "0.05000000" + ], + [ + "7705.22", + "0.03300000" + ], + [ + "7702.94", + "0.04909336" + ], + [ + "7701.97", + "0.00336147" + ], + [ + "7700.00", + "15.04826209" + ], + [ + "7697.72", + "0.50000000" + ], + [ + "7695.46", + "12.55000000" + ], + [ + "7690.00", + "0.33300429" + ], + [ + "7689.00", + "0.00304461" + ], + [ + "7687.00", + "0.02000000" + ], + [ + "7680.28", + "0.02516575" + ], + [ + "7680.00", + "0.67502308" + ], + [ + "7676.27", + "12.55000000" + ], + [ + "7673.68", + "0.00514405" + ], + [ + "7672.69", + "0.50000000" + ], + [ + "7672.00", + "0.01500000" + ], + [ + "7670.68", + "0.00130000" + ], + [ + "7670.00", + "0.00200000" + ], + [ + "7667.00", + "0.02000000" + ], + [ + "7665.38", + "0.04000000" + ], + [ + "7661.78", + "2.00000000" + ], + [ + "7661.40", + "0.10800000" + ], + [ + "7659.58", + "0.00977208" + ], + [ + "7659.00", + "0.00430000" + ], + [ + "7657.13", + "12.55000000" + ], + [ + "7653.33", + "0.00362951" + ], + [ + "7651.00", + "0.32085266" + ], + [ + "7650.00", + "1.37004104" + ], + [ + "7649.00", + "0.00350000" + ], + [ + "7647.00", + "0.02000000" + ], + [ + "7645.00", + "0.03888300" + ], + [ + "7644.00", + "0.00287132" + ], + [ + "7642.83", + "0.02000000" + ], + [ + "7641.00", + "1.34185000" + ], + [ + "7640.00", + "0.14044436" + ], + [ + "7638.03", + "12.55000000" + ], + [ + "7637.00", + "1.00043996" + ], + [ + "7633.00", + "2.76762531" + ], + [ + "7632.00", + "0.28000000" + ], + [ + "7631.37", + "0.33166129" + ], + [ + "7631.21", + "0.00352499" + ], + [ + "7630.00", + "0.14094937" + ], + [ + "7629.00", + "0.08143924" + ], + [ + "7627.00", + "0.09000000" + ], + [ + "7624.00", + "0.01500000" + ], + [ + "7622.00", + "0.14000000" + ], + [ + "7621.00", + "0.07000000" + ], + [ + "7620.00", + "0.97800000" + ], + [ + "7619.99", + "0.01453900" + ], + [ + "7619.00", + "0.00350000" + ], + [ + "7618.99", + "12.55000000" + ], + [ + "7613.86", + "0.32754900" + ], + [ + "7611.99", + "0.00500000" + ], + [ + "7610.00", + "2.00000000" + ], + [ + "7607.00", + "0.02000000" + ], + [ + "7606.40", + "1.73618268" + ], + [ + "7602.18", + "0.70958593" + ], + [ + "7602.00", + "0.13578926" + ], + [ + "7601.16", + "0.50000000" + ], + [ + "7600.91", + "1.03359466" + ], + [ + "7600.00", + "6.07444813" + ], + [ + "7599.99", + "12.55000000" + ], + [ + "7595.00", + "0.00094504" + ], + [ + "7590.00", + "0.00650000" + ], + [ + "7589.00", + "0.00340000" + ], + [ + "7588.66", + "0.60000000" + ], + [ + "7587.00", + "0.02000000" + ], + [ + "7581.03", + "12.55000000" + ], + [ + "7580.00", + "0.30365171" + ], + [ + "7577.00", + "0.04200000" + ], + [ + "7571.00", + "0.10000000" + ], + [ + "7569.43", + "0.10000000" + ], + [ + "7567.00", + "0.02000000" + ], + [ + "7562.13", + "12.55000000" + ], + [ + "7560.25", + "0.08000000" + ], + [ + "7560.00", + "0.20170639" + ], + [ + "7558.63", + "0.02000000" + ], + [ + "7557.00", + "1.54634511" + ], + [ + "7554.00", + "1.00000000" + ], + [ + "7552.00", + "0.95105135" + ], + [ + "7551.55", + "5.00000000" + ], + [ + "7551.00", + "5.39823788" + ], + [ + "7550.00", + "1.40561084" + ], + [ + "7549.00", + "0.00237162" + ], + [ + "7547.00", + "0.03000000" + ], + [ + "7545.85", + "0.08531875" + ], + [ + "7543.27", + "12.55000000" + ], + [ + "7541.19", + "0.05406635" + ], + [ + "7541.15", + "1.50950000" + ], + [ + "7541.00", + "0.02000000" + ], + [ + "7540.00", + "0.09010875" + ], + [ + "7535.57", + "0.00200000" + ], + [ + "7535.14", + "0.50000000" + ], + [ + "7535.00", + "0.05000000" + ], + [ + "7533.00", + "0.10000000" + ], + [ + "7530.00", + "0.16729748" + ], + [ + "7528.00", + "0.11408670" + ], + [ + "7527.00", + "0.02000000" + ], + [ + "7524.46", + "12.55000000" + ], + [ + "7521.19", + "0.02762861" + ], + [ + "7520.00", + "0.20015427" + ], + [ + "7515.00", + "1.00000000" + ], + [ + "7512.00", + "1.00000000" + ], + [ + "7511.06", + "0.09120283" + ], + [ + "7511.00", + "0.40000000" + ], + [ + "7508.90", + "2.79444792" + ], + [ + "7507.23", + "0.79617959" + ], + [ + "7507.00", + "0.02000000" + ], + [ + "7505.69", + "12.55000000" + ], + [ + "7505.00", + "0.20000000" + ], + [ + "7504.00", + "1.32025852" + ], + [ + "7503.51", + "0.00106350" + ], + [ + "7502.83", + "1.16399031" + ], + [ + "7502.02", + "0.01671950" + ], + [ + "7502.00", + "1.00000000" + ], + [ + "7501.01", + "0.90173057" + ], + [ + "7501.00", + "0.50000000" + ], + [ + "7500.88", + "3.99237721" + ], + [ + "7500.59", + "0.02902305" + ], + [ + "7500.02", + "0.08637377" + ], + [ + "7500.00", + "57.22523149" + ], + [ + "7498.00", + "0.14000000" + ], + [ + "7497.46", + "0.29772761" + ], + [ + "7496.86", + "0.04793373" + ], + [ + "7496.60", + "0.03500000" + ], + [ + "7494.00", + "0.04200000" + ], + [ + "7493.00", + "1.00883536" + ], + [ + "7492.00", + "0.14000000" + ], + [ + "7490.00", + "1.10000000" + ], + [ + "7488.00", + "8.32223558" + ], + [ + "7487.00", + "0.02000000" + ], + [ + "7486.98", + "12.55000000" + ], + [ + "7486.13", + "0.33778400" + ], + [ + "7486.00", + "1.00000000" + ], + [ + "7484.21", + "0.00583685" + ], + [ + "7482.00", + "0.22400000" + ], + [ + "7480.00", + "0.10000000" + ], + [ + "7479.18", + "0.18489534" + ], + [ + "7475.00", + "0.07000000" + ], + [ + "7474.74", + "2.88586894" + ], + [ + "7471.58", + "0.00537123" + ], + [ + "7469.11", + "0.50000000" + ], + [ + "7468.31", + "12.55000000" + ], + [ + "7467.00", + "0.02000000" + ], + [ + "7466.67", + "0.00210000" + ], + [ + "7464.00", + "0.04500000" + ], + [ + "7460.00", + "1.00000000" + ], + [ + "7459.97", + "0.50000000" + ], + [ + "7458.00", + "0.07000000" + ], + [ + "7456.00", + "0.11359798" + ], + [ + "7455.00", + "1.00000000" + ], + [ + "7452.64", + "0.08567800" + ], + [ + "7451.00", + "0.25977814" + ], + [ + "7450.00", + "2.49153177" + ], + [ + "7449.68", + "5.34832583" + ], + [ + "7448.00", + "5.00000000" + ], + [ + "7447.00", + "0.62003088" + ], + [ + "7446.00", + "0.02686366" + ], + [ + "7444.29", + "0.08760000" + ], + [ + "7444.00", + "1.00000000" + ], + [ + "7443.00", + "0.16000000" + ], + [ + "7440.00", + "0.11950672" + ], + [ + "7439.58", + "0.02000000" + ], + [ + "7436.00", + "0.00430000" + ], + [ + "7433.00", + "0.27000000" + ], + [ + "7431.10", + "12.55000000" + ], + [ + "7430.00", + "0.00100000" + ], + [ + "7428.44", + "0.02692355" + ], + [ + "7427.00", + "0.57000000" + ], + [ + "7426.66", + "0.37200000" + ], + [ + "7425.00", + "0.10000000" + ], + [ + "7424.46", + "0.13350735" + ], + [ + "7424.00", + "0.03800000" + ], + [ + "7423.14", + "0.07000000" + ], + [ + "7423.00", + "0.01000000" + ], + [ + "7422.67", + "0.50000000" + ], + [ + "7422.45", + "0.00070000" + ], + [ + "7422.05", + "0.00900000" + ], + [ + "7421.00", + "1.13295990" + ], + [ + "7420.17", + "0.00310059" + ], + [ + "7420.00", + "0.50000000" + ], + [ + "7419.99", + "2.05618762" + ], + [ + "7419.97", + "0.00500000" + ], + [ + "7419.00", + "0.00500000" + ], + [ + "7418.38", + "0.00093551" + ], + [ + "7418.00", + "0.10000000" + ], + [ + "7415.00", + "2.03985442" + ], + [ + "7414.05", + "0.02000000" + ], + [ + "7414.00", + "0.03900000" + ], + [ + "7413.00", + "0.81474617" + ], + [ + "7412.57", + "12.55000000" + ], + [ + "7412.18", + "0.04000000" + ], + [ + "7411.89", + "0.03301807" + ], + [ + "7411.00", + "0.02000000" + ], + [ + "7410.00", + "0.00088000" + ], + [ + "7408.40", + "0.10000000" + ], + [ + "7408.00", + "13.32348271" + ], + [ + "7407.00", + "0.02000000" + ], + [ + "7406.00", + "0.30000000" + ], + [ + "7405.00", + "0.10000000" + ], + [ + "7404.70", + "0.01367915" + ], + [ + "7404.65", + "0.11774567" + ], + [ + "7401.00", + "0.15000000" + ], + [ + "7400.39", + "0.30662033" + ], + [ + "7400.38", + "0.00500000" + ], + [ + "7400.00", + "19.32149034" + ], + [ + "7399.00", + "0.26000000" + ], + [ + "7397.00", + "0.02500000" + ], + [ + "7396.44", + "0.11100000" + ], + [ + "7395.00", + "0.09000000" + ], + [ + "7394.09", + "12.55000000" + ], + [ + "7393.63", + "0.00107931" + ], + [ + "7393.60", + "0.04000000" + ], + [ + "7393.00", + "0.05000000" + ], + [ + "7392.00", + "0.10800000" + ], + [ + "7391.30", + "0.02024339" + ], + [ + "7388.89", + "0.00319298" + ], + [ + "7388.88", + "0.03273705" + ], + [ + "7388.86", + "0.10000000" + ], + [ + "7388.44", + "0.00643234" + ], + [ + "7388.00", + "1.25500000" + ], + [ + "7387.00", + "0.02000000" + ], + [ + "7386.86", + "0.08444300" + ], + [ + "7385.55", + "0.50000000" + ], + [ + "7385.00", + "0.07000000" + ], + [ + "7380.00", + "0.02800000" + ], + [ + "7379.97", + "0.00500000" + ], + [ + "7378.02", + "0.15000000" + ], + [ + "7377.00", + "0.07500000" + ], + [ + "7375.65", + "12.55000000" + ], + [ + "7370.00", + "0.20000000" + ], + [ + "7369.00", + "0.00500000" + ], + [ + "7367.00", + "0.02000000" + ], + [ + "7366.67", + "0.00746604" + ], + [ + "7365.00", + "0.14000000" + ], + [ + "7364.00", + "0.06500000" + ], + [ + "7362.00", + "1.55897854" + ], + [ + "7360.00", + "0.06000000" + ], + [ + "7359.05", + "0.02408055" + ], + [ + "7357.26", + "12.55000000" + ], + [ + "7357.00", + "1.47305367" + ], + [ + "7356.00", + "0.19000000" + ], + [ + "7355.43", + "0.03000000" + ], + [ + "7355.00", + "0.49909451" + ], + [ + "7354.00", + "0.02000000" + ], + [ + "7353.00", + "0.18000000" + ], + [ + "7352.49", + "0.00068100" + ], + [ + "7351.51", + "5.44932130" + ], + [ + "7351.06", + "0.12020000" + ], + [ + "7350.99", + "0.02000000" + ], + [ + "7350.69", + "0.04176002" + ], + [ + "7350.00", + "2.46391559" + ], + [ + "7347.00", + "0.02000000" + ], + [ + "7345.00", + "0.28000000" + ], + [ + "7340.00", + "20.10000000" + ], + [ + "7339.00", + "0.01000000" + ], + [ + "7338.95", + "0.00600615" + ], + [ + "7338.91", + "12.55000000" + ], + [ + "7334.00", + "0.20564313" + ], + [ + "7333.66", + "0.13592654" + ], + [ + "7333.14", + "0.02040394" + ], + [ + "7333.00", + "0.02250000" + ], + [ + "7327.00", + "0.02000000" + ], + [ + "7325.00", + "10.00000000" + ], + [ + "7324.00", + "0.05000000" + ], + [ + "7323.23", + "0.02731035" + ], + [ + "7321.00", + "1.38804160" + ], + [ + "7320.61", + "12.55000000" + ], + [ + "7319.00", + "1.00500000" + ], + [ + "7318.73", + "0.50000000" + ], + [ + "7317.37", + "0.04259727" + ], + [ + "7315.85", + "0.02000000" + ], + [ + "7315.00", + "0.16118183" + ], + [ + "7314.85", + "0.00243963" + ], + [ + "7313.14", + "0.01500000" + ], + [ + "7312.00", + "1.00000000" + ], + [ + "7311.00", + "1.40000000" + ], + [ + "7310.00", + "1.12589000" + ], + [ + "7309.00", + "0.27000000" + ], + [ + "7308.86", + "0.01051892" + ], + [ + "7308.00", + "0.02000000" + ], + [ + "7307.00", + "0.02000000" + ], + [ + "7306.07", + "0.01000000" + ], + [ + "7306.00", + "0.00100000" + ], + [ + "7305.00", + "0.10000000" + ], + [ + "7303.23", + "0.10000000" + ], + [ + "7302.35", + "12.55000000" + ], + [ + "7301.00", + "3.00472456" + ], + [ + "7300.00", + "13.85415009" + ], + [ + "7298.63", + "0.11562580" + ], + [ + "7297.85", + "0.22340000" + ], + [ + "7294.74", + "0.00656564" + ], + [ + "7292.00", + "0.01500000" + ], + [ + "7289.97", + "0.00500000" + ], + [ + "7289.00", + "0.05000000" + ], + [ + "7288.00", + "0.09069841" + ], + [ + "7287.00", + "0.22000000" + ], + [ + "7284.14", + "12.55000000" + ], + [ + "7280.63", + "0.02055110" + ], + [ + "7280.00", + "0.80503663" + ], + [ + "7279.00", + "0.00500000" + ], + [ + "7278.00", + "0.04000000" + ], + [ + "7277.00", + "0.20000000" + ], + [ + "7276.54", + "0.22035472" + ], + [ + "7275.06", + "0.02000000" + ], + [ + "7272.00", + "0.05000000" + ], + [ + "7270.00", + "0.15502063" + ], + [ + "7268.56", + "0.15000000" + ], + [ + "7267.00", + "0.03000000" + ], + [ + "7266.98", + "0.00365901" + ], + [ + "7266.88", + "1.66000000" + ], + [ + "7266.66", + "0.01000000" + ], + [ + "7265.98", + "12.55000000" + ], + [ + "7265.00", + "0.05000000" + ], + [ + "7263.00", + "0.17876634" + ], + [ + "7260.00", + "2.27534430" + ], + [ + "7259.58", + "0.00381702" + ], + [ + "7259.00", + "1.00000000" + ], + [ + "7258.19", + "1.00000000" + ], + [ + "7258.00", + "0.00723201" + ], + [ + "7256.69", + "0.00637756" + ], + [ + "7255.00", + "0.44100000" + ], + [ + "7253.81", + "0.02000000" + ], + [ + "7253.65", + "0.50000000" + ], + [ + "7253.00", + "0.05000000" + ], + [ + "7252.49", + "0.00069100" + ], + [ + "7252.00", + "0.50000000" + ], + [ + "7251.50", + "0.00882510" + ], + [ + "7251.00", + "0.53885053" + ], + [ + "7250.62", + "0.06164317" + ], + [ + "7250.00", + "4.58603346" + ], + [ + "7249.00", + "0.20000000" + ], + [ + "7248.68", + "0.50000000" + ], + [ + "7248.17", + "0.01827391" + ], + [ + "7248.00", + "0.09000000" + ], + [ + "7247.86", + "10.08102638" + ], + [ + "7247.00", + "0.02000000" + ], + [ + "7246.12", + "0.00408770" + ], + [ + "7245.02", + "0.06471255" + ], + [ + "7242.11", + "0.00095688" + ], + [ + "7242.00", + "0.96218724" + ], + [ + "7241.85", + "0.00500000" + ], + [ + "7241.00", + "0.00500000" + ], + [ + "7240.64", + "0.07072615" + ], + [ + "7240.00", + "0.00580095" + ], + [ + "7239.97", + "0.00700000" + ], + [ + "7239.66", + "0.50000000" + ], + [ + "7239.00", + "0.11300000" + ], + [ + "7237.11", + "0.50000000" + ], + [ + "7236.21", + "0.02448934" + ], + [ + "7236.00", + "0.20000000" + ], + [ + "7235.00", + "0.01000000" + ], + [ + "7234.45", + "0.10741245" + ], + [ + "7233.69", + "0.02068446" + ], + [ + "7233.00", + "0.17943454" + ], + [ + "7232.70", + "0.10800000" + ], + [ + "7230.00", + "0.01708941" + ], + [ + "7229.78", + "12.55000000" + ], + [ + "7228.00", + "1.03087990" + ], + [ + "7227.71", + "0.25000000" + ], + [ + "7227.00", + "0.05800000" + ], + [ + "7225.71", + "0.08570281" + ], + [ + "7225.00", + "0.21383875" + ], + [ + "7224.55", + "0.00116683" + ], + [ + "7224.00", + "1.38000000" + ], + [ + "7222.00", + "0.16896289" + ], + [ + "7221.00", + "1.02084475" + ], + [ + "7220.41", + "0.03400000" + ], + [ + "7220.00", + "0.73818975" + ], + [ + "7219.00", + "0.18430000" + ], + [ + "7217.88", + "0.02770896" + ], + [ + "7217.00", + "0.30000000" + ], + [ + "7215.00", + "17.48299791" + ], + [ + "7213.00", + "0.01000000" + ], + [ + "7212.00", + "0.31976211" + ], + [ + "7211.75", + "8.95616202" + ], + [ + "7211.08", + "1.44860270" + ], + [ + "7211.00", + "0.30000000" + ], + [ + "7210.00", + "0.24690000" + ], + [ + "7209.00", + "0.33000000" + ], + [ + "7208.00", + "1.22000000" + ], + [ + "7207.00", + "0.02000000" + ], + [ + "7206.60", + "1.00000000" + ], + [ + "7206.32", + "0.00666445" + ], + [ + "7205.00", + "2.35000000" + ], + [ + "7204.48", + "0.10000000" + ], + [ + "7204.23", + "6.60000000" + ], + [ + "7204.00", + "0.40000000" + ], + [ + "7203.64", + "0.00600000" + ], + [ + "7203.46", + "0.50000000" + ], + [ + "7202.00", + "0.09000000" + ], + [ + "7201.16", + "0.15037716" + ], + [ + "7201.00", + "0.98464834" + ], + [ + "7200.50", + "0.10000000" + ], + [ + "7200.00", + "20.63338323" + ], + [ + "7196.64", + "0.06000000" + ], + [ + "7194.00", + "0.05000000" + ], + [ + "7193.77", + "12.55000000" + ], + [ + "7191.00", + "0.03600000" + ], + [ + "7189.97", + "0.02975957" + ], + [ + "7189.00", + "0.01000000" + ], + [ + "7188.32", + "0.76249182" + ], + [ + "7188.00", + "2.00000000" + ], + [ + "7187.00", + "0.07500000" + ], + [ + "7185.64", + "0.00111813" + ], + [ + "7185.00", + "0.22946555" + ], + [ + "7184.99", + "0.20051947" + ], + [ + "7183.35", + "0.00500000" + ], + [ + "7183.30", + "0.18000000" + ], + [ + "7181.22", + "0.05000000" + ], + [ + "7180.00", + "1.34730000" + ], + [ + "7177.77", + "0.05000000" + ], + [ + "7177.00", + "0.51419525" + ], + [ + "7175.83", + "12.55000000" + ], + [ + "7175.55", + "0.50000000" + ], + [ + "7175.00", + "6.00000000" + ], + [ + "7171.82", + "0.10000000" + ], + [ + "7171.80", + "0.01226960" + ], + [ + "7171.77", + "0.08000000" + ], + [ + "7171.57", + "0.15000000" + ], + [ + "7170.00", + "0.04787656" + ], + [ + "7168.23", + "0.01499999" + ], + [ + "7167.44", + "0.50000000" + ], + [ + "7167.00", + "0.04000000" + ], + [ + "7166.04", + "0.12617234" + ], + [ + "7166.00", + "0.30000000" + ], + [ + "7165.00", + "0.10200000" + ], + [ + "7164.00", + "0.02500000" + ], + [ + "7160.00", + "0.20000000" + ], + [ + "7159.00", + "0.11600000" + ], + [ + "7158.00", + "0.11812517" + ], + [ + "7157.93", + "12.55000000" + ], + [ + "7156.00", + "0.10000000" + ], + [ + "7152.97", + "0.00500000" + ], + [ + "7152.49", + "0.00070000" + ], + [ + "7151.06", + "0.12398022" + ], + [ + "7150.25", + "0.00783120" + ], + [ + "7150.00", + "3.14897868" + ], + [ + "7149.00", + "5.02993566" + ], + [ + "7148.52", + "0.50000000" + ], + [ + "7147.83", + "0.10000000" + ], + [ + "7147.00", + "0.02000000" + ], + [ + "7140.08", + "12.55000000" + ], + [ + "7139.97", + "0.01000000" + ], + [ + "7138.98", + "0.00108769" + ], + [ + "7137.60", + "0.02000000" + ], + [ + "7136.84", + "0.00150000" + ], + [ + "7132.50", + "0.56838275" + ], + [ + "7132.00", + "0.18000000" + ], + [ + "7131.60", + "0.50000000" + ], + [ + "7131.00", + "0.01000000" + ], + [ + "7130.83", + "0.15000000" + ], + [ + "7130.50", + "0.02498352" + ], + [ + "7130.47", + "0.05000000" + ], + [ + "7130.00", + "0.14025245" + ], + [ + "7129.00", + "0.10000000" + ], + [ + "7128.20", + "1.51028029" + ], + [ + "7128.00", + "0.02500000" + ], + [ + "7127.05", + "0.00200000" + ], + [ + "7127.00", + "0.02000000" + ], + [ + "7126.00", + "0.00500000" + ], + [ + "7125.00", + "8.31795930" + ], + [ + "7124.59", + "0.00473992" + ], + [ + "7124.58", + "0.02000000" + ], + [ + "7123.00", + "0.02553418" + ], + [ + "7122.28", + "12.55000000" + ], + [ + "7121.00", + "0.22000000" + ], + [ + "7120.00", + "0.66000000" + ], + [ + "7119.00", + "0.00500000" + ], + [ + "7118.00", + "0.60000000" + ], + [ + "7117.98", + "0.03447396" + ], + [ + "7112.54", + "0.02811934" + ], + [ + "7112.00", + "0.57901434" + ], + [ + "7111.00", + "0.70160034" + ], + [ + "7110.00", + "0.00092000" + ], + [ + "7109.97", + "0.00500000" + ], + [ + "7109.00", + "0.15000000" + ], + [ + "7108.00", + "0.02000000" + ], + [ + "7107.00", + "0.02000000" + ], + [ + "7105.26", + "0.00733335" + ], + [ + "7105.00", + "0.19011963" + ], + [ + "7104.52", + "12.55000000" + ], + [ + "7104.00", + "0.30000000" + ], + [ + "7103.00", + "0.61740884" + ], + [ + "7102.75", + "0.00252133" + ], + [ + "7101.00", + "1.03404027" + ], + [ + "7100.28", + "2.63019346" + ], + [ + "7100.21", + "0.60098645" + ], + [ + "7100.00", + "7.97865743" + ], + [ + "7095.94", + "0.50000000" + ], + [ + "7090.00", + "0.17000000" + ], + [ + "7089.97", + "0.00500000" + ], + [ + "7088.68", + "0.50000000" + ], + [ + "7088.38", + "0.69750351" + ], + [ + "7088.00", + "0.09325762" + ], + [ + "7087.00", + "0.02000000" + ], + [ + "7086.80", + "12.55000000" + ], + [ + "7086.10", + "10.00000000" + ], + [ + "7086.00", + "1.00000000" + ], + [ + "7083.33", + "0.00917649" + ], + [ + "7083.00", + "0.11000000" + ], + [ + "7082.26", + "6.38093066" + ], + [ + "7080.00", + "3.70801906" + ], + [ + "7078.00", + "0.44000000" + ], + [ + "7076.00", + "0.22000000" + ], + [ + "7075.25", + "0.05000000" + ], + [ + "7074.30", + "0.04000000" + ], + [ + "7074.00", + "0.10000000" + ], + [ + "7073.68", + "0.00734749" + ], + [ + "7073.06", + "0.06327813" + ], + [ + "7070.00", + "0.35703359" + ], + [ + "7069.69", + "0.54218643" + ], + [ + "7069.13", + "12.55000000" + ], + [ + "7068.00", + "0.50600000" + ], + [ + "7067.00", + "0.02000000" + ], + [ + "7066.21", + "0.10000000" + ], + [ + "7066.00", + "5.00000000" + ], + [ + "7064.87", + "0.06952994" + ], + [ + "7062.70", + "0.01406454" + ], + [ + "7061.70", + "0.01500000" + ], + [ + "7060.60", + "2.00000000" + ], + [ + "7060.46", + "0.50000000" + ], + [ + "7060.00", + "0.52715721" + ], + [ + "7059.88", + "0.00084279" + ], + [ + "7058.35", + "0.07993301" + ], + [ + "7055.00", + "5.01533998" + ], + [ + "7052.84", + "0.12771735" + ], + [ + "7052.49", + "0.00070970" + ], + [ + "7051.50", + "12.55000000" + ], + [ + "7051.00", + "0.50000000" + ], + [ + "7050.57", + "0.52595180" + ], + [ + "7050.00", + "20.38036170" + ], + [ + "7047.00", + "0.02000000" + ], + [ + "7046.52", + "0.48484358" + ], + [ + "7046.01", + "3.11453263" + ], + [ + "7045.00", + "7.30093115" + ], + [ + "7040.38", + "0.12936937" + ], + [ + "7040.00", + "20.27458948" + ], + [ + "7038.00", + "0.01000000" + ], + [ + "7035.65", + "0.00960928" + ], + [ + "7035.00", + "0.02562189" + ], + [ + "7034.98", + "0.02236200" + ], + [ + "7034.28", + "0.30000000" + ], + [ + "7033.91", + "12.55000000" + ], + [ + "7033.53", + "0.44240516" + ], + [ + "7032.24", + "0.30000000" + ], + [ + "7030.00", + "0.01070554" + ], + [ + "7027.13", + "0.01700000" + ], + [ + "7027.00", + "0.05000000" + ], + [ + "7026.10", + "0.00155000" + ], + [ + "7025.25", + "0.00910930" + ], + [ + "7025.00", + "0.03000000" + ], + [ + "7024.41", + "0.07732464" + ], + [ + "7024.00", + "0.54790899" + ], + [ + "7023.89", + "0.10000000" + ], + [ + "7023.00", + "0.06600000" + ], + [ + "7022.00", + "0.50000000" + ], + [ + "7021.00", + "0.04300000" + ], + [ + "7020.62", + "0.02848751" + ], + [ + "7020.00", + "0.40000000" + ], + [ + "7019.00", + "0.00500000" + ], + [ + "7016.37", + "12.55000000" + ], + [ + "7015.79", + "0.00111278" + ], + [ + "7015.73", + "0.02954968" + ], + [ + "7014.86", + "0.01500000" + ], + [ + "7014.22", + "0.50000000" + ], + [ + "7013.19", + "0.00300000" + ], + [ + "7013.00", + "0.00800000" + ], + [ + "7012.00", + "0.10000000" + ], + [ + "7011.51", + "1.00000000" + ], + [ + "7011.22", + "0.25000000" + ], + [ + "7011.06", + "0.11950000" + ], + [ + "7011.00", + "0.62690458" + ], + [ + "7010.00", + "0.27491098" + ], + [ + "7009.00", + "0.05430000" + ], + [ + "7008.82", + "0.02000000" + ], + [ + "7008.00", + "0.06600000" + ], + [ + "7007.00", + "7.02000000" + ], + [ + "7006.00", + "0.30000000" + ], + [ + "7005.28", + "0.13877960" + ], + [ + "7005.18", + "0.07300000" + ], + [ + "7005.00", + "4.14020413" + ], + [ + "7004.00", + "0.10000000" + ], + [ + "7003.90", + "1.30000000" + ], + [ + "7002.00", + "2.21352350" + ], + [ + "7001.00", + "6.75183973" + ], + [ + "7000.97", + "0.40000000" + ], + [ + "7000.68", + "0.22886583" + ], + [ + "7000.14", + "0.00670000" + ], + [ + "7000.00", + "40.97819268" + ], + [ + "6999.00", + "0.26123300" + ], + [ + "6998.87", + "12.55000000" + ], + [ + "6998.00", + "0.50000000" + ], + [ + "6996.00", + "0.13200000" + ], + [ + "6994.01", + "0.01000000" + ], + [ + "6987.00", + "0.06500000" + ], + [ + "6986.00", + "1.00000000" + ], + [ + "6982.56", + "1.00000000" + ], + [ + "6981.42", + "12.55000000" + ], + [ + "6980.00", + "0.16624212" + ], + [ + "6979.14", + "0.50000000" + ], + [ + "6979.00", + "0.00500000" + ], + [ + "6977.00", + "0.29400000" + ], + [ + "6976.00", + "0.03517775" + ], + [ + "6975.09", + "0.14389348" + ], + [ + "6973.97", + "0.00500000" + ], + [ + "6971.00", + "1.00000000" + ], + [ + "6968.88", + "0.50000000" + ], + [ + "6967.00", + "0.03000000" + ], + [ + "6966.00", + "0.33300000" + ], + [ + "6964.01", + "12.06279382" + ], + [ + "6960.00", + "2.01000000" + ], + [ + "6959.60", + "0.17522457" + ], + [ + "6959.44", + "0.01499999" + ], + [ + "6959.00", + "0.10000000" + ], + [ + "6958.00", + "0.04400000" + ], + [ + "6955.00", + "25.02863968" + ], + [ + "6954.00", + "0.04600000" + ], + [ + "6953.00", + "0.35000000" + ], + [ + "6952.49", + "0.00072000" + ], + [ + "6952.00", + "0.00800000" + ], + [ + "6951.00", + "0.04000000" + ], + [ + "6950.00", + "4.00992724" + ], + [ + "6949.28", + "0.02000000" + ], + [ + "6947.00", + "0.02000000" + ], + [ + "6946.64", + "50.20000000" + ], + [ + "6944.09", + "0.25000000" + ], + [ + "6943.00", + "0.02500000" + ], + [ + "6941.05", + "0.00805658" + ], + [ + "6940.00", + "0.00500000" + ], + [ + "6939.14", + "0.02000000" + ], + [ + "6936.42", + "0.05738360" + ], + [ + "6936.00", + "1.38491061" + ], + [ + "6935.11", + "0.05000000" + ], + [ + "6935.00", + "0.23560201" + ], + [ + "6933.23", + "0.09406438" + ], + [ + "6933.00", + "0.34000000" + ], + [ + "6932.00", + "0.50000000" + ], + [ + "6931.02", + "0.50000000" + ], + [ + "6930.00", + "10.03508225" + ], + [ + "6929.32", + "12.55000000" + ], + [ + "6929.00", + "0.11500000" + ], + [ + "6927.00", + "0.03000000" + ], + [ + "6926.00", + "0.12400000" + ], + [ + "6925.00", + "0.03124331" + ], + [ + "6924.38", + "0.33087495" + ], + [ + "6923.00", + "1.02000000" + ], + [ + "6920.00", + "0.50000000" + ], + [ + "6919.00", + "0.04400000" + ], + [ + "6917.00", + "0.11127643" + ], + [ + "6915.79", + "0.00814307" + ], + [ + "6915.00", + "0.52640219" + ], + [ + "6912.04", + "12.55000000" + ], + [ + "6912.00", + "0.11000000" + ], + [ + "6911.00", + "3.16719534" + ], + [ + "6910.00", + "0.39837000" + ], + [ + "6909.00", + "0.15000000" + ], + [ + "6908.00", + "0.03271424" + ], + [ + "6907.00", + "0.02000000" + ], + [ + "6906.67", + "0.00659587" + ], + [ + "6905.00", + "3.00039537" + ], + [ + "6904.67", + "0.14465137" + ], + [ + "6904.20", + "0.11385389" + ], + [ + "6903.00", + "1.00000000" + ], + [ + "6902.57", + "0.00112566" + ], + [ + "6901.00", + "0.05800000" + ], + [ + "6900.00", + "12.58427965" + ], + [ + "6899.00", + "0.50000000" + ], + [ + "6898.00", + "0.05000000" + ], + [ + "6896.36", + "0.50000000" + ], + [ + "6896.00", + "0.14000000" + ], + [ + "6894.80", + "2.50207259" + ], + [ + "6892.22", + "0.05300000" + ], + [ + "6891.00", + "0.10000000" + ], + [ + "6889.20", + "1.23092376" + ], + [ + "6888.88", + "0.05568000" + ], + [ + "6888.00", + "0.24965912" + ], + [ + "6887.00", + "0.13000000" + ], + [ + "6886.00", + "21.60463259" + ], + [ + "6880.00", + "10.08682878" + ], + [ + "6879.97", + "0.00550000" + ], + [ + "6879.00", + "0.03500000" + ], + [ + "6877.61", + "12.55000000" + ], + [ + "6875.00", + "1.85000000" + ], + [ + "6873.24", + "0.00200000" + ], + [ + "6871.52", + "0.10000000" + ], + [ + "6870.00", + "0.97204076" + ], + [ + "6869.98", + "0.01000000" + ], + [ + "6868.88", + "0.50000000" + ], + [ + "6868.13", + "0.01500000" + ], + [ + "6868.00", + "0.10200000" + ], + [ + "6867.00", + "0.02000000" + ], + [ + "6866.00", + "1.33300000" + ], + [ + "6865.00", + "0.05693168" + ], + [ + "6861.11", + "0.00414035" + ], + [ + "6860.46", + "12.55000000" + ], + [ + "6860.00", + "0.76200000" + ], + [ + "6859.97", + "0.00500000" + ], + [ + "6859.00", + "0.05000000" + ], + [ + "6858.00", + "10.00000000" + ], + [ + "6856.00", + "1.97000000" + ], + [ + "6855.92", + "0.99000000" + ], + [ + "6852.49", + "0.00073100" + ], + [ + "6852.00", + "3.16987147" + ], + [ + "6851.34", + "0.17104099" + ], + [ + "6851.00", + "1.00800000" + ], + [ + "6850.82", + "0.04231579" + ], + [ + "6850.65", + "0.50000000" + ], + [ + "6850.00", + "17.64020664" + ], + [ + "6849.83", + "0.00184050" + ], + [ + "6848.45", + "0.05465616" + ], + [ + "6848.00", + "0.00480000" + ], + [ + "6847.00", + "0.02000000" + ], + [ + "6844.44", + "0.00270000" + ], + [ + "6843.35", + "12.55000000" + ], + [ + "6841.00", + "0.00500000" + ], + [ + "6840.00", + "0.11695900" + ], + [ + "6839.47", + "0.00160000" + ], + [ + "6839.00", + "0.00500000" + ], + [ + "6838.11", + "0.02940000" + ], + [ + "6836.41", + "0.57647654" + ], + [ + "6835.00", + "0.39300000" + ], + [ + "6834.00", + "0.02018191" + ], + [ + "6833.00", + "0.08000000" + ], + [ + "6830.11", + "0.15000000" + ], + [ + "6830.00", + "0.48184480" + ], + [ + "6829.86", + "1.60000000" + ], + [ + "6827.00", + "0.02000000" + ], + [ + "6826.28", + "12.55000000" + ], + [ + "6826.00", + "0.25255928" + ], + [ + "6825.00", + "0.18500000" + ], + [ + "6824.68", + "0.76064665" + ], + [ + "6822.00", + "1.04000000" + ], + [ + "6821.00", + "1.00000000" + ], + [ + "6820.00", + "0.45475733" + ], + [ + "6819.00", + "0.02000000" + ], + [ + "6818.50", + "0.05000000" + ], + [ + "6818.00", + "0.04400000" + ], + [ + "6817.00", + "0.05800000" + ], + [ + "6816.39", + "0.50000000" + ], + [ + "6816.23", + "0.00080000" + ], + [ + "6816.00", + "0.25000000" + ], + [ + "6815.00", + "0.73257519" + ], + [ + "6812.40", + "0.00340000" + ], + [ + "6812.16", + "0.05000000" + ], + [ + "6812.00", + "0.02930000" + ], + [ + "6811.89", + "0.00081182" + ], + [ + "6811.06", + "0.12280000" + ], + [ + "6811.00", + "0.03000000" + ], + [ + "6810.00", + "0.52864867" + ], + [ + "6809.26", + "12.55000000" + ], + [ + "6809.00", + "0.05000000" + ], + [ + "6808.42", + "0.00879330" + ], + [ + "6807.00", + "0.12000000" + ], + [ + "6805.05", + "0.00400000" + ], + [ + "6805.00", + "1.60430000" + ], + [ + "6804.34", + "0.04580900" + ], + [ + "6804.00", + "0.35505789" + ], + [ + "6803.69", + "0.35200783" + ], + [ + "6803.66", + "0.02603378" + ], + [ + "6803.00", + "0.35664413" + ], + [ + "6802.00", + "0.20000000" + ], + [ + "6801.83", + "0.25000000" + ], + [ + "6801.37", + "0.33800000" + ], + [ + "6801.34", + "0.19000000" + ], + [ + "6801.00", + "3.94928126" + ], + [ + "6800.82", + "0.21488879" + ], + [ + "6800.00", + "115.86131555" + ], + [ + "6799.00", + "0.26000000" + ], + [ + "6792.28", + "12.55000000" + ], + [ + "6791.00", + "0.26000000" + ], + [ + "6789.47", + "0.00127907" + ], + [ + "6789.00", + "1.53305156" + ], + [ + "6787.00", + "0.02000000" + ], + [ + "6786.00", + "1.00000000" + ], + [ + "6785.73", + "0.01500000" + ], + [ + "6785.00", + "0.06780398" + ], + [ + "6784.00", + "9.98391273" + ], + [ + "6781.00", + "0.30000000" + ], + [ + "6780.09", + "0.14288306" + ], + [ + "6780.00", + "1.01973548" + ], + [ + "6779.16", + "0.02731901" + ], + [ + "6779.00", + "0.50000000" + ], + [ + "6778.00", + "0.10000000" + ], + [ + "6777.09", + "0.00432781" + ], + [ + "6776.38", + "1.00000000" + ], + [ + "6775.34", + "12.55000000" + ], + [ + "6775.11", + "1.00000000" + ], + [ + "6775.00", + "0.10855498" + ], + [ + "6770.00", + "0.08025004" + ], + [ + "6769.69", + "1.20577013" + ], + [ + "6767.33", + "0.08313659" + ], + [ + "6767.00", + "0.13800000" + ], + [ + "6766.00", + "0.58000000" + ], + [ + "6765.87", + "0.02000000" + ], + [ + "6765.00", + "0.07800000" + ], + [ + "6763.24", + "0.09086230" + ], + [ + "6762.00", + "0.13000000" + ], + [ + "6760.00", + "0.05316124" + ], + [ + "6758.45", + "12.55000000" + ], + [ + "6756.74", + "0.01499999" + ], + [ + "6756.00", + "0.19000000" + ], + [ + "6755.00", + "1.10194818" + ], + [ + "6753.84", + "0.00118155" + ], + [ + "6753.13", + "0.01472206" + ], + [ + "6753.00", + "0.07800000" + ], + [ + "6751.74", + "0.05044685" + ], + [ + "6751.09", + "0.22852984" + ], + [ + "6751.00", + "0.00800000" + ], + [ + "6750.57", + "0.10500000" + ], + [ + "6750.01", + "1.00000000" + ], + [ + "6750.00", + "12.07613889" + ], + [ + "6749.00", + "0.00500000" + ], + [ + "6748.00", + "0.33000000" + ], + [ + "6747.00", + "0.02000000" + ], + [ + "6746.00", + "0.57800000" + ], + [ + "6745.78", + "0.09271722" + ], + [ + "6741.59", + "12.55000000" + ], + [ + "6741.00", + "0.29520249" + ], + [ + "6736.00", + "0.01500000" + ], + [ + "6734.34", + "0.00200501" + ], + [ + "6734.00", + "0.74433769" + ], + [ + "6733.45", + "0.50000000" + ], + [ + "6732.36", + "0.50000000" + ], + [ + "6732.10", + "0.00350000" + ], + [ + "6730.00", + "0.02000000" + ], + [ + "6727.00", + "0.02000000" + ], + [ + "6726.32", + "0.00899842" + ], + [ + "6725.00", + "0.18292788" + ], + [ + "6724.78", + "12.55000000" + ], + [ + "6721.00", + "0.25510000" + ], + [ + "6720.00", + "1.05570014" + ], + [ + "6717.00", + "1.03900000" + ], + [ + "6715.00", + "0.02000000" + ], + [ + "6712.00", + "0.01091030" + ], + [ + "6711.91", + "0.27499324" + ], + [ + "6711.00", + "0.29856653" + ], + [ + "6710.00", + "0.01097000" + ], + [ + "6708.01", + "12.55000000" + ], + [ + "6708.00", + "1.00000000" + ], + [ + "6707.00", + "0.02000000" + ], + [ + "6706.00", + "0.31350000" + ], + [ + "6705.00", + "0.50000000" + ], + [ + "6702.10", + "0.10800000" + ], + [ + "6701.00", + "0.56141023" + ], + [ + "6700.22", + "0.00290000" + ], + [ + "6700.00", + "62.22654648" + ], + [ + "6693.00", + "0.13200000" + ], + [ + "6691.28", + "12.55000000" + ], + [ + "6690.00", + "0.12670000" + ], + [ + "6689.99", + "0.05000000" + ], + [ + "6687.00", + "0.02000000" + ], + [ + "6686.13", + "0.00360000" + ], + [ + "6686.00", + "1.00000000" + ], + [ + "6684.64", + "7.08560852" + ], + [ + "6682.91", + "0.90261614" + ], + [ + "6681.00", + "0.05400000" + ], + [ + "6680.00", + "0.10000000" + ], + [ + "6677.00", + "1.00000000" + ], + [ + "6675.79", + "0.00955928" + ], + [ + "6674.60", + "12.55000000" + ], + [ + "6672.73", + "0.00600000" + ], + [ + "6670.00", + "0.07850374" + ], + [ + "6668.00", + "0.01490000" + ], + [ + "6667.93", + "3.00000000" + ], + [ + "6667.12", + "0.54028801" + ], + [ + "6667.00", + "0.02000000" + ], + [ + "6666.69", + "0.23141619" + ], + [ + "6666.66", + "0.14123337" + ], + [ + "6666.00", + "9.11539078" + ], + [ + "6663.00", + "0.44000000" + ], + [ + "6660.82", + "0.07528352" + ], + [ + "6660.00", + "7.96541141" + ], + [ + "6659.97", + "0.01000000" + ], + [ + "6659.54", + "0.05000000" + ], + [ + "6658.35", + "0.16801269" + ], + [ + "6657.95", + "12.55000000" + ], + [ + "6657.00", + "0.92401382" + ], + [ + "6656.00", + "0.13000000" + ], + [ + "6655.00", + "0.06060000" + ], + [ + "6653.84", + "0.06800960" + ], + [ + "6651.06", + "0.12570000" + ], + [ + "6650.00", + "8.01711165" + ], + [ + "6647.00", + "0.02000000" + ], + [ + "6646.00", + "0.03500000" + ], + [ + "6641.35", + "12.55000000" + ], + [ + "6639.00", + "0.13000000" + ], + [ + "6636.41", + "0.00257467" + ], + [ + "6635.00", + "0.13400000" + ], + [ + "6633.00", + "0.07500000" + ], + [ + "6630.00", + "0.02500000" + ], + [ + "6628.60", + "0.15000000" + ], + [ + "6627.00", + "0.02000000" + ], + [ + "6625.92", + "0.01000000" + ], + [ + "6625.00", + "1.00000000" + ], + [ + "6624.79", + "12.55000000" + ], + [ + "6622.00", + "0.07000000" + ], + [ + "6621.64", + "0.00370000" + ], + [ + "6620.00", + "3.40000000" + ], + [ + "6616.00", + "0.04600000" + ], + [ + "6614.00", + "1.03000000" + ], + [ + "6613.00", + "0.10000000" + ], + [ + "6612.16", + "0.21591851" + ], + [ + "6611.83", + "0.01500000" + ], + [ + "6611.10", + "0.08500000" + ], + [ + "6611.00", + "0.02000000" + ], + [ + "6610.00", + "0.00098000" + ], + [ + "6608.36", + "0.01500000" + ], + [ + "6608.26", + "12.55000000" + ], + [ + "6608.00", + "1.00000000" + ], + [ + "6607.00", + "0.02430000" + ], + [ + "6606.75", + "0.01000000" + ], + [ + "6606.00", + "0.50000000" + ], + [ + "6605.00", + "0.50000000" + ], + [ + "6602.00", + "0.49000000" + ], + [ + "6601.43", + "0.09000000" + ], + [ + "6601.00", + "0.62604698" + ], + [ + "6600.63", + "0.25000000" + ], + [ + "6600.17", + "0.18599608" + ], + [ + "6600.00", + "14.31902803" + ], + [ + "6599.00", + "0.05000000" + ], + [ + "6597.09", + "0.00430190" + ], + [ + "6594.00", + "0.03000000" + ], + [ + "6591.79", + "12.55000000" + ], + [ + "6590.00", + "0.50000000" + ], + [ + "6589.77", + "0.15000000" + ], + [ + "6589.00", + "0.50500000" + ], + [ + "6587.00", + "0.03500000" + ], + [ + "6586.86", + "2.23063000" + ], + [ + "6586.45", + "1.04447222" + ], + [ + "6586.00", + "1.01499848" + ], + [ + "6584.00", + "0.06600000" + ], + [ + "6580.50", + "0.00568497" + ], + [ + "6580.00", + "11.44357903" + ], + [ + "6579.36", + "0.05000000" + ], + [ + "6578.60", + "0.05000000" + ], + [ + "6578.00", + "0.34000000" + ], + [ + "6577.24", + "0.02000000" + ], + [ + "6575.35", + "12.55000000" + ], + [ + "6575.00", + "0.04000000" + ], + [ + "6573.58", + "0.02000000" + ], + [ + "6572.52", + "0.06390246" + ], + [ + "6572.11", + "0.03040000" + ], + [ + "6570.00", + "0.22386914" + ], + [ + "6569.97", + "0.01000000" + ], + [ + "6568.56", + "1.95000000" + ], + [ + "6567.00", + "0.02000000" + ], + [ + "6563.20", + "0.10000000" + ], + [ + "6563.16", + "0.00145683" + ], + [ + "6561.01", + "3.34476401" + ], + [ + "6560.47", + "1.34000000" + ], + [ + "6560.28", + "0.02904906" + ], + [ + "6560.00", + "0.23056249" + ], + [ + "6559.35", + "0.03314439" + ], + [ + "6558.95", + "12.55000000" + ], + [ + "6558.00", + "0.01000000" + ], + [ + "6555.00", + "1.59866270" + ], + [ + "6553.95", + "0.07476407" + ], + [ + "6551.42", + "0.12855739" + ], + [ + "6551.00", + "0.02700000" + ], + [ + "6550.72", + "0.01000000" + ], + [ + "6550.00", + "14.46661070" + ], + [ + "6549.12", + "0.00525719" + ], + [ + "6548.00", + "9.79254429" + ], + [ + "6547.00", + "0.52000000" + ], + [ + "6545.00", + "0.60000000" + ], + [ + "6544.00", + "2.00000000" + ], + [ + "6543.16", + "0.01035633" + ], + [ + "6542.59", + "12.55000000" + ], + [ + "6542.30", + "0.00400000" + ], + [ + "6542.11", + "0.00180000" + ], + [ + "6541.87", + "0.14249901" + ], + [ + "6541.00", + "0.38128726" + ], + [ + "6540.00", + "1.07000000" + ], + [ + "6538.85", + "0.10000000" + ], + [ + "6538.00", + "2.00000000" + ], + [ + "6536.84", + "0.00990339" + ], + [ + "6534.00", + "0.11600000" + ], + [ + "6533.33", + "0.00833335" + ], + [ + "6533.00", + "0.16000000" + ], + [ + "6530.00", + "10.20200000" + ], + [ + "6529.00", + "0.10000000" + ], + [ + "6528.00", + "1.10859925" + ], + [ + "6527.00", + "0.02000000" + ], + [ + "6526.28", + "12.55000000" + ], + [ + "6525.60", + "0.07000000" + ], + [ + "6525.58", + "0.03000000" + ], + [ + "6525.19", + "0.00094480" + ], + [ + "6525.00", + "0.13900000" + ], + [ + "6524.06", + "0.00475010" + ], + [ + "6524.00", + "0.45000000" + ], + [ + "6522.60", + "1.00000000" + ], + [ + "6522.00", + "0.64000000" + ], + [ + "6521.02", + "0.52933590" + ], + [ + "6520.00", + "0.78000000" + ], + [ + "6519.00", + "0.15000000" + ], + [ + "6516.95", + "0.00383615" + ], + [ + "6516.67", + "0.01304345" + ], + [ + "6515.00", + "0.04600000" + ], + [ + "6514.00", + "0.01500000" + ], + [ + "6513.00", + "0.21000000" + ], + [ + "6512.64", + "5.00000000" + ], + [ + "6512.16", + "0.17023044" + ], + [ + "6512.00", + "0.04191339" + ], + [ + "6511.06", + "0.12840000" + ], + [ + "6510.95", + "0.03839685" + ], + [ + "6510.00", + "18.34695754" + ], + [ + "6508.00", + "1.00000000" + ], + [ + "6507.93", + "0.00100000" + ], + [ + "6507.00", + "0.35900000" + ], + [ + "6506.97", + "1.00000000" + ], + [ + "6505.00", + "2.00000000" + ], + [ + "6502.00", + "0.25000000" + ], + [ + "6501.09", + "0.33399916" + ], + [ + "6501.00", + "4.05000000" + ], + [ + "6500.71", + "0.06068625" + ], + [ + "6500.08", + "9.52129512" + ], + [ + "6500.01", + "0.14142670" + ], + [ + "6500.00", + "42.03203839" + ], + [ + "6499.00", + "0.10000000" + ], + [ + "6498.60", + "0.05000000" + ], + [ + "6498.00", + "10.00000000" + ], + [ + "6496.91", + "0.25391892" + ], + [ + "6493.77", + "12.55000000" + ], + [ + "6492.00", + "0.26406000" + ], + [ + "6491.00", + "0.50000000" + ], + [ + "6488.00", + "10.00000000" + ], + [ + "6487.00", + "1.03000000" + ], + [ + "6486.00", + "1.00000000" + ], + [ + "6485.34", + "0.00123047" + ], + [ + "6485.00", + "0.13284040" + ], + [ + "6480.29", + "0.02019200" + ], + [ + "6480.00", + "2.04960000" + ], + [ + "6479.97", + "0.02010179" + ], + [ + "6478.00", + "0.13276952" + ], + [ + "6477.57", + "12.55000000" + ], + [ + "6470.00", + "0.44465687" + ], + [ + "6469.00", + "0.09000000" + ], + [ + "6468.00", + "0.10800000" + ], + [ + "6467.00", + "0.46721045" + ], + [ + "6466.00", + "0.36768635" + ], + [ + "6465.88", + "14.00000000" + ], + [ + "6465.67", + "0.01500000" + ], + [ + "6464.87", + "0.00326327" + ], + [ + "6464.64", + "0.00949628" + ], + [ + "6464.10", + "0.10800000" + ], + [ + "6461.42", + "12.55000000" + ], + [ + "6460.00", + "0.97000000" + ], + [ + "6459.15", + "0.33000000" + ], + [ + "6458.96", + "0.07763634" + ], + [ + "6458.28", + "0.11500000" + ], + [ + "6456.00", + "2.50000000" + ], + [ + "6455.00", + "0.11115036" + ], + [ + "6452.49", + "0.00077600" + ], + [ + "6451.60", + "0.15000000" + ], + [ + "6451.00", + "1.49021568" + ], + [ + "6450.83", + "0.28300000" + ], + [ + "6450.01", + "0.25783370" + ], + [ + "6450.00", + "14.11318936" + ], + [ + "6448.00", + "0.10000000" + ], + [ + "6447.00", + "0.12200000" + ], + [ + "6446.00", + "0.25000000" + ], + [ + "6445.32", + "0.05322311" + ], + [ + "6445.31", + "12.55000000" + ], + [ + "6444.00", + "1.00000000" + ], + [ + "6441.00", + "0.19500000" + ], + [ + "6440.00", + "2.61228649" + ], + [ + "6430.50", + "0.02770391" + ], + [ + "6430.00", + "0.22000000" + ], + [ + "6429.95", + "0.12896713" + ], + [ + "6429.23", + "12.55000000" + ], + [ + "6425.60", + "0.10000000" + ], + [ + "6425.19", + "0.06423467" + ], + [ + "6425.00", + "1.00000000" + ], + [ + "6422.00", + "0.08400000" + ], + [ + "6421.55", + "0.00380000" + ], + [ + "6420.75", + "0.03114901" + ], + [ + "6420.00", + "51.82000000" + ], + [ + "6418.00", + "0.04310000" + ], + [ + "6415.00", + "0.10000000" + ], + [ + "6414.00", + "0.05130000" + ], + [ + "6413.20", + "12.55000000" + ], + [ + "6412.00", + "2.09492093" + ], + [ + "6410.53", + "0.01118635" + ], + [ + "6410.48", + "0.26956665" + ], + [ + "6410.00", + "2.33212000" + ], + [ + "6408.71", + "0.00150000" + ], + [ + "6408.21", + "0.24517611" + ], + [ + "6408.00", + "1.06200000" + ], + [ + "6407.04", + "0.01000000" + ], + [ + "6407.00", + "0.13000000" + ], + [ + "6406.86", + "0.00397698" + ], + [ + "6405.00", + "3.33432630" + ], + [ + "6403.00", + "0.52000000" + ], + [ + "6402.00", + "0.66600000" + ], + [ + "6401.00", + "3.00177393" + ], + [ + "6400.00", + "71.27892500" + ], + [ + "6397.21", + "12.55000000" + ], + [ + "6397.00", + "0.05000000" + ], + [ + "6395.00", + "1.63425956" + ], + [ + "6392.00", + "0.47931790" + ], + [ + "6390.00", + "0.30000000" + ], + [ + "6388.86", + "1.70000000" + ], + [ + "6388.44", + "0.75000000" + ], + [ + "6388.13", + "0.01800000" + ], + [ + "6387.00", + "0.05000000" + ], + [ + "6385.00", + "1.03797494" + ], + [ + "6384.00", + "0.67000000" + ], + [ + "6383.97", + "0.04436580" + ], + [ + "6383.00", + "0.15000000" + ], + [ + "6382.00", + "1.24370000" + ], + [ + "6381.25", + "12.55000000" + ], + [ + "6380.00", + "0.79760659" + ], + [ + "6377.83", + "0.02174328" + ], + [ + "6376.41", + "0.00145065" + ], + [ + "6376.12", + "0.01387076" + ], + [ + "6375.00", + "17.00211450" + ], + [ + "6374.10", + "0.10000000" + ], + [ + "6372.00", + "0.03083961" + ], + [ + "6371.22", + "1.00000000" + ], + [ + "6371.12", + "0.03500000" + ], + [ + "6370.00", + "1.57881868" + ], + [ + "6369.69", + "0.64074389" + ], + [ + "6369.00", + "0.44200000" + ], + [ + "6367.97", + "0.00500000" + ], + [ + "6367.00", + "0.10000000" + ], + [ + "6366.37", + "0.01000000" + ], + [ + "6366.00", + "0.15000000" + ], + [ + "6365.34", + "12.55000000" + ], + [ + "6365.00", + "0.25000000" + ], + [ + "6363.63", + "0.00964858" + ], + [ + "6363.00", + "1.23000000" + ], + [ + "6360.00", + "1.39712578" + ], + [ + "6358.00", + "0.15129434" + ], + [ + "6357.00", + "1.95664621" + ], + [ + "6356.00", + "0.08500000" + ], + [ + "6355.62", + "0.24237925" + ], + [ + "6355.00", + "1.54426947" + ], + [ + "6352.49", + "0.00078800" + ], + [ + "6352.20", + "4.00000000" + ], + [ + "6351.06", + "0.13170000" + ], + [ + "6351.00", + "1.57400000" + ], + [ + "6350.64", + "0.30582429" + ], + [ + "6350.00", + "12.95094758" + ], + [ + "6349.47", + "12.55000000" + ], + [ + "6347.37", + "0.01086235" + ], + [ + "6347.00", + "0.29000000" + ], + [ + "6345.43", + "0.07345522" + ], + [ + "6345.00", + "10.00000000" + ], + [ + "6341.50", + "0.01588267" + ], + [ + "6341.00", + "0.15000000" + ], + [ + "6340.00", + "0.32000000" + ], + [ + "6338.88", + "0.00088659" + ], + [ + "6336.84", + "0.00164729" + ], + [ + "6336.00", + "0.17630208" + ], + [ + "6333.63", + "12.55000000" + ], + [ + "6333.33", + "0.50524563" + ], + [ + "6333.00", + "1.80179851" + ], + [ + "6331.00", + "0.01747117" + ], + [ + "6330.00", + "0.55561057" + ], + [ + "6327.00", + "8.64210526" + ], + [ + "6325.00", + "4.07500000" + ], + [ + "6324.00", + "0.02561986" + ], + [ + "6322.81", + "0.01043760" + ], + [ + "6321.00", + "1.30200000" + ], + [ + "6320.33", + "0.25011985" + ], + [ + "6320.00", + "0.55250000" + ], + [ + "6318.00", + "0.03300000" + ], + [ + "6317.84", + "12.55000000" + ], + [ + "6317.00", + "32.58584055" + ], + [ + "6315.00", + "0.89099129" + ], + [ + "6313.00", + "0.04800000" + ], + [ + "6312.21", + "1.48093838" + ], + [ + "6312.00", + "30.55000000" + ], + [ + "6311.89", + "0.00316862" + ], + [ + "6311.86", + "0.00370000" + ], + [ + "6311.00", + "1.03151323" + ], + [ + "6310.48", + "0.31788571" + ], + [ + "6310.00", + "3.54005535" + ], + [ + "6308.00", + "1.00000000" + ], + [ + "6307.00", + "30.00000000" + ], + [ + "6306.60", + "1.00000000" + ], + [ + "6305.00", + "31.00000000" + ], + [ + "6304.87", + "1.00000000" + ], + [ + "6304.00", + "3.56000000" + ], + [ + "6303.49", + "0.00938000" + ], + [ + "6303.23", + "0.10000000" + ], + [ + "6302.08", + "12.55000000" + ], + [ + "6302.00", + "30.00000000" + ], + [ + "6301.30", + "0.47367686" + ], + [ + "6301.20", + "0.03173999" + ], + [ + "6301.00", + "2.37400000" + ], + [ + "6300.00", + "13.12691745" + ], + [ + "6299.00", + "1.02000000" + ], + [ + "6294.77", + "0.64373917" + ], + [ + "6292.00", + "1.00000000" + ], + [ + "6290.00", + "6.02008187" + ], + [ + "6289.00", + "0.50000000" + ], + [ + "6288.88", + "14.76148376" + ], + [ + "6288.01", + "5.00000000" + ], + [ + "6287.00", + "30.00000000" + ], + [ + "6286.37", + "12.55000000" + ], + [ + "6285.46", + "1.39800884" + ], + [ + "6283.95", + "0.10445366" + ], + [ + "6282.00", + "35.44505728" + ], + [ + "6280.00", + "9.68383120" + ], + [ + "6279.00", + "0.10200000" + ], + [ + "6278.59", + "0.08500000" + ], + [ + "6278.00", + "1.70000000" + ], + [ + "6277.89", + "0.01205151" + ], + [ + "6277.00", + "30.10000000" + ], + [ + "6276.00", + "0.17000000" + ], + [ + "6275.00", + "1.14557610" + ], + [ + "6272.00", + "30.00000000" + ], + [ + "6270.69", + "12.55000000" + ], + [ + "6270.15", + "0.00147364" + ], + [ + "6270.00", + "1.13400000" + ], + [ + "6269.04", + "0.00127292" + ], + [ + "6267.00", + "30.10200000" + ], + [ + "6266.00", + "0.82011410" + ], + [ + "6264.34", + "1.40272068" + ], + [ + "6262.20", + "3.24554310" + ], + [ + "6262.00", + "30.00000000" + ], + [ + "6261.00", + "4.20591279" + ], + [ + "6260.00", + "1.54739297" + ], + [ + "6259.00", + "0.06800000" + ], + [ + "6258.90", + "0.12174463" + ], + [ + "6257.00", + "30.03067340" + ], + [ + "6255.05", + "12.55000000" + ], + [ + "6255.00", + "0.79505595" + ], + [ + "6254.00", + "0.01000000" + ], + [ + "6253.00", + "0.01163922" + ], + [ + "6252.00", + "30.00000000" + ], + [ + "6251.00", + "0.28978083" + ], + [ + "6250.01", + "0.00083680" + ], + [ + "6250.00", + "18.22345537" + ], + [ + "6247.00", + "30.51000000" + ], + [ + "6245.00", + "0.34000000" + ], + [ + "6244.74", + "0.00200000" + ], + [ + "6243.30", + "1.40744839" + ], + [ + "6242.50", + "0.71905808" + ], + [ + "6242.00", + "30.00000000" + ], + [ + "6241.10", + "0.09000000" + ], + [ + "6240.00", + "19.65967387" + ], + [ + "6239.45", + "12.55000000" + ], + [ + "6238.00", + "5.03657422" + ], + [ + "6237.13", + "0.01500000" + ], + [ + "6237.00", + "30.00000000" + ], + [ + "6236.00", + "1.98500000" + ], + [ + "6235.00", + "1.00000000" + ], + [ + "6234.00", + "2.58832771" + ], + [ + "6233.33", + "0.01524067" + ], + [ + "6233.00", + "0.60367800" + ], + [ + "6232.00", + "30.00000000" + ], + [ + "6230.15", + "0.00420000" + ], + [ + "6230.00", + "1.19381058" + ], + [ + "6227.55", + "0.01000000" + ], + [ + "6227.00", + "30.15230000" + ], + [ + "6225.00", + "2.35499437" + ], + [ + "6223.90", + "12.55000000" + ], + [ + "6223.00", + "0.23493724" + ], + [ + "6222.33", + "1.41219204" + ], + [ + "6222.22", + "0.00330000" + ], + [ + "6222.00", + "32.45000000" + ], + [ + "6221.97", + "0.91010002" + ], + [ + "6221.00", + "0.13299469" + ], + [ + "6220.00", + "5.72000000" + ], + [ + "6218.00", + "5.94753297" + ], + [ + "6217.00", + "30.00000000" + ], + [ + "6216.10", + "0.12000000" + ], + [ + "6216.00", + "0.38000000" + ], + [ + "6215.09", + "0.00659362" + ], + [ + "6215.00", + "4.37711946" + ], + [ + "6213.11", + "0.03220000" + ], + [ + "6213.00", + "0.91842427" + ], + [ + "6212.00", + "33.83305988" + ], + [ + "6211.06", + "0.13470000" + ], + [ + "6211.00", + "7.09289444" + ], + [ + "6210.00", + "1.35167157" + ], + [ + "6209.66", + "0.07678681" + ], + [ + "6209.00", + "10.00000000" + ], + [ + "6208.37", + "12.55000000" + ], + [ + "6208.00", + "1.00000000" + ], + [ + "6207.00", + "0.10000000" + ], + [ + "6205.00", + "1.00000000" + ], + [ + "6204.14", + "0.05315161" + ], + [ + "6203.00", + "1.91393680" + ], + [ + "6201.43", + "1.41695168" + ], + [ + "6201.01", + "0.25000000" + ], + [ + "6201.00", + "0.63000000" + ], + [ + "6200.01", + "20.00000000" + ], + [ + "6200.00", + "67.97817627" + ], + [ + "6199.22", + "0.02000000" + ], + [ + "6199.00", + "1.20000000" + ], + [ + "6197.00", + "3.89000000" + ], + [ + "6195.00", + "0.02000000" + ], + [ + "6192.89", + "12.55000000" + ], + [ + "6192.85", + "0.86825000" + ], + [ + "6191.55", + "0.00403776" + ], + [ + "6190.00", + "10.00000000" + ], + [ + "6189.00", + "0.05200000" + ], + [ + "6188.01", + "10.00000000" + ], + [ + "6186.00", + "1.00000000" + ], + [ + "6185.00", + "6.88346557" + ], + [ + "6184.00", + "0.04919598" + ], + [ + "6181.10", + "0.03770445" + ], + [ + "6181.00", + "0.10000000" + ], + [ + "6180.60", + "1.42172736" + ], + [ + "6180.10", + "0.04045242" + ], + [ + "6180.00", + "1.32000000" + ], + [ + "6178.00", + "0.20270475" + ], + [ + "6177.45", + "12.55000000" + ], + [ + "6177.00", + "3.91293183" + ], + [ + "6176.00", + "1.00000000" + ], + [ + "6175.00", + "2.39627141" + ], + [ + "6171.66", + "0.50000000" + ], + [ + "6170.00", + "0.10000000" + ], + [ + "6169.69", + "0.62127756" + ], + [ + "6169.00", + "0.19800000" + ], + [ + "6168.00", + "0.07600000" + ], + [ + "6162.04", + "12.55000000" + ], + [ + "6160.00", + "0.55028139" + ], + [ + "6159.83", + "1.42651913" + ], + [ + "6159.14", + "0.00200000" + ], + [ + "6159.00", + "0.19000000" + ], + [ + "6157.89", + "0.01188037" + ], + [ + "6157.00", + "0.04012018" + ], + [ + "6156.00", + "5.62737981" + ], + [ + "6154.25", + "0.00231596" + ], + [ + "6153.00", + "0.34000000" + ], + [ + "6152.49", + "0.00081400" + ], + [ + "6152.04", + "0.00900000" + ], + [ + "6152.00", + "0.05000000" + ], + [ + "6151.00", + "1.60471630" + ], + [ + "6150.00", + "13.48869891" + ], + [ + "6149.97", + "0.02000000" + ], + [ + "6146.68", + "12.55000000" + ], + [ + "6145.26", + "0.01295394" + ], + [ + "6144.00", + "0.07600000" + ], + [ + "6141.00", + "5.82751180" + ], + [ + "6140.80", + "0.50000000" + ], + [ + "6140.00", + "0.42000000" + ], + [ + "6139.14", + "1.43132706" + ], + [ + "6137.91", + "0.49500000" + ], + [ + "6136.00", + "0.55800000" + ], + [ + "6135.00", + "0.17000000" + ], + [ + "6134.00", + "0.00100000" + ], + [ + "6133.00", + "0.02296714" + ], + [ + "6132.00", + "1.00000000" + ], + [ + "6131.35", + "12.55000000" + ], + [ + "6131.05", + "0.30000000" + ], + [ + "6130.00", + "0.13700000" + ], + [ + "6128.93", + "0.12500000" + ], + [ + "6128.00", + "0.00131853" + ], + [ + "6127.00", + "0.05700000" + ], + [ + "6126.58", + "0.03000000" + ], + [ + "6125.00", + "3.41792000" + ], + [ + "6123.00", + "2.32269638" + ], + [ + "6122.31", + "0.02510704" + ], + [ + "6122.22", + "20.00000000" + ], + [ + "6122.00", + "0.17157014" + ], + [ + "6121.00", + "0.26409001" + ], + [ + "6120.00", + "13.29320862" + ], + [ + "6119.60", + "0.37500000" + ], + [ + "6118.52", + "1.43615118" + ], + [ + "6118.24", + "0.02000000" + ], + [ + "6117.00", + "0.67338195" + ], + [ + "6116.06", + "12.55000000" + ], + [ + "6116.00", + "1.00000000" + ], + [ + "6115.00", + "1.01232215" + ], + [ + "6112.83", + "0.15000000" + ], + [ + "6112.00", + "5.50437981" + ], + [ + "6111.00", + "12.35169923" + ], + [ + "6110.53", + "0.00185185" + ], + [ + "6110.09", + "0.50000000" + ], + [ + "6110.00", + "4.87497201" + ], + [ + "6107.80", + "0.36651331" + ], + [ + "6106.58", + "0.10000000" + ], + [ + "6105.00", + "1.32437920" + ], + [ + "6103.04", + "0.00200000" + ], + [ + "6102.58", + "0.01800000" + ], + [ + "6101.01", + "0.25000000" + ], + [ + "6101.00", + "0.32781511" + ], + [ + "6100.81", + "12.55000000" + ], + [ + "6100.51", + "0.69946775" + ], + [ + "6100.32", + "1.00000000" + ], + [ + "6100.10", + "2.00000000" + ], + [ + "6100.00", + "70.10057565" + ], + [ + "6099.60", + "0.11400000" + ], + [ + "6098.00", + "0.15000000" + ], + [ + "6097.97", + "1.44099157" + ], + [ + "6097.60", + "0.15000000" + ], + [ + "6097.00", + "0.03000000" + ], + [ + "6095.60", + "0.03000000" + ], + [ + "6095.00", + "0.01000000" + ], + [ + "6093.77", + "0.00500000" + ], + [ + "6093.00", + "0.17000000" + ], + [ + "6092.83", + "0.21500000" + ], + [ + "6092.12", + "0.01000000" + ], + [ + "6092.02", + "0.05000000" + ], + [ + "6092.00", + "1.00000000" + ], + [ + "6091.00", + "0.02000000" + ], + [ + "6090.05", + "0.07000000" + ], + [ + "6088.11", + "0.03308000" + ], + [ + "6088.02", + "0.04500000" + ], + [ + "6088.01", + "10.00000000" + ], + [ + "6086.58", + "0.02000000" + ], + [ + "6086.00", + "1.00000000" + ], + [ + "6085.59", + "12.55000000" + ], + [ + "6085.00", + "0.03900000" + ], + [ + "6084.60", + "0.04338823" + ], + [ + "6083.00", + "2.40522192" + ], + [ + "6081.13", + "0.00100000" + ], + [ + "6081.00", + "0.05430000" + ], + [ + "6080.00", + "3.74750822" + ], + [ + "6079.53", + "0.50000000" + ], + [ + "6079.02", + "12.00000000" + ], + [ + "6079.00", + "4.00400000" + ], + [ + "6078.00", + "0.15000000" + ], + [ + "6077.49", + "1.44584828" + ], + [ + "6077.00", + "0.65000000" + ], + [ + "6075.93", + "0.01500000" + ], + [ + "6075.00", + "1.20000000" + ], + [ + "6073.97", + "0.00291658" + ], + [ + "6073.34", + "0.00300000" + ], + [ + "6070.42", + "12.55000000" + ], + [ + "6068.00", + "0.13200000" + ], + [ + "6067.00", + "0.05000000" + ], + [ + "6066.66", + "0.00400000" + ], + [ + "6066.00", + "1.51000000" + ], + [ + "6060.00", + "0.90083102" + ], + [ + "6058.96", + "5.03027664" + ], + [ + "6058.75", + "0.00200000" + ], + [ + "6057.07", + "1.45072135" + ], + [ + "6056.42", + "0.25317597" + ], + [ + "6055.28", + "12.55000000" + ], + [ + "6055.00", + "0.16571263" + ], + [ + "6053.62", + "0.01000000" + ], + [ + "6053.00", + "0.14440000" + ], + [ + "6052.49", + "0.00082700" + ], + [ + "6050.00", + "12.33311406" + ], + [ + "6048.12", + "0.00500000" + ], + [ + "6046.00", + "0.00430000" + ], + [ + "6044.00", + "0.30000000" + ], + [ + "6040.18", + "12.55000000" + ], + [ + "6040.00", + "0.32000000" + ], + [ + "6039.37", + "0.00411069" + ], + [ + "6037.84", + "0.00132166" + ], + [ + "6036.72", + "1.45561084" + ], + [ + "6036.00", + "0.30400000" + ], + [ + "6035.00", + "1.10025683" + ], + [ + "6034.00", + "4.39000000" + ], + [ + "6033.00", + "0.31981766" + ], + [ + "6032.20", + "0.13000000" + ], + [ + "6030.00", + "0.04883084" + ], + [ + "6029.00", + "0.07940000" + ], + [ + "6028.00", + "0.50000000" + ], + [ + "6026.80", + "0.50000000" + ], + [ + "6026.00", + "0.26400000" + ], + [ + "6025.11", + "12.55000000" + ], + [ + "6025.00", + "2.01000000" + ], + [ + "6024.98", + "0.11687175" + ], + [ + "6024.00", + "0.11400000" + ], + [ + "6023.00", + "5.97028225" + ], + [ + "6022.00", + "2.02827798" + ], + [ + "6021.00", + "1.10530800" + ], + [ + "6020.00", + "0.65655814" + ], + [ + "6018.02", + "0.11000000" + ], + [ + "6016.45", + "1.46051682" + ], + [ + "6015.00", + "1.00000000" + ], + [ + "6013.82", + "0.00140248" + ], + [ + "6013.13", + "1.00000000" + ], + [ + "6013.03", + "1.00000000" + ], + [ + "6013.00", + "0.70000000" + ], + [ + "6012.63", + "0.01389619" + ], + [ + "6012.44", + "4.00000000" + ], + [ + "6012.00", + "0.08320000" + ], + [ + "6011.11", + "0.10340519" + ], + [ + "6011.10", + "0.00500000" + ], + [ + "6011.06", + "0.13920000" + ], + [ + "6011.00", + "3.13316330" + ], + [ + "6010.66", + "0.00840000" + ], + [ + "6010.09", + "12.55000000" + ], + [ + "6010.00", + "6.57955241" + ], + [ + "6007.00", + "0.25891460" + ], + [ + "6006.07", + "0.01000000" + ], + [ + "6006.00", + "5.66958653" + ], + [ + "6005.00", + "1.15000000" + ], + [ + "6004.05", + "0.50000000" + ], + [ + "6003.28", + "0.01499999" + ], + [ + "6002.00", + "0.20000000" + ], + [ + "6001.09", + "0.25811707" + ], + [ + "6001.01", + "0.25000000" + ], + [ + "6001.00", + "2.13687651" + ], + [ + "6000.85", + "0.03000000" + ], + [ + "6000.82", + "0.01500000" + ], + [ + "6000.25", + "0.04522395" + ], + [ + "6000.00", + "53.50397761" + ], + [ + "5999.47", + "1.66843363" + ], + [ + "5999.00", + "0.10500000" + ], + [ + "5998.84", + "1.00000667" + ], + [ + "5998.00", + "0.00400000" + ], + [ + "5996.66", + "0.50000000" + ], + [ + "5996.24", + "1.46543933" + ], + [ + "5995.10", + "12.55000000" + ], + [ + "5995.00", + "0.01200000" + ], + [ + "5994.00", + "5.04878205" + ], + [ + "5993.58", + "0.04200000" + ], + [ + "5993.19", + "0.04172744" + ], + [ + "5992.00", + "0.00300000" + ], + [ + "5990.46", + "0.00203824" + ], + [ + "5990.00", + "0.53000000" + ], + [ + "5989.00", + "0.17700000" + ], + [ + "5988.01", + "10.00000000" + ], + [ + "5987.00", + "0.02000000" + ], + [ + "5986.00", + "1.00000000" + ], + [ + "5985.00", + "0.10580910" + ], + [ + "5984.00", + "0.03204400" + ], + [ + "5983.00", + "0.06000000" + ], + [ + "5980.15", + "12.55000000" + ], + [ + "5980.00", + "10.67313259" + ], + [ + "5979.00", + "3.90000000" + ], + [ + "5978.00", + "6.32277183" + ], + [ + "5976.10", + "1.47037843" + ], + [ + "5972.88", + "2.00000000" + ], + [ + "5972.81", + "0.03902015" + ], + [ + "5972.44", + "0.25000000" + ], + [ + "5972.00", + "0.10000000" + ], + [ + "5970.13", + "0.00500000" + ], + [ + "5970.00", + "0.57000000" + ], + [ + "5969.00", + "0.19740000" + ], + [ + "5968.42", + "0.01296297" + ], + [ + "5968.00", + "0.38000000" + ], + [ + "5966.67", + "0.50000000" + ], + [ + "5966.60", + "0.50540000" + ], + [ + "5966.00", + "0.25860000" + ], + [ + "5965.24", + "12.57800000" + ], + [ + "5964.00", + "0.30000000" + ], + [ + "5963.00", + "2.00591658" + ], + [ + "5962.96", + "17.00000000" + ], + [ + "5962.00", + "0.15000000" + ], + [ + "5961.01", + "3.68142867" + ], + [ + "5960.00", + "18.40560318" + ], + [ + "5959.83", + "0.21400000" + ], + [ + "5959.00", + "0.00600000" + ], + [ + "5956.02", + "1.47533418" + ], + [ + "5956.00", + "0.06000000" + ], + [ + "5955.00", + "0.16790925" + ], + [ + "5953.58", + "0.14000000" + ], + [ + "5953.00", + "0.00383504" + ], + [ + "5952.55", + "0.00500000" + ], + [ + "5952.49", + "0.00084100" + ], + [ + "5951.00", + "0.30242500" + ], + [ + "5950.36", + "12.55000000" + ], + [ + "5950.00", + "10.32756975" + ], + [ + "5949.97", + "0.02000000" + ], + [ + "5947.37", + "0.00220000" + ], + [ + "5947.00", + "0.55420000" + ], + [ + "5945.58", + "0.02520000" + ], + [ + "5940.00", + "0.32000000" + ], + [ + "5936.83", + "0.50000000" + ], + [ + "5936.49", + "0.00500000" + ], + [ + "5936.01", + "1.48030663" + ], + [ + "5936.00", + "0.23000000" + ], + [ + "5935.52", + "12.55000000" + ], + [ + "5935.00", + "0.08400000" + ], + [ + "5934.00", + "0.00500000" + ], + [ + "5932.90", + "0.00111813" + ], + [ + "5930.00", + "0.40000000" + ], + [ + "5928.00", + "1.00000000" + ], + [ + "5927.29", + "0.01000000" + ], + [ + "5927.00", + "0.23000000" + ], + [ + "5926.60", + "0.15800000" + ], + [ + "5926.00", + "0.21000000" + ], + [ + "5925.00", + "1.71000000" + ], + [ + "5924.00", + "0.04200000" + ], + [ + "5923.23", + "3.58179405" + ], + [ + "5923.00", + "0.12600000" + ], + [ + "5920.72", + "12.55000000" + ], + [ + "5920.60", + "0.19800000" + ], + [ + "5920.00", + "0.48190000" + ], + [ + "5919.83", + "0.27260000" + ], + [ + "5919.12", + "0.01400000" + ], + [ + "5919.02", + "0.05800000" + ], + [ + "5919.00", + "0.05100000" + ], + [ + "5918.60", + "0.04200000" + ], + [ + "5917.05", + "0.09800000" + ], + [ + "5916.07", + "1.48529584" + ], + [ + "5916.00", + "0.75679090" + ], + [ + "5915.02", + "0.06120000" + ], + [ + "5914.00", + "2.00000000" + ], + [ + "5913.58", + "0.02800000" + ], + [ + "5911.10", + "0.00500000" + ], + [ + "5911.00", + "0.08400000" + ], + [ + "5910.00", + "0.50103000" + ], + [ + "5907.14", + "0.50000000" + ], + [ + "5905.96", + "12.55000000" + ], + [ + "5905.00", + "1.00000000" + ], + [ + "5903.00", + "0.61200000" + ], + [ + "5902.93", + "0.02100000" + ], + [ + "5902.02", + "0.10000000" + ], + [ + "5901.00", + "0.00500000" + ], + [ + "5900.00", + "5.44288609" + ], + [ + "5899.00", + "20.96111544" + ], + [ + "5896.29", + "0.01000000" + ], + [ + "5896.20", + "1.49030186" + ], + [ + "5894.00", + "0.06300000" + ], + [ + "5892.00", + "0.92400712" + ], + [ + "5891.61", + "4.52083217" + ], + [ + "5891.53", + "0.60753319" + ], + [ + "5891.23", + "12.55000000" + ], + [ + "5890.00", + "0.40000000" + ], + [ + "5888.00", + "2.91939707" + ], + [ + "5887.28", + "1.63737000" + ], + [ + "5886.88", + "0.50000000" + ], + [ + "5886.00", + "1.11044342" + ], + [ + "5884.21", + "0.00207215" + ], + [ + "5883.00", + "0.02000000" + ], + [ + "5882.00", + "0.08400000" + ], + [ + "5880.00", + "0.46488095" + ], + [ + "5879.97", + "0.05000000" + ], + [ + "5879.50", + "7.00000000" + ], + [ + "5879.00", + "1.00000000" + ], + [ + "5878.00", + "0.20000000" + ], + [ + "5877.60", + "0.01830000" + ], + [ + "5877.10", + "0.10800000" + ], + [ + "5876.94", + "0.00500000" + ], + [ + "5876.54", + "12.55000000" + ], + [ + "5876.40", + "1.49532476" + ], + [ + "5876.00", + "0.04200000" + ], + [ + "5875.00", + "0.50000000" + ], + [ + "5873.00", + "0.09604631" + ], + [ + "5872.95", + "0.00914611" + ], + [ + "5872.88", + "3.00000000" + ], + [ + "5872.00", + "0.30000000" + ], + [ + "5870.00", + "0.47030000" + ], + [ + "5866.00", + "0.25000000" + ], + [ + "5865.57", + "0.00510000" + ], + [ + "5862.00", + "0.07800000" + ], + [ + "5861.88", + "12.55000000" + ], + [ + "5861.00", + "0.04013650" + ], + [ + "5860.85", + "0.01000000" + ], + [ + "5860.58", + "0.05400000" + ], + [ + "5860.00", + "0.65621672" + ], + [ + "5858.00", + "1.00000000" + ], + [ + "5856.66", + "1.50036459" + ], + [ + "5855.00", + "1.34005038" + ], + [ + "5852.00", + "0.21594000" + ], + [ + "5851.06", + "0.14300000" + ], + [ + "5851.00", + "0.05000000" + ], + [ + "5850.00", + "10.85557607" + ], + [ + "5848.21", + "0.50000000" + ], + [ + "5847.26", + "12.55000000" + ], + [ + "5847.00", + "0.05000000" + ], + [ + "5845.39", + "0.69232591" + ], + [ + "5844.90", + "0.01000000" + ], + [ + "5844.00", + "0.38576830" + ], + [ + "5842.71", + "0.00500000" + ], + [ + "5840.00", + "0.32000000" + ], + [ + "5837.00", + "0.19000000" + ], + [ + "5836.98", + "1.50542140" + ], + [ + "5835.00", + "0.14000000" + ], + [ + "5833.33", + "0.40000000" + ], + [ + "5832.81", + "15.00000000" + ], + [ + "5832.80", + "0.42000000" + ], + [ + "5832.68", + "12.55000000" + ], + [ + "5831.03", + "0.08500000" + ], + [ + "5830.00", + "0.40000000" + ], + [ + "5828.42", + "0.01499999" + ], + [ + "5827.00", + "0.01000000" + ], + [ + "5825.00", + "2.25000000" + ], + [ + "5824.52", + "0.01000000" + ], + [ + "5824.02", + "0.10000000" + ], + [ + "5823.00", + "0.12000000" + ], + [ + "5822.00", + "1.01710000" + ], + [ + "5820.00", + "8.62482852" + ], + [ + "5818.14", + "12.55000000" + ], + [ + "5818.00", + "0.05200000" + ], + [ + "5817.38", + "1.51049526" + ], + [ + "5815.57", + "0.00520000" + ], + [ + "5815.00", + "0.20000000" + ], + [ + "5813.97", + "0.01020793" + ], + [ + "5813.60", + "0.63580000" + ], + [ + "5813.18", + "0.50000000" + ], + [ + "5813.03", + "2.00000000" + ], + [ + "5812.24", + "0.03600000" + ], + [ + "5812.10", + "0.14000000" + ], + [ + "5811.00", + "0.34000000" + ], + [ + "5810.00", + "3.30758044" + ], + [ + "5806.83", + "0.27800000" + ], + [ + "5805.56", + "0.00655182" + ], + [ + "5805.10", + "0.60000000" + ], + [ + "5805.00", + "1.24711628" + ], + [ + "5804.00", + "0.10000000" + ], + [ + "5803.63", + "12.55000000" + ], + [ + "5801.01", + "0.25000000" + ], + [ + "5801.00", + "2.53000000" + ], + [ + "5800.58", + "0.18000000" + ], + [ + "5800.07", + "1.22379730" + ], + [ + "5800.00", + "63.76015897" + ], + [ + "5797.84", + "1.51558622" + ], + [ + "5796.00", + "0.08360000" + ], + [ + "5793.00", + "0.42200000" + ], + [ + "5790.00", + "0.92584100" + ], + [ + "5789.16", + "12.55000000" + ], + [ + "5788.77", + "0.03700000" + ], + [ + "5788.58", + "0.03240000" + ], + [ + "5786.67", + "0.01248078" + ], + [ + "5784.11", + "0.50000000" + ], + [ + "5783.13", + "0.07297907" + ], + [ + "5781.00", + "0.12600000" + ], + [ + "5780.00", + "1.13343080" + ], + [ + "5779.00", + "0.02000000" + ], + [ + "5778.95", + "0.01411656" + ], + [ + "5778.36", + "1.52069433" + ], + [ + "5778.00", + "0.25000000" + ], + [ + "5777.00", + "0.20000000" + ], + [ + "5775.00", + "1.26295276" + ], + [ + "5774.72", + "12.55000000" + ], + [ + "5770.00", + "1.50000000" + ], + [ + "5768.88", + "0.25000000" + ], + [ + "5768.00", + "0.54000000" + ], + [ + "5767.00", + "0.00699323" + ], + [ + "5766.00", + "0.11500000" + ], + [ + "5765.00", + "0.20000000" + ], + [ + "5762.83", + "0.01000000" + ], + [ + "5761.00", + "0.85534091" + ], + [ + "5760.32", + "12.55000000" + ], + [ + "5760.00", + "0.82000000" + ], + [ + "5758.95", + "1.52581966" + ], + [ + "5757.33", + "0.01028000" + ], + [ + "5757.00", + "7.94800000" + ], + [ + "5756.00", + "0.22051601" + ], + [ + "5755.18", + "0.50000000" + ], + [ + "5755.00", + "0.37414769" + ], + [ + "5754.00", + "2.92357489" + ], + [ + "5753.60", + "0.20200000" + ], + [ + "5752.00", + "0.27000000" + ], + [ + "5751.00", + "0.05400000" + ], + [ + "5750.94", + "0.01000000" + ], + [ + "5750.00", + "22.97311571" + ], + [ + "5749.00", + "0.00671855" + ], + [ + "5746.83", + "0.33020000" + ], + [ + "5746.12", + "0.01800000" + ], + [ + "5746.02", + "0.06600000" + ], + [ + "5745.95", + "12.55000000" + ], + [ + "5744.05", + "0.12600000" + ], + [ + "5743.60", + "0.24600000" + ], + [ + "5742.02", + "0.07740000" + ], + [ + "5741.60", + "0.05400000" + ], + [ + "5740.58", + "0.03600000" + ], + [ + "5740.00", + "0.32000000" + ], + [ + "5739.61", + "1.53096227" + ], + [ + "5737.70", + "10.00000000" + ], + [ + "5735.51", + "0.00510000" + ], + [ + "5735.00", + "0.55000000" + ], + [ + "5733.00", + "0.28280000" + ], + [ + "5731.62", + "12.55000000" + ], + [ + "5731.00", + "0.34000000" + ], + [ + "5730.00", + "2.51455497" + ], + [ + "5729.97", + "0.05000000" + ], + [ + "5729.93", + "0.02700000" + ], + [ + "5729.00", + "0.03000000" + ], + [ + "5727.58", + "0.06600000" + ], + [ + "5726.40", + "0.50000000" + ], + [ + "5725.00", + "2.00000000" + ], + [ + "5723.00", + "0.06800000" + ], + [ + "5720.00", + "0.32000000" + ], + [ + "5719.19", + "0.00200000" + ], + [ + "5717.33", + "12.55000000" + ], + [ + "5717.00", + "0.05300000" + ], + [ + "5715.25", + "0.08159041" + ], + [ + "5715.00", + "0.25000000" + ], + [ + "5714.40", + "0.44220018" + ], + [ + "5714.00", + "0.60660000" + ], + [ + "5711.06", + "0.14853459" + ], + [ + "5710.00", + "0.40106000" + ], + [ + "5707.00", + "0.03469423" + ], + [ + "5705.63", + "0.25000000" + ], + [ + "5703.07", + "12.55000000" + ], + [ + "5703.00", + "0.26200000" + ], + [ + "5702.00", + "0.09200000" + ], + [ + "5700.40", + "0.50000000" + ], + [ + "5700.00", + "7.73687919" + ], + [ + "5699.57", + "0.00611888" + ], + [ + "5699.00", + "0.00430000" + ], + [ + "5697.76", + "0.50000000" + ], + [ + "5694.35", + "0.00500000" + ], + [ + "5694.00", + "0.25000000" + ], + [ + "5693.00", + "0.23000000" + ], + [ + "5692.00", + "0.10400000" + ], + [ + "5691.00", + "0.42000000" + ], + [ + "5690.00", + "0.53800000" + ], + [ + "5689.89", + "0.01862000" + ], + [ + "5689.30", + "0.15756771" + ], + [ + "5689.00", + "2.90552470" + ], + [ + "5688.85", + "12.55000000" + ], + [ + "5685.00", + "2.32934388" + ], + [ + "5684.00", + "0.02120381" + ], + [ + "5682.95", + "0.05380548" + ], + [ + "5682.00", + "1.40190513" + ], + [ + "5681.10", + "1.00500000" + ], + [ + "5680.00", + "5.25748063" + ], + [ + "5678.00", + "0.09200000" + ], + [ + "5677.38", + "0.16174724" + ], + [ + "5675.00", + "0.02000000" + ], + [ + "5674.66", + "12.55000000" + ], + [ + "5672.57", + "0.00510000" + ], + [ + "5671.00", + "0.05000000" + ], + [ + "5670.00", + "1.06600000" + ], + [ + "5669.27", + "0.50000000" + ], + [ + "5667.00", + "5.20000000" + ], + [ + "5666.66", + "23.43811155" + ], + [ + "5664.31", + "0.84781098" + ], + [ + "5661.00", + "0.06900000" + ], + [ + "5660.60", + "0.76620000" + ], + [ + "5660.51", + "12.55000000" + ], + [ + "5660.00", + "0.32000000" + ], + [ + "5659.24", + "0.04400000" + ], + [ + "5658.66", + "0.01499999" + ], + [ + "5657.89", + "0.00231008" + ], + [ + "5657.00", + "0.12297861" + ], + [ + "5656.00", + "0.08422029" + ], + [ + "5655.00", + "0.00500000" + ], + [ + "5654.77", + "0.10000000" + ], + [ + "5653.86", + "0.22197200" + ], + [ + "5653.83", + "0.34200000" + ], + [ + "5653.00", + "0.12500000" + ], + [ + "5652.49", + "0.00088600" + ], + [ + "5650.00", + "38.81197522" + ], + [ + "5649.00", + "0.09200000" + ], + [ + "5647.58", + "0.22000000" + ], + [ + "5646.40", + "12.55000000" + ], + [ + "5641.00", + "1.00000000" + ], + [ + "5640.92", + "0.50000000" + ], + [ + "5640.00", + "0.74800000" + ], + [ + "5639.00", + "0.34000000" + ], + [ + "5637.74", + "0.01000000" + ], + [ + "5636.88", + "0.10000000" + ], + [ + "5634.77", + "0.10000000" + ], + [ + "5633.56", + "0.00200000" + ], + [ + "5632.32", + "12.55000000" + ], + [ + "5632.00", + "0.13602095" + ], + [ + "5631.66", + "5.08075638" + ], + [ + "5631.58", + "0.03960000" + ], + [ + "5631.00", + "1.00000000" + ], + [ + "5630.00", + "1.67196447" + ], + [ + "5627.68", + "0.09999369" + ], + [ + "5626.36", + "18.65000000" + ], + [ + "5625.00", + "1.10000000" + ], + [ + "5623.86", + "3.12404400" + ], + [ + "5622.00", + "0.04000000" + ], + [ + "5621.52", + "0.00510000" + ], + [ + "5621.00", + "13.57922523" + ], + [ + "5620.00", + "7.63335765" + ], + [ + "5619.00", + "0.05100000" + ], + [ + "5618.27", + "12.55000000" + ], + [ + "5617.00", + "4.06747819" + ], + [ + "5616.13", + "0.25000000" + ], + [ + "5616.00", + "0.05400000" + ], + [ + "5615.01", + "0.08900000" + ], + [ + "5614.77", + "0.10000000" + ], + [ + "5614.00", + "0.01000000" + ], + [ + "5613.00", + "0.10000000" + ], + [ + "5612.71", + "0.50000000" + ], + [ + "5612.00", + "1.03560000" + ], + [ + "5610.00", + "21.48083895" + ], + [ + "5606.00", + "0.01790000" + ], + [ + "5604.26", + "12.55000000" + ], + [ + "5602.00", + "0.14800000" + ], + [ + "5601.08", + "0.02526477" + ], + [ + "5601.00", + "0.63000000" + ], + [ + "5600.52", + "0.50000000" + ], + [ + "5600.01", + "0.00700000" + ], + [ + "5600.00", + "15.93889103" + ], + [ + "5594.77", + "0.10000000" + ], + [ + "5594.58", + "0.07800000" + ], + [ + "5591.65", + "0.01000000" + ], + [ + "5590.28", + "12.55000000" + ], + [ + "5590.00", + "0.40000000" + ], + [ + "5589.47", + "0.01534842" + ], + [ + "5589.00", + "0.08600000" + ], + [ + "5587.44", + "0.00500000" + ], + [ + "5587.00", + "0.05280000" + ], + [ + "5584.64", + "0.50000000" + ], + [ + "5583.83", + "0.02772218" + ], + [ + "5582.95", + "0.08397997" + ], + [ + "5580.60", + "0.24600000" + ], + [ + "5580.00", + "0.32000000" + ], + [ + "5579.00", + "33.07607815" + ], + [ + "5578.00", + "0.06600000" + ], + [ + "5577.00", + "74.09169176" + ], + [ + "5576.34", + "12.55000000" + ], + [ + "5575.39", + "74.11308805" + ], + [ + "5573.83", + "0.38780000" + ], + [ + "5573.12", + "0.02200000" + ], + [ + "5573.02", + "0.07400000" + ], + [ + "5573.00", + "60.53466355" + ], + [ + "5572.00", + "71.00000000" + ], + [ + "5571.05", + "0.15400000" + ], + [ + "5571.00", + "71.44000000" + ], + [ + "5570.57", + "0.00510000" + ], + [ + "5570.00", + "0.40000000" + ], + [ + "5569.02", + "0.09360000" + ], + [ + "5567.58", + "0.04400000" + ], + [ + "5566.60", + "0.29400000" + ], + [ + "5566.03", + "2.00000000" + ], + [ + "5564.60", + "0.06600000" + ], + [ + "5563.00", + "0.08780000" + ], + [ + "5562.44", + "0.10538055" + ], + [ + "5561.45", + "0.10011597" + ], + [ + "5561.00", + "1.00000000" + ], + [ + "5560.00", + "0.73300000" + ], + [ + "5558.99", + "0.02142115" + ], + [ + "5557.00", + "0.01000000" + ], + [ + "5556.93", + "0.03300000" + ], + [ + "5556.71", + "0.50000000" + ], + [ + "5556.00", + "0.29200000" + ], + [ + "5555.55", + "0.25047205" + ], + [ + "5555.30", + "0.50000000" + ], + [ + "5555.00", + "3.64573366" + ], + [ + "5553.00", + "0.40000000" + ], + [ + "5552.49", + "0.00090200" + ], + [ + "5551.11", + "0.40301850" + ], + [ + "5551.06", + "0.16657000" + ], + [ + "5551.00", + "74.63708160" + ], + [ + "5550.00", + "2.96581803" + ], + [ + "5549.00", + "0.00500000" + ], + [ + "5548.57", + "12.55000000" + ], + [ + "5547.00", + "0.44000000" + ], + [ + "5544.00", + "4.97645202" + ], + [ + "5540.00", + "0.20000000" + ], + [ + "5539.39", + "17.95031763" + ], + [ + "5538.00", + "0.13800000" + ], + [ + "5535.00", + "0.58000000" + ], + [ + "5534.73", + "12.55000000" + ], + [ + "5533.00", + "0.12930000" + ], + [ + "5532.89", + "0.00092899" + ], + [ + "5532.11", + "39.91610434" + ], + [ + "5532.00", + "0.12328000" + ], + [ + "5531.30", + "0.00510000" + ], + [ + "5530.00", + "0.01205786" + ], + [ + "5528.09", + "0.50000000" + ], + [ + "5528.00", + "0.17200000" + ], + [ + "5526.34", + "0.50000000" + ], + [ + "5524.00", + "0.48600000" + ], + [ + "5523.00", + "0.07500000" + ], + [ + "5520.93", + "12.55000000" + ], + [ + "5520.00", + "1.34334057" + ], + [ + "5517.00", + "0.07600000" + ], + [ + "5515.00", + "1.86055485" + ], + [ + "5510.00", + "0.26504000" + ], + [ + "5507.60", + "0.89660000" + ], + [ + "5507.16", + "12.55000000" + ], + [ + "5506.24", + "0.05200000" + ], + [ + "5505.00", + "2.33264214" + ], + [ + "5501.00", + "0.27000000" + ], + [ + "5500.83", + "0.40600000" + ], + [ + "5500.00", + "61.66532567" + ], + [ + "5499.99", + "0.03650000" + ], + [ + "5498.70", + "0.50000000" + ], + [ + "5498.00", + "0.38000000" + ], + [ + "5494.58", + "0.26000000" + ], + [ + "5493.85", + "0.01499999" + ], + [ + "5493.43", + "12.55000000" + ], + [ + "5490.00", + "0.07600000" + ], + [ + "5483.27", + "0.00100000" + ], + [ + "5482.51", + "0.00520000" + ], + [ + "5481.00", + "0.65900000" + ], + [ + "5479.73", + "12.55000000" + ], + [ + "5478.00", + "7.00000000" + ], + [ + "5475.00", + "0.50000000" + ], + [ + "5474.58", + "0.04680000" + ], + [ + "5471.20", + "0.50000000" + ], + [ + "5471.00", + "0.87653720" + ], + [ + "5470.00", + "0.38331353" + ], + [ + "5469.05", + "0.10180652" + ], + [ + "5469.00", + "0.10000000" + ], + [ + "5467.00", + "0.02000000" + ], + [ + "5466.12", + "0.00500000" + ], + [ + "5466.06", + "12.55000000" + ], + [ + "5466.00", + "0.17096597" + ], + [ + "5465.73", + "0.01606922" + ], + [ + "5462.00", + "0.01100000" + ], + [ + "5461.58", + "0.09000000" + ], + [ + "5461.00", + "0.27000000" + ], + [ + "5457.00", + "0.16000000" + ], + [ + "5453.00", + "0.01000000" + ], + [ + "5452.97", + "0.14526395" + ], + [ + "5452.43", + "12.55000000" + ], + [ + "5451.09", + "0.06809509" + ], + [ + "5451.00", + "1.00000000" + ], + [ + "5450.00", + "7.94880035" + ], + [ + "5446.00", + "0.21640000" + ], + [ + "5444.44", + "10.00000000" + ], + [ + "5442.90", + "0.00385107" + ], + [ + "5440.67", + "0.50000000" + ], + [ + "5440.00", + "0.36613800" + ], + [ + "5439.00", + "0.07466446" + ], + [ + "5438.83", + "12.55000000" + ], + [ + "5437.00", + "0.72000000" + ], + [ + "5435.30", + "1.04983441" + ], + [ + "5431.58", + "0.00256783" + ], + [ + "5430.54", + "0.00510000" + ], + [ + "5430.17", + "0.25376063" + ], + [ + "5430.00", + "2.71866114" + ], + [ + "5428.00", + "0.07500000" + ], + [ + "5425.27", + "12.55000000" + ], + [ + "5425.00", + "1.10000000" + ], + [ + "5419.25", + "0.14762190" + ], + [ + "5417.00", + "1.64500000" + ], + [ + "5416.62", + "0.01000000" + ], + [ + "5414.00", + "0.05600000" + ], + [ + "5413.46", + "0.50000000" + ], + [ + "5413.33", + "0.01498360" + ], + [ + "5411.74", + "12.55000000" + ], + [ + "5411.06", + "0.17088000" + ], + [ + "5410.00", + "0.37854068" + ], + [ + "5407.99", + "0.06688130" + ], + [ + "5407.60", + "0.29000000" + ], + [ + "5407.00", + "0.01000000" + ], + [ + "5406.00", + "2.82462865" + ], + [ + "5405.00", + "0.31997039" + ], + [ + "5404.54", + "0.50000000" + ], + [ + "5404.27", + "0.23077509" + ], + [ + "5404.00", + "0.06000000" + ], + [ + "5402.10", + "0.17615556" + ], + [ + "5401.00", + "0.27755600" + ], + [ + "5400.83", + "0.44540000" + ], + [ + "5400.12", + "0.02600000" + ], + [ + "5400.02", + "0.08200000" + ], + [ + "5400.00", + "19.41840245" + ], + [ + "5398.24", + "12.55000000" + ], + [ + "5398.05", + "0.18200000" + ], + [ + "5396.02", + "0.10980000" + ], + [ + "5394.58", + "0.05200000" + ], + [ + "5389.60", + "0.34200000" + ], + [ + "5389.00", + "0.00716830" + ], + [ + "5387.60", + "0.07800000" + ], + [ + "5386.39", + "0.50000000" + ], + [ + "5384.78", + "12.55000000" + ], + [ + "5383.93", + "0.03900000" + ], + [ + "5383.14", + "0.01000000" + ], + [ + "5383.00", + "0.08400000" + ], + [ + "5380.00", + "2.00000000" + ], + [ + "5372.00", + "0.19045041" + ], + [ + "5371.35", + "12.55000000" + ], + [ + "5370.74", + "2.00000000" + ], + [ + "5370.00", + "0.03106703" + ], + [ + "5369.00", + "0.15600000" + ], + [ + "5366.00", + "3.02708535" + ], + [ + "5363.00", + "0.01000000" + ], + [ + "5362.07", + "0.00520000" + ], + [ + "5360.10", + "0.03000000" + ], + [ + "5359.45", + "0.50000000" + ], + [ + "5358.13", + "0.09688135" + ], + [ + "5357.96", + "12.55000000" + ], + [ + "5354.60", + "1.02700000" + ], + [ + "5354.00", + "0.05700000" + ], + [ + "5353.24", + "0.06000000" + ], + [ + "5351.11", + "0.00423589" + ], + [ + "5350.00", + "1.50148039" + ], + [ + "5348.68", + "0.50000000" + ], + [ + "5347.83", + "0.47000000" + ], + [ + "5347.63", + "0.25000000" + ], + [ + "5344.60", + "12.55000000" + ], + [ + "5341.58", + "0.30000000" + ], + [ + "5340.48", + "0.00521114" + ], + [ + "5333.83", + "0.01499999" + ], + [ + "5333.00", + "0.40000000" + ], + [ + "5332.90", + "18.64535618" + ], + [ + "5332.65", + "0.50000000" + ], + [ + "5331.27", + "12.55000000" + ], + [ + "5330.00", + "0.09200000" + ], + [ + "5328.72", + "0.00100000" + ], + [ + "5328.58", + "0.10200000" + ], + [ + "5327.00", + "0.36000000" + ], + [ + "5318.00", + "0.03000000" + ], + [ + "5317.97", + "12.55000000" + ], + [ + "5317.71", + "1.00000000" + ], + [ + "5317.58", + "0.05400000" + ], + [ + "5316.18", + "0.00125654" + ], + [ + "5316.00", + "0.09400000" + ], + [ + "5313.00", + "0.05700000" + ], + [ + "5311.22", + "0.00310000" + ], + [ + "5311.00", + "0.17336147" + ], + [ + "5310.00", + "0.10537878" + ], + [ + "5308.13", + "0.01878329" + ], + [ + "5307.00", + "0.16000000" + ], + [ + "5305.98", + "0.50000000" + ], + [ + "5304.71", + "12.55000000" + ], + [ + "5304.00", + "0.25000000" + ], + [ + "5302.00", + "0.62000000" + ], + [ + "5301.90", + "2.00000000" + ], + [ + "5301.00", + "0.57621958" + ], + [ + "5300.84", + "0.56711000" + ], + [ + "5300.00", + "11.84921536" + ], + [ + "5298.00", + "0.46000000" + ], + [ + "5295.00", + "0.33000000" + ], + [ + "5294.32", + "2.00000000" + ], + [ + "5291.48", + "12.55000000" + ], + [ + "5291.00", + "0.53400000" + ], + [ + "5290.00", + "0.08100000" + ], + [ + "5284.00", + "0.08400000" + ], + [ + "5281.10", + "0.00540000" + ], + [ + "5279.45", + "0.50000000" + ], + [ + "5278.29", + "12.55000000" + ], + [ + "5277.80", + "0.65028799" + ], + [ + "5277.78", + "0.00811930" + ], + [ + "5277.00", + "0.03340000" + ], + [ + "5275.00", + "0.50000000" + ], + [ + "5274.00", + "0.23000000" + ], + [ + "5269.99", + "0.50000000" + ], + [ + "5267.00", + "0.06120000" + ], + [ + "5265.13", + "12.55000000" + ], + [ + "5265.00", + "1.00000000" + ], + [ + "5260.00", + "2.00000000" + ], + [ + "5259.97", + "0.05000000" + ], + [ + "5259.00", + "0.01000000" + ], + [ + "5258.13", + "0.25000000" + ], + [ + "5258.00", + "0.00500000" + ], + [ + "5257.99", + "8.16988050" + ], + [ + "5257.00", + "0.08400000" + ], + [ + "5255.56", + "0.00697672" + ], + [ + "5253.05", + "0.50000000" + ], + [ + "5253.00", + "0.51000000" + ], + [ + "5252.00", + "12.55000000" + ], + [ + "5251.02", + "0.09500000" + ], + [ + "5250.00", + "11.04865238" + ], + [ + "5248.80", + "0.00123886" + ], + [ + "5248.00", + "0.54000000" + ], + [ + "5247.39", + "5.00000000" + ], + [ + "5247.10", + "0.63388348" + ], + [ + "5238.90", + "12.55000000" + ], + [ + "5237.46", + "0.00376518" + ], + [ + "5237.00", + "0.32600000" + ], + [ + "5236.00", + "0.02928000" + ], + [ + "5234.60", + "0.33400000" + ], + [ + "5234.00", + "1.99898739" + ], + [ + "5233.00", + "2.36039365" + ], + [ + "5232.00", + "0.09000000" + ], + [ + "5230.72", + "0.24000000" + ], + [ + "5230.00", + "0.35000000" + ], + [ + "5228.00", + "0.29000000" + ], + [ + "5227.83", + "0.50300000" + ], + [ + "5227.12", + "0.03000000" + ], + [ + "5227.02", + "0.09000000" + ], + [ + "5226.78", + "0.50000000" + ], + [ + "5225.83", + "12.55000000" + ], + [ + "5225.05", + "0.21000000" + ], + [ + "5225.00", + "1.11639808" + ], + [ + "5224.00", + "0.16200000" + ], + [ + "5223.02", + "0.12600000" + ], + [ + "5221.58", + "0.06000000" + ], + [ + "5219.00", + "7.50000000" + ], + [ + "5218.08", + "0.05000000" + ], + [ + "5217.00", + "4.85561433" + ], + [ + "5216.00", + "1.63489264" + ], + [ + "5215.05", + "1.00000000" + ], + [ + "5215.00", + "0.00430000" + ], + [ + "5213.60", + "0.01000000" + ], + [ + "5212.80", + "12.55000000" + ], + [ + "5212.60", + "0.39000000" + ], + [ + "5212.00", + "0.16653924" + ], + [ + "5211.06", + "0.17745000" + ], + [ + "5211.00", + "1.50000000" + ], + [ + "5210.93", + "0.04500000" + ], + [ + "5210.60", + "0.09000000" + ], + [ + "5210.00", + "0.56906939" + ], + [ + "5205.26", + "0.00284800" + ], + [ + "5204.00", + "0.23400000" + ], + [ + "5201.60", + "1.15740000" + ], + [ + "5201.20", + "0.20000000" + ], + [ + "5201.00", + "7.80000000" + ], + [ + "5200.64", + "0.50000000" + ], + [ + "5200.24", + "0.06800000" + ], + [ + "5200.02", + "0.34294291" + ], + [ + "5200.00", + "23.00758654" + ], + [ + "5199.80", + "12.55000000" + ], + [ + "5199.77", + "0.02500000" + ], + [ + "5195.58", + "0.11400000" + ], + [ + "5195.00", + "0.08100000" + ], + [ + "5194.83", + "0.53400000" + ], + [ + "5193.59", + "5.50000000" + ], + [ + "5191.32", + "0.00540000" + ], + [ + "5188.68", + "0.50000000" + ], + [ + "5188.58", + "0.34000000" + ], + [ + "5186.84", + "12.55000000" + ], + [ + "5186.00", + "0.13446779" + ], + [ + "5183.00", + "0.88421956" + ], + [ + "5180.00", + "0.70352702" + ], + [ + "5175.00", + "101.00000000" + ], + [ + "5174.63", + "0.50000000" + ], + [ + "5173.90", + "12.55000000" + ], + [ + "5173.00", + "0.42000000" + ], + [ + "5169.41", + "0.00500000" + ], + [ + "5168.00", + "0.03000000" + ], + [ + "5161.00", + "12.55000000" + ], + [ + "5160.58", + "0.06120000" + ], + [ + "5160.00", + "0.11000000" + ], + [ + "5155.00", + "0.01000000" + ], + [ + "5152.49", + "0.00097200" + ], + [ + "5150.00", + "0.20553788" + ], + [ + "5148.75", + "0.50000000" + ], + [ + "5148.13", + "12.55000000" + ], + [ + "5145.70", + "0.00100000" + ], + [ + "5144.00", + "0.37394634" + ], + [ + "5140.13", + "0.00500000" + ], + [ + "5138.88", + "0.00097388" + ], + [ + "5136.00", + "0.16400000" + ], + [ + "5135.29", + "12.55000000" + ], + [ + "5133.00", + "17.10845945" + ], + [ + "5132.64", + "0.10000000" + ], + [ + "5126.26", + "0.00500000" + ], + [ + "5123.00", + "0.56467527" + ], + [ + "5122.48", + "12.55000000" + ], + [ + "5121.00", + "0.06120000" + ], + [ + "5120.00", + "0.10000000" + ], + [ + "5117.00", + "0.05900000" + ], + [ + "5111.00", + "15.00000000" + ], + [ + "5110.00", + "0.00117000" + ], + [ + "5109.71", + "12.55000000" + ], + [ + "5102.99", + "0.80000000" + ], + [ + "5102.64", + "0.00500000" + ], + [ + "5102.22", + "0.00653312" + ], + [ + "5101.00", + "0.47119976" + ], + [ + "5100.99", + "0.00600000" + ], + [ + "5100.00", + "18.83028457" + ], + [ + "5097.38", + "0.50000000" + ], + [ + "5097.00", + "0.09620000" + ], + [ + "5096.97", + "11.53037747" + ], + [ + "5094.00", + "0.23000000" + ], + [ + "5093.89", + "0.00750354" + ], + [ + "5085.01", + "0.05000000" + ], + [ + "5084.26", + "12.55000000" + ], + [ + "5083.00", + "0.58390000" + ], + [ + "5081.17", + "2.60000000" + ], + [ + "5081.00", + "0.45000000" + ], + [ + "5079.13", + "0.25000000" + ], + [ + "5072.00", + "0.03021983" + ], + [ + "5071.89", + "0.50000000" + ], + [ + "5071.58", + "11.20518578" + ], + [ + "5071.00", + "0.20000000" + ], + [ + "5067.00", + "0.14500000" + ], + [ + "5066.00", + "0.70000000" + ], + [ + "5064.00", + "0.16200000" + ], + [ + "5063.00", + "0.00430000" + ], + [ + "5062.58", + "0.12600000" + ], + [ + "5062.08", + "0.02000000" + ], + [ + "5062.00", + "0.18800000" + ], + [ + "5061.60", + "0.37800000" + ], + [ + "5061.00", + "2.77000000" + ], + [ + "5060.00", + "0.51000000" + ], + [ + "5059.00", + "0.10200000" + ], + [ + "5058.93", + "12.55000000" + ], + [ + "5058.00", + "0.10233392" + ], + [ + "5057.95", + "0.00338471" + ], + [ + "5057.00", + "0.09700000" + ], + [ + "5054.83", + "0.56060000" + ], + [ + "5054.12", + "0.03400000" + ], + [ + "5054.02", + "0.09800000" + ], + [ + "5053.00", + "0.21000000" + ], + [ + "5052.45", + "0.40049085" + ], + [ + "5052.05", + "0.23800000" + ], + [ + "5052.00", + "0.16200000" + ], + [ + "5051.00", + "0.10200000" + ], + [ + "5050.02", + "0.14220000" + ], + [ + "5050.00", + "1.76094100" + ], + [ + "5048.60", + "1.28780000" + ], + [ + "5048.58", + "0.06800000" + ], + [ + "5047.24", + "0.07600000" + ], + [ + "5047.10", + "0.12000000" + ], + [ + "5046.53", + "0.50000000" + ], + [ + "5046.31", + "4.97236327" + ], + [ + "5044.02", + "0.00100000" + ], + [ + "5043.00", + "0.10200000" + ], + [ + "5041.83", + "0.59800000" + ], + [ + "5041.59", + "0.01000000" + ], + [ + "5040.00", + "0.01785714" + ], + [ + "5037.93", + "0.05100000" + ], + [ + "5037.00", + "0.07800000" + ], + [ + "5035.60", + "0.43800000" + ], + [ + "5035.58", + "0.38000000" + ], + [ + "5034.44", + "0.00500000" + ], + [ + "5034.00", + "0.06540000" + ], + [ + "5033.77", + "0.06300000" + ], + [ + "5033.73", + "12.55000000" + ], + [ + "5033.60", + "0.10200000" + ], + [ + "5032.61", + "0.00540000" + ], + [ + "5032.22", + "0.00500000" + ], + [ + "5031.00", + "0.15000000" + ], + [ + "5030.00", + "2.06500000" + ], + [ + "5029.00", + "0.04966792" + ], + [ + "5025.00", + "0.50000000" + ], + [ + "5024.00", + "0.09200000" + ], + [ + "5022.14", + "0.26263704" + ], + [ + "5021.29", + "0.50000000" + ], + [ + "5021.18", + "12.55000000" + ], + [ + "5020.16", + "1.20000000" + ], + [ + "5020.00", + "5.05000000" + ], + [ + "5019.22", + "0.60614597" + ], + [ + "5019.00", + "1.00000000" + ], + [ + "5016.00", + "0.06000000" + ], + [ + "5015.00", + "10.62200000" + ], + [ + "5013.00", + "0.01000000" + ], + [ + "5012.00", + "0.10000000" + ], + [ + "5011.77", + "0.01000000" + ], + [ + "5011.11", + "0.00864746" + ], + [ + "5011.06", + "0.18452000" + ], + [ + "5011.00", + "2.00000000" + ], + [ + "5010.00", + "5.45120000" + ], + [ + "5008.65", + "12.55000000" + ], + [ + "5008.00", + "0.13864590" + ], + [ + "5006.35", + "0.00199147" + ], + [ + "5005.06", + "0.01000000" + ], + [ + "5005.00", + "2.45000000" + ], + [ + "5004.27", + "0.50000000" + ], + [ + "5004.00", + "0.35800000" + ], + [ + "5003.58", + "0.06840000" + ], + [ + "5001.09", + "0.33000000" + ], + [ + "5001.01", + "4.38808160" + ], + [ + "5001.00", + "0.25000000" + ], + [ + "5000.50", + "0.01006170" + ], + [ + "5000.00", + "69.24427202" + ], + [ + "4999.00", + "0.11935000" + ], + [ + "4996.28", + "0.16601017" + ], + [ + "4996.18", + "0.50000000" + ], + [ + "4996.16", + "12.55000000" + ], + [ + "4996.12", + "0.33229786" + ], + [ + "4995.00", + "0.31000000" + ], + [ + "4993.00", + "0.20000000" + ], + [ + "4991.00", + "0.17400000" + ], + [ + "4990.90", + "1.40000000" + ], + [ + "4989.97", + "0.10000000" + ], + [ + "4989.63", + "0.25000000" + ], + [ + "4988.00", + "0.03000000" + ], + [ + "4985.56", + "0.50000000" + ], + [ + "4985.00", + "47.01262186" + ], + [ + "4983.71", + "12.55000000" + ], + [ + "4982.00", + "0.20100000" + ], + [ + "4978.95", + "0.00315363" + ], + [ + "4971.28", + "12.55000000" + ], + [ + "4971.19", + "0.50000000" + ], + [ + "4971.00", + "0.24800000" + ], + [ + "4962.01", + "0.20000000" + ], + [ + "4962.00", + "0.08700000" + ], + [ + "4958.88", + "12.55000000" + ], + [ + "4953.00", + "0.01338441" + ], + [ + "4952.49", + "0.00101100" + ], + [ + "4951.00", + "0.00110000" + ], + [ + "4950.12", + "0.00202020" + ], + [ + "4950.00", + "31.73881818" + ], + [ + "4949.81", + "0.11191742" + ], + [ + "4947.00", + "0.01000000" + ], + [ + "4946.51", + "12.55000000" + ], + [ + "4946.33", + "0.50000000" + ], + [ + "4940.00", + "0.86000000" + ], + [ + "4934.18", + "12.55000000" + ], + [ + "4932.08", + "0.00510000" + ], + [ + "4930.00", + "1.00000000" + ], + [ + "4929.58", + "0.13800000" + ], + [ + "4924.00", + "0.03000000" + ], + [ + "4921.87", + "12.55000000" + ], + [ + "4921.59", + "0.50000000" + ], + [ + "4920.00", + "0.50000000" + ], + [ + "4919.00", + "0.34845293" + ], + [ + "4916.00", + "0.00430000" + ], + [ + "4912.28", + "1.00000000" + ], + [ + "4911.00", + "1.02500000" + ], + [ + "4910.00", + "50.00123000" + ], + [ + "4909.60", + "12.55000000" + ], + [ + "4909.00", + "0.04959250" + ], + [ + "4907.00", + "0.01003260" + ], + [ + "4903.00", + "0.17200000" + ], + [ + "4900.13", + "0.25000000" + ], + [ + "4900.00", + "2.45251428" + ], + [ + "4897.36", + "12.55000000" + ], + [ + "4896.98", + "0.50000000" + ], + [ + "4895.60", + "1.41820000" + ], + [ + "4894.24", + "0.08400000" + ], + [ + "4892.80", + "0.00540000" + ], + [ + "4890.00", + "10.00000000" + ], + [ + "4889.00", + "1.25000000" + ], + [ + "4888.88", + "0.05000000" + ], + [ + "4888.83", + "0.66200000" + ], + [ + "4888.77", + "0.12300000" + ], + [ + "4888.60", + "0.23200000" + ], + [ + "4888.00", + "1.06540000" + ], + [ + "4887.78", + "0.00909296" + ], + [ + "4887.00", + "0.57000000" + ], + [ + "4886.00", + "0.11400000" + ], + [ + "4885.14", + "12.55000000" + ], + [ + "4884.00", + "0.02500000" + ], + [ + "4882.58", + "0.42000000" + ], + [ + "4882.00", + "0.15000000" + ], + [ + "4881.83", + "0.61820000" + ], + [ + "4881.12", + "0.03800000" + ], + [ + "4881.02", + "0.10600000" + ], + [ + "4881.00", + "1.00000000" + ], + [ + "4879.05", + "0.26600000" + ], + [ + "4877.02", + "0.15840000" + ], + [ + "4877.00", + "3.68306540" + ], + [ + "4875.58", + "0.07600000" + ], + [ + "4872.96", + "12.55000000" + ], + [ + "4872.49", + "0.50000000" + ], + [ + "4870.00", + "0.20000000" + ], + [ + "4869.97", + "0.10000000" + ], + [ + "4868.00", + "0.00500000" + ], + [ + "4867.77", + "0.06380000" + ], + [ + "4864.00", + "0.19040000" + ], + [ + "4862.00", + "0.05400000" + ], + [ + "4860.81", + "12.55000000" + ], + [ + "4860.21", + "0.05000000" + ], + [ + "4860.00", + "1.75000000" + ], + [ + "4858.60", + "0.48600000" + ], + [ + "4856.60", + "0.11400000" + ], + [ + "4856.04", + "0.01504106" + ], + [ + "4855.00", + "0.01000000" + ], + [ + "4853.33", + "0.00906597" + ], + [ + "4853.00", + "0.01000000" + ], + [ + "4851.06", + "0.19061000" + ], + [ + "4851.00", + "0.88148643" + ], + [ + "4850.52", + "0.00150000" + ], + [ + "4850.00", + "2.04879382" + ], + [ + "4848.69", + "12.55000000" + ], + [ + "4847.37", + "0.50000000" + ], + [ + "4846.58", + "0.07560000" + ], + [ + "4843.00", + "0.01000000" + ], + [ + "4842.18", + "0.00550000" + ], + [ + "4840.00", + "0.20000000" + ], + [ + "4839.00", + "0.00500000" + ], + [ + "4836.60", + "12.55000000" + ], + [ + "4829.00", + "0.19600000" + ], + [ + "4827.00", + "0.03835353" + ], + [ + "4826.00", + "0.08000000" + ], + [ + "4824.53", + "12.55000000" + ], + [ + "4824.00", + "0.09300000" + ], + [ + "4823.00", + "0.54681097" + ], + [ + "4821.00", + "0.17400000" + ], + [ + "4820.30", + "0.50000000" + ], + [ + "4820.00", + "1.20000000" + ], + [ + "4818.00", + "0.56787775" + ], + [ + "4815.66", + "0.50000000" + ], + [ + "4815.00", + "0.08609968" + ], + [ + "4812.50", + "12.55000000" + ], + [ + "4812.03", + "0.10400000" + ], + [ + "4812.00", + "0.00700000" + ], + [ + "4811.00", + "1.00000000" + ], + [ + "4810.63", + "0.25000000" + ], + [ + "4810.00", + "0.00125000" + ], + [ + "4809.00", + "0.09200000" + ], + [ + "4807.00", + "0.00500000" + ], + [ + "4802.86", + "0.01000000" + ], + [ + "4802.00", + "0.05482999" + ], + [ + "4801.16", + "0.48843196" + ], + [ + "4801.00", + "2.26960000" + ], + [ + "4800.84", + "0.00600000" + ], + [ + "4800.50", + "12.55000000" + ], + [ + "4800.00", + "10.10240638" + ], + [ + "4797.00", + "0.01000000" + ], + [ + "4796.58", + "0.15000000" + ], + [ + "4792.14", + "0.00510000" + ], + [ + "4791.00", + "0.10000000" + ], + [ + "4790.06", + "0.50000000" + ], + [ + "4788.53", + "12.55000000" + ], + [ + "4788.00", + "0.01000000" + ], + [ + "4784.25", + "0.00360140" + ], + [ + "4783.06", + "0.27371436" + ], + [ + "4782.00", + "0.62000000" + ], + [ + "4781.00", + "0.00209161" + ], + [ + "4780.00", + "17.17782426" + ], + [ + "4779.00", + "8.00209249" + ], + [ + "4777.00", + "0.08267323" + ], + [ + "4776.59", + "1812.55000000" + ], + [ + "4775.12", + "0.03000000" + ], + [ + "4773.00", + "0.00430000" + ], + [ + "4771.00", + "0.44400000" + ], + [ + "4770.00", + "0.11000000" + ], + [ + "4768.00", + "0.00500000" + ], + [ + "4766.67", + "0.01048948" + ], + [ + "4766.00", + "1.00000000" + ], + [ + "4764.68", + "12.55000000" + ], + [ + "4763.00", + "50.00000000" + ], + [ + "4762.00", + "0.33000000" + ], + [ + "4760.00", + "0.06200000" + ], + [ + "4758.00", + "0.18600000" + ], + [ + "4757.77", + "0.03830000" + ], + [ + "4755.00", + "5.00000000" + ], + [ + "4752.80", + "7.81502992" + ], + [ + "4752.63", + "0.00348837" + ], + [ + "4752.00", + "0.54000000" + ], + [ + "4750.00", + "4.15774561" + ], + [ + "4747.00", + "0.01000000" + ], + [ + "4742.98", + "0.03180598" + ], + [ + "4742.60", + "1.08860000" + ], + [ + "4741.24", + "0.09200000" + ], + [ + "4740.94", + "12.55000000" + ], + [ + "4739.00", + "0.01000000" + ], + [ + "4738.00", + "0.26200000" + ], + [ + "4735.83", + "0.72600000" + ], + [ + "4729.58", + "0.46000000" + ], + [ + "4729.12", + "12.55000000" + ], + [ + "4729.00", + "0.01000000" + ], + [ + "4728.00", + "0.27000000" + ], + [ + "4724.32", + "0.01000000" + ], + [ + "4721.00", + "0.25000000" + ], + [ + "4720.02", + "0.00510000" + ], + [ + "4720.00", + "0.13656568" + ], + [ + "4717.33", + "12.55000000" + ], + [ + "4717.00", + "0.20668751" + ], + [ + "4715.60", + "0.25600000" + ], + [ + "4714.00", + "0.63000000" + ], + [ + "4713.00", + "0.12600000" + ], + [ + "4711.06", + "0.19628000" + ], + [ + "4710.00", + "7.00127000" + ], + [ + "4709.00", + "0.06600000" + ], + [ + "4708.83", + "0.67580000" + ], + [ + "4708.12", + "0.04200000" + ], + [ + "4708.02", + "0.11400000" + ], + [ + "4707.00", + "8.54000000" + ], + [ + "4706.05", + "0.29400000" + ], + [ + "4706.00", + "0.50000000" + ], + [ + "4705.56", + "12.55000000" + ], + [ + "4704.02", + "0.11160000" + ], + [ + "4702.58", + "0.08400000" + ], + [ + "4701.00", + "0.45000000" + ], + [ + "4700.00", + "1.15307041" + ], + [ + "4698.75", + "0.25000000" + ], + [ + "4692.00", + "0.03000000" + ], + [ + "4691.00", + "0.45200000" + ], + [ + "4690.00", + "1.00000000" + ], + [ + "4689.58", + "0.08280000" + ], + [ + "4688.00", + "0.00500000" + ], + [ + "4687.00", + "0.03000000" + ], + [ + "4686.00", + "0.50000000" + ], + [ + "4681.67", + "0.01082233" + ], + [ + "4681.60", + "0.53400000" + ], + [ + "4679.60", + "0.12600000" + ], + [ + "4677.00", + "0.20000000" + ], + [ + "4676.00", + "0.20000000" + ], + [ + "4671.07", + "0.03000000" + ], + [ + "4671.00", + "0.25000000" + ], + [ + "4670.00", + "8.68000000" + ], + [ + "4667.84", + "0.04978806" + ], + [ + "4666.00", + "1.00000000" + ], + [ + "4663.58", + "0.16200000" + ], + [ + "4660.61", + "0.20000000" + ], + [ + "4660.00", + "0.25000000" + ], + [ + "4658.21", + "0.01379285" + ], + [ + "4655.00", + "0.06960000" + ], + [ + "4653.00", + "0.01000000" + ], + [ + "4651.00", + "0.25000000" + ], + [ + "4648.00", + "0.00500000" + ], + [ + "4641.10", + "0.00510000" + ], + [ + "4635.00", + "0.01000000" + ], + [ + "4634.00", + "0.00430000" + ], + [ + "4631.00", + "46.19163735" + ], + [ + "4629.00", + "0.02500000" + ], + [ + "4625.81", + "0.01000000" + ], + [ + "4625.00", + "0.18992648" + ], + [ + "4621.00", + "0.22702445" + ], + [ + "4620.00", + "0.20000000" + ], + [ + "4619.00", + "0.03000000" + ], + [ + "4618.00", + "0.06600000" + ], + [ + "4616.00", + "0.00500000" + ], + [ + "4610.00", + "10.00130000" + ], + [ + "4609.25", + "0.25000000" + ], + [ + "4608.28", + "1.00000000" + ], + [ + "4608.08", + "2.00000000" + ], + [ + "4604.65", + "0.01083036" + ], + [ + "4604.44", + "0.01187264" + ], + [ + "4603.00", + "0.00500000" + ], + [ + "4601.00", + "0.20000000" + ], + [ + "4600.00", + "6.84454565" + ], + [ + "4599.33", + "0.10000000" + ], + [ + "4597.88", + "0.50000000" + ], + [ + "4596.00", + "0.20400000" + ], + [ + "4591.00", + "0.09900000" + ], + [ + "4589.60", + "1.17900000" + ], + [ + "4588.24", + "0.10000000" + ], + [ + "4588.00", + "0.00500000" + ], + [ + "4585.00", + "0.10800000" + ], + [ + "4582.83", + "0.79000000" + ], + [ + "4581.21", + "0.10000000" + ], + [ + "4576.58", + "0.50000000" + ], + [ + "4575.81", + "0.50000000" + ], + [ + "4575.00", + "0.34000000" + ], + [ + "4573.70", + "0.18462732" + ], + [ + "4570.23", + "0.01499999" + ], + [ + "4568.00", + "0.07380000" + ], + [ + "4567.00", + "0.34001094" + ], + [ + "4566.00", + "1.13680000" + ], + [ + "4563.00", + "0.25412667" + ], + [ + "4561.00", + "1.35000000" + ], + [ + "4558.00", + "8.79036946" + ], + [ + "4556.00", + "0.07800000" + ], + [ + "4555.00", + "0.33000000" + ], + [ + "4553.00", + "0.00728653" + ], + [ + "4552.00", + "0.01631371" + ], + [ + "4551.06", + "0.20331527" + ], + [ + "4551.00", + "0.50120000" + ], + [ + "4550.00", + "5.07037464" + ], + [ + "4545.00", + "0.04400000" + ], + [ + "4542.60", + "0.28000000" + ], + [ + "4541.00", + "0.69000000" + ], + [ + "4540.00", + "0.13800000" + ], + [ + "4539.00", + "0.03000000" + ], + [ + "4538.00", + "0.42200000" + ], + [ + "4535.83", + "0.73340000" + ], + [ + "4535.12", + "0.04600000" + ], + [ + "4535.02", + "0.12200000" + ], + [ + "4533.05", + "0.32200000" + ], + [ + "4532.58", + "0.09000000" + ], + [ + "4531.10", + "0.00480000" + ], + [ + "4531.02", + "0.12180000" + ], + [ + "4531.00", + "1.01000000" + ], + [ + "4530.58", + "0.17400000" + ], + [ + "4529.58", + "0.09200000" + ], + [ + "4529.00", + "0.35000000" + ], + [ + "4527.00", + "0.40000000" + ], + [ + "4526.32", + "0.00385658" + ], + [ + "4526.00", + "1.25000000" + ], + [ + "4525.00", + "0.19800000" + ], + [ + "4524.86", + "0.25000000" + ], + [ + "4524.00", + "1.00000000" + ], + [ + "4522.22", + "0.01253073" + ], + [ + "4521.00", + "0.95000000" + ], + [ + "4520.00", + "6.50000000" + ], + [ + "4518.00", + "0.11400000" + ], + [ + "4512.00", + "6.00000000" + ], + [ + "4510.00", + "0.00136000" + ], + [ + "4508.34", + "0.27969496" + ], + [ + "4505.00", + "4.47500000" + ], + [ + "4504.60", + "0.58200000" + ], + [ + "4504.31", + "0.01107162" + ], + [ + "4503.00", + "2.14948922" + ], + [ + "4502.60", + "0.13800000" + ], + [ + "4501.09", + "0.24950178" + ], + [ + "4501.00", + "1.35000000" + ], + [ + "4500.00", + "25.73536387" + ], + [ + "4499.46", + "0.50000000" + ], + [ + "4499.00", + "0.00430000" + ], + [ + "4498.00", + "0.00500000" + ], + [ + "4494.47", + "0.20000000" + ], + [ + "4494.00", + "0.02500000" + ], + [ + "4490.00", + "0.63800000" + ], + [ + "4486.00", + "1.25000000" + ], + [ + "4483.00", + "0.50000000" + ], + [ + "4480.00", + "0.50000000" + ], + [ + "4478.00", + "1.75000000" + ], + [ + "4477.19", + "0.01000000" + ], + [ + "4475.56", + "0.01271098" + ], + [ + "4474.47", + "0.20000000" + ], + [ + "4474.05", + "0.45000000" + ], + [ + "4471.04", + "2.35159000" + ], + [ + "4470.00", + "0.61840492" + ], + [ + "4469.97", + "0.20000000" + ], + [ + "4469.55", + "0.00931600" + ], + [ + "4466.00", + "1.75000000" + ], + [ + "4465.86", + "0.06374584" + ], + [ + "4465.00", + "0.07800000" + ], + [ + "4461.00", + "2.10354565" + ], + [ + "4460.00", + "0.50000000" + ], + [ + "4454.47", + "0.20000000" + ], + [ + "4454.00", + "0.57000000" + ], + [ + "4450.00", + "15.30000000" + ], + [ + "4448.00", + "0.05000000" + ], + [ + "4444.00", + "6.60000000" + ], + [ + "4440.35", + "0.05198915" + ], + [ + "4437.12", + "0.01499999" + ], + [ + "4437.00", + "0.18800000" + ], + [ + "4436.60", + "1.26940000" + ], + [ + "4435.95", + "3.00000000" + ], + [ + "4435.24", + "0.10800000" + ], + [ + "4434.47", + "0.20000000" + ], + [ + "4430.64", + "0.02257010" + ], + [ + "4429.87", + "0.01125767" + ], + [ + "4429.83", + "0.85400000" + ], + [ + "4427.00", + "0.01000000" + ], + [ + "4425.00", + "0.75000000" + ], + [ + "4423.58", + "0.54000000" + ], + [ + "4422.10", + "0.00510000" + ], + [ + "4422.00", + "0.07380000" + ], + [ + "4420.30", + "1.50000000" + ], + [ + "4418.28", + "2.00000000" + ], + [ + "4415.00", + "0.12840030" + ], + [ + "4414.47", + "0.20000000" + ], + [ + "4414.00", + "0.06000000" + ], + [ + "4413.00", + "1.28700000" + ], + [ + "4411.00", + "1.00000000" + ], + [ + "4410.00", + "0.25136000" + ], + [ + "4406.12", + "1.62336704" + ], + [ + "4401.00", + "100.20000000" + ], + [ + "4400.00", + "16.90887333" + ], + [ + "4398.00", + "0.10880000" + ], + [ + "4397.58", + "0.18600000" + ], + [ + "4394.47", + "0.20000000" + ], + [ + "4383.00", + "0.10000000" + ], + [ + "4375.58", + "0.09720000" + ], + [ + "4374.47", + "0.20000000" + ], + [ + "4369.60", + "0.30400000" + ], + [ + "4368.00", + "0.25430000" + ], + [ + "4367.00", + "0.15000000" + ], + [ + "4366.00", + "1.00000000" + ], + [ + "4363.00", + "0.21200000" + ], + [ + "4362.83", + "0.54100000" + ], + [ + "4362.12", + "0.05000000" + ], + [ + "4362.02", + "0.13000000" + ], + [ + "4361.01", + "1.05400000" + ], + [ + "4360.05", + "0.35000000" + ], + [ + "4358.02", + "0.13200000" + ], + [ + "4358.00", + "0.10500000" + ], + [ + "4356.58", + "0.10000000" + ], + [ + "4356.00", + "0.25000000" + ], + [ + "4355.56", + "0.01499994" + ], + [ + "4354.47", + "0.20000000" + ], + [ + "4352.00", + "0.11600000" + ], + [ + "4350.00", + "9.68389775" + ], + [ + "4336.00", + "0.50000000" + ], + [ + "4335.00", + "0.07800000" + ], + [ + "4334.47", + "0.20000000" + ], + [ + "4332.00", + "0.33000000" + ], + [ + "4331.00", + "1.00000000" + ], + [ + "4327.60", + "0.63000000" + ], + [ + "4326.00", + "0.50000000" + ], + [ + "4325.60", + "0.15000000" + ], + [ + "4325.00", + "0.11600000" + ], + [ + "4323.83", + "0.50000000" + ], + [ + "4323.00", + "0.10900000" + ], + [ + "4321.00", + "0.50000000" + ], + [ + "4320.00", + "0.12000000" + ], + [ + "4315.00", + "0.20000000" + ], + [ + "4314.47", + "0.20000000" + ], + [ + "4310.00", + "2.01340000" + ], + [ + "4308.23", + "0.32531457" + ], + [ + "4307.88", + "0.01499999" + ], + [ + "4306.60", + "0.00510000" + ], + [ + "4305.00", + "0.45400000" + ], + [ + "4301.00", + "3.28848848" + ], + [ + "4300.00", + "38.68397171" + ], + [ + "4299.98", + "0.58517714" + ], + [ + "4296.00", + "0.37000000" + ], + [ + "4294.47", + "0.20000000" + ], + [ + "4292.00", + "0.21000000" + ], + [ + "4289.89", + "0.03290000" + ], + [ + "4289.27", + "0.02778643" + ], + [ + "4287.00", + "8.20000000" + ], + [ + "4283.60", + "1.35980000" + ], + [ + "4282.24", + "0.11600000" + ], + [ + "4280.00", + "0.02500000" + ], + [ + "4278.00", + "0.00500000" + ], + [ + "4277.78", + "0.01480517" + ], + [ + "4277.00", + "11.39828851" + ], + [ + "4276.83", + "0.91800000" + ], + [ + "4274.47", + "0.20000000" + ], + [ + "4272.00", + "0.29000000" + ], + [ + "4270.58", + "0.58000000" + ], + [ + "4269.44", + "0.01478209" + ], + [ + "4267.00", + "0.48664635" + ], + [ + "4266.20", + "50.01000000" + ], + [ + "4266.18", + "2.00000000" + ], + [ + "4266.00", + "1.07000000" + ], + [ + "4264.58", + "0.19800000" + ], + [ + "4260.00", + "0.14200000" + ], + [ + "4259.97", + "0.20000000" + ], + [ + "4255.89", + "3.50000000" + ], + [ + "4254.86", + "0.30835327" + ], + [ + "4254.47", + "0.20000000" + ], + [ + "4252.59", + "14.92872343" + ], + [ + "4252.00", + "0.01307855" + ], + [ + "4251.39", + "0.01000000" + ], + [ + "4250.00", + "5.06752941" + ], + [ + "4241.00", + "0.00430000" + ], + [ + "4238.88", + "2.00000000" + ], + [ + "4234.47", + "0.20000000" + ], + [ + "4228.00", + "0.10000000" + ], + [ + "4226.60", + "0.00510000" + ], + [ + "4225.00", + "0.15000000" + ], + [ + "4222.60", + "1.00000000" + ], + [ + "4222.00", + "2.42741828" + ], + [ + "4220.00", + "0.50000000" + ], + [ + "4219.00", + "0.01000000" + ], + [ + "4218.58", + "0.10440000" + ], + [ + "4215.00", + "3.52102016" + ], + [ + "4214.47", + "0.20000000" + ], + [ + "4213.00", + "1.00000000" + ], + [ + "4211.00", + "0.14000000" + ], + [ + "4210.00", + "2.28684329" + ], + [ + "4206.02", + "1.18900000" + ], + [ + "4204.00", + "0.19600000" + ], + [ + "4201.00", + "0.20000000" + ], + [ + "4200.20", + "0.00600000" + ], + [ + "4200.00", + "13.84658868" + ], + [ + "4199.00", + "0.23107644" + ], + [ + "4196.60", + "0.32800000" + ], + [ + "4194.47", + "0.20000000" + ], + [ + "4194.00", + "0.16200000" + ], + [ + "4189.83", + "0.57860000" + ], + [ + "4189.12", + "0.05400000" + ], + [ + "4189.02", + "0.13800000" + ], + [ + "4189.00", + "0.07800000" + ], + [ + "4187.05", + "0.37800000" + ], + [ + "4185.02", + "0.14220000" + ], + [ + "4183.58", + "0.10800000" + ], + [ + "4182.41", + "0.01499999" + ], + [ + "4181.09", + "0.01993100" + ], + [ + "4179.68", + "0.00600000" + ], + [ + "4178.00", + "0.49701292" + ], + [ + "4175.50", + "0.00694042" + ], + [ + "4174.47", + "0.20000000" + ], + [ + "4173.00", + "0.04000000" + ], + [ + "4171.48", + "0.02000000" + ], + [ + "4166.00", + "1.00000000" + ], + [ + "4165.00", + "0.11300000" + ], + [ + "4163.50", + "0.20000000" + ], + [ + "4160.00", + "0.32000000" + ], + [ + "4154.47", + "0.20000000" + ], + [ + "4150.60", + "0.67800000" + ], + [ + "4150.00", + "12.04781927" + ], + [ + "4148.60", + "0.16200000" + ], + [ + "4145.75", + "1.79384700" + ], + [ + "4141.00", + "0.15000000" + ], + [ + "4134.47", + "0.20000000" + ], + [ + "4131.58", + "0.21000000" + ], + [ + "4130.60", + "1.45020000" + ], + [ + "4130.00", + "0.22000000" + ], + [ + "4129.24", + "0.12400000" + ], + [ + "4129.01", + "0.01000000" + ], + [ + "4126.41", + "1.00000000" + ], + [ + "4125.00", + "0.11100000" + ], + [ + "4123.83", + "0.98200000" + ], + [ + "4123.40", + "1.00000000" + ], + [ + "4123.00", + "1.00000000" + ], + [ + "4120.00", + "0.00500000" + ], + [ + "4119.00", + "0.12400000" + ], + [ + "4118.08", + "0.10000000" + ], + [ + "4117.58", + "0.62000000" + ], + [ + "4117.00", + "0.00430000" + ], + [ + "4115.00", + "0.01000000" + ], + [ + "4114.47", + "0.20000000" + ], + [ + "4113.00", + "0.07384147" + ], + [ + "4111.00", + "0.60000000" + ], + [ + "4110.00", + "2.01047000" + ], + [ + "4109.45", + "0.01950000" + ], + [ + "4107.87", + "0.53848344" + ], + [ + "4106.67", + "0.01850644" + ], + [ + "4102.00", + "3.08220000" + ], + [ + "4101.00", + "0.10000000" + ], + [ + "4100.00", + "28.63516938" + ], + [ + "4094.47", + "0.20000000" + ], + [ + "4092.00", + "0.12400000" + ], + [ + "4091.67", + "0.05000000" + ], + [ + "4090.00", + "10.00000000" + ], + [ + "4089.00", + "0.35000000" + ], + [ + "4088.00", + "0.01000000" + ], + [ + "4084.17", + "0.01000000" + ], + [ + "4080.00", + "10.10500000" + ], + [ + "4078.00", + "0.01000000" + ], + [ + "4075.00", + "2.58661595" + ], + [ + "4074.47", + "0.20000000" + ], + [ + "4072.00", + "0.48600000" + ], + [ + "4070.00", + "10.00000000" + ], + [ + "4063.33", + "0.01706321" + ], + [ + "4063.13", + "0.00203036" + ], + [ + "4063.00", + "0.39000000" + ], + [ + "4061.58", + "0.11160000" + ], + [ + "4060.59", + "0.01499999" + ], + [ + "4060.00", + "10.50000000" + ], + [ + "4059.00", + "0.22200000" + ], + [ + "4055.29", + "0.01238575" + ], + [ + "4054.47", + "0.20000000" + ], + [ + "4050.00", + "14.46214568" + ], + [ + "4043.33", + "0.01000000" + ], + [ + "4040.00", + "10.00000000" + ], + [ + "4039.00", + "0.30400000" + ], + [ + "4037.23", + "0.09000000" + ], + [ + "4034.47", + "0.20000000" + ], + [ + "4033.33", + "0.01735541" + ], + [ + "4033.07", + "0.10000000" + ], + [ + "4030.00", + "10.07593983" + ], + [ + "4026.00", + "0.00500000" + ], + [ + "4024.42", + "0.10000000" + ], + [ + "4023.60", + "0.35200000" + ], + [ + "4023.12", + "0.00300000" + ], + [ + "4021.20", + "0.00500000" + ], + [ + "4021.00", + "0.17400000" + ], + [ + "4020.00", + "10.00000000" + ], + [ + "4016.83", + "0.61620000" + ], + [ + "4016.12", + "0.05800000" + ], + [ + "4016.02", + "0.14600000" + ], + [ + "4014.47", + "0.20000000" + ], + [ + "4014.05", + "0.11600000" + ], + [ + "4012.02", + "0.15240000" + ], + [ + "4011.10", + "0.10000000" + ], + [ + "4011.00", + "0.01000000" + ], + [ + "4010.58", + "0.11600000" + ], + [ + "4010.00", + "12.00150000" + ], + [ + "4005.45", + "0.01000000" + ], + [ + "4005.00", + "3.00000000" + ], + [ + "4004.19", + "0.10000000" + ], + [ + "4004.00", + "0.01000000" + ], + [ + "4002.00", + "0.40166667" + ], + [ + "4001.13", + "0.47160927" + ], + [ + "4001.11", + "0.10000000" + ], + [ + "4001.00", + "0.62292414" + ], + [ + "4000.79", + "0.00700000" + ], + [ + "4000.08", + "0.23314882" + ], + [ + "4000.00", + "16.31307329" + ], + [ + "3999.99", + "0.44197360" + ], + [ + "3999.00", + "1.00000000" + ], + [ + "3998.58", + "0.22200000" + ], + [ + "3997.00", + "0.00430000" + ], + [ + "3996.17", + "0.97000000" + ], + [ + "3994.47", + "0.20000000" + ], + [ + "3994.00", + "0.02000000" + ], + [ + "3992.51", + "1.00000000" + ], + [ + "3990.00", + "0.15912781" + ], + [ + "3988.00", + "0.05000000" + ], + [ + "3980.00", + "0.05000000" + ], + [ + "3977.60", + "1.54060000" + ], + [ + "3976.24", + "0.13200000" + ], + [ + "3974.47", + "0.20000000" + ], + [ + "3973.60", + "0.72600000" + ], + [ + "3971.60", + "0.17400000" + ], + [ + "3970.83", + "0.88100000" + ], + [ + "3966.22", + "0.00158140" + ], + [ + "3965.00", + "1.00000000" + ], + [ + "3964.58", + "0.66000000" + ], + [ + "3961.00", + "7.00000000" + ], + [ + "3960.00", + "0.03148674" + ], + [ + "3959.97", + "0.20000000" + ], + [ + "3959.00", + "0.01000000" + ], + [ + "3956.00", + "0.08220000" + ], + [ + "3955.00", + "0.05200000" + ], + [ + "3954.47", + "0.20000000" + ], + [ + "3953.00", + "0.01594408" + ], + [ + "3951.00", + "0.00500000" + ], + [ + "3950.00", + "0.04433442" + ], + [ + "3946.10", + "0.06720902" + ], + [ + "3942.32", + "0.01499999" + ], + [ + "3938.00", + "0.02000000" + ], + [ + "3934.47", + "0.20000000" + ], + [ + "3933.05", + "0.10000000" + ], + [ + "3932.00", + "0.11720000" + ], + [ + "3929.00", + "0.01000000" + ], + [ + "3920.00", + "1.00000000" + ], + [ + "3914.47", + "0.20000000" + ], + [ + "3913.14", + "0.41500000" + ], + [ + "3911.28", + "0.00500000" + ], + [ + "3910.00", + "2.04654000" + ], + [ + "3907.00", + "0.76000000" + ], + [ + "3904.58", + "0.11880000" + ], + [ + "3900.00", + "5.17258917" + ], + [ + "3897.00", + "0.11983106" + ], + [ + "3894.73", + "0.20000000" + ], + [ + "3892.00", + "0.11700000" + ], + [ + "3891.00", + "0.22063222" + ], + [ + "3890.00", + "0.03205334" + ], + [ + "3889.00", + "0.06000000" + ], + [ + "3888.88", + "0.00128888" + ], + [ + "3888.00", + "0.10000000" + ], + [ + "3886.50", + "0.20000000" + ], + [ + "3886.00", + "0.13200000" + ], + [ + "3882.00", + "0.15000000" + ], + [ + "3881.00", + "0.00430000" + ], + [ + "3879.00", + "0.01000000" + ], + [ + "3877.00", + "0.00500000" + ], + [ + "3876.00", + "1.00000000" + ], + [ + "3874.73", + "0.20000000" + ], + [ + "3873.00", + "0.00500000" + ], + [ + "3869.00", + "0.08640000" + ], + [ + "3865.85", + "0.20000000" + ], + [ + "3865.58", + "0.23400000" + ], + [ + "3863.00", + "0.14658814" + ], + [ + "3862.00", + "0.00500000" + ], + [ + "3859.00", + "0.13200000" + ], + [ + "3857.78", + "0.02246540" + ], + [ + "3857.22", + "0.01958810" + ], + [ + "3857.00", + "0.10000000" + ], + [ + "3856.00", + "0.00500000" + ], + [ + "3854.73", + "0.20000000" + ], + [ + "3853.00", + "0.66105372" + ], + [ + "3850.60", + "0.37600000" + ], + [ + "3850.59", + "0.01000000" + ], + [ + "3850.00", + "1.00809870" + ], + [ + "3849.00", + "0.01000000" + ], + [ + "3848.00", + "0.01000000" + ], + [ + "3846.00", + "0.37000000" + ], + [ + "3845.21", + "0.10000000" + ], + [ + "3843.83", + "0.34380000" + ], + [ + "3843.12", + "0.06200000" + ], + [ + "3841.16", + "0.00150000" + ], + [ + "3841.05", + "0.12400000" + ], + [ + "3839.02", + "0.16260000" + ], + [ + "3839.00", + "0.53800000" + ], + [ + "3838.00", + "0.01000000" + ], + [ + "3837.58", + "0.12400000" + ], + [ + "3837.00", + "0.11360620" + ], + [ + "3835.75", + "0.25100000" + ], + [ + "3835.00", + "7.00000000" + ], + [ + "3834.73", + "0.20000000" + ], + [ + "3832.11", + "0.00500000" + ], + [ + "3831.41", + "0.31826664" + ], + [ + "3831.35", + "0.10000000" + ], + [ + "3830.00", + "0.54900000" + ], + [ + "3828.00", + "0.01000000" + ], + [ + "3827.50", + "0.01499999" + ], + [ + "3826.13", + "5.62379000" + ], + [ + "3826.00", + "0.23400000" + ], + [ + "3824.60", + "1.63100000" + ], + [ + "3824.40", + "0.05000000" + ], + [ + "3822.00", + "0.09617216" + ], + [ + "3820.00", + "0.03264071" + ], + [ + "3819.96", + "1.00000000" + ], + [ + "3817.83", + "0.93500000" + ], + [ + "3814.73", + "0.20000000" + ], + [ + "3813.13", + "1.49388000" + ], + [ + "3813.00", + "0.01000000" + ], + [ + "3812.00", + "0.00262329" + ], + [ + "3811.58", + "0.70000000" + ], + [ + "3810.00", + "2.00157000" + ], + [ + "3808.00", + "0.00500000" + ], + [ + "3806.00", + "0.31800000" + ], + [ + "3805.00", + "1.94978975" + ], + [ + "3803.00", + "0.01000000" + ], + [ + "3802.00", + "19.61145186" + ], + [ + "3801.55", + "0.01115150" + ], + [ + "3801.00", + "0.82200000" + ], + [ + "3800.00", + "18.53887962" + ], + [ + "3798.00", + "0.01000000" + ], + [ + "3797.00", + "0.01000000" + ], + [ + "3794.73", + "0.20000000" + ], + [ + "3788.89", + "0.02023459" + ], + [ + "3788.00", + "0.75000000" + ], + [ + "3787.00", + "0.01000000" + ], + [ + "3785.68", + "0.00600000" + ], + [ + "3780.00", + "0.01355820" + ], + [ + "3776.00", + "0.89913612" + ], + [ + "3775.00", + "10.00000000" + ], + [ + "3774.73", + "0.20000000" + ], + [ + "3773.27", + "1.00000000" + ], + [ + "3771.00", + "1.00000000" + ], + [ + "3768.00", + "0.00430000" + ], + [ + "3757.00", + "0.01000000" + ], + [ + "3754.94", + "0.05745497" + ], + [ + "3754.73", + "0.20000000" + ], + [ + "3754.00", + "0.00266383" + ], + [ + "3751.00", + "0.86120000" + ], + [ + "3750.00", + "4.93878449" + ], + [ + "3748.00", + "0.00500000" + ], + [ + "3737.00", + "0.01000000" + ], + [ + "3734.73", + "0.20000000" + ], + [ + "3733.78", + "0.08000000" + ], + [ + "3726.00", + "0.91700000" + ], + [ + "3725.00", + "0.02292254" + ], + [ + "3724.00", + "4.83808094" + ], + [ + "3720.00", + "0.44123387" + ], + [ + "3719.30", + "1.50000000" + ], + [ + "3717.00", + "0.01000000" + ], + [ + "3715.00", + "10.00000000" + ], + [ + "3714.73", + "0.20000000" + ], + [ + "3710.00", + "2.00162000" + ], + [ + "3707.00", + "0.00500000" + ], + [ + "3705.95", + "0.05000000" + ], + [ + "3701.00", + "0.87280000" + ], + [ + "3700.00", + "6.31582651" + ], + [ + "3699.00", + "0.01000000" + ], + [ + "3696.00", + "0.00500000" + ], + [ + "3694.73", + "0.20000000" + ], + [ + "3689.00", + "0.00162646" + ], + [ + "3688.09", + "0.00931932" + ], + [ + "3688.00", + "0.23500000" + ], + [ + "3683.00", + "0.32000000" + ], + [ + "3680.00", + "0.03388247" + ], + [ + "3676.00", + "0.87880000" + ], + [ + "3674.73", + "0.20000000" + ], + [ + "3668.40", + "0.05000000" + ], + [ + "3667.00", + "0.01000000" + ], + [ + "3666.00", + "0.00500000" + ], + [ + "3665.00", + "0.11000000" + ], + [ + "3661.00", + "0.00140000" + ], + [ + "3658.00", + "0.00430000" + ], + [ + "3654.73", + "0.20000000" + ], + [ + "3653.53", + "0.00844115" + ], + [ + "3653.00", + "0.11000000" + ], + [ + "3651.20", + "0.00500000" + ], + [ + "3651.11", + "0.02239807" + ], + [ + "3651.00", + "0.88480000" + ], + [ + "3650.00", + "4.41665469" + ], + [ + "3648.00", + "5.00000000" + ], + [ + "3647.00", + "0.05000000" + ], + [ + "3639.00", + "0.02500000" + ], + [ + "3637.00", + "0.01000000" + ], + [ + "3634.73", + "0.20000000" + ], + [ + "3626.00", + "0.89090000" + ], + [ + "3625.68", + "1.00000000" + ], + [ + "3625.00", + "0.20000000" + ], + [ + "3616.85", + "0.05000000" + ], + [ + "3614.73", + "0.20000000" + ], + [ + "3613.15", + "0.01499999" + ], + [ + "3611.00", + "1.10000000" + ], + [ + "3610.04", + "0.00220219" + ], + [ + "3610.00", + "2.00168000" + ], + [ + "3609.00", + "0.28630091" + ], + [ + "3608.89", + "0.02697042" + ], + [ + "3607.00", + "0.01000000" + ], + [ + "3606.89", + "1.00000000" + ], + [ + "3603.00", + "0.01000000" + ], + [ + "3601.00", + "0.89710000" + ], + [ + "3600.00", + "0.82261648" + ], + [ + "3596.00", + "0.05000000" + ], + [ + "3595.00", + "0.01000000" + ], + [ + "3594.73", + "0.20000000" + ], + [ + "3590.00", + "0.05000000" + ], + [ + "3589.97", + "0.10000000" + ], + [ + "3588.88", + "1.00000000" + ], + [ + "3588.00", + "0.05000000" + ], + [ + "3587.88", + "0.10000000" + ], + [ + "3581.10", + "0.00500000" + ], + [ + "3579.54", + "0.00339863" + ], + [ + "3576.00", + "0.90330000" + ], + [ + "3575.26", + "0.05000000" + ], + [ + "3574.73", + "0.20000000" + ], + [ + "3571.00", + "0.13500000" + ], + [ + "3570.00", + "0.10000000" + ], + [ + "3568.44", + "0.04966776" + ], + [ + "3567.22", + "0.20000000" + ], + [ + "3563.00", + "0.01000000" + ], + [ + "3559.00", + "0.02500000" + ], + [ + "3558.78", + "0.02809670" + ], + [ + "3558.00", + "0.70000000" + ], + [ + "3556.23", + "2.02467000" + ], + [ + "3555.00", + "0.02000000" + ], + [ + "3554.73", + "0.20000000" + ], + [ + "3551.00", + "0.91400000" + ], + [ + "3550.72", + "0.01500000" + ], + [ + "3550.00", + "1.58477183" + ], + [ + "3548.00", + "0.05000000" + ], + [ + "3546.66", + "0.00204535" + ], + [ + "3545.00", + "0.02200000" + ], + [ + "3544.44", + "0.02351104" + ], + [ + "3538.00", + "0.01000000" + ], + [ + "3537.01", + "0.02827000" + ], + [ + "3534.73", + "0.20000000" + ], + [ + "3531.50", + "0.01800000" + ], + [ + "3531.00", + "0.83150382" + ], + [ + "3526.00", + "0.91610000" + ], + [ + "3522.00", + "0.50000000" + ], + [ + "3520.00", + "0.01000000" + ], + [ + "3519.00", + "0.51000000" + ], + [ + "3514.73", + "0.20000000" + ], + [ + "3511.77", + "0.48000000" + ], + [ + "3510.00", + "4.30373000" + ], + [ + "3509.00", + "0.02000000" + ], + [ + "3507.91", + "0.01499999" + ], + [ + "3505.50", + "0.00500000" + ], + [ + "3502.00", + "1.50000000" + ], + [ + "3501.90", + "0.00227020" + ], + [ + "3501.00", + "0.98270000" + ], + [ + "3500.82", + "0.00900000" + ], + [ + "3500.00", + "29.52644856" + ], + [ + "3496.00", + "0.01000000" + ], + [ + "3494.73", + "0.20000000" + ], + [ + "3494.00", + "0.00500000" + ], + [ + "3491.00", + "0.01000000" + ], + [ + "3489.89", + "0.08101114" + ], + [ + "3488.00", + "0.05000000" + ], + [ + "3478.00", + "0.05000000" + ], + [ + "3475.00", + "1.10229640" + ], + [ + "3474.73", + "0.20000000" + ], + [ + "3467.79", + "0.10000000" + ], + [ + "3464.00", + "0.01000000" + ], + [ + "3459.00", + "0.05000000" + ], + [ + "3454.73", + "0.20000000" + ], + [ + "3450.00", + "10.00000000" + ], + [ + "3448.13", + "0.00217509" + ], + [ + "3448.00", + "0.00930000" + ], + [ + "3445.00", + "0.02554427" + ], + [ + "3444.00", + "0.05000000" + ], + [ + "3441.00", + "0.00400000" + ], + [ + "3440.00", + "0.40000000" + ], + [ + "3434.73", + "0.20000000" + ], + [ + "3432.00", + "0.26927738" + ], + [ + "3429.00", + "0.05000000" + ], + [ + "3420.97", + "0.00500000" + ], + [ + "3419.00", + "0.05000000" + ], + [ + "3414.73", + "0.20000000" + ], + [ + "3414.00", + "0.01000000" + ], + [ + "3412.85", + "0.00219758" + ], + [ + "3411.10", + "0.00500000" + ], + [ + "3411.00", + "1.00000000" + ], + [ + "3410.00", + "2.00178000" + ], + [ + "3409.00", + "0.05000000" + ], + [ + "3405.74", + "0.01499999" + ], + [ + "3404.00", + "0.00700000" + ], + [ + "3402.00", + "4.00000000" + ], + [ + "3401.00", + "0.17235999" + ], + [ + "3400.00", + "4.46965126" + ], + [ + "3399.97", + "0.30000000" + ], + [ + "3399.32", + "0.30000000" + ], + [ + "3397.79", + "3.90264407" + ], + [ + "3394.73", + "0.20000000" + ], + [ + "3389.00", + "0.05000000" + ], + [ + "3388.00", + "0.02000000" + ], + [ + "3387.00", + "0.01000000" + ], + [ + "3386.55", + "0.03435977" + ], + [ + "3383.00", + "0.07356488" + ], + [ + "3378.00", + "0.01000000" + ], + [ + "3374.73", + "0.20000000" + ], + [ + "3369.00", + "0.05000000" + ], + [ + "3360.00", + "0.78368250" + ], + [ + "3354.73", + "0.20000000" + ], + [ + "3350.00", + "1.30000000" + ], + [ + "3349.00", + "0.05000000" + ], + [ + "3348.00", + "0.00430000" + ], + [ + "3340.00", + "0.01500000" + ], + [ + "3338.00", + "0.02211500" + ], + [ + "3337.58", + "0.03339419" + ], + [ + "3335.00", + "0.08553523" + ], + [ + "3334.73", + "0.20000000" + ], + [ + "3333.00", + "146.26083108" + ], + [ + "3331.11", + "0.10000000" + ], + [ + "3330.01", + "0.09189600" + ], + [ + "3329.00", + "0.05000000" + ], + [ + "3325.00", + "0.00300000" + ], + [ + "3321.00", + "0.00300000" + ], + [ + "3320.00", + "1.00000000" + ], + [ + "3318.00", + "0.33579867" + ], + [ + "3314.73", + "0.20000000" + ], + [ + "3312.50", + "0.00580000" + ], + [ + "3310.00", + "2.10181000" + ], + [ + "3309.99", + "0.27658089" + ], + [ + "3309.00", + "0.05000000" + ], + [ + "3307.95", + "0.05199292" + ], + [ + "3306.54", + "0.01499999" + ], + [ + "3306.00", + "0.14000000" + ], + [ + "3305.00", + "0.10021180" + ], + [ + "3304.00", + "0.40000000" + ], + [ + "3303.00", + "7.05000000" + ], + [ + "3301.00", + "0.16777703" + ], + [ + "3300.98", + "0.00787100" + ], + [ + "3300.33", + "0.30000000" + ], + [ + "3300.05", + "3.03268132" + ], + [ + "3300.00", + "17.26844752" + ], + [ + "3298.00", + "0.31000000" + ], + [ + "3295.00", + "2.10000000" + ], + [ + "3294.73", + "0.20000000" + ], + [ + "3291.63", + "0.04000000" + ], + [ + "3288.00", + "0.50000000" + ], + [ + "3285.00", + "0.01000000" + ], + [ + "3283.00", + "1.01000000" + ], + [ + "3278.00", + "0.55000000" + ], + [ + "3277.00", + "0.27000000" + ], + [ + "3275.20", + "0.00500000" + ], + [ + "3275.00", + "0.25000000" + ], + [ + "3274.73", + "0.20000000" + ], + [ + "3270.21", + "0.10000000" + ], + [ + "3270.00", + "1.00000000" + ], + [ + "3269.00", + "0.25000000" + ], + [ + "3268.50", + "0.04382513" + ], + [ + "3267.00", + "0.25000000" + ], + [ + "3265.21", + "0.10000000" + ], + [ + "3262.00", + "0.81462311" + ], + [ + "3260.21", + "0.10000000" + ], + [ + "3259.00", + "1.00000000" + ], + [ + "3258.00", + "0.32500000" + ], + [ + "3256.13", + "0.19316796" + ], + [ + "3255.25", + "0.01000000" + ], + [ + "3255.21", + "0.10000000" + ], + [ + "3254.73", + "0.20000000" + ], + [ + "3252.00", + "0.06200000" + ], + [ + "3251.90", + "0.00244472" + ], + [ + "3251.21", + "1.50000000" + ], + [ + "3250.21", + "0.10000000" + ], + [ + "3250.00", + "2.02403230" + ], + [ + "3249.00", + "0.05000000" + ], + [ + "3248.01", + "0.05000000" + ], + [ + "3248.00", + "0.94000000" + ], + [ + "3247.00", + "0.25000000" + ], + [ + "3246.00", + "0.25000000" + ], + [ + "3245.21", + "0.10000000" + ], + [ + "3244.00", + "0.50000000" + ], + [ + "3243.31", + "0.05000000" + ], + [ + "3243.00", + "0.25000000" + ], + [ + "3240.45", + "0.30782360" + ], + [ + "3240.21", + "0.10000000" + ], + [ + "3240.00", + "0.00400000" + ], + [ + "3238.00", + "0.15000000" + ], + [ + "3237.11", + "0.00540000" + ], + [ + "3237.00", + "0.25000000" + ], + [ + "3235.21", + "0.10000000" + ], + [ + "3234.73", + "0.20000000" + ], + [ + "3234.41", + "0.00500000" + ], + [ + "3234.00", + "0.25000000" + ], + [ + "3233.00", + "2.00000000" + ], + [ + "3232.00", + "0.01000000" + ], + [ + "3230.21", + "0.10000000" + ], + [ + "3230.00", + "0.14757894" + ], + [ + "3229.00", + "0.25000000" + ], + [ + "3228.00", + "0.25000000" + ], + [ + "3225.21", + "11.61000000" + ], + [ + "3225.00", + "0.71635659" + ], + [ + "3224.84", + "0.05300000" + ], + [ + "3221.00", + "0.01682396" + ], + [ + "3220.00", + "1.00000000" + ], + [ + "3219.48", + "12.55000000" + ], + [ + "3218.00", + "0.50000000" + ], + [ + "3215.00", + "1.50893856" + ], + [ + "3214.73", + "0.20000000" + ], + [ + "3212.00", + "1.25000000" + ], + [ + "3211.45", + "12.55000000" + ], + [ + "3211.00", + "0.03000000" + ], + [ + "3210.99", + "1.00000000" + ], + [ + "3210.90", + "1.00000000" + ], + [ + "3210.00", + "2.00160000" + ], + [ + "3209.00", + "0.25000000" + ], + [ + "3205.20", + "0.00420000" + ], + [ + "3204.01", + "0.30000000" + ], + [ + "3204.00", + "7.00000000" + ], + [ + "3203.44", + "12.55000000" + ], + [ + "3203.00", + "31.14676865" + ], + [ + "3202.00", + "0.25000000" + ], + [ + "3200.00", + "6.45272187" + ], + [ + "3195.45", + "12.55000000" + ], + [ + "3195.00", + "0.25000000" + ], + [ + "3194.73", + "0.20000000" + ], + [ + "3193.78", + "0.10000000" + ], + [ + "3193.00", + "0.08856874" + ], + [ + "3188.00", + "1.76000000" + ], + [ + "3187.90", + "0.30000000" + ], + [ + "3187.48", + "12.55000000" + ], + [ + "3186.13", + "0.37994700" + ], + [ + "3185.00", + "0.00500000" + ], + [ + "3183.00", + "2.00000000" + ], + [ + "3182.01", + "1.10000000" + ], + [ + "3180.61", + "0.15918050" + ], + [ + "3180.00", + "1.02221698" + ], + [ + "3179.54", + "12.55000000" + ], + [ + "3178.00", + "0.75000000" + ], + [ + "3177.00", + "1.00000000" + ], + [ + "3176.00", + "0.26000000" + ], + [ + "3174.73", + "0.20000000" + ], + [ + "3174.00", + "0.25000000" + ], + [ + "3171.61", + "12.55000000" + ], + [ + "3168.00", + "1.25000000" + ], + [ + "3163.70", + "12.55000000" + ], + [ + "3163.32", + "0.03420000" + ], + [ + "3162.77", + "1.00000000" + ], + [ + "3158.00", + "1.01500000" + ], + [ + "3155.81", + "12.55000000" + ], + [ + "3154.73", + "0.20000000" + ], + [ + "3150.00", + "3.49417777" + ], + [ + "3147.94", + "12.55000000" + ], + [ + "3146.00", + "0.11000000" + ], + [ + "3142.04", + "0.00600000" + ], + [ + "3142.00", + "0.50000000" + ], + [ + "3140.09", + "12.55000000" + ], + [ + "3140.00", + "0.10000000" + ], + [ + "3138.68", + "1.00000000" + ], + [ + "3138.00", + "1.00000000" + ], + [ + "3134.73", + "0.20000000" + ], + [ + "3133.00", + "1.00500000" + ], + [ + "3132.26", + "12.60000000" + ], + [ + "3130.00", + "0.10000000" + ], + [ + "3128.00", + "1.26000000" + ], + [ + "3127.00", + "0.49024944" + ], + [ + "3126.00", + "0.00200000" + ], + [ + "3124.45", + "12.60000000" + ], + [ + "3122.55", + "0.94963091" + ], + [ + "3120.00", + "0.10500000" + ], + [ + "3118.82", + "0.00799341" + ], + [ + "3117.00", + "0.35000000" + ], + [ + "3116.65", + "12.55000000" + ], + [ + "3114.73", + "0.20000000" + ], + [ + "3113.13", + "0.16510714" + ], + [ + "3112.00", + "0.36000000" + ], + [ + "3111.70", + "0.10000000" + ], + [ + "3111.00", + "0.07000000" + ], + [ + "3110.00", + "0.10161000" + ], + [ + "3109.00", + "0.10000000" + ], + [ + "3108.88", + "12.55000000" + ], + [ + "3105.03", + "0.33239616" + ], + [ + "3105.00", + "1.80981323" + ], + [ + "3101.13", + "12.60000000" + ], + [ + "3101.00", + "0.50000000" + ], + [ + "3100.00", + "526.39378391" + ], + [ + "3099.00", + "3.00000000" + ], + [ + "3095.00", + "0.80148626" + ], + [ + "3094.73", + "0.20000000" + ], + [ + "3093.40", + "12.60000000" + ], + [ + "3091.75", + "100.20000000" + ], + [ + "3090.77", + "0.61500000" + ], + [ + "3090.30", + "0.00300000" + ], + [ + "3090.17", + "0.00420000" + ], + [ + "3090.00", + "0.10000000" + ], + [ + "3088.00", + "2.01619171" + ], + [ + "3085.68", + "12.60000000" + ], + [ + "3084.62", + "0.16000000" + ], + [ + "3082.84", + "0.03000000" + ], + [ + "3081.00", + "0.01280000" + ], + [ + "3080.00", + "0.10000000" + ], + [ + "3079.98", + "1.00000000" + ], + [ + "3078.00", + "0.00250000" + ], + [ + "3077.99", + "12.60000000" + ], + [ + "3077.00", + "0.00300000" + ], + [ + "3075.00", + "1.92055100" + ], + [ + "3074.73", + "0.20000000" + ], + [ + "3070.31", + "12.60000000" + ], + [ + "3070.00", + "0.10000000" + ], + [ + "3069.80", + "0.35688966" + ], + [ + "3068.00", + "0.25000000" + ], + [ + "3067.00", + "5.00000000" + ], + [ + "3065.33", + "0.20000000" + ], + [ + "3063.13", + "0.16780221" + ], + [ + "3062.65", + "12.60000000" + ], + [ + "3061.14", + "100.20000000" + ], + [ + "3060.00", + "0.10000000" + ], + [ + "3055.55", + "0.09270796" + ], + [ + "3055.00", + "1.31000000" + ], + [ + "3054.73", + "0.20000000" + ], + [ + "3052.00", + "0.27000000" + ], + [ + "3051.00", + "0.25000000" + ], + [ + "3050.00", + "3.30000000" + ], + [ + "3049.83", + "0.02000000" + ], + [ + "3049.48", + "0.40000000" + ], + [ + "3043.00", + "0.01000000" + ], + [ + "3042.82", + "1.63900000" + ], + [ + "3042.06", + "0.01132785" + ], + [ + "3042.00", + "0.02000000" + ], + [ + "3039.00", + "0.25000000" + ], + [ + "3038.02", + "0.16416943" + ], + [ + "3038.00", + "0.01645820" + ], + [ + "3035.69", + "0.15859086" + ], + [ + "3034.73", + "0.20000000" + ], + [ + "3033.28", + "0.00182310" + ], + [ + "3033.00", + "0.25000000" + ], + [ + "3032.00", + "0.00170000" + ], + [ + "3031.20", + "0.99918184" + ], + [ + "3031.00", + "5.00000000" + ], + [ + "3030.83", + "100.20000000" + ], + [ + "3030.00", + "1.04814284" + ], + [ + "3028.00", + "0.00170000" + ], + [ + "3026.00", + "1.00000000" + ], + [ + "3025.00", + "0.50000000" + ], + [ + "3024.54", + "0.63300000" + ], + [ + "3022.04", + "0.00620000" + ], + [ + "3021.22", + "22.45000000" + ], + [ + "3020.00", + "1.10143377" + ], + [ + "3017.17", + "0.67100000" + ], + [ + "3017.00", + "0.10200000" + ], + [ + "3015.10", + "0.17715232" + ], + [ + "3014.73", + "0.20000000" + ], + [ + "3013.13", + "0.17058673" + ], + [ + "3011.00", + "0.02000000" + ], + [ + "3010.00", + "7.58430957" + ], + [ + "3007.00", + "0.08200000" + ], + [ + "3006.66", + "1.00000000" + ], + [ + "3006.00", + "10.00000000" + ], + [ + "3005.50", + "0.05766947" + ], + [ + "3005.00", + "1.75140190" + ], + [ + "3003.00", + "0.03000000" + ], + [ + "3001.30", + "0.00170000" + ], + [ + "3001.10", + "0.02405000" + ], + [ + "3001.00", + "5.32000000" + ], + [ + "3000.82", + "100.20000000" + ], + [ + "3000.01", + "0.73809400" + ], + [ + "3000.00", + "37.96260157" + ], + [ + "2999.99", + "1.00000000" + ], + [ + "2999.00", + "0.00530426" + ], + [ + "2994.73", + "0.20000000" + ], + [ + "2994.00", + "0.17282565" + ], + [ + "2990.00", + "0.40000000" + ], + [ + "2988.00", + "3.11200000" + ], + [ + "2980.00", + "1.00500000" + ], + [ + "2979.98", + "1.00000000" + ], + [ + "2977.00", + "0.00200000" + ], + [ + "2975.00", + "1.00000000" + ], + [ + "2974.73", + "0.20000000" + ], + [ + "2973.88", + "0.61000000" + ], + [ + "2971.11", + "100.20000000" + ], + [ + "2967.97", + "0.10000000" + ], + [ + "2966.00", + "2.00000000" + ], + [ + "2961.00", + "0.15000000" + ], + [ + "2959.73", + "0.65200000" + ], + [ + "2955.86", + "2.00000000" + ], + [ + "2955.55", + "0.09021000" + ], + [ + "2954.73", + "0.20000000" + ], + [ + "2951.00", + "0.55300000" + ], + [ + "2950.00", + "1.90190361" + ], + [ + "2948.00", + "0.01000000" + ], + [ + "2946.08", + "0.03507798" + ], + [ + "2945.00", + "1.00000000" + ], + [ + "2935.00", + "55.00000000" + ], + [ + "2934.73", + "0.20000000" + ], + [ + "2933.00", + "0.00200000" + ], + [ + "2930.00", + "0.00372000" + ], + [ + "2926.00", + "0.28000000" + ], + [ + "2925.00", + "2.50000000" + ], + [ + "2920.00", + "1.00500000" + ], + [ + "2916.00", + "0.50000000" + ], + [ + "2914.73", + "0.20000000" + ], + [ + "2911.33", + "0.00740000" + ], + [ + "2910.00", + "0.00174000" + ], + [ + "2908.24", + "0.03429909" + ], + [ + "2907.70", + "0.12000000" + ], + [ + "2901.00", + "0.00300000" + ], + [ + "2900.00", + "4.09613449" + ], + [ + "2897.60", + "0.00180000" + ], + [ + "2896.31", + "0.67100000" + ], + [ + "2894.73", + "0.20000000" + ], + [ + "2890.00", + "0.01717993" + ], + [ + "2888.88", + "0.00174115" + ], + [ + "2888.00", + "0.01731302" + ], + [ + "2885.00", + "0.13000000" + ], + [ + "2882.14", + "0.02991474" + ], + [ + "2879.98", + "1.00000000" + ], + [ + "2877.00", + "0.00300000" + ], + [ + "2875.00", + "1.00000000" + ], + [ + "2874.73", + "0.20000000" + ], + [ + "2871.00", + "1.10000000" + ], + [ + "2868.00", + "0.00700000" + ], + [ + "2866.00", + "0.05833224" + ], + [ + "2865.05", + "0.26134048" + ], + [ + "2863.14", + "0.00833224" + ], + [ + "2859.82", + "0.05000000" + ], + [ + "2854.73", + "0.20000000" + ], + [ + "2850.10", + "0.01500000" + ], + [ + "2850.00", + "9.97968081" + ], + [ + "2835.00", + "2.00000000" + ], + [ + "2834.73", + "0.20000000" + ], + [ + "2830.00", + "0.50000000" + ], + [ + "2828.00", + "0.01200000" + ], + [ + "2826.00", + "0.29000000" + ], + [ + "2822.00", + "0.78560724" + ], + [ + "2821.00", + "0.45000000" + ], + [ + "2819.00", + "2.33804540" + ], + [ + "2817.00", + "0.30816116" + ], + [ + "2814.73", + "0.20000000" + ], + [ + "2812.00", + "0.51500000" + ], + [ + "2811.70", + "0.10000000" + ], + [ + "2810.00", + "1.07871701" + ], + [ + "2806.00", + "0.01530000" + ], + [ + "2805.06", + "0.02000000" + ], + [ + "2801.00", + "0.00400000" + ], + [ + "2800.11", + "1.32412655" + ], + [ + "2800.10", + "0.01500000" + ], + [ + "2800.00", + "8.14818539" + ], + [ + "2794.73", + "0.20000000" + ], + [ + "2789.00", + "1.00000000" + ], + [ + "2788.00", + "0.01100000" + ], + [ + "2782.47", + "0.31202169" + ], + [ + "2779.98", + "1.00000000" + ], + [ + "2777.00", + "0.00200000" + ], + [ + "2774.73", + "0.20000000" + ], + [ + "2773.52", + "1.41500000" + ], + [ + "2769.00", + "1.10000000" + ], + [ + "2766.00", + "1.00000000" + ], + [ + "2762.49", + "2.00000000" + ], + [ + "2759.17", + "10.14748275" + ], + [ + "2755.00", + "0.25000000" + ], + [ + "2754.73", + "0.20000000" + ], + [ + "2754.64", + "0.01000000" + ], + [ + "2750.10", + "0.01500000" + ], + [ + "2750.00", + "1.89720455" + ], + [ + "2735.88", + "0.01179910" + ], + [ + "2735.00", + "0.00400000" + ], + [ + "2734.73", + "0.20000000" + ], + [ + "2733.00", + "0.00200000" + ], + [ + "2726.00", + "0.66800000" + ], + [ + "2723.07", + "1.52000000" + ], + [ + "2714.73", + "0.20000000" + ], + [ + "2711.00", + "5.00000000" + ], + [ + "2710.80", + "0.01100000" + ], + [ + "2710.00", + "0.00190000" + ], + [ + "2705.00", + "0.12000000" + ], + [ + "2704.01", + "0.05000000" + ], + [ + "2704.00", + "0.50000000" + ], + [ + "2701.00", + "0.00800000" + ], + [ + "2700.10", + "0.01500000" + ], + [ + "2700.00", + "5.92794444" + ], + [ + "2694.73", + "0.20000000" + ], + [ + "2690.70", + "0.20000000" + ], + [ + "2690.00", + "0.10000000" + ], + [ + "2688.00", + "0.01200000" + ], + [ + "2685.54", + "0.00380000" + ], + [ + "2681.94", + "0.00303030" + ], + [ + "2680.00", + "0.00200000" + ], + [ + "2679.98", + "1.00000000" + ], + [ + "2679.97", + "1.00000000" + ], + [ + "2677.00", + "0.00300000" + ], + [ + "2675.00", + "0.02796729" + ], + [ + "2674.73", + "0.20000000" + ], + [ + "2671.00", + "1.15000000" + ], + [ + "2666.00", + "0.90000000" + ], + [ + "2656.00", + "0.38000000" + ], + [ + "2655.93", + "1.49900000" + ], + [ + "2654.73", + "0.20000000" + ], + [ + "2650.10", + "0.01500000" + ], + [ + "2650.00", + "5.65022641" + ], + [ + "2648.27", + "0.00467400" + ], + [ + "2645.00", + "3.10000000" + ], + [ + "2643.00", + "1.00000000" + ], + [ + "2636.00", + "0.00500000" + ], + [ + "2635.38", + "0.00303030" + ], + [ + "2635.00", + "0.00800000" + ], + [ + "2634.73", + "0.20000000" + ], + [ + "2631.90", + "0.61000000" + ], + [ + "2630.00", + "0.00500000" + ], + [ + "2625.50", + "0.71102500" + ], + [ + "2621.01", + "0.08000000" + ], + [ + "2614.73", + "0.20000000" + ], + [ + "2613.88", + "0.00953755" + ], + [ + "2611.00", + "0.00574900" + ], + [ + "2610.00", + "0.00195000" + ], + [ + "2607.00", + "100.00000000" + ], + [ + "2606.00", + "0.32000000" + ], + [ + "2604.00", + "0.00700000" + ], + [ + "2601.00", + "0.01300000" + ], + [ + "2600.00", + "13.64098172" + ], + [ + "2599.97", + "0.10000000" + ], + [ + "2599.60", + "0.00200000" + ], + [ + "2595.64", + "0.11305977" + ], + [ + "2594.73", + "0.20000000" + ], + [ + "2588.00", + "0.01900000" + ], + [ + "2584.47", + "0.35362453" + ], + [ + "2581.76", + "3.00000000" + ], + [ + "2580.00", + "0.40000000" + ], + [ + "2579.98", + "1.00000000" + ], + [ + "2577.00", + "0.00200000" + ], + [ + "2575.00", + "1.50000000" + ], + [ + "2574.73", + "0.20000000" + ], + [ + "2573.73", + "0.00707100" + ], + [ + "2572.27", + "0.05000000" + ], + [ + "2571.00", + "1.20000000" + ], + [ + "2568.00", + "1.00000000" + ], + [ + "2561.13", + "0.84300000" + ], + [ + "2559.00", + "0.02923017" + ], + [ + "2557.00", + "0.44669534" + ], + [ + "2555.00", + "1.01000000" + ], + [ + "2554.73", + "0.20000000" + ], + [ + "2554.08", + "0.00303030" + ], + [ + "2554.00", + "0.75000000" + ], + [ + "2551.00", + "0.73369658" + ], + [ + "2550.00", + "10.78323529" + ], + [ + "2548.00", + "0.01200000" + ], + [ + "2543.32", + "1.58700000" + ], + [ + "2541.74", + "0.04000000" + ], + [ + "2541.00", + "3.80000000" + ], + [ + "2536.46", + "0.00869700" + ], + [ + "2534.73", + "0.20000000" + ], + [ + "2533.53", + "1.18116225" + ], + [ + "2533.00", + "1.00000000" + ], + [ + "2532.00", + "0.21200000" + ], + [ + "2528.00", + "1.00000000" + ], + [ + "2527.00", + "0.77661315" + ], + [ + "2525.00", + "0.02962871" + ], + [ + "2523.00", + "0.10000000" + ], + [ + "2521.33", + "0.01100000" + ], + [ + "2521.00", + "0.10000000" + ], + [ + "2520.00", + "2.15099008" + ], + [ + "2516.00", + "1.00000000" + ], + [ + "2514.73", + "0.20000000" + ], + [ + "2513.00", + "1.00000000" + ], + [ + "2511.27", + "0.00300000" + ], + [ + "2511.00", + "10.00200000" + ], + [ + "2510.00", + "0.25200000" + ], + [ + "2508.00", + "1.00000000" + ], + [ + "2506.06", + "0.01000000" + ], + [ + "2506.00", + "0.34000000" + ], + [ + "2505.00", + "0.00200000" + ], + [ + "2501.00", + "3.00000000" + ], + [ + "2500.00", + "5.58857797" + ], + [ + "2499.19", + "0.01069700" + ], + [ + "2494.73", + "0.20000000" + ], + [ + "2488.00", + "0.01300000" + ], + [ + "2487.91", + "0.00937765" + ], + [ + "2485.40", + "0.01877418" + ], + [ + "2482.91", + "0.01876874" + ], + [ + "2479.98", + "1.00000000" + ], + [ + "2478.26", + "0.00941416" + ], + [ + "2477.00", + "0.00300000" + ], + [ + "2474.73", + "0.20000000" + ], + [ + "2472.49", + "0.00941819" + ], + [ + "2471.00", + "1.25000000" + ], + [ + "2468.00", + "1.00000000" + ], + [ + "2461.92", + "0.01315700" + ], + [ + "2454.73", + "0.20000000" + ], + [ + "2450.00", + "0.53053571" + ], + [ + "2435.50", + "1.68100000" + ], + [ + "2434.73", + "0.20000000" + ], + [ + "2433.00", + "1.24032339" + ], + [ + "2428.00", + "1.00000000" + ], + [ + "2424.65", + "0.01618300" + ], + [ + "2420.29", + "0.10000000" + ], + [ + "2414.73", + "0.20000000" + ], + [ + "2412.10", + "0.05389700" + ], + [ + "2410.00", + "0.00210000" + ], + [ + "2407.64", + "0.01000000" + ], + [ + "2407.00", + "100.03110000" + ], + [ + "2406.00", + "0.36000000" + ], + [ + "2405.00", + "0.54573826" + ], + [ + "2400.00", + "3.69216249" + ], + [ + "2399.97", + "1.00000000" + ], + [ + "2394.73", + "0.20000000" + ], + [ + "2391.00", + "1.00000000" + ], + [ + "2387.38", + "0.01990500" + ], + [ + "2384.44", + "0.33355253" + ], + [ + "2379.98", + "1.00000000" + ], + [ + "2378.97", + "0.00303030" + ], + [ + "2377.00", + "0.00300000" + ], + [ + "2375.00", + "0.03150000" + ], + [ + "2374.73", + "0.20000000" + ], + [ + "2372.27", + "0.05000000" + ], + [ + "2371.00", + "1.30000000" + ], + [ + "2366.00", + "0.02000000" + ], + [ + "2360.00", + "10.04230000" + ], + [ + "2357.20", + "0.01000000" + ], + [ + "2354.73", + "0.20000000" + ], + [ + "2350.11", + "0.02448300" + ], + [ + "2350.00", + "0.92000000" + ], + [ + "2347.11", + "2.00000000" + ], + [ + "2345.00", + "1.00000000" + ], + [ + "2334.73", + "0.20000000" + ], + [ + "2333.38", + "0.10000000" + ], + [ + "2333.00", + "2.49900000" + ], + [ + "2332.24", + "1.78000000" + ], + [ + "2331.00", + "0.25000000" + ], + [ + "2326.00", + "0.38000000" + ], + [ + "2318.36", + "4.49962699" + ], + [ + "2315.50", + "1.55200000" + ], + [ + "2314.73", + "0.20000000" + ], + [ + "2312.84", + "0.03011400" + ], + [ + "2312.30", + "0.00500000" + ], + [ + "2310.00", + "0.00220000" + ], + [ + "2308.00", + "40.81392980" + ], + [ + "2307.00", + "100.00000000" + ], + [ + "2303.00", + "0.00700000" + ], + [ + "2300.00", + "1.17954456" + ], + [ + "2299.97", + "0.30000000" + ], + [ + "2294.73", + "0.20000000" + ], + [ + "2288.00", + "1.67506118" + ], + [ + "2279.98", + "1.00000000" + ], + [ + "2277.00", + "5.45852876" + ], + [ + "2275.57", + "0.03704000" + ], + [ + "2274.73", + "0.20000000" + ], + [ + "2271.00", + "1.28700000" + ], + [ + "2268.94", + "0.32335804" + ], + [ + "2254.73", + "0.20000000" + ], + [ + "2251.16", + "0.75000000" + ], + [ + "2250.00", + "0.50000000" + ], + [ + "2240.00", + "0.02000000" + ], + [ + "2239.94", + "0.01000000" + ], + [ + "2239.00", + "0.00500000" + ], + [ + "2238.30", + "0.04555900" + ], + [ + "2234.73", + "0.20000000" + ], + [ + "2233.36", + "1.88500000" + ], + [ + "2233.00", + "44.71383788" + ], + [ + "2232.47", + "0.25458348" + ], + [ + "2230.70", + "0.30000000" + ], + [ + "2229.99", + "0.07157430" + ], + [ + "2226.00", + "0.40000000" + ], + [ + "2225.00", + "0.03362360" + ], + [ + "2222.00", + "0.99900000" + ], + [ + "2221.00", + "1.00000000" + ], + [ + "2214.73", + "0.20000000" + ], + [ + "2212.10", + "0.05877000" + ], + [ + "2210.00", + "0.30230000" + ], + [ + "2205.06", + "0.01000000" + ], + [ + "2201.41", + "0.05545000" + ], + [ + "2200.10", + "7.00000000" + ], + [ + "2200.00", + "9.96343619" + ], + [ + "2194.73", + "0.20000000" + ], + [ + "2188.00", + "0.00500000" + ], + [ + "2182.00", + "15.00000000" + ], + [ + "2180.00", + "0.66000000" + ], + [ + "2179.98", + "1.00000000" + ], + [ + "2175.00", + "1.75481594" + ], + [ + "2174.73", + "0.20000000" + ], + [ + "2172.27", + "0.05000000" + ], + [ + "2166.73", + "1.10489078" + ], + [ + "2161.10", + "0.10000000" + ], + [ + "2160.00", + "1.39708648" + ], + [ + "2154.73", + "0.20000000" + ], + [ + "2152.00", + "0.40000000" + ], + [ + "2150.00", + "0.49479651" + ], + [ + "2138.67", + "1.99600000" + ], + [ + "2135.26", + "0.01010101" + ], + [ + "2134.73", + "0.20000000" + ], + [ + "2133.00", + "0.00900000" + ], + [ + "2132.00", + "0.01000000" + ], + [ + "2130.00", + "0.00500000" + ], + [ + "2126.58", + "1.35100000" + ], + [ + "2126.00", + "1.00000000" + ], + [ + "2120.00", + "5.31298584" + ], + [ + "2114.73", + "0.20000000" + ], + [ + "2111.00", + "0.99900000" + ], + [ + "2110.50", + "0.01181237" + ], + [ + "2110.00", + "0.00240000" + ], + [ + "2109.89", + "3.00000000" + ], + [ + "2109.80", + "2.24399469" + ], + [ + "2107.00", + "100.00000000" + ], + [ + "2105.06", + "0.01000000" + ], + [ + "2105.00", + "1.00000000" + ], + [ + "2101.00", + "1.86000000" + ], + [ + "2100.00", + "101.51800000" + ], + [ + "2094.73", + "0.20000000" + ], + [ + "2088.00", + "0.02500000" + ], + [ + "2082.45", + "0.11449974" + ], + [ + "2079.98", + "1.00000000" + ], + [ + "2075.00", + "0.03605422" + ], + [ + "2074.73", + "0.20000000" + ], + [ + "2072.27", + "0.05000000" + ], + [ + "2069.00", + "0.01934751" + ], + [ + "2066.00", + "1.00000000" + ], + [ + "2064.80", + "0.04000000" + ], + [ + "2059.11", + "0.01000000" + ], + [ + "2054.73", + "0.20000000" + ], + [ + "2052.00", + "0.40000000" + ], + [ + "2051.17", + "2.00000000" + ], + [ + "2051.00", + "0.25000000" + ], + [ + "2050.00", + "3.84424390" + ], + [ + "2048.00", + "2.11300000" + ], + [ + "2042.00", + "0.08264936" + ], + [ + "2037.00", + "2.00000000" + ], + [ + "2035.00", + "0.45000000" + ], + [ + "2034.73", + "0.20000000" + ], + [ + "2032.04", + "0.03300000" + ], + [ + "2015.00", + "1.00000000" + ], + [ + "2014.73", + "0.20000000" + ], + [ + "2013.29", + "0.50728394" + ], + [ + "2012.10", + "0.07000000" + ], + [ + "2011.00", + "1.00000000" + ], + [ + "2010.00", + "0.00260000" + ], + [ + "2005.05", + "0.01000000" + ], + [ + "2005.00", + "0.36832376" + ], + [ + "2004.12", + "1.09100000" + ], + [ + "2001.36", + "0.02769007" + ], + [ + "2000.00", + "8.49237125" + ], + [ + "1999.00", + "1.01250900" + ], + [ + "1994.73", + "0.20000000" + ], + [ + "1988.27", + "0.05000000" + ], + [ + "1988.00", + "0.02900000" + ], + [ + "1987.00", + "0.02000000" + ], + [ + "1986.00", + "0.00400000" + ], + [ + "1985.00", + "0.00400000" + ], + [ + "1976.00", + "1.40000000" + ], + [ + "1974.73", + "0.20000000" + ], + [ + "1974.00", + "0.00700000" + ], + [ + "1970.00", + "5.26381218" + ], + [ + "1961.17", + "1.12300000" + ], + [ + "1960.00", + "2.50000000" + ], + [ + "1954.73", + "0.20000000" + ], + [ + "1953.00", + "0.01000000" + ], + [ + "1951.15", + "0.30000000" + ], + [ + "1950.00", + "0.51900000" + ], + [ + "1949.99", + "0.10737418" + ], + [ + "1936.00", + "0.12000000" + ], + [ + "1934.73", + "0.20000000" + ], + [ + "1928.00", + "0.50000000" + ], + [ + "1925.00", + "0.43886364" + ], + [ + "1920.00", + "0.35000000" + ], + [ + "1914.73", + "0.20000000" + ], + [ + "1910.00", + "0.00670000" + ], + [ + "1907.02", + "0.01758267" + ], + [ + "1906.00", + "0.45000000" + ], + [ + "1905.06", + "0.01000000" + ], + [ + "1904.33", + "0.50442810" + ], + [ + "1901.00", + "1.00000000" + ], + [ + "1900.00", + "4.46000000" + ], + [ + "1899.97", + "0.50000000" + ], + [ + "1894.73", + "0.20000000" + ], + [ + "1890.00", + "1.00000000" + ], + [ + "1888.00", + "0.05000000" + ], + [ + "1882.50", + "0.05000000" + ], + [ + "1881.00", + "1.50000000" + ], + [ + "1878.02", + "2.37000000" + ], + [ + "1877.03", + "0.50000000" + ], + [ + "1875.00", + "4.00000000" + ], + [ + "1874.73", + "0.20000000" + ], + [ + "1872.27", + "0.05000000" + ], + [ + "1865.00", + "12.51152278" + ], + [ + "1861.00", + "0.33000000" + ], + [ + "1858.00", + "0.01000000" + ], + [ + "1854.73", + "0.20000000" + ], + [ + "1853.80", + "0.00500000" + ], + [ + "1852.00", + "0.01000000" + ], + [ + "1850.00", + "2.69423919" + ], + [ + "1845.00", + "0.67691056" + ], + [ + "1843.00", + "0.30000000" + ], + [ + "1835.28", + "0.10737418" + ], + [ + "1835.00", + "0.55247411" + ], + [ + "1834.73", + "0.20000000" + ], + [ + "1830.00", + "0.10900000" + ], + [ + "1829.00", + "0.05467500" + ], + [ + "1826.00", + "1.45000000" + ], + [ + "1825.28", + "0.03000000" + ], + [ + "1821.10", + "0.08126900" + ], + [ + "1817.41", + "0.51992800" + ], + [ + "1814.73", + "0.20000000" + ], + [ + "1810.00", + "0.00280000" + ], + [ + "1805.34", + "1.00000000" + ], + [ + "1805.06", + "0.02000000" + ], + [ + "1800.00", + "10.91353127" + ], + [ + "1798.40", + "2.50900000" + ], + [ + "1794.73", + "0.20000000" + ], + [ + "1790.00", + "5.00000000" + ], + [ + "1777.00", + "0.99990000" + ], + [ + "1775.00", + "0.04214789" + ], + [ + "1774.73", + "0.20000000" + ], + [ + "1767.00", + "5.00000000" + ], + [ + "1755.00", + "3.00000000" + ], + [ + "1754.73", + "0.20000000" + ], + [ + "1752.00", + "0.45000000" + ], + [ + "1750.00", + "0.58454286" + ], + [ + "1744.61", + "0.05000000" + ], + [ + "1742.67", + "10.00000000" + ], + [ + "1729.07", + "0.50927716" + ], + [ + "1727.32", + "0.10737418" + ], + [ + "1722.16", + "2.65700000" + ], + [ + "1712.70", + "1.10000000" + ], + [ + "1710.00", + "0.00300000" + ], + [ + "1705.06", + "0.03000000" + ], + [ + "1700.00", + "17.50575440" + ], + [ + "1676.00", + "0.50000000" + ], + [ + "1672.27", + "0.05000000" + ], + [ + "1666.00", + "5.49764732" + ], + [ + "1649.14", + "2.81400000" + ], + [ + "1637.65", + "0.01522303" + ], + [ + "1626.00", + "0.50000000" + ], + [ + "1625.72", + "0.10737418" + ], + [ + "1625.10", + "0.08599000" + ], + [ + "1625.00", + "1.36507721" + ], + [ + "1612.00", + "5.00000000" + ], + [ + "1610.00", + "1.00330000" + ], + [ + "1609.87", + "0.51033726" + ], + [ + "1605.06", + "0.02000000" + ], + [ + "1604.00", + "0.01000000" + ], + [ + "1603.48", + "1.30000000" + ], + [ + "1600.00", + "15.23334375" + ], + [ + "1599.97", + "1.00000000" + ], + [ + "1599.00", + "1.00000000" + ], + [ + "1593.00", + "0.25000000" + ], + [ + "1589.60", + "0.00500000" + ], + [ + "1589.22", + "0.59526686" + ], + [ + "1589.00", + "0.00600000" + ], + [ + "1583.20", + "0.10000000" + ], + [ + "1579.22", + "2.98000000" + ], + [ + "1573.39", + "0.51298787" + ], + [ + "1569.70", + "3.08243831" + ], + [ + "1563.00", + "0.50000000" + ], + [ + "1561.10", + "0.20926000" + ], + [ + "1557.41", + "0.25000000" + ], + [ + "1552.00", + "0.50000000" + ], + [ + "1551.70", + "1.00000000" + ], + [ + "1551.13", + "0.15000000" + ], + [ + "1550.00", + "0.65000000" + ], + [ + "1549.46", + "4.00000000" + ], + [ + "1531.84", + "0.51088720" + ], + [ + "1530.09", + "0.10737418" + ], + [ + "1524.00", + "0.20132729" + ], + [ + "1517.40", + "1.13200000" + ], + [ + "1512.27", + "3.15500000" + ], + [ + "1511.00", + "0.01000000" + ], + [ + "1510.00", + "1.45350000" + ], + [ + "1506.00", + "0.60000000" + ], + [ + "1505.06", + "0.01000000" + ], + [ + "1501.00", + "1.59936708" + ], + [ + "1500.00", + "74.88965241" + ], + [ + "1499.00", + "1.00000000" + ], + [ + "1488.00", + "1.00000000" + ], + [ + "1476.87", + "0.24104882" + ], + [ + "1472.27", + "0.05000000" + ], + [ + "1452.00", + "1.29907336" + ], + [ + "1451.43", + "1.27381272" + ], + [ + "1450.00", + "50.00000000" + ], + [ + "1449.46", + "3.00000000" + ], + [ + "1448.15", + "3.34100000" + ], + [ + "1444.00", + "130.00000000" + ], + [ + "1440.08", + "0.10737418" + ], + [ + "1437.00", + "5.00000000" + ], + [ + "1414.00", + "0.10839462" + ], + [ + "1413.00", + "1.21726822" + ], + [ + "1410.69", + "0.50411280" + ], + [ + "1410.00", + "0.00380000" + ], + [ + "1405.06", + "0.01000000" + ], + [ + "1400.00", + "57.35000000" + ], + [ + "1399.97", + "0.50000000" + ], + [ + "1399.00", + "1.00000000" + ], + [ + "1389.00", + "2.00000000" + ], + [ + "1376.00", + "1.20000000" + ], + [ + "1361.00", + "7.93238060" + ], + [ + "1358.00", + "0.01000000" + ], + [ + "1355.37", + "0.10737418" + ], + [ + "1355.00", + "6.59933190" + ], + [ + "1350.00", + "11.80751111" + ], + [ + "1338.75", + "2.50000000" + ], + [ + "1333.00", + "40.26300525" + ], + [ + "1332.77", + "0.51429600" + ], + [ + "1327.96", + "7.44200000" + ], + [ + "1310.00", + "3.00420000" + ], + [ + "1305.06", + "0.01000000" + ], + [ + "1304.77", + "0.50000000" + ], + [ + "1300.00", + "79.41737401" + ], + [ + "1299.97", + "0.50000000" + ], + [ + "1299.80", + "0.00500000" + ], + [ + "1299.00", + "1.00000000" + ], + [ + "1275.64", + "0.10737418" + ], + [ + "1275.00", + "2.00000000" + ], + [ + "1272.27", + "0.05000000" + ], + [ + "1265.65", + "0.98217882" + ], + [ + "1255.00", + "1.00000000" + ], + [ + "1253.00", + "2.50000000" + ], + [ + "1250.00", + "1.00636000" + ], + [ + "1239.94", + "0.01000000" + ], + [ + "1234.57", + "2.00000000" + ], + [ + "1230.00", + "0.21048871" + ], + [ + "1226.00", + "2.00000000" + ], + [ + "1217.75", + "8.34500000" + ], + [ + "1217.50", + "1.21700000" + ], + [ + "1212.99", + "0.50449270" + ], + [ + "1212.00", + "1.00000000" + ], + [ + "1210.00", + "0.00440000" + ], + [ + "1205.06", + "0.02000000" + ], + [ + "1200.60", + "0.10737418" + ], + [ + "1200.00", + "17.17930832" + ], + [ + "1199.97", + "1.00000000" + ], + [ + "1199.60", + "0.01000000" + ], + [ + "1199.00", + "2.00000000" + ], + [ + "1190.00", + "1.13259838" + ], + [ + "1180.00", + "1.00000000" + ], + [ + "1165.30", + "0.01000000" + ], + [ + "1162.00", + "1.00000000" + ], + [ + "1156.00", + "0.66656574" + ], + [ + "1153.09", + "4.00000000" + ], + [ + "1132.45", + "1.29100000" + ], + [ + "1129.98", + "0.10737418" + ], + [ + "1120.00", + "6.86225000" + ], + [ + "1117.12", + "1.00000000" + ], + [ + "1116.68", + "9.35800000" + ], + [ + "1110.00", + "0.10470000" + ], + [ + "1109.50", + "0.01000000" + ], + [ + "1107.30", + "0.31969005" + ], + [ + "1105.06", + "0.02000000" + ], + [ + "1101.00", + "1.00000000" + ], + [ + "1100.03", + "0.50000000" + ], + [ + "1100.00", + "11.52474545" + ], + [ + "1099.00", + "1.00000000" + ], + [ + "1070.00", + "0.40000000" + ], + [ + "1065.71", + "0.97348200" + ], + [ + "1063.51", + "0.10737418" + ], + [ + "1055.10", + "0.01000000" + ], + [ + "1055.00", + "1.45393364" + ], + [ + "1053.00", + "0.76000000" + ], + [ + "1050.00", + "0.02000000" + ], + [ + "1037.79", + "2.00000000" + ], + [ + "1037.50", + "0.00864578" + ], + [ + "1037.00", + "0.01000000" + ], + [ + "1030.38", + "1.00000000" + ], + [ + "1028.70", + "0.00859776" + ], + [ + "1028.00", + "0.31227626" + ], + [ + "1024.00", + "10.49300000" + ], + [ + "1022.71", + "1.06374800" + ], + [ + "1015.00", + "1.60000000" + ], + [ + "1011.12", + "0.03000000" + ], + [ + "1010.00", + "1.00500000" + ], + [ + "1008.00", + "3.00000000" + ], + [ + "1006.95", + "1.00000000" + ], + [ + "1006.00", + "3.80000000" + ], + [ + "1005.90", + "2.00000000" + ], + [ + "1005.06", + "0.03000000" + ], + [ + "1003.45", + "0.50000000" + ], + [ + "1003.00", + "0.80611166" + ], + [ + "1002.06", + "2.72800000" + ], + [ + "1001.11", + "0.02000000" + ], + [ + "1001.00", + "3.00700000" + ], + [ + "1000.95", + "0.10737418" + ], + [ + "1000.11", + "0.00513848" + ], + [ + "1000.00", + "250.19563228" + ], + [ + "999.99", + "218.06427064" + ], + [ + "999.97", + "1.00000000" + ], + [ + "999.00", + "1.00000000" + ], + [ + "998.60", + "0.01000000" + ], + [ + "997.00", + "23.75940822" + ], + [ + "995.03", + "0.50000000" + ], + [ + "990.00", + "1.00000000" + ], + [ + "980.59", + "2.80700000" + ], + [ + "980.00", + "0.05000000" + ], + [ + "977.13", + "0.03873357" + ], + [ + "977.00", + "0.34494370" + ], + [ + "966.22", + "0.03000000" + ], + [ + "965.00", + "0.30108808" + ], + [ + "950.00", + "2.00000000" + ], + [ + "942.07", + "0.10737418" + ], + [ + "939.01", + "5.92400000" + ], + [ + "932.12", + "0.03000000" + ], + [ + "910.00", + "0.00560000" + ], + [ + "907.60", + "2.50000000" + ], + [ + "903.00", + "0.24197274" + ], + [ + "901.05", + "0.01000000" + ], + [ + "900.00", + "116.22694338" + ], + [ + "899.97", + "1.00000000" + ], + [ + "899.20", + "6.27400000" + ], + [ + "899.00", + "1.00000000" + ], + [ + "897.90", + "0.00800000" + ], + [ + "892.00", + "1.11900000" + ], + [ + "888.64", + "0.99749055" + ], + [ + "888.00", + "0.25000000" + ], + [ + "886.65", + "0.10737418" + ], + [ + "881.76", + "0.20000000" + ], + [ + "863.21", + "0.17920000" + ], + [ + "861.10", + "0.59000000" + ], + [ + "861.08", + "6.64300000" + ], + [ + "860.00", + "0.05965000" + ], + [ + "853.00", + "3.50000000" + ], + [ + "849.60", + "0.00800000" + ], + [ + "846.62", + "0.00781921" + ], + [ + "839.92", + "0.01231100" + ], + [ + "836.80", + "0.01000000" + ], + [ + "834.50", + "0.10737418" + ], + [ + "830.00", + "3.00000000" + ], + [ + "826.31", + "0.01348310" + ], + [ + "826.05", + "0.01960302" + ], + [ + "824.43", + "0.01679536" + ], + [ + "821.61", + "0.00707070" + ], + [ + "820.12", + "0.01839868" + ], + [ + "817.45", + "0.01247450" + ], + [ + "817.24", + "0.01832894" + ], + [ + "815.55", + "0.01923464" + ], + [ + "814.52", + "2.00000000" + ], + [ + "810.00", + "1.00630000" + ], + [ + "805.00", + "5.49953347" + ], + [ + "801.06", + "0.02000000" + ], + [ + "800.00", + "27.32442500" + ], + [ + "799.97", + "1.00000000" + ], + [ + "799.30", + "0.00650000" + ], + [ + "799.00", + "1.00000000" + ], + [ + "790.75", + "0.01998746" + ], + [ + "789.61", + "14.79600000" + ], + [ + "785.41", + "0.10737418" + ], + [ + "783.77", + "0.00910246" + ], + [ + "770.00", + "1.54678568" + ], + [ + "765.00", + "1.00000000" + ], + [ + "761.00", + "3.00000000" + ], + [ + "757.78", + "0.00931594" + ], + [ + "757.00", + "0.76036644" + ], + [ + "751.90", + "0.00800000" + ], + [ + "750.00", + "0.01060000" + ], + [ + "748.44", + "0.00707070" + ], + [ + "745.00", + "0.00679000" + ], + [ + "742.50", + "0.99749494" + ], + [ + "739.21", + "0.10737418" + ], + [ + "733.00", + "2.00000000" + ], + [ + "729.00", + "0.08000000" + ], + [ + "725.00", + "0.50000000" + ], + [ + "724.08", + "16.59100000" + ], + [ + "720.64", + "0.01840478" + ], + [ + "720.05", + "0.01984014" + ], + [ + "719.00", + "0.03000000" + ], + [ + "717.60", + "6.00000000" + ], + [ + "710.00", + "0.00730000" + ], + [ + "709.00", + "0.02430000" + ], + [ + "708.18", + "0.01015228" + ], + [ + "707.00", + "1.44260000" + ], + [ + "702.00", + "0.05000000" + ], + [ + "701.06", + "0.02000000" + ], + [ + "700.92", + "0.00976228" + ], + [ + "700.00", + "16.05000000" + ], + [ + "698.50", + "0.01000000" + ], + [ + "695.73", + "0.10737418" + ], + [ + "694.66", + "0.01664832" + ], + [ + "680.00", + "0.03000000" + ], + [ + "679.00", + "0.10245000" + ], + [ + "670.00", + "1.00000000" + ], + [ + "665.99", + "5.03111692" + ], + [ + "665.00", + "1.50000000" + ], + [ + "663.98", + "18.60500000" + ], + [ + "660.00", + "1.35555715" + ], + [ + "656.11", + "0.10000000" + ], + [ + "654.80", + "0.10737418" + ], + [ + "651.50", + "0.28822621" + ], + [ + "650.00", + "51.00000000" + ], + [ + "625.50", + "0.23058118" + ], + [ + "621.13", + "0.33000000" + ], + [ + "616.28", + "0.10737418" + ], + [ + "610.00", + "1.00840000" + ], + [ + "608.99", + "0.26450873" + ], + [ + "608.87", + "20.86300000" + ], + [ + "601.44", + "2.68065730" + ], + [ + "600.00", + "19.69298333" + ], + [ + "599.99", + "3.00000000" + ], + [ + "597.53", + "0.16734196" + ], + [ + "596.00", + "0.05000000" + ], + [ + "591.13", + "0.33000000" + ], + [ + "591.07", + "0.23057916" + ], + [ + "586.95", + "0.29067125" + ], + [ + "581.05", + "0.02000000" + ], + [ + "580.03", + "0.10737418" + ], + [ + "571.00", + "4.00000000" + ], + [ + "566.00", + "0.30133000" + ], + [ + "565.40", + "0.23057881" + ], + [ + "561.00", + "0.10834224" + ], + [ + "560.00", + "1.24694624" + ], + [ + "559.36", + "2.00000000" + ], + [ + "558.34", + "23.39400000" + ], + [ + "556.00", + "1.00000000" + ], + [ + "555.00", + "0.05000000" + ], + [ + "553.13", + "0.84000000" + ], + [ + "550.00", + "0.63315455" + ], + [ + "545.91", + "0.10737418" + ], + [ + "545.16", + "0.12831095" + ], + [ + "543.13", + "0.85000000" + ], + [ + "540.00", + "0.01472222" + ], + [ + "538.17", + "0.27872234" + ], + [ + "533.13", + "0.87000000" + ], + [ + "530.00", + "0.01500000" + ], + [ + "523.13", + "0.88500000" + ], + [ + "520.00", + "0.01528846" + ], + [ + "515.87", + "1.00000000" + ], + [ + "515.00", + "0.01543689" + ], + [ + "513.80", + "0.10737418" + ], + [ + "513.13", + "0.90000000" + ], + [ + "513.00", + "0.05000000" + ], + [ + "512.00", + "26.23300000" + ], + [ + "510.00", + "0.02558824" + ], + [ + "505.00", + "0.01574257" + ], + [ + "503.13", + "0.92000000" + ], + [ + "502.60", + "12.30000000" + ], + [ + "501.00", + "2.00000000" + ], + [ + "500.00", + "11.03146000" + ], + [ + "499.97", + "1.00000000" + ], + [ + "499.00", + "2.00000000" + ], + [ + "493.13", + "0.94000000" + ], + [ + "490.00", + "0.80000000" + ], + [ + "485.00", + "0.28880620" + ], + [ + "483.57", + "0.10737418" + ], + [ + "483.13", + "0.96000000" + ], + [ + "479.00", + "2.00000000" + ], + [ + "475.00", + "1.01673684" + ], + [ + "473.13", + "0.97700000" + ], + [ + "473.00", + "0.99944016" + ], + [ + "472.00", + "1.00000000" + ], + [ + "471.00", + "1.00000000" + ], + [ + "468.90", + "0.02546942" + ], + [ + "468.25", + "0.02084028" + ], + [ + "467.05", + "0.05000000" + ], + [ + "465.00", + "1.50000000" + ], + [ + "464.68", + "0.04216076" + ], + [ + "464.40", + "0.04669832" + ], + [ + "464.30", + "0.02197522" + ], + [ + "463.13", + "1.00000000" + ], + [ + "461.56", + "0.01644414" + ], + [ + "461.49", + "0.01904262" + ], + [ + "461.34", + "0.05170632" + ], + [ + "460.36", + "0.04533016" + ], + [ + "460.01", + "0.08824119" + ], + [ + "459.91", + "0.04775688" + ], + [ + "459.90", + "0.01671368" + ], + [ + "458.68", + "0.03797714" + ], + [ + "458.19", + "0.05732356" + ], + [ + "457.41", + "0.04754920" + ], + [ + "456.28", + "0.25315189" + ], + [ + "455.75", + "0.04199864" + ], + [ + "455.50", + "0.50000000" + ], + [ + "455.34", + "0.04531868" + ], + [ + "455.13", + "0.10737418" + ], + [ + "454.78", + "0.04291530" + ], + [ + "454.67", + "0.05958028" + ], + [ + "454.54", + "0.11664539" + ], + [ + "454.47", + "0.03866806" + ], + [ + "454.19", + "0.04809158" + ], + [ + "453.46", + "0.04358836" + ], + [ + "453.13", + "0.22044446" + ], + [ + "452.30", + "0.05128006" + ], + [ + "451.30", + "0.01200000" + ], + [ + "450.00", + "2.11766667" + ], + [ + "449.64", + "0.03600530" + ], + [ + "449.10", + "0.01955971" + ], + [ + "446.76", + "0.02836949" + ], + [ + "446.61", + "0.03949602" + ], + [ + "444.44", + "0.15000000" + ], + [ + "435.23", + "0.04220120" + ], + [ + "434.97", + "0.02247961" + ], + [ + "434.34", + "0.09749909" + ], + [ + "432.02", + "0.99747691" + ], + [ + "431.00", + "0.22573085" + ], + [ + "428.36", + "0.10737418" + ], + [ + "428.28", + "0.26970240" + ], + [ + "425.00", + "0.05829732" + ], + [ + "420.00", + "0.05000000" + ], + [ + "416.00", + "0.92555000" + ], + [ + "415.00", + "0.01915663" + ], + [ + "414.14", + "0.10759646" + ], + [ + "412.24", + "0.02161305" + ], + [ + "410.00", + "1.01300000" + ], + [ + "405.06", + "0.10000000" + ], + [ + "405.00", + "0.10000000" + ], + [ + "404.93", + "0.02836949" + ], + [ + "404.50", + "1.00000000" + ], + [ + "403.16", + "0.10737418" + ], + [ + "402.32", + "0.38119615" + ], + [ + "402.00", + "1.12154814" + ], + [ + "401.20", + "0.01500000" + ], + [ + "401.00", + "1.00000000" + ], + [ + "400.00", + "7.44253850" + ], + [ + "396.87", + "0.99748000" + ], + [ + "396.83", + "0.02742085" + ], + [ + "393.93", + "0.12725611" + ], + [ + "391.60", + "0.20000000" + ], + [ + "390.89", + "0.01300000" + ], + [ + "390.00", + "1.00000000" + ], + [ + "379.44", + "0.10737418" + ], + [ + "379.00", + "0.05000000" + ], + [ + "378.00", + "0.05000000" + ], + [ + "375.00", + "0.02120000" + ], + [ + "373.89", + "6.00558988" + ], + [ + "368.59", + "0.03556282" + ], + [ + "366.06", + "0.04760318" + ], + [ + "365.84", + "0.02683626" + ], + [ + "365.00", + "1.50000000" + ], + [ + "363.00", + "41.00000000" + ], + [ + "361.00", + "0.27700000" + ], + [ + "360.15", + "0.12225000" + ], + [ + "360.00", + "0.50505000" + ], + [ + "357.12", + "0.10737418" + ], + [ + "355.06", + "0.10000000" + ], + [ + "350.95", + "0.03728252" + ], + [ + "350.75", + "0.01864739" + ], + [ + "350.25", + "0.18250000" + ], + [ + "350.00", + "0.52271429" + ], + [ + "348.29", + "0.06809382" + ], + [ + "345.99", + "0.05654656" + ], + [ + "344.98", + "0.03777200" + ], + [ + "341.74", + "0.33695760" + ], + [ + "336.11", + "0.10737418" + ], + [ + "333.00", + "3.00000000" + ], + [ + "328.97", + "0.09142420" + ], + [ + "326.00", + "0.74400000" + ], + [ + "325.00", + "2.00000000" + ], + [ + "324.00", + "0.05000000" + ], + [ + "322.65", + "0.02288758" + ], + [ + "321.00", + "0.29000000" + ], + [ + "320.00", + "1.50000000" + ], + [ + "316.34", + "0.10737418" + ], + [ + "310.60", + "0.01800000" + ], + [ + "310.00", + "1.20387096" + ], + [ + "307.00", + "0.03500000" + ], + [ + "305.06", + "0.10000000" + ], + [ + "302.00", + "1.00000000" + ], + [ + "300.50", + "0.33000000" + ], + [ + "300.00", + "2.68464025" + ], + [ + "297.73", + "0.10737418" + ], + [ + "295.00", + "0.33000000" + ], + [ + "289.00", + "0.05000000" + ], + [ + "288.00", + "1.00000000" + ], + [ + "287.00", + "0.25000000" + ], + [ + "285.00", + "1.00000000" + ], + [ + "280.50", + "1.25000000" + ], + [ + "280.22", + "0.10737418" + ], + [ + "280.00", + "1.40696428" + ], + [ + "279.00", + "0.03300000" + ], + [ + "277.00", + "1.00000000" + ], + [ + "276.50", + "0.02000000" + ], + [ + "271.00", + "1.30000000" + ], + [ + "265.00", + "20.00000000" + ], + [ + "263.74", + "0.10737418" + ], + [ + "259.00", + "10.00000000" + ], + [ + "251.70", + "0.10000000" + ], + [ + "250.60", + "196.10223463" + ], + [ + "250.39", + "1.00000000" + ], + [ + "250.30", + "0.02000000" + ], + [ + "250.00", + "4.30000000" + ], + [ + "249.00", + "10.00000000" + ], + [ + "248.22", + "0.10737418" + ], + [ + "245.15", + "1.05763818" + ], + [ + "236.38", + "47.39368813" + ], + [ + "234.00", + "0.20000000" + ], + [ + "233.62", + "0.10737418" + ], + [ + "233.00", + "6.42469957" + ], + [ + "227.00", + "0.93488986" + ], + [ + "222.22", + "1.00000000" + ], + [ + "221.50", + "0.91000000" + ], + [ + "220.00", + "1.30000000" + ], + [ + "219.97", + "10.00000000" + ], + [ + "219.88", + "0.10737418" + ], + [ + "210.00", + "2.35495232" + ], + [ + "206.94", + "0.10737418" + ], + [ + "205.00", + "3.00000000" + ], + [ + "200.98", + "10.02718326" + ], + [ + "200.00", + "30.48700000" + ], + [ + "199.00", + "6.73392659" + ], + [ + "198.81", + "0.10000000" + ], + [ + "194.77", + "0.10737418" + ], + [ + "185.97", + "1.00000000" + ], + [ + "183.31", + "0.10737418" + ], + [ + "180.00", + "5.00000000" + ], + [ + "174.42", + "0.99747735" + ], + [ + "172.53", + "0.10737418" + ], + [ + "162.38", + "0.10737418" + ], + [ + "160.00", + "2.00000000" + ], + [ + "159.00", + "5.71000000" + ], + [ + "155.00", + "1.00019354" + ], + [ + "153.00", + "20.00000000" + ], + [ + "152.83", + "0.10737418" + ], + [ + "150.08", + "1.00000000" + ], + [ + "150.00", + "2.00000000" + ], + [ + "145.00", + "7.00000000" + ], + [ + "143.84", + "0.10737418" + ], + [ + "135.38", + "0.10737418" + ], + [ + "130.00", + "2.00000000" + ], + [ + "129.14", + "0.99744463" + ], + [ + "128.00", + "0.50000000" + ], + [ + "127.41", + "0.10737418" + ], + [ + "125.97", + "1.00000000" + ], + [ + "125.00", + "20.00000000" + ], + [ + "123.00", + "1.20000000" + ], + [ + "122.71", + "1000.00000000" + ], + [ + "122.00", + "2.00000000" + ], + [ + "119.92", + "0.10737418" + ], + [ + "112.86", + "0.10737418" + ], + [ + "111.00", + "1.09000000" + ], + [ + "110.00", + "12.47354545" + ], + [ + "108.06", + "60.00000000" + ], + [ + "106.22", + "0.10737418" + ], + [ + "102.00", + "2.00000000" + ], + [ + "101.00", + "1.50000000" + ], + [ + "100.60", + "162.40000000" + ], + [ + "100.00", + "86.06363339" + ], + [ + "99.98", + "0.10737418" + ], + [ + "99.90", + "2.00020000" + ], + [ + "95.00", + "208.73000000" + ], + [ + "91.00", + "1.00000000" + ], + [ + "90.00", + "30.70088888" + ], + [ + "82.00", + "1.00000000" + ], + [ + "72.00", + "1.00000000" + ], + [ + "69.69", + "10.94475534" + ], + [ + "67.00", + "1.00000000" + ], + [ + "65.00", + "50.00000000" + ], + [ + "62.00", + "1.00000000" + ], + [ + "60.11", + "0.99800365" + ], + [ + "58.00", + "5.50705991" + ], + [ + "56.20", + "35.00000000" + ], + [ + "52.00", + "1.00000000" + ], + [ + "50.00", + "29.00000000" + ], + [ + "46.00", + "12.20336956" + ], + [ + "42.00", + "4.20000000" + ], + [ + "41.99", + "1.00000000" + ], + [ + "40.00", + "4.00000000" + ], + [ + "37.00", + "22.75756757" + ], + [ + "36.99", + "1.00000000" + ], + [ + "32.00", + "1.00000000" + ], + [ + "31.31", + "10.00000000" + ], + [ + "28.99", + "1.00000000" + ], + [ + "28.51", + "0.99719396" + ], + [ + "25.25", + "30.00000000" + ], + [ + "24.41", + "0.99713232" + ], + [ + "23.00", + "1.00000000" + ], + [ + "22.99", + "1.00000000" + ], + [ + "22.00", + "1.00000000" + ], + [ + "21.21", + "10.00000000" + ], + [ + "20.00", + "1.00000000" + ], + [ + "18.99", + "0.99736703" + ], + [ + "17.90", + "1.00000000" + ], + [ + "17.72", + "0.99717833" + ], + [ + "15.99", + "1.00000000" + ], + [ + "15.25", + "30.00000000" + ], + [ + "15.00", + "33.99933333" + ], + [ + "14.50", + "0.51586206" + ], + [ + "14.34", + "0.99721059" + ], + [ + "13.99", + "10.00000000" + ], + [ + "13.00", + "100.83000000" + ], + [ + "12.99", + "11.00000000" + ], + [ + "12.78", + "3.00000000" + ], + [ + "12.27", + "52.00000000" + ], + [ + "12.10", + "10.00000000" + ], + [ + "12.01", + "10.00000000" + ], + [ + "12.00", + "111.00000000" + ], + [ + "11.99", + "11.00000000" + ], + [ + "11.98", + "10.00000000" + ], + [ + "11.97", + "10.00000000" + ], + [ + "11.17", + "1.01162788" + ], + [ + "11.15", + "0.50419039" + ], + [ + "11.10", + "10.00000000" + ], + [ + "11.00", + "31.00000000" + ], + [ + "10.25", + "1.00487805" + ], + [ + "10.10", + "10.00000000" + ], + [ + "10.01", + "1820.00000000" + ], + [ + "10.00", + "150.49249999" + ], + [ + "9.99", + "50.99994394" + ], + [ + "9.50", + "0.55684210" + ], + [ + "9.45", + "1.02874218" + ], + [ + "9.00", + "22.00000000" + ], + [ + "8.26", + "0.99636803" + ], + [ + "7.68", + "0.99000000" + ], + [ + "7.00", + "1.00000000" + ], + [ + "6.65", + "1.00000000" + ], + [ + "6.00", + "101.11999999" + ], + [ + "5.99", + "57.90074680" + ], + [ + "5.62", + "400.00000000" + ], + [ + "5.05", + "25.00000000" + ], + [ + "5.00", + "7796.27602731" + ], + [ + "4.02", + "30.00000000" + ], + [ + "4.00", + "50.00000000" + ], + [ + "3.69", + "10.00000000" + ], + [ + "3.50", + "793.48560000" + ], + [ + "3.45", + "3.00000000" + ], + [ + "3.40", + "3.00000000" + ], + [ + "2.99", + "67.71571304" + ], + [ + "2.90", + "20.00000000" + ], + [ + "2.59", + "100.00000000" + ], + [ + "2.50", + "9.00000000" + ], + [ + "2.46", + "8.10975610" + ], + [ + "2.39", + "100.00000000" + ], + [ + "2.29", + "100.00000000" + ], + [ + "2.00", + "2168.54778750" + ], + [ + "1.65", + "5000.00000000" + ], + [ + "1.50", + "100.00000000" + ], + [ + "1.20", + "10.00000000" + ], + [ + "1.13", + "50.00000000" + ], + [ + "1.10", + "4400.00000000" + ], + [ + "1.01", + "83.00000000" + ], + [ + "1.00", + "11758.97043643" + ], + [ + "0.90", + "45.00000000" + ], + [ + "0.83", + "263.85542168" + ], + [ + "0.80", + "10.00000000" + ], + [ + "0.70", + "10.00000000" + ], + [ + "0.69", + "100.00000000" + ], + [ + "0.60", + "10.00000000" + ], + [ + "0.50", + "10.00000000" + ], + [ + "0.40", + "15.00000000" + ], + [ + "0.35", + "15.00000000" + ], + [ + "0.34", + "68.26470588" + ], + [ + "0.33", + "20.00000000" + ], + [ + "0.32", + "20.00000000" + ], + [ + "0.31", + "20.00000000" + ], + [ + "0.30", + "3020.00000000" + ], + [ + "0.29", + "120.00000000" + ], + [ + "0.28", + "20.00000000" + ], + [ + "0.27", + "20.00000000" + ], + [ + "0.26", + "20.00000000" + ], + [ + "0.25", + "40.00000000" + ], + [ + "0.24", + "25.00000000" + ], + [ + "0.23", + "25.00000000" + ], + [ + "0.22", + "25.00000000" + ], + [ + "0.21", + "25.00000000" + ], + [ + "0.20", + "4025.00000000" + ], + [ + "0.19", + "30.00000000" + ], + [ + "0.18", + "30.00000000" + ], + [ + "0.17", + "62.15470588" + ], + [ + "0.16", + "40.00000000" + ], + [ + "0.15", + "40.00000000" + ], + [ + "0.14", + "40.00000000" + ], + [ + "0.13", + "140.00000000" + ], + [ + "0.12", + "50.00000000" + ], + [ + "0.11", + "50.00000000" + ], + [ + "0.10", + "99300.00000000" + ], + [ + "0.09", + "75.00000000" + ], + [ + "0.08", + "100.00000000" + ], + [ + "0.07", + "1000.00000000" + ], + [ + "0.06", + "250.00000000" + ], + [ + "0.05", + "11713.60000000" + ], + [ + "0.04", + "1000.00000000" + ], + [ + "0.03", + "12500.00000000" + ], + [ + "0.02", + "71479.00000000" + ], + [ + "0.01", + "230061.01500000" + ] + ], + "asks": [ + [ + "8202.65", + "1.49199358" + ], + [ + "8204.80", + "1.00000000" + ], + [ + "8205.66", + "5.00000000" + ], + [ + "8205.89", + "3.04640000" + ], + [ + "8206.80", + "0.00166065" + ], + [ + "8206.94", + "2.00000000" + ], + [ + "8207.83", + "0.02000000" + ], + [ + "8207.98", + "2.50000000" + ], + [ + "8209.95", + "10.00000000" + ], + [ + "8210.24", + "2.50000000" + ], + [ + "8210.57", + "0.00618343" + ], + [ + "8211.04", + "8.00000000" + ], + [ + "8211.20", + "5.00000000" + ], + [ + "8211.47", + "0.40000000" + ], + [ + "8212.20", + "5.00000000" + ], + [ + "8212.97", + "0.10000000" + ], + [ + "8214.55", + "1.50170000" + ], + [ + "8218.77", + "3.90000000" + ], + [ + "8218.89", + "0.63900000" + ], + [ + "8219.70", + "0.50000000" + ], + [ + "8221.84", + "1.00000000" + ], + [ + "8222.52", + "2.30000000" + ], + [ + "8225.66", + "4.14299111" + ], + [ + "8225.67", + "4.49000000" + ], + [ + "8227.87", + "5.00000000" + ], + [ + "8228.53", + "0.40000000" + ], + [ + "8229.34", + "4.34000000" + ], + [ + "8232.06", + "2.43090000" + ], + [ + "8233.17", + "7.00000000" + ], + [ + "8235.41", + "2.54988182" + ], + [ + "8236.00", + "1.46000000" + ], + [ + "8236.66", + "0.00517293" + ], + [ + "8237.30", + "0.19891901" + ], + [ + "8238.32", + "0.10000000" + ], + [ + "8238.84", + "5.21285300" + ], + [ + "8240.75", + "13.08137618" + ], + [ + "8243.44", + "0.01489973" + ], + [ + "8243.78", + "2.37600000" + ], + [ + "8245.00", + "0.30000320" + ], + [ + "8246.08", + "15.18890000" + ], + [ + "8249.13", + "2.00000000" + ], + [ + "8249.20", + "0.86000000" + ], + [ + "8250.66", + "0.01220143" + ], + [ + "8250.76", + "1.11000000" + ], + [ + "8251.91", + "0.05886000" + ], + [ + "8252.03", + "10.37735960" + ], + [ + "8255.39", + "1.45200000" + ], + [ + "8256.68", + "0.00674998" + ], + [ + "8257.40", + "0.05000000" + ], + [ + "8257.59", + "0.00136683" + ], + [ + "8264.58", + "0.00096058" + ], + [ + "8265.60", + "0.86000000" + ], + [ + "8266.23", + "13.53981780" + ], + [ + "8268.04", + "0.95865787" + ], + [ + "8268.11", + "0.20000000" + ], + [ + "8268.38", + "1.00000000" + ], + [ + "8268.79", + "0.00674998" + ], + [ + "8273.21", + "0.02260911" + ], + [ + "8273.24", + "3.44600000" + ], + [ + "8273.36", + "12.50000000" + ], + [ + "8277.01", + "0.01479943" + ], + [ + "8277.03", + "0.28285714" + ], + [ + "8278.40", + "0.00096059" + ], + [ + "8279.26", + "0.25000000" + ], + [ + "8280.53", + "0.00674998" + ], + [ + "8281.36", + "1.00000000" + ], + [ + "8281.46", + "16.69695724" + ], + [ + "8282.00", + "0.00096356" + ], + [ + "8282.69", + "2.00000000" + ], + [ + "8283.83", + "0.00241426" + ], + [ + "8285.24", + "1.00000000" + ], + [ + "8285.32", + "1.00000000" + ], + [ + "8285.84", + "1.29262634" + ], + [ + "8291.25", + "0.20000000" + ], + [ + "8291.52", + "0.00674998" + ], + [ + "8291.93", + "1.00000000" + ], + [ + "8292.21", + "0.00096060" + ], + [ + "8293.23", + "1.00000000" + ], + [ + "8294.04", + "12.50000000" + ], + [ + "8294.23", + "13.02328351" + ], + [ + "8294.33", + "1.00000000" + ], + [ + "8295.00", + "0.10000000" + ], + [ + "8297.03", + "0.02986655" + ], + [ + "8298.26", + "1.00000000" + ], + [ + "8300.00", + "2.67361158" + ], + [ + "8300.36", + "0.26000000" + ], + [ + "8301.82", + "0.23844388" + ], + [ + "8302.52", + "0.00674998" + ], + [ + "8303.64", + "8.14400000" + ], + [ + "8304.21", + "1.00000000" + ], + [ + "8306.03", + "0.00095061" + ], + [ + "8306.55", + "9.89471212" + ], + [ + "8307.33", + "3.00000000" + ], + [ + "8309.62", + "3.00000000" + ], + [ + "8310.46", + "0.01459908" + ], + [ + "8314.17", + "0.00674998" + ], + [ + "8314.77", + "12.50000000" + ], + [ + "8317.99", + "0.25000000" + ], + [ + "8319.85", + "0.00095062" + ], + [ + "8323.00", + "0.50000000" + ], + [ + "8324.41", + "11.55910187" + ], + [ + "8325.18", + "0.00674998" + ], + [ + "8332.39", + "0.50000000" + ], + [ + "8332.50", + "0.00095772" + ], + [ + "8333.67", + "0.00095063" + ], + [ + "8334.95", + "0.25000000" + ], + [ + "8335.03", + "0.25000000" + ], + [ + "8335.56", + "10.85057760" + ], + [ + "8336.19", + "0.00273418" + ], + [ + "8337.02", + "0.01500000" + ], + [ + "8337.41", + "0.00509432" + ], + [ + "8337.52", + "0.00541678" + ], + [ + "8337.95", + "11.47361618" + ], + [ + "8339.00", + "1.00000000" + ], + [ + "8340.00", + "0.10000000" + ], + [ + "8340.59", + "0.00770264" + ], + [ + "8341.02", + "0.10000000" + ], + [ + "8341.83", + "0.05000000" + ], + [ + "8342.99", + "0.22216058" + ], + [ + "8344.12", + "0.10000000" + ], + [ + "8345.35", + "0.06125000" + ], + [ + "8345.56", + "0.00400000" + ], + [ + "8345.62", + "8.00700000" + ], + [ + "8345.69", + "0.00218348" + ], + [ + "8347.00", + "0.02000000" + ], + [ + "8347.48", + "0.00095064" + ], + [ + "8348.00", + "0.10000000" + ], + [ + "8348.05", + "0.28000000" + ], + [ + "8348.88", + "2.00000000" + ], + [ + "8349.00", + "0.01000000" + ], + [ + "8349.40", + "0.00514284" + ], + [ + "8349.78", + "9.20591421" + ], + [ + "8350.00", + "4.66632036" + ], + [ + "8355.00", + "0.00315042" + ], + [ + "8355.94", + "0.50000000" + ], + [ + "8356.40", + "12.50000000" + ], + [ + "8357.75", + "0.00514284" + ], + [ + "8358.00", + "0.04261097" + ], + [ + "8358.43", + "0.65000000" + ], + [ + "8361.10", + "0.03312500" + ], + [ + "8361.30", + "0.00095065" + ], + [ + "8361.63", + "1.11000000" + ], + [ + "8362.89", + "8.12569115" + ], + [ + "8364.64", + "0.00456200" + ], + [ + "8365.58", + "0.00200000" + ], + [ + "8366.00", + "0.20000000" + ], + [ + "8366.10", + "0.00514284" + ], + [ + "8367.00", + "0.02000000" + ], + [ + "8367.32", + "0.03120293" + ], + [ + "8368.11", + "0.50000000" + ], + [ + "8369.00", + "0.06763003" + ], + [ + "8370.00", + "0.01000000" + ], + [ + "8373.42", + "0.00560190" + ], + [ + "8374.00", + "0.02629083" + ], + [ + "8374.45", + "0.00514284" + ], + [ + "8375.00", + "0.12500000" + ], + [ + "8375.12", + "0.00095066" + ], + [ + "8377.29", + "12.50000000" + ], + [ + "8378.00", + "0.50000000" + ], + [ + "8380.00", + "0.15616625" + ], + [ + "8380.04", + "7.63079813" + ], + [ + "8380.38", + "14.02500000" + ], + [ + "8382.80", + "0.00514284" + ], + [ + "8383.00", + "0.00095195" + ], + [ + "8384.00", + "0.00149507" + ], + [ + "8387.00", + "0.12000000" + ], + [ + "8388.00", + "0.14540024" + ], + [ + "8388.64", + "0.37191245" + ], + [ + "8388.78", + "1.39190411" + ], + [ + "8388.94", + "0.00094067" + ], + [ + "8390.00", + "0.10000000" + ], + [ + "8390.57", + "8.82022648" + ], + [ + "8391.15", + "0.00514284" + ], + [ + "8395.00", + "2.26788502" + ], + [ + "8398.24", + "12.50000000" + ], + [ + "8399.00", + "0.50000000" + ], + [ + "8399.50", + "0.00514284" + ], + [ + "8399.56", + "0.02571329" + ], + [ + "8400.00", + "5.44906742" + ], + [ + "8400.16", + "0.02000000" + ], + [ + "8401.65", + "0.70000000" + ], + [ + "8402.75", + "0.00094068" + ], + [ + "8403.68", + "5.84545852" + ], + [ + "8404.90", + "0.00199500" + ], + [ + "8407.00", + "0.04000000" + ], + [ + "8407.83", + "0.02500000" + ], + [ + "8407.85", + "0.00514284" + ], + [ + "8408.00", + "0.03000000" + ], + [ + "8408.75", + "0.00164100" + ], + [ + "8410.00", + "0.03597192" + ], + [ + "8415.00", + "0.10576470" + ], + [ + "8416.00", + "0.05000000" + ], + [ + "8416.20", + "0.00514284" + ], + [ + "8416.34", + "9.78661090" + ], + [ + "8416.57", + "0.00094069" + ], + [ + "8419.23", + "12.50000000" + ], + [ + "8420.00", + "1.25000000" + ], + [ + "8420.69", + "1.00000000" + ], + [ + "8422.23", + "0.10000000" + ], + [ + "8424.00", + "0.05000000" + ], + [ + "8425.26", + "0.00514284" + ], + [ + "8427.00", + "0.04000000" + ], + [ + "8428.01", + "0.00230619" + ], + [ + "8430.00", + "0.00168737" + ], + [ + "8430.39", + "0.00094070" + ], + [ + "8433.00", + "0.00100000" + ], + [ + "8433.50", + "0.00094625" + ], + [ + "8435.36", + "0.00514284" + ], + [ + "8439.00", + "1.00000000" + ], + [ + "8439.43", + "0.07153984" + ], + [ + "8439.80", + "1.48000000" + ], + [ + "8440.28", + "12.50000000" + ], + [ + "8442.63", + "0.41505948" + ], + [ + "8443.75", + "0.00276943" + ], + [ + "8444.20", + "0.00094071" + ], + [ + "8444.24", + "0.20000000" + ], + [ + "8444.44", + "0.01675383" + ], + [ + "8445.00", + "3.03075269" + ], + [ + "8445.35", + "0.00519229" + ], + [ + "8446.32", + "0.07639018" + ], + [ + "8447.00", + "0.17000000" + ], + [ + "8447.52", + "0.05000000" + ], + [ + "8448.00", + "0.05000000" + ], + [ + "8448.78", + "0.08683010" + ], + [ + "8449.00", + "0.06644731" + ], + [ + "8450.00", + "5.55492180" + ], + [ + "8451.15", + "0.00245600" + ], + [ + "8452.00", + "0.05000000" + ], + [ + "8453.00", + "0.10000000" + ], + [ + "8453.11", + "0.00163668" + ], + [ + "8453.76", + "0.00519229" + ], + [ + "8454.00", + "0.04200000" + ], + [ + "8455.00", + "0.26087250" + ], + [ + "8455.27", + "0.00377909" + ], + [ + "8455.53", + "0.28902717" + ], + [ + "8456.72", + "0.00968523" + ], + [ + "8456.86", + "0.20000000" + ], + [ + "8457.00", + "0.06000000" + ], + [ + "8458.00", + "0.09391645" + ], + [ + "8458.02", + "0.00094072" + ], + [ + "8459.84", + "0.05000000" + ], + [ + "8460.00", + "0.10000000" + ], + [ + "8460.08", + "0.10000000" + ], + [ + "8460.84", + "1.22514677" + ], + [ + "8461.00", + "0.10000000" + ], + [ + "8461.38", + "12.50000000" + ], + [ + "8462.18", + "0.00519229" + ], + [ + "8463.00", + "0.12000000" + ], + [ + "8463.35", + "0.10000000" + ], + [ + "8466.00", + "0.25000000" + ], + [ + "8467.00", + "0.02000000" + ], + [ + "8467.20", + "0.05000000" + ], + [ + "8468.90", + "1.00000000" + ], + [ + "8470.01", + "0.74394843" + ], + [ + "8470.11", + "0.10000000" + ], + [ + "8470.59", + "0.00519229" + ], + [ + "8471.77", + "0.00870569" + ], + [ + "8471.84", + "0.00094073" + ], + [ + "8473.76", + "0.03821576" + ], + [ + "8473.90", + "0.00120000" + ], + [ + "8474.12", + "0.00314587" + ], + [ + "8475.33", + "0.07612876" + ], + [ + "8475.99", + "0.00313252" + ], + [ + "8476.13", + "0.00316303" + ], + [ + "8479.00", + "0.00519229" + ], + [ + "8481.00", + "0.05000000" + ], + [ + "8482.53", + "12.50000000" + ], + [ + "8482.59", + "0.00078344" + ], + [ + "8484.00", + "0.00094061" + ], + [ + "8484.86", + "0.00400000" + ], + [ + "8485.66", + "0.00093074" + ], + [ + "8487.00", + "0.02000000" + ], + [ + "8489.00", + "0.25000000" + ], + [ + "8489.07", + "0.00519229" + ], + [ + "8489.45", + "0.20000000" + ], + [ + "8490.00", + "0.02000000" + ], + [ + "8492.55", + "0.00964436" + ], + [ + "8494.00", + "0.13796981" + ], + [ + "8495.01", + "0.02570000" + ], + [ + "8495.99", + "0.24989620" + ], + [ + "8496.15", + "0.60000000" + ], + [ + "8497.93", + "0.01500000" + ], + [ + "8499.00", + "0.90000000" + ], + [ + "8499.11", + "0.00519229" + ], + [ + "8499.47", + "0.00093075" + ], + [ + "8500.00", + "113.41158092" + ], + [ + "8500.08", + "0.23200000" + ], + [ + "8500.14", + "0.02215135" + ], + [ + "8500.50", + "0.66736347" + ], + [ + "8500.54", + "0.78511746" + ], + [ + "8502.56", + "0.09920684" + ], + [ + "8503.74", + "12.50000000" + ], + [ + "8504.53", + "0.02528911" + ], + [ + "8506.20", + "0.00519229" + ], + [ + "8506.93", + "0.10000000" + ], + [ + "8507.00", + "0.02000000" + ], + [ + "8509.67", + "0.02527383" + ], + [ + "8510.00", + "0.03587000" + ], + [ + "8510.53", + "0.00399466" + ], + [ + "8512.04", + "0.05000000" + ], + [ + "8513.29", + "0.00093076" + ], + [ + "8514.02", + "0.00124526" + ], + [ + "8514.83", + "0.06500000" + ], + [ + "8515.67", + "0.00519229" + ], + [ + "8519.00", + "0.02000000" + ], + [ + "8520.00", + "0.05000000" + ], + [ + "8520.58", + "0.03000000" + ], + [ + "8525.00", + "12.57000000" + ], + [ + "8525.76", + "0.00519229" + ], + [ + "8527.00", + "0.02000000" + ], + [ + "8527.11", + "0.00093077" + ], + [ + "8527.37", + "0.95804385" + ], + [ + "8528.39", + "0.00960384" + ], + [ + "8530.00", + "0.12121220" + ], + [ + "8532.57", + "0.00224680" + ], + [ + "8533.98", + "0.00519229" + ], + [ + "8534.50", + "0.00093505" + ], + [ + "8535.00", + "0.02000000" + ], + [ + "8535.40", + "0.08504566" + ], + [ + "8540.92", + "0.00093078" + ], + [ + "8542.00", + "0.01500000" + ], + [ + "8542.54", + "0.00337766" + ], + [ + "8543.17", + "0.00524270" + ], + [ + "8543.21", + "0.00450000" + ], + [ + "8546.31", + "12.50000000" + ], + [ + "8547.00", + "0.02000000" + ], + [ + "8549.00", + "5.13000000" + ], + [ + "8550.00", + "4.51533911" + ], + [ + "8550.07", + "0.01080565" + ], + [ + "8552.05", + "0.10978248" + ], + [ + "8553.13", + "0.00524270" + ], + [ + "8554.74", + "0.00093079" + ], + [ + "8560.00", + "0.02865445" + ], + [ + "8561.50", + "0.00524270" + ], + [ + "8564.22", + "0.00956366" + ], + [ + "8565.00", + "0.01000000" + ], + [ + "8567.00", + "0.22983036" + ], + [ + "8567.68", + "12.50000000" + ], + [ + "8568.56", + "0.00092080" + ], + [ + "8570.42", + "0.00524270" + ], + [ + "8572.00", + "0.02500000" + ], + [ + "8572.17", + "0.50000000" + ], + [ + "8574.00", + "0.14000000" + ], + [ + "8575.05", + "1.20000000" + ], + [ + "8577.00", + "0.00152513" + ], + [ + "8578.48", + "0.37191245" + ], + [ + "8579.49", + "0.02922786" + ], + [ + "8580.00", + "0.20000000" + ], + [ + "8581.55", + "0.00524270" + ], + [ + "8582.38", + "0.00092081" + ], + [ + "8584.00", + "0.14000000" + ], + [ + "8585.00", + "0.00092955" + ], + [ + "8587.00", + "0.48111454" + ], + [ + "8589.10", + "12.50000000" + ], + [ + "8590.00", + "0.15000000" + ], + [ + "8590.52", + "0.12318928" + ], + [ + "8591.12", + "0.00524270" + ], + [ + "8592.00", + "0.03000000" + ], + [ + "8592.28", + "0.03245000" + ], + [ + "8593.00", + "2.09720690" + ], + [ + "8595.00", + "0.03993125" + ], + [ + "8596.19", + "0.00092082" + ], + [ + "8598.88", + "0.40280017" + ], + [ + "8599.00", + "1.42814526" + ], + [ + "8599.67", + "0.00524270" + ], + [ + "8600.00", + "14.74100965" + ], + [ + "8600.05", + "0.00952381" + ], + [ + "8605.00", + "0.03298968" + ], + [ + "8607.00", + "0.02000000" + ], + [ + "8608.00", + "0.15000000" + ], + [ + "8608.22", + "0.00524270" + ], + [ + "8610.00", + "0.12586000" + ], + [ + "8610.01", + "0.00092083" + ], + [ + "8610.57", + "12.50000000" + ], + [ + "8615.00", + "3.34123339" + ], + [ + "8616.77", + "0.00385512" + ], + [ + "8617.00", + "0.28325823" + ], + [ + "8618.37", + "0.00529410" + ], + [ + "8619.00", + "0.00151750" + ], + [ + "8620.00", + "0.00430000" + ], + [ + "8622.22", + "0.00965840" + ], + [ + "8623.83", + "0.00092084" + ], + [ + "8624.00", + "0.10000000" + ], + [ + "8625.00", + "0.00350000" + ], + [ + "8627.00", + "0.02000000" + ], + [ + "8630.00", + "0.29916998" + ], + [ + "8630.71", + "0.00529410" + ], + [ + "8632.10", + "12.50000000" + ], + [ + "8635.00", + "1.50000000" + ], + [ + "8635.22", + "0.02484434" + ], + [ + "8635.50", + "0.00671388" + ], + [ + "8635.89", + "0.00948429" + ], + [ + "8637.64", + "0.00092085" + ], + [ + "8639.00", + "1.00000000" + ], + [ + "8639.32", + "0.00529410" + ], + [ + "8640.00", + "1.00624953" + ], + [ + "8647.00", + "0.02000000" + ], + [ + "8648.43", + "0.00529410" + ], + [ + "8649.00", + "0.50000000" + ], + [ + "8650.00", + "0.77508408" + ], + [ + "8650.85", + "1.61484276" + ], + [ + "8651.46", + "0.00092086" + ], + [ + "8653.58", + "0.03000000" + ], + [ + "8653.68", + "12.50000000" + ], + [ + "8654.22", + "0.00319156" + ], + [ + "8654.73", + "0.25738372" + ], + [ + "8655.00", + "0.47294999" + ], + [ + "8656.00", + "0.00650000" + ], + [ + "8656.42", + "0.00344644" + ], + [ + "8656.88", + "0.20140009" + ], + [ + "8657.00", + "0.01500000" + ], + [ + "8657.05", + "0.00529410" + ], + [ + "8657.65", + "0.00453280" + ], + [ + "8658.00", + "0.00219500" + ], + [ + "8659.73", + "0.54111397" + ], + [ + "8660.00", + "0.22958221" + ], + [ + "8663.37", + "1.45990362" + ], + [ + "8665.28", + "0.00091087" + ], + [ + "8665.67", + "0.00529410" + ], + [ + "8667.00", + "0.02000000" + ], + [ + "8667.68", + "0.06568408" + ], + [ + "8670.62", + "0.01000000" + ], + [ + "8670.90", + "0.77999000" + ], + [ + "8670.93", + "0.01500000" + ], + [ + "8671.72", + "0.00944510" + ], + [ + "8672.00", + "0.01000000" + ], + [ + "8674.29", + "0.00529410" + ], + [ + "8675.00", + "0.20000000" + ], + [ + "8675.31", + "12.50000000" + ], + [ + "8677.00", + "0.00100000" + ], + [ + "8679.10", + "0.00091088" + ], + [ + "8680.00", + "0.00371520" + ], + [ + "8680.13", + "0.02380614" + ], + [ + "8680.26", + "0.24517876" + ], + [ + "8681.00", + "0.01000000" + ], + [ + "8682.04", + "0.00073000" + ], + [ + "8682.91", + "0.00529410" + ], + [ + "8682.97", + "0.00333628" + ], + [ + "8683.02", + "0.01500000" + ], + [ + "8685.00", + "0.04200000" + ], + [ + "8685.04", + "0.02000000" + ], + [ + "8685.99", + "0.39321996" + ], + [ + "8686.00", + "0.00091874" + ], + [ + "8686.20", + "0.10000000" + ], + [ + "8686.86", + "0.00500000" + ], + [ + "8687.00", + "0.02000000" + ], + [ + "8687.02", + "0.10000000" + ], + [ + "8687.46", + "0.00317865" + ], + [ + "8687.83", + "0.14500000" + ], + [ + "8687.92", + "0.00400000" + ], + [ + "8688.00", + "0.04391645" + ], + [ + "8692.43", + "0.00529410" + ], + [ + "8692.91", + "0.00091089" + ], + [ + "8693.00", + "0.12000000" + ], + [ + "8694.60", + "0.04200000" + ], + [ + "8697.00", + "12.78120256" + ], + [ + "8697.97", + "0.20000000" + ], + [ + "8699.00", + "0.50000000" + ], + [ + "8700.00", + "3.19977872" + ], + [ + "8700.96", + "0.03821575" + ], + [ + "8701.00", + "0.07000000" + ], + [ + "8701.37", + "0.00529410" + ], + [ + "8704.00", + "0.02800000" + ], + [ + "8706.00", + "0.09000000" + ], + [ + "8706.73", + "0.00091090" + ], + [ + "8707.00", + "0.02000000" + ], + [ + "8710.00", + "0.06826262" + ], + [ + "8710.18", + "0.00445557" + ], + [ + "8713.83", + "0.07000000" + ], + [ + "8714.00", + "0.50000000" + ], + [ + "8714.13", + "0.00534651" + ], + [ + "8716.44", + "0.00186468" + ], + [ + "8717.19", + "0.05003031" + ], + [ + "8718.74", + "12.50000000" + ], + [ + "8720.55", + "0.00091091" + ], + [ + "8720.60", + "0.05000000" + ], + [ + "8721.00", + "1.12530000" + ], + [ + "8721.40", + "0.00280000" + ], + [ + "8722.81", + "0.00534651" + ], + [ + "8723.00", + "0.05000000" + ], + [ + "8724.00", + "0.19800000" + ], + [ + "8726.67", + "0.00432354" + ], + [ + "8726.87", + "0.00572915" + ], + [ + "8727.00", + "0.02000000" + ], + [ + "8729.00", + "0.07000000" + ], + [ + "8730.00", + "0.52015674" + ], + [ + "8730.65", + "0.00534651" + ], + [ + "8731.00", + "0.22383586" + ], + [ + "8733.00", + "0.05000000" + ], + [ + "8734.37", + "0.00091092" + ], + [ + "8735.00", + "0.07000000" + ], + [ + "8736.50", + "0.00091343" + ], + [ + "8737.00", + "0.32000000" + ], + [ + "8737.29", + "1.01182305" + ], + [ + "8737.91", + "0.00448136" + ], + [ + "8738.00", + "0.14000000" + ], + [ + "8739.00", + "0.07401390" + ], + [ + "8740.00", + "1.93051074" + ], + [ + "8740.54", + "12.50000000" + ], + [ + "8741.71", + "0.00534651" + ], + [ + "8743.00", + "0.10000000" + ], + [ + "8744.00", + "0.87941000" + ], + [ + "8744.44", + "0.54147669" + ], + [ + "8747.00", + "0.56147668" + ], + [ + "8748.18", + "0.00091093" + ], + [ + "8749.00", + "0.50000000" + ], + [ + "8749.62", + "0.00534651" + ], + [ + "8750.00", + "3.87593343" + ], + [ + "8750.60", + "0.03000000" + ], + [ + "8752.00", + "0.00091001" + ], + [ + "8757.30", + "0.00226051" + ], + [ + "8758.00", + "0.27030864" + ], + [ + "8759.30", + "0.05325263" + ], + [ + "8759.99", + "0.06045374" + ], + [ + "8760.00", + "0.10000000" + ], + [ + "8761.39", + "0.00481204" + ], + [ + "8762.00", + "0.00090094" + ], + [ + "8762.39", + "12.50000000" + ], + [ + "8763.66", + "0.00539998" + ], + [ + "8765.00", + "0.00566348" + ], + [ + "8765.61", + "0.00090002" + ], + [ + "8767.00", + "0.02000000" + ], + [ + "8770.00", + "1.39219770" + ], + [ + "8771.00", + "0.04500000" + ], + [ + "8772.00", + "0.10000000" + ], + [ + "8772.01", + "0.00062342" + ], + [ + "8773.25", + "0.00539998" + ], + [ + "8774.00", + "0.72831559" + ], + [ + "8778.92", + "1.00000000" + ], + [ + "8778.97", + "0.11154699" + ], + [ + "8779.22", + "0.01203044" + ], + [ + "8779.73", + "0.54111397" + ], + [ + "8780.00", + "0.74000000" + ], + [ + "8782.00", + "0.00539998" + ], + [ + "8784.29", + "12.50000000" + ], + [ + "8785.96", + "0.00299195" + ], + [ + "8787.00", + "0.02090818" + ], + [ + "8787.01", + "0.00231626" + ], + [ + "8787.70", + "0.05000000" + ], + [ + "8789.06", + "0.02844000" + ], + [ + "8790.00", + "0.17696146" + ], + [ + "8792.83", + "0.00090004" + ], + [ + "8796.00", + "0.05000000" + ], + [ + "8796.09", + "0.00539998" + ], + [ + "8797.00", + "0.10000000" + ], + [ + "8798.00", + "0.52760000" + ], + [ + "8799.00", + "0.50000000" + ], + [ + "8799.17", + "0.01591059" + ], + [ + "8800.00", + "8.84746918" + ], + [ + "8800.17", + "0.10000001" + ], + [ + "8800.50", + "0.50000000" + ], + [ + "8802.33", + "0.00291636" + ], + [ + "8804.89", + "0.00539998" + ], + [ + "8805.00", + "0.01000000" + ], + [ + "8806.26", + "12.50000000" + ], + [ + "8806.44", + "0.00090005" + ], + [ + "8807.00", + "0.02000000" + ], + [ + "8807.59", + "0.01768169" + ], + [ + "8810.00", + "0.00083000" + ], + [ + "8813.69", + "0.00539998" + ], + [ + "8820.00", + "0.00116667" + ], + [ + "8820.05", + "0.00090006" + ], + [ + "8822.49", + "0.00539998" + ], + [ + "8823.00", + "0.09000000" + ], + [ + "8824.00", + "0.35182702" + ], + [ + "8825.00", + "0.05000000" + ], + [ + "8827.00", + "0.02000000" + ], + [ + "8827.76", + "0.02726835" + ], + [ + "8828.27", + "12.50000000" + ], + [ + "8828.98", + "0.00373087" + ], + [ + "8831.00", + "0.15475517" + ], + [ + "8831.29", + "0.00539998" + ], + [ + "8833.00", + "0.19356990" + ], + [ + "8833.66", + "0.00090007" + ], + [ + "8836.00", + "0.05668478" + ], + [ + "8836.78", + "0.01357898" + ], + [ + "8837.50", + "0.00090299" + ], + [ + "8837.84", + "0.00300000" + ], + [ + "8839.00", + "1.00000000" + ], + [ + "8839.47", + "0.00565617" + ], + [ + "8840.00", + "0.60928566" + ], + [ + "8840.09", + "0.00523299" + ], + [ + "8842.32", + "0.00556699" + ], + [ + "8843.93", + "0.01500000" + ], + [ + "8844.00", + "0.01000000" + ], + [ + "8845.45", + "0.01437656" + ], + [ + "8846.13", + "0.50000000" + ], + [ + "8847.00", + "0.20000000" + ], + [ + "8847.27", + "0.00090008" + ], + [ + "8848.25", + "0.20000000" + ], + [ + "8848.48", + "0.10000000" + ], + [ + "8849.00", + "1.03500000" + ], + [ + "8850.00", + "15.46499706" + ], + [ + "8850.34", + "12.50000000" + ], + [ + "8851.78", + "0.00061455" + ], + [ + "8853.16", + "0.00263046" + ], + [ + "8855.00", + "0.01000000" + ], + [ + "8856.02", + "0.01500000" + ], + [ + "8856.66", + "0.00232080" + ], + [ + "8857.00", + "0.18000000" + ], + [ + "8858.04", + "0.02000000" + ], + [ + "8860.00", + "0.25000000" + ], + [ + "8860.58", + "0.10000000" + ], + [ + "8860.83", + "0.06500000" + ], + [ + "8860.88", + "0.00089009" + ], + [ + "8863.00", + "0.18000000" + ], + [ + "8865.00", + "0.29079269" + ], + [ + "8866.00", + "0.05000000" + ], + [ + "8866.83", + "0.07000000" + ], + [ + "8867.00", + "0.02000000" + ], + [ + "8867.51", + "1.00000000" + ], + [ + "8867.60", + "0.04200000" + ], + [ + "8868.75", + "0.00126000" + ], + [ + "8868.89", + "0.00234032" + ], + [ + "8869.00", + "1.50940541" + ], + [ + "8869.82", + "0.02288110" + ], + [ + "8870.09", + "0.50000000" + ], + [ + "8871.00", + "0.40000000" + ], + [ + "8872.33", + "0.03000000" + ], + [ + "8872.47", + "12.50000000" + ], + [ + "8872.79", + "1.00000000" + ], + [ + "8873.60", + "0.05000000" + ], + [ + "8874.49", + "0.00089010" + ], + [ + "8875.82", + "0.00299104" + ], + [ + "8876.00", + "1.00000000" + ], + [ + "8877.00", + "0.02500000" + ], + [ + "8878.00", + "0.27547275" + ], + [ + "8879.00", + "0.00430000" + ], + [ + "8880.00", + "0.78055032" + ], + [ + "8881.04", + "0.00524089" + ], + [ + "8885.61", + "0.06378424" + ], + [ + "8887.00", + "0.02000000" + ], + [ + "8887.36", + "3.68562830" + ], + [ + "8888.00", + "2.19038906" + ], + [ + "8888.09", + "0.00089011" + ], + [ + "8888.83", + "0.41600000" + ], + [ + "8889.00", + "0.51000000" + ], + [ + "8890.00", + "9.65256155" + ], + [ + "8892.00", + "0.20000000" + ], + [ + "8892.22", + "0.30000000" + ], + [ + "8892.76", + "0.47474402" + ], + [ + "8894.65", + "12.50000000" + ], + [ + "8895.00", + "1.00000000" + ], + [ + "8896.15", + "0.00228785" + ], + [ + "8897.78", + "0.00163000" + ], + [ + "8898.47", + "6.00000000" + ], + [ + "8899.00", + "0.76349446" + ], + [ + "8899.38", + "0.60007347" + ], + [ + "8899.98", + "0.03382107" + ], + [ + "8900.00", + "6.08119571" + ], + [ + "8901.70", + "0.00089012" + ], + [ + "8902.47", + "0.00119700" + ], + [ + "8902.75", + "0.00324195" + ], + [ + "8906.92", + "0.20000000" + ], + [ + "8907.00", + "0.02000000" + ], + [ + "8908.34", + "0.00344475" + ], + [ + "8910.00", + "0.00082000" + ], + [ + "8910.20", + "6.42595103" + ], + [ + "8911.98", + "0.12373142" + ], + [ + "8913.24", + "0.00431576" + ], + [ + "8913.65", + "0.01346250" + ], + [ + "8915.31", + "0.00089013" + ], + [ + "8916.89", + "12.50000000" + ], + [ + "8917.81", + "0.01685730" + ], + [ + "8917.93", + "0.00235120" + ], + [ + "8920.00", + "0.33297354" + ], + [ + "8921.00", + "0.47294999" + ], + [ + "8921.38", + "0.00060652" + ], + [ + "8923.21", + "0.01066439" + ], + [ + "8925.00", + "0.00450000" + ], + [ + "8926.00", + "0.03000000" + ], + [ + "8926.74", + "0.01290426" + ], + [ + "8927.00", + "0.12000000" + ], + [ + "8927.60", + "0.03000000" + ], + [ + "8928.92", + "0.00089014" + ], + [ + "8930.00", + "0.50567534" + ], + [ + "8932.00", + "0.03000000" + ], + [ + "8937.00", + "0.40000000" + ], + [ + "8938.50", + "0.00089279" + ], + [ + "8939.18", + "12.50000000" + ], + [ + "8942.53", + "0.00089015" + ], + [ + "8942.93", + "0.00309286" + ], + [ + "8943.00", + "0.05000000" + ], + [ + "8944.00", + "0.12000000" + ], + [ + "8944.90", + "1.37600000" + ], + [ + "8945.99", + "0.02190030" + ], + [ + "8946.91", + "0.25000000" + ], + [ + "8947.00", + "0.02000000" + ], + [ + "8947.27", + "0.00055939" + ], + [ + "8947.70", + "0.12781120" + ], + [ + "8948.00", + "0.01000000" + ], + [ + "8948.22", + "0.00240360" + ], + [ + "8948.97", + "0.20000000" + ], + [ + "8949.00", + "0.13500000" + ], + [ + "8950.00", + "6.85449841" + ], + [ + "8950.30", + "0.12689516" + ], + [ + "8955.00", + "0.02000000" + ], + [ + "8956.14", + "0.00088016" + ], + [ + "8959.00", + "10.00000000" + ], + [ + "8961.53", + "12.50000000" + ], + [ + "8965.00", + "0.03680981" + ], + [ + "8966.00", + "0.05000000" + ], + [ + "8966.62", + "0.50000000" + ], + [ + "8967.00", + "0.02000000" + ], + [ + "8969.75", + "0.00088017" + ], + [ + "8970.00", + "0.09000000" + ], + [ + "8974.00", + "0.02629083" + ], + [ + "8975.00", + "0.58900000" + ], + [ + "8976.00", + "0.00228994" + ], + [ + "8977.77", + "10.00000000" + ], + [ + "8978.00", + "0.09000000" + ], + [ + "8978.40", + "0.06700000" + ], + [ + "8979.61", + "0.00400000" + ], + [ + "8980.00", + "0.20950000" + ], + [ + "8980.78", + "0.00059931" + ], + [ + "8981.00", + "0.03600000" + ], + [ + "8981.92", + "0.00487509" + ], + [ + "8983.36", + "0.00088018" + ], + [ + "8983.93", + "12.50000000" + ], + [ + "8984.00", + "0.10000000" + ], + [ + "8984.36", + "36.90000000" + ], + [ + "8986.39", + "0.40000000" + ], + [ + "8987.00", + "0.02000000" + ], + [ + "8987.23", + "0.00122675" + ], + [ + "8989.00", + "0.02203382" + ], + [ + "8989.89", + "0.54602096" + ], + [ + "8990.00", + "1.04285774" + ], + [ + "8990.34", + "0.01344170" + ], + [ + "8991.00", + "2.00000000" + ], + [ + "8994.00", + "0.09000000" + ], + [ + "8995.00", + "3.61521220" + ], + [ + "8996.00", + "0.10000000" + ], + [ + "8996.97", + "0.00088019" + ], + [ + "8998.00", + "20.05815408" + ], + [ + "8999.00", + "4.56877573" + ], + [ + "8999.10", + "0.00649807" + ], + [ + "8999.89", + "0.00227352" + ], + [ + "9000.00", + "28.53099463" + ], + [ + "9000.01", + "0.01379311" + ], + [ + "9000.19", + "0.04409916" + ], + [ + "9000.44", + "0.00200000" + ], + [ + "9001.00", + "1.34269654" + ], + [ + "9005.00", + "0.03000000" + ], + [ + "9006.00", + "0.09000000" + ], + [ + "9006.39", + "12.50000000" + ], + [ + "9007.00", + "0.02000000" + ], + [ + "9010.00", + "0.00081000" + ], + [ + "9010.58", + "1.00088020" + ], + [ + "9012.00", + "0.09000000" + ], + [ + "9014.00", + "0.09000000" + ], + [ + "9015.00", + "0.18000000" + ], + [ + "9015.40", + "0.14179002" + ], + [ + "9017.00", + "2.32031718" + ], + [ + "9018.00", + "0.02108615" + ], + [ + "9019.63", + "0.00282601" + ], + [ + "9019.83", + "0.01500000" + ], + [ + "9020.00", + "0.03000000" + ], + [ + "9022.00", + "0.02000000" + ], + [ + "9024.19", + "0.00088021" + ], + [ + "9024.77", + "0.01050692" + ], + [ + "9026.60", + "0.03000000" + ], + [ + "9027.00", + "0.02000000" + ], + [ + "9028.00", + "0.02500000" + ], + [ + "9028.90", + "12.50000000" + ], + [ + "9029.02", + "0.01500000" + ], + [ + "9030.00", + "0.14059286" + ], + [ + "9030.89", + "0.00055421" + ], + [ + "9031.04", + "0.05000000" + ], + [ + "9033.02", + "0.10000000" + ], + [ + "9033.83", + "0.05000000" + ], + [ + "9035.00", + "0.17000000" + ], + [ + "9037.80", + "0.00088022" + ], + [ + "9038.34", + "0.00298173" + ], + [ + "9039.00", + "1.05000000" + ], + [ + "9039.50", + "0.00088281" + ], + [ + "9039.99", + "0.10505932" + ], + [ + "9040.00", + "0.50000000" + ], + [ + "9040.60", + "0.02000000" + ], + [ + "9041.76", + "0.01769567" + ], + [ + "9042.15", + "0.00504674" + ], + [ + "9047.00", + "0.02000000" + ], + [ + "9050.00", + "3.32504155" + ], + [ + "9050.90", + "0.10000000" + ], + [ + "9051.41", + "0.00088023" + ], + [ + "9051.48", + "12.50000000" + ], + [ + "9052.11", + "0.00209402" + ], + [ + "9053.12", + "0.49950000" + ], + [ + "9055.00", + "0.05000000" + ], + [ + "9057.66", + "0.56912754" + ], + [ + "9060.00", + "0.22975007" + ], + [ + "9064.59", + "0.00223085" + ], + [ + "9065.00", + "0.20000000" + ], + [ + "9065.02", + "0.00087024" + ], + [ + "9066.00", + "0.05000025" + ], + [ + "9067.00", + "0.02000000" + ], + [ + "9069.77", + "0.03724926" + ], + [ + "9070.00", + "0.01000000" + ], + [ + "9074.00", + "0.15000000" + ], + [ + "9074.11", + "12.50000000" + ], + [ + "9078.63", + "0.00087025" + ], + [ + "9080.00", + "0.01594819" + ], + [ + "9081.00", + "0.05000000" + ], + [ + "9085.58", + "0.02000000" + ], + [ + "9086.41", + "0.50000000" + ], + [ + "9087.00", + "0.02000000" + ], + [ + "9088.00", + "0.25000000" + ], + [ + "9088.88", + "0.04370302" + ], + [ + "9089.00", + "1.00000000" + ], + [ + "9089.09", + "1.00000000" + ], + [ + "9090.00", + "0.10686912" + ], + [ + "9092.24", + "0.00087026" + ], + [ + "9094.11", + "0.00190730" + ], + [ + "9095.00", + "0.25116600" + ], + [ + "9096.00", + "0.11000000" + ], + [ + "9096.79", + "4.32173278" + ], + [ + "9097.77", + "0.10000000" + ], + [ + "9098.88", + "17.72962657" + ], + [ + "9098.98", + "0.01767404" + ], + [ + "9099.57", + "0.00086817" + ], + [ + "9100.00", + "21.09159134" + ], + [ + "9100.29", + "0.00500000" + ], + [ + "9101.72", + "0.00899888" + ], + [ + "9103.00", + "0.01000000" + ], + [ + "9104.28", + "0.00082343" + ], + [ + "9104.60", + "0.21000000" + ], + [ + "9105.85", + "0.00087027" + ], + [ + "9105.99", + "0.44037307" + ], + [ + "9106.50", + "0.00083380" + ], + [ + "9107.00", + "0.02000000" + ], + [ + "9108.23", + "0.01630000" + ], + [ + "9109.78", + "1.00000000" + ], + [ + "9109.93", + "0.00293816" + ], + [ + "9110.00", + "0.00100000" + ], + [ + "9110.57", + "0.00086712" + ], + [ + "9110.98", + "0.00085736" + ], + [ + "9111.00", + "0.05644500" + ], + [ + "9111.11", + "1.00000000" + ], + [ + "9112.01", + "0.00333286" + ], + [ + "9113.00", + "0.11000000" + ], + [ + "9113.55", + "0.10000000" + ], + [ + "9116.78", + "0.00086113" + ], + [ + "9119.46", + "0.00087028" + ], + [ + "9119.53", + "12.50000000" + ], + [ + "9120.00", + "0.27300000" + ], + [ + "9121.41", + "0.00085012" + ], + [ + "9122.88", + "0.50000000" + ], + [ + "9123.45", + "0.01300000" + ], + [ + "9124.15", + "0.00082384" + ], + [ + "9126.00", + "0.05594885" + ], + [ + "9127.00", + "0.02000000" + ], + [ + "9127.29", + "0.00080110" + ], + [ + "9128.55", + "0.23093226" + ], + [ + "9130.00", + "0.39788966" + ], + [ + "9131.28", + "0.00085188" + ], + [ + "9132.57", + "0.00086504" + ], + [ + "9132.85", + "0.00084126" + ], + [ + "9133.07", + "0.00087029" + ], + [ + "9134.00", + "0.03000000" + ], + [ + "9135.12", + "0.00150000" + ], + [ + "9136.00", + "0.22000000" + ], + [ + "9136.94", + "0.00298509" + ], + [ + "9137.56", + "0.00896359" + ], + [ + "9138.00", + "0.04600000" + ], + [ + "9138.97", + "0.00073640" + ], + [ + "9140.50", + "0.00087305" + ], + [ + "9142.33", + "12.50000000" + ], + [ + "9143.57", + "0.00086400" + ], + [ + "9144.00", + "0.25000000" + ], + [ + "9145.00", + "0.10430000" + ], + [ + "9146.08", + "0.00082847" + ], + [ + "9147.00", + "0.02000000" + ], + [ + "9149.69", + "0.00086178" + ], + [ + "9150.00", + "0.19097702" + ], + [ + "9154.57", + "0.00086296" + ], + [ + "9156.32", + "0.00054662" + ], + [ + "9156.39", + "0.00083882" + ], + [ + "9157.00", + "0.15000000" + ], + [ + "9158.92", + "0.00083384" + ], + [ + "9163.99", + "0.00304265" + ], + [ + "9164.19", + "0.00294059" + ], + [ + "9165.00", + "0.07800000" + ], + [ + "9165.19", + "12.50000000" + ], + [ + "9165.57", + "0.00086192" + ], + [ + "9166.58", + "0.82000000" + ], + [ + "9167.00", + "0.02000000" + ], + [ + "9168.77", + "0.00081689" + ], + [ + "9169.02", + "0.03371459" + ], + [ + "9170.00", + "0.01598497" + ], + [ + "9172.83", + "0.93300000" + ], + [ + "9173.56", + "0.00294844" + ], + [ + "9174.57", + "0.02132877" + ], + [ + "9175.66", + "0.00080770" + ], + [ + "9176.57", + "0.00086089" + ], + [ + "9177.28", + "0.00080661" + ], + [ + "9178.10", + "3.25302614" + ], + [ + "9179.40", + "0.18377440" + ], + [ + "9179.60", + "1.15200000" + ], + [ + "9180.00", + "0.39442886" + ], + [ + "9182.07", + "0.00086089" + ], + [ + "9182.69", + "0.11000000" + ], + [ + "9185.33", + "0.00083507" + ], + [ + "9186.40", + "0.75000000" + ], + [ + "9186.96", + "0.00080615" + ], + [ + "9187.00", + "0.02000000" + ], + [ + "9188.10", + "12.50000000" + ], + [ + "9189.00", + "0.11000000" + ], + [ + "9189.13", + "0.01662019" + ], + [ + "9189.72", + "0.00084738" + ], + [ + "9189.85", + "0.00083028" + ], + [ + "9189.93", + "0.11100000" + ], + [ + "9190.00", + "0.22500000" + ], + [ + "9190.12", + "0.00079236" + ], + [ + "9190.25", + "0.00084877" + ], + [ + "9191.00", + "0.00086825" + ], + [ + "9192.00", + "0.13000000" + ], + [ + "9193.07", + "0.00086089" + ], + [ + "9193.59", + "0.00400000" + ], + [ + "9194.31", + "0.00085459" + ], + [ + "9194.88", + "2.39704035" + ], + [ + "9197.00", + "0.10000000" + ], + [ + "9197.70", + "0.00083180" + ], + [ + "9198.00", + "0.03900000" + ], + [ + "9199.00", + "0.40800000" + ], + [ + "9199.88", + "13.29721993" + ], + [ + "9200.00", + "12.12923423" + ], + [ + "9200.59", + "0.00110800" + ], + [ + "9202.02", + "0.30420000" + ], + [ + "9203.13", + "0.00084542" + ], + [ + "9204.04", + "0.51800000" + ], + [ + "9204.07", + "0.00086089" + ], + [ + "9206.02", + "0.17800000" + ], + [ + "9206.83", + "0.24860000" + ], + [ + "9207.00", + "0.02000000" + ], + [ + "9208.04", + "0.00083723" + ], + [ + "9208.82", + "0.07713813" + ], + [ + "9209.10", + "0.00950000" + ], + [ + "9210.00", + "0.26100000" + ], + [ + "9210.38", + "0.00083196" + ], + [ + "9211.07", + "12.50000000" + ], + [ + "9212.00", + "0.37000000" + ], + [ + "9213.60", + "0.44800000" + ], + [ + "9214.00", + "0.71045500" + ], + [ + "9214.15", + "0.00085879" + ], + [ + "9215.00", + "0.01000000" + ], + [ + "9215.07", + "0.00086089" + ], + [ + "9217.85", + "0.00084446" + ], + [ + "9220.00", + "0.04000000" + ], + [ + "9220.57", + "0.00085678" + ], + [ + "9222.74", + "1.78329713" + ], + [ + "9223.48", + "0.00305402" + ], + [ + "9224.39", + "0.00079835" + ], + [ + "9225.20", + "0.00229496" + ], + [ + "9227.00", + "0.02000000" + ], + [ + "9228.00", + "0.13000000" + ], + [ + "9230.00", + "0.01000000" + ], + [ + "9230.64", + "0.00082608" + ], + [ + "9231.18", + "0.00078402" + ], + [ + "9231.57", + "0.00085576" + ], + [ + "9233.00", + "0.14747800" + ], + [ + "9235.00", + "0.04200000" + ], + [ + "9236.51", + "0.00362637" + ], + [ + "9237.50", + "0.00084696" + ], + [ + "9238.85", + "0.00079411" + ], + [ + "9239.00", + "1.00000000" + ], + [ + "9239.12", + "0.00081412" + ], + [ + "9239.65", + "0.00085247" + ], + [ + "9240.00", + "0.00142666" + ], + [ + "9240.21", + "0.00081000" + ], + [ + "9241.50", + "0.00086351" + ], + [ + "9242.22", + "0.01580000" + ], + [ + "9242.57", + "0.00085576" + ], + [ + "9242.58", + "0.14760000" + ], + [ + "9244.00", + "0.23000000" + ], + [ + "9244.89", + "0.00077913" + ], + [ + "9245.12", + "0.00150000" + ], + [ + "9247.00", + "0.13000000" + ], + [ + "9248.22", + "0.00079170" + ], + [ + "9250.00", + "2.86398554" + ], + [ + "9251.06", + "0.00158063" + ], + [ + "9251.35", + "0.00084652" + ], + [ + "9252.32", + "0.00441291" + ], + [ + "9253.57", + "0.00085576" + ], + [ + "9254.61", + "0.00078749" + ], + [ + "9255.00", + "0.11000000" + ], + [ + "9256.56", + "0.00560000" + ], + [ + "9258.00", + "0.04400000" + ], + [ + "9259.74", + "0.00084561" + ], + [ + "9262.00", + "1.88406126" + ], + [ + "9262.82", + "0.00081349" + ], + [ + "9263.80", + "0.00085117" + ], + [ + "9264.13", + "0.00081989" + ], + [ + "9264.16", + "0.00080642" + ], + [ + "9264.74", + "0.00085919" + ], + [ + "9265.17", + "0.00085576" + ], + [ + "9265.20", + "0.00083938" + ], + [ + "9266.85", + "0.00084750" + ], + [ + "9267.00", + "0.02000000" + ], + [ + "9268.78", + "0.00303475" + ], + [ + "9270.00", + "0.04000000" + ], + [ + "9272.00", + "0.07000000" + ], + [ + "9275.00", + "0.12315965" + ], + [ + "9276.43", + "0.00546636" + ], + [ + "9276.77", + "0.00085576" + ], + [ + "9277.00", + "1.00000000" + ], + [ + "9278.00", + "0.29400000" + ], + [ + "9278.92", + "0.00432709" + ], + [ + "9281.60", + "0.22200000" + ], + [ + "9282.04", + "0.00085128" + ], + [ + "9283.00", + "0.11000000" + ], + [ + "9287.00", + "0.02000000" + ], + [ + "9287.14", + "0.00085196" + ], + [ + "9287.25", + "0.00442855" + ], + [ + "9288.00", + "0.44000000" + ], + [ + "9288.37", + "0.00085576" + ], + [ + "9288.57", + "0.00084722" + ], + [ + "9289.00", + "0.12000000" + ], + [ + "9289.24", + "0.00079526" + ], + [ + "9289.71", + "0.00083631" + ], + [ + "9290.00", + "0.10000000" + ], + [ + "9290.85", + "0.00078694" + ], + [ + "9291.00", + "0.11000000" + ], + [ + "9292.00", + "0.22085881" + ], + [ + "9293.00", + "0.50000000" + ], + [ + "9294.00", + "0.11000000" + ], + [ + "9295.00", + "0.00182200" + ], + [ + "9296.75", + "0.00335928" + ], + [ + "9296.80", + "0.00077980" + ], + [ + "9296.98", + "0.00085576" + ], + [ + "9297.97", + "0.20000000" + ], + [ + "9299.91", + "0.00084522" + ], + [ + "9299.97", + "0.00085053" + ], + [ + "9300.00", + "3.12890286" + ], + [ + "9301.11", + "0.00086796" + ], + [ + "9303.00", + "0.11150000" + ], + [ + "9303.06", + "0.00084013" + ], + [ + "9303.32", + "0.10000000" + ], + [ + "9304.20", + "0.00079380" + ], + [ + "9304.29", + "0.00081831" + ], + [ + "9306.00", + "15.64320000" + ], + [ + "9307.00", + "0.02000000" + ], + [ + "9307.42", + "0.00311351" + ], + [ + "9309.52", + "0.00086829" + ], + [ + "9309.76", + "0.00083975" + ], + [ + "9309.84", + "0.00084762" + ], + [ + "9309.86", + "0.00081878" + ], + [ + "9310.00", + "0.00100000" + ], + [ + "9311.33", + "0.00079033" + ], + [ + "9311.57", + "0.00084841" + ], + [ + "9312.00", + "0.11000000" + ], + [ + "9314.21", + "0.00085344" + ], + [ + "9314.91", + "0.00079152" + ], + [ + "9318.00", + "0.01000000" + ], + [ + "9318.75", + "0.00107100" + ], + [ + "9319.58", + "0.86000000" + ], + [ + "9320.00", + "0.23650000" + ], + [ + "9320.70", + "0.00083340" + ], + [ + "9321.00", + "0.02811243" + ], + [ + "9321.62", + "0.00078851" + ], + [ + "9323.15", + "0.00354190" + ], + [ + "9323.17", + "0.00084735" + ], + [ + "9323.57", + "0.00053682" + ], + [ + "9323.60", + "0.00338473" + ], + [ + "9324.00", + "1.00000000" + ], + [ + "9325.83", + "0.80700000" + ], + [ + "9325.95", + "0.00083887" + ], + [ + "9327.00", + "0.02000000" + ], + [ + "9330.00", + "0.00100000" + ], + [ + "9332.00", + "1.38016879" + ], + [ + "9332.60", + "1.20600000" + ], + [ + "9334.97", + "0.00084735" + ], + [ + "9337.57", + "0.00078844" + ], + [ + "9339.33", + "0.00300000" + ], + [ + "9339.46", + "0.00080904" + ], + [ + "9341.04", + "0.00081846" + ], + [ + "9341.41", + "0.00078477" + ], + [ + "9342.23", + "0.00080740" + ], + [ + "9342.50", + "0.00085417" + ], + [ + "9342.94", + "0.01000000" + ], + [ + "9343.35", + "0.01000000" + ], + [ + "9344.07", + "0.00085303" + ], + [ + "9345.00", + "0.71000000" + ], + [ + "9346.20", + "0.21600000" + ], + [ + "9346.35", + "0.00078865" + ], + [ + "9346.77", + "0.00084735" + ], + [ + "9347.00", + "0.02000000" + ], + [ + "9347.51", + "0.00077969" + ], + [ + "9349.00", + "0.50000000" + ], + [ + "9349.30", + "0.00077330" + ], + [ + "9349.65", + "0.12500000" + ], + [ + "9350.00", + "1.49172942" + ], + [ + "9353.48", + "0.00082015" + ], + [ + "9353.59", + "0.00077258" + ], + [ + "9353.95", + "0.10000000" + ], + [ + "9355.54", + "0.00084158" + ], + [ + "9358.57", + "0.00084735" + ], + [ + "9358.72", + "0.00081838" + ], + [ + "9359.63", + "0.00083052" + ], + [ + "9360.00", + "0.58000000" + ], + [ + "9360.10", + "0.27614952" + ], + [ + "9360.58", + "0.00077376" + ], + [ + "9362.93", + "0.11700000" + ], + [ + "9364.33", + "0.00084308" + ], + [ + "9365.11", + "0.00150000" + ], + [ + "9367.00", + "0.02000000" + ], + [ + "9369.00", + "0.43478753" + ], + [ + "9370.00", + "5.00000000" + ], + [ + "9370.37", + "0.00084735" + ], + [ + "9375.02", + "0.32040000" + ], + [ + "9375.55", + "0.02990192" + ], + [ + "9377.04", + "0.54600000" + ], + [ + "9377.40", + "0.00084795" + ], + [ + "9378.00", + "0.10000000" + ], + [ + "9379.02", + "0.18600000" + ], + [ + "9379.65", + "0.02091343" + ], + [ + "9379.83", + "0.25820000" + ], + [ + "9380.00", + "7.22695908" + ], + [ + "9380.23", + "0.00083613" + ], + [ + "9380.37", + "0.00082638" + ], + [ + "9381.71", + "0.00080351" + ], + [ + "9382.17", + "0.00084735" + ], + [ + "9382.39", + "0.00084865" + ], + [ + "9383.64", + "0.25000000" + ], + [ + "9383.89", + "0.00082805" + ], + [ + "9384.00", + "0.01000000" + ], + [ + "9384.87", + "0.00078948" + ], + [ + "9385.00", + "0.39000000" + ], + [ + "9386.15", + "0.00084594" + ], + [ + "9386.60", + "0.47200000" + ], + [ + "9387.00", + "49.02000000" + ], + [ + "9388.42", + "0.00083360" + ], + [ + "9389.00", + "1.11010722" + ], + [ + "9390.00", + "0.30000000" + ], + [ + "9391.85", + "0.00077700" + ], + [ + "9393.00", + "0.26000000" + ], + [ + "9394.17", + "0.00084735" + ], + [ + "9394.59", + "0.00080925" + ], + [ + "9394.77", + "0.00078694" + ], + [ + "9396.68", + "0.00077512" + ], + [ + "9396.98", + "0.05320900" + ], + [ + "9397.13", + "0.00084840" + ], + [ + "9397.67", + "0.00085731" + ], + [ + "9398.00", + "0.90000000" + ], + [ + "9398.24", + "0.50000000" + ], + [ + "9398.55", + "0.00085087" + ], + [ + "9399.00", + "0.27901750" + ], + [ + "9399.58", + "0.15480000" + ], + [ + "9400.00", + "5.50574921" + ], + [ + "9400.67", + "0.00077520" + ], + [ + "9402.00", + "0.00077276" + ], + [ + "9403.00", + "0.05500000" + ], + [ + "9403.31", + "0.00083332" + ], + [ + "9403.75", + "0.00078912" + ], + [ + "9404.51", + "0.01530000" + ], + [ + "9405.00", + "0.85000000" + ], + [ + "9405.23", + "0.00082153" + ], + [ + "9405.56", + "0.00078764" + ], + [ + "9406.17", + "0.00084095" + ], + [ + "9407.00", + "0.02000000" + ], + [ + "9409.00", + "0.26000000" + ], + [ + "9409.91", + "0.00082267" + ], + [ + "9410.00", + "0.08014040" + ], + [ + "9410.24", + "0.00084550" + ], + [ + "9411.00", + "0.05400000" + ], + [ + "9411.35", + "0.00077008" + ], + [ + "9412.74", + "0.00078973" + ], + [ + "9413.12", + "0.08147464" + ], + [ + "9413.64", + "0.00077840" + ], + [ + "9416.57", + "0.62000000" + ], + [ + "9416.67", + "0.00586239" + ], + [ + "9416.81", + "0.00078499" + ], + [ + "9417.41", + "0.00078380" + ], + [ + "9418.17", + "0.00084095" + ], + [ + "9418.25", + "0.00077647" + ], + [ + "9419.00", + "0.00430000" + ], + [ + "9419.75", + "0.01621328" + ], + [ + "9419.91", + "0.00078935" + ], + [ + "9420.00", + "0.57160000" + ], + [ + "9421.00", + "0.15000000" + ], + [ + "9421.10", + "0.00077619" + ], + [ + "9421.47", + "0.00077934" + ], + [ + "9422.00", + "0.13000000" + ], + [ + "9423.00", + "0.55916599" + ], + [ + "9424.85", + "0.00077662" + ], + [ + "9426.00", + "0.01000000" + ], + [ + "9426.05", + "0.00085371" + ], + [ + "9426.18", + "0.00079089" + ], + [ + "9427.00", + "0.02000000" + ], + [ + "9429.49", + "0.00078148" + ], + [ + "9430.00", + "0.10000000" + ], + [ + "9430.17", + "0.00083880" + ], + [ + "9430.38", + "0.09304571" + ], + [ + "9431.18", + "0.00078860" + ], + [ + "9431.42", + "0.00077166" + ], + [ + "9431.49", + "0.00085452" + ], + [ + "9432.00", + "0.20600000" + ], + [ + "9432.35", + "5.19514096" + ], + [ + "9432.77", + "0.00086178" + ], + [ + "9433.00", + "0.25000000" + ], + [ + "9434.93", + "0.00078060" + ], + [ + "9435.00", + "0.15000000" + ], + [ + "9435.05", + "0.00085585" + ], + [ + "9435.21", + "0.00441273" + ], + [ + "9435.25", + "0.00078919" + ], + [ + "9438.04", + "0.00083420" + ], + [ + "9438.19", + "0.00080378" + ], + [ + "9438.70", + "0.00078285" + ], + [ + "9438.89", + "0.00462272" + ], + [ + "9438.96", + "0.00077835" + ], + [ + "9439.00", + "1.00000000" + ], + [ + "9439.06", + "0.00077289" + ], + [ + "9441.52", + "0.01840399" + ], + [ + "9442.17", + "0.00084095" + ], + [ + "9442.81", + "0.00077665" + ], + [ + "9447.00", + "0.02000000" + ], + [ + "9448.25", + "0.20000000" + ], + [ + "9450.00", + "5.37968761" + ], + [ + "9450.18", + "0.00078589" + ], + [ + "9450.65", + "0.00077820" + ], + [ + "9450.83", + "0.00082845" + ], + [ + "9451.07", + "0.01500000" + ], + [ + "9451.12", + "0.00077284" + ], + [ + "9451.87", + "0.00081928" + ], + [ + "9452.18", + "0.00077450" + ], + [ + "9453.89", + "0.00303574" + ], + [ + "9453.97", + "0.00078773" + ], + [ + "9454.17", + "0.00084095" + ], + [ + "9455.00", + "1.03500000" + ], + [ + "9455.09", + "0.00077339" + ], + [ + "9455.65", + "0.00079165" + ], + [ + "9456.91", + "0.02323219" + ], + [ + "9457.10", + "0.00077751" + ], + [ + "9458.40", + "0.59442210" + ], + [ + "9458.60", + "0.23400000" + ], + [ + "9459.00", + "0.90400000" + ], + [ + "9459.62", + "0.00077161" + ], + [ + "9459.81", + "0.02158398" + ], + [ + "9460.79", + "0.00077666" + ], + [ + "9461.98", + "0.00053990" + ], + [ + "9463.59", + "0.00077859" + ], + [ + "9464.45", + "0.00080763" + ], + [ + "9464.99", + "0.00076225" + ], + [ + "9465.00", + "3.74040197" + ], + [ + "9465.01", + "0.00090324" + ], + [ + "9465.02", + "0.00075986" + ], + [ + "9465.93", + "0.00078482" + ], + [ + "9466.78", + "0.00077728" + ], + [ + "9466.85", + "0.00082026" + ], + [ + "9466.94", + "0.00078274" + ], + [ + "9467.00", + "0.02000000" + ], + [ + "9467.95", + "0.00079423" + ], + [ + "9468.57", + "0.01000000" + ], + [ + "9468.62", + "0.00081272" + ], + [ + "9468.99", + "0.00078175" + ], + [ + "9469.29", + "0.00078558" + ], + [ + "9470.46", + "0.00076776" + ], + [ + "9470.51", + "0.00075773" + ], + [ + "9471.20", + "0.00076165" + ], + [ + "9471.60", + "0.00078368" + ], + [ + "9472.04", + "0.00076703" + ], + [ + "9472.47", + "0.00078232" + ], + [ + "9472.58", + "0.90000000" + ], + [ + "9473.28", + "0.00082742" + ], + [ + "9474.11", + "0.00088990" + ], + [ + "9474.12", + "0.00077402" + ], + [ + "9474.37", + "0.00077121" + ], + [ + "9475.00", + "0.04279457" + ], + [ + "9475.42", + "0.00076756" + ], + [ + "9475.82", + "0.00078462" + ], + [ + "9477.12", + "0.00077076" + ], + [ + "9477.14", + "0.00076130" + ], + [ + "9477.27", + "0.00078282" + ], + [ + "9477.48", + "0.00076726" + ], + [ + "9477.80", + "0.00078675" + ], + [ + "9478.00", + "2.00000000" + ], + [ + "9478.01", + "0.00077073" + ], + [ + "9478.03", + "0.00075840" + ], + [ + "9478.05", + "0.00077042" + ], + [ + "9478.85", + "0.00083343" + ], + [ + "9480.00", + "0.16000000" + ], + [ + "9481.11", + "0.00077555" + ], + [ + "9481.12", + "0.00077606" + ], + [ + "9481.44", + "0.00080869" + ], + [ + "9481.96", + "0.00331854" + ], + [ + "9482.00", + "1.00000000" + ], + [ + "9482.29", + "0.00084803" + ], + [ + "9482.54", + "0.00078631" + ], + [ + "9482.57", + "0.00076369" + ], + [ + "9483.68", + "0.00078328" + ], + [ + "9484.17", + "0.00077078" + ], + [ + "9484.33", + "0.00077300" + ], + [ + "9484.92", + "0.00084835" + ], + [ + "9485.37", + "0.00076496" + ], + [ + "9485.60", + "0.36000000" + ], + [ + "9486.00", + "0.07850000" + ], + [ + "9486.89", + "0.00076123" + ], + [ + "9487.00", + "0.03000000" + ], + [ + "9487.51", + "0.00079372" + ], + [ + "9488.00", + "1.15000000" + ], + [ + "9489.00", + "0.17000000" + ], + [ + "9489.75", + "0.00079678" + ], + [ + "9490.00", + "0.03898682" + ], + [ + "9490.34", + "0.00079047" + ], + [ + "9490.40", + "0.00083254" + ], + [ + "9490.85", + "0.00077394" + ], + [ + "9491.07", + "0.00078735" + ], + [ + "9491.52", + "0.00081027" + ], + [ + "9492.68", + "0.00086012" + ], + [ + "9493.57", + "0.00077564" + ], + [ + "9494.24", + "0.00101900" + ], + [ + "9494.79", + "0.00076898" + ], + [ + "9495.00", + "0.21696794" + ], + [ + "9495.11", + "0.00125200" + ], + [ + "9496.00", + "0.00653186" + ], + [ + "9496.49", + "0.00077602" + ], + [ + "9496.93", + "0.00076580" + ], + [ + "9498.00", + "1.00000000" + ], + [ + "9498.66", + "0.00085442" + ], + [ + "9499.00", + "2.49191613" + ], + [ + "9499.34", + "0.00079853" + ], + [ + "9499.48", + "0.00075900" + ], + [ + "9499.65", + "0.01000000" + ], + [ + "9500.00", + "78.11653714" + ], + [ + "9501.10", + "0.11484668" + ], + [ + "9501.11", + "0.00078584" + ], + [ + "9501.50", + "0.00078714" + ], + [ + "9502.85", + "0.00083343" + ], + [ + "9503.57", + "0.00085470" + ], + [ + "9503.70", + "0.00077950" + ], + [ + "9504.27", + "0.00085051" + ], + [ + "9504.41", + "0.00290657" + ], + [ + "9504.84", + "1.11319408" + ], + [ + "9506.48", + "0.00076532" + ], + [ + "9507.00", + "0.02000000" + ], + [ + "9507.42", + "0.00075469" + ], + [ + "9507.62", + "0.00346956" + ], + [ + "9508.08", + "0.00079167" + ], + [ + "9508.49", + "0.00079066" + ], + [ + "9508.50", + "0.00077417" + ], + [ + "9509.21", + "0.00076845" + ], + [ + "9509.64", + "0.00077734" + ], + [ + "9510.00", + "0.20095000" + ], + [ + "9510.99", + "0.00077557" + ], + [ + "9512.11", + "0.00078649" + ], + [ + "9512.12", + "0.00076601" + ], + [ + "9512.75", + "1.00000000" + ], + [ + "9513.39", + "0.00077454" + ], + [ + "9514.01", + "0.00076953" + ], + [ + "9514.41", + "0.00077510" + ], + [ + "9514.51", + "0.00075489" + ], + [ + "9516.18", + "0.00076105" + ], + [ + "9516.20", + "0.00081028" + ], + [ + "9517.70", + "0.00080800" + ], + [ + "9517.86", + "0.00449908" + ], + [ + "9518.35", + "0.00079041" + ], + [ + "9518.74", + "0.00076686" + ], + [ + "9519.50", + "0.00076957" + ], + [ + "9520.09", + "0.18689932" + ], + [ + "9520.21", + "0.00077675" + ], + [ + "9520.59", + "0.00080415" + ], + [ + "9521.00", + "0.21000000" + ], + [ + "9521.44", + "0.01279743" + ], + [ + "9521.69", + "0.00370648" + ], + [ + "9522.13", + "0.00077256" + ], + [ + "9522.17", + "0.00081592" + ], + [ + "9523.00", + "2.13039905" + ], + [ + "9524.12", + "0.00078229" + ], + [ + "9524.63", + "0.00075617" + ], + [ + "9525.00", + "0.02572000" + ], + [ + "9527.00", + "0.02000000" + ], + [ + "9527.07", + "0.00078262" + ], + [ + "9527.12", + "0.00085676" + ], + [ + "9527.24", + "0.05000000" + ], + [ + "9528.06", + "0.00076838" + ], + [ + "9528.71", + "0.00084510" + ], + [ + "9528.79", + "0.00078455" + ], + [ + "9528.96", + "0.00083470" + ], + [ + "9529.75", + "0.00075497" + ], + [ + "9530.00", + "0.43500000" + ], + [ + "9530.79", + "0.00076476" + ], + [ + "9531.40", + "0.00079458" + ], + [ + "9532.00", + "0.13000000" + ], + [ + "9532.85", + "0.00082871" + ], + [ + "9533.00", + "0.01000000" + ], + [ + "9534.25", + "0.00077541" + ], + [ + "9534.70", + "0.00078567" + ], + [ + "9535.00", + "0.05200000" + ], + [ + "9535.13", + "0.00077952" + ], + [ + "9535.27", + "0.00075753" + ], + [ + "9535.93", + "0.12300000" + ], + [ + "9536.71", + "0.00075975" + ], + [ + "9537.52", + "0.00075888" + ], + [ + "9538.19", + "0.00075795" + ], + [ + "9538.22", + "0.00076318" + ], + [ + "9538.43", + "0.00076624" + ], + [ + "9539.64", + "0.00077782" + ], + [ + "9540.00", + "1.00087900" + ], + [ + "9540.10", + "0.00086244" + ], + [ + "9540.14", + "0.00077076" + ], + [ + "9540.20", + "0.00079077" + ], + [ + "9540.30", + "0.00075873" + ], + [ + "9540.74", + "0.00082515" + ], + [ + "9540.87", + "0.00081029" + ], + [ + "9541.55", + "0.00079352" + ], + [ + "9541.89", + "0.00076838" + ], + [ + "9542.00", + "1.00000000" + ], + [ + "9542.15", + "0.00085041" + ], + [ + "9542.22", + "0.00156493" + ], + [ + "9543.82", + "0.00082870" + ], + [ + "9545.60", + "0.00085697" + ], + [ + "9545.61", + "0.25000000" + ], + [ + "9545.83", + "0.05057189" + ], + [ + "9545.94", + "0.01000000" + ], + [ + "9546.53", + "0.00077595" + ], + [ + "9547.00", + "0.02000000" + ], + [ + "9547.36", + "0.00075771" + ], + [ + "9547.37", + "0.00077870" + ], + [ + "9547.40", + "0.00078565" + ], + [ + "9547.76", + "0.00076981" + ], + [ + "9547.80", + "0.00085315" + ], + [ + "9548.02", + "0.33660000" + ], + [ + "9548.22", + "0.00076361" + ], + [ + "9548.92", + "0.00075781" + ], + [ + "9549.00", + "0.03000000" + ], + [ + "9549.85", + "0.00076538" + ], + [ + "9549.96", + "0.00078959" + ], + [ + "9550.00", + "0.97082700" + ], + [ + "9550.04", + "0.57400000" + ], + [ + "9550.74", + "0.00085280" + ], + [ + "9551.60", + "0.03199316" + ], + [ + "9552.02", + "0.19400000" + ], + [ + "9552.15", + "0.00079229" + ], + [ + "9552.83", + "0.26780000" + ], + [ + "9553.72", + "0.00078206" + ], + [ + "9554.99", + "0.00078239" + ], + [ + "9555.00", + "0.34200000" + ], + [ + "9555.46", + "0.00082978" + ], + [ + "9556.52", + "0.00085101" + ], + [ + "9556.58", + "0.16200000" + ], + [ + "9556.62", + "0.00083842" + ], + [ + "9558.00", + "0.41000000" + ], + [ + "9558.87", + "0.00077521" + ], + [ + "9558.97", + "0.00078362" + ], + [ + "9559.45", + "0.00078043" + ], + [ + "9559.60", + "0.49600000" + ], + [ + "9560.00", + "0.23000000" + ], + [ + "9560.08", + "0.00079402" + ], + [ + "9560.52", + "0.00075689" + ], + [ + "9560.88", + "0.00078432" + ], + [ + "9560.94", + "0.00080741" + ], + [ + "9561.72", + "0.00076606" + ], + [ + "9562.65", + "0.00078693" + ], + [ + "9562.67", + "0.00076173" + ], + [ + "9562.87", + "0.00076556" + ], + [ + "9563.73", + "0.00082013" + ], + [ + "9563.91", + "0.00075364" + ], + [ + "9565.54", + "0.00081030" + ], + [ + "9565.98", + "0.00078668" + ], + [ + "9566.00", + "0.13000000" + ], + [ + "9566.29", + "0.00078387" + ], + [ + "9566.37", + "0.00083050" + ], + [ + "9566.46", + "0.00075474" + ], + [ + "9566.76", + "0.00077433" + ], + [ + "9566.92", + "0.00077251" + ], + [ + "9567.00", + "0.02000000" + ], + [ + "9567.89", + "0.02500000" + ], + [ + "9567.98", + "0.00079617" + ], + [ + "9568.00", + "0.13000000" + ], + [ + "9568.33", + "0.00075833" + ], + [ + "9569.00", + "0.26000000" + ], + [ + "9569.28", + "0.00077146" + ], + [ + "9569.38", + "0.00085006" + ], + [ + "9569.56", + "0.00727424" + ], + [ + "9569.81", + "0.00076707" + ], + [ + "9570.00", + "0.03307135" + ], + [ + "9570.77", + "0.00077429" + ], + [ + "9571.00", + "0.18122810" + ], + [ + "9571.13", + "0.00076966" + ], + [ + "9571.24", + "0.00585820" + ], + [ + "9571.31", + "0.00077010" + ], + [ + "9572.00", + "0.07660000" + ], + [ + "9572.10", + "0.00081493" + ], + [ + "9573.00", + "0.03000000" + ], + [ + "9574.53", + "0.00077339" + ], + [ + "9574.54", + "0.00083346" + ], + [ + "9575.43", + "0.00077227" + ], + [ + "9576.62", + "0.00076649" + ], + [ + "9578.24", + "0.00086032" + ], + [ + "9580.49", + "0.00078507" + ], + [ + "9581.05", + "0.00077014" + ], + [ + "9581.35", + "0.00083446" + ], + [ + "9582.08", + "0.00076403" + ], + [ + "9582.56", + "0.00075671" + ], + [ + "9583.11", + "22.79453815" + ], + [ + "9584.04", + "0.00081556" + ], + [ + "9586.53", + "0.00086496" + ], + [ + "9587.00", + "0.02000000" + ], + [ + "9587.94", + "0.00078444" + ], + [ + "9588.63", + "0.01490000" + ], + [ + "9589.00", + "0.13000000" + ], + [ + "9589.01", + "0.00075853" + ], + [ + "9589.13", + "0.00076200" + ], + [ + "9590.00", + "0.48176391" + ], + [ + "9590.03", + "0.00075573" + ], + [ + "9590.22", + "0.00081031" + ], + [ + "9590.86", + "0.00076643" + ], + [ + "9591.82", + "0.00077821" + ], + [ + "9593.96", + "0.00086330" + ], + [ + "9594.78", + "0.00078711" + ], + [ + "9594.95", + "0.00079071" + ], + [ + "9595.46", + "0.00077075" + ], + [ + "9596.56", + "0.00076491" + ], + [ + "9597.00", + "0.15000000" + ], + [ + "9597.50", + "0.00077989" + ], + [ + "9598.07", + "0.00077676" + ], + [ + "9598.34", + "0.00085432" + ], + [ + "9598.50", + "0.00937631" + ], + [ + "9599.00", + "0.50243785" + ], + [ + "9600.00", + "8.15207453" + ], + [ + "9600.25", + "0.00077055" + ], + [ + "9601.05", + "0.00084723" + ], + [ + "9601.25", + "0.00078007" + ], + [ + "9601.76", + "0.00079165" + ], + [ + "9602.00", + "0.19001356" + ], + [ + "9602.22", + "0.00082273" + ], + [ + "9603.34", + "0.00076058" + ], + [ + "9603.41", + "0.00085429" + ], + [ + "9603.66", + "0.00077580" + ], + [ + "9605.00", + "0.11500000" + ], + [ + "9605.37", + "0.00076342" + ], + [ + "9605.45", + "0.00080952" + ], + [ + "9606.49", + "0.00077035" + ], + [ + "9606.52", + "0.00076223" + ], + [ + "9607.00", + "0.02000000" + ], + [ + "9607.73", + "0.00077516" + ], + [ + "9608.28", + "0.00078164" + ], + [ + "9608.35", + "0.00115765" + ], + [ + "9609.23", + "0.00085146" + ], + [ + "9609.46", + "0.00076625" + ], + [ + "9610.00", + "1.00095000" + ], + [ + "9610.19", + "0.00079066" + ], + [ + "9612.00", + "0.00078750" + ], + [ + "9612.26", + "0.00079190" + ], + [ + "9612.51", + "0.00077560" + ], + [ + "9614.13", + "0.00076574" + ], + [ + "9614.22", + "0.00082170" + ], + [ + "9614.36", + "0.00085734" + ], + [ + "9614.89", + "0.00075367" + ], + [ + "9614.95", + "0.00078075" + ], + [ + "9615.59", + "0.00076517" + ], + [ + "9616.75", + "0.00084023" + ], + [ + "9617.09", + "0.00076443" + ], + [ + "9618.52", + "0.00077314" + ], + [ + "9618.76", + "0.00083602" + ], + [ + "9620.00", + "0.16241000" + ], + [ + "9621.28", + "0.00077186" + ], + [ + "9621.31", + "0.00213765" + ], + [ + "9621.87", + "0.00077126" + ], + [ + "9621.94", + "0.00076304" + ], + [ + "9622.66", + "0.00084800" + ], + [ + "9622.68", + "0.00078919" + ], + [ + "9623.65", + "0.00079461" + ], + [ + "9623.68", + "0.00076173" + ], + [ + "9624.70", + "0.00078925" + ], + [ + "9625.00", + "1.00000000" + ], + [ + "9626.22", + "0.00082068" + ], + [ + "9626.60", + "0.00077810" + ], + [ + "9627.00", + "0.02000000" + ], + [ + "9627.17", + "0.00078837" + ], + [ + "9627.65", + "0.00076614" + ], + [ + "9628.00", + "0.08000000" + ], + [ + "9628.36", + "0.00077032" + ], + [ + "9630.00", + "13.00000000" + ], + [ + "9630.78", + "0.00078595" + ], + [ + "9630.94", + "0.00083141" + ], + [ + "9631.80", + "0.00078250" + ], + [ + "9631.89", + "0.00306933" + ], + [ + "9632.00", + "0.02000000" + ], + [ + "9632.19", + "0.00076696" + ], + [ + "9632.42", + "0.00078252" + ], + [ + "9633.13", + "0.00079487" + ], + [ + "9633.80", + "0.00213423" + ], + [ + "9633.83", + "0.00081726" + ], + [ + "9635.00", + "0.00075433" + ], + [ + "9635.19", + "0.00074830" + ], + [ + "9635.37", + "0.00085165" + ], + [ + "9635.41", + "0.00076417" + ], + [ + "9635.45", + "0.00074940" + ], + [ + "9635.60", + "0.24600000" + ], + [ + "9636.06", + "0.00075415" + ], + [ + "9636.46", + "0.00077196" + ], + [ + "9636.48", + "0.00085280" + ], + [ + "9636.88", + "0.00076403" + ], + [ + "9636.93", + "0.00077631" + ], + [ + "9637.42", + "0.00076107" + ], + [ + "9638.60", + "0.37400000" + ], + [ + "9639.00", + "2.86381953" + ], + [ + "9639.23", + "0.00075231" + ], + [ + "9639.50", + "0.00078307" + ], + [ + "9639.57", + "0.00080033" + ], + [ + "9639.80", + "0.00075088" + ], + [ + "9640.00", + "1.05817769" + ], + [ + "9640.68", + "0.00076966" + ], + [ + "9641.00", + "0.01000000" + ], + [ + "9642.00", + "0.15000000" + ], + [ + "9642.64", + "0.00084042" + ], + [ + "9643.46", + "0.00075113" + ], + [ + "9643.64", + "0.00079137" + ], + [ + "9644.16", + "0.00075465" + ], + [ + "9644.22", + "0.00081914" + ], + [ + "9644.79", + "0.05000000" + ], + [ + "9645.00", + "0.02000000" + ], + [ + "9647.00", + "0.23054220" + ], + [ + "9647.81", + "0.00079267" + ], + [ + "9648.03", + "0.00077030" + ], + [ + "9648.70", + "0.00075168" + ], + [ + "9649.50", + "0.00077978" + ], + [ + "9649.65", + "0.00075626" + ], + [ + "9650.89", + "0.00077582" + ], + [ + "9650.95", + "0.00160522" + ], + [ + "9651.09", + "0.00077777" + ], + [ + "9651.31", + "0.01336305" + ], + [ + "9651.43", + "0.00077567" + ], + [ + "9652.29", + "0.00074368" + ], + [ + "9652.39", + "0.00075509" + ], + [ + "9653.27", + "0.00079430" + ], + [ + "9653.42", + "0.01369732" + ], + [ + "9653.53", + "0.00510907" + ], + [ + "9654.07", + "0.00076116" + ], + [ + "9654.77", + "0.00296800" + ], + [ + "9655.17", + "0.00077053" + ], + [ + "9655.61", + "0.00076365" + ], + [ + "9655.91", + "0.01581674" + ], + [ + "9656.05", + "0.00075217" + ], + [ + "9656.22", + "0.00081813" + ], + [ + "9656.71", + "0.00079380" + ], + [ + "9656.99", + "0.06957592" + ], + [ + "9659.97", + "1.00000000" + ], + [ + "9660.22", + "0.00076237" + ], + [ + "9660.24", + "0.00074987" + ], + [ + "9660.41", + "0.00075268" + ], + [ + "9660.93", + "0.00076757" + ], + [ + "9662.00", + "0.10000000" + ], + [ + "9662.60", + "0.00077734" + ], + [ + "9663.20", + "0.00079532" + ], + [ + "9664.24", + "0.00080034" + ], + [ + "9664.37", + "0.00075170" + ], + [ + "9664.95", + "0.00075346" + ], + [ + "9665.57", + "0.00076956" + ], + [ + "9666.00", + "0.30000000" + ], + [ + "9666.65", + "0.00075039" + ], + [ + "9666.71", + "0.00075471" + ], + [ + "9667.00", + "0.02000000" + ], + [ + "9668.04", + "0.50000000" + ], + [ + "9668.11", + "0.00150000" + ], + [ + "9669.00", + "0.00075019" + ], + [ + "9669.29", + "0.00078276" + ], + [ + "9669.31", + "0.00081973" + ], + [ + "9669.56", + "0.00077085" + ], + [ + "9669.57", + "0.00075606" + ], + [ + "9669.72", + "0.00074909" + ], + [ + "9669.92", + "0.00076010" + ], + [ + "9670.09", + "0.00076022" + ], + [ + "9671.00", + "0.10000000" + ], + [ + "9671.20", + "0.00075170" + ], + [ + "9671.83", + "0.00075840" + ], + [ + "9672.25", + "0.00074416" + ], + [ + "9672.95", + "0.00076453" + ], + [ + "9673.01", + "0.00074303" + ], + [ + "9674.00", + "1.23000000" + ], + [ + "9676.24", + "0.00081643" + ], + [ + "9676.63", + "0.00074611" + ], + [ + "9677.00", + "0.26156509" + ], + [ + "9678.11", + "0.00074312" + ], + [ + "9678.69", + "0.00077182" + ], + [ + "9678.86", + "0.00075585" + ], + [ + "9678.99", + "0.00075073" + ], + [ + "9679.79", + "0.00075624" + ], + [ + "9679.88", + "0.00077264" + ], + [ + "9680.00", + "0.06579127" + ], + [ + "9680.54", + "0.00083226" + ], + [ + "9680.70", + "0.00077272" + ], + [ + "9680.72", + "0.00074120" + ], + [ + "9680.82", + "0.00076432" + ], + [ + "9680.86", + "0.00075745" + ], + [ + "9681.12", + "0.00074621" + ], + [ + "9681.61", + "0.00075076" + ], + [ + "9682.23", + "0.00077420" + ], + [ + "9682.32", + "0.00077813" + ], + [ + "9684.26", + "0.00074612" + ], + [ + "9684.57", + "0.00077466" + ], + [ + "9685.17", + "0.00074481" + ], + [ + "9685.82", + "0.00077053" + ], + [ + "9685.87", + "0.00076442" + ], + [ + "9686.08", + "0.00081159" + ], + [ + "9687.00", + "0.03000000" + ], + [ + "9687.74", + "0.00075528" + ], + [ + "9688.29", + "0.00083436" + ], + [ + "9688.34", + "0.00075721" + ], + [ + "9688.91", + "0.00157277" + ], + [ + "9688.92", + "0.00081232" + ], + [ + "9689.42", + "0.00075312" + ], + [ + "9689.73", + "0.00082780" + ], + [ + "9690.00", + "0.36000000" + ], + [ + "9690.30", + "0.00078601" + ], + [ + "9690.39", + "0.00077316" + ], + [ + "9690.41", + "0.00084003" + ], + [ + "9690.84", + "0.00077217" + ], + [ + "9691.57", + "0.00077574" + ], + [ + "9692.58", + "0.00075207" + ], + [ + "9692.82", + "0.00077658" + ], + [ + "9693.48", + "0.12344000" + ], + [ + "9693.51", + "0.00077957" + ], + [ + "9693.70", + "0.00075067" + ], + [ + "9694.09", + "0.00074183" + ], + [ + "9694.23", + "0.00076636" + ], + [ + "9694.32", + "0.00075055" + ], + [ + "9694.49", + "0.00079544" + ], + [ + "9695.00", + "0.06600000" + ], + [ + "9695.29", + "0.00074358" + ], + [ + "9695.72", + "0.00074105" + ], + [ + "9696.30", + "0.00076249" + ], + [ + "9696.60", + "0.00085623" + ], + [ + "9696.78", + "0.00076646" + ], + [ + "9696.90", + "0.00078043" + ], + [ + "9697.91", + "0.00082286" + ], + [ + "9697.97", + "0.20000000" + ], + [ + "9698.05", + "0.00077962" + ], + [ + "9698.40", + "0.00076361" + ], + [ + "9699.00", + "0.10343785" + ], + [ + "9699.37", + "0.00077095" + ], + [ + "9699.75", + "0.30000000" + ], + [ + "9700.00", + "2.83221140" + ], + [ + "9700.22", + "0.00075089" + ], + [ + "9700.29", + "0.00075439" + ], + [ + "9700.53", + "0.00078681" + ], + [ + "9701.00", + "0.12000000" + ], + [ + "9702.00", + "0.00430000" + ], + [ + "9702.18", + "0.00076486" + ], + [ + "9702.69", + "0.00084622" + ], + [ + "9702.79", + "0.00074221" + ], + [ + "9702.97", + "0.00075180" + ], + [ + "9703.27", + "0.00078331" + ], + [ + "9703.32", + "0.00074219" + ], + [ + "9703.42", + "0.00079695" + ], + [ + "9703.58", + "0.00076505" + ], + [ + "9703.68", + "0.00076795" + ], + [ + "9704.17", + "0.00074704" + ], + [ + "9705.09", + "0.00074046" + ], + [ + "9705.35", + "0.00077585" + ], + [ + "9705.39", + "0.00074040" + ], + [ + "9705.51", + "0.00076618" + ], + [ + "9705.55", + "0.00077687" + ], + [ + "9706.47", + "0.00076495" + ], + [ + "9706.89", + "0.00074059" + ], + [ + "9707.00", + "0.02000000" + ], + [ + "9707.20", + "0.00075475" + ], + [ + "9707.26", + "0.00076045" + ], + [ + "9707.61", + "0.00078988" + ], + [ + "9707.91", + "0.00074597" + ], + [ + "9708.18", + "0.00074700" + ], + [ + "9708.39", + "0.00078917" + ], + [ + "9708.76", + "0.00074090" + ], + [ + "9708.93", + "0.12900000" + ], + [ + "9709.38", + "0.00074487" + ], + [ + "9709.63", + "0.00083830" + ], + [ + "9709.65", + "0.00077774" + ], + [ + "9710.00", + "52.89396710" + ], + [ + "9710.75", + "0.00075015" + ], + [ + "9710.98", + "0.00085136" + ], + [ + "9711.00", + "0.11000000" + ], + [ + "9711.11", + "0.00150537" + ], + [ + "9711.58", + "0.00077207" + ], + [ + "9711.72", + "0.00074947" + ], + [ + "9711.77", + "0.00078655" + ], + [ + "9711.88", + "0.00074729" + ], + [ + "9711.89", + "0.00074919" + ], + [ + "9712.00", + "0.60000000" + ], + [ + "9713.27", + "0.04000000" + ], + [ + "9713.58", + "0.16920000" + ], + [ + "9713.59", + "0.00079036" + ], + [ + "9713.75", + "0.00078506" + ], + [ + "9714.30", + "0.00080976" + ], + [ + "9714.60", + "0.00078049" + ], + [ + "9715.00", + "0.05000000" + ], + [ + "9715.05", + "0.00075637" + ], + [ + "9715.72", + "0.00077142" + ], + [ + "9715.73", + "0.00074691" + ], + [ + "9715.89", + "0.00075771" + ], + [ + "9716.71", + "0.00079065" + ], + [ + "9716.76", + "0.00077764" + ], + [ + "9716.81", + "0.00085481" + ], + [ + "9716.85", + "0.00078618" + ], + [ + "9716.92", + "0.00076208" + ], + [ + "9717.25", + "0.00073839" + ], + [ + "9717.40", + "0.00075941" + ], + [ + "9717.67", + "0.00075208" + ], + [ + "9718.34", + "0.00075809" + ], + [ + "9718.80", + "0.11000000" + ], + [ + "9719.02", + "0.00074474" + ], + [ + "9719.90", + "0.00074469" + ], + [ + "9719.98", + "0.00076729" + ], + [ + "9720.16", + "0.00074234" + ], + [ + "9720.29", + "0.00078558" + ], + [ + "9720.45", + "0.00074098" + ], + [ + "9721.02", + "0.35280000" + ], + [ + "9721.08", + "0.00086222" + ], + [ + "9721.10", + "0.00074656" + ], + [ + "9721.40", + "0.00075620" + ], + [ + "9722.24", + "0.00076314" + ], + [ + "9722.59", + "0.00082077" + ], + [ + "9723.01", + "0.00075962" + ], + [ + "9723.04", + "0.60200000" + ], + [ + "9723.30", + "0.71025140" + ], + [ + "9723.76", + "0.00076670" + ], + [ + "9723.91", + "0.00074320" + ], + [ + "9724.05", + "0.00075228" + ], + [ + "9724.83", + "0.00077987" + ], + [ + "9724.97", + "0.00077928" + ], + [ + "9725.00", + "0.10748819" + ], + [ + "9725.02", + "0.20200000" + ], + [ + "9725.49", + "0.00074834" + ], + [ + "9725.83", + "0.27740000" + ], + [ + "9726.13", + "0.00074653" + ], + [ + "9727.00", + "0.02000000" + ], + [ + "9727.44", + "0.00083144" + ], + [ + "9727.63", + "0.00075648" + ], + [ + "9727.74", + "0.00079748" + ], + [ + "9728.06", + "0.00078894" + ], + [ + "9728.73", + "0.00073903" + ], + [ + "9729.82", + "0.00085544" + ], + [ + "9730.00", + "0.05454194" + ], + [ + "9730.03", + "0.00077344" + ], + [ + "9730.38", + "0.00076440" + ], + [ + "9730.41", + "0.00075023" + ], + [ + "9730.58", + "0.00077071" + ], + [ + "9730.59", + "0.00076957" + ], + [ + "9731.00", + "0.43000000" + ], + [ + "9732.09", + "0.00075832" + ], + [ + "9732.60", + "0.52000000" + ], + [ + "9732.67", + "0.00076264" + ], + [ + "9733.09", + "0.00077025" + ], + [ + "9733.40", + "0.00079977" + ], + [ + "9734.09", + "0.00076635" + ], + [ + "9734.31", + "0.00074543" + ], + [ + "9734.47", + "0.00075035" + ], + [ + "9734.68", + "0.00076643" + ], + [ + "9734.76", + "0.00077248" + ], + [ + "9734.80", + "0.00075848" + ], + [ + "9735.00", + "0.91000000" + ], + [ + "9735.21", + "0.00079093" + ], + [ + "9735.61", + "0.00438008" + ], + [ + "9736.10", + "0.00073850" + ], + [ + "9736.80", + "0.00075029" + ], + [ + "9737.35", + "0.00075539" + ], + [ + "9737.70", + "0.00078410" + ], + [ + "9738.26", + "0.00079037" + ], + [ + "9738.40", + "0.00076356" + ], + [ + "9738.44", + "0.00077274" + ], + [ + "9738.55", + "0.00075746" + ], + [ + "9738.70", + "0.00074277" + ], + [ + "9739.42", + "0.00075272" + ], + [ + "9740.00", + "0.05077244" + ], + [ + "9740.15", + "0.00074692" + ], + [ + "9740.44", + "0.00075577" + ], + [ + "9740.52", + "0.00075444" + ], + [ + "9740.59", + "0.00075768" + ], + [ + "9740.61", + "0.00077220" + ], + [ + "9741.40", + "0.00074656" + ], + [ + "9741.89", + "0.00075881" + ], + [ + "9742.57", + "0.00073919" + ], + [ + "9742.96", + "0.00074816" + ], + [ + "9742.97", + "0.00077857" + ], + [ + "9743.00", + "0.84000000" + ], + [ + "9743.67", + "0.00076686" + ], + [ + "9743.93", + "0.00078932" + ], + [ + "9744.19", + "0.00078969" + ], + [ + "9744.53", + "0.00079389" + ], + [ + "9745.00", + "1.66300000" + ], + [ + "9745.34", + "0.00075536" + ], + [ + "9745.39", + "0.00076222" + ], + [ + "9745.55", + "0.00074979" + ], + [ + "9746.03", + "0.00078307" + ], + [ + "9747.00", + "1.02000000" + ], + [ + "9747.26", + "0.00081869" + ], + [ + "9747.55", + "0.00077256" + ], + [ + "9749.00", + "0.01000000" + ], + [ + "9749.66", + "0.00075893" + ], + [ + "9749.67", + "0.00078893" + ], + [ + "9749.84", + "0.00075190" + ], + [ + "9749.92", + "0.00073816" + ], + [ + "9749.93", + "0.00073843" + ], + [ + "9750.00", + "30.16697000" + ], + [ + "9750.95", + "0.00073669" + ], + [ + "9751.00", + "0.05000000" + ], + [ + "9751.20", + "0.00073561" + ], + [ + "9751.27", + "0.00074801" + ], + [ + "9751.35", + "0.00078531" + ], + [ + "9751.64", + "0.00075248" + ], + [ + "9751.73", + "0.00077234" + ], + [ + "9751.78", + "0.00077771" + ], + [ + "9751.91", + "0.00075071" + ], + [ + "9752.00", + "0.00074555" + ], + [ + "9752.38", + "0.00074490" + ], + [ + "9752.87", + "0.00077345" + ], + [ + "9753.21", + "0.00152224" + ], + [ + "9753.33", + "0.00075120" + ], + [ + "9754.00", + "0.23000000" + ], + [ + "9754.01", + "0.00073767" + ], + [ + "9754.26", + "0.00074003" + ], + [ + "9754.38", + "0.00076735" + ], + [ + "9754.42", + "0.00160173" + ], + [ + "9754.48", + "0.00074004" + ], + [ + "9754.76", + "0.00077241" + ], + [ + "9754.97", + "0.00077493" + ], + [ + "9755.00", + "0.05000000" + ], + [ + "9755.86", + "0.00075647" + ], + [ + "9756.18", + "0.00563600" + ], + [ + "9756.52", + "0.00075052" + ], + [ + "9756.56", + "0.00085552" + ], + [ + "9756.59", + "0.00078741" + ], + [ + "9756.61", + "0.00075865" + ], + [ + "9757.00", + "0.10000000" + ], + [ + "9757.15", + "0.00246198" + ], + [ + "9757.52", + "0.00075266" + ], + [ + "9757.61", + "0.00076728" + ], + [ + "9757.88", + "0.00073747" + ], + [ + "9758.75", + "0.00078300" + ], + [ + "9759.01", + "0.00077973" + ], + [ + "9759.05", + "0.00074213" + ], + [ + "9759.56", + "0.00073983" + ], + [ + "9759.68", + "0.00074127" + ], + [ + "9760.26", + "0.00077807" + ], + [ + "9760.68", + "0.00078462" + ], + [ + "9760.72", + "0.00077009" + ], + [ + "9760.82", + "0.00075981" + ], + [ + "9760.94", + "0.00073988" + ], + [ + "9761.30", + "0.00075794" + ], + [ + "9761.66", + "0.00077272" + ], + [ + "9761.69", + "0.00073773" + ], + [ + "9762.35", + "0.00076069" + ], + [ + "9762.93", + "0.00079038" + ], + [ + "9762.95", + "0.00076538" + ], + [ + "9763.29", + "0.00076623" + ], + [ + "9763.78", + "0.00074558" + ], + [ + "9763.96", + "0.00074455" + ], + [ + "9763.97", + "0.10000000" + ], + [ + "9763.99", + "0.00077415" + ], + [ + "9764.00", + "1.00000000" + ], + [ + "9764.57", + "0.00076690" + ], + [ + "9764.74", + "0.00075817" + ], + [ + "9765.23", + "0.00074320" + ], + [ + "9765.70", + "0.00075337" + ], + [ + "9765.73", + "0.00076479" + ], + [ + "9765.84", + "0.00077892" + ], + [ + "9766.51", + "0.00077979" + ], + [ + "9766.67", + "0.00078721" + ], + [ + "9767.00", + "0.02000000" + ], + [ + "9767.36", + "0.00076007" + ], + [ + "9767.38", + "0.00075448" + ], + [ + "9767.39", + "0.00077071" + ], + [ + "9767.70", + "0.00075059" + ], + [ + "9768.46", + "0.00074837" + ], + [ + "9769.54", + "0.00080707" + ], + [ + "9770.14", + "0.00077578" + ], + [ + "9771.02", + "0.00075232" + ], + [ + "9771.49", + "0.00077815" + ], + [ + "9771.63", + "0.00076052" + ], + [ + "9771.71", + "0.00083973" + ], + [ + "9771.91", + "0.00078438" + ], + [ + "9772.04", + "0.00076500" + ], + [ + "9772.59", + "0.00074298" + ], + [ + "9772.84", + "0.00074140" + ], + [ + "9772.88", + "0.00073765" + ], + [ + "9773.01", + "0.00073943" + ], + [ + "9773.42", + "0.00075808" + ], + [ + "9773.47", + "0.00081557" + ], + [ + "9773.55", + "0.00074020" + ], + [ + "9773.81", + "0.00083112" + ], + [ + "9774.59", + "0.00074013" + ], + [ + "9775.00", + "0.10000000" + ], + [ + "9775.55", + "0.13881537" + ], + [ + "9776.00", + "0.04000000" + ], + [ + "9776.43", + "0.00075693" + ], + [ + "9776.60", + "0.00079635" + ], + [ + "9776.81", + "0.00085549" + ], + [ + "9776.95", + "0.00074458" + ], + [ + "9777.00", + "0.10000000" + ], + [ + "9777.36", + "0.00077903" + ], + [ + "9777.50", + "0.00079528" + ], + [ + "9778.00", + "0.03342872" + ], + [ + "9778.14", + "0.00076923" + ], + [ + "9780.02", + "0.00086539" + ], + [ + "9780.57", + "0.00083843" + ], + [ + "9780.67", + "0.00078716" + ], + [ + "9781.18", + "0.00077485" + ], + [ + "9781.29", + "0.00076438" + ], + [ + "9781.34", + "0.00076032" + ], + [ + "9781.47", + "0.00076084" + ], + [ + "9781.52", + "0.00073528" + ], + [ + "9781.75", + "0.00077761" + ], + [ + "9781.83", + "0.00079166" + ], + [ + "9782.19", + "0.00074707" + ], + [ + "9782.39", + "0.00077792" + ], + [ + "9782.63", + "0.00076088" + ], + [ + "9783.12", + "0.00075937" + ], + [ + "9783.43", + "0.00083600" + ], + [ + "9783.59", + "0.00075350" + ], + [ + "9783.81", + "0.00078055" + ], + [ + "9784.00", + "0.18108327" + ], + [ + "9784.13", + "0.00075210" + ], + [ + "9785.00", + "0.01537000" + ], + [ + "9785.10", + "0.00075466" + ], + [ + "9786.00", + "1.47794560" + ], + [ + "9786.10", + "0.00073894" + ], + [ + "9786.32", + "0.00075735" + ], + [ + "9786.60", + "0.00074051" + ], + [ + "9786.73", + "0.00075285" + ], + [ + "9787.00", + "0.02000000" + ], + [ + "9787.32", + "0.00075335" + ], + [ + "9787.43", + "0.00076224" + ], + [ + "9787.61", + "0.00079039" + ], + [ + "9787.88", + "0.00078293" + ], + [ + "9788.00", + "0.25000000" + ], + [ + "9788.08", + "0.00075068" + ], + [ + "9788.19", + "0.00073307" + ], + [ + "9788.62", + "0.00075530" + ], + [ + "9789.89", + "0.00074954" + ], + [ + "9789.96", + "0.00077275" + ], + [ + "9790.00", + "1.87420726" + ], + [ + "9790.13", + "0.00076087" + ], + [ + "9791.18", + "0.00079211" + ], + [ + "9791.60", + "0.38800000" + ], + [ + "9791.65", + "0.01450000" + ], + [ + "9792.88", + "0.00078383" + ], + [ + "9792.90", + "0.00074506" + ], + [ + "9793.13", + "0.00075358" + ], + [ + "9793.25", + "0.00077453" + ], + [ + "9793.28", + "0.00075197" + ], + [ + "9794.04", + "0.00150986" + ], + [ + "9794.06", + "0.00073804" + ], + [ + "9794.51", + "0.00080689" + ], + [ + "9794.85", + "0.00073995" + ], + [ + "9795.63", + "0.00073805" + ], + [ + "9795.71", + "0.00078750" + ], + [ + "9796.39", + "0.00076749" + ], + [ + "9796.92", + "0.00074502" + ], + [ + "9796.96", + "0.00078430" + ], + [ + "9797.00", + "16.82160000" + ], + [ + "9798.00", + "13.00000000" + ], + [ + "9799.61", + "0.00080615" + ], + [ + "9799.71", + "0.00078276" + ], + [ + "9800.00", + "14.31231003" + ], + [ + "9800.01", + "0.00084838" + ], + [ + "9800.35", + "0.00074243" + ], + [ + "9800.41", + "0.03362481" + ], + [ + "9800.43", + "0.00077568" + ], + [ + "9800.82", + "0.00075890" + ], + [ + "9800.93", + "0.00075171" + ], + [ + "9801.23", + "0.00075527" + ], + [ + "9801.32", + "0.00075443" + ], + [ + "9802.70", + "0.00077166" + ], + [ + "9803.10", + "0.00074036" + ], + [ + "9803.17", + "0.00082225" + ], + [ + "9803.59", + "0.00085201" + ], + [ + "9803.60", + "0.00077070" + ], + [ + "9803.61", + "0.00073821" + ], + [ + "9804.22", + "0.00118989" + ], + [ + "9805.07", + "0.00075510" + ], + [ + "9805.14", + "0.00078383" + ], + [ + "9805.43", + "0.00085411" + ], + [ + "9806.37", + "0.00157765" + ], + [ + "9806.41", + "0.00078601" + ], + [ + "9806.46", + "0.00313468" + ], + [ + "9806.96", + "0.00074015" + ], + [ + "9807.00", + "0.02000000" + ], + [ + "9807.35", + "0.00074610" + ], + [ + "9807.78", + "0.00077916" + ], + [ + "9807.90", + "0.00073252" + ], + [ + "9808.02", + "0.00085190" + ], + [ + "9808.09", + "0.00074096" + ], + [ + "9808.29", + "0.00078242" + ], + [ + "9808.33", + "0.00077450" + ], + [ + "9808.35", + "0.00077343" + ], + [ + "9808.79", + "0.00077224" + ], + [ + "9809.00", + "0.15000000" + ], + [ + "9810.00", + "0.00099000" + ], + [ + "9810.36", + "0.00075802" + ], + [ + "9810.43", + "0.00077039" + ], + [ + "9810.99", + "0.00075097" + ], + [ + "9812.00", + "0.06000000" + ], + [ + "9812.19", + "0.01416000" + ], + [ + "9812.28", + "0.00079040" + ], + [ + "9812.52", + "0.00075399" + ], + [ + "9812.60", + "0.25800000" + ], + [ + "9813.00", + "0.11000000" + ], + [ + "9813.41", + "0.00079659" + ], + [ + "9813.60", + "0.00074270" + ], + [ + "9813.95", + "0.00074154" + ], + [ + "9814.32", + "0.00086876" + ], + [ + "9814.48", + "0.00075253" + ], + [ + "9814.51", + "0.00359607" + ], + [ + "9814.70", + "0.00075063" + ], + [ + "9815.00", + "0.12000000" + ], + [ + "9815.23", + "0.00078085" + ], + [ + "9815.74", + "0.00078410" + ], + [ + "9815.77", + "0.00074300" + ], + [ + "9816.00", + "0.15111150" + ], + [ + "9816.61", + "0.00082015" + ], + [ + "9816.96", + "0.00073977" + ], + [ + "9817.15", + "0.00077530" + ], + [ + "9817.85", + "0.00075000" + ], + [ + "9817.92", + "0.00085519" + ], + [ + "9818.54", + "0.00076115" + ], + [ + "9818.97", + "0.00074476" + ], + [ + "9819.04", + "0.00077099" + ], + [ + "9819.46", + "0.00075668" + ], + [ + "9820.00", + "0.00782672" + ], + [ + "9820.03", + "0.00154676" + ], + [ + "9821.28", + "0.00075887" + ], + [ + "9821.49", + "0.00078593" + ], + [ + "9821.69", + "0.00081966" + ], + [ + "9821.86", + "0.00074193" + ], + [ + "9822.24", + "0.00083355" + ], + [ + "9822.90", + "0.00076295" + ], + [ + "9822.92", + "0.00075998" + ], + [ + "9822.95", + "0.00074186" + ], + [ + "9823.10", + "0.00076126" + ], + [ + "9823.61", + "0.00080419" + ], + [ + "9823.77", + "0.00077628" + ], + [ + "9823.81", + "0.00428203" + ], + [ + "9824.11", + "0.00075522" + ], + [ + "9824.14", + "0.00075996" + ], + [ + "9824.36", + "0.00078531" + ], + [ + "9825.08", + "0.00075855" + ], + [ + "9825.39", + "0.00074929" + ], + [ + "9825.55", + "0.00079051" + ], + [ + "9825.66", + "0.00074308" + ], + [ + "9825.91", + "0.00074860" + ], + [ + "9826.69", + "0.00077742" + ], + [ + "9827.00", + "0.02000000" + ], + [ + "9828.00", + "1.08719384" + ], + [ + "9828.06", + "0.00078921" + ], + [ + "9828.48", + "0.00077321" + ], + [ + "9828.69", + "0.00078128" + ], + [ + "9828.80", + "0.00073039" + ], + [ + "9829.13", + "0.00077312" + ], + [ + "9829.31", + "0.00074386" + ], + [ + "9829.33", + "0.00077313" + ], + [ + "9830.00", + "0.02974119" + ], + [ + "9831.00", + "0.25000000" + ], + [ + "9831.58", + "0.00075128" + ], + [ + "9832.00", + "0.39000000" + ], + [ + "9832.25", + "0.00074604" + ], + [ + "9832.83", + "0.00072899" + ], + [ + "9832.99", + "0.00074110" + ], + [ + "9833.17", + "0.00085049" + ], + [ + "9833.69", + "0.00077598" + ], + [ + "9834.25", + "0.00075334" + ], + [ + "9834.56", + "0.00075092" + ], + [ + "9834.90", + "0.00076099" + ], + [ + "9835.93", + "0.00074269" + ], + [ + "9836.09", + "0.00075300" + ], + [ + "9836.31", + "0.00073785" + ], + [ + "9836.34", + "0.12113000" + ], + [ + "9836.64", + "0.00080725" + ], + [ + "9836.76", + "0.00074528" + ], + [ + "9836.96", + "0.00078041" + ], + [ + "9837.22", + "0.00075723" + ], + [ + "9837.29", + "0.00076879" + ], + [ + "9837.30", + "0.00159656" + ], + [ + "9837.50", + "0.00074800" + ], + [ + "9837.73", + "0.00074568" + ], + [ + "9838.92", + "0.00076239" + ], + [ + "9839.09", + "0.00075945" + ], + [ + "9839.61", + "0.00074256" + ], + [ + "9839.68", + "0.00077388" + ], + [ + "9840.05", + "0.00151428" + ], + [ + "9840.13", + "0.00076766" + ], + [ + "9841.00", + "0.00296232" + ], + [ + "9841.38", + "0.00073822" + ], + [ + "9841.46", + "0.00085008" + ], + [ + "9841.67", + "0.00076147" + ], + [ + "9841.69", + "0.00074829" + ], + [ + "9841.94", + "0.00436279" + ], + [ + "9841.99", + "0.00076673" + ], + [ + "9842.09", + "0.00074672" + ], + [ + "9842.39", + "0.00076953" + ], + [ + "9842.54", + "0.00077290" + ], + [ + "9843.00", + "0.11000000" + ], + [ + "9843.51", + "0.00074620" + ], + [ + "9843.66", + "0.00074123" + ], + [ + "9843.77", + "0.00075987" + ], + [ + "9843.99", + "0.00075143" + ], + [ + "9844.13", + "0.00076012" + ], + [ + "9844.45", + "0.00077313" + ], + [ + "9844.62", + "0.00074952" + ], + [ + "9844.74", + "0.00078343" + ], + [ + "9845.00", + "0.15000000" + ], + [ + "9845.25", + "0.00077174" + ], + [ + "9845.39", + "0.00080610" + ], + [ + "9845.70", + "0.10000000" + ], + [ + "9846.01", + "0.00081818" + ], + [ + "9846.22", + "0.00077015" + ], + [ + "9847.00", + "0.02000000" + ], + [ + "9847.12", + "0.00074280" + ], + [ + "9847.36", + "0.00077085" + ], + [ + "9847.74", + "0.00074458" + ], + [ + "9847.85", + "0.00073597" + ], + [ + "9848.00", + "0.15000000" + ], + [ + "9848.25", + "0.20000000" + ], + [ + "9848.26", + "0.00076585" + ], + [ + "9848.27", + "0.00074598" + ], + [ + "9848.79", + "0.00083542" + ], + [ + "9849.00", + "0.05000000" + ], + [ + "9849.49", + "0.67355936" + ], + [ + "9849.78", + "0.00075163" + ], + [ + "9850.00", + "0.28698011" + ], + [ + "9850.87", + "0.00074278" + ], + [ + "9851.59", + "0.00073811" + ], + [ + "9851.75", + "0.00077729" + ], + [ + "9851.92", + "0.00074702" + ], + [ + "9852.58", + "0.00074328" + ], + [ + "9852.69", + "0.00083951" + ], + [ + "9852.84", + "0.00074978" + ], + [ + "9852.89", + "0.00078363" + ], + [ + "9853.54", + "0.00074270" + ], + [ + "9854.16", + "0.00079036" + ], + [ + "9854.40", + "0.00082183" + ], + [ + "9854.79", + "0.00073138" + ], + [ + "9855.00", + "0.13000000" + ], + [ + "9855.34", + "0.00073349" + ], + [ + "9855.53", + "0.23439342" + ], + [ + "9856.00", + "0.01074602" + ], + [ + "9856.51", + "0.00074829" + ], + [ + "9857.30", + "0.00077146" + ], + [ + "9857.36", + "0.00074983" + ], + [ + "9857.45", + "0.00075955" + ], + [ + "9857.66", + "0.00073851" + ], + [ + "9857.84", + "0.00075351" + ], + [ + "9857.93", + "0.00075511" + ], + [ + "9858.00", + "1.08719384" + ], + [ + "9858.13", + "0.00079256" + ], + [ + "9858.29", + "0.00075241" + ], + [ + "9858.56", + "0.00078739" + ], + [ + "9858.68", + "0.00074629" + ], + [ + "9858.75", + "0.00073727" + ], + [ + "9858.97", + "0.00080436" + ], + [ + "9859.00", + "30.00000000" + ], + [ + "9859.68", + "0.00078172" + ], + [ + "9859.75", + "0.00081569" + ], + [ + "9859.95", + "0.00073090" + ], + [ + "9860.12", + "0.00079304" + ], + [ + "9860.77", + "0.00076123" + ], + [ + "9860.96", + "0.00078316" + ], + [ + "9861.41", + "0.00078327" + ], + [ + "9861.95", + "0.00077151" + ], + [ + "9862.58", + "0.00074728" + ], + [ + "9863.44", + "0.00075388" + ], + [ + "9863.54", + "0.00073408" + ], + [ + "9863.57", + "0.00075512" + ], + [ + "9863.87", + "0.00074594" + ], + [ + "9864.11", + "0.00075355" + ], + [ + "9864.45", + "0.00079274" + ], + [ + "9864.81", + "0.25000000" + ], + [ + "9865.00", + "0.13316482" + ], + [ + "9865.51", + "0.00084123" + ], + [ + "9865.89", + "0.00075043" + ], + [ + "9866.00", + "0.15076394" + ], + [ + "9866.45", + "0.00075312" + ], + [ + "9867.00", + "0.02000000" + ], + [ + "9867.61", + "0.00075312" + ], + [ + "9867.74", + "0.00078410" + ], + [ + "9868.00", + "0.10000000" + ], + [ + "9868.99", + "0.00073901" + ], + [ + "9869.55", + "0.00075562" + ], + [ + "9869.69", + "0.00085529" + ], + [ + "9870.00", + "0.12000000" + ], + [ + "9870.10", + "0.00077071" + ], + [ + "9870.79", + "0.00076128" + ], + [ + "9870.80", + "0.00075577" + ], + [ + "9870.99", + "0.00077544" + ], + [ + "9871.31", + "0.00075832" + ], + [ + "9871.81", + "0.00073381" + ], + [ + "9871.99", + "0.00076110" + ], + [ + "9872.29", + "0.00072924" + ], + [ + "9872.32", + "0.00078005" + ], + [ + "9872.34", + "0.00075792" + ], + [ + "9872.51", + "0.00079421" + ], + [ + "9873.01", + "0.00084012" + ], + [ + "9873.29", + "0.00073584" + ], + [ + "9873.32", + "0.00075658" + ], + [ + "9874.11", + "0.00075680" + ], + [ + "9874.41", + "0.00073741" + ], + [ + "9874.84", + "0.00075928" + ], + [ + "9875.09", + "0.00075760" + ], + [ + "9875.16", + "0.00078285" + ], + [ + "9875.75", + "0.00076452" + ], + [ + "9875.79", + "0.00077482" + ], + [ + "9876.87", + "0.14000000" + ], + [ + "9877.75", + "0.00074404" + ], + [ + "9877.81", + "0.00074864" + ], + [ + "9878.03", + "0.00073748" + ], + [ + "9878.11", + "0.00081460" + ], + [ + "9878.69", + "0.00073846" + ], + [ + "9878.76", + "0.00073101" + ], + [ + "9878.82", + "0.00076903" + ], + [ + "9879.00", + "0.10000000" + ], + [ + "9879.21", + "0.00082017" + ], + [ + "9879.43", + "0.00077171" + ], + [ + "9879.48", + "0.00307493" + ], + [ + "9879.61", + "0.00073239" + ], + [ + "9879.65", + "0.00077221" + ], + [ + "9880.00", + "0.15000000" + ], + [ + "9880.08", + "0.00072888" + ], + [ + "9881.67", + "0.00073568" + ], + [ + "9881.86", + "0.00072950" + ], + [ + "9881.93", + "0.13500000" + ], + [ + "9882.16", + "0.00074721" + ], + [ + "9882.51", + "0.00078317" + ], + [ + "9882.57", + "0.00075009" + ], + [ + "9883.28", + "0.01995365" + ], + [ + "9883.57", + "0.00075417" + ], + [ + "9883.62", + "0.00078129" + ], + [ + "9883.87", + "0.00074595" + ], + [ + "9883.92", + "0.00074610" + ], + [ + "9884.33", + "0.41920755" + ], + [ + "9884.95", + "0.00075109" + ], + [ + "9885.23", + "0.00073810" + ], + [ + "9885.75", + "0.00074770" + ], + [ + "9885.96", + "0.00078338" + ], + [ + "9886.20", + "0.05000000" + ], + [ + "9886.41", + "0.00075122" + ], + [ + "9886.44", + "0.00078469" + ], + [ + "9886.46", + "0.00074617" + ], + [ + "9886.53", + "0.00078401" + ], + [ + "9886.91", + "0.00079538" + ], + [ + "9887.00", + "1.02000000" + ], + [ + "9887.15", + "0.00075762" + ], + [ + "9887.23", + "0.00307840" + ], + [ + "9887.64", + "0.00075670" + ], + [ + "9887.78", + "0.00075642" + ], + [ + "9888.00", + "3.56277548" + ], + [ + "9889.00", + "0.01000000" + ], + [ + "9889.14", + "0.00079009" + ], + [ + "9889.32", + "0.00076334" + ], + [ + "9889.52", + "0.00072964" + ], + [ + "9889.99", + "0.05060000" + ], + [ + "9890.00", + "0.15000000" + ], + [ + "9890.83", + "0.00077228" + ], + [ + "9892.07", + "0.00075991" + ], + [ + "9892.34", + "0.00074394" + ], + [ + "9893.00", + "0.23327939" + ], + [ + "9894.02", + "0.09900000" + ], + [ + "9894.62", + "0.00077385" + ], + [ + "9895.06", + "0.00077269" + ], + [ + "9895.09", + "0.00074335" + ], + [ + "9895.79", + "0.00076366" + ], + [ + "9895.82", + "0.00146538" + ], + [ + "9896.31", + "0.00074220" + ], + [ + "9896.44", + "0.00073947" + ], + [ + "9896.86", + "0.00075295" + ], + [ + "9897.27", + "0.00074919" + ], + [ + "9897.33", + "0.00075624" + ], + [ + "9897.35", + "0.00074845" + ], + [ + "9897.43", + "0.01532721" + ], + [ + "9897.81", + "0.00077140" + ], + [ + "9898.00", + "0.10000000" + ], + [ + "9898.02", + "0.21000000" + ], + [ + "9898.18", + "1.23002790" + ], + [ + "9898.27", + "0.00078998" + ], + [ + "9898.35", + "0.00076091" + ], + [ + "9898.83", + "0.28700000" + ], + [ + "9898.86", + "0.62808448" + ], + [ + "9899.00", + "0.55000000" + ], + [ + "9899.02", + "0.00075412" + ], + [ + "9899.29", + "0.00073983" + ], + [ + "9899.94", + "0.00075631" + ], + [ + "9900.00", + "9.02819971" + ], + [ + "9900.03", + "0.00075982" + ], + [ + "9900.38", + "0.00075712" + ], + [ + "9900.49", + "0.00075327" + ], + [ + "9901.27", + "0.00076426" + ], + [ + "9901.43", + "0.00087954" + ], + [ + "9901.60", + "0.00078313" + ], + [ + "9901.92", + "0.00077895" + ], + [ + "9902.36", + "0.00074119" + ], + [ + "9902.63", + "0.00152537" + ], + [ + "9902.92", + "0.00076521" + ], + [ + "9903.20", + "0.00250000" + ], + [ + "9903.79", + "0.00076867" + ], + [ + "9904.00", + "0.45000000" + ], + [ + "9904.79", + "0.00073011" + ], + [ + "9905.19", + "0.00077515" + ], + [ + "9905.60", + "0.54400000" + ], + [ + "9905.72", + "0.00075691" + ], + [ + "9905.84", + "0.00077178" + ], + [ + "9906.14", + "0.00074613" + ], + [ + "9906.15", + "0.00078518" + ], + [ + "9907.00", + "0.02000000" + ], + [ + "9907.10", + "0.78178268" + ], + [ + "9908.03", + "0.00077565" + ], + [ + "9908.19", + "0.00078275" + ], + [ + "9908.33", + "0.00078174" + ], + [ + "9908.67", + "0.00079463" + ], + [ + "9908.73", + "0.00073385" + ], + [ + "9909.53", + "0.00073975" + ], + [ + "9910.00", + "0.01585000" + ], + [ + "9910.28", + "0.00074196" + ], + [ + "9910.66", + "0.00078928" + ], + [ + "9910.83", + "0.00084232" + ], + [ + "9911.10", + "0.00075372" + ], + [ + "9911.70", + "0.00075611" + ], + [ + "9911.73", + "0.00078670" + ], + [ + "9912.19", + "0.00073968" + ], + [ + "9912.50", + "0.00076435" + ], + [ + "9912.98", + "0.00077393" + ], + [ + "9913.05", + "0.00075791" + ], + [ + "9913.12", + "0.00084852" + ], + [ + "9914.22", + "0.00084808" + ], + [ + "9914.26", + "0.30000000" + ], + [ + "9914.44", + "0.00074064" + ], + [ + "9915.05", + "0.00082128" + ], + [ + "9915.15", + "0.00075762" + ], + [ + "9915.44", + "0.00073082" + ], + [ + "9915.46", + "0.00074860" + ], + [ + "9916.00", + "0.13219829" + ], + [ + "9916.12", + "0.00078349" + ], + [ + "9917.12", + "0.00076289" + ], + [ + "9918.79", + "0.00074926" + ], + [ + "9919.00", + "0.00144400" + ], + [ + "9919.06", + "0.00072644" + ], + [ + "9919.29", + "0.00074210" + ], + [ + "9919.87", + "0.00072590" + ], + [ + "9920.59", + "0.00078282" + ], + [ + "9921.41", + "0.00073762" + ], + [ + "9921.91", + "0.00076967" + ], + [ + "9922.09", + "0.00075682" + ], + [ + "9922.14", + "0.00075280" + ], + [ + "9922.66", + "0.00073071" + ], + [ + "9923.15", + "0.00072455" + ], + [ + "9923.35", + "0.00072372" + ], + [ + "9923.79", + "0.00075179" + ], + [ + "9923.95", + "0.00077036" + ], + [ + "9924.10", + "0.13169107" + ], + [ + "9924.45", + "0.00075805" + ], + [ + "9924.73", + "0.00076360" + ], + [ + "9925.00", + "0.12101538" + ], + [ + "9925.56", + "0.00074693" + ], + [ + "9925.58", + "0.00073006" + ], + [ + "9926.11", + "0.00072760" + ], + [ + "9926.20", + "0.00073365" + ], + [ + "9927.00", + "0.02000000" + ], + [ + "9927.15", + "0.00077799" + ], + [ + "9927.84", + "0.00075125" + ], + [ + "9927.99", + "0.00073850" + ], + [ + "9928.07", + "0.00074932" + ], + [ + "9928.28", + "3.00000000" + ], + [ + "9928.29", + "0.00072614" + ], + [ + "9928.73", + "0.00074275" + ], + [ + "9928.83", + "0.00075184" + ], + [ + "9929.14", + "0.00076778" + ], + [ + "9929.15", + "0.00075326" + ], + [ + "9929.31", + "0.00075635" + ], + [ + "9929.38", + "0.00075014" + ], + [ + "9929.79", + "0.00075847" + ], + [ + "9930.00", + "0.09829421" + ], + [ + "9930.02", + "0.10516128" + ], + [ + "9931.08", + "0.00074880" + ], + [ + "9931.32", + "0.00079168" + ], + [ + "9931.40", + "0.00074921" + ], + [ + "9931.47", + "0.00085125" + ], + [ + "9931.87", + "0.00082608" + ], + [ + "9931.91", + "0.00075312" + ], + [ + "9932.21", + "0.00077244" + ], + [ + "9932.72", + "0.00072517" + ], + [ + "9932.95", + "0.00082811" + ], + [ + "9932.99", + "0.00074729" + ], + [ + "9933.00", + "0.00077049" + ], + [ + "9933.06", + "0.00074230" + ], + [ + "9934.71", + "0.00072267" + ], + [ + "9934.75", + "0.00075621" + ], + [ + "9935.67", + "0.00079348" + ], + [ + "9936.31", + "0.00074009" + ], + [ + "9936.46", + "0.00073207" + ], + [ + "9936.99", + "0.00076736" + ], + [ + "9937.06", + "0.00077800" + ], + [ + "9937.40", + "0.00073802" + ], + [ + "9937.62", + "0.00075115" + ], + [ + "9937.69", + "0.00075622" + ], + [ + "9938.19", + "0.00073548" + ], + [ + "9938.36", + "0.00075562" + ], + [ + "9939.00", + "0.34000000" + ], + [ + "9939.17", + "0.00073229" + ], + [ + "9939.30", + "0.00078507" + ], + [ + "9939.40", + "0.00075754" + ], + [ + "9939.74", + "0.00073887" + ], + [ + "9939.77", + "0.00074633" + ], + [ + "9940.00", + "0.49215649" + ], + [ + "9940.51", + "0.00076409" + ], + [ + "9940.72", + "0.00075888" + ], + [ + "9940.95", + "0.00084448" + ], + [ + "9941.24", + "0.00075124" + ], + [ + "9941.29", + "0.00075203" + ], + [ + "9941.54", + "0.00075097" + ], + [ + "9941.56", + "0.00085904" + ], + [ + "9941.98", + "0.00074046" + ], + [ + "9942.00", + "0.25000000" + ], + [ + "9942.07", + "0.00077278" + ], + [ + "9942.33", + "0.00074817" + ], + [ + "9942.52", + "0.00079063" + ], + [ + "9942.54", + "0.00078511" + ], + [ + "9942.63", + "0.00155578" + ], + [ + "9942.82", + "0.00072695" + ], + [ + "9942.99", + "0.00083434" + ], + [ + "9943.36", + "0.00077741" + ], + [ + "9943.93", + "0.00052400" + ], + [ + "9944.60", + "1.40200000" + ], + [ + "9944.77", + "0.00072980" + ], + [ + "9945.10", + "0.00072766" + ], + [ + "9945.33", + "0.00084458" + ], + [ + "9945.35", + "0.00073010" + ], + [ + "9945.72", + "0.00073268" + ], + [ + "9946.02", + "0.00072844" + ], + [ + "9946.65", + "0.00079260" + ], + [ + "9947.00", + "0.02000000" + ], + [ + "9947.39", + "0.01448722" + ], + [ + "9947.99", + "0.00082169" + ], + [ + "9948.34", + "0.00077620" + ], + [ + "9948.38", + "0.00079315" + ], + [ + "9948.58", + "0.00073499" + ], + [ + "9948.62", + "0.00072179" + ], + [ + "9948.97", + "0.20000000" + ], + [ + "9949.49", + "0.01000000" + ], + [ + "9949.98", + "0.00425356" + ], + [ + "9950.00", + "1.94233565" + ], + [ + "9950.24", + "0.04254892" + ], + [ + "9952.48", + "0.00079377" + ], + [ + "9955.00", + "0.47257715" + ], + [ + "9957.00", + "0.07500000" + ], + [ + "9959.25", + "1.00000000" + ], + [ + "9960.00", + "0.30000000" + ], + [ + "9962.48", + "0.00079298" + ], + [ + "9967.00", + "0.02000000" + ], + [ + "9968.88", + "0.50000000" + ], + [ + "9971.71", + "0.00200000" + ], + [ + "9972.48", + "0.00079218" + ], + [ + "9973.00", + "0.04693600" + ], + [ + "9975.00", + "0.01388600" + ], + [ + "9976.47", + "0.01138758" + ], + [ + "9977.00", + "0.10000000" + ], + [ + "9977.56", + "0.10000000" + ], + [ + "9978.00", + "0.14077823" + ], + [ + "9978.67", + "0.00206066" + ], + [ + "9979.00", + "0.01000000" + ], + [ + "9980.00", + "0.37300000" + ], + [ + "9982.48", + "0.00079139" + ], + [ + "9982.87", + "0.01000000" + ], + [ + "9983.67", + "0.10000000" + ], + [ + "9984.00", + "0.10000000" + ], + [ + "9985.00", + "0.09506256" + ], + [ + "9987.00", + "0.03000000" + ], + [ + "9988.00", + "0.02700000" + ], + [ + "9988.70", + "0.28266378" + ], + [ + "9989.00", + "0.74949967" + ], + [ + "9989.01", + "0.07000000" + ], + [ + "9990.00", + "0.06353699" + ], + [ + "9990.75", + "0.00311701" + ], + [ + "9990.98", + "0.01101176" + ], + [ + "9992.48", + "0.00079059" + ], + [ + "9993.00", + "30.76337925" + ], + [ + "9995.00", + "0.02161245" + ], + [ + "9998.00", + "0.75000000" + ], + [ + "9998.98", + "2.00000000" + ], + [ + "9999.00", + "10.08023955" + ], + [ + "9999.50", + "0.76294726" + ], + [ + "9999.52", + "0.01000000" + ], + [ + "9999.95", + "0.00441272" + ], + [ + "9999.99", + "1.49786458" + ], + [ + "10000.00", + "30.21110565" + ], + [ + "10001.00", + "0.02000000" + ], + [ + "10001.67", + "0.89736402" + ], + [ + "10002.75", + "0.01000000" + ], + [ + "10004.03", + "0.00111468" + ], + [ + "10004.88", + "0.00078961" + ], + [ + "10007.00", + "0.02000000" + ], + [ + "10008.22", + "0.29950814" + ], + [ + "10009.46", + "0.01410000" + ], + [ + "10010.00", + "0.00080000" + ], + [ + "10014.92", + "0.00212899" + ], + [ + "10017.28", + "0.00078864" + ], + [ + "10020.00", + "0.00499900" + ], + [ + "10022.00", + "4.63670162" + ], + [ + "10025.00", + "0.10000000" + ], + [ + "10027.00", + "0.02000000" + ], + [ + "10029.68", + "0.00078766" + ], + [ + "10042.08", + "0.00078669" + ], + [ + "10045.00", + "0.01607982" + ], + [ + "10047.00", + "0.02000000" + ], + [ + "10048.24", + "0.06742918" + ], + [ + "10048.50", + "1.00000000" + ], + [ + "10050.00", + "0.20750815" + ], + [ + "10050.88", + "0.01000000" + ], + [ + "10054.32", + "0.04265755" + ], + [ + "10054.48", + "0.00078572" + ], + [ + "10054.93", + "0.14100000" + ], + [ + "10065.00", + "0.01000000" + ], + [ + "10066.88", + "0.00078475" + ], + [ + "10067.00", + "0.02000000" + ], + [ + "10068.15", + "0.00074600" + ], + [ + "10069.18", + "0.00230312" + ], + [ + "10071.02", + "0.21800000" + ], + [ + "10071.83", + "0.29660000" + ], + [ + "10074.00", + "1.62760600" + ], + [ + "10076.22", + "0.00210729" + ], + [ + "10077.00", + "0.47000000" + ], + [ + "10078.60", + "0.37800000" + ], + [ + "10079.28", + "0.00078379" + ], + [ + "10080.00", + "0.00096700" + ], + [ + "10087.00", + "0.02000000" + ], + [ + "10089.00", + "0.06800000" + ], + [ + "10090.00", + "0.11000000" + ], + [ + "10090.52", + "0.04194101" + ], + [ + "10091.68", + "0.00078282" + ], + [ + "10092.85", + "0.00512739" + ], + [ + "10097.00", + "0.03597260" + ], + [ + "10097.60", + "0.41600000" + ], + [ + "10098.80", + "0.10000000" + ], + [ + "10099.98", + "1.00000000" + ], + [ + "10100.00", + "0.87399565" + ], + [ + "10101.00", + "0.11000000" + ], + [ + "10101.01", + "0.02500000" + ], + [ + "10104.08", + "0.00078186" + ], + [ + "10107.00", + "0.02000000" + ], + [ + "10110.00", + "0.00080000" + ], + [ + "10116.48", + "0.00078090" + ], + [ + "10122.00", + "0.17000000" + ], + [ + "10127.00", + "0.02000000" + ], + [ + "10128.88", + "0.00077995" + ], + [ + "10141.09", + "0.44936062" + ], + [ + "10141.28", + "0.00077899" + ], + [ + "10141.47", + "0.16828063" + ], + [ + "10142.99", + "0.00400000" + ], + [ + "10143.00", + "0.17000000" + ], + [ + "10145.85", + "0.01495193" + ], + [ + "10147.00", + "0.02000000" + ], + [ + "10150.00", + "0.50000000" + ], + [ + "10152.17", + "0.14000000" + ], + [ + "10153.68", + "0.00077804" + ], + [ + "10166.08", + "0.00077709" + ], + [ + "10167.00", + "0.02000000" + ], + [ + "10169.00", + "0.00700000" + ], + [ + "10170.30", + "0.00200000" + ], + [ + "10171.00", + "0.03500000" + ], + [ + "10178.48", + "0.00077615" + ], + [ + "10178.72", + "0.00982442" + ], + [ + "10180.00", + "0.08166116" + ], + [ + "10186.86", + "0.05500000" + ], + [ + "10187.00", + "0.02000000" + ], + [ + "10198.99", + "1.00000000" + ], + [ + "10199.00", + "7.71951250" + ], + [ + "10200.00", + "1.44485096" + ], + [ + "10200.73", + "0.12400000" + ], + [ + "10207.00", + "0.02000000" + ], + [ + "10210.00", + "0.00080000" + ], + [ + "10212.00", + "0.38000000" + ], + [ + "10221.00", + "0.70000000" + ], + [ + "10224.71", + "0.00265124" + ], + [ + "10225.00", + "0.03975986" + ], + [ + "10227.00", + "0.02000000" + ], + [ + "10230.00", + "0.15078173" + ], + [ + "10234.00", + "0.08500000" + ], + [ + "10237.00", + "0.05122809" + ], + [ + "10239.22", + "0.01370000" + ], + [ + "10242.40", + "0.00077130" + ], + [ + "10244.83", + "0.30620000" + ], + [ + "10247.00", + "0.02000000" + ], + [ + "10248.98", + "1.00000000" + ], + [ + "10250.00", + "1.01559140" + ], + [ + "10250.60", + "0.43000000" + ], + [ + "10251.60", + "0.39400000" + ], + [ + "10254.80", + "0.00077037" + ], + [ + "10260.00", + "0.09038209" + ], + [ + "10267.00", + "0.02000000" + ], + [ + "10267.20", + "0.00076944" + ], + [ + "10279.60", + "0.00076851" + ], + [ + "10285.00", + "0.01463000" + ], + [ + "10287.00", + "0.02000000" + ], + [ + "10290.00", + "0.10000000" + ], + [ + "10292.00", + "0.00076759" + ], + [ + "10293.00", + "0.00430000" + ], + [ + "10298.99", + "1.00000000" + ], + [ + "10300.00", + "5.24820958" + ], + [ + "10307.00", + "0.02000000" + ], + [ + "10310.00", + "0.00156923" + ], + [ + "10318.00", + "0.00076565" + ], + [ + "10322.00", + "1.60000000" + ], + [ + "10327.00", + "0.02000000" + ], + [ + "10330.00", + "5.00000000" + ], + [ + "10331.00", + "0.00076469" + ], + [ + "10331.55", + "0.00679903" + ], + [ + "10333.65", + "0.00967712" + ], + [ + "10336.00", + "0.45530203" + ], + [ + "10344.00", + "0.00076373" + ], + [ + "10345.00", + "0.60000000" + ], + [ + "10347.00", + "0.02000000" + ], + [ + "10348.42", + "0.00065400" + ], + [ + "10350.00", + "0.03718282" + ], + [ + "10357.00", + "0.00076277" + ], + [ + "10366.00", + "0.07600000" + ], + [ + "10367.00", + "0.02000000" + ], + [ + "10370.00", + "0.00076181" + ], + [ + "10377.00", + "0.05000000" + ], + [ + "10383.00", + "0.00076086" + ], + [ + "10383.65", + "0.00963167" + ], + [ + "10387.00", + "0.02000000" + ], + [ + "10388.00", + "1.60000000" + ], + [ + "10396.00", + "0.02375991" + ], + [ + "10399.00", + "0.24000000" + ], + [ + "10400.00", + "2.58854813" + ], + [ + "10403.60", + "0.10200000" + ], + [ + "10407.00", + "0.02000000" + ], + [ + "10407.01", + "0.01000000" + ], + [ + "10409.00", + "0.00075896" + ], + [ + "10410.00", + "0.00160000" + ], + [ + "10414.00", + "0.00300000" + ], + [ + "10417.83", + "0.31580000" + ], + [ + "10422.00", + "0.00075801" + ], + [ + "10424.60", + "0.41000000" + ], + [ + "10427.00", + "0.02000000" + ], + [ + "10431.74", + "0.00049800" + ], + [ + "10435.00", + "0.00075707" + ], + [ + "10445.00", + "0.70000000" + ], + [ + "10446.10", + "0.00495400" + ], + [ + "10447.00", + "0.02000000" + ], + [ + "10448.00", + "0.00075613" + ], + [ + "10448.97", + "0.25000000" + ], + [ + "10450.00", + "0.00105964" + ], + [ + "10453.55", + "0.46900000" + ], + [ + "10455.00", + "0.02800000" + ], + [ + "10466.00", + "0.05000000" + ], + [ + "10467.00", + "0.02000000" + ], + [ + "10470.00", + "0.00075713" + ], + [ + "10477.89", + "0.01330000" + ], + [ + "10480.50", + "0.00075378" + ], + [ + "10486.32", + "0.03028585" + ], + [ + "10487.00", + "0.02000000" + ], + [ + "10493.50", + "0.00075285" + ], + [ + "10498.34", + "0.01000000" + ], + [ + "10498.98", + "1.00000000" + ], + [ + "10499.00", + "1.00000000" + ], + [ + "10500.00", + "24.54547265" + ], + [ + "10502.69", + "0.27614953" + ], + [ + "10506.50", + "0.00075192" + ], + [ + "10507.00", + "0.02000000" + ], + [ + "10508.64", + "0.05000000" + ], + [ + "10510.00", + "0.00080000" + ], + [ + "10511.00", + "0.09500000" + ], + [ + "10513.36", + "0.03515055" + ], + [ + "10519.50", + "0.00075099" + ], + [ + "10521.00", + "0.25000000" + ], + [ + "10527.00", + "0.02000000" + ], + [ + "10530.00", + "0.00125700" + ], + [ + "10532.50", + "0.00075006" + ], + [ + "10533.65", + "0.00949339" + ], + [ + "10543.00", + "0.20000000" + ], + [ + "10545.50", + "0.00074913" + ], + [ + "10547.00", + "0.02000000" + ], + [ + "10550.00", + "0.25000000" + ], + [ + "10555.00", + "0.10000000" + ], + [ + "10556.60", + "0.10400000" + ], + [ + "10558.50", + "0.00074821" + ], + [ + "10566.00", + "1.00000000" + ], + [ + "10567.00", + "0.02000000" + ], + [ + "10571.50", + "0.00074729" + ], + [ + "10581.30", + "0.00074729" + ], + [ + "10587.00", + "0.02000000" + ], + [ + "10590.83", + "0.32540000" + ], + [ + "10599.00", + "11.56000000" + ], + [ + "10600.00", + "0.39050126" + ], + [ + "10602.00", + "0.00430000" + ], + [ + "10605.00", + "0.01000000" + ], + [ + "10606.10", + "0.10000000" + ], + [ + "10607.00", + "0.02000000" + ], + [ + "10610.00", + "0.00080000" + ], + [ + "10620.00", + "0.00074612" + ], + [ + "10624.23", + "0.00056300" + ], + [ + "10627.00", + "0.02000000" + ], + [ + "10631.40", + "0.00800000" + ], + [ + "10632.00", + "0.00074388" + ], + [ + "10640.47", + "0.01136085" + ], + [ + "10642.31", + "0.00800000" + ], + [ + "10644.00", + "0.00074388" + ], + [ + "10646.00", + "0.03300000" + ], + [ + "10647.00", + "0.02000000" + ], + [ + "10648.00", + "0.10000000" + ], + [ + "10652.45", + "0.00068000" + ], + [ + "10656.00", + "0.00074388" + ], + [ + "10666.00", + "0.00653048" + ], + [ + "10667.00", + "0.02000000" + ], + [ + "10670.00", + "0.00074253" + ], + [ + "10673.65", + "0.00936887" + ], + [ + "10676.00", + "0.21000000" + ], + [ + "10676.26", + "0.08428648" + ], + [ + "10680.00", + "0.00074388" + ], + [ + "10681.92", + "0.00800000" + ], + [ + "10682.72", + "0.05332193" + ], + [ + "10687.00", + "0.02000000" + ], + [ + "10688.68", + "0.50000000" + ], + [ + "10692.74", + "0.07000000" + ], + [ + "10700.00", + "0.20000000" + ], + [ + "10707.00", + "0.02000000" + ], + [ + "10709.60", + "0.10600000" + ], + [ + "10710.00", + "0.00080000" + ], + [ + "10719.12", + "0.14506077" + ], + [ + "10725.08", + "0.01300000" + ], + [ + "10727.00", + "0.02000000" + ], + [ + "10730.00", + "0.23073826" + ], + [ + "10746.38", + "0.00800000" + ], + [ + "10747.00", + "0.02000000" + ], + [ + "10749.00", + "0.20000000" + ], + [ + "10750.00", + "0.02526659" + ], + [ + "10755.00", + "0.32395999" + ], + [ + "10760.12", + "0.00800000" + ], + [ + "10762.59", + "0.00800000" + ], + [ + "10763.83", + "0.33500000" + ], + [ + "10767.00", + "0.02000000" + ], + [ + "10770.00", + "0.00072848" + ], + [ + "10773.65", + "0.00928191" + ], + [ + "10777.00", + "0.10000000" + ], + [ + "10780.00", + "0.00073473" + ], + [ + "10785.00", + "0.01395000" + ], + [ + "10787.00", + "0.02000000" + ], + [ + "10788.00", + "0.10500000" + ], + [ + "10799.00", + "0.01428712" + ], + [ + "10800.00", + "0.37012224" + ], + [ + "10805.97", + "0.00381503" + ], + [ + "10807.00", + "0.02000000" + ], + [ + "10810.00", + "0.00152573" + ], + [ + "10811.71", + "0.00478648" + ], + [ + "10813.00", + "0.00450000" + ], + [ + "10817.08", + "5.00000000" + ], + [ + "10820.00", + "0.00073194" + ], + [ + "10827.00", + "0.02000000" + ], + [ + "10833.00", + "0.00060001" + ], + [ + "10835.00", + "0.09675945" + ], + [ + "10839.80", + "0.00800000" + ], + [ + "10847.00", + "0.02000000" + ], + [ + "10850.00", + "0.00072300" + ], + [ + "10854.28", + "0.00800000" + ], + [ + "10862.60", + "0.10800000" + ], + [ + "10867.00", + "0.02000000" + ], + [ + "10873.65", + "0.00919654" + ], + [ + "10874.69", + "0.80037771" + ], + [ + "10887.00", + "0.02000000" + ], + [ + "10888.00", + "0.01000000" + ], + [ + "10890.00", + "0.04072030" + ], + [ + "10891.45", + "0.00098992" + ], + [ + "10894.52", + "0.18071579" + ], + [ + "10895.58", + "0.00051900" + ], + [ + "10900.00", + "0.33812656" + ], + [ + "10901.62", + "0.00800000" + ], + [ + "10907.00", + "0.02000000" + ], + [ + "10910.00", + "0.00080000" + ], + [ + "10920.00", + "0.00430000" + ], + [ + "10927.00", + "0.02000000" + ], + [ + "10930.00", + "0.00071761" + ], + [ + "10936.83", + "0.34460000" + ], + [ + "10940.00", + "0.62000000" + ], + [ + "10943.53", + "0.04314842" + ], + [ + "10945.00", + "0.10000000" + ], + [ + "10947.00", + "0.02000000" + ], + [ + "10948.97", + "0.25000000" + ], + [ + "10949.00", + "1.00000000" + ], + [ + "10949.28", + "0.00500000" + ], + [ + "10950.00", + "1.00000000" + ], + [ + "10953.00", + "0.23000000" + ], + [ + "10962.00", + "0.40000000" + ], + [ + "10967.00", + "0.02000000" + ], + [ + "10970.00", + "0.00072165" + ], + [ + "10971.71", + "0.00200000" + ], + [ + "10973.65", + "0.00911274" + ], + [ + "10978.55", + "0.01260000" + ], + [ + "10981.75", + "0.00800000" + ], + [ + "10982.00", + "0.00088933" + ], + [ + "10987.00", + "0.53000000" + ], + [ + "10987.99", + "0.00190000" + ], + [ + "10998.00", + "5.00000000" + ], + [ + "10998.75", + "1.44985494" + ], + [ + "10998.98", + "1.00000000" + ], + [ + "10999.00", + "0.32203112" + ], + [ + "11000.00", + "8.96422425" + ], + [ + "11000.21", + "0.10000000" + ], + [ + "11006.55", + "0.00800000" + ], + [ + "11007.00", + "0.02000000" + ], + [ + "11010.00", + "0.00070000" + ], + [ + "11015.60", + "0.11000000" + ], + [ + "11016.57", + "0.00500000" + ], + [ + "11018.92", + "0.00269726" + ], + [ + "11020.00", + "0.00071828" + ], + [ + "11021.00", + "0.00402112" + ], + [ + "11027.00", + "0.02000000" + ], + [ + "11039.67", + "0.00800000" + ], + [ + "11040.00", + "0.10000000" + ], + [ + "11047.00", + "0.02000000" + ], + [ + "11048.66", + "0.01000000" + ], + [ + "11060.00", + "0.23786227" + ], + [ + "11065.00", + "0.11500000" + ], + [ + "11067.00", + "0.02000000" + ], + [ + "11067.38", + "0.01000000" + ], + [ + "11070.00", + "0.00071495" + ], + [ + "11079.00", + "0.00396370" + ], + [ + "11087.00", + "0.02000000" + ], + [ + "11088.00", + "0.11880000" + ], + [ + "11091.00", + "0.05900000" + ], + [ + "11095.00", + "10.63200000" + ], + [ + "11100.00", + "0.45540973" + ], + [ + "11103.00", + "0.02000000" + ], + [ + "11107.00", + "0.02000000" + ], + [ + "11109.83", + "0.22580000" + ], + [ + "11111.00", + "0.81931564" + ], + [ + "11120.00", + "0.00071165" + ], + [ + "11121.00", + "0.00107613" + ], + [ + "11122.00", + "0.00800000" + ], + [ + "11125.24", + "0.00390000" + ], + [ + "11127.00", + "0.02000000" + ], + [ + "11128.00", + "0.00097200" + ], + [ + "11134.40", + "0.89736403" + ], + [ + "11135.01", + "0.10458500" + ], + [ + "11140.00", + "0.10000000" + ], + [ + "11147.00", + "0.02000000" + ], + [ + "11149.90", + "13.57851685" + ], + [ + "11162.47", + "0.00050500" + ], + [ + "11167.00", + "0.02000000" + ], + [ + "11168.60", + "0.11200000" + ], + [ + "11170.00", + "0.00070837" + ], + [ + "11172.44", + "0.02825244" + ], + [ + "11187.00", + "0.03000000" + ], + [ + "11190.12", + "0.00462461" + ], + [ + "11199.00", + "0.10000000" + ], + [ + "11200.00", + "2.47530000" + ], + [ + "11205.00", + "0.06542701" + ], + [ + "11207.00", + "0.02000000" + ], + [ + "11212.45", + "0.00048500" + ], + [ + "11220.00", + "0.00070513" + ], + [ + "11226.75", + "0.00800000" + ], + [ + "11227.00", + "0.02000000" + ], + [ + "11230.00", + "0.35000000" + ], + [ + "11236.92", + "0.00600000" + ], + [ + "11240.00", + "0.10000000" + ], + [ + "11246.26", + "0.05000000" + ], + [ + "11247.00", + "0.02430000" + ], + [ + "11248.00", + "0.00250000" + ], + [ + "11250.00", + "2.00000000" + ], + [ + "11251.10", + "0.22313953" + ], + [ + "11261.37", + "0.00361920" + ], + [ + "11267.00", + "0.02000000" + ], + [ + "11270.00", + "0.00070191" + ], + [ + "11273.00", + "0.35000000" + ], + [ + "11275.00", + "0.05234900" + ], + [ + "11282.83", + "0.23120000" + ], + [ + "11285.00", + "0.01333000" + ], + [ + "11287.00", + "0.03000000" + ], + [ + "11288.43", + "0.27499166" + ], + [ + "11300.00", + "50.46862968" + ], + [ + "11303.65", + "0.00884721" + ], + [ + "11307.00", + "0.02000000" + ], + [ + "11314.55", + "0.00442969" + ], + [ + "11320.00", + "0.00069873" + ], + [ + "11321.60", + "0.11400000" + ], + [ + "11324.00", + "1.00000000" + ], + [ + "11324.84", + "0.00800000" + ], + [ + "11327.00", + "0.52000000" + ], + [ + "11334.04", + "0.00800000" + ], + [ + "11336.36", + "0.00800000" + ], + [ + "11340.00", + "0.15000000" + ], + [ + "11342.00", + "0.12500000" + ], + [ + "11347.00", + "0.02000000" + ], + [ + "11348.45", + "0.00441646" + ], + [ + "11350.00", + "0.17702875" + ], + [ + "11359.00", + "0.10000000" + ], + [ + "11360.00", + "0.00069620" + ], + [ + "11367.00", + "0.02000000" + ], + [ + "11369.00", + "8.99204314" + ], + [ + "11383.00", + "0.15000000" + ], + [ + "11387.00", + "0.03000000" + ], + [ + "11387.79", + "0.00800000" + ], + [ + "11388.00", + "0.46106478" + ], + [ + "11394.41", + "0.00800000" + ], + [ + "11400.00", + "0.09223759" + ], + [ + "11402.47", + "0.01840541" + ], + [ + "11402.58", + "0.00439550" + ], + [ + "11407.00", + "0.02000000" + ], + [ + "11407.80", + "0.20000000" + ], + [ + "11420.00", + "0.10000000" + ], + [ + "11424.68", + "1.00000000" + ], + [ + "11424.90", + "0.00049100" + ], + [ + "11427.00", + "0.02000000" + ], + [ + "11431.24", + "0.06000000" + ], + [ + "11436.79", + "0.06398632" + ], + [ + "11440.00", + "0.00069120" + ], + [ + "11447.00", + "0.02000000" + ], + [ + "11447.31", + "0.16200000" + ], + [ + "11448.00", + "0.20000000" + ], + [ + "11448.97", + "0.25000000" + ], + [ + "11455.83", + "0.23660000" + ], + [ + "11467.00", + "0.02000000" + ], + [ + "11473.65", + "0.00871562" + ], + [ + "11474.60", + "0.11600000" + ], + [ + "11480.00", + "0.00068873" + ], + [ + "11487.00", + "0.38059080" + ], + [ + "11489.48", + "0.00800000" + ], + [ + "11498.00", + "0.01000000" + ], + [ + "11499.00", + "0.01364500" + ], + [ + "11500.00", + "10.11790043" + ], + [ + "11507.00", + "0.29000000" + ], + [ + "11508.00", + "0.20000000" + ], + [ + "11520.00", + "0.00068627" + ], + [ + "11527.00", + "0.02000000" + ], + [ + "11535.00", + "0.36038916" + ], + [ + "11547.00", + "0.02000000" + ], + [ + "11555.00", + "0.10000000" + ], + [ + "11560.00", + "0.00068384" + ], + [ + "11567.00", + "0.02000000" + ], + [ + "11581.78", + "0.00446823" + ], + [ + "11585.00", + "0.38930000" + ], + [ + "11587.00", + "0.03000000" + ], + [ + "11589.10", + "0.00600000" + ], + [ + "11592.91", + "0.00273226" + ], + [ + "11596.00", + "11.16000000" + ], + [ + "11597.07", + "0.30580300" + ], + [ + "11600.00", + "2.15068142" + ], + [ + "11607.00", + "0.02000000" + ], + [ + "11610.00", + "0.22573325" + ], + [ + "11627.00", + "0.02000000" + ], + [ + "11627.60", + "0.11800000" + ], + [ + "11628.83", + "0.06600000" + ], + [ + "11630.00", + "1.09859489" + ], + [ + "11639.00", + "0.50000000" + ], + [ + "11640.00", + "0.08653434" + ], + [ + "11645.00", + "0.11054362" + ], + [ + "11647.00", + "0.02000000" + ], + [ + "11667.00", + "0.02000000" + ], + [ + "11670.00", + "0.05000000" + ], + [ + "11675.06", + "0.00800000" + ], + [ + "11680.00", + "0.00567663" + ], + [ + "11682.86", + "0.00047900" + ], + [ + "11687.00", + "0.02000000" + ], + [ + "11690.00", + "0.17276292" + ], + [ + "11699.97", + "0.01632662" + ], + [ + "11700.00", + "2.54132167" + ], + [ + "11700.94", + "0.01356696" + ], + [ + "11707.00", + "0.02000000" + ], + [ + "11712.00", + "0.02343789" + ], + [ + "11713.00", + "0.00078100" + ], + [ + "11716.00", + "0.03319539" + ], + [ + "11717.22", + "0.19000000" + ], + [ + "11719.09", + "0.01351000" + ], + [ + "11720.00", + "2.24068259" + ], + [ + "11723.06", + "3.00000000" + ], + [ + "11724.91", + "0.29123471" + ], + [ + "11727.00", + "0.02000000" + ], + [ + "11732.58", + "0.17135000" + ], + [ + "11732.85", + "0.09571777" + ], + [ + "11744.45", + "0.07000000" + ], + [ + "11745.00", + "0.00200000" + ], + [ + "11745.90", + "0.00500000" + ], + [ + "11745.99", + "0.00347932" + ], + [ + "11747.00", + "0.02000000" + ], + [ + "11750.00", + "0.60479521" + ], + [ + "11760.00", + "0.00114027" + ], + [ + "11762.30", + "0.00043800" + ], + [ + "11767.00", + "0.02000000" + ], + [ + "11770.00", + "0.13800000" + ], + [ + "11777.77", + "0.03300000" + ], + [ + "11780.60", + "0.12000000" + ], + [ + "11785.00", + "0.01276000" + ], + [ + "11786.00", + "1.00000000" + ], + [ + "11786.28", + "0.00566629" + ], + [ + "11787.00", + "0.02000000" + ], + [ + "11789.34", + "0.10161563" + ], + [ + "11799.87", + "0.20000000" + ], + [ + "11800.00", + "4.07141217" + ], + [ + "11801.00", + "2.42154670" + ], + [ + "11801.83", + "0.06720000" + ], + [ + "11807.00", + "0.02000000" + ], + [ + "11812.50", + "0.01000000" + ], + [ + "11820.00", + "0.00180000" + ], + [ + "11827.00", + "0.02000000" + ], + [ + "11828.37", + "0.00071910" + ], + [ + "11832.52", + "0.00128374" + ], + [ + "11847.00", + "0.02000000" + ], + [ + "11847.05", + "0.00500000" + ], + [ + "11850.00", + "0.45000000" + ], + [ + "11867.00", + "0.02000000" + ], + [ + "11867.09", + "0.00043000" + ], + [ + "11870.51", + "0.00085956" + ], + [ + "11876.50", + "0.03165054" + ], + [ + "11876.54", + "0.02500000" + ], + [ + "11878.14", + "0.00051018" + ], + [ + "11880.00", + "0.50364944" + ], + [ + "11885.00", + "0.06870000" + ], + [ + "11887.00", + "0.02000000" + ], + [ + "11888.00", + "0.01000000" + ], + [ + "11890.00", + "0.16040000" + ], + [ + "11894.00", + "0.03127359" + ], + [ + "11900.00", + "1.37045200" + ], + [ + "11900.44", + "0.50000000" + ], + [ + "11900.68", + "0.00108365" + ], + [ + "11907.00", + "0.02000000" + ], + [ + "11910.24", + "0.00178954" + ], + [ + "11915.00", + "0.00600000" + ], + [ + "11923.00", + "5.00000000" + ], + [ + "11927.00", + "0.02000000" + ], + [ + "11930.00", + "0.00411500" + ], + [ + "11932.00", + "0.00430000" + ], + [ + "11935.00", + "0.00144478" + ], + [ + "11936.37", + "0.00046700" + ], + [ + "11946.26", + "0.00241200" + ], + [ + "11947.00", + "0.02000000" + ], + [ + "11947.61", + "0.00500000" + ], + [ + "11948.97", + "0.25000000" + ], + [ + "11950.00", + "0.41997426" + ], + [ + "11967.00", + "0.02000000" + ], + [ + "11970.00", + "0.01000000" + ], + [ + "11974.83", + "0.06840000" + ], + [ + "11980.00", + "0.05000000" + ], + [ + "11987.00", + "0.05500000" + ], + [ + "11987.14", + "0.00431713" + ], + [ + "11988.00", + "0.00986701" + ], + [ + "11989.00", + "0.14774800" + ], + [ + "11990.00", + "0.00066722" + ], + [ + "11990.55", + "0.00700000" + ], + [ + "11990.92", + "0.00836000" + ], + [ + "11994.94", + "0.01000000" + ], + [ + "11998.00", + "10.80000000" + ], + [ + "11998.71", + "0.00254198" + ], + [ + "11998.90", + "2.00000000" + ], + [ + "11999.00", + "0.79098409" + ], + [ + "12000.00", + "5.34790186" + ], + [ + "12000.31", + "0.18112875" + ], + [ + "12007.00", + "0.02000000" + ], + [ + "12012.12", + "0.05000000" + ], + [ + "12027.00", + "0.02000000" + ], + [ + "12046.27", + "0.00050306" + ], + [ + "12047.00", + "0.02000000" + ], + [ + "12050.00", + "3.02284672" + ], + [ + "12060.00", + "0.10000867" + ], + [ + "12067.00", + "0.02000000" + ], + [ + "12070.20", + "1.51655496" + ], + [ + "12074.74", + "0.00068083" + ], + [ + "12081.00", + "0.80615533" + ], + [ + "12087.00", + "0.02000000" + ], + [ + "12089.00", + "0.03622277" + ], + [ + "12095.14", + "0.00248726" + ], + [ + "12100.00", + "1.03000000" + ], + [ + "12107.00", + "0.02000000" + ], + [ + "12119.96", + "0.00324084" + ], + [ + "12120.00", + "0.18650000" + ], + [ + "12125.00", + "0.73367049" + ], + [ + "12127.00", + "0.02000000" + ], + [ + "12131.00", + "0.00500000" + ], + [ + "12138.51", + "0.00700000" + ], + [ + "12140.20", + "4.79284132" + ], + [ + "12140.76", + "0.00800000" + ], + [ + "12147.00", + "0.02000000" + ], + [ + "12147.83", + "0.06960000" + ], + [ + "12167.00", + "0.02000000" + ], + [ + "12173.65", + "0.00821241" + ], + [ + "12177.48", + "0.00700000" + ], + [ + "12185.42", + "0.00045600" + ], + [ + "12187.00", + "0.02000000" + ], + [ + "12188.00", + "0.08880000" + ], + [ + "12190.00", + "0.15042000" + ], + [ + "12195.00", + "1.81000000" + ], + [ + "12200.00", + "22.09815560" + ], + [ + "12206.82", + "0.05000000" + ], + [ + "12207.00", + "0.02000000" + ], + [ + "12212.25", + "0.00500000" + ], + [ + "12220.00", + "0.00172000" + ], + [ + "12222.00", + "0.06667898" + ], + [ + "12226.42", + "0.00100230" + ], + [ + "12227.00", + "0.02000000" + ], + [ + "12233.00", + "0.07880000" + ], + [ + "12247.00", + "0.02000000" + ], + [ + "12258.42", + "0.00250000" + ], + [ + "12260.00", + "0.10000000" + ], + [ + "12267.00", + "0.02000000" + ], + [ + "12285.00", + "0.01282800" + ], + [ + "12287.00", + "0.02000000" + ], + [ + "12290.00", + "0.00430000" + ], + [ + "12292.82", + "0.00500000" + ], + [ + "12297.82", + "0.00700000" + ], + [ + "12299.00", + "0.02000000" + ], + [ + "12300.00", + "0.03340000" + ], + [ + "12307.00", + "0.02000000" + ], + [ + "12309.96", + "0.01000000" + ], + [ + "12320.83", + "0.07080000" + ], + [ + "12326.21", + "0.00065704" + ], + [ + "12327.00", + "0.02000000" + ], + [ + "12342.27", + "0.00612170" + ], + [ + "12345.67", + "0.20000000" + ], + [ + "12347.00", + "0.02000000" + ], + [ + "12367.00", + "0.02000000" + ], + [ + "12387.00", + "0.02000000" + ], + [ + "12398.00", + "0.08000000" + ], + [ + "12398.09", + "0.00484699" + ], + [ + "12399.00", + "0.10000000" + ], + [ + "12400.00", + "0.28029369" + ], + [ + "12407.00", + "0.02000000" + ], + [ + "12427.00", + "0.02000000" + ], + [ + "12430.01", + "0.00044500" + ], + [ + "12447.00", + "0.02000000" + ], + [ + "12448.97", + "0.25000000" + ], + [ + "12450.00", + "0.12556350" + ], + [ + "12467.00", + "0.02000000" + ], + [ + "12485.00", + "0.00350000" + ], + [ + "12487.00", + "0.02000000" + ], + [ + "12491.55", + "0.00041300" + ], + [ + "12493.83", + "0.07200000" + ], + [ + "12496.00", + "12.56000000" + ], + [ + "12498.98", + "1.00000000" + ], + [ + "12498.99", + "1.00000000" + ], + [ + "12500.00", + "1.14555652" + ], + [ + "12507.00", + "0.02000000" + ], + [ + "12521.00", + "0.50000000" + ], + [ + "12527.00", + "0.02000000" + ], + [ + "12547.00", + "0.02000000" + ], + [ + "12550.00", + "1.00000000" + ], + [ + "12558.42", + "0.00400000" + ], + [ + "12567.00", + "0.02000000" + ], + [ + "12578.00", + "0.10000000" + ], + [ + "12579.00", + "0.00700000" + ], + [ + "12582.83", + "0.00064364" + ], + [ + "12587.00", + "0.02000000" + ], + [ + "12600.00", + "1.05590656" + ], + [ + "12607.00", + "0.02000000" + ], + [ + "12619.45", + "0.00118274" + ], + [ + "12627.00", + "0.02000000" + ], + [ + "12641.94", + "0.03000000" + ], + [ + "12647.00", + "0.02000000" + ], + [ + "12647.63", + "0.00309929" + ], + [ + "12648.42", + "0.00250000" + ], + [ + "12659.00", + "0.00430000" + ], + [ + "12666.00", + "0.01000000" + ], + [ + "12666.83", + "0.07320000" + ], + [ + "12667.00", + "0.02000000" + ], + [ + "12675.48", + "0.00225986" + ], + [ + "12687.00", + "0.02000000" + ], + [ + "12696.06", + "0.00040600" + ], + [ + "12700.00", + "2.79330266" + ], + [ + "12700.33", + "0.00238000" + ], + [ + "12702.90", + "0.19034000" + ], + [ + "12707.00", + "0.02000000" + ], + [ + "12719.40", + "0.00040800" + ], + [ + "12727.00", + "0.02000000" + ], + [ + "12731.00", + "0.20000000" + ], + [ + "12735.00", + "0.10000000" + ], + [ + "12740.00", + "0.00062794" + ], + [ + "12747.00", + "0.02000000" + ], + [ + "12767.00", + "0.02000000" + ], + [ + "12777.26", + "0.05000000" + ], + [ + "12785.00", + "0.01176400" + ], + [ + "12785.54", + "0.00172445" + ], + [ + "12787.00", + "0.02000000" + ], + [ + "12794.00", + "0.01217732" + ], + [ + "12800.00", + "5.95568960" + ], + [ + "12807.00", + "0.02000000" + ], + [ + "12823.00", + "0.00600000" + ], + [ + "12827.00", + "0.02000000" + ], + [ + "12839.83", + "0.07440000" + ], + [ + "12844.01", + "0.08000000" + ], + [ + "12844.93", + "0.00062971" + ], + [ + "12846.85", + "0.00040000" + ], + [ + "12847.00", + "0.02000000" + ], + [ + "12850.00", + "0.00931334" + ], + [ + "12867.00", + "0.02000000" + ], + [ + "12869.00", + "0.13000000" + ], + [ + "12875.00", + "0.04000000" + ], + [ + "12887.00", + "0.02000000" + ], + [ + "12888.00", + "0.09880000" + ], + [ + "12894.66", + "0.01000000" + ], + [ + "12898.00", + "0.38030000" + ], + [ + "12899.00", + "12.00000000" + ], + [ + "12900.00", + "0.09600000" + ], + [ + "12901.01", + "0.00700000" + ], + [ + "12907.00", + "0.02000000" + ], + [ + "12911.00", + "0.05588800" + ], + [ + "12922.00", + "0.01100000" + ], + [ + "12927.00", + "0.02000000" + ], + [ + "12938.92", + "0.00774000" + ], + [ + "12947.00", + "0.02000000" + ], + [ + "12948.97", + "0.25000000" + ], + [ + "12950.00", + "0.35000000" + ], + [ + "12967.00", + "0.02000000" + ], + [ + "12973.64", + "0.00700000" + ], + [ + "12980.00", + "0.11609570" + ], + [ + "12987.00", + "0.44509144" + ], + [ + "12994.94", + "0.01000000" + ], + [ + "12999.00", + "0.03573051" + ], + [ + "13000.00", + "3.24797430" + ], + [ + "13000.65", + "0.00139563" + ], + [ + "13003.45", + "0.11625312" + ], + [ + "13004.19", + "0.00154000" + ], + [ + "13004.54", + "0.00700000" + ], + [ + "13007.00", + "0.02000000" + ], + [ + "13007.39", + "1.00000000" + ], + [ + "13012.83", + "0.07560000" + ], + [ + "13027.00", + "0.02000000" + ], + [ + "13031.00", + "0.07880000" + ], + [ + "13039.00", + "0.00430000" + ], + [ + "13046.77", + "0.00543404" + ], + [ + "13047.00", + "0.02000000" + ], + [ + "13067.00", + "0.02000000" + ], + [ + "13076.00", + "0.00500000" + ], + [ + "13087.00", + "0.02000000" + ], + [ + "13107.00", + "0.02000000" + ], + [ + "13112.33", + "0.00060911" + ], + [ + "13122.53", + "0.00046180" + ], + [ + "13127.00", + "0.02000000" + ], + [ + "13131.00", + "0.07130000" + ], + [ + "13147.00", + "0.02000000" + ], + [ + "13167.00", + "0.02000000" + ], + [ + "13187.00", + "0.02000000" + ], + [ + "13190.00", + "0.15000000" + ], + [ + "13200.00", + "0.10500000" + ], + [ + "13207.00", + "0.02000000" + ], + [ + "13220.00", + "0.20915912" + ], + [ + "13227.00", + "0.02000000" + ], + [ + "13231.00", + "0.50000000" + ], + [ + "13247.00", + "0.02000000" + ], + [ + "13250.00", + "0.74100699" + ], + [ + "13260.65", + "0.01538462" + ], + [ + "13261.00", + "0.07130000" + ], + [ + "13267.00", + "0.02000000" + ], + [ + "13270.00", + "0.02000000" + ], + [ + "13273.00", + "0.00500000" + ], + [ + "13285.00", + "0.01132000" + ], + [ + "13287.00", + "0.02000000" + ], + [ + "13296.35", + "0.01534331" + ], + [ + "13299.00", + "0.00100000" + ], + [ + "13300.00", + "22.12941679" + ], + [ + "13307.00", + "0.02000000" + ], + [ + "13327.00", + "0.02000000" + ], + [ + "13332.06", + "0.01530222" + ], + [ + "13333.00", + "0.79452840" + ], + [ + "13346.85", + "0.00500000" + ], + [ + "13347.00", + "0.02000000" + ], + [ + "13350.00", + "0.06000000" + ], + [ + "13359.00", + "0.10000000" + ], + [ + "13359.04", + "0.00500000" + ], + [ + "13367.00", + "0.02000000" + ], + [ + "13376.74", + "0.00158564" + ], + [ + "13377.00", + "0.00218899" + ], + [ + "13385.38", + "0.00059365" + ], + [ + "13387.00", + "0.02000000" + ], + [ + "13391.00", + "0.07130000" + ], + [ + "13400.00", + "0.79582816" + ], + [ + "13403.46", + "0.01522070" + ], + [ + "13407.00", + "0.02000000" + ], + [ + "13411.37", + "0.01267700" + ], + [ + "13421.48", + "0.00039784" + ], + [ + "13421.76", + "0.05571031" + ], + [ + "13422.88", + "0.00293657" + ], + [ + "13427.00", + "0.02000000" + ], + [ + "13439.16", + "0.01518027" + ], + [ + "13447.00", + "0.02000000" + ], + [ + "13450.00", + "0.13000000" + ], + [ + "13458.17", + "0.00700000" + ], + [ + "13467.00", + "0.02000000" + ], + [ + "13474.86", + "0.01514005" + ], + [ + "13487.00", + "0.02000000" + ], + [ + "13490.00", + "0.02897050" + ], + [ + "13494.78", + "0.00700000" + ], + [ + "13500.00", + "4.59001017" + ], + [ + "13501.00", + "0.07130000" + ], + [ + "13505.00", + "0.31815837" + ], + [ + "13507.00", + "0.02000000" + ], + [ + "13510.56", + "0.01510004" + ], + [ + "13527.00", + "0.02000000" + ], + [ + "13546.27", + "0.01506024" + ], + [ + "13547.00", + "0.02000000" + ], + [ + "13552.00", + "0.01000000" + ], + [ + "13556.79", + "0.01000000" + ], + [ + "13566.00", + "0.00700000" + ], + [ + "13567.00", + "0.02000000" + ], + [ + "13569.56", + "0.01000000" + ], + [ + "13581.97", + "0.01502065" + ], + [ + "13587.00", + "0.02000000" + ], + [ + "13587.57", + "0.00500000" + ], + [ + "13590.80", + "0.00500000" + ], + [ + "13597.92", + "0.00700000" + ], + [ + "13599.30", + "0.00700000" + ], + [ + "13600.00", + "0.51000000" + ], + [ + "13604.10", + "0.00218247" + ], + [ + "13607.00", + "0.02000000" + ], + [ + "13617.67", + "0.01498127" + ], + [ + "13618.00", + "0.00500000" + ], + [ + "13627.00", + "0.02000000" + ], + [ + "13632.58", + "0.00138000" + ], + [ + "13647.00", + "0.02000000" + ], + [ + "13653.37", + "0.01494210" + ], + [ + "13664.20", + "0.00058004" + ], + [ + "13667.00", + "0.02000000" + ], + [ + "13676.31", + "0.03040500" + ], + [ + "13687.00", + "0.02000000" + ], + [ + "13689.07", + "0.01490313" + ], + [ + "13700.50", + "0.00038769" + ], + [ + "13707.00", + "0.02000000" + ], + [ + "13724.77", + "0.01486436" + ], + [ + "13727.00", + "0.02000000" + ], + [ + "13730.14", + "0.00200000" + ], + [ + "13737.00", + "0.20000000" + ], + [ + "13743.63", + "0.22430542" + ], + [ + "13747.00", + "0.02500000" + ], + [ + "13760.48", + "0.01482580" + ], + [ + "13767.00", + "0.02000000" + ], + [ + "13781.00", + "0.07130000" + ], + [ + "13785.00", + "0.01091000" + ], + [ + "13787.00", + "0.02000000" + ], + [ + "13789.95", + "0.01000000" + ], + [ + "13796.18", + "0.01478743" + ], + [ + "13800.00", + "2.00000000" + ], + [ + "13800.59", + "0.00399285" + ], + [ + "13807.00", + "0.02000000" + ], + [ + "13810.11", + "0.26244000" + ], + [ + "13827.00", + "0.02000000" + ], + [ + "13834.04", + "0.00278445" + ], + [ + "13834.10", + "0.00131199" + ], + [ + "13836.00", + "0.01000000" + ], + [ + "13846.11", + "0.01838500" + ], + [ + "13847.00", + "0.02000000" + ], + [ + "13850.00", + "0.02500000" + ], + [ + "13860.00", + "0.10000000" + ], + [ + "13867.00", + "0.02000000" + ], + [ + "13867.58", + "0.01471129" + ], + [ + "13887.00", + "0.02000000" + ], + [ + "13888.00", + "0.01000000" + ], + [ + "13898.69", + "0.00398700" + ], + [ + "13900.00", + "0.01000000" + ], + [ + "13903.28", + "0.01467351" + ], + [ + "13907.00", + "0.02000000" + ], + [ + "13911.00", + "0.07130000" + ], + [ + "13912.88", + "0.10000000" + ], + [ + "13927.00", + "0.02000000" + ], + [ + "13938.98", + "0.01463593" + ], + [ + "13940.00", + "0.13000000" + ], + [ + "13947.00", + "0.02000000" + ], + [ + "13948.66", + "0.00056822" + ], + [ + "13948.97", + "0.35000000" + ], + [ + "13956.79", + "0.03000000" + ], + [ + "13967.00", + "0.02000000" + ], + [ + "13974.69", + "0.01459854" + ], + [ + "13980.00", + "0.08440603" + ], + [ + "13987.00", + "0.02000000" + ], + [ + "13994.74", + "0.01000000" + ], + [ + "14000.00", + "4.95355605" + ], + [ + "14000.39", + "0.00600000" + ], + [ + "14001.00", + "0.50000000" + ], + [ + "14004.56", + "0.00773130" + ], + [ + "14007.00", + "0.02000000" + ], + [ + "14007.32", + "0.10788102" + ], + [ + "14010.39", + "0.01456134" + ], + [ + "14016.37", + "0.04000000" + ], + [ + "14027.00", + "0.02000000" + ], + [ + "14037.38", + "0.00428151" + ], + [ + "14041.00", + "0.07130000" + ], + [ + "14046.09", + "0.01452433" + ], + [ + "14047.00", + "0.02000000" + ], + [ + "14050.00", + "0.02000000" + ], + [ + "14051.55", + "0.15736300" + ], + [ + "14067.00", + "0.02000000" + ], + [ + "14081.79", + "0.01448750" + ], + [ + "14087.00", + "0.02000000" + ], + [ + "14089.74", + "0.01806358" + ], + [ + "14093.97", + "0.00052568" + ], + [ + "14100.00", + "2.37900000" + ], + [ + "14107.00", + "0.02000000" + ], + [ + "14120.06", + "0.20909886" + ], + [ + "14120.98", + "0.00198836" + ], + [ + "14121.00", + "0.00500000" + ], + [ + "14125.38", + "0.01801802" + ], + [ + "14127.00", + "0.02000000" + ], + [ + "14147.00", + "0.02000000" + ], + [ + "14150.00", + "0.01073284" + ], + [ + "14161.01", + "0.01797268" + ], + [ + "14167.00", + "0.02000000" + ], + [ + "14171.00", + "0.07130000" + ], + [ + "14187.00", + "0.02000000" + ], + [ + "14190.00", + "0.30000000" + ], + [ + "14196.64", + "0.01792757" + ], + [ + "14200.00", + "0.10000000" + ], + [ + "14203.33", + "0.22287000" + ], + [ + "14207.00", + "0.02000000" + ], + [ + "14208.00", + "0.05000000" + ], + [ + "14211.63", + "0.00141000" + ], + [ + "14211.76", + "0.01000000" + ], + [ + "14212.27", + "0.00037360" + ], + [ + "14227.00", + "0.02000000" + ], + [ + "14232.27", + "0.01788269" + ], + [ + "14239.27", + "0.00055662" + ], + [ + "14240.00", + "0.00056180" + ], + [ + "14247.00", + "0.02000000" + ], + [ + "14247.59", + "0.00703000" + ], + [ + "14250.00", + "0.06300000" + ], + [ + "14267.00", + "0.02000000" + ], + [ + "14267.90", + "0.01783803" + ], + [ + "14276.66", + "0.00381010" + ], + [ + "14285.00", + "0.01053000" + ], + [ + "14287.00", + "0.02000000" + ], + [ + "14299.00", + "0.05000000" + ], + [ + "14301.00", + "0.07130000" + ], + [ + "14303.53", + "0.03558718" + ], + [ + "14307.00", + "0.02000000" + ], + [ + "14327.00", + "0.02000000" + ], + [ + "14331.62", + "0.07000000" + ], + [ + "14335.23", + "0.00202221" + ], + [ + "14343.00", + "0.09000000" + ], + [ + "14347.00", + "0.02000000" + ], + [ + "14350.00", + "0.09638581" + ], + [ + "14355.00", + "0.14004550" + ], + [ + "14358.01", + "1.00000000" + ], + [ + "14367.00", + "0.02000000" + ], + [ + "14375.00", + "0.13809487" + ], + [ + "14387.00", + "0.02000000" + ], + [ + "14400.00", + "0.01000000" + ], + [ + "14407.00", + "0.02000000" + ], + [ + "14408.00", + "0.05000000" + ], + [ + "14410.43", + "0.01766160" + ], + [ + "14427.00", + "0.02000000" + ], + [ + "14445.46", + "0.00035500" + ], + [ + "14446.06", + "0.01761804" + ], + [ + "14447.00", + "0.02000000" + ], + [ + "14449.00", + "3.00000000" + ], + [ + "14461.48", + "0.67000000" + ], + [ + "14467.00", + "0.02000000" + ], + [ + "14475.00", + "0.00416121" + ], + [ + "14480.00", + "5.03993525" + ], + [ + "14481.69", + "0.01757469" + ], + [ + "14487.00", + "0.02000000" + ], + [ + "14489.00", + "0.10000000" + ], + [ + "14500.00", + "0.86822844" + ], + [ + "14501.93", + "0.00035000" + ], + [ + "14507.00", + "0.02000000" + ], + [ + "14508.00", + "0.05000000" + ], + [ + "14517.32", + "0.01753156" + ], + [ + "14527.00", + "0.02000000" + ], + [ + "14534.00", + "0.13000000" + ], + [ + "14535.71", + "0.00054037" + ], + [ + "14538.29", + "0.00267910" + ], + [ + "14544.84", + "0.00500000" + ], + [ + "14547.00", + "0.02000000" + ], + [ + "14552.95", + "0.01748863" + ], + [ + "14560.00", + "0.00690000" + ], + [ + "14567.00", + "0.02000000" + ], + [ + "14587.00", + "0.02000000" + ], + [ + "14600.00", + "0.71956932" + ], + [ + "14607.00", + "0.02000000" + ], + [ + "14624.00", + "0.01500000" + ], + [ + "14627.00", + "0.02000000" + ], + [ + "14640.71", + "0.00035000" + ], + [ + "14647.00", + "0.02000000" + ], + [ + "14650.00", + "0.36000000" + ], + [ + "14667.00", + "0.02000000" + ], + [ + "14687.00", + "0.02000000" + ], + [ + "14700.00", + "10.88500000" + ], + [ + "14707.00", + "0.02000000" + ], + [ + "14708.94", + "0.00132000" + ], + [ + "14722.22", + "0.25000000" + ], + [ + "14723.00", + "0.10000000" + ], + [ + "14727.00", + "0.02000000" + ], + [ + "14740.33", + "0.00212444" + ], + [ + "14747.00", + "0.02000000" + ], + [ + "14753.40", + "0.00274319" + ], + [ + "14763.27", + "0.00226745" + ], + [ + "14767.00", + "0.02000000" + ], + [ + "14777.26", + "0.05000000" + ], + [ + "14785.00", + "0.01017100" + ], + [ + "14787.00", + "0.02000000" + ], + [ + "14787.13", + "0.51919623" + ], + [ + "14800.00", + "1.02080000" + ], + [ + "14803.00", + "0.03000000" + ], + [ + "14805.00", + "0.10000000" + ], + [ + "14807.00", + "0.02000000" + ], + [ + "14827.00", + "0.02000000" + ], + [ + "14838.35", + "0.00051837" + ], + [ + "14847.00", + "0.02000000" + ], + [ + "14865.29", + "0.45583602" + ], + [ + "14867.00", + "0.02000000" + ], + [ + "14887.00", + "0.02000000" + ], + [ + "14892.00", + "4.00000000" + ], + [ + "14899.90", + "0.00450000" + ], + [ + "14900.00", + "2.25048490" + ], + [ + "14907.00", + "0.02000000" + ], + [ + "14918.32", + "0.00892716" + ], + [ + "14927.00", + "0.02000000" + ], + [ + "14947.00", + "0.02000000" + ], + [ + "14948.97", + "0.34000000" + ], + [ + "14949.89", + "0.25000000" + ], + [ + "14967.00", + "0.10929350" + ], + [ + "14973.52", + "1.00000000" + ], + [ + "14987.00", + "0.02000000" + ], + [ + "14990.00", + "0.00200000" + ], + [ + "14995.00", + "1.02000000" + ], + [ + "14998.50", + "0.00100000" + ], + [ + "14999.99", + "0.06000000" + ], + [ + "15000.00", + "17.75924732" + ], + [ + "15000.78", + "0.14114187" + ], + [ + "15007.00", + "0.02000000" + ], + [ + "15025.68", + "0.02500000" + ], + [ + "15027.00", + "0.02000000" + ], + [ + "15034.44", + "0.00364875" + ], + [ + "15046.60", + "0.00055521" + ], + [ + "15047.00", + "0.02000000" + ], + [ + "15067.00", + "0.02000000" + ], + [ + "15077.72", + "0.00034720" + ], + [ + "15079.00", + "0.12000000" + ], + [ + "15087.00", + "0.02000000" + ], + [ + "15091.44", + "0.00800000" + ], + [ + "15095.00", + "1.00000000" + ], + [ + "15100.00", + "0.09170622" + ], + [ + "15107.00", + "0.02000000" + ], + [ + "15114.63", + "0.00272178" + ], + [ + "15116.31", + "0.01000000" + ], + [ + "15125.00", + "0.68300000" + ], + [ + "15125.12", + "0.00190848" + ], + [ + "15147.33", + "0.00050310" + ], + [ + "15147.84", + "0.02500000" + ], + [ + "15164.02", + "0.02850000" + ], + [ + "15183.09", + "0.00258348" + ], + [ + "15190.00", + "0.15000000" + ], + [ + "15190.52", + "0.00500000" + ], + [ + "15195.00", + "1.00000000" + ], + [ + "15200.00", + "0.10342423" + ], + [ + "15211.61", + "0.40582430" + ], + [ + "15212.31", + "0.40062000" + ], + [ + "15221.48", + "0.00549440" + ], + [ + "15232.65", + "0.24293794" + ], + [ + "15250.00", + "0.00061620" + ], + [ + "15261.23", + "0.19952000" + ], + [ + "15268.50", + "0.02839211" + ], + [ + "15270.00", + "0.00033000" + ], + [ + "15274.00", + "0.07130000" + ], + [ + "15285.00", + "0.01000000" + ], + [ + "15295.00", + "1.00000000" + ], + [ + "15300.00", + "0.25998341" + ], + [ + "15326.00", + "0.03465975" + ], + [ + "15343.80", + "0.00658020" + ], + [ + "15370.17", + "4.00000000" + ], + [ + "15374.76", + "0.00942253" + ], + [ + "15395.00", + "1.00000000" + ], + [ + "15400.00", + "0.02831955" + ], + [ + "15420.00", + "0.00548768" + ], + [ + "15448.12", + "0.00268530" + ], + [ + "15462.78", + "0.00048823" + ], + [ + "15480.00", + "0.02310000" + ], + [ + "15494.00", + "0.14000000" + ], + [ + "15495.00", + "1.00000000" + ], + [ + "15499.82", + "0.00700000" + ], + [ + "15500.00", + "2.98287455" + ], + [ + "15529.44", + "0.00650044" + ], + [ + "15550.00", + "0.00187744" + ], + [ + "15553.72", + "0.00649047" + ], + [ + "15553.80", + "0.00649047" + ], + [ + "15555.00", + "0.77957830" + ], + [ + "15564.01", + "0.00649047" + ], + [ + "15565.97", + "0.00649047" + ], + [ + "15595.00", + "1.00000000" + ], + [ + "15600.00", + "0.39308935" + ], + [ + "15600.72", + "0.00038844" + ], + [ + "15662.21", + "0.00038691" + ], + [ + "15694.69", + "0.00700000" + ], + [ + "15695.00", + "1.00000000" + ], + [ + "15695.26", + "1.00000000" + ], + [ + "15695.28", + "0.00500000" + ], + [ + "15723.35", + "0.00289830" + ], + [ + "15740.00", + "0.00050826" + ], + [ + "15750.00", + "0.50000000" + ], + [ + "15753.00", + "0.00472144" + ], + [ + "15761.16", + "0.00700000" + ], + [ + "15777.26", + "0.05000000" + ], + [ + "15784.70", + "0.00047763" + ], + [ + "15785.00", + "0.01000000" + ], + [ + "15795.00", + "1.00000000" + ], + [ + "15820.00", + "0.52673918" + ], + [ + "15831.00", + "2.00000000" + ], + [ + "15850.00", + "0.10000000" + ], + [ + "15850.05", + "0.00035520" + ], + [ + "15867.00", + "0.01000000" + ], + [ + "15875.00", + "0.05000000" + ], + [ + "15893.54", + "0.00700000" + ], + [ + "15895.00", + "1.00000000" + ], + [ + "15898.56", + "0.04000000" + ], + [ + "15900.00", + "0.10000000" + ], + [ + "15948.97", + "0.31500000" + ], + [ + "15950.00", + "0.50000000" + ], + [ + "15953.23", + "1.00000000" + ], + [ + "15962.55", + "0.66067033" + ], + [ + "15988.71", + "0.10000000" + ], + [ + "15989.00", + "0.01000000" + ], + [ + "15990.00", + "0.00050031" + ], + [ + "15995.00", + "1.00000000" + ], + [ + "15999.00", + "0.22288000" + ], + [ + "16000.00", + "7.97594916" + ], + [ + "16001.00", + "0.10980000" + ], + [ + "16002.00", + "0.11290000" + ], + [ + "16023.00", + "0.48710000" + ], + [ + "16042.04", + "0.00629107" + ], + [ + "16074.51", + "0.00628110" + ], + [ + "16074.69", + "0.00628110" + ], + [ + "16095.00", + "1.05000000" + ], + [ + "16100.00", + "0.05000000" + ], + [ + "16100.20", + "0.00627113" + ], + [ + "16117.46", + "0.00046713" + ], + [ + "16150.00", + "0.01000000" + ], + [ + "16153.98", + "0.00241739" + ], + [ + "16167.65", + "0.11500007" + ], + [ + "16179.69", + "0.04545199" + ], + [ + "16180.00", + "0.70000000" + ], + [ + "16185.53", + "0.00624122" + ], + [ + "16195.00", + "1.00000000" + ], + [ + "16200.02", + "0.06400000" + ], + [ + "16227.58", + "0.00037343" + ], + [ + "16239.00", + "0.00600000" + ], + [ + "16250.00", + "1.30000000" + ], + [ + "16270.52", + "0.01000000" + ], + [ + "16274.11", + "0.00620134" + ], + [ + "16297.39", + "0.00300000" + ], + [ + "16300.00", + "0.07800000" + ], + [ + "16319.04", + "0.75255384" + ], + [ + "16320.75", + "0.22274669" + ], + [ + "16345.00", + "1.00000000" + ], + [ + "16363.52", + "0.00321099" + ], + [ + "16416.00", + "0.02967987" + ], + [ + "16428.87", + "0.00036886" + ], + [ + "16449.00", + "0.39700467" + ], + [ + "16450.00", + "0.10000000" + ], + [ + "16453.15", + "0.00045760" + ], + [ + "16456.00", + "0.01000000" + ], + [ + "16484.00", + "0.20000000" + ], + [ + "16495.00", + "1.00000000" + ], + [ + "16500.00", + "2.88107774" + ], + [ + "16512.98", + "0.00500000" + ], + [ + "16530.00", + "1.60000000" + ], + [ + "16550.00", + "0.10000000" + ], + [ + "16563.22", + "0.00924488" + ], + [ + "16566.27", + "0.00171113" + ], + [ + "16576.26", + "0.00030300" + ], + [ + "16579.69", + "0.00500000" + ], + [ + "16580.90", + "0.06524197" + ], + [ + "16655.00", + "0.10000000" + ], + [ + "16657.00", + "0.50000000" + ], + [ + "16661.00", + "0.03020000" + ], + [ + "16667.00", + "0.75000000" + ], + [ + "16700.20", + "0.12142526" + ], + [ + "16719.13", + "0.01049151" + ], + [ + "16742.08", + "0.00940638" + ], + [ + "16745.00", + "1.00000000" + ], + [ + "16750.00", + "0.10500000" + ], + [ + "16761.99", + "0.18469000" + ], + [ + "16777.26", + "0.05000000" + ], + [ + "16785.00", + "0.01000000" + ], + [ + "16788.77", + "0.00029900" + ], + [ + "16795.68", + "0.00044767" + ], + [ + "16800.00", + "0.70026252" + ], + [ + "16802.22", + "0.00036066" + ], + [ + "16824.80", + "0.00059049" + ], + [ + "16830.85", + "0.00500000" + ], + [ + "16833.00", + "1.00000000" + ], + [ + "16866.66", + "0.01609000" + ], + [ + "16866.97", + "0.00356357" + ], + [ + "16884.56", + "0.52848000" + ], + [ + "16888.00", + "0.07000000" + ], + [ + "16890.00", + "0.10000000" + ], + [ + "16900.00", + "0.05000000" + ], + [ + "16906.50", + "0.02200000" + ], + [ + "16921.91", + "0.00755900" + ], + [ + "16931.00", + "1.00000000" + ], + [ + "16948.97", + "0.29500000" + ], + [ + "16950.00", + "0.01304861" + ], + [ + "16985.00", + "0.05000000" + ], + [ + "16989.00", + "0.01000000" + ], + [ + "16994.40", + "0.53444176" + ], + [ + "16995.00", + "1.00000000" + ], + [ + "16998.00", + "0.30000000" + ], + [ + "16998.90", + "0.02731427" + ], + [ + "16999.00", + "1.45837340" + ], + [ + "17000.00", + "3.43808601" + ], + [ + "17035.76", + "0.04000000" + ], + [ + "17036.00", + "0.09790000" + ], + [ + "17071.91", + "0.00029400" + ], + [ + "17077.07", + "0.44298000" + ], + [ + "17085.00", + "0.00360000" + ], + [ + "17095.00", + "0.06862161" + ], + [ + "17099.00", + "0.05000000" + ], + [ + "17099.97", + "0.15675382" + ], + [ + "17100.00", + "0.13180509" + ], + [ + "17120.11", + "0.01000000" + ], + [ + "17123.30", + "0.01100000" + ], + [ + "17135.76", + "0.04000000" + ], + [ + "17140.33", + "0.00035355" + ], + [ + "17176.75", + "0.20000000" + ], + [ + "17180.00", + "0.00600000" + ], + [ + "17200.00", + "1.14062871" + ], + [ + "17212.00", + "0.02343789" + ], + [ + "17221.58", + "0.04003847" + ], + [ + "17234.00", + "0.10000000" + ], + [ + "17240.00", + "0.00046404" + ], + [ + "17245.00", + "1.00000000" + ], + [ + "17250.00", + "0.53454735" + ], + [ + "17265.00", + "0.57500000" + ], + [ + "17300.00", + "0.03368631" + ], + [ + "17305.01", + "0.06000000" + ], + [ + "17327.24", + "0.00034973" + ], + [ + "17334.57", + "0.00347296" + ], + [ + "17350.00", + "0.10000000" + ], + [ + "17363.02", + "0.06000000" + ], + [ + "17388.00", + "0.01000000" + ], + [ + "17395.00", + "0.50000000" + ], + [ + "17399.00", + "0.00900000" + ], + [ + "17400.49", + "0.00028900" + ], + [ + "17403.97", + "0.51654000" + ], + [ + "17412.00", + "0.16000000" + ], + [ + "17422.46", + "0.00162825" + ], + [ + "17423.52", + "0.00100000" + ], + [ + "17430.00", + "0.05000000" + ], + [ + "17440.00", + "1.00000000" + ], + [ + "17462.00", + "0.15800000" + ], + [ + "17495.00", + "1.09394342" + ], + [ + "17498.70", + "0.00080000" + ], + [ + "17499.00", + "1.10000000" + ], + [ + "17500.00", + "2.12499918" + ], + [ + "17509.00", + "0.48710000" + ], + [ + "17532.00", + "0.10000000" + ], + [ + "17560.00", + "0.00033289" + ], + [ + "17561.07", + "0.14250106" + ], + [ + "17571.96", + "0.00221747" + ], + [ + "17582.50", + "0.00500000" + ], + [ + "17600.00", + "1.03857865" + ], + [ + "17632.00", + "0.10000000" + ], + [ + "17686.00", + "0.03100000" + ], + [ + "17688.00", + "0.61695627" + ], + [ + "17700.00", + "0.03615625" + ], + [ + "17700.77", + "0.95000000" + ], + [ + "17735.21", + "0.00054092" + ], + [ + "17738.00", + "0.10000000" + ], + [ + "17741.18", + "0.00054093" + ], + [ + "17745.00", + "1.00000000" + ], + [ + "17747.16", + "0.00054094" + ], + [ + "17750.00", + "0.02500000" + ], + [ + "17753.13", + "0.00054095" + ], + [ + "17759.10", + "0.00054096" + ], + [ + "17760.00", + "0.03000000" + ], + [ + "17765.07", + "0.00054097" + ], + [ + "17770.12", + "0.07000000" + ], + [ + "17771.04", + "0.00054098" + ], + [ + "17775.00", + "0.10000000" + ], + [ + "17777.00", + "0.04560000" + ], + [ + "17777.01", + "0.00054099" + ], + [ + "17777.26", + "0.05000000" + ], + [ + "17782.99", + "0.00054100" + ], + [ + "17785.00", + "0.01000000" + ], + [ + "17786.00", + "0.17000000" + ], + [ + "17788.96", + "0.00054101" + ], + [ + "17794.93", + "0.00054102" + ], + [ + "17800.00", + "0.28068221" + ], + [ + "17800.90", + "0.00054103" + ], + [ + "17803.12", + "0.01000000" + ], + [ + "17806.87", + "0.00054104" + ], + [ + "17812.85", + "0.00054105" + ], + [ + "17818.82", + "0.00054106" + ], + [ + "17824.79", + "0.00054107" + ], + [ + "17826.70", + "0.30000000" + ], + [ + "17830.76", + "0.00054108" + ], + [ + "17836.73", + "0.00054109" + ], + [ + "17838.00", + "0.10000000" + ], + [ + "17842.70", + "0.00054110" + ], + [ + "17848.68", + "0.00054111" + ], + [ + "17850.00", + "0.00500000" + ], + [ + "17854.65", + "0.00054112" + ], + [ + "17860.62", + "0.00054113" + ], + [ + "17862.64", + "0.00373482" + ], + [ + "17863.21", + "0.06000000" + ], + [ + "17866.59", + "0.00054114" + ], + [ + "17872.56", + "0.00054115" + ], + [ + "17878.53", + "0.00054116" + ], + [ + "17880.00", + "0.01000000" + ], + [ + "17884.51", + "0.00054117" + ], + [ + "17888.00", + "0.02000000" + ], + [ + "17889.00", + "0.01000000" + ], + [ + "17890.48", + "0.00054118" + ], + [ + "17896.45", + "0.00054119" + ], + [ + "17897.39", + "0.01000000" + ], + [ + "17900.00", + "0.44319995" + ], + [ + "17902.42", + "0.00054120" + ], + [ + "17908.39", + "0.00054121" + ], + [ + "17912.07", + "0.01213300" + ], + [ + "17914.37", + "0.00054122" + ], + [ + "17920.34", + "0.00054123" + ], + [ + "17926.31", + "0.00054124" + ], + [ + "17932.28", + "0.00054125" + ], + [ + "17938.00", + "0.10000000" + ], + [ + "17938.25", + "0.00053126" + ], + [ + "17938.58", + "0.00057859" + ], + [ + "17944.22", + "0.00053127" + ], + [ + "17948.97", + "0.29500000" + ], + [ + "17950.00", + "0.28263464" + ], + [ + "17950.20", + "0.00053128" + ], + [ + "17956.17", + "0.00053129" + ], + [ + "17957.76", + "0.00057862" + ], + [ + "17960.00", + "0.17259130" + ], + [ + "17962.14", + "0.00053130" + ], + [ + "17968.11", + "0.00053131" + ], + [ + "17974.08", + "0.00053132" + ], + [ + "17976.94", + "0.00057865" + ], + [ + "17980.05", + "0.00053133" + ], + [ + "17986.03", + "0.00053134" + ], + [ + "17987.30", + "0.00500000" + ], + [ + "17988.00", + "0.10000000" + ], + [ + "17990.00", + "0.05904634" + ], + [ + "17992.00", + "0.00053135" + ], + [ + "17995.00", + "1.00000000" + ], + [ + "17996.11", + "0.00057868" + ], + [ + "17997.97", + "0.00053136" + ], + [ + "17999.00", + "0.11892785" + ], + [ + "17999.99", + "0.29360393" + ], + [ + "18000.00", + "4.47287845" + ], + [ + "18000.02", + "0.05730379" + ], + [ + "18000.99", + "0.00028439" + ], + [ + "18002.00", + "0.11290000" + ], + [ + "18003.94", + "0.00053137" + ], + [ + "18009.91", + "0.00053138" + ], + [ + "18015.29", + "0.00057871" + ], + [ + "18015.89", + "0.00053139" + ], + [ + "18020.00", + "0.00155800" + ], + [ + "18021.86", + "0.00053140" + ], + [ + "18027.83", + "0.00053141" + ], + [ + "18028.50", + "0.02000000" + ], + [ + "18033.80", + "0.00053142" + ], + [ + "18039.77", + "0.00053143" + ], + [ + "18045.74", + "0.00053144" + ], + [ + "18051.72", + "0.00053145" + ], + [ + "18054.00", + "0.02000000" + ], + [ + "18057.69", + "0.00053146" + ], + [ + "18063.66", + "0.00053147" + ], + [ + "18069.63", + "0.00053148" + ], + [ + "18075.60", + "0.00053149" + ], + [ + "18079.50", + "0.02000000" + ], + [ + "18081.45", + "0.32836000" + ], + [ + "18081.58", + "0.00053150" + ], + [ + "18087.55", + "0.00053151" + ], + [ + "18088.00", + "0.10000000" + ], + [ + "18093.52", + "0.00053152" + ], + [ + "18099.49", + "0.00053153" + ], + [ + "18101.00", + "0.00440000" + ], + [ + "18105.00", + "0.02000000" + ], + [ + "18105.46", + "0.00053154" + ], + [ + "18110.00", + "0.05000000" + ], + [ + "18110.06", + "0.00798530" + ], + [ + "18111.43", + "0.00053155" + ], + [ + "18117.41", + "0.00053156" + ], + [ + "18117.50", + "0.61840441" + ], + [ + "18123.38", + "0.00053157" + ], + [ + "18126.31", + "0.02000000" + ], + [ + "18129.35", + "0.00053158" + ], + [ + "18130.50", + "0.02000000" + ], + [ + "18135.32", + "0.00053159" + ], + [ + "18135.72", + "0.00226646" + ], + [ + "18141.29", + "0.00053160" + ], + [ + "18147.26", + "0.00053161" + ], + [ + "18153.24", + "0.00053162" + ], + [ + "18156.00", + "0.02000000" + ], + [ + "18157.30", + "0.00500000" + ], + [ + "18159.21", + "0.00053163" + ], + [ + "18160.78", + "0.00156228" + ], + [ + "18165.18", + "0.00053164" + ], + [ + "18171.15", + "0.00053165" + ], + [ + "18177.12", + "0.00053166" + ], + [ + "18181.00", + "0.20000000" + ], + [ + "18181.50", + "0.02000000" + ], + [ + "18181.82", + "0.50000000" + ], + [ + "18183.10", + "0.00053167" + ], + [ + "18188.00", + "0.10000000" + ], + [ + "18189.07", + "0.00053168" + ], + [ + "18195.04", + "0.00053169" + ], + [ + "18200.00", + "0.56060198" + ], + [ + "18201.01", + "0.00053170" + ], + [ + "18206.98", + "0.00053171" + ], + [ + "18207.00", + "0.02000000" + ], + [ + "18211.11", + "0.05000000" + ], + [ + "18212.95", + "0.00053172" + ], + [ + "18218.93", + "0.00053173" + ], + [ + "18224.90", + "0.00053174" + ], + [ + "18230.76", + "0.00109782" + ], + [ + "18230.87", + "0.00053175" + ], + [ + "18232.50", + "0.02000000" + ], + [ + "18236.84", + "0.00053176" + ], + [ + "18238.00", + "0.05000000" + ], + [ + "18242.81", + "0.00053177" + ], + [ + "18248.79", + "0.00053178" + ], + [ + "18250.00", + "0.02500000" + ], + [ + "18254.76", + "0.00053179" + ], + [ + "18255.00", + "0.10000000" + ], + [ + "18258.00", + "0.02000000" + ], + [ + "18260.73", + "0.00053180" + ], + [ + "18266.70", + "0.00053181" + ], + [ + "18272.67", + "0.00053182" + ], + [ + "18278.64", + "0.00052183" + ], + [ + "18283.50", + "0.02000000" + ], + [ + "18284.62", + "0.00052184" + ], + [ + "18288.00", + "0.06000000" + ], + [ + "18290.59", + "0.00052185" + ], + [ + "18296.56", + "0.00052186" + ], + [ + "18302.53", + "0.00052187" + ], + [ + "18308.50", + "0.00052188" + ], + [ + "18309.00", + "0.02000000" + ], + [ + "18314.47", + "0.00052189" + ], + [ + "18320.45", + "0.00052190" + ], + [ + "18321.20", + "1.00000000" + ], + [ + "18326.42", + "0.00052191" + ], + [ + "18332.39", + "0.00052192" + ], + [ + "18334.50", + "0.02000000" + ], + [ + "18338.00", + "0.05000000" + ], + [ + "18338.36", + "0.00052193" + ], + [ + "18344.33", + "0.00052194" + ], + [ + "18350.31", + "0.00052195" + ], + [ + "18356.28", + "0.00052196" + ], + [ + "18360.00", + "0.02000000" + ], + [ + "18362.25", + "0.00052197" + ], + [ + "18368.22", + "0.00052198" + ], + [ + "18370.00", + "0.05000000" + ], + [ + "18374.19", + "0.00052199" + ], + [ + "18380.16", + "0.00052200" + ], + [ + "18385.50", + "0.04000000" + ], + [ + "18386.14", + "0.00052201" + ], + [ + "18392.11", + "0.00052202" + ], + [ + "18398.08", + "0.00052203" + ], + [ + "18400.00", + "0.75000000" + ], + [ + "18404.05", + "0.00052204" + ], + [ + "18411.00", + "0.04000000" + ], + [ + "18421.97", + "0.00052207" + ], + [ + "18422.00", + "0.02000000" + ], + [ + "18436.50", + "0.04000000" + ], + [ + "18438.00", + "0.05000000" + ], + [ + "18439.00", + "0.01000000" + ], + [ + "18439.88", + "0.00052210" + ], + [ + "18457.80", + "0.00052213" + ], + [ + "18459.00", + "0.01000000" + ], + [ + "18462.00", + "0.04000000" + ], + [ + "18469.74", + "0.00052215" + ], + [ + "18472.07", + "0.00278972" + ], + [ + "18475.71", + "0.00052216" + ], + [ + "18481.68", + "0.00052217" + ], + [ + "18487.50", + "0.04000000" + ], + [ + "18487.66", + "0.00052218" + ], + [ + "18488.00", + "0.05000000" + ], + [ + "18493.63", + "0.00052219" + ], + [ + "18494.56", + "0.10000000" + ], + [ + "18495.00", + "1.00000000" + ], + [ + "18495.89", + "0.00276107" + ], + [ + "18498.50", + "0.05171943" + ], + [ + "18499.60", + "0.00052220" + ], + [ + "18500.00", + "3.43380001" + ], + [ + "18505.57", + "0.00052221" + ], + [ + "18511.54", + "0.00052222" + ], + [ + "18513.00", + "0.04000000" + ], + [ + "18517.52", + "0.00052223" + ], + [ + "18523.49", + "0.00052224" + ], + [ + "18529.46", + "0.00104450" + ], + [ + "18535.43", + "0.00052226" + ], + [ + "18537.47", + "0.00390870" + ], + [ + "18538.00", + "0.05000000" + ], + [ + "18538.50", + "0.04000000" + ], + [ + "18541.40", + "0.00052227" + ], + [ + "18547.37", + "0.00052228" + ], + [ + "18550.00", + "10.00000000" + ], + [ + "18553.35", + "0.00052229" + ], + [ + "18559.32", + "0.00052230" + ], + [ + "18564.00", + "0.04000000" + ], + [ + "18565.29", + "0.00052231" + ], + [ + "18571.26", + "0.00052232" + ], + [ + "18577.23", + "0.00052233" + ], + [ + "18582.50", + "0.00500000" + ], + [ + "18583.00", + "0.05000000" + ], + [ + "18583.20", + "0.00052234" + ], + [ + "18589.18", + "0.00052235" + ], + [ + "18589.50", + "0.04000000" + ], + [ + "18595.15", + "0.00052236" + ], + [ + "18600.00", + "0.30000000" + ], + [ + "18601.12", + "0.00052237" + ], + [ + "18607.09", + "0.00052238" + ], + [ + "18613.06", + "0.00052239" + ], + [ + "18615.00", + "0.04000000" + ], + [ + "18619.04", + "0.00052240" + ], + [ + "18625.01", + "0.00052241" + ], + [ + "18630.00", + "0.50000000" + ], + [ + "18630.98", + "0.00052242" + ], + [ + "18633.00", + "0.05000000" + ], + [ + "18636.95", + "0.00051243" + ], + [ + "18640.50", + "0.04000000" + ], + [ + "18642.92", + "0.00051244" + ], + [ + "18648.89", + "0.00051245" + ], + [ + "18650.00", + "0.20000000" + ], + [ + "18654.87", + "0.00051246" + ], + [ + "18660.84", + "0.00051247" + ], + [ + "18662.25", + "0.00363063" + ], + [ + "18666.00", + "0.04000000" + ], + [ + "18666.81", + "0.00051248" + ], + [ + "18672.78", + "0.00051249" + ], + [ + "18678.75", + "0.00051250" + ], + [ + "18683.00", + "0.05000000" + ], + [ + "18684.73", + "0.00051251" + ], + [ + "18690.70", + "0.00051252" + ], + [ + "18691.50", + "0.04000000" + ], + [ + "18696.67", + "0.00051253" + ], + [ + "18700.00", + "0.33999988" + ], + [ + "18702.64", + "0.00051254" + ], + [ + "18708.61", + "0.00051255" + ], + [ + "18714.58", + "0.00051256" + ], + [ + "18717.00", + "0.04000000" + ], + [ + "18720.56", + "0.00051257" + ], + [ + "18721.84", + "0.01000000" + ], + [ + "18726.53", + "0.00051258" + ], + [ + "18732.50", + "0.00051259" + ], + [ + "18732.73", + "0.01049151" + ], + [ + "18733.00", + "0.05000000" + ], + [ + "18738.47", + "0.00051260" + ], + [ + "18742.50", + "0.04000000" + ], + [ + "18744.44", + "0.00051261" + ], + [ + "18750.00", + "0.79200000" + ], + [ + "18750.41", + "0.00051262" + ], + [ + "18756.39", + "0.00051263" + ], + [ + "18762.36", + "0.00051264" + ], + [ + "18763.48", + "0.01919348" + ], + [ + "18765.00", + "0.18352560" + ], + [ + "18768.00", + "0.04000000" + ], + [ + "18768.33", + "0.00051265" + ], + [ + "18769.00", + "0.02963512" + ], + [ + "18774.30", + "0.00051266" + ], + [ + "18777.26", + "0.05000000" + ], + [ + "18780.00", + "1.00000000" + ], + [ + "18780.27", + "0.00051267" + ], + [ + "18781.80", + "0.00813018" + ], + [ + "18786.25", + "0.00051268" + ], + [ + "18792.22", + "0.00051269" + ], + [ + "18793.50", + "0.04000000" + ], + [ + "18798.19", + "0.00051270" + ], + [ + "18800.00", + "0.03039127" + ], + [ + "18804.16", + "0.00051271" + ], + [ + "18810.13", + "0.00051272" + ], + [ + "18816.10", + "0.00051273" + ], + [ + "18819.00", + "0.04000000" + ], + [ + "18822.08", + "0.00051274" + ], + [ + "18825.00", + "0.37279005" + ], + [ + "18828.05", + "0.00051275" + ], + [ + "18834.02", + "0.00051276" + ], + [ + "18837.52", + "0.00032169" + ], + [ + "18839.99", + "0.00051277" + ], + [ + "18844.50", + "0.04000000" + ], + [ + "18845.96", + "0.00051278" + ], + [ + "18851.93", + "0.00051279" + ], + [ + "18857.91", + "0.00051280" + ], + [ + "18863.88", + "0.00051281" + ], + [ + "18866.69", + "0.01846683" + ], + [ + "18869.85", + "0.00051282" + ], + [ + "18870.00", + "0.04000000" + ], + [ + "18875.82", + "0.00051283" + ], + [ + "18881.79", + "0.00051284" + ], + [ + "18883.00", + "0.10000000" + ], + [ + "18887.77", + "0.00051285" + ], + [ + "18888.00", + "2.02000000" + ], + [ + "18888.23", + "0.34200000" + ], + [ + "18892.00", + "0.02652234" + ], + [ + "18893.74", + "0.00051286" + ], + [ + "18895.50", + "0.04000000" + ], + [ + "18897.88", + "0.34934000" + ], + [ + "18897.89", + "0.00270367" + ], + [ + "18899.71", + "0.00051287" + ], + [ + "18900.00", + "3.14684635" + ], + [ + "18905.68", + "0.00051288" + ], + [ + "18911.65", + "0.00051289" + ], + [ + "18917.62", + "0.00051290" + ], + [ + "18920.00", + "0.00148400" + ], + [ + "18921.00", + "0.04000000" + ], + [ + "18923.60", + "0.00051291" + ], + [ + "18926.20", + "0.34619386" + ], + [ + "18929.57", + "0.00051292" + ], + [ + "18930.00", + "0.10000000" + ], + [ + "18933.00", + "0.05000000" + ], + [ + "18935.54", + "0.00051293" + ], + [ + "18941.51", + "0.00051294" + ], + [ + "18946.50", + "0.04000000" + ], + [ + "18947.48", + "0.00051295" + ], + [ + "18948.97", + "0.29500000" + ], + [ + "18950.00", + "1.02726167" + ], + [ + "18953.46", + "0.00051296" + ], + [ + "18959.43", + "0.00051297" + ], + [ + "18965.40", + "0.00051298" + ], + [ + "18971.37", + "0.00051299" + ], + [ + "18972.00", + "0.04000000" + ], + [ + "18977.34", + "0.00051300" + ], + [ + "18983.00", + "0.05000000" + ], + [ + "18983.31", + "0.00051301" + ], + [ + "18989.29", + "0.00051302" + ], + [ + "18990.00", + "0.00316550" + ], + [ + "18995.00", + "1.20000000" + ], + [ + "18995.26", + "0.00051303" + ], + [ + "18997.50", + "0.04000000" + ], + [ + "18999.99", + "0.00626999" + ], + [ + "19000.00", + "31.58215709" + ], + [ + "19001.00", + "1.00000000" + ], + [ + "19001.23", + "0.00051304" + ], + [ + "19007.20", + "0.00051305" + ], + [ + "19013.17", + "0.00050306" + ], + [ + "19016.16", + "0.54856916" + ], + [ + "19019.14", + "0.00050307" + ], + [ + "19020.00", + "0.02244418" + ], + [ + "19023.00", + "0.04000000" + ], + [ + "19025.12", + "0.00050308" + ], + [ + "19030.00", + "0.00147500" + ], + [ + "19031.09", + "0.00050309" + ], + [ + "19037.06", + "0.00050310" + ], + [ + "19043.03", + "0.00050311" + ], + [ + "19044.24", + "0.03798613" + ], + [ + "19049.00", + "0.00050312" + ], + [ + "19054.98", + "0.00050313" + ], + [ + "19057.16", + "1.00000000" + ], + [ + "19060.95", + "0.00050314" + ], + [ + "19066.92", + "0.00050315" + ], + [ + "19068.00", + "1.00000000" + ], + [ + "19072.89", + "0.00050316" + ], + [ + "19073.99", + "0.05555800" + ], + [ + "19074.00", + "0.04000000" + ], + [ + "19078.46", + "0.01712813" + ], + [ + "19078.86", + "0.00100634" + ], + [ + "19084.83", + "0.00050318" + ], + [ + "19090.81", + "0.00050319" + ], + [ + "19096.78", + "0.00050320" + ], + [ + "19099.50", + "0.04000000" + ], + [ + "19100.00", + "1.17036502" + ], + [ + "19102.75", + "0.00050321" + ], + [ + "19108.72", + "0.00050322" + ], + [ + "19110.00", + "0.05000000" + ], + [ + "19114.69", + "0.00050323" + ], + [ + "19120.66", + "0.00050324" + ], + [ + "19123.45", + "0.00100000" + ], + [ + "19125.00", + "0.04000000" + ], + [ + "19126.64", + "0.00050325" + ], + [ + "19127.00", + "0.10000000" + ], + [ + "19132.61", + "0.00050326" + ], + [ + "19138.58", + "0.00050327" + ], + [ + "19140.00", + "0.00146700" + ], + [ + "19144.55", + "0.00050328" + ], + [ + "19148.16", + "0.20000000" + ], + [ + "19150.50", + "0.04000000" + ], + [ + "19150.52", + "0.00050329" + ], + [ + "19152.17", + "0.01852186" + ], + [ + "19156.50", + "0.00050330" + ], + [ + "19162.47", + "0.00050331" + ], + [ + "19168.44", + "0.00050332" + ], + [ + "19174.41", + "0.00050333" + ], + [ + "19176.00", + "0.04000000" + ], + [ + "19179.00", + "0.01000000" + ], + [ + "19180.38", + "0.00050334" + ], + [ + "19186.00", + "0.09525243" + ], + [ + "19186.35", + "0.00050335" + ], + [ + "19192.33", + "0.00050336" + ], + [ + "19198.30", + "0.00050337" + ], + [ + "19200.00", + "0.08935354" + ], + [ + "19201.50", + "0.04000000" + ], + [ + "19204.27", + "0.00050338" + ], + [ + "19208.68", + "0.35666641" + ], + [ + "19210.24", + "0.00050339" + ], + [ + "19216.21", + "0.00050340" + ], + [ + "19222.19", + "0.00050341" + ], + [ + "19227.00", + "0.04000000" + ], + [ + "19228.16", + "0.00050342" + ], + [ + "19233.33", + "0.03671000" + ], + [ + "19234.13", + "0.00050343" + ], + [ + "19239.00", + "0.01000000" + ], + [ + "19239.59", + "0.00031497" + ], + [ + "19239.89", + "0.00500000" + ], + [ + "19240.10", + "0.00050344" + ], + [ + "19245.00", + "0.01179226" + ], + [ + "19246.07", + "0.00050345" + ], + [ + "19250.00", + "0.42500000" + ], + [ + "19252.04", + "0.00050346" + ], + [ + "19252.50", + "0.04000000" + ], + [ + "19257.99", + "0.39875722" + ], + [ + "19258.02", + "0.00050347" + ], + [ + "19258.96", + "0.00383306" + ], + [ + "19263.99", + "0.00050348" + ], + [ + "19266.84", + "0.00255753" + ], + [ + "19269.96", + "0.00050349" + ], + [ + "19275.93", + "0.00050350" + ], + [ + "19278.00", + "0.04000000" + ], + [ + "19281.90", + "0.00050351" + ], + [ + "19287.87", + "0.00050352" + ], + [ + "19288.00", + "0.01000000" + ], + [ + "19293.85", + "0.00050353" + ], + [ + "19299.82", + "0.00050354" + ], + [ + "19300.00", + "0.10000000" + ], + [ + "19303.50", + "0.04000000" + ], + [ + "19305.79", + "0.00050355" + ], + [ + "19311.76", + "0.00050356" + ], + [ + "19323.71", + "0.00050358" + ], + [ + "19329.00", + "0.04000000" + ], + [ + "19333.00", + "0.21802803" + ], + [ + "19335.65", + "0.00050360" + ], + [ + "19341.62", + "0.00050361" + ], + [ + "19347.59", + "0.00050362" + ], + [ + "19353.56", + "0.00050363" + ], + [ + "19354.50", + "0.04000000" + ], + [ + "19359.54", + "0.00050364" + ], + [ + "19365.51", + "0.00050365" + ], + [ + "19369.14", + "0.00204812" + ], + [ + "19369.16", + "0.00146324" + ], + [ + "19371.48", + "0.00050366" + ], + [ + "19372.00", + "0.10000000" + ], + [ + "19375.00", + "0.00300000" + ], + [ + "19377.45", + "0.00050367" + ], + [ + "19380.00", + "0.04000000" + ], + [ + "19383.42", + "0.00050368" + ], + [ + "19389.00", + "0.04000000" + ], + [ + "19389.40", + "0.00050369" + ], + [ + "19395.37", + "0.00049370" + ], + [ + "19400.00", + "1.44161454" + ], + [ + "19401.34", + "0.00049371" + ], + [ + "19405.50", + "0.04000000" + ], + [ + "19407.31", + "0.00049372" + ], + [ + "19413.28", + "0.00049373" + ], + [ + "19419.25", + "0.00049374" + ], + [ + "19425.23", + "0.00049375" + ], + [ + "19431.00", + "0.04000000" + ], + [ + "19431.20", + "0.00049376" + ], + [ + "19435.67", + "0.00100718" + ], + [ + "19437.17", + "0.00049377" + ], + [ + "19440.00", + "0.25032019" + ], + [ + "19443.14", + "0.00049378" + ], + [ + "19447.11", + "0.01000000" + ], + [ + "19449.11", + "0.00049379" + ], + [ + "19454.00", + "0.02980000" + ], + [ + "19455.08", + "0.00049380" + ], + [ + "19456.50", + "0.04000000" + ], + [ + "19461.06", + "0.00049381" + ], + [ + "19463.41", + "0.00251255" + ], + [ + "19467.03", + "0.00049382" + ], + [ + "19470.00", + "1.61000000" + ], + [ + "19472.00", + "0.10000000" + ], + [ + "19473.00", + "0.00049383" + ], + [ + "19478.97", + "0.00049384" + ], + [ + "19480.00", + "0.02087011" + ], + [ + "19482.00", + "0.04000000" + ], + [ + "19484.94", + "0.00049385" + ], + [ + "19490.92", + "0.00049386" + ], + [ + "19495.00", + "1.00000000" + ], + [ + "19496.89", + "0.00049387" + ], + [ + "19499.00", + "0.33505293" + ], + [ + "19500.00", + "7.58205559" + ], + [ + "19500.51", + "0.01000000" + ], + [ + "19502.86", + "0.00049388" + ], + [ + "19507.50", + "0.04000000" + ], + [ + "19508.83", + "0.00049389" + ], + [ + "19514.80", + "0.00049390" + ], + [ + "19520.77", + "0.00049391" + ], + [ + "19524.00", + "3.51092745" + ], + [ + "19526.75", + "0.00049392" + ], + [ + "19529.00", + "1.00000000" + ], + [ + "19530.22", + "0.50000000" + ], + [ + "19531.20", + "2.00000000" + ], + [ + "19532.72", + "0.00049393" + ], + [ + "19533.00", + "0.04000000" + ], + [ + "19538.69", + "0.00049394" + ], + [ + "19540.00", + "0.00143700" + ], + [ + "19544.66", + "0.00049395" + ], + [ + "19547.00", + "0.27182692" + ], + [ + "19548.00", + "0.10000000" + ], + [ + "19550.00", + "0.00310372" + ], + [ + "19550.61", + "0.21000000" + ], + [ + "19550.63", + "0.00049396" + ], + [ + "19553.14", + "0.01734434" + ], + [ + "19556.60", + "0.00049397" + ], + [ + "19558.50", + "0.04000000" + ], + [ + "19562.58", + "0.00049398" + ], + [ + "19568.55", + "0.00049399" + ], + [ + "19572.00", + "0.09112591" + ], + [ + "19574.52", + "0.00049400" + ], + [ + "19580.49", + "0.00049401" + ], + [ + "19584.00", + "0.04000000" + ], + [ + "19586.46", + "0.00049402" + ], + [ + "19592.44", + "0.00049403" + ], + [ + "19598.41", + "0.00049404" + ], + [ + "19599.84", + "0.21000000" + ], + [ + "19600.00", + "4.29510276" + ], + [ + "19604.38", + "0.00049405" + ], + [ + "19609.50", + "0.04000000" + ], + [ + "19610.35", + "0.00049406" + ], + [ + "19615.35", + "0.00030894" + ], + [ + "19616.32", + "0.00049407" + ], + [ + "19622.29", + "0.00049408" + ], + [ + "19628.27", + "0.00049409" + ], + [ + "19634.24", + "0.00049410" + ], + [ + "19635.00", + "0.04000000" + ], + [ + "19638.00", + "0.10000000" + ], + [ + "19640.21", + "0.00049411" + ], + [ + "19646.18", + "0.00049412" + ], + [ + "19650.00", + "0.05256778" + ], + [ + "19652.15", + "0.00049413" + ], + [ + "19658.13", + "0.00049414" + ], + [ + "19662.02", + "0.00248512" + ], + [ + "19663.93", + "0.00847730" + ], + [ + "19664.10", + "0.00049415" + ], + [ + "19670.07", + "0.00049416" + ], + [ + "19676.04", + "0.00049417" + ], + [ + "19682.01", + "0.00049418" + ], + [ + "19686.00", + "0.02000000" + ], + [ + "19687.98", + "0.00049419" + ], + [ + "19693.55", + "0.00255762" + ], + [ + "19693.96", + "0.00049420" + ], + [ + "19699.93", + "0.00049421" + ], + [ + "19700.00", + "0.05000000" + ], + [ + "19705.90", + "0.00049422" + ], + [ + "19711.50", + "0.02000000" + ], + [ + "19711.87", + "0.00049423" + ], + [ + "19717.84", + "0.00049424" + ], + [ + "19720.00", + "0.10000000" + ], + [ + "19723.81", + "0.00049425" + ], + [ + "19729.79", + "0.00049426" + ], + [ + "19735.76", + "0.00049427" + ], + [ + "19737.00", + "0.04000000" + ], + [ + "19741.73", + "0.00049428" + ], + [ + "19747.70", + "0.00049429" + ], + [ + "19750.00", + "0.12500000" + ], + [ + "19753.67", + "0.00049430" + ], + [ + "19759.65", + "0.00049431" + ], + [ + "19762.50", + "0.02000000" + ], + [ + "19765.62", + "0.00049432" + ], + [ + "19771.59", + "0.00049433" + ], + [ + "19777.26", + "0.10000000" + ], + [ + "19777.56", + "0.00049434" + ], + [ + "19780.00", + "0.10000000" + ], + [ + "19781.00", + "0.20000000" + ], + [ + "19783.53", + "0.00049435" + ], + [ + "19785.00", + "0.01000000" + ], + [ + "19788.00", + "0.02000000" + ], + [ + "19795.00", + "1.00000000" + ], + [ + "19800.00", + "30.66910862" + ], + [ + "19801.52", + "0.00500000" + ], + [ + "19810.00", + "0.00141700" + ], + [ + "19813.50", + "0.02000000" + ], + [ + "19815.59", + "0.40000000" + ], + [ + "19836.51", + "0.00030549" + ], + [ + "19839.00", + "0.02141500" + ], + [ + "19843.00", + "3.00000000" + ], + [ + "19864.50", + "0.02000000" + ], + [ + "19870.00", + "0.00141300" + ], + [ + "19880.00", + "0.03508507" + ], + [ + "19888.00", + "0.02000000" + ], + [ + "19888.99", + "0.00856919" + ], + [ + "19896.53", + "0.10000000" + ], + [ + "19897.97", + "0.27500000" + ], + [ + "19900.00", + "0.96496163" + ], + [ + "19910.00", + "0.00141000" + ], + [ + "19920.00", + "0.00140900" + ], + [ + "19928.90", + "0.00539089" + ], + [ + "19945.00", + "1.00000000" + ], + [ + "19949.42", + "0.10000000" + ], + [ + "19950.00", + "1.15140700" + ], + [ + "19953.13", + "0.01000000" + ], + [ + "19957.00", + "0.03300000" + ], + [ + "19957.67", + "0.08041002" + ], + [ + "19968.96", + "0.16804946" + ], + [ + "19970.00", + "0.00140600" + ], + [ + "19975.00", + "0.25000000" + ], + [ + "19980.00", + "1.00000000" + ], + [ + "19982.20", + "0.27084351" + ], + [ + "19987.30", + "0.00100000" + ], + [ + "19989.90", + "1.00000000" + ], + [ + "19990.00", + "1.35402469" + ], + [ + "19994.94", + "0.01000000" + ], + [ + "19995.00", + "1.00000000" + ], + [ + "19998.70", + "0.01000000" + ], + [ + "19999.00", + "0.65603431" + ], + [ + "19999.84", + "0.05643323" + ], + [ + "19999.99", + "2.00000000" + ], + [ + "20000.00", + "37.04628228" + ], + [ + "20000.99", + "0.01633100" + ], + [ + "20001.00", + "0.02455000" + ], + [ + "20002.00", + "0.06632200" + ], + [ + "20003.71", + "0.00466803" + ], + [ + "20035.00", + "0.10000000" + ], + [ + "20040.00", + "0.00140100" + ], + [ + "20045.00", + "0.18294002" + ], + [ + "20070.00", + "0.00139900" + ], + [ + "20080.00", + "0.00139800" + ], + [ + "20100.00", + "0.02000000" + ], + [ + "20110.00", + "0.05139600" + ], + [ + "20120.00", + "0.00139500" + ], + [ + "20123.00", + "0.25000000" + ], + [ + "20125.00", + "0.17259130" + ], + [ + "20147.30", + "0.00200000" + ], + [ + "20150.00", + "0.00139300" + ], + [ + "20160.00", + "0.00036693" + ], + [ + "20180.00", + "0.00139100" + ], + [ + "20190.00", + "0.05000000" + ], + [ + "20200.00", + "0.00139000" + ], + [ + "20213.00", + "1.00000000" + ], + [ + "20220.00", + "0.50138800" + ], + [ + "20225.00", + "0.40000000" + ], + [ + "20240.00", + "0.00138700" + ], + [ + "20250.00", + "3.49590000" + ], + [ + "20261.00", + "0.05000000" + ], + [ + "20270.00", + "0.00138500" + ], + [ + "20280.00", + "0.00138400" + ], + [ + "20300.00", + "50.07000000" + ], + [ + "20310.00", + "0.00138200" + ], + [ + "20340.00", + "0.00138000" + ], + [ + "20360.00", + "0.00137900" + ], + [ + "20370.00", + "0.00137800" + ], + [ + "20381.00", + "0.06000000" + ], + [ + "20420.00", + "0.00137500" + ], + [ + "20430.00", + "0.00137400" + ], + [ + "20445.00", + "1.00000000" + ], + [ + "20455.00", + "0.04000000" + ], + [ + "20490.00", + "0.00137000" + ], + [ + "20500.00", + "3.29687352" + ], + [ + "20520.00", + "0.00136800" + ], + [ + "20547.60", + "0.00300000" + ], + [ + "20549.42", + "0.10000000" + ], + [ + "20550.00", + "0.00136600" + ], + [ + "20590.00", + "0.00136300" + ], + [ + "20600.00", + "0.05000000" + ], + [ + "20620.00", + "0.00136100" + ], + [ + "20640.00", + "0.00136000" + ], + [ + "20649.00", + "0.20000000" + ], + [ + "20650.00", + "0.00135900" + ], + [ + "20670.00", + "0.00135800" + ], + [ + "20680.00", + "0.00135700" + ], + [ + "20720.00", + "0.00135500" + ], + [ + "20730.00", + "0.00135400" + ], + [ + "20760.00", + "0.00135200" + ], + [ + "20780.00", + "0.00135100" + ], + [ + "20800.00", + "0.00024427" + ], + [ + "20810.00", + "0.00134900" + ], + [ + "20820.00", + "0.00134800" + ], + [ + "20850.00", + "0.00134600" + ], + [ + "20870.00", + "0.00134500" + ], + [ + "20880.00", + "0.00134400" + ], + [ + "20888.00", + "0.02000000" + ], + [ + "20900.00", + "0.05000000" + ], + [ + "20920.00", + "0.00134200" + ], + [ + "20930.00", + "0.00134100" + ], + [ + "20945.00", + "1.00000000" + ], + [ + "20949.42", + "0.10000000" + ], + [ + "20960.00", + "0.00133900" + ], + [ + "20962.88", + "1.00000000" + ], + [ + "20978.40", + "0.00500000" + ], + [ + "20980.00", + "0.00133800" + ], + [ + "20986.00", + "0.50000000" + ], + [ + "20986.50", + "0.50000000" + ], + [ + "20987.00", + "0.50000000" + ], + [ + "20987.50", + "0.50000000" + ], + [ + "20988.00", + "0.50000000" + ], + [ + "20988.40", + "0.50000000" + ], + [ + "20989.00", + "0.50000000" + ], + [ + "20990.00", + "0.00133700" + ], + [ + "20997.97", + "0.25000000" + ], + [ + "20998.00", + "0.50000000" + ], + [ + "20998.50", + "0.50000000" + ], + [ + "20999.89", + "0.50000000" + ], + [ + "21000.00", + "0.46964027" + ], + [ + "21005.00", + "0.50000000" + ], + [ + "21010.00", + "0.00133600" + ], + [ + "21030.00", + "0.00133500" + ], + [ + "21060.00", + "0.00133300" + ], + [ + "21070.00", + "0.00133300" + ], + [ + "21100.00", + "0.05000000" + ], + [ + "21168.00", + "0.25000000" + ], + [ + "21199.00", + "0.26032282" + ], + [ + "21200.00", + "0.06000000" + ], + [ + "21212.12", + "0.02500000" + ], + [ + "21360.00", + "0.00078287" + ], + [ + "21400.00", + "0.02800000" + ], + [ + "21445.00", + "1.00000000" + ], + [ + "21456.00", + "0.00113569" + ], + [ + "21468.85", + "0.04000000" + ], + [ + "21500.00", + "0.15110000" + ], + [ + "21512.12", + "0.02243125" + ], + [ + "21549.42", + "0.10000000" + ], + [ + "21600.00", + "0.00063507" + ], + [ + "21672.00", + "0.00300000" + ], + [ + "21800.00", + "0.05000000" + ], + [ + "21833.29", + "0.35144596" + ], + [ + "21840.00", + "0.10000000" + ], + [ + "21870.61", + "0.01000000" + ], + [ + "21888.00", + "0.01000000" + ], + [ + "21931.24", + "0.09058264" + ], + [ + "21945.00", + "1.00000000" + ], + [ + "21949.42", + "0.10000000" + ], + [ + "21973.00", + "0.25000000" + ], + [ + "22000.00", + "0.31757679" + ], + [ + "22100.00", + "0.05000000" + ], + [ + "22113.05", + "0.10000000" + ], + [ + "22148.97", + "0.25000000" + ], + [ + "22222.22", + "1.00000000" + ], + [ + "22400.00", + "0.05000000" + ], + [ + "22445.00", + "1.00000000" + ], + [ + "22461.00", + "0.04445269" + ], + [ + "22499.00", + "1.00000000" + ], + [ + "22549.42", + "0.10000000" + ], + [ + "22555.00", + "0.20000000" + ], + [ + "22700.00", + "0.05000000" + ], + [ + "22777.26", + "0.05000000" + ], + [ + "22945.00", + "1.00000000" + ], + [ + "22949.42", + "0.10000000" + ], + [ + "23000.00", + "5.99698326" + ], + [ + "23059.17", + "1.00000000" + ], + [ + "23200.00", + "0.01386563" + ], + [ + "23284.00", + "0.25000000" + ], + [ + "23300.00", + "0.10000000" + ], + [ + "23333.00", + "2.30000000" + ], + [ + "23348.97", + "0.25000000" + ], + [ + "23445.00", + "1.00000000" + ], + [ + "23540.00", + "0.02700000" + ], + [ + "23549.42", + "0.10000000" + ], + [ + "23600.00", + "0.05000000" + ], + [ + "23710.61", + "0.01000000" + ], + [ + "23738.00", + "0.18000000" + ], + [ + "23785.00", + "0.00812335" + ], + [ + "23900.00", + "0.05000000" + ], + [ + "23945.00", + "1.00000000" + ], + [ + "23949.42", + "0.10000000" + ], + [ + "23998.00", + "1.00355199" + ], + [ + "24000.00", + "1.01000000" + ], + [ + "24164.00", + "0.04170000" + ], + [ + "24200.00", + "0.05000000" + ], + [ + "24429.00", + "0.50000000" + ], + [ + "24445.00", + "1.00000000" + ], + [ + "24500.00", + "0.05000000" + ], + [ + "24549.42", + "0.10000000" + ], + [ + "24748.97", + "0.25000000" + ], + [ + "24777.26", + "0.05000000" + ], + [ + "24800.00", + "1.05000000" + ], + [ + "24894.00", + "0.02090000" + ], + [ + "24945.00", + "1.00000000" + ], + [ + "24949.42", + "0.10000000" + ], + [ + "24980.00", + "0.20000000" + ], + [ + "24989.90", + "0.52899289" + ], + [ + "24999.00", + "0.45000000" + ], + [ + "25000.00", + "4.62909641" + ], + [ + "25100.00", + "0.05000000" + ], + [ + "25125.00", + "0.20000000" + ], + [ + "25365.09", + "1.00000000" + ], + [ + "25400.00", + "0.05000000" + ], + [ + "25549.42", + "0.10000000" + ], + [ + "25652.00", + "0.48908403" + ], + [ + "25700.00", + "0.05000000" + ], + [ + "25894.00", + "0.02560000" + ], + [ + "25949.42", + "0.10000000" + ], + [ + "26000.00", + "0.45560005" + ], + [ + "26248.97", + "0.25000000" + ], + [ + "26250.00", + "10.00000000" + ], + [ + "26300.00", + "0.05000000" + ], + [ + "26314.00", + "0.07619627" + ], + [ + "26500.00", + "0.05000000" + ], + [ + "26549.42", + "0.10000000" + ], + [ + "26600.00", + "0.05000000" + ], + [ + "26745.41", + "0.01049152" + ], + [ + "26900.00", + "0.05000000" + ], + [ + "26949.42", + "0.10000000" + ], + [ + "27000.00", + "2.04790335" + ], + [ + "27200.00", + "0.05000000" + ], + [ + "27500.00", + "0.05000000" + ], + [ + "27549.42", + "0.10000000" + ], + [ + "27677.70", + "0.00664868" + ], + [ + "27777.26", + "0.05000000" + ], + [ + "27800.00", + "0.05000000" + ], + [ + "27820.24", + "6.27928487" + ], + [ + "27901.59", + "1.00000000" + ], + [ + "27925.67", + "0.25021559" + ], + [ + "27948.97", + "0.25000000" + ], + [ + "27949.42", + "0.10000000" + ], + [ + "27961.00", + "12.40807321" + ], + [ + "28000.00", + "0.01000000" + ], + [ + "28100.00", + "0.05000000" + ], + [ + "28234.00", + "0.03572000" + ], + [ + "28345.00", + "0.10000000" + ], + [ + "28400.00", + "0.05000000" + ], + [ + "28485.00", + "0.16000000" + ], + [ + "28549.42", + "0.10000000" + ], + [ + "28700.00", + "0.05000000" + ], + [ + "28900.00", + "0.07025143" + ], + [ + "28949.42", + "0.10000000" + ], + [ + "29000.00", + "1.06000000" + ], + [ + "29008.01", + "0.06116814" + ], + [ + "29300.00", + "0.05000000" + ], + [ + "29600.00", + "0.05000000" + ], + [ + "29659.92", + "0.01000000" + ], + [ + "29685.00", + "0.50000000" + ], + [ + "29748.97", + "0.25000000" + ], + [ + "29780.00", + "2.09231640" + ], + [ + "29895.00", + "0.10000000" + ], + [ + "29900.00", + "0.05000000" + ], + [ + "29974.00", + "0.60880593" + ], + [ + "30000.00", + "1.98503065" + ], + [ + "30691.75", + "1.00000000" + ], + [ + "31000.00", + "0.01000000" + ], + [ + "31200.00", + "0.09300000" + ], + [ + "31234.00", + "0.23000000" + ], + [ + "31748.97", + "0.25000000" + ], + [ + "32000.00", + "0.10990000" + ], + [ + "32261.00", + "0.03125000" + ], + [ + "32349.00", + "0.02500000" + ], + [ + "33000.00", + "0.01000000" + ], + [ + "33333.00", + "3.34000000" + ], + [ + "33748.97", + "0.25000000" + ], + [ + "33760.93", + "1.00000000" + ], + [ + "34000.00", + "0.01000000" + ], + [ + "34182.00", + "0.14000000" + ], + [ + "34494.65", + "0.02327633" + ], + [ + "34932.00", + "0.25000000" + ], + [ + "35000.00", + "1.01000044" + ], + [ + "35848.97", + "0.25000000" + ], + [ + "35963.14", + "0.02814749" + ], + [ + "36000.00", + "0.01000000" + ], + [ + "37000.00", + "0.01000000" + ], + [ + "37137.02", + "1.00000000" + ], + [ + "37225.00", + "0.13500000" + ], + [ + "37777.26", + "0.10000000" + ], + [ + "38000.00", + "0.01000000" + ], + [ + "38500.00", + "0.05000000" + ], + [ + "38828.42", + "2.00502435" + ], + [ + "38900.00", + "0.10000000" + ], + [ + "39000.00", + "0.25465075" + ], + [ + "39400.00", + "0.35000000" + ], + [ + "39456.00", + "0.12500000" + ], + [ + "39468.00", + "0.00241298" + ], + [ + "40000.00", + "2.18554048" + ], + [ + "40850.73", + "1.00000000" + ], + [ + "40988.00", + "0.02000000" + ], + [ + "41019.00", + "0.14000000" + ], + [ + "42424.00", + "0.03630000" + ], + [ + "42839.00", + "0.00233432" + ], + [ + "43210.00", + "0.10000000" + ], + [ + "43680.00", + "0.05000000" + ], + [ + "44953.93", + "0.02814749" + ], + [ + "45000.00", + "0.01000000" + ], + [ + "47737.82", + "0.03184990" + ], + [ + "47777.26", + "0.05000000" + ], + [ + "48000.00", + "0.30695548" + ], + [ + "48880.63", + "1.34192964" + ], + [ + "49222.00", + "0.16000000" + ], + [ + "49456.00", + "0.12500000" + ], + [ + "49657.00", + "0.30283439" + ], + [ + "49899.00", + "0.01000000" + ], + [ + "49990.00", + "1.00000000" + ], + [ + "50000.00", + "49.31908105" + ], + [ + "50050.00", + "0.00010000" + ], + [ + "50200.00", + "0.06000000" + ], + [ + "50234.00", + "0.10000000" + ], + [ + "50400.00", + "0.93725323" + ], + [ + "52300.00", + "10.00000000" + ], + [ + "52577.87", + "0.28791550" + ], + [ + "54437.57", + "0.30078552" + ], + [ + "55000.00", + "0.10000000" + ], + [ + "56192.41", + "0.02814749" + ], + [ + "57500.00", + "0.50000000" + ], + [ + "57777.56", + "0.02870973" + ], + [ + "59067.00", + "0.18000000" + ], + [ + "59456.00", + "0.12500000" + ], + [ + "60000.00", + "1.04400000" + ], + [ + "62999.97", + "1.05207174" + ], + [ + "63000.00", + "0.03000000" + ], + [ + "64000.00", + "0.06900000" + ], + [ + "64900.00", + "1.93490805" + ], + [ + "65000.00", + "0.02000000" + ], + [ + "66666.00", + "3.33293980" + ], + [ + "70000.00", + "0.03000000" + ], + [ + "70240.52", + "0.02814749" + ], + [ + "70402.00", + "0.02000000" + ], + [ + "70880.00", + "0.20000000" + ], + [ + "73000.00", + "0.00956148" + ], + [ + "74151.09", + "0.50000000" + ], + [ + "75000.00", + "1.11528997" + ], + [ + "78743.00", + "0.09700000" + ], + [ + "79500.00", + "0.05000000" + ], + [ + "79528.00", + "0.12500000" + ], + [ + "80000.00", + "0.33854001" + ], + [ + "82250.13", + "0.50000000" + ], + [ + "82550.00", + "1.00000000" + ], + [ + "85000.00", + "0.02000000" + ], + [ + "85056.00", + "0.22000000" + ], + [ + "86000.00", + "0.30000000" + ], + [ + "87800.65", + "0.02814749" + ], + [ + "88495.00", + "0.01000000" + ], + [ + "88743.00", + "0.09700000" + ], + [ + "89899.00", + "4.00000000" + ], + [ + "89999.00", + "0.28068457" + ], + [ + "89999.09", + "0.60000000" + ], + [ + "90000.00", + "0.03000000" + ], + [ + "91000.00", + "0.02000000" + ], + [ + "91799.90", + "0.20000000" + ], + [ + "92000.00", + "0.01000000" + ], + [ + "92650.00", + "0.27233698" + ], + [ + "96800.00", + "0.83916560" + ], + [ + "97740.00", + "4.52500000" + ], + [ + "98650.66", + "1.00000000" + ], + [ + "98745.00", + "0.12500000" + ], + [ + "99500.00", + "0.03000000" + ], + [ + "99678.00", + "0.03354333" + ], + [ + "99940.51", + "0.09816342" + ], + [ + "99999.00", + "2.05000000" + ], + [ + "100000.00", + "3.36017259" + ], + [ + "101234.00", + "0.10000000" + ], + [ + "102067.00", + "0.24000000" + ], + [ + "105131.72", + "0.36570639" + ], + [ + "109750.81", + "0.02814749" + ], + [ + "111111.00", + "1.00000000" + ], + [ + "122481.00", + "0.26000000" + ], + [ + "128041.91", + "0.04292354" + ], + [ + "129563.00", + "0.12500000" + ], + [ + "137188.52", + "0.02814749" + ], + [ + "146977.00", + "0.28000000" + ], + [ + "150000.00", + "0.49792648" + ], + [ + "156870.66", + "1.00000000" + ], + [ + "158000.00", + "1.00000000" + ], + [ + "160000.00", + "0.00003271" + ], + [ + "171485.65", + "0.02814749" + ], + [ + "176373.00", + "0.30000000" + ], + [ + "178699.00", + "0.12500000" + ], + [ + "200000.00", + "0.01000000" + ], + [ + "201234.00", + "0.10000000" + ], + [ + "210000.00", + "0.10714033" + ], + [ + "211647.00", + "0.34000000" + ], + [ + "214357.06", + "0.02814749" + ], + [ + "238000.00", + "0.50000000" + ], + [ + "250000.00", + "0.51105866" + ], + [ + "253977.00", + "0.40000000" + ], + [ + "267946.33", + "0.02814749" + ], + [ + "277589.00", + "0.12500000" + ], + [ + "300000.00", + "0.02685644" + ], + [ + "300001.00", + "0.00010000" + ], + [ + "301234.00", + "0.05000000" + ], + [ + "302999.00", + "0.02000000" + ], + [ + "304772.00", + "0.50000000" + ], + [ + "334932.92", + "0.02814749" + ], + [ + "341000.00", + "0.00596168" + ], + [ + "365726.00", + "0.70000000" + ], + [ + "400202.00", + "0.02000000" + ], + [ + "418666.15", + "0.02814749" + ], + [ + "438871.00", + "1.00000000" + ], + [ + "448981.00", + "0.25000000" + ], + [ + "500000.00", + "1.01250000" + ], + [ + "523332.68", + "0.02814749" + ], + [ + "526646.00", + "1.00000000" + ], + [ + "631975.00", + "0.70000000" + ], + [ + "654165.86", + "0.02814749" + ], + [ + "796559.52", + "0.00211111" + ], + [ + "817707.32", + "0.02814749" + ], + [ + "875000.00", + "0.60000000" + ], + [ + "888000.00", + "0.50000000" + ], + [ + "888888.00", + "0.00100000" + ], + [ + "889999.00", + "0.02000000" + ], + [ + "999900.00", + "0.56600000" + ], + [ + "999999.00", + "19.37965338" + ], + [ + "1000000.00", + "4.26782108" + ], + [ + "1022134.15", + "0.02814749" + ], + [ + "1300000.00", + "0.30075883" + ], + [ + "2000000.00", + "0.47922827" + ], + [ + "2500000.00", + "0.01000000" + ], + [ + "5000000.00", + "1.01003223" + ], + [ + "10000000.00", + "2.75837235" + ], + [ + "15000000.00", + "0.25000000" + ], + [ + "25000000.00", + "0.01000000" + ], + [ + "50000000.00", + "0.02500000" + ], + [ + "1000000000.00", + "0.00200000" + ] + ] + }, + "queryString": "", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v2/ticker/btcusd/": { + "GET": [ + { + "data": { + "high": "8335.56", + "last": "8213.59", + "timestamp": "1560482195", + "bid": "8210.38", + "vwap": "8190.45", + "volume": "6793.04518082", + "low": "8049.22", + "ask": "8213.59", + "open": "8235.44" + }, + "queryString": "", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v2/trading-pairs-info/": { + "GET": [ + { + "data": [ + { + "base_decimals": 8, + "minimum_order": "5.0 USD", + "name": "LTC/USD", + "counter_decimals": 2, + "trading": "Enabled", + "url_symbol": "ltcusd", + "description": "Litecoin / U.S. dollar" + }, + { + "base_decimals": 8, + "minimum_order": "5.0 USD", + "name": "ETH/USD", + "counter_decimals": 2, + "trading": "Enabled", + "url_symbol": "ethusd", + "description": "Ether / U.S. dollar" + }, + { + "base_decimals": 8, + "minimum_order": "5.0 EUR", + "name": "XRP/EUR", + "counter_decimals": 5, + "trading": "Enabled", + "url_symbol": "xrpeur", + "description": "XRP / Euro" + }, + { + "base_decimals": 8, + "minimum_order": "5.0 USD", + "name": "BCH/USD", + "counter_decimals": 2, + "trading": "Enabled", + "url_symbol": "bchusd", + "description": "Bitcoin Cash / U.S. dollar" + }, + { + "base_decimals": 8, + "minimum_order": "5.0 EUR", + "name": "BCH/EUR", + "counter_decimals": 2, + "trading": "Enabled", + "url_symbol": "bcheur", + "description": "Bitcoin Cash / Euro" + }, + { + "base_decimals": 8, + "minimum_order": "5.0 EUR", + "name": "BTC/EUR", + "counter_decimals": 2, + "trading": "Enabled", + "url_symbol": "btceur", + "description": "Bitcoin / Euro" + }, + { + "base_decimals": 8, + "minimum_order": "0.001 BTC", + "name": "XRP/BTC", + "counter_decimals": 8, + "trading": "Enabled", + "url_symbol": "xrpbtc", + "description": "XRP / Bitcoin" + }, + { + "base_decimals": 5, + "minimum_order": "5.0 USD", + "name": "EUR/USD", + "counter_decimals": 5, + "trading": "Enabled", + "url_symbol": "eurusd", + "description": "Euro / U.S. dollar" + }, + { + "base_decimals": 8, + "minimum_order": "0.001 BTC", + "name": "BCH/BTC", + "counter_decimals": 8, + "trading": "Enabled", + "url_symbol": "bchbtc", + "description": "Bitcoin Cash / Bitcoin" + }, + { + "base_decimals": 8, + "minimum_order": "5.0 EUR", + "name": "LTC/EUR", + "counter_decimals": 2, + "trading": "Enabled", + "url_symbol": "ltceur", + "description": "Litecoin / Euro" + }, + { + "base_decimals": 8, + "minimum_order": "5.0 USD", + "name": "BTC/USD", + "counter_decimals": 2, + "trading": "Enabled", + "url_symbol": "btcusd", + "description": "Bitcoin / U.S. dollar" + }, + { + "base_decimals": 8, + "minimum_order": "0.001 BTC", + "name": "LTC/BTC", + "counter_decimals": 8, + "trading": "Enabled", + "url_symbol": "ltcbtc", + "description": "Litecoin / Bitcoin" + }, + { + "base_decimals": 8, + "minimum_order": "5.0 USD", + "name": "XRP/USD", + "counter_decimals": 5, + "trading": "Enabled", + "url_symbol": "xrpusd", + "description": "XRP / U.S. dollar" + }, + { + "base_decimals": 8, + "minimum_order": "0.001 BTC", + "name": "ETH/BTC", + "counter_decimals": 8, + "trading": "Enabled", + "url_symbol": "ethbtc", + "description": "Ether / Bitcoin" + }, + { + "base_decimals": 8, + "minimum_order": "5.0 EUR", + "name": "ETH/EUR", + "counter_decimals": 2, + "trading": "Enabled", + "url_symbol": "etheur", + "description": "Ether / Euro" + } + ], + "queryString": "", + "bodyParams": "\u003cnil\u003e", + "headers": { + "Referer": [ + "https://www.bitstamp.net/api/v2/trading-pairs-info" + ] + } + } + ] + }, + "/api/v2/transactions/btcusd/": { + "GET": [ + { + "data": [ + { + "date": "1560481868", + "tid": "90574283", + "price": "8194.79", + "type": "1", + "amount": "0.00288599" + }, + { + "date": "1560481858", + "tid": "90574282", + "price": "8193.12", + "type": "1", + "amount": "0.00156303" + }, + { + "date": "1560481854", + "tid": "90574281", + "price": "8193.12", + "type": "1", + "amount": "0.11947600" + }, + { + "date": "1560481828", + "tid": "90574272", + "price": "8193.12", + "type": "1", + "amount": "0.00141971" + }, + { + "date": "1560481819", + "tid": "90574264", + "price": "8197.67", + "type": "0", + "amount": "0.00119500" + }, + { + "date": "1560481808", + "tid": "90574258", + "price": "8195.95", + "type": "0", + "amount": "0.01725404" + }, + { + "date": "1560481798", + "tid": "90574252", + "price": "8195.95", + "type": "0", + "amount": "0.00416020" + }, + { + "date": "1560481787", + "tid": "90574250", + "price": "8191.11", + "type": "1", + "amount": "0.00417354" + }, + { + "date": "1560481781", + "tid": "90574249", + "price": "8194.06", + "type": "1", + "amount": "0.00430000" + }, + { + "date": "1560481747", + "tid": "90574247", + "price": "8195.89", + "type": "0", + "amount": "0.00124352" + }, + { + "date": "1560481728", + "tid": "90574242", + "price": "8194.19", + "type": "0", + "amount": "0.82077122" + }, + { + "date": "1560481700", + "tid": "90574236", + "price": "8190.75", + "type": "1", + "amount": "0.01277685" + }, + { + "date": "1560481687", + "tid": "90574234", + "price": "8193.91", + "type": "0", + "amount": "1.06050603" + }, + { + "date": "1560481686", + "tid": "90574231", + "price": "8193.91", + "type": "1", + "amount": "0.05997981" + }, + { + "date": "1560481685", + "tid": "90574229", + "price": "8193.43", + "type": "0", + "amount": "0.04703946" + }, + { + "date": "1560481677", + "tid": "90574226", + "price": "8193.00", + "type": "0", + "amount": "0.00994649" + }, + { + "date": "1560481664", + "tid": "90574225", + "price": "8193.91", + "type": "0", + "amount": "0.41363917" + }, + { + "date": "1560481662", + "tid": "90574224", + "price": "8193.43", + "type": "1", + "amount": "0.99999996" + }, + { + "date": "1560481652", + "tid": "90574216", + "price": "8193.45", + "type": "1", + "amount": "1.37549538" + }, + { + "date": "1560481639", + "tid": "90574209", + "price": "8197.30", + "type": "0", + "amount": "0.25624057" + }, + { + "date": "1560481638", + "tid": "90574207", + "price": "8197.30", + "type": "0", + "amount": "0.09860505" + }, + { + "date": "1560481638", + "tid": "90574206", + "price": "8197.30", + "type": "0", + "amount": "0.64515438" + }, + { + "date": "1560481619", + "tid": "90574203", + "price": "8198.93", + "type": "0", + "amount": "0.72516277" + }, + { + "date": "1560481601", + "tid": "90574202", + "price": "8199.00", + "type": "1", + "amount": "0.25493189" + }, + { + "date": "1560481587", + "tid": "90574200", + "price": "8201.82", + "type": "0", + "amount": "0.01662791" + }, + { + "date": "1560481577", + "tid": "90574196", + "price": "8201.72", + "type": "0", + "amount": "0.04141934" + }, + { + "date": "1560481567", + "tid": "90574192", + "price": "8202.10", + "type": "0", + "amount": "0.02485456" + }, + { + "date": "1560481557", + "tid": "90574191", + "price": "8202.10", + "type": "0", + "amount": "0.00063795" + }, + { + "date": "1560481547", + "tid": "90574188", + "price": "8202.04", + "type": "0", + "amount": "1.03008232" + }, + { + "date": "1560481547", + "tid": "90574187", + "price": "8200.15", + "type": "0", + "amount": "0.12771209" + }, + { + "date": "1560481531", + "tid": "90574183", + "price": "8200.15", + "type": "0", + "amount": "0.76353303" + }, + { + "date": "1560481529", + "tid": "90574181", + "price": "8199.00", + "type": "1", + "amount": "0.01506811" + }, + { + "date": "1560481516", + "tid": "90574180", + "price": "8200.15", + "type": "0", + "amount": "0.00671556" + }, + { + "date": "1560481497", + "tid": "90574172", + "price": "8200.15", + "type": "0", + "amount": "0.00116003" + }, + { + "date": "1560481467", + "tid": "90574158", + "price": "8200.15", + "type": "0", + "amount": "0.03170947" + }, + { + "date": "1560481457", + "tid": "90574154", + "price": "8200.15", + "type": "0", + "amount": "0.00157439" + }, + { + "date": "1560481447", + "tid": "90574153", + "price": "8199.67", + "type": "1", + "amount": "0.03848400" + }, + { + "date": "1560481431", + "tid": "90574150", + "price": "8200.15", + "type": "0", + "amount": "0.02722810" + }, + { + "date": "1560481417", + "tid": "90574148", + "price": "8200.15", + "type": "0", + "amount": "0.01776867" + }, + { + "date": "1560481408", + "tid": "90574147", + "price": "8201.83", + "type": "0", + "amount": "0.00082837" + }, + { + "date": "1560481381", + "tid": "90574138", + "price": "8198.44", + "type": "1", + "amount": "0.00580000" + }, + { + "date": "1560481377", + "tid": "90574137", + "price": "8201.36", + "type": "0", + "amount": "1.03424883" + }, + { + "date": "1560481368", + "tid": "90574136", + "price": "8201.36", + "type": "0", + "amount": "0.00350600" + }, + { + "date": "1560481352", + "tid": "90574133", + "price": "8201.36", + "type": "0", + "amount": "0.02268749" + }, + { + "date": "1560481347", + "tid": "90574130", + "price": "8201.36", + "type": "0", + "amount": "0.00082841" + }, + { + "date": "1560481329", + "tid": "90574126", + "price": "8198.21", + "type": "1", + "amount": "0.01075383" + }, + { + "date": "1560481294", + "tid": "90574121", + "price": "8201.92", + "type": "1", + "amount": "0.02259866" + }, + { + "date": "1560481293", + "tid": "90574120", + "price": "8202.21", + "type": "1", + "amount": "0.02700000" + }, + { + "date": "1560481286", + "tid": "90574119", + "price": "8202.21", + "type": "1", + "amount": "0.03663485" + }, + { + "date": "1560481277", + "tid": "90574116", + "price": "8202.21", + "type": "1", + "amount": "0.01078744" + }, + { + "date": "1560481277", + "tid": "90574115", + "price": "8204.01", + "type": "0", + "amount": "1.03414367" + }, + { + "date": "1560481257", + "tid": "90574111", + "price": "8202.21", + "type": "1", + "amount": "0.01717467" + }, + { + "date": "1560481247", + "tid": "90574108", + "price": "8203.91", + "type": "0", + "amount": "0.42117205" + }, + { + "date": "1560481186", + "tid": "90574103", + "price": "8205.69", + "type": "0", + "amount": "0.01056779" + }, + { + "date": "1560481166", + "tid": "90574099", + "price": "8207.44", + "type": "0", + "amount": "0.00827493" + }, + { + "date": "1560481164", + "tid": "90574098", + "price": "8206.02", + "type": "1", + "amount": "0.00127718" + }, + { + "date": "1560481156", + "tid": "90574095", + "price": "8207.12", + "type": "1", + "amount": "0.00075400" + }, + { + "date": "1560481126", + "tid": "90574091", + "price": "8211.59", + "type": "0", + "amount": "0.00413715" + }, + { + "date": "1560481086", + "tid": "90574090", + "price": "8212.12", + "type": "0", + "amount": "0.00271006" + }, + { + "date": "1560481078", + "tid": "90574089", + "price": "8212.12", + "type": "0", + "amount": "0.00136500" + }, + { + "date": "1560481066", + "tid": "90574088", + "price": "8212.12", + "type": "0", + "amount": "0.02733873" + }, + { + "date": "1560481066", + "tid": "90574087", + "price": "8212.12", + "type": "0", + "amount": "0.00165491" + }, + { + "date": "1560481056", + "tid": "90574036", + "price": "8212.12", + "type": "0", + "amount": "0.00247470" + }, + { + "date": "1560481053", + "tid": "90574035", + "price": "8207.04", + "type": "1", + "amount": "0.15965733" + }, + { + "date": "1560481053", + "tid": "90574034", + "price": "8207.04", + "type": "1", + "amount": "0.26734267" + }, + { + "date": "1560481052", + "tid": "90574033", + "price": "8207.04", + "type": "1", + "amount": "0.05474916" + }, + { + "date": "1560481051", + "tid": "90574032", + "price": "8208.87", + "type": "1", + "amount": "0.94525084" + }, + { + "date": "1560481027", + "tid": "90574030", + "price": "8211.35", + "type": "0", + "amount": "0.93461738" + }, + { + "date": "1560481026", + "tid": "90574029", + "price": "8208.07", + "type": "0", + "amount": "0.00098009" + }, + { + "date": "1560481003", + "tid": "90574027", + "price": "8201.94", + "type": "0", + "amount": "0.38000000" + }, + { + "date": "1560481003", + "tid": "90574026", + "price": "8201.94", + "type": "1", + "amount": "0.02000000" + }, + { + "date": "1560481003", + "tid": "90574025", + "price": "8202.04", + "type": "0", + "amount": "0.50000000" + }, + { + "date": "1560480998", + "tid": "90574024", + "price": "8209.11", + "type": "0", + "amount": "0.76351584" + }, + { + "date": "1560480996", + "tid": "90574023", + "price": "8209.11", + "type": "0", + "amount": "0.02838519" + }, + { + "date": "1560480995", + "tid": "90574022", + "price": "8209.11", + "type": "0", + "amount": "0.01154327" + }, + { + "date": "1560480986", + "tid": "90574020", + "price": "8205.95", + "type": "0", + "amount": "0.00609385" + }, + { + "date": "1560480986", + "tid": "90574019", + "price": "8205.51", + "type": "0", + "amount": "0.00005444" + }, + { + "date": "1560480979", + "tid": "90574018", + "price": "8205.51", + "type": "0", + "amount": "0.00096529" + }, + { + "date": "1560480954", + "tid": "90574015", + "price": "8203.97", + "type": "0", + "amount": "0.01951399" + }, + { + "date": "1560480926", + "tid": "90574012", + "price": "8200.65", + "type": "1", + "amount": "0.00193497" + }, + { + "date": "1560480924", + "tid": "90574011", + "price": "8204.61", + "type": "0", + "amount": "0.00371156" + }, + { + "date": "1560480924", + "tid": "90574010", + "price": "8201.60", + "type": "0", + "amount": "0.00107544" + }, + { + "date": "1560480916", + "tid": "90574009", + "price": "8203.99", + "type": "0", + "amount": "0.00082806" + }, + { + "date": "1560480910", + "tid": "90574008", + "price": "8206.16", + "type": "0", + "amount": "2.56982410" + }, + { + "date": "1560480910", + "tid": "90574007", + "price": "8204.78", + "type": "0", + "amount": "0.43017590" + }, + { + "date": "1560480886", + "tid": "90574005", + "price": "8201.71", + "type": "1", + "amount": "0.02170656" + }, + { + "date": "1560480886", + "tid": "90574004", + "price": "8201.71", + "type": "1", + "amount": "0.04765181" + }, + { + "date": "1560480865", + "tid": "90574003", + "price": "8205.56", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560480864", + "tid": "90574002", + "price": "8204.78", + "type": "0", + "amount": "4.25102766" + }, + { + "date": "1560480856", + "tid": "90574001", + "price": "8201.71", + "type": "1", + "amount": "0.00068040" + }, + { + "date": "1560480846", + "tid": "90573998", + "price": "8204.43", + "type": "0", + "amount": "0.00662655" + }, + { + "date": "1560480840", + "tid": "90573996", + "price": "8201.71", + "type": "1", + "amount": "0.13295981" + }, + { + "date": "1560480836", + "tid": "90573995", + "price": "8202.78", + "type": "0", + "amount": "0.00968757" + }, + { + "date": "1560480834", + "tid": "90573994", + "price": "8201.71", + "type": "1", + "amount": "0.26819887" + }, + { + "date": "1560480834", + "tid": "90573992", + "price": "8202.78", + "type": "0", + "amount": "0.06688532" + }, + { + "date": "1560480826", + "tid": "90573979", + "price": "8202.78", + "type": "0", + "amount": "0.00372670" + }, + { + "date": "1560480816", + "tid": "90573977", + "price": "8201.71", + "type": "1", + "amount": "0.46546655" + }, + { + "date": "1560480807", + "tid": "90573976", + "price": "8204.43", + "type": "0", + "amount": "0.12785477" + }, + { + "date": "1560480796", + "tid": "90573975", + "price": "8204.43", + "type": "0", + "amount": "0.02153265" + }, + { + "date": "1560480775", + "tid": "90573968", + "price": "8204.43", + "type": "0", + "amount": "0.28041955" + }, + { + "date": "1560480775", + "tid": "90573967", + "price": "8204.43", + "type": "0", + "amount": "0.28043567" + }, + { + "date": "1560480775", + "tid": "90573965", + "price": "8204.43", + "type": "0", + "amount": "0.28044676" + }, + { + "date": "1560480766", + "tid": "90573964", + "price": "8201.71", + "type": "1", + "amount": "0.01591045" + }, + { + "date": "1560480746", + "tid": "90573962", + "price": "8204.78", + "type": "0", + "amount": "0.00545812" + }, + { + "date": "1560480735", + "tid": "90573961", + "price": "8204.78", + "type": "0", + "amount": "0.00082781" + }, + { + "date": "1560480726", + "tid": "90573960", + "price": "8207.39", + "type": "0", + "amount": "0.00082781" + }, + { + "date": "1560480695", + "tid": "90573959", + "price": "8207.13", + "type": "0", + "amount": "0.03311518" + }, + { + "date": "1560480691", + "tid": "90573958", + "price": "8207.13", + "type": "0", + "amount": "0.07249340" + }, + { + "date": "1560480685", + "tid": "90573957", + "price": "8207.75", + "type": "0", + "amount": "0.00889152" + }, + { + "date": "1560480685", + "tid": "90573956", + "price": "8207.13", + "type": "0", + "amount": "0.15129841" + }, + { + "date": "1560480676", + "tid": "90573954", + "price": "8205.74", + "type": "1", + "amount": "0.25600000" + }, + { + "date": "1560480675", + "tid": "90573953", + "price": "8207.13", + "type": "0", + "amount": "0.00612624" + }, + { + "date": "1560480655", + "tid": "90573952", + "price": "8207.13", + "type": "0", + "amount": "0.08273031" + }, + { + "date": "1560480647", + "tid": "90573950", + "price": "8209.84", + "type": "0", + "amount": "0.00243001" + }, + { + "date": "1560480645", + "tid": "90573949", + "price": "8205.81", + "type": "1", + "amount": "0.00460139" + }, + { + "date": "1560480645", + "tid": "90573948", + "price": "8207.04", + "type": "1", + "amount": "0.00300700" + }, + { + "date": "1560480639", + "tid": "90573946", + "price": "8210.59", + "type": "0", + "amount": "1.40607657" + }, + { + "date": "1560480624", + "tid": "90573942", + "price": "8206.43", + "type": "1", + "amount": "0.43326786" + }, + { + "date": "1560480624", + "tid": "90573941", + "price": "8206.43", + "type": "1", + "amount": "0.56623214" + }, + { + "date": "1560480595", + "tid": "90573935", + "price": "8210.65", + "type": "0", + "amount": "0.16543346" + }, + { + "date": "1560480585", + "tid": "90573934", + "price": "8210.58", + "type": "0", + "amount": "0.00827500" + }, + { + "date": "1560480565", + "tid": "90573930", + "price": "8212.27", + "type": "0", + "amount": "0.08273535" + }, + { + "date": "1560480554", + "tid": "90573919", + "price": "8212.27", + "type": "0", + "amount": "0.02887220" + }, + { + "date": "1560480546", + "tid": "90573914", + "price": "8210.58", + "type": "0", + "amount": "0.00414357" + }, + { + "date": "1560480538", + "tid": "90573913", + "price": "8210.58", + "type": "0", + "amount": "0.02673700" + }, + { + "date": "1560480511", + "tid": "90573911", + "price": "8209.15", + "type": "1", + "amount": "0.12369077" + }, + { + "date": "1560480485", + "tid": "90573909", + "price": "8210.08", + "type": "0", + "amount": "0.00453028" + }, + { + "date": "1560480476", + "tid": "90573904", + "price": "8210.09", + "type": "0", + "amount": "0.00992971" + }, + { + "date": "1560480459", + "tid": "90573902", + "price": "8212.09", + "type": "0", + "amount": "0.00245534" + }, + { + "date": "1560480444", + "tid": "90573900", + "price": "8212.09", + "type": "1", + "amount": "0.00576635" + }, + { + "date": "1560480435", + "tid": "90573899", + "price": "8215.53", + "type": "0", + "amount": "0.01739012" + }, + { + "date": "1560480415", + "tid": "90573885", + "price": "8217.17", + "type": "0", + "amount": "0.05456435" + }, + { + "date": "1560480410", + "tid": "90573884", + "price": "8210.91", + "type": "1", + "amount": "0.01260000" + }, + { + "date": "1560480390", + "tid": "90573883", + "price": "8212.76", + "type": "0", + "amount": "0.76245209" + }, + { + "date": "1560480390", + "tid": "90573882", + "price": "8212.76", + "type": "1", + "amount": "0.05865337" + }, + { + "date": "1560480385", + "tid": "90573880", + "price": "8215.47", + "type": "0", + "amount": "0.02067307" + }, + { + "date": "1560480373", + "tid": "90573875", + "price": "8215.47", + "type": "0", + "amount": "0.00240932" + }, + { + "date": "1560480367", + "tid": "90573873", + "price": "8216.63", + "type": "0", + "amount": "0.00576635" + }, + { + "date": "1560480352", + "tid": "90573867", + "price": "8217.44", + "type": "1", + "amount": "0.46810000" + }, + { + "date": "1560480338", + "tid": "90573862", + "price": "8218.17", + "type": "0", + "amount": "0.02025940" + }, + { + "date": "1560480335", + "tid": "90573860", + "price": "8212.55", + "type": "1", + "amount": "0.18946883" + }, + { + "date": "1560480335", + "tid": "90573859", + "price": "8212.55", + "type": "1", + "amount": "0.02449105" + }, + { + "date": "1560480325", + "tid": "90573857", + "price": "8217.44", + "type": "0", + "amount": "0.07698833" + }, + { + "date": "1560480315", + "tid": "90573855", + "price": "8212.45", + "type": "1", + "amount": "0.02389867" + }, + { + "date": "1560480305", + "tid": "90573850", + "price": "8217.44", + "type": "0", + "amount": "0.01663677" + }, + { + "date": "1560480295", + "tid": "90573849", + "price": "8216.54", + "type": "0", + "amount": "0.00372047" + }, + { + "date": "1560480234", + "tid": "90573833", + "price": "8216.40", + "type": "0", + "amount": "0.01385378" + }, + { + "date": "1560480214", + "tid": "90573828", + "price": "8216.40", + "type": "0", + "amount": "0.00414622" + }, + { + "date": "1560480184", + "tid": "90573823", + "price": "8214.90", + "type": "0", + "amount": "0.00324999" + }, + { + "date": "1560480174", + "tid": "90573820", + "price": "8210.60", + "type": "1", + "amount": "0.78875913" + }, + { + "date": "1560480164", + "tid": "90573819", + "price": "8210.48", + "type": "1", + "amount": "0.00708531" + }, + { + "date": "1560480155", + "tid": "90573802", + "price": "8210.55", + "type": "1", + "amount": "0.01086528" + }, + { + "date": "1560480144", + "tid": "90573798", + "price": "8214.35", + "type": "0", + "amount": "0.02017215" + }, + { + "date": "1560480134", + "tid": "90573797", + "price": "8210.61", + "type": "0", + "amount": "0.72610452" + }, + { + "date": "1560480114", + "tid": "90573777", + "price": "8210.61", + "type": "0", + "amount": "0.01811930" + }, + { + "date": "1560480107", + "tid": "90573776", + "price": "8210.61", + "type": "0", + "amount": "0.00204686" + }, + { + "date": "1560480094", + "tid": "90573774", + "price": "8208.97", + "type": "0", + "amount": "0.00372462" + }, + { + "date": "1560480074", + "tid": "90573773", + "price": "8208.97", + "type": "0", + "amount": "0.00082730" + }, + { + "date": "1560480064", + "tid": "90573772", + "price": "8208.96", + "type": "1", + "amount": "0.00479372" + }, + { + "date": "1560480055", + "tid": "90573770", + "price": "8214.35", + "type": "0", + "amount": "0.00082785" + }, + { + "date": "1560480044", + "tid": "90573769", + "price": "8209.60", + "type": "1", + "amount": "0.08666623" + }, + { + "date": "1560480044", + "tid": "90573768", + "price": "8209.60", + "type": "1", + "amount": "0.04419220" + }, + { + "date": "1560480043", + "tid": "90573767", + "price": "8209.60", + "type": "1", + "amount": "0.35000000" + }, + { + "date": "1560480024", + "tid": "90573766", + "price": "8212.35", + "type": "0", + "amount": "0.00082751" + }, + { + "date": "1560480009", + "tid": "90573764", + "price": "8212.30", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560480005", + "tid": "90573760", + "price": "8209.60", + "type": "1", + "amount": "0.15401468" + }, + { + "date": "1560480001", + "tid": "90573759", + "price": "8212.81", + "type": "1", + "amount": "0.05746101" + }, + { + "date": "1560480000", + "tid": "90573758", + "price": "8212.81", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560479984", + "tid": "90573755", + "price": "8211.39", + "type": "0", + "amount": "0.00662006" + }, + { + "date": "1560479974", + "tid": "90573754", + "price": "8210.21", + "type": "0", + "amount": "0.00165513" + }, + { + "date": "1560479964", + "tid": "90573753", + "price": "8211.33", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560479964", + "tid": "90573752", + "price": "8211.32", + "type": "0", + "amount": "0.06000000" + }, + { + "date": "1560479954", + "tid": "90573751", + "price": "8209.75", + "type": "1", + "amount": "0.00476779" + }, + { + "date": "1560479954", + "tid": "90573750", + "price": "8209.75", + "type": "1", + "amount": "0.00694838" + }, + { + "date": "1560479934", + "tid": "90573747", + "price": "8212.60", + "type": "0", + "amount": "0.29311100" + }, + { + "date": "1560479934", + "tid": "90573746", + "price": "8212.30", + "type": "0", + "amount": "0.02100000" + }, + { + "date": "1560479919", + "tid": "90573744", + "price": "8210.25", + "type": "0", + "amount": "0.01700000" + }, + { + "date": "1560479919", + "tid": "90573743", + "price": "8210.06", + "type": "0", + "amount": "0.05675000" + }, + { + "date": "1560479914", + "tid": "90573742", + "price": "8206.14", + "type": "1", + "amount": "0.01009167" + }, + { + "date": "1560479912", + "tid": "90573741", + "price": "8209.05", + "type": "0", + "amount": "1.99995665" + }, + { + "date": "1560479912", + "tid": "90573740", + "price": "8208.20", + "type": "0", + "amount": "0.01900000" + }, + { + "date": "1560479907", + "tid": "90573739", + "price": "8206.14", + "type": "1", + "amount": "0.02438795" + }, + { + "date": "1560479874", + "tid": "90573738", + "price": "8204.09", + "type": "1", + "amount": "0.08065600" + }, + { + "date": "1560479834", + "tid": "90573734", + "price": "8206.93", + "type": "0", + "amount": "0.00490800" + }, + { + "date": "1560479814", + "tid": "90573732", + "price": "8207.06", + "type": "0", + "amount": "0.01614692" + }, + { + "date": "1560479814", + "tid": "90573731", + "price": "8206.15", + "type": "0", + "amount": "0.02100000" + }, + { + "date": "1560479814", + "tid": "90573730", + "price": "8204.10", + "type": "0", + "amount": "0.04486540" + }, + { + "date": "1560479794", + "tid": "90573727", + "price": "8204.10", + "type": "0", + "amount": "0.00245641" + }, + { + "date": "1560479794", + "tid": "90573726", + "price": "8204.10", + "type": "0", + "amount": "0.01800000" + }, + { + "date": "1560479785", + "tid": "90573724", + "price": "8202.35", + "type": "1", + "amount": "0.07670897" + }, + { + "date": "1560479785", + "tid": "90573723", + "price": "8204.09", + "type": "1", + "amount": "1.42329103" + }, + { + "date": "1560479769", + "tid": "90573722", + "price": "8200.00", + "type": "0", + "amount": "42.14277558" + }, + { + "date": "1560479768", + "tid": "90573721", + "price": "8200.00", + "type": "0", + "amount": "0.00656743" + }, + { + "date": "1560479759", + "tid": "90573717", + "price": "8200.00", + "type": "0", + "amount": "0.03025153" + }, + { + "date": "1560479754", + "tid": "90573713", + "price": "8200.00", + "type": "0", + "amount": "0.00414321" + }, + { + "date": "1560479723", + "tid": "90573712", + "price": "8200.00", + "type": "0", + "amount": "0.00248201" + }, + { + "date": "1560479720", + "tid": "90573711", + "price": "8200.00", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560479719", + "tid": "90573710", + "price": "8200.00", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560479718", + "tid": "90573709", + "price": "8200.00", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560479717", + "tid": "90573708", + "price": "8200.00", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560479716", + "tid": "90573707", + "price": "8200.00", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560479715", + "tid": "90573706", + "price": "8200.00", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560479714", + "tid": "90573705", + "price": "8200.00", + "type": "0", + "amount": "4.00000000" + }, + { + "date": "1560479713", + "tid": "90573704", + "price": "8200.00", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560479708", + "tid": "90573703", + "price": "8199.58", + "type": "1", + "amount": "1.00000000" + }, + { + "date": "1560479708", + "tid": "90573702", + "price": "8200.00", + "type": "0", + "amount": "0.38000000" + }, + { + "date": "1560479708", + "tid": "90573701", + "price": "8200.00", + "type": "0", + "amount": "0.12674813" + }, + { + "date": "1560479707", + "tid": "90573700", + "price": "8200.00", + "type": "0", + "amount": "1.75816073" + }, + { + "date": "1560479707", + "tid": "90573699", + "price": "8199.60", + "type": "0", + "amount": "3.47183927" + }, + { + "date": "1560479707", + "tid": "90573698", + "price": "8199.60", + "type": "0", + "amount": "0.06000000" + }, + { + "date": "1560479707", + "tid": "90573697", + "price": "8200.00", + "type": "0", + "amount": "8.00000000" + }, + { + "date": "1560479707", + "tid": "90573696", + "price": "8200.00", + "type": "0", + "amount": "4.00000000" + }, + { + "date": "1560479707", + "tid": "90573695", + "price": "8200.00", + "type": "0", + "amount": "0.03183927" + }, + { + "date": "1560479706", + "tid": "90573694", + "price": "8200.68", + "type": "1", + "amount": "8.00000000" + }, + { + "date": "1560479706", + "tid": "90573693", + "price": "8200.94", + "type": "1", + "amount": "2.50000000" + }, + { + "date": "1560479706", + "tid": "90573692", + "price": "8201.17", + "type": "1", + "amount": "10.00000000" + }, + { + "date": "1560479706", + "tid": "90573691", + "price": "8202.68", + "type": "1", + "amount": "0.06000000" + }, + { + "date": "1560479706", + "tid": "90573690", + "price": "8203.30", + "type": "1", + "amount": "4.00000000" + }, + { + "date": "1560479706", + "tid": "90573689", + "price": "8203.44", + "type": "1", + "amount": "5.00000000" + }, + { + "date": "1560479706", + "tid": "90573688", + "price": "8203.56", + "type": "1", + "amount": "1.00000000" + }, + { + "date": "1560479706", + "tid": "90573687", + "price": "8204.21", + "type": "1", + "amount": "3.04400000" + }, + { + "date": "1560479706", + "tid": "90573686", + "price": "8204.21", + "type": "1", + "amount": "1.46816073" + }, + { + "date": "1560479706", + "tid": "90573685", + "price": "8204.40", + "type": "1", + "amount": "2.50000000" + }, + { + "date": "1560479706", + "tid": "90573684", + "price": "8207.00", + "type": "1", + "amount": "0.98487138" + }, + { + "date": "1560479706", + "tid": "90573683", + "price": "8207.00", + "type": "1", + "amount": "0.80000000" + }, + { + "date": "1560479705", + "tid": "90573682", + "price": "8207.01", + "type": "1", + "amount": "0.02000000" + }, + { + "date": "1560479704", + "tid": "90573681", + "price": "8207.01", + "type": "0", + "amount": "0.66488753" + }, + { + "date": "1560479703", + "tid": "90573680", + "price": "8207.01", + "type": "1", + "amount": "0.02000000" + }, + { + "date": "1560479703", + "tid": "90573678", + "price": "8212.53", + "type": "0", + "amount": "0.00686005" + }, + { + "date": "1560479684", + "tid": "90573677", + "price": "8207.00", + "type": "1", + "amount": "0.20000000" + }, + { + "date": "1560479673", + "tid": "90573676", + "price": "8208.88", + "type": "1", + "amount": "0.76618189" + }, + { + "date": "1560479673", + "tid": "90573675", + "price": "8208.88", + "type": "1", + "amount": "0.02187674" + }, + { + "date": "1560479663", + "tid": "90573674", + "price": "8213.18", + "type": "0", + "amount": "0.95208092" + }, + { + "date": "1560479663", + "tid": "90573673", + "price": "8212.73", + "type": "0", + "amount": "0.02600000" + }, + { + "date": "1560479663", + "tid": "90573672", + "price": "8210.68", + "type": "0", + "amount": "0.02472458" + }, + { + "date": "1560479661", + "tid": "90573671", + "price": "8207.01", + "type": "0", + "amount": "0.08000000" + }, + { + "date": "1560479654", + "tid": "90573670", + "price": "8207.01", + "type": "1", + "amount": "0.02000000" + }, + { + "date": "1560479613", + "tid": "90573665", + "price": "8210.68", + "type": "0", + "amount": "0.00427542" + }, + { + "date": "1560479576", + "tid": "90573660", + "price": "8210.41", + "type": "0", + "amount": "0.05321761" + }, + { + "date": "1560479564", + "tid": "90573656", + "price": "8212.18", + "type": "0", + "amount": "0.00993314" + }, + { + "date": "1560479560", + "tid": "90573654", + "price": "8208.74", + "type": "0", + "amount": "0.00005662" + }, + { + "date": "1560479560", + "tid": "90573653", + "price": "8208.30", + "type": "0", + "amount": "0.99089157" + }, + { + "date": "1560479553", + "tid": "90573649", + "price": "8208.30", + "type": "0", + "amount": "0.00910843" + }, + { + "date": "1560479519", + "tid": "90573640", + "price": "8204.52", + "type": "0", + "amount": "0.02578028" + }, + { + "date": "1560479519", + "tid": "90573639", + "price": "8203.04", + "type": "0", + "amount": "0.00257719" + }, + { + "date": "1560479514", + "tid": "90573634", + "price": "8204.52", + "type": "0", + "amount": "0.00655700" + }, + { + "date": "1560479510", + "tid": "90573632", + "price": "8200.77", + "type": "1", + "amount": "0.05154382" + }, + { + "date": "1560479503", + "tid": "90573629", + "price": "8200.55", + "type": "0", + "amount": "1.03087649" + }, + { + "date": "1560479503", + "tid": "90573628", + "price": "8199.24", + "type": "0", + "amount": "0.05472265" + }, + { + "date": "1560479483", + "tid": "90573627", + "price": "8197.31", + "type": "1", + "amount": "0.03183927" + }, + { + "date": "1560479452", + "tid": "90573622", + "price": "8202.13", + "type": "0", + "amount": "0.01220000" + }, + { + "date": "1560479444", + "tid": "90573621", + "price": "8201.44", + "type": "0", + "amount": "0.00789578" + }, + { + "date": "1560479438", + "tid": "90573618", + "price": "8199.00", + "type": "1", + "amount": "2.02000000" + }, + { + "date": "1560479427", + "tid": "90573616", + "price": "8207.28", + "type": "0", + "amount": "0.00474456" + }, + { + "date": "1560479416", + "tid": "90573615", + "price": "8205.60", + "type": "0", + "amount": "0.05000000" + }, + { + "date": "1560479412", + "tid": "90573614", + "price": "8205.55", + "type": "0", + "amount": "0.03500000" + }, + { + "date": "1560479390", + "tid": "90573611", + "price": "8207.28", + "type": "0", + "amount": "0.09343478" + }, + { + "date": "1560479384", + "tid": "90573610", + "price": "8207.37", + "type": "0", + "amount": "0.02500000" + }, + { + "date": "1560479373", + "tid": "90573609", + "price": "8210.85", + "type": "0", + "amount": "0.05738772" + }, + { + "date": "1560479363", + "tid": "90573608", + "price": "8210.85", + "type": "0", + "amount": "0.00082739" + }, + { + "date": "1560479356", + "tid": "90573606", + "price": "8210.85", + "type": "0", + "amount": "0.09030000" + }, + { + "date": "1560479343", + "tid": "90573602", + "price": "8207.86", + "type": "0", + "amount": "0.00372343" + }, + { + "date": "1560479324", + "tid": "90573597", + "price": "8208.17", + "type": "0", + "amount": "0.07500000" + }, + { + "date": "1560479323", + "tid": "90573596", + "price": "8208.17", + "type": "0", + "amount": "0.00860000" + }, + { + "date": "1560479322", + "tid": "90573595", + "price": "8208.17", + "type": "0", + "amount": "0.00307000" + }, + { + "date": "1560479313", + "tid": "90573592", + "price": "8209.85", + "type": "0", + "amount": "0.00835703" + }, + { + "date": "1560479261", + "tid": "90573560", + "price": "8212.07", + "type": "0", + "amount": "0.12471398" + }, + { + "date": "1560479252", + "tid": "90573559", + "price": "8212.07", + "type": "0", + "amount": "0.00253650" + }, + { + "date": "1560479232", + "tid": "90573558", + "price": "8211.02", + "type": "0", + "amount": "0.01994223" + }, + { + "date": "1560479212", + "tid": "90573555", + "price": "8210.80", + "type": "0", + "amount": "0.02480593" + }, + { + "date": "1560479203", + "tid": "90573551", + "price": "8207.95", + "type": "1", + "amount": "0.02993723" + }, + { + "date": "1560479192", + "tid": "90573548", + "price": "8211.09", + "type": "0", + "amount": "0.00082751" + }, + { + "date": "1560479183", + "tid": "90573547", + "price": "8211.09", + "type": "0", + "amount": "0.03053248" + }, + { + "date": "1560479173", + "tid": "90573542", + "price": "8212.79", + "type": "0", + "amount": "0.00086431" + }, + { + "date": "1560479162", + "tid": "90573540", + "price": "8212.79", + "type": "0", + "amount": "0.02067115" + }, + { + "date": "1560479157", + "tid": "90573536", + "price": "8213.74", + "type": "1", + "amount": "0.28923200" + }, + { + "date": "1560479155", + "tid": "90573534", + "price": "8212.73", + "type": "1", + "amount": "0.02000000" + }, + { + "date": "1560479153", + "tid": "90573533", + "price": "8217.40", + "type": "0", + "amount": "0.09139912" + }, + { + "date": "1560479122", + "tid": "90573526", + "price": "8214.96", + "type": "0", + "amount": "0.04127993" + }, + { + "date": "1560479113", + "tid": "90573523", + "price": "8214.96", + "type": "0", + "amount": "0.00638833" + }, + { + "date": "1560479113", + "tid": "90573522", + "price": "8214.96", + "type": "0", + "amount": "0.00602517" + }, + { + "date": "1560479113", + "tid": "90573521", + "price": "8214.96", + "type": "0", + "amount": "0.00583306" + }, + { + "date": "1560479113", + "tid": "90573520", + "price": "8214.96", + "type": "0", + "amount": "0.00711027" + }, + { + "date": "1560479113", + "tid": "90573519", + "price": "8214.96", + "type": "0", + "amount": "0.01226392" + }, + { + "date": "1560479113", + "tid": "90573518", + "price": "8214.96", + "type": "0", + "amount": "0.01643246" + }, + { + "date": "1560479113", + "tid": "90573517", + "price": "8214.96", + "type": "0", + "amount": "0.11106518" + }, + { + "date": "1560479113", + "tid": "90573516", + "price": "8214.96", + "type": "0", + "amount": "0.02301980" + }, + { + "date": "1560479112", + "tid": "90573515", + "price": "8215.80", + "type": "0", + "amount": "0.07859474" + }, + { + "date": "1560479111", + "tid": "90573513", + "price": "8217.76", + "type": "1", + "amount": "0.04000000" + }, + { + "date": "1560479092", + "tid": "90573510", + "price": "8214.44", + "type": "0", + "amount": "0.00574909" + }, + { + "date": "1560479072", + "tid": "90573505", + "price": "8212.73", + "type": "1", + "amount": "0.02781989" + }, + { + "date": "1560479043", + "tid": "90573497", + "price": "8215.30", + "type": "0", + "amount": "0.00827258" + }, + { + "date": "1560479025", + "tid": "90573491", + "price": "8213.30", + "type": "0", + "amount": "0.05768813" + }, + { + "date": "1560479002", + "tid": "90573485", + "price": "8213.07", + "type": "0", + "amount": "0.00827855" + }, + { + "date": "1560478992", + "tid": "90573484", + "price": "8207.38", + "type": "0", + "amount": "0.01241108" + }, + { + "date": "1560478972", + "tid": "90573470", + "price": "8215.53", + "type": "0", + "amount": "0.21039602" + }, + { + "date": "1560478972", + "tid": "90573469", + "price": "8215.25", + "type": "0", + "amount": "1.00000000" + }, + { + "date": "1560478972", + "tid": "90573468", + "price": "8212.91", + "type": "0", + "amount": "0.64699989" + }, + { + "date": "1560478972", + "tid": "90573467", + "price": "8212.34", + "type": "0", + "amount": "0.14260409" + }, + { + "date": "1560478941", + "tid": "90573453", + "price": "8212.34", + "type": "0", + "amount": "2.17739591" + }, + { + "date": "1560478932", + "tid": "90573452", + "price": "8212.91", + "type": "0", + "amount": "0.00157623" + }, + { + "date": "1560478921", + "tid": "90573446", + "price": "8208.64", + "type": "1", + "amount": "0.00645909" + }, + { + "date": "1560478913", + "tid": "90573443", + "price": "8210.41", + "type": "0", + "amount": "0.01101883" + }, + { + "date": "1560478892", + "tid": "90573438", + "price": "8207.35", + "type": "1", + "amount": "0.00546297" + }, + { + "date": "1560478886", + "tid": "90573436", + "price": "8207.35", + "type": "1", + "amount": "0.07858079" + }, + { + "date": "1560478884", + "tid": "90573435", + "price": "8208.44", + "type": "1", + "amount": "0.02927716" + }, + { + "date": "1560478882", + "tid": "90573428", + "price": "8210.29", + "type": "1", + "amount": "0.39569435" + }, + { + "date": "1560478882", + "tid": "90573427", + "price": "8210.29", + "type": "0", + "amount": "0.00143200" + }, + { + "date": "1560478872", + "tid": "90573424", + "price": "8208.44", + "type": "1", + "amount": "0.01572284" + }, + { + "date": "1560478867", + "tid": "90573423", + "price": "8210.41", + "type": "0", + "amount": "0.84040505" + }, + { + "date": "1560478862", + "tid": "90573419", + "price": "8212.22", + "type": "0", + "amount": "0.15514014" + }, + { + "date": "1560478862", + "tid": "90573418", + "price": "8210.41", + "type": "0", + "amount": "1.49460755" + }, + { + "date": "1560478852", + "tid": "90573416", + "price": "8207.35", + "type": "1", + "amount": "0.27326256" + }, + { + "date": "1560478852", + "tid": "90573415", + "price": "8207.35", + "type": "1", + "amount": "0.06732046" + }, + { + "date": "1560478842", + "tid": "90573414", + "price": "8210.41", + "type": "0", + "amount": "0.00281441" + }, + { + "date": "1560478832", + "tid": "90573413", + "price": "8208.25", + "type": "0", + "amount": "0.02266849" + }, + { + "date": "1560478812", + "tid": "90573410", + "price": "8205.10", + "type": "1", + "amount": "0.00514136" + }, + { + "date": "1560478777", + "tid": "90573408", + "price": "8203.20", + "type": "1", + "amount": "0.00238608" + }, + { + "date": "1560478762", + "tid": "90573407", + "price": "8208.49", + "type": "0", + "amount": "0.05581238" + }, + { + "date": "1560478749", + "tid": "90573405", + "price": "8207.54", + "type": "1", + "amount": "0.22801975" + }, + { + "date": "1560478732", + "tid": "90573397", + "price": "8212.22", + "type": "0", + "amount": "0.04137929" + }, + { + "date": "1560478722", + "tid": "90573395", + "price": "8210.33", + "type": "0", + "amount": "0.03537044" + }, + { + "date": "1560478702", + "tid": "90573390", + "price": "8213.42", + "type": "0", + "amount": "0.00257804" + }, + { + "date": "1560478691", + "tid": "90573388", + "price": "8207.54", + "type": "1", + "amount": "0.06297953" + }, + { + "date": "1560478681", + "tid": "90573380", + "price": "8214.09", + "type": "0", + "amount": "0.10985983" + }, + { + "date": "1560478681", + "tid": "90573379", + "price": "8212.27", + "type": "0", + "amount": "0.01224017" + }, + { + "date": "1560478671", + "tid": "90573378", + "price": "8204.41", + "type": "0", + "amount": "0.39099421" + }, + { + "date": "1560478671", + "tid": "90573377", + "price": "8208.34", + "type": "1", + "amount": "0.10356500" + }, + { + "date": "1560478658", + "tid": "90573376", + "price": "8207.52", + "type": "0", + "amount": "0.03833000" + }, + { + "date": "1560478658", + "tid": "90573375", + "price": "8207.11", + "type": "0", + "amount": "0.00210835" + }, + { + "date": "1560478658", + "tid": "90573374", + "price": "8206.12", + "type": "0", + "amount": "0.67779052" + }, + { + "date": "1560478638", + "tid": "90573370", + "price": "8206.12", + "type": "0", + "amount": "1.17621806" + }, + { + "date": "1560478637", + "tid": "90573369", + "price": "8206.12", + "type": "0", + "amount": "0.04157141" + }, + { + "date": "1560478637", + "tid": "90573368", + "price": "8206.12", + "type": "0", + "amount": "4.69742001" + }, + { + "date": "1560478623", + "tid": "90573360", + "price": "8206.32", + "type": "1", + "amount": "0.04216696" + }, + { + "date": "1560478622", + "tid": "90573358", + "price": "8205.00", + "type": "0", + "amount": "9.94387452" + }, + { + "date": "1560478621", + "tid": "90573357", + "price": "8201.38", + "type": "1", + "amount": "0.16292090" + }, + { + "date": "1560478621", + "tid": "90573356", + "price": "8203.72", + "type": "1", + "amount": "0.62164797" + }, + { + "date": "1560478619", + "tid": "90573354", + "price": "8203.58", + "type": "1", + "amount": "0.84333929" + }, + { + "date": "1560478619", + "tid": "90573353", + "price": "8204.72", + "type": "0", + "amount": "0.00122718" + }, + { + "date": "1560478619", + "tid": "90573352", + "price": "8203.58", + "type": "0", + "amount": "9.08633489" + }, + { + "date": "1560478615", + "tid": "90573351", + "price": "8203.58", + "type": "0", + "amount": "0.23266511" + }, + { + "date": "1560478615", + "tid": "90573350", + "price": "8203.34", + "type": "0", + "amount": "0.08427896" + }, + { + "date": "1560478604", + "tid": "90573348", + "price": "8200.71", + "type": "1", + "amount": "0.01067897" + }, + { + "date": "1560478561", + "tid": "90573338", + "price": "8203.39", + "type": "0", + "amount": "0.00414116" + }, + { + "date": "1560478551", + "tid": "90573334", + "price": "8204.15", + "type": "0", + "amount": "0.00166703" + }, + { + "date": "1560478551", + "tid": "90573333", + "price": "8203.67", + "type": "0", + "amount": "0.03226172" + }, + { + "date": "1560478541", + "tid": "90573328", + "price": "8203.67", + "type": "0", + "amount": "0.01641746" + }, + { + "date": "1560478491", + "tid": "90573312", + "price": "8203.75", + "type": "0", + "amount": "0.03997237" + }, + { + "date": "1560478487", + "tid": "90573310", + "price": "8205.00", + "type": "0", + "amount": "0.00531524" + }, + { + "date": "1560478487", + "tid": "90573309", + "price": "8204.97", + "type": "0", + "amount": "0.00124819" + }, + { + "date": "1560478472", + "tid": "90573304", + "price": "8205.00", + "type": "0", + "amount": "0.05081024" + }, + { + "date": "1560478460", + "tid": "90573301", + "price": "8197.24", + "type": "0", + "amount": "0.29854898" + }, + { + "date": "1560478432", + "tid": "90573288", + "price": "8201.97", + "type": "0", + "amount": "0.00609280" + }, + { + "date": "1560478432", + "tid": "90573287", + "price": "8199.72", + "type": "0", + "amount": "0.00907522" + }, + { + "date": "1560478431", + "tid": "90573286", + "price": "8199.69", + "type": "0", + "amount": "0.00133569" + }, + { + "date": "1560478420", + "tid": "90573256", + "price": "8198.81", + "type": "0", + "amount": "0.02748281" + }, + { + "date": "1560478420", + "tid": "90573254", + "price": "8197.24", + "type": "0", + "amount": "9.56400000" + }, + { + "date": "1560478419", + "tid": "90573252", + "price": "8193.77", + "type": "0", + "amount": "0.00122866" + }, + { + "date": "1560478392", + "tid": "90573245", + "price": "8193.88", + "type": "0", + "amount": "1.50716419" + }, + { + "date": "1560478392", + "tid": "90573244", + "price": "8193.88", + "type": "0", + "amount": "0.01947458" + }, + { + "date": "1560478392", + "tid": "90573243", + "price": "8193.85", + "type": "0", + "amount": "0.00125589" + }, + { + "date": "1560478381", + "tid": "90573240", + "price": "8193.88", + "type": "0", + "amount": "0.00830954" + }, + { + "date": "1560478381", + "tid": "90573239", + "price": "8193.88", + "type": "0", + "amount": "6.22194605" + }, + { + "date": "1560478381", + "tid": "90573238", + "price": "8193.88", + "type": "0", + "amount": "0.11279097" + }, + { + "date": "1560478367", + "tid": "90573233", + "price": "8193.88", + "type": "0", + "amount": "0.11473652" + }, + { + "date": "1560478363", + "tid": "90573231", + "price": "8193.88", + "type": "0", + "amount": "0.00307500" + }, + { + "date": "1560478361", + "tid": "90573230", + "price": "8191.21", + "type": "1", + "amount": "0.17208231" + }, + { + "date": "1560478335", + "tid": "90573208", + "price": "8193.88", + "type": "0", + "amount": "1.83750315" + }, + { + "date": "1560478331", + "tid": "90573205", + "price": "8191.22", + "type": "1", + "amount": "0.00358570" + }, + { + "date": "1560478323", + "tid": "90573204", + "price": "8191.22", + "type": "1", + "amount": "0.21516241" + }, + { + "date": "1560478321", + "tid": "90573201", + "price": "8192.00", + "type": "1", + "amount": "0.00523360" + }, + { + "date": "1560478315", + "tid": "90573200", + "price": "8195.91", + "type": "0", + "amount": "0.00375700" + }, + { + "date": "1560478300", + "tid": "90573199", + "price": "8198.73", + "type": "0", + "amount": "0.04640000" + }, + { + "date": "1560478292", + "tid": "90573197", + "price": "8191.66", + "type": "1", + "amount": "0.00900080" + }, + { + "date": "1560478290", + "tid": "90573196", + "price": "8198.52", + "type": "0", + "amount": "0.37418131" + }, + { + "date": "1560478290", + "tid": "90573194", + "price": "8191.27", + "type": "0", + "amount": "0.00722083" + }, + { + "date": "1560478288", + "tid": "90573191", + "price": "8191.27", + "type": "0", + "amount": "0.09301806" + }, + { + "date": "1560478286", + "tid": "90573180", + "price": "8191.27", + "type": "0", + "amount": "0.02000000" + }, + { + "date": "1560478286", + "tid": "90573179", + "price": "8191.28", + "type": "1", + "amount": "0.01569927" + }, + { + "date": "1560478286", + "tid": "90573178", + "price": "8195.32", + "type": "1", + "amount": "0.01569927" + }, + { + "date": "1560478283", + "tid": "90573170", + "price": "8191.27", + "type": "0", + "amount": "0.96698972" + }, + { + "date": "1560478280", + "tid": "90573166", + "price": "8190.88", + "type": "0", + "amount": "0.04136303" + } + ], + "queryString": "time=hour", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + }, + "/api/v2/transfer-to-main/": { + "POST": [ + { + "data": { + "code": "API0000", + "reason": "Missing key, signature and nonce parameters", + "status": "error" + }, + "queryString": "", + "bodyParams": "amount=0.01\u0026currency=btc\u0026key=\u0026nonce=1563774326032825427\u0026signature=0EEA71F2E4811F720A9C5D294D33A75404D2C0902C120D976A50B68946F699C9\u0026subAccount=testAccount", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/v2/user_transactions/": { + "POST": [ + { + "data": [ + { + "fee": "0.00000000", + "btc_usd": "0.00", + "datetime": "2018-01-29 20:26:20", + "usd": 0.0, + "btc": "-0.94492494", + "type": "1", + "id": 50197270, + "eur": 0.0 + }, + { + "usd": "-10555.01", + "btc_usd": 11170.21000000, + "order_id": 857979733, + "datetime": "2018-01-29 20:22:34", + "fee": "26.39", + "btc": "0.94492494", + "type": "2", + "id": 50197020, + "eur": 0.0 + }, + { + "usd": "10588.90", + "btc_usd": "0.00", + "datetime": "2018-01-29 16:25:51", + "fee": "7.50", + "btc": 0.0, + "type": "0", + "id": 50174021, + "eur": 0.0 + }, + { + "fee": "0.00000000", + "btc_usd": "0.00", + "datetime": "2018-01-24 05:02:36", + "usd": 0.0, + "btc": "-0.94268200", + "type": "1", + "id": 49253685, + "eur": 0.0 + }, + { + "usd": "-1083.72", + "btc_usd": 10724.22000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "2.71", + "btc": "0.10105350", + "type": "2", + "id": 49252812, + "eur": 0.0 + }, + { + "usd": "-70.68", + "btc_usd": 10722.35000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "0.18", + "btc": "0.00659209", + "type": "2", + "id": 49252809, + "eur": 0.0 + }, + { + "usd": "-6.02", + "btc_usd": 10717.81000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "0.02", + "btc": "0.00056127", + "type": "2", + "id": 49252807, + "eur": 0.0 + }, + { + "usd": "-2287.82", + "btc_usd": 10712.50000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "5.72", + "btc": "0.21356588", + "type": "2", + "id": 49252806, + "eur": 0.0 + }, + { + "usd": "-5356.25", + "btc_usd": 10712.50000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "13.40", + "btc": "0.50000000", + "type": "2", + "id": 49252805, + "eur": 0.0 + }, + { + "usd": "-203.54", + "btc_usd": 10712.50000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "0.51", + "btc": "0.01900000", + "type": "2", + "id": 49252804, + "eur": 0.0 + }, + { + "usd": "-801.75", + "btc_usd": 10699.99000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:55", + "fee": "2.01", + "btc": "0.07493008", + "type": "2", + "id": 49252803, + "eur": 0.0 + }, + { + "usd": "-288.68", + "btc_usd": 10699.99000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:54", + "fee": "0.73", + "btc": "0.02697918", + "type": "2", + "id": 49252802, + "eur": 0.0 + }, + { + "usd": "10131.24", + "btc_usd": "0.00", + "datetime": "2018-01-23 13:41:55", + "fee": "7.50", + "btc": 0.0, + "type": "0", + "id": 49102898, + "eur": 0.0 + }, + { + "fee": "0.00000000", + "btc_usd": "0.00", + "datetime": "2018-01-19 18:35:42", + "usd": 0.0, + "btc": "-0.56161419", + "type": "1", + "id": 48187525, + "eur": 0.0 + }, + { + "usd": "-142.59", + "btc_usd": 11279.95000000, + "order_id": 796260299, + "datetime": "2018-01-19 18:23:34", + "fee": "0.36", + "btc": "0.01264068", + "type": "2", + "id": 48181460, + "eur": 0.0 + }, + { + "usd": "-6192.39", + "btc_usd": 11279.95000000, + "order_id": 796260299, + "datetime": "2018-01-19 18:23:33", + "fee": "15.49", + "btc": "0.54897351", + "type": "2", + "id": 48181458, + "eur": 0.0 + }, + { + "usd": "6358.33", + "btc_usd": "0.00", + "datetime": "2018-01-19 14:11:11", + "fee": "7.50", + "btc": 0.0, + "type": "0", + "id": 48118600, + "eur": 0.0 + } + ], + "queryString": "", + "bodyParams": "key=\u0026nonce=1560470788603888812\u0026signature=4B9BBCF0E9A4134800F50D540506ECBE0E8431E9C339B5641613A5BDC1193B18", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/v2/user_transactions/btcusd/": { + "POST": [ + { + "data": [ + { + "usd": "-10555.01", + "btc_usd": 11170.21000000, + "order_id": 857979733, + "datetime": "2018-01-29 20:22:34", + "fee": "26.39", + "btc": "0.94492494", + "type": "2", + "id": 50197020, + "eur": 0.0 + }, + { + "usd": "-1083.72", + "btc_usd": 10724.22000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "2.71", + "btc": "0.10105350", + "type": "2", + "id": 49252812, + "eur": 0.0 + }, + { + "usd": "-70.68", + "btc_usd": 10722.35000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "0.18", + "btc": "0.00659209", + "type": "2", + "id": 49252809, + "eur": 0.0 + }, + { + "usd": "-6.02", + "btc_usd": 10717.81000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "0.02", + "btc": "0.00056127", + "type": "2", + "id": 49252807, + "eur": 0.0 + }, + { + "usd": "-2287.82", + "btc_usd": 10712.50000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "5.72", + "btc": "0.21356588", + "type": "2", + "id": 49252806, + "eur": 0.0 + }, + { + "usd": "-5356.25", + "btc_usd": 10712.50000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "13.40", + "btc": "0.50000000", + "type": "2", + "id": 49252805, + "eur": 0.0 + }, + { + "usd": "-203.54", + "btc_usd": 10712.50000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:56", + "fee": "0.51", + "btc": "0.01900000", + "type": "2", + "id": 49252804, + "eur": 0.0 + }, + { + "usd": "-801.75", + "btc_usd": 10699.99000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:55", + "fee": "2.01", + "btc": "0.07493008", + "type": "2", + "id": 49252803, + "eur": 0.0 + }, + { + "usd": "-288.68", + "btc_usd": 10699.99000000, + "order_id": 824343036, + "datetime": "2018-01-24 04:54:54", + "fee": "0.73", + "btc": "0.02697918", + "type": "2", + "id": 49252802, + "eur": 0.0 + }, + { + "usd": "-142.59", + "btc_usd": 11279.95000000, + "order_id": 796260299, + "datetime": "2018-01-19 18:23:34", + "fee": "0.36", + "btc": "0.01264068", + "type": "2", + "id": 48181460, + "eur": 0.0 + }, + { + "usd": "-6192.39", + "btc_usd": 11279.95000000, + "order_id": 796260299, + "datetime": "2018-01-19 18:23:33", + "fee": "15.49", + "btc": "0.54897351", + "type": "2", + "id": 48181458, + "eur": 0.0 + } + ], + "queryString": "", + "bodyParams": "key=\u0026nonce=1560480869548338381\u0026signature=323C42A4A251A4ADC2FB65BE8F97B92342A6CF88BE524F7AF1E9FB639CD63DCC", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/v2/withdrawal/open/": { + "POST": [ + { + "data": { + "status": "error", + "reason": { + "amount": [ + "You have only 0.00 USD available. Check your account balance for details." + ] + } + }, + "queryString": "", + "bodyParams": "account_currency=USD\u0026address=123+Fake+St\u0026amount=-1\u0026bank_address=123+Fake+St\u0026bank_city=Tarry+Town\u0026bank_country=AU\u0026bank_name=Federal+Reserve+Bank\u0026bank_postal_code=2088\u0026bic=CTBAAU2S\u0026city=Tarry+Town\u0026comment=WITHDRAW+IT+ALL\u0026country=AU\u0026currency=USD\u0026iban=IT60X0542811101000000123456\u0026key=\u0026name=Satoshi+Nakamoto\u0026nonce=1560404081220544346\u0026postal_code=2088\u0026signature=17C71D3A9175255D46D786E9709B815E6C1B450B0E4E60B622EF35F0AD966B70\u0026type=international", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + }, + { + "data": { + "status": "error", + "reason": { + "amount": [ + "You have only 0.00 USD available. Check your account balance for details." + ] + } + }, + "queryString": "", + "bodyParams": "account_currency=USD\u0026address=123+Fake+St\u0026amount=-1\u0026bic=CTBAAU2S\u0026city=Tarry+Town\u0026comment=WITHDRAW+IT+ALL\u0026country=AU\u0026iban=IT60X0542811101000000123456\u0026key=\u0026name=Satoshi+Nakamoto\u0026nonce=1560405334882045192\u0026postal_code=2088\u0026signature=1EB01441F5FB1FA1335EF5586D86F210101F641768C7568A4F9D592AA529628B\u0026type=sepa", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/withdrawal_requests/": { + "POST": [ + { + "data": [], + "queryString": "", + "bodyParams": "key=\u0026nonce=1560480184558170717\u0026signature=5CE4CEFAA663FA03DE0005741683D2AE77D9ACD75F4644A2A1E9030E596D4E04", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/testdata/http_mock/exclusion.json b/testdata/http_mock/exclusion.json new file mode 100644 index 00000000..ad277e1d --- /dev/null +++ b/testdata/http_mock/exclusion.json @@ -0,0 +1,19 @@ +{ + "headers": [ + "Key", + "X-Mbx-Apikey", + "Rest-Key", + "Apiauth-Key", + "X-Gemini-Apikey" + ], + "variables": [ + "bsb", + "user", + "name", + "real_name", + "receiver_name", + "account_number", + "username", + "login" + ] +} \ No newline at end of file diff --git a/testdata/http_mock/gemini/gemini.json b/testdata/http_mock/gemini/gemini.json new file mode 100644 index 00000000..317e5701 --- /dev/null +++ b/testdata/http_mock/gemini/gemini.json @@ -0,0 +1,2734 @@ +{ + "routes": { + "/v1/auction/btcusd": { + "GET": [ + { + "data": { + "last_auction_eid": 265387648, + "next_auction_ms": 1565726400000, + "next_update_ms": 1565725800000 + }, + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] + }, + "/v1/auction/btcusd/history": { + "GET": [ + { + "data": [ + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265367818, + "event_type": "auction", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565640000, + "timestampms": 1565640000000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265367738, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639985, + "timestampms": 1565639985000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265367670, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639970, + "timestampms": 1565639970000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265367594, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639955, + "timestampms": 1565639955000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265367525, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639940, + "timestampms": 1565639940000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265367238, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639880, + "timestampms": 1565639880000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265367030, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639820, + "timestampms": 1565639820000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265366772, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639760, + "timestampms": 1565639760000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265366530, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639700, + "timestampms": 1565639700000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265366513, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639640, + "timestampms": 1565639640000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265366505, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639580, + "timestampms": 1565639580000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265366242, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639520, + "timestampms": 1565639520000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.94", + "eid": 265366000, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.32", + "timestamp": 1565639460, + "timestampms": 1565639460000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3136, + "auction_result": "failure", + "collar_price": "11391.90", + "eid": 265365766, + "event_type": "indicative", + "highest_bid_price": "11391.56", + "lowest_ask_price": "11392.24", + "timestamp": 1565639400, + "timestampms": 1565639400000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.955", + "eid": 264896173, + "event_type": "auction", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.91", + "timestamp": 1565553600, + "timestampms": 1565553600000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.955", + "eid": 264896124, + "event_type": "indicative", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.91", + "timestamp": 1565553585, + "timestampms": 1565553585000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.955", + "eid": 264896071, + "event_type": "indicative", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.91", + "timestamp": 1565553570, + "timestampms": 1565553570000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.955", + "eid": 264895999, + "event_type": "indicative", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.91", + "timestamp": 1565553555, + "timestampms": 1565553555000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.955", + "eid": 264895915, + "event_type": "indicative", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.91", + "timestamp": 1565553540, + "timestampms": 1565553540000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.955", + "eid": 264895644, + "event_type": "indicative", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.91", + "timestamp": 1565553480, + "timestampms": 1565553480000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.955", + "eid": 264895370, + "event_type": "indicative", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.91", + "timestamp": 1565553420, + "timestampms": 1565553420000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.955", + "eid": 264895088, + "event_type": "indicative", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.91", + "timestamp": 1565553360, + "timestampms": 1565553360000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.955", + "eid": 264894814, + "event_type": "indicative", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.91", + "timestamp": 1565553300, + "timestampms": 1565553300000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.82", + "eid": 264894529, + "event_type": "indicative", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.64", + "timestamp": 1565553240, + "timestampms": 1565553240000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11365.82", + "eid": 264894334, + "event_type": "indicative", + "highest_bid_price": "11364.00", + "lowest_ask_price": "11367.64", + "timestamp": 1565553180, + "timestampms": 1565553180000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "10949.09", + "eid": 264893399, + "event_type": "indicative", + "highest_bid_price": "10530.54", + "lowest_ask_price": "11367.64", + "timestamp": 1565553120, + "timestampms": 1565553120000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11367.31", + "eid": 264892625, + "event_type": "indicative", + "highest_bid_price": "11365.10", + "lowest_ask_price": "11369.52", + "timestamp": 1565553060, + "timestampms": 1565553060000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3127, + "auction_result": "failure", + "collar_price": "11367.31", + "eid": 264892355, + "event_type": "indicative", + "highest_bid_price": "11365.10", + "lowest_ask_price": "11369.52", + "timestamp": 1565553000, + "timestampms": 1565553000000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11310.96", + "eid": 264422533, + "event_type": "auction", + "highest_bid_price": "11308.76", + "lowest_ask_price": "11313.16", + "timestamp": 1565467200, + "timestampms": 1565467200000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11310.96", + "eid": 264422460, + "event_type": "indicative", + "highest_bid_price": "11308.76", + "lowest_ask_price": "11313.16", + "timestamp": 1565467185, + "timestampms": 1565467185000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11310.96", + "eid": 264422378, + "event_type": "indicative", + "highest_bid_price": "11308.76", + "lowest_ask_price": "11313.16", + "timestamp": 1565467170, + "timestampms": 1565467170000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11310.96", + "eid": 264422311, + "event_type": "indicative", + "highest_bid_price": "11308.76", + "lowest_ask_price": "11313.16", + "timestamp": 1565467155, + "timestampms": 1565467155000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11310.96", + "eid": 264422230, + "event_type": "indicative", + "highest_bid_price": "11308.76", + "lowest_ask_price": "11313.16", + "timestamp": 1565467140, + "timestampms": 1565467140000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11311.025", + "eid": 264421990, + "event_type": "indicative", + "highest_bid_price": "11308.89", + "lowest_ask_price": "11313.16", + "timestamp": 1565467080, + "timestampms": 1565467080000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11311.095", + "eid": 264421860, + "event_type": "indicative", + "highest_bid_price": "11309.03", + "lowest_ask_price": "11313.16", + "timestamp": 1565467020, + "timestampms": 1565467020000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11311.095", + "eid": 264421842, + "event_type": "indicative", + "highest_bid_price": "11309.03", + "lowest_ask_price": "11313.16", + "timestamp": 1565466960, + "timestampms": 1565466960000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11320.14", + "eid": 264420567, + "event_type": "indicative", + "highest_bid_price": "11317.26", + "lowest_ask_price": "11323.02", + "timestamp": 1565466900, + "timestampms": 1565466900000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11320.15", + "eid": 264420293, + "event_type": "indicative", + "highest_bid_price": "11317.28", + "lowest_ask_price": "11323.02", + "timestamp": 1565466840, + "timestampms": 1565466840000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11320.33", + "eid": 264420012, + "event_type": "indicative", + "highest_bid_price": "11317.64", + "lowest_ask_price": "11323.02", + "timestamp": 1565466780, + "timestampms": 1565466780000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11320.33", + "eid": 264419740, + "event_type": "indicative", + "highest_bid_price": "11317.64", + "lowest_ask_price": "11323.02", + "timestamp": 1565466720, + "timestampms": 1565466720000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11320.33", + "eid": 264419500, + "event_type": "indicative", + "highest_bid_price": "11317.64", + "lowest_ask_price": "11323.02", + "timestamp": 1565466660, + "timestampms": 1565466660000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3121, + "auction_result": "failure", + "collar_price": "11320.33", + "eid": 264419235, + "event_type": "indicative", + "highest_bid_price": "11317.64", + "lowest_ask_price": "11323.02", + "timestamp": 1565466600, + "timestampms": 1565466600000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3118, + "auction_result": "failure", + "collar_price": "11808.195", + "eid": 263947225, + "event_type": "auction", + "highest_bid_price": "11806.62", + "lowest_ask_price": "11809.77", + "timestamp": 1565380800, + "timestampms": 1565380800000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3118, + "auction_result": "failure", + "collar_price": "11808.195", + "eid": 263947144, + "event_type": "indicative", + "highest_bid_price": "11806.62", + "lowest_ask_price": "11809.77", + "timestamp": 1565380785, + "timestampms": 1565380785000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3118, + "auction_result": "failure", + "collar_price": "11808.195", + "eid": 263947071, + "event_type": "indicative", + "highest_bid_price": "11806.62", + "lowest_ask_price": "11809.77", + "timestamp": 1565380770, + "timestampms": 1565380770000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3118, + "auction_result": "failure", + "collar_price": "11822.65", + "eid": 263945809, + "event_type": "indicative", + "highest_bid_price": "11821.61", + "lowest_ask_price": "11823.69", + "timestamp": 1565380755, + "timestampms": 1565380755000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3118, + "auction_result": "failure", + "collar_price": "11822.65", + "eid": 263945730, + "event_type": "indicative", + "highest_bid_price": "11821.61", + "lowest_ask_price": "11823.69", + "timestamp": 1565380740, + "timestampms": 1565380740000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3118, + "auction_result": "failure", + "collar_price": "11822.65", + "eid": 263945457, + "event_type": "indicative", + "highest_bid_price": "11821.61", + "lowest_ask_price": "11823.69", + "timestamp": 1565380680, + "timestampms": 1565380680000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3118, + "auction_result": "failure", + "collar_price": "11822.65", + "eid": 263945229, + "event_type": "indicative", + "highest_bid_price": "11821.61", + "lowest_ask_price": "11823.69", + "timestamp": 1565380620, + "timestampms": 1565380620000, + "unmatched_collar_quantity": "0" + }, + { + "auction_id": 3118, + "auction_result": "failure", + "collar_price": "11822.65", + "eid": 263945016, + "event_type": "indicative", + "highest_bid_price": "11821.61", + "lowest_ask_price": "11823.69", + "timestamp": 1565380560, + "timestampms": 1565380560000, + "unmatched_collar_quantity": "0" + } + ], + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] + }, + "/v1/balances": { + "POST": [ + { + "data": [ + { + "amount": "2015", + "available": "2015", + "availableForWithdrawal": "2015", + "currency": "BTC", + "type": "exchange" + }, + { + "amount": "234354.69", + "available": "234354.69", + "availableForWithdrawal": "234354.69", + "currency": "USD", + "type": "exchange" + }, + { + "amount": "40000", + "available": "40000", + "availableForWithdrawal": "40000", + "currency": "ETH", + "type": "exchange" + }, + { + "amount": "40000", + "available": "40000", + "availableForWithdrawal": "40000", + "currency": "BCH", + "type": "exchange" + }, + { + "amount": "20000", + "available": "20000", + "availableForWithdrawal": "20000", + "currency": "LTC", + "type": "exchange" + }, + { + "amount": "20000", + "available": "20000", + "availableForWithdrawal": "20000", + "currency": "ZEC", + "type": "exchange" + } + ], + "queryString": "", + "bodyParams": "{\"nonce\":\"1565675398767136594\",\"request\":\"/v1/balances\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU2NzUzOTg3NjcxMzY1OTQiLCJyZXF1ZXN0IjoiL3YxL2JhbGFuY2VzIn0=" + ], + "X-Gemini-Signature": [ + "41d4e13ca3bbf5eba0a785ff213754c16df1679d07885f1ee7923ea8ee211f2bf848ef3c6dec71ff612ebdc2e7d419f4" + ] + } + } + ] + }, + "/v1/book/btcusd": { + "GET": [ + { + "data": { + "asks": [ + { + "amount": "5.34093158", + "price": "11385.22", + "timestamp": "1565588056" + }, + { + "amount": "0.13914442", + "price": "11390.45", + "timestamp": "1565588056" + }, + { + "amount": "1", + "price": "11390.46", + "timestamp": "1565588056" + }, + { + "amount": "2", + "price": "11390.97", + "timestamp": "1565588056" + }, + { + "amount": "2", + "price": "11391.11", + "timestamp": "1565588056" + }, + { + "amount": "0.753", + "price": "11391.50", + "timestamp": "1565588056" + }, + { + "amount": "0.11803397", + "price": "11392.38", + "timestamp": "1565588056" + }, + { + "amount": "0.35", + "price": "11392.91", + "timestamp": "1565588056" + }, + { + "amount": "1", + "price": "11393.19", + "timestamp": "1565588056" + }, + { + "amount": "0.468", + "price": "11394.90", + "timestamp": "1565588056" + }, + { + "amount": "1", + "price": "11396.23", + "timestamp": "1565588056" + }, + { + "amount": "4", + "price": "11396.92", + "timestamp": "1565588056" + }, + { + "amount": "1.02153048", + "price": "11397.44", + "timestamp": "1565588056" + }, + { + "amount": "2.8", + "price": "11397.92", + "timestamp": "1565588056" + }, + { + "amount": "0.35", + "price": "11398.91", + "timestamp": "1565588056" + }, + { + "amount": "1", + "price": "11399.01", + "timestamp": "1565588056" + }, + { + "amount": "10", + "price": "11402.12", + "timestamp": "1565588056" + }, + { + "amount": "4.98", + "price": "11403.74", + "timestamp": "1565588056" + }, + { + "amount": "0.35", + "price": "11404.01", + "timestamp": "1565588056" + }, + { + "amount": "6.80387906", + "price": "11405.20", + "timestamp": "1565588056" + }, + { + "amount": "12.1", + "price": "11406.34", + "timestamp": "1565588056" + }, + { + "amount": "0.25", + "price": "11407.69", + "timestamp": "1565588056" + }, + { + "amount": "0.78", + "price": "11407.74", + "timestamp": "1565588056" + }, + { + "amount": "1.80045786", + "price": "11409.46", + "timestamp": "1565588056" + }, + { + "amount": "5.2", + "price": "11410.07", + "timestamp": "1565588056" + }, + { + "amount": "0.35", + "price": "11410.29", + "timestamp": "1565588056" + }, + { + "amount": "8.1", + "price": "11411.66", + "timestamp": "1565588056" + }, + { + "amount": "10", + "price": "11413.95", + "timestamp": "1565588056" + }, + { + "amount": "2.67", + "price": "11414.86", + "timestamp": "1565588056" + }, + { + "amount": "9.9", + "price": "11415.63", + "timestamp": "1565588056" + }, + { + "amount": "5", + "price": "11416.18", + "timestamp": "1565588056" + }, + { + "amount": "3.7", + "price": "11416.70", + "timestamp": "1565588056" + }, + { + "amount": "2", + "price": "11420.44", + "timestamp": "1565588056" + }, + { + "amount": "3.41", + "price": "11423.05", + "timestamp": "1565588056" + }, + { + "amount": "5", + "price": "11426.51", + "timestamp": "1565588056" + }, + { + "amount": "9.45", + "price": "11430.68", + "timestamp": "1565588056" + }, + { + "amount": "0.402", + "price": "11433.22", + "timestamp": "1565588056" + }, + { + "amount": "1.60397802", + "price": "11435.50", + "timestamp": "1565588056" + }, + { + "amount": "12.0726", + "price": "11438.59", + "timestamp": "1565588056" + }, + { + "amount": "5", + "price": "11439.20", + "timestamp": "1565588056" + }, + { + "amount": "0.06", + "price": "11440.00", + "timestamp": "1565588056" + }, + { + "amount": "2.21365892", + "price": "11448.12", + "timestamp": "1565588056" + }, + { + "amount": "0.06", + "price": "11451.00", + "timestamp": "1565588056" + }, + { + "amount": "10.94", + "price": "11454.79", + "timestamp": "1565588056" + }, + { + "amount": "0.6", + "price": "11460.00", + "timestamp": "1565588056" + }, + { + "amount": "0.06", + "price": "11462.00", + "timestamp": "1565588056" + }, + { + "amount": "2.79111801", + "price": "11463.20", + "timestamp": "1565588056" + }, + { + "amount": "10.9", + "price": "11464.12", + "timestamp": "1565588056" + }, + { + "amount": "25.65", + "price": "11467.89", + "timestamp": "1565588056" + }, + { + "amount": "0.06", + "price": "11473.00", + "timestamp": "1565588056" + } + ], + "bids": [ + { + "amount": "2", + "price": "11382.49", + "timestamp": "1565588056" + }, + { + "amount": "2.06957456", + "price": "11382.45", + "timestamp": "1565588056" + }, + { + "amount": "2.06957719", + "price": "11381.72", + "timestamp": "1565588056" + }, + { + "amount": "2", + "price": "11379.89", + "timestamp": "1565588056" + }, + { + "amount": "2", + "price": "11379.75", + "timestamp": "1565588056" + }, + { + "amount": "1", + "price": "11377.36", + "timestamp": "1565588056" + }, + { + "amount": "6.01991146", + "price": "11376.63", + "timestamp": "1565588056" + }, + { + "amount": "0.11803397", + "price": "11376.52", + "timestamp": "1565588056" + }, + { + "amount": "5", + "price": "11376.51", + "timestamp": "1565588056" + }, + { + "amount": "1.313", + "price": "11375.75", + "timestamp": "1565588056" + }, + { + "amount": "5", + "price": "11375.00", + "timestamp": "1565588056" + }, + { + "amount": "0.35", + "price": "11374.80", + "timestamp": "1565588056" + }, + { + "amount": "1", + "price": "11374.57", + "timestamp": "1565588056" + }, + { + "amount": "2.97846952", + "price": "11373.81", + "timestamp": "1565588056" + }, + { + "amount": "1", + "price": "11372.29", + "timestamp": "1565588056" + }, + { + "amount": "0.35", + "price": "11369.80", + "timestamp": "1565588056" + }, + { + "amount": "1", + "price": "11369.68", + "timestamp": "1565588056" + }, + { + "amount": "10", + "price": "11368.56", + "timestamp": "1565588056" + }, + { + "amount": "2.81", + "price": "11366.06", + "timestamp": "1565588056" + }, + { + "amount": "0.35", + "price": "11364.60", + "timestamp": "1565588056" + }, + { + "amount": "5", + "price": "11364.08", + "timestamp": "1565588056" + }, + { + "amount": "1.09840451", + "price": "11362.99", + "timestamp": "1565588056" + }, + { + "amount": "0.537", + "price": "11362.52", + "timestamp": "1565588056" + }, + { + "amount": "4.3744", + "price": "11360.03", + "timestamp": "1565588056" + }, + { + "amount": "3.5098", + "price": "11359.70", + "timestamp": "1565588056" + }, + { + "amount": "0.35", + "price": "11359.60", + "timestamp": "1565588056" + }, + { + "amount": "0.79", + "price": "11359.20", + "timestamp": "1565588056" + }, + { + "amount": "0.732", + "price": "11359.12", + "timestamp": "1565588056" + }, + { + "amount": "0.26", + "price": "11358.91", + "timestamp": "1565588056" + }, + { + "amount": "2", + "price": "11358.90", + "timestamp": "1565588056" + }, + { + "amount": "0.09", + "price": "11358.15", + "timestamp": "1565588056" + }, + { + "amount": "10", + "price": "11355.90", + "timestamp": "1565588056" + }, + { + "amount": "4.8", + "price": "11354.03", + "timestamp": "1565588056" + }, + { + "amount": "5", + "price": "11353.80", + "timestamp": "1565588056" + }, + { + "amount": "2.68", + "price": "11353.50", + "timestamp": "1565588056" + }, + { + "amount": "6.31636908", + "price": "11350.90", + "timestamp": "1565588056" + }, + { + "amount": "0.04396475", + "price": "11350.00", + "timestamp": "1565588056" + }, + { + "amount": "1.35938518", + "price": "11348.55", + "timestamp": "1565588056" + }, + { + "amount": "4.78", + "price": "11347.44", + "timestamp": "1565588056" + }, + { + "amount": "8.1963", + "price": "11343.21", + "timestamp": "1565588056" + }, + { + "amount": "3.7947", + "price": "11340.58", + "timestamp": "1565588056" + }, + { + "amount": "0.44098282", + "price": "11338.31", + "timestamp": "1565588056" + }, + { + "amount": "0.00536173", + "price": "11333.03", + "timestamp": "1565588056" + }, + { + "amount": "0.5", + "price": "11330.20", + "timestamp": "1565588056" + }, + { + "amount": "10.27", + "price": "11329.63", + "timestamp": "1565588056" + }, + { + "amount": "0.727", + "price": "11329.41", + "timestamp": "1565588056" + }, + { + "amount": "1.69494039", + "price": "11329.27", + "timestamp": "1565588056" + }, + { + "amount": "0.00033196", + "price": "11326.42", + "timestamp": "1565588056" + }, + { + "amount": "25.66", + "price": "11323.30", + "timestamp": "1565588056" + }, + { + "amount": "0.00033119", + "price": "11322.81", + "timestamp": "1565588056" + } + ] + }, + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] + }, + "/v1/deposit/btc/newAddress": { + "POST": [ + { + "data": { + "message": "Cryptocurrency operations are not available in sandbox", + "reason": "EndpointUnavailableInThisEnvironment", + "result": "error" + }, + "queryString": "", + "bodyParams": "{\"label\":\"LOL123\",\"nonce\":\"1565675520004698760\",\"request\":\"/v1/deposit/btc/newAddress\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJsYWJlbCI6IkxPTDEyMyIsIm5vbmNlIjoiMTU2NTY3NTUyMDAwNDY5ODc2MCIsInJlcXVlc3QiOiIvdjEvZGVwb3NpdC9idGMvbmV3QWRkcmVzcyJ9" + ], + "X-Gemini-Signature": [ + "0bed59b3d0ded52ead1f8f5d34918457c253ce293684f0c6c4b0de237ed45467e98b5c1d6173be06f3d75de929de0289" + ] + } + } + ] + }, + "/v1/heartbeat": { + "POST": [ + { + "data": { + "result": "ok" + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565678439020022654\",\"request\":\"/v1/heartbeat\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU2Nzg0MzkwMjAwMjI2NTQiLCJyZXF1ZXN0IjoiL3YxL2hlYXJ0YmVhdCJ9" + ], + "X-Gemini-Signature": [ + "acf32982c5f48455a29e0e93c1a0e6298cb87f99a3c62f08e4036120ba483fc6ba4026eb1341c6d9ca925f8fcacff065" + ] + } + } + ] + }, + "/v1/mytrades": { + "POST": [ + { + "data": [ + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72877194", + "price": "4365.44", + "tid": 72877196, + "timestamp": 1504774407, + "timestampms": 1504774407342, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72876948", + "price": "4365.44", + "tid": 72876950, + "timestamp": 1504774256, + "timestampms": 1504774256923, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72876939", + "price": "4365.44", + "tid": 72876941, + "timestamp": 1504774215, + "timestampms": 1504774215737, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72854559", + "price": "4365.44", + "tid": 72854561, + "timestamp": 1504742429, + "timestampms": 1504742429222, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72854554", + "price": "4365.44", + "tid": 72854556, + "timestamp": 1504742410, + "timestampms": 1504742410563, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72854469", + "price": "4365.44", + "tid": 72854471, + "timestamp": 1504742362, + "timestampms": 1504742362798, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72854064", + "price": "4365.44", + "tid": 72854066, + "timestamp": 1504742101, + "timestampms": 1504742101052, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72853499", + "price": "4365.44", + "tid": 72853501, + "timestamp": 1504741638, + "timestampms": 1504741638555, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72852534", + "price": "4365.44", + "tid": 72852536, + "timestamp": 1504740907, + "timestampms": 1504740907598, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72788487", + "price": "4365.44", + "tid": 72788489, + "timestamp": 1504692849, + "timestampms": 1504692849358, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72782959", + "price": "4365.44", + "tid": 72782961, + "timestamp": 1504688727, + "timestampms": 1504688727991, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72782794", + "price": "4365.44", + "tid": 72782796, + "timestamp": 1504688636, + "timestampms": 1504688636950, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72782789", + "price": "4365.44", + "tid": 72782791, + "timestamp": 1504688602, + "timestampms": 1504688602038, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72782704", + "price": "4365.44", + "tid": 72782706, + "timestamp": 1504688567, + "timestampms": 1504688567723, + "type": "Buy" + }, + { + "aggressor": true, + "amount": "1", + "exchange": "gemini", + "fee_amount": "10.9136", + "fee_currency": "USD", + "is_auction_fill": false, + "order_id": "72782619", + "price": "4365.44", + "tid": 72782621, + "timestamp": 1504688467, + "timestampms": 1504688467928, + "type": "Buy" + } + ], + "queryString": "", + "bodyParams": "{\"nonce\":\"1565675130339156532\",\"request\":\"/v1/mytrades\",\"symbol\":\"btcusd\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU2NzUxMzAzMzkxNTY1MzIiLCJyZXF1ZXN0IjoiL3YxL215dHJhZGVzIiwic3ltYm9sIjoiYnRjdXNkIn0=" + ], + "X-Gemini-Signature": [ + "ad2fa169f26d48df2515e90c02b77c7efd7e3e8a061b5c0ecfdbc732b1bf94416c9c7c50fff9ab86b2f6b324ccf47b07" + ] + } + }, + { + "data": { + "message": "Received invalid timestamp '-62135596800'. Timestamp field in payload must be either a string or an int representing seconds or milliseconds that can be parsed to a valid date and time.", + "reason": "InvalidTimestampInPayload", + "result": "error" + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565754197574051025\",\"request\":\"/v1/mytrades\",\"symbol\":\"LTCBTC\",\"timestamp\":-62135596800}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU3NTQxOTc1NzQwNTEwMjUiLCJyZXF1ZXN0IjoiL3YxL215dHJhZGVzIiwic3ltYm9sIjoiTFRDQlRDIiwidGltZXN0YW1wIjotNjIxMzU1OTY4MDB9" + ], + "X-Gemini-Signature": [ + "fece9c3bbf0ab545666beac19b61aa247746caa65dd1ee9fb2fa86aa2aa28e1fc2be58ebaa08530dc10063e62a48bf91" + ] + } + }, + { + "data": null, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565754332374545960\",\"request\":\"/v1/mytrades\",\"symbol\":\"LTCBTC\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU3NTQzMzIzNzQ1NDU5NjAiLCJyZXF1ZXN0IjoiL3YxL215dHJhZGVzIiwic3ltYm9sIjoiTFRDQlRDIn0=" + ], + "X-Gemini-Signature": [ + "ce08e4878c682731d0d158ef91c250d318c290b44d920550b192bcd09100eee0314abf731e09f90036f41f4caf9a894e" + ] + } + } + ] + }, + "/v1/notionalvolume": { + "POST": [ + { + "data": { + "api_auction_fee_bps": 20, + "api_maker_fee_bps": 10, + "api_taker_fee_bps": 35, + "block_maker_fee_bps": 0, + "block_taker_fee_bps": 0, + "date": "2019-08-13", + "fix_auction_fee_bps": 20, + "fix_maker_fee_bps": 10, + "fix_taker_fee_bps": 35, + "last_updated_ms": 1565654400000, + "notional_1d_volume": [], + "notional_30d_volume": 0, + "web_auction_fee_bps": 100, + "web_maker_fee_bps": 100, + "web_taker_fee_bps": 100 + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565671281858874030\",\"request\":\"/v1/notionalvolume\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU2NzEyODE4NTg4NzQwMzAiLCJyZXF1ZXN0IjoiL3YxL25vdGlvbmFsdm9sdW1lIn0=" + ], + "X-Gemini-Signature": [ + "3a28c6e51697af5df88141b45e57a1b42add6ee72c5c03ca714166ddcc5ac24ed9ea06453ec3684407959420ddc07c9d" + ] + } + } + ] + }, + "/v1/order/cancel": { + "POST": [ + { + "data": { + "avg_execution_price": "0.00", + "exchange": "gemini", + "executed_amount": "0", + "id": "265555413", + "is_cancelled": true, + "is_hidden": false, + "is_live": false, + "options": [], + "order_id": "265555413", + "original_amount": "1", + "price": "4500.00", + "reason": "Requested", + "remaining_amount": "1", + "side": "buy", + "symbol": "btcusd", + "timestamp": "1565673596", + "timestampms": 1565673596842, + "type": "exchange limit", + "was_forced": false + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565673746786096494\",\"order_id\":265555413,\"request\":\"/v1/order/cancel\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU2NzM3NDY3ODYwOTY0OTQiLCJvcmRlcl9pZCI6MjY1NTU1NDEzLCJyZXF1ZXN0IjoiL3YxL29yZGVyL2NhbmNlbCJ9" + ], + "X-Gemini-Signature": [ + "165f303fd96b7d32c8ad0cda1dffbd644928329fb503604785da92ac972ab5bc5a9152bac46096606e642913207d9421" + ] + } + }, + { + "data": { + "avg_execution_price": "0.00847", + "exchange": "gemini", + "executed_amount": "1", + "id": "266029865", + "is_cancelled": false, + "is_hidden": false, + "is_live": false, + "options": [], + "order_id": "266029865", + "original_amount": "1", + "price": "10", + "remaining_amount": "0", + "side": "buy", + "symbol": "ltcbtc", + "timestamp": "1565754961", + "timestampms": 1565754961967, + "type": "exchange limit", + "was_forced": false + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565755351866830715\",\"order_id\":266029865,\"request\":\"/v1/order/cancel\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU3NTUzNTE4NjY4MzA3MTUiLCJvcmRlcl9pZCI6MjY2MDI5ODY1LCJyZXF1ZXN0IjoiL3YxL29yZGVyL2NhbmNlbCJ9" + ], + "X-Gemini-Signature": [ + "8bdd2fad91c8606618392624e7461dbf143df27c18c46364e27857c97e99e319eff3c16138cd3c2753c9ff34dd73ddcc" + ] + } + } + ] + }, + "/v1/order/cancel/all": { + "POST": [ + { + "data": { + "details": { + "cancelRejects": [], + "cancelledOrders": [] + }, + "result": "ok" + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565674594946783260\",\"request\":\"/v1/order/cancel/all\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU2NzQ1OTQ5NDY3ODMyNjAiLCJyZXF1ZXN0IjoiL3YxL29yZGVyL2NhbmNlbC9hbGwifQ==" + ], + "X-Gemini-Signature": [ + "a555d19a45170c0304c4fc39d1b0059a7969dd03038c9ad6c5a03579750271dec92c3a131a2f138d58bd150f9b3a8b26" + ] + } + } + ] + }, + "/v1/order/new": { + "POST": [ + { + "data": { + "avg_execution_price": "0.00", + "exchange": "gemini", + "executed_amount": "0", + "id": "265555413", + "is_cancelled": false, + "is_hidden": false, + "is_live": true, + "options": [], + "order_id": "265555413", + "original_amount": "1", + "price": "4500.00", + "remaining_amount": "1", + "side": "buy", + "symbol": "btcusd", + "timestamp": "1565673596", + "timestampms": 1565673596842, + "type": "exchange limit", + "was_forced": false + }, + "queryString": "", + "bodyParams": "{\"amount\":\"1\",\"nonce\":\"1565673595820662072\",\"price\":\"4500\",\"request\":\"/v1/order/new\",\"side\":\"buy\",\"symbol\":\"btcusd\",\"type\":\"exchange limit\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhbW91bnQiOiIxIiwibm9uY2UiOiIxNTY1NjczNTk1ODIwNjYyMDcyIiwicHJpY2UiOiI0NTAwIiwicmVxdWVzdCI6Ii92MS9vcmRlci9uZXciLCJzaWRlIjoiYnV5Iiwic3ltYm9sIjoiYnRjdXNkIiwidHlwZSI6ImV4Y2hhbmdlIGxpbWl0In0=" + ], + "X-Gemini-Signature": [ + "2d7336135e57c1245d6d66385fab5e2b8cd03ba82b675c0fbb61c030e1c17647cbf880e1bca6b918992310a20d994c92" + ] + } + }, + { + "data": { + "message": "Received bad symbol 'LTC_BTC' but expected a supported symbol from ['BTCUSD', 'ETHBTC', 'ETHUSD', 'BCHUSD', 'BCHBTC', 'BCHETH', 'LTCUSD', 'LTCBTC', 'LTCETH', 'LTCBCH', 'ZECUSD', 'ZECBTC', 'ZECETH', 'ZECBCH', 'ZECLTC']", + "reason": "InvalidSymbol", + "result": "error" + }, + "queryString": "", + "bodyParams": "{\"amount\":\"1\",\"nonce\":\"1565754502321084878\",\"price\":\"10\",\"request\":\"/v1/order/new\",\"side\":\"BUY\",\"symbol\":\"LTC_BTC\",\"type\":\"MARKET\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhbW91bnQiOiIxIiwibm9uY2UiOiIxNTY1NzU0NTAyMzIxMDg0ODc4IiwicHJpY2UiOiIxMCIsInJlcXVlc3QiOiIvdjEvb3JkZXIvbmV3Iiwic2lkZSI6IkJVWSIsInN5bWJvbCI6IkxUQ19CVEMiLCJ0eXBlIjoiTUFSS0VUIn0=" + ], + "X-Gemini-Signature": [ + "bfd7efe0b682881339db42b87fd72c91a977a9e6e15b3d71dd5570f812d22c6669b6b9df9eeafdcd3a41a66145d94b03" + ] + } + }, + { + "data": { + "message": "Invalid order type for symbol LTCBTC: 'MARKET'", + "reason": "InvalidOrderType", + "result": "error" + }, + "queryString": "", + "bodyParams": "{\"amount\":\"1\",\"nonce\":\"1565754590390234675\",\"price\":\"10\",\"request\":\"/v1/order/new\",\"side\":\"BUY\",\"symbol\":\"LTCBTC\",\"type\":\"MARKET\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhbW91bnQiOiIxIiwibm9uY2UiOiIxNTY1NzU0NTkwMzkwMjM0Njc1IiwicHJpY2UiOiIxMCIsInJlcXVlc3QiOiIvdjEvb3JkZXIvbmV3Iiwic2lkZSI6IkJVWSIsInN5bWJvbCI6IkxUQ0JUQyIsInR5cGUiOiJNQVJLRVQifQ==" + ], + "X-Gemini-Signature": [ + "0f09187d9a0da71ae0c97fc582d55b7c40726f5287d0cfe276a06adbc7700cfd9e322ca03bc134d8ec9e7e1987bd8e14" + ] + } + }, + { + "data": { + "message": "Invalid order type for symbol LTCBTC: 'LIMIT'", + "reason": "InvalidOrderType", + "result": "error" + }, + "queryString": "", + "bodyParams": "{\"amount\":\"1\",\"nonce\":\"1565754739098421455\",\"price\":\"10\",\"request\":\"/v1/order/new\",\"side\":\"BUY\",\"symbol\":\"LTCBTC\",\"type\":\"LIMIT\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhbW91bnQiOiIxIiwibm9uY2UiOiIxNTY1NzU0NzM5MDk4NDIxNDU1IiwicHJpY2UiOiIxMCIsInJlcXVlc3QiOiIvdjEvb3JkZXIvbmV3Iiwic2lkZSI6IkJVWSIsInN5bWJvbCI6IkxUQ0JUQyIsInR5cGUiOiJMSU1JVCJ9" + ], + "X-Gemini-Signature": [ + "32bc55cb25e1f5c432e6bd350bf09ba0e40f3e11031d4a9142d82949e3f83a13887fe56611f705924abcce3ed1f738cf" + ] + } + }, + { + "data": { + "avg_execution_price": "0.00847", + "exchange": "gemini", + "executed_amount": "1", + "id": "266029865", + "is_cancelled": false, + "is_hidden": false, + "is_live": false, + "options": [], + "order_id": "266029865", + "original_amount": "1", + "price": "10", + "remaining_amount": "0", + "side": "buy", + "symbol": "ltcbtc", + "timestamp": "1565754961", + "timestampms": 1565754961967, + "type": "exchange limit", + "was_forced": false + }, + "queryString": "", + "bodyParams": "{\"amount\":\"1\",\"nonce\":\"1565754960920111289\",\"price\":\"10\",\"request\":\"/v1/order/new\",\"side\":\"BUY\",\"symbol\":\"LTCBTC\",\"type\":\"exchange limit\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhbW91bnQiOiIxIiwibm9uY2UiOiIxNTY1NzU0OTYwOTIwMTExMjg5IiwicHJpY2UiOiIxMCIsInJlcXVlc3QiOiIvdjEvb3JkZXIvbmV3Iiwic2lkZSI6IkJVWSIsInN5bWJvbCI6IkxUQ0JUQyIsInR5cGUiOiJleGNoYW5nZSBsaW1pdCJ9" + ], + "X-Gemini-Signature": [ + "81412117758f361e7c6929335a28431775586bee9378b490df68a16034aba8877fee9f2c373db7739e3c50d490910e94" + ] + } + }, + { + "data": { + "message": "InvalidSignature", + "reason": "InvalidSignature", + "result": "error" + }, + "queryString": "", + "bodyParams": "{\"amount\":\"1\",\"nonce\":\"1565757283294274961\",\"price\":\"9000\",\"request\":\"/v1/order/new\",\"side\":\"buy\",\"symbol\":\"btcusd\",\"type\":\"exchange limit\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhbW91bnQiOiIxIiwibm9uY2UiOiIxNTY1NzU3MjgzMjk0Mjc0OTYxIiwicHJpY2UiOiI5MDAwIiwicmVxdWVzdCI6Ii92MS9vcmRlci9uZXciLCJzaWRlIjoiYnV5Iiwic3ltYm9sIjoiYnRjdXNkIiwidHlwZSI6ImV4Y2hhbmdlIGxpbWl0In0=" + ], + "X-Gemini-Signature": [ + "7f709eb431a5ef61e86cc62713d6787143cec35743dce2dbd75c77f5c9079b4c306371d7923520f292362296aa5d3a60" + ] + } + }, + { + "data": { + "message": "InvalidSignature", + "reason": "InvalidSignature", + "result": "error" + }, + "queryString": "", + "bodyParams": "{\"amount\":\"1\",\"nonce\":\"1565757330556534438\",\"price\":\"9000\",\"request\":\"/v1/order/new\",\"side\":\"buy\",\"symbol\":\"btcusd\",\"type\":\"exchange limit\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhbW91bnQiOiIxIiwibm9uY2UiOiIxNTY1NzU3MzMwNTU2NTM0NDM4IiwicHJpY2UiOiI5MDAwIiwicmVxdWVzdCI6Ii92MS9vcmRlci9uZXciLCJzaWRlIjoiYnV5Iiwic3ltYm9sIjoiYnRjdXNkIiwidHlwZSI6ImV4Y2hhbmdlIGxpbWl0In0=" + ], + "X-Gemini-Signature": [ + "cea8a1e0e7f5138c460b46885258df29fd9686d08f679a4a054e5be368a718f7873662708a3d46c9a406f8ee63914c83" + ] + } + }, + { + "data": { + "avg_execution_price": "0.00", + "exchange": "gemini", + "executed_amount": "0", + "id": "266043798", + "is_cancelled": false, + "is_hidden": false, + "is_live": true, + "options": [], + "order_id": "266043798", + "original_amount": "1", + "price": "9000.00", + "remaining_amount": "1", + "side": "buy", + "symbol": "btcusd", + "timestamp": "1565757360", + "timestampms": 1565757360991, + "type": "exchange limit", + "was_forced": false + }, + "queryString": "", + "bodyParams": "{\"amount\":\"1\",\"nonce\":\"1565757359970836683\",\"price\":\"9000\",\"request\":\"/v1/order/new\",\"side\":\"buy\",\"symbol\":\"btcusd\",\"type\":\"exchange limit\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhbW91bnQiOiIxIiwibm9uY2UiOiIxNTY1NzU3MzU5OTcwODM2NjgzIiwicHJpY2UiOiI5MDAwIiwicmVxdWVzdCI6Ii92MS9vcmRlci9uZXciLCJzaWRlIjoiYnV5Iiwic3ltYm9sIjoiYnRjdXNkIiwidHlwZSI6ImV4Y2hhbmdlIGxpbWl0In0=" + ], + "X-Gemini-Signature": [ + "8531b95a96ea6381d0e1d027687fd1a1ed2bc80e2ad3f138707d70367fac02b0b71a853be3af28ed9bcbab85791585bb" + ] + } + } + ] + }, + "/v1/order/status": { + "POST": [ + { + "data": { + "avg_execution_price": "0.00", + "exchange": "gemini", + "executed_amount": "0", + "id": "265563260", + "is_cancelled": true, + "is_hidden": false, + "is_live": false, + "options": [], + "order_id": "265563260", + "original_amount": "1", + "price": "9000.00", + "reason": "Requested", + "remaining_amount": "1", + "side": "buy", + "symbol": "btcusd", + "timestamp": "1565674818", + "timestampms": 1565674818313, + "type": "exchange limit", + "was_forced": false + }, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565674861178731900\",\"order_id\":265563260,\"request\":\"/v1/order/status\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU2NzQ4NjExNzg3MzE5MDAiLCJvcmRlcl9pZCI6MjY1NTYzMjYwLCJyZXF1ZXN0IjoiL3YxL29yZGVyL3N0YXR1cyJ9" + ], + "X-Gemini-Signature": [ + "ddc154a1ca1398d9de075ddd81d99001e3d56b4fa7934dd269681c2dee3b2d3451b968067d97ab9bc9afde96573d42a9" + ] + } + } + ] + }, + "/v1/orders": { + "POST": [ + { + "data": null, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565675056878829054\",\"request\":\"/v1/orders\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU2NzUwNTY4Nzg4MjkwNTQiLCJyZXF1ZXN0IjoiL3YxL29yZGVycyJ9" + ], + "X-Gemini-Signature": [ + "8c193c505c6cdc3fb34fd968ba0f62a10b583d048f101cf242568fe04f1ab8066d29e2760d9d73b1a1755ade4b68b13e" + ] + } + }, + { + "data": null, + "queryString": "", + "bodyParams": "{\"nonce\":\"1565749950340319722\",\"request\":\"/v1/orders\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU3NDk5NTAzNDAzMTk3MjIiLCJyZXF1ZXN0IjoiL3YxL29yZGVycyJ9" + ], + "X-Gemini-Signature": [ + "b95f7062b488830d9a8e4c28b8451ddfbfa310808c95442112a10865993ffd91170ea82b26b45ee9ef28e7acfb3b57c0" + ] + } + } + ] + }, + "/v1/pubticker/BTCUSD": { + "GET": [ + { + "data": { + "ask": "11388.08", + "bid": "11381.50", + "last": "11381.47", + "volume": { + "BTC": "915.6534281291", + "USD": "10394749.066094351423000000000000000095", + "timestamp": 1565587800000 + } + }, + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] + }, + "/v1/pubticker/bla": { + "GET": [ + { + "data": { + "message": "Supplied value 'bla' is not a valid symbol. Please correct your API request to use one of the supported symbols: [zecbch, bchbtc, zecusd, ethusd, zecbtc, bcheth, zecltc, ltcbch, bchusd, ethbtc, ltcbtc, ltceth, zeceth, ltcusd, btcusd]", + "reason": "Bad Request", + "result": "error" + }, + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] + }, + "/v1/symbols": { + "GET": [ + { + "data": [ + "btcusd", + "ethbtc", + "ethusd", + "bchusd", + "bchbtc", + "bcheth", + "ltcusd", + "ltcbtc", + "ltceth", + "ltcbch", + "zecusd", + "zecbtc", + "zeceth", + "zecbch", + "zecltc" + ], + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] + }, + "/v1/trades/btcusd": { + "GET": [ + { + "data": [ + { + "amount": "0.03", + "exchange": "gemini", + "price": "11410.49", + "tid": 7756154092, + "timestamp": 1565662735, + "timestampms": 1565662735226, + "type": "sell" + }, + { + "amount": "0.0071767", + "exchange": "gemini", + "price": "11414.59", + "tid": 7756153529, + "timestamp": 1565662726, + "timestampms": 1565662726360, + "type": "buy" + }, + { + "amount": "0.01717355", + "exchange": "gemini", + "price": "11408.01", + "tid": 7756151885, + "timestamp": 1565662714, + "timestampms": 1565662714333, + "type": "buy" + }, + { + "amount": "0.0045808", + "exchange": "gemini", + "price": "11408.01", + "tid": 7756149211, + "timestamp": 1565662687, + "timestampms": 1565662687630, + "type": "buy" + }, + { + "amount": "0.00846135", + "exchange": "gemini", + "price": "11408.01", + "tid": 7756149149, + "timestamp": 1565662686, + "timestampms": 1565662686732, + "type": "buy" + }, + { + "amount": "0.04296096", + "exchange": "gemini", + "price": "11408.01", + "tid": 7756147940, + "timestamp": 1565662676, + "timestampms": 1565662676986, + "type": "buy" + }, + { + "amount": "0.00832", + "exchange": "gemini", + "price": "11408.00", + "tid": 7756146325, + "timestamp": 1565662656, + "timestampms": 1565662656071, + "type": "sell" + }, + { + "amount": "0.00213244", + "exchange": "gemini", + "price": "11408.01", + "tid": 7756144273, + "timestamp": 1565662637, + "timestampms": 1565662637642, + "type": "buy" + }, + { + "amount": "0.00701265", + "exchange": "gemini", + "price": "11408.01", + "tid": 7756141855, + "timestamp": 1565662599, + "timestampms": 1565662599028, + "type": "buy" + }, + { + "amount": "0.00032601", + "exchange": "gemini", + "price": "11408.01", + "tid": 7756140963, + "timestamp": 1565662591, + "timestampms": 1565662591120, + "type": "buy" + }, + { + "amount": "0.01019987", + "exchange": "gemini", + "price": "11408.01", + "tid": 7756140337, + "timestamp": 1565662583, + "timestampms": 1565662583942, + "type": "buy" + }, + { + "amount": "0.00552876", + "exchange": "gemini", + "price": "11408.01", + "tid": 7756139733, + "timestamp": 1565662570, + "timestampms": 1565662570945, + "type": "buy" + }, + { + "amount": "0.00043825", + "exchange": "gemini", + "price": "11408.01", + "tid": 7756134697, + "timestamp": 1565662520, + "timestampms": 1565662520860, + "type": "buy" + }, + { + "amount": "0.00583", + "exchange": "gemini", + "price": "11407.94", + "tid": 7756118198, + "timestamp": 1565662327, + "timestampms": 1565662327677, + "type": "sell" + }, + { + "amount": "0.01263828", + "exchange": "gemini", + "price": "11410.72", + "tid": 7756117317, + "timestamp": 1565662317, + "timestampms": 1565662317982, + "type": "buy" + }, + { + "amount": "0.04291914", + "exchange": "gemini", + "price": "11410.72", + "tid": 7756113787, + "timestamp": 1565662281, + "timestampms": 1565662281322, + "type": "buy" + }, + { + "amount": "0.00315954", + "exchange": "gemini", + "price": "11410.72", + "tid": 7756111860, + "timestamp": 1565662258, + "timestampms": 1565662258557, + "type": "buy" + }, + { + "amount": "0.00845327", + "exchange": "gemini", + "price": "11410.72", + "tid": 7756111825, + "timestamp": 1565662258, + "timestampms": 1565662258071, + "type": "buy" + }, + { + "amount": "0.03667255", + "exchange": "gemini", + "price": "11414.35", + "tid": 7756105458, + "timestamp": 1565662212, + "timestampms": 1565662212913, + "type": "buy" + }, + { + "amount": "0.41013308", + "exchange": "gemini", + "price": "11414.38", + "tid": 7756100793, + "timestamp": 1565662172, + "timestampms": 1565662172924, + "type": "buy" + }, + { + "amount": "0.15663523", + "exchange": "gemini", + "price": "11409.25", + "tid": 7756100791, + "timestamp": 1565662172, + "timestampms": 1565662172924, + "type": "buy" + }, + { + "amount": "0.0343667", + "exchange": "gemini", + "price": "11408.70", + "tid": 7756096273, + "timestamp": 1565662114, + "timestampms": 1565662114857, + "type": "buy" + }, + { + "amount": "0.21478195", + "exchange": "gemini", + "price": "11408.70", + "tid": 7756095445, + "timestamp": 1565662108, + "timestampms": 1565662108455, + "type": "buy" + }, + { + "amount": "0.12445907", + "exchange": "gemini", + "price": "11404.40", + "tid": 7756087118, + "timestamp": 1565662042, + "timestampms": 1565662042548, + "type": "buy" + }, + { + "amount": "0.00438358", + "exchange": "gemini", + "price": "11404.40", + "tid": 7756085279, + "timestamp": 1565662024, + "timestampms": 1565662024021, + "type": "buy" + }, + { + "amount": "0.01366728", + "exchange": "gemini", + "price": "11406.18", + "tid": 7756071480, + "timestamp": 1565661926, + "timestampms": 1565661926111, + "type": "buy" + }, + { + "amount": "0.00157993", + "exchange": "gemini", + "price": "11406.18", + "tid": 7756070690, + "timestamp": 1565661906, + "timestampms": 1565661906420, + "type": "buy" + }, + { + "amount": "0.16916508", + "exchange": "gemini", + "price": "11405.87", + "tid": 7756066120, + "timestamp": 1565661839, + "timestampms": 1565661839371, + "type": "buy" + }, + { + "amount": "0.04297209", + "exchange": "gemini", + "price": "11405.06", + "tid": 7756064552, + "timestamp": 1565661817, + "timestampms": 1565661817849, + "type": "buy" + }, + { + "amount": "0.00197396", + "exchange": "gemini", + "price": "11405.80", + "tid": 7756059702, + "timestamp": 1565661787, + "timestampms": 1565661787203, + "type": "buy" + }, + { + "amount": "0.01718667", + "exchange": "gemini", + "price": "11405.80", + "tid": 7756058987, + "timestamp": 1565661784, + "timestampms": 1565661784015, + "type": "buy" + }, + { + "amount": "0.01464033", + "exchange": "gemini", + "price": "11416.39", + "tid": 7756045315, + "timestamp": 1565661724, + "timestampms": 1565661724752, + "type": "buy" + }, + { + "amount": "0.00236957", + "exchange": "gemini", + "price": "11415.53", + "tid": 7756041566, + "timestamp": 1565661698, + "timestampms": 1565661698300, + "type": "buy" + }, + { + "amount": "0.00991881", + "exchange": "gemini", + "price": "11409.33", + "tid": 7756030166, + "timestamp": 1565661644, + "timestampms": 1565661644988, + "type": "buy" + }, + { + "amount": "0.00316114", + "exchange": "gemini", + "price": "11409.33", + "tid": 7756026522, + "timestamp": 1565661634, + "timestampms": 1565661634763, + "type": "buy" + }, + { + "amount": "0.00316114", + "exchange": "gemini", + "price": "11409.31", + "tid": 7756021972, + "timestamp": 1565661616, + "timestampms": 1565661616557, + "type": "buy" + }, + { + "amount": "0.00008688", + "exchange": "gemini", + "price": "11396.47", + "tid": 7756018962, + "timestamp": 1565661603, + "timestampms": 1565661603828, + "type": "buy" + }, + { + "amount": "0.00846306", + "exchange": "gemini", + "price": "11399.41", + "tid": 7756015474, + "timestamp": 1565661561, + "timestampms": 1565661561099, + "type": "buy" + }, + { + "amount": "0.03669667", + "exchange": "gemini", + "price": "11403.82", + "tid": 7756002472, + "timestamp": 1565661433, + "timestampms": 1565661433637, + "type": "buy" + }, + { + "amount": "0.01579948", + "exchange": "gemini", + "price": "11403.83", + "tid": 7755997677, + "timestamp": 1565661391, + "timestampms": 1565661391558, + "type": "buy" + }, + { + "amount": "0.0017609", + "exchange": "gemini", + "price": "11400.85", + "tid": 7755995726, + "timestamp": 1565661376, + "timestampms": 1565661376001, + "type": "sell" + }, + { + "amount": "0.1", + "exchange": "gemini", + "price": "11397.94", + "tid": 7755979361, + "timestamp": 1565661289, + "timestampms": 1565661289055, + "type": "buy" + }, + { + "amount": "0.0084669", + "exchange": "gemini", + "price": "11397.93", + "tid": 7755976484, + "timestamp": 1565661263, + "timestampms": 1565661263285, + "type": "buy" + }, + { + "amount": "0.1088999", + "exchange": "gemini", + "price": "11393.57", + "tid": 7755972520, + "timestamp": 1565661244, + "timestampms": 1565661244552, + "type": "sell" + }, + { + "amount": "0.01003316", + "exchange": "gemini", + "price": "11399.03", + "tid": 7755954448, + "timestamp": 1565661127, + "timestampms": 1565661127872, + "type": "buy" + }, + { + "amount": "0.00561447", + "exchange": "gemini", + "price": "11396.26", + "tid": 7755952680, + "timestamp": 1565661118, + "timestampms": 1565661118348, + "type": "sell" + }, + { + "amount": "0.000032", + "exchange": "gemini", + "price": "11411.34", + "tid": 7755939408, + "timestamp": 1565661046, + "timestampms": 1565661046130, + "type": "buy" + }, + { + "amount": "0.00052406", + "exchange": "gemini", + "price": "11410.99", + "tid": 7755939171, + "timestamp": 1565661044, + "timestampms": 1565661044529, + "type": "buy" + }, + { + "amount": "0.028", + "exchange": "gemini", + "price": "11391.45", + "tid": 7755932784, + "timestamp": 1565661019, + "timestampms": 1565661019773, + "type": "sell" + }, + { + "amount": "0.03435606", + "exchange": "gemini", + "price": "11391.35", + "tid": 7755926007, + "timestamp": 1565660948, + "timestampms": 1565660948681, + "type": "sell" + } + ], + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] + }, + "/v1/tradevolume": { + "POST": [ + { + "data": [ + null + ], + "queryString": "", + "bodyParams": "{\"nonce\":\"1565675273154242836\",\"request\":\"/v1/tradevolume\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJub25jZSI6IjE1NjU2NzUyNzMxNTQyNDI4MzYiLCJyZXF1ZXN0IjoiL3YxL3RyYWRldm9sdW1lIn0=" + ], + "X-Gemini-Signature": [ + "dcd95c9f6713ea6cb1337d2c5cc6ab1299474d844fdd2ad9b10c0c593ed5d830495356894a7323c5fac2bed27a46fb95" + ] + } + } + ] + }, + "/v1/withdraw/btc": { + "POST": [ + { + "data": { + "message": "Cryptocurrency operations are not available in sandbox", + "reason": "EndpointUnavailableInThisEnvironment", + "result": "error" + }, + "queryString": "", + "bodyParams": "{\"address\":\"LOL123\",\"amount\":\"1\",\"nonce\":\"1565675733865010769\",\"request\":\"/v1/withdraw/btc\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhZGRyZXNzIjoiTE9MMTIzIiwiYW1vdW50IjoiMSIsIm5vbmNlIjoiMTU2NTY3NTczMzg2NTAxMDc2OSIsInJlcXVlc3QiOiIvdjEvd2l0aGRyYXcvYnRjIn0=" + ], + "X-Gemini-Signature": [ + "f40cc365ed5d04b79bcdd97c31bac22378ab86ce2308417c20329bc0f5dde1ce4da4ff2c99cdb13504a08d7b45312e87" + ] + } + }, + { + "data": { + "message": "Cryptocurrency operations are not available in sandbox", + "reason": "EndpointUnavailableInThisEnvironment", + "result": "error" + }, + "queryString": "", + "bodyParams": "{\"address\":\"LOL123\",\"amount\":\"1\",\"nonce\":\"1565678114011106013\",\"request\":\"/v1/withdraw/btc\"}", + "headers": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhZGRyZXNzIjoiTE9MMTIzIiwiYW1vdW50IjoiMSIsIm5vbmNlIjoiMTU2NTY3ODExNDAxMTEwNjAxMyIsInJlcXVlc3QiOiIvdjEvd2l0aGRyYXcvYnRjIn0=" + ], + "X-Gemini-Signature": [ + "ef3e63bc5f7e124df66cea42463560e7178d5224ba456e92db92184220e2235c0f17d317cc9d332add5e7e34b7fe159a" + ] + } + }, + { + "data": { + "message": "Cryptocurrency operations are not available in sandbox", + "reason": "EndpointUnavailableInThisEnvironment", + "result": "error" + }, + "queryString": "", + "bodyParams": "{\"address\":\"1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB\",\"amount\":\"-1\",\"nonce\":\"1565755733919473409\",\"request\":\"/v1/withdraw/btc\"}", + "headers": { + "Cache-Control": [ + "no-cache" + ], + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain" + ], + "X-Gemini-Apikey": [ + "" + ], + "X-Gemini-Payload": [ + "eyJhZGRyZXNzIjoiMUY1elZEZ05qb3JKNTFvR2ViU3ZOQ3JTQUhwd0drVWREQiIsImFtb3VudCI6IjEwMCIsIm5vbmNlIjoiMTU2NTc1NTczMzkxOTQ3MzQwOSIsInJlcXVlc3QiOiIvdjEvd2l0aGRyYXcvYnRjIn0=" + ], + "X-Gemini-Signature": [ + "1e421f7d1726bfec26770e53b8f860f7406c33a329517ef199b1bdb2adf070e5866996bedcce1f69ffc6df19013ddc4e" + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/testdata/http_mock/localbitcoins/localbitcoins.json b/testdata/http_mock/localbitcoins/localbitcoins.json new file mode 100644 index 00000000..1553be63 --- /dev/null +++ b/testdata/http_mock/localbitcoins/localbitcoins.json @@ -0,0 +1,3400 @@ +{ + "routes": { + "/api/ad-create/": { + "POST": [ + { + "data": { + "error": { + "message": "Invalid parameters.", + "errors": { + "bank_name": "* This field is required.", + "trade_type": "* This field is required.", + "countrycode": "* This field is required.", + "lon": "* This field is required.", + "currency": "* This field is required.", + "online_provider": "* This field is required.", + "lat": "* This field is required.", + "price_equation": "* This field is required." + }, + "error_code": 9, + "error_lists": { + "bank_name": [ + "This field is required." + ], + "trade_type": [ + "This field is required." + ], + "countrycode": [ + "This field is required." + ], + "lon": [ + "This field is required." + ], + "currency": [ + "This field is required." + ], + "online_provider": [ + "This field is required." + ], + "lat": [ + "This field is required." + ], + "price_equation": [ + "This field is required." + ] + } + } + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1560494018449563145" + ], + "Apiauth-Signature": [ + "C369F0749F574A16403D561CD35987F18E956640E320EEEE9B864930050C0242" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/ad-delete/1/": { + "POST": [ + { + "data": { + "error": { + "message": "No resource matching criteria was found.", + "error_code": 6 + } + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1560493270888731262" + ], + "Apiauth-Signature": [ + "597AF7294B303FA10CA398123133158C0998F37AB93E2DA5D6233F3D4E0E1E2A" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/ad-get/": { + "GET": [ + { + "data": { + "error": { + "error_code": 11, + "message": "One or more of the ad IDs was not a valid integer" + } + }, + "queryString": "ads=", + "bodyParams": "ads=", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1561959253543681207" + ], + "Apiauth-Signature": [ + "A81AEA23AB65937ED0F957BAD72AC892E273383C83B6F55A574C3090091D89A0" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/ad/1337/": { + "POST": [ + { + "data": { + "error": { + "message": "No resource matching criteria was found.", + "error_code": 6 + } + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1560495093788997079" + ], + "Apiauth-Signature": [ + "AE887E05554C9E9DFBD553A3698156D9479576F185BC0C833E6ED38243060848" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/ads/": { + "GET": [ + { + "data": { + "data": { + "ad_list": [], + "ad_count": 0 + } + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1560492666483445443" + ], + "Apiauth-Signature": [ + "6F5246109A416AD75CC8F1C708BD7CA7426400CBE40464AE119EBE313B4F69BC" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/dashboard/": { + "GET": [ + { + "data": { + "data": { + "contact_count": 0, + "contact_list": [] + } + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1561960286136838890" + ], + "Apiauth-Signature": [ + "88B82299DB73CA1CE039C13785605AFAFDEBC03B2A1FF5DC93773F30096150D6" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/dashboard/canceled/": { + "GET": [ + { + "data": { + "data": { + "contact_count": 2, + "contact_list": [ + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/32805", + "advertisement_url": "https://localbitcoins.com/api/ad-get/32805/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/16885873/", + "messages_url": "https://localbitcoins.com/api/contact_messages/16885873/", + "release_url": "https://localbitcoins.com/api/contact_release/16885873/" + }, + "data": { + "account_info": "{}", + "advertisement": { + "advertiser": { + "feedback_score": 99, + "last_online": "2019-04-12T07:33:34+00:00", + "name": "", + "trade_count": "3000+", + "username": "" + }, + "id": 32805, + "payment_method": "CASH_DEPOSIT", + "trade_type": "ONLINE_BUY" + }, + "amount": "2000.00", + "amount_btc": "0.08903554", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 99, + "last_online": "2019-04-12T07:33:34+00:00", + "name": "", + "real_name": "", + "trade_count": "3000+", + "username": "" + }, + "canceled_at": "2017-12-08T04:49:40+00:00", + "closed_at": "2017-12-08T04:49:40+00:00", + "contact_id": 16885873, + "created_at": "2017-12-08T04:31:29+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-12-08T04:31:29+00:00", + "exchange_rate_updated_at": "2017-12-08T04:31:29+00:00", + "fee_btc": "0.00089036", + "funded_at": "2017-12-08T04:31:29+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": null, + "reference_code": "L16885873BA1X81", + "released_at": null, + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/556123", + "advertisement_url": "https://localbitcoins.com/api/ad-get/556123/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/15160798/", + "messages_url": "https://localbitcoins.com/api/contact_messages/15160798/", + "release_url": "https://localbitcoins.com/api/contact_release/15160798/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2018-06-25T08:15:40+00:00", + "name": "", + "trade_count": "1000+", + "username": "" + }, + "id": 556123, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1055.84", + "amount_btc": "0.14999986", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2018-06-25T08:15:40+00:00", + "name": "", + "real_name": "", + "trade_count": "1000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-10-16T23:08:02+00:00", + "contact_id": 15160798, + "created_at": "2017-10-16T21:37:44+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-10-16T21:37:44+00:00", + "exchange_rate_updated_at": "2017-10-16T21:37:44+00:00", + "fee_btc": "0.00150000", + "funded_at": "2017-10-16T21:37:44+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": null, + "reference_code": "L15160798B90Y5A", + "released_at": null, + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + } + ] + } + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1561964296416293293" + ], + "Apiauth-Signature": [ + "DFEC2CF704C6E63A2B7F6C5EB52086BB50737A3AFE8A17517BBA0B216C64C5B6" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/dashboard/closed/": { + "GET": [ + { + "data": { + "data": { + "contact_count": 18, + "contact_list": [ + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/654093", + "advertisement_url": "https://localbitcoins.com/api/ad-get/654093/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/20543871/", + "messages_url": "https://localbitcoins.com/api/contact_messages/20543871/", + "release_url": "https://localbitcoins.com/api/contact_release/20543871/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-30T21:32:38+00:00", + "name": "", + "trade_count": "3000+", + "username": "" + }, + "id": 654093, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "3038.91", + "amount_btc": "0.29599994", + "buyer": { + "company_name": null, + "countrycode_by_ip": "GB", + "countrycode_by_phone_number": "GB", + "feedback_score": 100, + "last_online": "2019-06-30T21:32:38+00:00", + "name": "", + "real_name": "", + "trade_count": "3000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2018-04-13T13:02:00+00:00", + "contact_id": 20543871, + "created_at": "2018-04-13T12:50:22+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2018-04-13T12:50:22+00:00", + "exchange_rate_updated_at": "2018-04-13T12:50:22+00:00", + "fee_btc": "0.00296000", + "funded_at": "2018-04-13T12:50:22+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2018-04-13T12:59:04+00:00", + "reference_code": "L20543871BC8BR3", + "released_at": "2018-04-13T13:02:00+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/243945", + "advertisement_url": "https://localbitcoins.com/api/ad-get/243945/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/18364961/", + "messages_url": "https://localbitcoins.com/api/contact_messages/18364961/", + "release_url": "https://localbitcoins.com/api/contact_release/18364961/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "lbrun1.3" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-16T02:46:03+00:00", + "name": "", + "trade_count": "10 000+", + "username": "" + }, + "id": 243945, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "304.26", + "amount_btc": "0.02247995", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-06-16T02:46:03+00:00", + "name": "", + "real_name": "", + "trade_count": "10 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2018-01-24T06:31:43+00:00", + "contact_id": 18364961, + "created_at": "2018-01-24T05:58:35+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2018-01-24T05:58:35+00:00", + "exchange_rate_updated_at": "2018-01-24T05:58:35+00:00", + "fee_btc": "0.00022480", + "funded_at": "2018-01-24T05:58:35+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2018-01-24T06:27:05+00:00", + "reference_code": "L18364961BAXMHT", + "released_at": "2018-01-24T06:31:43+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/512104", + "advertisement_url": "https://localbitcoins.com/api/ad-get/512104/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/18364860/", + "messages_url": "https://localbitcoins.com/api/contact_messages/18364860/", + "release_url": "https://localbitcoins.com/api/contact_release/18364860/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "lbrun1.6" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-27T12:07:08+00:00", + "name": "", + "trade_count": "30+", + "username": "" + }, + "id": 512104, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "7000.00", + "amount_btc": "0.49696073", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-06-27T12:07:08+00:00", + "name": "", + "real_name": "", + "trade_count": "30+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2018-01-24T06:30:07+00:00", + "contact_id": 18364860, + "created_at": "2018-01-24T05:48:23+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2018-01-24T05:48:23+00:00", + "exchange_rate_updated_at": "2018-01-24T05:48:23+00:00", + "fee_btc": "0.00496961", + "funded_at": "2018-01-24T05:48:23+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2018-01-24T06:26:15+00:00", + "reference_code": "L18364860BAXMF0", + "released_at": "2018-01-24T06:30:07+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/367850", + "advertisement_url": "https://localbitcoins.com/api/ad-get/367850/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/18364846/", + "messages_url": "https://localbitcoins.com/api/contact_messages/18364846/", + "release_url": "https://localbitcoins.com/api/contact_release/18364846/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "lbrun1.5" + }, + "advertisement": { + "advertiser": { + "feedback_score": 95, + "last_online": "2019-05-23T11:22:33+00:00", + "name": "", + "trade_count": "500+", + "username": "" + }, + "id": 367850, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "5000.00", + "amount_btc": "0.35453299", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 95, + "last_online": "2019-05-23T11:22:33+00:00", + "name": "", + "real_name": "", + "trade_count": "500+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2018-01-24T06:13:59+00:00", + "contact_id": 18364846, + "created_at": "2018-01-24T05:47:12+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2018-01-24T05:47:12+00:00", + "exchange_rate_updated_at": "2018-01-24T05:47:12+00:00", + "fee_btc": "0.00354533", + "funded_at": "2018-01-24T05:47:12+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2018-01-24T06:05:45+00:00", + "reference_code": "L18364846BAXMEM", + "released_at": "2018-01-24T06:13:59+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/127500", + "advertisement_url": "https://localbitcoins.com/api/ad-get/127500/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/18364904/", + "messages_url": "https://localbitcoins.com/api/contact_messages/18364904/", + "release_url": "https://localbitcoins.com/api/contact_release/18364904/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "lbrun1.4" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-07-01T06:54:40+00:00", + "name": "", + "trade_count": "20 000+", + "username": "" + }, + "id": 127500, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "962.00", + "amount_btc": "0.06836873", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-07-01T06:54:40+00:00", + "name": "", + "real_name": "", + "trade_count": "20 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2018-01-24T06:02:29+00:00", + "contact_id": 18364904, + "created_at": "2018-01-24T05:51:59+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2018-01-24T05:51:59+00:00", + "exchange_rate_updated_at": "2018-01-24T05:51:59+00:00", + "fee_btc": "0.00068369", + "funded_at": "2018-01-24T05:51:59+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2018-01-24T06:00:18+00:00", + "reference_code": "L18364904BAXMG8", + "released_at": "2018-01-24T06:02:29+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/613428", + "advertisement_url": "https://localbitcoins.com/api/ad-get/613428/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/17356107/", + "messages_url": "https://localbitcoins.com/api/contact_messages/17356107/", + "release_url": "https://localbitcoins.com/api/contact_release/17356107/" + }, + "data": { + "account_info": "{}", + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-07-01T06:42:40+00:00", + "name": "", + "trade_count": "10 000+", + "username": "" + }, + "id": 613428, + "payment_method": "SPECIFIC_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "2000.00", + "amount_btc": "0.09279954", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-07-01T06:42:40+00:00", + "name": "", + "real_name": "", + "trade_count": "10 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-12-20T21:04:14+00:00", + "contact_id": 17356107, + "created_at": "2017-12-20T20:57:32+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-12-20T20:57:32+00:00", + "exchange_rate_updated_at": "2017-12-20T20:57:32+00:00", + "fee_btc": "0.00092800", + "funded_at": "2017-12-20T20:57:32+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-12-20T21:02:59+00:00", + "reference_code": "L17356107BAC023", + "released_at": "2017-12-20T21:04:14+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/484383", + "advertisement_url": "https://localbitcoins.com/api/ad-get/484383/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/16887828/", + "messages_url": "https://localbitcoins.com/api/contact_messages/16887828/", + "release_url": "https://localbitcoins.com/api/contact_release/16887828/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-07-01T06:42:40+00:00", + "name": "", + "trade_count": "10 000+", + "username": "" + }, + "id": 484383, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1800.00", + "amount_btc": "0.08733069", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-07-01T06:42:40+00:00", + "name": "", + "real_name": "", + "trade_count": "10 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-12-08T06:30:04+00:00", + "contact_id": 16887828, + "created_at": "2017-12-08T06:16:01+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-12-08T06:16:01+00:00", + "exchange_rate_updated_at": "2017-12-08T06:16:01+00:00", + "fee_btc": "0.00087331", + "funded_at": "2017-12-08T06:16:01+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-12-08T06:25:30+00:00", + "reference_code": "L16887828BA1YQC", + "released_at": "2017-12-08T06:30:04+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/32805", + "advertisement_url": "https://localbitcoins.com/api/ad-get/32805/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/16885873/", + "messages_url": "https://localbitcoins.com/api/contact_messages/16885873/", + "release_url": "https://localbitcoins.com/api/contact_release/16885873/" + }, + "data": { + "account_info": "{}", + "advertisement": { + "advertiser": { + "feedback_score": 99, + "last_online": "2019-04-12T07:33:34+00:00", + "name": "", + "trade_count": "3000+", + "username": "" + }, + "id": 32805, + "payment_method": "CASH_DEPOSIT", + "trade_type": "ONLINE_BUY" + }, + "amount": "2000.00", + "amount_btc": "0.08903554", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 99, + "last_online": "2019-04-12T07:33:34+00:00", + "name": "", + "real_name": "", + "trade_count": "3000+", + "username": "" + }, + "canceled_at": "2017-12-08T04:49:40+00:00", + "closed_at": "2017-12-08T04:49:40+00:00", + "contact_id": 16885873, + "created_at": "2017-12-08T04:31:29+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-12-08T04:31:29+00:00", + "exchange_rate_updated_at": "2017-12-08T04:31:29+00:00", + "fee_btc": "0.00089036", + "funded_at": "2017-12-08T04:31:29+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": null, + "reference_code": "L16885873BA1X81", + "released_at": null, + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/367850", + "advertisement_url": "https://localbitcoins.com/api/ad-get/367850/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/16658839/", + "messages_url": "https://localbitcoins.com/api/contact_messages/16658839/", + "release_url": "https://localbitcoins.com/api/contact_release/16658839/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 95, + "last_online": "2019-05-23T11:22:33+00:00", + "name": "", + "trade_count": "500+", + "username": "" + }, + "id": 367850, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1200.00", + "amount_btc": "0.08218750", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 95, + "last_online": "2019-05-23T11:22:33+00:00", + "name": "", + "real_name": "", + "trade_count": "500+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-12-02T00:24:27+00:00", + "contact_id": 16658839, + "created_at": "2017-12-01T23:57:20+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-12-01T23:57:20+00:00", + "exchange_rate_updated_at": "2017-12-01T23:57:20+00:00", + "fee_btc": "0.00082188", + "funded_at": "2017-12-01T23:57:20+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-12-02T00:23:08+00:00", + "reference_code": "L16658839B9X21J", + "released_at": "2017-12-02T00:24:27+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/600091", + "advertisement_url": "https://localbitcoins.com/api/ad-get/600091/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/16269655/", + "messages_url": "https://localbitcoins.com/api/contact_messages/16269655/", + "release_url": "https://localbitcoins.com/api/contact_release/16269655/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-30T21:32:38+00:00", + "name": "", + "trade_count": "3000+", + "username": "" + }, + "id": 600091, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1200.00", + "amount_btc": "0.11403170", + "buyer": { + "company_name": null, + "countrycode_by_ip": "GB", + "countrycode_by_phone_number": "GB", + "feedback_score": 100, + "last_online": "2019-06-30T21:32:38+00:00", + "name": "", + "real_name": "", + "trade_count": "3000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-11-20T06:33:08+00:00", + "contact_id": 16269655, + "created_at": "2017-11-20T06:29:02+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-11-20T06:29:02+00:00", + "exchange_rate_updated_at": "2017-11-20T06:29:02+00:00", + "fee_btc": "0.00114032", + "funded_at": "2017-11-20T06:29:02+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-11-20T06:31:43+00:00", + "reference_code": "L16269655B9OPQV", + "released_at": "2017-11-20T06:33:08+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/557284", + "advertisement_url": "https://localbitcoins.com/api/ad-get/557284/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/16135919/", + "messages_url": "https://localbitcoins.com/api/contact_messages/16135919/", + "release_url": "https://localbitcoins.com/api/contact_release/16135919/" + }, + "data": { + "account_info": "{}", + "advertisement": { + "advertiser": { + "feedback_score": 98, + "last_online": "2019-05-18T23:48:12+00:00", + "name": "", + "trade_count": "3000+", + "username": "" + }, + "id": 557284, + "payment_method": "CASH_DEPOSIT", + "trade_type": "ONLINE_BUY" + }, + "amount": "1000.00", + "amount_btc": "0.10197017", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 98, + "last_online": "2019-05-18T23:48:12+00:00", + "name": "", + "real_name": "", + "trade_count": "3000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-11-16T01:25:10+00:00", + "contact_id": 16135919, + "created_at": "2017-11-15T23:50:30+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-11-15T23:50:30+00:00", + "exchange_rate_updated_at": "2017-11-15T23:50:30+00:00", + "fee_btc": "0.00101970", + "funded_at": "2017-11-15T23:50:30+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-11-16T00:12:48+00:00", + "reference_code": "L16135919B9LUJZ", + "released_at": "2017-11-16T01:25:10+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/556123", + "advertisement_url": "https://localbitcoins.com/api/ad-get/556123/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/15625291/", + "messages_url": "https://localbitcoins.com/api/contact_messages/15625291/", + "release_url": "https://localbitcoins.com/api/contact_release/15625291/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2018-06-25T08:15:40+00:00", + "name": "", + "trade_count": "1000+", + "username": "" + }, + "id": 556123, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1000.00", + "amount_btc": "0.11965859", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2018-06-25T08:15:40+00:00", + "name": "", + "real_name": "", + "trade_count": "1000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-11-01T07:50:21+00:00", + "contact_id": 15625291, + "created_at": "2017-11-01T06:25:20+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-11-01T06:25:20+00:00", + "exchange_rate_updated_at": "2017-11-01T06:25:20+00:00", + "fee_btc": "0.00119659", + "funded_at": "2017-11-01T06:25:20+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-11-01T07:09:10+00:00", + "reference_code": "L15625291B9AWJV", + "released_at": "2017-11-01T07:50:21+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/513602", + "advertisement_url": "https://localbitcoins.com/api/ad-get/513602/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/15326580/", + "messages_url": "https://localbitcoins.com/api/contact_messages/15326580/", + "release_url": "https://localbitcoins.com/api/contact_release/15326580/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-29T17:21:27+00:00", + "name": "", + "trade_count": "3000+", + "username": "" + }, + "id": 513602, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1200.00", + "amount_btc": "0.16343052", + "buyer": { + "company_name": null, + "countrycode_by_ip": "BR", + "countrycode_by_phone_number": "BR", + "feedback_score": 100, + "last_online": "2019-06-29T17:21:27+00:00", + "name": "", + "real_name": "", + "trade_count": "3000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-10-22T05:40:05+00:00", + "contact_id": 15326580, + "created_at": "2017-10-22T04:53:51+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-10-22T04:53:51+00:00", + "exchange_rate_updated_at": "2017-10-22T04:53:51+00:00", + "fee_btc": "0.00163431", + "funded_at": "2017-10-22T04:53:51+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-10-22T05:11:29+00:00", + "reference_code": "L15326580B94I2C", + "released_at": "2017-10-22T05:40:05+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/271961", + "advertisement_url": "https://localbitcoins.com/api/ad-get/271961/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/15165674/", + "messages_url": "https://localbitcoins.com/api/contact_messages/15165674/", + "release_url": "https://localbitcoins.com/api/contact_release/15165674/" + }, + "data": { + "account_info": "{}", + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2018-05-24T00:35:20+00:00", + "name": "", + "trade_count": "10 000+", + "username": "" + }, + "id": 271961, + "payment_method": "SPECIFIC_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1049.16", + "amount_btc": "0.15000000", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": null, + "feedback_score": 100, + "last_online": "2018-05-24T00:35:20+00:00", + "name": "", + "real_name": "", + "trade_count": "10 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-10-17T03:18:50+00:00", + "contact_id": 15165674, + "created_at": "2017-10-17T02:49:59+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-10-17T02:49:59+00:00", + "exchange_rate_updated_at": "2017-10-17T02:49:59+00:00", + "fee_btc": "0.00150000", + "funded_at": "2017-10-17T02:49:59+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-10-17T03:12:46+00:00", + "reference_code": "L15165674B911WQ", + "released_at": "2017-10-17T03:18:50+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/556123", + "advertisement_url": "https://localbitcoins.com/api/ad-get/556123/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/15160798/", + "messages_url": "https://localbitcoins.com/api/contact_messages/15160798/", + "release_url": "https://localbitcoins.com/api/contact_release/15160798/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2018-06-25T08:15:40+00:00", + "name": "", + "trade_count": "1000+", + "username": "" + }, + "id": 556123, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1055.84", + "amount_btc": "0.14999986", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2018-06-25T08:15:40+00:00", + "name": "", + "real_name": "", + "trade_count": "1000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-10-16T23:08:02+00:00", + "contact_id": 15160798, + "created_at": "2017-10-16T21:37:44+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-10-16T21:37:44+00:00", + "exchange_rate_updated_at": "2017-10-16T21:37:44+00:00", + "fee_btc": "0.00150000", + "funded_at": "2017-10-16T21:37:44+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": null, + "reference_code": "L15160798B90Y5A", + "released_at": null, + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/555158", + "advertisement_url": "https://localbitcoins.com/api/ad-get/555158/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/14635637/", + "messages_url": "https://localbitcoins.com/api/contact_messages/14635637/", + "release_url": "https://localbitcoins.com/api/contact_release/14635637/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-05-16T12:33:56+00:00", + "name": "", + "trade_count": "1000+", + "username": "" + }, + "id": 555158, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1039.09", + "amount_btc": "0.20000077", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-05-16T12:33:56+00:00", + "name": "", + "real_name": "", + "trade_count": "1000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-09-29T01:11:57+00:00", + "contact_id": 14635637, + "created_at": "2017-09-29T00:56:22+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-09-29T00:56:22+00:00", + "exchange_rate_updated_at": "2017-09-29T00:56:22+00:00", + "fee_btc": "0.00200001", + "funded_at": "2017-09-29T00:56:22+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-09-29T01:10:58+00:00", + "reference_code": "L14635637B8POXH", + "released_at": "2017-09-29T01:11:57+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/72713", + "advertisement_url": "https://localbitcoins.com/api/ad-get/72713/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/14353081/", + "messages_url": "https://localbitcoins.com/api/contact_messages/14353081/", + "release_url": "https://localbitcoins.com/api/contact_release/14353081/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 98, + "last_online": "2019-02-09T05:54:26+00:00", + "name": "", + "trade_count": "15 000+", + "username": "" + }, + "id": 72713, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1046.01", + "amount_btc": "0.20699993", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 98, + "last_online": "2019-02-09T05:54:26+00:00", + "name": "", + "real_name": "", + "trade_count": "15 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-09-18T23:10:20+00:00", + "contact_id": 14353081, + "created_at": "2017-09-18T21:53:08+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-09-18T21:53:08+00:00", + "exchange_rate_updated_at": "2017-09-18T21:53:08+00:00", + "fee_btc": "0.00207000", + "funded_at": "2017-09-18T21:53:08+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-09-18T22:24:20+00:00", + "reference_code": "L14353081B8JMWP", + "released_at": "2017-09-18T23:10:20+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/243945", + "advertisement_url": "https://localbitcoins.com/api/ad-get/243945/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/13641301/", + "messages_url": "https://localbitcoins.com/api/contact_messages/13641301/", + "release_url": "https://localbitcoins.com/api/contact_release/13641301/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-16T02:46:03+00:00", + "name": "", + "trade_count": "10 000+", + "username": "" + }, + "id": 243945, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1272.60", + "amount_btc": "0.25000098", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-06-16T02:46:03+00:00", + "name": "", + "real_name": "", + "trade_count": "10 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-08-24T05:33:01+00:00", + "contact_id": 13641301, + "created_at": "2017-08-24T05:01:24+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-08-24T05:01:24+00:00", + "exchange_rate_updated_at": "2017-08-24T05:01:24+00:00", + "fee_btc": "0.00250001", + "funded_at": "2017-08-24T05:01:24+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-08-24T05:27:34+00:00", + "reference_code": "L13641301B84DP1", + "released_at": "2017-08-24T05:33:01+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + } + ] + } + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1561964296416293294" + ], + "Apiauth-Signature": [ + "32B5FDB6D38A3D068C9FCB1076F38E6329AEE43C38BBF85F5243ADC7C9EA588E" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/dashboard/released/": { + "GET": [ + { + "data": { + "data": { + "contact_count": 16, + "contact_list": [ + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/654093", + "advertisement_url": "https://localbitcoins.com/api/ad-get/654093/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/20543871/", + "messages_url": "https://localbitcoins.com/api/contact_messages/20543871/", + "release_url": "https://localbitcoins.com/api/contact_release/20543871/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-30T21:32:38+00:00", + "name": "", + "trade_count": "3000+", + "username": "" + }, + "id": 654093, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "3038.91", + "amount_btc": "0.29599994", + "buyer": { + "company_name": null, + "countrycode_by_ip": "GB", + "countrycode_by_phone_number": "GB", + "feedback_score": 100, + "last_online": "2019-06-30T21:32:38+00:00", + "name": "", + "real_name": "", + "trade_count": "3000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2018-04-13T13:02:00+00:00", + "contact_id": 20543871, + "created_at": "2018-04-13T12:50:22+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2018-04-13T12:50:22+00:00", + "exchange_rate_updated_at": "2018-04-13T12:50:22+00:00", + "fee_btc": "0.00296000", + "funded_at": "2018-04-13T12:50:22+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2018-04-13T12:59:04+00:00", + "reference_code": "L20543871BC8BR3", + "released_at": "2018-04-13T13:02:00+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/243945", + "advertisement_url": "https://localbitcoins.com/api/ad-get/243945/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/18364961/", + "messages_url": "https://localbitcoins.com/api/contact_messages/18364961/", + "release_url": "https://localbitcoins.com/api/contact_release/18364961/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "lbrun1.3" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-16T02:46:03+00:00", + "name": "", + "trade_count": "10 000+", + "username": "" + }, + "id": 243945, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "304.26", + "amount_btc": "0.02247995", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-06-16T02:46:03+00:00", + "name": "", + "real_name": "", + "trade_count": "10 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2018-01-24T06:31:43+00:00", + "contact_id": 18364961, + "created_at": "2018-01-24T05:58:35+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2018-01-24T05:58:35+00:00", + "exchange_rate_updated_at": "2018-01-24T05:58:35+00:00", + "fee_btc": "0.00022480", + "funded_at": "2018-01-24T05:58:35+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2018-01-24T06:27:05+00:00", + "reference_code": "L18364961BAXMHT", + "released_at": "2018-01-24T06:31:43+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/512104", + "advertisement_url": "https://localbitcoins.com/api/ad-get/512104/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/18364860/", + "messages_url": "https://localbitcoins.com/api/contact_messages/18364860/", + "release_url": "https://localbitcoins.com/api/contact_release/18364860/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "lbrun1.6" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-27T12:07:08+00:00", + "name": "", + "trade_count": "30+", + "username": "" + }, + "id": 512104, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "7000.00", + "amount_btc": "0.49696073", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-06-27T12:07:08+00:00", + "name": "", + "real_name": "", + "trade_count": "30+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2018-01-24T06:30:07+00:00", + "contact_id": 18364860, + "created_at": "2018-01-24T05:48:23+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2018-01-24T05:48:23+00:00", + "exchange_rate_updated_at": "2018-01-24T05:48:23+00:00", + "fee_btc": "0.00496961", + "funded_at": "2018-01-24T05:48:23+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2018-01-24T06:26:15+00:00", + "reference_code": "L18364860BAXMF0", + "released_at": "2018-01-24T06:30:07+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/367850", + "advertisement_url": "https://localbitcoins.com/api/ad-get/367850/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/18364846/", + "messages_url": "https://localbitcoins.com/api/contact_messages/18364846/", + "release_url": "https://localbitcoins.com/api/contact_release/18364846/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "lbrun1.5" + }, + "advertisement": { + "advertiser": { + "feedback_score": 95, + "last_online": "2019-05-23T11:22:33+00:00", + "name": "", + "trade_count": "500+", + "username": "" + }, + "id": 367850, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "5000.00", + "amount_btc": "0.35453299", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 95, + "last_online": "2019-05-23T11:22:33+00:00", + "name": "", + "real_name": "", + "trade_count": "500+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2018-01-24T06:13:59+00:00", + "contact_id": 18364846, + "created_at": "2018-01-24T05:47:12+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2018-01-24T05:47:12+00:00", + "exchange_rate_updated_at": "2018-01-24T05:47:12+00:00", + "fee_btc": "0.00354533", + "funded_at": "2018-01-24T05:47:12+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2018-01-24T06:05:45+00:00", + "reference_code": "L18364846BAXMEM", + "released_at": "2018-01-24T06:13:59+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/127500", + "advertisement_url": "https://localbitcoins.com/api/ad-get/127500/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/18364904/", + "messages_url": "https://localbitcoins.com/api/contact_messages/18364904/", + "release_url": "https://localbitcoins.com/api/contact_release/18364904/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "lbrun1.4" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-07-01T06:54:40+00:00", + "name": "", + "trade_count": "20 000+", + "username": "" + }, + "id": 127500, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "962.00", + "amount_btc": "0.06836873", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-07-01T06:54:40+00:00", + "name": "", + "real_name": "", + "trade_count": "20 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2018-01-24T06:02:29+00:00", + "contact_id": 18364904, + "created_at": "2018-01-24T05:51:59+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2018-01-24T05:51:59+00:00", + "exchange_rate_updated_at": "2018-01-24T05:51:59+00:00", + "fee_btc": "0.00068369", + "funded_at": "2018-01-24T05:51:59+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2018-01-24T06:00:18+00:00", + "reference_code": "L18364904BAXMG8", + "released_at": "2018-01-24T06:02:29+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/613428", + "advertisement_url": "https://localbitcoins.com/api/ad-get/613428/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/17356107/", + "messages_url": "https://localbitcoins.com/api/contact_messages/17356107/", + "release_url": "https://localbitcoins.com/api/contact_release/17356107/" + }, + "data": { + "account_info": "{}", + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-07-01T06:42:40+00:00", + "name": "", + "trade_count": "10 000+", + "username": "" + }, + "id": 613428, + "payment_method": "SPECIFIC_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "2000.00", + "amount_btc": "0.09279954", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-07-01T06:42:40+00:00", + "name": "", + "real_name": "", + "trade_count": "10 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-12-20T21:04:14+00:00", + "contact_id": 17356107, + "created_at": "2017-12-20T20:57:32+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-12-20T20:57:32+00:00", + "exchange_rate_updated_at": "2017-12-20T20:57:32+00:00", + "fee_btc": "0.00092800", + "funded_at": "2017-12-20T20:57:32+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-12-20T21:02:59+00:00", + "reference_code": "L17356107BAC023", + "released_at": "2017-12-20T21:04:14+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/484383", + "advertisement_url": "https://localbitcoins.com/api/ad-get/484383/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/16887828/", + "messages_url": "https://localbitcoins.com/api/contact_messages/16887828/", + "release_url": "https://localbitcoins.com/api/contact_release/16887828/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-07-01T06:42:40+00:00", + "name": "", + "trade_count": "10 000+", + "username": "" + }, + "id": 484383, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1800.00", + "amount_btc": "0.08733069", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-07-01T06:42:40+00:00", + "name": "", + "real_name": "", + "trade_count": "10 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-12-08T06:30:04+00:00", + "contact_id": 16887828, + "created_at": "2017-12-08T06:16:01+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-12-08T06:16:01+00:00", + "exchange_rate_updated_at": "2017-12-08T06:16:01+00:00", + "fee_btc": "0.00087331", + "funded_at": "2017-12-08T06:16:01+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-12-08T06:25:30+00:00", + "reference_code": "L16887828BA1YQC", + "released_at": "2017-12-08T06:30:04+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/367850", + "advertisement_url": "https://localbitcoins.com/api/ad-get/367850/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/16658839/", + "messages_url": "https://localbitcoins.com/api/contact_messages/16658839/", + "release_url": "https://localbitcoins.com/api/contact_release/16658839/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 95, + "last_online": "2019-05-23T11:22:33+00:00", + "name": "", + "trade_count": "500+", + "username": "" + }, + "id": 367850, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1200.00", + "amount_btc": "0.08218750", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 95, + "last_online": "2019-05-23T11:22:33+00:00", + "name": "", + "real_name": "", + "trade_count": "500+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-12-02T00:24:27+00:00", + "contact_id": 16658839, + "created_at": "2017-12-01T23:57:20+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-12-01T23:57:20+00:00", + "exchange_rate_updated_at": "2017-12-01T23:57:20+00:00", + "fee_btc": "0.00082188", + "funded_at": "2017-12-01T23:57:20+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-12-02T00:23:08+00:00", + "reference_code": "L16658839B9X21J", + "released_at": "2017-12-02T00:24:27+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/600091", + "advertisement_url": "https://localbitcoins.com/api/ad-get/600091/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/16269655/", + "messages_url": "https://localbitcoins.com/api/contact_messages/16269655/", + "release_url": "https://localbitcoins.com/api/contact_release/16269655/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-30T21:32:38+00:00", + "name": "", + "trade_count": "3000+", + "username": "" + }, + "id": 600091, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1200.00", + "amount_btc": "0.11403170", + "buyer": { + "company_name": null, + "countrycode_by_ip": "GB", + "countrycode_by_phone_number": "GB", + "feedback_score": 100, + "last_online": "2019-06-30T21:32:38+00:00", + "name": "", + "real_name": "", + "trade_count": "3000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-11-20T06:33:08+00:00", + "contact_id": 16269655, + "created_at": "2017-11-20T06:29:02+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-11-20T06:29:02+00:00", + "exchange_rate_updated_at": "2017-11-20T06:29:02+00:00", + "fee_btc": "0.00114032", + "funded_at": "2017-11-20T06:29:02+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-11-20T06:31:43+00:00", + "reference_code": "L16269655B9OPQV", + "released_at": "2017-11-20T06:33:08+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/557284", + "advertisement_url": "https://localbitcoins.com/api/ad-get/557284/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/16135919/", + "messages_url": "https://localbitcoins.com/api/contact_messages/16135919/", + "release_url": "https://localbitcoins.com/api/contact_release/16135919/" + }, + "data": { + "account_info": "{}", + "advertisement": { + "advertiser": { + "feedback_score": 98, + "last_online": "2019-05-18T23:48:12+00:00", + "name": "", + "trade_count": "3000+", + "username": "" + }, + "id": 557284, + "payment_method": "CASH_DEPOSIT", + "trade_type": "ONLINE_BUY" + }, + "amount": "1000.00", + "amount_btc": "0.10197017", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 98, + "last_online": "2019-05-18T23:48:12+00:00", + "name": "", + "real_name": "", + "trade_count": "3000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-11-16T01:25:10+00:00", + "contact_id": 16135919, + "created_at": "2017-11-15T23:50:30+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-11-15T23:50:30+00:00", + "exchange_rate_updated_at": "2017-11-15T23:50:30+00:00", + "fee_btc": "0.00101970", + "funded_at": "2017-11-15T23:50:30+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-11-16T00:12:48+00:00", + "reference_code": "L16135919B9LUJZ", + "released_at": "2017-11-16T01:25:10+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/556123", + "advertisement_url": "https://localbitcoins.com/api/ad-get/556123/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/15625291/", + "messages_url": "https://localbitcoins.com/api/contact_messages/15625291/", + "release_url": "https://localbitcoins.com/api/contact_release/15625291/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2018-06-25T08:15:40+00:00", + "name": "", + "trade_count": "1000+", + "username": "" + }, + "id": 556123, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1000.00", + "amount_btc": "0.11965859", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2018-06-25T08:15:40+00:00", + "name": "", + "real_name": "", + "trade_count": "1000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-11-01T07:50:21+00:00", + "contact_id": 15625291, + "created_at": "2017-11-01T06:25:20+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-11-01T06:25:20+00:00", + "exchange_rate_updated_at": "2017-11-01T06:25:20+00:00", + "fee_btc": "0.00119659", + "funded_at": "2017-11-01T06:25:20+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-11-01T07:09:10+00:00", + "reference_code": "L15625291B9AWJV", + "released_at": "2017-11-01T07:50:21+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/513602", + "advertisement_url": "https://localbitcoins.com/api/ad-get/513602/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/15326580/", + "messages_url": "https://localbitcoins.com/api/contact_messages/15326580/", + "release_url": "https://localbitcoins.com/api/contact_release/15326580/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-29T17:21:27+00:00", + "name": "", + "trade_count": "3000+", + "username": "" + }, + "id": 513602, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1200.00", + "amount_btc": "0.16343052", + "buyer": { + "company_name": null, + "countrycode_by_ip": "BR", + "countrycode_by_phone_number": "BR", + "feedback_score": 100, + "last_online": "2019-06-29T17:21:27+00:00", + "name": "", + "real_name": "", + "trade_count": "3000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-10-22T05:40:05+00:00", + "contact_id": 15326580, + "created_at": "2017-10-22T04:53:51+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-10-22T04:53:51+00:00", + "exchange_rate_updated_at": "2017-10-22T04:53:51+00:00", + "fee_btc": "0.00163431", + "funded_at": "2017-10-22T04:53:51+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-10-22T05:11:29+00:00", + "reference_code": "L15326580B94I2C", + "released_at": "2017-10-22T05:40:05+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/271961", + "advertisement_url": "https://localbitcoins.com/api/ad-get/271961/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/15165674/", + "messages_url": "https://localbitcoins.com/api/contact_messages/15165674/", + "release_url": "https://localbitcoins.com/api/contact_release/15165674/" + }, + "data": { + "account_info": "{}", + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2018-05-24T00:35:20+00:00", + "name": "", + "trade_count": "10 000+", + "username": "" + }, + "id": 271961, + "payment_method": "SPECIFIC_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1049.16", + "amount_btc": "0.15000000", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": null, + "feedback_score": 100, + "last_online": "2018-05-24T00:35:20+00:00", + "name": "", + "real_name": "", + "trade_count": "10 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-10-17T03:18:50+00:00", + "contact_id": 15165674, + "created_at": "2017-10-17T02:49:59+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-10-17T02:49:59+00:00", + "exchange_rate_updated_at": "2017-10-17T02:49:59+00:00", + "fee_btc": "0.00150000", + "funded_at": "2017-10-17T02:49:59+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-10-17T03:12:46+00:00", + "reference_code": "L15165674B911WQ", + "released_at": "2017-10-17T03:18:50+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/555158", + "advertisement_url": "https://localbitcoins.com/api/ad-get/555158/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/14635637/", + "messages_url": "https://localbitcoins.com/api/contact_messages/14635637/", + "release_url": "https://localbitcoins.com/api/contact_release/14635637/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-05-16T12:33:56+00:00", + "name": "", + "trade_count": "1000+", + "username": "" + }, + "id": 555158, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1039.09", + "amount_btc": "0.20000077", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-05-16T12:33:56+00:00", + "name": "", + "real_name": "", + "trade_count": "1000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-09-29T01:11:57+00:00", + "contact_id": 14635637, + "created_at": "2017-09-29T00:56:22+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-09-29T00:56:22+00:00", + "exchange_rate_updated_at": "2017-09-29T00:56:22+00:00", + "fee_btc": "0.00200001", + "funded_at": "2017-09-29T00:56:22+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-09-29T01:10:58+00:00", + "reference_code": "L14635637B8POXH", + "released_at": "2017-09-29T01:11:57+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/72713", + "advertisement_url": "https://localbitcoins.com/api/ad-get/72713/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/14353081/", + "messages_url": "https://localbitcoins.com/api/contact_messages/14353081/", + "release_url": "https://localbitcoins.com/api/contact_release/14353081/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 98, + "last_online": "2019-02-09T05:54:26+00:00", + "name": "", + "trade_count": "15 000+", + "username": "" + }, + "id": 72713, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1046.01", + "amount_btc": "0.20699993", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 98, + "last_online": "2019-02-09T05:54:26+00:00", + "name": "", + "real_name": "", + "trade_count": "15 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-09-18T23:10:20+00:00", + "contact_id": 14353081, + "created_at": "2017-09-18T21:53:08+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-09-18T21:53:08+00:00", + "exchange_rate_updated_at": "2017-09-18T21:53:08+00:00", + "fee_btc": "0.00207000", + "funded_at": "2017-09-18T21:53:08+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-09-18T22:24:20+00:00", + "reference_code": "L14353081B8JMWP", + "released_at": "2017-09-18T23:10:20+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + }, + { + "actions": { + "advertisement_public_view": "https://localbitcoins.com/ad/243945", + "advertisement_url": "https://localbitcoins.com/api/ad-get/243945/", + "message_post_url": "https://localbitcoins.com/api/contact_message_post/13641301/", + "messages_url": "https://localbitcoins.com/api/contact_messages/13641301/", + "release_url": "https://localbitcoins.com/api/contact_release/13641301/" + }, + "data": { + "account_details": { + "account_number": "", + "bsb": "", + "receiver_name": "", + "reference": "" + }, + "advertisement": { + "advertiser": { + "feedback_score": 100, + "last_online": "2019-06-16T02:46:03+00:00", + "name": "", + "trade_count": "10 000+", + "username": "" + }, + "id": 243945, + "payment_method": "NATIONAL_BANK", + "trade_type": "ONLINE_BUY" + }, + "amount": "1272.60", + "amount_btc": "0.25000098", + "buyer": { + "company_name": null, + "countrycode_by_ip": "AU", + "countrycode_by_phone_number": "AU", + "feedback_score": 100, + "last_online": "2019-06-16T02:46:03+00:00", + "name": "", + "real_name": "", + "trade_count": "10 000+", + "username": "" + }, + "canceled_at": null, + "closed_at": "2017-08-24T05:33:01+00:00", + "contact_id": 13641301, + "created_at": "2017-08-24T05:01:24+00:00", + "currency": "AUD", + "disputed_at": null, + "escrowed_at": "2017-08-24T05:01:24+00:00", + "exchange_rate_updated_at": "2017-08-24T05:01:24+00:00", + "fee_btc": "0.00250001", + "funded_at": "2017-08-24T05:01:24+00:00", + "is_buying": false, + "is_selling": true, + "payment_completed_at": "2017-08-24T05:27:34+00:00", + "reference_code": "L13641301B84DP1", + "released_at": "2017-08-24T05:33:01+00:00", + "seller": { + "feedback_score": 100, + "last_online": "2019-07-01T06:56:40+00:00", + "name": "", + "trade_count": "16", + "username": "" + } + } + } + ] + } + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1561964296416293295" + ], + "Apiauth-Signature": [ + "00C8D42CE7ABC89BB9DD68A839628BC1E482F5A47A9BBC1D5AE12A1601A0434D" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/myself/": { + "GET": [ + { + "data": { + "data": { + "age_text": "4 years, 11 months", + "blocked_count": 0, + "confirmed_trade_count_text": "16", + "created_at": "2014-07-18T21:12:23+00:00", + "feedback_count": 5, + "feedback_score": 100, + "feedbacks_unconfirmed_count": 3, + "has_common_trades": false, + "has_feedback": false, + "identity_verified_at": "2017-12-08T05:59:48+00:00", + "real_name_verifications_rejected": 0, + "real_name_verifications_trusted": 0, + "real_name_verifications_untrusted": 0, + "trade_volume_text": "2-5 BTC", + "trading_partners_count": 12, + "trusted_count": 2, + "url": "", + "username": "" + } + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1561959130359152477" + ], + "Apiauth-Signature": [ + "80D9F6E9EC1AC1BD30F24C7F74B6D4737052C41D14142422C02FD4DF0E81B291" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/wallet-addr/": { + "POST": [ + { + "data": { + "data": { + "address": "3BxxAysZpNkHL2cFLmft8923cGfMa2WdQw", + "message": "OK!" + } + }, + "queryString": "", + "bodyParams": "", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1561957957028764420" + ], + "Apiauth-Signature": [ + "51D4769CC4714D2B459D25A66FF1E72FF46CB8E94B65EA18455BB24AE35C068B" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/api/wallet-send/": { + "POST": [ + { + "data": { + "error": { + "message": "One or more parameters did not validate. Please check the API documentation for usage.", + "errors": { + "amount": "Not enough balance" + }, + "error_code": 19 + } + }, + "queryString": "", + "bodyParams": "address=1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB\u0026amount=-1", + "headers": { + "Apiauth-Key": [ + "" + ], + "Apiauth-Nonce": [ + "1560491240845487036" + ], + "Apiauth-Signature": [ + "8B7AB8E9648102F2EB7EE55F5A75FCEE4722680516D8CDFA9722942641C28ADA" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ] + } + } + ] + }, + "/bitcoinaverage/ticker-all-currencies/": { + "GET": [ + { + "data": { + "COP": { + "avg_12h": "25537196.67", + "volume_btc": "57.30794705", + "avg_24h": "25449596.12", + "avg_1h": "25764777.31", + "rates": { + "last": "25897996.85" + }, + "avg_6h": "25412534.30" + }, + "USD": { + "avg_12h": "8562.48", + "volume_btc": "140.85448370", + "avg_24h": "8586.36", + "avg_1h": "8492.03", + "rates": { + "last": "8214.00" + }, + "avg_6h": "8568.07" + }, + "JPY": { + "avg_12h": "847915.64", + "volume_btc": "0.02230900", + "avg_24h": "847915.64", + "rates": { + "last": "845980.00" + }, + "avg_6h": "845980.00" + }, + "TWD": { + "avg_12h": "281295.08", + "volume_btc": "2.26245037", + "avg_24h": "278202.56", + "avg_1h": "278103.89", + "rates": { + "last": "276672.61" + }, + "avg_6h": "281295.08" + }, + "GHS": { + "avg_12h": "44134.30", + "volume_btc": "1.98283093", + "avg_24h": "43651.70", + "rates": { + "last": "43650.15" + }, + "avg_6h": "44060.04" + }, + "NGN": { + "avg_12h": "2916614.26", + "volume_btc": "72.41011739", + "avg_24h": "2899101.32", + "avg_1h": "2921923.59", + "rates": { + "last": "2923100.01" + }, + "avg_6h": "2916981.81" + }, + "EGP": { + "avg_12h": "151813.87", + "volume_btc": "0.68349646", + "avg_24h": "150018.16", + "rates": { + "last": "209994.20" + } + }, + "IDR": { + "avg_12h": "124164915.73", + "volume_btc": "0.09193647", + "avg_24h": "123191340.72", + "avg_1h": "121643007.78", + "rates": { + "last": "121643007.78" + }, + "avg_6h": "124164915.73" + }, + "MVR": { + "volume_btc": "0.02007789", + "avg_24h": "149418.09", + "rates": { + "last": "149418.09" + } + }, + "SZL": { + "volume_btc": "0.01069374", + "avg_24h": "140268.98", + "rates": { + "last": "150000.15" + } + }, + "ISK": { + "volume_btc": "0.20086962", + "avg_24h": "995670.72", + "rates": { + "last": "995670.72" + } + }, + "CRC": { + "avg_12h": "4757140.30", + "volume_btc": "0.41437268", + "avg_24h": "4811297.94", + "rates": { + "last": "4955842.50" + }, + "avg_6h": "4968659.22" + }, + "XRP": { + "avg_12h": "21090.70", + "volume_btc": "0.25386409", + "avg_24h": "21023.45", + "rates": { + "last": "21124.01" + }, + "avg_6h": "21184.65" + }, + "AED": { + "volume_btc": "2.18671211", + "avg_24h": "29974.93", + "rates": { + "last": "29777.40" + } + }, + "GBP": { + "avg_12h": "6517.46", + "volume_btc": "67.51203997", + "avg_24h": "6430.57", + "avg_1h": "6545.48", + "rates": { + "last": "6159.46" + }, + "avg_6h": "6619.35" + }, + "ETH": { + "avg_12h": "31.98", + "volume_btc": "0.30525867", + "avg_24h": "31.19", + "rates": { + "last": "31.93" + } + }, + "BHD": { + "volume_btc": "0.02748994", + "avg_24h": "3455.81", + "rates": { + "last": "3455.81" + } + }, + "LKR": { + "avg_12h": "1606872.20", + "volume_btc": "0.78053341", + "avg_24h": "1608097.45", + "avg_1h": "1804552.72", + "rates": { + "last": "1804552.72" + }, + "avg_6h": "1606872.20" + }, + "BOB": { + "avg_12h": "59376.89", + "volume_btc": "0.24588149", + "avg_24h": "55902.99", + "rates": { + "last": "59189.11" + }, + "avg_6h": "59189.11" + }, + "RSD": { + "avg_12h": "793701.45", + "volume_btc": "0.01338060", + "avg_24h": "869090.33", + "rates": { + "last": "793701.45" + } + }, + "VES": { + "avg_12h": "59124424.76", + "volume_btc": "138.63954251", + "avg_24h": "58500831.77", + "rates": { + "last": "57319869.26" + }, + "avg_6h": "59157392.40" + }, + "PKR": { + "avg_12h": "1260400.03", + "volume_btc": "4.67788383", + "avg_24h": "1257798.07", + "avg_1h": "1281991.62", + "rates": { + "last": "1278624.55" + }, + "avg_6h": "1273742.81" + }, + "LBP": { + "avg_12h": "13596207.95", + "volume_btc": "0.03653960", + "avg_24h": "13596207.95", + "rates": { + "last": "13596207.95" + } + }, + "TZS": { + "avg_12h": "19530777.00", + "volume_btc": "1.06554909", + "avg_24h": "19227157.06", + "avg_1h": "20357598.44", + "rates": { + "last": "20405664.61" + }, + "avg_6h": "19407156.98" + }, + "VND": { + "avg_12h": "201629484.83", + "volume_btc": "0.26768301", + "avg_24h": "201557439.41", + "rates": { + "last": "206358652.99" + }, + "avg_6h": "201629484.83" + }, + "GEL": { + "avg_12h": "20897.64", + "volume_btc": "0.05139655", + "avg_24h": "22491.78", + "rates": { + "last": "20897.64" + }, + "avg_6h": "20897.64" + }, + "LTC": { + "avg_12h": "64.20", + "volume_btc": "0.21374261", + "avg_24h": "63.91", + "rates": { + "last": "61.25" + } + }, + "RON": { + "avg_12h": "32314.31", + "volume_btc": "3.41826662", + "avg_24h": "32447.47", + "rates": { + "last": "30515.15" + }, + "avg_6h": "32024.17" + }, + "HUF": { + "volume_btc": "0.10050384", + "avg_24h": "2437717.80", + "rates": { + "last": "2819598.69" + } + }, + "MYR": { + "avg_12h": "33996.15", + "volume_btc": "8.81478056", + "avg_24h": "33956.83", + "avg_1h": "34476.72", + "rates": { + "last": "33858.00" + }, + "avg_6h": "33755.81" + }, + "GTQ": { + "avg_12h": "68974.61", + "volume_btc": "0.00959425", + "avg_24h": "69312.35", + "rates": { + "last": "70153.06" + } + }, + "ZMW": { + "avg_12h": "123419.20", + "volume_btc": "0.24337546", + "avg_24h": "124378.68", + "rates": { + "last": "123078.65" + }, + "avg_6h": "123419.20" + }, + "MUR": { + "volume_btc": "0.01487938", + "avg_24h": "349476.93", + "rates": { + "last": "352219.22" + } + }, + "UAH": { + "avg_12h": "211829.83", + "volume_btc": "16.66117945", + "avg_24h": "209677.23", + "avg_1h": "212630.36", + "rates": { + "last": "220388.34" + }, + "avg_6h": "208239.32" + }, + "UGX": { + "avg_12h": "29686183.01", + "volume_btc": "0.37545684", + "avg_24h": "29711353.80", + "avg_1h": "30835713.02", + "rates": { + "last": "30829092.51" + }, + "avg_6h": "29620104.90" + }, + "XOF": { + "avg_12h": "4861132.03", + "volume_btc": "0.09694489", + "avg_24h": "4772973.28", + "rates": { + "last": "4275733.70" + } + }, + "QAR": { + "volume_btc": "0.27356116", + "avg_24h": "33256.77", + "rates": { + "last": "33732.04" + } + }, + "SAR": { + "avg_12h": "32647.94", + "volume_btc": "5.37359276", + "avg_24h": "32537.28", + "rates": { + "last": "33848.64" + }, + "avg_6h": "32663.09" + }, + "DKK": { + "volume_btc": "0.22476957", + "avg_24h": "68590.24", + "rates": { + "last": "70473.55" + } + }, + "CAD": { + "avg_12h": "11078.67", + "volume_btc": "8.94868269", + "avg_24h": "11031.59", + "avg_1h": "10446.42", + "rates": { + "last": "10446.42" + }, + "avg_6h": "10959.13" + }, + "SEK": { + "avg_12h": "80823.78", + "volume_btc": "8.90365116", + "avg_24h": "81244.54", + "avg_1h": "83288.12", + "rates": { + "last": "83343.47" + }, + "avg_6h": "81469.29" + }, + "SGD": { + "avg_12h": "11503.01", + "volume_btc": "5.99224704", + "avg_24h": "11330.54", + "avg_1h": "11532.92", + "rates": { + "last": "13134.52" + }, + "avg_6h": "11360.59" + }, + "HKD": { + "avg_12h": "67815.30", + "volume_btc": "19.69548211", + "avg_24h": "66039.28", + "rates": { + "last": "69484.34" + }, + "avg_6h": "67853.45" + }, + "TTD": { + "avg_12h": "58619.24", + "volume_btc": "0.16048185", + "avg_24h": "73729.08", + "rates": { + "last": "58619.24" + } + }, + "BWP": { + "volume_btc": "0.04654223", + "avg_24h": "107429.32", + "rates": { + "last": "107429.32" + } + }, + "OMR": { + "volume_btc": "0.01492234", + "avg_24h": "3350.68", + "rates": { + "last": "3350.68" + } + }, + "AUD": { + "avg_12h": "12277.45", + "volume_btc": "13.09770688", + "avg_24h": "12128.46", + "avg_1h": "12108.70", + "rates": { + "last": "11641.01" + }, + "avg_6h": "12253.63" + }, + "CHF": { + "avg_12h": "7818.23", + "volume_btc": "0.72358711", + "avg_24h": "8291.80", + "avg_1h": "7800.76", + "rates": { + "last": "7800.76" + }, + "avg_6h": "7800.76" + }, + "IRR": { + "avg_12h": "1114656387.13", + "volume_btc": "1.80755109", + "avg_24h": "1086115901.27", + "rates": { + "last": "1093155904.81" + }, + "avg_6h": "1093155904.81" + }, + "JOD": { + "avg_12h": "6408.70", + "volume_btc": "0.01560379", + "avg_24h": "6408.70", + "rates": { + "last": "6408.70" + } + }, + "KRW": { + "avg_12h": "10521366.18", + "volume_btc": "0.66149015", + "avg_24h": "10241401.49", + "avg_1h": "10484782.42", + "rates": { + "last": "10448609.25" + }, + "avg_6h": "10511052.91" + }, + "CNY": { + "avg_12h": "57135.62", + "volume_btc": "76.07019214", + "avg_24h": "57038.10", + "avg_1h": "57867.84", + "rates": { + "last": "58069.13" + }, + "avg_6h": "57003.97" + }, + "BYN": { + "avg_12h": "17747.08", + "volume_btc": "5.35404022", + "avg_24h": "17605.81", + "avg_1h": "17717.32", + "rates": { + "last": "17517.74" + }, + "avg_6h": "17886.64" + }, + "BDT": { + "volume_btc": "0.05384503", + "avg_24h": "759587.28", + "rates": { + "last": "761916.37" + } + }, + "PAB": { + "avg_12h": "8325.37", + "volume_btc": "10.00787138", + "avg_24h": "8290.60", + "rates": { + "last": "9050.00" + }, + "avg_6h": "8301.55" + }, + "HRK": { + "avg_12h": "49999.80", + "volume_btc": "0.15555978", + "avg_24h": "55059.22", + "rates": { + "last": "49999.80" + } + }, + "NZD": { + "avg_12h": "12810.86", + "volume_btc": "2.47636431", + "avg_24h": "12794.70", + "avg_1h": "13146.12", + "rates": { + "last": "13126.00" + }, + "avg_6h": "12761.27" + }, + "UYU": { + "avg_12h": "322604.75", + "volume_btc": "0.12983623", + "avg_24h": "318832.42", + "rates": { + "last": "324518.87" + } + }, + "DOP": { + "avg_12h": "420879.60", + "volume_btc": "3.19533678", + "avg_24h": "417549.81", + "rates": { + "last": "425721.21" + }, + "avg_6h": "426208.10" + }, + "CLP": { + "avg_12h": "5819221.14", + "volume_btc": "4.28572068", + "avg_24h": "5814758.27", + "rates": { + "last": "6119335.79" + }, + "avg_6h": "5846384.78" + }, + "THB": { + "avg_12h": "259567.58", + "volume_btc": "7.37083947", + "avg_24h": "258321.79", + "avg_1h": "262775.63", + "rates": { + "last": "266794.22" + }, + "avg_6h": "260131.35" + }, + "XAF": { + "volume_btc": "0.02868090", + "avg_24h": "4826286.14", + "rates": { + "last": "5611300.00" + } + }, + "EUR": { + "avg_12h": "7412.86", + "volume_btc": "76.17180176", + "avg_24h": "7287.46", + "avg_1h": "7382.68", + "rates": { + "last": "7397.11" + }, + "avg_6h": "7456.86" + }, + "TRY": { + "avg_12h": "47907.50", + "volume_btc": "1.02053262", + "avg_24h": "47153.18", + "rates": { + "last": "62899.91" + } + }, + "ARS": { + "avg_12h": "382366.90", + "volume_btc": "4.92769759", + "avg_24h": "380152.45", + "rates": { + "last": "365500.00" + }, + "avg_6h": "384722.46" + }, + "ILS": { + "avg_12h": "31194.83", + "volume_btc": "0.10562972", + "avg_24h": "30071.17", + "rates": { + "last": "31194.83" + } + }, + "RWF": { + "avg_12h": "7673007.07", + "volume_btc": "0.08569526", + "avg_24h": "7510809.81", + "rates": { + "last": "7748214.04" + } + }, + "KZT": { + "avg_12h": "3301025.25", + "volume_btc": "2.59196053", + "avg_24h": "3297043.02", + "avg_1h": "3356280.87", + "rates": { + "last": "3384713.28" + }, + "avg_6h": "3350564.14" + }, + "NOK": { + "avg_12h": "79420.45", + "volume_btc": "4.09546929", + "avg_24h": "78398.04", + "avg_1h": "79396.38", + "rates": { + "last": "79396.38" + }, + "avg_6h": "79417.99" + }, + "RUB": { + "avg_12h": "548638.43", + "volume_btc": "302.23941176", + "avg_24h": "543679.83", + "avg_1h": "543842.57", + "rates": { + "last": "563329.00" + }, + "avg_6h": "547455.94" + }, + "KES": { + "avg_12h": "826764.39", + "volume_btc": "8.09195505", + "avg_24h": "818332.89", + "avg_1h": "820436.51", + "rates": { + "last": "810682.85" + }, + "avg_6h": "826962.52" + }, + "KWD": { + "volume_btc": "0.39533718", + "avg_24h": "2605.37", + "rates": { + "last": "2737.64" + } + }, + "PEN": { + "avg_12h": "27718.82", + "volume_btc": "17.70290717", + "avg_24h": "27664.96", + "avg_1h": "28018.35", + "rates": { + "last": "27035.01" + }, + "avg_6h": "27791.63" + }, + "INR": { + "avg_12h": "585205.33", + "volume_btc": "34.16374139", + "avg_24h": "584777.59", + "avg_1h": "585045.89", + "rates": { + "last": "575000.00" + }, + "avg_6h": "585831.47" + }, + "MXN": { + "avg_12h": "157593.02", + "volume_btc": "9.59066308", + "avg_24h": "160108.13", + "avg_1h": "167000.11", + "rates": { + "last": "167000.11" + }, + "avg_6h": "162571.49" + }, + "CZK": { + "volume_btc": "0.41412138", + "avg_24h": "201936.54", + "rates": { + "last": "174537.75" + } + }, + "BRL": { + "avg_12h": "32900.90", + "volume_btc": "8.97094556", + "avg_24h": "32589.27", + "avg_1h": "33176.14", + "rates": { + "last": "33176.14" + }, + "avg_6h": "33886.68" + }, + "MAD": { + "avg_12h": "76479.19", + "volume_btc": "1.14882279", + "avg_24h": "80824.78", + "avg_1h": "74500.77", + "rates": { + "last": "74500.77" + }, + "avg_6h": "74500.77" + }, + "PLN": { + "avg_12h": "29765.85", + "volume_btc": "2.60538097", + "avg_24h": "29825.91", + "avg_1h": "33232.43", + "rates": { + "last": "28826.25" + }, + "avg_6h": "33232.43" + }, + "PHP": { + "avg_12h": "435189.26", + "volume_btc": "7.04342165", + "avg_24h": "433672.76", + "avg_1h": "421470.00", + "rates": { + "last": "508063.17" + }, + "avg_6h": "434893.31" + }, + "ZAR": { + "avg_12h": "126001.90", + "volume_btc": "20.14267295", + "avg_24h": "125704.49", + "avg_1h": "128216.45", + "rates": { + "last": "124066.59" + }, + "avg_6h": "127185.17" + }, + "JMD": { + "volume_btc": "0.00976113", + "avg_24h": "1690378.06", + "rates": { + "last": "1690378.06" + } + } + }, + "queryString": "", + "bodyParams": "\u003cnil\u003e", + "headers": {} + } + ] + } + } +} \ No newline at end of file diff --git a/testdata/http_mock/poloniex/poloniex.json b/testdata/http_mock/poloniex/poloniex.json new file mode 100644 index 00000000..10f443d0 --- /dev/null +++ b/testdata/http_mock/poloniex/poloniex.json @@ -0,0 +1,9031 @@ +{ + "routes": { + "/public": { + "GET": [ + { + "data": { + "offers": [ + { + "rate": "0.00001000", + "amount": "0.01106802", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00005000", + "amount": "0.01421371", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051121", + "amount": "0.01194792", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051180", + "amount": "0.20407105", + "rangeMin": 10, + "rangeMax": 10 + }, + { + "rate": "0.00051200", + "amount": "1.39196698", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051221", + "amount": "0.01251915", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051259", + "amount": "0.13936621", + "rangeMin": 2, + "rangeMax": 7 + }, + { + "rate": "0.00051261", + "amount": "0.08577806", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051300", + "amount": "30.62159808", + "rangeMin": 2, + "rangeMax": 3 + }, + { + "rate": "0.00051307", + "amount": "0.11240023", + "rangeMin": 2, + "rangeMax": 15 + }, + { + "rate": "0.00051321", + "amount": "0.04123649", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051359", + "amount": "0.01000000", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051361", + "amount": "0.01222452", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051383", + "amount": "0.04123649", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051400", + "amount": "11.86021806", + "rangeMin": 2, + "rangeMax": 60 + }, + { + "rate": "0.00051407", + "amount": "0.01156721", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051445", + "amount": "0.04123649", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051461", + "amount": "0.01000000", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051472", + "amount": "0.01139563", + "rangeMin": 7, + "rangeMax": 7 + }, + { + "rate": "0.00051490", + "amount": "0.17328314", + "rangeMin": 30, + "rangeMax": 30 + }, + { + "rate": "0.00051500", + "amount": "1.58067150", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051507", + "amount": "0.31612314", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051509", + "amount": "0.02767539", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051599", + "amount": "0.02591969", + "rangeMin": 7, + "rangeMax": 7 + }, + { + "rate": "0.00051600", + "amount": "7.56304844", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051607", + "amount": "0.31683506", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051686", + "amount": "0.01139563", + "rangeMin": 7, + "rangeMax": 7 + }, + { + "rate": "0.00051700", + "amount": "1.68550042", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051707", + "amount": "0.00574744", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051709", + "amount": "0.02867412", + "rangeMin": 60, + "rangeMax": 60 + }, + { + "rate": "0.00051729", + "amount": "0.01219097", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051730", + "amount": "0.01264114", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051799", + "amount": "0.01043269", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051800", + "amount": "4.58136320", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051829", + "amount": "0.06309791", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051859", + "amount": "0.01079156", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051899", + "amount": "0.24673262", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00051900", + "amount": "1.75619162", + "rangeMin": 7, + "rangeMax": 60 + }, + { + "rate": "0.00052000", + "amount": "0.01905141", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00052100", + "amount": "0.05404361", + "rangeMin": 2, + "rangeMax": 30 + }, + { + "rate": "0.00052189", + "amount": "0.08045529", + "rangeMin": 60, + "rangeMax": 60 + }, + { + "rate": "0.00052200", + "amount": "0.30165715", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00052299", + "amount": "0.00521692", + "rangeMin": 19, + "rangeMax": 19 + }, + { + "rate": "0.00052300", + "amount": "1.34778650", + "rangeMin": 2, + "rangeMax": 60 + }, + { + "rate": "0.00052389", + "amount": "0.07713974", + "rangeMin": 10, + "rangeMax": 20 + }, + { + "rate": "0.00052400", + "amount": "1.28093787", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00052500", + "amount": "0.38279138", + "rangeMin": 2, + "rangeMax": 60 + }, + { + "rate": "0.00052550", + "amount": "0.01065640", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00052600", + "amount": "3.20808196", + "rangeMin": 2, + "rangeMax": 2 + }, + { + "rate": "0.00052800", + "amount": "3.21858925", + "rangeMin": 2, + "rangeMax": 2 + } + ], + "demands": [ + { + "rate": "0.00000100", + "amount": "0.07730000", + "rangeMin": 2, + "rangeMax": 2 + } + ] + }, + "queryString": "command=returnLoanOrders\u0026currency=BTC", + "bodyParams": "\u003cnil\u003e", + "headers": { + "Key": [ + "" + ] + } + }, + { + "data": { + "1CR": { + "id": 1, + "name": "1CRedit", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "ABY": { + "id": 2, + "name": "ArtByte", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "AC": { + "id": 3, + "name": "AsiaCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "ACH": { + "id": 4, + "name": "Altcoin Herald", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "ADN": { + "id": 5, + "name": "Aiden", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "AEON": { + "id": 6, + "name": "AEON Coin", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": "WmrzAb4yy6i4otzaTZznsmaeBn8UKoBqS14XdW2DDzXr3DQnwz4o1XPJAJuo7QTqSoD9WVrdEQfn15udzhsJWmNR1jAEETQ2N", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "AERO": { + "id": 7, + "name": "Aerocoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "AIR": { + "id": 8, + "name": "AIRcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "AMP": { + "id": 275, + "name": "Synereo AMP", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "5.00000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "APH": { + "id": 9, + "name": "AphroditeCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "ARCH": { + "id": 258, + "name": "ARCHcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "ARDR": { + "id": 285, + "name": "Ardor", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "2.00000000", + "minConf": 20, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "ATOM": { + "id": 313, + "name": "Cosmos", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00500000", + "minConf": 0, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "AUR": { + "id": 10, + "name": "Auroracoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "AXIS": { + "id": 11, + "name": "Axis", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BALLS": { + "id": 12, + "name": "Snowballs", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "6.40000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BANK": { + "id": 13, + "name": "BankCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BAT": { + "id": 302, + "name": "Basic Attention Token", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "9.00000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "BBL": { + "id": 14, + "name": "BitBlock", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BBR": { + "id": 15, + "name": "Boolberry", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.00500000", + "minConf": 10000, + "depositAddress": "1D9hJ1nEjwuhxZMk6fupoTjKLtS2KzkfCQ7kF25k5B6Sc4UJjt9FrvDNYomVd4ZVHv36FskVRJGZa1JZAnZ35GiuAHf7gBy", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BCC": { + "id": 16, + "name": "BTCtalkcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BCH": { + "id": 292, + "name": "Bitcoin Cash (FROZEN)", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00010000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 1, + "isGeofenced": 0 + }, + "BCHABC": { + "id": 308, + "name": "Bitcoin Cash", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00100000", + "minConf": 11, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "BCHSV": { + "id": 309, + "name": "Bitcoin SV", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00100000", + "minConf": 36, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "BCN": { + "id": 17, + "name": "Bytecoin", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "1.00000000", + "minConf": 1000, + "depositAddress": "25cZNQYVAi3issDCoa6fWA2Aogd4FgPhYdpX3p8KLfhKC6sN8s6Q9WpcW4778TPwcUS5jEM25JrQvjD3XjsvXuNHSWhYUsu", + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "BCY": { + "id": 269, + "name": "BitCrystals", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "4.00000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BDC": { + "id": 18, + "name": "Black Dragon Coin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BDG": { + "id": 19, + "name": "Badgercoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BELA": { + "id": 20, + "name": "Bela Legacy", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BITCNY": { + "id": 273, + "name": "BitCNY", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "1.00000000", + "minConf": 10000, + "depositAddress": "poloniexwallet", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BITS": { + "id": 21, + "name": "Bitstar", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BITUSD": { + "id": 272, + "name": "BitUSD", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.15000000", + "minConf": 10000, + "depositAddress": "poloniexwallet", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BLK": { + "id": 22, + "name": "BlackCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BLOCK": { + "id": 23, + "name": "Blocknet", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 100000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 1, + "isGeofenced": 0 + }, + "BLU": { + "id": 24, + "name": "BlueCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BNS": { + "id": 25, + "name": "BonusCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BNT": { + "id": 305, + "name": "Bancor", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.10000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "BONES": { + "id": 26, + "name": "Bones", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BOST": { + "id": 27, + "name": "BoostCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BTC": { + "id": 28, + "name": "Bitcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00050000", + "minConf": 1, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "BTCD": { + "id": 29, + "name": "BitcoinDark", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BTCS": { + "id": 30, + "name": "Bitcoin-sCrypt", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BTM": { + "id": 31, + "name": "Bitmark", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BTS": { + "id": 32, + "name": "BitShares", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "5.00000000", + "minConf": 50, + "depositAddress": "poloniexwallet", + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "BURN": { + "id": 33, + "name": "BurnerCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "BURST": { + "id": 34, + "name": "Burst", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.00000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 0, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "C2": { + "id": 35, + "name": "Coin2.0", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CACH": { + "id": 36, + "name": "CACHeCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CAI": { + "id": 37, + "name": "CaiShen", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CC": { + "id": 38, + "name": "Colbert Coin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CCN": { + "id": 39, + "name": "Cannacoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CGA": { + "id": 40, + "name": "Cryptographic Anomaly", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00100000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CHA": { + "id": 41, + "name": "Chancecoin", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CINNI": { + "id": 42, + "name": "CinniCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CLAM": { + "id": 43, + "name": "CLAMS", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00100000", + "minConf": 6, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "CNL": { + "id": 44, + "name": "ConcealCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CNMT": { + "id": 45, + "name": "Coinomat1", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CNOTE": { + "id": 46, + "name": "C-Note", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "COMM": { + "id": 47, + "name": "CommunityCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CON": { + "id": 48, + "name": "Coino", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.10000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CORG": { + "id": 49, + "name": "CorgiCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CRYPT": { + "id": 50, + "name": "CryptCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CURE": { + "id": 51, + "name": "Curecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "CVC": { + "id": 294, + "name": "Civic", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.00000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "CYC": { + "id": 52, + "name": "Conspiracy Coin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "DAO": { + "id": 279, + "name": "The DAO", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "DASH": { + "id": 60, + "name": "Dash", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 50, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "DCR": { + "id": 277, + "name": "Decred", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.10000000", + "minConf": 4, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "DGB": { + "id": 53, + "name": "DigiByte", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.10000000", + "minConf": 40, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "DICE": { + "id": 54, + "name": "NeoDICE", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "DIEM": { + "id": 55, + "name": "Diem", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "DIME": { + "id": 56, + "name": "Dimecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "DIS": { + "id": 57, + "name": "DistroCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "DNS": { + "id": 58, + "name": "BitShares DNS", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.50000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "DOGE": { + "id": 59, + "name": "Dogecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "100.00000000", + "minConf": 6, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "DRKC": { + "id": 61, + "name": "DarkCash", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "DRM": { + "id": 62, + "name": "Dreamcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "DSH": { + "id": 63, + "name": "Dashcoin", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "1.00000000", + "minConf": 10000, + "depositAddress": "D8PDwmpq4KMhTy6u8RvWt5PLTMwEayGN3jeYoS3pkJQJMULNt2CyYKXG7KhqSahe9DPppRKqzkWGoMjf8C4L2mzFAgsJZXs", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "DVK": { + "id": 64, + "name": "DvoraKoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "EAC": { + "id": 65, + "name": "EarthCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "EBT": { + "id": 66, + "name": "EBTcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "ECC": { + "id": 67, + "name": "ECCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "EFL": { + "id": 68, + "name": "Electronic Gulden", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "EMC2": { + "id": 69, + "name": "Einsteinium", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "EMO": { + "id": 70, + "name": "EmotiCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "ENC": { + "id": 71, + "name": "Entropycoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "EOS": { + "id": 298, + "name": "EOS", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.00000000", + "minConf": 600, + "depositAddress": "poloniexeos1", + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "ETC": { + "id": 283, + "name": "Ethereum Classic", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 600, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "ETH": { + "id": 267, + "name": "Ethereum", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "eTOK": { + "id": 72, + "name": "eToken", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00100000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "EXE": { + "id": 73, + "name": "Execoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "EXP": { + "id": 270, + "name": "Expanse", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 8000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FAC": { + "id": 74, + "name": "Faircoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FCN": { + "id": 75, + "name": "Fantomcoin", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": "6mkbrPdSAjeaQKCCx8Nus1JiKb4HdeTDXC4bEWunDzTMXMPESd7aFNYPAG1U4MUpnhHfJfzVyTqnRFRW2REKN6efBVRMoSY", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FCT": { + "id": 271, + "name": "Factom", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 3, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "FIBRE": { + "id": 76, + "name": "Fibrecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FLAP": { + "id": 77, + "name": "FlappyCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FLDC": { + "id": 78, + "name": "FoldingCoin", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "750.00000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FLO": { + "id": 254, + "name": "Florincoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FLT": { + "id": 79, + "name": "FlutterCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FOAM": { + "id": 307, + "name": "Foam", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.00000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "FOX": { + "id": 80, + "name": "FoxCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FRAC": { + "id": 81, + "name": "Fractalcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FRK": { + "id": 82, + "name": "Franko", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FRQ": { + "id": 83, + "name": "FairQuark", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FVZ": { + "id": 84, + "name": "FVZCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FZ": { + "id": 85, + "name": "Frozen", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "FZN": { + "id": 86, + "name": "Fuzon", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GAME": { + "id": 93, + "name": "GameCredits", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 80, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "GAP": { + "id": 87, + "name": "Gapcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GAS": { + "id": 296, + "name": "Gas", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 18, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "GDN": { + "id": 88, + "name": "Global Denomination", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GEMZ": { + "id": 89, + "name": "GetGems", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "500.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GEO": { + "id": 90, + "name": "GeoCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GIAR": { + "id": 91, + "name": "Giarcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GLB": { + "id": 92, + "name": "Globe", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GML": { + "id": 94, + "name": "GameleagueCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 1, + "isGeofenced": 0 + }, + "GNO": { + "id": 291, + "name": "Gnosis", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.01500000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GNS": { + "id": 95, + "name": "GenesisCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GNT": { + "id": 290, + "name": "Golem", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.00000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "GOLD": { + "id": 96, + "name": "GoldEagles", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GPC": { + "id": 97, + "name": "GROUPCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GPUC": { + "id": 98, + "name": "GPU Coin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GRC": { + "id": 261, + "name": "Gridcoin Research", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GRCX": { + "id": 99, + "name": "Gridcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GRIN": { + "id": 314, + "name": "Grin", + "humanType": "MimbleWimble", + "currencyType": "mimblewimble", + "txFee": "0.10000000", + "minConf": 15, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "GRS": { + "id": 100, + "name": "Groestlcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 100000, + "depositAddress": null, + "disabled": 0, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "GUE": { + "id": 101, + "name": "Guerillacoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "H2O": { + "id": 102, + "name": "H2O Coin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "HIRO": { + "id": 103, + "name": "Hirocoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "HOT": { + "id": 104, + "name": "Hotcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "HUC": { + "id": 105, + "name": "Huntercoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 0, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "HUGE": { + "id": 260, + "name": "BIGcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 1, + "isGeofenced": 0 + }, + "HVC": { + "id": 106, + "name": "Heavycoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "HYP": { + "id": 107, + "name": "HyperStake", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "HZ": { + "id": 108, + "name": "Horizon", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "IFC": { + "id": 109, + "name": "Infinitecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "2000.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "INDEX": { + "id": 265, + "name": "CoinoIndex", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "IOC": { + "id": 263, + "name": "IO Digital Currency", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "ITC": { + "id": 110, + "name": "Information Coin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "IXC": { + "id": 111, + "name": "iXcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "JLH": { + "id": 112, + "name": "jl777hodl", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "JPC": { + "id": 113, + "name": "JackpotCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "JUG": { + "id": 114, + "name": "JuggaloCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "KDC": { + "id": 115, + "name": "KlondikeCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "KEY": { + "id": 116, + "name": "KeyCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "KNC": { + "id": 301, + "name": "Kyber", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.50000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "LBC": { + "id": 280, + "name": "LBRY Credits", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.05000000", + "minConf": 11, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "LC": { + "id": 117, + "name": "Limecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "LCL": { + "id": 118, + "name": "Limecoin Lite", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "LEAF": { + "id": 119, + "name": "Leafcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.10000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "LGC": { + "id": 120, + "name": "Logicoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "LOL": { + "id": 121, + "name": "LeagueCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "LOOM": { + "id": 303, + "name": "LOOM Network", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "19.00000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "LOVE": { + "id": 122, + "name": "LOVEcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "LPT": { + "id": 312, + "name": "Livepeer", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.05000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "LQD": { + "id": 123, + "name": "LIQUID", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "LSK": { + "id": 278, + "name": "Lisk", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.10000000", + "minConf": 303, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "LTBC": { + "id": 124, + "name": "LTBCoin", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "15000.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "LTC": { + "id": 125, + "name": "Litecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00100000", + "minConf": 4, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "LTCX": { + "id": 126, + "name": "LiteCoinX", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MAID": { + "id": 127, + "name": "MaidSafeCoin", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "80.00000000", + "minConf": 1, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "MANA": { + "id": 306, + "name": "Decentraland", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "22.00000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "MAST": { + "id": 128, + "name": "MastiffCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MAX": { + "id": 129, + "name": "MaxCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MCN": { + "id": 130, + "name": "Moneta Verde", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "1.00000000", + "minConf": 10000, + "depositAddress": "VdttvavdPJNGD4NzPQf8Hn4tWUGyy9mQ58ScwXxKbx9ve4Kp8xXZ1kD1KmXUMp5qtX8GmRgMk16aNVBhENEHNQAb3488Ha7Vv", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MEC": { + "id": 131, + "name": "Megacoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "METH": { + "id": 132, + "name": "CryptoMETH", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MIL": { + "id": 133, + "name": "Millennium Coin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MIN": { + "id": 134, + "name": "Minerals", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MINT": { + "id": 135, + "name": "Mintcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.10000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 0, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MMC": { + "id": 136, + "name": "MemoryCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MMNXT": { + "id": 137, + "name": "MMNXT", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MMXIV": { + "id": 138, + "name": "Maieuticoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00100000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MNTA": { + "id": 139, + "name": "Moneta", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MON": { + "id": 140, + "name": "Monocle", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MRC": { + "id": 141, + "name": "microCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MRS": { + "id": 142, + "name": "Marscoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MTS": { + "id": 144, + "name": "Metiscoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MUN": { + "id": 145, + "name": "Muniti", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MYR": { + "id": 146, + "name": "Myriadcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 0, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "MZC": { + "id": 147, + "name": "MazaCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "N5X": { + "id": 148, + "name": "N5coin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NAS": { + "id": 149, + "name": "NAS", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NAUT": { + "id": 150, + "name": "Nautiluscoin", + "humanType": "Sweep to Main Account", + "currencyType": "address-payment-id", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NAV": { + "id": 151, + "name": "NAVCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 480, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "NBT": { + "id": 152, + "name": "NuBits", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NEOS": { + "id": 153, + "name": "Neoscoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00010000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NL": { + "id": 154, + "name": "Nanolite", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NMC": { + "id": 155, + "name": "Namecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NMR": { + "id": 310, + "name": "Numeraire", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.05000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "NOBL": { + "id": 156, + "name": "NobleCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NOTE": { + "id": 157, + "name": "DNotes", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NOXT": { + "id": 158, + "name": "NobleNXT", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NRS": { + "id": 159, + "name": "NoirShares", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NSR": { + "id": 160, + "name": "NuShares", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "2.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NTX": { + "id": 161, + "name": "NTX", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NXC": { + "id": 288, + "name": "Nexium", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "10.00000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "NXT": { + "id": 162, + "name": "NXT", + "humanType": "Sweep to Main Account", + "currencyType": "address-payment-id", + "txFee": "1.00000000", + "minConf": 24, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "NXTI": { + "id": 163, + "name": "NXTInspect", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "OMG": { + "id": 295, + "name": "OmiseGO", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.30000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "OMNI": { + "id": 143, + "name": "Omni", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "4.00000000", + "minConf": 1, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "OPAL": { + "id": 164, + "name": "Opal", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "PAND": { + "id": 165, + "name": "PandaCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "PASC": { + "id": 289, + "name": "PascalCoin", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.01000000", + "minConf": 300, + "depositAddress": "86646-64", + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "PAWN": { + "id": 166, + "name": "Pawncoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "PIGGY": { + "id": 167, + "name": "New Piggycoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "PINK": { + "id": 168, + "name": "Pinkcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "PLX": { + "id": 169, + "name": "ParallaxCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "PMC": { + "id": 170, + "name": "Premine", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "POLY": { + "id": 311, + "name": "Polymath", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.25000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "POT": { + "id": 171, + "name": "PotCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "PPC": { + "id": 172, + "name": "Peercoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 0, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "PRC": { + "id": 173, + "name": "ProsperCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "PRT": { + "id": 174, + "name": "Particle", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "PTS": { + "id": 175, + "name": "BitShares PTS", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "25.00000000", + "minConf": 10000, + "depositAddress": "poloniexwallet", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "Q2C": { + "id": 176, + "name": "QubitCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "QBK": { + "id": 177, + "name": "Qibuck", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "QCN": { + "id": 178, + "name": "QuazarCoin", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": "1VQpANF1pcKHPRAsZpeyG4jLDd1kbPn32YMeXkr9n8jNFvf8aaJdecB3FyAvo7X1DWJDQt3nii9eUTP5kJSfRpL5AwT72FM", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "QORA": { + "id": 179, + "name": "Qora", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "QTL": { + "id": 180, + "name": "Quatloo", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 0, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "QTUM": { + "id": 304, + "name": "Qtum", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 6, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "RADS": { + "id": 274, + "name": "Radium", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "RBY": { + "id": 181, + "name": "Rubycoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "RDD": { + "id": 182, + "name": "Reddcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 0, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "REP": { + "id": 284, + "name": "Augur", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.10000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "RIC": { + "id": 183, + "name": "Riecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "RZR": { + "id": 184, + "name": "Razor", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SBD": { + "id": 282, + "name": "Steem Dollars", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": "poloniex", + "disabled": 0, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SC": { + "id": 268, + "name": "Siacoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "10.00000000", + "minConf": 6, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "SDC": { + "id": 185, + "name": "Shadow", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SHIBE": { + "id": 186, + "name": "ShibeCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SHOPX": { + "id": 187, + "name": "ShopX", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SILK": { + "id": 188, + "name": "Silkcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SJCX": { + "id": 189, + "name": "Storjcoin X", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "3.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SLR": { + "id": 190, + "name": "SolarCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SMC": { + "id": 191, + "name": "SmartCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SNT": { + "id": 300, + "name": "Status", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "200.00000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "SOC": { + "id": 192, + "name": "SocialCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SPA": { + "id": 193, + "name": "Spaincoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SQL": { + "id": 194, + "name": "Squallcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SRCC": { + "id": 195, + "name": "SourceCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SRG": { + "id": 196, + "name": "Surge", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SSD": { + "id": 197, + "name": "Sonic", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "STEEM": { + "id": 281, + "name": "STEEM", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.01000000", + "minConf": 50, + "depositAddress": "poloniex", + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "STORJ": { + "id": 297, + "name": "Storj", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1.00000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "STR": { + "id": 198, + "name": "Stellar", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.00001000", + "minConf": 2, + "depositAddress": "GCF7F72LNF3ODSJIIWPJWEVWX33VT2SVZSUQ5NMDKDLK3N2NFCUAUHPT", + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "STRAT": { + "id": 287, + "name": "Stratis", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 24, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "SUM": { + "id": 199, + "name": "SummerCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SUN": { + "id": 200, + "name": "Suncoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SWARM": { + "id": 201, + "name": "SWARM", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "1000.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SXC": { + "id": 202, + "name": "Sexcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SYNC": { + "id": 203, + "name": "Sync", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00010000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "SYS": { + "id": 204, + "name": "Syscoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "TAC": { + "id": 205, + "name": "Talkcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "TOR": { + "id": 206, + "name": "TorCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "TRUST": { + "id": 207, + "name": "TrustPlus", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "TWE": { + "id": 208, + "name": "Twecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "UIS": { + "id": 209, + "name": "Unitus", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "ULTC": { + "id": 210, + "name": "Umbrella-LTC", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "UNITY": { + "id": 211, + "name": "SuperNET", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "URO": { + "id": 212, + "name": "Uro", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00100000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "USDC": { + "id": 299, + "name": "USD Coin", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.02000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "USDE": { + "id": 213, + "name": "USDE", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "USDT": { + "id": 214, + "name": "Tether USD", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "10.00000000", + "minConf": 2, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "UTC": { + "id": 215, + "name": "UltraCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "UTIL": { + "id": 216, + "name": "UtilityCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "UVC": { + "id": 217, + "name": "UniversityCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "VIA": { + "id": 218, + "name": "Viacoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 60, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "VOOT": { + "id": 219, + "name": "VootCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "VOX": { + "id": 276, + "name": "Voxels", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "VRC": { + "id": 220, + "name": "VeriCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "VTC": { + "id": 221, + "name": "Vertcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00100000", + "minConf": 600, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "WC": { + "id": 222, + "name": "WhiteCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "WDC": { + "id": 223, + "name": "Worldcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "WIKI": { + "id": 224, + "name": "Wikicoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.10000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "WOLF": { + "id": 225, + "name": "InsanityCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.02000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "X13": { + "id": 226, + "name": "X13Coin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XAI": { + "id": 227, + "name": "Sapience AIFX", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XAP": { + "id": 228, + "name": "API Coin", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "100.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XBC": { + "id": 229, + "name": "BitcoinPlus", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00010000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XC": { + "id": 230, + "name": "XCurrency", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XCH": { + "id": 231, + "name": "ClearingHouse", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XCN": { + "id": 232, + "name": "Cryptonite", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.02000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XCP": { + "id": 233, + "name": "Counterparty", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "16.00000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 0, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XCR": { + "id": 234, + "name": "Crypti", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00017818", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XDN": { + "id": 235, + "name": "DigitalNote", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "1.00000000", + "minConf": 10000, + "depositAddress": "ddddecTXF5B9PDwqZXP4Bs1FwttZbhF2YTRstruKctMoFJTE3VgD5uKd4RzmTHdzkv52wWWK2NYmfJeaiTuut2Pe2RVimGCvZ", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XDP": { + "id": 236, + "name": "Dogeparty", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XEM": { + "id": 256, + "name": "NEM", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "15.00000000", + "minConf": 18, + "depositAddress": "NBZMQO7ZPBYNBDUR7F75MAKA2S3DHDCIFG775N3D", + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "XHC": { + "id": 237, + "name": "Honorcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XLB": { + "id": 238, + "name": "Libertycoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XMG": { + "id": 239, + "name": "Magi", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XMR": { + "id": 240, + "name": "Monero", + "humanType": "BTC Clone", + "currencyType": "address-payment-id", + "txFee": "0.00010000", + "minConf": 8, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "XPB": { + "id": 241, + "name": "Pebblecoin", + "humanType": "Payment ID", + "currencyType": "address-payment-id", + "txFee": "0.10000000", + "minConf": 10000, + "depositAddress": "PByFtxkMuoFRwrhXU19PBXLgHssTtcvTDSGzNf7Rvt5uKEf5PnRL4ccK4pWN4dpwvGhggGq12gH4bMqbxLyQGhkf3RLfzurMdK", + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XPM": { + "id": 242, + "name": "Primecoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 2880, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "XRP": { + "id": 243, + "name": "Ripple", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.15000000", + "minConf": 12, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "XSI": { + "id": 244, + "name": "Stability Shares", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XST": { + "id": 245, + "name": "StealthCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XSV": { + "id": 246, + "name": "Silicon Valley Coin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XUSD": { + "id": 247, + "name": "CoinoUSD", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "0.00000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XVC": { + "id": 253, + "name": "Vcash", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 1000000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "XXC": { + "id": 248, + "name": "CREDS", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "YACC": { + "id": 249, + "name": "YACCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "YANG": { + "id": 250, + "name": "Yangcoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "YC": { + "id": 251, + "name": "YellowCoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "YIN": { + "id": 252, + "name": "Yincoin", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.01000000", + "minConf": 10000, + "depositAddress": null, + "disabled": 1, + "delisted": 1, + "frozen": 0, + "isGeofenced": 0 + }, + "ZEC": { + "id": 286, + "name": "Zcash", + "humanType": "BTC Clone", + "currencyType": "address", + "txFee": "0.00100000", + "minConf": 8, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + }, + "ZRX": { + "id": 293, + "name": "0x", + "humanType": "Sweep to Main Account", + "currencyType": "address", + "txFee": "5.00000000", + "minConf": 30, + "depositAddress": null, + "disabled": 0, + "delisted": 0, + "frozen": 0, + "isGeofenced": 0 + } + }, + "queryString": "command=returnCurrencies", + "bodyParams": "\u003cnil\u003e", + "headers": { + "Key": [ + "" + ] + } + }, + { + "data": [ + { + "date": 1405699200, + "high": 0.00413615, + "low": 0.00403986, + "open": 0.00404545, + "close": 0.00403997, + "volume": 4.95713239, + "quoteVolume": 1205.10503896, + "weightedAverage": 0.00411344 + } + ], + "queryString": "command=returnChartData\u0026currencyPair=BTC_XMR\u0026end=1405699400\u0026period=300\u0026start=1405699200", + "bodyParams": "\u003cnil\u003e", + "headers": { + "Key": [ + "" + ] + } + }, + { + "data": [ + { + "globalTradeID": 419660422, + "tradeID": 19495952, + "date": "2019-06-11 04:31:22", + "type": "buy", + "rate": "0.01087441", + "amount": "3.67796814", + "total": "0.03999573", + "orderNumber": 378714356664 + }, + { + "globalTradeID": 419660404, + "tradeID": 19495951, + "date": "2019-06-11 04:30:31", + "type": "sell", + "rate": "0.01086990", + "amount": "0.00571394", + "total": "0.00006210", + "orderNumber": 378714099921 + }, + { + "globalTradeID": 419660400, + "tradeID": 19495950, + "date": "2019-06-11 04:30:26", + "type": "sell", + "rate": "0.01086990", + "amount": "0.00230268", + "total": "0.00002502", + "orderNumber": 378714088932 + }, + { + "globalTradeID": 419660390, + "tradeID": 19495949, + "date": "2019-06-11 04:30:07", + "type": "buy", + "rate": "0.01086995", + "amount": "0.09199674", + "total": "0.00099999", + "orderNumber": 378714018003 + }, + { + "globalTradeID": 419660384, + "tradeID": 19495948, + "date": "2019-06-11 04:29:44", + "type": "buy", + "rate": "0.01086995", + "amount": "0.18399348", + "total": "0.00199999", + "orderNumber": 378713958063 + }, + { + "globalTradeID": 419660320, + "tradeID": 19495947, + "date": "2019-06-11 04:27:43", + "type": "buy", + "rate": "0.01086995", + "amount": "0.00921345", + "total": "0.00010014", + "orderNumber": 378713556465 + }, + { + "globalTradeID": 419660311, + "tradeID": 19495946, + "date": "2019-06-11 04:27:16", + "type": "sell", + "rate": "0.01087360", + "amount": "0.00977015", + "total": "0.00010623", + "orderNumber": 378713374647 + }, + { + "globalTradeID": 419660294, + "tradeID": 19495945, + "date": "2019-06-11 04:27:08", + "type": "sell", + "rate": "0.01087361", + "amount": "0.00059317", + "total": "0.00000644", + "orderNumber": 378713343678 + }, + { + "globalTradeID": 419660267, + "tradeID": 19495944, + "date": "2019-06-11 04:25:46", + "type": "buy", + "rate": "0.01087743", + "amount": "0.09193288", + "total": "0.00099999", + "orderNumber": 378713085936 + }, + { + "globalTradeID": 419660245, + "tradeID": 19495943, + "date": "2019-06-11 04:25:30", + "type": "sell", + "rate": "0.01087360", + "amount": "0.00391038", + "total": "0.00004251", + "orderNumber": 378712987035 + }, + { + "globalTradeID": 419660244, + "tradeID": 19495942, + "date": "2019-06-11 04:25:30", + "type": "buy", + "rate": "0.01087756", + "amount": "0.18386391", + "total": "0.00199999", + "orderNumber": 378712985037 + }, + { + "globalTradeID": 419660236, + "tradeID": 19495941, + "date": "2019-06-11 04:25:00", + "type": "sell", + "rate": "0.01088420", + "amount": "0.01369197", + "total": "0.00014902", + "orderNumber": 378712820202 + }, + { + "globalTradeID": 419660229, + "tradeID": 19495940, + "date": "2019-06-11 04:24:35", + "type": "sell", + "rate": "0.01088519", + "amount": "0.01427388", + "total": "0.00015537", + "orderNumber": 378712682340 + }, + { + "globalTradeID": 419660226, + "tradeID": 19495939, + "date": "2019-06-11 04:24:34", + "type": "sell", + "rate": "0.01088768", + "amount": "0.00097304", + "total": "0.00001059", + "orderNumber": 378712674348 + }, + { + "globalTradeID": 419660173, + "tradeID": 19495938, + "date": "2019-06-11 04:22:05", + "type": "sell", + "rate": "0.01088769", + "amount": "1.48700000", + "total": "0.01618999", + "orderNumber": 378711816207 + }, + { + "globalTradeID": 419660171, + "tradeID": 19495937, + "date": "2019-06-11 04:22:02", + "type": "sell", + "rate": "0.01088768", + "amount": "1.31174004", + "total": "0.01428180", + "orderNumber": 378711803220 + }, + { + "globalTradeID": 419660170, + "tradeID": 19495936, + "date": "2019-06-11 04:22:02", + "type": "sell", + "rate": "0.01089626", + "amount": "0.01425996", + "total": "0.00015538", + "orderNumber": 378711803220 + }, + { + "globalTradeID": 419660132, + "tradeID": 19495935, + "date": "2019-06-11 04:20:05", + "type": "sell", + "rate": "0.01089627", + "amount": "5.95100000", + "total": "0.06484370", + "orderNumber": 378711129894 + }, + { + "globalTradeID": 419660131, + "tradeID": 19495934, + "date": "2019-06-11 04:20:05", + "type": "sell", + "rate": "0.01089627", + "amount": "3.65950000", + "total": "0.03987490", + "orderNumber": 378711122901 + }, + { + "globalTradeID": 419660129, + "tradeID": 19495933, + "date": "2019-06-11 04:20:01", + "type": "sell", + "rate": "0.01091500", + "amount": "0.16900000", + "total": "0.00184463", + "orderNumber": 378711079944 + }, + { + "globalTradeID": 419660126, + "tradeID": 19495932, + "date": "2019-06-11 04:20:01", + "type": "sell", + "rate": "0.01091935", + "amount": "21.93500000", + "total": "0.23951594", + "orderNumber": 378711075948 + }, + { + "globalTradeID": 419660124, + "tradeID": 19495931, + "date": "2019-06-11 04:20:01", + "type": "sell", + "rate": "0.01091935", + "amount": "11.44500000", + "total": "0.12497196", + "orderNumber": 378711073950 + }, + { + "globalTradeID": 419660123, + "tradeID": 19495930, + "date": "2019-06-11 04:20:00", + "type": "sell", + "rate": "0.01091935", + "amount": "37.87000000", + "total": "0.41351578", + "orderNumber": 378711071952 + }, + { + "globalTradeID": 419660122, + "tradeID": 19495929, + "date": "2019-06-11 04:20:00", + "type": "sell", + "rate": "0.01091937", + "amount": "0.11441976", + "total": "0.00124939", + "orderNumber": 378711070953 + }, + { + "globalTradeID": 419660106, + "tradeID": 19495928, + "date": "2019-06-11 04:19:08", + "type": "buy", + "rate": "0.01092168", + "amount": "0.00001589", + "total": "0.00000017", + "orderNumber": 378710872152 + }, + { + "globalTradeID": 419660079, + "tradeID": 19495927, + "date": "2019-06-11 04:17:33", + "type": "buy", + "rate": "0.01094157", + "amount": "0.00000361", + "total": "0.00000003", + "orderNumber": 378710442582 + }, + { + "globalTradeID": 419660064, + "tradeID": 19495926, + "date": "2019-06-11 04:17:25", + "type": "buy", + "rate": "0.01094825", + "amount": "0.10771685", + "total": "0.00117931", + "orderNumber": 378710362662 + }, + { + "globalTradeID": 419660041, + "tradeID": 19495925, + "date": "2019-06-11 04:17:17", + "type": "buy", + "rate": "0.01092001", + "amount": "0.00038186", + "total": "0.00000416", + "orderNumber": 378710310714 + }, + { + "globalTradeID": 419660040, + "tradeID": 19495924, + "date": "2019-06-11 04:17:17", + "type": "buy", + "rate": "0.01092000", + "amount": "0.00020349", + "total": "0.00000222", + "orderNumber": 378710310714 + }, + { + "globalTradeID": 419660037, + "tradeID": 19495923, + "date": "2019-06-11 04:17:15", + "type": "buy", + "rate": "0.01092000", + "amount": "0.05500000", + "total": "0.00060060", + "orderNumber": 378710295729 + }, + { + "globalTradeID": 419659984, + "tradeID": 19495922, + "date": "2019-06-11 04:15:16", + "type": "sell", + "rate": "0.01091912", + "amount": "13.40208261", + "total": "0.14633894", + "orderNumber": 378709791234 + }, + { + "globalTradeID": 419659967, + "tradeID": 19495921, + "date": "2019-06-11 04:14:24", + "type": "buy", + "rate": "0.01092001", + "amount": "0.00003551", + "total": "0.00000038", + "orderNumber": 378709500525 + }, + { + "globalTradeID": 419659939, + "tradeID": 19495920, + "date": "2019-06-11 04:12:52", + "type": "sell", + "rate": "0.01091700", + "amount": "0.05223877", + "total": "0.00057029", + "orderNumber": 378709035990 + }, + { + "globalTradeID": 419659917, + "tradeID": 19495919, + "date": "2019-06-11 04:11:42", + "type": "buy", + "rate": "0.01092001", + "amount": "0.11400000", + "total": "0.00124488", + "orderNumber": 378708709317 + }, + { + "globalTradeID": 419659887, + "tradeID": 19495918, + "date": "2019-06-11 04:10:11", + "type": "buy", + "rate": "0.01092641", + "amount": "0.59200000", + "total": "0.00646843", + "orderNumber": 378708191835 + }, + { + "globalTradeID": 419659868, + "tradeID": 19495917, + "date": "2019-06-11 04:09:36", + "type": "sell", + "rate": "0.01090201", + "amount": "8.04867515", + "total": "0.08774673", + "orderNumber": 378707979048 + }, + { + "globalTradeID": 419659867, + "tradeID": 19495916, + "date": "2019-06-11 04:09:36", + "type": "sell", + "rate": "0.01090202", + "amount": "0.11441976", + "total": "0.00124740", + "orderNumber": 378707979048 + }, + { + "globalTradeID": 419659866, + "tradeID": 19495915, + "date": "2019-06-11 04:09:36", + "type": "sell", + "rate": "0.01090310", + "amount": "2.31990509", + "total": "0.02529415", + "orderNumber": 378707979048 + }, + { + "globalTradeID": 419659865, + "tradeID": 19495914, + "date": "2019-06-11 04:09:36", + "type": "sell", + "rate": "0.01092004", + "amount": "0.01700000", + "total": "0.00018564", + "orderNumber": 378707979048 + }, + { + "globalTradeID": 419659807, + "tradeID": 19495913, + "date": "2019-06-11 04:07:31", + "type": "sell", + "rate": "0.01095075", + "amount": "0.01418870", + "total": "0.00015537", + "orderNumber": 378707249778 + }, + { + "globalTradeID": 419659769, + "tradeID": 19495912, + "date": "2019-06-11 04:05:05", + "type": "sell", + "rate": "0.01096500", + "amount": "0.04206385", + "total": "0.00046123", + "orderNumber": 378706749279 + }, + { + "globalTradeID": 419659591, + "tradeID": 19495911, + "date": "2019-06-11 04:00:04", + "type": "buy", + "rate": "0.01096866", + "amount": "0.00000182", + "total": "0.00000001", + "orderNumber": 378705401628 + }, + { + "globalTradeID": 419659509, + "tradeID": 19495910, + "date": "2019-06-11 03:58:27", + "type": "sell", + "rate": "0.01096500", + "amount": "0.00131691", + "total": "0.00001443", + "orderNumber": 378705024006 + }, + { + "globalTradeID": 419659489, + "tradeID": 19495909, + "date": "2019-06-11 03:56:54", + "type": "buy", + "rate": "0.01096874", + "amount": "0.00000503", + "total": "0.00000005", + "orderNumber": 378704656374 + }, + { + "globalTradeID": 419659483, + "tradeID": 19495908, + "date": "2019-06-11 03:56:52", + "type": "sell", + "rate": "0.01096500", + "amount": "0.00095029", + "total": "0.00001041", + "orderNumber": 378704634396 + }, + { + "globalTradeID": 419659466, + "tradeID": 19495907, + "date": "2019-06-11 03:55:53", + "type": "buy", + "rate": "0.01096886", + "amount": "0.77346138", + "total": "0.00848398", + "orderNumber": 378704373657 + }, + { + "globalTradeID": 419659465, + "tradeID": 19495906, + "date": "2019-06-11 03:55:53", + "type": "buy", + "rate": "0.01096885", + "amount": "0.13853132", + "total": "0.00151952", + "orderNumber": 378704373657 + }, + { + "globalTradeID": 419659451, + "tradeID": 19495905, + "date": "2019-06-11 03:55:18", + "type": "buy", + "rate": "0.01097328", + "amount": "0.00000182", + "total": "0.00000001", + "orderNumber": 378704154876 + }, + { + "globalTradeID": 419659388, + "tradeID": 19495904, + "date": "2019-06-11 03:53:35", + "type": "sell", + "rate": "0.01097102", + "amount": "0.00323397", + "total": "0.00003547", + "orderNumber": 378703612419 + }, + { + "globalTradeID": 419659387, + "tradeID": 19495903, + "date": "2019-06-11 03:53:35", + "type": "sell", + "rate": "0.01097102", + "amount": "0.01121592", + "total": "0.00012305", + "orderNumber": 378703612419 + }, + { + "globalTradeID": 419659361, + "tradeID": 19495902, + "date": "2019-06-11 03:51:59", + "type": "buy", + "rate": "0.01097329", + "amount": "0.00000551", + "total": "0.00000006", + "orderNumber": 378703243788 + }, + { + "globalTradeID": 419659311, + "tradeID": 19495901, + "date": "2019-06-11 03:50:22", + "type": "buy", + "rate": "0.01097337", + "amount": "0.00000302", + "total": "0.00000003", + "orderNumber": 378702896136 + }, + { + "globalTradeID": 419659308, + "tradeID": 19495900, + "date": "2019-06-11 03:50:19", + "type": "sell", + "rate": "0.01097102", + "amount": "0.00001640", + "total": "0.00000017", + "orderNumber": 378702885147 + }, + { + "globalTradeID": 419659273, + "tradeID": 19495899, + "date": "2019-06-11 03:49:39", + "type": "buy", + "rate": "0.01097341", + "amount": "0.02814583", + "total": "0.00030885", + "orderNumber": 378702765267 + }, + { + "globalTradeID": 419659247, + "tradeID": 19495898, + "date": "2019-06-11 03:48:46", + "type": "buy", + "rate": "0.01097347", + "amount": "0.00000202", + "total": "0.00000002", + "orderNumber": 378702529503 + }, + { + "globalTradeID": 419659206, + "tradeID": 19495897, + "date": "2019-06-11 03:47:11", + "type": "buy", + "rate": "0.01098449", + "amount": "0.00000903", + "total": "0.00000009", + "orderNumber": 378702105927 + }, + { + "globalTradeID": 419659201, + "tradeID": 19495896, + "date": "2019-06-11 03:47:06", + "type": "sell", + "rate": "0.01097123", + "amount": "0.00114936", + "total": "0.00001260", + "orderNumber": 378702095937 + }, + { + "globalTradeID": 419659162, + "tradeID": 19495895, + "date": "2019-06-11 03:45:33", + "type": "buy", + "rate": "0.01098220", + "amount": "0.00000607", + "total": "0.00000006", + "orderNumber": 378701612421 + }, + { + "globalTradeID": 419659121, + "tradeID": 19495894, + "date": "2019-06-11 03:43:57", + "type": "buy", + "rate": "0.01098466", + "amount": "0.00001116", + "total": "0.00000012", + "orderNumber": 378701091942 + }, + { + "globalTradeID": 419659098, + "tradeID": 19495893, + "date": "2019-06-11 03:42:20", + "type": "buy", + "rate": "0.01098497", + "amount": "0.00000381", + "total": "0.00000004", + "orderNumber": 378700626408 + }, + { + "globalTradeID": 419659053, + "tradeID": 19495892, + "date": "2019-06-11 03:39:06", + "type": "buy", + "rate": "0.01098849", + "amount": "0.00000521", + "total": "0.00000005", + "orderNumber": 378699644391 + }, + { + "globalTradeID": 419659027, + "tradeID": 19495891, + "date": "2019-06-11 03:37:49", + "type": "buy", + "rate": "0.01098390", + "amount": "1.56192145", + "total": "0.01715598", + "orderNumber": 378699299736 + }, + { + "globalTradeID": 419659020, + "tradeID": 19495890, + "date": "2019-06-11 03:37:30", + "type": "buy", + "rate": "0.01098390", + "amount": "0.00000203", + "total": "0.00000002", + "orderNumber": 378699228807 + }, + { + "globalTradeID": 419659010, + "tradeID": 19495889, + "date": "2019-06-11 03:37:09", + "type": "buy", + "rate": "0.01098499", + "amount": "1.82224405", + "total": "0.02001733", + "orderNumber": 378699142893 + }, + { + "globalTradeID": 419659009, + "tradeID": 19495888, + "date": "2019-06-11 03:37:09", + "type": "buy", + "rate": "0.01097301", + "amount": "0.63389687", + "total": "0.00695575", + "orderNumber": 378699142893 + }, + { + "globalTradeID": 419658982, + "tradeID": 19495887, + "date": "2019-06-11 03:35:52", + "type": "buy", + "rate": "0.01097301", + "amount": "0.00000313", + "total": "0.00000003", + "orderNumber": 378698852184 + }, + { + "globalTradeID": 419658951, + "tradeID": 19495886, + "date": "2019-06-11 03:34:14", + "type": "buy", + "rate": "0.01097401", + "amount": "0.00000343", + "total": "0.00000003", + "orderNumber": 378698676360 + }, + { + "globalTradeID": 419658906, + "tradeID": 19495885, + "date": "2019-06-11 03:32:36", + "type": "buy", + "rate": "0.01098544", + "amount": "0.00003856", + "total": "0.00000042", + "orderNumber": 378698162874 + }, + { + "globalTradeID": 419658856, + "tradeID": 19495884, + "date": "2019-06-11 03:30:54", + "type": "buy", + "rate": "0.01100470", + "amount": "0.00000822", + "total": "0.00000009", + "orderNumber": 378697679358 + }, + { + "globalTradeID": 419658782, + "tradeID": 19495883, + "date": "2019-06-11 03:27:44", + "type": "buy", + "rate": "0.01099742", + "amount": "0.00005336", + "total": "0.00000058", + "orderNumber": 378696543495 + }, + { + "globalTradeID": 419658690, + "tradeID": 19495882, + "date": "2019-06-11 03:25:32", + "type": "buy", + "rate": "0.01099155", + "amount": "1.59326119", + "total": "0.01751241", + "orderNumber": 378696202836 + }, + { + "globalTradeID": 419658624, + "tradeID": 19495881, + "date": "2019-06-11 03:22:57", + "type": "buy", + "rate": "0.01100279", + "amount": "0.00000776", + "total": "0.00000008", + "orderNumber": 378695513526 + }, + { + "globalTradeID": 419658597, + "tradeID": 19495880, + "date": "2019-06-11 03:21:18", + "type": "sell", + "rate": "0.01099501", + "amount": "0.00181536", + "total": "0.00001995", + "orderNumber": 378694987053 + }, + { + "globalTradeID": 419658524, + "tradeID": 19495879, + "date": "2019-06-11 03:18:10", + "type": "buy", + "rate": "0.01102096", + "amount": "0.00000823", + "total": "0.00000009", + "orderNumber": 378694251789 + }, + { + "globalTradeID": 419658405, + "tradeID": 19495878, + "date": "2019-06-11 03:16:43", + "type": "sell", + "rate": "0.01100509", + "amount": "0.04754874", + "total": "0.00052327", + "orderNumber": 378693959082 + }, + { + "globalTradeID": 419658370, + "tradeID": 19495877, + "date": "2019-06-11 03:15:56", + "type": "sell", + "rate": "0.01099326", + "amount": "0.63690866", + "total": "0.00700170", + "orderNumber": 378693752289 + }, + { + "globalTradeID": 419658369, + "tradeID": 19495876, + "date": "2019-06-11 03:15:56", + "type": "sell", + "rate": "0.01099501", + "amount": "0.11409631", + "total": "0.00125449", + "orderNumber": 378693752289 + }, + { + "globalTradeID": 419658368, + "tradeID": 19495875, + "date": "2019-06-11 03:15:56", + "type": "sell", + "rate": "0.01099826", + "amount": "0.01688839", + "total": "0.00018574", + "orderNumber": 378693752289 + }, + { + "globalTradeID": 419658367, + "tradeID": 19495874, + "date": "2019-06-11 03:15:56", + "type": "sell", + "rate": "0.01102244", + "amount": "0.15215954", + "total": "0.00167716", + "orderNumber": 378693752289 + }, + { + "globalTradeID": 419658294, + "tradeID": 19495873, + "date": "2019-06-11 03:15:10", + "type": "buy", + "rate": "0.01102244", + "amount": "0.21255099", + "total": "0.00234283", + "orderNumber": 378693585456 + }, + { + "globalTradeID": 419658268, + "tradeID": 19495872, + "date": "2019-06-11 03:14:56", + "type": "sell", + "rate": "0.01101010", + "amount": "0.00019436", + "total": "0.00000213", + "orderNumber": 378693515526 + }, + { + "globalTradeID": 419658029, + "tradeID": 19495871, + "date": "2019-06-11 03:08:32", + "type": "sell", + "rate": "0.01100114", + "amount": "0.00000181", + "total": "0.00000001", + "orderNumber": 378691456587 + }, + { + "globalTradeID": 419658023, + "tradeID": 19495870, + "date": "2019-06-11 03:08:29", + "type": "sell", + "rate": "0.01100112", + "amount": "0.00101626", + "total": "0.00001117", + "orderNumber": 378691443600 + }, + { + "globalTradeID": 419658017, + "tradeID": 19495869, + "date": "2019-06-11 03:08:23", + "type": "sell", + "rate": "0.01100110", + "amount": "0.00026088", + "total": "0.00000286", + "orderNumber": 378691431612 + }, + { + "globalTradeID": 419657999, + "tradeID": 19495868, + "date": "2019-06-11 03:06:50", + "type": "sell", + "rate": "0.01100023", + "amount": "0.00016545", + "total": "0.00000181", + "orderNumber": 378691103940 + }, + { + "globalTradeID": 419657996, + "tradeID": 19495867, + "date": "2019-06-11 03:06:49", + "type": "sell", + "rate": "0.01100023", + "amount": "0.00023999", + "total": "0.00000263", + "orderNumber": 378691102941 + }, + { + "globalTradeID": 419657740, + "tradeID": 19495866, + "date": "2019-06-11 02:58:48", + "type": "sell", + "rate": "0.01099843", + "amount": "0.00011456", + "total": "0.00000125", + "orderNumber": 378689243802 + }, + { + "globalTradeID": 419657701, + "tradeID": 19495865, + "date": "2019-06-11 02:56:40", + "type": "buy", + "rate": "0.01101556", + "amount": "0.03917163", + "total": "0.00043149", + "orderNumber": 378688729317 + }, + { + "globalTradeID": 419657601, + "tradeID": 19495864, + "date": "2019-06-11 02:53:35", + "type": "sell", + "rate": "0.01101519", + "amount": "0.13679308", + "total": "0.00150680", + "orderNumber": 378687637410 + }, + { + "globalTradeID": 419657476, + "tradeID": 19495863, + "date": "2019-06-11 02:50:54", + "type": "sell", + "rate": "0.01101013", + "amount": "0.01058570", + "total": "0.00011654", + "orderNumber": 378686860188 + }, + { + "globalTradeID": 419657453, + "tradeID": 19495862, + "date": "2019-06-11 02:50:18", + "type": "buy", + "rate": "0.01101828", + "amount": "0.04280000", + "total": "0.00047158", + "orderNumber": 378686618430 + }, + { + "globalTradeID": 419657452, + "tradeID": 19495861, + "date": "2019-06-11 02:50:18", + "type": "buy", + "rate": "0.01101780", + "amount": "0.01398753", + "total": "0.00015411", + "orderNumber": 378686618430 + }, + { + "globalTradeID": 419657451, + "tradeID": 19495860, + "date": "2019-06-11 02:50:18", + "type": "buy", + "rate": "0.01101629", + "amount": "2.39150539", + "total": "0.02634551", + "orderNumber": 378686618430 + }, + { + "globalTradeID": 419657450, + "tradeID": 19495859, + "date": "2019-06-11 02:50:18", + "type": "buy", + "rate": "0.01101230", + "amount": "0.01340400", + "total": "0.00014760", + "orderNumber": 378686618430 + }, + { + "globalTradeID": 419657449, + "tradeID": 19495858, + "date": "2019-06-11 02:50:18", + "type": "buy", + "rate": "0.01100868", + "amount": "0.16756661", + "total": "0.00184468", + "orderNumber": 378686618430 + }, + { + "globalTradeID": 419657448, + "tradeID": 19495857, + "date": "2019-06-11 02:50:18", + "type": "buy", + "rate": "0.01100810", + "amount": "0.01390076", + "total": "0.00015302", + "orderNumber": 378686618430 + }, + { + "globalTradeID": 419657447, + "tradeID": 19495856, + "date": "2019-06-11 02:50:18", + "type": "buy", + "rate": "0.01100551", + "amount": "0.01411744", + "total": "0.00015536", + "orderNumber": 378686618430 + }, + { + "globalTradeID": 419657445, + "tradeID": 19495855, + "date": "2019-06-11 02:50:18", + "type": "buy", + "rate": "0.01100543", + "amount": "0.04273596", + "total": "0.00047032", + "orderNumber": 378686615433 + }, + { + "globalTradeID": 419657298, + "tradeID": 19495854, + "date": "2019-06-11 02:45:31", + "type": "buy", + "rate": "0.01100142", + "amount": "0.01515621", + "total": "0.00016673", + "orderNumber": 378685060989 + }, + { + "globalTradeID": 419657290, + "tradeID": 19495853, + "date": "2019-06-11 02:44:56", + "type": "buy", + "rate": "0.01100060", + "amount": "0.01395757", + "total": "0.00015354", + "orderNumber": 378684828222 + }, + { + "globalTradeID": 419657119, + "tradeID": 19495852, + "date": "2019-06-11 02:39:35", + "type": "sell", + "rate": "0.01099521", + "amount": "0.07110823", + "total": "0.00078184", + "orderNumber": 378683079972 + }, + { + "globalTradeID": 419657062, + "tradeID": 19495851, + "date": "2019-06-11 02:38:00", + "type": "sell", + "rate": "0.01098911", + "amount": "0.00017289", + "total": "0.00000189", + "orderNumber": 378682556496 + }, + { + "globalTradeID": 419657049, + "tradeID": 19495850, + "date": "2019-06-11 02:37:35", + "type": "buy", + "rate": "0.01100000", + "amount": "0.28712708", + "total": "0.00315839", + "orderNumber": 378682396656 + }, + { + "globalTradeID": 419657048, + "tradeID": 19495849, + "date": "2019-06-11 02:37:35", + "type": "buy", + "rate": "0.01100000", + "amount": "11.98089159", + "total": "0.13178980", + "orderNumber": 378682396656 + }, + { + "globalTradeID": 419657047, + "tradeID": 19495848, + "date": "2019-06-11 02:37:35", + "type": "buy", + "rate": "0.01100000", + "amount": "20.28670000", + "total": "0.22315370", + "orderNumber": 378682391661 + }, + { + "globalTradeID": 419657037, + "tradeID": 19495847, + "date": "2019-06-11 02:37:23", + "type": "buy", + "rate": "0.01100000", + "amount": "19.58857431", + "total": "0.21547431", + "orderNumber": 378682329723 + }, + { + "globalTradeID": 419657036, + "tradeID": 19495846, + "date": "2019-06-11 02:37:23", + "type": "buy", + "rate": "0.01100000", + "amount": "0.10000400", + "total": "0.00110004", + "orderNumber": 378682329723 + }, + { + "globalTradeID": 419657035, + "tradeID": 19495845, + "date": "2019-06-11 02:37:23", + "type": "buy", + "rate": "0.01099996", + "amount": "0.10000000", + "total": "0.00109999", + "orderNumber": 378682329723 + }, + { + "globalTradeID": 419657034, + "tradeID": 19495844, + "date": "2019-06-11 02:37:23", + "type": "buy", + "rate": "0.01099969", + "amount": "0.09091165", + "total": "0.00099999", + "orderNumber": 378682329723 + }, + { + "globalTradeID": 419657033, + "tradeID": 19495843, + "date": "2019-06-11 02:37:23", + "type": "buy", + "rate": "0.01099922", + "amount": "2.39651004", + "total": "0.02635974", + "orderNumber": 378682329723 + }, + { + "globalTradeID": 419657007, + "tradeID": 19495842, + "date": "2019-06-11 02:37:02", + "type": "buy", + "rate": "0.01099588", + "amount": "1.02574734", + "total": "0.01127899", + "orderNumber": 378682212840 + }, + { + "globalTradeID": 419656977, + "tradeID": 19495841, + "date": "2019-06-11 02:36:41", + "type": "buy", + "rate": "0.01099588", + "amount": "0.27600000", + "total": "0.00303486", + "orderNumber": 378682057995 + }, + { + "globalTradeID": 419656960, + "tradeID": 19495840, + "date": "2019-06-11 02:36:09", + "type": "buy", + "rate": "0.01098950", + "amount": "2.76229338", + "total": "0.03035622", + "orderNumber": 378681860193 + }, + { + "globalTradeID": 419656926, + "tradeID": 19495839, + "date": "2019-06-11 02:34:47", + "type": "sell", + "rate": "0.01097903", + "amount": "0.00085253", + "total": "0.00000935", + "orderNumber": 378681331722 + }, + { + "globalTradeID": 419656922, + "tradeID": 19495838, + "date": "2019-06-11 02:34:44", + "type": "sell", + "rate": "0.01097902", + "amount": "0.00263502", + "total": "0.00002892", + "orderNumber": 378681311742 + }, + { + "globalTradeID": 419656903, + "tradeID": 19495837, + "date": "2019-06-11 02:33:09", + "type": "sell", + "rate": "0.01097738", + "amount": "0.00072603", + "total": "0.00000796", + "orderNumber": 378680730324 + }, + { + "globalTradeID": 419656875, + "tradeID": 19495836, + "date": "2019-06-11 02:31:53", + "type": "buy", + "rate": "0.01098999", + "amount": "3.81362404", + "total": "0.04191169", + "orderNumber": 378680249805 + }, + { + "globalTradeID": 419656867, + "tradeID": 19495835, + "date": "2019-06-11 02:31:36", + "type": "sell", + "rate": "0.01099140", + "amount": "3.51884830", + "total": "0.03867706", + "orderNumber": 378680109945 + }, + { + "globalTradeID": 419656866, + "tradeID": 19495834, + "date": "2019-06-11 02:31:32", + "type": "buy", + "rate": "0.01099141", + "amount": "0.00000181", + "total": "0.00000001", + "orderNumber": 378680107947 + }, + { + "globalTradeID": 419656858, + "tradeID": 19495833, + "date": "2019-06-11 02:31:13", + "type": "sell", + "rate": "0.01099140", + "amount": "1.19047895", + "total": "0.01308503", + "orderNumber": 378680036019 + }, + { + "globalTradeID": 419656857, + "tradeID": 19495832, + "date": "2019-06-11 02:31:11", + "type": "sell", + "rate": "0.01099140", + "amount": "0.09067275", + "total": "0.00099662", + "orderNumber": 378680020035 + }, + { + "globalTradeID": 419656847, + "tradeID": 19495831, + "date": "2019-06-11 02:30:30", + "type": "buy", + "rate": "0.01099140", + "amount": "0.20000000", + "total": "0.00219828", + "orderNumber": 378679878177 + }, + { + "globalTradeID": 419656831, + "tradeID": 19495830, + "date": "2019-06-11 02:29:54", + "type": "sell", + "rate": "0.01098366", + "amount": "0.00028223", + "total": "0.00000309", + "orderNumber": 378679691364 + }, + { + "globalTradeID": 419656743, + "tradeID": 19495829, + "date": "2019-06-11 02:26:56", + "type": "buy", + "rate": "0.01099141", + "amount": "14.18000000", + "total": "0.15585819", + "orderNumber": 378678498558 + }, + { + "globalTradeID": 419656741, + "tradeID": 19495828, + "date": "2019-06-11 02:26:55", + "type": "buy", + "rate": "0.01099141", + "amount": "6.67800000", + "total": "0.07340063", + "orderNumber": 378678496560 + }, + { + "globalTradeID": 419656738, + "tradeID": 19495827, + "date": "2019-06-11 02:26:55", + "type": "buy", + "rate": "0.01099141", + "amount": "9.80754099", + "total": "0.10779870", + "orderNumber": 378678492564 + }, + { + "globalTradeID": 419656737, + "tradeID": 19495826, + "date": "2019-06-11 02:26:55", + "type": "buy", + "rate": "0.01099100", + "amount": "0.10000000", + "total": "0.00109910", + "orderNumber": 378678492564 + }, + { + "globalTradeID": 419656736, + "tradeID": 19495825, + "date": "2019-06-11 02:26:55", + "type": "buy", + "rate": "0.01099100", + "amount": "0.58045901", + "total": "0.00637982", + "orderNumber": 378678492564 + }, + { + "globalTradeID": 419656692, + "tradeID": 19495824, + "date": "2019-06-11 02:25:28", + "type": "buy", + "rate": "0.01099141", + "amount": "0.97118176", + "total": "0.01067465", + "orderNumber": 378677876181 + }, + { + "globalTradeID": 419656686, + "tradeID": 19495823, + "date": "2019-06-11 02:25:22", + "type": "buy", + "rate": "0.01099141", + "amount": "1.21323848", + "total": "0.01333520", + "orderNumber": 378677830227 + }, + { + "globalTradeID": 419656674, + "tradeID": 19495822, + "date": "2019-06-11 02:25:10", + "type": "buy", + "rate": "0.01099141", + "amount": "0.61200000", + "total": "0.00672674", + "orderNumber": 378677745312 + }, + { + "globalTradeID": 419656673, + "tradeID": 19495821, + "date": "2019-06-11 02:25:10", + "type": "buy", + "rate": "0.01099141", + "amount": "4.42298415", + "total": "0.04861483", + "orderNumber": 378677743314 + }, + { + "globalTradeID": 419656672, + "tradeID": 19495820, + "date": "2019-06-11 02:25:10", + "type": "buy", + "rate": "0.01099140", + "amount": "0.32001585", + "total": "0.00351742", + "orderNumber": 378677743314 + }, + { + "globalTradeID": 419656622, + "tradeID": 19495819, + "date": "2019-06-11 02:23:58", + "type": "buy", + "rate": "0.01099139", + "amount": "0.09194560", + "total": "0.00101060", + "orderNumber": 378677170887 + }, + { + "globalTradeID": 419656536, + "tradeID": 19495818, + "date": "2019-06-11 02:22:33", + "type": "buy", + "rate": "0.01098805", + "amount": "0.89333991", + "total": "0.00981606", + "orderNumber": 378676565493 + }, + { + "globalTradeID": 419656535, + "tradeID": 19495817, + "date": "2019-06-11 02:22:33", + "type": "buy", + "rate": "0.01098804", + "amount": "3.46666009", + "total": "0.03809179", + "orderNumber": 378676565493 + }, + { + "globalTradeID": 419656510, + "tradeID": 19495816, + "date": "2019-06-11 02:21:53", + "type": "buy", + "rate": "0.01098616", + "amount": "2.83098726", + "total": "0.03110167", + "orderNumber": 378676284774 + }, + { + "globalTradeID": 419656509, + "tradeID": 19495815, + "date": "2019-06-11 02:21:53", + "type": "buy", + "rate": "0.01098615", + "amount": "9.88401274", + "total": "0.10858724", + "orderNumber": 378676284774 + }, + { + "globalTradeID": 419656507, + "tradeID": 19495814, + "date": "2019-06-11 02:21:52", + "type": "buy", + "rate": "0.01098615", + "amount": "2.51900000", + "total": "0.02767411", + "orderNumber": 378676279779 + }, + { + "globalTradeID": 419656505, + "tradeID": 19495813, + "date": "2019-06-11 02:21:52", + "type": "buy", + "rate": "0.01098615", + "amount": "6.19100000", + "total": "0.06801525", + "orderNumber": 378676271787 + }, + { + "globalTradeID": 419656504, + "tradeID": 19495812, + "date": "2019-06-11 02:21:52", + "type": "buy", + "rate": "0.01098615", + "amount": "1.80100000", + "total": "0.01978605", + "orderNumber": 378676269789 + }, + { + "globalTradeID": 419656503, + "tradeID": 19495811, + "date": "2019-06-11 02:21:52", + "type": "buy", + "rate": "0.01098615", + "amount": "10.26000000", + "total": "0.11271789", + "orderNumber": 378676268790 + }, + { + "globalTradeID": 419656502, + "tradeID": 19495810, + "date": "2019-06-11 02:21:51", + "type": "buy", + "rate": "0.01098615", + "amount": "1.98492975", + "total": "0.02180673", + "orderNumber": 378676266792 + }, + { + "globalTradeID": 419656501, + "tradeID": 19495809, + "date": "2019-06-11 02:21:51", + "type": "buy", + "rate": "0.01098614", + "amount": "0.22807025", + "total": "0.00250561", + "orderNumber": 378676266792 + }, + { + "globalTradeID": 419656499, + "tradeID": 19495808, + "date": "2019-06-11 02:21:51", + "type": "buy", + "rate": "0.01098615", + "amount": "8.26400000", + "total": "0.09078954", + "orderNumber": 378676262796 + }, + { + "globalTradeID": 419656496, + "tradeID": 19495807, + "date": "2019-06-11 02:21:51", + "type": "buy", + "rate": "0.01098615", + "amount": "3.63000000", + "total": "0.03987972", + "orderNumber": 378676261797 + }, + { + "globalTradeID": 419656491, + "tradeID": 19495806, + "date": "2019-06-11 02:21:46", + "type": "buy", + "rate": "0.01098615", + "amount": "5.82605751", + "total": "0.06400594", + "orderNumber": 378676256802 + }, + { + "globalTradeID": 419656488, + "tradeID": 19495805, + "date": "2019-06-11 02:21:46", + "type": "buy", + "rate": "0.01098616", + "amount": "5.82459583", + "total": "0.06398994", + "orderNumber": 378676251807 + }, + { + "globalTradeID": 419656478, + "tradeID": 19495804, + "date": "2019-06-11 02:21:46", + "type": "buy", + "rate": "0.01098617", + "amount": "5.82323047", + "total": "0.06397499", + "orderNumber": 378676247811 + }, + { + "globalTradeID": 419656401, + "tradeID": 19495803, + "date": "2019-06-11 02:17:12", + "type": "sell", + "rate": "0.01097003", + "amount": "0.00011303", + "total": "0.00000123", + "orderNumber": 378674915145 + }, + { + "globalTradeID": 419656344, + "tradeID": 19495802, + "date": "2019-06-11 02:15:35", + "type": "sell", + "rate": "0.01096700", + "amount": "0.00622868", + "total": "0.00006830", + "orderNumber": 378674362698 + }, + { + "globalTradeID": 419656335, + "tradeID": 19495801, + "date": "2019-06-11 02:14:58", + "type": "sell", + "rate": "0.01096644", + "amount": "6.44556753", + "total": "0.07068492", + "orderNumber": 378674125935 + }, + { + "globalTradeID": 419656229, + "tradeID": 19495800, + "date": "2019-06-11 02:13:59", + "type": "sell", + "rate": "0.01097611", + "amount": "0.00881642", + "total": "0.00009676", + "orderNumber": 378673796265 + }, + { + "globalTradeID": 419656134, + "tradeID": 19495799, + "date": "2019-06-11 02:11:11", + "type": "sell", + "rate": "0.01097600", + "amount": "4.34874974", + "total": "0.04773187", + "orderNumber": 378672998064 + }, + { + "globalTradeID": 419656133, + "tradeID": 19495798, + "date": "2019-06-11 02:11:11", + "type": "sell", + "rate": "0.01097601", + "amount": "0.12865121", + "total": "0.00141207", + "orderNumber": 378672998064 + }, + { + "globalTradeID": 419656132, + "tradeID": 19495797, + "date": "2019-06-11 02:11:11", + "type": "sell", + "rate": "0.01097601", + "amount": "1.32690213", + "total": "0.01456409", + "orderNumber": 378672998064 + }, + { + "globalTradeID": 419656131, + "tradeID": 19495796, + "date": "2019-06-11 02:11:11", + "type": "sell", + "rate": "0.01097632", + "amount": "4.08748000", + "total": "0.04486548", + "orderNumber": 378672998064 + }, + { + "globalTradeID": 419656071, + "tradeID": 19495795, + "date": "2019-06-11 02:10:51", + "type": "sell", + "rate": "0.01097629", + "amount": "0.00268670", + "total": "0.00002948", + "orderNumber": 378672914148 + }, + { + "globalTradeID": 419656068, + "tradeID": 19495794, + "date": "2019-06-11 02:10:50", + "type": "sell", + "rate": "0.01097629", + "amount": "0.00828786", + "total": "0.00009096", + "orderNumber": 378672908154 + }, + { + "globalTradeID": 419655987, + "tradeID": 19495793, + "date": "2019-06-11 02:09:13", + "type": "sell", + "rate": "0.01097625", + "amount": "0.00062589", + "total": "0.00000686", + "orderNumber": 378672486576 + }, + { + "globalTradeID": 419655936, + "tradeID": 19495792, + "date": "2019-06-11 02:07:30", + "type": "buy", + "rate": "0.01099588", + "amount": "0.08730618", + "total": "0.00096000", + "orderNumber": 378671928135 + }, + { + "globalTradeID": 419655935, + "tradeID": 19495791, + "date": "2019-06-11 02:07:27", + "type": "buy", + "rate": "0.01099590", + "amount": "2.94600000", + "total": "0.03239392", + "orderNumber": 378671914149 + }, + { + "globalTradeID": 419655932, + "tradeID": 19495790, + "date": "2019-06-11 02:07:23", + "type": "buy", + "rate": "0.01099590", + "amount": "18.99819158", + "total": "0.20890221", + "orderNumber": 378671909154 + }, + { + "globalTradeID": 419655931, + "tradeID": 19495789, + "date": "2019-06-11 02:07:23", + "type": "buy", + "rate": "0.01098289", + "amount": "8.49186817", + "total": "0.09326525", + "orderNumber": 378671909154 + }, + { + "globalTradeID": 419655930, + "tradeID": 19495788, + "date": "2019-06-11 02:07:23", + "type": "buy", + "rate": "0.01098289", + "amount": "0.92516246", + "total": "0.01016095", + "orderNumber": 378671909154 + }, + { + "globalTradeID": 419655888, + "tradeID": 19495787, + "date": "2019-06-11 02:05:51", + "type": "buy", + "rate": "0.01098982", + "amount": "40.55104177", + "total": "0.44564864", + "orderNumber": 378671584479 + }, + { + "globalTradeID": 419655839, + "tradeID": 19495786, + "date": "2019-06-11 02:04:30", + "type": "sell", + "rate": "0.01097601", + "amount": "0.00059948", + "total": "0.00000657", + "orderNumber": 378671231832 + }, + { + "globalTradeID": 419655835, + "tradeID": 19495785, + "date": "2019-06-11 02:04:29", + "type": "sell", + "rate": "0.01097601", + "amount": "0.00230684", + "total": "0.00002531", + "orderNumber": 378671224839 + }, + { + "globalTradeID": 419655760, + "tradeID": 19495784, + "date": "2019-06-11 02:02:52", + "type": "sell", + "rate": "0.01097624", + "amount": "0.00004919", + "total": "0.00000053", + "orderNumber": 378670836228 + }, + { + "globalTradeID": 419655755, + "tradeID": 19495783, + "date": "2019-06-11 02:02:50", + "type": "sell", + "rate": "0.01097624", + "amount": "0.00080537", + "total": "0.00000883", + "orderNumber": 378670822242 + }, + { + "globalTradeID": 419655714, + "tradeID": 19495782, + "date": "2019-06-11 02:01:24", + "type": "sell", + "rate": "0.01097609", + "amount": "0.14937440", + "total": "0.00163954", + "orderNumber": 378670531533 + }, + { + "globalTradeID": 419655711, + "tradeID": 19495781, + "date": "2019-06-11 02:01:18", + "type": "sell", + "rate": "0.01097610", + "amount": "0.11135441", + "total": "0.00122223", + "orderNumber": 378670504560 + }, + { + "globalTradeID": 419655698, + "tradeID": 19495780, + "date": "2019-06-11 02:01:12", + "type": "sell", + "rate": "0.01097610", + "amount": "0.00214374", + "total": "0.00002352", + "orderNumber": 378670483581 + }, + { + "globalTradeID": 419655693, + "tradeID": 19495779, + "date": "2019-06-11 02:01:11", + "type": "sell", + "rate": "0.01097610", + "amount": "0.00071883", + "total": "0.00000788", + "orderNumber": 378670480584 + }, + { + "globalTradeID": 419655690, + "tradeID": 19495778, + "date": "2019-06-11 02:01:10", + "type": "buy", + "rate": "0.01098999", + "amount": "0.09090000", + "total": "0.00099899", + "orderNumber": 378670475589 + }, + { + "globalTradeID": 419655677, + "tradeID": 19495777, + "date": "2019-06-11 02:00:41", + "type": "buy", + "rate": "0.01098998", + "amount": "0.10000000", + "total": "0.00109899", + "orderNumber": 378670377687 + }, + { + "globalTradeID": 419655660, + "tradeID": 19495776, + "date": "2019-06-11 01:59:51", + "type": "buy", + "rate": "0.01098999", + "amount": "22.47015128", + "total": "0.24694673", + "orderNumber": 378670228836 + }, + { + "globalTradeID": 419655659, + "tradeID": 19495775, + "date": "2019-06-11 01:59:51", + "type": "buy", + "rate": "0.01098642", + "amount": "0.15373084", + "total": "0.00168895", + "orderNumber": 378670228836 + }, + { + "globalTradeID": 419655658, + "tradeID": 19495774, + "date": "2019-06-11 01:59:51", + "type": "buy", + "rate": "0.01098400", + "amount": "0.00928634", + "total": "0.00010200", + "orderNumber": 378670228836 + }, + { + "globalTradeID": 419655657, + "tradeID": 19495773, + "date": "2019-06-11 01:59:51", + "type": "buy", + "rate": "0.01098025", + "amount": "0.05936375", + "total": "0.00065182", + "orderNumber": 378670228836 + }, + { + "globalTradeID": 419655656, + "tradeID": 19495772, + "date": "2019-06-11 01:59:51", + "type": "buy", + "rate": "0.01098000", + "amount": "0.02000000", + "total": "0.00021960", + "orderNumber": 378670228836 + }, + { + "globalTradeID": 419655655, + "tradeID": 19495771, + "date": "2019-06-11 01:59:51", + "type": "buy", + "rate": "0.01098000", + "amount": "0.00981951", + "total": "0.00010781", + "orderNumber": 378670228836 + }, + { + "globalTradeID": 419655652, + "tradeID": 19495770, + "date": "2019-06-11 01:59:51", + "type": "sell", + "rate": "0.01097603", + "amount": "0.00912443", + "total": "0.00010015", + "orderNumber": 378670225839 + }, + { + "globalTradeID": 419655640, + "tradeID": 19495769, + "date": "2019-06-11 01:59:35", + "type": "sell", + "rate": "0.01097606", + "amount": "0.00028061", + "total": "0.00000307", + "orderNumber": 378670189875 + }, + { + "globalTradeID": 419655633, + "tradeID": 19495768, + "date": "2019-06-11 01:59:23", + "type": "buy", + "rate": "0.01098000", + "amount": "1.29702296", + "total": "0.01424131", + "orderNumber": 378670155909 + }, + { + "globalTradeID": 419655630, + "tradeID": 19495767, + "date": "2019-06-11 01:59:05", + "type": "buy", + "rate": "0.01098000", + "amount": "1.18408220", + "total": "0.01300122", + "orderNumber": 378670101963 + }, + { + "globalTradeID": 419655590, + "tradeID": 19495766, + "date": "2019-06-11 01:58:00", + "type": "sell", + "rate": "0.01097603", + "amount": "0.02045548", + "total": "0.00022451", + "orderNumber": 378669894171 + }, + { + "globalTradeID": 419655589, + "tradeID": 19495765, + "date": "2019-06-11 01:58:00", + "type": "sell", + "rate": "0.01097605", + "amount": "0.01740344", + "total": "0.00019102", + "orderNumber": 378669894171 + }, + { + "globalTradeID": 419655588, + "tradeID": 19495764, + "date": "2019-06-11 01:58:00", + "type": "sell", + "rate": "0.01097605", + "amount": "0.00259656", + "total": "0.00002849", + "orderNumber": 378669890175 + }, + { + "globalTradeID": 419655571, + "tradeID": 19495763, + "date": "2019-06-11 01:57:42", + "type": "sell", + "rate": "0.01097605", + "amount": "2.29560855", + "total": "0.02519671", + "orderNumber": 378669825240 + }, + { + "globalTradeID": 419655547, + "tradeID": 19495762, + "date": "2019-06-11 01:57:06", + "type": "buy", + "rate": "0.01098000", + "amount": "8.67379891", + "total": "0.09523831", + "orderNumber": 378669717348 + }, + { + "globalTradeID": 419655520, + "tradeID": 19495761, + "date": "2019-06-11 01:56:19", + "type": "sell", + "rate": "0.01097606", + "amount": "0.00017674", + "total": "0.00000193", + "orderNumber": 378669567498 + }, + { + "globalTradeID": 419655454, + "tradeID": 19495760, + "date": "2019-06-11 01:53:01", + "type": "sell", + "rate": "0.01097600", + "amount": "0.00047831", + "total": "0.00000524", + "orderNumber": 378668865201 + }, + { + "globalTradeID": 419655453, + "tradeID": 19495759, + "date": "2019-06-11 01:52:57", + "type": "sell", + "rate": "0.01097600", + "amount": "0.00133017", + "total": "0.00001459", + "orderNumber": 378668862204 + }, + { + "globalTradeID": 419655381, + "tradeID": 19495758, + "date": "2019-06-11 01:50:44", + "type": "sell", + "rate": "0.01097600", + "amount": "0.01109161", + "total": "0.00012174", + "orderNumber": 378668335731 + }, + { + "globalTradeID": 419655380, + "tradeID": 19495757, + "date": "2019-06-11 01:50:41", + "type": "buy", + "rate": "0.01098000", + "amount": "0.01028779", + "total": "0.00011295", + "orderNumber": 378668318748 + }, + { + "globalTradeID": 419655343, + "tradeID": 19495756, + "date": "2019-06-11 01:49:39", + "type": "sell", + "rate": "0.01097613", + "amount": "0.00912435", + "total": "0.00010015", + "orderNumber": 378668097969 + }, + { + "globalTradeID": 419655339, + "tradeID": 19495755, + "date": "2019-06-11 01:49:38", + "type": "buy", + "rate": "0.01098000", + "amount": "17.58144635", + "total": "0.19304428", + "orderNumber": 378668091975 + }, + { + "globalTradeID": 419655307, + "tradeID": 19495754, + "date": "2019-06-11 01:48:21", + "type": "sell", + "rate": "0.01097631", + "amount": "0.06014124", + "total": "0.00066012", + "orderNumber": 378667813254 + }, + { + "globalTradeID": 419655303, + "tradeID": 19495753, + "date": "2019-06-11 01:48:15", + "type": "sell", + "rate": "0.01097631", + "amount": "0.00205442", + "total": "0.00002254", + "orderNumber": 378667800267 + } + ], + "queryString": "command=returnTradeHistory\u0026currencyPair=BTC_XMR", + "bodyParams": "\u003cnil\u003e", + "headers": { + "Key": [ + "" + ] + } + }, + { + "data": { + "asks": [ + [ + "0.01087421", + 7.14432824 + ], + [ + "0.01087422", + 0.22865644 + ], + [ + "0.01087442", + 1.9545156 + ], + [ + "0.01090127", + 115.93784476 + ], + [ + "0.01090131", + 61.893 + ], + [ + "0.01090133", + 61.325 + ], + [ + "0.01090196", + 50.36 + ], + [ + "0.01090218", + 5.05268287 + ], + [ + "0.01091700", + 48.8702 + ], + [ + "0.01091999", + 21.12523711 + ], + [ + "0.01092000", + 162.46658193 + ], + [ + "0.01092040", + 1008.518 + ], + [ + "0.01092042", + 497.15 + ], + [ + "0.01092044", + 71.25 + ], + [ + "0.01094817", + 8.77 + ], + [ + "0.01095034", + 0.01220759 + ], + [ + "0.01095757", + 29.94 + ], + [ + "0.01097007", + 7.77 + ], + [ + "0.01097660", + 5.84 + ], + [ + "0.01098948", + 300.394 + ], + [ + "0.01098949", + 137.05136069 + ], + [ + "0.01098955", + 0.0425 + ], + [ + "0.01099201", + 4.77 + ], + [ + "0.01100551", + 0.01411744 + ], + [ + "0.01102293", + 0.05364355 + ], + [ + "0.01102299", + 0.52734657 + ], + [ + "0.01102301", + 243.03 + ], + [ + "0.01102578", + 1.50994843 + ], + [ + "0.01102849", + 0.0425 + ], + [ + "0.01102990", + 0.06737554 + ], + [ + "0.01103094", + 0.18264761 + ], + [ + "0.01103900", + 0.01356688 + ], + [ + "0.01104080", + 0.01415803 + ], + [ + "0.01104996", + 0.1 + ], + [ + "0.01105139", + 0.0169 + ], + [ + "0.01105321", + 0.19908589 + ], + [ + "0.01105708", + 0.01 + ], + [ + "0.01105960", + 0.01355974 + ], + [ + "0.01106046", + 0.04658219 + ], + [ + "0.01106054", + 0.01404622 + ], + [ + "0.01107000", + 0.08953913 + ], + [ + "0.01107290", + 0.01353714 + ], + [ + "0.01107547", + 0.21700362 + ], + [ + "0.01108019", + 5.01253132 + ], + [ + "0.01108973", + 0.74490566 + ], + [ + "0.01108979", + 210.746 + ], + [ + "0.01108980", + 0.01026208 + ], + [ + "0.01109000", + 0.01329155 + ], + [ + "0.01109527", + 0.01830431 + ], + [ + "0.01109558", + 4.9875 + ] + ], + "bids": [ + [ + "0.01086991", + 4.52422513 + ], + [ + "0.01086990", + 0.08398054 + ], + [ + "0.01085138", + 0.1145743 + ], + [ + "0.01085028", + 7.125 + ], + [ + "0.01085000", + 72.53101291 + ], + [ + "0.01084788", + 0.15106432 + ], + [ + "0.01084205", + 52.38273012 + ], + [ + "0.01084204", + 0.01433001 + ], + [ + "0.01083991", + 0.0323 + ], + [ + "0.01083501", + 7.72480323 + ], + [ + "0.01083192", + 0.05157812 + ], + [ + "0.01082740", + 0.01375269 + ], + [ + "0.01082612", + 1.012434 + ], + [ + "0.01082240", + 0.01374712 + ], + [ + "0.01082000", + 0.1889159 + ], + [ + "0.01080831", + 50.37 + ], + [ + "0.01080830", + 0.01382319 + ], + [ + "0.01080824", + 0.0323 + ], + [ + "0.01080701", + 0.555 + ], + [ + "0.01080698", + 0.165 + ], + [ + "0.01080670", + 4.16190881 + ], + [ + "0.01080560", + 0.01377585 + ], + [ + "0.01079702", + 146.76028561 + ], + [ + "0.01079701", + 497.16 + ], + [ + "0.01079613", + 9.77 + ], + [ + "0.01079500", + 0.03233627 + ], + [ + "0.01079000", + 0.56624022 + ], + [ + "0.01078951", + 0.01187171 + ], + [ + "0.01078796", + 0.01440185 + ], + [ + "0.01078733", + 0.02321905 + ], + [ + "0.01078675", + 5.10102765 + ], + [ + "0.01078674", + 0.2 + ], + [ + "0.01078289", + 0.0349632 + ], + [ + "0.01078102", + 0.0101 + ], + [ + "0.01077950", + 0.01428874 + ], + [ + "0.01077850", + 120.98761144 + ], + [ + "0.01077790", + 0.01420192 + ], + [ + "0.01077688", + 0.01312173 + ], + [ + "0.01077630", + 0.01433467 + ], + [ + "0.01077440", + 0.01423717 + ], + [ + "0.01077401", + 0.01052533 + ], + [ + "0.01076460", + 0.01422698 + ], + [ + "0.01076327", + 2.34474921 + ], + [ + "0.01076310", + 0.01420384 + ], + [ + "0.01075664", + 0.42659413 + ], + [ + "0.01075558", + 0.01105659 + ], + [ + "0.01075461", + 243.04 + ], + [ + "0.01075380", + 0.05008624 + ], + [ + "0.01075310", + 500.9246824 + ], + [ + "0.01075307", + 3.77 + ] + ], + "isFrozen": "0", + "seq": 445571843 + }, + "queryString": "command=returnOrderBook\u0026currencyPair=BTC_XMR\u0026depth=50", + "bodyParams": "\u003cnil\u003e", + "headers": { + "Key": [ + "" + ] + } + }, + { + "data": { + "BTC_BCN": { + "BTC": "2.10597417", + "BCN": "18580513.78451419" + }, + "BTC_BTS": { + "BTC": "11.31814868", + "BTS": "1422525.76536768" + }, + "BTC_CLAM": { + "BTC": "2.91928382", + "CLAM": "5008.88307610" + }, + "BTC_DASH": { + "BTC": "121.56922496", + "DASH": "6476.07273599" + }, + "BTC_DGB": { + "BTC": "16.15966014", + "DGB": "9761759.50030071" + }, + "BTC_DOGE": { + "BTC": "19.98598120", + "DOGE": "52317925.99866673" + }, + "BTC_GAME": { + "BTC": "3.82370347", + "GAME": "300446.22176630" + }, + "BTC_LTC": { + "BTC": "666.94447027", + "LTC": "42339.40507638" + }, + "BTC_MAID": { + "BTC": "2.56267002", + "MAID": "107646.79242367" + }, + "BTC_OMNI": { + "BTC": "0.20882002", + "OMNI": "732.64551893" + }, + "BTC_NAV": { + "BTC": "0.85599154", + "NAV": "30239.29066641" + }, + "BTC_NXT": { + "BTC": "1.64399713", + "NXT": "360674.76672284" + }, + "BTC_STR": { + "BTC": "29.15587511", + "STR": "1887833.72135812" + }, + "BTC_VIA": { + "BTC": "6.00937608", + "VIA": "82838.51366051" + }, + "BTC_VTC": { + "BTC": "8.10964192", + "VTC": "118741.18466855" + }, + "BTC_XEM": { + "BTC": "11.47584618", + "XEM": "1080591.66263058" + }, + "BTC_XMR": { + "BTC": "42.01213244", + "XMR": "3857.26226555" + }, + "BTC_XPM": { + "BTC": "0.71263628", + "XPM": "24729.03795341" + }, + "BTC_XRP": { + "BTC": "201.36631863", + "XRP": "4023790.64131056" + }, + "USDT_BTC": { + "USDT": "4421897.17330826", + "BTC": "561.77921782" + }, + "USDT_DASH": { + "USDT": "163148.07688044", + "DASH": "1097.22131727" + }, + "USDT_LTC": { + "USDT": "1316866.42418109", + "LTC": "10573.16167640" + }, + "USDT_NXT": { + "USDT": "10352.62135341", + "NXT": "287419.90703917" + }, + "USDT_STR": { + "USDT": "119159.96377689", + "STR": "975973.89241271" + }, + "USDT_XMR": { + "USDT": "44311.82773756", + "XMR": "513.51239329" + }, + "USDT_XRP": { + "USDT": "628653.50566678", + "XRP": "1590094.01666263" + }, + "XMR_BCN": { + "XMR": "0.16285090", + "BCN": "15121.34000573" + }, + "XMR_DASH": { + "XMR": "199.20221390", + "DASH": "116.70250730" + }, + "XMR_LTC": { + "XMR": "170.93153487", + "LTC": "118.22421521" + }, + "XMR_MAID": { + "XMR": "11.27396401", + "MAID": "5131.12258542" + }, + "XMR_NXT": { + "XMR": "1.90717544", + "NXT": "4664.12738280" + }, + "BTC_ETH": { + "BTC": "155.94348625", + "ETH": "5059.54434879" + }, + "USDT_ETH": { + "USDT": "883511.34773111", + "ETH": "3668.33116661" + }, + "BTC_SC": { + "BTC": "1.87511642", + "SC": "4769865.10799687" + }, + "BTC_FCT": { + "BTC": "10.36741741", + "FCT": "13773.13207446" + }, + "BTC_DCR": { + "BTC": "2.34160137", + "DCR": "684.41654989" + }, + "BTC_LSK": { + "BTC": "5.21884194", + "LSK": "20377.01855963" + }, + "ETH_LSK": { + "ETH": "0.76656131", + "LSK": "93.01338152" + }, + "BTC_LBC": { + "BTC": "3.00058286", + "LBC": "699495.42159049" + }, + "BTC_STEEM": { + "BTC": "4.42639165", + "STEEM": "85043.49213557" + }, + "ETH_STEEM": { + "ETH": "6.47349009", + "STEEM": "3859.75860945" + }, + "BTC_ETC": { + "BTC": "10.91386394", + "ETC": "10520.96374951" + }, + "ETH_ETC": { + "ETH": "50.23223183", + "ETC": "1477.91871918" + }, + "USDT_ETC": { + "USDT": "143484.54569522", + "ETC": "17511.35515526" + }, + "BTC_REP": { + "BTC": "3.58890958", + "REP": "1490.86836755" + }, + "USDT_REP": { + "USDT": "21917.80239715", + "REP": "1157.81116881" + }, + "ETH_REP": { + "ETH": "43.83047186", + "REP": "566.61066154" + }, + "BTC_ARDR": { + "BTC": "3.03108905", + "ARDR": "238083.87187020" + }, + "BTC_ZEC": { + "BTC": "6.83567402", + "ZEC": "679.67342401" + }, + "ETH_ZEC": { + "ETH": "20.70785072", + "ZEC": "63.08405800" + }, + "USDT_ZEC": { + "USDT": "22621.88453883", + "ZEC": "285.13456697" + }, + "XMR_ZEC": { + "XMR": "18.27667017", + "ZEC": "19.92803337" + }, + "BTC_STRAT": { + "BTC": "3.69185582", + "STRAT": "30477.03971572" + }, + "BTC_PASC": { + "BTC": "1.25124963", + "PASC": "45122.06215166" + }, + "BTC_GNT": { + "BTC": "4.93328876", + "GNT": "404745.01492216" + }, + "ETH_GNT": { + "ETH": "33.26795789", + "GNT": "85767.44098332" + }, + "BTC_ZRX": { + "BTC": "2.07845804", + "ZRX": "49776.28138902" + }, + "ETH_ZRX": { + "ETH": "22.37346857", + "ZRX": "16548.11602204" + }, + "BTC_CVC": { + "BTC": "0.75331703", + "CVC": "68777.88900435" + }, + "ETH_CVC": { + "ETH": "7.76889158", + "CVC": "22217.14581100" + }, + "BTC_OMG": { + "BTC": "1.25616463", + "OMG": "4963.85040878" + }, + "ETH_OMG": { + "ETH": "8.05512623", + "OMG": "971.21438737" + }, + "BTC_GAS": { + "BTC": "0.77232780", + "GAS": "1916.80182489" + }, + "ETH_GAS": { + "ETH": "3.04307877", + "GAS": "227.12349806" + }, + "BTC_STORJ": { + "BTC": "0.59303686", + "STORJ": "16796.76970761" + }, + "BTC_EOS": { + "BTC": "18.59593565", + "EOS": "23099.92132301" + }, + "ETH_EOS": { + "ETH": "57.85287084", + "EOS": "2204.63200411" + }, + "USDT_EOS": { + "USDT": "278043.19290915", + "EOS": "43736.05905905" + }, + "BTC_SNT": { + "BTC": "1.74882337", + "SNT": "463292.11309769" + }, + "ETH_SNT": { + "ETH": "11.98247233", + "SNT": "97344.62846475" + }, + "USDT_SNT": { + "USDT": "3314.06637263", + "SNT": "111527.99375151" + }, + "BTC_KNC": { + "BTC": "1.07605151", + "KNC": "31066.52899332" + }, + "ETH_KNC": { + "ETH": "3.97445644", + "KNC": "3526.69313018" + }, + "USDT_KNC": { + "USDT": "3460.19386639", + "KNC": "12879.73674180" + }, + "BTC_BAT": { + "BTC": "2.13275613", + "BAT": "51122.24173657" + }, + "ETH_BAT": { + "ETH": "15.19774324", + "BAT": "11202.68186565" + }, + "USDT_BAT": { + "USDT": "19328.23384182", + "BAT": "58700.42769184" + }, + "BTC_LOOM": { + "BTC": "5.48711026", + "LOOM": "522861.14578247" + }, + "ETH_LOOM": { + "ETH": "65.66658897", + "LOOM": "193088.49239467" + }, + "USDT_LOOM": { + "USDT": "7884.22722657", + "LOOM": "94061.17501115" + }, + "USDT_DOGE": { + "USDT": "30256.35268923", + "DOGE": "10052886.96556405" + }, + "USDT_GNT": { + "USDT": "13022.42294546", + "GNT": "136510.36772013" + }, + "USDT_LSK": { + "USDT": "13223.12589230", + "LSK": "6568.77436503" + }, + "USDT_SC": { + "USDT": "25558.80796699", + "SC": "8307626.97734275" + }, + "USDT_ZRX": { + "USDT": "25505.45281006", + "ZRX": "78505.36961414" + }, + "BTC_QTUM": { + "BTC": "9.94417285", + "QTUM": "23898.37003779" + }, + "ETH_QTUM": { + "ETH": "19.21848012", + "QTUM": "1428.71665478" + }, + "USDT_QTUM": { + "USDT": "39682.13887751", + "QTUM": "12370.15246589" + }, + "USDC_BTC": { + "USDC": "3482664.85806597", + "BTC": "441.87385476" + }, + "USDC_ETH": { + "USDC": "501153.45720283", + "ETH": "2047.00238213" + }, + "USDC_USDT": { + "USDC": "57933.50068393", + "USDT": "57719.93500183" + }, + "BTC_MANA": { + "BTC": "2.70084416", + "MANA": "362727.87722537" + }, + "ETH_MANA": { + "ETH": "22.07735950", + "MANA": "90624.84430328" + }, + "USDT_MANA": { + "USDT": "3529.09794786", + "MANA": "59699.03615938" + }, + "BTC_BNT": { + "BTC": "0.06242819", + "BNT": "708.36829451" + }, + "ETH_BNT": { + "ETH": "14.29896888", + "BNT": "5013.99102334" + }, + "USDT_BNT": { + "USDT": "363.53451604", + "BNT": "514.58745683" + }, + "BTC_BCHABC": { + "BTC": "36.20758372", + "BCHABC": "733.46224700" + }, + "USDC_BCHABC": { + "USDC": "135429.66946378", + "BCHABC": "345.71602989" + }, + "BTC_BCHSV": { + "BTC": "107.87823714", + "BCHSV": "4528.05891976" + }, + "USDC_BCHSV": { + "USDC": "219303.91833302", + "BCHSV": "1158.73869926" + }, + "USDC_XRP": { + "USDC": "199920.97825116", + "XRP": "501260.01445723" + }, + "USDC_XMR": { + "USDC": "99062.18940901", + "XMR": "1147.67641269" + }, + "USDC_STR": { + "USDC": "21935.95722630", + "STR": "178840.89704069" + }, + "USDC_DOGE": { + "USDC": "2750.23843586", + "DOGE": "907350.09402904" + }, + "USDC_LTC": { + "USDC": "566449.26199652", + "LTC": "4496.10093795" + }, + "USDC_ZEC": { + "USDC": "18301.41164199", + "ZEC": "229.40589669" + }, + "BTC_FOAM": { + "BTC": "0.89731724", + "FOAM": "166155.79008468" + }, + "USDC_FOAM": { + "USDC": "722.65429834", + "FOAM": "16885.98552482" + }, + "BTC_NMR": { + "BTC": "1.64609151", + "NMR": "1912.03148904" + }, + "BTC_POLY": { + "BTC": "1.91462892", + "POLY": "152996.00496795" + }, + "BTC_LPT": { + "BTC": "2.49829681", + "LPT": "4080.23729692" + }, + "BTC_GRIN": { + "BTC": "24.37429922", + "GRIN": "62838.47716938" + }, + "USDC_GRIN": { + "USDC": "23814.63241472", + "GRIN": "7937.02292983" + }, + "BTC_ATOM": { + "BTC": "39.67100725", + "ATOM": "52381.46699854" + }, + "USDC_ATOM": { + "USDC": "8589.72151742", + "ATOM": "1435.15971856" + }, + "USDT_ATOM": { + "USDT": "125149.60868108", + "ATOM": "21087.06757208" + }, + "totalBTC": "1628.64800905", + "totalETH": "406.78806917", + "totalUSDC": "5338032.44894085", + "totalUSDT": "8364245.62980983", + "totalXMR": "401.75440929", + "totalXUSD": "0.00000000" + }, + "queryString": "command=return24hVolume", + "bodyParams": "\u003cnil\u003e", + "headers": { + "Key": [ + "" + ] + } + }, + { + "data": { + "BTC_BCN": { + "id": 7, + "last": "0.00000012", + "lowestAsk": "0.00000012", + "highestBid": "0.00000011", + "percentChange": "0.00000000", + "baseVolume": "2.10597417", + "quoteVolume": "18580513.78451419", + "isFrozen": "0", + "high24hr": "0.00000012", + "low24hr": "0.00000011" + }, + "BTC_BTS": { + "id": 14, + "last": "0.00000770", + "lowestAsk": "0.00000771", + "highestBid": "0.00000770", + "percentChange": "-0.00900900", + "baseVolume": "11.31814868", + "quoteVolume": "1422525.76536768", + "isFrozen": "0", + "high24hr": "0.00000804", + "low24hr": "0.00000764" + }, + "BTC_CLAM": { + "id": 20, + "last": "0.00058833", + "lowestAsk": "0.00059410", + "highestBid": "0.00058663", + "percentChange": "0.00896930", + "baseVolume": "2.91928382", + "quoteVolume": "5008.88307610", + "isFrozen": "0", + "high24hr": "0.00060427", + "low24hr": "0.00056130" + }, + "BTC_DASH": { + "id": 24, + "last": "0.01880000", + "lowestAsk": "0.01880610", + "highestBid": "0.01880000", + "percentChange": "-0.00407587", + "baseVolume": "121.56922496", + "quoteVolume": "6476.07273599", + "isFrozen": "0", + "high24hr": "0.01901934", + "low24hr": "0.01830550" + }, + "BTC_DGB": { + "id": 25, + "last": "0.00000162", + "lowestAsk": "0.00000164", + "highestBid": "0.00000162", + "percentChange": "-0.04142011", + "baseVolume": "16.15966014", + "quoteVolume": "9761759.50030071", + "isFrozen": "0", + "high24hr": "0.00000172", + "low24hr": "0.00000160" + }, + "BTC_DOGE": { + "id": 27, + "last": "0.00000039", + "lowestAsk": "0.00000039", + "highestBid": "0.00000038", + "percentChange": "0.02631578", + "baseVolume": "19.98598120", + "quoteVolume": "52317925.99866673", + "isFrozen": "0", + "high24hr": "0.00000039", + "low24hr": "0.00000038" + }, + "BTC_GAME": { + "id": 38, + "last": "0.00001153", + "lowestAsk": "0.00001176", + "highestBid": "0.00001154", + "percentChange": "-0.00945017", + "baseVolume": "3.82370347", + "quoteVolume": "300446.22176630", + "isFrozen": "0", + "high24hr": "0.00001391", + "low24hr": "0.00001128" + }, + "BTC_LTC": { + "id": 50, + "last": "0.01617495", + "lowestAsk": "0.01617366", + "highestBid": "0.01613601", + "percentChange": "0.06261463", + "baseVolume": "666.94447027", + "quoteVolume": "42339.40507638", + "isFrozen": "0", + "high24hr": "0.01630000", + "low24hr": "0.01497000" + }, + "BTC_MAID": { + "id": 51, + "last": "0.00002364", + "lowestAsk": "0.00002364", + "highestBid": "0.00002362", + "percentChange": "-0.03194103", + "baseVolume": "2.56267002", + "quoteVolume": "107646.79242367", + "isFrozen": "0", + "high24hr": "0.00002442", + "low24hr": "0.00002351" + }, + "BTC_OMNI": { + "id": 58, + "last": "0.00028837", + "lowestAsk": "0.00028798", + "highestBid": "0.00027236", + "percentChange": "0.00215464", + "baseVolume": "0.20882002", + "quoteVolume": "732.64551893", + "isFrozen": "0", + "high24hr": "0.00029480", + "low24hr": "0.00027000" + }, + "BTC_NAV": { + "id": 61, + "last": "0.00002838", + "lowestAsk": "0.00002850", + "highestBid": "0.00002824", + "percentChange": "0.03728070", + "baseVolume": "0.85599154", + "quoteVolume": "30239.29066641", + "isFrozen": "0", + "high24hr": "0.00002880", + "low24hr": "0.00002736" + }, + "BTC_NXT": { + "id": 69, + "last": "0.00000454", + "lowestAsk": "0.00000455", + "highestBid": "0.00000453", + "percentChange": "-0.01731601", + "baseVolume": "1.64399713", + "quoteVolume": "360674.76672284", + "isFrozen": "0", + "high24hr": "0.00000466", + "low24hr": "0.00000450" + }, + "BTC_STR": { + "id": 89, + "last": "0.00001537", + "lowestAsk": "0.00001537", + "highestBid": "0.00001533", + "percentChange": "-0.00646412", + "baseVolume": "29.15587511", + "quoteVolume": "1887833.72135812", + "isFrozen": "0", + "high24hr": "0.00001571", + "low24hr": "0.00001532" + }, + "BTC_VIA": { + "id": 97, + "last": "0.00007259", + "lowestAsk": "0.00007261", + "highestBid": "0.00007216", + "percentChange": "0.02862406", + "baseVolume": "6.00937608", + "quoteVolume": "82838.51366051", + "isFrozen": "0", + "high24hr": "0.00007528", + "low24hr": "0.00007013" + }, + "BTC_VTC": { + "id": 100, + "last": "0.00006767", + "lowestAsk": "0.00006769", + "highestBid": "0.00006727", + "percentChange": "-0.00907892", + "baseVolume": "8.10964192", + "quoteVolume": "118741.18466855", + "isFrozen": "0", + "high24hr": "0.00006995", + "low24hr": "0.00006620" + }, + "BTC_XEM": { + "id": 112, + "last": "0.00001060", + "lowestAsk": "0.00001063", + "highestBid": "0.00001058", + "percentChange": "-0.01303538", + "baseVolume": "11.47584618", + "quoteVolume": "1080591.66263058", + "isFrozen": "0", + "high24hr": "0.00001091", + "low24hr": "0.00001049" + }, + "BTC_XMR": { + "id": 114, + "last": "0.01087441", + "lowestAsk": "0.01089415", + "highestBid": "0.01088305", + "percentChange": "-0.00620981", + "baseVolume": "42.01213244", + "quoteVolume": "3857.26226555", + "isFrozen": "0", + "high24hr": "0.01102244", + "low24hr": "0.01079251" + }, + "BTC_XPM": { + "id": 116, + "last": "0.00002756", + "lowestAsk": "0.00002860", + "highestBid": "0.00002756", + "percentChange": "-0.04735568", + "baseVolume": "0.71263628", + "quoteVolume": "24729.03795341", + "isFrozen": "0", + "high24hr": "0.00002962", + "low24hr": "0.00002756" + }, + "BTC_XRP": { + "id": 117, + "last": "0.00004960", + "lowestAsk": "0.00004961", + "highestBid": "0.00004960", + "percentChange": "-0.02131018", + "baseVolume": "201.36631863", + "quoteVolume": "4023790.64131056", + "isFrozen": "0", + "high24hr": "0.00005090", + "low24hr": "0.00004933" + }, + "USDT_BTC": { + "id": 121, + "last": "7938.84630781", + "lowestAsk": "7939.11267940", + "highestBid": "7934.30612610", + "percentChange": "0.04175843", + "baseVolume": "4421897.17330826", + "quoteVolume": "561.77921782", + "isFrozen": "0", + "high24hr": "8010.00000000", + "low24hr": "7594.72485562" + }, + "USDT_DASH": { + "id": 122, + "last": "149.00000000", + "lowestAsk": "149.49998944", + "highestBid": "148.18792540", + "percentChange": "0.03081761", + "baseVolume": "163148.07688044", + "quoteVolume": "1097.22131727", + "isFrozen": "0", + "high24hr": "151.49790691", + "low24hr": "141.36289327" + }, + "USDT_LTC": { + "id": 123, + "last": "128.29999999", + "lowestAsk": "128.29999899", + "highestBid": "127.31927721", + "percentChange": "0.10600510", + "baseVolume": "1316866.42418109", + "quoteVolume": "10573.16167640", + "isFrozen": "0", + "high24hr": "129.80000000", + "low24hr": "114.19423572" + }, + "USDT_NXT": { + "id": 124, + "last": "0.03564433", + "lowestAsk": "0.03661779", + "highestBid": "0.03565562", + "percentChange": "0.01783769", + "baseVolume": "10352.62135341", + "quoteVolume": "287419.90703917", + "isFrozen": "0", + "high24hr": "0.03725000", + "low24hr": "0.03465000" + }, + "USDT_STR": { + "id": 125, + "last": "0.12153229", + "lowestAsk": "0.12192981", + "highestBid": "0.12155670", + "percentChange": "0.02869133", + "baseVolume": "119159.96377689", + "quoteVolume": "975973.89241271", + "isFrozen": "0", + "high24hr": "0.12367652", + "low24hr": "0.11783976" + }, + "USDT_XMR": { + "id": 126, + "last": "86.80322950", + "lowestAsk": "86.63121390", + "highestBid": "86.01288637", + "percentChange": "0.03842924", + "baseVolume": "44311.82773756", + "quoteVolume": "513.51239329", + "isFrozen": "0", + "high24hr": "87.30000000", + "low24hr": "83.20226300" + }, + "USDT_XRP": { + "id": 127, + "last": "0.39377688", + "lowestAsk": "0.39377649", + "highestBid": "0.39152696", + "percentChange": "0.01987867", + "baseVolume": "628653.50566678", + "quoteVolume": "1590094.01666263", + "isFrozen": "0", + "high24hr": "0.40300000", + "low24hr": "0.38130000" + }, + "XMR_BCN": { + "id": 129, + "last": "0.00001085", + "lowestAsk": "0.00001085", + "highestBid": "0.00001077", + "percentChange": "0.00370027", + "baseVolume": "0.16285090", + "quoteVolume": "15121.34000573", + "isFrozen": "0", + "high24hr": "0.00001086", + "low24hr": "0.00001076" + }, + "XMR_DASH": { + "id": 132, + "last": "1.71734933", + "lowestAsk": "1.72597923", + "highestBid": "1.70985983", + "percentChange": "-0.00135971", + "baseVolume": "199.20221390", + "quoteVolume": "116.70250730", + "isFrozen": "0", + "high24hr": "1.74792000", + "low24hr": "1.69000000" + }, + "XMR_LTC": { + "id": 137, + "last": "1.47455980", + "lowestAsk": "1.48934226", + "highestBid": "1.47456588", + "percentChange": "0.06048168", + "baseVolume": "170.93153487", + "quoteVolume": "118.22421521", + "isFrozen": "0", + "high24hr": "1.49999000", + "low24hr": "1.37511020" + }, + "XMR_MAID": { + "id": 138, + "last": "0.00215539", + "lowestAsk": "0.00218535", + "highestBid": "0.00215074", + "percentChange": "-0.01503907", + "baseVolume": "11.27396401", + "quoteVolume": "5131.12258542", + "isFrozen": "0", + "high24hr": "0.00219909", + "low24hr": "0.00215539" + }, + "XMR_NXT": { + "id": 140, + "last": "0.00040927", + "lowestAsk": "0.00041471", + "highestBid": "0.00040892", + "percentChange": "-0.02296545", + "baseVolume": "1.90717544", + "quoteVolume": "4664.12738280", + "isFrozen": "0", + "high24hr": "0.00042734", + "low24hr": "0.00040123" + }, + "BTC_ETH": { + "id": 148, + "last": "0.03079500", + "lowestAsk": "0.03081459", + "highestBid": "0.03079004", + "percentChange": "0.00818332", + "baseVolume": "155.94348625", + "quoteVolume": "5059.54434879", + "isFrozen": "0", + "high24hr": "0.03119996", + "low24hr": "0.03042230" + }, + "USDT_ETH": { + "id": 149, + "last": "243.56443038", + "lowestAsk": "244.56399997", + "highestBid": "243.60640010", + "percentChange": "0.04452331", + "baseVolume": "883511.34773111", + "quoteVolume": "3668.33116661", + "isFrozen": "0", + "high24hr": "247.86593850", + "low24hr": "232.63838377" + }, + "BTC_SC": { + "id": 150, + "last": "0.00000039", + "lowestAsk": "0.00000040", + "highestBid": "0.00000039", + "percentChange": "0.00000000", + "baseVolume": "1.87511642", + "quoteVolume": "4769865.10799687", + "isFrozen": "0", + "high24hr": "0.00000040", + "low24hr": "0.00000039" + }, + "BTC_FCT": { + "id": 155, + "last": "0.00073952", + "lowestAsk": "0.00074498", + "highestBid": "0.00073982", + "percentChange": "-0.02689615", + "baseVolume": "10.36741741", + "quoteVolume": "13773.13207446", + "isFrozen": "0", + "high24hr": "0.00078617", + "low24hr": "0.00072961" + }, + "BTC_DCR": { + "id": 162, + "last": "0.00346266", + "lowestAsk": "0.00346266", + "highestBid": "0.00343891", + "percentChange": "0.00119414", + "baseVolume": "2.34160137", + "quoteVolume": "684.41654989", + "isFrozen": "0", + "high24hr": "0.00350651", + "low24hr": "0.00336074" + }, + "BTC_LSK": { + "id": 163, + "last": "0.00025799", + "lowestAsk": "0.00025836", + "highestBid": "0.00025678", + "percentChange": "0.02907857", + "baseVolume": "5.21884194", + "quoteVolume": "20377.01855963", + "isFrozen": "0", + "high24hr": "0.00026000", + "low24hr": "0.00024928" + }, + "ETH_LSK": { + "id": 166, + "last": "0.00836598", + "lowestAsk": "0.00841578", + "highestBid": "0.00828726", + "percentChange": "0.02650061", + "baseVolume": "0.76656131", + "quoteVolume": "93.01338152", + "isFrozen": "0", + "high24hr": "0.00849344", + "low24hr": "0.00815000" + }, + "BTC_LBC": { + "id": 167, + "last": "0.00000437", + "lowestAsk": "0.00000445", + "highestBid": "0.00000438", + "percentChange": "0.01627906", + "baseVolume": "3.00058286", + "quoteVolume": "699495.42159049", + "isFrozen": "0", + "high24hr": "0.00000459", + "low24hr": "0.00000411" + }, + "BTC_STEEM": { + "id": 168, + "last": "0.00005167", + "lowestAsk": "0.00005160", + "highestBid": "0.00005127", + "percentChange": "-0.01449551", + "baseVolume": "4.42639165", + "quoteVolume": "85043.49213557", + "isFrozen": "0", + "high24hr": "0.00005363", + "low24hr": "0.00005040" + }, + "ETH_STEEM": { + "id": 169, + "last": "0.00167610", + "lowestAsk": "0.00168151", + "highestBid": "0.00166580", + "percentChange": "-0.03115606", + "baseVolume": "6.47349009", + "quoteVolume": "3859.75860945", + "isFrozen": "0", + "high24hr": "0.00175423", + "low24hr": "0.00165255" + }, + "BTC_ETC": { + "id": 171, + "last": "0.00102820", + "lowestAsk": "0.00103251", + "highestBid": "0.00102764", + "percentChange": "-0.02908404", + "baseVolume": "10.91386394", + "quoteVolume": "10520.96374951", + "isFrozen": "0", + "high24hr": "0.00106777", + "low24hr": "0.00102551" + }, + "ETH_ETC": { + "id": 172, + "last": "0.03342225", + "lowestAsk": "0.03345178", + "highestBid": "0.03340000", + "percentChange": "-0.03665230", + "baseVolume": "50.23223183", + "quoteVolume": "1477.91871918", + "isFrozen": "0", + "high24hr": "0.03469387", + "low24hr": "0.03342225" + }, + "USDT_ETC": { + "id": 173, + "last": "8.15001414", + "lowestAsk": "8.17000000", + "highestBid": "8.15001491", + "percentChange": "0.01628235", + "baseVolume": "143484.54569522", + "quoteVolume": "17511.35515526", + "isFrozen": "0", + "high24hr": "8.38997233", + "low24hr": "7.88066582" + }, + "BTC_REP": { + "id": 174, + "last": "0.00238110", + "lowestAsk": "0.00238013", + "highestBid": "0.00236841", + "percentChange": "0.00882953", + "baseVolume": "3.58890958", + "quoteVolume": "1490.86836755", + "isFrozen": "0", + "high24hr": "0.00249520", + "low24hr": "0.00233000" + }, + "USDT_REP": { + "id": 175, + "last": "18.81359647", + "lowestAsk": "18.81359647", + "highestBid": "18.73413490", + "percentChange": "0.04529910", + "baseVolume": "21917.80239715", + "quoteVolume": "1157.81116881", + "isFrozen": "0", + "high24hr": "19.50000000", + "low24hr": "18.07435000" + }, + "ETH_REP": { + "id": 176, + "last": "0.07729862", + "lowestAsk": "0.07729862", + "highestBid": "0.07691404", + "percentChange": "0.00550880", + "baseVolume": "43.83047186", + "quoteVolume": "566.61066154", + "isFrozen": "0", + "high24hr": "0.08044678", + "low24hr": "0.07610657" + }, + "BTC_ARDR": { + "id": 177, + "last": "0.00001248", + "lowestAsk": "0.00001252", + "highestBid": "0.00001248", + "percentChange": "-0.01732283", + "baseVolume": "3.03108905", + "quoteVolume": "238083.87187020", + "isFrozen": "0", + "high24hr": "0.00001333", + "low24hr": "0.00001220" + }, + "BTC_ZEC": { + "id": 178, + "last": "0.00990515", + "lowestAsk": "0.00993296", + "highestBid": "0.00990500", + "percentChange": "-0.02472465", + "baseVolume": "6.83567402", + "quoteVolume": "679.67342401", + "isFrozen": "0", + "high24hr": "0.01024922", + "low24hr": "0.00990501" + }, + "ETH_ZEC": { + "id": 179, + "last": "0.32250027", + "lowestAsk": "0.32473808", + "highestBid": "0.32237743", + "percentChange": "-0.03832020", + "baseVolume": "20.70785072", + "quoteVolume": "63.08405800", + "isFrozen": "0", + "high24hr": "0.33600013", + "low24hr": "0.32250027" + }, + "USDT_ZEC": { + "id": 180, + "last": "78.59493101", + "lowestAsk": "79.24350590", + "highestBid": "78.59493102", + "percentChange": "0.01370161", + "baseVolume": "22621.88453883", + "quoteVolume": "285.13456697", + "isFrozen": "0", + "high24hr": "81.19999999", + "low24hr": "77.53260899" + }, + "XMR_ZEC": { + "id": 181, + "last": "0.91217810", + "lowestAsk": "0.91363635", + "highestBid": "0.90478668", + "percentChange": "-0.01510586", + "baseVolume": "18.27667017", + "quoteVolume": "19.92803337", + "isFrozen": "0", + "high24hr": "0.93682194", + "low24hr": "0.90298672" + }, + "BTC_STRAT": { + "id": 182, + "last": "0.00011859", + "lowestAsk": "0.00011955", + "highestBid": "0.00011869", + "percentChange": "-0.04006799", + "baseVolume": "3.69185582", + "quoteVolume": "30477.03971572", + "isFrozen": "0", + "high24hr": "0.00012497", + "low24hr": "0.00011892" + }, + "BTC_PASC": { + "id": 184, + "last": "0.00002812", + "lowestAsk": "0.00002815", + "highestBid": "0.00002814", + "percentChange": "-0.01678321", + "baseVolume": "1.25124963", + "quoteVolume": "45122.06215166", + "isFrozen": "0", + "high24hr": "0.00002870", + "low24hr": "0.00002670" + }, + "BTC_GNT": { + "id": 185, + "last": "0.00001257", + "lowestAsk": "0.00001266", + "highestBid": "0.00001260", + "percentChange": "0.06887755", + "baseVolume": "4.93328876", + "quoteVolume": "404745.01492216", + "isFrozen": "0", + "high24hr": "0.00001291", + "low24hr": "0.00001152" + }, + "ETH_GNT": { + "id": 186, + "last": "0.00040752", + "lowestAsk": "0.00041263", + "highestBid": "0.00040681", + "percentChange": "0.05777916", + "baseVolume": "33.26795789", + "quoteVolume": "85767.44098332", + "isFrozen": "0", + "high24hr": "0.00041473", + "low24hr": "0.00037826" + }, + "BTC_ZRX": { + "id": 192, + "last": "0.00004091", + "lowestAsk": "0.00004091", + "highestBid": "0.00004080", + "percentChange": "-0.00559066", + "baseVolume": "2.07845804", + "quoteVolume": "49776.28138902", + "isFrozen": "0", + "high24hr": "0.00004218", + "low24hr": "0.00004081" + }, + "ETH_ZRX": { + "id": 193, + "last": "0.00132759", + "lowestAsk": "0.00132744", + "highestBid": "0.00132743", + "percentChange": "-0.01595125", + "baseVolume": "22.37346857", + "quoteVolume": "16548.11602204", + "isFrozen": "0", + "high24hr": "0.00137937", + "low24hr": "0.00132759" + }, + "BTC_CVC": { + "id": 194, + "last": "0.00001081", + "lowestAsk": "0.00001081", + "highestBid": "0.00001074", + "percentChange": "0.01217228", + "baseVolume": "0.75331703", + "quoteVolume": "68777.88900435", + "isFrozen": "0", + "high24hr": "0.00001123", + "low24hr": "0.00001070" + }, + "ETH_CVC": { + "id": 195, + "last": "0.00034931", + "lowestAsk": "0.00035104", + "highestBid": "0.00034931", + "percentChange": "-0.00498490", + "baseVolume": "7.76889158", + "quoteVolume": "22217.14581100", + "isFrozen": "0", + "high24hr": "0.00036357", + "low24hr": "0.00034633" + }, + "BTC_OMG": { + "id": 196, + "last": "0.00025135", + "lowestAsk": "0.00025240", + "highestBid": "0.00025135", + "percentChange": "0.00741482", + "baseVolume": "1.25616463", + "quoteVolume": "4963.85040878", + "isFrozen": "0", + "high24hr": "0.00026071", + "low24hr": "0.00024544" + }, + "ETH_OMG": { + "id": 197, + "last": "0.00823187", + "lowestAsk": "0.00820725", + "highestBid": "0.00816185", + "percentChange": "0.01803604", + "baseVolume": "8.05512623", + "quoteVolume": "971.21438737", + "isFrozen": "0", + "high24hr": "0.00850000", + "low24hr": "0.00812951" + }, + "BTC_GAS": { + "id": 198, + "last": "0.00039654", + "lowestAsk": "0.00039595", + "highestBid": "0.00038985", + "percentChange": "-0.00284155", + "baseVolume": "0.77232780", + "quoteVolume": "1916.80182489", + "isFrozen": "0", + "high24hr": "0.00041046", + "low24hr": "0.00038985" + }, + "ETH_GAS": { + "id": 199, + "last": "0.01300000", + "lowestAsk": "0.01309755", + "highestBid": "0.01262073", + "percentChange": "-0.01555871", + "baseVolume": "3.04307877", + "quoteVolume": "227.12349806", + "isFrozen": "0", + "high24hr": "0.01390000", + "low24hr": "0.01271658" + }, + "BTC_STORJ": { + "id": 200, + "last": "0.00003506", + "lowestAsk": "0.00003515", + "highestBid": "0.00003506", + "percentChange": "0.00228702", + "baseVolume": "0.59303686", + "quoteVolume": "16796.76970761", + "isFrozen": "0", + "high24hr": "0.00003583", + "low24hr": "0.00003460" + }, + "BTC_EOS": { + "id": 201, + "last": "0.00080185", + "lowestAsk": "0.00080171", + "highestBid": "0.00080063", + "percentChange": "-0.00661554", + "baseVolume": "18.59593565", + "quoteVolume": "23099.92132301", + "isFrozen": "0", + "high24hr": "0.00081224", + "low24hr": "0.00079800" + }, + "ETH_EOS": { + "id": 202, + "last": "0.02596528", + "lowestAsk": "0.02603759", + "highestBid": "0.02596530", + "percentChange": "-0.01068667", + "baseVolume": "57.85287084", + "quoteVolume": "2204.63200411", + "isFrozen": "0", + "high24hr": "0.02644714", + "low24hr": "0.02596528" + }, + "USDT_EOS": { + "id": 203, + "last": "6.33455816", + "lowestAsk": "6.38358433", + "highestBid": "6.33676472", + "percentChange": "0.01939777", + "baseVolume": "278043.19290915", + "quoteVolume": "43736.05905905", + "isFrozen": "0", + "high24hr": "6.49451490", + "low24hr": "6.06318315" + }, + "BTC_SNT": { + "id": 204, + "last": "0.00000380", + "lowestAsk": "0.00000381", + "highestBid": "0.00000377", + "percentChange": "0.01063829", + "baseVolume": "1.74882337", + "quoteVolume": "463292.11309769", + "isFrozen": "0", + "high24hr": "0.00000391", + "low24hr": "0.00000365" + }, + "ETH_SNT": { + "id": 205, + "last": "0.00012341", + "lowestAsk": "0.00012330", + "highestBid": "0.00012281", + "percentChange": "-0.00684049", + "baseVolume": "11.98247233", + "quoteVolume": "97344.62846475", + "isFrozen": "0", + "high24hr": "0.00012595", + "low24hr": "0.00011976" + }, + "USDT_SNT": { + "id": 206, + "last": "0.03002016", + "lowestAsk": "0.03099489", + "highestBid": "0.02965894", + "percentChange": "0.05260397", + "baseVolume": "3314.06637263", + "quoteVolume": "111527.99375151", + "isFrozen": "0", + "high24hr": "0.03099999", + "low24hr": "0.02852000" + }, + "BTC_KNC": { + "id": 207, + "last": "0.00003377", + "lowestAsk": "0.00003373", + "highestBid": "0.00003333", + "percentChange": "-0.00295246", + "baseVolume": "1.07605151", + "quoteVolume": "31066.52899332", + "isFrozen": "0", + "high24hr": "0.00003550", + "low24hr": "0.00003371" + }, + "ETH_KNC": { + "id": 208, + "last": "0.00109269", + "lowestAsk": "0.00114156", + "highestBid": "0.00108615", + "percentChange": "-0.02501070", + "baseVolume": "3.97445644", + "quoteVolume": "3526.69313018", + "isFrozen": "0", + "high24hr": "0.00115000", + "low24hr": "0.00108891" + }, + "USDT_KNC": { + "id": 209, + "last": "0.26815800", + "lowestAsk": "0.27081578", + "highestBid": "0.26681722", + "percentChange": "0.04138446", + "baseVolume": "3460.19386639", + "quoteVolume": "12879.73674180", + "isFrozen": "0", + "high24hr": "0.27487268", + "low24hr": "0.24150034" + }, + "BTC_BAT": { + "id": 210, + "last": "0.00004095", + "lowestAsk": "0.00004095", + "highestBid": "0.00004088", + "percentChange": "-0.02777777", + "baseVolume": "2.13275613", + "quoteVolume": "51122.24173657", + "isFrozen": "0", + "high24hr": "0.00004245", + "low24hr": "0.00004087" + }, + "ETH_BAT": { + "id": 211, + "last": "0.00133065", + "lowestAsk": "0.00133346", + "highestBid": "0.00132716", + "percentChange": "-0.03673809", + "baseVolume": "15.19774324", + "quoteVolume": "11202.68186565", + "isFrozen": "0", + "high24hr": "0.00138539", + "low24hr": "0.00133065" + }, + "USDT_BAT": { + "id": 212, + "last": "0.32726271", + "lowestAsk": "0.32705989", + "highestBid": "0.32445561", + "percentChange": "0.01806377", + "baseVolume": "19328.23384182", + "quoteVolume": "58700.42769184", + "isFrozen": "0", + "high24hr": "0.33689089", + "low24hr": "0.32026548" + }, + "BTC_LOOM": { + "id": 213, + "last": "0.00001042", + "lowestAsk": "0.00001048", + "highestBid": "0.00001042", + "percentChange": "0.03066271", + "baseVolume": "5.48711026", + "quoteVolume": "522861.14578247", + "isFrozen": "0", + "high24hr": "0.00001100", + "low24hr": "0.00000982" + }, + "ETH_LOOM": { + "id": 214, + "last": "0.00033930", + "lowestAsk": "0.00034802", + "highestBid": "0.00033766", + "percentChange": "0.02818181", + "baseVolume": "65.66658897", + "quoteVolume": "193088.49239467", + "isFrozen": "0", + "high24hr": "0.00035769", + "low24hr": "0.00032567" + }, + "USDT_LOOM": { + "id": 215, + "last": "0.08366370", + "lowestAsk": "0.08582526", + "highestBid": "0.08138381", + "percentChange": "0.10209527", + "baseVolume": "7884.22722657", + "quoteVolume": "94061.17501115", + "isFrozen": "0", + "high24hr": "0.08800000", + "low24hr": "0.07699900" + }, + "USDT_DOGE": { + "id": 216, + "last": "0.00303376", + "lowestAsk": "0.00304854", + "highestBid": "0.00303372", + "percentChange": "0.02803427", + "baseVolume": "30256.35268923", + "quoteVolume": "10052886.96556405", + "isFrozen": "0", + "high24hr": "0.00305492", + "low24hr": "0.00294256" + }, + "USDT_GNT": { + "id": 217, + "last": "0.09841380", + "lowestAsk": "0.10132826", + "highestBid": "0.09879707", + "percentChange": "0.09122782", + "baseVolume": "13022.42294546", + "quoteVolume": "136510.36772013", + "isFrozen": "0", + "high24hr": "0.10259935", + "low24hr": "0.08921015" + }, + "USDT_LSK": { + "id": 218, + "last": "2.04000000", + "lowestAsk": "2.04000000", + "highestBid": "2.02319825", + "percentChange": "0.05997011", + "baseVolume": "13223.12589230", + "quoteVolume": "6568.77436503", + "isFrozen": "0", + "high24hr": "2.07878065", + "low24hr": "1.90125984" + }, + "USDT_SC": { + "id": 219, + "last": "0.00310875", + "lowestAsk": "0.00315390", + "highestBid": "0.00310953", + "percentChange": "0.02377361", + "baseVolume": "25558.80796699", + "quoteVolume": "8307626.97734275", + "isFrozen": "0", + "high24hr": "0.00315902", + "low24hr": "0.00301287" + }, + "USDT_ZRX": { + "id": 220, + "last": "0.32372110", + "lowestAsk": "0.32556376", + "highestBid": "0.32372112", + "percentChange": "0.02641624", + "baseVolume": "25505.45281006", + "quoteVolume": "78505.36961414", + "isFrozen": "0", + "high24hr": "0.33012685", + "low24hr": "0.31581054" + }, + "BTC_QTUM": { + "id": 221, + "last": "0.00039711", + "lowestAsk": "0.00039946", + "highestBid": "0.00039745", + "percentChange": "0.01102398", + "baseVolume": "9.94417285", + "quoteVolume": "23898.37003779", + "isFrozen": "0", + "high24hr": "0.00044164", + "low24hr": "0.00039430" + }, + "ETH_QTUM": { + "id": 222, + "last": "0.01279123", + "lowestAsk": "0.01292445", + "highestBid": "0.01286128", + "percentChange": "0.01196680", + "baseVolume": "19.21848012", + "quoteVolume": "1428.71665478", + "isFrozen": "0", + "high24hr": "0.01445578", + "low24hr": "0.01279123" + }, + "USDT_QTUM": { + "id": 223, + "last": "3.15643449", + "lowestAsk": "3.19643113", + "highestBid": "3.15135845", + "percentChange": "0.04613305", + "baseVolume": "39682.13887751", + "quoteVolume": "12370.15246589", + "isFrozen": "0", + "high24hr": "3.38228600", + "low24hr": "3.01724000" + }, + "USDC_BTC": { + "id": 224, + "last": "7962.50000000", + "lowestAsk": "7977.13341202", + "highestBid": "7961.71516578", + "percentChange": "0.04403385", + "baseVolume": "3482664.85806597", + "quoteVolume": "441.87385476", + "isFrozen": "0", + "high24hr": "8077.47999788", + "low24hr": "7608.58328444" + }, + "USDC_ETH": { + "id": 225, + "last": "245.36055774", + "lowestAsk": "246.00046378", + "highestBid": "245.35955433", + "percentChange": "0.05094127", + "baseVolume": "501153.45720283", + "quoteVolume": "2047.00238213", + "isFrozen": "0", + "high24hr": "248.44047988", + "low24hr": "233.31933799" + }, + "USDC_USDT": { + "id": 226, + "last": "1.00420002", + "lowestAsk": "1.00519997", + "highestBid": "1.00420002", + "percentChange": "0.00170380", + "baseVolume": "57933.50068393", + "quoteVolume": "57719.93500183", + "isFrozen": "0", + "high24hr": "1.00849999", + "low24hr": "1.00048874" + }, + "BTC_MANA": { + "id": 229, + "last": "0.00000745", + "lowestAsk": "0.00000743", + "highestBid": "0.00000740", + "percentChange": "-0.00534045", + "baseVolume": "2.70084416", + "quoteVolume": "362727.87722537", + "isFrozen": "0", + "high24hr": "0.00000765", + "low24hr": "0.00000734" + }, + "ETH_MANA": { + "id": 230, + "last": "0.00024138", + "lowestAsk": "0.00024135", + "highestBid": "0.00024084", + "percentChange": "-0.00829909", + "baseVolume": "22.07735950", + "quoteVolume": "90624.84430328", + "isFrozen": "0", + "high24hr": "0.00025004", + "low24hr": "0.00024027" + }, + "USDT_MANA": { + "id": 231, + "last": "0.05874674", + "lowestAsk": "0.06023530", + "highestBid": "0.05875324", + "percentChange": "0.02910433", + "baseVolume": "3529.09794786", + "quoteVolume": "59699.03615938", + "isFrozen": "0", + "high24hr": "0.06000000", + "low24hr": "0.05685000" + }, + "BTC_BNT": { + "id": 232, + "last": "0.00008844", + "lowestAsk": "0.00008843", + "highestBid": "0.00008793", + "percentChange": "-0.02609844", + "baseVolume": "0.06242819", + "quoteVolume": "708.36829451", + "isFrozen": "0", + "high24hr": "0.00009088", + "low24hr": "0.00008666" + }, + "ETH_BNT": { + "id": 233, + "last": "0.00287809", + "lowestAsk": "0.00288685", + "highestBid": "0.00285346", + "percentChange": "0.00985614", + "baseVolume": "14.29896888", + "quoteVolume": "5013.99102334", + "isFrozen": "0", + "high24hr": "0.00288000", + "low24hr": "0.00284999" + }, + "USDT_BNT": { + "id": 234, + "last": "0.69821937", + "lowestAsk": "0.70982400", + "highestBid": "0.69821937", + "percentChange": "0.00089890", + "baseVolume": "363.53451604", + "quoteVolume": "514.58745683", + "isFrozen": "0", + "high24hr": "0.70982964", + "low24hr": "0.68340485" + }, + "BTC_BCHABC": { + "id": 236, + "last": "0.04900933", + "lowestAsk": "0.04904416", + "highestBid": "0.04900933", + "percentChange": "-0.00782334", + "baseVolume": "36.20758372", + "quoteVolume": "733.46224700", + "isFrozen": "0", + "high24hr": "0.05032733", + "low24hr": "0.04900932" + }, + "USDC_BCHABC": { + "id": 237, + "last": "389.35423311", + "lowestAsk": "393.59958829", + "highestBid": "389.35541111", + "percentChange": "0.03548741", + "baseVolume": "135429.66946378", + "quoteVolume": "345.71602989", + "isFrozen": "0", + "high24hr": "401.17968999", + "low24hr": "376.01059082" + }, + "BTC_BCHSV": { + "id": 238, + "last": "0.02387932", + "lowestAsk": "0.02389139", + "highestBid": "0.02375483", + "percentChange": "0.00192628", + "baseVolume": "107.87823714", + "quoteVolume": "4528.05891976", + "isFrozen": "0", + "high24hr": "0.02449024", + "low24hr": "0.02353105" + }, + "USDC_BCHSV": { + "id": 239, + "last": "188.54622778", + "lowestAsk": "190.89997204", + "highestBid": "188.54624615", + "percentChange": "0.03030725", + "baseVolume": "219303.91833302", + "quoteVolume": "1158.73869926", + "isFrozen": "0", + "high24hr": "194.44671873", + "low24hr": "180.49999776" + }, + "USDC_XRP": { + "id": 240, + "last": "0.39275049", + "lowestAsk": "0.39644645", + "highestBid": "0.39407291", + "percentChange": "0.00705997", + "baseVolume": "199920.97825116", + "quoteVolume": "501260.01445723", + "isFrozen": "0", + "high24hr": "0.40499909", + "low24hr": "0.38130058" + }, + "USDC_XMR": { + "id": 241, + "last": "87.18629626", + "lowestAsk": "87.13843846", + "highestBid": "86.23864800", + "percentChange": "0.04513660", + "baseVolume": "99062.18940901", + "quoteVolume": "1147.67641269", + "isFrozen": "0", + "high24hr": "87.99994711", + "low24hr": "83.40045078" + }, + "USDC_STR": { + "id": 242, + "last": "0.12152532", + "lowestAsk": "0.12344054", + "highestBid": "0.12186123", + "percentChange": "0.02557729", + "baseVolume": "21935.95722630", + "quoteVolume": "178840.89704069", + "isFrozen": "0", + "high24hr": "0.12485687", + "low24hr": "0.11824094" + }, + "USDC_DOGE": { + "id": 243, + "last": "0.00303215", + "lowestAsk": "0.00306000", + "highestBid": "0.00303215", + "percentChange": "0.01921008", + "baseVolume": "2750.23843586", + "quoteVolume": "907350.09402904", + "isFrozen": "0", + "high24hr": "0.00310197", + "low24hr": "0.00297500" + }, + "USDC_LTC": { + "id": 244, + "last": "128.93961490", + "lowestAsk": "128.93961440", + "highestBid": "128.27905224", + "percentChange": "0.10369106", + "baseVolume": "566449.26199652", + "quoteVolume": "4496.10093795", + "isFrozen": "0", + "high24hr": "129.99999999", + "low24hr": "114.11407952" + }, + "USDC_ZEC": { + "id": 245, + "last": "79.00000000", + "lowestAsk": "79.66042211", + "highestBid": "78.78616778", + "percentChange": "0.00697317", + "baseVolume": "18301.41164199", + "quoteVolume": "229.40589669", + "isFrozen": "0", + "high24hr": "81.51460533", + "low24hr": "77.79410548" + }, + "BTC_FOAM": { + "id": 246, + "last": "0.00000550", + "lowestAsk": "0.00000550", + "highestBid": "0.00000545", + "percentChange": "0.05769230", + "baseVolume": "0.89731724", + "quoteVolume": "166155.79008468", + "isFrozen": "0", + "high24hr": "0.00000555", + "low24hr": "0.00000515" + }, + "USDC_FOAM": { + "id": 247, + "last": "0.04380999", + "lowestAsk": "0.04381000", + "highestBid": "0.04380999", + "percentChange": "0.08844695", + "baseVolume": "722.65429834", + "quoteVolume": "16885.98552482", + "isFrozen": "0", + "high24hr": "0.04381000", + "low24hr": "0.03968067" + }, + "BTC_NMR": { + "id": 248, + "last": "0.00086057", + "lowestAsk": "0.00089787", + "highestBid": "0.00086212", + "percentChange": "0.06548385", + "baseVolume": "1.64609151", + "quoteVolume": "1912.03148904", + "isFrozen": "0", + "high24hr": "0.00090549", + "low24hr": "0.00079044" + }, + "BTC_POLY": { + "id": 249, + "last": "0.00001306", + "lowestAsk": "0.00001311", + "highestBid": "0.00001306", + "percentChange": "0.08471760", + "baseVolume": "1.91462892", + "quoteVolume": "152996.00496795", + "isFrozen": "0", + "high24hr": "0.00001311", + "low24hr": "0.00001178" + }, + "BTC_LPT": { + "id": 250, + "last": "0.00062300", + "lowestAsk": "0.00062500", + "highestBid": "0.00062300", + "percentChange": "-0.01954612", + "baseVolume": "2.49829681", + "quoteVolume": "4080.23729692", + "isFrozen": "0", + "high24hr": "0.00063805", + "low24hr": "0.00057190" + }, + "BTC_GRIN": { + "id": 251, + "last": "0.00038601", + "lowestAsk": "0.00038831", + "highestBid": "0.00038600", + "percentChange": "-0.01686065", + "baseVolume": "24.37429922", + "quoteVolume": "62838.47716938", + "isFrozen": "0", + "high24hr": "0.00041400", + "low24hr": "0.00036639" + }, + "USDC_GRIN": { + "id": 252, + "last": "3.11998999", + "lowestAsk": "3.10999999", + "highestBid": "3.04000004", + "percentChange": "0.02294753", + "baseVolume": "23814.63241472", + "quoteVolume": "7937.02292983", + "isFrozen": "0", + "high24hr": "3.16500000", + "low24hr": "2.89999999" + }, + "BTC_ATOM": { + "id": 253, + "last": "0.00074800", + "lowestAsk": "0.00074909", + "highestBid": "0.00074800", + "percentChange": "-0.02023708", + "baseVolume": "39.67100725", + "quoteVolume": "52381.46699854", + "isFrozen": "0", + "high24hr": "0.00077303", + "low24hr": "0.00074568" + }, + "USDC_ATOM": { + "id": 254, + "last": "5.94846938", + "lowestAsk": "5.98266834", + "highestBid": "5.91425840", + "percentChange": "0.00578678", + "baseVolume": "8589.72151742", + "quoteVolume": "1435.15971856", + "isFrozen": "0", + "high24hr": "6.13222707", + "low24hr": "5.77734693" + }, + "USDT_ATOM": { + "id": 255, + "last": "5.93000005", + "lowestAsk": "5.96804558", + "highestBid": "5.91263924", + "percentChange": "0.02241380", + "baseVolume": "125149.60868108", + "quoteVolume": "21087.06757208", + "isFrozen": "0", + "high24hr": "6.09972825", + "low24hr": "5.74700000" + } + }, + "queryString": "command=returnTicker", + "bodyParams": "\u003cnil\u003e", + "headers": { + "Key": [ + "" + ] + } + } + ] + }, + "/tradingApi": { + "POST": [ + { + "data": { + "DASH": "XdtARx7FhLAqz4aXx6A9zpVXHzAcMjTyvi", + "LTC": "LaqbMc97SWBHpL7PeXNDDkgjWXC637ybGi", + "MAID": "1DonoYTRh83D9z8GLSMK65Czrcecb7R3VF" + }, + "queryString": "", + "bodyParams": "command=returnDepositAddresses\u0026nonce=1560218814364717859", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "Key": [ + "" + ], + "Sign": [ + "0bbe2891561d0370c90eaae6490f891ff2b1c6cbde6e1e7cdf5268de00723702ed6c2354ec058af150caecc723f571cd1c6f5d25eaba9c376d805f3462219de1" + ] + } + }, + { + "data": { + "response": "Withdrew 0.0 LTC." + }, + "queryString": "", + "bodyParams": "address=1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB\u0026amount=0\u0026command=withdraw\u0026currency=LTC\u0026nonce=1560225458841479798", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "Key": [ + "" + ], + "Sign": [ + "94f9d1a32849f95fd9b895ba71f1d1a1366d01c970f17ceb66391a5629b6c61a8d5d6195acab570615996f5f0274330393d68713d0a39b7335b2fd240abd54f4" + ] + } + }, + { + "data": { + "success": 1, + "orderNumber": "514851232549", + "resultingTrades": { + "BTC_ETH": [] + } + }, + "queryString": "", + "bodyParams": "command=moveOrder\u0026nonce=1560225718965364448\u0026orderNumber=1337\u0026rate=1337", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "Key": [ + "" + ], + "Sign": [ + "54133cb80288b7d35407333f91bcf5e48f6a03b5e59472cd81e26c5175b4225885e12b708da623a0841ac901cb3da220273239917d65920ced3af8d39796d182" + ] + } + }, + { + "data": { + "BTC_ARDR": [], + "BTC_ATOM": [], + "BTC_BAT": [], + "BTC_BCHABC": [], + "BTC_BCHSV": [], + "BTC_BCN": [], + "BTC_BNT": [], + "BTC_BTS": [], + "BTC_CLAM": [], + "BTC_CVC": [], + "BTC_DASH": [], + "BTC_DCR": [], + "BTC_DGB": [], + "BTC_DOGE": [], + "BTC_EOS": [], + "BTC_ETC": [], + "BTC_ETH": [], + "BTC_FCT": [], + "BTC_FOAM": [], + "BTC_GAME": [], + "BTC_GAS": [], + "BTC_GNT": [], + "BTC_GRIN": [], + "BTC_KNC": [], + "BTC_LBC": [], + "BTC_LOOM": [], + "BTC_LPT": [], + "BTC_LSK": [], + "BTC_LTC": [], + "BTC_MAID": [], + "BTC_MANA": [], + "BTC_NAV": [], + "BTC_NMR": [], + "BTC_NXT": [], + "BTC_OMG": [], + "BTC_OMNI": [], + "BTC_PASC": [], + "BTC_POLY": [], + "BTC_QTUM": [], + "BTC_REP": [], + "BTC_SC": [], + "BTC_SNT": [], + "BTC_STEEM": [], + "BTC_STORJ": [], + "BTC_STR": [], + "BTC_STRAT": [], + "BTC_VIA": [], + "BTC_VTC": [], + "BTC_XEM": [], + "BTC_XMR": [], + "BTC_XPM": [], + "BTC_XRP": [], + "BTC_ZEC": [], + "BTC_ZRX": [], + "ETH_BAT": [], + "ETH_BNT": [], + "ETH_CVC": [], + "ETH_EOS": [], + "ETH_ETC": [], + "ETH_GAS": [], + "ETH_GNT": [], + "ETH_KNC": [], + "ETH_LOOM": [], + "ETH_LSK": [], + "ETH_MANA": [], + "ETH_OMG": [], + "ETH_QTUM": [], + "ETH_REP": [], + "ETH_SNT": [], + "ETH_STEEM": [], + "ETH_ZEC": [], + "ETH_ZRX": [], + "USDC_ATOM": [], + "USDC_BCHABC": [], + "USDC_BCHSV": [], + "USDC_BTC": [], + "USDC_DOGE": [], + "USDC_ETH": [], + "USDC_FOAM": [], + "USDC_GRIN": [], + "USDC_LTC": [], + "USDC_STR": [], + "USDC_USDT": [], + "USDC_XMR": [], + "USDC_XRP": [], + "USDC_ZEC": [], + "USDT_ATOM": [], + "USDT_BAT": [], + "USDT_BNT": [], + "USDT_BTC": [], + "USDT_DASH": [], + "USDT_DOGE": [], + "USDT_EOS": [], + "USDT_ETC": [], + "USDT_ETH": [], + "USDT_GNT": [], + "USDT_KNC": [], + "USDT_LOOM": [], + "USDT_LSK": [], + "USDT_LTC": [], + "USDT_MANA": [], + "USDT_NXT": [], + "USDT_QTUM": [], + "USDT_REP": [], + "USDT_SC": [], + "USDT_SNT": [], + "USDT_STR": [], + "USDT_XMR": [], + "USDT_XRP": [], + "USDT_ZEC": [], + "USDT_ZRX": [], + "XMR_BCN": [], + "XMR_DASH": [], + "XMR_LTC": [], + "XMR_MAID": [], + "XMR_NXT": [], + "XMR_ZEC": [] + }, + "queryString": "", + "bodyParams": "command=returnOpenOrders\u0026currencyPair=all\u0026nonce=1560225851841503490", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "Key": [ + "" + ], + "Sign": [ + "c27f0881fc635d76be8f886b44af2c6e2bd8852bcf4c87f48ce8492236092678371dbf2303b623bd4c8404556caadfb5422c62e30484e29ffdb2905c62623e3f" + ] + } + }, + { + "data": { + "success": 1, + "amount": "50.00000000", + "message": "Order #1 canceled." + }, + "queryString": "", + "bodyParams": "command=cancelOrder\u0026nonce=1560225940514896227\u0026orderNumber=1", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "Key": [ + "" + ], + "Sign": [ + "18162a3bb203900573424e8b4fd5e9b6bae02c98e8d2612866f129b6da3fd0d3e41ac8c4e914f48fa75544b6106c79932312c1085c038307b2802667356b6434" + ] + } + }, + { + "data": { + "orderNumber": "514845991795", + "resultingTrades": [ + { + "amount": "3.0", + "date": "2018-10-25 23:03:21", + "rate": "0.0002", + "total": "0.0006", + "tradeID": "251834", + "type": "buy" + } + ], + "fee": "0.01000000", + "currencyPair": "BTC_LTC" + }, + "queryString": "", + "bodyParams": "amount=10000000\u0026command=buy\u0026currencyPair=BTC_LTC\u0026fillOrKill=1\u0026nonce=1560226024406623067\u0026rate=10", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "Key": [ + "" + ], + "Sign": [ + "4c1a783636b6511a0ac070f1043b06a04f4e8120216912d64c4aca700860aef3bde6bab5305d4cc6e297b3930416064ca661c852607c76a218490787f7f557c9" + ] + } + }, + { + "data": { + "BTC_BCH": [ + { + "globalTradeID": 394131412, + "tradeID": "5455033", + "date": "2018-10-16 18:05:17", + "rate": "0.06935244", + "amount": "1.40308443", + "total": "0.09730732", + "fee": "0.00100000", + "orderNumber": "104768235081", + "type": "sell", + "category": "exchange" + }, + { + "globalTradeID": 394126818, + "tradeID": "5455007", + "date": "2018-10-16 16:55:34", + "rate": "0.06935244", + "amount": "0.00155709", + "total": "0.00010798", + "fee": "0.00200000", + "orderNumber": "104768179137", + "type": "sell", + "category": "exchange" + } + ], + "BTC_STR": [ + { + "globalTradeID": 394127362, + "tradeID": "13536351", + "date": "2018-10-16 17:03:43", + "rate": "0.00003432", + "amount": "3696.05342780", + "total": "0.12684855", + "fee": "0.00200000", + "orderNumber": "96238912841", + "type": "buy", + "category": "exchange" + }, + { + "globalTradeID": 394127361, + "tradeID": "13536350", + "date": "2018-10-16 17:03:43", + "rate": "0.00003432", + "amount": "3600.53748129", + "total": "0.12357044", + "fee": "0.00200000", + "orderNumber": "96238912841", + "type": "buy", + "category": "exchange" + } + ] + }, + "queryString": "", + "bodyParams": "command=returnTradeHistory\u0026currencyPair=all\u0026limit=10000\u0026nonce=1560226172960887950", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "Key": [ + "" + ], + "Sign": [ + "1b044c268e1ae4ddc9573a30b10d8ac4761e7e7f771a71397a168dfa54bcfe970c3a90103812e9070bde5616261d6e07ce372138a0ad27e7a315858298417aed" + ] + } + }, + { + "data": { + "makerFee": "0.00150000", + "takerFee": "0.00250000", + "thirtyDayVolume": "0.00000000", + "nextTier": 25000 + }, + "queryString": "", + "bodyParams": "command=returnFeeInfo\u0026nonce=1560227050375298330", + "headers": { + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "Key": [ + "" + ], + "Sign": [ + "d2a6e9fd6365cac63b778006c7fd8afe9473c4d4002a15ca915addd6c6b04101eaec7541397a5f2ccc102937453a5098ac51c062416b8d5fe273e5c88e2a31c2" + ] + } + } + ] + } + } +} \ No newline at end of file From f6afeee800eaae4cfe98ddadb72d8bf5156cc28b Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 26 Aug 2019 12:46:53 +1000 Subject: [PATCH 24/71] Minor improvements 1) gen_otp supports single use OTP secrets 2) improve database config check logic 3) reconnect websocket routine to engine (apart from the exchange pair syncer) 4) ImPrOvE CoNsIsTeNcY wItH LoG OuTpUt --- cmd/gen_otp/otp_gen.go | 47 +++++++++++++++++++++++++++++++++++------- config/config.go | 6 +++++- engine/database.go | 14 ++++++------- engine/engine.go | 5 +++++ engine/routines.go | 10 +++++++++ 5 files changed, 66 insertions(+), 16 deletions(-) diff --git a/cmd/gen_otp/otp_gen.go b/cmd/gen_otp/otp_gen.go index dbf9b438..f6b37348 100644 --- a/cmd/gen_otp/otp_gen.go +++ b/cmd/gen_otp/otp_gen.go @@ -2,13 +2,17 @@ package main import ( "flag" + "fmt" "log" "time" "github.com/pquerna/otp/totp" "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/core" ) +const defaultSleepTime = time.Second * 30 + func containsOTP(cfg *config.Config) bool { for x := range cfg.Exchanges { if cfg.Exchanges[x].API.Credentials.OTPSecret != "" { @@ -19,39 +23,66 @@ func containsOTP(cfg *config.Config) bool { } func main() { - var inFile string + var cfgFile, code string + var single bool + var err error + defaultCfg, err := config.GetFilePath("") if err != nil { log.Fatal(err) } - flag.StringVar(&inFile, "infile", defaultCfg, "The config input file to process.") + flag.StringVar(&cfgFile, "config", defaultCfg, "The config input file to process.") + flag.BoolVar(&single, "single", false, "prompt for single use OTP code gen") flag.Parse() log.Println("GoCryptoTrader: OTP code generator tool.") + log.Println(core.Copyright) + // Handle single use OTP code gen + if single { + var input string + for { + log.Println("Please enter in your OTP secret:") + fmt.Scanln(&input) + if input != "" { + break + } + } + + for { + code, err = totp.GenerateCode(input, time.Now()) + if err != nil { + log.Fatalf("Unable to generate OTP code. Err: %s", err) + } + log.Printf("OTP code: %s\n", code) + time.Sleep(defaultSleepTime) + } + } + + // Otherwise default to loading the config file and generating OTP codes from it var cfg config.Config - err = cfg.LoadConfig(inFile) + err = cfg.LoadConfig(cfgFile) if err != nil { log.Fatal(err) } log.Println("Loaded config file.") if !containsOTP(&cfg) { - log.Println("No exchanges with OTP code stored. Exiting.") + log.Fatal("No exchanges with OTP code stored. Exiting.") } for { for x := range cfg.Exchanges { if cfg.Exchanges[x].API.Credentials.OTPSecret != "" { - code, err := totp.GenerateCode(cfg.Exchanges[x].API.Credentials.OTPSecret, time.Now()) + code, err = totp.GenerateCode(cfg.Exchanges[x].API.Credentials.OTPSecret, time.Now()) if err != nil { - log.Printf("Exchange %s: Failed to generate OTP code. Err: %s", cfg.Exchanges[x].Name, err) + log.Printf("Exchange %s: Failed to generate OTP code. Err: %s\n", cfg.Exchanges[x].Name, err) continue } - log.Printf("%s: %s", cfg.Exchanges[x].Name, code) + log.Printf("%s: %s\n", cfg.Exchanges[x].Name, code) } - time.Sleep(time.Second) } + time.Sleep(defaultSleepTime) } } diff --git a/config/config.go b/config/config.go index 343ec58b..30db3fe7 100644 --- a/config/config.go +++ b/config/config.go @@ -1303,9 +1303,13 @@ func (c *Config) checkDatabaseConfig() error { m.Lock() defer m.Unlock() + if !c.Database.Enabled { + return nil + } + if !common.StringDataCompare(database.SupportedDrivers, c.Database.Driver) { c.Database.Enabled = false - return fmt.Errorf("unsupported database driver %v database disabled", c.Database.Driver) + return fmt.Errorf("unsupported database driver %v, database disabled", c.Database.Driver) } if c.Database.Driver == "sqlite" { diff --git a/engine/database.go b/engine/database.go index 696ef965..76644977 100644 --- a/engine/database.go +++ b/engine/database.go @@ -34,7 +34,7 @@ func (a *databaseManager) Start() (err error) { return errors.New("database manager already started") } - log.Debugln(log.DatabaseMgr, "database manager starting...") + log.Debugln(log.DatabaseMgr, "Database manager starting...") a.shutdown = make(chan struct{}) @@ -60,7 +60,7 @@ func (a *databaseManager) Start() (err error) { audit.Audit = auditSQLite.Audit() } dbConn.Connected = true - log.Debugf(log.DatabaseMgr, "connection established to %v using %v", dbConn.Config.Host, dbConn.Config.Driver) + log.Debugf(log.DatabaseMgr, "Database connection established to host: %v using %v driver\n", dbConn.Config.Host, dbConn.Config.Driver) mLogger := mg.MLogger{} migrations := mg.Migrator{ @@ -91,7 +91,7 @@ func (a *databaseManager) Stop() error { return errors.New("database manager already stopped") } - log.Debugln(log.DatabaseMgr, "database manager shutting down...") + log.Debugln(log.DatabaseMgr, "Database manager shutting down...") err := dbConn.SQL.Close() if err != nil { log.Errorf(log.DatabaseMgr, "Failed to close database: %v", err) @@ -101,7 +101,7 @@ func (a *databaseManager) Stop() error { } func (a *databaseManager) run() { - log.Debugln(log.DatabaseMgr, "database manager started.") + log.Debugln(log.DatabaseMgr, "Database manager started.") Bot.ServicesWG.Add(1) t := time.NewTicker(time.Second * 2) @@ -113,7 +113,7 @@ func (a *databaseManager) run() { Bot.ServicesWG.Done() - log.Debugln(log.DatabaseMgr, "database manager shutdown.") + log.Debugln(log.DatabaseMgr, "Database manager shutdown.") }() for { @@ -132,13 +132,13 @@ func (a *databaseManager) checkConnection() { err := dbConn.SQL.Ping() if err != nil { - log.Errorf(log.DatabaseMgr, "database connection error: %v", err) + log.Errorf(log.DatabaseMgr, "Database connection error: %v\n", err) dbConn.Connected = false return } if !dbConn.Connected { - log.Info(log.DatabaseMgr, "database connection reestablished") + log.Info(log.DatabaseMgr, "Database connection reestablished") dbConn.Connected = true } } diff --git a/engine/engine.go b/engine/engine.go index 33be5800..675a33fd 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -164,6 +164,7 @@ func ValidateSettings(b *Engine, s *Settings) { b.Settings.EnableExchangeHTTPDebugging = s.EnableExchangeHTTPDebugging b.Settings.DisableExchangeAutoPairUpdates = s.DisableExchangeAutoPairUpdates b.Settings.ExchangePurgeCredentials = s.ExchangePurgeCredentials + b.Settings.EnableWebsocketRoutine = s.EnableWebsocketRoutine if !b.Settings.EnableExchangeHTTPRateLimiter { request.DisableRateLimiter = true @@ -383,6 +384,10 @@ func (e *Engine) Start() { go EventManger() } + if e.Settings.EnableWebsocketRoutine { + go WebsocketRoutine() + } + <-e.Shutdown e.Stop() } diff --git a/engine/routines.go b/engine/routines.go index 8a82ad52..1c553f39 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -295,11 +295,21 @@ func WebsocketRoutine() { common.IsEnabled(Bot.Exchanges[i].IsWebsocketEnabled())) } + // TO-DO: expose IsConnected() and IsConnecting so this can be simplified if Bot.Exchanges[i].IsWebsocketEnabled() { ws, err := Bot.Exchanges[i].GetWebsocket() if err != nil { + log.Errorf(log.WebsocketMgr, "Exchange %s GetWebsocket error: %s\n", + Bot.Exchanges[i].GetName(), err) return } + + // Exchange sync manager might have already started ws + // service or is in the process of connecting, so check + if ws.IsConnected() || ws.IsConnecting() { + return + } + // Data handler routine go WebsocketDataHandler(ws) From 5e9d13f7be5a32a9fbc2d8d3dbb00cbcaa1ce5cf Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 27 Aug 2019 08:54:10 +1000 Subject: [PATCH 25/71] Expand RetrieveConfigCurrencyPairs to support different asset types --- cmd/portfolio/portfolio.go | 3 ++- config/config.go | 16 +++++++++++++--- config/config_test.go | 4 ++-- engine/helpers_test.go | 2 +- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/cmd/portfolio/portfolio.go b/cmd/portfolio/portfolio.go index bc4bafcc..ea44cc34 100644 --- a/cmd/portfolio/portfolio.go +++ b/cmd/portfolio/portfolio.go @@ -8,6 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/bitfinex" "github.com/thrasher-corp/gocryptotrader/portfolio" ) @@ -95,7 +96,7 @@ func main() { Subtotal float64 } - cfg.RetrieveConfigCurrencyPairs(true) + cfg.RetrieveConfigCurrencyPairs(true, asset.Spot) portfolioMap := make(map[currency.Code]PortfolioTemp) total := float64(0) diff --git a/config/config.go b/config/config.go index 30db3fe7..f38b9dbc 100644 --- a/config/config.go +++ b/config/config.go @@ -1214,7 +1214,7 @@ func (c *Config) CheckCurrencyConfigValues() error { // RetrieveConfigCurrencyPairs splits, assigns and verifies enabled currency // pairs either cryptoCurrencies or fiatCurrencies -func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool) error { +func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool, assetType asset.Item) error { cryptoCurrencies := c.Currency.Cryptocurrencies fiatCurrencies := currency.GetFiatCurrencies() @@ -1223,6 +1223,11 @@ func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool) error { continue } + supports, _ := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType) + if !supports { + continue + } + baseCurrencies := c.Exchanges[x].BaseCurrencies for y := range baseCurrencies { if !fiatCurrencies.Contains(baseCurrencies[y]) { @@ -1232,12 +1237,17 @@ func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool) error { } for x := range c.Exchanges { + supports, _ := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType) + if !supports { + continue + } + var pairs []currency.Pair var err error if !c.Exchanges[x].Enabled && enabledOnly { - pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name, asset.Spot) + pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name, assetType) } else { - pairs, err = c.GetAvailablePairs(c.Exchanges[x].Name, asset.Spot) + pairs, err = c.GetAvailablePairs(c.Exchanges[x].Name, assetType) } if err != nil { diff --git a/config/config_test.go b/config/config_test.go index 1613817a..27f38894 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -735,7 +735,7 @@ func TestRetrieveConfigCurrencyPairs(t *testing.T) { "Test failed. TestRetrieveConfigCurrencyPairs.LoadConfig: %s", err.Error(), ) } - err = cfg.RetrieveConfigCurrencyPairs(true) + err = cfg.RetrieveConfigCurrencyPairs(true, asset.Spot) if err != nil { t.Errorf( "Test failed. TestRetrieveConfigCurrencyPairs.RetrieveConfigCurrencyPairs: %s", @@ -743,7 +743,7 @@ func TestRetrieveConfigCurrencyPairs(t *testing.T) { ) } - err = cfg.RetrieveConfigCurrencyPairs(false) + err = cfg.RetrieveConfigCurrencyPairs(false, asset.Spot) if err != nil { t.Errorf( "Test failed. TestRetrieveConfigCurrencyPairs.RetrieveConfigCurrencyPairs: %s", diff --git a/engine/helpers_test.go b/engine/helpers_test.go index 838275a3..8e82911b 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -35,7 +35,7 @@ func SetupTestHelpers(t *testing.T) { } testSetup = true } - err := Bot.Config.RetrieveConfigCurrencyPairs(true) + err := Bot.Config.RetrieveConfigCurrencyPairs(true, asset.Spot) if err != nil { t.Fatalf("Failed to retrieve config currency pairs. %s", err) } From fb7149a37246a6dfab8e88bcfcace9e59d7995c5 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 27 Aug 2019 12:06:52 +1000 Subject: [PATCH 26/71] BTSE orderbook --- exchanges/btse/btse.go | 57 +++++++++++++++++++++++++++++++--- exchanges/btse/btse_test.go | 8 +++++ exchanges/btse/btse_types.go | 12 +++++++ exchanges/btse/btse_wrapper.go | 36 ++++++++++++++++++++- 4 files changed, 107 insertions(+), 6 deletions(-) diff --git a/exchanges/btse/btse.go b/exchanges/btse/btse.go index 32be20ba..d6417151 100644 --- a/exchanges/btse/btse.go +++ b/exchanges/btse/btse.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "strconv" "strings" "time" @@ -23,14 +24,16 @@ type BTSE struct { const ( btseAPIURL = "https://api.btse.com/v1/restapi" + btseAPIURLv2 = "https://api.btse.com/spot/v2" btseAPIVersion = "1" // Public endpoints - btseMarkets = "markets" - btseTrades = "trades" - btseTicker = "ticker" - btseStats = "stats" - btseTime = "time" + btseMarkets = "markets" + btseTrades = "trades" + btseTicker = "ticker" + btseOrderbook = "orderbook" + btseStats = "stats" + btseTime = "time" // Authenticated endpoints btseAccount = "account" @@ -66,6 +69,34 @@ func (b *BTSE) GetTicker(symbol string) (*Ticker, error) { return &t, nil } +// GetOrderbook returns the orderbook for a specified symbol +func (b *BTSE) GetOrderbook(symbol string, group, limitAsks, limitBids int64) (*Orderbook, error) { + var t Orderbook + vals := url.Values{} + if group != 0 { + vals.Set("group", strconv.FormatInt(group, 10)) + } + + if limitAsks != 0 { + vals.Set("limit_asks", strconv.FormatInt(limitAsks, 10)) + } + + if limitBids != 0 { + vals.Set("limit_bids", strconv.FormatInt(limitBids, 10)) + } + + if symbol == "" { + return nil, errors.New("symbol not set") + } + + endpoint := fmt.Sprintf("%s/%s", btseOrderbook, symbol) + err := b.SendHTTPRequestv2(http.MethodGet, endpoint, vals, &t) + if err != nil { + return nil, err + } + return &t, nil +} + // GetMarketStatistics gets market statistics for a specificed market func (b *BTSE) GetMarketStatistics(symbol string) (*MarketStatistics, error) { var m MarketStatistics @@ -188,6 +219,22 @@ func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}) erro b.HTTPRecording) } +// SendHTTPRequestv2 sends an HTTP request to the desired endpoint +func (b *BTSE) SendHTTPRequestv2(method, endpoint string, values url.Values, result interface{}) error { + path := fmt.Sprintf("%s/%s", btseAPIURLv2, endpoint) + path = common.EncodeURLValues(path, values) + return b.SendPayload(method, + path, + nil, + nil, + &result, + false, + false, + b.Verbose, + b.HTTPDebugging, + b.HTTPRecording) +} + // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the desired endpoint func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[string]interface{}, result interface{}) error { if !b.AllowAuthenticatedRequest() { diff --git a/exchanges/btse/btse_test.go b/exchanges/btse/btse_test.go index 47b46f6f..d4093681 100644 --- a/exchanges/btse/btse_test.go +++ b/exchanges/btse/btse_test.go @@ -61,6 +61,14 @@ func TestGetTicker(t *testing.T) { } } +func TestGetOrderbook(t *testing.T) { + b.SetDefaults() + _, err := b.GetOrderbook("BTC-USD", 0, 0, 0) + if err != nil { + t.Fatalf("Test failed. Err: %s", err) + } +} + func TestGetMarketStatistics(t *testing.T) { b.SetDefaults() _, err := b.GetMarketStatistics("BTC-USD") diff --git a/exchanges/btse/btse_types.go b/exchanges/btse/btse_types.go index 6ac5781c..2f4031a6 100644 --- a/exchanges/btse/btse_types.go +++ b/exchanges/btse/btse_types.go @@ -41,6 +41,18 @@ type Ticker struct { Time string `json:"time"` } +// OrderbookItem stores the price and size orderbook data +type OrderbookItem struct { + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` +} + +// Orderbook stores the orderbook bids and asks +type Orderbook struct { + Bids []OrderbookItem `json:"buyQuote"` + Asks []OrderbookItem `json:"sellQuote"` +} + // MarketStatistics stores market statistics for a particular product type MarketStatistics struct { Open float64 `json:"open,string"` diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 674a89ae..67f99822 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -247,7 +247,41 @@ func (b *BTSE) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook. // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *BTSE) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - return orderbook.Base{}, common.ErrFunctionNotSupported + var orderBook orderbook.Base + obNew, err := b.GetOrderbook( + b.FormatExchangeCurrency(p, assetType).String(), 0, 0, 0) + if err != nil { + return orderBook, err + } + + for x := range obNew.Bids { + orderBook.Bids = append(orderBook.Bids, + orderbook.Item{ + Amount: obNew.Bids[x].Size, + Price: obNew.Bids[x].Price, + }, + ) + } + + for x := range obNew.Asks { + orderBook.Asks = append(orderBook.Asks, + orderbook.Item{ + Amount: obNew.Asks[x].Size, + Price: obNew.Asks[x].Price, + }, + ) + } + + orderBook.Pair = p + orderBook.ExchangeName = b.Name + orderBook.AssetType = assetType + + err = orderBook.Process() + if err != nil { + return orderBook, err + } + + return orderbook.Get(b.Name, p, assetType) } // GetAccountInfo retrieves balances for all enabled currencies for the From 833a8665064e9fa266eb9ad2633bbbb7219b6b93 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 2 Sep 2019 09:58:23 +1000 Subject: [PATCH 27/71] (Engine) Logger - fixes issues with multiwriter go routine (#347) * Fixes goroutine leak and comments * passed error down from newLogEvent to each sub logger to handle and display * Update loggers.go --- logger/logger.go | 8 ++++++-- logger/logger_multiwriter.go | 11 +++++------ logger/logger_rotate.go | 8 ++++---- logger/logger_setup.go | 1 - logger/logger_types.go | 14 ++++++++++---- logger/loggers.go | 23 +++++++++++++++-------- logger/sublogger_types.go | 1 + 7 files changed, 41 insertions(+), 25 deletions(-) diff --git a/logger/logger.go b/logger/logger.go index 7dfdc323..a7a43de3 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -22,6 +22,7 @@ func (l *Logger) newLogEvent(data, header string, w io.Writer) error { if w == nil { return errors.New("io.Writer not set") } + e := eventPool.Get().(*LogEvent) e.output = w e.data = append(e.data, []byte(header)...) @@ -34,11 +35,12 @@ func (l *Logger) newLogEvent(data, header string, w io.Writer) error { if data == "" || data[len(data)-1] != '\n' { e.data = append(e.data, '\n') } - e.output.Write(e.data) + _, err := e.output.Write(e.data) + e.data = e.data[:0] eventPool.Put(e) - return nil + return err } // CloseLogger is called on shutdown of application @@ -58,6 +60,7 @@ func validSubLogger(s string) (bool, *subLogger) { return false, nil } +// Level retries the current sublogger levels func Level(s string) (*Levels, error) { found, logger := validSubLogger(s) if !found { @@ -67,6 +70,7 @@ func Level(s string) (*Levels, error) { return &logger.Levels, nil } +// SetLevel sets sublogger levels func SetLevel(s, level string) (*Levels, error) { found, logger := validSubLogger(s) if !found { diff --git a/logger/logger_multiwriter.go b/logger/logger_multiwriter.go index af07cfff..2d59fe62 100644 --- a/logger/logger_multiwriter.go +++ b/logger/logger_multiwriter.go @@ -11,7 +11,7 @@ func (mw *multiWriter) Add(writer io.Writer) { mw.mu.Unlock() } -// Remove removes exisiting writer from multiwriter slice +// Remove removes existing writer from multiwriter slice func (mw *multiWriter) Remove(writer io.Writer) { mw.mu.Lock() @@ -37,8 +37,10 @@ func (mw *multiWriter) Write(p []byte) (n int, err error) { n int err error } + mw.mu.RLock() + defer mw.mu.RUnlock() - results := make(chan data) + results := make(chan data, len(mw.writers)) for _, wr := range mw.writers { go func(w io.Writer, p []byte, ch chan data) { @@ -51,10 +53,7 @@ func (mw *multiWriter) Write(p []byte) (n int, err error) { ch <- data{n, io.ErrShortWrite} return } - select { - case ch <- data{n, nil}: - default: - } + ch <- data{n, nil} }(wr, p, results) } diff --git a/logger/logger_rotate.go b/logger/logger_rotate.go index dc4a0f3d..985c4c70 100644 --- a/logger/logger_rotate.go +++ b/logger/logger_rotate.go @@ -72,13 +72,12 @@ func (r *Rotate) openOrCreateFile(n int64) error { func (r *Rotate) openNew() error { name := filepath.Join(LogPath, r.FileName) - - t := time.Now() - timestamp := t.Format("2006-01-02T15-04-05") - newName := filepath.Join(LogPath, timestamp+"-"+r.FileName) _, err := os.Stat(name) if err == nil { + timestamp := time.Now().Format("2006-01-02T15-04-05") + newName := filepath.Join(LogPath, timestamp+"-"+r.FileName) + err = os.Rename(name, newName) if err != nil { return fmt.Errorf("can't rename log file: %s", err) @@ -105,6 +104,7 @@ func (r *Rotate) close() (err error) { return err } +// Close handler for open file func (r *Rotate) Close() error { r.mu.Lock() defer r.mu.Unlock() diff --git a/logger/logger_setup.go b/logger/logger_setup.go index bf391b3c..14ac5a07 100644 --- a/logger/logger_setup.go +++ b/logger/logger_setup.go @@ -27,7 +27,6 @@ func getWriters(s *SubLoggerConfig) io.Writer { m.Add(ioutil.Discard) } } - return m } diff --git a/logger/logger_types.go b/logger/logger_types.go index 5ec424da..a9608744 100644 --- a/logger/logger_types.go +++ b/logger/logger_types.go @@ -51,6 +51,7 @@ type Logger struct { Spacer string } +// Levels flags for each sub logger type type Levels struct { Info, Debug, Warn, Error bool } @@ -61,6 +62,7 @@ type subLogger struct { output io.Writer } +// LogEvent holds the data sent to the log and which multiwriter to send to type LogEvent struct { data []byte output io.Writer @@ -68,14 +70,17 @@ type LogEvent struct { type multiWriter struct { writers []io.Writer - mu sync.Mutex + mu sync.RWMutex } var ( - logger = &Logger{} + logger = &Logger{} + // FileLoggingConfiguredCorrectly flag set during config check if file logging meets requirements FileLoggingConfiguredCorrectly bool - GlobalLogConfig = &Config{} // GlobalLogConfig hold global configuration options for logger - GlobalLogFile = &Rotate{} + // GlobalLogConfig holds global configuration options for logger + GlobalLogConfig = &Config{} + // GlobalLogFile hold global configuration options for file logger + GlobalLogFile = &Rotate{} eventPool = &sync.Pool{ New: func() interface{} { @@ -85,5 +90,6 @@ var ( }, } + // LogPath system path to store log files in LogPath string ) diff --git a/logger/loggers.go b/logger/loggers.go index e8fdfb45..4173b437 100644 --- a/logger/loggers.go +++ b/logger/loggers.go @@ -2,6 +2,7 @@ package logger import ( "fmt" + "log" ) // Info takes a pointer subLogger struct and string sends to newLogEvent @@ -14,7 +15,7 @@ func Info(sl *subLogger, data string) { return } - logger.newLogEvent(data, logger.InfoHeader, sl.output) + displayError(logger.newLogEvent(data, logger.InfoHeader, sl.output)) } // Infoln takes a pointer subLogger struct and interface sends to newLogEvent @@ -27,7 +28,7 @@ func Infoln(sl *subLogger, v ...interface{}) { return } - logger.newLogEvent(fmt.Sprintln(v...), logger.InfoHeader, sl.output) + displayError(logger.newLogEvent(fmt.Sprintln(v...), logger.InfoHeader, sl.output)) } // Infof takes a pointer subLogger struct, string & interface formats and sends to Info() @@ -53,7 +54,7 @@ func Debug(sl *subLogger, data string) { return } - logger.newLogEvent(data, logger.DebugHeader, sl.output) + displayError(logger.newLogEvent(data, logger.DebugHeader, sl.output)) } // Debugln takes a pointer subLogger struct, string and interface sends to newLogEvent @@ -66,7 +67,7 @@ func Debugln(sl *subLogger, v ...interface{}) { return } - logger.newLogEvent(fmt.Sprintln(v...), logger.DebugHeader, sl.output) + displayError(logger.newLogEvent(fmt.Sprintln(v...), logger.DebugHeader, sl.output)) } // Debugf takes a pointer subLogger struct, string & interface formats and sends to Info() @@ -92,7 +93,7 @@ func Warn(sl *subLogger, data string) { return } - logger.newLogEvent(data, logger.WarnHeader, sl.output) + displayError(logger.newLogEvent(data, logger.WarnHeader, sl.output)) } // Warnln takes a pointer subLogger struct & interface formats and sends to newLogEvent() @@ -105,7 +106,7 @@ func Warnln(sl *subLogger, v ...interface{}) { return } - logger.newLogEvent(fmt.Sprintln(v...), logger.WarnHeader, sl.output) + displayError(logger.newLogEvent(fmt.Sprintln(v...), logger.WarnHeader, sl.output)) } // Warnf takes a pointer subLogger struct, string & interface formats and sends to Warn() @@ -131,7 +132,7 @@ func Error(sl *subLogger, data ...interface{}) { return } - logger.newLogEvent(fmt.Sprint(data...), logger.ErrorHeader, sl.output) + displayError(logger.newLogEvent(fmt.Sprint(data...), logger.ErrorHeader, sl.output)) } // Errorln takes a pointer subLogger struct, string & interface formats and sends to newLogEvent() @@ -144,7 +145,7 @@ func Errorln(sl *subLogger, v ...interface{}) { return } - logger.newLogEvent(fmt.Sprintln(v...), logger.ErrorHeader, sl.output) + displayError(logger.newLogEvent(fmt.Sprintln(v...), logger.ErrorHeader, sl.output)) } // Errorf takes a pointer subLogger struct, string & interface formats and sends to Debug() @@ -159,3 +160,9 @@ func Errorf(sl *subLogger, data string, v ...interface{}) { Error(sl, fmt.Sprintf(data, v...)) } + +func displayError(err error) { + if err != nil { + log.Printf("Logger write error: %v\n", err) + } +} diff --git a/logger/sublogger_types.go b/logger/sublogger_types.go index 3a406ae2..362e34d5 100644 --- a/logger/sublogger_types.go +++ b/logger/sublogger_types.go @@ -1,5 +1,6 @@ package logger +//nolint var ( subLoggers = map[string]*subLogger{} From 74d5bca03be4f5a2398e517511dbb7001f96adb2 Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 2 Sep 2019 18:05:09 +1000 Subject: [PATCH 28/71] engine: Ticker REST/Websocket improvements (#345) * Adds extra properties to Websocket ticker. Adds new properties to binance and bitfinex, but doesn't test anything yet. Changes names and properties of ticker package to make more streamlined * Adds support for coinbasepro, coinut, gateio, gitbtc, huobi, hadax, kraken, okex, okcoin. Adds quoteVolume * Adds poloniex and ZB ticker datas * Updates ANX, Binance, Bitfinex, Bistamp, Bittrex, BTCMarkets, BTSE, CoinbasePRo, Coinut, Exmo tickers. It looks like a whole bunch of stuff is wrong in how tickers are done though :/ * Updates tickers everywhere. Will revert batch ones * Re-Preseves ticker batching * Minor fixes to ticks and removal of comment * Logging errors instead of returning mid loop. Adds bitfinex batch ticker processing. Fixes unrelated okgroup wallet bug * Removes bad code I wrote preventing function from running if feature not enabled * Fixes issue with bitmex and rebase issues * Fixes bitmex iterator error, splits hitbtc ticker requests * Fixes okgroup currency pair formatting. Updates okgroup to use ticker batching. Fixes okgroup ticker issues due to assetTypes. Fixes okgroup ws pinging. Fixes Kraken's currency pairs formatting. Reverts ANXs auto parsing back to strings because ANX json makes me cry. Minor property improvements for coinut, coinbasepro, btse, exmo. Protects wshandler manageSubscriptions() from running and returning an error when feature not supported * Updates config example to reflect the underscore dash situation * Fixes a config delimiter oopsie. Simplifies ANX wrapper ticker parsing. Fixes bittrex date parsing. Simplifies okcoin switch to if. * Fixes super fun issue where kraken has updated their currency pair format and must add new delimiter support. Fixes super fun issue where okex has updated their currency pair format and must add new delimiter support. Fixes super fun issue where okcoin has updated their currency pair format and must add new delimiter support. * Updates config example for kraken * Adds lbank batch ticker support. Adds details to errors * Updates FetchTradablePairs to use the config delimiter to prevent issues if the delimiter ever changes * Fixes nil reference bug. Uses NAme not GetNAme * Fixes hardcoded delimiter in Binance. Expands bitfinex websocket ticker fields. Updates Bitstamp rate limits. Expands BTSE ticker data fields. Fixes typo in coibasepro. Expands Coinut ticker data. Renames currency to curr as it conflicts with package name. Expands GateIO websocket ticker data. Fixes ticker data implementation for huobi and huobi hadax. Reverts ticker map to string instead of assetType. Fixes real stupid bug I introduced which preveted subscriptions from running :glitch_crab: Adds Price ATH to ws ticker type. Adds quotevolume to yobit ticker. Uses delimiter in ZB rather than hardcoded field. * Fixes bug in syncer where if the websocket is already connected, then UsingWebsocket is not set * Updates broken tests * Simplifies poloniex frozen check * Updates configtest.json with new delimiters * Renames shorthand properties of structs. Fixes delimiter referencing * Fixes some bugs and nits around variable declaration, currency pairs and config upscaling * Adds config upgrade path for okcoin and okex. Reverts configtest.json. * Fixes okex futures currency formatting by no longer using global currency format. updates Run code to adapt. Removes BTSE value * Adds ":" as a delimiter for when a delimiter only shows up SOMETIMES * Adds support for optional delimiter --- config_example.json | 20 +- currency/pair.go | 2 +- engine/routines.go | 19 +- engine/syncer.go | 2 + exchanges/anx/anx_types.go | 20 +- exchanges/anx/anx_wrapper.go | 73 ++---- exchanges/binance/binance_types.go | 84 +++---- exchanges/binance/binance_websocket.go | 27 ++- exchanges/binance/binance_wrapper.go | 43 ++-- exchanges/bitfinex/bitfinex_test.go | 14 ++ exchanges/bitfinex/bitfinex_types.go | 3 + exchanges/bitfinex/bitfinex_websocket.go | 17 +- exchanges/bitfinex/bitfinex_wrapper.go | 54 +++-- exchanges/bitflyer/bitflyer_wrapper.go | 4 +- exchanges/bithumb/bithumb_wrapper.go | 27 ++- exchanges/bitmex/bitmex_types.go | 209 +++++++++--------- exchanges/bitmex/bitmex_wrapper.go | 43 ++-- exchanges/bitstamp/bitstamp.go | 4 +- exchanges/bitstamp/bitstamp_wrapper.go | 21 +- exchanges/bittrex/bittrex_types.go | 4 +- exchanges/bittrex/bittrex_wrapper.go | 37 ++-- exchanges/btcmarkets/btcmarkets_types.go | 17 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 17 +- exchanges/btse/btse_types.go | 12 +- exchanges/btse/btse_websocket.go | 12 +- exchanges/btse/btse_wrapper.go | 1 + exchanges/coinbasepro/coinbasepro_types.go | 47 ++-- .../coinbasepro/coinbasepro_websocket.go | 21 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 18 +- exchanges/coinut/coinut_types.go | 29 ++- exchanges/coinut/coinut_websocket.go | 39 ++-- exchanges/coinut/coinut_wrapper.go | 19 +- exchanges/exmo/exmo.go | 3 +- exchanges/exmo/exmo_test.go | 2 +- exchanges/exmo/exmo_wrapper.go | 43 ++-- exchanges/gateio/gateio_types.go | 18 +- exchanges/gateio/gateio_websocket.go | 21 +- exchanges/gateio/gateio_wrapper.go | 34 +-- exchanges/gemini/gemini.go | 46 +--- exchanges/gemini/gemini_types.go | 15 ++ exchanges/gemini/gemini_wrapper.go | 15 +- exchanges/hitbtc/hitbtc.go | 66 +----- exchanges/hitbtc/hitbtc_test.go | 14 ++ exchanges/hitbtc/hitbtc_types.go | 54 ++--- exchanges/hitbtc/hitbtc_websocket.go | 20 +- exchanges/hitbtc/hitbtc_wrapper.go | 44 ++-- exchanges/huobi/huobi.go | 8 + exchanges/huobi/huobi_test.go | 7 + exchanges/huobi/huobi_types.go | 35 ++- exchanges/huobi/huobi_websocket.go | 33 ++- exchanges/huobi/huobi_wrapper.go | 43 ++-- exchanges/huobihadax/huobihadax.go | 8 + exchanges/huobihadax/huobihadax_test.go | 7 + exchanges/huobihadax/huobihadax_types.go | 35 ++- exchanges/huobihadax/huobihadax_websocket.go | 25 ++- exchanges/huobihadax/huobihadax_wrapper.go | 43 ++-- exchanges/itbit/itbit_types.go | 36 +-- exchanges/itbit/itbit_wrapper.go | 20 +- exchanges/kraken/kraken.go | 18 +- exchanges/kraken/kraken_test.go | 2 +- exchanges/kraken/kraken_types.go | 156 ++++++------- exchanges/kraken/kraken_websocket.go | 24 +- exchanges/kraken/kraken_wrapper.go | 83 ++++--- exchanges/lakebtc/lakebtc_websocket.go | 11 +- exchanges/lakebtc/lakebtc_wrapper.go | 26 ++- exchanges/lbank/lbank.go | 9 + exchanges/lbank/lbank_test.go | 11 + exchanges/lbank/lbank_types.go | 8 +- exchanges/lbank/lbank_wrapper.go | 32 ++- .../localbitcoins/localbitcoins_types.go | 3 +- .../localbitcoins/localbitcoins_wrapper.go | 16 +- exchanges/okcoin/okcoin_wrapper.go | 79 ++++++- exchanges/okex/okex_wrapper.go | 162 +++++++++++++- exchanges/okgroup/okgroup_types.go | 68 +++--- exchanges/okgroup/okgroup_websocket.go | 22 +- exchanges/okgroup/okgroup_wrapper.go | 33 +-- exchanges/poloniex/poloniex_websocket.go | 26 +-- exchanges/poloniex/poloniex_wrapper.go | 6 +- exchanges/ticker/ticker.go | 11 +- exchanges/websocket/wshandler/wshandler.go | 4 +- .../websocket/wshandler/wshandler_types.go | 23 +- exchanges/yobit/yobit_wrapper.go | 20 +- exchanges/zb/zb_types.go | 12 +- exchanges/zb/zb_websocket.go | 18 +- exchanges/zb/zb_wrapper.go | 22 +- testdata/http_mock/gemini/gemini.json | 58 +++++ 86 files changed, 1575 insertions(+), 1042 deletions(-) diff --git a/config_example.json b/config_example.json index 143156ae..f7b336ce 100644 --- a/config_example.json +++ b/config_example.json @@ -1211,18 +1211,18 @@ "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", "proxyAddress": "", "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC_USD,LTC_USD,ETH_USD,ETC_USD,TUSD_USD,USDT_USD,ZEC_USD,ADA_USD,XLM_USD,ZRX_USD,XRP_USD,BAT_USD,PAX_USD,GUSD_USD,USDC_USD,BCH_USD,BSV_USD,TRX_USD,GRIN_USD,DCR_USD,EOS_USD,BTC_TUSD,BTC_USDT,BTC_PAX,BTC_GUSD,BTC_USDC", - "enabledPairs": "BTC_USD", + "availablePairs": "BTC-USD,LTC-USD,ETH-USD,ETC-USD,TUSD-USD,USDT-USD,ZEC-USD,ADA-USD,XLM-USD,ZRX-USD,XRP-USD,BAT-USD,PAX-USD,GUSD-USD,USDC-USD,BCH-USD,BSV-USD,TRX-USD,GRIN-USD,DCR-USD,EOS-USD,BTC-TUSD,BTC-USDT,BTC-PAX,BTC-GUSD,BTC-USDC", + "enabledPairs": "BTC-USD", "baseCurrencies": "USD", "assetTypes": "SPOT", "supportsAutoPairUpdates": true, "configCurrencyPairFormat": { "uppercase": true, - "delimiter": "_" + "delimiter": "-" }, "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "uppercase": true, + "delimiter": "-" }, "bankAccounts": [ { @@ -1256,18 +1256,18 @@ "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", "proxyAddress": "", "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "DASH_BTC,CTXC_BTC,ZIL_BTC,YOU_BTC,LBA_BTC,LSK_BTC,CAI_BTC,AE_BTC,SC_BTC,KAN_BTC,WIN_BTC,DCR_BTC,WAVES_BTC,ORS_BTC,NXT_BTC,ARDR_BTC,XAS_BTC,CVT_BTC,EGT_BTC,ZCO_BTC,LET_BTC,HPB_BTC,ADA_BTC,HYC_BTC,VITE_BTC,ABL_BTC,PAX_BTC,TUSD_BTC,USDC_BTC,GUSD_BTC,BCH_BTC,BSV_BTC,BTT_BTC,ATOM_BTC,BLOC_BTC,XRP_BTC,LRC_BTC,NULS_BTC,MCO_BTC,ELF_BTC,ZEC_BTC,CMT_BTC,ITC_BTC,SBTC_BTC,EDO_BTC,BCX_BTC,NEO_BTC,GAS_BTC,HC_BTC,QTUM_BTC,IOTA_BTC,XUC_BTC,EOS_BTC,SNT_BTC,OMG_BTC,LTC_BTC,ETH_BTC,ETC_BTC,BCD_BTC,BTG_BTC,ACT_BTC,PAY_BTC,BTM_BTC,DGD_BTC,GNT_BTC,LINK_BTC,WTC_BTC,ZRX_BTC,BNT_BTC,CVC_BTC,MANA_BTC,KNC_BTC,GNX_BTC,ICX_BTC,XEM_BTC,ARK_BTC,YOYO_BTC,FUN_BTC,ACE_BTC,TRX_BTC,DGB_BTC,SWFTC_BTC,XMR_BTC,XLM_BTC,KCASH_BTC,MDT_BTC,NAS_BTC,UGC_BTC,DPY_BTC,SSC_BTC,AAC_BTC,VIB_BTC,QUN_BTC,INT_BTC,IOST_BTC,INS_BTC,MOF_BTC,TCT_BTC,STC_BTC,THETA_BTC,PST_BTC,SNC_BTC,MKR_BTC,LIGHT_BTC,TRUE_BTC,OF_BTC,SOC_BTC,ZEN_BTC,HMC_BTC,ZIP_BTC,NANO_BTC,CIC_BTC,GTO_BTC,CHAT_BTC,INSUR_BTC,R_BTC,BEC_BTC,MITH_BTC,ABT_BTC,BKX_BTC,RFR_BTC,TRIO_BTC,DADI_BTC,ONT_BTC,OKB_BTC,CTXC_ETH,ZIL_ETH,YOU_ETH,LBA_ETH,LSK_ETH,CAI_ETH,SC_ETH,AE_ETH,KAN_ETH,WIN_ETH,DCR_ETH,WAVES_ETH,ORS_ETH,MVP_ETH,EGT_ETH,ZCO_ETH,LET_ETH,HPB_ETH,SDA_ETH,ADA_ETH,HYC_ETH,VITE_ETH,ABL_ETH,BTT_ETH,ATOM_ETH,ELF_ETH,LTC_ETH,CMT_ETH,ITC_ETH,PRA_ETH,EDO_ETH,LRC_ETH,NULS_ETH,MCO_ETH,STORJ_ETH,SNT_ETH,PAY_ETH,DGD_ETH,GNT_ETH,ACT_ETH,BTM_ETH,EOS_ETH,OMG_ETH,DASH_ETH,XRP_ETH,ZEC_ETH,NEO_ETH,GAS_ETH,HC_ETH,QTUM_ETH,IOTA_ETH,XUC_ETH,ETC_ETH,LINK_ETH,WTC_ETH,ZRX_ETH,BNT_ETH,CVC_ETH,MANA_ETH,GNX_ETH,ICX_ETH,XEM_ETH,ARK_ETH,YOYO_ETH,TRX_ETH,DGB_ETH,PPT_ETH,SWFTC_ETH,XMR_ETH,XLM_ETH,KCASH_ETH,MDT_ETH,NAS_ETH,RNT_ETH,UGC_ETH,DPY_ETH,SSC_ETH,AAC_ETH,FAIR_ETH,RCT_ETH,VIB_ETH,TOPC_ETH,QUN_ETH,INT_ETH,IOST_ETH,INS_ETH,MOF_ETH,REF_ETH,THETA_ETH,PST_ETH,SNC_ETH,MKR_ETH,LIGHT_ETH,TRUE_ETH,OF_ETH,SOC_ETH,ZEN_ETH,HMC_ETH,ZIP_ETH,NANO_ETH,CIC_ETH,GTO_ETH,INSUR_ETH,R_ETH,UCT_ETH,BEC_ETH,MITH_ETH,ABT_ETH,BKX_ETH,AUTO_ETH,RFR_ETH,TRIO_ETH,TRA_ETH,DADI_ETH,ONT_ETH,OKB_ETH,CTXC_USDT,ZIL_USDT,YOU_OKB,YOU_USDT,LBA_OKB,LBA_USDT,CAI_OKB,LSK_USDT,CAI_USDT,AE_OKB,SC_OKB,KAN_OKB,WIN_OKB,SC_USDT,AE_USDT,KAN_USDT,WIN_USDT,ORS_OKB,DCR_OKB,DCR_USDT,WAVES_OKB,WAVES_USDT,ORS_USDT,MVP_USDT,NAS_OKB,XAS_OKB,ZCO_OKB,EGT_OKB,XAS_USDT,CVT_USDT,EGT_USDT,LET_OKB,LET_USDT,HPB_OKB,HPB_USDT,SDA_OKB,ADA_OKB,ADA_USDT,HYC_USDT,VITE_OKB,TRX_OKB,PAX_USDT,TUSD_USDT,USDC_USDT,GUSD_USDT,BCH_USDT,BSV_USDT,BTT_USDT,BLOC_OKB,BLOC_USDT,ATOM_USDT,ELF_USDT,DASH_USDT,LRC_USDT,NULS_USDT,MCO_USDT,BTG_USDT,DASH_OKB,XRP_USDT,ZEC_USDT,NEO_USDT,GAS_USDT,HC_USDT,QTUM_USDT,IOTA_USDT,BTC_USDT,BCD_USDT,XUC_USDT,CMT_USDT,ITC_USDT,PRA_USDT,EDO_USDT,ETH_USDT,LTC_USDT,ETC_USDT,EOS_USDT,OMG_USDT,ACT_USDT,BTM_USDT,STORJ_USDT,PAY_USDT,DGD_USDT,GNT_USDT,SNT_USDT,LINK_USDT,WTC_USDT,ZRX_USDT,BNT_USDT,CVC_USDT,MANA_USDT,KNC_USDT,ICX_USDT,XEM_USDT,ARK_USDT,YOYO_USDT,AST_USDT,TRX_USDT,MDA_USDT,DGB_USDT,PPT_USDT,SWFTC_USDT,XMR_USDT,XLM_USDT,KCASH_USDT,MDT_USDT,NAS_USDT,RNT_USDT,UGC_USDT,DPY_USDT,SSC_USDT,AAC_USDT,FAIR_USDT,UBTC_USDT,SHOW_USDT,VIB_USDT,MOT_USDT,UTK_USDT,TOPC_USDT,QUN_USDT,INT_USDT,IPC_USDT,IOST_USDT,INS_USDT,YEE_USDT,MOF_USDT,TCT_USDT,STC_USDT,THETA_USDT,PST_USDT,MKR_USDT,LIGHT_USDT,TRUE_USDT,OF_USDT,SOC_USDT,ZEN_USDT,HMC_USDT,ZIP_USDT,NANO_USDT,CIC_USDT,GTO_USDT,CHAT_USDT,INSUR_USDT,R_USDT,BEC_USDT,MITH_USDT,ABT_USDT,BKX_USDT,RFR_USDT,TRIO_USDT,DADI_USDT,ONT_USDT,OKB_USDT,NEO_OKB,LTC_OKB,ETC_OKB,XRP_OKB,ZEC_OKB,QTUM_OKB,IOTA_OKB,EOS_OKB", - "enabledPairs": "eos_usdt", + "availablePairs": "DASH-BTC,CTXC-BTC,ZIL-BTC,YOU-BTC,LBA-BTC,LSK-BTC,CAI-BTC,AE-BTC,SC-BTC,KAN-BTC,WIN-BTC,DCR-BTC,WAVES-BTC,ORS-BTC,NXT-BTC,ARDR-BTC,XAS-BTC,CVT-BTC,EGT-BTC,ZCO-BTC,LET-BTC,HPB-BTC,ADA-BTC,HYC-BTC,VITE-BTC,ABL-BTC,PAX-BTC,TUSD-BTC,USDC-BTC,GUSD-BTC,BCH-BTC,BSV-BTC,BTT-BTC,ATOM-BTC,BLOC-BTC,XRP-BTC,LRC-BTC,NULS-BTC,MCO-BTC,ELF-BTC,ZEC-BTC,CMT-BTC,ITC-BTC,SBTC-BTC,EDO-BTC,BCX-BTC,NEO-BTC,GAS-BTC,HC-BTC,QTUM-BTC,IOTA-BTC,XUC-BTC,EOS-BTC,SNT-BTC,OMG-BTC,LTC-BTC,ETH-BTC,ETC-BTC,BCD-BTC,BTG-BTC,ACT-BTC,PAY-BTC,BTM-BTC,DGD-BTC,GNT-BTC,LINK-BTC,WTC-BTC,ZRX-BTC,BNT-BTC,CVC-BTC,MANA-BTC,KNC-BTC,GNX-BTC,ICX-BTC,XEM-BTC,ARK-BTC,YOYO-BTC,FUN-BTC,ACE-BTC,TRX-BTC,DGB-BTC,SWFTC-BTC,XMR-BTC,XLM-BTC,KCASH-BTC,MDT-BTC,NAS-BTC,UGC-BTC,DPY-BTC,SSC-BTC,AAC-BTC,VIB-BTC,QUN-BTC,INT-BTC,IOST-BTC,INS-BTC,MOF-BTC,TCT-BTC,STC-BTC,THETA-BTC,PST-BTC,SNC-BTC,MKR-BTC,LIGHT-BTC,TRUE-BTC,OF-BTC,SOC-BTC,ZEN-BTC,HMC-BTC,ZIP-BTC,NANO-BTC,CIC-BTC,GTO-BTC,CHAT-BTC,INSUR-BTC,R-BTC,BEC-BTC,MITH-BTC,ABT-BTC,BKX-BTC,RFR-BTC,TRIO-BTC,DADI-BTC,ONT-BTC,OKB-BTC,CTXC-ETH,ZIL-ETH,YOU-ETH,LBA-ETH,LSK-ETH,CAI-ETH,SC-ETH,AE-ETH,KAN-ETH,WIN-ETH,DCR-ETH,WAVES-ETH,ORS-ETH,MVP-ETH,EGT-ETH,ZCO-ETH,LET-ETH,HPB-ETH,SDA-ETH,ADA-ETH,HYC-ETH,VITE-ETH,ABL-ETH,BTT-ETH,ATOM-ETH,ELF-ETH,LTC-ETH,CMT-ETH,ITC-ETH,PRA-ETH,EDO-ETH,LRC-ETH,NULS-ETH,MCO-ETH,STORJ-ETH,SNT-ETH,PAY-ETH,DGD-ETH,GNT-ETH,ACT-ETH,BTM-ETH,EOS-ETH,OMG-ETH,DASH-ETH,XRP-ETH,ZEC-ETH,NEO-ETH,GAS-ETH,HC-ETH,QTUM-ETH,IOTA-ETH,XUC-ETH,ETC-ETH,LINK-ETH,WTC-ETH,ZRX-ETH,BNT-ETH,CVC-ETH,MANA-ETH,GNX-ETH,ICX-ETH,XEM-ETH,ARK-ETH,YOYO-ETH,TRX-ETH,DGB-ETH,PPT-ETH,SWFTC-ETH,XMR-ETH,XLM-ETH,KCASH-ETH,MDT-ETH,NAS-ETH,RNT-ETH,UGC-ETH,DPY-ETH,SSC-ETH,AAC-ETH,FAIR-ETH,RCT-ETH,VIB-ETH,TOPC-ETH,QUN-ETH,INT-ETH,IOST-ETH,INS-ETH,MOF-ETH,REF-ETH,THETA-ETH,PST-ETH,SNC-ETH,MKR-ETH,LIGHT-ETH,TRUE-ETH,OF-ETH,SOC-ETH,ZEN-ETH,HMC-ETH,ZIP-ETH,NANO-ETH,CIC-ETH,GTO-ETH,INSUR-ETH,R-ETH,UCT-ETH,BEC-ETH,MITH-ETH,ABT-ETH,BKX-ETH,AUTO-ETH,RFR-ETH,TRIO-ETH,TRA-ETH,DADI-ETH,ONT-ETH,OKB-ETH,CTXC-USDT,ZIL-USDT,YOU-OKB,YOU-USDT,LBA-OKB,LBA-USDT,CAI-OKB,LSK-USDT,CAI-USDT,AE-OKB,SC-OKB,KAN-OKB,WIN-OKB,SC-USDT,AE-USDT,KAN-USDT,WIN-USDT,ORS-OKB,DCR-OKB,DCR-USDT,WAVES-OKB,WAVES-USDT,ORS-USDT,MVP-USDT,NAS-OKB,XAS-OKB,ZCO-OKB,EGT-OKB,XAS-USDT,CVT-USDT,EGT-USDT,LET-OKB,LET-USDT,HPB-OKB,HPB-USDT,SDA-OKB,ADA-OKB,ADA-USDT,HYC-USDT,VITE-OKB,TRX-OKB,PAX-USDT,TUSD-USDT,USDC-USDT,GUSD-USDT,BCH-USDT,BSV-USDT,BTT-USDT,BLOC-OKB,BLOC-USDT,ATOM-USDT,ELF-USDT,DASH-USDT,LRC-USDT,NULS-USDT,MCO-USDT,BTG-USDT,DASH-OKB,XRP-USDT,ZEC-USDT,NEO-USDT,GAS-USDT,HC-USDT,QTUM-USDT,IOTA-USDT,BTC-USDT,BCD-USDT,XUC-USDT,CMT-USDT,ITC-USDT,PRA-USDT,EDO-USDT,ETH-USDT,LTC-USDT,ETC-USDT,EOS-USDT,OMG-USDT,ACT-USDT,BTM-USDT,STORJ-USDT,PAY-USDT,DGD-USDT,GNT-USDT,SNT-USDT,LINK-USDT,WTC-USDT,ZRX-USDT,BNT-USDT,CVC-USDT,MANA-USDT,KNC-USDT,ICX-USDT,XEM-USDT,ARK-USDT,YOYO-USDT,AST-USDT,TRX-USDT,MDA-USDT,DGB-USDT,PPT-USDT,SWFTC-USDT,XMR-USDT,XLM-USDT,KCASH-USDT,MDT-USDT,NAS-USDT,RNT-USDT,UGC-USDT,DPY-USDT,SSC-USDT,AAC-USDT,FAIR-USDT,UBTC-USDT,SHOW-USDT,VIB-USDT,MOT-USDT,UTK-USDT,TOPC-USDT,QUN-USDT,INT-USDT,IPC-USDT,IOST-USDT,INS-USDT,YEE-USDT,MOF-USDT,TCT-USDT,STC-USDT,THETA-USDT,PST-USDT,MKR-USDT,LIGHT-USDT,TRUE-USDT,OF-USDT,SOC-USDT,ZEN-USDT,HMC-USDT,ZIP-USDT,NANO-USDT,CIC-USDT,GTO-USDT,CHAT-USDT,INSUR-USDT,R-USDT,BEC-USDT,MITH-USDT,ABT-USDT,BKX-USDT,RFR-USDT,TRIO-USDT,DADI-USDT,ONT-USDT,OKB-USDT,NEO-OKB,LTC-OKB,ETC-OKB,XRP-OKB,ZEC-OKB,QTUM-OKB,IOTA-OKB,EOS-OKB", + "enabledPairs": "EOS-USDT", "baseCurrencies": "USD", "assetTypes": "SPOT", "supportsAutoPairUpdates": true, "configCurrencyPairFormat": { "uppercase": true, - "delimiter": "_" + "delimiter": "-" }, "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "uppercase": true, + "delimiter": "-" }, "bankAccounts": [ { diff --git a/currency/pair.go b/currency/pair.go index 9c46730b..74e22195 100644 --- a/currency/pair.go +++ b/currency/pair.go @@ -62,7 +62,7 @@ func NewPairFromIndex(currencyPair, index string) (Pair, error) { // NewPairFromString converts currency string into a new CurrencyPair // with or without delimeter func NewPairFromString(currencyPair string) Pair { - delimiters := []string{"_", "-", "/"} + delimiters := []string{"_", "-", "/", ":"} var delimiter string for _, x := range delimiters { if strings.Contains(currencyPair, x) { diff --git a/engine/routines.go b/engine/routines.go index 1c553f39..54f140d4 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -428,18 +428,27 @@ func WebsocketDataHandler(ws *wshandler.Websocket) { // } tickerNew := ticker.Price{ + Last: d.Last, + High: d.High, + Low: d.Low, + Bid: d.Bid, + Ask: d.Ask, + Volume: d.Volume, + QuoteVolume: d.QuoteVolume, + PriceATH: d.PriceATH, + Open: d.Open, + Close: d.Close, Pair: d.Pair, LastUpdated: d.Timestamp, - Last: d.ClosePrice, - High: d.HighPrice, - Low: d.LowPrice, - Volume: d.Quantity, } if Bot.Settings.EnableExchangeSyncManager && Bot.ExchangeCurrencyPairManager != nil { Bot.ExchangeCurrencyPairManager.update(ws.GetName(), d.Pair, d.AssetType, SyncItemTicker, nil) } - ticker.ProcessTicker(ws.GetName(), &tickerNew, d.AssetType) + err := ticker.ProcessTicker(ws.GetName(), &tickerNew, d.AssetType) + if err != nil { + log.Errorf(log.WebsocketMgr, "routines.go exchange %s websocket error - %s", ws.GetName(), err) + } printTickerSummary(&tickerNew, tickerNew.Pair, d.AssetType, ws.GetName(), nil) case wshandler.KlineData: // Kline data diff --git a/engine/syncer.go b/engine/syncer.go index cf254f57..ff84a516 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -486,6 +486,8 @@ func (e *ExchangeCurrencyPairSyncer) Start() { } else { usingWebsocket = true } + } else { + usingWebsocket = true } } else if supportsREST { usingREST = true diff --git a/exchanges/anx/anx_types.go b/exchanges/anx/anx_types.go index 7c590ee8..cfcee857 100644 --- a/exchanges/anx/anx_types.go +++ b/exchanges/anx/anx_types.go @@ -161,16 +161,16 @@ type TickerComponent struct { type Ticker struct { Result string `json:"result"` Data struct { - High TickerComponent `json:"high"` - Low TickerComponent `json:"low"` - Avg TickerComponent `json:"avg"` - Vwap TickerComponent `json:"vwap"` - Vol TickerComponent `json:"vol"` - Last TickerComponent `json:"last"` - Buy TickerComponent `json:"buy"` - Sell TickerComponent `json:"sell"` - Now string `json:"now"` - UpdateTime string `json:"dataUpdateTime"` + High TickerComponent `json:"high"` + Low TickerComponent `json:"low"` + Average TickerComponent `json:"avg"` + VolumeWeightedAveragePrice TickerComponent `json:"vwap"` + Volume TickerComponent `json:"vol"` + Last TickerComponent `json:"last"` + Buy TickerComponent `json:"buy"` + Sell TickerComponent `json:"sell"` + Now int64 `json:"now,string"` + UpdateTime int64 `json:"dataUpdateTime,string"` } `json:"data"` } diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 0cd37dd0..6dcaab39 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -2,12 +2,12 @@ package anx import ( "fmt" - "strconv" "strings" "sync" "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -173,7 +173,7 @@ func (a *ANX) FetchTradablePairs(asset asset.Item) ([]string, error) { var currencies []string for x := range result.CurrencyPairs { - currencies = append(currencies, result.CurrencyPairs[x].TradedCcy+"_"+result.CurrencyPairs[x].SettlementCcy) + currencies = append(currencies, fmt.Sprintf("%v%v%v", result.CurrencyPairs[x].TradedCcy, a.GetPairFormat(asset, false).Delimiter, result.CurrencyPairs[x].SettlementCcy)) } return currencies, nil @@ -186,61 +186,22 @@ func (a *ANX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, if err != nil { return tickerPrice, err } + last, _ := convert.FloatFromString(tick.Data.Last.Value) + high, _ := convert.FloatFromString(tick.Data.High.Value) + low, _ := convert.FloatFromString(tick.Data.Low.Value) + bid, _ := convert.FloatFromString(tick.Data.Buy.Value) + ask, _ := convert.FloatFromString(tick.Data.Sell.Value) + volume, _ := convert.FloatFromString(tick.Data.Volume.Value) - tickerPrice.Pair = p - - 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 + tickerPrice = ticker.Price{ + Last: last, + High: high, + Low: low, + Bid: bid, + Ask: ask, + Volume: volume, + Pair: p, + LastUpdated: time.Unix(0, tick.Data.UpdateTime), } err = ticker.ProcessTicker(a.GetName(), &tickerPrice, assetType) diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index 276b4480..78bfc660 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -167,29 +167,29 @@ type KlineStream struct { // TickerStream holds the ticker stream data type TickerStream struct { - EventType string `json:"e"` - EventTime int64 `json:"E"` - Symbol string `json:"s"` - PriceChange string `json:"p"` - PriceChangePercent string `json:"P"` - WeightedAvgPrice string `json:"w"` - PrevDayClose string `json:"x"` - CurrDayClose string `json:"c"` - CloseTradeQuantity string `json:"Q"` - BestBidPrice string `json:"b"` - BestBidQuantity string `json:"B"` - BestAskPrice string `json:"a"` - BestAskQuantity string `json:"A"` - OpenPrice string `json:"o"` - HighPrice string `json:"h"` - LowPrice string `json:"l"` - TotalTradedVolume string `json:"v"` - TotalTradedQuoteVolume string `json:"q"` - OpenTime int64 `json:"O"` - CloseTime int64 `json:"C"` - FirstTradeID int64 `json:"F"` - LastTradeID int64 `json:"L"` - NumberOfTrades int64 `json:"n"` + EventType string `json:"e"` + EventTime int64 `json:"E"` + Symbol currency.Pair `json:"s"` + PriceChange float64 `json:"p,string"` + PriceChangePercent float64 `json:"P,string"` + WeightedAvgPrice float64 `json:"w,string"` + ClosePrice float64 `json:"x,string"` + LastPrice float64 `json:"c,string"` + LastPriceQuantity float64 `json:"Q,string"` + BestBidPrice float64 `json:"b,string"` + BestBidQuantity float64 `json:"B,string"` + BestAskPrice float64 `json:"a,string"` + BestAskQuantity float64 `json:"A,string"` + OpenPrice float64 `json:"o,string"` + HighPrice float64 `json:"h,string"` + LowPrice float64 `json:"l,string"` + TotalTradedVolume float64 `json:"v,string"` + TotalTradedQuoteVolume float64 `json:"q,string"` + OpenTime int64 `json:"O"` + CloseTime int64 `json:"C"` + FirstTradeID int64 `json:"F"` + LastTradeID int64 `json:"L"` + NumberOfTrades int64 `json:"n"` } // HistoricalTrade holds recent trade data @@ -239,25 +239,25 @@ type AveragePrice struct { // PriceChangeStats contains statistics for the last 24 hours trade type PriceChangeStats struct { - Symbol string `json:"symbol"` - PriceChange float64 `json:"priceChange,string"` - PriceChangePercent float64 `json:"priceChangePercent,string"` - WeightedAvgPrice float64 `json:"weightedAvgPrice,string"` - PrevClosePrice float64 `json:"prevClosePrice,string"` - LastPrice float64 `json:"lastPrice,string"` - LastQty float64 `json:"lastQty,string"` - BidPrice float64 `json:"bidPrice,string"` - AskPrice float64 `json:"askPrice,string"` - OpenPrice float64 `json:"openPrice,string"` - HighPrice float64 `json:"highPrice,string"` - LowPrice float64 `json:"lowPrice,string"` - Volume float64 `json:"volume,string"` - QuoteVolume float64 `json:"quoteVolume,string"` - OpenTime int64 `json:"openTime"` - CloseTime int64 `json:"closeTime"` - FirstID int64 `json:"fristId"` - LastID int64 `json:"lastId"` - Count int64 `json:"count"` + Symbol currency.Pair `json:"symbol"` + PriceChange float64 `json:"priceChange,string"` + PriceChangePercent float64 `json:"priceChangePercent,string"` + WeightedAvgPrice float64 `json:"weightedAvgPrice,string"` + PrevClosePrice float64 `json:"prevClosePrice,string"` + LastPrice float64 `json:"lastPrice,string"` + LastQty float64 `json:"lastQty,string"` + BidPrice float64 `json:"bidPrice,string"` + AskPrice float64 `json:"askPrice,string"` + OpenPrice float64 `json:"openPrice,string"` + HighPrice float64 `json:"highPrice,string"` + LowPrice float64 `json:"lowPrice,string"` + Volume float64 `json:"volume,string"` + QuoteVolume float64 `json:"quoteVolume,string"` + OpenTime int64 `json:"openTime"` + CloseTime int64 `json:"closeTime"` + FirstID int64 `json:"fristId"` + LastID int64 `json:"lastId"` + Count int64 `json:"count"` } // SymbolPrice holds basic symbol price diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index 052d53fe..32108435 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -147,18 +147,21 @@ func (b *Binance) WsHandleData() { continue } - var wsTicker wshandler.TickerData - wsTicker.Timestamp = time.Unix(t.EventTime/1000, 0) - wsTicker.Pair = currency.NewPairFromString(t.Symbol) - wsTicker.AssetType = asset.Spot - wsTicker.Exchange = b.GetName() - wsTicker.ClosePrice, _ = strconv.ParseFloat(t.CurrDayClose, 64) - wsTicker.Quantity, _ = strconv.ParseFloat(t.TotalTradedVolume, 64) - wsTicker.OpenPrice, _ = strconv.ParseFloat(t.OpenPrice, 64) - wsTicker.HighPrice, _ = strconv.ParseFloat(t.HighPrice, 64) - wsTicker.LowPrice, _ = strconv.ParseFloat(t.LowPrice, 64) - - b.Websocket.DataHandler <- wsTicker + b.Websocket.DataHandler <- wshandler.TickerData{ + Exchange: b.Name, + Open: t.OpenPrice, + Close: t.ClosePrice, + Volume: t.TotalTradedVolume, + QuoteVolume: t.TotalTradedQuoteVolume, + High: t.HighPrice, + Low: t.LowPrice, + Bid: t.BestBidPrice, + Ask: t.BestAskPrice, + Last: t.LastPrice, + Timestamp: time.Unix(0, t.EventTime), + AssetType: asset.Spot, + Pair: t.Symbol, + } continue case "kline": diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 5966e596..b046889d 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -163,9 +163,9 @@ func (b *Binance) Run() { } forceUpdate := false - if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), "-") || - !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), "-") { - enabledPairs := currency.NewPairsFromStrings([]string{"BTC-USDT"}) + if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), b.GetPairFormat(asset.Spot, false).Delimiter) || + !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), b.GetPairFormat(asset.Spot, false).Delimiter) { + enabledPairs := currency.NewPairsFromStrings([]string{fmt.Sprintf("BTC%vUSDT", b.GetPairFormat(asset.Spot, false).Delimiter)}) log.Warn(log.ExchangeSys, "Available pairs for Binance reset due to config upgrade, please enable the ones you would like again") forceUpdate = true @@ -197,8 +197,10 @@ func (b *Binance) FetchTradablePairs(asset asset.Item) ([]string, error) { for x := range info.Symbols { if info.Symbols[x].Status == "TRADING" { - validCurrencyPairs = append(validCurrencyPairs, - info.Symbols[x].BaseAsset+"-"+info.Symbols[x].QuoteAsset) + validCurrencyPairs = append(validCurrencyPairs, fmt.Sprintf("%v%v%v", + info.Symbols[x].BaseAsset, + b.GetPairFormat(asset, false).Delimiter, + info.Symbols[x].QuoteAsset)) } } return validCurrencyPairs, nil @@ -222,21 +224,28 @@ func (b *Binance) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr if err != nil { return tickerPrice, err } - - for _, x := range b.GetEnabledPairs(assetType) { - curr := b.FormatExchangeCurrency(x, assetType) + pairs := b.GetEnabledPairs(assetType) + for i := range pairs { for y := range tick { - if tick[y].Symbol != curr.String() { + if !tick[y].Symbol.Equal(pairs[i]) { continue } - tickerPrice.Pair = x - tickerPrice.Ask = tick[y].AskPrice - tickerPrice.Bid = tick[y].BidPrice - tickerPrice.High = tick[y].HighPrice - tickerPrice.Last = tick[y].LastPrice - tickerPrice.Low = tick[y].LowPrice - tickerPrice.Volume = tick[y].Volume - ticker.ProcessTicker(b.Name, &tickerPrice, assetType) + tickerPrice := ticker.Price{ + Last: tick[y].LastPrice, + High: tick[y].HighPrice, + Low: tick[y].LowPrice, + Bid: tick[y].BidPrice, + Ask: tick[y].AskPrice, + Volume: tick[y].Volume, + QuoteVolume: tick[y].QuoteVolume, + Open: tick[y].OpenPrice, + Close: tick[y].PrevClosePrice, + Pair: pairs[i], + } + err = ticker.ProcessTicker(b.Name, &tickerPrice, assetType) + if err != nil { + log.Error(log.Ticker, err) + } } } return ticker.GetTicker(b.Name, p, assetType) diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 4f301994..3659ca83 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -48,6 +48,20 @@ func TestSetup(t *testing.T) { b.Requester.SetRateLimit(false, time.Millisecond*300, 1) } +func TestAppendOptionalDelimiter(t *testing.T) { + curr1 := currency.NewPairFromString("BTCUSD") + b.appendOptionalDelimiter(&curr1) + if curr1.Delimiter != "" { + t.Errorf("Expected no delimiter, received %v", curr1.Delimiter) + } + curr2 := currency.NewPairFromString("DUSK:USD") + curr2.Delimiter = "" + b.appendOptionalDelimiter(&curr2) + if curr2.Delimiter != ":" { + t.Errorf("Expected \"-\" as a delimiter, received %v", curr2.Delimiter) + } +} + func TestGetPlatformStatus(t *testing.T) { t.Parallel() diff --git a/exchanges/bitfinex/bitfinex_types.go b/exchanges/bitfinex/bitfinex_types.go index 38a5d585..a54e7584 100644 --- a/exchanges/bitfinex/bitfinex_types.go +++ b/exchanges/bitfinex/bitfinex_types.go @@ -1,5 +1,7 @@ package bitfinex +import "time" + // Ticker holds basic ticker information from the exchange type Ticker struct { Mid float64 `json:"mid,string"` @@ -28,6 +30,7 @@ type Tickerv2 struct { Volume float64 High float64 Low float64 + Timestamp time.Time } // Tickersv2 holds the version 2 tickers information diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 7b88ecd6..7503304f 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -271,13 +271,15 @@ func (b *Bitfinex) WsDataHandler() { } case "ticker": b.Websocket.DataHandler <- wshandler.TickerData{ - Quantity: chanData[8].(float64), - ClosePrice: chanData[7].(float64), - HighPrice: chanData[9].(float64), - LowPrice: chanData[10].(float64), - Pair: currency.NewPairFromString(chanInfo.Pair), - Exchange: b.GetName(), - AssetType: asset.Spot, + Exchange: b.Name, + Volume: chanData[8].(float64), + High: chanData[9].(float64), + Low: chanData[10].(float64), + Bid: chanData[1].(float64), + Ask: chanData[3].(float64), + Last: chanData[7].(float64), + AssetType: asset.Spot, + Pair: currency.NewPairFromString(chanInfo.Pair), } case "account": @@ -539,6 +541,7 @@ func (b *Bitfinex) GenerateDefaultSubscriptions() { for i := range channels { enabledPairs := b.GetEnabledPairs(asset.Spot) for j := range enabledPairs { + b.appendOptionalDelimiter(&enabledPairs[j]) params := make(map[string]interface{}) if channels[i] == "book" { params["prec"] = "P0" diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index a0e76ac6..4fbe2455 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -194,40 +194,39 @@ func (b *Bitfinex) UpdateTradablePairs(forceUpdate bool) error { func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price enabledPairs := b.GetEnabledPairs(assetType) - var pairs []string for x := range enabledPairs { + b.appendOptionalDelimiter(&enabledPairs[x]) pairs = append(pairs, "t"+enabledPairs[x].String()) } - tickerNew, err := b.GetTickersV2(strings.Join(pairs, ",")) if err != nil { return tickerPrice, err } - - for x := range tickerNew { - newP := currency.NewPairFromStrings(tickerNew[x].Symbol[1:4], - tickerNew[x].Symbol[4:]) - - var tick ticker.Price - tick.Pair = newP - tick.Ask = tickerNew[x].Ask - tick.Bid = tickerNew[x].Bid - tick.Low = tickerNew[x].Low - tick.Last = tickerNew[x].Last - tick.Volume = tickerNew[x].Volume - tick.High = tickerNew[x].High - + for i := range tickerNew { + newP := tickerNew[i].Symbol[1:] // Remove the "t" prefix + tick := ticker.Price{ + Last: tickerNew[i].Last, + High: tickerNew[i].High, + Low: tickerNew[i].Low, + Bid: tickerNew[i].Bid, + Ask: tickerNew[i].Ask, + Volume: tickerNew[i].Volume, + Pair: currency.NewPairFromString(newP), + LastUpdated: tickerNew[i].Timestamp, + } err = ticker.ProcessTicker(b.Name, &tick, assetType) if err != nil { - return tickerPrice, err + log.Error(log.Ticker, err) } } + return ticker.GetTicker(b.Name, p, assetType) } // FetchTicker returns the ticker for a currency pair func (b *Bitfinex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { + b.appendOptionalDelimiter(&p) tick, err := ticker.GetTicker(b.GetName(), p, asset.Spot) if err != nil { return b.UpdateTicker(p, assetType) @@ -237,6 +236,7 @@ func (b *Bitfinex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pr // FetchOrderbook returns the orderbook for a currency pair func (b *Bitfinex) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { + b.appendOptionalDelimiter(&p) ob, err := orderbook.Get(b.GetName(), p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) @@ -246,6 +246,7 @@ func (b *Bitfinex) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderb // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { + b.appendOptionalDelimiter(&p) var orderBook orderbook.Base urlVals := url.Values{} urlVals.Set("limit_bids", "100") @@ -339,7 +340,7 @@ func (b *Bitfinex) SubmitOrder(order *exchange.OrderSubmission) (exchange.Submit if order.OrderSide == exchange.BuyOrderSide { isBuying = true } - + b.appendOptionalDelimiter(&order.Pair) response, err := b.NewOrder(order.Pair.String(), order.Amount, order.Price, @@ -592,6 +593,9 @@ func (b *Bitfinex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + for i := range getOrdersRequest.Currencies { + b.appendOptionalDelimiter(&getOrdersRequest.Currencies[i]) + } exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) return orders, nil } @@ -599,6 +603,9 @@ func (b *Bitfinex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) // SubscribeToWebsocketChannels appends to ChannelsToSubscribe // which lets websocket.manageSubscriptions handle subscribing func (b *Bitfinex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { + for i := range channels { + b.appendOptionalDelimiter(&channels[i].Currency) + } b.Websocket.SubscribeToChannels(channels) return nil } @@ -606,6 +613,9 @@ func (b *Bitfinex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketCh // UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe // which lets websocket.manageSubscriptions handle unsubscribing func (b *Bitfinex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { + for i := range channels { + b.appendOptionalDelimiter(&channels[i].Currency) + } b.Websocket.RemoveSubscribedChannels(channels) return nil } @@ -619,3 +629,11 @@ func (b *Bitfinex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, func (b *Bitfinex) AuthenticateWebsocket() error { return b.WsSendAuth() } + +// appendOptionalDelimiter ensures that a delimiter is present for long character currencies +func (b *Bitfinex) appendOptionalDelimiter(p *currency.Pair) { + if len(p.Quote.String()) > 3 || + len(p.Base.String()) > 3 { + p.Delimiter = ":" + } +} diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index 08223ba8..ab9f7ddc 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -138,7 +138,7 @@ func (b *Bitflyer) FetchTradablePairs(assetType asset.Item) ([]string, error) { for _, info := range pairs { if info.Alias != "" && assetType == asset.Futures { products = append(products, info.Alias) - } else if info.Alias == "" && assetType == asset.Spot && strings.Contains(info.ProductCode, "_") { + } else if info.Alias == "" && assetType == asset.Spot && strings.Contains(info.ProductCode, b.GetPairFormat(assetType, false).Delimiter) { products = append(products, info.ProductCode) } } @@ -177,10 +177,8 @@ func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.P tickerPrice.Pair = p tickerPrice.Ask = tickerNew.BestAsk tickerPrice.Bid = tickerNew.BestBid - // tickerPrice.Low tickerPrice.Last = tickerNew.Last tickerPrice.Volume = tickerNew.Volume - // tickerPrice.High err = ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) if err != nil { return tickerPrice, err diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 16a7138f..7871622a 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -154,24 +154,27 @@ func (b *Bithumb) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (b *Bithumb) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - tickers, err := b.GetAllTickers() if err != nil { return tickerPrice, err } - - for _, x := range b.GetEnabledPairs(assetType) { - currency := x.Base.String() - var tp ticker.Price - tp.Pair = x - tp.Low = tickers[currency].MinPrice - tp.Last = tickers[currency].ClosingPrice - tp.Volume = tickers[currency].UnitsTraded24Hr - tp.High = tickers[currency].MaxPrice - + pairs := b.GetEnabledPairs(assetType) + for i := range pairs { + curr := pairs[i].Base.String() + if _, ok := tickers[curr]; !ok { + continue + } + tp := ticker.Price{ + High: tickers[curr].MaxPrice, + Low: tickers[curr].MinPrice, + Volume: tickers[curr].UnitsTraded24Hr, + Open: tickers[curr].OpeningPrice, + Close: tickers[curr].ClosingPrice, + Pair: pairs[i], + } err = ticker.ProcessTicker(b.Name, &tp, assetType) if err != nil { - return tickerPrice, err + log.Error(log.Ticker, err) } } return ticker.GetTicker(b.Name, p, assetType) diff --git a/exchanges/bitmex/bitmex_types.go b/exchanges/bitmex/bitmex_types.go index 3a84e8fa..399ee0a9 100644 --- a/exchanges/bitmex/bitmex_types.go +++ b/exchanges/bitmex/bitmex_types.go @@ -1,6 +1,11 @@ package bitmex -import exchange "github.com/thrasher-corp/gocryptotrader/exchanges" +import ( + "time" + + "github.com/thrasher-corp/gocryptotrader/currency" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" +) // RequestError allows for a general error capture from requests type RequestError struct { @@ -117,107 +122,107 @@ type Funding struct { // Instrument Tradeable Contracts, Indices, and History type Instrument struct { - AskPrice float64 `json:"askPrice"` - BankruptLimitDownPrice float64 `json:"bankruptLimitDownPrice"` - BankruptLimitUpPrice float64 `json:"bankruptLimitUpPrice"` - BidPrice float64 `json:"bidPrice"` - BuyLeg string `json:"buyLeg"` - CalcInterval string `json:"calcInterval"` - Capped bool `json:"capped"` - ClosingTimestamp string `json:"closingTimestamp"` - Deleverage bool `json:"deleverage"` - Expiry string `json:"expiry"` - FairBasis float64 `json:"fairBasis"` - FairBasisRate float64 `json:"fairBasisRate"` - FairMethod string `json:"fairMethod"` - FairPrice float64 `json:"fairPrice"` - Front string `json:"front"` - FundingBaseSymbol string `json:"fundingBaseSymbol"` - FundingInterval string `json:"fundingInterval"` - FundingPremiumSymbol string `json:"fundingPremiumSymbol"` - FundingQuoteSymbol string `json:"fundingQuoteSymbol"` - FundingRate float64 `json:"fundingRate"` - FundingTimestamp string `json:"fundingTimestamp"` - HasLiquidity bool `json:"hasLiquidity"` - HighPrice float64 `json:"highPrice"` - ImpactAskPrice float64 `json:"impactAskPrice"` - ImpactBidPrice float64 `json:"impactBidPrice"` - ImpactMidPrice float64 `json:"impactMidPrice"` - IndicativeFundingRate float64 `json:"indicativeFundingRate"` - IndicativeSettlePrice float64 `json:"indicativeSettlePrice"` - IndicativeTaxRate float64 `json:"indicativeTaxRate"` - InitMargin float64 `json:"initMargin"` - InsuranceFee float64 `json:"insuranceFee"` - InverseLeg string `json:"inverseLeg"` - IsInverse bool `json:"isInverse"` - IsQuanto bool `json:"isQuanto"` - LastChangePcnt float64 `json:"lastChangePcnt"` - LastPrice float64 `json:"lastPrice"` - LastPriceProtected float64 `json:"lastPriceProtected"` - LastTickDirection string `json:"lastTickDirection"` - Limit float64 `json:"limit"` - LimitDownPrice float64 `json:"limitDownPrice"` - LimitUpPrice float64 `json:"limitUpPrice"` - Listing string `json:"listing"` - LotSize int64 `json:"lotSize"` - LowPrice float64 `json:"lowPrice"` - MaintMargin float64 `json:"maintMargin"` - MakerFee float64 `json:"makerFee"` - MarkMethod string `json:"markMethod"` - MarkPrice float64 `json:"markPrice"` - MaxOrderQty int64 `json:"maxOrderQty"` - MaxPrice float64 `json:"maxPrice"` - MidPrice float64 `json:"midPrice"` - Multiplier int64 `json:"multiplier"` - OpenInterest int64 `json:"openInterest"` - OpenValue int64 `json:"openValue"` - OpeningTimestamp string `json:"openingTimestamp"` - OptionMultiplier float64 `json:"optionMultiplier"` - OptionStrikePcnt float64 `json:"optionStrikePcnt"` - OptionStrikePrice float64 `json:"optionStrikePrice"` - OptionStrikeRound float64 `json:"optionStrikeRound"` - OptionUnderlyingPrice float64 `json:"optionUnderlyingPrice"` - PositionCurrency string `json:"positionCurrency"` - PrevClosePrice float64 `json:"prevClosePrice"` - PrevPrice24h float64 `json:"prevPrice24h"` - PrevTotalTurnover int64 `json:"prevTotalTurnover"` - PrevTotalVolume int64 `json:"prevTotalVolume"` - PublishInterval string `json:"publishInterval"` - PublishTime string `json:"publishTime"` - QuoteCurrency string `json:"quoteCurrency"` - QuoteToSettleMultiplier int64 `json:"quoteToSettleMultiplier"` - RebalanceInterval string `json:"rebalanceInterval"` - RebalanceTimestamp string `json:"rebalanceTimestamp"` - Reference string `json:"reference"` - ReferenceSymbol string `json:"referenceSymbol"` - RelistInterval string `json:"relistInterval"` - RiskLimit int64 `json:"riskLimit"` - RiskStep int64 `json:"riskStep"` - RootSymbol string `json:"rootSymbol"` - SellLeg string `json:"sellLeg"` - SessionInterval string `json:"sessionInterval"` - SettlCurrency string `json:"settlCurrency"` - Settle string `json:"settle"` - SettledPrice float64 `json:"settledPrice"` - SettlementFee float64 `json:"settlementFee"` - State string `json:"state"` - Symbol string `json:"symbol"` - TakerFee float64 `json:"takerFee"` - Taxed bool `json:"taxed"` - TickSize float64 `json:"tickSize"` - Timestamp string `json:"timestamp"` - TotalTurnover int64 `json:"totalTurnover"` - TotalVolume int64 `json:"totalVolume"` - Turnover int64 `json:"turnover"` - Turnover24h int64 `json:"turnover24h"` - Typ string `json:"typ"` - Underlying string `json:"underlying"` - UnderlyingSymbol string `json:"underlyingSymbol"` - UnderlyingToPositionMultiplier int64 `json:"underlyingToPositionMultiplier"` - UnderlyingToSettleMultiplier int64 `json:"underlyingToSettleMultiplier"` - Volume int64 `json:"volume"` - Volume24h int64 `json:"volume24h"` - Vwap float64 `json:"vwap"` + AskPrice float64 `json:"askPrice"` + BankruptLimitDownPrice float64 `json:"bankruptLimitDownPrice"` + BankruptLimitUpPrice float64 `json:"bankruptLimitUpPrice"` + BidPrice float64 `json:"bidPrice"` + BuyLeg string `json:"buyLeg"` + CalcInterval string `json:"calcInterval"` + Capped bool `json:"capped"` + ClosingTimestamp string `json:"closingTimestamp"` + Deleverage bool `json:"deleverage"` + Expiry string `json:"expiry"` + FairBasis float64 `json:"fairBasis"` + FairBasisRate float64 `json:"fairBasisRate"` + FairMethod string `json:"fairMethod"` + FairPrice float64 `json:"fairPrice"` + Front string `json:"front"` + FundingBaseSymbol string `json:"fundingBaseSymbol"` + FundingInterval string `json:"fundingInterval"` + FundingPremiumSymbol string `json:"fundingPremiumSymbol"` + FundingQuoteSymbol string `json:"fundingQuoteSymbol"` + FundingRate float64 `json:"fundingRate"` + FundingTimestamp string `json:"fundingTimestamp"` + HasLiquidity bool `json:"hasLiquidity"` + HighPrice float64 `json:"highPrice"` + ImpactAskPrice float64 `json:"impactAskPrice"` + ImpactBidPrice float64 `json:"impactBidPrice"` + ImpactMidPrice float64 `json:"impactMidPrice"` + IndicativeFundingRate float64 `json:"indicativeFundingRate"` + IndicativeSettlePrice float64 `json:"indicativeSettlePrice"` + IndicativeTaxRate float64 `json:"indicativeTaxRate"` + InitMargin float64 `json:"initMargin"` + InsuranceFee float64 `json:"insuranceFee"` + InverseLeg string `json:"inverseLeg"` + IsInverse bool `json:"isInverse"` + IsQuanto bool `json:"isQuanto"` + LastChangePcnt float64 `json:"lastChangePcnt"` + LastPrice float64 `json:"lastPrice"` + LastPriceProtected float64 `json:"lastPriceProtected"` + LastTickDirection string `json:"lastTickDirection"` + Limit float64 `json:"limit"` + LimitDownPrice float64 `json:"limitDownPrice"` + LimitUpPrice float64 `json:"limitUpPrice"` + Listing string `json:"listing"` + LotSize int64 `json:"lotSize"` + LowPrice float64 `json:"lowPrice"` + MaintMargin float64 `json:"maintMargin"` + MakerFee float64 `json:"makerFee"` + MarkMethod string `json:"markMethod"` + MarkPrice float64 `json:"markPrice"` + MaxOrderQty int64 `json:"maxOrderQty"` + MaxPrice float64 `json:"maxPrice"` + MidPrice float64 `json:"midPrice"` + Multiplier int64 `json:"multiplier"` + OpenInterest int64 `json:"openInterest"` + OpenValue int64 `json:"openValue"` + OpeningTimestamp string `json:"openingTimestamp"` + OptionMultiplier float64 `json:"optionMultiplier"` + OptionStrikePcnt float64 `json:"optionStrikePcnt"` + OptionStrikePrice float64 `json:"optionStrikePrice"` + OptionStrikeRound float64 `json:"optionStrikeRound"` + OptionUnderlyingPrice float64 `json:"optionUnderlyingPrice"` + PositionCurrency string `json:"positionCurrency"` + PrevClosePrice float64 `json:"prevClosePrice"` + PrevPrice24h float64 `json:"prevPrice24h"` + PrevTotalTurnover int64 `json:"prevTotalTurnover"` + PrevTotalVolume int64 `json:"prevTotalVolume"` + PublishInterval string `json:"publishInterval"` + PublishTime string `json:"publishTime"` + QuoteCurrency string `json:"quoteCurrency"` + QuoteToSettleMultiplier int64 `json:"quoteToSettleMultiplier"` + RebalanceInterval string `json:"rebalanceInterval"` + RebalanceTimestamp string `json:"rebalanceTimestamp"` + Reference string `json:"reference"` + ReferenceSymbol string `json:"referenceSymbol"` + RelistInterval string `json:"relistInterval"` + RiskLimit int64 `json:"riskLimit"` + RiskStep int64 `json:"riskStep"` + RootSymbol string `json:"rootSymbol"` + SellLeg string `json:"sellLeg"` + SessionInterval string `json:"sessionInterval"` + SettlCurrency string `json:"settlCurrency"` + Settle string `json:"settle"` + SettledPrice float64 `json:"settledPrice"` + SettlementFee float64 `json:"settlementFee"` + State string `json:"state"` + Symbol currency.Pair `json:"symbol"` + TakerFee float64 `json:"takerFee"` + Taxed bool `json:"taxed"` + TickSize float64 `json:"tickSize"` + Timestamp time.Time `json:"timestamp"` + TotalTurnover int64 `json:"totalTurnover"` + TotalVolume int64 `json:"totalVolume"` + Turnover int64 `json:"turnover"` + Turnover24h int64 `json:"turnover24h"` + Typ string `json:"typ"` + Underlying string `json:"underlying"` + UnderlyingSymbol string `json:"underlyingSymbol"` + UnderlyingToPositionMultiplier int64 `json:"underlyingToPositionMultiplier"` + UnderlyingToSettleMultiplier int64 `json:"underlyingToSettleMultiplier"` + Volume float64 `json:"volume"` + Volume24h float64 `json:"volume24h"` + Vwap float64 `json:"vwap"` } // InstrumentInterval instrument interval diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index af31e895..167be7e4 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -2,7 +2,6 @@ package bitmex import ( "errors" - "fmt" "math" "strings" "sync" @@ -93,7 +92,7 @@ func (b *Bitmex) SetDefaults() { Websocket: true, RESTCapabilities: exchange.ProtocolFeatures{ AutoPairUpdates: true, - TickerBatching: false, + TickerBatching: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.WithdrawCryptoWithEmail | @@ -205,7 +204,7 @@ func (b *Bitmex) FetchTradablePairs(asset asset.Item) ([]string, error) { var products []string for x := range marketInfo { - products = append(products, marketInfo[x].Symbol) + products = append(products, marketInfo[x].Symbol.String()) } return products, nil @@ -260,24 +259,34 @@ func (b *Bitmex) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (b *Bitmex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - currency := b.FormatExchangeCurrency(p, assetType) - - tick, err := b.GetTrade(&GenericRequestParams{ - Symbol: currency.String(), - Reverse: true, - Count: 1}) + tick, err := b.GetActiveInstruments(&GenericRequestParams{}) if err != nil { return tickerPrice, err } - - if len(tick) == 0 { - return tickerPrice, fmt.Errorf("%s REST error: no ticker return", b.Name) + pairs := b.GetEnabledPairs(assetType) + for i := range pairs { + for j := range tick { + if !pairs[i].Equal(tick[j].Symbol) { + continue + } + tickerPrice = ticker.Price{ + Last: tick[j].LastPrice, + High: tick[j].HighPrice, + Low: tick[j].LowPrice, + Bid: tick[j].BidPrice, + Ask: tick[j].AskPrice, + Volume: tick[j].Volume24h, + Close: tick[j].PrevClosePrice, + Pair: tick[j].Symbol, + LastUpdated: tick[j].Timestamp, + } + err = ticker.ProcessTicker(b.Name, &tickerPrice, assetType) + if err != nil { + log.Error(log.Ticker, err) + } + } } - - tickerPrice.Pair = p - tickerPrice.Last = tick[0].Price - tickerPrice.Volume = float64(tick[0].Size) - return tickerPrice, ticker.ProcessTicker(b.Name, &tickerPrice, assetType) + return ticker.GetTicker(b.Name, p, assetType) } // FetchTicker returns the ticker for a currency pair diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 7a91c74b..612769ee 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -51,8 +51,8 @@ const ( bitstampAPIReturnType = "string" bitstampAPITradingPairsInfo = "trading-pairs-info" - bitstampAuthRate = 600 - bitstampUnauthRate = 600 + bitstampAuthRate = 8000 + bitstampUnauthRate = 8000 ) // Bitstamp is the overarching type across the bitstamp package diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index c07f84d5..790ca785 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -71,7 +71,6 @@ func (b *Bitstamp) SetDefaults() { Websocket: true, RESTCapabilities: exchange.ProtocolFeatures{ AutoPairUpdates: true, - TickerBatching: false, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.AutoWithdrawFiat, @@ -206,15 +205,19 @@ func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.P tick, err := b.GetTicker(p.String(), false) if err != nil { return tickerPrice, err - } - tickerPrice.Pair = p - tickerPrice.Ask = tick.Ask - tickerPrice.Bid = tick.Bid - tickerPrice.Low = tick.Low - tickerPrice.Last = tick.Last - tickerPrice.Volume = tick.Volume - tickerPrice.High = tick.High + + tickerPrice = ticker.Price{ + Last: tick.Last, + High: tick.High, + Low: tick.Low, + Bid: tick.Bid, + Ask: tick.Ask, + Volume: tick.Volume, + Open: tick.Open, + Pair: p, + LastUpdated: time.Unix(tick.Timestamp, 0), + } err = ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) if err != nil { diff --git a/exchanges/bittrex/bittrex_types.go b/exchanges/bittrex/bittrex_types.go index 854281a5..be400fd5 100644 --- a/exchanges/bittrex/bittrex_types.go +++ b/exchanges/bittrex/bittrex_types.go @@ -1,6 +1,8 @@ package bittrex -import "encoding/json" +import ( + "encoding/json" +) // Response is the generalised response type for Bittrex type Response struct { diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index ffa41e59..6985f74f 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -196,27 +196,36 @@ func (b *Bittrex) GetAccountInfo() (exchange.AccountInfo, error) { // UpdateTicker updates and returns the ticker for a currency pair func (b *Bittrex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - tick, err := b.GetMarketSummaries() + ticks, err := b.GetMarketSummaries() if err != nil { return tickerPrice, err } - - for _, x := range b.GetEnabledPairs(assetType) { - curr := b.FormatExchangeCurrency(x, assetType) - for y := range tick.Result { - if tick.Result[y].MarketName != curr.String() { + pairs := b.GetEnabledPairs(assetType) + for i := range pairs { + for j := range ticks.Result { + if !strings.EqualFold(ticks.Result[j].MarketName, pairs[i].String()) { continue } - tickerPrice.Pair = x - tickerPrice.High = tick.Result[y].High - tickerPrice.Low = tick.Result[y].Low - tickerPrice.Ask = tick.Result[y].Ask - tickerPrice.Bid = tick.Result[y].Bid - tickerPrice.Last = tick.Result[y].Last - tickerPrice.Volume = tick.Result[y].Volume - ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) + tickerTime, _ := time.Parse(time.RFC3339, ticks.Result[j].TimeStamp) + tickerPrice = ticker.Price{ + Last: ticks.Result[j].Last, + High: ticks.Result[j].High, + Low: ticks.Result[j].Low, + Bid: ticks.Result[j].Bid, + Ask: ticks.Result[j].Ask, + Volume: ticks.Result[j].BaseVolume, + QuoteVolume: ticks.Result[j].Volume, + Close: ticks.Result[j].PrevDay, + Pair: pairs[i], + LastUpdated: tickerTime, + } + err = ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) + if err != nil { + log.Error(log.Ticker, err) + } } } + return ticker.GetTicker(b.Name, p, assetType) } diff --git a/exchanges/btcmarkets/btcmarkets_types.go b/exchanges/btcmarkets/btcmarkets_types.go index 896cf221..4f23cfe4 100644 --- a/exchanges/btcmarkets/btcmarkets_types.go +++ b/exchanges/btcmarkets/btcmarkets_types.go @@ -30,13 +30,16 @@ type Market struct { // Ticker holds ticker information type Ticker struct { - BestBID float64 `json:"bestBid"` - BestAsk float64 `json:"bestAsk"` - LastPrice float64 `json:"lastPrice"` - Currency string `json:"currency"` - Instrument string `json:"instrument"` - Timestamp int64 `json:"timestamp"` - Volume float64 `json:"volume24h"` + BestAsk float64 `json:"bestAsk"` + BestBid float64 `json:"bestBid"` + Currency currency.Code `json:"currency"` + High24h float64 `json:"high24h"` + Instrument currency.Pair `json:"instrument"` + LastPrice float64 `json:"lastPrice"` + Low24h float64 `json:"low24h"` + Price24h float64 `json:"price24h"` + Timestamp int64 `json:"timestamp"` + Volume24h float64 `json:"volume24h"` } // Orderbook holds current orderbook information returned from the exchange diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index a7f8e7d2..b8d0b116 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -147,7 +147,7 @@ func (b *BTCMarkets) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range markets { - pairs = append(pairs, markets[x].Instrument+"-"+markets[x].Currency) + pairs = append(pairs, fmt.Sprintf("%v%v%v", markets[x].Instrument, b.GetPairFormat(asset, false).Delimiter, markets[x].Currency)) } return pairs, nil @@ -171,10 +171,17 @@ func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker if err != nil { return tickerPrice, err } - tickerPrice.Pair = p - tickerPrice.Ask = tick.BestAsk - tickerPrice.Bid = tick.BestBID - tickerPrice.Last = tick.LastPrice + + tickerPrice = ticker.Price{ + Last: tick.LastPrice, + High: tick.High24h, + Low: tick.Low24h, + Bid: tick.BestBid, + Ask: tick.BestAsk, + Volume: tick.Volume24h, + Pair: p, + LastUpdated: time.Unix(tick.Timestamp, 0), + } err = ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) if err != nil { diff --git a/exchanges/btse/btse_types.go b/exchanges/btse/btse_types.go index 6ac5781c..0cfc2d26 100644 --- a/exchanges/btse/btse_types.go +++ b/exchanges/btse/btse_types.go @@ -43,12 +43,12 @@ type Ticker struct { // MarketStatistics stores market statistics for a particular product type MarketStatistics struct { - Open float64 `json:"open,string"` - Low float64 `json:"low,string"` - High float64 `json:"high,string"` - Close float64 `json:"close,string"` - Volume float64 `json:"volume,string"` - Time string `json:"time"` + Open float64 `json:"open,string"` + Low float64 `json:"low,string"` + High float64 `json:"high,string"` + Close float64 `json:"close,string"` + Volume float64 `json:"volume,string"` + Time time.Time `json:"time"` } // ServerTime stores the server time data diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index dbaa6009..1ce85e65 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -94,12 +94,12 @@ func (b *BTSE) WsHandleData() { } b.Websocket.DataHandler <- wshandler.TickerData{ - Timestamp: time.Now(), - Pair: currency.NewPairDelimiter(t.ProductID, "-"), - AssetType: asset.Spot, - Exchange: b.GetName(), - ClosePrice: price, - Quantity: t.LastSize, + Exchange: b.Name, + Close: price, + Bid: t.BestBids, + Ask: t.BestAsk, + AssetType: asset.Spot, + Pair: currency.NewPairDelimiter(t.ProductID, b.GetPairFormat(asset.Spot, false).Delimiter), } case "snapshot": snapshot := websocketOrderbookSnapshot{} diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 674a89ae..8066eea1 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -219,6 +219,7 @@ func (b *BTSE) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price tickerPrice.Last = t.Price tickerPrice.Volume = s.Volume tickerPrice.High = s.High + tickerPrice.LastUpdated = s.Time err = ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) if err != nil { diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 23b75ea2..52287efd 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -1,6 +1,10 @@ package coinbasepro -import "time" +import ( + "time" + + "github.com/thrasher-corp/gocryptotrader/currency" +) // Product holds product information type Product struct { @@ -15,10 +19,13 @@ type Product struct { // 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"` + TradeID int64 `json:"trade_id"` + Ask float64 `json:"ask,string"` + Bid float64 `json:"bid,string"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Volume float64 `json:"volume,string"` + Time time.Time `json:"time"` } // Trade holds executed trade information @@ -435,21 +442,21 @@ type WebsocketHeartBeat struct { // WebsocketTicker defines ticker websocket response type WebsocketTicker struct { - Type string `json:"type"` - Sequence int64 `json:"sequence"` - ProductID string `json:"product_id"` - Price float64 `json:"price,string"` - Open24H float64 `json:"open_24h,string"` - Volume24H float64 `json:"volumen_24h,string"` - Low24H float64 `json:"low_24h,string"` - High24H float64 `json:"high_24h,string"` - Volume30D float64 `json:"volume_30d,string"` - BestBid float64 `json:"best_bid,string"` - BestAsk float64 `json:"best_ask,string"` - Side string `json:"side"` - Time time.Time `json:"time,string"` - TradeID int64 `json:"trade_id"` - LastSize float64 `json:"last_size,string"` + Type string `json:"type"` + Sequence int64 `json:"sequence"` + ProductID currency.Pair `json:"product_id"` + Price float64 `json:"price,string"` + Open24H float64 `json:"open_24h,string"` + Volume24H float64 `json:"volume_24h,string"` + Low24H float64 `json:"low_24h,string"` + High24H float64 `json:"high_24h,string"` + Volume30D float64 `json:"volume_30d,string"` + BestBid float64 `json:"best_bid,string"` + BestAsk float64 `json:"best_ask,string"` + Side string `json:"side"` + Time time.Time `json:"time,string"` + TradeID int64 `json:"trade_id"` + LastSize float64 `json:"last_size,string"` } // WebsocketOrderbookSnapshot defines a snapshot response diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 610d9cbb..c6471bcb 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -89,15 +89,18 @@ func (c *CoinbasePro) WsHandleData() { } c.Websocket.DataHandler <- wshandler.TickerData{ - Timestamp: ticker.Time, - Pair: currency.NewPairFromString(ticker.ProductID), - AssetType: asset.Spot, - Exchange: c.GetName(), - OpenPrice: ticker.Open24H, - HighPrice: ticker.High24H, - LowPrice: ticker.Low24H, - ClosePrice: ticker.Price, - Quantity: ticker.Volume24H, + Timestamp: ticker.Time, + Pair: ticker.ProductID, + AssetType: asset.Spot, + Exchange: c.Name, + Open: ticker.Open24H, + High: ticker.High24H, + Low: ticker.Low24H, + Close: ticker.Price, + Volume: ticker.Volume24H, + Bid: ticker.BestBid, + Ask: ticker.BestAsk, + Last: ticker.LastSize, } case "snapshot": diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 04af5c16..560211e0 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -232,18 +232,22 @@ func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType asset.Item) (ticke if err != nil { return ticker.Price{}, err } - stats, err := c.GetStats(c.FormatExchangeCurrency(p, assetType).String()) - if err != nil { return ticker.Price{}, err } - tickerPrice.Pair = p - tickerPrice.Volume = stats.Volume - tickerPrice.Last = tick.Price - tickerPrice.High = stats.High - tickerPrice.Low = stats.Low + tickerPrice = ticker.Price{ + Last: tick.Size, + High: stats.High, + Low: stats.Low, + Bid: tick.Bid, + Ask: tick.Ask, + Volume: tick.Volume, + Open: stats.Open, + Pair: p, + LastUpdated: tick.Time, + } err = ticker.ProcessTicker(c.GetName(), &tickerPrice, assetType) if err != nil { diff --git a/exchanges/coinut/coinut_types.go b/exchanges/coinut/coinut_types.go index b4bbff32..900e9fd0 100644 --- a/exchanges/coinut/coinut_types.go +++ b/exchanges/coinut/coinut_types.go @@ -33,7 +33,7 @@ type Ticker struct { Last float64 `json:"last,string"` LowestSell float64 `json:"lowest_sell,string"` OpenInterest float64 `json:"open_interest,string"` - Timestamp float64 `json:"timestamp"` + Timestamp int64 `json:"timestamp"` TransID int64 `json:"trans_id"` Volume float64 `json:"volume,string"` Volume24 float64 `json:"volume24,string"` @@ -280,16 +280,23 @@ type wsHeartbeatResp struct { // WsTicker defines the resp for ticker updates from the websocket connection type WsTicker struct { - HighestBuy float64 `json:"highest_buy,string"` - InstID int64 `json:"inst_id"` - Last float64 `json:"last,string"` - LowestSell float64 `json:"lowest_sell,string"` - OpenInterest float64 `json:"open_interest,string"` - Reply string `json:"reply"` - Timestamp int64 `json:"timestamp"` - TransID int64 `json:"trans_id"` - Volume float64 `json:"volume,string"` - Volume24H float64 `json:"volume24,string"` + High24 float64 `json:"high24,string"` + HighestBuy float64 `json:"highest_buy,string"` + InstID int64 `json:"inst_id"` + Last float64 `json:"last,string"` + Low24 float64 `json:"low24,string"` + LowestSell float64 `json:"lowest_sell,string"` + Nonce int64 `json:"nonce"` + PrevTransID int64 `json:"prev_trans_id"` + PriceChange24 float64 `json:"price_change_24,string"` + Reply string `json:"reply"` + Status []string `json:"status"` + Timestamp int64 `json:"timestamp"` + TransID int64 `json:"trans_id"` + Volume float64 `json:"volume,string"` + Volume24 float64 `json:"volume24,string"` + Volume24Quote float64 `json:"volume24_quote,string"` + VolumeQuote float64 `json:"volume_quote,string"` } // WsOrderbookSnapshot defines the resp for orderbook snapshot updates from diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index d5616549..cd50a9e6 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -139,14 +139,15 @@ func (c *COINUT) wsProcessResponse(resp []byte) { } currencyPair := instrumentListByCode[ticker.InstID] c.Websocket.DataHandler <- wshandler.TickerData{ - Timestamp: time.Unix(0, ticker.Timestamp), - Pair: currency.NewPairFromString(currencyPair), - Exchange: c.GetName(), - AssetType: asset.Spot, - HighPrice: ticker.HighestBuy, - LowPrice: ticker.LowestSell, - ClosePrice: ticker.Last, - Quantity: ticker.Volume, + Exchange: c.Name, + Volume: ticker.Volume, + QuoteVolume: ticker.VolumeQuote, + High: ticker.HighestBuy, + Low: ticker.LowestSell, + Last: ticker.Last, + Timestamp: time.Unix(0, ticker.Timestamp), + AssetType: asset.Spot, + Pair: currency.NewPairFromString(currencyPair), } case "inst_order_book": @@ -245,9 +246,9 @@ func (c *COINUT) WsSetInstrumentList() error { if err != nil { return err } - for currency, data := range list.Spot { - instrumentListByString[currency] = data[0].InstID - instrumentListByCode[data[0].InstID] = currency + for curr, data := range list.Spot { + instrumentListByString[curr] = data[0].InstID + instrumentListByCode[data[0].InstID] = curr } if len(instrumentListByString) == 0 || len(instrumentListByCode) == 0 { return errors.New("instrument lists failed to populate") @@ -414,11 +415,11 @@ func (c *COINUT) wsSubmitOrder(order *WsSubmitOrderParameters) (*WsStandardOrder if !c.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authorised to submit order", c.Name) } - currency := c.FormatExchangeCurrency(order.Currency, asset.Spot).String() + curr := c.FormatExchangeCurrency(order.Currency, asset.Spot).String() var orderSubmissionRequest WsSubmitOrderRequest orderSubmissionRequest.Request = "new_order" orderSubmissionRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) - orderSubmissionRequest.InstID = instrumentListByString[currency] + orderSubmissionRequest.InstID = instrumentListByString[curr] orderSubmissionRequest.Qty = order.Amount orderSubmissionRequest.Price = order.Price orderSubmissionRequest.Side = string(order.Side) @@ -523,13 +524,13 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO } orderRequest := WsSubmitOrdersRequest{} for i := range orders { - currency := c.FormatExchangeCurrency(orders[i].Currency, asset.Spot).String() + curr := c.FormatExchangeCurrency(orders[i].Currency, asset.Spot).String() orderRequest.Orders = append(orderRequest.Orders, WsSubmitOrdersRequestData{ Qty: orders[i].Amount, Price: orders[i].Price, Side: string(orders[i].Side), - InstID: instrumentListByString[currency], + InstID: instrumentListByString[curr], ClientOrdID: i + 1, }) } @@ -582,11 +583,11 @@ func (c *COINUT) wsGetOpenOrders(p currency.Pair) error { if !c.Websocket.CanUseAuthenticatedEndpoints() { return fmt.Errorf("%v not authorised to get open orders", c.Name) } - currency := c.FormatExchangeCurrency(p, asset.Spot).String() + curr := c.FormatExchangeCurrency(p, asset.Spot).String() var openOrdersRequest WsGetOpenOrdersRequest openOrdersRequest.Request = "user_open_orders" openOrdersRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) - openOrdersRequest.InstID = instrumentListByString[currency] + openOrdersRequest.InstID = instrumentListByString[curr] resp, err := c.WebsocketConn.SendMessageReturnResponse(openOrdersRequest.Nonce, openOrdersRequest) if err != nil { @@ -679,10 +680,10 @@ func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) error { if !c.Websocket.CanUseAuthenticatedEndpoints() { return fmt.Errorf("%v not authorised to get trade history", c.Name) } - currency := c.FormatExchangeCurrency(p, asset.Spot).String() + curr := c.FormatExchangeCurrency(p, asset.Spot).String() var request WsTradeHistoryRequest request.Request = "trade_history" - request.InstID = instrumentListByString[currency] + request.InstID = instrumentListByString[curr] request.Nonce = c.WebsocketConn.GenerateMessageID(false) request.Start = start request.Limit = limit diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 06e3f1f6..cb59e59a 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -278,17 +278,18 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { // UpdateTicker updates and returns the ticker for a currency pair func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - tick, err := c.GetInstrumentTicker(c.InstrumentMap[p.String()]) + tick, err := c.GetInstrumentTicker(c.InstrumentMap[c.FormatExchangeCurrency(p, assetType).String()]) if err != nil { - return ticker.Price{}, err + return tickerPrice, err + } + tickerPrice = ticker.Price{ + Last: tick.Last, + High: tick.HighestBuy, + Low: tick.LowestSell, + Volume: tick.Volume24, + Pair: p, + LastUpdated: time.Unix(0, tick.Timestamp), } - - tickerPrice.Pair = p - tickerPrice.Volume = tick.Volume - tickerPrice.Last = tick.Last - tickerPrice.High = tick.HighestBuy - tickerPrice.Low = tick.LowestSell - err = ticker.ProcessTicker(c.GetName(), &tickerPrice, assetType) if err != nil { return tickerPrice, err diff --git a/exchanges/exmo/exmo.go b/exchanges/exmo/exmo.go index f95bdf40..e4f41b26 100644 --- a/exchanges/exmo/exmo.go +++ b/exchanges/exmo/exmo.go @@ -68,9 +68,8 @@ func (e *EXMO) GetOrderbook(symbol string) (map[string]Orderbook, error) { } // GetTicker returns the ticker for a symbol or symbols -func (e *EXMO) GetTicker(symbol string) (map[string]Ticker, error) { +func (e *EXMO) GetTicker() (map[string]Ticker, error) { v := url.Values{} - v.Set("pair", symbol) result := make(map[string]Ticker) urlPath := fmt.Sprintf("%s/v%s/%s", e.API.Endpoints.URL, exmoAPIVersion, exmoTicker) return result, e.SendHTTPRequest(common.EncodeURLValues(urlPath, v), &result) diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index 3e341d61..65fac92e 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -56,7 +56,7 @@ func TestGetOrderbook(t *testing.T) { func TestGetTicker(t *testing.T) { t.Parallel() - _, err := e.GetTicker("BTC_USD") + _, err := e.GetTicker() if err != nil { t.Errorf("Test failed. Err: %s", err) } diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index 3eccf7ab..abccfa43 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -73,7 +73,7 @@ func (e *EXMO) SetDefaults() { Websocket: false, RESTCapabilities: exchange.ProtocolFeatures{ AutoPairUpdates: true, - TickerBatching: false, + TickerBatching: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | exchange.NoFiatWithdrawals, @@ -156,31 +156,32 @@ func (e *EXMO) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (e *EXMO) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - pairsCollated, err := e.FormatExchangeCurrencies(e.GetEnabledPairs(assetType), assetType) + result, err := e.GetTicker() if err != nil { return tickerPrice, err } - - result, err := e.GetTicker(pairsCollated) - if err != nil { + if _, ok := result[p.String()]; !ok { return tickerPrice, err } - - for _, x := range e.GetEnabledPairs(assetType) { - currency := e.FormatExchangeCurrency(x, assetType).String() - var tickerPrice ticker.Price - tickerPrice.Pair = x - tickerPrice.Last = result[currency].Last - tickerPrice.Ask = result[currency].Sell - tickerPrice.High = result[currency].High - tickerPrice.Bid = result[currency].Buy - tickerPrice.Last = result[currency].Last - tickerPrice.Low = result[currency].Low - tickerPrice.Volume = result[currency].Volume - - err = ticker.ProcessTicker(e.Name, &tickerPrice, assetType) - if err != nil { - return tickerPrice, err + pairs := e.GetEnabledPairs(assetType) + for i := range pairs { + for j := range result { + if !strings.EqualFold(pairs[i].String(), j) { + continue + } + tickerPrice = ticker.Price{ + Pair: pairs[i], + Last: result[j].Last, + Ask: result[j].Sell, + High: result[j].High, + Bid: result[j].Buy, + Low: result[j].Low, + Volume: result[j].Volume, + } + err = ticker.ProcessTicker(e.Name, &tickerPrice, assetType) + if err != nil { + log.Error(log.Ticker, err) + } } } return ticker.GetTicker(e.Name, p, assetType) diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index 7d692dd8..585bf48f 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -70,15 +70,15 @@ type KLineResponse struct { // TickerResponse holds the ticker response data type TickerResponse struct { - Result string `json:"result"` - Volume float64 `json:"baseVolume,string"` // Trading volume - High float64 `json:"high24hr,string"` // 24 hour high price - Open float64 `json:"highestBid,string"` // Openening price - Last float64 `json:"last,string"` // Last price - Low float64 `json:"low24hr,string"` // 24 hour low price - Close float64 `json:"lowestAsk,string"` // Closing price - PercentChange float64 `json:"percentChange,string"` // Percentage change - QuoteVolume float64 `json:"quoteVolume,string"` // Quote currency volume + Period int64 `json:"period"` + BaseVolume float64 `json:"baseVolume,string"` + Change float64 `json:"change,string"` + Close float64 `json:"close,string"` + High float64 `json:"high,string"` + Last float64 `json:"last,string"` + Low float64 `json:"low,string"` + Open float64 `json:"open,string"` + QuoteVolume float64 `json:"quoteVolume,string"` } // OrderbookResponse stores the orderbook data diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index d2c82988..0d8ad76e 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -136,15 +136,16 @@ func (g *Gateio) WsHandleData() { } g.Websocket.DataHandler <- wshandler.TickerData{ - Timestamp: time.Now(), - Pair: currency.NewPairFromString(c), - AssetType: asset.Spot, - Exchange: g.GetName(), - ClosePrice: ticker.Close, - Quantity: ticker.BaseVolume, - OpenPrice: ticker.Open, - HighPrice: ticker.High, - LowPrice: ticker.Low, + Exchange: g.Name, + Open: ticker.Open, + Close: ticker.Close, + Volume: ticker.BaseVolume, + QuoteVolume: ticker.QuoteVolume, + High: ticker.High, + Low: ticker.Low, + Last: ticker.Last, + AssetType: asset.Spot, + Pair: currency.NewPairFromString(c), } case strings.Contains(result.Method, "trades"): @@ -238,7 +239,7 @@ func (g *Gateio) WsHandleData() { newOrderBook.Pair = currency.NewPairFromString(c) err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook, - false) + true) if err != nil { g.Websocket.DataHandler <- err } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index f07893c1..7bd1db10 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -198,20 +198,26 @@ func (g *Gateio) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri if err != nil { return tickerPrice, err } - - for _, x := range g.GetEnabledPairs(assetType) { - currency := g.FormatExchangeCurrency(x, assetType).String() - var tp ticker.Price - tp.Pair = x - tp.High = result[currency].High - tp.Last = result[currency].Last - tp.Last = result[currency].Last - tp.Low = result[currency].Low - tp.Volume = result[currency].Volume - - err = ticker.ProcessTicker(g.Name, &tp, assetType) - if err != nil { - return tickerPrice, err + pairs := g.GetEnabledPairs(assetType) + for i := range pairs { + for k := range result { + if !strings.EqualFold(k, pairs[i].String()) { + continue + } + tickerPrice = ticker.Price{ + Last: result[k].Last, + High: result[k].High, + Low: result[k].Low, + Volume: result[k].BaseVolume, + QuoteVolume: result[k].QuoteVolume, + Open: result[k].Open, + Close: result[k].Close, + Pair: pairs[i], + } + err = ticker.ProcessTicker(g.Name, &tickerPrice, assetType) + if err != nil { + log.Error(log.Ticker, err) + } } } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index beddc304..0e11a441 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -74,48 +74,20 @@ func (g *Gemini) GetSymbols() ([]string, error) { } // 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"` - Bid float64 `json:"bid,string"` - Last float64 `json:"last,string"` - Volume map[string]interface{} - Message string `json:"message"` - } - - ticker := Ticker{} - resp := TickerResponse{} - path := fmt.Sprintf("%s/v%s/%s/%s", g.API.Endpoints.URL, geminiAPIVersion, geminiTicker, currencyPair) - - err := g.SendHTTPRequest(path, &resp) +func (g *Gemini) GetTicker(currencyPair string) (TickerV2, error) { + ticker := TickerV2{} + path := fmt.Sprintf("%s/v2/ticker/%s", g.API.Endpoints.URL, currencyPair) + err := g.SendHTTPRequest(path, &ticker) if err != nil { return ticker, err } - - if resp.Message != "" { - return ticker, errors.New(resp.Message) + if ticker.Result == "error" { + return ticker, fmt.Errorf("%v %v %v", + g.Name, + ticker.Reason, + ticker.Message) } - ticker.Ask = resp.Ask - ticker.Bid = resp.Bid - ticker.Last = resp.Last - ticker.Volume.Currency, _ = strconv.ParseFloat(resp.Volume[currencyPair[0:3]].(string), 64) - - if strings.Contains(currencyPair, "USD") { - ticker.Volume.USD, _ = strconv.ParseFloat(resp.Volume["USD"].(string), 64) - } else { - if resp.Volume["ETH"] != nil { - ticker.Volume.ETH, _ = strconv.ParseFloat(resp.Volume["ETH"].(string), 64) - } - - if resp.Volume["BTC"] != nil { - ticker.Volume.BTC, _ = strconv.ParseFloat(resp.Volume["BTC"].(string), 64) - } - } - - time, _ := resp.Volume["timestamp"].(float64) - ticker.Volume.Timestamp = int64(time) - return ticker, nil } diff --git a/exchanges/gemini/gemini_types.go b/exchanges/gemini/gemini_types.go index ebe461ec..de687b43 100644 --- a/exchanges/gemini/gemini_types.go +++ b/exchanges/gemini/gemini_types.go @@ -16,6 +16,21 @@ type Ticker struct { } } +// TickerV2 holds returned ticker data from the exchange +type TickerV2 struct { + Ask float64 `json:"ask,string"` + Bid float64 `json:"bid,string"` + Changes []string `json:"changes"` + Close float64 `json:"close,string"` + High float64 `json:"high,string"` + Low float64 `json:"low,string"` + Open float64 `json:"open,string"` + Message string `json:"message,omitempty"` + Reason string `json:"reason,omitempty"` + Result string `json:"result,omitempty"` + Symbol currency.Pair `json:"symbol"` +} + // Orderbook contains orderbook information for both bid and ask side type Orderbook struct { Bids []OrderbookEntry `json:"bids"` diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 43f6cda0..0e4d76f9 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -222,12 +222,15 @@ func (g *Gemini) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri if err != nil { return tickerPrice, err } - tickerPrice.Pair = p - tickerPrice.Ask = tick.Ask - tickerPrice.Bid = tick.Bid - tickerPrice.Last = tick.Last - tickerPrice.Volume = tick.Volume.USD - + tickerPrice = ticker.Price{ + High: tick.High, + Low: tick.Low, + Bid: tick.Bid, + Ask: tick.Ask, + Open: tick.Open, + Close: tick.Close, + Pair: p, + } err = ticker.ProcessTicker(g.GetName(), &tickerPrice, assetType) if err != nil { return tickerPrice, err diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index e187209f..f6145843 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -116,65 +116,17 @@ func (h *HitBTC) GetSymbolsDetailed() ([]Symbol, error) { } // GetTicker returns ticker information -func (h *HitBTC) GetTicker(symbol string) (map[string]Ticker, error) { - var resp1 []TickerResponse - resp2 := TickerResponse{} - ret := make(map[string]TickerResponse) - result := make(map[string]Ticker) +func (h *HitBTC) GetTicker(symbol string) (TickerResponse, error) { + var resp TickerResponse path := fmt.Sprintf("%s/%s/%s", h.API.Endpoints.URL, apiV2Ticker, symbol) - var err error + return resp, h.SendHTTPRequest(path, &resp) +} - if symbol == "" { - err = h.SendHTTPRequest(path, &resp1) - if err != nil { - return nil, err - } - - for i := range resp1 { - if resp1[i].Symbol != "" { - ret[resp1[i].Symbol] = resp1[i] - } - } - } else { - err = h.SendHTTPRequest(path, &resp2) - ret[resp2.Symbol] = resp2 - } - - if err == nil { - for i := range ret { - tick := Ticker{} - - ask, _ := strconv.ParseFloat(ret[i].Ask, 64) - tick.Ask = ask - - bid, _ := strconv.ParseFloat(ret[i].Bid, 64) - tick.Bid = bid - - high, _ := strconv.ParseFloat(ret[i].High, 64) - tick.High = high - - last, _ := strconv.ParseFloat(ret[i].Last, 64) - tick.Last = last - - low, _ := strconv.ParseFloat(ret[i].Low, 64) - tick.Low = low - - open, _ := strconv.ParseFloat(ret[i].Open, 64) - tick.Open = open - - vol, _ := strconv.ParseFloat(ret[i].Volume, 64) - tick.Volume = vol - - volQuote, _ := strconv.ParseFloat(ret[i].VolumeQuote, 64) - tick.VolumeQuote = volQuote - - tick.Symbol = ret[i].Symbol - tick.Timestamp = ret[i].Timestamp - result[i] = tick - } - } - - return result, err +// GetTickers returns ticker information +func (h *HitBTC) GetTickers() ([]TickerResponse, error) { + var resp []TickerResponse + path := fmt.Sprintf("%s/%s/", h.API.Endpoints.URL, apiV2Ticker) + return resp, h.SendHTTPRequest(path, &resp) } // GetTrades returns trades from hitbtc diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index 962956f9..71482667 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -97,6 +97,20 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } } +func TestGetAllTickers(t *testing.T) { + _, err := h.GetTickers() + if err != nil { + t.Error(err) + } +} + +func TestGetSingularTicker(t *testing.T) { + _, err := h.GetTicker("BTCUSD") + if err != nil { + t.Error(err) + } +} + func TestGetFee(t *testing.T) { h.SetDefaults() TestSetup(t) diff --git a/exchanges/hitbtc/hitbtc_types.go b/exchanges/hitbtc/hitbtc_types.go index 251c46e5..131ee00f 100644 --- a/exchanges/hitbtc/hitbtc_types.go +++ b/exchanges/hitbtc/hitbtc_types.go @@ -6,32 +6,18 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" ) -// Ticker holds ticker information -type Ticker struct { - Last float64 - Ask float64 - Bid float64 - Timestamp time.Time - Volume float64 - VolumeQuote float64 - Symbol string - High float64 - Low float64 - Open float64 -} - // TickerResponse is the response type type TickerResponse struct { - Last string `json:"last"` // Last trade price - Ask string `json:"ask"` // Best ask price - Bid string `json:"bid"` // Best bid price - Timestamp time.Time `json:"timestamp,string"` // Last update or refresh ticker timestamp - Volume string `json:"volume"` // Total trading amount within 24 hours in base currency - VolumeQuote string `json:"volumeQuote"` // Total trading amount within 24 hours in quote currency - Symbol string `json:"symbol"` - High string `json:"high"` // Highest trade price within 24 hours - Low string `json:"low"` // Lowest trade price within 24 hours - Open string `json:"open"` // Last trade price 24 hours ago + Ask float64 `json:"ask,string"` + Bid float64 `json:"bid,string"` + High float64 `json:"high,string"` + Last float64 `json:"last,string"` + Low float64 `json:"low,string"` + Open float64 `json:"open,string"` + Volume float64 `json:"volume,string"` + VolumeQuote float64 `json:"volumeQuote,string"` + Symbol currency.Pair `json:"symbol"` + Timestamp time.Time `json:"timestamp"` } // Symbol holds symbol data @@ -336,16 +322,16 @@ type params struct { // WsTicker defines websocket ticker feed return params type WsTicker struct { Params struct { - Ask float64 `json:"ask,string"` - Bid float64 `json:"bid,string"` - Last float64 `json:"last,string"` - Open float64 `json:"open,string"` - Low float64 `json:"low,string"` - High float64 `json:"high,string"` - Volume float64 `json:"volume,string"` - VolumeQuote float64 `json:"volumeQuote,string"` - Timestamp string `json:"timestamp"` - Symbol string `json:"symbol"` + Ask float64 `json:"ask,string"` + Bid float64 `json:"bid,string"` + Last float64 `json:"last,string"` + Open float64 `json:"open,string"` + Low float64 `json:"low,string"` + High float64 `json:"high,string"` + Volume float64 `json:"volume,string"` + VolumeQuote float64 `json:"volumeQuote,string"` + Timestamp string `json:"timestamp"` + Symbol currency.Pair `json:"symbol"` } `json:"params"` } diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index 68c8a94d..d979998e 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -116,14 +116,18 @@ func (h *HitBTC) handleSubscriptionUpdates(resp wshandler.WebsocketResponse, ini return } h.Websocket.DataHandler <- wshandler.TickerData{ - Exchange: h.GetName(), - AssetType: asset.Spot, - Pair: currency.NewPairFromString(ticker.Params.Symbol), - Quantity: ticker.Params.Volume, - Timestamp: ts, - OpenPrice: ticker.Params.Open, - HighPrice: ticker.Params.High, - LowPrice: ticker.Params.Low, + Exchange: h.Name, + Open: ticker.Params.Open, + Volume: ticker.Params.Volume, + QuoteVolume: ticker.Params.VolumeQuote, + High: ticker.Params.High, + Low: ticker.Params.Low, + Bid: ticker.Params.Bid, + Ask: ticker.Params.Ask, + Last: ticker.Params.Last, + Timestamp: ts, + AssetType: asset.Spot, + Pair: ticker.Params.Symbol, } case "snapshotOrderbook": var obSnapshot WsOrderbook diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index a76fc734..0a3ec744 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -196,7 +196,7 @@ func (h *HitBTC) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range symbols { - pairs = append(pairs, symbols[x].BaseCurrency+"-"+symbols[x].QuoteCurrency) + pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) } return pairs, nil } @@ -214,25 +214,33 @@ func (h *HitBTC) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType asset.Item) (ticker.Price, error) { - tick, err := h.GetTicker("") + var tickerPrice ticker.Price + tick, err := h.GetTickers() if err != nil { - return ticker.Price{}, err + return tickerPrice, err } - - for _, x := range h.GetEnabledPairs(assetType) { - var tp ticker.Price - curr := h.FormatExchangeCurrency(x, assetType).String() - tp.Pair = x - tp.Ask = tick[curr].Ask - tp.Bid = tick[curr].Bid - tp.High = tick[curr].High - tp.Last = tick[curr].Last - tp.Low = tick[curr].Low - tp.Volume = tick[curr].Volume - - err = ticker.ProcessTicker(h.GetName(), &tp, assetType) - if err != nil { - return ticker.Price{}, err + pairs := h.GetEnabledPairs(assetType) + for i := range pairs { + for j := range tick { + if !tick[j].Symbol.Equal(pairs[i]) { + continue + } + tickerPrice := ticker.Price{ + Last: tick[j].Last, + High: tick[j].High, + Low: tick[j].Low, + Bid: tick[j].Bid, + Ask: tick[j].Ask, + Volume: tick[j].Volume, + QuoteVolume: tick[j].VolumeQuote, + Open: tick[j].Open, + Pair: pairs[i], + LastUpdated: tick[j].Timestamp, + } + err = ticker.ProcessTicker(h.GetName(), &tickerPrice, assetType) + if err != nil { + log.Error(log.Ticker, err) + } } } return ticker.GetTicker(h.Name, currencyPair, assetType) diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index 826fe3a7..f01482ea 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -32,6 +32,7 @@ const ( huobiMarketDetailMerged = "market/detail/merged" huobiMarketDepth = "market/depth" huobiMarketTrade = "market/trade" + huobiMarketTickers = "market/tickers" huobiMarketTradeHistory = "market/history/trade" huobiSymbols = "common/symbols" huobiCurrencies = "common/currencys" @@ -95,6 +96,13 @@ func (h *HUOBI) GetSpotKline(arg KlinesRequestParams) ([]KlineItem, error) { return result.Data, err } +// GetTickers returns the ticker for the specified symbol +func (h *HUOBI) GetTickers() (Tickers, error) { + var result Tickers + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobiMarketTickers) + return result, h.SendHTTPRequest(urlPath, &result) +} + // GetMarketDetailMerged returns the ticker for the specified symbol func (h *HUOBI) GetMarketDetailMerged(symbol string) (DetailMerged, error) { vals := url.Values{} diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 5786d817..7f4b401e 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -170,6 +170,13 @@ func TestGetCurrencies(t *testing.T) { } } +func TestGetTicker(t *testing.T) { + _, err := h.GetTickers() + if err != nil { + t.Error(err) + } +} + func TestGetTimestamp(t *testing.T) { t.Parallel() _, err := h.GetTimestamp() diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index 7e2d3ee0..d40fe90e 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -19,7 +19,7 @@ type KlineItem struct { Low float64 `json:"low"` High float64 `json:"high"` Amount float64 `json:"amount"` - Vol float64 `json:"vol"` + Volume float64 `json:"vol"` Count int `json:"count"` } @@ -42,6 +42,23 @@ type DetailMerged struct { Bid []float64 `json:"bid"` } +// Tickers contain all tickers +type Tickers struct { + Data []Ticker `json:"data"` +} + +// Ticker latest ticker data +type Ticker struct { + Amount float64 `json:"amount"` + Close float64 `json:"close"` + Count int64 `json:"count"` + High float64 `json:"high"` + Low float64 `json:"low"` + Open float64 `json:"open"` + Symbol currency.Pair `json:"symbol"` + Volume float64 `json:"vol"` +} + // OrderBookDataRequestParamsType var for request param types type OrderBookDataRequestParamsType string @@ -322,6 +339,22 @@ type WsKline struct { } } +type WsTick struct { + Channel string `json:"ch"` + Timestamp int64 `json:"ts"` + Tick struct { + Amount float64 `json:"amount"` + Close float64 `json:"close"` + Count float64 `json:"count"` + High float64 `json:"high"` + ID float64 `json:"id"` + Low float64 `json:"low"` + Open float64 `json:"open"` + Timestamp float64 `json:"ts"` + Volume float64 `json:"vol"` + } `json:"tick"` +} + // WsTrade defines market trade websocket response type WsTrade struct { Channel string `json:"ch"` diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index daef0a08..3bbca6cc 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -23,10 +23,11 @@ import ( const ( baseWSURL = "wss://api.huobi.pro" - wsMarketURL = baseWSURL + "/ws" - wsMarketKline = "market.%s.kline.1min" - wsMarketDepth = "market.%s.depth.step0" - wsMarketTrade = "market.%s.trade.detail" + wsMarketURL = baseWSURL + "/ws" + wsMarketKline = "market.%s.kline.1min" + wsMarketDepth = "market.%s.depth.step0" + wsMarketTrade = "market.%s.trade.detail" + wsMarketTicker = "market.%s.detail" wsAccountsOrdersEndPoint = "/ws/v1" wsAccountsList = "accounts.list" @@ -253,7 +254,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { LowPrice: kline.Tick.Low, Volume: kline.Tick.Volume, } - case strings.Contains(init.Channel, "trade"): + case strings.Contains(init.Channel, "trade.detail"): var trade WsTrade err := common.JSONDecode(resp.Raw, &trade) if err != nil { @@ -267,6 +268,26 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { CurrencyPair: currency.NewPairFromString(data[1]), Timestamp: time.Unix(0, trade.Tick.Timestamp), } + case strings.Contains(init.Channel, "detail"): + var ticker WsTick + err := common.JSONDecode(resp.Raw, &ticker) + if err != nil { + h.Websocket.DataHandler <- err + return + } + data := strings.Split(ticker.Channel, ".") + h.Websocket.DataHandler <- wshandler.TickerData{ + Exchange: h.Name, + Open: ticker.Tick.Open, + Close: ticker.Tick.Close, + Volume: ticker.Tick.Amount, + QuoteVolume: ticker.Tick.Volume, + High: ticker.Tick.High, + Low: ticker.Tick.Low, + Timestamp: time.Unix(0, ticker.Timestamp), + AssetType: asset.Spot, + Pair: currency.NewPairFromString(data[1]), + } } } @@ -303,7 +324,7 @@ func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (h *HUOBI) GenerateDefaultSubscriptions() { - var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade} + var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade, wsMarketTicker} var subscriptions []wshandler.WebsocketChannelSubscription if h.Websocket.CanUseAuthenticatedEndpoints() { channels = append(channels, "orders.%v", "orders.%v.update") diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 69c4c2fa..db57407c 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -72,7 +72,7 @@ func (h *HUOBI) SetDefaults() { Websocket: true, RESTCapabilities: exchange.ProtocolFeatures{ AutoPairUpdates: true, - TickerBatching: false, + TickerBatching: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | exchange.NoFiatWithdrawals, @@ -228,7 +228,7 @@ func (h *HUOBI) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range symbols { - pairs = append(pairs, symbols[x].BaseCurrency+"-"+symbols[x].QuoteCurrency) + pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) } return pairs, nil @@ -248,28 +248,29 @@ func (h *HUOBI) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (h *HUOBI) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - tick, err := h.GetMarketDetailMerged(h.FormatExchangeCurrency(p, assetType).String()) + tickers, err := h.GetTickers() if err != nil { return tickerPrice, err } - - tickerPrice.Pair = p - tickerPrice.Low = tick.Low - tickerPrice.Last = tick.Close - tickerPrice.Volume = tick.Volume - tickerPrice.High = tick.High - - if len(tick.Ask) > 0 { - tickerPrice.Ask = tick.Ask[0] - } - - if len(tick.Bid) > 0 { - tickerPrice.Bid = tick.Bid[0] - } - - err = ticker.ProcessTicker(h.GetName(), &tickerPrice, assetType) - if err != nil { - return tickerPrice, err + pairs := h.GetEnabledPairs(assetType) + for i := range pairs { + for j := range tickers.Data { + if !pairs[i].Equal(tickers.Data[j].Symbol) { + continue + } + tickerPrice := ticker.Price{ + High: tickers.Data[j].High, + Low: tickers.Data[j].Low, + Volume: tickers.Data[j].Volume, + Open: tickers.Data[j].Open, + Close: tickers.Data[j].Close, + Pair: tickers.Data[j].Symbol, + } + err = ticker.ProcessTicker(h.GetName(), &tickerPrice, assetType) + if err != nil { + log.Error(log.Ticker, err) + } + } } return ticker.GetTicker(h.Name, p, assetType) diff --git a/exchanges/huobihadax/huobihadax.go b/exchanges/huobihadax/huobihadax.go index bb2ccd5d..662b219e 100644 --- a/exchanges/huobihadax/huobihadax.go +++ b/exchanges/huobihadax/huobihadax.go @@ -28,6 +28,7 @@ const ( huobihadaxMarketDetail = "market/detail" huobihadaxMarketDetailMerged = "market/detail/merged" huobihadaxMarketDepth = "market/depth" + huobihadaxMarketTicker = "market/tickers" huobihadaxMarketTrade = "market/trade" huobihadaxMarketTradeHistory = "market/history/trade" huobihadaxSymbols = "common/symbols" @@ -252,6 +253,13 @@ func (h *HUOBIHADAX) GetCurrencies() ([]string, error) { return result.Currencies, err } +// GetTickers returns the ticker for the specified symbol +func (h *HUOBIHADAX) GetTickers() (Tickers, error) { + var result Tickers + urlPath := fmt.Sprintf("%s/%s", h.API.Endpoints.URL, huobihadaxMarketTicker) + return result, h.SendHTTPRequest(urlPath, &result) +} + // GetTimestamp returns the Huobi server time func (h *HUOBIHADAX) GetTimestamp() (int64, error) { type response struct { diff --git a/exchanges/huobihadax/huobihadax_test.go b/exchanges/huobihadax/huobihadax_test.go index 66092405..2ea71a8b 100644 --- a/exchanges/huobihadax/huobihadax_test.go +++ b/exchanges/huobihadax/huobihadax_test.go @@ -215,6 +215,13 @@ func TestGetCurrencies(t *testing.T) { } } +func TestGetTicker(t *testing.T) { + _, err := h.GetTickers() + if err != nil { + t.Error(err) + } +} + func TestGetTimestamp(t *testing.T) { t.Parallel() _, err := h.GetTimestamp() diff --git a/exchanges/huobihadax/huobihadax_types.go b/exchanges/huobihadax/huobihadax_types.go index fd8149c9..518ebd9d 100644 --- a/exchanges/huobihadax/huobihadax_types.go +++ b/exchanges/huobihadax/huobihadax_types.go @@ -21,10 +21,43 @@ type KlineItem struct { Low float64 `json:"low"` High float64 `json:"high"` Amount float64 `json:"amount"` - Vol float64 `json:"vol"` + Volume float64 `json:"vol"` Count int `json:"count"` } +type WsTick struct { + Channel string `json:"ch"` + Timestamp int64 `json:"ts"` + Tick struct { + Amount float64 `json:"amount"` + Close float64 `json:"close"` + Count float64 `json:"count"` + High float64 `json:"high"` + ID float64 `json:"id"` + Low float64 `json:"low"` + Open float64 `json:"open"` + Timestamp float64 `json:"ts"` + Volume float64 `json:"vol"` + } `json:"tick"` +} + +// Tickers contain all tickers +type Tickers struct { + Data []Ticker `json:"data"` +} + +// Ticker latest ticker data +type Ticker struct { + Amount float64 `json:"amount"` + Close float64 `json:"close"` + Count int64 `json:"count"` + High float64 `json:"high"` + Low float64 `json:"low"` + Open float64 `json:"open"` + Symbol currency.Pair `json:"symbol"` + Volume float64 `json:"vol"` +} + // DetailMerged stores the ticker detail merged data type DetailMerged struct { Detail diff --git a/exchanges/huobihadax/huobihadax_websocket.go b/exchanges/huobihadax/huobihadax_websocket.go index d5f57cfc..bf1c803c 100644 --- a/exchanges/huobihadax/huobihadax_websocket.go +++ b/exchanges/huobihadax/huobihadax_websocket.go @@ -25,6 +25,7 @@ const ( wsMarketKline = "market.%s.kline.1min" wsMarketDepth = "market.%s.depth.step0" wsMarketTrade = "market.%s.trade.detail" + wsMarketTicker = "market.%s.detail" wsAccountsOrdersBaseURL = "wss://api.huobi.pro" wsAccountsOrdersEndPoint = "/ws/v1" @@ -254,7 +255,7 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) { LowPrice: kline.Tick.Low, Volume: kline.Tick.Volume, } - case strings.Contains(init.Channel, "trade"): + case strings.Contains(init.Channel, "trade.detail"): var trade WsTrade err := common.JSONDecode(resp.Raw, &trade) if err != nil { @@ -268,6 +269,26 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) { CurrencyPair: currency.NewPairFromString(data[1]), Timestamp: time.Unix(0, trade.Tick.Timestamp), } + case strings.Contains(init.Channel, "detail"): + var ticker WsTick + err := common.JSONDecode(resp.Raw, &ticker) + if err != nil { + h.Websocket.DataHandler <- err + return + } + data := strings.Split(ticker.Channel, ".") + h.Websocket.DataHandler <- wshandler.TickerData{ + Exchange: h.Name, + Open: ticker.Tick.Open, + Close: ticker.Tick.Close, + Volume: ticker.Tick.Amount, + QuoteVolume: ticker.Tick.Volume, + High: ticker.Tick.High, + Low: ticker.Tick.Low, + Timestamp: time.Unix(0, ticker.Timestamp), + AssetType: asset.Spot, + Pair: currency.NewPairFromString(data[1]), + } } } @@ -304,7 +325,7 @@ func (h *HUOBIHADAX) WsProcessOrderbook(update *WsDepth, symbol string) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (h *HUOBIHADAX) GenerateDefaultSubscriptions() { - var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade} + var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade, wsMarketTicker} var subscriptions []wshandler.WebsocketChannelSubscription if h.Websocket.CanUseAuthenticatedEndpoints() { channels = append(channels, "orders.%v", "orders.%v.update") diff --git a/exchanges/huobihadax/huobihadax_wrapper.go b/exchanges/huobihadax/huobihadax_wrapper.go index e1b53229..59a9f02f 100644 --- a/exchanges/huobihadax/huobihadax_wrapper.go +++ b/exchanges/huobihadax/huobihadax_wrapper.go @@ -72,7 +72,7 @@ func (h *HUOBIHADAX) SetDefaults() { Websocket: true, RESTCapabilities: exchange.ProtocolFeatures{ AutoPairUpdates: true, - TickerBatching: false, + TickerBatching: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | exchange.NoFiatWithdrawals, @@ -191,7 +191,7 @@ func (h *HUOBIHADAX) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range symbols { - pairs = append(pairs, symbols[x].BaseCurrency+"-"+symbols[x].QuoteCurrency) + pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) } return pairs, nil @@ -211,28 +211,29 @@ func (h *HUOBIHADAX) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (h *HUOBIHADAX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - tick, err := h.GetMarketDetailMerged(h.FormatExchangeCurrency(p, assetType).String()) + tickers, err := h.GetTickers() if err != nil { return tickerPrice, err } - - tickerPrice.Pair = p - tickerPrice.Low = tick.Low - tickerPrice.Last = tick.Close - tickerPrice.Volume = tick.Volume - tickerPrice.High = tick.High - - if len(tick.Ask) > 0 { - tickerPrice.Ask = tick.Ask[0] - } - - if len(tick.Bid) > 0 { - tickerPrice.Bid = tick.Bid[0] - } - - err = ticker.ProcessTicker(h.GetName(), &tickerPrice, assetType) - if err != nil { - return tickerPrice, err + pairs := h.GetEnabledPairs(assetType) + for i := range pairs { + for j := range tickers.Data { + if !pairs[i].Equal(tickers.Data[j].Symbol) { + continue + } + tickerPrice := ticker.Price{ + High: tickers.Data[j].High, + Low: tickers.Data[j].Low, + Volume: tickers.Data[j].Volume, + Open: tickers.Data[j].Open, + Close: tickers.Data[j].Close, + Pair: tickers.Data[j].Symbol, + } + err = ticker.ProcessTicker(h.GetName(), &tickerPrice, assetType) + if err != nil { + log.Error(log.Ticker, err) + } + } } return ticker.GetTicker(h.Name, p, assetType) diff --git a/exchanges/itbit/itbit_types.go b/exchanges/itbit/itbit_types.go index c3d06b07..6408c3cf 100644 --- a/exchanges/itbit/itbit_types.go +++ b/exchanges/itbit/itbit_types.go @@ -1,5 +1,7 @@ package itbit +import "time" + // GeneralReturn is a generalized return type to capture any errors type GeneralReturn struct { Code int `json:"code"` @@ -9,23 +11,23 @@ type GeneralReturn struct { // 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"` + 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 time.Time `json:"serverTimeUTC"` } // OrderbookResponse contains multi-arrayed strings of bid and ask side diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index efdff51a..092baa9e 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -133,15 +133,17 @@ func (i *ItBit) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric if err != nil { return tickerPrice, err } - - tickerPrice.Pair = p - tickerPrice.Ask = tick.Ask - tickerPrice.Bid = tick.Bid - tickerPrice.Last = tick.LastPrice - tickerPrice.High = tick.High24h - tickerPrice.Low = tick.Low24h - tickerPrice.Volume = tick.Volume24h - + tickerPrice = ticker.Price{ + Last: tick.LastPrice, + High: tick.High24h, + Low: tick.Low24h, + Bid: tick.Bid, + Ask: tick.Ask, + Volume: tick.Volume24h, + Open: tick.OpenToday, + Pair: p, + LastUpdated: tick.ServertimeUTC, + } err = ticker.ProcessTicker(i.GetName(), &tickerPrice, assetType) if err != nil { return tickerPrice, err diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index e301d3f7..b3dd7633 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -51,6 +51,8 @@ const ( krakenUnauthRate = 0 ) +var assetPairMap map[string]string + // Kraken is the overarching type across the alphapoint package type Kraken struct { exchange.Base @@ -102,6 +104,12 @@ func (k *Kraken) GetAssetPairs() (map[string]AssetPairs, error) { if err := k.SendHTTPRequest(path, &response); err != nil { return response.Result, err } + for i := range response.Result { + if assetPairMap == nil { + assetPairMap = make(map[string]string) + } + assetPairMap[i] = response.Result[i].Altname + } return response.Result, GetError(response.Error) } @@ -134,7 +142,7 @@ func (k *Kraken) GetTicker(symbol string) (Ticker, error) { tick.Bid, _ = strconv.ParseFloat(resp.Data[i].Bid[0], 64) tick.Last, _ = strconv.ParseFloat(resp.Data[i].Last[0], 64) tick.Volume, _ = strconv.ParseFloat(resp.Data[i].Volume[1], 64) - tick.VWAP, _ = strconv.ParseFloat(resp.Data[i].VWAP[1], 64) + tick.VolumeWeightedAveragePrice, _ = strconv.ParseFloat(resp.Data[i].VolumeWeightedAveragePrice[1], 64) tick.Trades = resp.Data[i].Trades[1] tick.Low, _ = strconv.ParseFloat(resp.Data[i].Low[1], 64) tick.High, _ = strconv.ParseFloat(resp.Data[i].High[1], 64) @@ -175,7 +183,7 @@ func (k *Kraken) GetTickers(pairList string) (Tickers, error) { tick.Bid, _ = strconv.ParseFloat(resp.Data[i].Bid[0], 64) tick.Last, _ = strconv.ParseFloat(resp.Data[i].Last[0], 64) tick.Volume, _ = strconv.ParseFloat(resp.Data[i].Volume[1], 64) - tick.VWAP, _ = strconv.ParseFloat(resp.Data[i].VWAP[1], 64) + tick.VolumeWeightedAveragePrice, _ = strconv.ParseFloat(resp.Data[i].VolumeWeightedAveragePrice[1], 64) tick.Trades = resp.Data[i].Trades[1] tick.Low, _ = strconv.ParseFloat(resp.Data[i].Low[1], 64) tick.High, _ = strconv.ParseFloat(resp.Data[i].High[1], 64) @@ -224,7 +232,7 @@ func (k *Kraken) GetOHLC(symbol string) ([]OpenHighLowClose, error) { case 4: o.Close, _ = strconv.ParseFloat(x.(string), 64) case 5: - o.Vwap, _ = strconv.ParseFloat(x.(string), 64) + o.VolumeWeightedAveragePrice, _ = strconv.ParseFloat(x.(string), 64) case 6: o.Volume, _ = strconv.ParseFloat(x.(string), 64) case 7: @@ -767,8 +775,8 @@ func (k *Kraken) AddOrder(symbol, side, orderType string, volume, price, price2, params.Set("leverage", strconv.FormatFloat(leverage, 'f', -1, 64)) } - if args.Oflags != "" { - params.Set("oflags", args.Oflags) + if args.OrderFlags != "" { + params.Set("oflags", args.OrderFlags) } if args.StartTm != "" { diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index ef09cb61..2b4ce98f 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -235,7 +235,7 @@ func TestGetTradeVolume(t *testing.T) { // TestAddOrder API endpoint test func TestAddOrder(t *testing.T) { t.Parallel() - args := AddOrderOptions{Oflags: "fcib"} + args := AddOrderOptions{OrderFlags: "fcib"} _, err := k.AddOrder("XXBTZUSD", exchange.SellOrderSide.ToLower().ToString(), exchange.LimitOrderType.ToLower().ToString(), 0.00000001, 0, 0, 0, &args) diff --git a/exchanges/kraken/kraken_types.go b/exchanges/kraken/kraken_types.go index e815f651..e816afd4 100644 --- a/exchanges/kraken/kraken_types.go +++ b/exchanges/kraken/kraken_types.go @@ -38,15 +38,15 @@ type AssetPairs struct { // Ticker is a standard ticker type type Ticker struct { - Ask float64 - Bid float64 - Last float64 - Volume float64 - VWAP float64 - Trades int64 - Low float64 - High float64 - Open float64 + Ask float64 + Bid float64 + Last float64 + Volume float64 + VolumeWeightedAveragePrice float64 + Trades int64 + Low float64 + High float64 + Open float64 } // Tickers stores a map of tickers @@ -54,27 +54,27 @@ type Tickers map[string]Ticker // TickerResponse holds ticker information before its put into the Ticker struct type TickerResponse struct { - Ask []string `json:"a"` - Bid []string `json:"b"` - Last []string `json:"c"` - Volume []string `json:"v"` - VWAP []string `json:"p"` - Trades []int64 `json:"t"` - Low []string `json:"l"` - High []string `json:"h"` - Open string `json:"o"` + Ask []string `json:"a"` + Bid []string `json:"b"` + Last []string `json:"c"` + Volume []string `json:"v"` + VolumeWeightedAveragePrice []string `json:"p"` + Trades []int64 `json:"t"` + Low []string `json:"l"` + High []string `json:"h"` + Open string `json:"o"` } // OpenHighLowClose contains ticker event information type OpenHighLowClose struct { - Time float64 - Open float64 - High float64 - Low float64 - Close float64 - Vwap float64 - Volume float64 - Count float64 + Time float64 + Open float64 + High float64 + Low float64 + Close float64 + VolumeWeightedAveragePrice float64 + Volume float64 + Count float64 } // RecentTrades holds recent trade data @@ -125,13 +125,13 @@ type TradeBalanceInfo struct { // OrderInfo type type OrderInfo struct { - RefID string `json:"refid"` - UserRef int32 `json:"userref"` - Status string `json:"status"` - OpenTm float64 `json:"opentm"` - StartTm float64 `json:"starttm"` - ExpireTm float64 `json:"expiretm"` - Descr struct { + RefID string `json:"refid"` + UserRef int32 `json:"userref"` + Status string `json:"status"` + OpenTime float64 `json:"opentm"` + StartTime float64 `json:"starttm"` + ExpireTime float64 `json:"expiretm"` + Description struct { Pair string `json:"pair"` Type string `json:"type"` OrderType string `json:"ordertype"` @@ -141,16 +141,16 @@ type OrderInfo struct { Order string `json:"order"` Close string `json:"close"` } `json:"descr"` - Vol float64 `json:"vol,string"` - VolExec float64 `json:"vol_exec,string"` - Cost float64 `json:"cost,string"` - Fee float64 `json:"fee,string"` - Price float64 `json:"price,string"` - StopPrice float64 `json:"stopprice,string"` - LimitPrice float64 `json:"limitprice,string"` - Misc string `json:"misc"` - Oflags string `json:"oflags"` - Trades []string `json:"trades"` + Volume float64 `json:"vol,string"` + VolumeExecuted float64 `json:"vol_exec,string"` + Cost float64 `json:"cost,string"` + Fee float64 `json:"fee,string"` + Price float64 `json:"price,string"` + StopPrice float64 `json:"stopprice,string"` + LimitPrice float64 `json:"limitprice,string"` + Misc string `json:"misc"` + OrderFlags string `json:"oflags"` + Trades []string `json:"trades"` } // OpenOrders type @@ -198,44 +198,44 @@ type TradesHistory struct { // TradeInfo type type TradeInfo struct { - OrderTxID string `json:"ordertxid"` - Pair string `json:"pair"` - Time float64 `json:"time"` - Type string `json:"type"` - OrderType string `json:"ordertype"` - Price float64 `json:"price,string"` - Cost float64 `json:"cost,string"` - Fee float64 `json:"fee,string"` - Vol float64 `json:"vol,string"` - Margin float64 `json:"margin,string"` - Misc string `json:"misc"` - PosTxID string `json:"postxid"` - Cprice float64 `json:"cprice,string"` - Cfee float64 `json:"cfee,string"` - Cvol float64 `json:"cvol,string"` - Cmargin float64 `json:"cmargin,string"` - Trades []string `json:"trades"` - PosStatus string `json:"posstatus"` + OrderTxID string `json:"ordertxid"` + Pair string `json:"pair"` + Time float64 `json:"time"` + Type string `json:"type"` + OrderType string `json:"ordertype"` + Price float64 `json:"price,string"` + Cost float64 `json:"cost,string"` + Fee float64 `json:"fee,string"` + Volume float64 `json:"vol,string"` + Margin float64 `json:"margin,string"` + Misc string `json:"misc"` + PosTxID string `json:"postxid"` + ClosedPositionAveragePrice float64 `json:"cprice,string"` + ClosedPositionFee float64 `json:"cfee,string"` + ClosedPositionVolume float64 `json:"cvol,string"` + ClosedPositionMargin float64 `json:"cmargin,string"` + Trades []string `json:"trades"` + PosStatus string `json:"posstatus"` } // Position holds the opened position type Position struct { - Ordertxid string `json:"ordertxid"` - Pair string `json:"pair"` - Time float64 `json:"time"` - Type string `json:"type"` - OrderType string `json:"ordertype"` - Cost float64 `json:"cost,string"` - Fee float64 `json:"fee,string"` - Vol float64 `json:"vol,string"` - VolClosed float64 `json:"vol_closed,string"` - Margin float64 `json:"margin,string"` - Rollovertm int64 `json:"rollovertm,string"` - Misc string `json:"misc"` - Oflags string `json:"oflags"` - PosStatus string `json:"posstatus"` - Net string `json:"net"` - Terms string `json:"terms"` + Ordertxid string `json:"ordertxid"` + Pair string `json:"pair"` + Time float64 `json:"time"` + Type string `json:"type"` + OrderType string `json:"ordertype"` + Cost float64 `json:"cost,string"` + Fee float64 `json:"fee,string"` + Volume float64 `json:"vol,string"` + VolumeClosed float64 `json:"vol_closed,string"` + Margin float64 `json:"margin,string"` + RolloverTime int64 `json:"rollovertm,string"` + Misc string `json:"misc"` + OrderFlags string `json:"oflags"` + PositionStatus string `json:"posstatus"` + Net string `json:"net"` + Terms string `json:"terms"` } // GetLedgersOptions type @@ -314,7 +314,7 @@ type OrderDescription struct { // AddOrderOptions represents the AddOrder options type AddOrderOptions struct { UserRef int32 - Oflags string + OrderFlags string StartTm string ExpireTm string CloseOrderType string diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index 0fe9456a..ddb3c98c 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -241,6 +241,8 @@ func getSubscriptionChannelData(id int64) WebsocketChannelData { // wsProcessTickers converts ticker data and sends it to the datahandler func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data interface{}) { tickerData := data.(map[string]interface{}) + askData := tickerData["a"].([]interface{}) + bidData := tickerData["b"].([]interface{}) closeData := tickerData["c"].([]interface{}) openData := tickerData["o"].([]interface{}) lowData := tickerData["l"].([]interface{}) @@ -251,17 +253,21 @@ func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data interf highPrice, _ := strconv.ParseFloat(highData[0].(string), 64) lowPrice, _ := strconv.ParseFloat(lowData[0].(string), 64) quantity, _ := strconv.ParseFloat(volumeData[0].(string), 64) + ask, _ := strconv.ParseFloat(askData[0].(string), 64) + bid, _ := strconv.ParseFloat(bidData[0].(string), 64) k.Websocket.DataHandler <- wshandler.TickerData{ - Timestamp: time.Now(), - Exchange: k.Name, - AssetType: asset.Spot, - Pair: channelData.Pair, - ClosePrice: closePrice, - OpenPrice: openPrice, - HighPrice: highPrice, - LowPrice: lowPrice, - Quantity: quantity, + Exchange: k.Name, + Open: openPrice, + Close: closePrice, + Volume: quantity, + High: highPrice, + Low: lowPrice, + Bid: bid, + Ask: ask, + Timestamp: time.Now(), + AssetType: asset.Spot, + Pair: channelData.Pair, } } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 4baa3fc4..1b5158ea 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -62,8 +62,9 @@ func (k *Kraken) SetDefaults() { Separator: ",", }, ConfigFormat: ¤cy.PairFormat{ - Delimiter: "-", Uppercase: true, + Delimiter: "-", + Separator: ",", }, } @@ -167,9 +168,9 @@ func (k *Kraken) Run() { } forceUpdate := false - if !common.StringDataContains(k.GetEnabledPairs(asset.Spot).Strings(), "-") || - !common.StringDataContains(k.GetAvailablePairs(asset.Spot).Strings(), "-") { - enabledPairs := currency.NewPairsFromStrings([]string{"XBT-USD"}) + if !common.StringDataContains(k.GetEnabledPairs(asset.Spot).Strings(), k.GetPairFormat(asset.Spot, false).Delimiter) || + !common.StringDataContains(k.GetAvailablePairs(asset.Spot).Strings(), k.GetPairFormat(asset.Spot, false).Delimiter) { + enabledPairs := currency.NewPairsFromStrings([]string{fmt.Sprintf("BTC%vUSD", k.GetPairFormat(asset.Spot, false).Delimiter)}) log.Warn(log.ExchangeSys, "Available pairs for Kraken reset due to config upgrade, please enable the ones you would like again") forceUpdate = true @@ -210,7 +211,7 @@ func (k *Kraken) FetchTradablePairs(asset asset.Item) ([]string, error) { if v.Quote[0] == 'Z' || v.Quote[0] == 'X' { v.Quote = v.Quote[1:] } - products = append(products, v.Base+"-"+v.Quote) + products = append(products, fmt.Sprintf("%v%v%v", v.Base, k.GetPairFormat(asset, false).Delimiter, v.Quote)) } return products, nil } @@ -239,21 +240,33 @@ func (k *Kraken) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri return tickerPrice, err } - for _, x := range pairs { - for y, z := range tickers { - if !strings.Contains(y, x.Base.Upper().String()) || - !strings.Contains(y, x.Quote.Upper().String()) { - continue + for i := range pairs { + for curr, v := range tickers { + if !strings.EqualFold(pairs[i].String(), curr) { + var altCurrency string + var ok bool + if altCurrency, ok = assetPairMap[curr]; !ok { + continue + } + if !strings.EqualFold(pairs[i].String(), altCurrency) { + continue + } + } + + tickerPrice = ticker.Price{ + Last: v.Last, + High: v.High, + Low: v.Low, + Bid: v.Bid, + Ask: v.Ask, + Volume: v.Volume, + Open: v.Open, + Pair: pairs[i], + } + err = ticker.ProcessTicker(k.Name, &tickerPrice, assetType) + if err != nil { + log.Error(log.Ticker, err) } - var tp ticker.Price - tp.Pair = x - tp.Last = z.Last - tp.Ask = z.Ask - tp.Bid = z.Bid - tp.High = z.High - tp.Low = z.Low - tp.Volume = z.Volume - ticker.ProcessTicker(k.GetName(), &tp, assetType) } } return ticker.GetTicker(k.GetName(), p, assetType) @@ -473,19 +486,19 @@ func (k *Kraken) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp.Open { - symbol := currency.NewPairFromString(resp.Open[i].Descr.Pair) - orderDate := time.Unix(int64(resp.Open[i].StartTm), 0) - side := exchange.OrderSide(strings.ToUpper(resp.Open[i].Descr.Type)) - orderType := exchange.OrderType(strings.ToUpper(resp.Open[i].Descr.OrderType)) + symbol := currency.NewPairFromString(resp.Open[i].Description.Pair) + orderDate := time.Unix(int64(resp.Open[i].StartTime), 0) + side := exchange.OrderSide(strings.ToUpper(resp.Open[i].Description.Type)) + orderType := exchange.OrderType(strings.ToUpper(resp.Open[i].Description.OrderType)) orders = append(orders, exchange.OrderDetail{ ID: i, - Amount: resp.Open[i].Vol, - RemainingAmount: (resp.Open[i].Vol - resp.Open[i].VolExec), - ExecutedAmount: resp.Open[i].VolExec, + Amount: resp.Open[i].Volume, + RemainingAmount: (resp.Open[i].Volume - resp.Open[i].VolumeExecuted), + ExecutedAmount: resp.Open[i].VolumeExecuted, Exchange: k.Name, OrderDate: orderDate, - Price: resp.Open[i].Descr.Price, + Price: resp.Open[i].Description.Price, OrderSide: side, OrderType: orderType, CurrencyPair: symbol, @@ -517,19 +530,19 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ var orders []exchange.OrderDetail for i := range resp.Closed { - symbol := currency.NewPairFromString(resp.Closed[i].Descr.Pair) - orderDate := time.Unix(int64(resp.Closed[i].StartTm), 0) - side := exchange.OrderSide(strings.ToUpper(resp.Closed[i].Descr.Type)) - orderType := exchange.OrderType(strings.ToUpper(resp.Closed[i].Descr.OrderType)) + symbol := currency.NewPairFromString(resp.Closed[i].Description.Pair) + orderDate := time.Unix(int64(resp.Closed[i].StartTime), 0) + side := exchange.OrderSide(strings.ToUpper(resp.Closed[i].Description.Type)) + orderType := exchange.OrderType(strings.ToUpper(resp.Closed[i].Description.OrderType)) orders = append(orders, exchange.OrderDetail{ ID: i, - Amount: resp.Closed[i].Vol, - RemainingAmount: (resp.Closed[i].Vol - resp.Closed[i].VolExec), - ExecutedAmount: resp.Closed[i].VolExec, + Amount: resp.Closed[i].Volume, + RemainingAmount: (resp.Closed[i].Volume - resp.Closed[i].VolumeExecuted), + ExecutedAmount: resp.Closed[i].VolumeExecuted, Exchange: k.Name, OrderDate: orderDate, - Price: resp.Closed[i].Descr.Price, + Price: resp.Closed[i].Description.Price, OrderSide: side, OrderType: orderType, CurrencyPair: symbol, diff --git a/exchanges/lakebtc/lakebtc_websocket.go b/exchanges/lakebtc/lakebtc_websocket.go index 94f1dff2..162ebaf6 100644 --- a/exchanges/lakebtc/lakebtc_websocket.go +++ b/exchanges/lakebtc/lakebtc_websocket.go @@ -239,13 +239,12 @@ func (l *LakeBTC) processTicker(ticker string) error { continue } l.Websocket.DataHandler <- wshandler.TickerData{ - Timestamp: time.Now(), - Pair: currency.NewPairFromString(k), + Exchange: l.Name, + Volume: vol, + High: high, + Low: low, AssetType: asset.Spot, - Exchange: l.GetName(), - Quantity: vol, - HighPrice: high, - LowPrice: low, + Pair: currency.NewPairFromString(k), } } return nil diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 5934bd0e..f865d0e6 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -186,25 +186,29 @@ func (l *LakeBTC) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tick, err := l.GetTicker() + ticks, err := l.GetTicker() if err != nil { return ticker.Price{}, err } - for _, x := range l.GetEnabledPairs(assetType) { - currency := l.FormatExchangeCurrency(x, assetType).String() + pairs := l.GetEnabledPairs(assetType) + for i := range pairs { + currency := l.FormatExchangeCurrency(pairs[i], assetType).String() + if _, ok := ticks[currency]; !ok { + continue + } 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 + tickerPrice.Pair = pairs[i] + tickerPrice.Ask = ticks[currency].Ask + tickerPrice.Bid = ticks[currency].Bid + tickerPrice.Volume = ticks[currency].Volume + tickerPrice.High = ticks[currency].High + tickerPrice.Low = ticks[currency].Low + tickerPrice.Last = ticks[currency].Last err = ticker.ProcessTicker(l.GetName(), &tickerPrice, assetType) if err != nil { - return tickerPrice, err + log.Error(log.Ticker, err) } } return ticker.GetTicker(l.Name, p, assetType) diff --git a/exchanges/lbank/lbank.go b/exchanges/lbank/lbank.go index 72130ff0..d7589e15 100644 --- a/exchanges/lbank/lbank.go +++ b/exchanges/lbank/lbank.go @@ -68,6 +68,15 @@ func (l *Lbank) GetTicker(symbol string) (TickerResponse, error) { return t, l.SendHTTPRequest(path, &t) } +// GetTickers returns all tickers +func (l *Lbank) GetTickers() ([]TickerResponse, error) { + var t []TickerResponse + params := url.Values{} + params.Set("symbol", "all") + path := fmt.Sprintf("%s/v%s/%s?%s", l.API.Endpoints.URL, lbankAPIVersion, lbankTicker, params.Encode()) + return t, l.SendHTTPRequest(path, &t) +} + // GetCurrencyPairs returns a list of supported currency pairs by the exchange func (l *Lbank) GetCurrencyPairs() ([]string, error) { path := fmt.Sprintf("%s/v%s/%s", l.API.Endpoints.URL, lbankAPIVersion, diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go index e9067980..5d16b128 100644 --- a/exchanges/lbank/lbank_test.go +++ b/exchanges/lbank/lbank_test.go @@ -59,6 +59,17 @@ func TestGetTicker(t *testing.T) { } } +func TestGetTickers(t *testing.T) { + TestSetup(t) + tickers, err := l.GetTickers() + if err != nil { + t.Errorf("test failed: %v", err) + } + if len(tickers) <= 1 { + t.Errorf("Expected multiple tickers, received %v", len(tickers)) + } +} + func TestGetCurrencyPairs(t *testing.T) { TestSetup(t) _, err := l.GetCurrencyPairs() diff --git a/exchanges/lbank/lbank_types.go b/exchanges/lbank/lbank_types.go index cb6653ac..71ce1df2 100644 --- a/exchanges/lbank/lbank_types.go +++ b/exchanges/lbank/lbank_types.go @@ -2,6 +2,8 @@ package lbank import ( "encoding/json" + + "github.com/thrasher-corp/gocryptotrader/currency" ) // Ticker stores the ticker price data for a currency pair @@ -16,9 +18,9 @@ type Ticker struct { // TickerResponse stores the ticker price data and timestamp for a currency pair type TickerResponse struct { - Symbol string `json:"symbol"` - Timestamp int64 `json:"timestamp"` - Ticker Ticker `json:"ticker"` + Symbol currency.Pair `json:"symbol"` + Timestamp int64 `json:"timestamp"` + Ticker Ticker `json:"ticker"` } // MarketDepthResponse stores arrays for asks, bids and a timestamp for a currecy pair diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index b69e1965..e6c5c4df 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -69,6 +69,7 @@ func (l *Lbank) SetDefaults() { REST: true, RESTCapabilities: exchange.ProtocolFeatures{ AutoPairUpdates: true, + TickerBatching: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.NoFiatWithdrawals, @@ -157,21 +158,30 @@ func (l *Lbank) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (l *Lbank) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - tickerInfo, err := l.GetTicker(l.FormatExchangeCurrency(p, assetType).String()) + tickerInfo, err := l.GetTickers() if err != nil { return tickerPrice, err } - tickerPrice.Pair = p - tickerPrice.Last = tickerInfo.Ticker.Latest - tickerPrice.High = tickerInfo.Ticker.High - tickerPrice.Volume = tickerInfo.Ticker.Volume - tickerPrice.Low = tickerInfo.Ticker.Low - - err = ticker.ProcessTicker(l.GetName(), &tickerPrice, assetType) - if err != nil { - return tickerPrice, err + pairs := l.GetEnabledPairs(assetType) + for i := range pairs { + for j := range tickerInfo { + if !pairs[i].Equal(tickerInfo[j].Symbol) { + continue + } + tickerPrice = ticker.Price{ + Last: tickerInfo[j].Ticker.Latest, + High: tickerInfo[j].Ticker.High, + Low: tickerInfo[j].Ticker.Low, + Volume: tickerInfo[j].Ticker.Volume, + Pair: tickerInfo[j].Symbol, + LastUpdated: time.Unix(0, tickerInfo[j].Timestamp), + } + err = ticker.ProcessTicker(l.GetName(), &tickerPrice, assetType) + if err != nil { + log.Error(log.Ticker, err) + } + } } - return ticker.GetTicker(l.Name, p, assetType) } diff --git a/exchanges/localbitcoins/localbitcoins_types.go b/exchanges/localbitcoins/localbitcoins_types.go index d0b3e337..763c48ea 100644 --- a/exchanges/localbitcoins/localbitcoins_types.go +++ b/exchanges/localbitcoins/localbitcoins_types.go @@ -312,7 +312,8 @@ type WalletBalanceInfo struct { // Ticker contains ticker information type Ticker struct { Avg12h float64 `json:"avg_12h,string"` - Avg1h float64 `json:"avg_1h,string"` + Avg1h float64 `json:"avg_1h,string,omitempty"` + Avg6h float64 `json:"avg_6h,string,omitempty"` Avg24h float64 `json:"avg_24h,string"` Rates struct { Last float64 `json:"last,string"` diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 4c63780b..67671a14 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -160,16 +160,20 @@ func (l *LocalBitcoins) UpdateTicker(p currency.Pair, assetType asset.Item) (tic return tickerPrice, err } - for _, x := range l.GetEnabledPairs(assetType) { - currency := x.Quote.String() + pairs := l.GetEnabledPairs(assetType) + for i := range pairs { + curr := pairs[i].Quote.String() + if _, ok := tick[curr]; !ok { + continue + } var tp ticker.Price - tp.Pair = x - tp.Last = tick[currency].Avg24h - tp.Volume = tick[currency].VolumeBTC + tp.Pair = pairs[i] + tp.Last = tick[curr].Avg24h + tp.Volume = tick[curr].VolumeBTC err = ticker.ProcessTicker(l.GetName(), &tp, assetType) if err != nil { - return tickerPrice, err + log.Error(log.Ticker, err) } } diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index f0aea1f0..10579e76 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -1,6 +1,7 @@ package okcoin import ( + "fmt" "sync" "time" @@ -10,6 +11,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -57,13 +59,13 @@ func (o *OKCoin) SetDefaults() { UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ - Uppercase: false, - Delimiter: "_", + Uppercase: true, + Delimiter: "-", }, ConfigFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: "_", + Delimiter: "-", }, } @@ -73,7 +75,7 @@ func (o *OKCoin) SetDefaults() { Websocket: true, RESTCapabilities: exchange.ProtocolFeatures{ AutoPairUpdates: true, - TickerBatching: false, + TickerBatching: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.NoFiatWithdrawals, @@ -122,6 +124,28 @@ func (o *OKCoin) Run() { log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) } + if o.Config.CurrencyPairs.ConfigFormat.Delimiter != o.CurrencyPairs.ConfigFormat.Delimiter { + o.Config.CurrencyPairs.ConfigFormat.Delimiter = o.CurrencyPairs.ConfigFormat.Delimiter + } + if o.Config.CurrencyPairs.RequestFormat.Uppercase != o.CurrencyPairs.RequestFormat.Uppercase { + o.Config.CurrencyPairs.RequestFormat.Uppercase = true + } + if o.Config.CurrencyPairs.RequestFormat.Delimiter != o.CurrencyPairs.RequestFormat.Delimiter { + o.Config.CurrencyPairs.RequestFormat.Delimiter = o.CurrencyPairs.RequestFormat.Delimiter + } + + if !common.StringDataContains(o.Config.CurrencyPairs.Pairs[asset.Spot].Enabled.Strings(), o.CurrencyPairs.RequestFormat.Delimiter) { + enabledPairs := currency.NewPairsFromStrings([]string{"BTC-USD"}) + log.Warnf(log.ExchangeSys, + "Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.", o.Name) + + err := o.UpdatePairs(enabledPairs, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", o.GetName()) + return + } + } + if !o.GetEnabledFeatures().AutoPairUpdates { return } @@ -141,7 +165,7 @@ func (o *OKCoin) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range prods { - pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency) + pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].BaseCurrency, o.GetPairFormat(asset, false).Delimiter, prods[x].QuoteCurrency)) } return pairs, nil @@ -158,3 +182,48 @@ func (o *OKCoin) UpdateTradablePairs(forceUpdate bool) error { return o.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } + +// UpdateTicker updates and returns the ticker for a currency pair +func (o *OKCoin) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { + var tickerData ticker.Price + if assetType == asset.Spot { + resp, err := o.GetSpotAllTokenPairsInformation() + if err != nil { + return tickerData, err + } + pairs := o.GetEnabledPairs(assetType) + for i := range pairs { + for j := range resp { + if !pairs[i].Equal(resp[j].InstrumentID) { + continue + } + tickerData = ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24h, + Low: resp[j].Low24h, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].BaseVolume24h, + QuoteVolume: resp[j].QuoteVolume24h, + Open: resp[j].Open24h, + Pair: pairs[i], + LastUpdated: resp[j].Timestamp, + } + err = ticker.ProcessTicker(o.Name, &tickerData, assetType) + if err != nil { + log.Error(log.Ticker, err) + } + } + } + } + return ticker.GetTicker(o.GetName(), p, assetType) +} + +// FetchTicker returns the ticker for a currency pair +func (o *OKCoin) FetchTicker(p currency.Pair, assetType asset.Item) (tickerData ticker.Price, err error) { + tickerData, err = ticker.GetTicker(o.GetName(), p, assetType) + if err != nil { + return o.UpdateTicker(p, assetType) + } + return +} diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go index b0d7c541..f8c7ea20 100644 --- a/exchanges/okex/okex_wrapper.go +++ b/exchanges/okex/okex_wrapper.go @@ -11,6 +11,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -56,17 +57,34 @@ func (o *OKEX) SetDefaults() { asset.PerpetualSwap, asset.Index, }, - UseGlobalFormat: true, + UseGlobalFormat: false, + } + // Same format used for perpetual swap and futures + fmt1 := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ - Uppercase: false, - Delimiter: "_", + Uppercase: true, + Delimiter: "-", }, - ConfigFormat: ¤cy.PairFormat{ Uppercase: true, Delimiter: "_", }, } + o.CurrencyPairs.Store(asset.PerpetualSwap, fmt1) + o.CurrencyPairs.Store(asset.Futures, fmt1) + + fmt2 := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + } + o.CurrencyPairs.Store(asset.Spot, fmt2) + o.CurrencyPairs.Store(asset.Index, fmt2) o.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ @@ -74,6 +92,7 @@ func (o *OKEX) SetDefaults() { Websocket: true, RESTCapabilities: exchange.ProtocolFeatures{ AutoPairUpdates: true, + TickerBatching: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.NoFiatWithdrawals, @@ -121,6 +140,32 @@ func (o *OKEX) Run() { if o.Verbose { log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.API.Endpoints.WebsocketURL) } + if o.Config.CurrencyPairs.Pairs[asset.Spot].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Spot].RequestFormat == nil { + fmt := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + } + o.CurrencyPairs.Store(asset.Spot, fmt) + o.Config.CurrencyPairs.Store(asset.Spot, fmt) + } + + if !common.StringDataContains(o.Config.CurrencyPairs.Pairs[asset.Spot].Enabled.Strings(), o.CurrencyPairs.Pairs[asset.Spot].RequestFormat.Delimiter) { + enabledPairs := currency.NewPairsFromStrings([]string{"EOS-USDT"}) + log.Warnf(log.ExchangeSys, + "Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.", o.Name) + + err := o.UpdatePairs(enabledPairs, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", o.GetName()) + return + } + } if !o.GetEnabledFeatures().AutoPairUpdates { return @@ -143,7 +188,7 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { } for x := range prods { - pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency) + pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].BaseCurrency, o.GetPairFormat(i, false).Delimiter, prods[x].QuoteCurrency)) } return pairs, nil case asset.Futures: @@ -154,7 +199,7 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { var pairs []string for x := range prods { - pairs = append(pairs, prods[x].UnderlyingIndex+prods[x].QuoteCurrency+"_"+prods[x].Delivery) + pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].UnderlyingIndex+prods[x].QuoteCurrency, o.GetPairFormat(i, false).Delimiter, prods[x].Delivery)) } return pairs, nil @@ -166,11 +211,11 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { var pairs []string for x := range prods { - pairs = append(pairs, prods[x].UnderlyingIndex+"_"+prods[x].QuoteCurrency+"_SWAP") + pairs = append(pairs, fmt.Sprintf("%v%v%v%vSWAP", prods[x].UnderlyingIndex, o.GetPairFormat(i, false).Delimiter, prods[x].QuoteCurrency, o.GetPairFormat(i, false).Delimiter)) } return pairs, nil case asset.Index: - return []string{"BTC_USD"}, nil + return []string{fmt.Sprintf("BTC%vUSD", o.GetPairFormat(i, false).Delimiter)}, nil } return nil, fmt.Errorf("%s invalid asset type", o.Name) @@ -193,3 +238,104 @@ func (o *OKEX) UpdateTradablePairs(forceUpdate bool) error { } return nil } + +// UpdateTicker updates and returns the ticker for a currency pair +func (o *OKEX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { + var tickerData ticker.Price + switch assetType { + case asset.Spot: + resp, err := o.GetSpotAllTokenPairsInformation() + if err != nil { + return tickerData, err + } + pairs := o.GetEnabledPairs(assetType) + for i := range pairs { + for j := range resp { + if !pairs[i].Equal(resp[j].InstrumentID) { + continue + } + tickerData = ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24h, + Low: resp[j].Low24h, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].BaseVolume24h, + QuoteVolume: resp[j].QuoteVolume24h, + Open: resp[j].Open24h, + Pair: pairs[i], + LastUpdated: resp[j].Timestamp, + } + err = ticker.ProcessTicker(o.Name, &tickerData, assetType) + if err != nil { + log.Error(log.Ticker, err) + } + } + } + case asset.PerpetualSwap: + resp, err := o.GetAllSwapTokensInformation() + if err != nil { + return tickerData, err + } + pairs := o.GetEnabledPairs(assetType) + for i := range pairs { + for j := range resp { + if !pairs[i].Equal(resp[j].InstrumentID) { + continue + } + tickerData = ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24H, + Low: resp[j].Low24H, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].Volume24H, + Pair: resp[j].InstrumentID, + LastUpdated: resp[j].Timestamp, + } + err = ticker.ProcessTicker(o.Name, &tickerData, assetType) + if err != nil { + log.Error(log.Ticker, err) + } + } + } + case asset.Futures: + resp, err := o.GetAllFuturesTokenInfo() + if err != nil { + return tickerData, err + } + pairs := o.GetEnabledPairs(assetType) + for i := range pairs { + for j := range resp { + if !pairs[i].Equal(resp[j].InstrumentID) { + continue + } + tickerData = ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24h, + Low: resp[j].Low24h, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].Volume24h, + Pair: resp[j].InstrumentID, + LastUpdated: resp[j].Timestamp, + } + err = ticker.ProcessTicker(o.Name, &tickerData, assetType) + if err != nil { + log.Error(log.Ticker, err) + } + } + } + } + + return ticker.GetTicker(o.GetName(), p, assetType) +} + +// FetchTicker returns the ticker for a currency pair +func (o *OKEX) FetchTicker(p currency.Pair, assetType asset.Item) (tickerData ticker.Price, err error) { + tickerData, err = ticker.GetTicker(o.GetName(), p, assetType) + if err != nil { + return o.UpdateTicker(p, assetType) + } + return +} diff --git a/exchanges/okgroup/okgroup_types.go b/exchanges/okgroup/okgroup_types.go index bf495f52..4eb8493e 100644 --- a/exchanges/okgroup/okgroup_types.go +++ b/exchanges/okgroup/okgroup_types.go @@ -2,6 +2,8 @@ package okgroup import ( "time" + + "github.com/thrasher-corp/gocryptotrader/currency" ) // GetAccountCurrenciesResponse response data for GetAccountCurrencies @@ -288,16 +290,16 @@ type GetSpotOrderBookResponse struct { // GetSpotTokenPairsInformationResponse response data for GetSpotTokenPairsInformation type GetSpotTokenPairsInformationResponse struct { - BaseVolume24h float64 `json:"base_volume_24h,string"` // 24 trading volume of the base currency - BestAsk float64 `json:"best_ask,string"` // best ask price - BestBid float64 `json:"best_bid,string"` // best bid price - High24h float64 `json:"high_24h,string"` // 24 hour high - InstrumentID string `json:"instrument_id"` // trading pair - Last float64 `json:"last,string"` // last traded price - Low24h float64 `json:"low_24h,string"` // 24 hour low - Open24h float64 `json:"open_24h,string"` // 24 hour open - QuoteVolume24h float64 `json:"quote_volume_24h,string"` // 24 trading volume of the quote currency - Timestamp time.Time `json:"timestamp"` + BaseVolume24h float64 `json:"base_volume_24h,string"` // 24 trading volume of the base currency + BestAsk float64 `json:"best_ask,string"` // best ask price + BestBid float64 `json:"best_bid,string"` // best bid price + High24h float64 `json:"high_24h,string"` // 24 hour high + InstrumentID currency.Pair `json:"instrument_id"` // trading pair + Last float64 `json:"last,string"` // last traded price + Low24h float64 `json:"low_24h,string"` // 24 hour low + Open24h float64 `json:"open_24h,string"` // 24 hour open + QuoteVolume24h float64 `json:"quote_volume_24h,string"` // 24 trading volume of the quote currency + Timestamp time.Time `json:"timestamp"` } // GetSpotFilledOrdersInformationRequest request data for GetSpotFilledOrdersInformation @@ -699,14 +701,14 @@ type GetFuturesOrderBookResponse struct { // GetFuturesTokenInfoResponse response data for GetFuturesOrderBook type GetFuturesTokenInfoResponse struct { - BestAsk float64 `json:"best_ask,string"` - BestBid float64 `json:"best_bid,string"` - High24h float64 `json:"high_24h,string"` - InstrumentID string `json:"instrument_id"` - Last float64 `json:"last,string"` - Low24h float64 `json:"low_24h,string"` - Timestamp time.Time `json:"timestamp"` - Volume24h int64 `json:"volume_24h,string"` + BestAsk float64 `json:"best_ask,string"` + BestBid float64 `json:"best_bid,string"` + High24h float64 `json:"high_24h,string"` + InstrumentID currency.Pair `json:"instrument_id"` + Last float64 `json:"last,string"` + Low24h float64 `json:"low_24h,string"` + Timestamp time.Time `json:"timestamp"` + Volume24h float64 `json:"volume_24h,string"` } // GetFuturesFilledOrderRequest request data for GetFuturesFilledOrder @@ -1057,14 +1059,14 @@ type GetSwapOrderBookResponse struct { // GetAllSwapTokensInformationResponse response data for GetAllSwapTokensInformation type GetAllSwapTokensInformationResponse struct { - InstrumentID string `json:"instrument_id"` - Last float64 `json:"last,string"` - High24H float64 `json:"high_24h,string"` - Low24H float64 `json:"low_24h,string"` - BestBid float64 `json:"best_bid,string"` - BestAsk float64 `json:"best_ask,string"` - Volume24H float64 `json:"volume_24h,string"` - Timestamp time.Time `json:"timestamp"` + InstrumentID currency.Pair `json:"instrument_id"` + Last float64 `json:"last,string"` + High24H float64 `json:"high_24h,string"` + Low24H float64 `json:"low_24h,string"` + BestBid float64 `json:"best_bid,string"` + BestAsk float64 `json:"best_ask,string"` + Volume24H float64 `json:"volume_24h,string"` + Timestamp time.Time `json:"timestamp"` } // GetSwapFilledOrdersDataRequest request data for GetSwapFilledOrdersData @@ -1340,12 +1342,14 @@ type WebsocketDataWrapper struct { // WebsocketTickerData contains formatted data for ticker related websocket responses type WebsocketTickerData struct { - High24H float64 `json:"high_24h,string,omitempty"` - Last float64 `json:"last,string,omitempty"` - BestBid float64 `json:"best_bid,string,omitempty"` - BestAsk float64 `json:"best_ask,string,omitempty"` - Low24H float64 `json:"low_24h,string,omitempty"` - Volume24H float64 `json:"volume_24h,string,omitempty"` + BaseVolume24h float64 `json:"base_volume_24h,string,omitempty"` + BestAsk float64 `json:"best_ask,string,omitempty"` + BestBid float64 `json:"best_bid,string,omitempty"` + High24h float64 `json:"high_24h,string,omitempty"` + Last float64 `json:"last,string,omitempty"` + Low24h float64 `json:"low_24h,string,omitempty"` + Open24h float64 `json:"open_24h,string,omitempty"` + QuoteVolume24h float64 `json:"quote_volume_24h,string,omitempty"` } // WebsocketTradeResponse contains formatted data for trade related websocket responses diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index b55b5f71..558252d6 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -193,7 +193,7 @@ func (o *OKGroup) wsPingHandler(wg *sync.WaitGroup) { return case <-ticker.C: - err := o.WebsocketConn.SendMessage("ping") + err := o.WebsocketConn.Connection.WriteMessage(websocket.TextMessage, []byte("ping")) if o.Verbose { log.Debugf(log.ExchangeSys, "%v sending ping", o.GetName()) } @@ -361,13 +361,19 @@ func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) { for i := range response.Data { instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-") o.Websocket.DataHandler <- wshandler.TickerData{ - Timestamp: response.Data[i].Timestamp, - Exchange: o.GetName(), - AssetType: o.GetAssetTypeFromTableName(response.Table), - HighPrice: response.Data[i].High24H, - LowPrice: response.Data[i].Low24H, - ClosePrice: response.Data[i].Last, - Pair: instrument, + Exchange: o.Name, + Open: response.Data[i].Open24h, + Close: response.Data[i].Last, + Volume: response.Data[i].BaseVolume24h, + QuoteVolume: response.Data[i].QuoteVolume24h, + High: response.Data[i].High24h, + Low: response.Data[i].Low24h, + Bid: response.Data[i].BestBid, + Ask: response.Data[i].BestAsk, + Last: response.Data[i].Last, + Timestamp: response.Data[i].Timestamp, + AssetType: o.GetAssetTypeFromTableName(response.Table), + Pair: instrument, } } } diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index da95dfe7..863af600 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -11,7 +11,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -65,36 +64,6 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) error { return nil } -// UpdateTicker updates and returns the ticker for a currency pair -func (o *OKGroup) UpdateTicker(p currency.Pair, assetType asset.Item) (tickerData ticker.Price, err error) { - resp, err := o.GetSpotAllTokenPairsInformationForCurrency(o.FormatExchangeCurrency(p, assetType).String()) - if err != nil { - return - } - tickerData = ticker.Price{ - Ask: resp.BestAsk, - Bid: resp.BestBid, - High: resp.High24h, - Last: resp.Last, - LastUpdated: resp.Timestamp, - Low: resp.Low24h, - Pair: o.FormatExchangeCurrency(p, assetType), - Volume: resp.BaseVolume24h, - } - - err = ticker.ProcessTicker(o.Name, &tickerData, assetType) - return -} - -// FetchTicker returns the ticker for a currency pair -func (o *OKGroup) FetchTicker(p currency.Pair, assetType asset.Item) (tickerData ticker.Price, err error) { - tickerData, err = ticker.GetTicker(o.GetName(), p, assetType) - if err != nil { - return o.UpdateTicker(p, assetType) - } - return -} - // FetchOrderbook returns orderbook base on the currency pair func (o *OKGroup) FetchOrderbook(p currency.Pair, assetType asset.Item) (resp orderbook.Base, err error) { ob, err := orderbook.Get(o.GetName(), p, assetType) @@ -339,7 +308,7 @@ func (o *OKGroup) GetOrderInfo(orderID string) (resp exchange.OrderDetail, err e // GetDepositAddress returns a deposit address for a specified currency func (o *OKGroup) GetDepositAddress(p currency.Code, accountID string) (_ string, err error) { wallet, err := o.GetAccountDepositAddressForCurrency(p.Lower().String()) - if err != nil { + if err != nil || len(wallet) == 0 { return } return wallet[0].Address, nil diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index 7331898b..a6ef2ed1 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -210,23 +210,22 @@ func (p *Poloniex) wsHandleTickerData(data []interface{}) { t.PercentageChange, _ = strconv.ParseFloat(tickerData[4].(string), 64) t.BaseCurrencyVolume24H, _ = strconv.ParseFloat(tickerData[5].(string), 64) t.QuoteCurrencyVolume24H, _ = strconv.ParseFloat(tickerData[6].(string), 64) - isFrozen := false - if tickerData[7].(float64) == 1 { - isFrozen = true - } - t.IsFrozen = isFrozen + t.IsFrozen = tickerData[7].(float64) == 1 t.HighestTradeIn24H, _ = strconv.ParseFloat(tickerData[8].(string), 64) t.LowestTradePrice24H, _ = strconv.ParseFloat(tickerData[9].(string), 64) p.Websocket.DataHandler <- wshandler.TickerData{ - Timestamp: time.Now(), - Pair: currency.NewPairDelimiter(currencyPair, "_"), - Exchange: p.GetName(), - AssetType: asset.Spot, - ClosePrice: t.LastPrice, - LowPrice: t.LowestAsk, - HighPrice: t.HighestBid, - Quantity: t.QuoteCurrencyVolume24H, + Exchange: p.Name, + Volume: t.BaseCurrencyVolume24H, + QuoteVolume: t.QuoteCurrencyVolume24H, + High: t.HighestBid, + Low: t.LowestAsk, + Bid: t.HighestBid, + Ask: t.LowestAsk, + Last: t.LastPrice, + Timestamp: time.Now(), + AssetType: asset.Spot, + Pair: currency.NewPairDelimiter(currencyPair, "_"), } } @@ -362,7 +361,6 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []inter // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (p *Poloniex) GenerateDefaultSubscriptions() { var subscriptions []wshandler.WebsocketChannelSubscription - // Tickerdata is its own channel subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ Channel: fmt.Sprintf("%v", wsTickerDataID), }) diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 3bcf3d64..13611997 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -215,6 +215,9 @@ func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType asset.Item for _, x := range p.GetEnabledPairs(assetType) { var tp ticker.Price curr := p.FormatExchangeCurrency(x, assetType).String() + if _, ok := tick[curr]; !ok { + continue + } tp.Pair = x tp.Ask = tick[curr].LowestAsk tp.Bid = tick[curr].HighestBid @@ -222,10 +225,11 @@ func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType asset.Item tp.Last = tick[curr].Last tp.Low = tick[curr].Low24Hr tp.Volume = tick[curr].BaseVolume + tp.QuoteVolume = tick[curr].QuoteVolume err = ticker.ProcessTicker(p.GetName(), &tp, assetType) if err != nil { - return tickerPrice, err + log.Error(log.Ticker, err) } } return ticker.GetTicker(p.Name, currencyPair, assetType) diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index 2c7ad8cc..4087f31d 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -2,6 +2,7 @@ package ticker import ( "errors" + "fmt" "strconv" "strings" "sync" @@ -28,14 +29,17 @@ var ( // Price struct stores the currency pair and pricing information type Price struct { - Pair currency.Pair `json:"Pair"` Last float64 `json:"Last"` High float64 `json:"High"` Low float64 `json:"Low"` Bid float64 `json:"Bid"` Ask float64 `json:"Ask"` Volume float64 `json:"Volume"` + QuoteVolume float64 `json:"QuoteVolume"` PriceATH float64 `json:"PriceATH"` + Open float64 `json:"Open"` + Close float64 `json:"Close"` + Pair currency.Pair `json:"Pair"` LastUpdated time.Time } @@ -151,11 +155,11 @@ func CreateNewTicker(exchangeName string, tickerNew *Price, tickerType asset.Ite // list func ProcessTicker(exchangeName string, tickerNew *Price, assetType asset.Item) error { if tickerNew.Pair.IsEmpty() { - return errors.New(errPairNotSet) + return fmt.Errorf("%v %v", exchangeName, errPairNotSet) } if assetType == "" { - return errors.New(errAssetTypeNotSet) + return fmt.Errorf("%v %v %v", exchangeName, tickerNew.Pair.String(), errAssetTypeNotSet) } if tickerNew.LastUpdated.IsZero() { @@ -178,6 +182,7 @@ func ProcessTicker(exchangeName string, tickerNew *Price, assetType asset.Item) } m.Lock() + a := make(map[string]map[string]Price) b := make(map[string]Price) b[assetType.String()] = *tickerNew diff --git a/exchanges/websocket/wshandler/wshandler.go b/exchanges/websocket/wshandler/wshandler.go index 3089969d..931ee5c8 100644 --- a/exchanges/websocket/wshandler/wshandler.go +++ b/exchanges/websocket/wshandler/wshandler.go @@ -103,7 +103,9 @@ func (w *Websocket) Connect() error { if !w.connectionMonitorRunning { go w.connectionMonitor() } - go w.manageSubscriptions() + if w.SupportsFunctionality(WebsocketSubscribeSupported) || w.SupportsFunctionality(WebsocketUnsubscribeSupported) { + go w.manageSubscriptions() + } return nil } diff --git a/exchanges/websocket/wshandler/wshandler_types.go b/exchanges/websocket/wshandler/wshandler_types.go index 1d5a1b76..529c124d 100644 --- a/exchanges/websocket/wshandler/wshandler_types.go +++ b/exchanges/websocket/wshandler/wshandler_types.go @@ -144,15 +144,20 @@ type TradeData struct { // TickerData defines ticker feed type TickerData struct { - Timestamp time.Time - Pair currency.Pair - AssetType asset.Item - Exchange string - ClosePrice float64 - Quantity float64 - OpenPrice float64 - HighPrice float64 - LowPrice float64 + Exchange string + Open float64 + Close float64 + Volume float64 + QuoteVolume float64 + High float64 + Low float64 + Bid float64 + Ask float64 + Last float64 + PriceATH float64 + Timestamp time.Time + AssetType asset.Item + Pair currency.Pair } // KlineData defines kline feed diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index a9a2b290..b2f960a1 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -170,19 +170,23 @@ func (y *Yobit) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric } for _, x := range y.GetEnabledPairs(assetType) { - currency := y.FormatExchangeCurrency(x, assetType).Lower().String() + curr := y.FormatExchangeCurrency(x, assetType).Lower().String() + if _, ok := result[curr]; !ok { + continue + } var tickerPrice ticker.Price tickerPrice.Pair = x - tickerPrice.Last = result[currency].Last - tickerPrice.Ask = result[currency].Sell - tickerPrice.Bid = result[currency].Buy - tickerPrice.Last = result[currency].Last - tickerPrice.Low = result[currency].Low - tickerPrice.Volume = result[currency].VolumeCurrent + tickerPrice.Last = result[curr].Last + tickerPrice.Ask = result[curr].Sell + tickerPrice.Bid = result[curr].Buy + tickerPrice.Last = result[curr].Last + tickerPrice.Low = result[curr].Low + tickerPrice.QuoteVolume = result[curr].VolumeCurrent + tickerPrice.Volume = result[curr].Vol err = ticker.ProcessTicker(y.Name, &tickerPrice, assetType) if err != nil { - return tickerPrice, err + log.Error(log.Ticker, err) } } return ticker.GetTicker(y.Name, p, assetType) diff --git a/exchanges/zb/zb_types.go b/exchanges/zb/zb_types.go index 0e062207..c1613da6 100644 --- a/exchanges/zb/zb_types.go +++ b/exchanges/zb/zb_types.go @@ -72,12 +72,12 @@ type TickerResponse struct { // TickerChildResponse holds the ticker child response data type TickerChildResponse struct { - Vol float64 `json:"vol,string"` // 成交量(最近的24小时) - Last float64 `json:"last,string"` // 最新成交价 - Sell float64 `json:"sell,string"` // 卖一价 - Buy float64 `json:"buy,string"` // 买一价 - High float64 `json:"high,string"` // 最高价 - Low float64 `json:"low,string"` // 最低价 + Volume float64 `json:"vol,string"` // 成交量(最近的24小时) + Last float64 `json:"last,string"` // 最新成交价 + Sell float64 `json:"sell,string"` // 卖一价 + Buy float64 `json:"buy,string"` // 买一价 + High float64 `json:"high,string"` // 最高价 + Low float64 `json:"low,string"` // 最低价 } // SpotNewOrderRequestParamsType ZB 交易类型 diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 35bfe5e9..7cd23866 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -96,13 +96,17 @@ func (z *ZB) WsHandleData() { } z.Websocket.DataHandler <- wshandler.TickerData{ - Timestamp: time.Unix(0, ticker.Date), - Pair: currency.NewPairFromString(cPair[0]), - AssetType: asset.Spot, - Exchange: z.GetName(), - ClosePrice: ticker.Data.Last, - HighPrice: ticker.Data.High, - LowPrice: ticker.Data.Low, + Exchange: z.Name, + Close: ticker.Data.Last, + Volume: ticker.Data.Volume24Hr, + High: ticker.Data.High, + Low: ticker.Data.Low, + Last: ticker.Data.Last, + Bid: ticker.Data.Buy, + Ask: ticker.Data.Sell, + Timestamp: time.Unix(0, ticker.Date), + AssetType: asset.Spot, + Pair: currency.NewPairFromString(cPair[0]), } case strings.Contains(result.Channel, "depth"): diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index faf19c92..34b46ded 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -204,21 +204,23 @@ func (z *ZB) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, } for _, x := range z.GetEnabledPairs(assetType) { - currencySplit := strings.Split(z.FormatExchangeCurrency(x, assetType).String(), "_") - currency := currencySplit[0] + currencySplit[1] + currencySplit := strings.Split(z.FormatExchangeCurrency(x, assetType).String(), z.GetPairFormat(assetType, false).Delimiter) + curr := currencySplit[0] + currencySplit[1] + if _, ok := result[curr]; !ok { + continue + } var tp ticker.Price tp.Pair = x - tp.High = result[currency].High - 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 + tp.High = result[curr].High + tp.Last = result[curr].Last + tp.Ask = result[curr].Sell + tp.Bid = result[curr].Buy + tp.Low = result[curr].Low + tp.Volume = result[curr].Volume err = ticker.ProcessTicker(z.Name, &tp, assetType) if err != nil { - return tickerPrice, err + log.Error(log.Ticker, err) } } diff --git a/testdata/http_mock/gemini/gemini.json b/testdata/http_mock/gemini/gemini.json index 317e5701..b40bab4a 100644 --- a/testdata/http_mock/gemini/gemini.json +++ b/testdata/http_mock/gemini/gemini.json @@ -2729,6 +2729,64 @@ } } ] + }, + "/v2/ticker/BTCUSD": { + "GET": [ + { + "data": { + "ask": "9663.28", + "bid": "9662.94", + "changes": [ + "9719", + "9730.13", + "9718.58", + "9672.54", + "9668.57", + "9701.67", + "10191.27", + "10225.8", + "10238.78", + "10210.5", + "10171.2", + "10156.31", + "10138.69", + "10121.71", + "10147.31", + "10120.74", + "10149.82", + "10185.68", + "10128.28", + "10070.56", + "10082.86", + "10114", + "10089.25", + "10142.29" + ], + "close": "9662.94", + "high": "11000", + "low": "9210", + "open": "10148.67", + "symbol": "BTCUSD" + }, + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] + }, + "/v2/ticker/bla": { + "GET": [ + { + "data": { + "message": "Supplied value 'bla' is not a valid symbol. Please correct your API request to use one of the supported symbols: [zecbch, bchbtc, zecusd, ethusd, zecbtc, bcheth, zecltc, ltcbch, bchusd, ethbtc, ltcbtc, ltceth, zeceth, ltcusd, btcusd]", + "reason": "Bad Request", + "result": "error" + }, + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] } } } \ No newline at end of file From 2307834fb687fee3eb12be6cf9da04ccacaf3836 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 3 Sep 2019 10:32:17 +1000 Subject: [PATCH 29/71] Minor fixes 1) Fixes go fmt issue 2) Removes TO-DO item for supported asset pairs for GetExchangeInfo API 3) Fixes useless select used for testing --- currency/storage.go | 2 +- engine/rpcserver.go | 37 +- gctrpc/rpc.pb.go | 779 +++++++++++++++++++++------------------- gctrpc/rpc.pb.gw.go | 117 +++--- gctrpc/rpc.proto | 11 +- gctrpc/rpc.swagger.json | 22 +- 6 files changed, 497 insertions(+), 471 deletions(-) diff --git a/currency/storage.go b/currency/storage.go index af71342a..a74947ab 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -130,7 +130,7 @@ func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration Verbose: settings.CryptocurrencyProvider.Verbose, }) if err != nil { - log.Errorf(log.Global, + log.Errorf(log.Global, "Unable to setup CoinMarketCap analysis. Error: %s", err) c = nil settings.CryptocurrencyProvider.Enabled = false diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 9e7fcc1f..ad3e48b6 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -138,8 +138,6 @@ func StartRPCRESTProxy() { }() log.Debugln(log.GRPCSys, "gRPC proxy server started!") - select {} - } // GetInfo returns info about the current GoCryptoTrader session @@ -252,23 +250,26 @@ func (s *RPCServer) GetExchangeInfo(ctx context.Context, r *gctrpc.GenericExchan return nil, err } - return &gctrpc.GetExchangeInfoResponse{ - Name: exchCfg.Name, - Enabled: exchCfg.Enabled, - Verbose: exchCfg.Verbose, - UsingSandbox: exchCfg.UseSandbox, - HttpTimeout: exchCfg.HTTPTimeout.String(), - HttpUseragent: exchCfg.HTTPUserAgent, - HttpProxy: exchCfg.ProxyAddress, - BaseCurrencies: strings.Join(exchCfg.BaseCurrencies.Strings(), ","), - SupportedAssets: exchCfg.CurrencyPairs.AssetTypes.JoinToString(","), + resp := &gctrpc.GetExchangeInfoResponse{ + Name: exchCfg.Name, + Enabled: exchCfg.Enabled, + Verbose: exchCfg.Verbose, + UsingSandbox: exchCfg.UseSandbox, + HttpTimeout: exchCfg.HTTPTimeout.String(), + HttpUseragent: exchCfg.HTTPUserAgent, + HttpProxy: exchCfg.ProxyAddress, + BaseCurrencies: strings.Join(exchCfg.BaseCurrencies.Strings(), ","), + } - // TO-DO fix pairs - //EnabledPairs: strings.Join( - // exchCfg.CurrencyPairs.Pairs.GetPairs().Enabled.Strings(), ","), - //AvailablePairs: strings.Join( - // exchCfg.CurrencyPairs.Spot.Available.Strings(), ","), - }, nil + resp.SupportedAssets = make(map[string]*gctrpc.PairsSupported) + for x := range exchCfg.CurrencyPairs.AssetTypes { + a := exchCfg.CurrencyPairs.AssetTypes[x] + resp.SupportedAssets[a.String()] = &gctrpc.PairsSupported{ + EnabledPairs: exchCfg.CurrencyPairs.Get(a).Enabled.Join(), + AvailablePairs: exchCfg.CurrencyPairs.Get(a).Available.Join(), + } + } + return resp, nil } // GetTicker returns the ticker for a specified exchange, currency pair and diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index b80231e3..b64224ea 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -811,29 +811,74 @@ func (m *DisableExchangeRequest) GetExchange() string { return "" } -type GetExchangeInfoResponse struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` - Verbose bool `protobuf:"varint,3,opt,name=verbose,proto3" json:"verbose,omitempty"` - UsingSandbox bool `protobuf:"varint,4,opt,name=using_sandbox,json=usingSandbox,proto3" json:"using_sandbox,omitempty"` - HttpTimeout string `protobuf:"bytes,5,opt,name=http_timeout,json=httpTimeout,proto3" json:"http_timeout,omitempty"` - HttpUseragent string `protobuf:"bytes,6,opt,name=http_useragent,json=httpUseragent,proto3" json:"http_useragent,omitempty"` - HttpProxy string `protobuf:"bytes,7,opt,name=http_proxy,json=httpProxy,proto3" json:"http_proxy,omitempty"` - BaseCurrencies string `protobuf:"bytes,8,opt,name=base_currencies,json=baseCurrencies,proto3" json:"base_currencies,omitempty"` - SupportedAssets string `protobuf:"bytes,9,opt,name=supported_assets,json=supportedAssets,proto3" json:"supported_assets,omitempty"` - EnabledPairs string `protobuf:"bytes,10,opt,name=enabled_pairs,json=enabledPairs,proto3" json:"enabled_pairs,omitempty"` - AvailablePairs string `protobuf:"bytes,11,opt,name=available_pairs,json=availablePairs,proto3" json:"available_pairs,omitempty"` - AuthenticatedApi bool `protobuf:"varint,12,opt,name=authenticated_api,json=authenticatedApi,proto3" json:"authenticated_api,omitempty"` +type PairsSupported struct { + AvailablePairs string `protobuf:"bytes,1,opt,name=available_pairs,json=availablePairs,proto3" json:"available_pairs,omitempty"` + EnabledPairs string `protobuf:"bytes,2,opt,name=enabled_pairs,json=enabledPairs,proto3" json:"enabled_pairs,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } +func (m *PairsSupported) Reset() { *m = PairsSupported{} } +func (m *PairsSupported) String() string { return proto.CompactTextString(m) } +func (*PairsSupported) ProtoMessage() {} +func (*PairsSupported) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{20} +} + +func (m *PairsSupported) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PairsSupported.Unmarshal(m, b) +} +func (m *PairsSupported) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PairsSupported.Marshal(b, m, deterministic) +} +func (m *PairsSupported) XXX_Merge(src proto.Message) { + xxx_messageInfo_PairsSupported.Merge(m, src) +} +func (m *PairsSupported) XXX_Size() int { + return xxx_messageInfo_PairsSupported.Size(m) +} +func (m *PairsSupported) XXX_DiscardUnknown() { + xxx_messageInfo_PairsSupported.DiscardUnknown(m) +} + +var xxx_messageInfo_PairsSupported proto.InternalMessageInfo + +func (m *PairsSupported) GetAvailablePairs() string { + if m != nil { + return m.AvailablePairs + } + return "" +} + +func (m *PairsSupported) GetEnabledPairs() string { + if m != nil { + return m.EnabledPairs + } + return "" +} + +type GetExchangeInfoResponse struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + Verbose bool `protobuf:"varint,3,opt,name=verbose,proto3" json:"verbose,omitempty"` + UsingSandbox bool `protobuf:"varint,4,opt,name=using_sandbox,json=usingSandbox,proto3" json:"using_sandbox,omitempty"` + HttpTimeout string `protobuf:"bytes,5,opt,name=http_timeout,json=httpTimeout,proto3" json:"http_timeout,omitempty"` + HttpUseragent string `protobuf:"bytes,6,opt,name=http_useragent,json=httpUseragent,proto3" json:"http_useragent,omitempty"` + HttpProxy string `protobuf:"bytes,7,opt,name=http_proxy,json=httpProxy,proto3" json:"http_proxy,omitempty"` + BaseCurrencies string `protobuf:"bytes,8,opt,name=base_currencies,json=baseCurrencies,proto3" json:"base_currencies,omitempty"` + SupportedAssets map[string]*PairsSupported `protobuf:"bytes,9,rep,name=supported_assets,json=supportedAssets,proto3" json:"supported_assets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + AuthenticatedApi bool `protobuf:"varint,10,opt,name=authenticated_api,json=authenticatedApi,proto3" json:"authenticated_api,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + func (m *GetExchangeInfoResponse) Reset() { *m = GetExchangeInfoResponse{} } func (m *GetExchangeInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetExchangeInfoResponse) ProtoMessage() {} func (*GetExchangeInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{20} + return fileDescriptor_77a6da22d6a3feb1, []int{21} } func (m *GetExchangeInfoResponse) XXX_Unmarshal(b []byte) error { @@ -910,25 +955,11 @@ func (m *GetExchangeInfoResponse) GetBaseCurrencies() string { return "" } -func (m *GetExchangeInfoResponse) GetSupportedAssets() string { +func (m *GetExchangeInfoResponse) GetSupportedAssets() map[string]*PairsSupported { if m != nil { return m.SupportedAssets } - return "" -} - -func (m *GetExchangeInfoResponse) GetEnabledPairs() string { - if m != nil { - return m.EnabledPairs - } - return "" -} - -func (m *GetExchangeInfoResponse) GetAvailablePairs() string { - if m != nil { - return m.AvailablePairs - } - return "" + return nil } func (m *GetExchangeInfoResponse) GetAuthenticatedApi() bool { @@ -951,7 +982,7 @@ func (m *GetTickerRequest) Reset() { *m = GetTickerRequest{} } func (m *GetTickerRequest) String() string { return proto.CompactTextString(m) } func (*GetTickerRequest) ProtoMessage() {} func (*GetTickerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{21} + return fileDescriptor_77a6da22d6a3feb1, []int{22} } func (m *GetTickerRequest) XXX_Unmarshal(b []byte) error { @@ -1006,7 +1037,7 @@ func (m *CurrencyPair) Reset() { *m = CurrencyPair{} } func (m *CurrencyPair) String() string { return proto.CompactTextString(m) } func (*CurrencyPair) ProtoMessage() {} func (*CurrencyPair) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{22} + return fileDescriptor_77a6da22d6a3feb1, []int{23} } func (m *CurrencyPair) XXX_Unmarshal(b []byte) error { @@ -1068,7 +1099,7 @@ func (m *TickerResponse) Reset() { *m = TickerResponse{} } func (m *TickerResponse) String() string { return proto.CompactTextString(m) } func (*TickerResponse) ProtoMessage() {} func (*TickerResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{23} + return fileDescriptor_77a6da22d6a3feb1, []int{24} } func (m *TickerResponse) XXX_Unmarshal(b []byte) error { @@ -1169,7 +1200,7 @@ func (m *GetTickersRequest) Reset() { *m = GetTickersRequest{} } func (m *GetTickersRequest) String() string { return proto.CompactTextString(m) } func (*GetTickersRequest) ProtoMessage() {} func (*GetTickersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{24} + return fileDescriptor_77a6da22d6a3feb1, []int{25} } func (m *GetTickersRequest) XXX_Unmarshal(b []byte) error { @@ -1202,7 +1233,7 @@ func (m *Tickers) Reset() { *m = Tickers{} } func (m *Tickers) String() string { return proto.CompactTextString(m) } func (*Tickers) ProtoMessage() {} func (*Tickers) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{25} + return fileDescriptor_77a6da22d6a3feb1, []int{26} } func (m *Tickers) XXX_Unmarshal(b []byte) error { @@ -1248,7 +1279,7 @@ func (m *GetTickersResponse) Reset() { *m = GetTickersResponse{} } func (m *GetTickersResponse) String() string { return proto.CompactTextString(m) } func (*GetTickersResponse) ProtoMessage() {} func (*GetTickersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{26} + return fileDescriptor_77a6da22d6a3feb1, []int{27} } func (m *GetTickersResponse) XXX_Unmarshal(b []byte) error { @@ -1289,7 +1320,7 @@ func (m *GetOrderbookRequest) Reset() { *m = GetOrderbookRequest{} } func (m *GetOrderbookRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderbookRequest) ProtoMessage() {} func (*GetOrderbookRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{27} + return fileDescriptor_77a6da22d6a3feb1, []int{28} } func (m *GetOrderbookRequest) XXX_Unmarshal(b []byte) error { @@ -1344,7 +1375,7 @@ func (m *OrderbookItem) Reset() { *m = OrderbookItem{} } func (m *OrderbookItem) String() string { return proto.CompactTextString(m) } func (*OrderbookItem) ProtoMessage() {} func (*OrderbookItem) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{28} + return fileDescriptor_77a6da22d6a3feb1, []int{29} } func (m *OrderbookItem) XXX_Unmarshal(b []byte) error { @@ -1402,7 +1433,7 @@ func (m *OrderbookResponse) Reset() { *m = OrderbookResponse{} } func (m *OrderbookResponse) String() string { return proto.CompactTextString(m) } func (*OrderbookResponse) ProtoMessage() {} func (*OrderbookResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{29} + return fileDescriptor_77a6da22d6a3feb1, []int{30} } func (m *OrderbookResponse) XXX_Unmarshal(b []byte) error { @@ -1475,7 +1506,7 @@ func (m *GetOrderbooksRequest) Reset() { *m = GetOrderbooksRequest{} } func (m *GetOrderbooksRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderbooksRequest) ProtoMessage() {} func (*GetOrderbooksRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{30} + return fileDescriptor_77a6da22d6a3feb1, []int{31} } func (m *GetOrderbooksRequest) XXX_Unmarshal(b []byte) error { @@ -1508,7 +1539,7 @@ func (m *Orderbooks) Reset() { *m = Orderbooks{} } func (m *Orderbooks) String() string { return proto.CompactTextString(m) } func (*Orderbooks) ProtoMessage() {} func (*Orderbooks) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{31} + return fileDescriptor_77a6da22d6a3feb1, []int{32} } func (m *Orderbooks) XXX_Unmarshal(b []byte) error { @@ -1554,7 +1585,7 @@ func (m *GetOrderbooksResponse) Reset() { *m = GetOrderbooksResponse{} } func (m *GetOrderbooksResponse) String() string { return proto.CompactTextString(m) } func (*GetOrderbooksResponse) ProtoMessage() {} func (*GetOrderbooksResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{32} + return fileDescriptor_77a6da22d6a3feb1, []int{33} } func (m *GetOrderbooksResponse) XXX_Unmarshal(b []byte) error { @@ -1593,7 +1624,7 @@ func (m *GetAccountInfoRequest) Reset() { *m = GetAccountInfoRequest{} } func (m *GetAccountInfoRequest) String() string { return proto.CompactTextString(m) } func (*GetAccountInfoRequest) ProtoMessage() {} func (*GetAccountInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{33} + return fileDescriptor_77a6da22d6a3feb1, []int{34} } func (m *GetAccountInfoRequest) XXX_Unmarshal(b []byte) error { @@ -1633,7 +1664,7 @@ func (m *Account) Reset() { *m = Account{} } func (m *Account) String() string { return proto.CompactTextString(m) } func (*Account) ProtoMessage() {} func (*Account) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{34} + return fileDescriptor_77a6da22d6a3feb1, []int{35} } func (m *Account) XXX_Unmarshal(b []byte) error { @@ -1681,7 +1712,7 @@ func (m *AccountCurrencyInfo) Reset() { *m = AccountCurrencyInfo{} } func (m *AccountCurrencyInfo) String() string { return proto.CompactTextString(m) } func (*AccountCurrencyInfo) ProtoMessage() {} func (*AccountCurrencyInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{35} + return fileDescriptor_77a6da22d6a3feb1, []int{36} } func (m *AccountCurrencyInfo) XXX_Unmarshal(b []byte) error { @@ -1735,7 +1766,7 @@ func (m *GetAccountInfoResponse) Reset() { *m = GetAccountInfoResponse{} func (m *GetAccountInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetAccountInfoResponse) ProtoMessage() {} func (*GetAccountInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{36} + return fileDescriptor_77a6da22d6a3feb1, []int{37} } func (m *GetAccountInfoResponse) XXX_Unmarshal(b []byte) error { @@ -1780,7 +1811,7 @@ func (m *GetConfigRequest) Reset() { *m = GetConfigRequest{} } func (m *GetConfigRequest) String() string { return proto.CompactTextString(m) } func (*GetConfigRequest) ProtoMessage() {} func (*GetConfigRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{37} + return fileDescriptor_77a6da22d6a3feb1, []int{38} } func (m *GetConfigRequest) XXX_Unmarshal(b []byte) error { @@ -1812,7 +1843,7 @@ func (m *GetConfigResponse) Reset() { *m = GetConfigResponse{} } func (m *GetConfigResponse) String() string { return proto.CompactTextString(m) } func (*GetConfigResponse) ProtoMessage() {} func (*GetConfigResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{38} + return fileDescriptor_77a6da22d6a3feb1, []int{39} } func (m *GetConfigResponse) XXX_Unmarshal(b []byte) error { @@ -1854,7 +1885,7 @@ func (m *PortfolioAddress) Reset() { *m = PortfolioAddress{} } func (m *PortfolioAddress) String() string { return proto.CompactTextString(m) } func (*PortfolioAddress) ProtoMessage() {} func (*PortfolioAddress) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{39} + return fileDescriptor_77a6da22d6a3feb1, []int{40} } func (m *PortfolioAddress) XXX_Unmarshal(b []byte) error { @@ -1913,7 +1944,7 @@ func (m *GetPortfolioRequest) Reset() { *m = GetPortfolioRequest{} } func (m *GetPortfolioRequest) String() string { return proto.CompactTextString(m) } func (*GetPortfolioRequest) ProtoMessage() {} func (*GetPortfolioRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{40} + return fileDescriptor_77a6da22d6a3feb1, []int{41} } func (m *GetPortfolioRequest) XXX_Unmarshal(b []byte) error { @@ -1945,7 +1976,7 @@ func (m *GetPortfolioResponse) Reset() { *m = GetPortfolioResponse{} } func (m *GetPortfolioResponse) String() string { return proto.CompactTextString(m) } func (*GetPortfolioResponse) ProtoMessage() {} func (*GetPortfolioResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{41} + return fileDescriptor_77a6da22d6a3feb1, []int{42} } func (m *GetPortfolioResponse) XXX_Unmarshal(b []byte) error { @@ -1983,7 +2014,7 @@ func (m *GetPortfolioSummaryRequest) Reset() { *m = GetPortfolioSummaryR func (m *GetPortfolioSummaryRequest) String() string { return proto.CompactTextString(m) } func (*GetPortfolioSummaryRequest) ProtoMessage() {} func (*GetPortfolioSummaryRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{42} + return fileDescriptor_77a6da22d6a3feb1, []int{43} } func (m *GetPortfolioSummaryRequest) XXX_Unmarshal(b []byte) error { @@ -2018,7 +2049,7 @@ func (m *Coin) Reset() { *m = Coin{} } func (m *Coin) String() string { return proto.CompactTextString(m) } func (*Coin) ProtoMessage() {} func (*Coin) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{43} + return fileDescriptor_77a6da22d6a3feb1, []int{44} } func (m *Coin) XXX_Unmarshal(b []byte) error { @@ -2080,7 +2111,7 @@ func (m *OfflineCoinSummary) Reset() { *m = OfflineCoinSummary{} } func (m *OfflineCoinSummary) String() string { return proto.CompactTextString(m) } func (*OfflineCoinSummary) ProtoMessage() {} func (*OfflineCoinSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{44} + return fileDescriptor_77a6da22d6a3feb1, []int{45} } func (m *OfflineCoinSummary) XXX_Unmarshal(b []byte) error { @@ -2134,7 +2165,7 @@ func (m *OnlineCoinSummary) Reset() { *m = OnlineCoinSummary{} } func (m *OnlineCoinSummary) String() string { return proto.CompactTextString(m) } func (*OnlineCoinSummary) ProtoMessage() {} func (*OnlineCoinSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{45} + return fileDescriptor_77a6da22d6a3feb1, []int{46} } func (m *OnlineCoinSummary) XXX_Unmarshal(b []byte) error { @@ -2180,7 +2211,7 @@ func (m *OfflineCoins) Reset() { *m = OfflineCoins{} } func (m *OfflineCoins) String() string { return proto.CompactTextString(m) } func (*OfflineCoins) ProtoMessage() {} func (*OfflineCoins) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{46} + return fileDescriptor_77a6da22d6a3feb1, []int{47} } func (m *OfflineCoins) XXX_Unmarshal(b []byte) error { @@ -2219,7 +2250,7 @@ func (m *OnlineCoins) Reset() { *m = OnlineCoins{} } func (m *OnlineCoins) String() string { return proto.CompactTextString(m) } func (*OnlineCoins) ProtoMessage() {} func (*OnlineCoins) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{47} + return fileDescriptor_77a6da22d6a3feb1, []int{48} } func (m *OnlineCoins) XXX_Unmarshal(b []byte) error { @@ -2262,7 +2293,7 @@ func (m *GetPortfolioSummaryResponse) Reset() { *m = GetPortfolioSummary func (m *GetPortfolioSummaryResponse) String() string { return proto.CompactTextString(m) } func (*GetPortfolioSummaryResponse) ProtoMessage() {} func (*GetPortfolioSummaryResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{48} + return fileDescriptor_77a6da22d6a3feb1, []int{49} } func (m *GetPortfolioSummaryResponse) XXX_Unmarshal(b []byte) error { @@ -2332,7 +2363,7 @@ func (m *AddPortfolioAddressRequest) Reset() { *m = AddPortfolioAddressR func (m *AddPortfolioAddressRequest) String() string { return proto.CompactTextString(m) } func (*AddPortfolioAddressRequest) ProtoMessage() {} func (*AddPortfolioAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{49} + return fileDescriptor_77a6da22d6a3feb1, []int{50} } func (m *AddPortfolioAddressRequest) XXX_Unmarshal(b []byte) error { @@ -2391,7 +2422,7 @@ func (m *AddPortfolioAddressResponse) Reset() { *m = AddPortfolioAddress func (m *AddPortfolioAddressResponse) String() string { return proto.CompactTextString(m) } func (*AddPortfolioAddressResponse) ProtoMessage() {} func (*AddPortfolioAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{50} + return fileDescriptor_77a6da22d6a3feb1, []int{51} } func (m *AddPortfolioAddressResponse) XXX_Unmarshal(b []byte) error { @@ -2425,7 +2456,7 @@ func (m *RemovePortfolioAddressRequest) Reset() { *m = RemovePortfolioAd func (m *RemovePortfolioAddressRequest) String() string { return proto.CompactTextString(m) } func (*RemovePortfolioAddressRequest) ProtoMessage() {} func (*RemovePortfolioAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{51} + return fileDescriptor_77a6da22d6a3feb1, []int{52} } func (m *RemovePortfolioAddressRequest) XXX_Unmarshal(b []byte) error { @@ -2477,7 +2508,7 @@ func (m *RemovePortfolioAddressResponse) Reset() { *m = RemovePortfolioA func (m *RemovePortfolioAddressResponse) String() string { return proto.CompactTextString(m) } func (*RemovePortfolioAddressResponse) ProtoMessage() {} func (*RemovePortfolioAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{52} + return fileDescriptor_77a6da22d6a3feb1, []int{53} } func (m *RemovePortfolioAddressResponse) XXX_Unmarshal(b []byte) error { @@ -2508,7 +2539,7 @@ func (m *GetForexProvidersRequest) Reset() { *m = GetForexProvidersReque func (m *GetForexProvidersRequest) String() string { return proto.CompactTextString(m) } func (*GetForexProvidersRequest) ProtoMessage() {} func (*GetForexProvidersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{53} + return fileDescriptor_77a6da22d6a3feb1, []int{54} } func (m *GetForexProvidersRequest) XXX_Unmarshal(b []byte) error { @@ -2546,7 +2577,7 @@ func (m *ForexProvider) Reset() { *m = ForexProvider{} } func (m *ForexProvider) String() string { return proto.CompactTextString(m) } func (*ForexProvider) ProtoMessage() {} func (*ForexProvider) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{54} + return fileDescriptor_77a6da22d6a3feb1, []int{55} } func (m *ForexProvider) XXX_Unmarshal(b []byte) error { @@ -2627,7 +2658,7 @@ func (m *GetForexProvidersResponse) Reset() { *m = GetForexProvidersResp func (m *GetForexProvidersResponse) String() string { return proto.CompactTextString(m) } func (*GetForexProvidersResponse) ProtoMessage() {} func (*GetForexProvidersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{55} + return fileDescriptor_77a6da22d6a3feb1, []int{56} } func (m *GetForexProvidersResponse) XXX_Unmarshal(b []byte) error { @@ -2665,7 +2696,7 @@ func (m *GetForexRatesRequest) Reset() { *m = GetForexRatesRequest{} } func (m *GetForexRatesRequest) String() string { return proto.CompactTextString(m) } func (*GetForexRatesRequest) ProtoMessage() {} func (*GetForexRatesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{56} + return fileDescriptor_77a6da22d6a3feb1, []int{57} } func (m *GetForexRatesRequest) XXX_Unmarshal(b []byte) error { @@ -2700,7 +2731,7 @@ func (m *ForexRatesConversion) Reset() { *m = ForexRatesConversion{} } func (m *ForexRatesConversion) String() string { return proto.CompactTextString(m) } func (*ForexRatesConversion) ProtoMessage() {} func (*ForexRatesConversion) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{57} + return fileDescriptor_77a6da22d6a3feb1, []int{58} } func (m *ForexRatesConversion) XXX_Unmarshal(b []byte) error { @@ -2760,7 +2791,7 @@ func (m *GetForexRatesResponse) Reset() { *m = GetForexRatesResponse{} } func (m *GetForexRatesResponse) String() string { return proto.CompactTextString(m) } func (*GetForexRatesResponse) ProtoMessage() {} func (*GetForexRatesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{58} + return fileDescriptor_77a6da22d6a3feb1, []int{59} } func (m *GetForexRatesResponse) XXX_Unmarshal(b []byte) error { @@ -2810,7 +2841,7 @@ func (m *OrderDetails) Reset() { *m = OrderDetails{} } func (m *OrderDetails) String() string { return proto.CompactTextString(m) } func (*OrderDetails) ProtoMessage() {} func (*OrderDetails) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{59} + return fileDescriptor_77a6da22d6a3feb1, []int{60} } func (m *OrderDetails) XXX_Unmarshal(b []byte) error { @@ -2928,7 +2959,7 @@ func (m *GetOrdersRequest) Reset() { *m = GetOrdersRequest{} } func (m *GetOrdersRequest) String() string { return proto.CompactTextString(m) } func (*GetOrdersRequest) ProtoMessage() {} func (*GetOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{60} + return fileDescriptor_77a6da22d6a3feb1, []int{61} } func (m *GetOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -2981,7 +3012,7 @@ func (m *GetOrdersResponse) Reset() { *m = GetOrdersResponse{} } func (m *GetOrdersResponse) String() string { return proto.CompactTextString(m) } func (*GetOrdersResponse) ProtoMessage() {} func (*GetOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{61} + return fileDescriptor_77a6da22d6a3feb1, []int{62} } func (m *GetOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -3021,7 +3052,7 @@ func (m *GetOrderRequest) Reset() { *m = GetOrderRequest{} } func (m *GetOrderRequest) String() string { return proto.CompactTextString(m) } func (*GetOrderRequest) ProtoMessage() {} func (*GetOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{62} + return fileDescriptor_77a6da22d6a3feb1, []int{63} } func (m *GetOrderRequest) XXX_Unmarshal(b []byte) error { @@ -3073,7 +3104,7 @@ func (m *SubmitOrderRequest) Reset() { *m = SubmitOrderRequest{} } func (m *SubmitOrderRequest) String() string { return proto.CompactTextString(m) } func (*SubmitOrderRequest) ProtoMessage() {} func (*SubmitOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{63} + return fileDescriptor_77a6da22d6a3feb1, []int{64} } func (m *SubmitOrderRequest) XXX_Unmarshal(b []byte) error { @@ -3155,7 +3186,7 @@ func (m *SubmitOrderResponse) Reset() { *m = SubmitOrderResponse{} } func (m *SubmitOrderResponse) String() string { return proto.CompactTextString(m) } func (*SubmitOrderResponse) ProtoMessage() {} func (*SubmitOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{64} + return fileDescriptor_77a6da22d6a3feb1, []int{65} } func (m *SubmitOrderResponse) XXX_Unmarshal(b []byte) error { @@ -3204,7 +3235,7 @@ func (m *SimulateOrderRequest) Reset() { *m = SimulateOrderRequest{} } func (m *SimulateOrderRequest) String() string { return proto.CompactTextString(m) } func (*SimulateOrderRequest) ProtoMessage() {} func (*SimulateOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{65} + return fileDescriptor_77a6da22d6a3feb1, []int{66} } func (m *SimulateOrderRequest) XXX_Unmarshal(b []byte) error { @@ -3269,7 +3300,7 @@ func (m *SimulateOrderResponse) Reset() { *m = SimulateOrderResponse{} } func (m *SimulateOrderResponse) String() string { return proto.CompactTextString(m) } func (*SimulateOrderResponse) ProtoMessage() {} func (*SimulateOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{66} + return fileDescriptor_77a6da22d6a3feb1, []int{67} } func (m *SimulateOrderResponse) XXX_Unmarshal(b []byte) error { @@ -3346,7 +3377,7 @@ func (m *WhaleBombRequest) Reset() { *m = WhaleBombRequest{} } func (m *WhaleBombRequest) String() string { return proto.CompactTextString(m) } func (*WhaleBombRequest) ProtoMessage() {} func (*WhaleBombRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{67} + return fileDescriptor_77a6da22d6a3feb1, []int{68} } func (m *WhaleBombRequest) XXX_Unmarshal(b []byte) error { @@ -3412,7 +3443,7 @@ func (m *CancelOrderRequest) Reset() { *m = CancelOrderRequest{} } func (m *CancelOrderRequest) String() string { return proto.CompactTextString(m) } func (*CancelOrderRequest) ProtoMessage() {} func (*CancelOrderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{68} + return fileDescriptor_77a6da22d6a3feb1, []int{69} } func (m *CancelOrderRequest) XXX_Unmarshal(b []byte) error { @@ -3492,7 +3523,7 @@ func (m *CancelOrderResponse) Reset() { *m = CancelOrderResponse{} } func (m *CancelOrderResponse) String() string { return proto.CompactTextString(m) } func (*CancelOrderResponse) ProtoMessage() {} func (*CancelOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{69} + return fileDescriptor_77a6da22d6a3feb1, []int{70} } func (m *CancelOrderResponse) XXX_Unmarshal(b []byte) error { @@ -3524,7 +3555,7 @@ func (m *CancelAllOrdersRequest) Reset() { *m = CancelAllOrdersRequest{} func (m *CancelAllOrdersRequest) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersRequest) ProtoMessage() {} func (*CancelAllOrdersRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{70} + return fileDescriptor_77a6da22d6a3feb1, []int{71} } func (m *CancelAllOrdersRequest) XXX_Unmarshal(b []byte) error { @@ -3563,7 +3594,7 @@ func (m *CancelAllOrdersResponse) Reset() { *m = CancelAllOrdersResponse func (m *CancelAllOrdersResponse) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse) ProtoMessage() {} func (*CancelAllOrdersResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{71} + return fileDescriptor_77a6da22d6a3feb1, []int{72} } func (m *CancelAllOrdersResponse) XXX_Unmarshal(b []byte) error { @@ -3603,7 +3634,7 @@ func (m *CancelAllOrdersResponse_Orders) Reset() { *m = CancelAllOrdersR func (m *CancelAllOrdersResponse_Orders) String() string { return proto.CompactTextString(m) } func (*CancelAllOrdersResponse_Orders) ProtoMessage() {} func (*CancelAllOrdersResponse_Orders) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{71, 0} + return fileDescriptor_77a6da22d6a3feb1, []int{72, 0} } func (m *CancelAllOrdersResponse_Orders) XXX_Unmarshal(b []byte) error { @@ -3648,7 +3679,7 @@ func (m *GetEventsRequest) Reset() { *m = GetEventsRequest{} } func (m *GetEventsRequest) String() string { return proto.CompactTextString(m) } func (*GetEventsRequest) ProtoMessage() {} func (*GetEventsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{72} + return fileDescriptor_77a6da22d6a3feb1, []int{73} } func (m *GetEventsRequest) XXX_Unmarshal(b []byte) error { @@ -3684,7 +3715,7 @@ func (m *ConditionParams) Reset() { *m = ConditionParams{} } func (m *ConditionParams) String() string { return proto.CompactTextString(m) } func (*ConditionParams) ProtoMessage() {} func (*ConditionParams) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{73} + return fileDescriptor_77a6da22d6a3feb1, []int{74} } func (m *ConditionParams) XXX_Unmarshal(b []byte) error { @@ -3757,7 +3788,7 @@ func (m *GetEventsResponse) Reset() { *m = GetEventsResponse{} } func (m *GetEventsResponse) String() string { return proto.CompactTextString(m) } func (*GetEventsResponse) ProtoMessage() {} func (*GetEventsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{74} + return fileDescriptor_77a6da22d6a3feb1, []int{75} } func (m *GetEventsResponse) XXX_Unmarshal(b []byte) error { @@ -3843,7 +3874,7 @@ func (m *AddEventRequest) Reset() { *m = AddEventRequest{} } func (m *AddEventRequest) String() string { return proto.CompactTextString(m) } func (*AddEventRequest) ProtoMessage() {} func (*AddEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{75} + return fileDescriptor_77a6da22d6a3feb1, []int{76} } func (m *AddEventRequest) XXX_Unmarshal(b []byte) error { @@ -3917,7 +3948,7 @@ func (m *AddEventResponse) Reset() { *m = AddEventResponse{} } func (m *AddEventResponse) String() string { return proto.CompactTextString(m) } func (*AddEventResponse) ProtoMessage() {} func (*AddEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{76} + return fileDescriptor_77a6da22d6a3feb1, []int{77} } func (m *AddEventResponse) XXX_Unmarshal(b []byte) error { @@ -3956,7 +3987,7 @@ func (m *RemoveEventRequest) Reset() { *m = RemoveEventRequest{} } func (m *RemoveEventRequest) String() string { return proto.CompactTextString(m) } func (*RemoveEventRequest) ProtoMessage() {} func (*RemoveEventRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{77} + return fileDescriptor_77a6da22d6a3feb1, []int{78} } func (m *RemoveEventRequest) XXX_Unmarshal(b []byte) error { @@ -3994,7 +4025,7 @@ func (m *RemoveEventResponse) Reset() { *m = RemoveEventResponse{} } func (m *RemoveEventResponse) String() string { return proto.CompactTextString(m) } func (*RemoveEventResponse) ProtoMessage() {} func (*RemoveEventResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{78} + return fileDescriptor_77a6da22d6a3feb1, []int{79} } func (m *RemoveEventResponse) XXX_Unmarshal(b []byte) error { @@ -4028,7 +4059,7 @@ func (m *GetCryptocurrencyDepositAddressesRequest) Reset() { func (m *GetCryptocurrencyDepositAddressesRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{79} + return fileDescriptor_77a6da22d6a3feb1, []int{80} } func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Unmarshal(b []byte) error { @@ -4069,7 +4100,7 @@ func (m *GetCryptocurrencyDepositAddressesResponse) Reset() { func (m *GetCryptocurrencyDepositAddressesResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressesResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{80} + return fileDescriptor_77a6da22d6a3feb1, []int{81} } func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Unmarshal(b []byte) error { @@ -4111,7 +4142,7 @@ func (m *GetCryptocurrencyDepositAddressRequest) Reset() { func (m *GetCryptocurrencyDepositAddressRequest) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{81} + return fileDescriptor_77a6da22d6a3feb1, []int{82} } func (m *GetCryptocurrencyDepositAddressRequest) XXX_Unmarshal(b []byte) error { @@ -4159,7 +4190,7 @@ func (m *GetCryptocurrencyDepositAddressResponse) Reset() { func (m *GetCryptocurrencyDepositAddressResponse) String() string { return proto.CompactTextString(m) } func (*GetCryptocurrencyDepositAddressResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{82} + return fileDescriptor_77a6da22d6a3feb1, []int{83} } func (m *GetCryptocurrencyDepositAddressResponse) XXX_Unmarshal(b []byte) error { @@ -4214,7 +4245,7 @@ func (m *WithdrawCurrencyRequest) Reset() { *m = WithdrawCurrencyRequest func (m *WithdrawCurrencyRequest) String() string { return proto.CompactTextString(m) } func (*WithdrawCurrencyRequest) ProtoMessage() {} func (*WithdrawCurrencyRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{83} + return fileDescriptor_77a6da22d6a3feb1, []int{84} } func (m *WithdrawCurrencyRequest) XXX_Unmarshal(b []byte) error { @@ -4365,7 +4396,7 @@ func (m *WithdrawResponse) Reset() { *m = WithdrawResponse{} } func (m *WithdrawResponse) String() string { return proto.CompactTextString(m) } func (*WithdrawResponse) ProtoMessage() {} func (*WithdrawResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{84} + return fileDescriptor_77a6da22d6a3feb1, []int{85} } func (m *WithdrawResponse) XXX_Unmarshal(b []byte) error { @@ -4404,7 +4435,7 @@ func (m *GetLoggerDetailsRequest) Reset() { *m = GetLoggerDetailsRequest func (m *GetLoggerDetailsRequest) String() string { return proto.CompactTextString(m) } func (*GetLoggerDetailsRequest) ProtoMessage() {} func (*GetLoggerDetailsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{85} + return fileDescriptor_77a6da22d6a3feb1, []int{86} } func (m *GetLoggerDetailsRequest) XXX_Unmarshal(b []byte) error { @@ -4446,7 +4477,7 @@ func (m *GetLoggerDetailsResponse) Reset() { *m = GetLoggerDetailsRespon func (m *GetLoggerDetailsResponse) String() string { return proto.CompactTextString(m) } func (*GetLoggerDetailsResponse) ProtoMessage() {} func (*GetLoggerDetailsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{86} + return fileDescriptor_77a6da22d6a3feb1, []int{87} } func (m *GetLoggerDetailsResponse) XXX_Unmarshal(b []byte) error { @@ -4507,7 +4538,7 @@ func (m *SetLoggerDetailsRequest) Reset() { *m = SetLoggerDetailsRequest func (m *SetLoggerDetailsRequest) String() string { return proto.CompactTextString(m) } func (*SetLoggerDetailsRequest) ProtoMessage() {} func (*SetLoggerDetailsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{87} + return fileDescriptor_77a6da22d6a3feb1, []int{88} } func (m *SetLoggerDetailsRequest) XXX_Unmarshal(b []byte) error { @@ -4569,7 +4600,9 @@ func init() { proto.RegisterType((*GetExchangeOTPsResponse)(nil), "gctrpc.GetExchangeOTPsResponse") proto.RegisterMapType((map[string]string)(nil), "gctrpc.GetExchangeOTPsResponse.OtpCodesEntry") proto.RegisterType((*DisableExchangeRequest)(nil), "gctrpc.DisableExchangeRequest") + proto.RegisterType((*PairsSupported)(nil), "gctrpc.PairsSupported") proto.RegisterType((*GetExchangeInfoResponse)(nil), "gctrpc.GetExchangeInfoResponse") + proto.RegisterMapType((map[string]*PairsSupported)(nil), "gctrpc.GetExchangeInfoResponse.SupportedAssetsEntry") proto.RegisterType((*GetTickerRequest)(nil), "gctrpc.GetTickerRequest") proto.RegisterType((*CurrencyPair)(nil), "gctrpc.CurrencyPair") proto.RegisterType((*TickerResponse)(nil), "gctrpc.TickerResponse") @@ -4648,281 +4681,283 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 4379 bytes of a gzipped FileDescriptorProto + // 4415 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x4d, 0x6f, 0x1c, 0x57, - 0x72, 0xe8, 0x21, 0xc5, 0x8f, 0x9a, 0x21, 0x67, 0xf8, 0xf8, 0x35, 0x1a, 0x91, 0xfa, 0x68, 0xad, - 0x64, 0x49, 0xb6, 0x29, 0x5b, 0x16, 0xb2, 0x8e, 0xbd, 0xd9, 0x84, 0xa6, 0x65, 0xae, 0xb2, 0x5e, - 0x8b, 0x69, 0x6a, 0x25, 0xc0, 0x1b, 0xec, 0xa4, 0xd9, 0xfd, 0x38, 0xec, 0xa8, 0xa7, 0xbb, 0xdd, - 0xdd, 0x43, 0x8a, 0x46, 0x80, 0x00, 0x0b, 0x24, 0xc8, 0x29, 0x39, 0x2c, 0x02, 0xe4, 0x90, 0x53, - 0x4e, 0x41, 0x80, 0x5c, 0x82, 0x9c, 0x72, 0x30, 0x72, 0x0d, 0x72, 0xcc, 0x25, 0x3f, 0x20, 0xc8, - 0x2d, 0x09, 0x10, 0x20, 0x97, 0x9c, 0x82, 0x57, 0xef, 0xa3, 0xdf, 0xeb, 0xee, 0x19, 0x0e, 0xd7, - 0x8a, 0x2f, 0xd2, 0x74, 0xbd, 0x7a, 0x55, 0xf5, 0xaa, 0xea, 0xd5, 0xab, 0x57, 0xaf, 0x08, 0x8b, - 0x69, 0xe2, 0xed, 0x24, 0x69, 0x9c, 0xc7, 0x64, 0x6e, 0xe0, 0xe5, 0x69, 0xe2, 0xf5, 0xb6, 0x06, - 0x71, 0x3c, 0x08, 0xe9, 0x43, 0x37, 0x09, 0x1e, 0xba, 0x51, 0x14, 0xe7, 0x6e, 0x1e, 0xc4, 0x51, - 0xc6, 0xb1, 0xec, 0x0e, 0x2c, 0xef, 0xd3, 0xfc, 0x69, 0x74, 0x1c, 0x3b, 0xf4, 0xab, 0x11, 0xcd, - 0x72, 0xfb, 0xef, 0x67, 0xa1, 0xad, 0x40, 0x59, 0x12, 0x47, 0x19, 0x25, 0x1b, 0x30, 0x37, 0x4a, - 0xf2, 0x60, 0x48, 0xbb, 0xd6, 0x4d, 0xeb, 0xde, 0xa2, 0x23, 0xbe, 0xc8, 0x43, 0x58, 0x75, 0x4f, - 0xdd, 0x20, 0x74, 0x8f, 0x42, 0xda, 0xa7, 0xaf, 0xbd, 0x13, 0x37, 0x1a, 0xd0, 0xac, 0xdb, 0xb8, - 0x69, 0xdd, 0x9b, 0x71, 0x88, 0x1a, 0x7a, 0x22, 0x47, 0xc8, 0xdb, 0xb0, 0x42, 0x23, 0x06, 0xf2, - 0x35, 0xf4, 0x19, 0x44, 0xef, 0x88, 0x81, 0x02, 0xf9, 0x31, 0x6c, 0xf8, 0xf4, 0xd8, 0x1d, 0x85, - 0x79, 0xff, 0x38, 0x4e, 0xe9, 0xeb, 0x7e, 0x92, 0xc6, 0xa7, 0x81, 0x4f, 0xd3, 0xee, 0x2c, 0x4a, - 0xb1, 0x26, 0x46, 0x3f, 0x63, 0x83, 0x07, 0x62, 0x8c, 0x3c, 0x82, 0x75, 0x35, 0x2b, 0x70, 0xf3, - 0xbe, 0x37, 0x4a, 0x53, 0x1a, 0x79, 0xe7, 0xdd, 0x2b, 0x38, 0x69, 0x55, 0x4e, 0x0a, 0xdc, 0x7c, - 0x4f, 0x0c, 0x91, 0x97, 0xd0, 0xc9, 0x46, 0x47, 0xd9, 0x79, 0x96, 0xd3, 0x61, 0x3f, 0xcb, 0xdd, - 0x7c, 0x94, 0x75, 0xe7, 0x6e, 0xce, 0xdc, 0x6b, 0x3e, 0x7a, 0x67, 0x87, 0xab, 0x71, 0xa7, 0xa4, - 0x92, 0x9d, 0x43, 0x89, 0x7f, 0x88, 0xe8, 0x4f, 0xa2, 0x3c, 0x3d, 0x77, 0xda, 0x99, 0x09, 0x25, - 0x5f, 0xc0, 0x52, 0x9a, 0x78, 0x7d, 0x1a, 0xf9, 0x49, 0x1c, 0x44, 0x79, 0xd6, 0x9d, 0x47, 0xaa, - 0xf7, 0xc7, 0x51, 0x75, 0x12, 0xef, 0x89, 0xc4, 0xe5, 0x24, 0x5b, 0xa9, 0x06, 0xea, 0x7d, 0x02, - 0x6b, 0x75, 0x8c, 0x49, 0x07, 0x66, 0x5e, 0xd1, 0x73, 0x61, 0x1d, 0xf6, 0x93, 0xac, 0xc1, 0x95, - 0x53, 0x37, 0x1c, 0x51, 0x34, 0xc6, 0x82, 0xc3, 0x3f, 0x3e, 0x6a, 0x7c, 0x68, 0xf5, 0x9e, 0xc3, - 0x4a, 0x85, 0x4d, 0x0d, 0x81, 0xfb, 0x3a, 0x81, 0xe6, 0xa3, 0x55, 0x29, 0xb2, 0x73, 0xb0, 0x27, - 0xe7, 0x6a, 0x54, 0xed, 0x5b, 0x70, 0x63, 0x9f, 0xe6, 0x7b, 0xf1, 0x70, 0x38, 0x8a, 0x02, 0x0f, - 0x7d, 0xcc, 0xa1, 0xa1, 0x7b, 0x4e, 0xd3, 0x4c, 0x7a, 0xd6, 0x17, 0xb0, 0x56, 0x37, 0x4e, 0xba, - 0x30, 0x2f, 0x6c, 0x8f, 0xfc, 0x17, 0x1c, 0xf9, 0x49, 0xb6, 0x60, 0xd1, 0x8b, 0xa3, 0x88, 0x7a, - 0x39, 0xf5, 0xc5, 0x42, 0x0a, 0x80, 0xfd, 0xc7, 0x0d, 0xb8, 0x39, 0x9e, 0xa7, 0x70, 0xdd, 0xaf, - 0x61, 0xc3, 0xd3, 0x11, 0xfa, 0xa9, 0xc0, 0xe8, 0x5a, 0x68, 0x8a, 0x3d, 0xcd, 0x14, 0x13, 0x29, - 0xed, 0xd4, 0x8e, 0x72, 0x23, 0xad, 0x7b, 0x75, 0x63, 0xbd, 0x63, 0xe8, 0x8d, 0x9f, 0x54, 0xa3, - 0xf2, 0x47, 0xa6, 0xca, 0xb7, 0xa4, 0x68, 0x75, 0x44, 0x74, 0xdd, 0x7f, 0x1f, 0x36, 0xf7, 0x69, - 0x44, 0xd3, 0xc0, 0x53, 0xce, 0x21, 0x74, 0xce, 0x34, 0xa8, 0x7c, 0x52, 0xb0, 0x2a, 0x00, 0x76, - 0x0f, 0xba, 0xd5, 0x89, 0x7c, 0xb9, 0xf6, 0x06, 0xac, 0xed, 0xd3, 0x5c, 0xc1, 0x95, 0x15, 0xbf, - 0xb1, 0x60, 0x1d, 0x07, 0xb2, 0xa3, 0xec, 0x9c, 0x0f, 0x08, 0x55, 0xff, 0x1e, 0xac, 0x28, 0xd2, - 0x99, 0xdc, 0x46, 0x5c, 0xcb, 0x1f, 0x68, 0x5a, 0xae, 0xce, 0x2c, 0x36, 0x53, 0xa6, 0xef, 0xa6, - 0x62, 0x4f, 0x0a, 0x70, 0x6f, 0x0f, 0xd6, 0x6b, 0x51, 0x2f, 0xe3, 0xff, 0x76, 0x17, 0x36, 0xf6, - 0x69, 0xae, 0xb9, 0xb1, 0xe6, 0xa0, 0x4d, 0x0d, 0xcc, 0xfc, 0x32, 0xcb, 0xdd, 0x34, 0x2f, 0xfc, - 0x52, 0x7c, 0x92, 0x3b, 0xb0, 0x1c, 0x06, 0x59, 0x4e, 0xa3, 0xbe, 0xeb, 0xfb, 0x29, 0xcd, 0x78, - 0xc8, 0x5b, 0x74, 0x96, 0x38, 0x74, 0x97, 0x03, 0xed, 0x7f, 0xb0, 0x98, 0x61, 0x4a, 0xac, 0x84, - 0xb2, 0x3e, 0x87, 0xc5, 0x22, 0x2a, 0x70, 0x25, 0xed, 0x68, 0x4a, 0xaa, 0x9b, 0xb3, 0x53, 0x0a, - 0x0d, 0x05, 0x81, 0xde, 0xef, 0xc0, 0xf2, 0x9b, 0xde, 0xd0, 0x1f, 0x42, 0x4f, 0xf8, 0x86, 0x8c, - 0xc8, 0x5f, 0xb8, 0x43, 0x2a, 0xfd, 0xaa, 0x07, 0x0b, 0x32, 0x80, 0x0b, 0x1e, 0xea, 0xdb, 0xde, - 0x86, 0x6b, 0xb5, 0x33, 0x85, 0x63, 0x3d, 0x84, 0xd5, 0x7d, 0x9a, 0xab, 0x30, 0x2f, 0x29, 0x8e, - 0x8d, 0x02, 0xf6, 0x63, 0xf4, 0x44, 0x6d, 0x82, 0x50, 0xe1, 0x16, 0x2c, 0x16, 0x87, 0x88, 0xf0, - 0x6d, 0x05, 0xb0, 0x1f, 0xa1, 0x9b, 0xca, 0x59, 0xcf, 0x9e, 0x1f, 0x38, 0x94, 0x4f, 0xbb, 0x0a, - 0x0b, 0x71, 0x9e, 0xf4, 0xbd, 0xd8, 0x97, 0xa2, 0xcf, 0xc7, 0x79, 0xb2, 0x17, 0xfb, 0x54, 0xb8, - 0x86, 0x36, 0x47, 0xb9, 0xc6, 0x5f, 0x71, 0x53, 0x9a, 0x43, 0x42, 0x8e, 0xdf, 0x86, 0x45, 0x49, - 0x50, 0x9a, 0xf2, 0x5d, 0xcd, 0x94, 0x75, 0x73, 0x76, 0x9e, 0x71, 0x8e, 0xc2, 0x92, 0x0b, 0x42, - 0x80, 0xac, 0xf7, 0x31, 0x2c, 0x19, 0x43, 0x17, 0x79, 0xf6, 0xa2, 0x6e, 0xb2, 0xc7, 0xb0, 0xf1, - 0x69, 0x90, 0xe9, 0x27, 0xee, 0x34, 0xe6, 0xfa, 0x66, 0xc6, 0x58, 0x9a, 0x71, 0xf0, 0x13, 0x98, - 0x8d, 0x5c, 0x75, 0xec, 0xe3, 0x6f, 0xdd, 0x50, 0x0d, 0x33, 0x5c, 0x77, 0x61, 0xfe, 0x94, 0xa6, - 0x47, 0x71, 0x46, 0xf1, 0x4c, 0x5f, 0x70, 0xe4, 0x27, 0xb9, 0x0d, 0x4b, 0xa3, 0x2c, 0x88, 0x06, - 0xfd, 0xcc, 0x8d, 0xfc, 0xa3, 0xf8, 0x35, 0x9e, 0xe0, 0x0b, 0x4e, 0x0b, 0x81, 0x87, 0x1c, 0x46, - 0x6e, 0x41, 0xeb, 0x24, 0xcf, 0x93, 0x3e, 0x4b, 0x2d, 0xe2, 0x51, 0x2e, 0x0e, 0xec, 0x26, 0x83, - 0x3d, 0xe7, 0x20, 0xb6, 0xf1, 0x10, 0x65, 0x94, 0xd1, 0xd4, 0x1d, 0xd0, 0x28, 0xef, 0xce, 0xf1, - 0x8d, 0xc7, 0xa0, 0x3f, 0x95, 0x40, 0xb2, 0x0d, 0x80, 0x68, 0x49, 0x1a, 0xbf, 0x3e, 0xef, 0xce, - 0x73, 0xd7, 0x60, 0x90, 0x03, 0x06, 0x20, 0x6f, 0x41, 0xfb, 0xc8, 0xcd, 0xa8, 0x4c, 0x0d, 0x02, - 0x9a, 0x75, 0x17, 0x10, 0x67, 0x99, 0x81, 0xf7, 0x14, 0x94, 0xdc, 0x67, 0x79, 0x41, 0x92, 0xc4, - 0x6c, 0xd3, 0xf7, 0xdd, 0x2c, 0xa3, 0x79, 0xd6, 0x5d, 0x44, 0xcc, 0xb6, 0x82, 0xef, 0x22, 0x98, - 0xad, 0x50, 0x66, 0x36, 0x89, 0x1b, 0xa4, 0x59, 0x17, 0x10, 0xaf, 0x25, 0x80, 0x07, 0x0c, 0xc6, - 0x18, 0x17, 0xf9, 0x12, 0x47, 0x6b, 0x72, 0xc6, 0x0a, 0xcc, 0x11, 0xdf, 0x86, 0x15, 0x77, 0x94, - 0x9f, 0xd0, 0x28, 0x67, 0x51, 0x9f, 0x31, 0x4f, 0x82, 0x6e, 0x0b, 0x75, 0xd6, 0x31, 0x06, 0x76, - 0x93, 0xc0, 0x3e, 0x83, 0xce, 0x3e, 0xcd, 0x9f, 0x07, 0xde, 0x2b, 0x9a, 0x4e, 0x61, 0x70, 0x72, - 0x0f, 0x66, 0x19, 0x6f, 0x11, 0x07, 0xd6, 0xd4, 0x29, 0x23, 0xb2, 0x21, 0x26, 0x81, 0x83, 0x18, - 0x4c, 0x8f, 0xb8, 0xea, 0x7e, 0x7e, 0x9e, 0x70, 0x9b, 0x2e, 0x3a, 0x8b, 0x08, 0x79, 0x7e, 0x9e, - 0x50, 0xfb, 0x05, 0xb4, 0xf4, 0x49, 0x6c, 0x43, 0xfa, 0x34, 0x0c, 0x86, 0x41, 0x4e, 0x53, 0xb9, - 0x21, 0x15, 0x80, 0xf9, 0x12, 0x53, 0xaf, 0x70, 0x5b, 0xfc, 0xcd, 0x7c, 0xf9, 0xab, 0x51, 0x9c, - 0x4b, 0xda, 0xfc, 0xc3, 0xfe, 0xf3, 0x06, 0x2c, 0xcb, 0xe5, 0x08, 0x47, 0x94, 0x32, 0x5b, 0x17, - 0xca, 0x7c, 0x0b, 0x5a, 0xa1, 0x9b, 0xe5, 0xfd, 0x51, 0xe2, 0xbb, 0x32, 0x6d, 0x98, 0x71, 0x9a, - 0x0c, 0xf6, 0x53, 0x0e, 0x62, 0xb6, 0x92, 0x59, 0x21, 0x5a, 0x41, 0x70, 0x6f, 0x79, 0xfa, 0x62, - 0x08, 0xcc, 0xb2, 0x39, 0xe8, 0xa9, 0x96, 0x83, 0xbf, 0x19, 0xec, 0x24, 0x18, 0x9c, 0xa0, 0x67, - 0x5a, 0x0e, 0xfe, 0x66, 0x1b, 0x34, 0x8c, 0xcf, 0xd0, 0x0f, 0x2d, 0x87, 0xfd, 0x64, 0x90, 0xa3, - 0xc0, 0x47, 0xb7, 0xb3, 0x1c, 0xf6, 0x93, 0x41, 0xdc, 0xec, 0x15, 0x3a, 0x99, 0xe5, 0xb0, 0x9f, - 0x2c, 0xa3, 0x3e, 0x8d, 0xc3, 0xd1, 0x90, 0xa2, 0x3f, 0x59, 0x8e, 0xf8, 0x22, 0xd7, 0x60, 0x31, - 0x49, 0x03, 0x8f, 0xf6, 0xdd, 0xfc, 0x04, 0x5d, 0xc8, 0x72, 0x16, 0x10, 0xb0, 0x9b, 0x9f, 0xd8, - 0xab, 0xb0, 0xa2, 0x0c, 0xad, 0x22, 0xd3, 0x4b, 0x98, 0x17, 0x90, 0x89, 0x46, 0x7f, 0x0f, 0xe6, - 0x73, 0x8e, 0xd6, 0x6d, 0x60, 0x88, 0xda, 0x90, 0x3a, 0x34, 0x35, 0xed, 0x48, 0x34, 0xfb, 0x37, - 0x81, 0xe8, 0xdc, 0x84, 0x21, 0xee, 0x17, 0x74, 0x78, 0xa8, 0x6b, 0x9b, 0x74, 0xb2, 0x82, 0xc0, - 0xd7, 0x18, 0xe8, 0x9f, 0xa5, 0x3e, 0x0b, 0x02, 0xf1, 0xab, 0xef, 0xd4, 0x35, 0x7f, 0x02, 0x4b, - 0x8a, 0xf1, 0xd3, 0x9c, 0x0e, 0x99, 0xc2, 0xdd, 0x61, 0x3c, 0x8a, 0x72, 0xe4, 0x69, 0x39, 0xe2, - 0x8b, 0x79, 0x20, 0xea, 0x17, 0x59, 0x5a, 0x0e, 0xff, 0x20, 0xcb, 0xd0, 0x08, 0x7c, 0x71, 0x31, - 0x69, 0x04, 0xbe, 0xfd, 0xbf, 0x16, 0xac, 0x68, 0x0b, 0xb9, 0xb4, 0x53, 0x56, 0x3c, 0xae, 0x51, - 0xe3, 0x71, 0xf7, 0x61, 0xf6, 0x28, 0xf0, 0xd9, 0x7d, 0x88, 0xe9, 0x75, 0x5d, 0x92, 0x33, 0xd6, - 0xe1, 0x20, 0x0a, 0x43, 0x75, 0xb3, 0x57, 0x59, 0x77, 0x76, 0x22, 0x2a, 0x43, 0xa9, 0xec, 0x87, - 0x2b, 0xd5, 0xfd, 0x60, 0xea, 0x72, 0xae, 0xac, 0x4b, 0x9e, 0x09, 0x2a, 0xda, 0xca, 0xf3, 0x3c, - 0x80, 0x02, 0x38, 0xd1, 0xac, 0xbf, 0x0e, 0x10, 0x2b, 0x4c, 0xe1, 0x7f, 0x57, 0x2b, 0x42, 0x2b, - 0x17, 0xd4, 0x90, 0xed, 0x1f, 0xe3, 0x31, 0xae, 0x33, 0x17, 0xca, 0x7f, 0x64, 0xd0, 0xe4, 0xbe, - 0x48, 0x2a, 0x34, 0x33, 0x83, 0xd8, 0x07, 0x48, 0x6c, 0xd7, 0xf3, 0x98, 0xe9, 0xb5, 0x4b, 0xef, - 0xc4, 0xf3, 0xf1, 0x05, 0xcc, 0x8b, 0x19, 0xc2, 0x2d, 0x38, 0x42, 0x23, 0xf0, 0xc9, 0xc7, 0x00, - 0xda, 0x19, 0xc2, 0xd7, 0x75, 0x4d, 0xca, 0x20, 0x26, 0x49, 0x6f, 0x40, 0x76, 0x1a, 0xba, 0x7d, - 0x0c, 0xab, 0x35, 0x28, 0x4c, 0x14, 0x75, 0x65, 0x15, 0xa2, 0xc8, 0x6f, 0x72, 0x03, 0x9a, 0x79, - 0x9c, 0xbb, 0x61, 0xbf, 0x48, 0x00, 0x2c, 0x07, 0x10, 0xf4, 0x82, 0x41, 0x30, 0x40, 0xc5, 0x21, - 0xf7, 0x5c, 0x16, 0xa0, 0xe2, 0xd0, 0xb7, 0x5d, 0x4c, 0x6a, 0x8c, 0x45, 0x0b, 0x15, 0x4e, 0x32, - 0xd9, 0xdb, 0xb0, 0xe0, 0xf2, 0x29, 0x72, 0x61, 0xed, 0xd2, 0xc2, 0x1c, 0x85, 0x60, 0x13, 0x3c, - 0x81, 0xf6, 0xe2, 0xe8, 0x38, 0x18, 0x48, 0xef, 0x78, 0x0b, 0x83, 0x95, 0x84, 0x15, 0xf9, 0x84, - 0xef, 0xe6, 0x2e, 0x72, 0x6b, 0x39, 0xf8, 0xdb, 0xfe, 0x23, 0x0b, 0x3a, 0x07, 0x71, 0x9a, 0x1f, - 0xc7, 0x61, 0x10, 0x8b, 0xd4, 0x99, 0xa5, 0x12, 0x32, 0xb5, 0x16, 0x39, 0x9a, 0xf8, 0x64, 0x11, - 0xd2, 0x8b, 0x83, 0x88, 0xfb, 0x6a, 0x43, 0x28, 0x28, 0x0e, 0x22, 0xe6, 0xaa, 0xe4, 0x26, 0x34, - 0x7d, 0x9a, 0x79, 0x69, 0x90, 0xb0, 0xab, 0x92, 0x08, 0x0b, 0x3a, 0x88, 0x11, 0x3e, 0x72, 0x43, - 0x37, 0xf2, 0xa8, 0x88, 0xec, 0xf2, 0xd3, 0x5e, 0xc7, 0x70, 0xa5, 0x24, 0xd1, 0x6e, 0xad, 0x26, - 0x58, 0x2c, 0xe5, 0xd7, 0x60, 0x31, 0x91, 0x40, 0xe1, 0x7e, 0x5d, 0xa9, 0xa1, 0xf2, 0x72, 0x9c, - 0x02, 0xd5, 0xde, 0x62, 0x79, 0x75, 0x41, 0xef, 0x70, 0x34, 0x1c, 0xba, 0xe9, 0xb9, 0xe4, 0x16, - 0xc1, 0xec, 0x5e, 0x1c, 0x44, 0x4c, 0x51, 0x6c, 0x51, 0x32, 0xf1, 0x62, 0xbf, 0x75, 0xd1, 0x1b, - 0x86, 0xe8, 0xba, 0xb6, 0x66, 0x4c, 0x6d, 0x5d, 0x07, 0x48, 0x68, 0xea, 0xd1, 0x28, 0x77, 0x07, - 0x72, 0xc5, 0x1a, 0xc4, 0x3e, 0x01, 0xf2, 0xec, 0xf8, 0x38, 0x0c, 0x22, 0xca, 0xd8, 0x0a, 0x61, - 0x26, 0x68, 0x7f, 0xbc, 0x0c, 0x26, 0xa7, 0x99, 0x0a, 0xa7, 0x9f, 0xc0, 0xca, 0xb3, 0xa8, 0x86, - 0x91, 0x24, 0x67, 0x4d, 0x22, 0xd7, 0xa8, 0x90, 0xfb, 0x11, 0xb4, 0x34, 0xc1, 0x33, 0xf2, 0x21, - 0x2c, 0x0a, 0x19, 0x55, 0x12, 0xde, 0x53, 0xd1, 0xa0, 0xb2, 0x42, 0xa7, 0x40, 0xb6, 0xff, 0xc2, - 0x82, 0x66, 0x21, 0x59, 0x46, 0x1e, 0xc3, 0x15, 0xa6, 0x6e, 0x49, 0xe5, 0xba, 0xa2, 0x52, 0xe0, - 0xec, 0xe0, 0xbf, 0x3c, 0x77, 0xe7, 0xc8, 0xbd, 0x43, 0x80, 0x02, 0x58, 0x93, 0xb5, 0x3f, 0x34, - 0x6f, 0x5f, 0x57, 0xab, 0x54, 0xa5, 0x68, 0x5a, 0x42, 0xff, 0xcf, 0xb3, 0xec, 0x2a, 0x55, 0xe3, - 0x2c, 0xc2, 0x07, 0xdf, 0x85, 0x26, 0xdf, 0x0b, 0x2c, 0x02, 0x48, 0x81, 0x5b, 0x45, 0xd9, 0x20, - 0x88, 0x1c, 0xc0, 0xbd, 0x81, 0xe3, 0xe4, 0x7d, 0x58, 0x42, 0x61, 0xfb, 0x31, 0x57, 0x88, 0xd8, - 0xd8, 0xe6, 0x84, 0x16, 0xa2, 0x08, 0x95, 0x91, 0x04, 0xd6, 0x8d, 0x29, 0xfd, 0x8c, 0x8b, 0x20, - 0x0e, 0xa9, 0x1f, 0x68, 0xf7, 0x9c, 0x71, 0x52, 0x72, 0x65, 0x09, 0x82, 0x62, 0x8c, 0xab, 0x6e, - 0xd5, 0xab, 0x8e, 0x90, 0x87, 0xd0, 0x12, 0x1c, 0x51, 0x33, 0xe2, 0x88, 0x33, 0x65, 0x6c, 0xf2, - 0x89, 0x88, 0x40, 0x86, 0xb0, 0xa6, 0x4f, 0x50, 0x12, 0x5e, 0xc1, 0x89, 0x1f, 0x4f, 0x2f, 0x61, - 0x54, 0x11, 0x90, 0x78, 0x95, 0x81, 0xde, 0xef, 0x42, 0x77, 0xdc, 0x82, 0x6a, 0xcc, 0xfe, 0xc0, - 0x34, 0xfb, 0x5a, 0x8d, 0x4b, 0x66, 0x7a, 0x71, 0xee, 0x4b, 0xd8, 0x1c, 0x23, 0xcc, 0x25, 0x6e, - 0xf4, 0x9a, 0xa7, 0xea, 0xde, 0xf4, 0x67, 0x16, 0xf4, 0x76, 0x7d, 0xbf, 0x12, 0x9c, 0x8a, 0x0b, - 0xf8, 0x77, 0x1d, 0x72, 0xb7, 0xe1, 0x5a, 0xad, 0x40, 0xa2, 0x52, 0xf0, 0x1a, 0xb6, 0x1d, 0x3a, - 0x8c, 0x4f, 0xe9, 0x77, 0x2d, 0xb2, 0x7d, 0x13, 0xae, 0x8f, 0xe3, 0x2c, 0x64, 0xc3, 0xd2, 0x99, - 0x59, 0x7a, 0x56, 0x89, 0xd1, 0x7f, 0x58, 0xb0, 0x64, 0x16, 0xa5, 0xdf, 0xd4, 0x3d, 0xfa, 0x1d, - 0x20, 0x29, 0xcd, 0xf2, 0x7e, 0x1a, 0x87, 0x21, 0xbb, 0x4e, 0xfb, 0x34, 0x74, 0xcf, 0x45, 0x39, - 0xbc, 0xc3, 0x46, 0x1c, 0x3e, 0xf0, 0x29, 0x83, 0x93, 0x4d, 0x98, 0x77, 0x93, 0xa0, 0xcf, 0xbc, - 0x86, 0xdf, 0xa5, 0xe7, 0xdc, 0x24, 0xf8, 0x31, 0x3d, 0x27, 0x36, 0x2c, 0x89, 0x81, 0x7e, 0x48, - 0x4f, 0x69, 0x88, 0x39, 0xdf, 0x8c, 0xd3, 0xe4, 0xc3, 0x9f, 0x33, 0x10, 0xbb, 0xfb, 0x26, 0x69, - 0xc0, 0xdc, 0xaf, 0xa8, 0xbb, 0xcf, 0xa3, 0x34, 0x6d, 0x01, 0x97, 0xab, 0xb3, 0x7f, 0x06, 0x57, - 0x6b, 0x74, 0x21, 0x62, 0xd4, 0x0f, 0xa1, 0x6d, 0x56, 0xef, 0x65, 0x9c, 0x52, 0x59, 0xab, 0x31, - 0xd1, 0x59, 0x3e, 0x36, 0xe8, 0x88, 0xec, 0x13, 0x71, 0x1c, 0x37, 0x57, 0xf5, 0x22, 0xfb, 0x2b, - 0x58, 0x2b, 0x80, 0x7b, 0x71, 0x74, 0x4a, 0xd3, 0x8c, 0x79, 0x1b, 0x81, 0xd9, 0xe3, 0x34, 0x96, - 0xc5, 0x4e, 0xfc, 0xcd, 0xf2, 0xb6, 0x3c, 0x16, 0x6e, 0xd0, 0xc8, 0x63, 0x86, 0x93, 0xba, 0xb9, - 0x3c, 0xa5, 0xf0, 0x37, 0xcb, 0x93, 0x03, 0x24, 0x42, 0xfb, 0x38, 0xc6, 0x5d, 0xb5, 0x29, 0x60, - 0x8c, 0x8b, 0xfd, 0x02, 0xd3, 0x47, 0x5d, 0x14, 0xb1, 0xc6, 0xdf, 0x80, 0x26, 0x5f, 0x23, 0x9b, - 0x29, 0xd7, 0xb7, 0x65, 0xac, 0xaf, 0x24, 0xa6, 0x03, 0xc7, 0x0a, 0x6a, 0xff, 0x57, 0x03, 0x5a, - 0x98, 0xb1, 0x7e, 0x4a, 0x73, 0x37, 0x08, 0x27, 0xe7, 0xd2, 0x3c, 0x07, 0x6d, 0xa8, 0x1c, 0xf4, - 0x36, 0x2c, 0xe9, 0xc5, 0x8c, 0x73, 0x79, 0x99, 0xd5, 0x4a, 0x19, 0xe7, 0xe4, 0x0e, 0x2c, 0xe3, - 0xd5, 0xba, 0xc0, 0xe2, 0x3e, 0xb3, 0x84, 0x50, 0x85, 0x66, 0x5e, 0x04, 0xae, 0x94, 0x2e, 0x02, - 0x6c, 0x18, 0x93, 0xe9, 0x7e, 0x16, 0xf8, 0xea, 0x9e, 0x80, 0x90, 0xc3, 0xc0, 0xd7, 0x86, 0x71, - 0xf6, 0xbc, 0x36, 0x8c, 0xb3, 0xd9, 0x1d, 0x28, 0xa5, 0xbc, 0x08, 0x8f, 0x6f, 0x49, 0x0b, 0xe8, - 0x74, 0x2d, 0x09, 0x7c, 0x1e, 0x0c, 0xf1, 0xa5, 0x49, 0x14, 0x8e, 0x79, 0x9d, 0x45, 0x7c, 0x15, - 0xd7, 0x34, 0xd0, 0xaf, 0x69, 0xc5, 0xa5, 0xae, 0x69, 0x5c, 0xea, 0x6e, 0x40, 0x33, 0x4e, 0x68, - 0xd4, 0x17, 0x57, 0xec, 0x16, 0xcf, 0x1e, 0x18, 0xe8, 0x05, 0x42, 0x44, 0xc9, 0x04, 0x75, 0x9e, - 0x4d, 0x73, 0x2f, 0x35, 0x15, 0xd3, 0x28, 0x2b, 0x46, 0x5e, 0x04, 0x67, 0x2e, 0xba, 0x08, 0xda, - 0xbb, 0x98, 0x15, 0x4b, 0xc6, 0xc2, 0x7d, 0xde, 0x81, 0x39, 0x54, 0x93, 0xf4, 0x9c, 0x35, 0xe3, - 0x1a, 0x23, 0x9c, 0xc2, 0x11, 0x38, 0xf6, 0x8f, 0xf0, 0x7d, 0x0e, 0x87, 0xa6, 0x11, 0xfd, 0x2a, - 0x2c, 0x70, 0xab, 0x28, 0xaf, 0x99, 0xc7, 0xef, 0xa7, 0xbe, 0xfd, 0xaf, 0x16, 0x90, 0xc3, 0xd1, - 0xd1, 0x30, 0x98, 0x9e, 0xda, 0xf4, 0x17, 0x74, 0x02, 0xb3, 0xe8, 0x26, 0xdc, 0x1d, 0xf1, 0x77, - 0xc9, 0x43, 0x66, 0xcb, 0x1e, 0x52, 0x98, 0xf3, 0x4a, 0xfd, 0x1d, 0x7d, 0x4e, 0x37, 0x3e, 0x0b, - 0xf1, 0x61, 0x40, 0xa3, 0xbc, 0x2f, 0x8a, 0x2d, 0x2c, 0xc4, 0x23, 0xe0, 0xa9, 0x6f, 0x1f, 0xc2, - 0xaa, 0xb1, 0x32, 0xa1, 0xe9, 0x5b, 0xd0, 0xe2, 0x02, 0x24, 0xa1, 0xeb, 0xa9, 0x4a, 0x73, 0x13, - 0x61, 0x07, 0x08, 0x9a, 0xa4, 0xaf, 0x3f, 0xb1, 0x60, 0xed, 0x30, 0x18, 0x8e, 0x42, 0x37, 0xa7, - 0xff, 0x0f, 0x1a, 0x2b, 0x96, 0x3f, 0x63, 0x2c, 0x5f, 0x6a, 0x72, 0xb6, 0xd0, 0xa4, 0xfd, 0xdf, - 0x16, 0xac, 0x97, 0x44, 0x51, 0x39, 0xa1, 0xe9, 0x4c, 0x63, 0x8a, 0x03, 0x02, 0x49, 0x63, 0xda, - 0x30, 0x98, 0xde, 0x86, 0xa5, 0x61, 0x10, 0x05, 0xc3, 0xd1, 0xb0, 0xcf, 0x75, 0xcf, 0x65, 0x6a, - 0x09, 0xe0, 0x01, 0x9a, 0x80, 0x21, 0xb9, 0xaf, 0x35, 0xa4, 0x59, 0x81, 0xc4, 0x81, 0x1c, 0xe9, - 0x3d, 0x58, 0x2b, 0xf2, 0xf6, 0xfe, 0xc0, 0x0d, 0xa2, 0x7e, 0x18, 0x67, 0x99, 0xb0, 0x31, 0x29, - 0xc6, 0xf6, 0xdd, 0x20, 0xfa, 0x3c, 0xce, 0x32, 0x2d, 0x08, 0xcc, 0xe9, 0x41, 0x80, 0x25, 0x30, - 0x9d, 0x97, 0x27, 0x6e, 0x48, 0x3f, 0x89, 0x87, 0x47, 0x6f, 0x56, 0xf7, 0xb7, 0xa0, 0xc5, 0xeb, - 0x6e, 0xb9, 0x9b, 0x0e, 0xa8, 0xb4, 0x40, 0x13, 0x61, 0xcf, 0x11, 0x54, 0x6b, 0x86, 0xff, 0xb4, - 0x80, 0xec, 0xb1, 0x54, 0x26, 0x9c, 0xda, 0x1f, 0x58, 0x28, 0xe1, 0xf7, 0xe6, 0xc2, 0xc3, 0x16, - 0x05, 0xe4, 0xa9, 0xe9, 0x7e, 0x33, 0x86, 0xfb, 0xa9, 0xd5, 0xcc, 0x5e, 0xb2, 0x38, 0x56, 0x89, - 0xe3, 0x77, 0x60, 0xf9, 0xcc, 0x0d, 0x43, 0x9a, 0xab, 0xe7, 0x2b, 0x51, 0x45, 0xe7, 0x50, 0x79, - 0x07, 0x97, 0x0b, 0x9e, 0xd7, 0x16, 0xbc, 0x0e, 0xab, 0xc6, 0x7a, 0x45, 0x36, 0xf4, 0x18, 0x36, - 0x38, 0x78, 0x37, 0x0c, 0xa7, 0x8e, 0xaa, 0xf6, 0x5f, 0x36, 0x60, 0xb3, 0x32, 0x4d, 0xa5, 0x0d, - 0xa6, 0x1b, 0xdf, 0x55, 0xcb, 0xad, 0x9f, 0xb0, 0x23, 0x3e, 0xc5, 0xac, 0xde, 0x3f, 0x5a, 0x30, - 0xc7, 0x41, 0x13, 0xad, 0xf1, 0xa5, 0x0c, 0x08, 0xc2, 0xe1, 0xf8, 0x8d, 0xe8, 0xfb, 0xd3, 0x31, - 0xe3, 0xff, 0xe9, 0x4f, 0x96, 0x3c, 0x92, 0x88, 0xd7, 0xca, 0x1f, 0x42, 0xa7, 0x8c, 0x70, 0xa9, - 0xe7, 0x1c, 0x5e, 0x55, 0x79, 0x72, 0x4a, 0xb5, 0x27, 0xca, 0x6f, 0x2c, 0x68, 0xef, 0xc5, 0x91, - 0x1f, 0xb0, 0x13, 0xf3, 0xc0, 0x4d, 0xdd, 0x61, 0x26, 0x5e, 0xc9, 0x39, 0x48, 0x96, 0xdd, 0x15, - 0x60, 0x4c, 0x81, 0x73, 0x1b, 0xc0, 0x3b, 0xa1, 0xde, 0xab, 0xbe, 0xa8, 0x38, 0xf2, 0xa7, 0x75, - 0x06, 0xf9, 0x24, 0xf0, 0x33, 0xf2, 0x2e, 0xac, 0x16, 0xc3, 0x7d, 0x37, 0xf2, 0xfb, 0xa2, 0xdc, - 0x88, 0x2f, 0x10, 0x0a, 0x6f, 0x37, 0xf2, 0x77, 0xb3, 0x57, 0xf8, 0x4e, 0xa2, 0xaa, 0x6c, 0x7d, - 0x23, 0x84, 0xb7, 0x15, 0x7c, 0x17, 0xc1, 0xf6, 0xff, 0x58, 0x78, 0x02, 0xca, 0x55, 0x09, 0x6b, - 0x17, 0x85, 0x35, 0xac, 0xb7, 0x1a, 0x26, 0x6b, 0x94, 0x4c, 0x46, 0x60, 0x36, 0xc8, 0xe9, 0x50, - 0x1e, 0x2c, 0xec, 0x37, 0xf9, 0x04, 0x3a, 0x6a, 0xc5, 0xfd, 0x04, 0xd5, 0x22, 0xb6, 0xc9, 0x66, - 0x71, 0x71, 0x34, 0xb4, 0xe6, 0xb4, 0xbd, 0x92, 0x1a, 0xe5, 0xf6, 0xba, 0x32, 0x55, 0xa0, 0xf6, - 0x50, 0xdb, 0x22, 0x3e, 0xf1, 0x2f, 0x2e, 0x35, 0xf5, 0x46, 0x39, 0xf5, 0x45, 0xaa, 0xac, 0xbe, - 0xed, 0x7f, 0xb7, 0xa0, 0xbd, 0xeb, 0xfb, 0xb8, 0xee, 0x69, 0xc2, 0x84, 0x5c, 0x65, 0xe3, 0x82, - 0x55, 0xce, 0xfc, 0x8a, 0xab, 0xfc, 0xd6, 0x41, 0x64, 0x8c, 0x12, 0x6c, 0x1b, 0x3a, 0xc5, 0x3a, - 0xeb, 0xcd, 0x6b, 0x7f, 0x0f, 0x08, 0xbf, 0x5e, 0x19, 0xea, 0x28, 0x63, 0xad, 0xc3, 0xaa, 0x81, - 0x25, 0x62, 0xcd, 0x67, 0x70, 0x6f, 0x9f, 0xe6, 0x7b, 0xe9, 0x79, 0x92, 0xc7, 0x32, 0x9d, 0xfd, - 0x94, 0x26, 0x71, 0x16, 0xc8, 0xc8, 0x45, 0xa7, 0x8a, 0x3e, 0xff, 0x64, 0xc1, 0xfd, 0x29, 0x08, - 0x89, 0x25, 0xfc, 0xbc, 0x5a, 0x5f, 0xfa, 0x2d, 0xbd, 0x75, 0x64, 0x2a, 0x2a, 0x3b, 0x0a, 0x22, - 0x5e, 0xf0, 0x15, 0xc9, 0xde, 0x0f, 0x60, 0xd9, 0x1c, 0xbc, 0x54, 0xa8, 0x08, 0xe1, 0xee, 0x05, - 0x42, 0x4c, 0xe3, 0x73, 0x77, 0x61, 0xd9, 0x33, 0x48, 0x08, 0x46, 0x25, 0xa8, 0xbd, 0x07, 0x6f, - 0x5d, 0xc8, 0x4d, 0xa8, 0x6d, 0xec, 0x0d, 0xdd, 0xfe, 0xdb, 0x59, 0xd8, 0x7c, 0x19, 0xe4, 0x27, - 0x7e, 0xea, 0x9e, 0x49, 0xef, 0x9b, 0x46, 0xc8, 0xd2, 0xe5, 0xbd, 0x51, 0xad, 0x37, 0x3c, 0x80, - 0x95, 0x38, 0xa2, 0x78, 0xc7, 0xe8, 0x27, 0x6e, 0x96, 0x9d, 0xc5, 0xa9, 0x3c, 0x4b, 0xdb, 0x71, - 0x44, 0xd9, 0x3d, 0xe3, 0x40, 0x80, 0x4b, 0xa7, 0xf1, 0x6c, 0xf9, 0x34, 0xee, 0xc0, 0x4c, 0x12, - 0x44, 0xe2, 0xcd, 0x84, 0xfd, 0x64, 0x67, 0x67, 0x9e, 0xba, 0xbe, 0x46, 0x59, 0x9c, 0x9d, 0x08, - 0x55, 0x74, 0xf5, 0x2a, 0xfe, 0x7c, 0xa9, 0x8a, 0xaf, 0xe9, 0x64, 0xc1, 0xac, 0x5a, 0xdc, 0x80, - 0xa6, 0xf8, 0xd9, 0xcf, 0xdd, 0x81, 0xb8, 0x02, 0x81, 0x00, 0x3d, 0x77, 0x07, 0x5a, 0xb6, 0x06, - 0x46, 0xb6, 0xb6, 0x0d, 0x70, 0x4c, 0x69, 0xdf, 0xb8, 0x0c, 0x2d, 0x1e, 0x53, 0xca, 0x83, 0x2e, - 0x4b, 0x95, 0x8f, 0xdc, 0xe8, 0x55, 0x1f, 0x6b, 0x10, 0x2d, 0x2e, 0x0e, 0x03, 0x7c, 0xe1, 0x0e, - 0x31, 0x27, 0xc6, 0x41, 0x29, 0xd3, 0x12, 0xd7, 0x28, 0x83, 0xed, 0x16, 0xd5, 0x14, 0x44, 0xf1, - 0x82, 0xfc, 0xbc, 0xbb, 0x5c, 0xcc, 0xdf, 0x0b, 0xf2, 0x73, 0x35, 0x1f, 0x75, 0x96, 0x9e, 0x77, - 0xdb, 0xc5, 0xfc, 0x3d, 0x0e, 0x62, 0xe2, 0x65, 0x67, 0xc1, 0x31, 0xe5, 0x4d, 0x17, 0x1d, 0xd1, - 0x86, 0xc4, 0x20, 0x7b, 0xb1, 0x8f, 0x69, 0xe4, 0x59, 0x90, 0x6a, 0x97, 0xd3, 0x15, 0x7e, 0x85, - 0x65, 0x40, 0xe9, 0x1a, 0xf6, 0x03, 0xe8, 0x48, 0x77, 0xd1, 0xfb, 0x12, 0x53, 0x9a, 0x8d, 0xc2, - 0x5c, 0xf6, 0x25, 0xf2, 0x2f, 0xfb, 0x7d, 0xec, 0x68, 0xf8, 0x3c, 0x1e, 0x0c, 0x8a, 0xeb, 0x93, - 0x70, 0xad, 0x0d, 0x98, 0x0b, 0x11, 0x2e, 0xa7, 0xf0, 0x2f, 0x3b, 0xc2, 0x7a, 0x4e, 0x69, 0x4a, - 0xf1, 0x6a, 0x11, 0x44, 0xc7, 0xb1, 0xb8, 0x2d, 0xe0, 0x6f, 0xb6, 0x17, 0x7d, 0x7a, 0x34, 0x1a, - 0xc8, 0xfe, 0x22, 0xfc, 0x60, 0x98, 0x67, 0x6e, 0x1a, 0x89, 0x03, 0x15, 0x7f, 0x33, 0x4c, 0x9a, - 0xa6, 0x71, 0x2a, 0x4e, 0x4f, 0xfe, 0x61, 0xef, 0xc3, 0xe6, 0xe1, 0xe5, 0x44, 0x64, 0x84, 0x78, - 0xb5, 0x46, 0x6c, 0x7f, 0xfc, 0x78, 0xf4, 0xd7, 0xb7, 0x61, 0x79, 0x3f, 0xe6, 0x9b, 0xf1, 0x39, - 0xf3, 0xc1, 0x94, 0x3c, 0x83, 0x79, 0xd1, 0x58, 0x48, 0x36, 0x2a, 0x9d, 0x86, 0xc8, 0xa3, 0xb7, - 0x39, 0xa6, 0x03, 0xd1, 0x5e, 0xfd, 0xc5, 0xbf, 0xfc, 0xdb, 0x2f, 0x1b, 0x4b, 0xa4, 0xf9, 0xf0, - 0xf4, 0xfd, 0x87, 0x03, 0x9a, 0xe3, 0x62, 0x4f, 0x60, 0xc9, 0xe8, 0x05, 0x23, 0x5b, 0x46, 0x3f, - 0x57, 0xa9, 0x45, 0xac, 0xb7, 0x3d, 0xb1, 0xdb, 0xcb, 0xee, 0x21, 0x8b, 0x35, 0x42, 0x04, 0x8b, - 0x0c, 0x51, 0x38, 0xe1, 0xaf, 0xa0, 0xfd, 0x04, 0xab, 0x60, 0x8a, 0x2a, 0xb9, 0x51, 0x50, 0xab, - 0xed, 0x71, 0xeb, 0xdd, 0x1c, 0x8f, 0x20, 0x38, 0x5e, 0x43, 0x8e, 0xeb, 0x64, 0x95, 0x71, 0xe4, - 0x55, 0x36, 0xd5, 0x5b, 0x46, 0x32, 0xe8, 0x88, 0xae, 0x99, 0x37, 0xca, 0x73, 0x0b, 0x79, 0x6e, - 0x90, 0x35, 0xc6, 0xd3, 0xe7, 0x0c, 0x0a, 0xa6, 0x31, 0x5e, 0xe2, 0xf5, 0x2e, 0x2f, 0x72, 0x7d, - 0x6c, 0xfb, 0x17, 0x67, 0x79, 0xe3, 0x82, 0xf6, 0x30, 0x73, 0x95, 0x03, 0xca, 0x70, 0x55, 0x87, - 0x18, 0xf9, 0xa5, 0x85, 0x0e, 0x5e, 0xdb, 0x8f, 0x48, 0xde, 0xba, 0xb8, 0x09, 0x92, 0xcb, 0x70, - 0x6f, 0xda, 0x6e, 0x49, 0xfb, 0x7b, 0x28, 0xcc, 0x75, 0xb2, 0x25, 0x84, 0x31, 0x3a, 0x24, 0x65, - 0x0f, 0x26, 0xf1, 0xa0, 0xa5, 0xb7, 0x76, 0x91, 0x6b, 0x35, 0x7d, 0x53, 0x8a, 0xf9, 0x56, 0xfd, - 0xa0, 0x60, 0xd8, 0x45, 0x86, 0x84, 0x74, 0x04, 0x43, 0xd5, 0x09, 0x46, 0xbe, 0x86, 0x76, 0xa9, - 0x2d, 0x8a, 0xd8, 0x25, 0xf3, 0xd5, 0xb4, 0xb8, 0xf5, 0x6e, 0x4f, 0xc4, 0x11, 0x5c, 0xaf, 0x23, - 0xd7, 0xae, 0xbd, 0xaa, 0x59, 0x59, 0x72, 0xfe, 0xc8, 0x7a, 0x40, 0x32, 0xb4, 0xb3, 0xde, 0x5b, - 0x35, 0x15, 0xef, 0x1b, 0x35, 0x4b, 0x35, 0xb6, 0x69, 0xd9, 0xd6, 0x92, 0x27, 0x6e, 0xd7, 0x0c, - 0x3b, 0x37, 0xb4, 0xbe, 0x33, 0x8c, 0xb2, 0xd3, 0xf0, 0xdd, 0xae, 0xef, 0x5b, 0x13, 0xad, 0x73, - 0x95, 0x9d, 0x2b, 0xb9, 0xc6, 0x79, 0x42, 0x32, 0xa3, 0xad, 0x4f, 0x30, 0x35, 0xbd, 0xba, 0xa6, - 0xb1, 0xae, 0x76, 0xa5, 0x7a, 0xa7, 0xdc, 0xd8, 0x95, 0xc6, 0x79, 0x92, 0x91, 0xd7, 0xb0, 0xcc, - 0xc3, 0xc5, 0x9b, 0xb7, 0xec, 0x36, 0xf2, 0xdd, 0xb4, 0x49, 0x11, 0x33, 0x74, 0xc3, 0xbe, 0x84, - 0x45, 0xd5, 0x1d, 0x43, 0xba, 0xda, 0x22, 0x8c, 0x3e, 0xac, 0xde, 0x98, 0x2e, 0x1b, 0xe9, 0xad, - 0xf6, 0x92, 0x58, 0x15, 0xef, 0x99, 0x61, 0x84, 0x7f, 0x06, 0x50, 0xb4, 0xdd, 0x90, 0xab, 0x15, - 0xca, 0x4a, 0x73, 0xbd, 0xba, 0x21, 0xd9, 0xbc, 0x8b, 0xe4, 0x3b, 0x64, 0xd9, 0x20, 0x2f, 0xf7, - 0x9b, 0xaa, 0x04, 0x19, 0xfb, 0xad, 0xdc, 0xa8, 0xd3, 0x1b, 0xdf, 0xa1, 0x21, 0x8d, 0x62, 0xcb, - 0xcd, 0xa6, 0x6e, 0x79, 0x6c, 0x05, 0x03, 0x3c, 0x2d, 0xb4, 0xd6, 0x90, 0xad, 0x3a, 0x2e, 0xb5, - 0xa7, 0x45, 0xb5, 0xcf, 0xc3, 0xbe, 0x8a, 0xac, 0x56, 0xc9, 0x4a, 0x99, 0x55, 0x46, 0x5e, 0xe1, - 0x1f, 0x2f, 0x68, 0x9d, 0x0d, 0x44, 0xa7, 0x55, 0x6d, 0xf3, 0xe8, 0x5d, 0x1f, 0x37, 0x3c, 0xe6, - 0x64, 0x12, 0x89, 0x20, 0x6e, 0x2a, 0x6e, 0x70, 0xde, 0xcf, 0x60, 0x18, 0xdc, 0x68, 0x7b, 0xe8, - 0x5d, 0xad, 0x19, 0x11, 0xd4, 0xd7, 0x91, 0x7a, 0x9b, 0x2c, 0xa9, 0x90, 0x88, 0xb4, 0xb8, 0x4d, - 0xd4, 0x43, 0x93, 0x61, 0x93, 0x72, 0x37, 0x82, 0x11, 0x03, 0x2b, 0x3d, 0x09, 0x95, 0x18, 0xa8, - 0xba, 0x0e, 0xc8, 0x1f, 0x9a, 0xcd, 0x0d, 0xf2, 0xb1, 0xd5, 0x9e, 0xf8, 0x3a, 0x5a, 0xd9, 0x2d, - 0x63, 0x5f, 0x50, 0xed, 0x1b, 0xc8, 0xf9, 0x2a, 0xd9, 0x2c, 0x73, 0x16, 0xaf, 0xb1, 0xe4, 0x17, - 0x16, 0xac, 0xd6, 0xbc, 0xf5, 0x15, 0x12, 0x8c, 0x7f, 0x99, 0x2c, 0x24, 0x98, 0xf4, 0x58, 0x68, - 0xa3, 0x04, 0x5b, 0x36, 0x4a, 0xe0, 0xfa, 0xbe, 0x92, 0x40, 0xe4, 0xb5, 0xcc, 0x33, 0xff, 0xd4, - 0x82, 0x8d, 0xfa, 0x77, 0x3d, 0x72, 0x47, 0xb5, 0x43, 0x4f, 0x7a, 0x71, 0xec, 0xdd, 0xbd, 0x08, - 0x4d, 0x48, 0x73, 0x07, 0xa5, 0xb9, 0x61, 0xf7, 0x98, 0x34, 0x29, 0xe2, 0xd6, 0x09, 0x74, 0x86, - 0xc5, 0x10, 0xf3, 0xe5, 0x8c, 0x68, 0xb9, 0x45, 0xfd, 0x03, 0x63, 0xef, 0xd6, 0x04, 0x0c, 0x33, - 0x7c, 0x91, 0x75, 0x61, 0x10, 0x7c, 0x6e, 0x52, 0x4f, 0x70, 0x62, 0x8f, 0x16, 0x2f, 0x53, 0xc6, - 0x1e, 0xad, 0x3c, 0xb6, 0x19, 0x7b, 0xb4, 0xfa, 0xfe, 0x55, 0xd9, 0xa3, 0xc8, 0x0c, 0xdf, 0xc2, - 0xc8, 0x97, 0xb8, 0x6d, 0x44, 0x25, 0xae, 0x5b, 0xde, 0xea, 0x59, 0xdd, 0xb6, 0x31, 0x6b, 0x6d, - 0x95, 0x50, 0xc9, 0x0b, 0x7c, 0x4c, 0x7b, 0x0e, 0x2c, 0x48, 0x74, 0xb2, 0x59, 0x26, 0x20, 0x29, - 0xd7, 0x3e, 0xa6, 0xd8, 0x9b, 0x48, 0x74, 0xc5, 0x6e, 0xe9, 0x44, 0x19, 0xcd, 0x23, 0x68, 0x6a, - 0x0f, 0x07, 0x44, 0x05, 0xd9, 0xea, 0x3b, 0x49, 0xef, 0x5a, 0xed, 0x98, 0x19, 0x4a, 0xec, 0x36, - 0x63, 0x90, 0x21, 0x82, 0xe2, 0xf1, 0xfb, 0xb0, 0x64, 0xd4, 0xee, 0x0b, 0xe5, 0xd7, 0xbd, 0x2e, - 0x14, 0xca, 0xaf, 0x2d, 0xf8, 0xcb, 0x44, 0xd3, 0x46, 0xe5, 0x67, 0x02, 0x45, 0xf1, 0xfa, 0x39, - 0x2c, 0xaa, 0x92, 0x79, 0xa1, 0xff, 0x72, 0x15, 0xfd, 0x22, 0x1e, 0x86, 0x0d, 0xce, 0xd8, 0xe4, - 0xa3, 0x78, 0x78, 0x24, 0xf4, 0xa5, 0x15, 0x84, 0x0b, 0x7d, 0x55, 0xab, 0xe2, 0x85, 0xbe, 0xea, - 0x2a, 0xc8, 0x86, 0xbe, 0x3c, 0x44, 0x50, 0x6b, 0x48, 0xa1, 0x5d, 0x2a, 0xc4, 0x16, 0x69, 0x45, - 0x7d, 0xd9, 0xb9, 0x48, 0x2b, 0xc6, 0x54, 0x70, 0xcd, 0xc4, 0x8d, 0xf3, 0x73, 0xc3, 0xb0, 0xf0, - 0x2d, 0x1e, 0xee, 0x79, 0x99, 0xd2, 0xf0, 0x5b, 0xa3, 0x1e, 0x6b, 0xf8, 0xad, 0x59, 0xd3, 0xac, - 0x84, 0x7b, 0xca, 0x69, 0xbd, 0x80, 0x05, 0x59, 0x1f, 0x2b, 0x9c, 0xb6, 0x54, 0x19, 0xec, 0x75, - 0xab, 0x03, 0x82, 0xaa, 0xe1, 0xb8, 0xae, 0xef, 0x23, 0x55, 0x61, 0x08, 0xad, 0x5a, 0x56, 0x18, - 0xa2, 0x5a, 0x68, 0x2b, 0x0c, 0x51, 0x57, 0x5e, 0x33, 0x0c, 0xc1, 0x23, 0x97, 0xe2, 0xf1, 0x77, - 0x16, 0xdc, 0xba, 0xb0, 0xd8, 0x45, 0xde, 0xbb, 0x44, 0x5d, 0x8c, 0x0b, 0xf4, 0xfe, 0xa5, 0x2b, - 0x69, 0xf6, 0x3d, 0x14, 0xd3, 0xb6, 0xb7, 0xe5, 0x61, 0x8a, 0xd3, 0x7c, 0x8e, 0xae, 0xca, 0x6a, - 0x4c, 0xe8, 0xbf, 0xb1, 0xf8, 0x9f, 0xa6, 0x4d, 0xa0, 0x4b, 0x76, 0xa6, 0x14, 0x40, 0x0a, 0xfc, - 0x70, 0x6a, 0x7c, 0x21, 0xee, 0x5d, 0x14, 0xf7, 0xa6, 0x7d, 0x6d, 0x82, 0xb8, 0x4c, 0xd8, 0x3f, - 0x80, 0x6b, 0xaa, 0x28, 0x66, 0xd0, 0xfd, 0x6c, 0x14, 0xf9, 0x59, 0x71, 0x2f, 0x1d, 0x53, 0x39, - 0x2b, 0x1c, 0xa7, 0x5c, 0x2b, 0x31, 0xcf, 0xc7, 0x33, 0x31, 0xca, 0xc5, 0x38, 0x66, 0xb4, 0x19, - 0xf7, 0x04, 0x56, 0xe4, 0xbc, 0xcf, 0x02, 0x37, 0xff, 0xd6, 0x3c, 0x6f, 0x22, 0xcf, 0x9e, 0xbd, - 0xae, 0xf3, 0x3c, 0x0e, 0xdc, 0x5c, 0x71, 0xcc, 0xf0, 0x8d, 0xc3, 0x28, 0x83, 0xe8, 0x97, 0xef, - 0xda, 0x02, 0x89, 0x7e, 0xf9, 0xae, 0xaf, 0xd8, 0x98, 0x97, 0xef, 0x01, 0xcd, 0x79, 0x05, 0xc5, - 0x17, 0x0c, 0x4e, 0xa1, 0x73, 0x38, 0x96, 0xe9, 0xe1, 0xaf, 0xcc, 0x54, 0xe4, 0x40, 0x36, 0x32, - 0xcd, 0x4a, 0x4c, 0x3f, 0xb2, 0x1e, 0x1c, 0xcd, 0xe1, 0xdf, 0xdc, 0x7e, 0xf0, 0x7f, 0x01, 0x00, - 0x00, 0xff, 0xff, 0x0a, 0xe0, 0x50, 0x4a, 0xa6, 0x3b, 0x00, 0x00, + 0x72, 0xe8, 0xe1, 0x88, 0xe4, 0xd4, 0x0c, 0xc9, 0xe1, 0xe3, 0xd7, 0x68, 0x44, 0xea, 0xa3, 0xbd, + 0x92, 0x25, 0xad, 0x97, 0xb2, 0x65, 0x21, 0xeb, 0xd8, 0x9b, 0x4d, 0x68, 0x5a, 0xe6, 0x2a, 0xeb, + 0xb5, 0x98, 0xa6, 0x56, 0x02, 0xbc, 0x81, 0x3b, 0xcd, 0xe9, 0xc7, 0x61, 0x47, 0x3d, 0xdd, 0xed, + 0xee, 0x1e, 0x52, 0x34, 0x02, 0x04, 0x30, 0x90, 0x20, 0xa7, 0xe4, 0xb0, 0x08, 0x90, 0x43, 0x4e, + 0x39, 0x05, 0x01, 0x72, 0x09, 0x72, 0xca, 0x61, 0x91, 0x6b, 0x90, 0x63, 0x2e, 0xf9, 0x01, 0x41, + 0x6e, 0x49, 0x80, 0x00, 0xb9, 0xe4, 0x14, 0xbc, 0x7a, 0x1f, 0xfd, 0x5e, 0x77, 0xcf, 0x70, 0xb4, + 0xab, 0xf8, 0x22, 0x4d, 0xd7, 0xab, 0x57, 0x55, 0xaf, 0xaa, 0x5e, 0xbd, 0x7a, 0xf5, 0x8a, 0xd0, + 0x4a, 0x93, 0xc1, 0x6e, 0x92, 0xc6, 0x79, 0x4c, 0xe6, 0x87, 0x83, 0x3c, 0x4d, 0x06, 0xfd, 0xed, + 0x61, 0x1c, 0x0f, 0x43, 0xfa, 0xc0, 0x4b, 0x82, 0x07, 0x5e, 0x14, 0xc5, 0xb9, 0x97, 0x07, 0x71, + 0x94, 0x71, 0x2c, 0xbb, 0x0b, 0xcb, 0x07, 0x34, 0x7f, 0x12, 0x9d, 0xc4, 0x0e, 0xfd, 0x6a, 0x4c, + 0xb3, 0xdc, 0xfe, 0xfb, 0x26, 0xac, 0x28, 0x50, 0x96, 0xc4, 0x51, 0x46, 0xc9, 0x26, 0xcc, 0x8f, + 0x93, 0x3c, 0x18, 0xd1, 0x9e, 0x75, 0xd3, 0xba, 0xdb, 0x72, 0xc4, 0x17, 0x79, 0x00, 0x6b, 0xde, + 0x99, 0x17, 0x84, 0xde, 0x71, 0x48, 0x5d, 0xfa, 0x6a, 0x70, 0xea, 0x45, 0x43, 0x9a, 0xf5, 0x1a, + 0x37, 0xad, 0xbb, 0x73, 0x0e, 0x51, 0x43, 0x8f, 0xe5, 0x08, 0xf9, 0x2e, 0xac, 0xd2, 0x88, 0x81, + 0x7c, 0x0d, 0x7d, 0x0e, 0xd1, 0xbb, 0x62, 0xa0, 0x40, 0x7e, 0x04, 0x9b, 0x3e, 0x3d, 0xf1, 0xc6, + 0x61, 0xee, 0x9e, 0xc4, 0x29, 0x7d, 0xe5, 0x26, 0x69, 0x7c, 0x16, 0xf8, 0x34, 0xed, 0x35, 0x51, + 0x8a, 0x75, 0x31, 0xfa, 0x29, 0x1b, 0x3c, 0x14, 0x63, 0xe4, 0x21, 0x6c, 0xa8, 0x59, 0x81, 0x97, + 0xbb, 0x83, 0x71, 0x9a, 0xd2, 0x68, 0x70, 0xd1, 0xbb, 0x82, 0x93, 0xd6, 0xe4, 0xa4, 0xc0, 0xcb, + 0xf7, 0xc5, 0x10, 0x79, 0x01, 0xdd, 0x6c, 0x7c, 0x9c, 0x5d, 0x64, 0x39, 0x1d, 0xb9, 0x59, 0xee, + 0xe5, 0xe3, 0xac, 0x37, 0x7f, 0x73, 0xee, 0x6e, 0xfb, 0xe1, 0x3b, 0xbb, 0x5c, 0x8d, 0xbb, 0x25, + 0x95, 0xec, 0x1e, 0x49, 0xfc, 0x23, 0x44, 0x7f, 0x1c, 0xe5, 0xe9, 0x85, 0xb3, 0x92, 0x99, 0x50, + 0xf2, 0x39, 0x2c, 0xa5, 0xc9, 0xc0, 0xa5, 0x91, 0x9f, 0xc4, 0x41, 0x94, 0x67, 0xbd, 0x05, 0xa4, + 0x7a, 0x6f, 0x12, 0x55, 0x27, 0x19, 0x3c, 0x96, 0xb8, 0x9c, 0x64, 0x27, 0xd5, 0x40, 0xfd, 0x8f, + 0x61, 0xbd, 0x8e, 0x31, 0xe9, 0xc2, 0xdc, 0x4b, 0x7a, 0x21, 0xac, 0xc3, 0x7e, 0x92, 0x75, 0xb8, + 0x72, 0xe6, 0x85, 0x63, 0x8a, 0xc6, 0x58, 0x74, 0xf8, 0xc7, 0x87, 0x8d, 0x0f, 0xac, 0xfe, 0x33, + 0x58, 0xad, 0xb0, 0xa9, 0x21, 0x70, 0x4f, 0x27, 0xd0, 0x7e, 0xb8, 0x26, 0x45, 0x76, 0x0e, 0xf7, + 0xe5, 0x5c, 0x8d, 0xaa, 0x7d, 0x0b, 0x6e, 0x1c, 0xd0, 0x7c, 0x3f, 0x1e, 0x8d, 0xc6, 0x51, 0x30, + 0x40, 0x1f, 0x73, 0x68, 0xe8, 0x5d, 0xd0, 0x34, 0x93, 0x9e, 0xf5, 0x39, 0xac, 0xd7, 0x8d, 0x93, + 0x1e, 0x2c, 0x08, 0xdb, 0x23, 0xff, 0x45, 0x47, 0x7e, 0x92, 0x6d, 0x68, 0x0d, 0xe2, 0x28, 0xa2, + 0x83, 0x9c, 0xfa, 0x62, 0x21, 0x05, 0xc0, 0xfe, 0xe3, 0x06, 0xdc, 0x9c, 0xcc, 0x53, 0xb8, 0xee, + 0xd7, 0xb0, 0x39, 0xd0, 0x11, 0xdc, 0x54, 0x60, 0xf4, 0x2c, 0x34, 0xc5, 0xbe, 0x66, 0x8a, 0xa9, + 0x94, 0x76, 0x6b, 0x47, 0xb9, 0x91, 0x36, 0x06, 0x75, 0x63, 0xfd, 0x13, 0xe8, 0x4f, 0x9e, 0x54, + 0xa3, 0xf2, 0x87, 0xa6, 0xca, 0xb7, 0xa5, 0x68, 0x75, 0x44, 0x74, 0xdd, 0x7f, 0x1f, 0xb6, 0x0e, + 0x68, 0x44, 0xd3, 0x60, 0xa0, 0x9c, 0x43, 0xe8, 0x9c, 0x69, 0x50, 0xf9, 0xa4, 0x60, 0x55, 0x00, + 0xec, 0x3e, 0xf4, 0xaa, 0x13, 0xf9, 0x72, 0xed, 0x4d, 0x58, 0x3f, 0xa0, 0xb9, 0x82, 0x2b, 0x2b, + 0xfe, 0xc2, 0x82, 0x0d, 0x1c, 0xc8, 0x8e, 0xb3, 0x0b, 0x3e, 0x20, 0x54, 0xfd, 0x7b, 0xb0, 0xaa, + 0x48, 0x67, 0x72, 0x1b, 0x71, 0x2d, 0xbf, 0xaf, 0x69, 0xb9, 0x3a, 0xb3, 0xd8, 0x4c, 0x99, 0xbe, + 0x9b, 0x8a, 0x3d, 0x29, 0xc0, 0xfd, 0x7d, 0xd8, 0xa8, 0x45, 0x7d, 0x1d, 0xff, 0xb7, 0x7b, 0xb0, + 0x79, 0x40, 0x73, 0xcd, 0x8d, 0x35, 0x07, 0x6d, 0x6b, 0x60, 0xe6, 0x97, 0x59, 0xee, 0xa5, 0x79, + 0xe1, 0x97, 0xe2, 0x93, 0xdc, 0x86, 0xe5, 0x30, 0xc8, 0x72, 0x1a, 0xb9, 0x9e, 0xef, 0xa7, 0x34, + 0xe3, 0x21, 0xaf, 0xe5, 0x2c, 0x71, 0xe8, 0x1e, 0x07, 0xda, 0xff, 0x60, 0x31, 0xc3, 0x94, 0x58, + 0x09, 0x65, 0x7d, 0x06, 0xad, 0x22, 0x2a, 0x70, 0x25, 0xed, 0x6a, 0x4a, 0xaa, 0x9b, 0xb3, 0x5b, + 0x0a, 0x0d, 0x05, 0x81, 0xfe, 0xef, 0xc0, 0xf2, 0x9b, 0xde, 0xd0, 0x1f, 0x40, 0x5f, 0xf8, 0x86, + 0x8c, 0xc8, 0x9f, 0x7b, 0x23, 0x2a, 0xfd, 0xaa, 0x0f, 0x8b, 0x32, 0x80, 0x0b, 0x1e, 0xea, 0xdb, + 0xde, 0x81, 0x6b, 0xb5, 0x33, 0x85, 0x63, 0x3d, 0x80, 0xb5, 0x03, 0x9a, 0xab, 0x30, 0x2f, 0x29, + 0x4e, 0x8c, 0x02, 0xf6, 0x23, 0xf4, 0x44, 0x6d, 0x82, 0x50, 0xe1, 0x36, 0xb4, 0x8a, 0x43, 0x44, + 0xf8, 0xb6, 0x02, 0xd8, 0x0f, 0xd1, 0x4d, 0xe5, 0xac, 0xa7, 0xcf, 0x0e, 0x1d, 0xca, 0xa7, 0x5d, + 0x85, 0xc5, 0x38, 0x4f, 0xdc, 0x41, 0xec, 0x4b, 0xd1, 0x17, 0xe2, 0x3c, 0xd9, 0x8f, 0x7d, 0x2a, + 0x5c, 0x43, 0x9b, 0xa3, 0x5c, 0xe3, 0xaf, 0xb8, 0x29, 0xcd, 0x21, 0x21, 0xc7, 0x6f, 0x43, 0x4b, + 0x12, 0x94, 0xa6, 0xfc, 0x9e, 0x66, 0xca, 0xba, 0x39, 0xbb, 0x4f, 0x39, 0x47, 0x61, 0xc9, 0x45, + 0x21, 0x40, 0xd6, 0xff, 0x08, 0x96, 0x8c, 0xa1, 0xcb, 0x3c, 0xbb, 0xa5, 0x9b, 0xec, 0x11, 0x6c, + 0x7e, 0x12, 0x64, 0xfa, 0x89, 0x3b, 0x8b, 0xb9, 0xbe, 0x84, 0xe5, 0x43, 0x2f, 0x48, 0xb3, 0xa3, + 0x71, 0x92, 0xc4, 0xe8, 0xde, 0x6f, 0xc3, 0x4a, 0x71, 0xac, 0x27, 0x6c, 0x4c, 0x4c, 0x5a, 0x56, + 0x60, 0x9c, 0x41, 0xde, 0x82, 0x25, 0x79, 0x9c, 0x73, 0x34, 0x2e, 0x52, 0x47, 0x00, 0x11, 0xc9, + 0xfe, 0xa6, 0x69, 0xa8, 0xce, 0x48, 0x2c, 0x08, 0x34, 0x23, 0x4f, 0xa5, 0x15, 0xf8, 0x5b, 0x77, + 0x84, 0x86, 0x79, 0x1c, 0xf4, 0x60, 0xe1, 0x8c, 0xa6, 0xc7, 0x71, 0x46, 0x31, 0x67, 0x58, 0x74, + 0xe4, 0x27, 0x13, 0x64, 0x9c, 0x05, 0xd1, 0xd0, 0xcd, 0xbc, 0xc8, 0x3f, 0x8e, 0x5f, 0x61, 0x86, + 0xb0, 0xe8, 0x74, 0x10, 0x78, 0xc4, 0x61, 0xe4, 0x16, 0x74, 0x4e, 0xf3, 0x3c, 0x71, 0x59, 0xea, + 0x12, 0x8f, 0x73, 0x91, 0x10, 0xb4, 0x19, 0xec, 0x19, 0x07, 0xb1, 0x8d, 0x8d, 0x28, 0xe3, 0x8c, + 0xa6, 0xde, 0x90, 0x46, 0x79, 0x6f, 0x9e, 0x6f, 0x6c, 0x06, 0xfd, 0xa9, 0x04, 0x92, 0x1d, 0x00, + 0x44, 0x4b, 0xd2, 0xf8, 0xd5, 0x45, 0x6f, 0x81, 0xbb, 0x1e, 0x83, 0x1c, 0x32, 0x00, 0xd3, 0xdf, + 0xb1, 0x97, 0x51, 0x99, 0x7a, 0x04, 0x34, 0xeb, 0x2d, 0x72, 0xfd, 0x31, 0xf0, 0xbe, 0x82, 0x12, + 0x97, 0xe5, 0x1d, 0x42, 0xeb, 0xae, 0x97, 0x65, 0x34, 0xcf, 0x7a, 0x2d, 0x74, 0xa0, 0x47, 0x35, + 0x0e, 0x54, 0xca, 0x3f, 0xc4, 0xbc, 0x3d, 0x9c, 0xa6, 0xf2, 0x0f, 0x03, 0xca, 0xf2, 0x2d, 0x6f, + 0x9c, 0x9f, 0xd2, 0x28, 0x67, 0xa7, 0x07, 0x63, 0x92, 0x04, 0x3d, 0x40, 0xdd, 0x74, 0x8d, 0x81, + 0xbd, 0x24, 0xe8, 0x7f, 0xc1, 0x92, 0x8b, 0x2a, 0xd5, 0x1a, 0x17, 0x7c, 0xc7, 0x0c, 0x25, 0x9b, + 0x52, 0x58, 0xd3, 0x8f, 0x74, 0xd7, 0x3c, 0x87, 0xee, 0x01, 0xcd, 0x9f, 0x05, 0x83, 0x97, 0x34, + 0x9d, 0xc1, 0x29, 0xc9, 0x5d, 0x68, 0x32, 0x8f, 0x12, 0x0c, 0xd6, 0xd5, 0x49, 0x28, 0x32, 0x36, + 0xc6, 0xc8, 0x41, 0x0c, 0x66, 0x0b, 0xd4, 0x9c, 0x9b, 0x5f, 0x24, 0xdc, 0x2f, 0x5a, 0x4e, 0x0b, + 0x21, 0xcf, 0x2e, 0x12, 0x6a, 0x3f, 0x87, 0x8e, 0x3e, 0x89, 0x05, 0x0d, 0x9f, 0x86, 0xc1, 0x28, + 0xc8, 0x69, 0x2a, 0x83, 0x86, 0x02, 0x30, 0x7f, 0x64, 0x26, 0x12, 0x7e, 0x8c, 0xbf, 0xd9, 0x7e, + 0xfb, 0x6a, 0x1c, 0xe7, 0x92, 0x36, 0xff, 0xb0, 0xff, 0xbc, 0x01, 0xcb, 0x72, 0x39, 0xc2, 0x99, + 0xa5, 0xcc, 0xd6, 0xa5, 0x32, 0xdf, 0x82, 0x4e, 0xe8, 0x65, 0xb9, 0x3b, 0x4e, 0x7c, 0x4f, 0xa6, + 0x36, 0x73, 0x4e, 0x9b, 0xc1, 0x7e, 0xca, 0x41, 0xcc, 0xa3, 0x65, 0xe6, 0x8a, 0x7b, 0x4b, 0x70, + 0xef, 0x0c, 0xf4, 0xc5, 0x10, 0x68, 0xb2, 0x39, 0xe8, 0xed, 0x96, 0x83, 0xbf, 0x19, 0xec, 0x34, + 0x18, 0x9e, 0xa2, 0x77, 0x5b, 0x0e, 0xfe, 0x66, 0x16, 0x0c, 0xe3, 0x73, 0xf4, 0x65, 0xcb, 0x61, + 0x3f, 0x19, 0xe4, 0x38, 0xf0, 0xd1, 0x75, 0x2d, 0x87, 0xfd, 0x64, 0x10, 0x2f, 0x7b, 0x89, 0x8e, + 0x6a, 0x39, 0xec, 0x27, 0xcb, 0xfa, 0xcf, 0xe2, 0x70, 0x3c, 0xa2, 0xbd, 0x16, 0x02, 0xc5, 0x17, + 0xb9, 0x06, 0xad, 0x24, 0x0d, 0x06, 0xd4, 0xf5, 0xf2, 0x53, 0x74, 0x26, 0xcb, 0x59, 0x44, 0xc0, + 0x5e, 0x7e, 0x6a, 0xaf, 0xc1, 0xaa, 0x32, 0xb4, 0x8a, 0x9e, 0x2f, 0x60, 0x41, 0x40, 0xa6, 0x1a, + 0xfd, 0x5d, 0x58, 0xc8, 0x39, 0x5a, 0xaf, 0x81, 0xbb, 0x40, 0x39, 0x96, 0xa9, 0x69, 0x47, 0xa2, + 0xd9, 0xbf, 0x09, 0x44, 0xe7, 0x26, 0x0c, 0x71, 0xaf, 0xa0, 0xc3, 0xc3, 0xf1, 0x8a, 0x49, 0x27, + 0x2b, 0x08, 0x7c, 0x8d, 0x87, 0xd1, 0xd3, 0xd4, 0x67, 0x81, 0x24, 0x7e, 0xf9, 0xad, 0xba, 0xe6, + 0x4f, 0x60, 0x49, 0x31, 0x7e, 0x92, 0xd3, 0x11, 0x53, 0xb8, 0x37, 0x8a, 0xc7, 0x51, 0x8e, 0x3c, + 0x2d, 0x47, 0x7c, 0x31, 0x0f, 0x44, 0xfd, 0x22, 0x4b, 0xcb, 0xe1, 0x1f, 0x64, 0x19, 0x1a, 0x81, + 0x2f, 0x2e, 0x4f, 0x8d, 0xc0, 0xb7, 0xff, 0xd7, 0x82, 0x55, 0x6d, 0x21, 0xaf, 0xed, 0x94, 0x15, + 0x8f, 0x6b, 0xd4, 0x78, 0xdc, 0x3d, 0x68, 0x1e, 0x07, 0x3e, 0xbb, 0xb3, 0x31, 0xbd, 0x6e, 0x48, + 0x72, 0xc6, 0x3a, 0x1c, 0x44, 0x61, 0xa8, 0x5e, 0xf6, 0x32, 0xeb, 0x35, 0xa7, 0xa2, 0x32, 0x94, + 0xca, 0x7e, 0xb8, 0x52, 0xdd, 0x0f, 0xa6, 0x2e, 0xe7, 0xcb, 0xba, 0xe4, 0xd9, 0xaa, 0xa2, 0xad, + 0x3c, 0x6f, 0x00, 0x50, 0x00, 0xa7, 0x9a, 0xf5, 0xd7, 0x01, 0x62, 0x85, 0x29, 0xfc, 0xef, 0x6a, + 0x45, 0x68, 0xe5, 0x82, 0x1a, 0xb2, 0xfd, 0x63, 0x4c, 0x35, 0x74, 0xe6, 0x42, 0xf9, 0x0f, 0x0d, + 0x9a, 0xdc, 0x17, 0x49, 0x85, 0x66, 0x66, 0x10, 0x7b, 0x1f, 0x89, 0xed, 0x0d, 0x06, 0xcc, 0xf4, + 0xda, 0xc5, 0x7c, 0xea, 0x19, 0xfe, 0x1c, 0x16, 0xc4, 0x0c, 0xe1, 0x16, 0x1c, 0xa1, 0x11, 0xf8, + 0xe4, 0x23, 0x00, 0xed, 0x1c, 0xe2, 0xeb, 0xba, 0x26, 0x65, 0x10, 0x93, 0xa4, 0x37, 0x20, 0x3b, + 0x0d, 0xdd, 0x3e, 0x81, 0xb5, 0x1a, 0x14, 0x26, 0x8a, 0xba, 0x56, 0x0b, 0x51, 0xe4, 0x37, 0xb9, + 0x01, 0xed, 0x3c, 0xce, 0xbd, 0xd0, 0x2d, 0x4e, 0x08, 0xcb, 0x01, 0x04, 0x3d, 0x67, 0x10, 0x0c, + 0x50, 0x71, 0xc8, 0x3d, 0x97, 0x05, 0xa8, 0x38, 0xf4, 0x6d, 0x0f, 0x13, 0x2f, 0x63, 0xd1, 0x42, + 0x85, 0xd3, 0x4c, 0xf6, 0x5d, 0x58, 0xf4, 0xf8, 0x14, 0xb9, 0xb0, 0x95, 0xd2, 0xc2, 0x1c, 0x85, + 0x60, 0x13, 0x3c, 0x81, 0xf6, 0xe3, 0xe8, 0x24, 0x18, 0x4a, 0xef, 0x78, 0x1b, 0x83, 0x95, 0x84, + 0x15, 0x39, 0x89, 0xef, 0xe5, 0x1e, 0x72, 0xeb, 0x38, 0xf8, 0xdb, 0xfe, 0x23, 0x0b, 0xba, 0x87, + 0x71, 0x9a, 0x9f, 0xc4, 0x61, 0x10, 0x8b, 0xf4, 0x9e, 0xa5, 0x23, 0x32, 0xfd, 0x17, 0x79, 0xa4, + 0xf8, 0x64, 0x11, 0x72, 0x10, 0x07, 0x11, 0xf7, 0xd5, 0x86, 0x50, 0x50, 0x1c, 0x44, 0xcc, 0x55, + 0xc9, 0x4d, 0x68, 0xfb, 0x34, 0x1b, 0xa4, 0x41, 0xc2, 0xae, 0x73, 0x22, 0x2c, 0xe8, 0x20, 0x46, + 0xf8, 0xd8, 0x0b, 0xbd, 0x68, 0x40, 0x45, 0x64, 0x97, 0x9f, 0xf6, 0x06, 0x86, 0x2b, 0x25, 0x89, + 0x76, 0xb3, 0x36, 0xc1, 0x62, 0x29, 0xbf, 0x06, 0xad, 0x44, 0x02, 0x85, 0xfb, 0xf5, 0xd4, 0x59, + 0x5d, 0x5a, 0x8e, 0x53, 0xa0, 0xda, 0xdb, 0x2c, 0xf7, 0x2f, 0xe8, 0x1d, 0x8d, 0x47, 0x23, 0x2f, + 0xbd, 0x90, 0xdc, 0x22, 0x68, 0xee, 0xc7, 0x41, 0xc4, 0x14, 0xc5, 0x16, 0x25, 0x93, 0x37, 0xf6, + 0x5b, 0x17, 0xbd, 0x61, 0x88, 0xae, 0x6b, 0x6b, 0xce, 0xd4, 0xd6, 0x75, 0x80, 0x84, 0xa6, 0x03, + 0x1a, 0xe5, 0xde, 0x50, 0xae, 0x58, 0x83, 0xd8, 0xa7, 0x40, 0x9e, 0x9e, 0x9c, 0x84, 0x41, 0x44, + 0x19, 0x5b, 0x21, 0xcc, 0x14, 0xed, 0x4f, 0x96, 0xc1, 0xe4, 0x34, 0x57, 0xe1, 0xf4, 0x13, 0x58, + 0x7d, 0x1a, 0xd5, 0x30, 0x92, 0xe4, 0xac, 0x69, 0xe4, 0x1a, 0x15, 0x72, 0x3f, 0x82, 0x8e, 0x26, + 0x78, 0x46, 0x3e, 0x80, 0x96, 0x90, 0x51, 0x5d, 0x14, 0xfa, 0x2a, 0x1a, 0x54, 0x56, 0xe8, 0x14, + 0xc8, 0xf6, 0x5f, 0x58, 0xd0, 0x2e, 0x24, 0xcb, 0xc8, 0x23, 0xb8, 0xc2, 0xd4, 0x2d, 0xa9, 0x5c, + 0x57, 0x54, 0x0a, 0x9c, 0x5d, 0xfc, 0x97, 0xe7, 0x85, 0x1c, 0xb9, 0x7f, 0x04, 0x50, 0x00, 0x6b, + 0xd2, 0xba, 0x07, 0x66, 0x5a, 0x77, 0xb5, 0x4a, 0x55, 0x8a, 0xa6, 0x65, 0x76, 0xff, 0xdc, 0x64, + 0xd7, 0xbd, 0x1a, 0x67, 0x11, 0x3e, 0xf8, 0x3d, 0x68, 0xf3, 0xbd, 0xc0, 0x22, 0x80, 0x14, 0xb8, + 0x53, 0x94, 0x36, 0x82, 0xc8, 0x01, 0xdc, 0x1b, 0x38, 0x4e, 0xde, 0x83, 0x25, 0x14, 0xd6, 0x8d, + 0xb9, 0x42, 0xc4, 0xc6, 0x36, 0x27, 0x74, 0x10, 0x45, 0xa8, 0x8c, 0x24, 0xb0, 0x61, 0x4c, 0x71, + 0x33, 0x2e, 0x82, 0x38, 0xa4, 0x7e, 0xa0, 0xa5, 0xd2, 0x93, 0xa4, 0xe4, 0xca, 0x12, 0x04, 0xc5, + 0x18, 0x57, 0xdd, 0xda, 0xa0, 0x3a, 0x42, 0x1e, 0x40, 0x47, 0x70, 0x44, 0xcd, 0x88, 0x23, 0xce, + 0x94, 0xb1, 0xcd, 0x27, 0x22, 0x02, 0x19, 0xc1, 0xba, 0x3e, 0x41, 0x49, 0x78, 0x05, 0x27, 0x7e, + 0x34, 0xbb, 0x84, 0x51, 0x45, 0x40, 0x32, 0xa8, 0x0c, 0xf4, 0x7f, 0x17, 0x7a, 0x93, 0x16, 0x54, + 0x63, 0xf6, 0xfb, 0xa6, 0xd9, 0xd7, 0x6b, 0x5c, 0x32, 0xd3, 0x0b, 0x88, 0x5f, 0xc0, 0xd6, 0x04, + 0x61, 0x5e, 0xa3, 0xea, 0xa0, 0x79, 0xaa, 0xee, 0x4d, 0x7f, 0x66, 0x41, 0x7f, 0xcf, 0xf7, 0x2b, + 0xc1, 0xa9, 0x28, 0x12, 0x7c, 0xdb, 0x21, 0x77, 0x07, 0xae, 0xd5, 0x0a, 0x24, 0xaa, 0x19, 0xaf, + 0x60, 0xc7, 0xa1, 0xa3, 0xf8, 0x8c, 0x7e, 0xdb, 0x22, 0xdb, 0x37, 0xe1, 0xfa, 0x24, 0xce, 0x42, + 0x36, 0x2c, 0xef, 0x99, 0xe5, 0x71, 0x95, 0x18, 0xfd, 0x87, 0x05, 0x4b, 0x66, 0xe1, 0xfc, 0x4d, + 0xdd, 0xc5, 0xdf, 0x01, 0x92, 0xd2, 0x2c, 0x77, 0xd3, 0x38, 0x0c, 0xd9, 0x95, 0xdc, 0xa7, 0xa1, + 0x77, 0x21, 0x4a, 0xf6, 0x5d, 0x36, 0xe2, 0xf0, 0x81, 0x4f, 0x18, 0x9c, 0x6c, 0xc1, 0x82, 0x97, + 0x04, 0x2e, 0xf3, 0x1a, 0x7e, 0x1f, 0x9f, 0xf7, 0x92, 0xe0, 0xc7, 0xf4, 0x82, 0xd8, 0xb0, 0x24, + 0x06, 0xdc, 0x90, 0x9e, 0xd1, 0x10, 0x73, 0xbe, 0x39, 0xa7, 0xcd, 0x87, 0x3f, 0x63, 0x20, 0x72, + 0x0f, 0xba, 0x49, 0x1a, 0x30, 0xf7, 0x2b, 0xde, 0x06, 0x16, 0x50, 0x9a, 0x15, 0x01, 0x97, 0xab, + 0xb3, 0x7f, 0x06, 0x57, 0x6b, 0x74, 0x21, 0x62, 0xd4, 0x0f, 0x61, 0xc5, 0x7c, 0x61, 0x90, 0x71, + 0x4a, 0x65, 0xad, 0xc6, 0x44, 0x67, 0xf9, 0xc4, 0xa0, 0x23, 0xb2, 0x4f, 0xc4, 0x71, 0xbc, 0x5c, + 0xd5, 0xb4, 0xec, 0xaf, 0x60, 0xbd, 0x00, 0xee, 0xc7, 0xd1, 0x19, 0x4d, 0x33, 0xe6, 0x6d, 0x04, + 0x9a, 0x27, 0x69, 0x2c, 0x0b, 0xb2, 0xf8, 0x9b, 0xe5, 0x6d, 0x79, 0x2c, 0xdc, 0xa0, 0x91, 0xc7, + 0x0c, 0x27, 0xf5, 0x72, 0x79, 0x4a, 0xe1, 0x6f, 0x96, 0x27, 0x07, 0x48, 0x84, 0xba, 0x38, 0xc6, + 0x5d, 0xb5, 0x2d, 0x60, 0x8c, 0x8b, 0xfd, 0x1c, 0xd3, 0x47, 0x5d, 0x14, 0xb1, 0xc6, 0xdf, 0x80, + 0x36, 0x5f, 0x23, 0x9b, 0x29, 0xd7, 0xb7, 0x6d, 0xac, 0xaf, 0x24, 0xa6, 0x03, 0x27, 0x0a, 0x6a, + 0xff, 0x57, 0x03, 0x3a, 0x98, 0xb1, 0x7e, 0x42, 0x73, 0x2f, 0x08, 0xa7, 0xe7, 0xd2, 0x3c, 0x07, + 0x6d, 0xa8, 0x1c, 0xf4, 0x2d, 0x58, 0xd2, 0x0b, 0x22, 0x17, 0xf2, 0x32, 0xab, 0x95, 0x43, 0x2e, + 0xc8, 0x6d, 0x58, 0xc6, 0xab, 0x75, 0x81, 0xc5, 0x7d, 0x66, 0x09, 0xa1, 0x0a, 0xcd, 0xbc, 0x08, + 0x5c, 0x29, 0x5d, 0x04, 0xd8, 0x30, 0x26, 0xd3, 0x6e, 0x16, 0xf8, 0xea, 0x9e, 0x80, 0x90, 0xa3, + 0xc0, 0xd7, 0x86, 0x71, 0xf6, 0x82, 0x36, 0x8c, 0xb3, 0xd9, 0x1d, 0x28, 0xa5, 0xfc, 0xa1, 0x00, + 0xdf, 0xbb, 0x16, 0xd1, 0xe9, 0x3a, 0x12, 0xf8, 0x2c, 0x18, 0xe1, 0x6b, 0x98, 0x28, 0x6e, 0xb7, + 0xb8, 0xc7, 0xf2, 0xaf, 0xe2, 0x9a, 0x06, 0xfa, 0x35, 0xad, 0xb8, 0xd4, 0xb5, 0x8d, 0x4b, 0xdd, + 0x0d, 0x68, 0xc7, 0x09, 0x8d, 0x5c, 0x71, 0xc5, 0xee, 0xf0, 0xec, 0x81, 0x81, 0x9e, 0x23, 0x44, + 0x94, 0x4c, 0x50, 0xe7, 0xd9, 0x2c, 0xf7, 0x52, 0x53, 0x31, 0x8d, 0xb2, 0x62, 0xe4, 0x45, 0x70, + 0xee, 0xb2, 0x8b, 0xa0, 0xbd, 0x87, 0x59, 0xb1, 0x64, 0x2c, 0xdc, 0xe7, 0x1d, 0x98, 0x47, 0x35, + 0x49, 0xcf, 0x59, 0x37, 0xae, 0x31, 0xc2, 0x29, 0x1c, 0x81, 0x63, 0xff, 0x08, 0xdf, 0x10, 0x71, + 0x68, 0x16, 0xd1, 0xaf, 0xc2, 0x22, 0xb7, 0x8a, 0xf2, 0x9a, 0x05, 0xfc, 0x7e, 0xe2, 0xdb, 0xff, + 0x6a, 0x01, 0x39, 0x1a, 0x1f, 0x8f, 0x82, 0xd9, 0xa9, 0xcd, 0x7e, 0x41, 0x27, 0xd0, 0x44, 0x37, + 0xe1, 0xee, 0x88, 0xbf, 0x4b, 0x1e, 0xd2, 0x2c, 0x7b, 0x48, 0x61, 0xce, 0x2b, 0xf5, 0x77, 0xf4, + 0x79, 0xdd, 0xf8, 0x2c, 0xc4, 0x87, 0x01, 0x8d, 0x72, 0x57, 0x14, 0x5b, 0x58, 0x88, 0x47, 0xc0, + 0x13, 0xdf, 0x3e, 0x82, 0x35, 0x63, 0x65, 0x42, 0xd3, 0xb7, 0xa0, 0xc3, 0x05, 0x48, 0x42, 0x6f, + 0xa0, 0xaa, 0xe1, 0x6d, 0x84, 0x1d, 0x22, 0x68, 0x9a, 0xbe, 0xfe, 0xc4, 0x82, 0xf5, 0xa3, 0x60, + 0x34, 0x0e, 0xbd, 0x9c, 0xfe, 0x3f, 0x68, 0xac, 0x58, 0xfe, 0x9c, 0xb1, 0x7c, 0xa9, 0xc9, 0x66, + 0xa1, 0x49, 0xfb, 0xbf, 0x2d, 0xd8, 0x28, 0x89, 0xa2, 0x72, 0x42, 0xd3, 0x99, 0x26, 0x14, 0x07, + 0x04, 0x92, 0xc6, 0xb4, 0x61, 0x30, 0x7d, 0x0b, 0x96, 0x46, 0x41, 0x14, 0x8c, 0xc6, 0x23, 0x97, + 0xeb, 0x9e, 0xcb, 0xd4, 0x11, 0xc0, 0x43, 0x34, 0x01, 0x43, 0xf2, 0x5e, 0x69, 0x48, 0x4d, 0x81, + 0xc4, 0x81, 0x1c, 0xe9, 0x5d, 0x58, 0x2f, 0xf2, 0x76, 0x77, 0xe8, 0x05, 0x91, 0x1b, 0xc6, 0x59, + 0x26, 0x6c, 0x4c, 0x8a, 0xb1, 0x03, 0x2f, 0x88, 0x3e, 0x8b, 0xb3, 0x4c, 0x0b, 0x02, 0xf3, 0x7a, + 0x10, 0x60, 0x09, 0x4c, 0xf7, 0xc5, 0xa9, 0x17, 0xd2, 0x8f, 0xe3, 0xd1, 0xf1, 0x9b, 0xd5, 0xfd, + 0x2d, 0xe8, 0xf0, 0xba, 0x5b, 0xee, 0xa5, 0x43, 0x2a, 0x2d, 0xd0, 0x46, 0xd8, 0x33, 0x04, 0xd5, + 0x9a, 0xe1, 0x3f, 0x2d, 0x20, 0xfb, 0x2c, 0x95, 0x09, 0x67, 0xf6, 0x07, 0x16, 0x4a, 0xf8, 0xbd, + 0xb9, 0xf0, 0xb0, 0x96, 0x80, 0x3c, 0x31, 0xdd, 0x6f, 0xce, 0x70, 0x3f, 0xb5, 0x9a, 0xe6, 0x6b, + 0x16, 0xc7, 0x2a, 0x71, 0xfc, 0x36, 0x2c, 0x9f, 0x7b, 0x61, 0x48, 0x73, 0xf5, 0xc4, 0x26, 0x2a, + 0xf1, 0x1c, 0x2a, 0xef, 0xe0, 0x72, 0xc1, 0x0b, 0xda, 0x82, 0x37, 0x60, 0xcd, 0x58, 0xaf, 0xc8, + 0x86, 0x1e, 0xc1, 0x26, 0x07, 0xef, 0x85, 0xe1, 0xcc, 0x51, 0xd5, 0xfe, 0xcb, 0x06, 0x6c, 0x55, + 0xa6, 0xa9, 0xb4, 0xc1, 0x74, 0xe3, 0x3b, 0x6a, 0xb9, 0xf5, 0x13, 0x76, 0xc5, 0xa7, 0x98, 0xd5, + 0xff, 0x47, 0x0b, 0xe6, 0x39, 0x68, 0xaa, 0x35, 0xbe, 0x90, 0x01, 0x41, 0x38, 0x1c, 0xbf, 0x11, + 0x7d, 0x7f, 0x36, 0x66, 0xfc, 0x3f, 0xfd, 0x59, 0x95, 0x47, 0x12, 0xf1, 0xa2, 0xfa, 0x43, 0xe8, + 0x96, 0x11, 0x5e, 0xeb, 0xc9, 0x89, 0x57, 0x55, 0x1e, 0x9f, 0x51, 0xed, 0x19, 0xf5, 0x17, 0x16, + 0xac, 0xec, 0xc7, 0x91, 0x1f, 0xb0, 0x13, 0xf3, 0xd0, 0x4b, 0xbd, 0x51, 0x26, 0x5e, 0xf2, 0x39, + 0x48, 0x96, 0xdd, 0x15, 0x60, 0x42, 0x81, 0x73, 0x07, 0x60, 0x70, 0x4a, 0x07, 0x2f, 0x5d, 0x51, + 0x71, 0xe4, 0xcf, 0xff, 0x0c, 0xf2, 0x71, 0xe0, 0x67, 0xe4, 0x7b, 0xb0, 0x56, 0x0c, 0xbb, 0x5e, + 0xe4, 0xbb, 0xa2, 0xdc, 0x88, 0xaf, 0x1b, 0x0a, 0x6f, 0x2f, 0xf2, 0xf7, 0xb2, 0x97, 0x19, 0xcb, + 0x15, 0x55, 0x95, 0xcd, 0x35, 0x42, 0xf8, 0x8a, 0x82, 0xef, 0x21, 0xd8, 0xfe, 0x1f, 0x0b, 0x4f, + 0x40, 0xb9, 0x2a, 0x61, 0xed, 0xa2, 0xb0, 0x86, 0xf5, 0x56, 0xc3, 0x64, 0x8d, 0x92, 0xc9, 0x08, + 0x34, 0x83, 0x9c, 0x8e, 0xe4, 0xc1, 0xc2, 0x7e, 0x93, 0x8f, 0xa1, 0xab, 0x56, 0xec, 0x26, 0xa8, + 0x16, 0xb1, 0x4d, 0xb6, 0x8a, 0x8b, 0xa3, 0xa1, 0x35, 0x67, 0x65, 0x50, 0x52, 0xa3, 0xdc, 0x5e, + 0x57, 0x66, 0x0a, 0xd4, 0x03, 0xd4, 0xb6, 0x88, 0x4f, 0xfc, 0x8b, 0x4b, 0x4d, 0x07, 0xe3, 0x9c, + 0xfa, 0x22, 0x55, 0x56, 0xdf, 0xf6, 0xbf, 0x5b, 0xb0, 0xb2, 0xe7, 0xfb, 0xb8, 0xee, 0x59, 0xc2, + 0x84, 0x5c, 0x65, 0xe3, 0x92, 0x55, 0xce, 0xfd, 0x92, 0xab, 0xfc, 0x95, 0x83, 0xc8, 0x04, 0x25, + 0xd8, 0x36, 0x74, 0x8b, 0x75, 0xd6, 0x9b, 0xd7, 0xfe, 0x0e, 0x10, 0x7e, 0xbd, 0x32, 0xd4, 0x51, + 0xc6, 0xda, 0x80, 0x35, 0x03, 0x4b, 0xc4, 0x9a, 0x4f, 0xe1, 0xee, 0x01, 0xcd, 0xf7, 0xd3, 0x8b, + 0x24, 0x8f, 0x65, 0x3a, 0xfb, 0x09, 0x4d, 0xe2, 0x2c, 0x90, 0x91, 0x8b, 0xce, 0x14, 0x7d, 0xfe, + 0xc9, 0x82, 0x7b, 0x33, 0x10, 0x12, 0x4b, 0xf8, 0xb2, 0x5a, 0x5f, 0xfa, 0x2d, 0xbd, 0xbd, 0x65, + 0x26, 0x2a, 0xbb, 0x0a, 0x22, 0xba, 0x0c, 0x14, 0xc9, 0xfe, 0x0f, 0x60, 0xd9, 0x1c, 0x7c, 0xad, + 0x50, 0x11, 0xc2, 0x9d, 0x4b, 0x84, 0x98, 0xc5, 0xe7, 0xee, 0xc0, 0xf2, 0xc0, 0x20, 0x21, 0x18, + 0x95, 0xa0, 0xf6, 0x3e, 0xbc, 0x7d, 0x29, 0x37, 0xa1, 0xb6, 0x89, 0x37, 0x74, 0xfb, 0x6f, 0x9b, + 0xb0, 0xf5, 0x22, 0xc8, 0x4f, 0xfd, 0xd4, 0x3b, 0x97, 0xde, 0x37, 0x8b, 0x90, 0xa5, 0xcb, 0x7b, + 0xa3, 0x5a, 0x6f, 0xb8, 0x0f, 0xab, 0x71, 0x44, 0xf1, 0x8e, 0xe1, 0x26, 0x5e, 0x96, 0x9d, 0xc7, + 0xa9, 0x3c, 0x4b, 0x57, 0xe2, 0x88, 0xb2, 0x7b, 0xc6, 0xa1, 0x00, 0x97, 0x4e, 0xe3, 0x66, 0xf9, + 0x34, 0xee, 0xc2, 0x5c, 0x12, 0x44, 0xe2, 0xcd, 0x84, 0xfd, 0x64, 0x67, 0x67, 0x9e, 0x7a, 0xbe, + 0x46, 0x59, 0x9c, 0x9d, 0x08, 0x55, 0x74, 0xf5, 0x2a, 0xfe, 0x42, 0xa9, 0x8a, 0xaf, 0xe9, 0x64, + 0xd1, 0xac, 0x5a, 0xdc, 0x80, 0xb6, 0xf8, 0xe9, 0xe6, 0xde, 0x50, 0x5c, 0x81, 0x40, 0x80, 0x9e, + 0x79, 0x43, 0x2d, 0x5b, 0x03, 0x23, 0x5b, 0xdb, 0x01, 0x38, 0xa1, 0xd4, 0x35, 0x2e, 0x43, 0xad, + 0x13, 0x4a, 0x79, 0xd0, 0x65, 0xa9, 0xf2, 0xb1, 0x17, 0xbd, 0x74, 0xb1, 0x06, 0xd1, 0xe1, 0xe2, + 0x30, 0xc0, 0xe7, 0xde, 0x08, 0x73, 0x62, 0x1c, 0x94, 0x32, 0x2d, 0x71, 0x8d, 0x32, 0xd8, 0x5e, + 0x51, 0x4d, 0x41, 0x94, 0x41, 0x90, 0x5f, 0xf4, 0x96, 0x8b, 0xf9, 0xfb, 0x41, 0x7e, 0xa1, 0xe6, + 0xa3, 0xce, 0xd2, 0x8b, 0xde, 0x4a, 0x31, 0x7f, 0x9f, 0x83, 0x98, 0x78, 0xd9, 0x79, 0x70, 0x42, + 0x79, 0x63, 0x48, 0x57, 0xb4, 0x4a, 0x31, 0xc8, 0x7e, 0xec, 0x63, 0x1a, 0x79, 0x1e, 0xa4, 0xda, + 0xe5, 0x74, 0x95, 0x5f, 0x61, 0x19, 0x50, 0xba, 0x86, 0x7d, 0x1f, 0xba, 0xd2, 0x5d, 0xf4, 0xde, + 0xc9, 0x94, 0x66, 0xe3, 0x30, 0x97, 0xbd, 0x93, 0xfc, 0xcb, 0x7e, 0x0f, 0xbb, 0x22, 0x3e, 0x8b, + 0x87, 0xc3, 0xe2, 0xfa, 0x24, 0x5c, 0x6b, 0x13, 0xe6, 0x43, 0x84, 0xcb, 0x29, 0xfc, 0xcb, 0x8e, + 0xb0, 0x9e, 0x53, 0x9a, 0x52, 0xbc, 0x5a, 0x04, 0xd1, 0x49, 0x2c, 0x6e, 0x0b, 0xf8, 0x9b, 0xed, + 0x45, 0x9f, 0x1e, 0x8f, 0x87, 0xb2, 0x07, 0x0a, 0x3f, 0x18, 0xe6, 0xb9, 0x97, 0x46, 0xe2, 0x40, + 0xc5, 0xdf, 0x0c, 0x93, 0xa6, 0x69, 0x9c, 0x8a, 0xd3, 0x93, 0x7f, 0xd8, 0x07, 0xb0, 0x75, 0xf4, + 0x7a, 0x22, 0x32, 0x42, 0xbc, 0x5a, 0x23, 0xb6, 0x3f, 0x7e, 0x3c, 0xfc, 0xeb, 0xb7, 0x60, 0xf9, + 0x20, 0xe6, 0x9b, 0xf1, 0x19, 0xf3, 0xc1, 0x94, 0x3c, 0x85, 0x05, 0xd1, 0xfc, 0x48, 0x36, 0x2b, + 0xdd, 0x90, 0xc8, 0xa3, 0xbf, 0x35, 0xa1, 0x4b, 0xd2, 0x5e, 0xfb, 0xe6, 0x5f, 0xfe, 0xed, 0xe7, + 0x8d, 0x25, 0xd2, 0x7e, 0x70, 0xf6, 0xde, 0x83, 0x21, 0xcd, 0x71, 0xb1, 0xa7, 0xb0, 0x64, 0xf4, + 0xab, 0x91, 0x6d, 0xa3, 0xe7, 0xac, 0xd4, 0xc6, 0xd6, 0xdf, 0x99, 0xda, 0x91, 0x66, 0xf7, 0x91, + 0xc5, 0x3a, 0x21, 0x82, 0x45, 0x86, 0x28, 0x9c, 0xf0, 0x57, 0xb0, 0xf2, 0x18, 0xab, 0x60, 0x8a, + 0x2a, 0xb9, 0x51, 0x50, 0xab, 0xed, 0xc3, 0xeb, 0xdf, 0x9c, 0x8c, 0x20, 0x38, 0x5e, 0x43, 0x8e, + 0x1b, 0x64, 0x8d, 0x71, 0xe4, 0x55, 0x36, 0xd5, 0xff, 0x46, 0x32, 0xe8, 0x8a, 0xce, 0x9e, 0x37, + 0xca, 0x73, 0x1b, 0x79, 0x6e, 0x92, 0x75, 0xc6, 0xd3, 0xe7, 0x0c, 0x0a, 0xa6, 0x31, 0x5e, 0xe2, + 0xf5, 0x4e, 0x34, 0x72, 0x7d, 0x62, 0x8b, 0x1a, 0x67, 0x79, 0xe3, 0x92, 0x16, 0x36, 0x73, 0x95, + 0x43, 0xca, 0x70, 0x55, 0x17, 0x1b, 0xf9, 0xb9, 0x85, 0x0e, 0x5e, 0xdb, 0x33, 0x49, 0xde, 0xbe, + 0xbc, 0x51, 0x93, 0xcb, 0x70, 0x77, 0xd6, 0x8e, 0x4e, 0xfb, 0x3b, 0x28, 0xcc, 0x75, 0xb2, 0x2d, + 0x84, 0x31, 0xba, 0x38, 0x65, 0x9f, 0x28, 0x19, 0x40, 0x47, 0x6f, 0x3f, 0x23, 0xd7, 0x6a, 0x5a, + 0x73, 0x14, 0xf3, 0xed, 0xfa, 0x41, 0xc1, 0xb0, 0x87, 0x0c, 0x09, 0xe9, 0x0a, 0x86, 0xaa, 0x5b, + 0x8d, 0x7c, 0x0d, 0x2b, 0xa5, 0xd6, 0x2d, 0x62, 0x97, 0xcc, 0x57, 0xd3, 0x86, 0xd7, 0x7f, 0x6b, + 0x2a, 0x8e, 0xe0, 0x7a, 0x1d, 0xb9, 0xf6, 0xec, 0x35, 0xcd, 0xca, 0x92, 0xf3, 0x87, 0xd6, 0x7d, + 0x92, 0xa1, 0x9d, 0xf5, 0x2e, 0xa3, 0x99, 0x78, 0xdf, 0xb8, 0xa4, 0x45, 0xa9, 0x62, 0x6b, 0xc9, + 0x13, 0xb7, 0x6b, 0x86, 0x9d, 0x1b, 0x5a, 0x6f, 0x1c, 0x46, 0xd9, 0x59, 0xf8, 0xee, 0xd4, 0xf7, + 0xd6, 0x89, 0xf6, 0xbe, 0xca, 0xce, 0x95, 0x5c, 0xe3, 0x3c, 0x21, 0x99, 0xd1, 0x7a, 0x28, 0x98, + 0x9a, 0x5e, 0x5d, 0xd3, 0xfc, 0x57, 0xbb, 0x52, 0xbd, 0x9b, 0x6f, 0xe2, 0x4a, 0xe3, 0x3c, 0xc9, + 0xc8, 0x2b, 0x58, 0xe6, 0xe1, 0xe2, 0xcd, 0x5b, 0x76, 0x07, 0xf9, 0x6e, 0xd9, 0xa4, 0x88, 0x19, + 0xba, 0x61, 0x5f, 0x40, 0x4b, 0x75, 0xc7, 0x90, 0x9e, 0xb6, 0x08, 0xa3, 0x0f, 0xab, 0x3f, 0xa1, + 0xcb, 0x46, 0x7a, 0xab, 0xbd, 0x24, 0x56, 0xc5, 0x7b, 0x66, 0x18, 0xe1, 0x9f, 0x01, 0x14, 0x6d, + 0x37, 0xe4, 0x6a, 0x85, 0xb2, 0xd2, 0x5c, 0xbf, 0x6e, 0x48, 0x36, 0x18, 0x23, 0xf9, 0x2e, 0x59, + 0x36, 0xc8, 0xcb, 0xfd, 0xa6, 0x2a, 0x41, 0xc6, 0x7e, 0x2b, 0x37, 0xea, 0xf4, 0x27, 0x77, 0x68, + 0x48, 0xa3, 0xd8, 0x72, 0xb3, 0xa9, 0x5b, 0x1e, 0x5b, 0xc1, 0x10, 0x4f, 0x0b, 0xad, 0x35, 0x64, + 0xbb, 0x8e, 0x4b, 0xed, 0x69, 0x51, 0xed, 0xf3, 0xb0, 0xaf, 0x22, 0xab, 0x35, 0xb2, 0x5a, 0x66, + 0x95, 0x91, 0x97, 0xf8, 0x07, 0x16, 0x5a, 0x67, 0x03, 0xd1, 0x69, 0x55, 0xdb, 0x3c, 0xfa, 0xd7, + 0x27, 0x0d, 0x4f, 0x38, 0x99, 0x44, 0x22, 0x88, 0x9b, 0x8a, 0x1b, 0x9c, 0xf7, 0x33, 0x18, 0x06, + 0x37, 0xda, 0x1e, 0xfa, 0x57, 0x6b, 0x46, 0x04, 0xf5, 0x0d, 0xa4, 0xbe, 0x42, 0x96, 0x54, 0x48, + 0x44, 0x5a, 0xdc, 0x26, 0xea, 0xa1, 0xc9, 0xb0, 0x49, 0xb9, 0x1b, 0xc1, 0x88, 0x81, 0x95, 0x9e, + 0x84, 0x4a, 0x0c, 0x54, 0x5d, 0x07, 0xe4, 0x0f, 0xcd, 0xe6, 0x06, 0xf9, 0xd8, 0x6a, 0x4f, 0x7d, + 0x1d, 0xad, 0xec, 0x96, 0x89, 0x2f, 0xa8, 0xf6, 0x0d, 0xe4, 0x7c, 0x95, 0x6c, 0x95, 0x39, 0x8b, + 0xd7, 0x58, 0xf2, 0x8d, 0x05, 0x6b, 0x35, 0x6f, 0x7d, 0x85, 0x04, 0x93, 0x5f, 0x26, 0x0b, 0x09, + 0xa6, 0x3d, 0x16, 0xda, 0x28, 0xc1, 0xb6, 0x8d, 0x12, 0x78, 0xbe, 0xaf, 0x24, 0x10, 0x79, 0x2d, + 0xf3, 0xcc, 0x3f, 0xb5, 0x60, 0xb3, 0xfe, 0x5d, 0x8f, 0xdc, 0x56, 0x2d, 0xdb, 0xd3, 0x5e, 0x1c, + 0xfb, 0x77, 0x2e, 0x43, 0x13, 0xd2, 0xdc, 0x46, 0x69, 0x6e, 0xd8, 0x7d, 0x26, 0x4d, 0x8a, 0xb8, + 0x75, 0x02, 0x9d, 0x63, 0x31, 0xc4, 0x7c, 0x39, 0x23, 0x5a, 0x6e, 0x51, 0xff, 0xc0, 0xd8, 0xbf, + 0x35, 0x05, 0xc3, 0x0c, 0x5f, 0x64, 0x43, 0x18, 0x04, 0x9f, 0x9b, 0xd4, 0x13, 0x9c, 0xd8, 0xa3, + 0xc5, 0xcb, 0x94, 0xb1, 0x47, 0x2b, 0x8f, 0x6d, 0xc6, 0x1e, 0xad, 0xbe, 0x7f, 0x55, 0xf6, 0x28, + 0x32, 0xc3, 0xb7, 0x30, 0xf2, 0x05, 0x6e, 0x1b, 0x51, 0x89, 0xeb, 0x95, 0xb7, 0x7a, 0x56, 0xb7, + 0x6d, 0xcc, 0x5a, 0x5b, 0x25, 0x54, 0xf2, 0x02, 0x1f, 0xd3, 0x9e, 0x03, 0x8b, 0x12, 0x9d, 0x6c, + 0x95, 0x09, 0x48, 0xca, 0xb5, 0x8f, 0x29, 0xf6, 0x16, 0x12, 0x5d, 0xb5, 0x3b, 0x3a, 0x51, 0x46, + 0xf3, 0x18, 0xda, 0xda, 0xc3, 0x01, 0x51, 0x41, 0xb6, 0xfa, 0x4e, 0xd2, 0xbf, 0x56, 0x3b, 0x66, + 0x86, 0x12, 0x7b, 0x85, 0x31, 0xc8, 0x10, 0x41, 0xf1, 0xf8, 0x7d, 0x58, 0x32, 0x6a, 0xf7, 0x85, + 0xf2, 0xeb, 0x5e, 0x17, 0x0a, 0xe5, 0xd7, 0x16, 0xfc, 0x65, 0xa2, 0x69, 0xa3, 0xf2, 0x33, 0x81, + 0xa2, 0x78, 0x7d, 0x09, 0x2d, 0x55, 0x32, 0x2f, 0xf4, 0x5f, 0xae, 0xa2, 0x5f, 0xc6, 0xc3, 0xb0, + 0xc1, 0x39, 0x9b, 0x7c, 0x1c, 0x8f, 0x8e, 0x85, 0xbe, 0xb4, 0x82, 0x70, 0xa1, 0xaf, 0x6a, 0x55, + 0xbc, 0xd0, 0x57, 0x5d, 0x05, 0xd9, 0xd0, 0xd7, 0x00, 0x11, 0xd4, 0x1a, 0x52, 0x58, 0x29, 0x15, + 0x62, 0x8b, 0xb4, 0xa2, 0xbe, 0xec, 0x5c, 0xa4, 0x15, 0x13, 0x2a, 0xb8, 0x66, 0xe2, 0xc6, 0xf9, + 0x79, 0x61, 0x58, 0xf8, 0x16, 0x0f, 0xf7, 0xbc, 0x4c, 0x69, 0xf8, 0xad, 0x51, 0x8f, 0x35, 0xfc, + 0xd6, 0xac, 0x69, 0x56, 0xc2, 0x3d, 0xe5, 0xb4, 0x9e, 0xc3, 0xa2, 0xac, 0x8f, 0x15, 0x4e, 0x5b, + 0xaa, 0x0c, 0xf6, 0x7b, 0xd5, 0x01, 0x41, 0xd5, 0x70, 0x5c, 0xcf, 0xf7, 0x91, 0xaa, 0x30, 0x84, + 0x56, 0x2d, 0x2b, 0x0c, 0x51, 0x2d, 0xb4, 0x15, 0x86, 0xa8, 0x2b, 0xaf, 0x19, 0x86, 0xe0, 0x91, + 0x4b, 0xf1, 0xf8, 0x3b, 0x0b, 0x6e, 0x5d, 0x5a, 0xec, 0x22, 0xef, 0xbe, 0x46, 0x5d, 0x8c, 0x0b, + 0xf4, 0xde, 0x6b, 0x57, 0xd2, 0xec, 0xbb, 0x28, 0xa6, 0x6d, 0xef, 0xc8, 0xc3, 0x14, 0xa7, 0xf9, + 0x1c, 0x5d, 0x95, 0xd5, 0x98, 0xd0, 0x7f, 0x63, 0xf1, 0x3f, 0x9f, 0x9b, 0x42, 0x97, 0xec, 0xce, + 0x28, 0x80, 0x14, 0xf8, 0xc1, 0xcc, 0xf8, 0x42, 0xdc, 0x3b, 0x28, 0xee, 0x4d, 0xfb, 0xda, 0x14, + 0x71, 0x99, 0xb0, 0x7f, 0x00, 0xd7, 0x54, 0x51, 0xcc, 0xa0, 0xfb, 0xe9, 0x38, 0xf2, 0xb3, 0xe2, + 0x5e, 0x3a, 0xa1, 0x72, 0x56, 0x38, 0x4e, 0xb9, 0x56, 0x62, 0x9e, 0x8f, 0xe7, 0x62, 0x94, 0x8b, + 0x71, 0xc2, 0x68, 0x33, 0xee, 0x09, 0xac, 0xca, 0x79, 0x9f, 0x06, 0x5e, 0xfe, 0x2b, 0xf3, 0xbc, + 0x89, 0x3c, 0xfb, 0xf6, 0x86, 0xce, 0xf3, 0x24, 0xf0, 0x72, 0xc5, 0x31, 0xc3, 0x37, 0x0e, 0xa3, + 0x0c, 0xa2, 0x5f, 0xbe, 0x6b, 0x0b, 0x24, 0xfa, 0xe5, 0xbb, 0xbe, 0x62, 0x63, 0x5e, 0xbe, 0x87, + 0x34, 0xe7, 0x15, 0x14, 0x5f, 0x30, 0x38, 0x83, 0xee, 0xd1, 0x44, 0xa6, 0x47, 0xbf, 0x34, 0x53, + 0x91, 0x03, 0xd9, 0xc8, 0x34, 0x2b, 0x31, 0xfd, 0xd0, 0xba, 0x7f, 0x3c, 0x8f, 0x7f, 0x17, 0xfc, + 0xfe, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x95, 0x64, 0xd9, 0x15, 0x4a, 0x3c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 4bd4c1e7..3423d9be 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -9,13 +9,13 @@ It translates gRPC into RESTful JSON APIs. package gctrpc import ( - "context" "io" "net/http" "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/grpclog" @@ -54,10 +54,7 @@ func request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshaler run var protoReq GenericSubsystemRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_EnableSubsystem_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_EnableSubsystem_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -74,10 +71,7 @@ func request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marshaler ru var protoReq GenericSubsystemRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_DisableSubsystem_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_DisableSubsystem_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -112,10 +106,7 @@ func request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtim var protoReq GetExchangesRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchanges_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -149,10 +140,7 @@ func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler run var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -169,10 +157,7 @@ func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -267,10 +252,7 @@ func request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runt var protoReq GetAccountInfoRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetAccountInfo_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -596,10 +578,7 @@ func request_GoCryptoTrader_GetLoggerDetails_0(ctx context.Context, marshaler ru var protoReq GetLoggerDetailsRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetLoggerDetails_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetLoggerDetails_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1467,85 +1446,85 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve } var ( - pattern_GoCryptoTrader_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, "")) - pattern_GoCryptoTrader_GetSubsystems_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getsusbsystems"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetSubsystems_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getsusbsystems"}, "")) - pattern_GoCryptoTrader_EnableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enablesubsystem"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_EnableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enablesubsystem"}, "")) - pattern_GoCryptoTrader_DisableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disablesubsystem"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_DisableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disablesubsystem"}, "")) - pattern_GoCryptoTrader_GetRPCEndpoints_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getrpcendpoints"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetRPCEndpoints_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getrpcendpoints"}, "")) - pattern_GoCryptoTrader_GetCommunicationRelayers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcommunicationrelayers"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetCommunicationRelayers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcommunicationrelayers"}, "")) - pattern_GoCryptoTrader_GetExchanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchanges"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetExchanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchanges"}, "")) - pattern_GoCryptoTrader_DisableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchange"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_DisableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchange"}, "")) - pattern_GoCryptoTrader_GetExchangeInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeinfo"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetExchangeInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeinfo"}, "")) - pattern_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotp"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotp"}, "")) - pattern_GoCryptoTrader_GetExchangeOTPCodes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotps"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetExchangeOTPCodes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotps"}, "")) - pattern_GoCryptoTrader_EnableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchange"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_EnableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchange"}, "")) - pattern_GoCryptoTrader_GetTicker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getticker"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetTicker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getticker"}, "")) - pattern_GoCryptoTrader_GetTickers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "gettickers"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetTickers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "gettickers"}, "")) - pattern_GoCryptoTrader_GetOrderbook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbook"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetOrderbook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbook"}, "")) - pattern_GoCryptoTrader_GetOrderbooks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbooks"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetOrderbooks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbooks"}, "")) - pattern_GoCryptoTrader_GetAccountInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getaccountinfo"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetAccountInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getaccountinfo"}, "")) - pattern_GoCryptoTrader_GetConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getconfig"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getconfig"}, "")) - pattern_GoCryptoTrader_GetPortfolio_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfolio"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetPortfolio_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfolio"}, "")) - pattern_GoCryptoTrader_GetPortfolioSummary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfoliosummary"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetPortfolioSummary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfoliosummary"}, "")) - pattern_GoCryptoTrader_AddPortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addportfolioaddress"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_AddPortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addportfolioaddress"}, "")) - pattern_GoCryptoTrader_RemovePortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeportfolioaddress"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_RemovePortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeportfolioaddress"}, "")) - pattern_GoCryptoTrader_GetForexProviders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexproviders"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetForexProviders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexproviders"}, "")) - pattern_GoCryptoTrader_GetForexRates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexrates"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetForexRates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexrates"}, "")) - pattern_GoCryptoTrader_GetOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorders"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorders"}, "")) - pattern_GoCryptoTrader_GetOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorder"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorder"}, "")) - pattern_GoCryptoTrader_SubmitOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "submitorder"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_SubmitOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "submitorder"}, "")) - pattern_GoCryptoTrader_SimulateOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "simulateorder"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_SimulateOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "simulateorder"}, "")) - pattern_GoCryptoTrader_WhaleBomb_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "whalebomb"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_WhaleBomb_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "whalebomb"}, "")) - pattern_GoCryptoTrader_CancelOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelorder"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_CancelOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelorder"}, "")) - pattern_GoCryptoTrader_CancelAllOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelallorders"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_CancelAllOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelallorders"}, "")) - pattern_GoCryptoTrader_GetEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getevents"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getevents"}, "")) - pattern_GoCryptoTrader_AddEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addevent"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_AddEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addevent"}, "")) - pattern_GoCryptoTrader_RemoveEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeevent"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_RemoveEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeevent"}, "")) - pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddresses"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddresses"}, "")) - pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddress"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddress"}, "")) - pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawcryptofunds"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawcryptofunds"}, "")) - pattern_GoCryptoTrader_WithdrawFiatFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawfiatfunds"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_WithdrawFiatFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawfiatfunds"}, "")) - pattern_GoCryptoTrader_GetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getloggerdetails"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getloggerdetails"}, "")) - pattern_GoCryptoTrader_SetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "setloggerdetails"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_SetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "setloggerdetails"}, "")) ) var ( diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index aa0cde00..76efbe51 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -78,6 +78,11 @@ message DisableExchangeRequest { string exchange = 1; } +message PairsSupported { + string available_pairs = 1; + string enabled_pairs = 2; +} + message GetExchangeInfoResponse { string name = 1; bool enabled = 2; @@ -87,10 +92,8 @@ message GetExchangeInfoResponse { string http_useragent = 6; string http_proxy = 7; string base_currencies = 8; - string supported_assets = 9; - string enabled_pairs = 10; - string available_pairs = 11; - bool authenticated_api = 12; + map supported_assets = 9; + bool authenticated_api = 10; } message GetTickerRequest { diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index c590bbd3..e478f30d 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -1323,13 +1323,10 @@ "type": "string" }, "supported_assets": { - "type": "string" - }, - "enabled_pairs": { - "type": "string" - }, - "available_pairs": { - "type": "string" + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/gctrpcPairsSupported" + } }, "authenticated_api": { "type": "boolean", @@ -1754,6 +1751,17 @@ } } }, + "gctrpcPairsSupported": { + "type": "object", + "properties": { + "available_pairs": { + "type": "string" + }, + "enabled_pairs": { + "type": "string" + } + } + }, "gctrpcPortfolioAddress": { "type": "object", "properties": { From c041c990a987a01b58be39daf0fd5e7456389430 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 6 Sep 2019 11:24:38 +1000 Subject: [PATCH 30/71] Improve ticker batching and websocket pair consistency --- currency/pair.go | 17 +- currency/pair_test.go | 24 +++ exchanges/binance/binance_types.go | 84 ++++---- exchanges/binance/binance_websocket.go | 32 +-- exchanges/binance/binance_wrapper.go | 5 +- exchanges/bitfinex/bitfinex_websocket.go | 3 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 22 +- exchanges/exchange.go | 1 - exchanges/gateio/gateio_websocket.go | 6 +- exchanges/hitbtc/hitbtc_test.go | 17 ++ exchanges/hitbtc/hitbtc_types.go | 214 +++++++++---------- exchanges/hitbtc/hitbtc_websocket.go | 37 ++-- exchanges/hitbtc/hitbtc_wrapper.go | 16 +- exchanges/huobi/huobi_types.go | 153 ++++++------- exchanges/huobi/huobi_websocket.go | 28 +-- exchanges/huobi/huobi_wrapper.go | 11 +- exchanges/huobihadax/huobihadax_types.go | 140 ++++++------ exchanges/huobihadax/huobihadax_websocket.go | 28 +-- exchanges/huobihadax/huobihadax_wrapper.go | 8 +- exchanges/kraken/kraken_websocket.go | 3 +- exchanges/kraken/kraken_wrapper.go | 7 +- exchanges/lakebtc/lakebtc_test.go | 5 +- exchanges/lakebtc/lakebtc_websocket.go | 48 +++-- exchanges/zb/zb_wrapper.go | 9 +- 24 files changed, 519 insertions(+), 399 deletions(-) diff --git a/currency/pair.go b/currency/pair.go index 74e22195..e83ee8e2 100644 --- a/currency/pair.go +++ b/currency/pair.go @@ -73,6 +73,20 @@ func NewPairFromString(currencyPair string) Pair { return NewPairFromStrings(currencyPair[0:3], currencyPair[3:]) } +// NewPairFromFormattedPairs matches a supplied currency pair to a list of pairs +// with a specific format. This is helpful for exchanges which +// provide currency pairs with no delimiter so we can match it with a list and +// apply the same format +func NewPairFromFormattedPairs(currencyPair string, pairs Pairs, pairFmt PairFormat) Pair { + for x := range pairs { + if strings.EqualFold(pairs[x].Format(pairFmt.Delimiter, + pairFmt.Uppercase).String(), currencyPair) { + return pairs[x] + } + } + return NewPairFromString(currencyPair) +} + // Pair holds currency pair information type Pair struct { Delimiter string `json:"delimiter"` @@ -133,7 +147,8 @@ func (p Pair) Format(delimiter string, uppercase bool) Pair { // Equal compares two currency pairs and returns whether or not they are equal func (p Pair) Equal(cPair Pair) bool { - return p.Base.Item == cPair.Base.Item && p.Quote.Item == cPair.Quote.Item + return strings.EqualFold(p.Base.String(), cPair.Base.String()) && + strings.EqualFold(p.Quote.String(), cPair.Quote.String()) } // EqualIncludeReciprocal compares two currency pairs and returns whether or not diff --git a/currency/pair_test.go b/currency/pair_test.go index 8c46fd44..07e9f789 100644 --- a/currency/pair_test.go +++ b/currency/pair_test.go @@ -407,6 +407,30 @@ func TestNewPairFromString(t *testing.T) { } } +func TestNewPairFromFormattedPairs(t *testing.T) { + t.Parallel() + pairs := Pairs{ + NewPairDelimiter("BTC-USDT", "-"), + NewPairDelimiter("LTC-USD", "-"), + } + + p := NewPairFromFormattedPairs("BTCUSDT", pairs, PairFormat{Uppercase: true}) + if p.String() != "BTC-USDT" { + t.Error("Test failed. TestNewPairFromFormattedPairs: Expected currency was not found") + } + + p = NewPairFromFormattedPairs("btcusdt", pairs, PairFormat{Uppercase: false}) + if p.String() != "BTC-USDT" { + t.Error("Test failed. TestNewPairFromFormattedPairs: Expected currency was not found") + } + + // Now a wrong one, will default to NewPairFromString + p = NewPairFromFormattedPairs("ethusdt", pairs, PairFormat{}) + if p.String() != "ethusdt" && p.Base.String() != "eth" { + t.Error("Test failed. TestNewPairFromFormattedPairs: Expected currency was not found") + } +} + func TestContainsCurrency(t *testing.T) { p := NewPair(BTC, USD) diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index 78bfc660..30718199 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -167,29 +167,29 @@ type KlineStream struct { // TickerStream holds the ticker stream data type TickerStream struct { - EventType string `json:"e"` - EventTime int64 `json:"E"` - Symbol currency.Pair `json:"s"` - PriceChange float64 `json:"p,string"` - PriceChangePercent float64 `json:"P,string"` - WeightedAvgPrice float64 `json:"w,string"` - ClosePrice float64 `json:"x,string"` - LastPrice float64 `json:"c,string"` - LastPriceQuantity float64 `json:"Q,string"` - BestBidPrice float64 `json:"b,string"` - BestBidQuantity float64 `json:"B,string"` - BestAskPrice float64 `json:"a,string"` - BestAskQuantity float64 `json:"A,string"` - OpenPrice float64 `json:"o,string"` - HighPrice float64 `json:"h,string"` - LowPrice float64 `json:"l,string"` - TotalTradedVolume float64 `json:"v,string"` - TotalTradedQuoteVolume float64 `json:"q,string"` - OpenTime int64 `json:"O"` - CloseTime int64 `json:"C"` - FirstTradeID int64 `json:"F"` - LastTradeID int64 `json:"L"` - NumberOfTrades int64 `json:"n"` + EventType string `json:"e"` + EventTime int64 `json:"E"` + Symbol string `json:"s"` + PriceChange float64 `json:"p,string"` + PriceChangePercent float64 `json:"P,string"` + WeightedAvgPrice float64 `json:"w,string"` + ClosePrice float64 `json:"x,string"` + LastPrice float64 `json:"c,string"` + LastPriceQuantity float64 `json:"Q,string"` + BestBidPrice float64 `json:"b,string"` + BestBidQuantity float64 `json:"B,string"` + BestAskPrice float64 `json:"a,string"` + BestAskQuantity float64 `json:"A,string"` + OpenPrice float64 `json:"o,string"` + HighPrice float64 `json:"h,string"` + LowPrice float64 `json:"l,string"` + TotalTradedVolume float64 `json:"v,string"` + TotalTradedQuoteVolume float64 `json:"q,string"` + OpenTime int64 `json:"O"` + CloseTime int64 `json:"C"` + FirstTradeID int64 `json:"F"` + LastTradeID int64 `json:"L"` + NumberOfTrades int64 `json:"n"` } // HistoricalTrade holds recent trade data @@ -239,25 +239,25 @@ type AveragePrice struct { // PriceChangeStats contains statistics for the last 24 hours trade type PriceChangeStats struct { - Symbol currency.Pair `json:"symbol"` - PriceChange float64 `json:"priceChange,string"` - PriceChangePercent float64 `json:"priceChangePercent,string"` - WeightedAvgPrice float64 `json:"weightedAvgPrice,string"` - PrevClosePrice float64 `json:"prevClosePrice,string"` - LastPrice float64 `json:"lastPrice,string"` - LastQty float64 `json:"lastQty,string"` - BidPrice float64 `json:"bidPrice,string"` - AskPrice float64 `json:"askPrice,string"` - OpenPrice float64 `json:"openPrice,string"` - HighPrice float64 `json:"highPrice,string"` - LowPrice float64 `json:"lowPrice,string"` - Volume float64 `json:"volume,string"` - QuoteVolume float64 `json:"quoteVolume,string"` - OpenTime int64 `json:"openTime"` - CloseTime int64 `json:"closeTime"` - FirstID int64 `json:"fristId"` - LastID int64 `json:"lastId"` - Count int64 `json:"count"` + Symbol string `json:"symbol"` + PriceChange float64 `json:"priceChange,string"` + PriceChangePercent float64 `json:"priceChangePercent,string"` + WeightedAvgPrice float64 `json:"weightedAvgPrice,string"` + PrevClosePrice float64 `json:"prevClosePrice,string"` + LastPrice float64 `json:"lastPrice,string"` + LastQty float64 `json:"lastQty,string"` + BidPrice float64 `json:"bidPrice,string"` + AskPrice float64 `json:"askPrice,string"` + OpenPrice float64 `json:"openPrice,string"` + HighPrice float64 `json:"highPrice,string"` + LowPrice float64 `json:"lowPrice,string"` + Volume float64 `json:"volume,string"` + QuoteVolume float64 `json:"quoteVolume,string"` + OpenTime int64 `json:"openTime"` + CloseTime int64 `json:"closeTime"` + FirstID int64 `json:"fristId"` + LastID int64 `json:"lastId"` + Count int64 `json:"count"` } // SymbolPrice holds basic symbol price diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index 32108435..e8b24c3b 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -128,13 +128,14 @@ func (b *Binance) WsHandleData() { } b.Websocket.DataHandler <- wshandler.TradeData{ - CurrencyPair: currency.NewPairFromString(trade.Symbol), - Timestamp: time.Unix(0, trade.TimeStamp), - Price: price, - Amount: amount, - Exchange: b.GetName(), - AssetType: asset.Spot, - Side: trade.EventType, + CurrencyPair: currency.NewPairFromFormattedPairs(trade.Symbol, b.GetEnabledPairs(asset.Spot), + b.GetPairFormat(asset.Spot, true)), + Timestamp: time.Unix(0, trade.TimeStamp), + Price: price, + Amount: amount, + Exchange: b.GetName(), + AssetType: asset.Spot, + Side: trade.EventType, } continue case "ticker": @@ -160,7 +161,8 @@ func (b *Binance) WsHandleData() { Last: t.LastPrice, Timestamp: time.Unix(0, t.EventTime), AssetType: asset.Spot, - Pair: t.Symbol, + Pair: currency.NewPairFromFormattedPairs(t.Symbol, b.GetEnabledPairs(asset.Spot), + b.GetPairFormat(asset.Spot, true)), } continue @@ -176,7 +178,8 @@ func (b *Binance) WsHandleData() { var wsKline wshandler.KlineData wsKline.Timestamp = time.Unix(0, kline.EventTime) - wsKline.Pair = currency.NewPairFromString(kline.Symbol) + wsKline.Pair = currency.NewPairFromFormattedPairs(kline.Symbol, b.GetEnabledPairs(asset.Spot), + b.GetPairFormat(asset.Spot, true)) wsKline.AssetType = asset.Spot wsKline.Exchange = b.GetName() wsKline.StartTime = time.Unix(0, kline.Kline.StartTime) @@ -207,7 +210,8 @@ func (b *Binance) WsHandleData() { continue } - currencyPair := currency.NewPairFromString(depth.Pair) + currencyPair := currency.NewPairFromFormattedPairs(depth.Pair, b.GetEnabledPairs(asset.Spot), + b.GetPairFormat(asset.Spot, true)) b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: currencyPair, Asset: asset.Spot, @@ -222,10 +226,9 @@ func (b *Binance) WsHandleData() { // SeedLocalCache seeds depth data func (b *Binance) SeedLocalCache(p currency.Pair) error { var newOrderBook orderbook.Base - formattedPair := b.FormatExchangeCurrency(p, asset.Spot) orderbookNew, err := b.GetOrderBook( OrderBookDataRequestParams{ - Symbol: formattedPair.String(), + Symbol: b.FormatExchangeCurrency(p, asset.Spot).String(), Limit: 1000, }) if err != nil { @@ -242,7 +245,7 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error { } newOrderBook.LastUpdated = time.Unix(orderbookNew.LastUpdateID, 0) - newOrderBook.Pair = currency.NewPairFromString(formattedPair.String()) + newOrderBook.Pair = p newOrderBook.AssetType = asset.Spot return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false) @@ -276,7 +279,8 @@ func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error { } updateAsk = append(updateAsk, priceToBeUpdated) } - currencyPair := currency.NewPairFromString(wsdp.Pair) + currencyPair := currency.NewPairFromFormattedPairs(wsdp.Pair, b.GetEnabledPairs(asset.Spot), + b.GetPairFormat(asset.Spot, true)) return b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ Bids: updateBid, diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index b046889d..707b9858 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -167,7 +167,7 @@ func (b *Binance) Run() { !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), b.GetPairFormat(asset.Spot, false).Delimiter) { enabledPairs := currency.NewPairsFromStrings([]string{fmt.Sprintf("BTC%vUSDT", b.GetPairFormat(asset.Spot, false).Delimiter)}) log.Warn(log.ExchangeSys, - "Available pairs for Binance reset due to config upgrade, please enable the ones you would like again") + "Available pairs for Binance reset due to config upgrade, please enable the ones you would like to use again") forceUpdate = true err := b.UpdatePairs(enabledPairs, asset.Spot, true, true) @@ -227,7 +227,8 @@ func (b *Binance) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr pairs := b.GetEnabledPairs(assetType) for i := range pairs { for y := range tick { - if !tick[y].Symbol.Equal(pairs[i]) { + pairFmt := b.FormatExchangeCurrency(pairs[i], assetType).String() + if tick[y].Symbol != pairFmt { continue } tickerPrice := ticker.Price{ diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 7503304f..557553e5 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -562,7 +562,8 @@ func (b *Bitfinex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscr req["event"] = "subscribe" req["channel"] = channelToSubscribe.Channel if channelToSubscribe.Currency.String() != "" { - req["pair"] = channelToSubscribe.Currency.String() + req["pair"] = b.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String() } if len(channelToSubscribe.Params) > 0 { for k, v := range channelToSubscribe.Params { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 560211e0..40064905 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -62,6 +62,7 @@ func (c *CoinbasePro) SetDefaults() { Uppercase: true, }, ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", Uppercase: true, }, } @@ -162,11 +163,25 @@ func (c *CoinbasePro) Run() { c.PrintEnabledPairs() } - if !c.GetEnabledFeatures().AutoPairUpdates { + forceUpdate := false + if !common.StringDataContains(c.GetEnabledPairs(asset.Spot).Strings(), c.GetPairFormat(asset.Spot, false).Delimiter) || + !common.StringDataContains(c.GetAvailablePairs(asset.Spot).Strings(), c.GetPairFormat(asset.Spot, false).Delimiter) { + enabledPairs := currency.NewPairsFromStrings([]string{fmt.Sprintf("BTC%vUSD", c.GetPairFormat(asset.Spot, false).Delimiter)}) + log.Warn(log.ExchangeSys, + "Enabled pairs for CoinbasePro reset due to config upgrade, please enable the ones you would like to use again") + forceUpdate = true + + err := c.UpdatePairs(enabledPairs, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", c.Name, err) + } + } + + if !c.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { return } - err := c.UpdateTradablePairs(false) + err := c.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", c.Name, err) } @@ -181,7 +196,8 @@ func (c *CoinbasePro) FetchTradablePairs(asset asset.Item) ([]string, error) { var products []string for x := range pairs { - products = append(products, pairs[x].BaseCurrency+pairs[x].QuoteCurrency) + products = append(products, fmt.Sprintf("%s%s%s", pairs[x].BaseCurrency, + c.GetPairFormat(asset, false).Delimiter, pairs[x].QuoteCurrency)) } return products, nil diff --git a/exchanges/exchange.go b/exchanges/exchange.go index f247b151..d3b8d9a8 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -262,7 +262,6 @@ func (e *Base) SetCurrencyPairFormat() { if e.Config.CurrencyPairs.RequestFormat == nil { e.Config.CurrencyPairs.RequestFormat = e.CurrencyPairs.RequestFormat } - return } diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 0d8ad76e..15ebcdf0 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -338,7 +338,8 @@ func (g *Gateio) GenerateDefaultSubscriptions() { // Subscribe sends a websocket message to receive data from the channel func (g *Gateio) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - params := []interface{}{channelToSubscribe.Currency.String()} + params := []interface{}{g.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String()} for i := range channelToSubscribe.Params { params = append(params, channelToSubscribe.Params[i]) } @@ -370,7 +371,8 @@ func (g *Gateio) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscr subscribe := WebsocketRequest{ ID: g.WebsocketConn.GenerateMessageID(true), Method: unsbuscribeText, - Params: []interface{}{channelToSubscribe.Currency.String(), 1800}, + Params: []interface{}{g.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String(), 1800}, } resp, err := g.WebsocketConn.SendMessageReturnResponse(subscribe.ID, subscribe) if err != nil { diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index 71482667..eca699be 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -97,6 +98,22 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } } +func TestUpdateTicker(t *testing.T) { + h.SetDefaults() + TestSetup(t) + + h.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTC-USD", "XRP-USD"}), true) + _, err := h.UpdateTicker(currency.NewPair(currency.BTC, currency.USD), asset.Spot) + if err != nil { + t.Error(err) + } + + _, err = h.FetchTicker(currency.NewPair(currency.XRP, currency.USD), asset.Spot) + if err != nil { + t.Error(err) + } +} + func TestGetAllTickers(t *testing.T) { _, err := h.GetTickers() if err != nil { diff --git a/exchanges/hitbtc/hitbtc_types.go b/exchanges/hitbtc/hitbtc_types.go index 131ee00f..1b38ac32 100644 --- a/exchanges/hitbtc/hitbtc_types.go +++ b/exchanges/hitbtc/hitbtc_types.go @@ -8,16 +8,16 @@ import ( // TickerResponse is the response type type TickerResponse struct { - Ask float64 `json:"ask,string"` - Bid float64 `json:"bid,string"` - High float64 `json:"high,string"` - Last float64 `json:"last,string"` - Low float64 `json:"low,string"` - Open float64 `json:"open,string"` - Volume float64 `json:"volume,string"` - VolumeQuote float64 `json:"volumeQuote,string"` - Symbol currency.Pair `json:"symbol"` - Timestamp time.Time `json:"timestamp"` + Ask float64 `json:"ask,string"` + Bid float64 `json:"bid,string"` + High float64 `json:"high,string"` + Last float64 `json:"last,string"` + Low float64 `json:"low,string"` + Open float64 `json:"open,string"` + Volume float64 `json:"volume,string"` + VolumeQuote float64 `json:"volumeQuote,string"` + Symbol string `json:"symbol"` + Timestamp time.Time `json:"timestamp"` } // Symbol holds symbol data @@ -322,16 +322,16 @@ type params struct { // WsTicker defines websocket ticker feed return params type WsTicker struct { Params struct { - Ask float64 `json:"ask,string"` - Bid float64 `json:"bid,string"` - Last float64 `json:"last,string"` - Open float64 `json:"open,string"` - Low float64 `json:"low,string"` - High float64 `json:"high,string"` - Volume float64 `json:"volume,string"` - VolumeQuote float64 `json:"volumeQuote,string"` - Timestamp string `json:"timestamp"` - Symbol currency.Pair `json:"symbol"` + Ask float64 `json:"ask,string"` + Bid float64 `json:"bid,string"` + Last float64 `json:"last,string"` + Open float64 `json:"open,string"` + Low float64 `json:"low,string"` + High float64 `json:"high,string"` + Volume float64 `json:"volume,string"` + VolumeQuote float64 `json:"volumeQuote,string"` + Timestamp string `json:"timestamp"` + Symbol string `json:"symbol"` } `json:"params"` } @@ -387,20 +387,20 @@ type WsActiveOrdersResponse struct { // WsActiveOrdersResponseData Active order data for WsActiveOrdersResponse type WsActiveOrdersResponseData struct { - ID string `json:"id"` - ClientOrderID string `json:"clientOrderId,omitempty"` - Symbol currency.Pair `json:"symbol"` - Side string `json:"side"` - Status string `json:"status"` - Type string `json:"type"` - TimeInForce string `json:"timeInForce"` - Quantity float64 `json:"quantity,string"` - Price float64 `json:"price,string"` - CumQuantity float64 `json:"cumQuantity,string"` - PostOnly bool `json:"postOnly"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - ReportType string `json:"reportType"` + ID string `json:"id"` + ClientOrderID string `json:"clientOrderId,omitempty"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Status string `json:"status"` + Type string `json:"type"` + TimeInForce string `json:"timeInForce"` + Quantity float64 `json:"quantity,string"` + Price float64 `json:"price,string"` + CumQuantity float64 `json:"cumQuantity,string"` + PostOnly bool `json:"postOnly"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + ReportType string `json:"reportType"` } // WsReportResponse report response for auth subscription to reports @@ -411,24 +411,24 @@ type WsReportResponse struct { // WsReportResponseData Report data for WsReportResponse type WsReportResponseData struct { - ID string `json:"id"` - ClientOrderID string `json:"clientOrderId,omitempty"` - Symbol currency.Pair `json:"symbol"` - Side string `json:"side"` - Status string `json:"status"` - Type string `json:"type"` - TimeInForce string `json:"timeInForce"` - Quantity float64 `json:"quantity,string"` - Price float64 `json:"price,string"` - CumQuantity float64 `json:"cumQuantity,string"` - PostOnly bool `json:"postOnly"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - ReportType string `json:"reportType"` - TradeQuantity float64 `json:"tradeQuantity,string"` - TradePrice float64 `json:"tradePrice,string"` - TradeID int64 `json:"tradeId"` - TradeFee float64 `json:"tradeFee,string"` + ID string `json:"id"` + ClientOrderID string `json:"clientOrderId,omitempty"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Status string `json:"status"` + Type string `json:"type"` + TimeInForce string `json:"timeInForce"` + Quantity float64 `json:"quantity,string"` + Price float64 `json:"price,string"` + CumQuantity float64 `json:"cumQuantity,string"` + PostOnly bool `json:"postOnly"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + ReportType string `json:"reportType"` + TradeQuantity float64 `json:"tradeQuantity,string"` + TradePrice float64 `json:"tradePrice,string"` + TradeID int64 `json:"tradeId"` + TradeFee float64 `json:"tradeFee,string"` } // WsSubmitOrderRequest WS request @@ -440,11 +440,11 @@ type WsSubmitOrderRequest struct { // WsSubmitOrderRequestData WS request data type WsSubmitOrderRequestData struct { - ClientOrderID int64 `json:"clientOrderId,string,omitempty"` - Symbol currency.Pair `json:"symbol"` - Side string `json:"side"` - Price float64 `json:"price,string"` - Quantity float64 `json:"quantity,string"` + ClientOrderID int64 `json:"clientOrderId,string,omitempty"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Price float64 `json:"price,string"` + Quantity float64 `json:"quantity,string"` } // WsSubmitOrderSuccessResponse WS response @@ -494,20 +494,20 @@ type WsCancelOrderResponse struct { // WsCancelOrderResponseData WS response data type WsCancelOrderResponseData struct { - ID string `json:"id"` - ClientOrderID string `json:"clientOrderId,omitempty"` - Symbol currency.Pair `json:"symbol"` - Side string `json:"side"` - Status string `json:"status"` - Type string `json:"type"` - TimeInForce string `json:"timeInForce"` - Quantity float64 `json:"quantity,string"` - Price float64 `json:"price,string"` - CumQuantity float64 `json:"cumQuantity,string"` - PostOnly bool `json:"postOnly"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - ReportType string `json:"reportType"` + ID string `json:"id"` + ClientOrderID string `json:"clientOrderId,omitempty"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Status string `json:"status"` + Type string `json:"type"` + TimeInForce string `json:"timeInForce"` + Quantity float64 `json:"quantity,string"` + Price float64 `json:"price,string"` + CumQuantity float64 `json:"cumQuantity,string"` + PostOnly bool `json:"postOnly"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + ReportType string `json:"reportType"` } // WsReplaceOrderResponse WS response @@ -519,21 +519,21 @@ type WsReplaceOrderResponse struct { // WsReplaceOrderResponseData WS response data type WsReplaceOrderResponseData struct { - ID string `json:"id"` - ClientOrderID string `json:"clientOrderId,omitempty"` - Symbol currency.Pair `json:"symbol"` - Side string `json:"side"` - Status string `json:"status"` - Type string `json:"type"` - TimeInForce string `json:"timeInForce"` - Quantity float64 `json:"quantity,string"` - Price float64 `json:"price,string"` - CumQuantity float64 `json:"cumQuantity,string"` - PostOnly bool `json:"postOnly"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - ReportType string `json:"reportType"` - OriginalRequestClientOrderID string `json:"originalRequestClientOrderId"` + ID string `json:"id"` + ClientOrderID string `json:"clientOrderId,omitempty"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Status string `json:"status"` + Type string `json:"type"` + TimeInForce string `json:"timeInForce"` + Quantity float64 `json:"quantity,string"` + Price float64 `json:"price,string"` + CumQuantity float64 `json:"cumQuantity,string"` + PostOnly bool `json:"postOnly"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + ReportType string `json:"reportType"` + OriginalRequestClientOrderID string `json:"originalRequestClientOrderId"` } // WsGetActiveOrdersResponse WS response @@ -545,21 +545,21 @@ type WsGetActiveOrdersResponse struct { // WsGetActiveOrdersResponseData WS response data type WsGetActiveOrdersResponseData struct { - ID string `json:"id"` - ClientOrderID string `json:"clientOrderId,omitempty"` - Symbol currency.Pair `json:"symbol"` - Side string `json:"side"` - Status string `json:"status"` - Type string `json:"type"` - TimeInForce string `json:"timeInForce"` - Quantity float64 `json:"quantity,string"` - Price float64 `json:"price,string"` - CumQuantity float64 `json:"cumQuantity,string"` - PostOnly bool `json:"postOnly"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - ReportType string `json:"reportType"` - OriginalRequestClientOrderID string `json:"originalRequestClientOrderId"` + ID string `json:"id"` + ClientOrderID string `json:"clientOrderId,omitempty"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Status string `json:"status"` + Type string `json:"type"` + TimeInForce string `json:"timeInForce"` + Quantity float64 `json:"quantity,string"` + Price float64 `json:"price,string"` + CumQuantity float64 `json:"cumQuantity,string"` + PostOnly bool `json:"postOnly"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + ReportType string `json:"reportType"` + OriginalRequestClientOrderID string `json:"originalRequestClientOrderId"` } // WsGetTradingBalanceResponse WS response @@ -646,7 +646,7 @@ type WsGetSymbolsRequest struct { // WsGetSymbolsRequestParameters request parameters type WsGetSymbolsRequestParameters struct { - Symbol currency.Pair `json:"symbol"` + Symbol string `json:"symbol"` } // WsGetSymbolsResponse symbol response @@ -658,7 +658,7 @@ type WsGetSymbolsResponse struct { // WsGetSymbolsResponseData symbol response data type WsGetSymbolsResponseData struct { - ID currency.Pair `json:"id"` + ID string `json:"id"` BaseCurrency currency.Code `json:"baseCurrency"` QuoteCurrency currency.Code `json:"quoteCurrency"` QuantityIncrement float64 `json:"quantityIncrement,string"` @@ -677,10 +677,10 @@ type WsGetTradesRequest struct { // WsGetTradesRequestParameters trade request params type WsGetTradesRequestParameters struct { - Symbol currency.Pair `json:"symbol"` - Limit int64 `json:"limit"` - Sort string `json:"sort"` - By string `json:"by"` + Symbol string `json:"symbol"` + Limit int64 `json:"limit"` + Sort string `json:"sort"` + By string `json:"by"` } // WsGetTradesResponse response diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index d979998e..4e058a05 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -127,7 +127,8 @@ func (h *HitBTC) handleSubscriptionUpdates(resp wshandler.WebsocketResponse, ini Last: ticker.Params.Last, Timestamp: ts, AssetType: asset.Spot, - Pair: ticker.Params.Symbol, + Pair: currency.NewPairFromFormattedPairs(ticker.Params.Symbol, + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), } case "snapshotOrderbook": var obSnapshot WsOrderbook @@ -241,7 +242,8 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { asks = append(asks, orderbook.Item{Amount: ob.Params.Ask[i].Size, Price: ob.Params.Ask[i].Price}) } - p := currency.NewPairFromString(ob.Params.Symbol) + p := currency.NewPairFromFormattedPairs(ob.Params.Symbol, + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)) var newOrderBook orderbook.Base newOrderBook.Asks = asks @@ -278,7 +280,8 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error { asks = append(asks, orderbook.Item{Price: update.Params.Ask[i].Price, Amount: update.Params.Ask[i].Size}) } - p := currency.NewPairFromString(update.Params.Symbol) + p := currency.NewPairFromFormattedPairs(update.Params.Symbol, + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)) err := h.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ Asks: asks, Bids: bids, @@ -327,17 +330,20 @@ func (h *HitBTC) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscrip } if channelToSubscribe.Currency.String() != "" { subscribe.Params = params{ - Symbol: channelToSubscribe.Currency.String(), + Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String(), } } if strings.EqualFold(channelToSubscribe.Channel, "subscribeTrades") { subscribe.Params = params{ - Symbol: channelToSubscribe.Currency.String(), - Limit: 100, + Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String(), + Limit: 100, } } else if strings.EqualFold(channelToSubscribe.Channel, "subscribeCandles") { subscribe.Params = params{ - Symbol: channelToSubscribe.Currency.String(), + Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String(), Period: "M30", Limit: 100, } @@ -353,17 +359,20 @@ func (h *HitBTC) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscr JSONRPCVersion: rpcVersion, Method: unsubscribeChannel, Params: params{ - Symbol: channelToSubscribe.Currency.String(), + Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String(), }, } if strings.EqualFold(unsubscribeChannel, "unsubscribeTrades") { subscribe.Params = params{ - Symbol: channelToSubscribe.Currency.String(), - Limit: 100, + Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String(), + Limit: 100, } } else if strings.EqualFold(unsubscribeChannel, "unsubscribeCandles") { subscribe.Params = params{ - Symbol: channelToSubscribe.Currency.String(), + Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String(), Period: "M30", Limit: 100, } @@ -408,7 +417,7 @@ func (h *HitBTC) wsPlaceOrder(pair currency.Pair, side string, price, quantity f Method: "newOrder", Params: WsSubmitOrderRequestData{ ClientOrderID: id, - Symbol: pair, + Symbol: h.FormatExchangeCurrency(pair, asset.Spot).String(), Side: strings.ToLower(side), Price: price, Quantity: quantity, @@ -566,7 +575,7 @@ func (h *HitBTC) wsGetSymbols(currencyItem currency.Pair) (*WsGetSymbolsResponse request := WsGetSymbolsRequest{ Method: "getSymbol", Params: WsGetSymbolsRequestParameters{ - Symbol: currencyItem, + Symbol: h.FormatExchangeCurrency(currencyItem, asset.Spot).String(), }, ID: h.WebsocketConn.GenerateMessageID(false), } @@ -590,7 +599,7 @@ func (h *HitBTC) wsGetTrades(currencyItem currency.Pair, limit int64, sort, by s request := WsGetTradesRequest{ Method: "getTrades", Params: WsGetTradesRequestParameters{ - Symbol: currencyItem, + Symbol: h.FormatExchangeCurrency(currencyItem, asset.Spot).String(), Limit: limit, Sort: sort, By: by, diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 0a3ec744..a7cc2534 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -196,7 +196,8 @@ func (h *HitBTC) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range symbols { - pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) + pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, + h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) } return pairs, nil } @@ -222,8 +223,17 @@ func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType asset.Item) pairs := h.GetEnabledPairs(assetType) for i := range pairs { for j := range tick { - if !tick[j].Symbol.Equal(pairs[i]) { - continue + pairFmt := h.FormatExchangeCurrency(pairs[i], assetType).String() + if tick[j].Symbol != pairFmt { + found := false + if strings.Contains(tick[j].Symbol, "USDT") { + if pairFmt == tick[j].Symbol[0:len(tick[j].Symbol)-1] { + found = true + } + } + if !found { + continue + } } tickerPrice := ticker.Price{ Last: tick[j].Last, diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index d40fe90e..7dd12b22 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -1,7 +1,5 @@ package huobi -import "github.com/thrasher-corp/gocryptotrader/currency" - // Response stores the Huobi response information type Response struct { Status string `json:"status"` @@ -49,14 +47,14 @@ type Tickers struct { // Ticker latest ticker data type Ticker struct { - Amount float64 `json:"amount"` - Close float64 `json:"close"` - Count int64 `json:"count"` - High float64 `json:"high"` - Low float64 `json:"low"` - Open float64 `json:"open"` - Symbol currency.Pair `json:"symbol"` - Volume float64 `json:"vol"` + Amount float64 `json:"amount"` + Close float64 `json:"close"` + Count int64 `json:"count"` + High float64 `json:"high"` + Low float64 `json:"low"` + Open float64 `json:"open"` + Symbol string `json:"symbol"` + Volume float64 `json:"vol"` } // OrderBookDataRequestParamsType var for request param types @@ -118,11 +116,17 @@ type Detail struct { // Symbol stores the symbol data type Symbol struct { - BaseCurrency string `json:"base-currency"` - QuoteCurrency string `json:"quote-currency"` - PricePrecision int `json:"price-precision"` - AmountPrecision int `json:"amount-precision"` - SymbolPartition string `json:"symbol-partition"` + BaseCurrency string `json:"base-currency"` + QuoteCurrency string `json:"quote-currency"` + PricePrecision int `json:"price-precision"` + AmountPrecision int `json:"amount-precision"` + SymbolPartition string `json:"symbol-partition"` + Innovation string `json:"innovation"` + State string `json:"state"` + ValuePrecision int `json:"value-precision"` + MinimumOrderAmount float64 `json:"min-order-amt"` + MaximumOrderAmount float64 `json:"max-order-amt"` + MinimumOrderValue float64 `json:"min-order-value"` } // Account stores the account data @@ -339,6 +343,7 @@ type WsKline struct { } } +// WsTick stores websocket ticker data type WsTick struct { Channel string `json:"ch"` Timestamp int64 `json:"ts"` @@ -403,15 +408,15 @@ type WsAuthenticatedSubscriptionRequest struct { // WsAuthenticatedAccountsListRequest request for account list authenticated connection type WsAuthenticatedAccountsListRequest struct { - Op string `json:"op"` - AccessKeyID string `json:"AccessKeyId"` - SignatureMethod string `json:"SignatureMethod"` - SignatureVersion string `json:"SignatureVersion"` - Timestamp string `json:"Timestamp"` - Signature string `json:"Signature"` - Topic string `json:"topic"` - Symbol currency.Pair `json:"symbol"` - ClientID int64 `json:"cid,string,omitempty"` + Op string `json:"op"` + AccessKeyID string `json:"AccessKeyId"` + SignatureMethod string `json:"SignatureMethod"` + SignatureVersion string `json:"SignatureVersion"` + Timestamp string `json:"Timestamp"` + Signature string `json:"Signature"` + Topic string `json:"topic"` + Symbol string `json:"symbol"` + ClientID int64 `json:"cid,string,omitempty"` } // WsAuthenticatedOrderDetailsRequest request for order details authenticated connection @@ -429,17 +434,17 @@ type WsAuthenticatedOrderDetailsRequest struct { // WsAuthenticatedOrdersListRequest request for orderslist authenticated connection type WsAuthenticatedOrdersListRequest struct { - Op string `json:"op"` - AccessKeyID string `json:"AccessKeyId"` - SignatureMethod string `json:"SignatureMethod"` - SignatureVersion string `json:"SignatureVersion"` - Timestamp string `json:"Timestamp"` - Signature string `json:"Signature"` - Topic string `json:"topic"` - States string `json:"states"` - AccountID int64 `json:"account-id"` - Symbol currency.Pair `json:"symbol"` - ClientID int64 `json:"cid,string,omitempty"` + Op string `json:"op"` + AccessKeyID string `json:"AccessKeyId"` + SignatureMethod string `json:"SignatureMethod"` + SignatureVersion string `json:"SignatureVersion"` + Timestamp string `json:"Timestamp"` + Signature string `json:"Signature"` + Topic string `json:"topic"` + States string `json:"states"` + AccountID int64 `json:"account-id"` + Symbol string `json:"symbol"` + ClientID int64 `json:"cid,string,omitempty"` } // WsAuthenticatedDataResponse response from authenticated connection @@ -481,15 +486,15 @@ type WsAuthenticatedOrdersUpdateResponse struct { // WsAuthenticatedOrdersUpdateResponseData order updatedata type WsAuthenticatedOrdersUpdateResponseData struct { - UnfilledAmount float64 `json:"unfilled-amount,string"` - FilledAmount float64 `json:"filled-amount,string"` - Price float64 `json:"price,string"` - OrderID int64 `json:"order-id"` - Symbol currency.Pair `json:"symbol"` - MatchID int64 `json:"match-id"` - FilledCashAmount float64 `json:"filled-cash-amount,string"` - Role string `json:"role"` - OrderState string `json:"order-state"` + UnfilledAmount float64 `json:"unfilled-amount,string"` + FilledAmount float64 `json:"filled-amount,string"` + Price float64 `json:"price,string"` + OrderID int64 `json:"order-id"` + Symbol string `json:"symbol"` + MatchID int64 `json:"match-id"` + FilledCashAmount float64 `json:"filled-cash-amount,string"` + Role string `json:"role"` + OrderState string `json:"order-state"` } // WsAuthenticatedOrdersResponse response from Orders authenticated subscription @@ -500,22 +505,22 @@ type WsAuthenticatedOrdersResponse struct { // WsAuthenticatedOrdersResponseData order data type WsAuthenticatedOrdersResponseData struct { - SeqID int64 `json:"seq-id"` - OrderID int64 `json:"order-id"` - Symbol currency.Pair `json:"symbol"` - AccountID int64 `json:"account-id"` - OrderAmount float64 `json:"order-amount,string"` - OrderPrice float64 `json:"order-price,string"` - CreatedAt int64 `json:"created-at"` - OrderType string `json:"order-type"` - OrderSource string `json:"order-source"` - OrderState string `json:"order-state"` - Role string `json:"role"` - Price float64 `json:"price,string"` - FilledAmount float64 `json:"filled-amount,string"` - UnfilledAmount float64 `json:"unfilled-amount,string"` - FilledCashAmount float64 `json:"filled-cash-amount,string"` - FilledFees float64 `json:"filled-fees,string"` + SeqID int64 `json:"seq-id"` + OrderID int64 `json:"order-id"` + Symbol string `json:"symbol"` + AccountID int64 `json:"account-id"` + OrderAmount float64 `json:"order-amount,string"` + OrderPrice float64 `json:"order-price,string"` + CreatedAt int64 `json:"created-at"` + OrderType string `json:"order-type"` + OrderSource string `json:"order-source"` + OrderState string `json:"order-state"` + Role string `json:"role"` + Price float64 `json:"price,string"` + FilledAmount float64 `json:"filled-amount,string"` + UnfilledAmount float64 `json:"unfilled-amount,string"` + FilledCashAmount float64 `json:"filled-cash-amount,string"` + FilledFees float64 `json:"filled-fees,string"` } // WsAuthenticatedAccountsListResponse response from AccountsList authenticated endpoint @@ -547,20 +552,20 @@ type WsAuthenticatedOrdersListResponse struct { // WsAuthenticatedOrdersListResponseData contains order details type WsAuthenticatedOrdersListResponseData struct { - ID int64 `json:"id"` - Symbol currency.Pair `json:"symbol"` - AccountID int64 `json:"account-id"` - Amount float64 `json:"amount,string"` - Price float64 `json:"price,string"` - CreatedAt int64 `json:"created-at"` - Type string `json:"type"` - FilledAmount float64 `json:"filled-amount,string"` - FilledCashAmount float64 `json:"filled-cash-amount,string"` - FilledFees float64 `json:"filled-fees,string"` - FinishedAt int64 `json:"finished-at"` - Source string `json:"source"` - State string `json:"state"` - CanceledAt int64 `json:"canceled-at"` + ID int64 `json:"id"` + Symbol string `json:"symbol"` + AccountID int64 `json:"account-id"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + CreatedAt int64 `json:"created-at"` + Type string `json:"type"` + FilledAmount float64 `json:"filled-amount,string"` + FilledCashAmount float64 `json:"filled-cash-amount,string"` + FilledFees float64 `json:"filled-fees,string"` + FinishedAt int64 `json:"finished-at"` + Source string `json:"source"` + State string `json:"state"` + CanceledAt int64 `json:"canceled-at"` } // WsAuthenticatedOrderDetailResponse response from OrderDetail authenticated endpoint diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index 3bbca6cc..ce063bd5 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -244,10 +244,11 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { } data := strings.Split(kline.Channel, ".") h.Websocket.DataHandler <- wshandler.KlineData{ - Timestamp: time.Unix(0, kline.Timestamp), - Exchange: h.GetName(), - AssetType: asset.Spot, - Pair: currency.NewPairFromString(data[1]), + Timestamp: time.Unix(0, kline.Timestamp), + Exchange: h.GetName(), + AssetType: asset.Spot, + Pair: currency.NewPairFromFormattedPairs(data[1], + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), OpenPrice: kline.Tick.Open, ClosePrice: kline.Tick.Close, HighPrice: kline.Tick.High, @@ -263,10 +264,11 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { } data := strings.Split(trade.Channel, ".") h.Websocket.DataHandler <- wshandler.TradeData{ - Exchange: h.GetName(), - AssetType: asset.Spot, - CurrencyPair: currency.NewPairFromString(data[1]), - Timestamp: time.Unix(0, trade.Tick.Timestamp), + Exchange: h.GetName(), + AssetType: asset.Spot, + CurrencyPair: currency.NewPairFromFormattedPairs(data[1], + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), + Timestamp: time.Unix(0, trade.Tick.Timestamp), } case strings.Contains(init.Channel, "detail"): var ticker WsTick @@ -286,14 +288,16 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { Low: ticker.Tick.Low, Timestamp: time.Unix(0, ticker.Timestamp), AssetType: asset.Spot, - Pair: currency.NewPairFromString(data[1]), + Pair: currency.NewPairFromFormattedPairs(data[1], + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), } } } // WsProcessOrderbook processes new orderbook data func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { - p := currency.NewPairFromString(symbol) + p := currency.NewPairFromFormattedPairs(symbol, + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)) var bids, asks []orderbook.Item for i := 0; i < len(update.Tick.Bids); i++ { bidLevel := update.Tick.Bids[i].([]interface{}) @@ -428,7 +432,7 @@ func (h *HUOBI) wsGetAccountsList(pair currency.Pair) (*WsAuthenticatedAccountsL SignatureVersion: signatureVersion, Timestamp: timestamp, Topic: wsAccountsList, - Symbol: pair, + Symbol: h.FormatExchangeCurrency(pair, asset.Spot).String(), } hmac := h.wsGenerateSignature(timestamp, wsAccountListEndpoint) request.Signature = crypto.Base64Encode(hmac) @@ -455,7 +459,7 @@ func (h *HUOBI) wsGetOrdersList(accountID int64, pair currency.Pair) (*WsAuthent Timestamp: timestamp, Topic: wsOrdersList, AccountID: accountID, - Symbol: pair.Lower(), + Symbol: h.FormatExchangeCurrency(pair, asset.Spot).String(), States: "submitted,partial-filled", } hmac := h.wsGenerateSignature(timestamp, wsOrdersListEndpoint) diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index db57407c..f2678e9e 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -228,7 +228,11 @@ func (h *HUOBI) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range symbols { - pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) + if symbols[x].State != "online" { + continue + } + pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, + h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) } return pairs, nil @@ -255,7 +259,8 @@ func (h *HUOBI) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric pairs := h.GetEnabledPairs(assetType) for i := range pairs { for j := range tickers.Data { - if !pairs[i].Equal(tickers.Data[j].Symbol) { + pairFmt := h.FormatExchangeCurrency(pairs[i], assetType).String() + if pairFmt != tickers.Data[j].Symbol { continue } tickerPrice := ticker.Price{ @@ -264,7 +269,7 @@ func (h *HUOBI) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric Volume: tickers.Data[j].Volume, Open: tickers.Data[j].Open, Close: tickers.Data[j].Close, - Pair: tickers.Data[j].Symbol, + Pair: pairs[i], } err = ticker.ProcessTicker(h.GetName(), &tickerPrice, assetType) if err != nil { diff --git a/exchanges/huobihadax/huobihadax_types.go b/exchanges/huobihadax/huobihadax_types.go index 518ebd9d..40e3bf85 100644 --- a/exchanges/huobihadax/huobihadax_types.go +++ b/exchanges/huobihadax/huobihadax_types.go @@ -1,9 +1,5 @@ package huobihadax -import ( - "github.com/thrasher-corp/gocryptotrader/currency" -) - // Response stores the Huobi response information type Response struct { Status string `json:"status"` @@ -25,6 +21,7 @@ type KlineItem struct { Count int `json:"count"` } +// WsTick stores websocket ticker data type WsTick struct { Channel string `json:"ch"` Timestamp int64 `json:"ts"` @@ -48,14 +45,14 @@ type Tickers struct { // Ticker latest ticker data type Ticker struct { - Amount float64 `json:"amount"` - Close float64 `json:"close"` - Count int64 `json:"count"` - High float64 `json:"high"` - Low float64 `json:"low"` - Open float64 `json:"open"` - Symbol currency.Pair `json:"symbol"` - Volume float64 `json:"vol"` + Amount float64 `json:"amount"` + Close float64 `json:"close"` + Count int64 `json:"count"` + High float64 `json:"high"` + Low float64 `json:"low"` + Open float64 `json:"open"` + Symbol string `json:"symbol"` + Volume float64 `json:"vol"` } // DetailMerged stores the ticker detail merged data @@ -121,6 +118,7 @@ type Symbol struct { PricePrecision int `json:"price-precision"` AmountPrecision int `json:"amount-precision"` SymbolPartition string `json:"symbol-partition"` + Symbol string `json:"innovation"` } // Account stores the account data @@ -396,15 +394,15 @@ type WsAuthenticatedSubscriptionRequest struct { // WsAuthenticatedAccountsListRequest request for account list authenticated connection type WsAuthenticatedAccountsListRequest struct { - Op string `json:"op"` - AccessKeyID string `json:"AccessKeyId"` - SignatureMethod string `json:"SignatureMethod"` - SignatureVersion string `json:"SignatureVersion"` - Timestamp string `json:"Timestamp"` - Signature string `json:"Signature"` - Topic string `json:"topic"` - Symbol currency.Pair `json:"symbol"` - ClientID int64 `json:"cid,string,omitempty"` + Op string `json:"op"` + AccessKeyID string `json:"AccessKeyId"` + SignatureMethod string `json:"SignatureMethod"` + SignatureVersion string `json:"SignatureVersion"` + Timestamp string `json:"Timestamp"` + Signature string `json:"Signature"` + Topic string `json:"topic"` + Symbol string `json:"symbol"` + ClientID int64 `json:"cid,string,omitempty"` } // WsAuthenticatedOrderDetailsRequest request for order details authenticated connection @@ -422,17 +420,17 @@ type WsAuthenticatedOrderDetailsRequest struct { // WsAuthenticatedOrdersListRequest request for orderslist authenticated connection type WsAuthenticatedOrdersListRequest struct { - Op string `json:"op"` - AccessKeyID string `json:"AccessKeyId"` - SignatureMethod string `json:"SignatureMethod"` - SignatureVersion string `json:"SignatureVersion"` - Timestamp string `json:"Timestamp"` - Signature string `json:"Signature"` - Topic string `json:"topic"` - States string `json:"states"` - AccountID int64 `json:"account-id"` - Symbol currency.Pair `json:"symbol"` - ClientID int64 `json:"cid,string,omitempty"` + Op string `json:"op"` + AccessKeyID string `json:"AccessKeyId"` + SignatureMethod string `json:"SignatureMethod"` + SignatureVersion string `json:"SignatureVersion"` + Timestamp string `json:"Timestamp"` + Signature string `json:"Signature"` + Topic string `json:"topic"` + States string `json:"states"` + AccountID int64 `json:"account-id"` + Symbol string `json:"symbol"` + ClientID int64 `json:"cid,string,omitempty"` } // WsAuthenticatedDataResponse response from authenticated connection @@ -474,15 +472,15 @@ type WsAuthenticatedOrdersUpdateResponse struct { // WsAuthenticatedOrdersUpdateResponseData order updatedata type WsAuthenticatedOrdersUpdateResponseData struct { - UnfilledAmount float64 `json:"unfilled-amount,string"` - FilledAmount float64 `json:"filled-amount,string"` - Price float64 `json:"price,string"` - OrderID int64 `json:"order-id"` - Symbol currency.Pair `json:"symbol"` - MatchID int64 `json:"match-id"` - FilledCashAmount float64 `json:"filled-cash-amount,string"` - Role string `json:"role"` - OrderState string `json:"order-state"` + UnfilledAmount float64 `json:"unfilled-amount,string"` + FilledAmount float64 `json:"filled-amount,string"` + Price float64 `json:"price,string"` + OrderID int64 `json:"order-id"` + Symbol string `json:"symbol"` + MatchID int64 `json:"match-id"` + FilledCashAmount float64 `json:"filled-cash-amount,string"` + Role string `json:"role"` + OrderState string `json:"order-state"` } // WsAuthenticatedOrdersResponse response from Orders authenticated subscription @@ -493,22 +491,22 @@ type WsAuthenticatedOrdersResponse struct { // WsAuthenticatedOrdersResponseData order data type WsAuthenticatedOrdersResponseData struct { - SeqID int64 `json:"seq-id"` - OrderID int64 `json:"order-id"` - Symbol currency.Pair `json:"symbol"` - AccountID int64 `json:"account-id"` - OrderAmount float64 `json:"order-amount,string"` - OrderPrice float64 `json:"order-price,string"` - CreatedAt int64 `json:"created-at"` - OrderType string `json:"order-type"` - OrderSource string `json:"order-source"` - OrderState string `json:"order-state"` - Role string `json:"role"` - Price float64 `json:"price,string"` - FilledAmount float64 `json:"filled-amount,string"` - UnfilledAmount float64 `json:"unfilled-amount,string"` - FilledCashAmount float64 `json:"filled-cash-amount,string"` - FilledFees float64 `json:"filled-fees,string"` + SeqID int64 `json:"seq-id"` + OrderID int64 `json:"order-id"` + Symbol string `json:"symbol"` + AccountID int64 `json:"account-id"` + OrderAmount float64 `json:"order-amount,string"` + OrderPrice float64 `json:"order-price,string"` + CreatedAt int64 `json:"created-at"` + OrderType string `json:"order-type"` + OrderSource string `json:"order-source"` + OrderState string `json:"order-state"` + Role string `json:"role"` + Price float64 `json:"price,string"` + FilledAmount float64 `json:"filled-amount,string"` + UnfilledAmount float64 `json:"unfilled-amount,string"` + FilledCashAmount float64 `json:"filled-cash-amount,string"` + FilledFees float64 `json:"filled-fees,string"` } // WsAuthenticatedAccountsListResponse response from AccountsList authenticated endpoint @@ -540,20 +538,20 @@ type WsAuthenticatedOrdersListResponse struct { // WsAuthenticatedOrdersListResponseData contains order details type WsAuthenticatedOrdersListResponseData struct { - ID int64 `json:"id"` - Symbol currency.Pair `json:"symbol"` - AccountID int64 `json:"account-id"` - Amount float64 `json:"amount,string"` - Price float64 `json:"price,string"` - CreatedAt int64 `json:"created-at"` - Type string `json:"type"` - FilledAmount float64 `json:"filled-amount,string"` - FilledCashAmount float64 `json:"filled-cash-amount,string"` - FilledFees float64 `json:"filled-fees,string"` - FinishedAt int64 `json:"finished-at"` - Source string `json:"source"` - State string `json:"state"` - CanceledAt int64 `json:"canceled-at"` + ID int64 `json:"id"` + Symbol string `json:"symbol"` + AccountID int64 `json:"account-id"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + CreatedAt int64 `json:"created-at"` + Type string `json:"type"` + FilledAmount float64 `json:"filled-amount,string"` + FilledCashAmount float64 `json:"filled-cash-amount,string"` + FilledFees float64 `json:"filled-fees,string"` + FinishedAt int64 `json:"finished-at"` + Source string `json:"source"` + State string `json:"state"` + CanceledAt int64 `json:"canceled-at"` } // WsAuthenticatedOrderDetailResponse response from OrderDetail authenticated endpoint diff --git a/exchanges/huobihadax/huobihadax_websocket.go b/exchanges/huobihadax/huobihadax_websocket.go index bf1c803c..da9fef33 100644 --- a/exchanges/huobihadax/huobihadax_websocket.go +++ b/exchanges/huobihadax/huobihadax_websocket.go @@ -245,10 +245,11 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) { } data := strings.Split(kline.Channel, ".") h.Websocket.DataHandler <- wshandler.KlineData{ - Timestamp: time.Unix(0, kline.Timestamp), - Exchange: h.GetName(), - AssetType: asset.Spot, - Pair: currency.NewPairFromString(data[1]), + Timestamp: time.Unix(0, kline.Timestamp), + Exchange: h.GetName(), + AssetType: asset.Spot, + Pair: currency.NewPairFromFormattedPairs(data[1], + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), OpenPrice: kline.Tick.Open, ClosePrice: kline.Tick.Close, HighPrice: kline.Tick.High, @@ -264,10 +265,11 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) { } data := strings.Split(trade.Channel, ".") h.Websocket.DataHandler <- wshandler.TradeData{ - Exchange: h.GetName(), - AssetType: asset.Spot, - CurrencyPair: currency.NewPairFromString(data[1]), - Timestamp: time.Unix(0, trade.Tick.Timestamp), + Exchange: h.GetName(), + AssetType: asset.Spot, + CurrencyPair: currency.NewPairFromFormattedPairs(data[1], + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), + Timestamp: time.Unix(0, trade.Tick.Timestamp), } case strings.Contains(init.Channel, "detail"): var ticker WsTick @@ -287,14 +289,16 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) { Low: ticker.Tick.Low, Timestamp: time.Unix(0, ticker.Timestamp), AssetType: asset.Spot, - Pair: currency.NewPairFromString(data[1]), + Pair: currency.NewPairFromFormattedPairs(data[1], + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), } } } // WsProcessOrderbook processes new orderbook data func (h *HUOBIHADAX) WsProcessOrderbook(update *WsDepth, symbol string) error { - p := currency.NewPairFromString(symbol) + p := currency.NewPairFromFormattedPairs(symbol, + h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)) var bids, asks []orderbook.Item for i := 0; i < len(update.Tick.Bids); i++ { bidLevel := update.Tick.Bids[i].([]interface{}) @@ -429,7 +433,7 @@ func (h *HUOBIHADAX) wsGetAccountsList(pair currency.Pair) (*WsAuthenticatedAcco SignatureVersion: signatureVersion, Timestamp: timestamp, Topic: wsAccountsList, - Symbol: pair, + Symbol: h.FormatExchangeCurrency(pair, asset.Spot).String(), } hmac := h.wsGenerateSignature(timestamp, wsAccountListEndpoint) request.Signature = crypto.Base64Encode(hmac) @@ -456,7 +460,7 @@ func (h *HUOBIHADAX) wsGetOrdersList(accountID int64, pair currency.Pair) (*WsAu Timestamp: timestamp, Topic: wsOrdersList, AccountID: accountID, - Symbol: pair.Lower(), + Symbol: h.FormatExchangeCurrency(pair, asset.Spot).String(), States: "submitted,partial-filled", } hmac := h.wsGenerateSignature(timestamp, wsOrdersListEndpoint) diff --git a/exchanges/huobihadax/huobihadax_wrapper.go b/exchanges/huobihadax/huobihadax_wrapper.go index 59a9f02f..5045b9ac 100644 --- a/exchanges/huobihadax/huobihadax_wrapper.go +++ b/exchanges/huobihadax/huobihadax_wrapper.go @@ -191,7 +191,8 @@ func (h *HUOBIHADAX) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range symbols { - pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) + pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, + h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) } return pairs, nil @@ -218,7 +219,8 @@ func (h *HUOBIHADAX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker pairs := h.GetEnabledPairs(assetType) for i := range pairs { for j := range tickers.Data { - if !pairs[i].Equal(tickers.Data[j].Symbol) { + pairFmt := h.FormatExchangeCurrency(pairs[i], assetType).String() + if pairFmt != tickers.Data[j].Symbol { continue } tickerPrice := ticker.Price{ @@ -227,7 +229,7 @@ func (h *HUOBIHADAX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker Volume: tickers.Data[j].Volume, Open: tickers.Data[j].Open, Close: tickers.Data[j].Close, - Pair: tickers.Data[j].Symbol, + Pair: pairs[i], } err = ticker.ProcessTicker(h.GetName(), &tickerPrice, assetType) if err != nil { diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index ddb3c98c..7d855966 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -220,7 +220,8 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse, rawResp // allowing correlation between subscriptions and returned data func addNewSubscriptionChannelData(response *WebsocketEventResponse) { // We change the / to - to maintain compatibility with REST/config - pair := currency.NewPairWithDelimiter(response.Pair.Base.String(), response.Pair.Quote.String(), "-") + pair := currency.NewPairWithDelimiter(response.Pair.Base.String(), + response.Pair.Quote.String(), "-") subscriptionChannelPair = append(subscriptionChannelPair, WebsocketChannelData{ Subscription: response.Subscription.Name, Pair: pair, diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 1b5158ea..5d4912b6 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -170,7 +170,7 @@ func (k *Kraken) Run() { forceUpdate := false if !common.StringDataContains(k.GetEnabledPairs(asset.Spot).Strings(), k.GetPairFormat(asset.Spot, false).Delimiter) || !common.StringDataContains(k.GetAvailablePairs(asset.Spot).Strings(), k.GetPairFormat(asset.Spot, false).Delimiter) { - enabledPairs := currency.NewPairsFromStrings([]string{fmt.Sprintf("BTC%vUSD", k.GetPairFormat(asset.Spot, false).Delimiter)}) + enabledPairs := currency.NewPairsFromStrings([]string{fmt.Sprintf("XBT%vUSD", k.GetPairFormat(asset.Spot, false).Delimiter)}) log.Warn(log.ExchangeSys, "Available pairs for Kraken reset due to config upgrade, please enable the ones you would like again") forceUpdate = true @@ -242,13 +242,14 @@ func (k *Kraken) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri for i := range pairs { for curr, v := range tickers { - if !strings.EqualFold(pairs[i].String(), curr) { + pairFmt := k.FormatExchangeCurrency(pairs[i], assetType).String() + if !strings.EqualFold(pairFmt, curr) { var altCurrency string var ok bool if altCurrency, ok = assetPairMap[curr]; !ok { continue } - if !strings.EqualFold(pairs[i].String(), altCurrency) { + if !strings.EqualFold(pairFmt, altCurrency) { continue } } diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index 0eea8ece..0ddee837 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -491,8 +491,9 @@ func TestWsTradeProcessing(t *testing.T) { func TestWsTickerProcessing(t *testing.T) { TestSetDefaults(t) TestSetup(t) - l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() + const testChanSize = 26 + l.Websocket.DataHandler = make(chan interface{}, testChanSize) + l.Websocket.TrafficAlert = make(chan struct{}, testChanSize) json := `{"btcusd":{"low":"10990.05","high":"11966.24","last":"11903.29","volume":"1803.967079","sell":"11912.39","buy":"11902.2"},"btceur":{"low":"9886.87","high":"10732.72","last":"10691.44","volume":"87.994478","sell":"10711.62","buy":"10691.44"},"btchkd":{"low":null,"high":null,"last":"51776.98","volume":null,"sell":"93307.37","buy":"93177.56"},"btcjpy":{"low":"1176039.0","high":"1272246.0","last":"1265680.0","volume":"129.021421","sell":"1266764.0","buy":"1265680.0"},"btcgbp":{"low":"9157.12","high":"9953.43","last":"9941.28","volume":"10.4997","sell":"10007.89","buy":"9941.28"},"btcaud":{"low":"16102.57","high":"17594.22","last":"17548.16","volume":"7.338316","sell":"17616.67","buy":"17549.69"},"btccad":{"low":"14541.69","high":"15834.87","last":"15763.54","volume":"30.480309","sell":"15793.45","buy":"15756.13"},"btcsgd":{"low":"15133.82","high":"16501.62","last":"16455.53","volume":"4.044026","sell":"16484.37","buy":"16462.18"},"btcchf":{"low":"10800.58","high":"11526.24","last":"11526.24","volume":"0.1765","sell":"11675.34","buy":"11632.02"},"btcnzd":{"low":null,"high":null,"last":"8340.98","volume":null,"sell":"18315.49","buy":"18221.37"},"btcngn":{"low":null,"high":null,"last":"600000.0","volume":null,"sell":null,"buy":null},"eurusd":{"low":"1.1088","high":"1.1138","last":"1.1125","volume":"2680.105249","sell":"1.1142","buy":"1.1121"},"gbpusd":{"low":"1.1934","high":"1.1958","last":"1.1934","volume":"1493.923823","sell":"1.1979","buy":"1.1903"},"usdjpy":{"low":"105.26","high":"107.25","last":"106.33","volume":"114490.2179","sell":"106.34","buy":"106.27"},"usdhkd":{"low":null,"high":null,"last":"7.851","volume":null,"sell":"7.8328","buy":"7.8286"},"usdcad":{"low":"1.3225","high":"1.3272","last":"1.3255","volume":"11033.9877","sell":"1.3258","buy":"1.3238"},"usdsgd":{"low":"1.3776","high":"1.3839","last":"1.3838","volume":"2523.75","sell":"1.3838","buy":"1.3819"},"audusd":{"low":"0.6764","high":"0.6853","last":"0.6771","volume":"5442.608321","sell":"0.6782","buy":"0.6762"},"nzdusd":{"low":null,"high":null,"last":"0.6758","volume":null,"sell":"0.6532","buy":"0.6504"},"usdchf":{"low":"0.9838","high":"0.9838","last":"0.9838","volume":"108.3352","sell":"0.9801","buy":"0.9773"},"usdngn":{"low":null,"high":null,"last":"200.0","volume":null,"sell":null,"buy":null},"ethbtc":{"low":"0.0205","high":"0.025","last":"0.0205","volume":null,"sell":"0.03","buy":"0.0194"},"ltcbtc":{"low":null,"high":null,"last":"0.0114","volume":null,"sell":"0.009","buy":"0.0073"},"bchbtc":{"low":null,"high":null,"last":"0.0544","volume":null,"sell":"0.0322","buy":"0.0274"},"xrpbtc":{"low":"0.000042","high":"0.000042","last":"0.000042","volume":null,"sell":"0.000037","buy":"0.000022"},"baceth":{"low":"0.000035","high":"0.000035","last":"0.000035","volume":null,"sell":"0.0015","buy":null}}` err := l.processTicker(json) if err != nil { diff --git a/exchanges/lakebtc/lakebtc_websocket.go b/exchanges/lakebtc/lakebtc_websocket.go index 162ebaf6..690e2f41 100644 --- a/exchanges/lakebtc/lakebtc_websocket.go +++ b/exchanges/lakebtc/lakebtc_websocket.go @@ -21,9 +21,12 @@ const ( marketGlobalEndpoint = "market-global" marketSubstring = "market-" globalSubstring = "-global" - volumeString = "volume" - highString = "high" - lowString = "low" + tickerBuyString = "buy" + tickerHighString = "high" + tickerLastString = "last" + tickerLowString = "low" + tickerSellString = "sell" + tickerVolumeString = "volume" wssSchem = "wss" ) @@ -220,29 +223,28 @@ func (l *LakeBTC) processTicker(ticker string) error { } for k, v := range tUpdate { tickerData := v.(map[string]interface{}) - if tickerData[highString] == nil || tickerData[lowString] == nil || tickerData[volumeString] == nil { - continue - } - high, err := strconv.ParseFloat(tickerData[highString].(string), 64) - if err != nil { - l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'high' %v", l.Name, tickerData) - continue - } - low, err := strconv.ParseFloat(tickerData[lowString].(string), 64) - if err != nil { - l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'low' %v", l.Name, tickerData) - continue - } - vol, err := strconv.ParseFloat(tickerData[volumeString].(string), 64) - if err != nil { - l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'volume' %v", l.Name, tickerData) - continue + processTickerItem := func(tick map[string]interface{}, item string) float64 { + if tick[item] == nil { + return 0 + } + + p, err := strconv.ParseFloat(tick[item].(string), 64) + if err != nil { + l.Websocket.DataHandler <- fmt.Errorf("%s error parsing ticker data '%s' %v", l.Name, item, tickerData) + return 0 + } + + return p } + l.Websocket.DataHandler <- wshandler.TickerData{ Exchange: l.Name, - Volume: vol, - High: high, - Low: low, + Bid: processTickerItem(tickerData, tickerBuyString), + High: processTickerItem(tickerData, tickerHighString), + Last: processTickerItem(tickerData, tickerLastString), + Low: processTickerItem(tickerData, tickerLowString), + Ask: processTickerItem(tickerData, tickerSellString), + Volume: processTickerItem(tickerData, tickerVolumeString), AssetType: asset.Spot, Pair: currency.NewPairFromString(k), } diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 34b46ded..e415043d 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "strconv" - "strings" "sync" "time" @@ -188,9 +187,8 @@ func (z *ZB) FetchTradablePairs(asset asset.Item) ([]string, error) { func (z *ZB) UpdateTradablePairs(forceUpdate bool) error { pairs, err := z.FetchTradablePairs(asset.Spot) if err != nil { - return nil + return err } - return z.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } @@ -204,8 +202,9 @@ func (z *ZB) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, } for _, x := range z.GetEnabledPairs(assetType) { - currencySplit := strings.Split(z.FormatExchangeCurrency(x, assetType).String(), z.GetPairFormat(assetType, false).Delimiter) - curr := currencySplit[0] + currencySplit[1] + // We can't use either pair format here, so format it to lower- + // case and without any delimiter + curr := x.Format("", false).String() if _, ok := result[curr]; !ok { continue } From 0fcf8676975677ed721ed1e5743ecedf5e9a95f8 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 6 Sep 2019 11:47:58 +1000 Subject: [PATCH 31/71] Add support for get/set/rm of exchange pairs via gRPC --- cmd/gctcli/commands.go | 235 +++++++++- cmd/gctcli/main.go | 3 + currency/manager.go | 59 +++ currency/manager_test.go | 67 +++ currency/pairs.go | 21 + currency/pairs_test.go | 35 ++ engine/rpcserver.go | 98 +++++ exchanges/asset/asset.go | 6 +- exchanges/asset/asset_test.go | 4 + gctrpc/rpc.pb.go | 806 ++++++++++++++++++++++------------ gctrpc/rpc.pb.gw.go | 123 ++++++ gctrpc/rpc.proto | 36 ++ gctrpc/rpc.swagger.json | 114 +++++ 13 files changed, 1320 insertions(+), 287 deletions(-) diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index 92b72cd7..37ee55a3 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -345,13 +345,13 @@ func disableExchange(c *cli.Context) error { var getExchangeOTPCommand = cli.Command{ Name: "getexchangeotp", - Usage: "gets a specific exchanges otp code", + Usage: "gets a specific exchange OTP code", ArgsUsage: "", Action: getExchangeOTPCode, Flags: []cli.Flag{ cli.StringFlag{ Name: "exchange", - Usage: "the exchange to get the otp code for", + Usage: "the exchange to get the OTP code for", }, }, } @@ -392,7 +392,7 @@ func getExchangeOTPCode(c *cli.Context) error { var getExchangeOTPsCommand = cli.Command{ Name: "getexchangeotps", - Usage: "gets all exchange OTPs", + Usage: "gets all exchange OTP codes", Action: getExchangeOTPCodes, } @@ -1950,8 +1950,10 @@ func withdrawFiatFunds(_ *cli.Context) error { } var getLoggerDetailsCommand = cli.Command{ - Name: "getloggerdetails", - Action: getLoggerDetails, + Name: "getloggerdetails", + Usage: "gets an individual loggers details", + ArgsUsage: "", + Action: getLoggerDetails, Flags: []cli.Flag{ cli.StringFlag{ Name: "logger", @@ -1994,8 +1996,10 @@ func getLoggerDetails(c *cli.Context) error { } var setLoggerDetailsCommand = cli.Command{ - Name: "setloggerdetails", - Action: setLoggerDetails, + Name: "setloggerdetails", + Usage: "sets an individual loggers details", + ArgsUsage: " ", + Action: setLoggerDetails, Flags: []cli.Flag{ cli.StringFlag{ Name: "logger", @@ -2049,3 +2053,220 @@ func setLoggerDetails(c *cli.Context) error { jsonOutput(result) return nil } + +var getExchangePairsCommand = cli.Command{ + Name: "getexchangepairs", + Usage: "gets an exchanges supported currency pairs (available and enabled) plus asset types", + ArgsUsage: " ", + Action: getExchangePairs, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to list of the currency pairs of", + }, + cli.StringFlag{ + Name: "asset", + Usage: "the asset type to filter by", + }, + }, +} + +func getExchangePairs(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getexchangepairs") + return nil + } + + var exchange string + var asset string + + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if c.IsSet("asset") { + asset = c.String("asset") + } else { + asset = c.Args().Get(1) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + + result, err := client.GetExchangePairs(context.Background(), + &gctrpc.GetExchangePairsRequest{ + Exchange: exchange, + Asset: asset, + }, + ) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +var enableExchangePairCommand = cli.Command{ + Name: "enableexchangepair", + Usage: "enables an exchange currency pair", + ArgsUsage: " ", + Action: enableExchangePair, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to enable the currency pair for", + }, + cli.StringFlag{ + Name: "pair", + Usage: "the currency pair to enable", + }, + cli.StringFlag{ + Name: "asset", + Usage: "the asset type to enable the currency pair for", + }, + }, +} + +func enableExchangePair(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "enableexchangepair") + return nil + } + + var exchange string + var pair string + var asset string + + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if c.IsSet("pair") { + pair = c.String("pair") + } else { + pair = c.Args().Get(1) + } + + if c.IsSet("asset") { + asset = c.String("asset") + } else { + asset = c.Args().Get(2) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + if !validPair(pair) { + return errInvalidPair + } + + p := currency.NewPairDelimiter(pair, pairDelimiter) + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.EnableExchangePair(context.Background(), + &gctrpc.ExchangePairRequest{ + Exchange: exchange, + Pair: &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }, + AssetType: asset, + }, + ) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +var disableExchangePairCommand = cli.Command{ + Name: "disableexchangepair", + Usage: "disables a previously enabled exchange currency pair", + ArgsUsage: " ", + Action: disableExchangePair, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to disable the currency pair for", + }, + cli.StringFlag{ + Name: "pair", + Usage: "the currency pair to disable", + }, + cli.StringFlag{ + Name: "asset", + Usage: "the asset type to disable the currency pair for", + }, + }, +} + +func disableExchangePair(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "disableexchangepair") + return nil + } + + var exchange string + var pair string + var asset string + + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if c.IsSet("pair") { + pair = c.String("pair") + } else { + pair = c.Args().Get(1) + } + + if c.IsSet("asset") { + asset = c.String("asset") + } else { + asset = c.Args().Get(2) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + if !validPair(pair) { + return errInvalidPair + } + + p := currency.NewPairDelimiter(pair, pairDelimiter) + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.DisableExchangePair(context.Background(), + &gctrpc.ExchangePairRequest{ + Exchange: exchange, + Pair: &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }, + AssetType: asset, + }, + ) + if err != nil { + return err + } + jsonOutput(result) + return nil +} diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index aadaf0af..8fcf4250 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -124,6 +124,9 @@ func main() { withdrawFiatFundsCommand, getLoggerDetailsCommand, setLoggerDetailsCommand, + getExchangePairsCommand, + enableExchangePairCommand, + disableExchangePairCommand, } err := app.Run(os.Args) diff --git a/currency/manager.go b/currency/manager.go index 7248e1f6..c77aaa36 100644 --- a/currency/manager.go +++ b/currency/manager.go @@ -1,6 +1,8 @@ package currency import ( + "errors" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) @@ -105,3 +107,60 @@ func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) { p.Pairs[a] = c } + +// DisablePair removes the pair from the enabled pairs list if found +func (p *PairsManager) DisablePair(a asset.Item, pair Pair) error { + p.m.Lock() + defer p.m.Unlock() + + if p.Pairs == nil { + return errors.New("pair manager not initialised") + } + + c, ok := p.Pairs[a] + if !ok { + return errors.New("asset type not found") + } + + if c == nil { + return errors.New("currency store is nil") + } + + if !c.Enabled.Contains(pair, true) { + return errors.New("specified pair is not enabled") + } + + c.Enabled = c.Enabled.Remove(pair) + return nil +} + +// EnablePair adds a pair to the list of enabled pairs if it exists in the list +// of available pairs and isn't already added +func (p *PairsManager) EnablePair(a asset.Item, pair Pair) error { + p.m.Lock() + defer p.m.Unlock() + + if p.Pairs == nil { + return errors.New("pair manager not initialised") + } + + c, ok := p.Pairs[a] + if !ok { + return errors.New("asset type not found") + } + + if c == nil { + return errors.New("currency store is nil") + } + + if !c.Available.Contains(pair, true) { + return errors.New("specified pair was not found in the list of available pairs") + } + + if c.Enabled.Contains(pair, true) { + return errors.New("specified pair is already enabled") + } + + c.Enabled = c.Enabled.Add(pair) + return nil +} diff --git a/currency/manager_test.go b/currency/manager_test.go index f9d464cc..67a58490 100644 --- a/currency/manager_test.go +++ b/currency/manager_test.go @@ -138,3 +138,70 @@ func TestStorePairs(t *testing.T) { t.Errorf("TestStorePairs failed, unexpected result") } } + +func TestDisablePair(t *testing.T) { + p.Pairs = nil + // Test disabling a pair when the pair manager is not initialised + if err := p.DisablePair(asset.Spot, NewPair(BTC, USD)); err == nil { + t.Error("unexpected result") + } + + // Test asset type which doesn't exist + initTest() + if err := p.DisablePair(asset.Futures, Pair{}); err == nil { + t.Error("unexpected result") + } + + // Test asset type which has an empty pair store + p.Pairs[asset.Spot] = nil + if err := p.DisablePair(asset.Spot, Pair{}); err == nil { + t.Error("unexpected result") + } + + // Test disabling a pair which isn't enabled + initTest() + if err := p.DisablePair(asset.Spot, NewPair(LTC, USD)); err == nil { + t.Error("unexpected result") + } + + // Test disabling a valid pair and ensure nil is empty + if err := p.DisablePair(asset.Spot, NewPair(BTC, USD)); err != nil { + t.Error("unexpected result") + } +} + +func TestEnablePair(t *testing.T) { + p.Pairs = nil + // Test enabling a pair when the pair manager is not initialised + if err := p.EnablePair(asset.Spot, NewPair(BTC, USD)); err == nil { + t.Error("unexpected result") + } + + // Test asset type which doesn't exist + initTest() + if err := p.EnablePair(asset.Futures, Pair{}); err == nil { + t.Error("unexpected result") + } + + // Test asset type which has an empty pair store + p.Pairs[asset.Spot] = nil + if err := p.EnablePair(asset.Spot, Pair{}); err == nil { + t.Error("unexpected result") + } + + // Test enabling a pair which isn't in the list of available pairs + initTest() + if err := p.EnablePair(asset.Spot, NewPair(ETH, USD)); err == nil { + t.Error("unexpected result") + } + + // Test enabling a pair which already is enabled + if err := p.EnablePair(asset.Spot, NewPair(BTC, USD)); err == nil { + t.Error("unexpected result") + } + + // Test enabling a valid pair + if err := p.EnablePair(asset.Spot, NewPair(LTC, USD)); err != nil { + t.Error("unexpected result") + } +} diff --git a/currency/pairs.go b/currency/pairs.go index 7eba29d2..06cdedd1 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -134,6 +134,27 @@ func (p Pairs) RemovePairsByFilter(filter Code) Pairs { return pairs } +// Remove removes the specified pair from the list of pairs if it exists +func (p Pairs) Remove(pair Pair) Pairs { + var pairs Pairs + for x := range p { + if p[x].Equal(pair) { + continue + } + pairs = append(pairs, p[x]) + } + return pairs +} + +// Add adds a specified pair to the list of pairs if it doesn't exist +func (p Pairs) Add(pair Pair) Pairs { + if p.Contains(pair, true) { + return p + } + p = append(p, pair) + return p +} + // FindDifferences returns pairs which are new or have been removed func (p Pairs) FindDifferences(pairs Pairs) (newPairs, removedPairs Pairs) { for x := range pairs { diff --git a/currency/pairs_test.go b/currency/pairs_test.go index 1dd69d15..e8d7e1e5 100644 --- a/currency/pairs_test.go +++ b/currency/pairs_test.go @@ -123,6 +123,41 @@ func TestRemovePairsByFilter(t *testing.T) { } } +func TestRemove(t *testing.T) { + var pairs = Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(LTC, USDT), + } + + p := NewPair(BTC, USD) + pairs = pairs.Remove(p) + if pairs.Contains(p, true) || len(pairs) != 2 { + t.Error("Test failed. TestRemove unexpected result") + } +} + +func TestAdd(t *testing.T) { + var pairs = Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(LTC, USDT), + } + + // Test adding a new pair to the list of pairs + p := NewPair(BTC, USDT) + pairs = pairs.Add(p) + if !pairs.Contains(p, true) || len(pairs) != 4 { + t.Error("Test failed. TestAdd unexpected result") + } + + // Now test adding a pair which already exists + pairs = pairs.Add(p) + if len(pairs) != 4 { + t.Error("Test failed. TestAdd unexpected result") + } +} + func TestContains(t *testing.T) { var pairs = Pairs{ NewPair(BTC, USD), diff --git a/engine/rpcserver.go b/engine/rpcserver.go index ad3e48b6..309beae3 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -858,3 +858,101 @@ func (s *RPCServer) SetLoggerDetails(ctx context.Context, r *gctrpc.SetLoggerDet Error: levels.Error, }, nil } + +// GetExchangePairs returns a list of exchange supported assets and related pairs +func (s *RPCServer) GetExchangePairs(ctx context.Context, r *gctrpc.GetExchangePairsRequest) (*gctrpc.GetExchangePairsResponse, error) { + exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) + if err != nil { + return nil, err + } + + if r.Asset != "" && + !exchCfg.CurrencyPairs.GetAssetTypes().Contains(asset.Item(r.Asset)) { + return nil, errors.New("specified asset type does not exist") + } + + var resp gctrpc.GetExchangePairsResponse + resp.SupportedAssets = make(map[string]*gctrpc.PairsSupported) + assetTypes := exchCfg.CurrencyPairs.GetAssetTypes() + for x := range assetTypes { + a := assetTypes[x] + if r.Asset != "" && !strings.EqualFold(a.String(), r.Asset) { + continue + } + resp.SupportedAssets[a.String()] = &gctrpc.PairsSupported{ + AvailablePairs: exchCfg.CurrencyPairs.Get(a).Available.Join(), + EnabledPairs: exchCfg.CurrencyPairs.Get(a).Enabled.Join(), + } + } + return &resp, nil +} + +// EnableExchangePair enables the specified pair on an exchange +func (s *RPCServer) EnableExchangePair(ctx context.Context, r *gctrpc.ExchangePairRequest) (*gctrpc.GenericExchangeNameResponse, error) { + exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) + if err != nil { + return nil, err + } + + if r.AssetType != "" && + !exchCfg.CurrencyPairs.GetAssetTypes().Contains(asset.Item(r.AssetType)) { + return nil, errors.New("specified asset type does not exist") + } + + // Default to spot asset type unless set + a := asset.Spot + if r.AssetType != "" { + a = asset.Item(r.AssetType) + } + + pairFmt, err := Bot.Config.GetPairFormat(r.Exchange, a) + if err != nil { + return nil, err + } + + p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote).Format( + pairFmt.Delimiter, pairFmt.Uppercase) + err = exchCfg.CurrencyPairs.EnablePair(a, p) + if err != nil { + return nil, err + } + err = GetExchangeByName(r.Exchange).GetBase().CurrencyPairs.EnablePair( + asset.Item(r.AssetType), p) + return &gctrpc.GenericExchangeNameResponse{}, err + +} + +// DisableExchangePair disables the specified pair on an exchange +func (s *RPCServer) DisableExchangePair(ctx context.Context, r *gctrpc.ExchangePairRequest) (*gctrpc.GenericExchangeNameResponse, error) { + exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) + if err != nil { + return nil, err + } + + if r.AssetType != "" && + !exchCfg.CurrencyPairs.GetAssetTypes().Contains(asset.Item(r.AssetType)) { + return nil, errors.New("specified asset type does not exist") + } + + // Default to spot asset type unless set + a := asset.Spot + if r.AssetType != "" { + a = asset.Item(r.AssetType) + } + + pairFmt, err := Bot.Config.GetPairFormat(r.Exchange, a) + if err != nil { + return nil, err + } + + p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote).Format( + pairFmt.Delimiter, pairFmt.Uppercase) + err = exchCfg.CurrencyPairs.DisablePair(asset.Item(r.AssetType), p) + if err != nil { + return nil, err + } + err = GetExchangeByName(r.Exchange).GetBase().CurrencyPairs.DisablePair( + asset.Item(r.AssetType), p) + return &gctrpc.GenericExchangeNameResponse{}, err + +} diff --git a/exchanges/asset/asset.go b/exchanges/asset/asset.go index 289eb9fb..77137482 100644 --- a/exchanges/asset/asset.go +++ b/exchanges/asset/asset.go @@ -56,13 +56,13 @@ func (a Items) Strings() []string { // Contains returns whether or not the supplied asset exists // in the list of Items -func (a Items) Contains(asset Item) bool { - if !IsValid(asset) { +func (a Items) Contains(i Item) bool { + if !IsValid(i) { return false } for x := range a { - if a[x] == asset { + if strings.EqualFold(a[x].String(), i.String()) { return true } } diff --git a/exchanges/asset/asset_test.go b/exchanges/asset/asset_test.go index a3ef949f..475058c5 100644 --- a/exchanges/asset/asset_test.go +++ b/exchanges/asset/asset_test.go @@ -36,6 +36,10 @@ func TestContains(t *testing.T) { if a.Contains(Binary) { t.Fatal("Test failed - TestContains returned an unexpected result") } + + if !a.Contains("SpOt") { + t.Error("Test failed - TestContains returned an unexpected result") + } } func TestJoinToString(t *testing.T) { diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index b64224ea..71453f7f 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -4573,6 +4573,147 @@ func (m *SetLoggerDetailsRequest) GetLevel() string { return "" } +type GetExchangePairsRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Asset string `protobuf:"bytes,2,opt,name=asset,proto3" json:"asset,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangePairsRequest) Reset() { *m = GetExchangePairsRequest{} } +func (m *GetExchangePairsRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangePairsRequest) ProtoMessage() {} +func (*GetExchangePairsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{89} +} + +func (m *GetExchangePairsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangePairsRequest.Unmarshal(m, b) +} +func (m *GetExchangePairsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangePairsRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangePairsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangePairsRequest.Merge(m, src) +} +func (m *GetExchangePairsRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangePairsRequest.Size(m) +} +func (m *GetExchangePairsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangePairsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangePairsRequest proto.InternalMessageInfo + +func (m *GetExchangePairsRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *GetExchangePairsRequest) GetAsset() string { + if m != nil { + return m.Asset + } + return "" +} + +type GetExchangePairsResponse struct { + SupportedAssets map[string]*PairsSupported `protobuf:"bytes,1,rep,name=supported_assets,json=supportedAssets,proto3" json:"supported_assets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangePairsResponse) Reset() { *m = GetExchangePairsResponse{} } +func (m *GetExchangePairsResponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangePairsResponse) ProtoMessage() {} +func (*GetExchangePairsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{90} +} + +func (m *GetExchangePairsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangePairsResponse.Unmarshal(m, b) +} +func (m *GetExchangePairsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangePairsResponse.Marshal(b, m, deterministic) +} +func (m *GetExchangePairsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangePairsResponse.Merge(m, src) +} +func (m *GetExchangePairsResponse) XXX_Size() int { + return xxx_messageInfo_GetExchangePairsResponse.Size(m) +} +func (m *GetExchangePairsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangePairsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangePairsResponse proto.InternalMessageInfo + +func (m *GetExchangePairsResponse) GetSupportedAssets() map[string]*PairsSupported { + if m != nil { + return m.SupportedAssets + } + return nil +} + +type ExchangePairRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + AssetType string `protobuf:"bytes,2,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,3,opt,name=pair,proto3" json:"pair,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ExchangePairRequest) Reset() { *m = ExchangePairRequest{} } +func (m *ExchangePairRequest) String() string { return proto.CompactTextString(m) } +func (*ExchangePairRequest) ProtoMessage() {} +func (*ExchangePairRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{91} +} + +func (m *ExchangePairRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ExchangePairRequest.Unmarshal(m, b) +} +func (m *ExchangePairRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ExchangePairRequest.Marshal(b, m, deterministic) +} +func (m *ExchangePairRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExchangePairRequest.Merge(m, src) +} +func (m *ExchangePairRequest) XXX_Size() int { + return xxx_messageInfo_ExchangePairRequest.Size(m) +} +func (m *ExchangePairRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ExchangePairRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ExchangePairRequest proto.InternalMessageInfo + +func (m *ExchangePairRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *ExchangePairRequest) GetAssetType() string { + if m != nil { + return m.AssetType + } + return "" +} + +func (m *ExchangePairRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + func init() { proto.RegisterType((*GetInfoRequest)(nil), "gctrpc.GetInfoRequest") proto.RegisterType((*GetInfoResponse)(nil), "gctrpc.GetInfoResponse") @@ -4676,288 +4817,300 @@ func init() { proto.RegisterType((*GetLoggerDetailsRequest)(nil), "gctrpc.GetLoggerDetailsRequest") proto.RegisterType((*GetLoggerDetailsResponse)(nil), "gctrpc.GetLoggerDetailsResponse") proto.RegisterType((*SetLoggerDetailsRequest)(nil), "gctrpc.SetLoggerDetailsRequest") + proto.RegisterType((*GetExchangePairsRequest)(nil), "gctrpc.GetExchangePairsRequest") + proto.RegisterType((*GetExchangePairsResponse)(nil), "gctrpc.GetExchangePairsResponse") + proto.RegisterMapType((map[string]*PairsSupported)(nil), "gctrpc.GetExchangePairsResponse.SupportedAssetsEntry") + proto.RegisterType((*ExchangePairRequest)(nil), "gctrpc.ExchangePairRequest") } func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 4415 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x4d, 0x6f, 0x1c, 0x57, - 0x72, 0xe8, 0xe1, 0x88, 0xe4, 0xd4, 0x0c, 0xc9, 0xe1, 0xe3, 0xd7, 0x68, 0x44, 0xea, 0xa3, 0xbd, - 0x92, 0x25, 0xad, 0x97, 0xb2, 0x65, 0x21, 0xeb, 0xd8, 0x9b, 0x4d, 0x68, 0x5a, 0xe6, 0x2a, 0xeb, - 0xb5, 0x98, 0xa6, 0x56, 0x02, 0xbc, 0x81, 0x3b, 0xcd, 0xe9, 0xc7, 0x61, 0x47, 0x3d, 0xdd, 0xed, - 0xee, 0x1e, 0x52, 0x34, 0x02, 0x04, 0x30, 0x90, 0x20, 0xa7, 0xe4, 0xb0, 0x08, 0x90, 0x43, 0x4e, - 0x39, 0x05, 0x01, 0x72, 0x09, 0x72, 0xca, 0x61, 0x91, 0x6b, 0x90, 0x63, 0x2e, 0xf9, 0x01, 0x41, - 0x6e, 0x49, 0x80, 0x00, 0xb9, 0xe4, 0x14, 0xbc, 0x7a, 0x1f, 0xfd, 0x5e, 0x77, 0xcf, 0x70, 0xb4, - 0xab, 0xf8, 0x22, 0x4d, 0xd7, 0xab, 0x57, 0x55, 0xaf, 0xaa, 0x5e, 0xbd, 0x7a, 0xf5, 0x8a, 0xd0, - 0x4a, 0x93, 0xc1, 0x6e, 0x92, 0xc6, 0x79, 0x4c, 0xe6, 0x87, 0x83, 0x3c, 0x4d, 0x06, 0xfd, 0xed, - 0x61, 0x1c, 0x0f, 0x43, 0xfa, 0xc0, 0x4b, 0x82, 0x07, 0x5e, 0x14, 0xc5, 0xb9, 0x97, 0x07, 0x71, - 0x94, 0x71, 0x2c, 0xbb, 0x0b, 0xcb, 0x07, 0x34, 0x7f, 0x12, 0x9d, 0xc4, 0x0e, 0xfd, 0x6a, 0x4c, - 0xb3, 0xdc, 0xfe, 0xfb, 0x26, 0xac, 0x28, 0x50, 0x96, 0xc4, 0x51, 0x46, 0xc9, 0x26, 0xcc, 0x8f, - 0x93, 0x3c, 0x18, 0xd1, 0x9e, 0x75, 0xd3, 0xba, 0xdb, 0x72, 0xc4, 0x17, 0x79, 0x00, 0x6b, 0xde, - 0x99, 0x17, 0x84, 0xde, 0x71, 0x48, 0x5d, 0xfa, 0x6a, 0x70, 0xea, 0x45, 0x43, 0x9a, 0xf5, 0x1a, - 0x37, 0xad, 0xbb, 0x73, 0x0e, 0x51, 0x43, 0x8f, 0xe5, 0x08, 0xf9, 0x2e, 0xac, 0xd2, 0x88, 0x81, - 0x7c, 0x0d, 0x7d, 0x0e, 0xd1, 0xbb, 0x62, 0xa0, 0x40, 0x7e, 0x04, 0x9b, 0x3e, 0x3d, 0xf1, 0xc6, - 0x61, 0xee, 0x9e, 0xc4, 0x29, 0x7d, 0xe5, 0x26, 0x69, 0x7c, 0x16, 0xf8, 0x34, 0xed, 0x35, 0x51, - 0x8a, 0x75, 0x31, 0xfa, 0x29, 0x1b, 0x3c, 0x14, 0x63, 0xe4, 0x21, 0x6c, 0xa8, 0x59, 0x81, 0x97, - 0xbb, 0x83, 0x71, 0x9a, 0xd2, 0x68, 0x70, 0xd1, 0xbb, 0x82, 0x93, 0xd6, 0xe4, 0xa4, 0xc0, 0xcb, - 0xf7, 0xc5, 0x10, 0x79, 0x01, 0xdd, 0x6c, 0x7c, 0x9c, 0x5d, 0x64, 0x39, 0x1d, 0xb9, 0x59, 0xee, - 0xe5, 0xe3, 0xac, 0x37, 0x7f, 0x73, 0xee, 0x6e, 0xfb, 0xe1, 0x3b, 0xbb, 0x5c, 0x8d, 0xbb, 0x25, - 0x95, 0xec, 0x1e, 0x49, 0xfc, 0x23, 0x44, 0x7f, 0x1c, 0xe5, 0xe9, 0x85, 0xb3, 0x92, 0x99, 0x50, - 0xf2, 0x39, 0x2c, 0xa5, 0xc9, 0xc0, 0xa5, 0x91, 0x9f, 0xc4, 0x41, 0x94, 0x67, 0xbd, 0x05, 0xa4, - 0x7a, 0x6f, 0x12, 0x55, 0x27, 0x19, 0x3c, 0x96, 0xb8, 0x9c, 0x64, 0x27, 0xd5, 0x40, 0xfd, 0x8f, - 0x61, 0xbd, 0x8e, 0x31, 0xe9, 0xc2, 0xdc, 0x4b, 0x7a, 0x21, 0xac, 0xc3, 0x7e, 0x92, 0x75, 0xb8, - 0x72, 0xe6, 0x85, 0x63, 0x8a, 0xc6, 0x58, 0x74, 0xf8, 0xc7, 0x87, 0x8d, 0x0f, 0xac, 0xfe, 0x33, - 0x58, 0xad, 0xb0, 0xa9, 0x21, 0x70, 0x4f, 0x27, 0xd0, 0x7e, 0xb8, 0x26, 0x45, 0x76, 0x0e, 0xf7, - 0xe5, 0x5c, 0x8d, 0xaa, 0x7d, 0x0b, 0x6e, 0x1c, 0xd0, 0x7c, 0x3f, 0x1e, 0x8d, 0xc6, 0x51, 0x30, - 0x40, 0x1f, 0x73, 0x68, 0xe8, 0x5d, 0xd0, 0x34, 0x93, 0x9e, 0xf5, 0x39, 0xac, 0xd7, 0x8d, 0x93, - 0x1e, 0x2c, 0x08, 0xdb, 0x23, 0xff, 0x45, 0x47, 0x7e, 0x92, 0x6d, 0x68, 0x0d, 0xe2, 0x28, 0xa2, - 0x83, 0x9c, 0xfa, 0x62, 0x21, 0x05, 0xc0, 0xfe, 0xe3, 0x06, 0xdc, 0x9c, 0xcc, 0x53, 0xb8, 0xee, - 0xd7, 0xb0, 0x39, 0xd0, 0x11, 0xdc, 0x54, 0x60, 0xf4, 0x2c, 0x34, 0xc5, 0xbe, 0x66, 0x8a, 0xa9, - 0x94, 0x76, 0x6b, 0x47, 0xb9, 0x91, 0x36, 0x06, 0x75, 0x63, 0xfd, 0x13, 0xe8, 0x4f, 0x9e, 0x54, - 0xa3, 0xf2, 0x87, 0xa6, 0xca, 0xb7, 0xa5, 0x68, 0x75, 0x44, 0x74, 0xdd, 0x7f, 0x1f, 0xb6, 0x0e, - 0x68, 0x44, 0xd3, 0x60, 0xa0, 0x9c, 0x43, 0xe8, 0x9c, 0x69, 0x50, 0xf9, 0xa4, 0x60, 0x55, 0x00, - 0xec, 0x3e, 0xf4, 0xaa, 0x13, 0xf9, 0x72, 0xed, 0x4d, 0x58, 0x3f, 0xa0, 0xb9, 0x82, 0x2b, 0x2b, - 0xfe, 0xc2, 0x82, 0x0d, 0x1c, 0xc8, 0x8e, 0xb3, 0x0b, 0x3e, 0x20, 0x54, 0xfd, 0x7b, 0xb0, 0xaa, - 0x48, 0x67, 0x72, 0x1b, 0x71, 0x2d, 0xbf, 0xaf, 0x69, 0xb9, 0x3a, 0xb3, 0xd8, 0x4c, 0x99, 0xbe, - 0x9b, 0x8a, 0x3d, 0x29, 0xc0, 0xfd, 0x7d, 0xd8, 0xa8, 0x45, 0x7d, 0x1d, 0xff, 0xb7, 0x7b, 0xb0, - 0x79, 0x40, 0x73, 0xcd, 0x8d, 0x35, 0x07, 0x6d, 0x6b, 0x60, 0xe6, 0x97, 0x59, 0xee, 0xa5, 0x79, - 0xe1, 0x97, 0xe2, 0x93, 0xdc, 0x86, 0xe5, 0x30, 0xc8, 0x72, 0x1a, 0xb9, 0x9e, 0xef, 0xa7, 0x34, - 0xe3, 0x21, 0xaf, 0xe5, 0x2c, 0x71, 0xe8, 0x1e, 0x07, 0xda, 0xff, 0x60, 0x31, 0xc3, 0x94, 0x58, - 0x09, 0x65, 0x7d, 0x06, 0xad, 0x22, 0x2a, 0x70, 0x25, 0xed, 0x6a, 0x4a, 0xaa, 0x9b, 0xb3, 0x5b, - 0x0a, 0x0d, 0x05, 0x81, 0xfe, 0xef, 0xc0, 0xf2, 0x9b, 0xde, 0xd0, 0x1f, 0x40, 0x5f, 0xf8, 0x86, - 0x8c, 0xc8, 0x9f, 0x7b, 0x23, 0x2a, 0xfd, 0xaa, 0x0f, 0x8b, 0x32, 0x80, 0x0b, 0x1e, 0xea, 0xdb, - 0xde, 0x81, 0x6b, 0xb5, 0x33, 0x85, 0x63, 0x3d, 0x80, 0xb5, 0x03, 0x9a, 0xab, 0x30, 0x2f, 0x29, - 0x4e, 0x8c, 0x02, 0xf6, 0x23, 0xf4, 0x44, 0x6d, 0x82, 0x50, 0xe1, 0x36, 0xb4, 0x8a, 0x43, 0x44, - 0xf8, 0xb6, 0x02, 0xd8, 0x0f, 0xd1, 0x4d, 0xe5, 0xac, 0xa7, 0xcf, 0x0e, 0x1d, 0xca, 0xa7, 0x5d, - 0x85, 0xc5, 0x38, 0x4f, 0xdc, 0x41, 0xec, 0x4b, 0xd1, 0x17, 0xe2, 0x3c, 0xd9, 0x8f, 0x7d, 0x2a, - 0x5c, 0x43, 0x9b, 0xa3, 0x5c, 0xe3, 0xaf, 0xb8, 0x29, 0xcd, 0x21, 0x21, 0xc7, 0x6f, 0x43, 0x4b, - 0x12, 0x94, 0xa6, 0xfc, 0x9e, 0x66, 0xca, 0xba, 0x39, 0xbb, 0x4f, 0x39, 0x47, 0x61, 0xc9, 0x45, - 0x21, 0x40, 0xd6, 0xff, 0x08, 0x96, 0x8c, 0xa1, 0xcb, 0x3c, 0xbb, 0xa5, 0x9b, 0xec, 0x11, 0x6c, - 0x7e, 0x12, 0x64, 0xfa, 0x89, 0x3b, 0x8b, 0xb9, 0xbe, 0x84, 0xe5, 0x43, 0x2f, 0x48, 0xb3, 0xa3, - 0x71, 0x92, 0xc4, 0xe8, 0xde, 0x6f, 0xc3, 0x4a, 0x71, 0xac, 0x27, 0x6c, 0x4c, 0x4c, 0x5a, 0x56, - 0x60, 0x9c, 0x41, 0xde, 0x82, 0x25, 0x79, 0x9c, 0x73, 0x34, 0x2e, 0x52, 0x47, 0x00, 0x11, 0xc9, - 0xfe, 0xa6, 0x69, 0xa8, 0xce, 0x48, 0x2c, 0x08, 0x34, 0x23, 0x4f, 0xa5, 0x15, 0xf8, 0x5b, 0x77, - 0x84, 0x86, 0x79, 0x1c, 0xf4, 0x60, 0xe1, 0x8c, 0xa6, 0xc7, 0x71, 0x46, 0x31, 0x67, 0x58, 0x74, - 0xe4, 0x27, 0x13, 0x64, 0x9c, 0x05, 0xd1, 0xd0, 0xcd, 0xbc, 0xc8, 0x3f, 0x8e, 0x5f, 0x61, 0x86, - 0xb0, 0xe8, 0x74, 0x10, 0x78, 0xc4, 0x61, 0xe4, 0x16, 0x74, 0x4e, 0xf3, 0x3c, 0x71, 0x59, 0xea, - 0x12, 0x8f, 0x73, 0x91, 0x10, 0xb4, 0x19, 0xec, 0x19, 0x07, 0xb1, 0x8d, 0x8d, 0x28, 0xe3, 0x8c, - 0xa6, 0xde, 0x90, 0x46, 0x79, 0x6f, 0x9e, 0x6f, 0x6c, 0x06, 0xfd, 0xa9, 0x04, 0x92, 0x1d, 0x00, - 0x44, 0x4b, 0xd2, 0xf8, 0xd5, 0x45, 0x6f, 0x81, 0xbb, 0x1e, 0x83, 0x1c, 0x32, 0x00, 0xd3, 0xdf, - 0xb1, 0x97, 0x51, 0x99, 0x7a, 0x04, 0x34, 0xeb, 0x2d, 0x72, 0xfd, 0x31, 0xf0, 0xbe, 0x82, 0x12, - 0x97, 0xe5, 0x1d, 0x42, 0xeb, 0xae, 0x97, 0x65, 0x34, 0xcf, 0x7a, 0x2d, 0x74, 0xa0, 0x47, 0x35, - 0x0e, 0x54, 0xca, 0x3f, 0xc4, 0xbc, 0x3d, 0x9c, 0xa6, 0xf2, 0x0f, 0x03, 0xca, 0xf2, 0x2d, 0x6f, - 0x9c, 0x9f, 0xd2, 0x28, 0x67, 0xa7, 0x07, 0x63, 0x92, 0x04, 0x3d, 0x40, 0xdd, 0x74, 0x8d, 0x81, - 0xbd, 0x24, 0xe8, 0x7f, 0xc1, 0x92, 0x8b, 0x2a, 0xd5, 0x1a, 0x17, 0x7c, 0xc7, 0x0c, 0x25, 0x9b, - 0x52, 0x58, 0xd3, 0x8f, 0x74, 0xd7, 0x3c, 0x87, 0xee, 0x01, 0xcd, 0x9f, 0x05, 0x83, 0x97, 0x34, - 0x9d, 0xc1, 0x29, 0xc9, 0x5d, 0x68, 0x32, 0x8f, 0x12, 0x0c, 0xd6, 0xd5, 0x49, 0x28, 0x32, 0x36, - 0xc6, 0xc8, 0x41, 0x0c, 0x66, 0x0b, 0xd4, 0x9c, 0x9b, 0x5f, 0x24, 0xdc, 0x2f, 0x5a, 0x4e, 0x0b, - 0x21, 0xcf, 0x2e, 0x12, 0x6a, 0x3f, 0x87, 0x8e, 0x3e, 0x89, 0x05, 0x0d, 0x9f, 0x86, 0xc1, 0x28, - 0xc8, 0x69, 0x2a, 0x83, 0x86, 0x02, 0x30, 0x7f, 0x64, 0x26, 0x12, 0x7e, 0x8c, 0xbf, 0xd9, 0x7e, - 0xfb, 0x6a, 0x1c, 0xe7, 0x92, 0x36, 0xff, 0xb0, 0xff, 0xbc, 0x01, 0xcb, 0x72, 0x39, 0xc2, 0x99, - 0xa5, 0xcc, 0xd6, 0xa5, 0x32, 0xdf, 0x82, 0x4e, 0xe8, 0x65, 0xb9, 0x3b, 0x4e, 0x7c, 0x4f, 0xa6, - 0x36, 0x73, 0x4e, 0x9b, 0xc1, 0x7e, 0xca, 0x41, 0xcc, 0xa3, 0x65, 0xe6, 0x8a, 0x7b, 0x4b, 0x70, - 0xef, 0x0c, 0xf4, 0xc5, 0x10, 0x68, 0xb2, 0x39, 0xe8, 0xed, 0x96, 0x83, 0xbf, 0x19, 0xec, 0x34, - 0x18, 0x9e, 0xa2, 0x77, 0x5b, 0x0e, 0xfe, 0x66, 0x16, 0x0c, 0xe3, 0x73, 0xf4, 0x65, 0xcb, 0x61, - 0x3f, 0x19, 0xe4, 0x38, 0xf0, 0xd1, 0x75, 0x2d, 0x87, 0xfd, 0x64, 0x10, 0x2f, 0x7b, 0x89, 0x8e, - 0x6a, 0x39, 0xec, 0x27, 0xcb, 0xfa, 0xcf, 0xe2, 0x70, 0x3c, 0xa2, 0xbd, 0x16, 0x02, 0xc5, 0x17, - 0xb9, 0x06, 0xad, 0x24, 0x0d, 0x06, 0xd4, 0xf5, 0xf2, 0x53, 0x74, 0x26, 0xcb, 0x59, 0x44, 0xc0, - 0x5e, 0x7e, 0x6a, 0xaf, 0xc1, 0xaa, 0x32, 0xb4, 0x8a, 0x9e, 0x2f, 0x60, 0x41, 0x40, 0xa6, 0x1a, - 0xfd, 0x5d, 0x58, 0xc8, 0x39, 0x5a, 0xaf, 0x81, 0xbb, 0x40, 0x39, 0x96, 0xa9, 0x69, 0x47, 0xa2, - 0xd9, 0xbf, 0x09, 0x44, 0xe7, 0x26, 0x0c, 0x71, 0xaf, 0xa0, 0xc3, 0xc3, 0xf1, 0x8a, 0x49, 0x27, - 0x2b, 0x08, 0x7c, 0x8d, 0x87, 0xd1, 0xd3, 0xd4, 0x67, 0x81, 0x24, 0x7e, 0xf9, 0xad, 0xba, 0xe6, - 0x4f, 0x60, 0x49, 0x31, 0x7e, 0x92, 0xd3, 0x11, 0x53, 0xb8, 0x37, 0x8a, 0xc7, 0x51, 0x8e, 0x3c, - 0x2d, 0x47, 0x7c, 0x31, 0x0f, 0x44, 0xfd, 0x22, 0x4b, 0xcb, 0xe1, 0x1f, 0x64, 0x19, 0x1a, 0x81, - 0x2f, 0x2e, 0x4f, 0x8d, 0xc0, 0xb7, 0xff, 0xd7, 0x82, 0x55, 0x6d, 0x21, 0xaf, 0xed, 0x94, 0x15, - 0x8f, 0x6b, 0xd4, 0x78, 0xdc, 0x3d, 0x68, 0x1e, 0x07, 0x3e, 0xbb, 0xb3, 0x31, 0xbd, 0x6e, 0x48, - 0x72, 0xc6, 0x3a, 0x1c, 0x44, 0x61, 0xa8, 0x5e, 0xf6, 0x32, 0xeb, 0x35, 0xa7, 0xa2, 0x32, 0x94, - 0xca, 0x7e, 0xb8, 0x52, 0xdd, 0x0f, 0xa6, 0x2e, 0xe7, 0xcb, 0xba, 0xe4, 0xd9, 0xaa, 0xa2, 0xad, - 0x3c, 0x6f, 0x00, 0x50, 0x00, 0xa7, 0x9a, 0xf5, 0xd7, 0x01, 0x62, 0x85, 0x29, 0xfc, 0xef, 0x6a, - 0x45, 0x68, 0xe5, 0x82, 0x1a, 0xb2, 0xfd, 0x63, 0x4c, 0x35, 0x74, 0xe6, 0x42, 0xf9, 0x0f, 0x0d, - 0x9a, 0xdc, 0x17, 0x49, 0x85, 0x66, 0x66, 0x10, 0x7b, 0x1f, 0x89, 0xed, 0x0d, 0x06, 0xcc, 0xf4, - 0xda, 0xc5, 0x7c, 0xea, 0x19, 0xfe, 0x1c, 0x16, 0xc4, 0x0c, 0xe1, 0x16, 0x1c, 0xa1, 0x11, 0xf8, - 0xe4, 0x23, 0x00, 0xed, 0x1c, 0xe2, 0xeb, 0xba, 0x26, 0x65, 0x10, 0x93, 0xa4, 0x37, 0x20, 0x3b, - 0x0d, 0xdd, 0x3e, 0x81, 0xb5, 0x1a, 0x14, 0x26, 0x8a, 0xba, 0x56, 0x0b, 0x51, 0xe4, 0x37, 0xb9, - 0x01, 0xed, 0x3c, 0xce, 0xbd, 0xd0, 0x2d, 0x4e, 0x08, 0xcb, 0x01, 0x04, 0x3d, 0x67, 0x10, 0x0c, - 0x50, 0x71, 0xc8, 0x3d, 0x97, 0x05, 0xa8, 0x38, 0xf4, 0x6d, 0x0f, 0x13, 0x2f, 0x63, 0xd1, 0x42, - 0x85, 0xd3, 0x4c, 0xf6, 0x5d, 0x58, 0xf4, 0xf8, 0x14, 0xb9, 0xb0, 0x95, 0xd2, 0xc2, 0x1c, 0x85, - 0x60, 0x13, 0x3c, 0x81, 0xf6, 0xe3, 0xe8, 0x24, 0x18, 0x4a, 0xef, 0x78, 0x1b, 0x83, 0x95, 0x84, - 0x15, 0x39, 0x89, 0xef, 0xe5, 0x1e, 0x72, 0xeb, 0x38, 0xf8, 0xdb, 0xfe, 0x23, 0x0b, 0xba, 0x87, - 0x71, 0x9a, 0x9f, 0xc4, 0x61, 0x10, 0x8b, 0xf4, 0x9e, 0xa5, 0x23, 0x32, 0xfd, 0x17, 0x79, 0xa4, - 0xf8, 0x64, 0x11, 0x72, 0x10, 0x07, 0x11, 0xf7, 0xd5, 0x86, 0x50, 0x50, 0x1c, 0x44, 0xcc, 0x55, - 0xc9, 0x4d, 0x68, 0xfb, 0x34, 0x1b, 0xa4, 0x41, 0xc2, 0xae, 0x73, 0x22, 0x2c, 0xe8, 0x20, 0x46, - 0xf8, 0xd8, 0x0b, 0xbd, 0x68, 0x40, 0x45, 0x64, 0x97, 0x9f, 0xf6, 0x06, 0x86, 0x2b, 0x25, 0x89, - 0x76, 0xb3, 0x36, 0xc1, 0x62, 0x29, 0xbf, 0x06, 0xad, 0x44, 0x02, 0x85, 0xfb, 0xf5, 0xd4, 0x59, - 0x5d, 0x5a, 0x8e, 0x53, 0xa0, 0xda, 0xdb, 0x2c, 0xf7, 0x2f, 0xe8, 0x1d, 0x8d, 0x47, 0x23, 0x2f, - 0xbd, 0x90, 0xdc, 0x22, 0x68, 0xee, 0xc7, 0x41, 0xc4, 0x14, 0xc5, 0x16, 0x25, 0x93, 0x37, 0xf6, - 0x5b, 0x17, 0xbd, 0x61, 0x88, 0xae, 0x6b, 0x6b, 0xce, 0xd4, 0xd6, 0x75, 0x80, 0x84, 0xa6, 0x03, - 0x1a, 0xe5, 0xde, 0x50, 0xae, 0x58, 0x83, 0xd8, 0xa7, 0x40, 0x9e, 0x9e, 0x9c, 0x84, 0x41, 0x44, - 0x19, 0x5b, 0x21, 0xcc, 0x14, 0xed, 0x4f, 0x96, 0xc1, 0xe4, 0x34, 0x57, 0xe1, 0xf4, 0x13, 0x58, - 0x7d, 0x1a, 0xd5, 0x30, 0x92, 0xe4, 0xac, 0x69, 0xe4, 0x1a, 0x15, 0x72, 0x3f, 0x82, 0x8e, 0x26, - 0x78, 0x46, 0x3e, 0x80, 0x96, 0x90, 0x51, 0x5d, 0x14, 0xfa, 0x2a, 0x1a, 0x54, 0x56, 0xe8, 0x14, - 0xc8, 0xf6, 0x5f, 0x58, 0xd0, 0x2e, 0x24, 0xcb, 0xc8, 0x23, 0xb8, 0xc2, 0xd4, 0x2d, 0xa9, 0x5c, - 0x57, 0x54, 0x0a, 0x9c, 0x5d, 0xfc, 0x97, 0xe7, 0x85, 0x1c, 0xb9, 0x7f, 0x04, 0x50, 0x00, 0x6b, - 0xd2, 0xba, 0x07, 0x66, 0x5a, 0x77, 0xb5, 0x4a, 0x55, 0x8a, 0xa6, 0x65, 0x76, 0xff, 0xdc, 0x64, - 0xd7, 0xbd, 0x1a, 0x67, 0x11, 0x3e, 0xf8, 0x3d, 0x68, 0xf3, 0xbd, 0xc0, 0x22, 0x80, 0x14, 0xb8, - 0x53, 0x94, 0x36, 0x82, 0xc8, 0x01, 0xdc, 0x1b, 0x38, 0x4e, 0xde, 0x83, 0x25, 0x14, 0xd6, 0x8d, - 0xb9, 0x42, 0xc4, 0xc6, 0x36, 0x27, 0x74, 0x10, 0x45, 0xa8, 0x8c, 0x24, 0xb0, 0x61, 0x4c, 0x71, - 0x33, 0x2e, 0x82, 0x38, 0xa4, 0x7e, 0xa0, 0xa5, 0xd2, 0x93, 0xa4, 0xe4, 0xca, 0x12, 0x04, 0xc5, - 0x18, 0x57, 0xdd, 0xda, 0xa0, 0x3a, 0x42, 0x1e, 0x40, 0x47, 0x70, 0x44, 0xcd, 0x88, 0x23, 0xce, - 0x94, 0xb1, 0xcd, 0x27, 0x22, 0x02, 0x19, 0xc1, 0xba, 0x3e, 0x41, 0x49, 0x78, 0x05, 0x27, 0x7e, - 0x34, 0xbb, 0x84, 0x51, 0x45, 0x40, 0x32, 0xa8, 0x0c, 0xf4, 0x7f, 0x17, 0x7a, 0x93, 0x16, 0x54, - 0x63, 0xf6, 0xfb, 0xa6, 0xd9, 0xd7, 0x6b, 0x5c, 0x32, 0xd3, 0x0b, 0x88, 0x5f, 0xc0, 0xd6, 0x04, - 0x61, 0x5e, 0xa3, 0xea, 0xa0, 0x79, 0xaa, 0xee, 0x4d, 0x7f, 0x66, 0x41, 0x7f, 0xcf, 0xf7, 0x2b, - 0xc1, 0xa9, 0x28, 0x12, 0x7c, 0xdb, 0x21, 0x77, 0x07, 0xae, 0xd5, 0x0a, 0x24, 0xaa, 0x19, 0xaf, - 0x60, 0xc7, 0xa1, 0xa3, 0xf8, 0x8c, 0x7e, 0xdb, 0x22, 0xdb, 0x37, 0xe1, 0xfa, 0x24, 0xce, 0x42, - 0x36, 0x2c, 0xef, 0x99, 0xe5, 0x71, 0x95, 0x18, 0xfd, 0x87, 0x05, 0x4b, 0x66, 0xe1, 0xfc, 0x4d, - 0xdd, 0xc5, 0xdf, 0x01, 0x92, 0xd2, 0x2c, 0x77, 0xd3, 0x38, 0x0c, 0xd9, 0x95, 0xdc, 0xa7, 0xa1, - 0x77, 0x21, 0x4a, 0xf6, 0x5d, 0x36, 0xe2, 0xf0, 0x81, 0x4f, 0x18, 0x9c, 0x6c, 0xc1, 0x82, 0x97, - 0x04, 0x2e, 0xf3, 0x1a, 0x7e, 0x1f, 0x9f, 0xf7, 0x92, 0xe0, 0xc7, 0xf4, 0x82, 0xd8, 0xb0, 0x24, - 0x06, 0xdc, 0x90, 0x9e, 0xd1, 0x10, 0x73, 0xbe, 0x39, 0xa7, 0xcd, 0x87, 0x3f, 0x63, 0x20, 0x72, - 0x0f, 0xba, 0x49, 0x1a, 0x30, 0xf7, 0x2b, 0xde, 0x06, 0x16, 0x50, 0x9a, 0x15, 0x01, 0x97, 0xab, - 0xb3, 0x7f, 0x06, 0x57, 0x6b, 0x74, 0x21, 0x62, 0xd4, 0x0f, 0x61, 0xc5, 0x7c, 0x61, 0x90, 0x71, - 0x4a, 0x65, 0xad, 0xc6, 0x44, 0x67, 0xf9, 0xc4, 0xa0, 0x23, 0xb2, 0x4f, 0xc4, 0x71, 0xbc, 0x5c, - 0xd5, 0xb4, 0xec, 0xaf, 0x60, 0xbd, 0x00, 0xee, 0xc7, 0xd1, 0x19, 0x4d, 0x33, 0xe6, 0x6d, 0x04, - 0x9a, 0x27, 0x69, 0x2c, 0x0b, 0xb2, 0xf8, 0x9b, 0xe5, 0x6d, 0x79, 0x2c, 0xdc, 0xa0, 0x91, 0xc7, - 0x0c, 0x27, 0xf5, 0x72, 0x79, 0x4a, 0xe1, 0x6f, 0x96, 0x27, 0x07, 0x48, 0x84, 0xba, 0x38, 0xc6, - 0x5d, 0xb5, 0x2d, 0x60, 0x8c, 0x8b, 0xfd, 0x1c, 0xd3, 0x47, 0x5d, 0x14, 0xb1, 0xc6, 0xdf, 0x80, - 0x36, 0x5f, 0x23, 0x9b, 0x29, 0xd7, 0xb7, 0x6d, 0xac, 0xaf, 0x24, 0xa6, 0x03, 0x27, 0x0a, 0x6a, - 0xff, 0x57, 0x03, 0x3a, 0x98, 0xb1, 0x7e, 0x42, 0x73, 0x2f, 0x08, 0xa7, 0xe7, 0xd2, 0x3c, 0x07, - 0x6d, 0xa8, 0x1c, 0xf4, 0x2d, 0x58, 0xd2, 0x0b, 0x22, 0x17, 0xf2, 0x32, 0xab, 0x95, 0x43, 0x2e, - 0xc8, 0x6d, 0x58, 0xc6, 0xab, 0x75, 0x81, 0xc5, 0x7d, 0x66, 0x09, 0xa1, 0x0a, 0xcd, 0xbc, 0x08, - 0x5c, 0x29, 0x5d, 0x04, 0xd8, 0x30, 0x26, 0xd3, 0x6e, 0x16, 0xf8, 0xea, 0x9e, 0x80, 0x90, 0xa3, - 0xc0, 0xd7, 0x86, 0x71, 0xf6, 0x82, 0x36, 0x8c, 0xb3, 0xd9, 0x1d, 0x28, 0xa5, 0xfc, 0xa1, 0x00, - 0xdf, 0xbb, 0x16, 0xd1, 0xe9, 0x3a, 0x12, 0xf8, 0x2c, 0x18, 0xe1, 0x6b, 0x98, 0x28, 0x6e, 0xb7, - 0xb8, 0xc7, 0xf2, 0xaf, 0xe2, 0x9a, 0x06, 0xfa, 0x35, 0xad, 0xb8, 0xd4, 0xb5, 0x8d, 0x4b, 0xdd, - 0x0d, 0x68, 0xc7, 0x09, 0x8d, 0x5c, 0x71, 0xc5, 0xee, 0xf0, 0xec, 0x81, 0x81, 0x9e, 0x23, 0x44, - 0x94, 0x4c, 0x50, 0xe7, 0xd9, 0x2c, 0xf7, 0x52, 0x53, 0x31, 0x8d, 0xb2, 0x62, 0xe4, 0x45, 0x70, - 0xee, 0xb2, 0x8b, 0xa0, 0xbd, 0x87, 0x59, 0xb1, 0x64, 0x2c, 0xdc, 0xe7, 0x1d, 0x98, 0x47, 0x35, - 0x49, 0xcf, 0x59, 0x37, 0xae, 0x31, 0xc2, 0x29, 0x1c, 0x81, 0x63, 0xff, 0x08, 0xdf, 0x10, 0x71, - 0x68, 0x16, 0xd1, 0xaf, 0xc2, 0x22, 0xb7, 0x8a, 0xf2, 0x9a, 0x05, 0xfc, 0x7e, 0xe2, 0xdb, 0xff, - 0x6a, 0x01, 0x39, 0x1a, 0x1f, 0x8f, 0x82, 0xd9, 0xa9, 0xcd, 0x7e, 0x41, 0x27, 0xd0, 0x44, 0x37, - 0xe1, 0xee, 0x88, 0xbf, 0x4b, 0x1e, 0xd2, 0x2c, 0x7b, 0x48, 0x61, 0xce, 0x2b, 0xf5, 0x77, 0xf4, - 0x79, 0xdd, 0xf8, 0x2c, 0xc4, 0x87, 0x01, 0x8d, 0x72, 0x57, 0x14, 0x5b, 0x58, 0x88, 0x47, 0xc0, - 0x13, 0xdf, 0x3e, 0x82, 0x35, 0x63, 0x65, 0x42, 0xd3, 0xb7, 0xa0, 0xc3, 0x05, 0x48, 0x42, 0x6f, - 0xa0, 0xaa, 0xe1, 0x6d, 0x84, 0x1d, 0x22, 0x68, 0x9a, 0xbe, 0xfe, 0xc4, 0x82, 0xf5, 0xa3, 0x60, - 0x34, 0x0e, 0xbd, 0x9c, 0xfe, 0x3f, 0x68, 0xac, 0x58, 0xfe, 0x9c, 0xb1, 0x7c, 0xa9, 0xc9, 0x66, - 0xa1, 0x49, 0xfb, 0xbf, 0x2d, 0xd8, 0x28, 0x89, 0xa2, 0x72, 0x42, 0xd3, 0x99, 0x26, 0x14, 0x07, - 0x04, 0x92, 0xc6, 0xb4, 0x61, 0x30, 0x7d, 0x0b, 0x96, 0x46, 0x41, 0x14, 0x8c, 0xc6, 0x23, 0x97, - 0xeb, 0x9e, 0xcb, 0xd4, 0x11, 0xc0, 0x43, 0x34, 0x01, 0x43, 0xf2, 0x5e, 0x69, 0x48, 0x4d, 0x81, - 0xc4, 0x81, 0x1c, 0xe9, 0x5d, 0x58, 0x2f, 0xf2, 0x76, 0x77, 0xe8, 0x05, 0x91, 0x1b, 0xc6, 0x59, - 0x26, 0x6c, 0x4c, 0x8a, 0xb1, 0x03, 0x2f, 0x88, 0x3e, 0x8b, 0xb3, 0x4c, 0x0b, 0x02, 0xf3, 0x7a, - 0x10, 0x60, 0x09, 0x4c, 0xf7, 0xc5, 0xa9, 0x17, 0xd2, 0x8f, 0xe3, 0xd1, 0xf1, 0x9b, 0xd5, 0xfd, - 0x2d, 0xe8, 0xf0, 0xba, 0x5b, 0xee, 0xa5, 0x43, 0x2a, 0x2d, 0xd0, 0x46, 0xd8, 0x33, 0x04, 0xd5, - 0x9a, 0xe1, 0x3f, 0x2d, 0x20, 0xfb, 0x2c, 0x95, 0x09, 0x67, 0xf6, 0x07, 0x16, 0x4a, 0xf8, 0xbd, - 0xb9, 0xf0, 0xb0, 0x96, 0x80, 0x3c, 0x31, 0xdd, 0x6f, 0xce, 0x70, 0x3f, 0xb5, 0x9a, 0xe6, 0x6b, - 0x16, 0xc7, 0x2a, 0x71, 0xfc, 0x36, 0x2c, 0x9f, 0x7b, 0x61, 0x48, 0x73, 0xf5, 0xc4, 0x26, 0x2a, - 0xf1, 0x1c, 0x2a, 0xef, 0xe0, 0x72, 0xc1, 0x0b, 0xda, 0x82, 0x37, 0x60, 0xcd, 0x58, 0xaf, 0xc8, - 0x86, 0x1e, 0xc1, 0x26, 0x07, 0xef, 0x85, 0xe1, 0xcc, 0x51, 0xd5, 0xfe, 0xcb, 0x06, 0x6c, 0x55, - 0xa6, 0xa9, 0xb4, 0xc1, 0x74, 0xe3, 0x3b, 0x6a, 0xb9, 0xf5, 0x13, 0x76, 0xc5, 0xa7, 0x98, 0xd5, - 0xff, 0x47, 0x0b, 0xe6, 0x39, 0x68, 0xaa, 0x35, 0xbe, 0x90, 0x01, 0x41, 0x38, 0x1c, 0xbf, 0x11, - 0x7d, 0x7f, 0x36, 0x66, 0xfc, 0x3f, 0xfd, 0x59, 0x95, 0x47, 0x12, 0xf1, 0xa2, 0xfa, 0x43, 0xe8, - 0x96, 0x11, 0x5e, 0xeb, 0xc9, 0x89, 0x57, 0x55, 0x1e, 0x9f, 0x51, 0xed, 0x19, 0xf5, 0x17, 0x16, - 0xac, 0xec, 0xc7, 0x91, 0x1f, 0xb0, 0x13, 0xf3, 0xd0, 0x4b, 0xbd, 0x51, 0x26, 0x5e, 0xf2, 0x39, - 0x48, 0x96, 0xdd, 0x15, 0x60, 0x42, 0x81, 0x73, 0x07, 0x60, 0x70, 0x4a, 0x07, 0x2f, 0x5d, 0x51, - 0x71, 0xe4, 0xcf, 0xff, 0x0c, 0xf2, 0x71, 0xe0, 0x67, 0xe4, 0x7b, 0xb0, 0x56, 0x0c, 0xbb, 0x5e, - 0xe4, 0xbb, 0xa2, 0xdc, 0x88, 0xaf, 0x1b, 0x0a, 0x6f, 0x2f, 0xf2, 0xf7, 0xb2, 0x97, 0x19, 0xcb, - 0x15, 0x55, 0x95, 0xcd, 0x35, 0x42, 0xf8, 0x8a, 0x82, 0xef, 0x21, 0xd8, 0xfe, 0x1f, 0x0b, 0x4f, - 0x40, 0xb9, 0x2a, 0x61, 0xed, 0xa2, 0xb0, 0x86, 0xf5, 0x56, 0xc3, 0x64, 0x8d, 0x92, 0xc9, 0x08, - 0x34, 0x83, 0x9c, 0x8e, 0xe4, 0xc1, 0xc2, 0x7e, 0x93, 0x8f, 0xa1, 0xab, 0x56, 0xec, 0x26, 0xa8, - 0x16, 0xb1, 0x4d, 0xb6, 0x8a, 0x8b, 0xa3, 0xa1, 0x35, 0x67, 0x65, 0x50, 0x52, 0xa3, 0xdc, 0x5e, - 0x57, 0x66, 0x0a, 0xd4, 0x03, 0xd4, 0xb6, 0x88, 0x4f, 0xfc, 0x8b, 0x4b, 0x4d, 0x07, 0xe3, 0x9c, - 0xfa, 0x22, 0x55, 0x56, 0xdf, 0xf6, 0xbf, 0x5b, 0xb0, 0xb2, 0xe7, 0xfb, 0xb8, 0xee, 0x59, 0xc2, - 0x84, 0x5c, 0x65, 0xe3, 0x92, 0x55, 0xce, 0xfd, 0x92, 0xab, 0xfc, 0x95, 0x83, 0xc8, 0x04, 0x25, - 0xd8, 0x36, 0x74, 0x8b, 0x75, 0xd6, 0x9b, 0xd7, 0xfe, 0x0e, 0x10, 0x7e, 0xbd, 0x32, 0xd4, 0x51, - 0xc6, 0xda, 0x80, 0x35, 0x03, 0x4b, 0xc4, 0x9a, 0x4f, 0xe1, 0xee, 0x01, 0xcd, 0xf7, 0xd3, 0x8b, - 0x24, 0x8f, 0x65, 0x3a, 0xfb, 0x09, 0x4d, 0xe2, 0x2c, 0x90, 0x91, 0x8b, 0xce, 0x14, 0x7d, 0xfe, - 0xc9, 0x82, 0x7b, 0x33, 0x10, 0x12, 0x4b, 0xf8, 0xb2, 0x5a, 0x5f, 0xfa, 0x2d, 0xbd, 0xbd, 0x65, - 0x26, 0x2a, 0xbb, 0x0a, 0x22, 0xba, 0x0c, 0x14, 0xc9, 0xfe, 0x0f, 0x60, 0xd9, 0x1c, 0x7c, 0xad, - 0x50, 0x11, 0xc2, 0x9d, 0x4b, 0x84, 0x98, 0xc5, 0xe7, 0xee, 0xc0, 0xf2, 0xc0, 0x20, 0x21, 0x18, - 0x95, 0xa0, 0xf6, 0x3e, 0xbc, 0x7d, 0x29, 0x37, 0xa1, 0xb6, 0x89, 0x37, 0x74, 0xfb, 0x6f, 0x9b, - 0xb0, 0xf5, 0x22, 0xc8, 0x4f, 0xfd, 0xd4, 0x3b, 0x97, 0xde, 0x37, 0x8b, 0x90, 0xa5, 0xcb, 0x7b, - 0xa3, 0x5a, 0x6f, 0xb8, 0x0f, 0xab, 0x71, 0x44, 0xf1, 0x8e, 0xe1, 0x26, 0x5e, 0x96, 0x9d, 0xc7, - 0xa9, 0x3c, 0x4b, 0x57, 0xe2, 0x88, 0xb2, 0x7b, 0xc6, 0xa1, 0x00, 0x97, 0x4e, 0xe3, 0x66, 0xf9, - 0x34, 0xee, 0xc2, 0x5c, 0x12, 0x44, 0xe2, 0xcd, 0x84, 0xfd, 0x64, 0x67, 0x67, 0x9e, 0x7a, 0xbe, - 0x46, 0x59, 0x9c, 0x9d, 0x08, 0x55, 0x74, 0xf5, 0x2a, 0xfe, 0x42, 0xa9, 0x8a, 0xaf, 0xe9, 0x64, - 0xd1, 0xac, 0x5a, 0xdc, 0x80, 0xb6, 0xf8, 0xe9, 0xe6, 0xde, 0x50, 0x5c, 0x81, 0x40, 0x80, 0x9e, - 0x79, 0x43, 0x2d, 0x5b, 0x03, 0x23, 0x5b, 0xdb, 0x01, 0x38, 0xa1, 0xd4, 0x35, 0x2e, 0x43, 0xad, - 0x13, 0x4a, 0x79, 0xd0, 0x65, 0xa9, 0xf2, 0xb1, 0x17, 0xbd, 0x74, 0xb1, 0x06, 0xd1, 0xe1, 0xe2, - 0x30, 0xc0, 0xe7, 0xde, 0x08, 0x73, 0x62, 0x1c, 0x94, 0x32, 0x2d, 0x71, 0x8d, 0x32, 0xd8, 0x5e, - 0x51, 0x4d, 0x41, 0x94, 0x41, 0x90, 0x5f, 0xf4, 0x96, 0x8b, 0xf9, 0xfb, 0x41, 0x7e, 0xa1, 0xe6, - 0xa3, 0xce, 0xd2, 0x8b, 0xde, 0x4a, 0x31, 0x7f, 0x9f, 0x83, 0x98, 0x78, 0xd9, 0x79, 0x70, 0x42, - 0x79, 0x63, 0x48, 0x57, 0xb4, 0x4a, 0x31, 0xc8, 0x7e, 0xec, 0x63, 0x1a, 0x79, 0x1e, 0xa4, 0xda, - 0xe5, 0x74, 0x95, 0x5f, 0x61, 0x19, 0x50, 0xba, 0x86, 0x7d, 0x1f, 0xba, 0xd2, 0x5d, 0xf4, 0xde, - 0xc9, 0x94, 0x66, 0xe3, 0x30, 0x97, 0xbd, 0x93, 0xfc, 0xcb, 0x7e, 0x0f, 0xbb, 0x22, 0x3e, 0x8b, - 0x87, 0xc3, 0xe2, 0xfa, 0x24, 0x5c, 0x6b, 0x13, 0xe6, 0x43, 0x84, 0xcb, 0x29, 0xfc, 0xcb, 0x8e, - 0xb0, 0x9e, 0x53, 0x9a, 0x52, 0xbc, 0x5a, 0x04, 0xd1, 0x49, 0x2c, 0x6e, 0x0b, 0xf8, 0x9b, 0xed, - 0x45, 0x9f, 0x1e, 0x8f, 0x87, 0xb2, 0x07, 0x0a, 0x3f, 0x18, 0xe6, 0xb9, 0x97, 0x46, 0xe2, 0x40, - 0xc5, 0xdf, 0x0c, 0x93, 0xa6, 0x69, 0x9c, 0x8a, 0xd3, 0x93, 0x7f, 0xd8, 0x07, 0xb0, 0x75, 0xf4, - 0x7a, 0x22, 0x32, 0x42, 0xbc, 0x5a, 0x23, 0xb6, 0x3f, 0x7e, 0x3c, 0xfc, 0xeb, 0xb7, 0x60, 0xf9, - 0x20, 0xe6, 0x9b, 0xf1, 0x19, 0xf3, 0xc1, 0x94, 0x3c, 0x85, 0x05, 0xd1, 0xfc, 0x48, 0x36, 0x2b, - 0xdd, 0x90, 0xc8, 0xa3, 0xbf, 0x35, 0xa1, 0x4b, 0xd2, 0x5e, 0xfb, 0xe6, 0x5f, 0xfe, 0xed, 0xe7, - 0x8d, 0x25, 0xd2, 0x7e, 0x70, 0xf6, 0xde, 0x83, 0x21, 0xcd, 0x71, 0xb1, 0xa7, 0xb0, 0x64, 0xf4, - 0xab, 0x91, 0x6d, 0xa3, 0xe7, 0xac, 0xd4, 0xc6, 0xd6, 0xdf, 0x99, 0xda, 0x91, 0x66, 0xf7, 0x91, - 0xc5, 0x3a, 0x21, 0x82, 0x45, 0x86, 0x28, 0x9c, 0xf0, 0x57, 0xb0, 0xf2, 0x18, 0xab, 0x60, 0x8a, - 0x2a, 0xb9, 0x51, 0x50, 0xab, 0xed, 0xc3, 0xeb, 0xdf, 0x9c, 0x8c, 0x20, 0x38, 0x5e, 0x43, 0x8e, - 0x1b, 0x64, 0x8d, 0x71, 0xe4, 0x55, 0x36, 0xd5, 0xff, 0x46, 0x32, 0xe8, 0x8a, 0xce, 0x9e, 0x37, - 0xca, 0x73, 0x1b, 0x79, 0x6e, 0x92, 0x75, 0xc6, 0xd3, 0xe7, 0x0c, 0x0a, 0xa6, 0x31, 0x5e, 0xe2, - 0xf5, 0x4e, 0x34, 0x72, 0x7d, 0x62, 0x8b, 0x1a, 0x67, 0x79, 0xe3, 0x92, 0x16, 0x36, 0x73, 0x95, - 0x43, 0xca, 0x70, 0x55, 0x17, 0x1b, 0xf9, 0xb9, 0x85, 0x0e, 0x5e, 0xdb, 0x33, 0x49, 0xde, 0xbe, - 0xbc, 0x51, 0x93, 0xcb, 0x70, 0x77, 0xd6, 0x8e, 0x4e, 0xfb, 0x3b, 0x28, 0xcc, 0x75, 0xb2, 0x2d, - 0x84, 0x31, 0xba, 0x38, 0x65, 0x9f, 0x28, 0x19, 0x40, 0x47, 0x6f, 0x3f, 0x23, 0xd7, 0x6a, 0x5a, - 0x73, 0x14, 0xf3, 0xed, 0xfa, 0x41, 0xc1, 0xb0, 0x87, 0x0c, 0x09, 0xe9, 0x0a, 0x86, 0xaa, 0x5b, - 0x8d, 0x7c, 0x0d, 0x2b, 0xa5, 0xd6, 0x2d, 0x62, 0x97, 0xcc, 0x57, 0xd3, 0x86, 0xd7, 0x7f, 0x6b, - 0x2a, 0x8e, 0xe0, 0x7a, 0x1d, 0xb9, 0xf6, 0xec, 0x35, 0xcd, 0xca, 0x92, 0xf3, 0x87, 0xd6, 0x7d, - 0x92, 0xa1, 0x9d, 0xf5, 0x2e, 0xa3, 0x99, 0x78, 0xdf, 0xb8, 0xa4, 0x45, 0xa9, 0x62, 0x6b, 0xc9, - 0x13, 0xb7, 0x6b, 0x86, 0x9d, 0x1b, 0x5a, 0x6f, 0x1c, 0x46, 0xd9, 0x59, 0xf8, 0xee, 0xd4, 0xf7, - 0xd6, 0x89, 0xf6, 0xbe, 0xca, 0xce, 0x95, 0x5c, 0xe3, 0x3c, 0x21, 0x99, 0xd1, 0x7a, 0x28, 0x98, - 0x9a, 0x5e, 0x5d, 0xd3, 0xfc, 0x57, 0xbb, 0x52, 0xbd, 0x9b, 0x6f, 0xe2, 0x4a, 0xe3, 0x3c, 0xc9, - 0xc8, 0x2b, 0x58, 0xe6, 0xe1, 0xe2, 0xcd, 0x5b, 0x76, 0x07, 0xf9, 0x6e, 0xd9, 0xa4, 0x88, 0x19, - 0xba, 0x61, 0x5f, 0x40, 0x4b, 0x75, 0xc7, 0x90, 0x9e, 0xb6, 0x08, 0xa3, 0x0f, 0xab, 0x3f, 0xa1, - 0xcb, 0x46, 0x7a, 0xab, 0xbd, 0x24, 0x56, 0xc5, 0x7b, 0x66, 0x18, 0xe1, 0x9f, 0x01, 0x14, 0x6d, - 0x37, 0xe4, 0x6a, 0x85, 0xb2, 0xd2, 0x5c, 0xbf, 0x6e, 0x48, 0x36, 0x18, 0x23, 0xf9, 0x2e, 0x59, - 0x36, 0xc8, 0xcb, 0xfd, 0xa6, 0x2a, 0x41, 0xc6, 0x7e, 0x2b, 0x37, 0xea, 0xf4, 0x27, 0x77, 0x68, - 0x48, 0xa3, 0xd8, 0x72, 0xb3, 0xa9, 0x5b, 0x1e, 0x5b, 0xc1, 0x10, 0x4f, 0x0b, 0xad, 0x35, 0x64, - 0xbb, 0x8e, 0x4b, 0xed, 0x69, 0x51, 0xed, 0xf3, 0xb0, 0xaf, 0x22, 0xab, 0x35, 0xb2, 0x5a, 0x66, - 0x95, 0x91, 0x97, 0xf8, 0x07, 0x16, 0x5a, 0x67, 0x03, 0xd1, 0x69, 0x55, 0xdb, 0x3c, 0xfa, 0xd7, - 0x27, 0x0d, 0x4f, 0x38, 0x99, 0x44, 0x22, 0x88, 0x9b, 0x8a, 0x1b, 0x9c, 0xf7, 0x33, 0x18, 0x06, - 0x37, 0xda, 0x1e, 0xfa, 0x57, 0x6b, 0x46, 0x04, 0xf5, 0x0d, 0xa4, 0xbe, 0x42, 0x96, 0x54, 0x48, - 0x44, 0x5a, 0xdc, 0x26, 0xea, 0xa1, 0xc9, 0xb0, 0x49, 0xb9, 0x1b, 0xc1, 0x88, 0x81, 0x95, 0x9e, - 0x84, 0x4a, 0x0c, 0x54, 0x5d, 0x07, 0xe4, 0x0f, 0xcd, 0xe6, 0x06, 0xf9, 0xd8, 0x6a, 0x4f, 0x7d, - 0x1d, 0xad, 0xec, 0x96, 0x89, 0x2f, 0xa8, 0xf6, 0x0d, 0xe4, 0x7c, 0x95, 0x6c, 0x95, 0x39, 0x8b, - 0xd7, 0x58, 0xf2, 0x8d, 0x05, 0x6b, 0x35, 0x6f, 0x7d, 0x85, 0x04, 0x93, 0x5f, 0x26, 0x0b, 0x09, - 0xa6, 0x3d, 0x16, 0xda, 0x28, 0xc1, 0xb6, 0x8d, 0x12, 0x78, 0xbe, 0xaf, 0x24, 0x10, 0x79, 0x2d, - 0xf3, 0xcc, 0x3f, 0xb5, 0x60, 0xb3, 0xfe, 0x5d, 0x8f, 0xdc, 0x56, 0x2d, 0xdb, 0xd3, 0x5e, 0x1c, - 0xfb, 0x77, 0x2e, 0x43, 0x13, 0xd2, 0xdc, 0x46, 0x69, 0x6e, 0xd8, 0x7d, 0x26, 0x4d, 0x8a, 0xb8, - 0x75, 0x02, 0x9d, 0x63, 0x31, 0xc4, 0x7c, 0x39, 0x23, 0x5a, 0x6e, 0x51, 0xff, 0xc0, 0xd8, 0xbf, - 0x35, 0x05, 0xc3, 0x0c, 0x5f, 0x64, 0x43, 0x18, 0x04, 0x9f, 0x9b, 0xd4, 0x13, 0x9c, 0xd8, 0xa3, - 0xc5, 0xcb, 0x94, 0xb1, 0x47, 0x2b, 0x8f, 0x6d, 0xc6, 0x1e, 0xad, 0xbe, 0x7f, 0x55, 0xf6, 0x28, - 0x32, 0xc3, 0xb7, 0x30, 0xf2, 0x05, 0x6e, 0x1b, 0x51, 0x89, 0xeb, 0x95, 0xb7, 0x7a, 0x56, 0xb7, - 0x6d, 0xcc, 0x5a, 0x5b, 0x25, 0x54, 0xf2, 0x02, 0x1f, 0xd3, 0x9e, 0x03, 0x8b, 0x12, 0x9d, 0x6c, - 0x95, 0x09, 0x48, 0xca, 0xb5, 0x8f, 0x29, 0xf6, 0x16, 0x12, 0x5d, 0xb5, 0x3b, 0x3a, 0x51, 0x46, - 0xf3, 0x18, 0xda, 0xda, 0xc3, 0x01, 0x51, 0x41, 0xb6, 0xfa, 0x4e, 0xd2, 0xbf, 0x56, 0x3b, 0x66, - 0x86, 0x12, 0x7b, 0x85, 0x31, 0xc8, 0x10, 0x41, 0xf1, 0xf8, 0x7d, 0x58, 0x32, 0x6a, 0xf7, 0x85, - 0xf2, 0xeb, 0x5e, 0x17, 0x0a, 0xe5, 0xd7, 0x16, 0xfc, 0x65, 0xa2, 0x69, 0xa3, 0xf2, 0x33, 0x81, - 0xa2, 0x78, 0x7d, 0x09, 0x2d, 0x55, 0x32, 0x2f, 0xf4, 0x5f, 0xae, 0xa2, 0x5f, 0xc6, 0xc3, 0xb0, - 0xc1, 0x39, 0x9b, 0x7c, 0x1c, 0x8f, 0x8e, 0x85, 0xbe, 0xb4, 0x82, 0x70, 0xa1, 0xaf, 0x6a, 0x55, - 0xbc, 0xd0, 0x57, 0x5d, 0x05, 0xd9, 0xd0, 0xd7, 0x00, 0x11, 0xd4, 0x1a, 0x52, 0x58, 0x29, 0x15, - 0x62, 0x8b, 0xb4, 0xa2, 0xbe, 0xec, 0x5c, 0xa4, 0x15, 0x13, 0x2a, 0xb8, 0x66, 0xe2, 0xc6, 0xf9, - 0x79, 0x61, 0x58, 0xf8, 0x16, 0x0f, 0xf7, 0xbc, 0x4c, 0x69, 0xf8, 0xad, 0x51, 0x8f, 0x35, 0xfc, - 0xd6, 0xac, 0x69, 0x56, 0xc2, 0x3d, 0xe5, 0xb4, 0x9e, 0xc3, 0xa2, 0xac, 0x8f, 0x15, 0x4e, 0x5b, - 0xaa, 0x0c, 0xf6, 0x7b, 0xd5, 0x01, 0x41, 0xd5, 0x70, 0x5c, 0xcf, 0xf7, 0x91, 0xaa, 0x30, 0x84, - 0x56, 0x2d, 0x2b, 0x0c, 0x51, 0x2d, 0xb4, 0x15, 0x86, 0xa8, 0x2b, 0xaf, 0x19, 0x86, 0xe0, 0x91, - 0x4b, 0xf1, 0xf8, 0x3b, 0x0b, 0x6e, 0x5d, 0x5a, 0xec, 0x22, 0xef, 0xbe, 0x46, 0x5d, 0x8c, 0x0b, - 0xf4, 0xde, 0x6b, 0x57, 0xd2, 0xec, 0xbb, 0x28, 0xa6, 0x6d, 0xef, 0xc8, 0xc3, 0x14, 0xa7, 0xf9, - 0x1c, 0x5d, 0x95, 0xd5, 0x98, 0xd0, 0x7f, 0x63, 0xf1, 0x3f, 0x9f, 0x9b, 0x42, 0x97, 0xec, 0xce, - 0x28, 0x80, 0x14, 0xf8, 0xc1, 0xcc, 0xf8, 0x42, 0xdc, 0x3b, 0x28, 0xee, 0x4d, 0xfb, 0xda, 0x14, - 0x71, 0x99, 0xb0, 0x7f, 0x00, 0xd7, 0x54, 0x51, 0xcc, 0xa0, 0xfb, 0xe9, 0x38, 0xf2, 0xb3, 0xe2, - 0x5e, 0x3a, 0xa1, 0x72, 0x56, 0x38, 0x4e, 0xb9, 0x56, 0x62, 0x9e, 0x8f, 0xe7, 0x62, 0x94, 0x8b, - 0x71, 0xc2, 0x68, 0x33, 0xee, 0x09, 0xac, 0xca, 0x79, 0x9f, 0x06, 0x5e, 0xfe, 0x2b, 0xf3, 0xbc, - 0x89, 0x3c, 0xfb, 0xf6, 0x86, 0xce, 0xf3, 0x24, 0xf0, 0x72, 0xc5, 0x31, 0xc3, 0x37, 0x0e, 0xa3, - 0x0c, 0xa2, 0x5f, 0xbe, 0x6b, 0x0b, 0x24, 0xfa, 0xe5, 0xbb, 0xbe, 0x62, 0x63, 0x5e, 0xbe, 0x87, - 0x34, 0xe7, 0x15, 0x14, 0x5f, 0x30, 0x38, 0x83, 0xee, 0xd1, 0x44, 0xa6, 0x47, 0xbf, 0x34, 0x53, - 0x91, 0x03, 0xd9, 0xc8, 0x34, 0x2b, 0x31, 0xfd, 0xd0, 0xba, 0x7f, 0x3c, 0x8f, 0x7f, 0x17, 0xfc, - 0xfe, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x95, 0x64, 0xd9, 0x15, 0x4a, 0x3c, 0x00, 0x00, + // 4537 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7b, 0xcd, 0x6f, 0x1c, 0x57, + 0x72, 0x38, 0x66, 0x38, 0x22, 0x39, 0x35, 0x43, 0x72, 0xf8, 0xf8, 0x35, 0x1a, 0x92, 0xa2, 0xd4, + 0x5e, 0xc9, 0x92, 0xd7, 0xa6, 0x6c, 0x59, 0xbf, 0xdf, 0x3a, 0xeb, 0xcd, 0x26, 0x34, 0x2d, 0x73, + 0x15, 0x7b, 0x2d, 0xa6, 0xa9, 0x95, 0x00, 0x6f, 0xe0, 0x4e, 0x73, 0xfa, 0x71, 0xd8, 0x51, 0x4f, + 0x77, 0xbb, 0xbb, 0x87, 0xd4, 0x38, 0x01, 0x02, 0x18, 0x48, 0x90, 0x53, 0x72, 0x58, 0x04, 0xd8, + 0x43, 0x4e, 0x39, 0x06, 0xc8, 0x25, 0xc8, 0x29, 0x87, 0x45, 0xae, 0x41, 0x8e, 0xb9, 0xe4, 0x0f, + 0x08, 0x72, 0x4b, 0x02, 0x04, 0xc8, 0x25, 0xa7, 0xe0, 0xd5, 0xfb, 0xe8, 0xf7, 0xba, 0x7b, 0x86, + 0xa3, 0x5d, 0xad, 0x2f, 0xd2, 0x74, 0xbd, 0x7a, 0x55, 0xf5, 0xea, 0xd5, 0xab, 0x57, 0x55, 0xaf, + 0x08, 0xcd, 0x24, 0xee, 0xef, 0xc7, 0x49, 0x94, 0x45, 0x64, 0x7e, 0xd0, 0xcf, 0x92, 0xb8, 0xdf, + 0xdb, 0x19, 0x44, 0xd1, 0x20, 0xa0, 0xf7, 0xdd, 0xd8, 0xbf, 0xef, 0x86, 0x61, 0x94, 0xb9, 0x99, + 0x1f, 0x85, 0x29, 0xc7, 0xb2, 0x3a, 0xb0, 0x7c, 0x44, 0xb3, 0xc7, 0xe1, 0x59, 0x64, 0xd3, 0xaf, + 0x46, 0x34, 0xcd, 0xac, 0xbf, 0x6f, 0xc0, 0x8a, 0x02, 0xa5, 0x71, 0x14, 0xa6, 0x94, 0x6c, 0xc2, + 0xfc, 0x28, 0xce, 0xfc, 0x21, 0xed, 0xd6, 0x6e, 0xd6, 0xee, 0x36, 0x6d, 0xf1, 0x45, 0xee, 0xc3, + 0x9a, 0x7b, 0xe1, 0xfa, 0x81, 0x7b, 0x1a, 0x50, 0x87, 0xbe, 0xec, 0x9f, 0xbb, 0xe1, 0x80, 0xa6, + 0xdd, 0xfa, 0xcd, 0xda, 0xdd, 0x39, 0x9b, 0xa8, 0xa1, 0x47, 0x72, 0x84, 0x7c, 0x17, 0x56, 0x69, + 0xc8, 0x40, 0x9e, 0x86, 0x3e, 0x87, 0xe8, 0x1d, 0x31, 0x90, 0x23, 0x3f, 0x84, 0x4d, 0x8f, 0x9e, + 0xb9, 0xa3, 0x20, 0x73, 0xce, 0xa2, 0x84, 0xbe, 0x74, 0xe2, 0x24, 0xba, 0xf0, 0x3d, 0x9a, 0x74, + 0x1b, 0x28, 0xc5, 0xba, 0x18, 0xfd, 0x84, 0x0d, 0x1e, 0x8b, 0x31, 0xf2, 0x00, 0x36, 0xd4, 0x2c, + 0xdf, 0xcd, 0x9c, 0xfe, 0x28, 0x49, 0x68, 0xd8, 0x1f, 0x77, 0xaf, 0xe1, 0xa4, 0x35, 0x39, 0xc9, + 0x77, 0xb3, 0x43, 0x31, 0x44, 0x9e, 0x43, 0x27, 0x1d, 0x9d, 0xa6, 0xe3, 0x34, 0xa3, 0x43, 0x27, + 0xcd, 0xdc, 0x6c, 0x94, 0x76, 0xe7, 0x6f, 0xce, 0xdd, 0x6d, 0x3d, 0x78, 0x7b, 0x9f, 0xab, 0x71, + 0xbf, 0xa0, 0x92, 0xfd, 0x13, 0x89, 0x7f, 0x82, 0xe8, 0x8f, 0xc2, 0x2c, 0x19, 0xdb, 0x2b, 0xa9, + 0x09, 0x25, 0x9f, 0xc3, 0x52, 0x12, 0xf7, 0x1d, 0x1a, 0x7a, 0x71, 0xe4, 0x87, 0x59, 0xda, 0x5d, + 0x40, 0xaa, 0xf7, 0x26, 0x51, 0xb5, 0xe3, 0xfe, 0x23, 0x89, 0xcb, 0x49, 0xb6, 0x13, 0x0d, 0xd4, + 0xfb, 0x08, 0xd6, 0xab, 0x18, 0x93, 0x0e, 0xcc, 0xbd, 0xa0, 0x63, 0xb1, 0x3b, 0xec, 0x27, 0x59, + 0x87, 0x6b, 0x17, 0x6e, 0x30, 0xa2, 0xb8, 0x19, 0x8b, 0x36, 0xff, 0xf8, 0x7e, 0xfd, 0x83, 0x5a, + 0xef, 0x29, 0xac, 0x96, 0xd8, 0x54, 0x10, 0xb8, 0xa7, 0x13, 0x68, 0x3d, 0x58, 0x93, 0x22, 0xdb, + 0xc7, 0x87, 0x72, 0xae, 0x46, 0xd5, 0xba, 0x05, 0x7b, 0x47, 0x34, 0x3b, 0x8c, 0x86, 0xc3, 0x51, + 0xe8, 0xf7, 0xd1, 0xc6, 0x6c, 0x1a, 0xb8, 0x63, 0x9a, 0xa4, 0xd2, 0xb2, 0x3e, 0x87, 0xf5, 0xaa, + 0x71, 0xd2, 0x85, 0x05, 0xb1, 0xf7, 0xc8, 0x7f, 0xd1, 0x96, 0x9f, 0x64, 0x07, 0x9a, 0xfd, 0x28, + 0x0c, 0x69, 0x3f, 0xa3, 0x9e, 0x58, 0x48, 0x0e, 0xb0, 0xfe, 0xb4, 0x0e, 0x37, 0x27, 0xf3, 0x14, + 0xa6, 0xfb, 0x35, 0x6c, 0xf6, 0x75, 0x04, 0x27, 0x11, 0x18, 0xdd, 0x1a, 0x6e, 0xc5, 0xa1, 0xb6, + 0x15, 0x53, 0x29, 0xed, 0x57, 0x8e, 0xf2, 0x4d, 0xda, 0xe8, 0x57, 0x8d, 0xf5, 0xce, 0xa0, 0x37, + 0x79, 0x52, 0x85, 0xca, 0x1f, 0x98, 0x2a, 0xdf, 0x91, 0xa2, 0x55, 0x11, 0xd1, 0x75, 0xff, 0x3d, + 0xd8, 0x3a, 0xa2, 0x21, 0x4d, 0xfc, 0xbe, 0x32, 0x0e, 0xa1, 0x73, 0xa6, 0x41, 0x65, 0x93, 0x82, + 0x55, 0x0e, 0xb0, 0x7a, 0xd0, 0x2d, 0x4f, 0xe4, 0xcb, 0xb5, 0x36, 0x61, 0xfd, 0x88, 0x66, 0x0a, + 0xae, 0x76, 0xf1, 0x17, 0x35, 0xd8, 0xc0, 0x81, 0xf4, 0x34, 0x1d, 0xf3, 0x01, 0xa1, 0xea, 0xdf, + 0x87, 0x55, 0x45, 0x3a, 0x95, 0xc7, 0x88, 0x6b, 0xf9, 0x7d, 0x4d, 0xcb, 0xe5, 0x99, 0xf9, 0x61, + 0x4a, 0xf5, 0xd3, 0x94, 0x9f, 0x49, 0x01, 0xee, 0x1d, 0xc2, 0x46, 0x25, 0xea, 0xab, 0xd8, 0xbf, + 0xd5, 0x85, 0xcd, 0x23, 0x9a, 0x69, 0x66, 0xac, 0x19, 0x68, 0x4b, 0x03, 0x33, 0xbb, 0x4c, 0x33, + 0x37, 0xc9, 0x72, 0xbb, 0x14, 0x9f, 0xe4, 0x36, 0x2c, 0x07, 0x7e, 0x9a, 0xd1, 0xd0, 0x71, 0x3d, + 0x2f, 0xa1, 0x29, 0x77, 0x79, 0x4d, 0x7b, 0x89, 0x43, 0x0f, 0x38, 0xd0, 0xfa, 0x87, 0x1a, 0xdb, + 0x98, 0x02, 0x2b, 0xa1, 0xac, 0xcf, 0xa0, 0x99, 0x7b, 0x05, 0xae, 0xa4, 0x7d, 0x4d, 0x49, 0x55, + 0x73, 0xf6, 0x0b, 0xae, 0x21, 0x27, 0xd0, 0xfb, 0x5d, 0x58, 0x7e, 0xdd, 0x07, 0xfa, 0x03, 0xe8, + 0x09, 0xdb, 0x90, 0x1e, 0xf9, 0x73, 0x77, 0x48, 0xa5, 0x5d, 0xf5, 0x60, 0x51, 0x3a, 0x70, 0xc1, + 0x43, 0x7d, 0x5b, 0xbb, 0xb0, 0x5d, 0x39, 0x53, 0x18, 0xd6, 0x7d, 0x58, 0x3b, 0xa2, 0x99, 0x72, + 0xf3, 0x92, 0xe2, 0x44, 0x2f, 0x60, 0x3d, 0x44, 0x4b, 0xd4, 0x26, 0x08, 0x15, 0xee, 0x40, 0x33, + 0xbf, 0x44, 0x84, 0x6d, 0x2b, 0x80, 0xf5, 0x00, 0xcd, 0x54, 0xce, 0x7a, 0xf2, 0xf4, 0xd8, 0xa6, + 0x7c, 0xda, 0x75, 0x58, 0x8c, 0xb2, 0xd8, 0xe9, 0x47, 0x9e, 0x14, 0x7d, 0x21, 0xca, 0xe2, 0xc3, + 0xc8, 0xa3, 0xc2, 0x34, 0xb4, 0x39, 0xca, 0x34, 0xfe, 0x9a, 0x6f, 0xa5, 0x39, 0x24, 0xe4, 0xf8, + 0x1d, 0x68, 0x4a, 0x82, 0x72, 0x2b, 0xdf, 0xd1, 0xb6, 0xb2, 0x6a, 0xce, 0xfe, 0x13, 0xce, 0x51, + 0xec, 0xe4, 0xa2, 0x10, 0x20, 0xed, 0x7d, 0x08, 0x4b, 0xc6, 0xd0, 0x55, 0x96, 0xdd, 0xd4, 0xb7, + 0xec, 0x21, 0x6c, 0x7e, 0xec, 0xa7, 0xfa, 0x8d, 0x3b, 0xcb, 0x76, 0x7d, 0x09, 0xcb, 0xc7, 0xae, + 0x9f, 0xa4, 0x27, 0xa3, 0x38, 0x8e, 0xd0, 0xbc, 0xdf, 0x84, 0x95, 0xfc, 0x5a, 0x8f, 0xd9, 0x98, + 0x98, 0xb4, 0xac, 0xc0, 0x38, 0x83, 0xbc, 0x01, 0x4b, 0xf2, 0x3a, 0xe7, 0x68, 0x5c, 0xa4, 0xb6, + 0x00, 0x22, 0x92, 0xf5, 0x4d, 0xc3, 0x50, 0x9d, 0x11, 0x58, 0x10, 0x68, 0x84, 0xae, 0x0a, 0x2b, + 0xf0, 0xb7, 0x6e, 0x08, 0x75, 0xf3, 0x3a, 0xe8, 0xc2, 0xc2, 0x05, 0x4d, 0x4e, 0xa3, 0x94, 0x62, + 0xcc, 0xb0, 0x68, 0xcb, 0x4f, 0x26, 0xc8, 0x28, 0xf5, 0xc3, 0x81, 0x93, 0xba, 0xa1, 0x77, 0x1a, + 0xbd, 0xc4, 0x08, 0x61, 0xd1, 0x6e, 0x23, 0xf0, 0x84, 0xc3, 0xc8, 0x2d, 0x68, 0x9f, 0x67, 0x59, + 0xec, 0xb0, 0xd0, 0x25, 0x1a, 0x65, 0x22, 0x20, 0x68, 0x31, 0xd8, 0x53, 0x0e, 0x62, 0x07, 0x1b, + 0x51, 0x46, 0x29, 0x4d, 0xdc, 0x01, 0x0d, 0xb3, 0xee, 0x3c, 0x3f, 0xd8, 0x0c, 0xfa, 0x13, 0x09, + 0x24, 0xbb, 0x00, 0x88, 0x16, 0x27, 0xd1, 0xcb, 0x71, 0x77, 0x81, 0x9b, 0x1e, 0x83, 0x1c, 0x33, + 0x00, 0xd3, 0xdf, 0xa9, 0x9b, 0x52, 0x19, 0x7a, 0xf8, 0x34, 0xed, 0x2e, 0x72, 0xfd, 0x31, 0xf0, + 0xa1, 0x82, 0x12, 0x87, 0xc5, 0x1d, 0x42, 0xeb, 0x8e, 0x9b, 0xa6, 0x34, 0x4b, 0xbb, 0x4d, 0x34, + 0xa0, 0x87, 0x15, 0x06, 0x54, 0x88, 0x3f, 0xc4, 0xbc, 0x03, 0x9c, 0xa6, 0xe2, 0x0f, 0x03, 0xca, + 0xe2, 0x2d, 0x77, 0x94, 0x9d, 0xd3, 0x30, 0x63, 0xb7, 0x07, 0x63, 0x12, 0xfb, 0x5d, 0x40, 0xdd, + 0x74, 0x8c, 0x81, 0x83, 0xd8, 0xef, 0x7d, 0xc1, 0x82, 0x8b, 0x32, 0xd5, 0x0a, 0x13, 0x7c, 0xdb, + 0x74, 0x25, 0x9b, 0x52, 0x58, 0xd3, 0x8e, 0x74, 0xd3, 0xbc, 0x84, 0xce, 0x11, 0xcd, 0x9e, 0xfa, + 0xfd, 0x17, 0x34, 0x99, 0xc1, 0x28, 0xc9, 0x5d, 0x68, 0x30, 0x8b, 0x12, 0x0c, 0xd6, 0xd5, 0x4d, + 0x28, 0x22, 0x36, 0xc6, 0xc8, 0x46, 0x0c, 0xb6, 0x17, 0xa8, 0x39, 0x27, 0x1b, 0xc7, 0xdc, 0x2e, + 0x9a, 0x76, 0x13, 0x21, 0x4f, 0xc7, 0x31, 0xb5, 0x9e, 0x41, 0x5b, 0x9f, 0xc4, 0x9c, 0x86, 0x47, + 0x03, 0x7f, 0xe8, 0x67, 0x34, 0x91, 0x4e, 0x43, 0x01, 0x98, 0x3d, 0xb2, 0x2d, 0x12, 0x76, 0x8c, + 0xbf, 0xd9, 0x79, 0xfb, 0x6a, 0x14, 0x65, 0x92, 0x36, 0xff, 0xb0, 0xfe, 0xb2, 0x0e, 0xcb, 0x72, + 0x39, 0xc2, 0x98, 0xa5, 0xcc, 0xb5, 0x2b, 0x65, 0xbe, 0x05, 0xed, 0xc0, 0x4d, 0x33, 0x67, 0x14, + 0x7b, 0xae, 0x0c, 0x6d, 0xe6, 0xec, 0x16, 0x83, 0xfd, 0x84, 0x83, 0x98, 0x45, 0xcb, 0xc8, 0x15, + 0xcf, 0x96, 0xe0, 0xde, 0xee, 0xeb, 0x8b, 0x21, 0xd0, 0x60, 0x73, 0xd0, 0xda, 0x6b, 0x36, 0xfe, + 0x66, 0xb0, 0x73, 0x7f, 0x70, 0x8e, 0xd6, 0x5d, 0xb3, 0xf1, 0x37, 0xdb, 0xc1, 0x20, 0xba, 0x44, + 0x5b, 0xae, 0xd9, 0xec, 0x27, 0x83, 0x9c, 0xfa, 0x1e, 0x9a, 0x6e, 0xcd, 0x66, 0x3f, 0x19, 0xc4, + 0x4d, 0x5f, 0xa0, 0xa1, 0xd6, 0x6c, 0xf6, 0x93, 0x45, 0xfd, 0x17, 0x51, 0x30, 0x1a, 0xd2, 0x6e, + 0x13, 0x81, 0xe2, 0x8b, 0x6c, 0x43, 0x33, 0x4e, 0xfc, 0x3e, 0x75, 0xdc, 0xec, 0x1c, 0x8d, 0xa9, + 0x66, 0x2f, 0x22, 0xe0, 0x20, 0x3b, 0xb7, 0xd6, 0x60, 0x55, 0x6d, 0xb4, 0xf2, 0x9e, 0xcf, 0x61, + 0x41, 0x40, 0xa6, 0x6e, 0xfa, 0xbb, 0xb0, 0x90, 0x71, 0xb4, 0x6e, 0x1d, 0x4f, 0x81, 0x32, 0x2c, + 0x53, 0xd3, 0xb6, 0x44, 0xb3, 0x7e, 0x0b, 0x88, 0xce, 0x4d, 0x6c, 0xc4, 0xbd, 0x9c, 0x0e, 0x77, + 0xc7, 0x2b, 0x26, 0x9d, 0x34, 0x27, 0xf0, 0x35, 0x5e, 0x46, 0x4f, 0x12, 0x8f, 0x39, 0x92, 0xe8, + 0xc5, 0xb7, 0x6a, 0x9a, 0x3f, 0x86, 0x25, 0xc5, 0xf8, 0x71, 0x46, 0x87, 0x4c, 0xe1, 0xee, 0x30, + 0x1a, 0x85, 0x19, 0xf2, 0xac, 0xd9, 0xe2, 0x8b, 0x59, 0x20, 0xea, 0x17, 0x59, 0xd6, 0x6c, 0xfe, + 0x41, 0x96, 0xa1, 0xee, 0x7b, 0x22, 0x79, 0xaa, 0xfb, 0x9e, 0xf5, 0xbf, 0x35, 0x58, 0xd5, 0x16, + 0xf2, 0xca, 0x46, 0x59, 0xb2, 0xb8, 0x7a, 0x85, 0xc5, 0xdd, 0x83, 0xc6, 0xa9, 0xef, 0xb1, 0x9c, + 0x8d, 0xe9, 0x75, 0x43, 0x92, 0x33, 0xd6, 0x61, 0x23, 0x0a, 0x43, 0x75, 0xd3, 0x17, 0x69, 0xb7, + 0x31, 0x15, 0x95, 0xa1, 0x94, 0xce, 0xc3, 0xb5, 0xf2, 0x79, 0x30, 0x75, 0x39, 0x5f, 0xd4, 0x25, + 0x8f, 0x56, 0x15, 0x6d, 0x65, 0x79, 0x7d, 0x80, 0x1c, 0x38, 0x75, 0x5b, 0x7f, 0x03, 0x20, 0x52, + 0x98, 0xc2, 0xfe, 0xae, 0x97, 0x84, 0x56, 0x26, 0xa8, 0x21, 0x5b, 0x9f, 0x62, 0xa8, 0xa1, 0x33, + 0x17, 0xca, 0x7f, 0x60, 0xd0, 0xe4, 0xb6, 0x48, 0x4a, 0x34, 0x53, 0x83, 0xd8, 0xfb, 0x48, 0xec, + 0xa0, 0xdf, 0x67, 0x5b, 0xaf, 0x25, 0xe6, 0x53, 0xef, 0xf0, 0x67, 0xb0, 0x20, 0x66, 0x08, 0xb3, + 0xe0, 0x08, 0x75, 0xdf, 0x23, 0x1f, 0x02, 0x68, 0xf7, 0x10, 0x5f, 0xd7, 0xb6, 0x94, 0x41, 0x4c, + 0x92, 0xd6, 0x80, 0xec, 0x34, 0x74, 0xeb, 0x0c, 0xd6, 0x2a, 0x50, 0x98, 0x28, 0x2a, 0xad, 0x16, + 0xa2, 0xc8, 0x6f, 0xb2, 0x07, 0xad, 0x2c, 0xca, 0xdc, 0xc0, 0xc9, 0x6f, 0x88, 0x9a, 0x0d, 0x08, + 0x7a, 0xc6, 0x20, 0xe8, 0xa0, 0xa2, 0x80, 0x5b, 0x2e, 0x73, 0x50, 0x51, 0xe0, 0x59, 0x2e, 0x06, + 0x5e, 0xc6, 0xa2, 0x85, 0x0a, 0xa7, 0x6d, 0xd9, 0x77, 0x61, 0xd1, 0xe5, 0x53, 0xe4, 0xc2, 0x56, + 0x0a, 0x0b, 0xb3, 0x15, 0x82, 0x45, 0xf0, 0x06, 0x3a, 0x8c, 0xc2, 0x33, 0x7f, 0x20, 0xad, 0xe3, + 0x4d, 0x74, 0x56, 0x12, 0x96, 0xc7, 0x24, 0x9e, 0x9b, 0xb9, 0xc8, 0xad, 0x6d, 0xe3, 0x6f, 0xeb, + 0x4f, 0x6a, 0xd0, 0x39, 0x8e, 0x92, 0xec, 0x2c, 0x0a, 0xfc, 0x48, 0x84, 0xf7, 0x2c, 0x1c, 0x91, + 0xe1, 0xbf, 0x88, 0x23, 0xc5, 0x27, 0xf3, 0x90, 0xfd, 0xc8, 0x0f, 0xb9, 0xad, 0xd6, 0x85, 0x82, + 0x22, 0x3f, 0x64, 0xa6, 0x4a, 0x6e, 0x42, 0xcb, 0xa3, 0x69, 0x3f, 0xf1, 0x63, 0x96, 0xce, 0x09, + 0xb7, 0xa0, 0x83, 0x18, 0xe1, 0x53, 0x37, 0x70, 0xc3, 0x3e, 0x15, 0x9e, 0x5d, 0x7e, 0x5a, 0x1b, + 0xe8, 0xae, 0x94, 0x24, 0x5a, 0x66, 0x6d, 0x82, 0xc5, 0x52, 0xfe, 0x3f, 0x34, 0x63, 0x09, 0x14, + 0xe6, 0xd7, 0x55, 0x77, 0x75, 0x61, 0x39, 0x76, 0x8e, 0x6a, 0xed, 0xb0, 0xd8, 0x3f, 0xa7, 0x77, + 0x32, 0x1a, 0x0e, 0xdd, 0x64, 0x2c, 0xb9, 0x85, 0xd0, 0x38, 0x8c, 0xfc, 0x90, 0x29, 0x8a, 0x2d, + 0x4a, 0x06, 0x6f, 0xec, 0xb7, 0x2e, 0x7a, 0xdd, 0x10, 0x5d, 0xd7, 0xd6, 0x9c, 0xa9, 0xad, 0x1b, + 0x00, 0x31, 0x4d, 0xfa, 0x34, 0xcc, 0xdc, 0x81, 0x5c, 0xb1, 0x06, 0xb1, 0xce, 0x81, 0x3c, 0x39, + 0x3b, 0x0b, 0xfc, 0x90, 0x32, 0xb6, 0x42, 0x98, 0x29, 0xda, 0x9f, 0x2c, 0x83, 0xc9, 0x69, 0xae, + 0xc4, 0xe9, 0xc7, 0xb0, 0xfa, 0x24, 0xac, 0x60, 0x24, 0xc9, 0xd5, 0xa6, 0x91, 0xab, 0x97, 0xc8, + 0xfd, 0x08, 0xda, 0x9a, 0xe0, 0x29, 0xf9, 0x00, 0x9a, 0x42, 0x46, 0x95, 0x28, 0xf4, 0x94, 0x37, + 0x28, 0xad, 0xd0, 0xce, 0x91, 0xad, 0x9f, 0xd7, 0xa0, 0x95, 0x4b, 0x96, 0x92, 0x87, 0x70, 0x8d, + 0xa9, 0x5b, 0x52, 0xb9, 0xa1, 0xa8, 0xe4, 0x38, 0xfb, 0xf8, 0x2f, 0x8f, 0x0b, 0x39, 0x72, 0xef, + 0x04, 0x20, 0x07, 0x56, 0x84, 0x75, 0xf7, 0xcd, 0xb0, 0xee, 0x7a, 0x99, 0xaa, 0x14, 0x4d, 0x8b, + 0xec, 0xfe, 0xb9, 0xc1, 0xd2, 0xbd, 0x0a, 0x63, 0x11, 0x36, 0xf8, 0x0e, 0xb4, 0xf8, 0x59, 0x60, + 0x1e, 0x40, 0x0a, 0xdc, 0xce, 0x4b, 0x1b, 0x7e, 0x68, 0x03, 0x9e, 0x0d, 0x1c, 0x27, 0xef, 0xc1, + 0x12, 0x0a, 0xeb, 0x44, 0x5c, 0x21, 0xe2, 0x60, 0x9b, 0x13, 0xda, 0x88, 0x22, 0x54, 0x46, 0x62, + 0xd8, 0x30, 0xa6, 0x38, 0x29, 0x17, 0x41, 0x5c, 0x52, 0x3f, 0xd0, 0x42, 0xe9, 0x49, 0x52, 0x72, + 0x65, 0x09, 0x82, 0x62, 0x8c, 0xab, 0x6e, 0xad, 0x5f, 0x1e, 0x21, 0xf7, 0xa1, 0x2d, 0x38, 0xa2, + 0x66, 0xc4, 0x15, 0x67, 0xca, 0xd8, 0xe2, 0x13, 0x11, 0x81, 0x0c, 0x61, 0x5d, 0x9f, 0xa0, 0x24, + 0xbc, 0x86, 0x13, 0x3f, 0x9c, 0x5d, 0xc2, 0xb0, 0x24, 0x20, 0xe9, 0x97, 0x06, 0x7a, 0xbf, 0x07, + 0xdd, 0x49, 0x0b, 0xaa, 0xd8, 0xf6, 0xb7, 0xcc, 0x6d, 0x5f, 0xaf, 0x30, 0xc9, 0x54, 0x2f, 0x20, + 0x7e, 0x01, 0x5b, 0x13, 0x84, 0x79, 0x85, 0xaa, 0x83, 0x66, 0xa9, 0xba, 0x35, 0xfd, 0x45, 0x0d, + 0x7a, 0x07, 0x9e, 0x57, 0x72, 0x4e, 0x79, 0x91, 0xe0, 0xdb, 0x76, 0xb9, 0xbb, 0xb0, 0x5d, 0x29, + 0x90, 0xa8, 0x66, 0xbc, 0x84, 0x5d, 0x9b, 0x0e, 0xa3, 0x0b, 0xfa, 0x6d, 0x8b, 0x6c, 0xdd, 0x84, + 0x1b, 0x93, 0x38, 0x0b, 0xd9, 0xb0, 0xbc, 0x67, 0x96, 0xc7, 0x55, 0x60, 0xf4, 0x1f, 0x35, 0x58, + 0x32, 0x0b, 0xe7, 0xaf, 0x2b, 0x17, 0x7f, 0x1b, 0x48, 0x42, 0xd3, 0xcc, 0x49, 0xa2, 0x20, 0x60, + 0x29, 0xb9, 0x47, 0x03, 0x77, 0x2c, 0x4a, 0xf6, 0x1d, 0x36, 0x62, 0xf3, 0x81, 0x8f, 0x19, 0x9c, + 0x6c, 0xc1, 0x82, 0x1b, 0xfb, 0x0e, 0xb3, 0x1a, 0x9e, 0x8f, 0xcf, 0xbb, 0xb1, 0xff, 0x29, 0x1d, + 0x13, 0x0b, 0x96, 0xc4, 0x80, 0x13, 0xd0, 0x0b, 0x1a, 0x60, 0xcc, 0x37, 0x67, 0xb7, 0xf8, 0xf0, + 0x67, 0x0c, 0x44, 0xee, 0x41, 0x27, 0x4e, 0x7c, 0x66, 0x7e, 0xf9, 0xdb, 0xc0, 0x02, 0x4a, 0xb3, + 0x22, 0xe0, 0x72, 0x75, 0xd6, 0x4f, 0xe1, 0x7a, 0x85, 0x2e, 0x84, 0x8f, 0xfa, 0x21, 0xac, 0x98, + 0x2f, 0x0c, 0xd2, 0x4f, 0xa9, 0xa8, 0xd5, 0x98, 0x68, 0x2f, 0x9f, 0x19, 0x74, 0x44, 0xf4, 0x89, + 0x38, 0xb6, 0x9b, 0xa9, 0x9a, 0x96, 0xf5, 0x15, 0xac, 0xe7, 0xc0, 0xc3, 0x28, 0xbc, 0xa0, 0x49, + 0xca, 0xac, 0x8d, 0x40, 0xe3, 0x2c, 0x89, 0x64, 0x41, 0x16, 0x7f, 0xb3, 0xb8, 0x2d, 0x8b, 0x84, + 0x19, 0xd4, 0xb3, 0x88, 0xe1, 0x24, 0x6e, 0x26, 0x6f, 0x29, 0xfc, 0xcd, 0xe2, 0x64, 0x1f, 0x89, + 0x50, 0x07, 0xc7, 0xb8, 0xa9, 0xb6, 0x04, 0x8c, 0x71, 0xb1, 0x9e, 0x61, 0xf8, 0xa8, 0x8b, 0x22, + 0xd6, 0xf8, 0x9b, 0xd0, 0xe2, 0x6b, 0x64, 0x33, 0xe5, 0xfa, 0x76, 0x8c, 0xf5, 0x15, 0xc4, 0xb4, + 0xe1, 0x4c, 0x41, 0xad, 0xff, 0xaa, 0x43, 0x1b, 0x23, 0xd6, 0x8f, 0x69, 0xe6, 0xfa, 0xc1, 0xf4, + 0x58, 0x9a, 0xc7, 0xa0, 0x75, 0x15, 0x83, 0xbe, 0x01, 0x4b, 0x7a, 0x41, 0x64, 0x2c, 0x93, 0x59, + 0xad, 0x1c, 0x32, 0x26, 0xb7, 0x61, 0x19, 0x53, 0xeb, 0x1c, 0x8b, 0xdb, 0xcc, 0x12, 0x42, 0x15, + 0x9a, 0x99, 0x08, 0x5c, 0x2b, 0x24, 0x02, 0x6c, 0x18, 0x83, 0x69, 0x27, 0xf5, 0x3d, 0x95, 0x27, + 0x20, 0xe4, 0xc4, 0xf7, 0xb4, 0x61, 0x9c, 0xbd, 0xa0, 0x0d, 0xe3, 0x6c, 0x96, 0x03, 0x25, 0x94, + 0x3f, 0x14, 0xe0, 0x7b, 0xd7, 0x22, 0x1a, 0x5d, 0x5b, 0x02, 0x9f, 0xfa, 0x43, 0x7c, 0x0d, 0x13, + 0xc5, 0xed, 0x26, 0xb7, 0x58, 0xfe, 0x95, 0xa7, 0x69, 0xa0, 0xa7, 0x69, 0x79, 0x52, 0xd7, 0x32, + 0x92, 0xba, 0x3d, 0x68, 0x45, 0x31, 0x0d, 0x1d, 0x91, 0x62, 0xb7, 0x79, 0xf4, 0xc0, 0x40, 0xcf, + 0x10, 0x22, 0x4a, 0x26, 0xa8, 0xf3, 0x74, 0x96, 0xbc, 0xd4, 0x54, 0x4c, 0xbd, 0xa8, 0x18, 0x99, + 0x08, 0xce, 0x5d, 0x95, 0x08, 0x5a, 0x07, 0x18, 0x15, 0x4b, 0xc6, 0xc2, 0x7c, 0xde, 0x86, 0x79, + 0x54, 0x93, 0xb4, 0x9c, 0x75, 0x23, 0x8d, 0x11, 0x46, 0x61, 0x0b, 0x1c, 0xeb, 0x47, 0xf8, 0x86, + 0x88, 0x43, 0xb3, 0x88, 0x7e, 0x1d, 0x16, 0xf9, 0xae, 0x28, 0xab, 0x59, 0xc0, 0xef, 0xc7, 0x9e, + 0xf5, 0xaf, 0x35, 0x20, 0x27, 0xa3, 0xd3, 0xa1, 0x3f, 0x3b, 0xb5, 0xd9, 0x13, 0x74, 0x02, 0x0d, + 0x34, 0x13, 0x6e, 0x8e, 0xf8, 0xbb, 0x60, 0x21, 0x8d, 0xa2, 0x85, 0xe4, 0xdb, 0x79, 0xad, 0x3a, + 0x47, 0x9f, 0xd7, 0x37, 0x9f, 0xb9, 0xf8, 0xc0, 0xa7, 0x61, 0xe6, 0x88, 0x62, 0x0b, 0x73, 0xf1, + 0x08, 0x78, 0xec, 0x59, 0x27, 0xb0, 0x66, 0xac, 0x4c, 0x68, 0xfa, 0x16, 0xb4, 0xb9, 0x00, 0x71, + 0xe0, 0xf6, 0x55, 0x35, 0xbc, 0x85, 0xb0, 0x63, 0x04, 0x4d, 0xd3, 0xd7, 0x9f, 0xd5, 0x60, 0xfd, + 0xc4, 0x1f, 0x8e, 0x02, 0x37, 0xa3, 0xbf, 0x06, 0x8d, 0xe5, 0xcb, 0x9f, 0x33, 0x96, 0x2f, 0x35, + 0xd9, 0xc8, 0x35, 0x69, 0xfd, 0x77, 0x0d, 0x36, 0x0a, 0xa2, 0xa8, 0x98, 0xd0, 0x34, 0xa6, 0x09, + 0xc5, 0x01, 0x81, 0xa4, 0x31, 0xad, 0x1b, 0x4c, 0xdf, 0x80, 0xa5, 0xa1, 0x1f, 0xfa, 0xc3, 0xd1, + 0xd0, 0xe1, 0xba, 0xe7, 0x32, 0xb5, 0x05, 0xf0, 0x18, 0xb7, 0x80, 0x21, 0xb9, 0x2f, 0x35, 0xa4, + 0x86, 0x40, 0xe2, 0x40, 0x8e, 0xf4, 0x2e, 0xac, 0xe7, 0x71, 0xbb, 0x33, 0x70, 0xfd, 0xd0, 0x09, + 0xa2, 0x34, 0x15, 0x7b, 0x4c, 0xf2, 0xb1, 0x23, 0xd7, 0x0f, 0x3f, 0x8b, 0xd2, 0x54, 0x73, 0x02, + 0xf3, 0xba, 0x13, 0x60, 0x01, 0x4c, 0xe7, 0xf9, 0xb9, 0x1b, 0xd0, 0x8f, 0xa2, 0xe1, 0xe9, 0xeb, + 0xd5, 0xfd, 0x2d, 0x68, 0xf3, 0xba, 0x5b, 0xe6, 0x26, 0x03, 0x2a, 0x77, 0xa0, 0x85, 0xb0, 0xa7, + 0x08, 0xaa, 0xdc, 0x86, 0xff, 0xac, 0x01, 0x39, 0x64, 0xa1, 0x4c, 0x30, 0xb3, 0x3d, 0x30, 0x57, + 0xc2, 0xf3, 0xe6, 0xdc, 0xc2, 0x9a, 0x02, 0xf2, 0xd8, 0x34, 0xbf, 0x39, 0xc3, 0xfc, 0xd4, 0x6a, + 0x1a, 0xaf, 0x58, 0x1c, 0x2b, 0xf9, 0xf1, 0xdb, 0xb0, 0x7c, 0xe9, 0x06, 0x01, 0xcd, 0xd4, 0x13, + 0x9b, 0xa8, 0xc4, 0x73, 0xa8, 0xcc, 0xc1, 0xe5, 0x82, 0x17, 0xb4, 0x05, 0x6f, 0xc0, 0x9a, 0xb1, + 0x5e, 0x11, 0x0d, 0x3d, 0x84, 0x4d, 0x0e, 0x3e, 0x08, 0x82, 0x99, 0xbd, 0xaa, 0xf5, 0x57, 0x75, + 0xd8, 0x2a, 0x4d, 0x53, 0x61, 0x83, 0x69, 0xc6, 0x77, 0xd4, 0x72, 0xab, 0x27, 0xec, 0x8b, 0x4f, + 0x31, 0xab, 0xf7, 0x8f, 0x35, 0x98, 0xe7, 0xa0, 0xa9, 0xbb, 0xf1, 0x85, 0x74, 0x08, 0xc2, 0xe0, + 0x78, 0x46, 0xf4, 0xbd, 0xd9, 0x98, 0xf1, 0xff, 0xf4, 0x67, 0x55, 0xee, 0x49, 0xc4, 0x8b, 0xea, + 0x0f, 0xa1, 0x53, 0x44, 0x78, 0xa5, 0x27, 0x27, 0x5e, 0x55, 0x79, 0x74, 0x41, 0xb5, 0x67, 0xd4, + 0x5f, 0xd4, 0x60, 0xe5, 0x30, 0x0a, 0x3d, 0x9f, 0xdd, 0x98, 0xc7, 0x6e, 0xe2, 0x0e, 0x53, 0xf1, + 0x92, 0xcf, 0x41, 0xb2, 0xec, 0xae, 0x00, 0x13, 0x0a, 0x9c, 0xbb, 0x00, 0xfd, 0x73, 0xda, 0x7f, + 0xe1, 0x88, 0x8a, 0x23, 0x7f, 0xfe, 0x67, 0x90, 0x8f, 0x7c, 0x2f, 0x25, 0xef, 0xc0, 0x5a, 0x3e, + 0xec, 0xb8, 0xa1, 0xe7, 0x88, 0x72, 0x23, 0xbe, 0x6e, 0x28, 0xbc, 0x83, 0xd0, 0x3b, 0x48, 0x5f, + 0xa4, 0x2c, 0x56, 0x54, 0x55, 0x36, 0xc7, 0x70, 0xe1, 0x2b, 0x0a, 0x7e, 0x80, 0x60, 0xeb, 0x7f, + 0x6a, 0x78, 0x03, 0xca, 0x55, 0x89, 0xdd, 0xce, 0x0b, 0x6b, 0x58, 0x6f, 0x35, 0xb6, 0xac, 0x5e, + 0xd8, 0x32, 0x02, 0x0d, 0x3f, 0xa3, 0x43, 0x79, 0xb1, 0xb0, 0xdf, 0xe4, 0x23, 0xe8, 0xa8, 0x15, + 0x3b, 0x31, 0xaa, 0x45, 0x1c, 0x93, 0xad, 0x3c, 0x71, 0x34, 0xb4, 0x66, 0xaf, 0xf4, 0x0b, 0x6a, + 0x94, 0xc7, 0xeb, 0xda, 0x4c, 0x8e, 0xba, 0x8f, 0xda, 0x16, 0xfe, 0x89, 0x7f, 0x71, 0xa9, 0x69, + 0x7f, 0x94, 0x51, 0x4f, 0x84, 0xca, 0xea, 0xdb, 0xfa, 0xf7, 0x1a, 0xac, 0x1c, 0x78, 0x1e, 0xae, + 0x7b, 0x16, 0x37, 0x21, 0x57, 0x59, 0xbf, 0x62, 0x95, 0x73, 0xbf, 0xe4, 0x2a, 0x7f, 0x65, 0x27, + 0x32, 0x41, 0x09, 0x96, 0x05, 0x9d, 0x7c, 0x9d, 0xd5, 0xdb, 0x6b, 0x7d, 0x07, 0x08, 0x4f, 0xaf, + 0x0c, 0x75, 0x14, 0xb1, 0x36, 0x60, 0xcd, 0xc0, 0x12, 0xbe, 0xe6, 0x13, 0xb8, 0x7b, 0x44, 0xb3, + 0xc3, 0x64, 0x1c, 0x67, 0x91, 0x0c, 0x67, 0x3f, 0xa6, 0x71, 0x94, 0xfa, 0xd2, 0x73, 0xd1, 0x99, + 0xbc, 0xcf, 0x3f, 0xd5, 0xe0, 0xde, 0x0c, 0x84, 0xc4, 0x12, 0xbe, 0x2c, 0xd7, 0x97, 0x7e, 0x5b, + 0x6f, 0x6f, 0x99, 0x89, 0xca, 0xbe, 0x82, 0x88, 0x2e, 0x03, 0x45, 0xb2, 0xf7, 0x03, 0x58, 0x36, + 0x07, 0x5f, 0xc9, 0x55, 0x04, 0x70, 0xe7, 0x0a, 0x21, 0x66, 0xb1, 0xb9, 0x3b, 0xb0, 0xdc, 0x37, + 0x48, 0x08, 0x46, 0x05, 0xa8, 0x75, 0x08, 0x6f, 0x5e, 0xc9, 0x4d, 0xa8, 0x6d, 0x62, 0x86, 0x6e, + 0xfd, 0x6d, 0x03, 0xb6, 0x9e, 0xfb, 0xd9, 0xb9, 0x97, 0xb8, 0x97, 0xd2, 0xfa, 0x66, 0x11, 0xb2, + 0x90, 0xbc, 0xd7, 0xcb, 0xf5, 0x86, 0xb7, 0x60, 0x35, 0x0a, 0x29, 0xe6, 0x18, 0x4e, 0xec, 0xa6, + 0xe9, 0x65, 0x94, 0xc8, 0xbb, 0x74, 0x25, 0x0a, 0x29, 0xcb, 0x33, 0x8e, 0x05, 0xb8, 0x70, 0x1b, + 0x37, 0x8a, 0xb7, 0x71, 0x07, 0xe6, 0x62, 0x3f, 0x14, 0x6f, 0x26, 0xec, 0x27, 0xbb, 0x3b, 0xb3, + 0xc4, 0xf5, 0x34, 0xca, 0xe2, 0xee, 0x44, 0xa8, 0xa2, 0xab, 0x57, 0xf1, 0x17, 0x0a, 0x55, 0x7c, + 0x4d, 0x27, 0x8b, 0x66, 0xd5, 0x62, 0x0f, 0x5a, 0xe2, 0xa7, 0x93, 0xb9, 0x03, 0x91, 0x02, 0x81, + 0x00, 0x3d, 0x75, 0x07, 0x5a, 0xb4, 0x06, 0x46, 0xb4, 0xb6, 0x0b, 0x70, 0x46, 0xa9, 0x63, 0x24, + 0x43, 0xcd, 0x33, 0x4a, 0xb9, 0xd3, 0x65, 0xa1, 0xf2, 0xa9, 0x1b, 0xbe, 0x70, 0xb0, 0x06, 0xd1, + 0xe6, 0xe2, 0x30, 0xc0, 0xe7, 0xee, 0x10, 0x63, 0x62, 0x1c, 0x94, 0x32, 0x2d, 0x71, 0x8d, 0x32, + 0xd8, 0x41, 0x5e, 0x4d, 0x41, 0x94, 0xbe, 0x9f, 0x8d, 0xbb, 0xcb, 0xf9, 0xfc, 0x43, 0x3f, 0x1b, + 0xab, 0xf9, 0xa8, 0xb3, 0x64, 0xdc, 0x5d, 0xc9, 0xe7, 0x1f, 0x72, 0x10, 0x13, 0x2f, 0xbd, 0xf4, + 0xcf, 0x28, 0x6f, 0x0c, 0xe9, 0x88, 0x56, 0x29, 0x06, 0x39, 0x8c, 0x3c, 0x0c, 0x23, 0x2f, 0xfd, + 0x44, 0x4b, 0x4e, 0x57, 0x79, 0x0a, 0xcb, 0x80, 0xd2, 0x34, 0xac, 0xb7, 0xa0, 0x23, 0xcd, 0x45, + 0xef, 0x9d, 0x4c, 0x68, 0x3a, 0x0a, 0x32, 0xd9, 0x3b, 0xc9, 0xbf, 0xac, 0xf7, 0xb0, 0x2b, 0xe2, + 0xb3, 0x68, 0x30, 0xc8, 0xd3, 0x27, 0x61, 0x5a, 0x9b, 0x30, 0x1f, 0x20, 0x5c, 0x4e, 0xe1, 0x5f, + 0x56, 0x88, 0xf5, 0x9c, 0xc2, 0x94, 0xfc, 0xd5, 0xc2, 0x0f, 0xcf, 0x22, 0x91, 0x2d, 0xe0, 0x6f, + 0x76, 0x16, 0x3d, 0x7a, 0x3a, 0x1a, 0xc8, 0x1e, 0x28, 0xfc, 0x60, 0x98, 0x97, 0x6e, 0x12, 0x8a, + 0x0b, 0x15, 0x7f, 0x33, 0x4c, 0x9a, 0x24, 0x51, 0x22, 0x6e, 0x4f, 0xfe, 0x61, 0x1d, 0xc1, 0xd6, + 0xc9, 0xab, 0x89, 0xc8, 0x08, 0xf1, 0x6a, 0x8d, 0x38, 0xfe, 0xf8, 0x61, 0x7d, 0x6a, 0x74, 0x80, + 0x60, 0x97, 0xc0, 0x2c, 0xc7, 0x68, 0x1d, 0xae, 0xa1, 0x2f, 0x97, 0xc4, 0xf0, 0x83, 0x65, 0x84, + 0xdd, 0x32, 0x35, 0xd5, 0x83, 0x56, 0xee, 0xa8, 0xe0, 0x9e, 0xf0, 0xff, 0x55, 0x74, 0x54, 0x18, + 0x73, 0x67, 0x6b, 0xa9, 0xf8, 0xb5, 0x76, 0x49, 0x7c, 0x0d, 0x6b, 0xba, 0x68, 0xdf, 0x66, 0xd6, + 0xff, 0xe0, 0xe7, 0x77, 0x60, 0xf9, 0x28, 0xe2, 0x0e, 0xf3, 0x29, 0xf3, 0x13, 0x09, 0x79, 0x02, + 0x0b, 0xa2, 0x41, 0x95, 0x6c, 0x96, 0x3a, 0x56, 0x51, 0xb4, 0xde, 0xd6, 0x84, 0x4e, 0x56, 0x6b, + 0xed, 0x9b, 0x7f, 0xf9, 0xb7, 0x9f, 0xd5, 0x97, 0x48, 0xeb, 0xfe, 0xc5, 0x7b, 0xf7, 0x07, 0x34, + 0x43, 0x83, 0x3c, 0x87, 0x25, 0xa3, 0xa7, 0x90, 0xec, 0x18, 0x7d, 0x81, 0x85, 0x56, 0xc3, 0xde, + 0xee, 0xd4, 0xae, 0x41, 0xab, 0x87, 0x2c, 0xd6, 0x09, 0x11, 0x2c, 0x52, 0x44, 0xe1, 0x84, 0xbf, + 0x82, 0x95, 0x47, 0x58, 0xa9, 0x54, 0x54, 0xc9, 0x5e, 0x4e, 0xad, 0xb2, 0x57, 0xb2, 0x77, 0x73, + 0x32, 0x82, 0xe0, 0xb8, 0x8d, 0x1c, 0x37, 0xc8, 0x1a, 0xe3, 0xc8, 0x2b, 0xa1, 0xaa, 0x47, 0x91, + 0xa4, 0xd0, 0x11, 0xdd, 0x57, 0xaf, 0x95, 0xe7, 0x0e, 0xf2, 0xdc, 0x24, 0xeb, 0x8c, 0xa7, 0xc7, + 0x19, 0xe4, 0x4c, 0x23, 0x2c, 0xb4, 0xe8, 0xdd, 0x82, 0xe4, 0xc6, 0xc4, 0x36, 0x42, 0xce, 0x72, + 0xef, 0x8a, 0x36, 0x43, 0x73, 0x95, 0x03, 0xca, 0x70, 0x55, 0xa7, 0x21, 0xf9, 0x19, 0x3f, 0x7d, + 0x95, 0x7d, 0xad, 0xe4, 0xcd, 0xab, 0x9b, 0x69, 0xb9, 0x0c, 0x77, 0x67, 0xed, 0xba, 0xb5, 0xbe, + 0x83, 0xc2, 0xdc, 0x20, 0x3b, 0x42, 0x18, 0xa3, 0xd3, 0x56, 0xf6, 0xf2, 0x92, 0x3e, 0xb4, 0xf5, + 0x16, 0x41, 0xb2, 0x5d, 0x71, 0xd8, 0x15, 0xf3, 0x9d, 0xea, 0x41, 0xc1, 0xb0, 0x8b, 0x0c, 0x09, + 0xe9, 0x08, 0x86, 0xaa, 0xa3, 0x90, 0x7c, 0x0d, 0x2b, 0x85, 0xf6, 0x3a, 0x62, 0x15, 0xb6, 0xaf, + 0xa2, 0x55, 0xb2, 0xf7, 0xc6, 0x54, 0x1c, 0xc1, 0xf5, 0x06, 0x72, 0xed, 0x5a, 0x6b, 0xda, 0x2e, + 0x4b, 0xce, 0xdf, 0xaf, 0xbd, 0x45, 0x52, 0xdc, 0x67, 0xbd, 0x13, 0x6c, 0x26, 0xde, 0x7b, 0x57, + 0xb4, 0x91, 0x95, 0xf6, 0x5a, 0xf2, 0xc4, 0xe3, 0x9a, 0x62, 0x77, 0x8d, 0xd6, 0xbf, 0x88, 0x37, + 0xe1, 0x2c, 0x7c, 0x77, 0xab, 0xfb, 0x1f, 0x45, 0x0b, 0x66, 0xe9, 0xe4, 0x4a, 0xae, 0x51, 0x16, + 0x93, 0xd4, 0x68, 0x0f, 0x15, 0x4c, 0x4d, 0xab, 0xae, 0x68, 0xd0, 0xac, 0x5c, 0xa9, 0xde, 0x71, + 0x39, 0x71, 0xa5, 0x51, 0x16, 0xa7, 0xe4, 0x25, 0x2c, 0x73, 0x77, 0xf1, 0xfa, 0x77, 0x76, 0x17, + 0xf9, 0x6e, 0x59, 0x24, 0xf7, 0x19, 0xfa, 0xc6, 0x3e, 0x87, 0xa6, 0xea, 0x60, 0x22, 0x5d, 0x6d, + 0x11, 0x46, 0xaf, 0x5c, 0x6f, 0x42, 0x27, 0x94, 0xb4, 0x56, 0x6b, 0x49, 0xac, 0x8a, 0xf7, 0x35, + 0x31, 0xc2, 0x3f, 0x05, 0xc8, 0x5b, 0xa3, 0xc8, 0xf5, 0x12, 0x65, 0xa5, 0xb9, 0x5e, 0xd5, 0x90, + 0x6c, 0x02, 0x47, 0xf2, 0x1d, 0xb2, 0x6c, 0x90, 0x97, 0xe7, 0x4d, 0x55, 0xeb, 0x8c, 0xf3, 0x56, + 0x6c, 0xa6, 0xea, 0x4d, 0xee, 0xa2, 0x91, 0x9b, 0x62, 0xc9, 0xc3, 0xa6, 0x32, 0x71, 0xb6, 0x82, + 0x01, 0xde, 0x16, 0x5a, 0xfb, 0xce, 0x4e, 0x15, 0x97, 0xca, 0xdb, 0xa2, 0xdc, 0x8b, 0x63, 0x5d, + 0x47, 0x56, 0x6b, 0x64, 0xb5, 0xc8, 0x2a, 0x25, 0x2f, 0xf0, 0x8f, 0x60, 0xb4, 0xee, 0x13, 0xa2, + 0xd3, 0x2a, 0xb7, 0xe2, 0xf4, 0x6e, 0x4c, 0x1a, 0x9e, 0x70, 0x33, 0x89, 0x60, 0x1d, 0x0f, 0x15, + 0xdf, 0x70, 0xde, 0x73, 0x62, 0x6c, 0xb8, 0xd1, 0x9a, 0xd2, 0xbb, 0x5e, 0x31, 0x22, 0xa8, 0x6f, + 0x20, 0xf5, 0x15, 0xb2, 0xa4, 0x5c, 0x22, 0xd2, 0xe2, 0x7b, 0xa2, 0x1e, 0x03, 0x8d, 0x3d, 0x29, + 0x76, 0x8c, 0x18, 0x3e, 0xb0, 0xd4, 0x37, 0x52, 0xf2, 0x81, 0xaa, 0x33, 0x84, 0xfc, 0xb1, 0xd9, + 0x80, 0x22, 0x1f, 0xc4, 0xad, 0xa9, 0x2f, 0xd8, 0xa5, 0xd3, 0x32, 0xf1, 0x95, 0xdb, 0xda, 0x43, + 0xce, 0xd7, 0xc9, 0x56, 0x91, 0xb3, 0x78, 0x31, 0x27, 0xdf, 0xd4, 0x60, 0xad, 0xe2, 0x3d, 0x36, + 0x97, 0x60, 0xf2, 0xeb, 0x71, 0x2e, 0xc1, 0xb4, 0x07, 0x5d, 0x0b, 0x25, 0xd8, 0xb1, 0x50, 0x02, + 0xd7, 0xf3, 0x94, 0x04, 0x22, 0xf7, 0x60, 0x96, 0xf9, 0xe7, 0x35, 0xd8, 0xac, 0x7e, 0x7b, 0x25, + 0xb7, 0x55, 0x5b, 0xfd, 0xb4, 0x57, 0xe1, 0xde, 0x9d, 0xab, 0xd0, 0x84, 0x34, 0xb7, 0x51, 0x9a, + 0x3d, 0xab, 0xc7, 0xa4, 0x49, 0x10, 0xb7, 0x4a, 0xa0, 0x4b, 0x2c, 0x58, 0x99, 0xaf, 0x9b, 0x44, + 0x8b, 0x2d, 0xaa, 0x1f, 0x81, 0x7b, 0xb7, 0xa6, 0x60, 0x98, 0xee, 0x8b, 0x6c, 0x88, 0x0d, 0xc1, + 0x27, 0x41, 0xf5, 0x4c, 0x2a, 0xce, 0x68, 0xfe, 0x7a, 0x68, 0x9c, 0xd1, 0xd2, 0x83, 0xa8, 0x71, + 0x46, 0xcb, 0x6f, 0x94, 0xa5, 0x33, 0x8a, 0xcc, 0xf0, 0xbd, 0x92, 0x7c, 0x81, 0xc7, 0x46, 0x54, + 0x4b, 0xbb, 0xc5, 0xa3, 0x9e, 0x56, 0x1d, 0x1b, 0xb3, 0x1e, 0x5a, 0x72, 0x95, 0xbc, 0x08, 0xcb, + 0xb4, 0x67, 0xc3, 0xa2, 0x44, 0x27, 0x5b, 0x45, 0x02, 0x92, 0x72, 0xe5, 0x83, 0x97, 0xb5, 0x85, + 0x44, 0x57, 0xad, 0xb6, 0x4e, 0x94, 0xd1, 0x3c, 0x85, 0x96, 0xf6, 0xb8, 0x43, 0x94, 0x93, 0x2d, + 0xbf, 0x65, 0xf5, 0xb6, 0x2b, 0xc7, 0x4c, 0x57, 0x62, 0xad, 0x30, 0x06, 0x29, 0x22, 0x28, 0x1e, + 0x7f, 0x00, 0x4b, 0xc6, 0xfb, 0x4a, 0xae, 0xfc, 0xaa, 0x17, 0xa0, 0x5c, 0xf9, 0x95, 0x8f, 0x32, + 0x32, 0xd0, 0xb4, 0x50, 0xf9, 0xa9, 0x40, 0x51, 0xbc, 0xbe, 0x84, 0xa6, 0x7a, 0xd6, 0xc8, 0xf5, + 0x5f, 0x7c, 0xe9, 0xb8, 0x8a, 0x87, 0xb1, 0x07, 0x97, 0x6c, 0xf2, 0x69, 0x34, 0x3c, 0x15, 0xfa, + 0xd2, 0x8a, 0xf6, 0xb9, 0xbe, 0xca, 0x2f, 0x17, 0xb9, 0xbe, 0xaa, 0xaa, 0xfc, 0x86, 0xbe, 0xfa, + 0x88, 0xa0, 0xd6, 0x90, 0xc0, 0x4a, 0xa1, 0x58, 0x9e, 0x87, 0x15, 0xd5, 0x4f, 0x03, 0x79, 0x58, + 0x31, 0xa1, 0xca, 0x6e, 0x06, 0x6e, 0x9c, 0x9f, 0x1b, 0x04, 0xb9, 0x6d, 0x71, 0x77, 0xcf, 0x4b, + 0xc9, 0x86, 0xdd, 0x1a, 0x35, 0x73, 0xc3, 0x6e, 0xcd, 0xba, 0x73, 0xc9, 0xdd, 0x53, 0x4e, 0xeb, + 0x19, 0x2c, 0xca, 0x1a, 0x66, 0x6e, 0xb4, 0x85, 0xea, 0x6d, 0xaf, 0x5b, 0x1e, 0x10, 0x54, 0x0d, + 0xc3, 0x75, 0x3d, 0x0f, 0xa9, 0x8a, 0x8d, 0xd0, 0x2a, 0x9a, 0xf9, 0x46, 0x94, 0x8b, 0xa1, 0xf9, + 0x46, 0x54, 0x95, 0x40, 0x8d, 0x8d, 0xe0, 0x9e, 0x4b, 0xf1, 0xf8, 0xbb, 0x1a, 0xdc, 0xba, 0xb2, + 0x20, 0x49, 0xde, 0x7d, 0x85, 0xda, 0x25, 0x17, 0xe8, 0xbd, 0x57, 0xae, 0x76, 0x5a, 0x77, 0x51, + 0x4c, 0xcb, 0xda, 0x95, 0x97, 0x29, 0x4e, 0xf3, 0x38, 0xba, 0x2a, 0x7d, 0x32, 0xa1, 0xff, 0xa6, + 0xc6, 0xff, 0xc4, 0x71, 0x0a, 0x5d, 0xb2, 0x3f, 0xa3, 0x00, 0x52, 0xe0, 0xfb, 0x33, 0xe3, 0x0b, + 0x71, 0xef, 0xa0, 0xb8, 0x37, 0xad, 0xed, 0x29, 0xe2, 0x32, 0x61, 0xff, 0x08, 0xb6, 0x55, 0xe1, + 0xd2, 0xa0, 0xfb, 0xc9, 0x28, 0xf4, 0xd2, 0x3c, 0x2f, 0x9d, 0x50, 0xdd, 0xcc, 0x0d, 0xa7, 0x58, + 0xcf, 0x32, 0xef, 0xc7, 0x4b, 0x31, 0xca, 0xc5, 0x38, 0x63, 0xb4, 0x19, 0xf7, 0x18, 0x56, 0xe5, + 0xbc, 0x4f, 0x7c, 0x37, 0xfb, 0x95, 0x79, 0xde, 0x44, 0x9e, 0x3d, 0x6b, 0x43, 0xe7, 0x79, 0xe6, + 0xbb, 0x99, 0xe2, 0x98, 0xe2, 0x3b, 0x94, 0x51, 0xaa, 0xd2, 0x93, 0xef, 0xca, 0x22, 0x96, 0x9e, + 0x7c, 0x57, 0x57, 0xd5, 0xcc, 0xe4, 0x7b, 0x40, 0x33, 0x5e, 0xe5, 0xf2, 0x04, 0x83, 0x0b, 0xe8, + 0x9c, 0x4c, 0x64, 0x7a, 0xf2, 0x4b, 0x33, 0x15, 0x31, 0x90, 0x85, 0x4c, 0xd3, 0x02, 0x53, 0xb6, + 0xd8, 0x0b, 0xfe, 0xe8, 0xa6, 0x17, 0xb1, 0xc8, 0xde, 0xe4, 0xf2, 0x56, 0x99, 0x6f, 0x65, 0xfd, + 0xcb, 0xe4, 0xab, 0x65, 0x48, 0xf8, 0xa7, 0x5d, 0x8c, 0xef, 0x18, 0x88, 0x99, 0x25, 0xe1, 0x9f, + 0x04, 0x28, 0x2f, 0x50, 0x51, 0xba, 0x9a, 0x2d, 0x45, 0xba, 0x85, 0x8c, 0xb7, 0xad, 0xcd, 0x72, + 0x8a, 0xc4, 0x78, 0x33, 0xd6, 0x7f, 0x08, 0x6b, 0x85, 0xdc, 0xfb, 0x35, 0xf1, 0x36, 0xcc, 0xb9, + 0x90, 0x78, 0x0b, 0xe6, 0xa7, 0xf3, 0xf8, 0xb7, 0xf2, 0xef, 0xff, 0x5f, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xcb, 0x7f, 0xc6, 0x31, 0x5e, 0x3f, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -5012,6 +5165,9 @@ type GoCryptoTraderClient interface { WithdrawFiatFunds(ctx context.Context, in *WithdrawCurrencyRequest, opts ...grpc.CallOption) (*WithdrawResponse, error) GetLoggerDetails(ctx context.Context, in *GetLoggerDetailsRequest, opts ...grpc.CallOption) (*GetLoggerDetailsResponse, error) SetLoggerDetails(ctx context.Context, in *SetLoggerDetailsRequest, opts ...grpc.CallOption) (*GetLoggerDetailsResponse, error) + GetExchangePairs(ctx context.Context, in *GetExchangePairsRequest, opts ...grpc.CallOption) (*GetExchangePairsResponse, error) + EnableExchangePair(ctx context.Context, in *ExchangePairRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) + DisableExchangePair(ctx context.Context, in *ExchangePairRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) } type goCryptoTraderClient struct { @@ -5382,6 +5538,33 @@ func (c *goCryptoTraderClient) SetLoggerDetails(ctx context.Context, in *SetLogg return out, nil } +func (c *goCryptoTraderClient) GetExchangePairs(ctx context.Context, in *GetExchangePairsRequest, opts ...grpc.CallOption) (*GetExchangePairsResponse, error) { + out := new(GetExchangePairsResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetExchangePairs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) EnableExchangePair(ctx context.Context, in *ExchangePairRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) { + out := new(GenericExchangeNameResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/EnableExchangePair", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) DisableExchangePair(ctx context.Context, in *ExchangePairRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) { + out := new(GenericExchangeNameResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/DisableExchangePair", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // GoCryptoTraderServer is the server API for GoCryptoTrader service. type GoCryptoTraderServer interface { GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) @@ -5424,6 +5607,9 @@ type GoCryptoTraderServer interface { WithdrawFiatFunds(context.Context, *WithdrawCurrencyRequest) (*WithdrawResponse, error) GetLoggerDetails(context.Context, *GetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) SetLoggerDetails(context.Context, *SetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) + GetExchangePairs(context.Context, *GetExchangePairsRequest) (*GetExchangePairsResponse, error) + EnableExchangePair(context.Context, *ExchangePairRequest) (*GenericExchangeNameResponse, error) + DisableExchangePair(context.Context, *ExchangePairRequest) (*GenericExchangeNameResponse, error) } func RegisterGoCryptoTraderServer(s *grpc.Server, srv GoCryptoTraderServer) { @@ -6150,6 +6336,60 @@ func _GoCryptoTrader_SetLoggerDetails_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } +func _GoCryptoTrader_GetExchangePairs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetExchangePairsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetExchangePairs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetExchangePairs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetExchangePairs(ctx, req.(*GetExchangePairsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_EnableExchangePair_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExchangePairRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).EnableExchangePair(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/EnableExchangePair", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).EnableExchangePair(ctx, req.(*ExchangePairRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_DisableExchangePair_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExchangePairRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).DisableExchangePair(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/DisableExchangePair", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).DisableExchangePair(ctx, req.(*ExchangePairRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ ServiceName: "gctrpc.GoCryptoTrader", HandlerType: (*GoCryptoTraderServer)(nil), @@ -6314,6 +6554,18 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ MethodName: "SetLoggerDetails", Handler: _GoCryptoTrader_SetLoggerDetails_Handler, }, + { + MethodName: "GetExchangePairs", + Handler: _GoCryptoTrader_GetExchangePairs_Handler, + }, + { + MethodName: "EnableExchangePair", + Handler: _GoCryptoTrader_EnableExchangePair_Handler, + }, + { + MethodName: "DisableExchangePair", + Handler: _GoCryptoTrader_DisableExchangePair_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "rpc.proto", diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 3423d9be..37957646 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -604,6 +604,57 @@ func request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler ru } +func request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangePairsRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetExchangePairs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExchangePairRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.EnableExchangePair(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExchangePairRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DisableExchangePair(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + // RegisterGoCryptoTraderHandlerFromEndpoint is same as RegisterGoCryptoTraderHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterGoCryptoTraderHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { @@ -1442,6 +1493,66 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("POST", pattern_GoCryptoTrader_GetExchangePairs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetExchangePairs_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangePairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_EnableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_EnableExchangePair_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_EnableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_DisableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_DisableExchangePair_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_DisableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1525,6 +1636,12 @@ var ( pattern_GoCryptoTrader_GetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getloggerdetails"}, "")) pattern_GoCryptoTrader_SetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "setloggerdetails"}, "")) + + pattern_GoCryptoTrader_GetExchangePairs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangepairs"}, "")) + + pattern_GoCryptoTrader_EnableExchangePair_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchangepair"}, "")) + + pattern_GoCryptoTrader_DisableExchangePair_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchangepair"}, "")) ) var ( @@ -1607,4 +1724,10 @@ var ( forward_GoCryptoTrader_GetLoggerDetails_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_SetLoggerDetails_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetExchangePairs_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_EnableExchangePair_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_DisableExchangePair_0 = runtime.ForwardResponseMessage ) diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 76efbe51..33c4cbdb 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -474,6 +474,21 @@ message SetLoggerDetailsRequest { string level = 2; } +message GetExchangePairsRequest { + string exchange = 1; + string asset = 2; +} + +message GetExchangePairsResponse { + map supported_assets = 1; +} + +message ExchangePairRequest { + string exchange = 1; + string asset_type = 2; + CurrencyPair pair = 3; +} + service GoCryptoTrader { rpc GetInfo (GetInfoRequest) returns (GetInfoResponse) { option (google.api.http) = { @@ -735,4 +750,25 @@ service GoCryptoTrader { body: "*" }; } + + rpc GetExchangePairs(GetExchangePairsRequest) returns (GetExchangePairsResponse) { + option (google.api.http) = { + post: "/v1/getexchangepairs", + body: "*" + }; + } + + rpc EnableExchangePair(ExchangePairRequest) returns (GenericExchangeNameResponse) { + option (google.api.http) = { + post: "/v1/enableexchangepair", + body: "*" + }; + } + + rpc DisableExchangePair(ExchangePairRequest) returns (GenericExchangeNameResponse) { + option (google.api.http) = { + post: "/v1/disableexchangepair", + body: "*" + }; + } } diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index e478f30d..f1759936 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -145,6 +145,32 @@ ] } }, + "/v1/disableexchangepair": { + "post": { + "operationId": "DisableExchangePair", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericExchangeNameResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcExchangePairRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/disablesubsystem": { "get": { "operationId": "DisableSubsystem", @@ -195,6 +221,32 @@ ] } }, + "/v1/enableexchangepair": { + "post": { + "operationId": "EnableExchangePair", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericExchangeNameResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcExchangePairRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/enablesubsystem": { "get": { "operationId": "EnableSubsystem", @@ -407,6 +459,32 @@ ] } }, + "/v1/getexchangepairs": { + "post": { + "operationId": "GetExchangePairs", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetExchangePairsResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcGetExchangePairsRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getexchanges": { "get": { "operationId": "GetExchanges", @@ -1129,6 +1207,20 @@ } } }, + "gctrpcExchangePairRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "asset_type": { + "type": "string" + }, + "pair": { + "$ref": "#/definitions/gctrpcCurrencyPair" + } + } + }, "gctrpcForexProvider": { "type": "object", "properties": { @@ -1353,6 +1445,28 @@ } } }, + "gctrpcGetExchangePairsRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "asset": { + "type": "string" + } + } + }, + "gctrpcGetExchangePairsResponse": { + "type": "object", + "properties": { + "supported_assets": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/gctrpcPairsSupported" + } + } + } + }, "gctrpcGetExchangesResponse": { "type": "object", "properties": { From 0824ee04c97611c19a68c90d69a6c6948a9bc2a6 Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 10 Sep 2019 10:53:25 +1000 Subject: [PATCH 32/71] Engine: bugfix: OKEX Future swap panic (#350) * Ensures that futures, index and swap currencies are properly set on OKEX startup with an existing engine config that hasn't been updated yet * Removes package/variable CLASH --- exchanges/okex/okex_wrapper.go | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go index f8c7ea20..9d01460a 100644 --- a/exchanges/okex/okex_wrapper.go +++ b/exchanges/okex/okex_wrapper.go @@ -140,8 +140,10 @@ func (o *OKEX) Run() { if o.Verbose { log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.API.Endpoints.WebsocketURL) } - if o.Config.CurrencyPairs.Pairs[asset.Spot].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Spot].RequestFormat == nil { - fmt := currency.PairStore{ + + if o.Config.CurrencyPairs.Pairs[asset.Spot].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Spot].RequestFormat == nil || + o.Config.CurrencyPairs.Pairs[asset.Index].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Index].RequestFormat == nil { + currFmt := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, Delimiter: "-", @@ -151,8 +153,28 @@ func (o *OKEX) Run() { Delimiter: "-", }, } - o.CurrencyPairs.Store(asset.Spot, fmt) - o.Config.CurrencyPairs.Store(asset.Spot, fmt) + o.CurrencyPairs.Store(asset.Spot, currFmt) + o.Config.CurrencyPairs.Store(asset.Spot, currFmt) + o.CurrencyPairs.Store(asset.Index, currFmt) + o.Config.CurrencyPairs.Store(asset.Index, currFmt) + } + + if o.Config.CurrencyPairs.Pairs[asset.Futures].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Futures].RequestFormat == nil || + o.Config.CurrencyPairs.Pairs[asset.PerpetualSwap].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.PerpetualSwap].RequestFormat == nil { + currFmt := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "_", + }, + } + o.CurrencyPairs.Store(asset.Futures, currFmt) + o.Config.CurrencyPairs.Store(asset.Futures, currFmt) + o.CurrencyPairs.Store(asset.PerpetualSwap, currFmt) + o.Config.CurrencyPairs.Store(asset.PerpetualSwap, currFmt) } if !common.StringDataContains(o.Config.CurrencyPairs.Pairs[asset.Spot].Enabled.Strings(), o.CurrencyPairs.Pairs[asset.Spot].RequestFormat.Delimiter) { From e8b517ef0a6f6504fd7d51cb28cac283e8b5c0d4 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 10 Sep 2019 17:07:00 +1000 Subject: [PATCH 33/71] Daily engine changes 1) Although gRPC does server side validation currently, validate basic things on gctcli before relaying the request to the gRPC server 2) Make pair format consistent for the exchange sycner 3) Fix OKEX ticker failure due to thinking futures info is authenticated 4) Start filling out config tests 5) Extend timeout for golangci config so that AppVeyor has time to complete (Travis is fine) 6) Add IsSupported exchange func for easy lookup --- .golangci.yml | 2 +- cmd/gctcli/commands.go | 381 ++++++++++++++++++++++-------------- cmd/gctcli/validation.go | 9 +- config/config.go | 8 +- config/config_test.go | 193 ++++++++++++++++++ engine/syncer.go | 36 ++-- exchanges/okex/okex.go | 2 +- exchanges/okex/okex_test.go | 4 +- exchanges/support.go | 12 ++ exchanges/support_test.go | 13 ++ main.go | 4 +- 11 files changed, 490 insertions(+), 174 deletions(-) create mode 100644 exchanges/support_test.go diff --git a/.golangci.yml b/.golangci.yml index 2fcc3b32..9357ce01 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,5 @@ run: - deadline: 40s + deadline: 1m0s issues-exit-code: 1 tests: true skip-dirs: diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index 37ee55a3..943009a0 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "fmt" "strconv" @@ -82,12 +83,6 @@ func enableSubsystem(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var subsystemName string if c.IsSet("subsystem") { subsystemName = c.String("subsystem") @@ -95,6 +90,16 @@ func enableSubsystem(c *cli.Context) error { subsystemName = c.Args().First() } + if subsystemName == "" { + return errors.New("invalid subsystem supplied") + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.EnableSubsystem(context.Background(), &gctrpc.GenericSubsystemRequest{ @@ -129,12 +134,6 @@ func disableSubsystem(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var subsystemName string if c.IsSet("subsystem") { subsystemName = c.String("subsystem") @@ -142,6 +141,16 @@ func disableSubsystem(c *cli.Context) error { subsystemName = c.Args().First() } + if subsystemName == "" { + return errors.New("invalid subsystem supplied") + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.DisableSubsystem(context.Background(), &gctrpc.GenericSubsystemRequest{ @@ -268,12 +277,6 @@ func enableExchange(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string if c.IsSet("exchange") { exchangeName = c.String("exchange") @@ -281,6 +284,16 @@ func enableExchange(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.EnableExchange(context.Background(), &gctrpc.GenericExchangeNameRequest{ @@ -315,12 +328,6 @@ func disableExchange(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string if c.IsSet("exchange") { exchangeName = c.String("exchange") @@ -328,6 +335,16 @@ func disableExchange(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.DisableExchange(context.Background(), &gctrpc.GenericExchangeNameRequest{ @@ -362,12 +379,6 @@ func getExchangeOTPCode(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string if c.IsSet("exchange") { exchangeName = c.String("exchange") @@ -375,6 +386,16 @@ func getExchangeOTPCode(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetExchangeOTPCode(context.Background(), &gctrpc.GenericExchangeNameRequest{ @@ -434,12 +455,6 @@ func getExchangeInfo(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string if c.IsSet("exchange") { exchangeName = c.String("exchange") @@ -447,6 +462,16 @@ func getExchangeInfo(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetExchangeInfo(context.Background(), &gctrpc.GenericExchangeNameRequest{ @@ -489,12 +514,6 @@ func getTicker(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string var currencyPair string var assetType string @@ -505,23 +524,33 @@ func getTicker(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + if c.IsSet("pair") { currencyPair = c.String("pair") } else { currencyPair = c.Args().Get(1) } + if !validPair(currencyPair) { + return errInvalidPair + } + if c.IsSet("asset") { assetType = c.String("asset") } else { assetType = c.Args().Get(2) } - if !validPair(currencyPair) { - return errInvalidPair + conn, err := setupClient() + if err != nil { + return err } - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + defer conn.Close() + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetTicker(context.Background(), &gctrpc.GetTickerRequest{ @@ -545,7 +574,7 @@ func getTicker(c *cli.Context) error { var getTickersCommand = cli.Command{ Name: "gettickers", - Usage: "gets all tickers for all enabled exchanes and currency pairs", + Usage: "gets all tickers for all enabled exchanges and currency pairs", Action: getTickers, } @@ -593,12 +622,6 @@ func getOrderbook(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string var currencyPair string var assetType string @@ -609,23 +632,33 @@ func getOrderbook(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + if c.IsSet("pair") { currencyPair = c.String("pair") } else { currencyPair = c.Args().Get(1) } + if !validPair(currencyPair) { + return errInvalidPair + } + if c.IsSet("asset") { assetType = c.String("asset") } else { assetType = c.Args().Get(2) } - if !validPair(currencyPair) { - return errInvalidPair + conn, err := setupClient() + if err != nil { + return err } - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + defer conn.Close() + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetOrderbook(context.Background(), &gctrpc.GetOrderbookRequest{ @@ -649,7 +682,7 @@ func getOrderbook(c *cli.Context) error { var getOrderbooksCommand = cli.Command{ Name: "getorderbooks", - Usage: "gets all orderbooks for all enabled exchanes and currency pairs", + Usage: "gets all orderbooks for all enabled exchanges and currency pairs", Action: getOrderbooks, } @@ -689,12 +722,6 @@ func getAccountInfo(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchange string if c.IsSet("exchange") { exchange = c.String("exchange") @@ -702,6 +729,16 @@ func getAccountInfo(c *cli.Context) error { exchange = c.Args().First() } + if !validExchange(exchange) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetAccountInfo(context.Background(), &gctrpc.GetAccountInfoRequest{ @@ -1009,12 +1046,6 @@ var getOrdersCommand = cli.Command{ } func getOrders(c *cli.Context) error { - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string var assetType string var currencyPair string @@ -1025,6 +1056,10 @@ func getOrders(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + if c.IsSet("asset_type") { assetType = c.String("asset_type") } else { @@ -1040,8 +1075,14 @@ func getOrders(c *cli.Context) error { if !validPair(currencyPair) { return errInvalidPair } - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetOrders(context.Background(), &gctrpc.GetOrdersRequest{ Exchange: exchangeName, @@ -1083,12 +1124,6 @@ func getOrder(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string var orderID string @@ -1098,12 +1133,22 @@ func getOrder(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + if c.IsSet("order_id") { orderID = c.String("order_id") } else { orderID = c.Args().Get(1) } + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetOrder(context.Background(), &gctrpc.GetOrderRequest{ Exchange: exchangeName, @@ -1120,7 +1165,7 @@ func getOrder(c *cli.Context) error { var submitOrderCommand = cli.Command{ Name: "submitorder", Usage: "submit order submits an exchange order", - ArgsUsage: " ", + ArgsUsage: " ", Action: submitOrder, Flags: []cli.Flag{ cli.StringFlag{ @@ -1128,7 +1173,7 @@ var submitOrderCommand = cli.Command{ Usage: "the exchange to submit the order for", }, cli.StringFlag{ - Name: "currency_pair", + Name: "pair", Usage: "the currency pair", }, cli.StringFlag{ @@ -1160,12 +1205,6 @@ func submitOrder(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string var currencyPair string var orderSide string @@ -1180,12 +1219,20 @@ func submitOrder(c *cli.Context) error { exchangeName = c.Args().First() } - if c.IsSet("currency_pair") { - currencyPair = c.String("currency_pair") + if !validExchange(exchangeName) { + return errInvalidExchange + } + + if c.IsSet("pair") { + currencyPair = c.String("pair") } else { currencyPair = c.Args().Get(1) } + if !validPair(currencyPair) { + return errInvalidPair + } + if c.IsSet("side") { orderSide = c.String("side") } else { @@ -1216,11 +1263,13 @@ func submitOrder(c *cli.Context) error { clientID = c.Args().Get(6) } - if !validPair(currencyPair) { - return errInvalidPair + conn, err := setupClient() + if err != nil { + return err } - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + defer conn.Close() + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.SubmitOrder(context.Background(), &gctrpc.SubmitOrderRequest{ Exchange: exchangeName, @@ -1246,7 +1295,7 @@ func submitOrder(c *cli.Context) error { var simulateOrderCommand = cli.Command{ Name: "simulateorder", Usage: "simulate order simulates an exchange order", - ArgsUsage: " ", + ArgsUsage: " ", Action: simulateOrder, Flags: []cli.Flag{ cli.StringFlag{ @@ -1254,7 +1303,7 @@ var simulateOrderCommand = cli.Command{ Usage: "the exchange to simulate the order for", }, cli.StringFlag{ - Name: "currency_pair", + Name: "pair", Usage: "the currency pair", }, cli.StringFlag{ @@ -1274,12 +1323,6 @@ func simulateOrder(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string var currencyPair string var orderSide string @@ -1291,12 +1334,20 @@ func simulateOrder(c *cli.Context) error { exchangeName = c.Args().First() } - if c.IsSet("currency_pair") { - currencyPair = c.String("currency_pair") + if !validExchange(exchangeName) { + return errInvalidExchange + } + + if c.IsSet("pair") { + currencyPair = c.String("pair") } else { currencyPair = c.Args().Get(1) } + if !validPair(currencyPair) { + return errInvalidPair + } + if c.IsSet("side") { orderSide = c.String("side") } else { @@ -1309,11 +1360,13 @@ func simulateOrder(c *cli.Context) error { amount, _ = strconv.ParseFloat(c.Args().Get(3), 64) } - if !validPair(currencyPair) { - return errInvalidPair + conn, err := setupClient() + if err != nil { + return err } - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + defer conn.Close() + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.SimulateOrder(context.Background(), &gctrpc.SimulateOrderRequest{ Exchange: exchangeName, @@ -1336,7 +1389,7 @@ func simulateOrder(c *cli.Context) error { var whaleBombCommand = cli.Command{ Name: "whalebomb", Usage: "whale bomb finds the amount required to reach a price target", - ArgsUsage: " ", + ArgsUsage: " ", Action: whaleBomb, Flags: []cli.Flag{ cli.StringFlag{ @@ -1344,7 +1397,7 @@ var whaleBombCommand = cli.Command{ Usage: "the exchange to whale bomb", }, cli.StringFlag{ - Name: "currency_pair", + Name: "pair", Usage: "the currency pair", }, cli.StringFlag{ @@ -1364,12 +1417,6 @@ func whaleBomb(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string var currencyPair string var orderSide string @@ -1381,12 +1428,20 @@ func whaleBomb(c *cli.Context) error { exchangeName = c.Args().First() } - if c.IsSet("currency_pair") { - currencyPair = c.String("currency_pair") + if !validExchange(exchangeName) { + return errInvalidExchange + } + + if c.IsSet("pair") { + currencyPair = c.String("pair") } else { currencyPair = c.Args().Get(1) } + if !validPair(currencyPair) { + return errInvalidPair + } + if c.IsSet("side") { orderSide = c.String("side") } else { @@ -1399,11 +1454,13 @@ func whaleBomb(c *cli.Context) error { price, _ = strconv.ParseFloat(c.Args().Get(3), 64) } - if !validPair(currencyPair) { - return errInvalidPair + conn, err := setupClient() + if err != nil { + return err } - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + defer conn.Close() + p := currency.NewPairDelimiter(currencyPair, pairDelimiter) client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.WhaleBomb(context.Background(), &gctrpc.WhaleBombRequest{ Exchange: exchangeName, @@ -1426,7 +1483,7 @@ func whaleBomb(c *cli.Context) error { var cancelOrderCommand = cli.Command{ Name: "cancelorder", Usage: "cancel order cancels an exchange order", - ArgsUsage: " ", + ArgsUsage: " ", Action: cancelOrder, Flags: []cli.Flag{ cli.StringFlag{ @@ -1442,7 +1499,7 @@ var cancelOrderCommand = cli.Command{ Usage: "the order id", }, cli.StringFlag{ - Name: "currency_pair", + Name: "pair", Usage: "the currency pair to cancel the order for", }, cli.StringFlag{ @@ -1466,12 +1523,6 @@ func cancelOrder(c *cli.Context) error { return nil } - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string var accountID string var orderID string @@ -1486,6 +1537,10 @@ func cancelOrder(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + if c.IsSet("order_id") { orderID = c.String("order_id") } else { @@ -1496,8 +1551,8 @@ func cancelOrder(c *cli.Context) error { accountID = c.String("account_id") } - if c.IsSet("currency_pair") { - currencyPair = c.String("currency_pair") + if c.IsSet("pair") { + currencyPair = c.String("pair") } if c.IsSet("asset_type") { @@ -1520,6 +1575,12 @@ func cancelOrder(c *cli.Context) error { p = currency.NewPairDelimiter(currencyPair, pairDelimiter) } + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.CancelOrder(context.Background(), &gctrpc.CancelOrderRequest{ Exchange: exchangeName, @@ -1556,12 +1617,6 @@ var cancelAllOrdersCommand = cli.Command{ } func cancelAllOrders(c *cli.Context) error { - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - var exchangeName string if c.IsSet("exchange") { exchangeName = c.String("exchange") @@ -1569,6 +1624,19 @@ func cancelAllOrders(c *cli.Context) error { exchangeName = c.Args().First() } + // exchange name is an optional param + if exchangeName != "" { + if !validExchange(exchangeName) { + return errInvalidExchange + } + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.CancelAllOrders(context.Background(), &gctrpc.CancelAllOrdersRequest{ Exchange: exchangeName, @@ -1607,7 +1675,7 @@ func getEvents(_ *cli.Context) error { var addEventCommand = cli.Command{ Name: "addevent", Usage: "adds an event", - ArgsUsage: " ", + ArgsUsage: " ", Action: addEvent, Flags: []cli.Flag{ cli.StringFlag{ @@ -1639,7 +1707,7 @@ var addEventCommand = cli.Command{ Usage: "the orderbook amount to trigger the event", }, cli.StringFlag{ - Name: "currency_pair", + Name: "pair", Usage: "the currency pair", }, cli.StringFlag{ @@ -1704,8 +1772,8 @@ func addEvent(c *cli.Context) error { orderbookAmount = c.Float64("orderbook_amount") } - if c.IsSet("currency_pair") { - currencyPair = c.String("currency_pair") + if c.IsSet("pair") { + currencyPair = c.String("pair") } else { return fmt.Errorf("currency pair is required") } @@ -1720,17 +1788,17 @@ func addEvent(c *cli.Context) error { return fmt.Errorf("action is required") } + if !validPair(currencyPair) { + return errInvalidPair + } + conn, err := setupClient() if err != nil { return err } defer conn.Close() - if !validPair(currencyPair) { - return errInvalidPair - } p := currency.NewPairDelimiter(currencyPair, pairDelimiter) - client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.AddEvent(context.Background(), &gctrpc.AddEventRequest{ Exchange: exchangeName, @@ -1831,6 +1899,10 @@ func getCryptocurrencyDepositAddresses(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + conn, err := setupClient() if err != nil { return err @@ -1880,6 +1952,10 @@ func getCryptocurrencyDepositAddress(c *cli.Context) error { exchangeName = c.Args().First() } + if !validExchange(exchangeName) { + return errInvalidExchange + } + if c.IsSet("cryptocurrency") { cryptocurrency = c.String("cryptocurrency") } else { @@ -2086,6 +2162,10 @@ func getExchangePairs(c *cli.Context) error { exchange = c.Args().First() } + if !validExchange(exchange) { + return errInvalidExchange + } + if c.IsSet("asset") { asset = c.String("asset") } else { @@ -2099,7 +2179,6 @@ func getExchangePairs(c *cli.Context) error { defer conn.Close() client := gctrpc.NewGoCryptoTraderClient(conn) - result, err := client.GetExchangePairs(context.Background(), &gctrpc.GetExchangePairsRequest{ Exchange: exchange, @@ -2150,12 +2229,20 @@ func enableExchangePair(c *cli.Context) error { exchange = c.Args().First() } + if !validExchange(exchange) { + return errInvalidExchange + } + if c.IsSet("pair") { pair = c.String("pair") } else { pair = c.Args().Get(1) } + if !validPair(pair) { + return errInvalidPair + } + if c.IsSet("asset") { asset = c.String("asset") } else { @@ -2168,10 +2255,6 @@ func enableExchangePair(c *cli.Context) error { } defer conn.Close() - if !validPair(pair) { - return errInvalidPair - } - p := currency.NewPairDelimiter(pair, pairDelimiter) client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.EnableExchangePair(context.Background(), @@ -2229,12 +2312,20 @@ func disableExchangePair(c *cli.Context) error { exchange = c.Args().First() } + if !validExchange(exchange) { + return errInvalidExchange + } + if c.IsSet("pair") { pair = c.String("pair") } else { pair = c.Args().Get(1) } + if !validPair(pair) { + return errInvalidPair + } + if c.IsSet("asset") { asset = c.String("asset") } else { @@ -2247,10 +2338,6 @@ func disableExchangePair(c *cli.Context) error { } defer conn.Close() - if !validPair(pair) { - return errInvalidPair - } - p := currency.NewPairDelimiter(pair, pairDelimiter) client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.DisableExchangePair(context.Background(), diff --git a/cmd/gctcli/validation.go b/cmd/gctcli/validation.go index b170d98b..fd3202b0 100644 --- a/cmd/gctcli/validation.go +++ b/cmd/gctcli/validation.go @@ -3,12 +3,19 @@ package main import ( "errors" "strings" + + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" ) var ( - errInvalidPair = errors.New("invalid currency pair supplied") + errInvalidPair = errors.New("invalid currency pair supplied") + errInvalidExchange = errors.New("invalid exchange supplied") ) func validPair(pair string) bool { return strings.Contains(pair, pairDelimiter) } + +func validExchange(exch string) bool { + return exchange.IsSupported(exch) +} diff --git a/config/config.go b/config/config.go index f38b9dbc..d8741fc3 100644 --- a/config/config.go +++ b/config/config.go @@ -446,10 +446,6 @@ func (c *Config) CheckExchangeAssetsConsistency(exchName string) { return } - if exchCfg.CurrencyPairs == nil { - return - } - exchangeAssetTypes, err := c.GetExchangeAssetTypes(exchName) if err != nil { return @@ -458,7 +454,9 @@ func (c *Config) CheckExchangeAssetsConsistency(exchName string) { storedAssetTypes := exchCfg.CurrencyPairs.GetAssetTypes() for x := range storedAssetTypes { if !exchangeAssetTypes.Contains(storedAssetTypes[x]) { - log.Warnf(log.ConfigMgr, "%s has non-needed stored asset type %v. Removing..\n", exchName, storedAssetTypes[x]) + log.Warnf(log.ConfigMgr, + "%s has non-needed stored asset type %v. Removing..\n", + exchName, storedAssetTypes[x]) exchCfg.CurrencyPairs.Delete(storedAssetTypes[x]) } } diff --git a/config/config_test.go b/config/config_test.go index 27f38894..70597fe4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -15,6 +15,7 @@ const ( // Default number of enabled exchanges. Modify this whenever an exchange is // added or removed defaultEnabledExchanges = 28 + testFakeExchangeName = "Stampbit" ) func TestGetCurrencyConfig(t *testing.T) { @@ -143,6 +144,70 @@ func TestCheckClientBankAccounts(t *testing.T) { // TO-DO: Complete test coverage } +func TestPurgeExchangeCredentials(t *testing.T) { + t.Parallel() + var c Config + c.Exchanges = []ExchangeConfig{ + { + Name: "test", + API: APIConfig{ + AuthenticatedSupport: true, + AuthenticatedWebsocketSupport: true, + + CredentialsValidator: &APICredentialsValidatorConfig{ + RequiresKey: true, + RequiresSecret: true, + RequiresClientID: true, + }, + + Credentials: APICredentialsConfig{ + Key: "asdf123", + Secret: "secretp4ssw0rd", + ClientID: "1337", + OTPSecret: "otp", + PEMKey: "aaa", + }, + }, + }, + { + Name: "test123", + API: APIConfig{ + CredentialsValidator: &APICredentialsValidatorConfig{ + RequiresKey: true, + }, + Credentials: APICredentialsConfig{ + Key: "asdf", + Secret: DefaultAPISecret, + }, + }, + }, + } + + c.PurgeExchangeAPICredentials() + + exchCfg, err := c.GetExchangeConfig("test") + if err != nil { + t.Error(err) + } + + if exchCfg.API.Credentials.Key != DefaultAPIKey && + exchCfg.API.Credentials.ClientID != DefaultAPIClientID && + exchCfg.API.Credentials.Secret != DefaultAPISecret && + exchCfg.API.Credentials.OTPSecret != "" && + exchCfg.API.Credentials.PEMKey != "" { + t.Error("unexpected values") + } + + exchCfg, err = c.GetExchangeConfig("test123") + if err != nil { + t.Error(err) + } + + if exchCfg.API.Credentials.Key != "asdf" { + t.Error("unexpected values") + } +} + func TestGetCommunicationsConfig(t *testing.T) { cfg := GetConfig() err := cfg.LoadConfig(ConfigTestFile) @@ -224,6 +289,18 @@ func TestCheckCommunicationsConfig(t *testing.T) { t.Error("Test failed. CheckCommunicationsConfig error:", err) } + cfg.Communications.SMSGlobalConfig.From = "" + cfg.CheckCommunicationsConfig() + if cfg.Communications.SMSGlobalConfig.From != cfg.Name { + t.Error("Test failed. CheckCommunicationsConfig From value should of been set to the config name") + } + + cfg.Communications.SMSGlobalConfig.From = "aaaaaaaaaaaaaaaaaaa" + cfg.CheckCommunicationsConfig() + if cfg.Communications.SMSGlobalConfig.From != "aaaaaaaaaaa" { + t.Error("Test failed. CheckCommunicationsConfig From value should of been trimmed to 11 characters") + } + cfg.SMS = &SMSGlobalConfig{} cfg.CheckCommunicationsConfig() if cfg.SMS != nil { @@ -266,6 +343,122 @@ func TestCheckCommunicationsConfig(t *testing.T) { } } +func TestGetExchangeAssetTypes(t *testing.T) { + t.Parallel() + var c Config + _, err := c.GetExchangeAssetTypes("void") + if err == nil { + t.Error("err should of been thrown on a non-existent exchange") + } + + c.Exchanges = append(c.Exchanges, + ExchangeConfig{ + Name: testFakeExchangeName, + CurrencyPairs: ¤cy.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + asset.Futures, + }, + }, + }, + ) + + var assets asset.Items + assets, err = c.GetExchangeAssetTypes(testFakeExchangeName) + if err != nil { + t.Error(err) + } + + if assets.JoinToString(",") != "spot,futures" { + t.Error("unexpected results") + } + + c.Exchanges[0].CurrencyPairs = nil + _, err = c.GetExchangeAssetTypes(testFakeExchangeName) + if err == nil { + t.Error("a nil pair manager should throw an error") + } +} + +func TestSupportsExchangeAssetType(t *testing.T) { + t.Parallel() + var c Config + _, err := c.SupportsExchangeAssetType("void", asset.Spot) + if err == nil { + t.Error("unexpected result for non-existent exchange") + } + + c.Exchanges = append(c.Exchanges, + ExchangeConfig{ + Name: testFakeExchangeName, + CurrencyPairs: ¤cy.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + asset.Futures, + }, + }, + }, + ) + + supports, err := c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot) + if err != nil { + t.Error(err) + } + + if !supports { + t.Error("exchange should support spot asset item") + } + + _, err = c.SupportsExchangeAssetType(testFakeExchangeName, "asdf") + if err == nil { + t.Error("invalid asset item should throw an error") + } + + c.Exchanges[0].CurrencyPairs = nil + _, err = c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot) + if err == nil { + t.Error("a nil pair manager should throw an error") + } +} + +func TestCheckExchangeAssetsConsistency(t *testing.T) { + t.Parallel() + var c Config + // Test for non-existent exchange + c.CheckExchangeAssetsConsistency("void") + + c.Exchanges = append(c.Exchanges, + ExchangeConfig{ + Name: testFakeExchangeName, + }, + ) + + // Tests for nil currency pairs store but valid exchange name + c.CheckExchangeAssetsConsistency(testFakeExchangeName) + + // Simulate testing a diff between stored asset types (config loading) + // and pair store + c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + asset.Futures, + asset.Index, + }, + } + c.Exchanges[0].CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) + c.Exchanges[0].CurrencyPairs.Pairs[asset.PerpetualContract] = ¤cy.PairStore{} + c.CheckExchangeAssetsConsistency(testFakeExchangeName) + + supports, err := c.SupportsExchangeAssetType(testFakeExchangeName, asset.PerpetualContract) + if err != nil { + t.Error(err) + } + + if supports { + t.Error("perpetual contract should of been removed from the pair manager") + } +} + func TestCheckPairConsistency(t *testing.T) { cfg := GetConfig() err := cfg.LoadConfig(ConfigTestFile) diff --git a/engine/syncer.go b/engine/syncer.go index ff84a516..d62c24a9 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -92,8 +92,8 @@ func (e *ExchangeCurrencyPairSyncer) add(c *CurrencyPairSyncAgent) { defer e.mux.Unlock() if e.Cfg.SyncTicker { - log.Debugf(log.SyncMgr, "%s: Added ticker sync item %v: using websocket: %v using REST: %v\n", c.Exchange, c.Pair.String(), - c.Ticker.IsUsingWebsocket, c.Ticker.IsUsingREST) + log.Debugf(log.SyncMgr, "%s: Added ticker sync item %v: using websocket: %v using REST: %v\n", + c.Exchange, FormatCurrency(c.Pair).String(), c.Ticker.IsUsingWebsocket, c.Ticker.IsUsingREST) if atomic.LoadInt32(&e.initSyncCompleted) != 1 { e.initSyncWG.Add(1) createdCounter++ @@ -101,8 +101,8 @@ func (e *ExchangeCurrencyPairSyncer) add(c *CurrencyPairSyncAgent) { } if e.Cfg.SyncOrderbook { - log.Debugf(log.SyncMgr, "%s: Added orderbook sync item %v: using websocket: %v using REST: %v\n", c.Exchange, c.Pair.String(), - c.Orderbook.IsUsingWebsocket, c.Orderbook.IsUsingREST) + log.Debugf(log.SyncMgr, "%s: Added orderbook sync item %v: using websocket: %v using REST: %v\n", + c.Exchange, FormatCurrency(c.Pair).String(), c.Orderbook.IsUsingWebsocket, c.Orderbook.IsUsingREST) if atomic.LoadInt32(&e.initSyncCompleted) != 1 { e.initSyncWG.Add(1) createdCounter++ @@ -110,8 +110,8 @@ func (e *ExchangeCurrencyPairSyncer) add(c *CurrencyPairSyncAgent) { } if e.Cfg.SyncTrades { - log.Debugf(log.SyncMgr, "%s: Added trade sync item %v: using websocket: %v using REST: %v\n", c.Exchange, c.Pair.String(), - c.Trade.IsUsingWebsocket, c.Trade.IsUsingREST) + log.Debugf(log.SyncMgr, "%s: Added trade sync item %v: using websocket: %v using REST: %v\n", + c.Exchange, FormatCurrency(c.Pair).String(), c.Trade.IsUsingWebsocket, c.Trade.IsUsingREST) if atomic.LoadInt32(&e.initSyncCompleted) != 1 { e.initSyncWG.Add(1) createdCounter++ @@ -218,7 +218,8 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair e.CurrencyPairs[x].Ticker.HaveData = true e.CurrencyPairs[x].Ticker.IsProcessing = false if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { - log.Debugf(log.SyncMgr, "%s ticker sync complete %v [%d/%d].\n", exchangeName, p, removedCounter, createdCounter) + log.Debugf(log.SyncMgr, "%s ticker sync complete %v [%d/%d].\n", + exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter) removedCounter++ e.initSyncWG.Done() } @@ -232,7 +233,8 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair e.CurrencyPairs[x].Orderbook.HaveData = true e.CurrencyPairs[x].Orderbook.IsProcessing = false if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { - log.Debugf(log.SyncMgr, "%s orderbook sync complete %v [%d/%d].\n", exchangeName, p, removedCounter, createdCounter) + log.Debugf(log.SyncMgr, "%s orderbook sync complete %v [%d/%d].\n", + exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter) removedCounter++ e.initSyncWG.Done() } @@ -246,7 +248,8 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair e.CurrencyPairs[x].Trade.HaveData = true e.CurrencyPairs[x].Trade.IsProcessing = false if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { - log.Debugf(log.SyncMgr, "%s trade sync complete %v [%d/%d].\n", exchangeName, p, removedCounter, createdCounter) + log.Debugf(log.SyncMgr, "%s trade sync complete %v [%d/%d].\n", + exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter) removedCounter++ e.initSyncWG.Done() } @@ -346,7 +349,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { c.Ticker.IsUsingWebsocket = false c.Ticker.IsUsingREST = true log.Warnf(log.SyncMgr, "%s %s: No ticker update after 10 seconds, switching from websocket to rest\n", - c.Exchange, c.Pair.String()) + c.Exchange, FormatCurrency(p).String()) e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, false) } } @@ -374,7 +377,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { e.mux.Unlock() } else { if e.Cfg.Verbose { - log.Debugf(log.OrderMgr, "%s Using recent batching cache\n", exchangeName) + log.Debugf(log.SyncMgr, "%s Using recent batching cache\n", exchangeName) } result, err = Bot.Exchanges[x].FetchTicker(c.Pair, c.AssetType) } @@ -408,7 +411,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { c.Orderbook.IsUsingWebsocket = false c.Orderbook.IsUsingREST = true log.Warnf(log.SyncMgr, "%s %s: No orderbook update after 15 seconds, switching from websocket to rest\n", - c.Exchange, c.Pair.String()) + c.Exchange, FormatCurrency(c.Pair).String()) e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, false) } } @@ -531,10 +534,10 @@ func (e *ExchangeCurrencyPairSyncer) Start() { } if atomic.CompareAndSwapInt32(&e.initSyncStarted, 0, 1) { - log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer initial sync started.") + log.Debugf(log.SyncMgr, + "Exchange CurrencyPairSyncer initial sync started. %d items to process.\n", + createdCounter) e.initSyncStartTime = time.Now() - log.Debugln(log.SyncMgr, createdCounter) - log.Debugln(log.SyncMgr, removedCounter) } go func() { @@ -542,7 +545,8 @@ func (e *ExchangeCurrencyPairSyncer) Start() { if atomic.CompareAndSwapInt32(&e.initSyncCompleted, 0, 1) { log.Debugf(log.SyncMgr, "Exchange CurrencyPairSyncer initial sync is complete.\n") completedTime := time.Now() - log.Debugf(log.SyncMgr, "Exchange CurrencyPairSyncer initiial sync took %v [%v sync items].\n", completedTime.Sub(e.initSyncStartTime), createdCounter) + log.Debugf(log.SyncMgr, "Exchange CurrencyPairSyncer initiial sync took %v [%v sync items].\n", + completedTime.Sub(e.initSyncStartTime), createdCounter) if !e.Cfg.SyncContinuously { log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer stopping.") diff --git a/exchanges/okex/okex.go b/exchanges/okex/okex.go index 334bcf13..c452f490 100644 --- a/exchanges/okex/okex.go +++ b/exchanges/okex/okex.go @@ -207,7 +207,7 @@ func (o *OKEX) GetFuturesOrderBook(request okgroup.GetFuturesOrderBookRequest) ( // GetAllFuturesTokenInfo Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all contracts. func (o *OKEX) GetAllFuturesTokenInfo() (resp []okgroup.GetFuturesTokenInfoResponse, _ error) { requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupInstruments, okgroup.OKGroupTicker) - return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, true) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &resp, false) } // GetFuturesTokenInfoForCurrency Get the last traded price, best bid/ask price, 24 hour trading volume and more info of a contract. diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index 5febea19..a8b5d911 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -1045,7 +1045,9 @@ func TestGetAllFuturesTokenInfo(t *testing.T) { TestSetDefaults(t) t.Parallel() _, err := o.GetAllFuturesTokenInfo() - testStandardErrorHandling(t, err) + if err != nil { + t.Error(err) + } } // TestGetAllFuturesTokenInfo API endpoint test diff --git a/exchanges/support.go b/exchanges/support.go index 010fa626..16eae343 100644 --- a/exchanges/support.go +++ b/exchanges/support.go @@ -1,5 +1,17 @@ package exchange +import "strings" + +// IsSupported returns whether or not a specific exchange is supported +func IsSupported(exchangeName string) bool { + for x := range Exchanges { + if strings.EqualFold(exchangeName, Exchanges[x]) { + return true + } + } + return false +} + // Exchanges stores a list of supported exchanges var Exchanges = []string{ "anx", diff --git a/exchanges/support_test.go b/exchanges/support_test.go new file mode 100644 index 00000000..978ab4e8 --- /dev/null +++ b/exchanges/support_test.go @@ -0,0 +1,13 @@ +package exchange + +import "testing" + +func TestIsSupported(t *testing.T) { + if ok := IsSupported("BiTStaMp"); !ok { + t.Error("supported exchange should be valid") + } + + if ok := IsSupported("meowexch"); ok { + t.Error("non-supported exchange should be in valid") + } +} diff --git a/main.go b/main.go index 112737ab..b4c3b55e 100644 --- a/main.go +++ b/main.go @@ -62,9 +62,9 @@ func main() { flag.BoolVar(&settings.EnableOpenExchangeRates, "openexchangerates", false, "overrides config and sets up foreign exchange Open Exchange Rates") // Exchange tuning settings - flag.BoolVar(&settings.EnableExchangeAutoPairUpdates, "exchangeautopairupdates", true, "enables automatic available currency pair updates for supported exchanges") + flag.BoolVar(&settings.EnableExchangeAutoPairUpdates, "exchangeautopairupdates", false, "enables automatic available currency pair updates for supported exchanges") flag.BoolVar(&settings.DisableExchangeAutoPairUpdates, "exchangedisableautopairupdates", false, "disables exchange auto pair updates") - flag.BoolVar(&settings.EnableExchangeWebsocketSupport, "exchangewebsocketsupport", true, "enables Websocket support for exchanges") + flag.BoolVar(&settings.EnableExchangeWebsocketSupport, "exchangewebsocketsupport", false, "enables Websocket support for exchanges") flag.BoolVar(&settings.EnableExchangeRESTSupport, "exchangerestsupport", true, "enables REST support for exchanges") flag.BoolVar(&settings.EnableExchangeVerbose, "exchangeverbose", false, "increases exchange logging verbosity") flag.BoolVar(&settings.ExchangePurgeCredentials, "exchangepurgecredentials", false, "purges the stored exchange API credentials") From 6579196c573e819f656c6405a5903195a5f8f8e8 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 11 Sep 2019 18:00:31 +1000 Subject: [PATCH 34/71] Daily engine changes Expand config test coverage --- config/config.go | 102 ++++---- config/config_test.go | 523 +++++++++++++++++++++++++++++++++++------- 2 files changed, 499 insertions(+), 126 deletions(-) diff --git a/config/config.go b/config/config.go index d8741fc3..9446db63 100644 --- a/config/config.go +++ b/config/config.go @@ -464,6 +464,10 @@ func (c *Config) CheckExchangeAssetsConsistency(exchName string) { // SetPairs sets the exchanges currency pairs func (c *Config) SetPairs(exchName string, assetType asset.Item, enabled bool, pairs currency.Pairs) error { + if len(pairs) == 0 { + return fmt.Errorf("pairs is nil") + } + exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return err @@ -515,14 +519,9 @@ func (c *Config) CheckPairConfigFormats(exchName string) error { return err } - pairs, err := c.GetCurrencyPairConfig(exchName, assetType) - if err != nil { - return err - } - - if pairs == nil { - continue - } + // No err checking is required as the above checks the same + // conditions + pairs, _ := c.GetCurrencyPairConfig(exchName, assetType) if len(pairs.Available) == 0 || len(pairs.Enabled) == 0 { continue @@ -537,15 +536,22 @@ func (c *Config) CheckPairConfigFormats(exchName string) error { } for y := range loadedPairs { + if pairFmt.Delimiter != "" && pairFmt.Index != "" { + return fmt.Errorf( + "exchange %s %s %s cannot have an index and delimiter set at the same time", + exchName, pairsType, assetType) + } if pairFmt.Delimiter != "" { if !strings.Contains(loadedPairs[y].String(), pairFmt.Delimiter) { - return fmt.Errorf("exchange %s %s %v pairs does not contain delimiter", exchName, pairsType, assetType) + return fmt.Errorf( + "exchange %s %s %s pairs does not contain delimiter", + exchName, pairsType, assetType) } } - if pairFmt.Index != "" { if !strings.Contains(loadedPairs[y].String(), pairFmt.Index) { - return fmt.Errorf("exchange %s %s %v pairs does not contain an index", exchName, pairsType, assetType) + return fmt.Errorf("exchange %s %s %s pairs does not contain an index", + exchName, pairsType, assetType) } } } @@ -574,22 +580,13 @@ func (c *Config) CheckPairConsistency(exchName string) error { return err } - err = c.CheckPairConfigFormats(exchName) - if err != nil { - return err - } - for x := range assetTypes { enabledPairs, err := c.GetEnabledPairs(exchName, assetTypes[x]) if err != nil { return err } - availPairs, err := c.GetAvailablePairs(exchName, assetTypes[x]) - if err != nil { - return err - } - + availPairs, _ := c.GetAvailablePairs(exchName, assetTypes[x]) if len(availPairs) == 0 { continue } @@ -616,20 +613,12 @@ func (c *Config) CheckPairConsistency(exchName string) error { if len(pairs) == 0 || len(enabledPairs) == 0 { newPair := availPairs.GetRandomPair() - err = c.SetPairs(exchName, assetTypes[x], true, - currency.Pairs{newPair}, - ) - if err != nil { - return fmt.Errorf("exchange %s failed to set pairs: %v", exchName, err) - } + c.SetPairs(exchName, assetTypes[x], true, currency.Pairs{newPair}) log.Warnf(log.ExchangeSys, "Exchange %s: [%v] No enabled pairs found in available pairs, randomly added %v pair.\n", exchName, assetTypes[x], newPair) continue } else { - err = c.SetPairs(exchName, assetTypes[x], true, pairs) - if err != nil { - return fmt.Errorf("exchange %s failed to set pairs: %v", exchName, err) - } + c.SetPairs(exchName, assetTypes[x], true, pairs) } log.Warnf(log.ExchangeSys, "Exchange %s: [%v] Removing enabled pair(s) %v from enabled pairs as it isn't an available pair.\n", exchName, assetTypes[x], pairsRemoved.Strings()) @@ -661,18 +650,22 @@ func (c *Config) GetPairFormat(exchName string, assetType asset.Item) (currency. if !supports { return currency.PairFormat{}, - fmt.Errorf("exchange %s does not support asset type %v", exchName, assetType) - } - - if exchCfg.CurrencyPairs == nil { - return currency.PairFormat{}, errors.New("exchange currency pairs type is nil") + fmt.Errorf("exchange %s does not support asset type %s", exchName, + assetType) } if exchCfg.CurrencyPairs.UseGlobalFormat { return *exchCfg.CurrencyPairs.ConfigFormat, nil } - return *exchCfg.CurrencyPairs.Get(assetType).ConfigFormat, nil + p := exchCfg.CurrencyPairs.Get(assetType) + if p == nil { + return currency.PairFormat{}, + fmt.Errorf("exchange %s pair store for asset type %s is nil", exchName, + assetType) + } + + return *p.ConfigFormat, nil } // GetAvailablePairs returns a list of currency pairs for a specifc exchange @@ -687,12 +680,12 @@ func (c *Config) GetAvailablePairs(exchName string, assetType asset.Item) (curre return nil, err } - pairs := exchCfg.CurrencyPairs.Get(assetType) + pairs := exchCfg.CurrencyPairs.GetPairs(assetType, false) if pairs == nil { return nil, nil } - return pairs.Available.Format(pairFormat.Delimiter, pairFormat.Index, + return pairs.Format(pairFormat.Delimiter, pairFormat.Index, pairFormat.Uppercase), nil } @@ -708,12 +701,12 @@ func (c *Config) GetEnabledPairs(exchName string, assetType asset.Item) ([]curre return nil, err } - pairs := exchCfg.CurrencyPairs.Get(assetType) + pairs := exchCfg.CurrencyPairs.GetPairs(assetType, true) if pairs == nil { return nil, nil } - return pairs.Enabled.Format(pairFormat.Delimiter, pairFormat.Index, + return pairs.Format(pairFormat.Delimiter, pairFormat.Index, pairFormat.Uppercase), nil } @@ -934,12 +927,31 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].CurrencyPairs.ConfigFormat = c.Exchanges[i].ConfigCurrencyPairFormat c.Exchanges[i].CurrencyPairs.RequestFormat = c.Exchanges[i].RequestCurrencyPairFormat - c.Exchanges[i].CurrencyPairs.AssetTypes = asset.New(strings.ToLower(*c.Exchanges[i].AssetTypes)) + + if c.Exchanges[i].AssetTypes == nil { + c.Exchanges[i].CurrencyPairs.AssetTypes = asset.Items{ + asset.Spot, + } + } else { + c.Exchanges[i].CurrencyPairs.AssetTypes = asset.New( + strings.ToLower(*c.Exchanges[i].AssetTypes), + ) + } + + var availPairs, enabledPairs currency.Pairs + if c.Exchanges[i].AvailablePairs != nil { + availPairs = *c.Exchanges[i].AvailablePairs + } + + if c.Exchanges[i].EnabledPairs != nil { + enabledPairs = *c.Exchanges[i].EnabledPairs + } + c.Exchanges[i].CurrencyPairs.UseGlobalFormat = true c.Exchanges[i].CurrencyPairs.Store(asset.Spot, currency.PairStore{ - Available: *c.Exchanges[i].AvailablePairs, - Enabled: *c.Exchanges[i].EnabledPairs, + Available: availPairs, + Enabled: enabledPairs, }, ) @@ -954,7 +966,7 @@ func (c *Config) CheckExchangeConfigValues() error { if c.Exchanges[i].Enabled { if c.Exchanges[i].Name == "" { - log.Error(log.ConfigMgr, ErrExchangeNameEmpty, i) + log.Errorf(log.ConfigMgr, ErrExchangeNameEmpty, i) c.Exchanges[i].Enabled = false continue } diff --git a/config/config_test.go b/config/config_test.go index 70597fe4..9da4e113 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -153,13 +153,11 @@ func TestPurgeExchangeCredentials(t *testing.T) { API: APIConfig{ AuthenticatedSupport: true, AuthenticatedWebsocketSupport: true, - CredentialsValidator: &APICredentialsValidatorConfig{ RequiresKey: true, RequiresSecret: true, RequiresClientID: true, }, - Credentials: APICredentialsConfig{ Key: "asdf123", Secret: "secretp4ssw0rd", @@ -459,60 +457,272 @@ func TestCheckExchangeAssetsConsistency(t *testing.T) { } } -func TestCheckPairConsistency(t *testing.T) { - cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) - if err != nil { - t.Error("Test failed. CheckPairConsistency LoadConfig error", err) +func TestSetPairs(t *testing.T) { + t.Parallel() + + var c Config + pairs := currency.Pairs{ + currency.NewPair(currency.BTC, currency.USD), + currency.NewPair(currency.BTC, currency.EUR), } - err = cfg.CheckPairConsistency("asdf") + err := c.SetPairs("asdf", asset.Spot, true, nil) if err == nil { - t.Error("Test failed. CheckPairConsistency. Non-existent exchange returned nil error") + t.Error("nil pairs should throw an error") } - pairsMan := currency.PairsManager{ - UseGlobalFormat: true, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: true, + err = c.SetPairs("asdf", asset.Spot, true, pairs) + if err == nil { + t.Error("non-existent exchange should throw an error") + } + + c.Exchanges = append(c.Exchanges, + ExchangeConfig{ + Name: testFakeExchangeName, + }, + ) + + err = c.SetPairs(testFakeExchangeName, asset.Index, true, pairs) + if err == nil { + t.Error("non initialised pair manager should throw an error") + } + + c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + asset.Futures, }, } - pairsMan.Store(asset.Spot, currency.PairStore{ - Available: currency.NewPairsFromStrings([]string{"DOGE_USD,DOGE_AUD"}), - Enabled: currency.NewPairsFromStrings([]string{"DOGE_USD,DOGE_AUD,DOGE_BTC"}), - }) - cfg.Exchanges = append(cfg.Exchanges, ExchangeConfig{ - Name: "TestExchange", - Enabled: true, - CurrencyPairs: &pairsMan, - }) - - tec, err := cfg.GetExchangeConfig("TestExchange") - if err != nil { - t.Error("Test failed. CheckPairConsistency GetExchangeConfig error", err) + err = c.SetPairs(testFakeExchangeName, asset.Index, true, pairs) + if err == nil { + t.Error("non supported asset type should throw an error") } - err = cfg.CheckPairConsistency("TestExchange") + err = c.SetPairs(testFakeExchangeName, asset.Spot, true, pairs) if err != nil { - t.Error("Test failed. CheckPairConsistency error:", err) + t.Error(err) } - // Calling again immediately to hit the if !update {return nil} - err = cfg.CheckPairConsistency("TestExchange") - if err != nil { - t.Error("Test failed. CheckPairConsistency error:", err) +} + +func TestGetCurrencyPairConfig(t *testing.T) { + t.Parallel() + + var c Config + _, err := c.GetCurrencyPairConfig("asdfg", asset.Spot) + if err == nil { + t.Error("expected error with non-existent exchange") } - tec.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"DOGE_LTC,BTC_LTC"}), false) - err = cfg.UpdateExchangeConfig(tec) - if err != nil { - t.Error("Test failed. CheckPairConsistency Update config failed, error:", err) + c.Exchanges = append(c.Exchanges, + ExchangeConfig{ + Name: testFakeExchangeName, + }, + ) + + _, err = c.GetCurrencyPairConfig(testFakeExchangeName, asset.Index) + if err == nil { + t.Error("expected error with nil currency pair store") } - err = cfg.CheckPairConsistency("TestExchange") + pm := ¤cy.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + asset.Futures, + }, + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: { + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + Delimiter: "_", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "~", + }, + }, + }, + } + + c.Exchanges[0].CurrencyPairs = pm + _, err = c.GetCurrencyPairConfig(testFakeExchangeName, asset.Index) + if err == nil { + t.Error("expected error with unsupported asset") + } + + var p *currency.PairStore + p, err = c.GetCurrencyPairConfig(testFakeExchangeName, asset.Spot) if err != nil { - t.Error("Test failed. CheckPairConsistency error:", err) + t.Error(err) + } + + if p.RequestFormat.Delimiter != "_" || + p.RequestFormat.Uppercase || + !p.ConfigFormat.Uppercase || + p.ConfigFormat.Delimiter != "~" { + t.Error("unexpected values") + } +} + +func TestCheckPairConfigFormats(t *testing.T) { + var c Config + if err := c.CheckPairConfigFormats("non-existent"); err == nil { + t.Error("non-existent exchange should throw an error") + } + + // Test nil pair store + c.Exchanges = append(c.Exchanges, + ExchangeConfig{ + Name: testFakeExchangeName, + CurrencyPairs: ¤cy.PairsManager{ + AssetTypes: asset.Items{ + asset.Item("wrong"), + }, + }, + }, + ) + + if err := c.CheckPairConfigFormats(testFakeExchangeName); err == nil { + t.Error("nil pair store should return an error") + } + + c.Exchanges[0].CurrencyPairs.AssetTypes = asset.Items{asset.Spot} + c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ + asset.Spot: { + RequestFormat: ¤cy.PairFormat{}, + ConfigFormat: ¤cy.PairFormat{}, + }, + asset.Futures: { + RequestFormat: ¤cy.PairFormat{}, + ConfigFormat: ¤cy.PairFormat{}, + }, + } + if err := c.CheckPairConfigFormats(testFakeExchangeName); err != nil { + t.Error("nil pairs should be okay to continue") + } + + // Test having a pair index and delimiter set at the same time throws an error + c.Exchanges[0].CurrencyPairs.AssetTypes = asset.Items{asset.Spot} + c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ + asset.Spot: { + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + Delimiter: "_", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "~", + Index: "USD", + }, + Available: currency.Pairs{ + currency.NewPairDelimiter("BTC-USD", "-"), + }, + Enabled: currency.Pairs{ + currency.NewPairDelimiter("BTC~USD", "~"), + }, + }, + } + + if err := c.CheckPairConfigFormats(testFakeExchangeName); err == nil { + t.Error("invalid pair delimiter and index should throw an error") + } + + // Test wrong pair delimiter throws an error + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].ConfigFormat.Index = "" + if err := c.CheckPairConfigFormats(testFakeExchangeName); err == nil { + t.Error("invalid pair delimiter should throw an error") + } + + // Test wrong pair index in the enabled pairs throw an error + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + ConfigFormat: ¤cy.PairFormat{ + Index: currency.AUD.String(), + }, + } + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{ + currency.NewPair(currency.BTC, currency.AUD), + } + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{ + currency.NewPair(currency.BTC, currency.KRW), + } + + if err := c.CheckPairConfigFormats(testFakeExchangeName); err == nil { + t.Error("invalid pair index should throw an error") + } +} + +func TestCheckPairConsistency(t *testing.T) { + t.Parallel() + + var c Config + if err := c.CheckPairConsistency("asdf"); err == nil { + t.Error("non-existent exchange should return an error") + } + + c.Exchanges = append(c.Exchanges, + ExchangeConfig{ + Name: testFakeExchangeName, + CurrencyPairs: ¤cy.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + }, + }, + }, + ) + + // Test nil pair store + if err := c.CheckPairConsistency(testFakeExchangeName); err == nil { + t.Error("nil pair store should return an error") + } + + c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ + asset.Spot: { + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + Delimiter: "_", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "_", + }, + Enabled: currency.Pairs{ + currency.NewPairDelimiter("BTC_USD", "_"), + }, + }, + } + + // Test for nil avail pairs + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { + t.Error("nil available pairs should continue") + } + + // Test that enabled pair is not found in the available pairs + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{ + currency.NewPairDelimiter("LTC_USD", "_"), + } + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { + t.Error("unexpected result") + } + + // Test that an empty enabled pair is populated with an available pair + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = nil + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { + t.Error("unexpected result") + } + + // Test that an invalid enabled pair is removed from the list + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{ + currency.NewPairDelimiter("LTC_USD", "_"), + currency.NewPairDelimiter("BTC_USD", "_"), + } + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { + t.Error("unexpected result") + } + + // Test when no update is required as the available pairs and enabled pairs + // are consistent + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { + t.Error("unexpected result") } } @@ -543,47 +753,173 @@ func TestSupportsPair(t *testing.T) { } } -func TestGetAvailablePairs(t *testing.T) { - cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) - if err != nil { - t.Errorf( - "Test failed. TestGetAvailablePairs. LoadConfig Error: %s", err.Error()) - } +func TestGetPairFormat(t *testing.T) { + t.Parallel() - assetType := asset.Spot - _, err = cfg.GetAvailablePairs("asdf", assetType) + var c Config + _, err := c.GetPairFormat("meow", asset.Spot) if err == nil { - t.Error( - "Test failed. TestGetAvailablePairs. Non-existent exchange returned nil error") + t.Error("non-existent exchange should throw an error") } - _, err = cfg.GetAvailablePairs("Bitfinex", assetType) + c.Exchanges = append(c.Exchanges, + ExchangeConfig{ + Name: testFakeExchangeName, + }, + ) + _, err = c.GetPairFormat(testFakeExchangeName, asset.Spot) + if err == nil { + t.Error("nil pair manager should throw an error") + } + + c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ + AssetTypes: asset.Items{asset.Spot}, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + Delimiter: "_", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "_", + }, + } + _, err = c.GetPairFormat(testFakeExchangeName, asset.Item("invalid")) + if err == nil { + t.Error("non-existent asset item should throw an error") + } + + _, err = c.GetPairFormat(testFakeExchangeName, asset.Futures) + if err == nil { + t.Error("valid but non supported asset type should throw an error") + } + + var p currency.PairFormat + p, err = c.GetPairFormat(testFakeExchangeName, asset.Spot) if err != nil { - t.Errorf( - "Test failed. TestGetAvailablePairs. Incorrect values. Err: %s", err) + t.Error(err) + } + + if !p.Uppercase && p.Delimiter != "_" { + t.Error("unexpected results") + } + + // Test nil pair store + c.Exchanges[0].CurrencyPairs.UseGlobalFormat = false + _, err = c.GetPairFormat(testFakeExchangeName, asset.Spot) + if err == nil { + t.Error(err) + } + + c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ + asset.Spot: { + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "~", + }, + }, + } + p, err = c.GetPairFormat(testFakeExchangeName, asset.Spot) + if err != nil { + t.Error(err) + } + + if p.Delimiter != "~" && !p.Uppercase { + t.Error("unexpected results") + } +} + +func TestGetAvailablePairs(t *testing.T) { + t.Parallel() + + var c Config + _, err := c.GetAvailablePairs("asdf", asset.Spot) + if err == nil { + t.Error("non-existent exchange should throw an error") + } + + c.Exchanges = append(c.Exchanges, + ExchangeConfig{ + Name: testFakeExchangeName, + CurrencyPairs: ¤cy.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + }, + }, + }, + ) + + _, err = c.GetAvailablePairs(testFakeExchangeName, asset.Spot) + if err == nil { + t.Error("nil pair manager should throw an error") + } + + c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ + asset.Spot: { + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + }, + } + _, err = c.GetAvailablePairs(testFakeExchangeName, asset.Spot) + if err != nil { + t.Error("nil pairs should return a nil error") + } + + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{ + currency.NewPair(currency.BTC, currency.USD), + } + _, err = c.GetAvailablePairs(testFakeExchangeName, asset.Spot) + if err != nil { + t.Error(err) } } func TestGetEnabledPairs(t *testing.T) { - cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) - if err != nil { - t.Errorf( - "Test failed. TestGetEnabledPairs. LoadConfig Error: %s", err.Error()) - } + t.Parallel() - assetType := asset.Spot - _, err = cfg.GetEnabledPairs("asdf", assetType) + var c Config + _, err := c.GetEnabledPairs("asdf", asset.Spot) if err == nil { - t.Error( - "Test failed. TestGetEnabledPairs. Non-existent exchange returned nil error") + t.Error("non-existent exchange should throw an error") } - _, err = cfg.GetEnabledPairs("Bitfinex", assetType) + c.Exchanges = append(c.Exchanges, + ExchangeConfig{ + Name: testFakeExchangeName, + CurrencyPairs: ¤cy.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + }, + }, + }, + ) + + _, err = c.GetEnabledPairs(testFakeExchangeName, asset.Spot) + if err == nil { + t.Error("nil pair manager should throw an error") + } + + c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ + asset.Spot: { + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "-", + Uppercase: true, + }, + }, + } + _, err = c.GetEnabledPairs(testFakeExchangeName, asset.Spot) if err != nil { - t.Errorf( - "Test failed. TestGetEnabledPairs. Incorrect values. Err: %s", err) + t.Error("nil pairs should return a nil error") + } + + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{ + currency.NewPair(currency.BTC, currency.USD), + } + _, err = c.GetEnabledPairs(testFakeExchangeName, asset.Spot) + if err != nil { + t.Error(err) } } @@ -778,6 +1114,18 @@ func TestGetForexProviderConfig(t *testing.T) { } } +func TestGetForexProvidersConfig(t *testing.T) { + cfg := GetConfig() + err := cfg.LoadConfig(ConfigTestFile) + if err != nil { + t.Error(err) + } + + if r := cfg.GetForexProvidersConfig(); len(r) != 5 { + t.Error("unexpected length of forex providers") + } +} + func TestGetPrimaryForexProvider(t *testing.T) { cfg := GetConfig() err := cfg.LoadConfig(ConfigTestFile) @@ -799,25 +1147,27 @@ func TestGetPrimaryForexProvider(t *testing.T) { } func TestUpdateExchangeConfig(t *testing.T) { - UpdateExchangeConfig := GetConfig() - err := UpdateExchangeConfig.LoadConfig(ConfigTestFile) + c := GetConfig() + err := c.LoadConfig(ConfigTestFile) if err != nil { - t.Errorf( - "Test failed. UpdateExchangeConfig.LoadConfig Error: %s", err.Error(), - ) + t.Error(err) } - e, err2 := UpdateExchangeConfig.GetExchangeConfig("ANX") - if err2 != nil { - t.Errorf( - "Test failed. UpdateExchangeConfig.GetExchangeConfig: %s", err.Error(), - ) + + e := &ExchangeConfig{} + err = c.UpdateExchangeConfig(e) + if err == nil { + t.Error("non-existent exchange should throw an error") } + + e, err = c.GetExchangeConfig("ANX") + if err != nil { + t.Error(err) + } + e.API.Credentials.Key = "test1234" - err3 := UpdateExchangeConfig.UpdateExchangeConfig(e) - if err3 != nil { - t.Errorf( - "Test failed. UpdateExchangeConfig.UpdateExchangeConfig: %s", err.Error(), - ) + err = c.UpdateExchangeConfig(e) + if err != nil { + t.Error(err) } } @@ -838,6 +1188,17 @@ func TestCheckExchangeConfigValues(t *testing.T) { ) } + checkExchangeConfigValues.Exchanges[0].Name = "GDAX" + err = checkExchangeConfigValues.CheckExchangeConfigValues() + if err != nil { + t.Errorf("Test failed. checkExchangeConfigValues.CheckExchangeConfigValues: %s", + err.Error(), + ) + } + if checkExchangeConfigValues.Exchanges[0].Name != "CoinbasePro" { + t.Error("exchange name should have been updated from GDAX to CoinbasePRo") + } + checkExchangeConfigValues.Exchanges[0].WebsocketResponseMaxLimit = 0 checkExchangeConfigValues.Exchanges[0].WebsocketResponseCheckTimeout = 0 checkExchangeConfigValues.Exchanges[0].WebsocketOrderbookBufferLimit = 0 From ba7e3876433f6b21b52b814ed1172b75ed340d99 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 12 Sep 2019 15:00:24 +1000 Subject: [PATCH 35/71] (Engine) Gate-IO Websocket subscription pairs case sensitivity (#352) * convert pairs to upper case as gateio websocket subscription is case sensative * Update gateio_websocket.go --- exchanges/gateio/gateio_websocket.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 15ebcdf0..60853d4a 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -339,7 +339,8 @@ func (g *Gateio) GenerateDefaultSubscriptions() { // Subscribe sends a websocket message to receive data from the channel func (g *Gateio) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { params := []interface{}{g.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String()} + asset.Spot).Upper()} + for i := range channelToSubscribe.Params { params = append(params, channelToSubscribe.Params[i]) } @@ -372,7 +373,7 @@ func (g *Gateio) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscr ID: g.WebsocketConn.GenerateMessageID(true), Method: unsbuscribeText, Params: []interface{}{g.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String(), 1800}, + asset.Spot).Upper(), 1800}, } resp, err := g.WebsocketConn.SendMessageReturnResponse(subscribe.ID, subscribe) if err != nil { From 427f9c9d40c4732dabfb64481c1a466e48e47669 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 12 Sep 2019 17:27:07 +1000 Subject: [PATCH 36/71] Expand config test coverage --- config/config.go | 5 +- config/config_test.go | 323 +++++++++++++++++++++++++++++++++--------- 2 files changed, 259 insertions(+), 69 deletions(-) diff --git a/config/config.go b/config/config.go index 9446db63..34f679ed 100644 --- a/config/config.go +++ b/config/config.go @@ -873,12 +873,13 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].AuthenticatedAPISupport = nil c.Exchanges[i].AuthenticatedWebsocketAPISupport = nil c.Exchanges[i].APIKey = nil - c.Exchanges[i].APIAuthPEMKey = nil c.Exchanges[i].APISecret = nil + c.Exchanges[i].ClientID = nil + c.Exchanges[i].APIAuthPEMKeySupport = nil + c.Exchanges[i].APIAuthPEMKey = nil c.Exchanges[i].APIURL = nil c.Exchanges[i].APIURLSecondary = nil c.Exchanges[i].WebsocketURL = nil - c.Exchanges[i].ClientID = nil } if c.Exchanges[i].Features == nil { diff --git a/config/config_test.go b/config/config_test.go index 9da4e113..17865bb3 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1173,111 +1173,300 @@ func TestUpdateExchangeConfig(t *testing.T) { // TestCheckExchangeConfigValues logic test func TestCheckExchangeConfigValues(t *testing.T) { - checkExchangeConfigValues := Config{} - err := checkExchangeConfigValues.LoadConfig(ConfigTestFile) + var cfg Config + err := cfg.LoadConfig(ConfigTestFile) if err != nil { - t.Errorf( - "Test failed. checkExchangeConfigValues.LoadConfig: %s", err.Error(), - ) - } - err = checkExchangeConfigValues.CheckExchangeConfigValues() - if err != nil { - t.Errorf( - "Test failed. checkExchangeConfigValues.CheckExchangeConfigValues: %s", - err.Error(), - ) + t.Fatal(err) } - checkExchangeConfigValues.Exchanges[0].Name = "GDAX" - err = checkExchangeConfigValues.CheckExchangeConfigValues() + // Test our default test config and report any errors + err = cfg.CheckExchangeConfigValues() if err != nil { - t.Errorf("Test failed. checkExchangeConfigValues.CheckExchangeConfigValues: %s", - err.Error(), - ) + t.Fatal(err) } - if checkExchangeConfigValues.Exchanges[0].Name != "CoinbasePro" { + + cfg.Exchanges[0].Name = "GDAX" + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + if cfg.Exchanges[0].Name != "CoinbasePro" { t.Error("exchange name should have been updated from GDAX to CoinbasePRo") } - checkExchangeConfigValues.Exchanges[0].WebsocketResponseMaxLimit = 0 - checkExchangeConfigValues.Exchanges[0].WebsocketResponseCheckTimeout = 0 - checkExchangeConfigValues.Exchanges[0].WebsocketOrderbookBufferLimit = 0 - checkExchangeConfigValues.Exchanges[0].HTTPTimeout = 0 - err = checkExchangeConfigValues.CheckExchangeConfigValues() + // Test API settings migration + sptr := func(s string) *string { return &s } + bptr := func(b bool) *bool { return &b } + int64ptr := func(i int64) *int64 { return &i } + + cfg.Exchanges[0].APIKey = sptr("awesomeKey") + cfg.Exchanges[0].APISecret = sptr("meowSecret") + cfg.Exchanges[0].ClientID = sptr("clientIDerino") + cfg.Exchanges[0].APIAuthPEMKey = sptr("-----BEGIN EC PRIVATE KEY-----\nASDF\n-----END EC PRIVATE KEY-----\n") + cfg.Exchanges[0].APIAuthPEMKeySupport = bptr(true) + cfg.Exchanges[0].AuthenticatedAPISupport = bptr(true) + cfg.Exchanges[0].AuthenticatedWebsocketAPISupport = bptr(true) + cfg.Exchanges[0].WebsocketURL = sptr("wss://1337") + cfg.Exchanges[0].APIURL = sptr(APIURLNonDefaultMessage) + cfg.Exchanges[0].APIURLSecondary = sptr(APIURLNonDefaultMessage) + err = cfg.CheckExchangeConfigValues() if err != nil { - t.Errorf("Test failed. checkExchangeConfigValues.CheckExchangeConfigValues: %s", - err.Error(), - ) + t.Error(err) } - if checkExchangeConfigValues.Exchanges[0].WebsocketResponseMaxLimit == 0 { - t.Fatalf("Test failed. Expected exchange %s to have updated WebsocketResponseMaxLimit value", checkExchangeConfigValues.Exchanges[0].Name) + // Ensure that all of our previous settings are migrated + if cfg.Exchanges[0].API.Credentials.Key != "awesomeKey" || + cfg.Exchanges[0].API.Credentials.Secret != "meowSecret" || + cfg.Exchanges[0].API.Credentials.ClientID != "clientIDerino" || + !strings.Contains(cfg.Exchanges[0].API.Credentials.PEMKey, "ASDF") || + !cfg.Exchanges[0].API.PEMKeySupport || + !cfg.Exchanges[0].API.AuthenticatedSupport || + !cfg.Exchanges[0].API.AuthenticatedWebsocketSupport || + cfg.Exchanges[0].API.Endpoints.WebsocketURL != "wss://1337" || + cfg.Exchanges[0].API.Endpoints.URL != APIURLNonDefaultMessage || + cfg.Exchanges[0].API.Endpoints.URLSecondary != APIURLNonDefaultMessage { + t.Error("unexpected values") } - if checkExchangeConfigValues.Exchanges[0].WebsocketOrderbookBufferLimit == 0 { - t.Fatalf("Test failed. Expected exchange %s to have updated WebsocketOrderbookBufferLimit value", checkExchangeConfigValues.Exchanges[0].Name) + if cfg.Exchanges[0].APIKey != nil || + cfg.Exchanges[0].APISecret != nil || + cfg.Exchanges[0].ClientID != nil || + cfg.Exchanges[0].APIAuthPEMKey != nil || + cfg.Exchanges[0].APIAuthPEMKeySupport != nil || + cfg.Exchanges[0].AuthenticatedAPISupport != nil || + cfg.Exchanges[0].AuthenticatedWebsocketAPISupport != nil || + cfg.Exchanges[0].WebsocketURL != nil || + cfg.Exchanges[0].APIURL != nil || + cfg.Exchanges[0].APIURLSecondary != nil { + t.Error("unexpected values") } - if checkExchangeConfigValues.Exchanges[0].HTTPTimeout == 0 { - t.Fatalf("Test failed. Expected exchange %s to have updated HTTPTimeout value", checkExchangeConfigValues.Exchanges[0].Name) + // Test feature and endpoint migrations migrations + cfg.Exchanges[0].Features = nil + cfg.Exchanges[0].SupportsAutoPairUpdates = bptr(true) + cfg.Exchanges[0].Websocket = bptr(true) + cfg.Exchanges[0].API.Endpoints.URL = "" + cfg.Exchanges[0].API.Endpoints.URLSecondary = "" + cfg.Exchanges[0].API.Endpoints.WebsocketURL = "" + + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + + if !cfg.Exchanges[0].Features.Enabled.AutoPairUpdates || + !cfg.Exchanges[0].Features.Enabled.Websocket || + !cfg.Exchanges[0].Features.Supports.RESTCapabilities.AutoPairUpdates { + t.Error("unexpected values") + } + + if cfg.Exchanges[0].API.Endpoints.URL != APIURLNonDefaultMessage || + cfg.Exchanges[0].API.Endpoints.URLSecondary != APIURLNonDefaultMessage || + cfg.Exchanges[0].API.Endpoints.WebsocketURL != WebsocketURLNonDefaultMessage { + t.Error("unexpected values") + } + + // Test currency pair migration + setupPairs := func(emptyAssets bool) { + cfg.Exchanges[0].CurrencyPairs = nil + p := currency.Pairs{ + currency.NewPairDelimiter("BTC-USD", "-"), + } + cfg.Exchanges[0].PairsLastUpdated = int64ptr(1234567) + + if !emptyAssets { + cfg.Exchanges[0].AssetTypes = sptr("spot") + } + + cfg.Exchanges[0].AvailablePairs = &p + cfg.Exchanges[0].EnabledPairs = &p + cfg.Exchanges[0].ConfigCurrencyPairFormat = ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + } + cfg.Exchanges[0].RequestCurrencyPairFormat = ¤cy.PairFormat{ + Uppercase: false, + Delimiter: "~", + } + } + + setupPairs(false) + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + + setupPairs(true) + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + + if cfg.Exchanges[0].CurrencyPairs.LastUpdated != 1234567 { + t.Error("last updated has wrong value") + } + + pFmt := cfg.Exchanges[0].CurrencyPairs.ConfigFormat + if pFmt.Delimiter != "-" || + !pFmt.Uppercase { + t.Error("unexpected config format values") + } + + pFmt = cfg.Exchanges[0].CurrencyPairs.RequestFormat + if pFmt.Delimiter != "~" || + pFmt.Uppercase { + t.Error("unexpected request format values") + } + + if cfg.Exchanges[0].CurrencyPairs.AssetTypes.JoinToString(",") != "spot" || + !cfg.Exchanges[0].CurrencyPairs.UseGlobalFormat { + t.Error("unexpected results") + } + + pairs := cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, true) + if len(pairs) == 0 || pairs.Join() != "BTC-USD" { + t.Error("pairs not set properly") + } + + pairs = cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, false) + if len(pairs) == 0 || pairs.Join() != "BTC-USD" { + t.Error("pairs not set properly") + } + + // Ensure that all old settings are flushed + if cfg.Exchanges[0].PairsLastUpdated != nil || + cfg.Exchanges[0].ConfigCurrencyPairFormat != nil || + cfg.Exchanges[0].RequestCurrencyPairFormat != nil || + cfg.Exchanges[0].AssetTypes != nil || + cfg.Exchanges[0].AvailablePairs != nil || + cfg.Exchanges[0].EnabledPairs != nil { + t.Error("unexpected results") + } + + // Test AutoPairUpdates + cfg.Exchanges[0].Features.Supports.RESTCapabilities.AutoPairUpdates = false + cfg.Exchanges[0].Features.Supports.WebsocketCapabilities.AutoPairUpdates = false + cfg.Exchanges[0].CurrencyPairs.LastUpdated = 0 + cfg.CheckExchangeConfigValues() + + // Test HTTP rate limiter negative values + cfg.Exchanges[0].HTTPRateLimiter = &HTTPRateLimitConfig{ + Unauthenticated: HTTPRateConfig{ + Duration: -1, + Rate: -1, + }, + Authenticated: HTTPRateConfig{ + Duration: -1, + Rate: -1, + }, + } + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + + if cfg.Exchanges[0].HTTPRateLimiter.Authenticated.Duration != 0 || + cfg.Exchanges[0].HTTPRateLimiter.Authenticated.Rate != 0 || + cfg.Exchanges[0].HTTPRateLimiter.Unauthenticated.Duration != 0 || + cfg.Exchanges[0].HTTPRateLimiter.Unauthenticated.Rate != 0 { + t.Error("unexpected results") + } + + // Test exchange pair consistency error + cfg.Exchanges[0].CurrencyPairs.UseGlobalFormat = false + backup := cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot] + cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot] = nil + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + if cfg.Exchanges[0].Enabled { + t.Error("exchange should have been disabled") + } + + // Restore to previous state + cfg.Exchanges[0].Enabled = true + cfg.Exchanges[0].CurrencyPairs.UseGlobalFormat = true + cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot] = backup + + // Test websocket and HTTP timeout values + cfg.Exchanges[0].WebsocketResponseMaxLimit = 0 + cfg.Exchanges[0].WebsocketResponseCheckTimeout = 0 + cfg.Exchanges[0].WebsocketOrderbookBufferLimit = 0 + cfg.Exchanges[0].HTTPTimeout = 0 + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + + if cfg.Exchanges[0].WebsocketResponseMaxLimit == 0 { + t.Errorf("expected exchange %s to have updated WebsocketResponseMaxLimit value", + cfg.Exchanges[0].Name) + } + if cfg.Exchanges[0].WebsocketOrderbookBufferLimit == 0 { + t.Errorf("expected exchange %s to have updated WebsocketOrderbookBufferLimit value", + cfg.Exchanges[0].Name) + } + if cfg.Exchanges[0].HTTPTimeout == 0 { + t.Errorf("expected exchange %s to have updated HTTPTimeout value", + cfg.Exchanges[0].Name) } v := &APICredentialsValidatorConfig{ RequiresKey: true, RequiresSecret: true, } - checkExchangeConfigValues.Exchanges[0].API.CredentialsValidator = v - checkExchangeConfigValues.Exchanges[0].API.Credentials.Key = "Key" - checkExchangeConfigValues.Exchanges[0].API.Credentials.Secret = "Secret" - checkExchangeConfigValues.Exchanges[0].API.AuthenticatedSupport = true - checkExchangeConfigValues.Exchanges[0].API.AuthenticatedWebsocketSupport = true - checkExchangeConfigValues.CheckExchangeConfigValues() - if checkExchangeConfigValues.Exchanges[0].API.AuthenticatedSupport || - checkExchangeConfigValues.Exchanges[0].API.AuthenticatedWebsocketSupport { + cfg.Exchanges[0].API.CredentialsValidator = v + cfg.Exchanges[0].API.Credentials.Key = "Key" + cfg.Exchanges[0].API.Credentials.Secret = "Secret" + cfg.Exchanges[0].API.AuthenticatedSupport = true + cfg.Exchanges[0].API.AuthenticatedWebsocketSupport = true + cfg.CheckExchangeConfigValues() + if cfg.Exchanges[0].API.AuthenticatedSupport || + cfg.Exchanges[0].API.AuthenticatedWebsocketSupport { t.Error("Expected authenticated endpoints to be false from invalid API keys") } v.RequiresKey = false v.RequiresClientID = true - checkExchangeConfigValues.Exchanges[0].API.AuthenticatedSupport = true - checkExchangeConfigValues.Exchanges[0].API.AuthenticatedWebsocketSupport = true - checkExchangeConfigValues.Exchanges[0].API.Credentials.ClientID = DefaultAPIClientID - checkExchangeConfigValues.Exchanges[0].API.Credentials.Secret = "TESTYTEST" - checkExchangeConfigValues.CheckExchangeConfigValues() - if checkExchangeConfigValues.Exchanges[0].API.AuthenticatedSupport || - checkExchangeConfigValues.Exchanges[0].API.AuthenticatedWebsocketSupport { + cfg.Exchanges[0].API.AuthenticatedSupport = true + cfg.Exchanges[0].API.AuthenticatedWebsocketSupport = true + cfg.Exchanges[0].API.Credentials.ClientID = DefaultAPIClientID + cfg.Exchanges[0].API.Credentials.Secret = "TESTYTEST" + cfg.CheckExchangeConfigValues() + if cfg.Exchanges[0].API.AuthenticatedSupport || + cfg.Exchanges[0].API.AuthenticatedWebsocketSupport { t.Error("Expected AuthenticatedAPISupport to be false from invalid API keys") } v.RequiresKey = true - checkExchangeConfigValues.Exchanges[0].API.AuthenticatedSupport = true - checkExchangeConfigValues.Exchanges[0].API.AuthenticatedWebsocketSupport = true - checkExchangeConfigValues.Exchanges[0].API.Credentials.Key = "meow" - checkExchangeConfigValues.Exchanges[0].API.Credentials.Secret = "test123" - checkExchangeConfigValues.Exchanges[0].API.Credentials.ClientID = "clientIDerino" - checkExchangeConfigValues.CheckExchangeConfigValues() - if !checkExchangeConfigValues.Exchanges[0].API.AuthenticatedSupport || - !checkExchangeConfigValues.Exchanges[0].API.AuthenticatedWebsocketSupport { + cfg.Exchanges[0].API.AuthenticatedSupport = true + cfg.Exchanges[0].API.AuthenticatedWebsocketSupport = true + cfg.Exchanges[0].API.Credentials.Key = "meow" + cfg.Exchanges[0].API.Credentials.Secret = "test123" + cfg.Exchanges[0].API.Credentials.ClientID = "clientIDerino" + cfg.CheckExchangeConfigValues() + if !cfg.Exchanges[0].API.AuthenticatedSupport || + !cfg.Exchanges[0].API.AuthenticatedWebsocketSupport { t.Error("Expected AuthenticatedAPISupport and AuthenticatedWebsocketAPISupport to be false from invalid API keys") } - checkExchangeConfigValues.Exchanges[0].Enabled = true - checkExchangeConfigValues.Exchanges[0].Name = "" - checkExchangeConfigValues.CheckExchangeConfigValues() - if checkExchangeConfigValues.Exchanges[0].Enabled { + // Test empty exchange name for an enabled exchange + cfg.Exchanges[0].Enabled = true + cfg.Exchanges[0].Name = "" + cfg.CheckExchangeConfigValues() + if cfg.Exchanges[0].Enabled { t.Errorf( - "Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error", + "Test failed. Exchange with no name should be empty", ) } - checkExchangeConfigValues.Exchanges = checkExchangeConfigValues.Exchanges[:0] - cryptos := currency.NewCurrenciesFromStringArray([]string{"TESTYTEST"}) - checkExchangeConfigValues.Cryptocurrencies = &cryptos - err = checkExchangeConfigValues.CheckExchangeConfigValues() + // Test no enabled exchanges + cfg.Exchanges = cfg.Exchanges[:1] + cfg.Exchanges[0].Enabled = false + err = cfg.CheckExchangeConfigValues() if err == nil { - t.Errorf( - "Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error", - ) + t.Error("no enabled exchanges should throw an error") } } From 870fd6f4f3257676acd600f151c4bbdbd98b435d Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 13 Sep 2019 16:53:41 +1000 Subject: [PATCH 37/71] Increase config test coverage --- config/config.go | 5 ++ config/config_test.go | 118 +++++++++++++++++++++++++++++++++++++++--- database/db_types.go | 3 ++ engine/database.go | 11 +++- 4 files changed, 129 insertions(+), 8 deletions(-) diff --git a/config/config.go b/config/config.go index 1eac78b7..9f1cf7e4 100644 --- a/config/config.go +++ b/config/config.go @@ -1324,6 +1324,11 @@ func (c *Config) checkDatabaseConfig() error { m.Lock() defer m.Unlock() + if (c.Database == database.Config{}) { + c.Database.Driver = "sqlite" + c.Database.Database = database.DefaultSQLiteDatabase + } + if !c.Database.Enabled { return nil } diff --git a/config/config_test.go b/config/config_test.go index c6930769..17b09fcc 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,7 +5,9 @@ import ( "testing" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/connchecker" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/database" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" log "github.com/thrasher-corp/gocryptotrader/logger" "github.com/thrasher-corp/gocryptotrader/ntpclient" @@ -1538,6 +1540,24 @@ func TestSaveConfig(t *testing.T) { } } +func TestCheckConnectionMonitorConfig(t *testing.T) { + t.Parallel() + + var c Config + c.ConnectionMonitor.CheckInterval = 0 + c.ConnectionMonitor.DNSList = nil + c.ConnectionMonitor.PublicDomainList = nil + c.CheckConnectionMonitorConfig() + + if c.ConnectionMonitor.CheckInterval != connchecker.DefaultCheckInterval || + len(common.StringSliceDifference( + c.ConnectionMonitor.DNSList, connchecker.DefaultDNSList)) != 0 || + len(common.StringSliceDifference( + c.ConnectionMonitor.PublicDomainList, connchecker.DefaultDomainList)) != 0 { + t.Error("unexpected values") + } +} + func TestGetFilePath(t *testing.T) { expected := "blah.json" result, _ := GetFilePath("blah.json") @@ -1553,6 +1573,44 @@ func TestGetFilePath(t *testing.T) { testBypass = true } +func TestCheckRemoteControlConfig(t *testing.T) { + t.Parallel() + + var c Config + c.Webserver = &WebserverConfig{ + Enabled: true, + AdminUsername: "satoshi", + AdminPassword: "ultrasecurepassword", + ListenAddress: ":9050", + WebsocketConnectionLimit: 5, + WebsocketMaxAuthFailures: 10, + WebsocketAllowInsecureOrigin: true, + } + + c.CheckRemoteControlConfig() + + if c.RemoteControl.Username != "satoshi" || + c.RemoteControl.Password != "ultrasecurepassword" || + !c.RemoteControl.GRPC.Enabled || + c.RemoteControl.GRPC.ListenAddress != "localhost:9052" || + !c.RemoteControl.GRPC.GRPCProxyEnabled || + c.RemoteControl.GRPC.GRPCProxyListenAddress != "localhost:9053" || + !c.RemoteControl.DeprecatedRPC.Enabled || + c.RemoteControl.DeprecatedRPC.ListenAddress != "localhost:9050" || + !c.RemoteControl.WebsocketRPC.Enabled || + c.RemoteControl.WebsocketRPC.ListenAddress != "localhost:9051" || + !c.RemoteControl.WebsocketRPC.AllowInsecureOrigin || + c.RemoteControl.WebsocketRPC.ConnectionLimit != 5 || + c.RemoteControl.WebsocketRPC.MaxAuthFailures != 10 { + t.Error("unexpected results") + } + + // Now test to ensure the previous settings are flushed + if c.Webserver != nil { + t.Error("old webserver settings should be nil") + } +} + func TestCheckConfig(t *testing.T) { var c Config err := c.LoadConfig(ConfigTestFile) @@ -1596,7 +1654,6 @@ func TestUpdateConfig(t *testing.T) { func BenchmarkUpdateConfig(b *testing.B) { var c Config - err := c.LoadConfig(ConfigTestFile) if err != nil { b.Errorf("Unable to benchmark UpdateConfig(): %s", err) @@ -1609,16 +1666,34 @@ func BenchmarkUpdateConfig(b *testing.B) { } func TestCheckLoggerConfig(t *testing.T) { - c := GetConfig() - err := c.LoadConfig(ConfigTestFile) - if err != nil { - t.Fatal(err) - } + t.Parallel() + + var c Config c.Logging = log.Config{} + err := c.CheckLoggerConfig() + if err != nil { + t.Errorf("Failed to create default logger. Error: %s", err) + } + + if !*c.Logging.Enabled { + t.Error("unexpected result") + } + + c.Logging.LoggerFileConfig.FileName = "" + c.Logging.LoggerFileConfig.Rotate = nil + c.Logging.LoggerFileConfig.MaxSize = -1 + err = c.CheckLoggerConfig() if err != nil { - t.Errorf("Failed to create default logger reason: %v", err) + t.Error(err) } + + if c.Logging.LoggerFileConfig.FileName != "log.txt" || + c.Logging.LoggerFileConfig.Rotate == nil || + c.Logging.LoggerFileConfig.MaxSize != 100 { + t.Error("unexpected result") + } + c.LoadConfig(ConfigTestFile) err = c.CheckLoggerConfig() if err != nil { @@ -1627,6 +1702,8 @@ func TestCheckLoggerConfig(t *testing.T) { } func TestDisableNTPCheck(t *testing.T) { + t.Parallel() + c := GetConfig() err := c.LoadConfig(ConfigTestFile) if err != nil { @@ -1657,6 +1734,33 @@ func TestDisableNTPCheck(t *testing.T) { } } +func TestCheckDatabaseConfig(t *testing.T) { + t.Parallel() + + var c Config + if err := c.checkDatabaseConfig(); err != nil { + t.Error(err) + } + + if c.Database.Driver != "sqlite" || + c.Database.Database != database.DefaultSQLiteDatabase || + c.Database.Enabled { + t.Error("unexpected results") + } + + c.Database.Enabled = true + c.Database.Driver = "mssqlisthebest" + if err := c.checkDatabaseConfig(); err == nil { + t.Error("unexpected result") + } + + c.Database.Driver = "sqlite" + c.Database.Enabled = true + if err := c.checkDatabaseConfig(); err != nil { + t.Error(err) + } +} + func TestCheckNTPConfig(t *testing.T) { c := GetConfig() diff --git a/database/db_types.go b/database/db_types.go index 596106fe..235bef0c 100644 --- a/database/db_types.go +++ b/database/db_types.go @@ -35,4 +35,7 @@ var ( // SupportedDrivers slice of supported database driver types SupportedDrivers = []string{"sqlite", "postgres"} + + // DefaultSQLiteDatabase is the default sqlite database name to use + DefaultSQLiteDatabase = "gocryptotrader.db" ) diff --git a/engine/database.go b/engine/database.go index 76644977..b521ed8b 100644 --- a/engine/database.go +++ b/engine/database.go @@ -60,7 +60,16 @@ func (a *databaseManager) Start() (err error) { audit.Audit = auditSQLite.Audit() } dbConn.Connected = true - log.Debugf(log.DatabaseMgr, "Database connection established to host: %v using %v driver\n", dbConn.Config.Host, dbConn.Config.Driver) + + if Bot.Config.Database.Driver == "postgres" { + log.Debugf(log.DatabaseMgr, + "Database connection established to host: %s. Using postgres driver\n", + dbConn.Config.Host) + } else { + log.Debugf(log.DatabaseMgr, + "Database connection established to file database: %s. Using sqlite driver\n", + dbConn.Config.Database) + } mLogger := mg.MLogger{} migrations := mg.Migrator{ From 09d642c3819eb7912d60400f4a930be71e42b786 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 16 Sep 2019 17:15:38 +1000 Subject: [PATCH 38/71] Expand config test coverage --- config/config.go | 80 ++++++++-------------------- config/config_test.go | 86 ++++++++++++++++++++++++++++--- config/config_types.go | 43 ++++++++++++++++ exchanges/request/request.go | 4 +- exchanges/request/request_test.go | 6 +-- 5 files changed, 149 insertions(+), 70 deletions(-) diff --git a/config/config.go b/config/config.go index 9f1cf7e4..84358730 100644 --- a/config/config.go +++ b/config/config.go @@ -171,9 +171,12 @@ func (c *Config) CheckClientBankAccounts() { if len(c.BankAccounts) == 0 { c.BankAccounts = append(c.BankAccounts, BankAccount{ - BankName: "test", - BankAddress: "test", - AccountName: "TestAccount", + BankName: "Test Bank", + BankAddress: "42 Bank Street", + BankPostalCode: "13337", + BankPostalCity: "Satoshiville", + BankCountry: "Japan", + AccountName: "Satoshi Nakamoto", AccountNumber: "0234", SWIFTCode: "91272837", IBAN: "98218738671897", @@ -186,29 +189,10 @@ func (c *Config) CheckClientBankAccounts() { for i := range c.BankAccounts { if c.BankAccounts[i].Enabled { - if c.BankAccounts[i].BankName == "" || c.BankAccounts[i].BankAddress == "" { + err := c.BankAccounts[i].Validate() + if err != nil { c.BankAccounts[i].Enabled = false - log.Warnf(log.ConfigMgr, "banking details for %s is enabled but variables not set correctly\n", - c.BankAccounts[i].BankName) - continue - } - - if c.BankAccounts[i].AccountName == "" || c.BankAccounts[i].AccountNumber == "" { - c.BankAccounts[i].Enabled = false - log.Warnf(log.ConfigMgr, "banking account details for %s variables not set correctly\n", - c.BankAccounts[i].BankName) - continue - } - if c.BankAccounts[i].IBAN == "" && c.BankAccounts[i].SWIFTCode == "" && c.BankAccounts[i].BSBNumber == "" { - c.BankAccounts[i].Enabled = false - log.Warnf(log.ConfigMgr, "critical banking numbers not set for %s in %s account\n", - c.BankAccounts[i].BankName, - c.BankAccounts[i].AccountName) - continue - } - - if c.BankAccounts[i].SupportedExchanges == "" { - c.BankAccounts[i].SupportedExchanges = "ALL" + log.Warn(log.ConfigMgr, err.Error()) } } } @@ -834,6 +818,10 @@ func (c *Config) UpdateExchangeConfig(e *ExchangeConfig) error { // CheckExchangeConfigValues returns configuation values for all enabled // exchanges func (c *Config) CheckExchangeConfigValues() error { + if len(c.Exchanges) == 0 { + return errors.New("no exchange configs found") + } + exchanges := 0 for i := range c.Exchanges { if strings.EqualFold(c.Exchanges[i].Name, "GDAX") { @@ -1050,40 +1038,14 @@ func (c *Config) CheckExchangeConfigValues() error { c.CheckExchangeAssetsConsistency(c.Exchanges[i].Name) - if len(c.Exchanges[i].BankAccounts) > 0 { - for x := range c.Exchanges[i].BankAccounts { - if !c.Exchanges[i].BankAccounts[x].Enabled { - continue - } - bankError := false - if c.Exchanges[i].BankAccounts[x].BankName == "" || c.Exchanges[i].BankAccounts[x].BankAddress == "" { - log.Warnf(log.ExchangeSys, "banking details for %s is enabled but variables not set\n", - c.Exchanges[i].Name) - bankError = true - } - - if c.Exchanges[i].BankAccounts[x].AccountName == "" || c.Exchanges[i].BankAccounts[x].AccountNumber == "" { - log.Warnf(log.ExchangeSys, "banking account details for %s variables not set\n", - c.Exchanges[i].Name) - bankError = true - } - - if c.Exchanges[i].BankAccounts[x].SupportedCurrencies == "" { - log.Warnf(log.ExchangeSys, "banking account details for %s acceptable funding currencies not set\n", - c.Exchanges[i].Name) - bankError = true - } - - if c.Exchanges[i].BankAccounts[x].BSBNumber == "" && c.Exchanges[i].BankAccounts[x].IBAN == "" && - c.Exchanges[i].BankAccounts[x].SWIFTCode == "" { - log.Warnf(log.ExchangeSys, "banking account details for %s critical banking numbers not set\n", - c.Exchanges[i].Name) - bankError = true - } - - if bankError { - c.Exchanges[i].BankAccounts[x].Enabled = false - } + for x := range c.Exchanges[i].BankAccounts { + if !c.Exchanges[i].BankAccounts[x].Enabled { + continue + } + err := c.Exchanges[i].BankAccounts[x].Validate() + if err != nil { + c.Exchanges[i].BankAccounts[x].Enabled = false + log.Warn(log.ConfigMgr, err.Error()) } } exchanges++ diff --git a/config/config_test.go b/config/config_test.go index 17b09fcc..2fcb0977 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -134,16 +134,68 @@ func TestCheckClientBankAccounts(t *testing.T) { cfg.BankAccounts = nil cfg.CheckClientBankAccounts() - if err != nil || len(cfg.BankAccounts) == 0 { + if len(cfg.BankAccounts) == 0 { t.Error("Test failed. CheckClientBankAccounts error:", err) } cfg.BankAccounts = nil - cfg.BankAccounts = append(cfg.BankAccounts, BankAccount{ - Enabled: true, - BankName: "test", - }) - // TO-DO: Complete test coverage + cfg.BankAccounts = []BankAccount{ + { + Enabled: true, + }, + } + + cfg.CheckClientBankAccounts() + if cfg.BankAccounts[0].Enabled { + t.Error("unexpected result") + } + + b := BankAccount{ + Enabled: true, + BankName: "Commonwealth Bank of Awesome", + BankAddress: "123 Fake Street", + BankPostalCode: "1337", + BankPostalCity: "Satoshiville", + BankCountry: "Genesis", + AccountName: "Satoshi Nakamoto", + AccountNumber: "1231006505", + SupportedCurrencies: "USD", + } + cfg.BankAccounts = []BankAccount{b} + cfg.CheckClientBankAccounts() + if cfg.BankAccounts[0].Enabled || + cfg.BankAccounts[0].SupportedExchanges != "ALL" { + t.Error("unexpected result") + } + + // AU based bank, with no BSB number (required for domestic and international + // transfers) + b.SupportedCurrencies = "AUD" + b.SWIFTCode = "BACXSI22" + cfg.BankAccounts = []BankAccount{b} + cfg.CheckClientBankAccounts() + if cfg.BankAccounts[0].Enabled { + t.Error("unexpected result") + } + + // Valid AU bank + b.BSBNumber = "061337" + cfg.BankAccounts = []BankAccount{b} + cfg.CheckClientBankAccounts() + if !cfg.BankAccounts[0].Enabled { + t.Error("unexpected result") + } + + // Valid SWIFT/IBAN compliant bank + b.Enabled = true + b.IBAN = "SI56290000170073837" + b.SWIFTCode = "BACXSI22" + cfg.BankAccounts = []BankAccount{b} + cfg.CheckClientBankAccounts() + if !cfg.BankAccounts[0].Enabled { + t.Error("unexpected result") + } + } func TestPurgeExchangeCredentials(t *testing.T) { @@ -1176,6 +1228,10 @@ func TestUpdateExchangeConfig(t *testing.T) { // TestCheckExchangeConfigValues logic test func TestCheckExchangeConfigValues(t *testing.T) { var cfg Config + if err := cfg.CheckExchangeConfigValues(); err == nil { + t.Error("nil exchanges should throw an err") + } + err := cfg.LoadConfig(ConfigTestFile) if err != nil { t.Fatal(err) @@ -1453,6 +1509,24 @@ func TestCheckExchangeConfigValues(t *testing.T) { t.Error("Expected AuthenticatedAPISupport and AuthenticatedWebsocketAPISupport to be false from invalid API keys") } + // Test exchage bank accounts + b := BankAccount{ + Enabled: true, + BankName: "Commonwealth Bank of Awesome", + BankAddress: "123 Fake Street", + BankPostalCode: "1337", + BankPostalCity: "Satoshiville", + BankCountry: "Genesis", + AccountName: "Satoshi Nakamoto", + AccountNumber: "1231006505", + SupportedCurrencies: "USD", + } + cfg.Exchanges[0].BankAccounts = []BankAccount{b} + cfg.CheckExchangeConfigValues() + if cfg.Exchanges[0].BankAccounts[0].Enabled { + t.Error("unexpected result") + } + // Test empty exchange name for an enabled exchange cfg.Exchanges[0].Enabled = true cfg.Exchanges[0].Name = "" diff --git a/config/config_types.go b/config/config_types.go index 121b8434..33ea91f5 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -1,6 +1,8 @@ package config import ( + "fmt" + "strings" "time" "github.com/thrasher-corp/gocryptotrader/currency" @@ -163,6 +165,9 @@ type BankAccount struct { Enabled bool `json:"enabled"` BankName string `json:"bankName"` BankAddress string `json:"bankAddress"` + BankPostalCode string `json:"bankPostalCode"` + BankPostalCity string `json:"bankPostalCity"` + BankCountry string `json:"bankCountry"` AccountName string `json:"accountName"` AccountNumber string `json:"accountNumber"` SWIFTCode string `json:"swiftCode"` @@ -172,6 +177,44 @@ type BankAccount struct { SupportedExchanges string `json:"supportedExchanges,omitempty"` } +// Validate validates bank account settings +func (b *BankAccount) Validate() error { + if b.BankName == "" || + b.BankAddress == "" || + b.BankPostalCode == "" || + b.BankPostalCity == "" || + b.BankCountry == "" || + b.AccountName == "" || + b.SupportedCurrencies == "" { + return fmt.Errorf( + "banking details for %s is enabled but variables not set correctly", + b.BankName) + } + + if b.SupportedExchanges == "" { + b.SupportedExchanges = "ALL" + } + + if strings.Contains(strings.ToUpper( + b.SupportedCurrencies), + currency.AUD.String()) { + if b.BSBNumber == "" || + b.SWIFTCode == "" { + return fmt.Errorf( + "banking details for %s is enabled but BSB/SWIFT values not set", + b.BankName) + } + } else { + // Either IBAN or SWIFT code is OK + if b.IBAN == "" && b.SWIFTCode == "" { + return fmt.Errorf( + "banking details for %s is enabled but SWIFT/IBAN values not set", + b.BankName) + } + } + return nil +} + // BankTransaction defines a related banking transaction type BankTransaction struct { ReferenceNumber string `json:"referenceNumber"` diff --git a/exchanges/request/request.go b/exchanges/request/request.go index 61290a93..51cc651b 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -90,8 +90,8 @@ func NewRateLimit(d time.Duration, rate int) *RateLimit { return &RateLimit{Duration: d, Rate: rate} } -// ToString returns the rate limiter in string notation -func (r *RateLimit) ToString() string { +// String returns the rate limiter in string notation +func (r *RateLimit) String() string { return fmt.Sprintf("Rate limiter set to %d requests per %v", r.Rate, r.Duration) } diff --git a/exchanges/request/request_test.go b/exchanges/request/request_test.go index aa5c36df..b93eb826 100644 --- a/exchanges/request/request_test.go +++ b/exchanges/request/request_test.go @@ -66,15 +66,15 @@ func TestIsRateLimited(t *testing.T) { r := New("bitfinex", NewRateLimit(time.Second*10, 5), NewRateLimit(time.Second*20, 100), new(http.Client)) r.StartCycle() - if r.AuthLimit.ToString() != "Rate limiter set to 5 requests per 10s" { + if r.AuthLimit.String() != "Rate limiter set to 5 requests per 10s" { t.Fatal("unexcpted values") } - if r.UnauthLimit.ToString() != "Rate limiter set to 100 requests per 20s" { + if r.UnauthLimit.String() != "Rate limiter set to 100 requests per 20s" { t.Fatal("unexpected values") } - if r.AuthLimit.ToString() != "Rate limiter set to 5 requests per 10s" { + if r.AuthLimit.String() != "Rate limiter set to 5 requests per 10s" { t.Fatal("unexcpted values") } From 7c49ada7f64b13dbbe50bccd31c5cd895c76a24d Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 18 Sep 2019 10:22:52 +1000 Subject: [PATCH 39/71] Add bash/zsh autocomplete files and minor fixes --- cmd/gctcli/main.go | 1 + contrib/bash_autocomplete | 23 +++++++++++++++ contrib/zsh_autocomplete | 14 +++++++++ engine/routines.go | 12 ++++---- engine/rpcserver.go | 1 - engine/syncer.go | 61 ++++++++++++++++++++++++--------------- 6 files changed, 81 insertions(+), 31 deletions(-) create mode 100644 contrib/bash_autocomplete create mode 100644 contrib/zsh_autocomplete diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index 8fcf4250..afe0316d 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -56,6 +56,7 @@ func main() { app := cli.NewApp() app.Name = "gctcli" app.Version = core.Version(true) + app.EnableBashCompletion = true app.Usage = "command line interface for managing the gocryptotrader daemon" app.Flags = []cli.Flag{ cli.StringFlag{ diff --git a/contrib/bash_autocomplete b/contrib/bash_autocomplete new file mode 100644 index 00000000..bf09a110 --- /dev/null +++ b/contrib/bash_autocomplete @@ -0,0 +1,23 @@ +#! /bin/bash +# bash programmable completion for gctcli +# copy to /etc/bash_completion.d/gctcli and source it or restart your shell + +: ${PROG:=$(basename ${BASH_SOURCE})} + +_gctcli() { + if [[ "${COMP_WORDS[0]}" != "source" ]]; then + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + if [[ "$cur" == "-"* ]]; then + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) + else + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + fi + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi +} + +complete -o bashdefault -o default -o nospace -F _gctcli $PROG +unset PROG \ No newline at end of file diff --git a/contrib/zsh_autocomplete b/contrib/zsh_autocomplete new file mode 100644 index 00000000..6aaa9487 --- /dev/null +++ b/contrib/zsh_autocomplete @@ -0,0 +1,14 @@ +# zsh programmable completion for gctcli +# source zsh_autocomplete + +_gctcli() { + + local -a opts + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") + + _describe 'values' opts + + return +} + +compdef _gctcli gctcli \ No newline at end of file diff --git a/engine/routines.go b/engine/routines.go index 54f140d4..29466566 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -74,7 +74,7 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I log.Infof(log.Ticker, "%s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f\n", exchangeName, FormatCurrency(p).String(), - assetType, + strings.ToUpper(assetType.String()), printConvertCurrencyFormat(origCurrency, result.Last), printConvertCurrencyFormat(origCurrency, result.Ask), printConvertCurrencyFormat(origCurrency, result.Bid), @@ -87,7 +87,7 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I log.Infof(log.Ticker, "%s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f\n", exchangeName, FormatCurrency(p).String(), - assetType, + strings.ToUpper(assetType.String()), printCurrencyFormat(result.Last), printCurrencyFormat(result.Ask), printCurrencyFormat(result.Bid), @@ -98,7 +98,7 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I log.Infof(log.Ticker, "%s %s %s: TICKER: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f\n", exchangeName, FormatCurrency(p).String(), - assetType, + strings.ToUpper(assetType.String()), result.Last, result.Ask, result.Bid, @@ -128,7 +128,7 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType as log.Infof(log.OrderBook, "%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s\n", exchangeName, FormatCurrency(p).String(), - assetType, + strings.ToUpper(assetType.String()), len(result.Bids), bidsAmount, p.Base.String(), @@ -144,7 +144,7 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType as log.Infof(log.OrderBook, "%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s\n", exchangeName, FormatCurrency(p).String(), - assetType, + strings.ToUpper(assetType.String()), len(result.Bids), bidsAmount, p.Base.String(), @@ -158,7 +158,7 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType as log.Infof(log.OrderBook, "%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %f Asks len: %d Amount: %f %s. Total value: %f\n", exchangeName, FormatCurrency(p).String(), - assetType, + strings.ToUpper(assetType.String()), len(result.Bids), bidsAmount, p.Base.String(), diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 309beae3..79d79975 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -607,7 +607,6 @@ func (s *RPCServer) GetForexRates(ctx context.Context, r *gctrpc.GetForexRatesRe func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) (*gctrpc.GetOrdersResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - log.Debugln(log.GRPCSys, exch) return nil, errors.New("exchange is not loaded/doesn't exist") } diff --git a/engine/syncer.go b/engine/syncer.go index d62c24a9..8490eb0f 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -48,13 +48,11 @@ func NewCurrencyPairSyncer(c CurrencyPairSyncerConfig) (*ExchangeCurrencyPairSyn s.tickerBatchLastRequested = make(map[string]time.Time) - log.Debugln(log.SyncMgr, "Exchange currency pair syncer config:") - log.Debugf(log.SyncMgr, "SyncContinuously: %v\n", s.Cfg.SyncContinuously) - log.Debugf(log.SyncMgr, "SyncTicker: %v\n", s.Cfg.SyncTicker) - log.Debugf(log.SyncMgr, "SyncOrderbook: %v\n", s.Cfg.SyncOrderbook) - log.Debugf(log.SyncMgr, "SyncTrades: %v\n", s.Cfg.SyncTrades) - log.Debugf(log.SyncMgr, "NumWorkers: %v\n", s.Cfg.NumWorkers) - + log.Debugf(log.SyncMgr, + "Exchange currency pair syncer config: continuous: %v ticker: %v"+ + " orderbook: %v trades: %v workers: %v verbose: %v\n", + s.Cfg.SyncContinuously, s.Cfg.SyncTicker, s.Cfg.SyncOrderbook, + s.Cfg.SyncTrades, s.Cfg.NumWorkers, s.Cfg.Verbose) return &s, nil } @@ -92,8 +90,12 @@ func (e *ExchangeCurrencyPairSyncer) add(c *CurrencyPairSyncAgent) { defer e.mux.Unlock() if e.Cfg.SyncTicker { - log.Debugf(log.SyncMgr, "%s: Added ticker sync item %v: using websocket: %v using REST: %v\n", - c.Exchange, FormatCurrency(c.Pair).String(), c.Ticker.IsUsingWebsocket, c.Ticker.IsUsingREST) + if e.Cfg.Verbose { + log.Debugf(log.SyncMgr, + "%s: Added ticker sync item %v: using websocket: %v using REST: %v\n", + c.Exchange, FormatCurrency(c.Pair).String(), c.Ticker.IsUsingWebsocket, + c.Ticker.IsUsingREST) + } if atomic.LoadInt32(&e.initSyncCompleted) != 1 { e.initSyncWG.Add(1) createdCounter++ @@ -101,8 +103,12 @@ func (e *ExchangeCurrencyPairSyncer) add(c *CurrencyPairSyncAgent) { } if e.Cfg.SyncOrderbook { - log.Debugf(log.SyncMgr, "%s: Added orderbook sync item %v: using websocket: %v using REST: %v\n", - c.Exchange, FormatCurrency(c.Pair).String(), c.Orderbook.IsUsingWebsocket, c.Orderbook.IsUsingREST) + if e.Cfg.Verbose { + log.Debugf(log.SyncMgr, + "%s: Added orderbook sync item %v: using websocket: %v using REST: %v\n", + c.Exchange, FormatCurrency(c.Pair).String(), c.Orderbook.IsUsingWebsocket, + c.Orderbook.IsUsingREST) + } if atomic.LoadInt32(&e.initSyncCompleted) != 1 { e.initSyncWG.Add(1) createdCounter++ @@ -110,8 +116,12 @@ func (e *ExchangeCurrencyPairSyncer) add(c *CurrencyPairSyncAgent) { } if e.Cfg.SyncTrades { - log.Debugf(log.SyncMgr, "%s: Added trade sync item %v: using websocket: %v using REST: %v\n", - c.Exchange, FormatCurrency(c.Pair).String(), c.Trade.IsUsingWebsocket, c.Trade.IsUsingREST) + if e.Cfg.Verbose { + log.Debugf(log.SyncMgr, + "%s: Added trade sync item %v: using websocket: %v using REST: %v\n", + c.Exchange, FormatCurrency(c.Pair).String(), c.Trade.IsUsingWebsocket, + c.Trade.IsUsingREST) + } if atomic.LoadInt32(&e.initSyncCompleted) != 1 { e.initSyncWG.Add(1) createdCounter++ @@ -280,7 +290,8 @@ func (e *ExchangeCurrencyPairSyncer) worker() { if Bot.Exchanges[x].SupportsWebsocket() && Bot.Exchanges[x].IsWebsocketEnabled() { ws, err := Bot.Exchanges[x].GetWebsocket() if err != nil { - log.Errorf(log.SyncMgr, "%s unable to get websocket pointer. Err: %s\n", exchangeName, err) + log.Errorf(log.SyncMgr, "%s unable to get websocket pointer. Err: %s\n", + exchangeName, err) usingREST = true } @@ -348,7 +359,8 @@ func (e *ExchangeCurrencyPairSyncer) worker() { e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, true) c.Ticker.IsUsingWebsocket = false c.Ticker.IsUsingREST = true - log.Warnf(log.SyncMgr, "%s %s: No ticker update after 10 seconds, switching from websocket to rest\n", + log.Warnf(log.SyncMgr, + "%s %s: No ticker update after 10 seconds, switching from websocket to rest\n", c.Exchange, FormatCurrency(p).String()) e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, false) } @@ -410,7 +422,8 @@ func (e *ExchangeCurrencyPairSyncer) worker() { e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, true) c.Orderbook.IsUsingWebsocket = false c.Orderbook.IsUsingREST = true - log.Warnf(log.SyncMgr, "%s %s: No orderbook update after 15 seconds, switching from websocket to rest\n", + log.Warnf(log.SyncMgr, + "%s %s: No orderbook update after 15 seconds, switching from websocket to rest\n", c.Exchange, FormatCurrency(c.Pair).String()) e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, false) } @@ -461,21 +474,20 @@ func (e *ExchangeCurrencyPairSyncer) Start() { supportsREST := Bot.Exchanges[x].SupportsREST() if !supportsREST && !supportsWebsocket { - log.Warnf(log.SyncMgr, "Loaded exchange %s does not support REST or Websocket.\n", exchangeName) + log.Warnf(log.SyncMgr, + "Loaded exchange %s does not support REST or Websocket.\n", + exchangeName) continue } var usingWebsocket bool var usingREST bool - if supportsWebsocket { + if supportsWebsocket && Bot.Exchanges[x].IsWebsocketEnabled() { ws, err := Bot.Exchanges[x].GetWebsocket() if err != nil { - log.Errorf(log.SyncMgr, "%s failed to get websocket. Err: %s\n", exchangeName, err) - usingREST = true - } - - if !ws.IsEnabled() { + log.Errorf(log.SyncMgr, "%s failed to get websocket. Err: %s\n", + exchangeName, err) usingREST = true } @@ -484,7 +496,8 @@ func (e *ExchangeCurrencyPairSyncer) Start() { err = ws.Connect() if err != nil { - log.Errorf(log.SyncMgr, "%s websocket failed to connect. Err: %s\n", exchangeName, err) + log.Errorf(log.SyncMgr, "%s websocket failed to connect. Err: %s\n", + exchangeName, err) usingREST = true } else { usingWebsocket = true From 4c5b0a4aa1bfbca750d8f8e561305a5ce31cf70c Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 24 Sep 2019 18:16:42 +1000 Subject: [PATCH 40/71] Coinut code impovements and websocket pair formatting fixes --- .../coinbasepro/coinbasepro_websocket.go | 12 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 26 +- exchanges/coinut/coinut.go | 39 ++- exchanges/coinut/coinut_test.go | 101 +++++- exchanges/coinut/coinut_types.go | 87 ++++- exchanges/coinut/coinut_websocket.go | 99 +++--- exchanges/coinut/coinut_wrapper.go | 307 +++++++++++------- exchanges/okcoin/okcoin_wrapper.go | 32 +- 8 files changed, 491 insertions(+), 212 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index c6471bcb..74c3b9d0 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -285,10 +285,10 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() { continue } for j := range enabledCurrencies { - enabledCurrencies[j].Delimiter = "-" subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: channels[i], - Currency: enabledCurrencies[j], + Channel: channels[i], + Currency: c.FormatExchangeCurrency(enabledCurrencies[j], + asset.Spot), }) } } @@ -303,7 +303,8 @@ func (c *CoinbasePro) Subscribe(channelToSubscribe wshandler.WebsocketChannelSub { Name: channelToSubscribe.Channel, ProductIDs: []string{ - channelToSubscribe.Currency.String(), + c.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String(), }, }, }, @@ -329,7 +330,8 @@ func (c *CoinbasePro) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelS { Name: channelToSubscribe.Channel, ProductIDs: []string{ - channelToSubscribe.Currency.String(), + c.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String(), }, }, }, diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 40064905..3c207972 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -159,14 +159,22 @@ func (c *CoinbasePro) Start(wg *sync.WaitGroup) { // Run implements the coinbasepro wrapper func (c *CoinbasePro) Run() { if c.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket.IsEnabled()), coinbaseproWebsocketURL) + log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", + c.GetName(), + common.IsEnabled(c.Websocket.IsEnabled()), + coinbaseproWebsocketURL) c.PrintEnabledPairs() } forceUpdate := false - if !common.StringDataContains(c.GetEnabledPairs(asset.Spot).Strings(), c.GetPairFormat(asset.Spot, false).Delimiter) || - !common.StringDataContains(c.GetAvailablePairs(asset.Spot).Strings(), c.GetPairFormat(asset.Spot, false).Delimiter) { - enabledPairs := currency.NewPairsFromStrings([]string{fmt.Sprintf("BTC%vUSD", c.GetPairFormat(asset.Spot, false).Delimiter)}) + delim := c.GetPairFormat(asset.Spot, false).Delimiter + if !common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot, + true).Strings(), delim) || + !common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot, + false).Strings(), delim) { + enabledPairs := currency.NewPairsFromStrings( + []string{fmt.Sprintf("BTC%sUSD", delim)}, + ) log.Warn(log.ExchangeSys, "Enabled pairs for CoinbasePro reset due to config upgrade, please enable the ones you would like to use again") forceUpdate = true @@ -294,7 +302,8 @@ func (c *CoinbasePro) FetchOrderbook(p currency.Pair, assetType asset.Item) (ord // UpdateOrderbook updates and returns the orderbook for a currency pair func (c *CoinbasePro) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := c.GetOrderbook(c.FormatExchangeCurrency(p, assetType).String(), 2) + orderbookNew, err := c.GetOrderbook(c.FormatExchangeCurrency(p, + assetType).String(), 2) if err != nil { return orderBook, err } @@ -324,8 +333,7 @@ func (c *CoinbasePro) UpdateOrderbook(p currency.Pair, assetType asset.Item) (or // GetFundingHistory returns funding history, deposits and // withdrawals func (c *CoinbasePro) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. @@ -352,7 +360,7 @@ func (c *CoinbasePro) SubmitOrder(order *exchange.OrderSubmission) (exchange.Sub order.Amount, order.Amount, order.OrderSide.ToString(), - order.Pair.String(), + c.FormatExchangeCurrency(order.Pair, asset.Spot).String(), "") case exchange.LimitOrderType: response, err = c.PlaceLimitOrder("", @@ -361,7 +369,7 @@ func (c *CoinbasePro) SubmitOrder(order *exchange.OrderSubmission) (exchange.Sub order.OrderSide.ToString(), "", "", - order.Pair.String(), + c.FormatExchangeCurrency(order.Pair, asset.Spot).String(), "", false) default: diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index acd52481..faf70829 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -42,11 +42,29 @@ const ( coinutStatusOK = "OK" ) +var ( + errLookupInstrumentID = errors.New("unable to lookup instrument ID") + errLookupInstrumentCurrency = errors.New("unable to lookup instrument") +) + // COINUT is the overarching type across the coinut package type COINUT struct { exchange.Base WebsocketConn *wshandler.WebsocketConnection - InstrumentMap map[string]int + instrumentMap instrumentMap +} + +// SeedInstruments seeds the instrument map +func (c *COINUT) SeedInstruments() error { + i, err := c.GetInstruments() + if err != nil { + return err + } + + for _, y := range i.Instruments { + c.instrumentMap.Seed(y[0].Base+y[0].Quote, y[0].InstID) + } + return nil } // GetInstruments returns instruments @@ -54,21 +72,19 @@ func (c *COINUT) GetInstruments() (Instruments, error) { var result Instruments params := make(map[string]interface{}) params["sec_type"] = strings.ToUpper(asset.Spot.String()) - return result, c.SendHTTPRequest(coinutInstruments, params, false, &result) } // GetInstrumentTicker returns a ticker for a specific instrument -func (c *COINUT) GetInstrumentTicker(instrumentID int) (Ticker, error) { +func (c *COINUT) GetInstrumentTicker(instrumentID int64) (Ticker, error) { var result Ticker params := make(map[string]interface{}) params["inst_id"] = instrumentID - return result, c.SendHTTPRequest(coinutTicker, params, false, &result) } // GetInstrumentOrderbook returns the orderbooks for a specific instrument -func (c *COINUT) GetInstrumentOrderbook(instrumentID, limit int) (Orderbook, error) { +func (c *COINUT) GetInstrumentOrderbook(instrumentID, limit int64) (Orderbook, error) { var result Orderbook params := make(map[string]interface{}) params["inst_id"] = instrumentID @@ -96,7 +112,7 @@ func (c *COINUT) GetUserBalance() (UserBalance, error) { } // NewOrder places a new order on the exchange -func (c *COINUT) NewOrder(instrumentID int, quantity, price float64, buy bool, orderID uint32) (interface{}, error) { +func (c *COINUT) NewOrder(instrumentID int64, quantity, price float64, buy bool, orderID uint32) (interface{}, error) { var result interface{} params := make(map[string]interface{}) params["inst_id"] = instrumentID @@ -133,21 +149,20 @@ func (c *COINUT) NewOrders(orders []Order) ([]OrdersBase, error) { } // GetOpenOrders returns a list of open order and relevant information -func (c *COINUT) GetOpenOrders(instrumentID int) (GetOpenOrdersResponse, error) { +func (c *COINUT) GetOpenOrders(instrumentID int64) (GetOpenOrdersResponse, error) { var result GetOpenOrdersResponse params := make(map[string]interface{}) params["inst_id"] = instrumentID - return result, c.SendHTTPRequest(coinutOrdersOpen, params, true, &result) } // CancelExistingOrder cancels a specific order and returns if it was actioned -func (c *COINUT) CancelExistingOrder(instrumentID, orderID int) (bool, error) { +func (c *COINUT) CancelExistingOrder(instrumentID, orderID int64) (bool, error) { var result GenericResponse params := make(map[string]interface{}) type Request struct { - InstrumentID int `json:"inst_id"` - OrderID int `json:"order_id"` + InstrumentID int64 `json:"inst_id"` + OrderID int64 `json:"order_id"` } var entry = Request{ @@ -182,7 +197,7 @@ func (c *COINUT) CancelOrders(orders []CancelOrders) (CancelOrdersResponse, erro } // GetTradeHistory returns trade history for a specific instrument. -func (c *COINUT) GetTradeHistory(instrumentID, start, limit int) (TradeHistory, error) { +func (c *COINUT) GetTradeHistory(instrumentID, start, limit int64) (TradeHistory, error) { var result TradeHistory params := make(map[string]interface{}) params["inst_id"] = instrumentID diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 7c1b8436..54706e55 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -76,9 +76,6 @@ func setupWSTestAuth(t *testing.T) { if err != nil { t.Error(err) } - - instrumentListByString = make(map[string]int64) - instrumentListByString[currency.NewPair(currency.LTC, currency.BTC).String()] = 1 wsSetupRan = true } @@ -89,6 +86,18 @@ func TestGetInstruments(t *testing.T) { } } +func TestSeedInstruments(t *testing.T) { + err := c.SeedInstruments() + if err != nil { + // No point checking the next condition + t.Fatal(err) + } + + if len(c.instrumentMap.GetInstrumentIDs()) == 0 { + t.Error("instrument map hasn't been seeded") + } +} + func setFeeBuilder() *exchange.FeeBuilder { return &exchange.FeeBuilder{ Amount: 1, @@ -543,3 +552,89 @@ func TestWsAuthGetOpenOrders(t *testing.T) { t.Error(err) } } + +func TestCurrencyMapIsLoaded(t *testing.T) { + t.Parallel() + var i instrumentMap + if l := i.IsLoaded(); l { + t.Error("unexpected result") + } + + i.Seed("BTCUSD", 1337) + if l := i.IsLoaded(); !l { + t.Error("unexpected result") + } +} + +func TestCurrencyMapSeed(t *testing.T) { + t.Parallel() + var i instrumentMap + + // Test non-seeded lookups + if id := i.LookupInstrument(1234); id != "" { + t.Error("unexpected result") + } + if id := i.LookupID("BLAH"); id != 0 { + t.Error("unexpected result") + } + + // Test seeded lookups + i.Seed("BTCUSD", 1337) + if id := i.LookupID("BTCUSD"); id != 1337 { + t.Error("unexpected result") + } + if id := i.LookupInstrument(1337); id != "BTCUSD" { + t.Error("unexpected result") + } + + // Test invalid lookups + if id := i.LookupInstrument(1234); id != "" { + t.Error("unexpected result") + } + if id := i.LookupID("BLAH"); id != 0 { + t.Error("unexpected result") + } + + // Test seeding existing item + i.Seed("BTCUSD", 1234) + if id := i.LookupID("BTCUSD"); id != 1337 { + t.Error("unexpected result") + } + if id := i.LookupInstrument(1337); id != "BTCUSD" { + t.Error("unexpected result") + } +} + +func TestCurrencyMapInstrumentIDs(t *testing.T) { + t.Parallel() + + var i instrumentMap + if r := i.GetInstrumentIDs(); len(r) > 0 { + t.Error("non initialised instrument map shouldn't return any ids") + } + + // Seed the instrument map + i.Seed("BTCUSD", 1234) + i.Seed("LTCUSD", 1337) + + f := func(ids []int64, target int64) bool { + for x := range ids { + if ids[x] == target { + return true + } + } + return false + } + + // Test 2 valid instruments and one invalid + ids := i.GetInstrumentIDs() + if r := f(ids, 1234); !r { + t.Error("unexpected result") + } + if r := f(ids, 1337); !r { + t.Error("unexpected result") + } + if r := f(ids, 4321); r { + t.Error("unexpected result") + } +} diff --git a/exchanges/coinut/coinut_types.go b/exchanges/coinut/coinut_types.go index 900e9fd0..1822bfed 100644 --- a/exchanges/coinut/coinut_types.go +++ b/exchanges/coinut/coinut_types.go @@ -1,6 +1,9 @@ package coinut import ( + "strings" + "sync" + "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" ) @@ -17,7 +20,7 @@ type GenericResponse struct { type InstrumentBase struct { Base string `json:"base"` DecimalPlaces int `json:"decimal_places"` - InstID int `json:"inst_id"` + InstID int64 `json:"inst_id"` Quote string `json:"quote"` } @@ -665,3 +668,85 @@ type WsGetAccountBalanceResponse struct { Status []string `json:"status"` TransID int64 `json:"trans_id"` } + +type instrumentMap struct { + Instruments map[string]int64 + Loaded bool + m sync.Mutex +} + +// IsLoaded returns whether or not the instrument map has been seeded +func (i *instrumentMap) IsLoaded() bool { + i.m.Lock() + defer i.m.Unlock() + return i.Loaded +} + +// Seed seeds the instrument map +func (i *instrumentMap) Seed(currency string, id int64) { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + i.Instruments = make(map[string]int64) + } + + // check to see if the instrument already exists + _, ok := i.Instruments[currency] + if ok { + return + } + + i.Instruments[currency] = id + i.Loaded = true +} + +// LookupInstrument looks up an instrument based on an id +func (i *instrumentMap) LookupInstrument(id int64) string { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + return "" + } + + for k, v := range i.Instruments { + if v == id { + return k + } + } + return "" +} + +// LookupID looks up an ID based on a string +func (i *instrumentMap) LookupID(currency string) int64 { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + return 0 + } + + for k, v := range i.Instruments { + if strings.EqualFold(currency, k) { + return v + } + } + return 0 +} + +// GetInstrumentIDs returns a list of IDs +func (i *instrumentMap) GetInstrumentIDs() []int64 { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + return nil + } + + var instruments []int64 + for _, x := range i.Instruments { + instruments = append(instruments, x) + } + return instruments +} diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index cd50a9e6..bae03986 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -18,14 +18,15 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" ) -const coinutWebsocketURL = "wss://wsapi.coinut.com" -const coinutWebsocketRateLimit = 30 +const ( + coinutWebsocketURL = "wss://wsapi.coinut.com" + coinutWebsocketRateLimit = 30 +) -var nNonce map[int64]string -var channels map[string]chan []byte -var instrumentListByString map[string]int64 -var instrumentListByCode map[int64]string -var populatedList bool +var ( + channels map[string]chan []byte + wsInstrumentMap instrumentMap +) // NOTE for speed considerations // wss://wsapi-as.coinut.com @@ -44,14 +45,11 @@ func (c *COINUT) WsConnect() error { } go c.WsHandleData() - if !populatedList { - instrumentListByString = make(map[string]int64) - instrumentListByCode = make(map[int64]string) + if !wsInstrumentMap.IsLoaded() { err = c.WsSetInstrumentList() if err != nil { return err } - populatedList = true } c.wsAuthenticate() c.GenerateDefaultSubscriptions() @@ -137,7 +135,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } - currencyPair := instrumentListByCode[ticker.InstID] + currencyPair := wsInstrumentMap.LookupInstrument(ticker.InstID) c.Websocket.DataHandler <- wshandler.TickerData{ Exchange: c.Name, Volume: ticker.Volume, @@ -147,7 +145,9 @@ func (c *COINUT) wsProcessResponse(resp []byte) { Last: ticker.Last, Timestamp: time.Unix(0, ticker.Timestamp), AssetType: asset.Spot, - Pair: currency.NewPairFromString(currencyPair), + Pair: currency.NewPairFromFormattedPairs(currencyPair, + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true)), } case "inst_order_book": @@ -162,11 +162,13 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } - currencyPair := instrumentListByCode[orderbooksnapshot.InstID] + currencyPair := wsInstrumentMap.LookupInstrument(orderbooksnapshot.InstID) c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Exchange: c.GetName(), Asset: asset.Spot, - Pair: currency.NewPairFromString(currencyPair), + Pair: currency.NewPairFromFormattedPairs(currencyPair, + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true)), } case "inst_order_book_update": var orderbookUpdate WsOrderbookUpdate @@ -180,11 +182,13 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } - currencyPair := instrumentListByCode[orderbookUpdate.InstID] + currencyPair := wsInstrumentMap.LookupInstrument(orderbookUpdate.InstID) c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Exchange: c.GetName(), Asset: asset.Spot, - Pair: currency.NewPairFromString(currencyPair), + Pair: currency.NewPairFromFormattedPairs(currencyPair, + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true)), } case "inst_trade": var tradeSnap WsTradeSnapshot @@ -201,14 +205,16 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } - currencyPair := instrumentListByCode[tradeUpdate.InstID] + currencyPair := wsInstrumentMap.LookupInstrument(tradeUpdate.InstID) c.Websocket.DataHandler <- wshandler.TradeData{ - Timestamp: time.Unix(tradeUpdate.Timestamp, 0), - CurrencyPair: currency.NewPairFromString(currencyPair), - AssetType: asset.Spot, - Exchange: c.GetName(), - Price: tradeUpdate.Price, - Side: tradeUpdate.Side, + Timestamp: time.Unix(tradeUpdate.Timestamp, 0), + CurrencyPair: currency.NewPairFromFormattedPairs(currencyPair, + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true)), + AssetType: asset.Spot, + Exchange: c.GetName(), + Price: tradeUpdate.Price, + Side: tradeUpdate.Side, } default: if incoming.Nonce > 0 { @@ -247,11 +253,10 @@ func (c *COINUT) WsSetInstrumentList() error { return err } for curr, data := range list.Spot { - instrumentListByString[curr] = data[0].InstID - instrumentListByCode[data[0].InstID] = curr + wsInstrumentMap.Seed(curr, data[0].InstID) } - if len(instrumentListByString) == 0 || len(instrumentListByCode) == 0 { - return errors.New("instrument lists failed to populate") + if len(wsInstrumentMap.GetInstrumentIDs()) == 0 { + return errors.New("instrument list failed to populate") } return nil } @@ -277,7 +282,11 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.Pair = currency.NewPairFromString(instrumentListByCode[ob.InstID]) + newOrderBook.Pair = currency.NewPairFromFormattedPairs( + wsInstrumentMap.LookupInstrument(ob.InstID), + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true), + ) newOrderBook.AssetType = asset.Spot return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false) @@ -285,7 +294,11 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { // WsProcessOrderbookUpdate process an orderbook update func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error { - p := currency.NewPairFromString(instrumentListByCode[update.InstID]) + p := currency.NewPairFromFormattedPairs( + wsInstrumentMap.LookupInstrument(update.InstID), + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true), + ) bufferUpdate := &wsorderbook.WebsocketOrderbookUpdate{ CurrencyPair: p, UpdateID: update.TransID, @@ -318,8 +331,9 @@ func (c *COINUT) GenerateDefaultSubscriptions() { // Subscribe sends a websocket message to receive data from the channel func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { subscribe := wsRequest{ - Request: channelToSubscribe.Channel, - InstID: instrumentListByString[channelToSubscribe.Currency.String()], + Request: channelToSubscribe.Channel, + InstID: wsInstrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String()), Subscribe: true, Nonce: c.WebsocketConn.GenerateMessageID(false), } @@ -329,8 +343,9 @@ func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscrip // Unsubscribe sends a websocket message to stop receiving data from the channel func (c *COINUT) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { subscribe := wsRequest{ - Request: channelToSubscribe.Channel, - InstID: instrumentListByString[channelToSubscribe.Currency.String()], + Request: channelToSubscribe.Channel, + InstID: wsInstrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String()), Subscribe: false, Nonce: c.WebsocketConn.GenerateMessageID(false), } @@ -419,7 +434,7 @@ func (c *COINUT) wsSubmitOrder(order *WsSubmitOrderParameters) (*WsStandardOrder var orderSubmissionRequest WsSubmitOrderRequest orderSubmissionRequest.Request = "new_order" orderSubmissionRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) - orderSubmissionRequest.InstID = instrumentListByString[curr] + orderSubmissionRequest.InstID = wsInstrumentMap.LookupID(curr) orderSubmissionRequest.Qty = order.Amount orderSubmissionRequest.Price = order.Price orderSubmissionRequest.Side = string(order.Side) @@ -530,7 +545,7 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO Qty: orders[i].Amount, Price: orders[i].Price, Side: string(orders[i].Side), - InstID: instrumentListByString[curr], + InstID: wsInstrumentMap.LookupID(curr), ClientOrdID: i + 1, }) } @@ -567,7 +582,7 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO if len(standardOrder.Reasons) > 0 && standardOrder.Reasons[0] != "" { errors = append(errors, fmt.Errorf("%v order submission failed for currency %v and orderID %v, message %v ", c.Name, - instrumentListByCode[standardOrder.InstID], + wsInstrumentMap.LookupInstrument(standardOrder.InstID), standardOrder.OrderID, standardOrder.Reasons[0])) @@ -587,7 +602,7 @@ func (c *COINUT) wsGetOpenOrders(p currency.Pair) error { var openOrdersRequest WsGetOpenOrdersRequest openOrdersRequest.Request = "user_open_orders" openOrdersRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) - openOrdersRequest.InstID = instrumentListByString[curr] + openOrdersRequest.InstID = wsInstrumentMap.LookupID(curr) resp, err := c.WebsocketConn.SendMessageReturnResponse(openOrdersRequest.Nonce, openOrdersRequest) if err != nil { @@ -613,7 +628,7 @@ func (c *COINUT) wsCancelOrder(cancellation WsCancelOrderParameters) error { currency := c.FormatExchangeCurrency(cancellation.Currency, asset.Spot).String() var cancellationRequest WsCancelOrderRequest cancellationRequest.Request = "cancel_order" - cancellationRequest.InstID = instrumentListByString[currency] + cancellationRequest.InstID = wsInstrumentMap.LookupID(currency) cancellationRequest.OrderID = cancellation.OrderID cancellationRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) @@ -645,7 +660,7 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*WsCan for i := range cancellations { currency := c.FormatExchangeCurrency(cancellations[i].Currency, asset.Spot).String() cancelOrderRequest.Entries = append(cancelOrderRequest.Entries, WsCancelOrdersRequestEntry{ - InstID: instrumentListByString[currency], + InstID: wsInstrumentMap.LookupID(currency), OrderID: cancellations[i].OrderID, }) } @@ -668,7 +683,7 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*WsCan if response.Results[i].Status != "OK" { errors = append(errors, fmt.Errorf("%v order cancellation failed for currency %v and orderID %v, message %v", c.Name, - instrumentListByCode[response.Results[i].InstID], + wsInstrumentMap.LookupInstrument(response.Results[i].InstID), response.Results[i].OrderID, response.Results[i].Status)) } @@ -683,7 +698,7 @@ func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) error { curr := c.FormatExchangeCurrency(p, asset.Spot).String() var request WsTradeHistoryRequest request.Request = "trade_history" - request.InstID = instrumentListByString[curr] + request.InstID = wsInstrumentMap.LookupID(curr) request.Nonce = c.WebsocketConn.GenerateMessageID(false) request.Start = start request.Limit = limit diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index cb59e59a..78e60142 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -1,6 +1,7 @@ package coinut import ( + "errors" "fmt" "strconv" "strings" @@ -60,6 +61,7 @@ func (c *COINUT) SetDefaults() { }, ConfigFormat: ¤cy.PairFormat{ Uppercase: true, + Delimiter: "-", }, } @@ -162,11 +164,30 @@ func (c *COINUT) Run() { c.PrintEnabledPairs() } - if !c.GetEnabledFeatures().AutoPairUpdates { + forceUpdate := false + delim := c.GetPairFormat(asset.Spot, false).Delimiter + if !common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot, + true).Strings(), delim) || + !common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot, + false).Strings(), delim) { + enabledPairs := currency.NewPairsFromStrings( + []string{fmt.Sprintf("LTC%sUSDT", delim)}, + ) + log.Warn(log.ExchangeSys, + "Enabled pairs for Coinut reset due to config upgrade, please enable the ones you would like to use again") + forceUpdate = true + + err := c.UpdatePairs(enabledPairs, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", c.Name, err) + } + } + + if !c.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { return } - err := c.UpdateTradablePairs(false) + err := c.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", c.Name, err) } @@ -180,10 +201,10 @@ func (c *COINUT) FetchTradablePairs(asset asset.Item) ([]string, error) { } var pairs []string - c.InstrumentMap = make(map[string]int) - for x, y := range i.Instruments { - c.InstrumentMap[x] = y[0].InstID - pairs = append(pairs, x) + for _, y := range i.Instruments { + c.instrumentMap.Seed(y[0].Base+y[0].Quote, y[0].InstID) + p := y[0].Base + c.GetPairFormat(asset, false).Delimiter + y[0].Quote + pairs = append(pairs, p) } return pairs, nil @@ -197,7 +218,8 @@ func (c *COINUT) UpdateTradablePairs(forceUpdate bool) error { return err } - return c.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + return c.UpdatePairs(currency.NewPairsFromStrings(pairs), + asset.Spot, false, forceUpdate) } // GetAccountInfo retrieves balances for all enabled currencies for the @@ -278,7 +300,21 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { // UpdateTicker updates and returns the ticker for a currency pair func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - tick, err := c.GetInstrumentTicker(c.InstrumentMap[c.FormatExchangeCurrency(p, assetType).String()]) + + if !c.instrumentMap.IsLoaded() { + err := c.SeedInstruments() + if err != nil { + return tickerPrice, err + } + } + + instID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(p, + assetType).String()) + if instID == 0 { + return tickerPrice, errors.New("unable to lookup instrument ID") + } + + tick, err := c.GetInstrumentTicker(instID) if err != nil { return tickerPrice, err } @@ -320,7 +356,21 @@ func (c *COINUT) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderboo // UpdateOrderbook updates and returns the orderbook for a currency pair func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := c.GetInstrumentOrderbook(c.InstrumentMap[p.String()], 200) + + if !c.instrumentMap.IsLoaded() { + err := c.SeedInstruments() + if err != nil { + return orderBook, err + } + } + + instID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(p, + assetType).String()) + if instID == 0 { + return orderBook, errLookupInstrumentID + } + + orderbookNew, err := c.GetInstrumentOrderbook(instID, 200) if err != nil { return orderBook, err } @@ -378,14 +428,18 @@ func (c *COINUT) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOr clientIDUint := uint32(clientIDInt) - // Need to get the ID of the currency sent - instruments, err := c.GetInstruments() - if err != nil { - return submitOrderResponse, err + if !c.instrumentMap.IsLoaded() { + err = c.SeedInstruments() + if err != nil { + return submitOrderResponse, err + } } - currencyArray := instruments.Instruments[order.Pair.String()] - currencyID := currencyArray[0].InstID + currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(order.Pair, + asset.Spot).String()) + if currencyID == 0 { + return submitOrderResponse, errLookupInstrumentID + } switch order.OrderType { case exchange.LimitOrderType: @@ -425,23 +479,25 @@ func (c *COINUT) ModifyOrder(action *exchange.ModifyOrder) (string, error) { // CancelOrder cancels an order by its corresponding ID number func (c *COINUT) CancelOrder(order *exchange.OrderCancellation) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) - if err != nil { return err } - // Need to get the ID of the currency sent - instruments, err := c.GetInstruments() - - if err != nil { - return err + if !c.instrumentMap.IsLoaded() { + err = c.SeedInstruments() + if err != nil { + return err + } } - currencyArray := instruments.Instruments[c.FormatExchangeCurrency(order.CurrencyPair, - order.AssetType).String()] - currencyID := currencyArray[0].InstID - _, err = c.CancelExistingOrder(currencyID, int(orderIDInt)) - + currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency( + order.CurrencyPair, + asset.Spot).String(), + ) + if currencyID == 0 { + return errLookupInstrumentID + } + _, err = c.CancelExistingOrder(currencyID, orderIDInt) return err } @@ -454,22 +510,22 @@ func (c *COINUT) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ OrderStatus: make(map[string]string), } - instruments, err := c.GetInstruments() - if err != nil { - return cancelAllOrdersResponse, err + + if !c.instrumentMap.IsLoaded() { + err := c.SeedInstruments() + if err != nil { + return cancelAllOrdersResponse, err + } } var allTheOrders []OrderResponse - for _, allInstrumentData := range instruments.Instruments { - for _, instrumentData := range allInstrumentData { - - openOrders, err := c.GetOpenOrders(instrumentData.InstID) - if err != nil { - return cancelAllOrdersResponse, err - } - - allTheOrders = append(allTheOrders, openOrders.Orders...) + ids := c.instrumentMap.GetInstrumentIDs() + for x := range ids { + openOrders, err := c.GetOpenOrders(ids[x]) + if err != nil { + return cancelAllOrdersResponse, err } + allTheOrders = append(allTheOrders, openOrders.Orders...) } var allTheOrdersToCancel []CancelOrders @@ -499,8 +555,7 @@ func (c *COINUT) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel // GetOrderInfo returns information on a current open order func (c *COINUT) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail - return orderDetail, common.ErrNotYetImplemented + return exchange.OrderDetail{}, common.ErrNotYetImplemented } // GetDepositAddress returns a deposit address for a specified currency @@ -542,51 +597,51 @@ func (c *COINUT) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) // GetActiveOrders retrieves any orders that are active/open func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - instruments, err := c.GetInstruments() - if err != nil { - return nil, err - } - - var allTheOrders []OrderResponse - for instrument, allInstrumentData := range instruments.Instruments { - for _, instrumentData := range allInstrumentData { - for _, currency := range getOrdersRequest.Currencies { - currStr := fmt.Sprintf("%v%v%v", - currency.Base.String(), - c.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter, - currency.Quote.String()) - if strings.EqualFold(currStr, instrument) { - openOrders, err := c.GetOpenOrders(instrumentData.InstID) - if err != nil { - return nil, err - } - allTheOrders = append(allTheOrders, openOrders.Orders...) - - continue - } - } + if !c.instrumentMap.IsLoaded() { + err := c.SeedInstruments() + if err != nil { + return nil, err } } + var instrumentsToUse []int64 + if len(getOrdersRequest.Currencies) > 0 { + for x := range getOrdersRequest.Currencies { + currency := c.FormatExchangeCurrency(getOrdersRequest.Currencies[x], + asset.Spot).String() + instrumentsToUse = append(instrumentsToUse, + c.instrumentMap.LookupID(currency)) + } + } else { + instrumentsToUse = c.instrumentMap.GetInstrumentIDs() + } + + if len(instrumentsToUse) == 0 { + return nil, errors.New("no instrument IDs to use") + } + var orders []exchange.OrderDetail - for _, order := range allTheOrders { - for instrument, allInstrumentData := range instruments.Instruments { - for _, instrumentData := range allInstrumentData { - if instrumentData.InstID == int(order.InstrumentID) { - currPair := currency.NewPairDelimiter(instrument, "") - orderSide := exchange.OrderSide(strings.ToUpper(order.Side)) - orderDate := time.Unix(order.Timestamp, 0) - orders = append(orders, exchange.OrderDetail{ - ID: strconv.FormatInt(order.OrderID, 10), - Amount: order.Quantity, - Price: order.Price, - Exchange: c.Name, - OrderSide: orderSide, - OrderDate: orderDate, - CurrencyPair: currPair, - }) - } - } + for x := range instrumentsToUse { + openOrders, err := c.GetOpenOrders(instrumentsToUse[x]) + if err != nil { + return nil, err + } + for y := range openOrders.Orders { + curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) + p := currency.NewPairFromFormattedPairs(curr, + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true)) + orderSide := exchange.OrderSide(strings.ToUpper(openOrders.Orders[y].Side)) + orderDate := time.Unix(openOrders.Orders[y].Timestamp, 0) + orders = append(orders, exchange.OrderDetail{ + ID: strconv.FormatInt(openOrders.Orders[y].OrderID, 10), + Amount: openOrders.Orders[y].Quantity, + Price: openOrders.Orders[y].Price, + Exchange: c.Name, + OrderSide: orderSide, + OrderDate: orderDate, + CurrencyPair: p, + }) } } @@ -598,56 +653,60 @@ func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ // GetOrderHistory retrieves account order information // Can Limit response to specific order status func (c *COINUT) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - instruments, err := c.GetInstruments() - if err != nil { - return nil, err - } - - var allTheOrders []OrderFilledResponse - for instrument, allInstrumentData := range instruments.Instruments { - for _, instrumentData := range allInstrumentData { - for _, currency := range getOrdersRequest.Currencies { - currStr := fmt.Sprintf("%v%v", - currency.Base.String(), currency.Quote.String()) - if strings.EqualFold(currStr, instrument) { - orders, err := c.GetTradeHistory(instrumentData.InstID, -1, -1) - if err != nil { - return nil, err - } - allTheOrders = append(allTheOrders, orders.Trades...) - - continue - } - } + if !c.instrumentMap.IsLoaded() { + err := c.SeedInstruments() + if err != nil { + return nil, err } } - var orders []exchange.OrderDetail - for i := range allTheOrders { - for instrument, allInstrumentData := range instruments.Instruments { - for j := range allInstrumentData { - if allInstrumentData[j].InstID == int(allTheOrders[i].Order.InstrumentID) { - currPair := currency.NewPairDelimiter(instrument, "") - orderSide := exchange.OrderSide(strings.ToUpper(allTheOrders[i].Order.Side)) - orderDate := time.Unix(allTheOrders[i].Order.Timestamp, 0) - orders = append(orders, exchange.OrderDetail{ - ID: strconv.FormatInt(allTheOrders[i].Order.OrderID, 10), - Amount: allTheOrders[i].Order.Quantity, - Price: allTheOrders[i].Order.Price, - Exchange: c.Name, - OrderSide: orderSide, - OrderDate: orderDate, - CurrencyPair: currPair, - }) - } - } + var instrumentsToUse []int64 + if len(getOrdersRequest.Currencies) > 0 { + for x := range getOrdersRequest.Currencies { + currency := c.FormatExchangeCurrency(getOrdersRequest.Currencies[x], + asset.Spot).String() + instrumentsToUse = append(instrumentsToUse, + c.instrumentMap.LookupID(currency)) } + } else { + instrumentsToUse = c.instrumentMap.GetInstrumentIDs() } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, + if len(instrumentsToUse) == 0 { + return nil, errors.New("no instrument IDs to use") + } + + var allOrders []exchange.OrderDetail + for x := range instrumentsToUse { + orders, err := c.GetTradeHistory(instrumentsToUse[x], -1, -1) + if err != nil { + return nil, err + } + for y := range orders.Trades { + curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) + p := currency.NewPairFromFormattedPairs(curr, + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true)) + orderSide := exchange.OrderSide( + strings.ToUpper(orders.Trades[y].Order.Side)) + orderDate := time.Unix(orders.Trades[y].Order.Timestamp, 0) + allOrders = append(allOrders, exchange.OrderDetail{ + ID: strconv.FormatInt(orders.Trades[y].Order.OrderID, 10), + Amount: orders.Trades[y].Order.Quantity, + Price: orders.Trades[y].Order.Price, + Exchange: c.Name, + OrderSide: orderSide, + OrderDate: orderDate, + CurrencyPair: p, + }) + } + + } + + exchange.FilterOrdersByTickRange(&allOrders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - return orders, nil + exchange.FilterOrdersBySide(&allOrders, getOrdersRequest.OrderSide) + return allOrders, nil } // SubscribeToWebsocketChannels appends to ChannelsToSubscribe diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 10579e76..ef65153f 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -124,20 +124,19 @@ func (o *OKCoin) Run() { log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) } - if o.Config.CurrencyPairs.ConfigFormat.Delimiter != o.CurrencyPairs.ConfigFormat.Delimiter { - o.Config.CurrencyPairs.ConfigFormat.Delimiter = o.CurrencyPairs.ConfigFormat.Delimiter - } - if o.Config.CurrencyPairs.RequestFormat.Uppercase != o.CurrencyPairs.RequestFormat.Uppercase { - o.Config.CurrencyPairs.RequestFormat.Uppercase = true - } - if o.Config.CurrencyPairs.RequestFormat.Delimiter != o.CurrencyPairs.RequestFormat.Delimiter { - o.Config.CurrencyPairs.RequestFormat.Delimiter = o.CurrencyPairs.RequestFormat.Delimiter - } - - if !common.StringDataContains(o.Config.CurrencyPairs.Pairs[asset.Spot].Enabled.Strings(), o.CurrencyPairs.RequestFormat.Delimiter) { - enabledPairs := currency.NewPairsFromStrings([]string{"BTC-USD"}) + forceUpdate := false + delim := o.GetPairFormat(asset.Spot, false).Delimiter + if !common.StringDataContains(o.CurrencyPairs.GetPairs(asset.Spot, + true).Strings(), delim) || + !common.StringDataContains(o.CurrencyPairs.GetPairs(asset.Spot, + false).Strings(), delim) { + enabledPairs := currency.NewPairsFromStrings([]string{ + fmt.Sprintf("BTC%sUSD", delim), + }) log.Warnf(log.ExchangeSys, - "Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.", o.Name) + "Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.\n", + o.Name) + forceUpdate = true err := o.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { @@ -146,11 +145,11 @@ func (o *OKCoin) Run() { } } - if !o.GetEnabledFeatures().AutoPairUpdates { + if !o.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { return } - err := o.UpdateTradablePairs(false) + err := o.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", o.Name, err) } @@ -165,7 +164,8 @@ func (o *OKCoin) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range prods { - pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].BaseCurrency, o.GetPairFormat(asset, false).Delimiter, prods[x].QuoteCurrency)) + pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].BaseCurrency, + o.GetPairFormat(asset, false).Delimiter, prods[x].QuoteCurrency)) } return pairs, nil From 0493a215a63f1dd65228b1e5163dbf691c30da4a Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 24 Sep 2019 18:28:07 +1000 Subject: [PATCH 41/71] Exchange wrapper test tool (#353) * Initial commit which runs through all wrapper funcs for one of each currency pair for each asset type for each exchange and then outputs it all into a JSON file * Fixes up data holding responses to allow for good json output after completion * Starting work for reading credentials from a config. Some variable names are more appropriate * Keys can now be read from keys.json and applied to exchanges. Fixes naming bug for console output * Cleans up implementation. Uses templating to output some pretty html report * Changes config to include a bank account to customise for withdrawals. Updates template for warnings for not implemented. Updates template to include jsonified parameters that are sent to the wrapper. Jsonifies the response so you actually understand whats returned. * Adds bank for withdrawals. Adds wallet address for crypto withdrawals * Adds ordersubmission configuration to config. Sets command line overrides. Adds lbank to config. Adds okgroup clientid to config. * Adds missing comma * Adds config to gitignore * Removes because thats not how it works... * Revert go mod changes * Formatting for prettiness * Adds asset type override * Adds verbose, exchangesToExclude, filename CL flags. Removes wg redeclaration. Creates func to check to load exchange before its loaded. Removes double variables and uses flag.Stringvar instead * Prettifies the JSON output * Stringifies the []byteified jsonified wrapper response in the template * Puts types in their own EXCLUSIVE types.go. LOWERCASES COMMAND LINE ARGUMENTS. Wraps vars in var so theres less vars to look at. * Generates wrapperconfig.json on the fly if not present. Adds missing config fields and exchanges when not present. Saves config file on finish. Fixes some formatting on output when its just a number * micro format update * Changes printfs to printlns. Handles config error. Comments on types. Fixes lint issue with shadow err declaration * Fixes linting issue, removes returns after fatals, evaluates assetType, formats template * Stringifies byte output when console output is selected. Verbose mode now includes wrapper responses in console output --- cmd/exchange_wrapper_issues/.gitignore | 3 + cmd/exchange_wrapper_issues/main.go | 839 ++++++++++++++++++ cmd/exchange_wrapper_issues/report.tmpl | 155 ++++ cmd/exchange_wrapper_issues/types.go | 107 +++ .../wrapperconfig.json | 178 ++++ 5 files changed, 1282 insertions(+) create mode 100644 cmd/exchange_wrapper_issues/.gitignore create mode 100644 cmd/exchange_wrapper_issues/main.go create mode 100644 cmd/exchange_wrapper_issues/report.tmpl create mode 100644 cmd/exchange_wrapper_issues/types.go create mode 100644 cmd/exchange_wrapper_issues/wrapperconfig.json diff --git a/cmd/exchange_wrapper_issues/.gitignore b/cmd/exchange_wrapper_issues/.gitignore new file mode 100644 index 00000000..ff5a41ed --- /dev/null +++ b/cmd/exchange_wrapper_issues/.gitignore @@ -0,0 +1,3 @@ +/data.json +/output.json +/report.html \ No newline at end of file diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go new file mode 100644 index 00000000..ee8af437 --- /dev/null +++ b/cmd/exchange_wrapper_issues/main.go @@ -0,0 +1,839 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "text/template" + + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/engine" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" +) + +func main() { + log.Println("Loading flags...") + parseCLFlags() + var err error + log.Println("Loading engine...") + engine.Bot, err = engine.New() + if err != nil { + log.Fatalf("Failed to initialise engine. Err: %s", err) + } + + engine.Bot.Settings = engine.Settings{ + DisableExchangeAutoPairUpdates: true, + Verbose: verboseOverride, + } + + log.Println("Loading config...") + wrapperConfig, err := loadConfig() + if err != nil { + log.Printf("Error loading config: '%v', generating empty config", err) + wrapperConfig = Config{ + Exchanges: make(map[string]*config.APICredentialsConfig), + } + } + + log.Println("Loading exchanges..") + + var wg sync.WaitGroup + for x := range exchange.Exchanges { + name := exchange.Exchanges[x] + if _, ok := wrapperConfig.Exchanges[name]; !ok { + wrapperConfig.Exchanges[strings.ToLower(name)] = &config.APICredentialsConfig{} + } + if shouldLoadExchange(name) { + err = engine.LoadExchange(name, true, &wg) + if err != nil { + log.Printf("Failed to load exchange %s. Err: %s", name, err) + continue + } + } + } + wg.Wait() + log.Println("Done.") + + if withdrawAddressOverride != "" { + wrapperConfig.WalletAddress = withdrawAddressOverride + } + if orderTypeOverride != "LIMIT" { + wrapperConfig.OrderSubmission.OrderType = orderTypeOverride + } + if orderSideOverride != "BUY" { + wrapperConfig.OrderSubmission.OrderSide = orderSideOverride + } + if orderPriceOverride > 0 { + wrapperConfig.OrderSubmission.Price = orderPriceOverride + } + if orderAmountOverride > 0 { + wrapperConfig.OrderSubmission.Amount = orderAmountOverride + } + + log.Println("Testing exchange wrappers..") + var exchangeResponses []ExchangeResponses + + for x := range engine.Bot.Exchanges { + base := engine.Bot.Exchanges[x].GetBase() + if !base.Config.Enabled { + log.Printf("Exchange %v not enabled, skipping", base.GetName()) + continue + } + base.Config.Verbose = verboseOverride + base.Verbose = verboseOverride + base.HTTPDebugging = false + base.Config.HTTPDebugging = false + wg.Add(1) + + go func(num int) { + name := engine.Bot.Exchanges[num].GetName() + authenticated := setExchangeAPIKeys(name, wrapperConfig.Exchanges, base) + wrapperResult := ExchangeResponses{ + ID: fmt.Sprintf("Exchange%v", num), + ExchangeName: name, + APIKeysSet: authenticated, + AssetPairResponses: testWrappers(engine.Bot.Exchanges[num], base, &wrapperConfig), + } + for i := range wrapperResult.AssetPairResponses { + wrapperResult.ErrorCount += wrapperResult.AssetPairResponses[i].ErrorCount + } + exchangeResponses = append(exchangeResponses, wrapperResult) + wg.Done() + }(x) + } + wg.Wait() + + log.Println("Done.") + log.Println() + + sort.Slice(exchangeResponses, func(i, j int) bool { + return exchangeResponses[i].ExchangeName < exchangeResponses[j].ExchangeName + }) + + if strings.EqualFold(outputOverride, "Console") { + outputToConsole(exchangeResponses) + } + if strings.EqualFold(outputOverride, "JSON") { + outputToJSON(exchangeResponses) + } + if strings.EqualFold(outputOverride, "HTML") { + outputToHTML(exchangeResponses) + } + + saveConfig(&wrapperConfig) +} + +func parseCLFlags() { + flag.StringVar(&exchangesToUseOverride, "exchanges", "", "a + delimited list of exchange names to run tests against eg -exchanges=bitfinex+anx") + flag.StringVar(&exchangesToExcludeOverride, "excluded-exchanges", "", "a + delimited list of exchange names to ignore when they're being temperamental eg -exchangesToExlude=lbank") + flag.StringVar(&assetTypeOverride, "asset", "", "the asset type to run tests against (where applicable)") + flag.StringVar(¤cyPairOverride, "currency", "", "the currency to run tests against (where applicable)") + flag.StringVar(&outputOverride, "output", "HTML", "JSON, HTML or Console") + flag.BoolVar(&authenticatedOnly, "auth-only", false, "skip any wrapper function that doesn't require auth") + flag.BoolVar(&verboseOverride, "verbose", false, "verbose CL output - if console output is selected then wrapper response is included") + flag.StringVar(&orderSideOverride, "orderside", "BUY", "the order type for all order based wrapper tests") + flag.StringVar(&orderTypeOverride, "ordertype", "LIMIT", "the order type for all order based wrapper tests") + flag.Float64Var(&orderAmountOverride, "orderamount", 0, "the order amount for all order based wrapper tests") + flag.Float64Var(&orderPriceOverride, "orderprice", 0, "the order price for all order based wrapper tests") + flag.StringVar(&withdrawAddressOverride, "withdraw-wallet", "", "withdraw wallet address") + flag.StringVar(&outputFileName, "filename", "report", "name of the output file eg 'report'.html or 'report'.json") + flag.Parse() + + if exchangesToUseOverride != "" { + exchangesToUseList = strings.Split(exchangesToUseOverride, "+") + } + if exchangesToExcludeOverride != "" { + exchangesToExcludeList = strings.Split(exchangesToExcludeOverride, "+") + } +} + +func shouldLoadExchange(name string) bool { + shouldLoadExchange := true + if len(exchangesToUseList) > 0 { + var found bool + for i := range exchangesToUseList { + if strings.EqualFold(name, exchangesToUseList[i]) { + found = true + } + } + if !found { + shouldLoadExchange = false + } + } + + if len(exchangesToExcludeList) > 0 { + for i := range exchangesToExcludeList { + if strings.EqualFold(name, exchangesToExcludeList[i]) { + if shouldLoadExchange { + shouldLoadExchange = false + } + } + } + } + return shouldLoadExchange +} + +func setExchangeAPIKeys(name string, keys map[string]*config.APICredentialsConfig, base *exchange.Base) bool { + lowerExchangeName := strings.ToLower(name) + + if base.API.CredentialsValidator.RequiresKey && keys[lowerExchangeName].Key == "" { + keys[lowerExchangeName].Key = config.DefaultAPIKey + } + if base.API.CredentialsValidator.RequiresSecret && keys[lowerExchangeName].Secret == "" { + keys[lowerExchangeName].Secret = config.DefaultAPISecret + } + if base.API.CredentialsValidator.RequiresPEM && keys[lowerExchangeName].PEMKey == "" { + keys[lowerExchangeName].PEMKey = "PEM" + } + if base.API.CredentialsValidator.RequiresClientID && keys[lowerExchangeName].ClientID == "" { + keys[lowerExchangeName].ClientID = config.DefaultAPIClientID + } + if keys[lowerExchangeName].OTPSecret == "" { + keys[lowerExchangeName].OTPSecret = "-" // Ensure OTP is available for use + } + + base.API.Credentials.Key = keys[lowerExchangeName].Key + base.Config.API.Credentials.Key = keys[lowerExchangeName].Key + + base.API.Credentials.Secret = keys[lowerExchangeName].Secret + base.Config.API.Credentials.Secret = keys[lowerExchangeName].Secret + + base.API.Credentials.ClientID = keys[lowerExchangeName].ClientID + base.Config.API.Credentials.ClientID = keys[lowerExchangeName].ClientID + + if keys[lowerExchangeName].OTPSecret != "-" { + base.Config.API.Credentials.OTPSecret = keys[lowerExchangeName].OTPSecret + } + + base.API.AuthenticatedSupport = true + base.API.AuthenticatedWebsocketSupport = true + base.Config.API.AuthenticatedSupport = true + base.Config.API.AuthenticatedWebsocketSupport = true + + return base.ValidateAPICredentials() +} + +func parseOrderSide(orderSide string) exchange.OrderSide { + switch orderSide { + case exchange.AnyOrderSide.ToString(): + return exchange.AnyOrderSide + case exchange.BuyOrderSide.ToString(): + return exchange.BuyOrderSide + case exchange.SellOrderSide.ToString(): + return exchange.SellOrderSide + case exchange.BidOrderSide.ToString(): + return exchange.BidOrderSide + case exchange.AskOrderSide.ToString(): + return exchange.AskOrderSide + default: + log.Printf("Orderside '%v' not recognised, defaulting to BUY", orderSide) + return exchange.BuyOrderSide + } +} + +func parseOrderType(orderType string) exchange.OrderType { + switch orderType { + case exchange.AnyOrderType.ToString(): + return exchange.AnyOrderType + case exchange.LimitOrderType.ToString(): + return exchange.LimitOrderType + case exchange.MarketOrderType.ToString(): + return exchange.MarketOrderType + case exchange.ImmediateOrCancelOrderType.ToString(): + return exchange.ImmediateOrCancelOrderType + case exchange.StopOrderType.ToString(): + return exchange.StopOrderType + case exchange.TrailingStopOrderType.ToString(): + return exchange.TrailingStopOrderType + case exchange.UnknownOrderType.ToString(): + return exchange.UnknownOrderType + default: + log.Printf("OrderType '%v' not recognised, defaulting to LIMIT", orderTypeOverride) + return exchange.LimitOrderType + } +} + +func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) []ExchangeAssetPairResponses { + var response []ExchangeAssetPairResponses + testOrderSide := parseOrderSide(config.OrderSubmission.OrderSide) + testOrderType := parseOrderType(config.OrderSubmission.OrderType) + assetTypes := base.GetAssetTypes() + if assetTypeOverride != "" { + if asset.IsValid(asset.Item(assetTypeOverride)) { + assetTypes = asset.Items{asset.Item(assetTypeOverride)} + } else { + log.Printf("%v Asset Type '%v' not recognised, defaulting to exchange defaults", base.GetName(), assetTypeOverride) + } + } + for i := range assetTypes { + var msg string + var p currency.Pair + log.Printf("%v %v", base.GetName(), assetTypes[i]) + if _, ok := base.Config.CurrencyPairs.Pairs[assetTypes[i]]; !ok { + continue + } + + switch { + case currencyPairOverride != "": + p = currency.NewPairFromString(currencyPairOverride) + case len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Enabled) == 0: + if len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available) == 0 { + log.Printf("%v has no enabled or available currencies. Skipping", base.GetName()) + continue + } + p = base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available.GetRandomPair() + default: + p = base.Config.CurrencyPairs.Pairs[assetTypes[i]].Enabled.GetRandomPair() + } + + responseContainer := ExchangeAssetPairResponses{ + AssetType: assetTypes[i], + CurrencyPair: p, + } + + log.Printf("Setup config for %v %v %v", base.GetName(), assetTypes[i], p) + err := e.Setup(base.Config) + if err != nil { + log.Printf("%v Encountered error reloading config: '%v'", base.GetName(), err) + } + log.Printf("Executing wrappers for %v %v %v", base.GetName(), assetTypes[i], p) + + if !authenticatedOnly { + var r1 ticker.Price + r1, err = e.FetchTicker(p, assetTypes[i]) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}), + Function: "FetchTicker", + Error: msg, + Response: jsonifyInterface([]interface{}{r1}), + }) + + var r2 ticker.Price + r2, err = e.UpdateTicker(p, assetTypes[i]) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}), + Function: "UpdateTicker", + Error: msg, + Response: jsonifyInterface([]interface{}{r2}), + }) + + var r3 orderbook.Base + r3, err = e.FetchOrderbook(p, assetTypes[i]) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}), + Function: "FetchOrderbook", + Error: msg, + Response: jsonifyInterface([]interface{}{r3}), + }) + + var r4 orderbook.Base + r4, err = e.UpdateOrderbook(p, assetTypes[i]) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}), + Function: "UpdateOrderbook", + Error: msg, + Response: jsonifyInterface([]interface{}{r4}), + }) + + var r5 []string + r5, err = e.FetchTradablePairs(assetTypes[i]) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{assetTypes[i]}), + Function: "FetchTradablePairs", + Error: msg, + Response: jsonifyInterface([]interface{}{r5}), + }) + // r6 + err = e.UpdateTradablePairs(false) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{false}), + Function: "UpdateTradablePairs", + Error: msg, + Response: jsonifyInterface([]interface{}{nil}), + }) + } + + var r7 exchange.AccountInfo + r7, err = e.GetAccountInfo() + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + Function: "GetAccountInfo", + Error: msg, + Response: jsonifyInterface([]interface{}{r7}), + }) + + var r8 []exchange.TradeHistory + r8, err = e.GetExchangeHistory(p, assetTypes[i]) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}), + Function: "GetExchangeHistory", + Error: msg, + Response: jsonifyInterface([]interface{}{r8}), + }) + + var r9 []exchange.FundHistory + r9, err = e.GetFundingHistory() + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + Function: "GetFundingHistory", + Error: msg, + Response: jsonifyInterface([]interface{}{r9}), + }) + + feeType := exchange.FeeBuilder{ + FeeType: exchange.CryptocurrencyTradeFee, + Pair: p, + PurchasePrice: config.OrderSubmission.Price, + Amount: config.OrderSubmission.Amount, + } + var r10 float64 + r10, err = e.GetFeeByType(&feeType) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{feeType}), + Function: "GetFeeByType-Trade", + Error: msg, + Response: jsonifyInterface([]interface{}{r10}), + }) + + s := &exchange.OrderSubmission{ + Pair: p, + OrderSide: testOrderSide, + OrderType: testOrderType, + Amount: config.OrderSubmission.Amount, + Price: config.OrderSubmission.Price, + ClientID: config.OrderSubmission.OrderID, + } + var r11 exchange.SubmitOrderResponse + r11, err = e.SubmitOrder(s) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{*s}), + Function: "SubmitOrder", + Error: msg, + Response: jsonifyInterface([]interface{}{r11}), + }) + + modifyRequest := exchange.ModifyOrder{ + OrderID: config.OrderSubmission.OrderID, + OrderType: testOrderType, + OrderSide: testOrderSide, + CurrencyPair: p, + Price: config.OrderSubmission.Price, + Amount: config.OrderSubmission.Amount, + } + var r12 string + r12, err = e.ModifyOrder(&modifyRequest) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{modifyRequest}), + Function: "ModifyOrder", + Error: msg, + Response: r12, + }) + // r13 + cancelRequest := exchange.OrderCancellation{ + Side: testOrderSide, + CurrencyPair: p, + OrderID: config.OrderSubmission.OrderID, + } + err = e.CancelOrder(&cancelRequest) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{cancelRequest}), + Function: "CancelOrder", + Error: msg, + Response: jsonifyInterface([]interface{}{nil}), + }) + + var r14 exchange.CancelAllOrdersResponse + r14, err = e.CancelAllOrders(&cancelRequest) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{cancelRequest}), + Function: "CancelAllOrders", + Error: msg, + Response: jsonifyInterface([]interface{}{r14}), + }) + + var r15 exchange.OrderDetail + r15, err = e.GetOrderInfo(config.OrderSubmission.OrderID) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{config.OrderSubmission.OrderID}), + Function: "GetOrderInfo", + Error: msg, + Response: jsonifyInterface([]interface{}{r15}), + }) + + historyRequest := exchange.GetOrdersRequest{ + OrderType: testOrderType, + OrderSide: testOrderSide, + Currencies: []currency.Pair{p}, + } + var r16 []exchange.OrderDetail + r16, err = e.GetOrderHistory(&historyRequest) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{historyRequest}), + Function: "GetOrderHistory", + Error: msg, + Response: jsonifyInterface([]interface{}{r16}), + }) + + orderRequest := exchange.GetOrdersRequest{ + OrderType: testOrderType, + OrderSide: testOrderSide, + Currencies: []currency.Pair{p}, + } + var r17 []exchange.OrderDetail + r17, err = e.GetActiveOrders(&orderRequest) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{orderRequest}), + Function: "GetActiveOrders", + Error: msg, + Response: jsonifyInterface([]interface{}{r17}), + }) + + var r18 string + r18, err = e.GetDepositAddress(p.Base, "") + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{p.Base, ""}), + Function: "GetDepositAddress", + Error: msg, + Response: r18, + }) + + feeType = exchange.FeeBuilder{ + FeeType: exchange.CryptocurrencyWithdrawalFee, + Pair: p, + PurchasePrice: config.OrderSubmission.Price, + Amount: config.OrderSubmission.Amount, + } + var r19 float64 + r19, err = e.GetFeeByType(&feeType) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{feeType}), + Function: "GetFeeByType-Crypto-Withdraw", + Error: msg, + Response: jsonifyInterface([]interface{}{r19}), + }) + + genericWithdrawRequest := exchange.GenericWithdrawRequestInfo{ + Amount: config.OrderSubmission.Amount, + Currency: p.Quote, + } + withdrawRequest := exchange.CryptoWithdrawRequest{ + GenericWithdrawRequestInfo: genericWithdrawRequest, + Address: withdrawAddressOverride, + } + var r20 string + r20, err = e.WithdrawCryptocurrencyFunds(&withdrawRequest) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{withdrawRequest}), + Function: "WithdrawCryptocurrencyFunds", + Error: msg, + Response: r20, + }) + + feeType = exchange.FeeBuilder{ + FeeType: exchange.InternationalBankWithdrawalFee, + Pair: p, + PurchasePrice: config.OrderSubmission.Price, + Amount: config.OrderSubmission.Amount, + FiatCurrency: currency.AUD, + BankTransactionType: exchange.WireTransfer, + } + var r21 float64 + r21, err = e.GetFeeByType(&feeType) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{feeType}), + Function: "GetFeeByType-FIAT-Withdraw", + Error: msg, + Response: jsonifyInterface([]interface{}{r21}), + }) + + fiatWithdrawRequest := exchange.FiatWithdrawRequest{ + GenericWithdrawRequestInfo: genericWithdrawRequest, + BankAccountName: config.BankDetails.BankAccountName, + BankAccountNumber: config.BankDetails.BankAccountNumber, + SwiftCode: config.BankDetails.SwiftCode, + IBAN: config.BankDetails.Iban, + BankCity: config.BankDetails.BankCity, + BankName: config.BankDetails.BankName, + BankAddress: config.BankDetails.BankAddress, + BankCountry: config.BankDetails.BankCountry, + BankPostalCode: config.BankDetails.BankPostalCode, + BankCode: config.BankDetails.BankCode, + IsExpressWire: config.BankDetails.IsExpressWire, + RequiresIntermediaryBank: config.BankDetails.RequiresIntermediaryBank, + IntermediaryBankName: config.BankDetails.IntermediaryBankName, + IntermediaryBankAccountNumber: config.BankDetails.IntermediaryBankAccountNumber, + IntermediarySwiftCode: config.BankDetails.IntermediarySwiftCode, + IntermediaryIBAN: config.BankDetails.IntermediaryIban, + IntermediaryBankCity: config.BankDetails.IntermediaryBankCity, + IntermediaryBankAddress: config.BankDetails.IntermediaryBankAddress, + IntermediaryBankCountry: config.BankDetails.IntermediaryBankCountry, + IntermediaryBankPostalCode: config.BankDetails.IntermediaryBankPostalCode, + IntermediaryBankCode: config.BankDetails.IntermediaryBankCode, + } + var r22 string + r22, err = e.WithdrawFiatFunds(&fiatWithdrawRequest) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{fiatWithdrawRequest}), + Function: "WithdrawFiatFunds", + Error: msg, + Response: r22, + }) + + var r23 string + r23, err = e.WithdrawFiatFundsToInternationalBank(&fiatWithdrawRequest) + msg = "" + if err != nil { + msg = err.Error() + responseContainer.ErrorCount++ + } + responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ + SentParams: jsonifyInterface([]interface{}{fiatWithdrawRequest}), + Function: "WithdrawFiatFundsToInternationalBank", + Error: msg, + Response: r23, + }) + response = append(response, responseContainer) + } + return response +} + +func jsonifyInterface(params []interface{}) json.RawMessage { + response, _ := json.MarshalIndent(params, "", " ") + return response +} + +func loadConfig() (Config, error) { + var config Config + file, err := os.OpenFile("wrapperconfig.json", os.O_RDONLY, os.ModePerm) + if err != nil { + return config, err + } + defer file.Close() + keys, err := ioutil.ReadAll(file) + if err != nil { + return config, err + } + + err = common.JSONDecode(keys, &config) + return config, err +} + +func saveConfig(config *Config) { + log.Println("JSONifying config...") + jsonOutput, err := json.MarshalIndent(config, "", " ") + if err != nil { + log.Fatalf("Encountered error encoding JSON: %v", err) + } + + dir, err := os.Getwd() + if err != nil { + log.Printf("Encountered error retrieving output directory: %v", err) + return + } + + log.Printf("Outputting to: %v", filepath.Join(dir, "wrapperconfig.json")) + err = common.WriteFile(filepath.Join(dir, "wrapperconfig.json"), jsonOutput) + if err != nil { + log.Printf("Encountered error writing to disk: %v", err) + return + } +} + +func outputToJSON(exchangeResponses []ExchangeResponses) { + log.Println("JSONifying results...") + jsonOutput, err := json.MarshalIndent(exchangeResponses, "", " ") + if err != nil { + log.Fatalf("Encountered error encoding JSON: %v", err) + } + + dir, err := os.Getwd() + if err != nil { + log.Printf("Encountered error retrieving output directory: %v", err) + return + } + + log.Printf("Outputting to: %v", filepath.Join(dir, fmt.Sprintf("%v.json", outputFileName))) + err = common.WriteFile(filepath.Join(dir, fmt.Sprintf("%v.json", outputFileName)), jsonOutput) + if err != nil { + log.Printf("Encountered error writing to disk: %v", err) + return + } +} + +func outputToHTML(exchangeResponses []ExchangeResponses) { + log.Println("Generating HTML report...") + dir, err := os.Getwd() + if err != nil { + log.Print(err) + return + } + + tmpl, err := template.New("report.tmpl").ParseFiles(filepath.Join(dir, "report.tmpl")) + if err != nil { + log.Print(err) + return + } + + log.Printf("Outputting to: %v", filepath.Join(dir, fmt.Sprintf("%v.html", outputFileName))) + file, err := os.Create(filepath.Join(dir, fmt.Sprintf("%v.html", outputFileName))) + if err != nil { + log.Print(err) + return + } + + defer file.Close() + err = tmpl.Execute(file, exchangeResponses) + if err != nil { + log.Print(err) + return + } +} + +func outputToConsole(exchangeResponses []ExchangeResponses) { + var totalErrors int64 + for i := range exchangeResponses { + log.Printf("------------%v Results-------------\n", exchangeResponses[i].ExchangeName) + for j := range exchangeResponses[i].AssetPairResponses { + for k := range exchangeResponses[i].AssetPairResponses[j].EndpointResponses { + log.Printf("%v Result: %v", exchangeResponses[i].ExchangeName, k) + log.Printf("Function:\t%v", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Function) + log.Printf("AssetType:\t%v", exchangeResponses[i].AssetPairResponses[j].AssetType) + log.Printf("Currency:\t%v\n", exchangeResponses[i].AssetPairResponses[j].CurrencyPair) + log.Printf("Wrapper Params:\t%s\n", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].SentParams) + if exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Error != "" { + totalErrors++ + log.Printf("Error:\t%v", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Error) + } else { + log.Print("Error:\tnone") + } + if verboseOverride { + log.Printf("Wrapper Response:\t%s", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Response) + } + log.Println() + } + } + log.Println() + } +} diff --git a/cmd/exchange_wrapper_issues/report.tmpl b/cmd/exchange_wrapper_issues/report.tmpl new file mode 100644 index 00000000..2fcede34 --- /dev/null +++ b/cmd/exchange_wrapper_issues/report.tmpl @@ -0,0 +1,155 @@ +{{ $output := . }} + + + + +Wrapper results + + + + + + + + + + +
+ {{ range $i, $exchangeResult := $output }} +
+
+
+

{{ $exchangeResult.ExchangeName }}

+
+
+
+ {{ if $exchangeResult.APIKeysSet }} + API Keys Set {{ else }} API Keys Not Set {{ end }} +
+
+
+ +
+
+
+ +
+ + {{ range $j, $assetPairResponses := $exchangeResult.AssetPairResponses }} + {{ $length := len $exchangeResult.AssetPairResponses }} + {{ if eq $length 1 }} + {{ else }} +
+

{{ $assetPairResponses.AssetType }} {{ $assetPairResponses.CurrencyPair }} Results

+ +
+ {{ end }} + + + + + + + + + + + + {{ range $k, $endpointResponse := $assetPairResponses.EndpointResponses }} + + + + + + + + + + {{ end }} +
+ Asset Type + + Currency Pair + + Function + + Wrapper Params Sent + + Error + + Wrapper Response +
+ {{ $assetPairResponses.AssetType }} + + {{ $assetPairResponses.CurrencyPair }} + + {{ $endpointResponse.Function }} + + +
+
+ {{ $endpointResponse.SentParams | printf "%s" }} +
+
+
+ {{ $endpointResponse.Error }} + + +
+
+ {{ $endpointResponse.Response | printf "%s" }} +
+
+
+ {{ end }} +
+ {{ end }} +
+ + + \ No newline at end of file diff --git a/cmd/exchange_wrapper_issues/types.go b/cmd/exchange_wrapper_issues/types.go new file mode 100644 index 00000000..60427c52 --- /dev/null +++ b/cmd/exchange_wrapper_issues/types.go @@ -0,0 +1,107 @@ +package main + +import ( + "encoding/json" + + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" +) + +// variables for command line overrides +var ( + orderTypeOverride string + outputOverride string + orderSideOverride string + currencyPairOverride string + assetTypeOverride string + orderPriceOverride float64 + orderAmountOverride float64 + withdrawAddressOverride string + authenticatedOnly bool + verboseOverride bool + exchangesToUseOverride string + exchangesToExcludeOverride string + outputFileName string + exchangesToUseList []string + exchangesToExcludeList []string +) + +// Config the data structure for wrapperconfig.json to store all customisation +type Config struct { + OrderSubmission OrderSubmission `json:"orderSubmission"` + WalletAddress string `json:"withdrawWalletAddress"` + BankDetails Bank `json:"bankAccount"` + Exchanges map[string]*config.APICredentialsConfig `json:"exchanges"` +} + +// Key is the format for wrapperconfig.json to store API credentials +type Key struct { + APIKey string `json:"apiKey"` + APISecret string `json:"apiSecret,omitempty"` + ClientID string `json:"clientId,omitempty"` + OTPSecret string `json:"otpSecret,omitempty"` +} + +// ExchangeResponses contains all responses +// associated with an exchange +type ExchangeResponses struct { + ID string + ExchangeName string `json:"exchangeName"` + AssetPairResponses []ExchangeAssetPairResponses `json:"responses"` + ErrorCount int64 `json:"errorCount"` + APIKeysSet bool `json:"apiKeysSet"` +} + +// ExchangeAssetPairResponses contains all responses +// associated with an asset type and currency pair +type ExchangeAssetPairResponses struct { + ErrorCount int64 `json:"errorCount"` + AssetType asset.Item `json:"asset"` + CurrencyPair currency.Pair `json:"currency"` + EndpointResponses []EndpointResponse `json:"responses"` +} + +// EndpointResponse is the data for an individual wrapper response +type EndpointResponse struct { + Function string `json:"function"` + Error string `json:"error"` + Response interface{} `json:"response"` + SentParams json.RawMessage `json:"sentParams"` +} + +// Bank contains all required data for a wrapper withdrawal request +type Bank struct { + BankAccountName string `json:"bankAccountName"` + BankAccountNumber float64 `json:"bankAccountNumber"` + BankAddress string `json:"bankAddress"` + BankCity string `json:"bankCity"` + BankCountry string `json:"bankCountry"` + BankName string `json:"bankName"` + BankPostalCode string `json:"bankPostalCode"` + Iban string `json:"iban"` + IntermediaryBankAccountName string `json:"intermediaryBankAccountName"` + IntermediaryBankAccountNumber float64 `json:"intermediaryBankAccountNumber"` + IntermediaryBankAddress string `json:"intermediaryBankAddress"` + IntermediaryBankCity string `json:"intermediaryBankCity"` + IntermediaryBankCountry string `json:"intermediaryBankCountry"` + IntermediaryBankName string `json:"intermediaryBankName"` + IntermediaryBankPostalCode string `json:"intermediaryBankPostalCode"` + IntermediaryIban string `json:"intermediaryIban"` + IntermediaryIsExpressWire bool `json:"intermediaryIsExpressWire"` + IntermediarySwiftCode string `json:"intermediarySwiftCode"` + IsExpressWire bool `json:"isExpressWire"` + RequiresIntermediaryBank bool `json:"requiresIntermediaryBank"` + SwiftCode string `json:"swiftCode"` + BankCode float64 `json:"bankCode"` + IntermediaryBankCode float64 `json:"intermediaryBankCode"` +} + +// OrderSubmission contains all data required for a wrapper order submission +type OrderSubmission struct { + OrderSide string `json:"orderSide"` + OrderType string `json:"orderType"` + Amount float64 `json:"amount"` + Price float64 `json:"price"` + OrderID string `json:"orderID"` +} diff --git a/cmd/exchange_wrapper_issues/wrapperconfig.json b/cmd/exchange_wrapper_issues/wrapperconfig.json new file mode 100644 index 00000000..43afaeaf --- /dev/null +++ b/cmd/exchange_wrapper_issues/wrapperconfig.json @@ -0,0 +1,178 @@ +{ + "orderSubmission": { + "orderSide": "BUY", + "orderType": "LIMIT", + "amount": 1333333337, + "price": 1333333337, + "orderID": "" + }, + "withdrawWalletAddress": "", + "bankAccount": { + "bankAccountName": "bankAccountName", + "bankAccountNumber": 1337, + "bankAddress": "bankAddress", + "bankCity": "bankCity", + "bankCountry": "bankCountry", + "bankName": "bankName", + "bankPostalCode": "bankPostalCode", + "iban": "iban", + "intermediaryBankAccountName": "intermediaryBankAccountName", + "intermediaryBankAccountNumber": 1337, + "intermediaryBankAddress": "intermediaryBankAddress", + "intermediaryBankCity": "intermediaryBankCity", + "intermediaryBankCountry": "intermediaryBankCountry", + "intermediaryBankName": "intermediaryBankName", + "intermediaryBankPostalCode": "intermediaryBankPostalCode", + "intermediaryIban": "intermediaryIban", + "intermediaryIsExpressWire": false, + "intermediarySwiftCode": "intermediarySwiftCode", + "isExpressWire": false, + "requiresIntermediaryBank": false, + "swiftCode": "swiftCode", + "bankCode": 1337, + "intermediaryBankCode": 1337 + }, + "exchanges": { + "alphapoint": {}, + "anx": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "binance": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "bitfinex": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "bitflyer": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "bithumb": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "bitmex": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "bitstamp": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID", + "otpSecret": "-" + }, + "bittrex": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "btc markets": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "btse": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "coinbasepro": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID", + "otpSecret": "-" + }, + "coinut": { + "key": "Key", + "clientID": "ClientID", + "otpSecret": "-" + }, + "exmo": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "gateio": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "gemini": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "hitbtc": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "huobi": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "huobihadax": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "itbit": { + "secret": "Secret", + "clientID": "ClientID", + "otpSecret": "-" + }, + "kraken": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "lakebtc": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "lbank": {}, + "localbitcoins": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "okcoin international": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID", + "otpSecret": "-" + }, + "okex": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID", + "otpSecret": "-" + }, + "poloniex": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "yobit": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, + "zb": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + } + } +} \ No newline at end of file From 6bdbe236c017013171043807a3b173fa6a432ab2 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 27 Sep 2019 15:18:54 +1000 Subject: [PATCH 42/71] Rid GCT os.Exit's in packages outside of main --- cmd/dbmigrate/main.go | 6 ++---- engine/engine.go | 34 ++++------------------------------ main.go | 13 +++++++++++-- signaler/signaler.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 36 deletions(-) create mode 100644 signaler/signaler.go diff --git a/cmd/dbmigrate/main.go b/cmd/dbmigrate/main.go index bff65642..f88234e7 100644 --- a/cmd/dbmigrate/main.go +++ b/cmd/dbmigrate/main.go @@ -108,15 +108,14 @@ func main() { err = temp.LoadMigrations() if err != nil { fmt.Println(err) - os.Exit(0) + os.Exit(1) } conf := config.GetConfig() - err = conf.LoadConfig(configFile) if err != nil { fmt.Println(err) - os.Exit(0) + os.Exit(1) } err = openDbConnection(conf.Database.Driver) @@ -130,7 +129,6 @@ func main() { temp.Conn = dbConn err = temp.RunMigration() - if err != nil { fmt.Println(err) os.Exit(1) diff --git a/engine/engine.go b/engine/engine.go index a3d577f3..88d0e611 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -4,10 +4,7 @@ import ( "errors" "flag" "fmt" - "os" - "os/signal" "sync" - "syscall" "time" "github.com/thrasher-corp/gocryptotrader/common" @@ -35,7 +32,6 @@ type Engine struct { PortfolioManager portfolioManager CommsManager commsManager DepositAddressManager *DepositAddressManager - Shutdown chan struct{} Settings Settings Uptime time.Time ServicesWG sync.WaitGroup @@ -98,7 +94,6 @@ func NewFromSettings(settings *Settings) (*Engine, error) { return nil, fmt.Errorf("unable to adjust runtime GOMAXPROCS value. Err: %s", err) } - b.handleInterrupt() ValidateSettings(&b, settings) return &b, nil } @@ -256,11 +251,9 @@ func PrintSettings(s *Settings) { } // Start starts the engine -func (e *Engine) Start() { - // TO-DO: move this out of here +func (e *Engine) Start() error { if e == nil { - log.Errorln(log.Global, "Engine instance is nil") - os.Exit(1) + return errors.New("engine instance is nil") } if e.Settings.EnableDatabaseManager { @@ -302,10 +295,8 @@ func (e *Engine) Start() { log.Debugln(log.Global, "Setting up exchanges..") SetupExchanges() - // TO-DO: move this out of here if len(Bot.Exchanges) == 0 { - log.Errorln(log.Global, "No exchanges were able to be loaded. Exiting") - os.Exit(1) + return errors.New("no exchanges are loaded") } if e.Settings.EnableCommsRelayer { @@ -394,8 +385,7 @@ func (e *Engine) Start() { go WebsocketRoutine() } - <-e.Shutdown - e.Stop() + return nil } // Stop correctly shuts down engine saving configuration files @@ -453,24 +443,8 @@ func (e *Engine) Stop() { // Wait for services to gracefully shutdown e.ServicesWG.Wait() - log.Debugln(log.Global, "Exiting.") err := log.CloseLogger() if err != nil { fmt.Printf("Failed to close logger %v", err) } - - os.Exit(0) -} - -// handleInterrupt monitors and captures the SIGTERM in a new goroutine then -// shuts down the engine instance -func (e *Engine) handleInterrupt() { - c := make(chan os.Signal, 1) - e.Shutdown = make(chan struct{}) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - sig := <-c - log.Debugf(log.Global, "Captured %v, shutdown requested.\n", sig) - close(e.Shutdown) - }() } diff --git a/main.go b/main.go index b4c3b55e..4638fa38 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/engine" "github.com/thrasher-corp/gocryptotrader/exchanges/request" log "github.com/thrasher-corp/gocryptotrader/logger" + "github.com/thrasher-corp/gocryptotrader/signaler" ) func main() { @@ -93,10 +94,18 @@ func main() { engine.Bot, err = engine.NewFromSettings(&settings) if engine.Bot == nil || err != nil { - log.Errorf(log.Global, "Unable to initialise bot engine. Err: %s\n", err) + log.Errorf(log.Global, "Unable to initialise bot engine. Error: %s\n", err) os.Exit(1) } engine.PrintSettings(&engine.Bot.Settings) - engine.Bot.Start() + if err = engine.Bot.Start(); err != nil { + log.Errorf(log.Global, "Unable to start bot engine. Error: %s\n", err) + os.Exit(1) + } + + interrupt := signaler.WaitForInterrupt() + log.Infof(log.Global, "Captured %v, shutdown requested.\n", interrupt) + engine.Bot.Stop() + log.Infoln(log.Global, "Exiting.") } diff --git a/signaler/signaler.go b/signaler/signaler.go new file mode 100644 index 00000000..d2b70a07 --- /dev/null +++ b/signaler/signaler.go @@ -0,0 +1,28 @@ +package signaler + +import ( + "os" + "os/signal" + "syscall" +) + +var ( + s = make(chan os.Signal, 1) +) + +func init() { + sigs := []os.Signal{ + os.Interrupt, + os.Kill, + syscall.SIGTERM, + syscall.SIGQUIT, + syscall.SIGABRT, + } + signal.Notify(s, sigs...) +} + +// WaitForInterrupt waits until a os.Signal is +// received and returns the result +func WaitForInterrupt() os.Signal { + return <-s +} From e2d57540a6479b07858d988d855288f6f08ff4e2 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Fri, 27 Sep 2019 16:03:41 +1000 Subject: [PATCH 43/71] Config overwrite bugfix (#363) * Fix bug where on parsing an alternate new config it will overwrite main config.json in gct dir * Stop movement of config.json file from root dir when a new config is parsed in * Stop overiding config.json at gct dir with new config.json from root directory * RM LN :D * Fix bug where promptforconfig in config_encryption.go overwrites default config Ensure periphery command packages do not interact or save over configuration Ensure tests to not save over or change current testdata/config --- cmd/dbmigrate/main.go | 3 +- cmd/exchange_template/exchange_template.go | 4 +- cmd/gen_otp/otp_gen.go | 2 +- cmd/portfolio/portfolio.go | 2 +- cmd/websocket_client/main.go | 2 +- communications/slack/slack_test.go | 9 +- communications/smsglobal/smsglobal_test.go | 9 +- .../smtpservice/smtpservice_test.go | 11 ++- communications/telegram/telegram_test.go | 13 ++- config/config.go | 42 +++++++--- config/config_encryption.go | 8 +- config/config_encryption_test.go | 2 +- config/config_test.go | 84 +++++++++---------- engine/engine.go | 14 ++-- engine/exchange_test.go | 2 +- engine/helpers_test.go | 2 +- engine/restful_server.go | 2 +- engine/restful_server_test.go | 2 +- engine/syncer_test.go | 2 +- engine/websocket.go | 2 +- exchanges/anx/anx_live_test.go | 10 ++- exchanges/anx/anx_mock_test.go | 10 ++- exchanges/binance/binance_live_test.go | 10 ++- exchanges/binance/binance_mock_test.go | 10 ++- exchanges/bitfinex/bitfinex_test.go | 10 ++- exchanges/bitflyer/bitflyer_test.go | 10 ++- exchanges/bithumb/bithumb_test.go | 10 ++- exchanges/bitmex/bitmex_test.go | 10 ++- exchanges/bitstamp/bitstamp_live_test.go | 10 ++- exchanges/bitstamp/bitstamp_mock_test.go | 10 ++- exchanges/bittrex/bittrex_test.go | 10 ++- exchanges/btcmarkets/btcmarkets_test.go | 10 ++- exchanges/btse/btse_test.go | 11 ++- exchanges/coinbasepro/coinbasepro_test.go | 10 ++- exchanges/coinut/coinut_test.go | 10 ++- exchanges/exchange_test.go | 10 +-- exchanges/exmo/exmo_test.go | 13 ++- exchanges/gateio/gateio_test.go | 10 ++- exchanges/gemini/gemini_live_test.go | 10 ++- exchanges/gemini/gemini_mock_test.go | 10 ++- exchanges/hitbtc/hitbtc_test.go | 10 ++- exchanges/huobi/huobi_test.go | 11 ++- exchanges/itbit/itbit_test.go | 12 ++- exchanges/kraken/kraken_test.go | 11 ++- exchanges/lakebtc/lakebtc_test.go | 11 ++- exchanges/lbank/lbank_test.go | 11 ++- .../localbitcoins/localbitcoins_live_test.go | 10 ++- .../localbitcoins/localbitcoins_mock_test.go | 10 ++- exchanges/okcoin/okcoin_test.go | 10 ++- exchanges/okex/okex_test.go | 10 ++- exchanges/poloniex/poloniex_live_test.go | 10 ++- exchanges/poloniex/poloniex_mock_test.go | 10 ++- exchanges/yobit/yobit_test.go | 12 ++- exchanges/zb/zb_test.go | 12 ++- main.go | 10 +-- 55 files changed, 408 insertions(+), 173 deletions(-) diff --git a/cmd/dbmigrate/main.go b/cmd/dbmigrate/main.go index f88234e7..0cb733b3 100644 --- a/cmd/dbmigrate/main.go +++ b/cmd/dbmigrate/main.go @@ -112,7 +112,8 @@ func main() { } conf := config.GetConfig() - err = conf.LoadConfig(configFile) + + err = conf.LoadConfig(configFile, true) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/exchange_template/exchange_template.go b/cmd/exchange_template/exchange_template.go index 63b7a85a..d01224eb 100644 --- a/cmd/exchange_template/exchange_template.go +++ b/cmd/exchange_template/exchange_template.go @@ -99,7 +99,7 @@ func main() { } configTestFile := config.GetConfig() - err = configTestFile.LoadConfig(exchangeConfigPath) + err = configTestFile.LoadConfig(exchangeConfigPath, true) if err != nil { log.Fatal("GoCryptoTrader: Exchange templating configuration retrieval error ", err) } @@ -129,7 +129,7 @@ func main() { configTestFile.Exchanges = append(configTestFile.Exchanges, newExchConfig) // TODO sorting function so exchanges are in alphabetical order - low priority - err = configTestFile.SaveConfig(exchangeJSON) + err = configTestFile.SaveConfig(exchangeJSON, false) if err != nil { log.Fatal("GoCryptoTrader: Exchange templating configuration error - cannot save") } diff --git a/cmd/gen_otp/otp_gen.go b/cmd/gen_otp/otp_gen.go index f6b37348..f48c4fe2 100644 --- a/cmd/gen_otp/otp_gen.go +++ b/cmd/gen_otp/otp_gen.go @@ -62,7 +62,7 @@ func main() { // Otherwise default to loading the config file and generating OTP codes from it var cfg config.Config - err = cfg.LoadConfig(cfgFile) + err = cfg.LoadConfig(cfgFile, true) if err != nil { log.Fatal(err) } diff --git a/cmd/portfolio/portfolio.go b/cmd/portfolio/portfolio.go index ea44cc34..d9594674 100644 --- a/cmd/portfolio/portfolio.go +++ b/cmd/portfolio/portfolio.go @@ -77,7 +77,7 @@ func main() { log.Println("GoCryptoTrader: portfolio tool.") var cfg config.Config - err = cfg.LoadConfig(inFile) + err = cfg.LoadConfig(inFile, true) if err != nil { log.Println(err) os.Exit(1) diff --git a/cmd/websocket_client/main.go b/cmd/websocket_client/main.go index 956e57d1..7eba7875 100644 --- a/cmd/websocket_client/main.go +++ b/cmd/websocket_client/main.go @@ -76,7 +76,7 @@ func SendWebsocketEvent(event string, reqData interface{}, result *WebsocketEven func main() { cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigFile) + err := cfg.LoadConfig(config.ConfigFile, true) if err != nil { log.Fatalf("Failed to load config file: %s", err) } diff --git a/communications/slack/slack_test.go b/communications/slack/slack_test.go index 7597433f..73eb850d 100644 --- a/communications/slack/slack_test.go +++ b/communications/slack/slack_test.go @@ -41,7 +41,10 @@ type group struct { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig(config.ConfigTestFile) + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal(err) + } commsCfg := cfg.GetCommunicationsConfig() s.Setup(&commsCfg) @@ -51,7 +54,7 @@ func TestSetup(t *testing.T) { func TestConnect(t *testing.T) { err := s.Connect() if err == nil { - t.Error("test failed - slack Connect() error") + t.Error("test failed - slack Connect() error cannot be nil") } } @@ -59,7 +62,7 @@ func TestPushEvent(t *testing.T) { t.Parallel() err := s.PushEvent(base.Event{}) if err == nil { - t.Error("test failed - slack PushEvent() error") + t.Error("test failed - slack PushEvent() error cannot be nil") } } diff --git a/communications/smsglobal/smsglobal_test.go b/communications/smsglobal/smsglobal_test.go index a06cf4cd..2ffef201 100644 --- a/communications/smsglobal/smsglobal_test.go +++ b/communications/smsglobal/smsglobal_test.go @@ -11,7 +11,10 @@ var s SMSGlobal func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal(err) + } commsCfg := cfg.GetCommunicationsConfig() s.Setup(&commsCfg) } @@ -19,14 +22,14 @@ func TestSetup(t *testing.T) { func TestConnect(t *testing.T) { err := s.Connect() if err != nil { - t.Error("test failed - SMSGlobal Connect() error") + t.Error("test failed - SMSGlobal Connect() error", err) } } func TestPushEvent(t *testing.T) { err := s.PushEvent(base.Event{}) if err != nil { - t.Error("test failed - SMSGlobal PushEvent() error") + t.Error("test failed - SMSGlobal PushEvent() error", err) } } diff --git a/communications/smtpservice/smtpservice_test.go b/communications/smtpservice/smtpservice_test.go index 1a5be56f..10124690 100644 --- a/communications/smtpservice/smtpservice_test.go +++ b/communications/smtpservice/smtpservice_test.go @@ -11,7 +11,10 @@ var s SMTPservice func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal(err) + } commsCfg := cfg.GetCommunicationsConfig() s.Setup(&commsCfg) } @@ -26,17 +29,17 @@ func TestConnect(t *testing.T) { func TestPushEvent(t *testing.T) { err := s.PushEvent(base.Event{}) if err == nil { - t.Error("test failed - smtpservice PushEvent() error", err) + t.Error("test failed - smtpservice PushEvent() error cannot be nil") } } func TestSend(t *testing.T) { err := s.Send("", "") if err == nil { - t.Error("test failed - smtpservice Send() error", err) + t.Error("test failed - smtpservice Send() error cannot be nil") } err = s.Send("subject", "alertmessage") if err == nil { - t.Error("test failed - smtpservice Send() error", err) + t.Error("test failed - smtpservice Send() error cannot be nil") } } diff --git a/communications/telegram/telegram_test.go b/communications/telegram/telegram_test.go index e9943f5d..0643037e 100644 --- a/communications/telegram/telegram_test.go +++ b/communications/telegram/telegram_test.go @@ -15,13 +15,18 @@ var T Telegram func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal(err) + } commsCfg := cfg.GetCommunicationsConfig() T.Setup(&commsCfg) - if T.Name != "Telegram" || T.Enabled || - T.Token != "testest" || T.Verbose { + if T.Name != "Telegram" || T.Enabled || T.Token != "testest" || T.Verbose { t.Error("test failed - telegram Setup() error, unexpected setup values", - T.Name, T.Enabled, T.Token, T.Verbose) + T.Name, + T.Enabled, + T.Token, + T.Verbose) } } diff --git a/config/config.go b/config/config.go index 84358730..3bb42c56 100644 --- a/config/config.go +++ b/config/config.go @@ -1423,24 +1423,38 @@ func GetFilePath(file string) (string, error) { filepath.Join(newDir, EncryptedConfigFile), } - // First upgrade the old dir config file if it exists to the corresponding new one + // First upgrade the old dir config file if it exists to the corresponding + // new one for x := range oldDirs { _, err := os.Stat(oldDirs[x]) if os.IsNotExist(err) { continue } + _, err = os.Stat(newDirs[x]) + if !os.IsNotExist(err) { + log.Warnf(log.ConfigMgr, + "config.json file found in root dir and gct dir; cannot overwrite, defaulting to gct dir config.json at %s", + newDirs[x]) + return newDirs[x], nil + } if filepath.Ext(oldDirs[x]) == ".json" { err = os.Rename(oldDirs[x], newDirs[0]) if err != nil { return "", err } - log.Debugf(log.ConfigMgr, "Renamed old config file %s to %s\n", oldDirs[x], newDirs[0]) + log.Debugf(log.ConfigMgr, + "Renamed old config file %s to %s\n", + oldDirs[x], + newDirs[0]) } else { err = os.Rename(oldDirs[x], newDirs[1]) if err != nil { return "", err } - log.Debugf(log.ConfigMgr, "Renamed old config file %s to %s\n", oldDirs[x], newDirs[1]) + log.Debugf(log.ConfigMgr, + "Renamed old config file %s to %s\n", + oldDirs[x], + newDirs[1]) } } @@ -1485,7 +1499,7 @@ func GetFilePath(file string) (string, error) { // ReadConfig verifies and checks for encryption and verifies the unencrypted // file contains JSON. -func (c *Config) ReadConfig(configPath string) error { +func (c *Config) ReadConfig(configPath string, dryrun bool) error { defaultPath, err := GetFilePath(configPath) if err != nil { return err @@ -1510,9 +1524,9 @@ func (c *Config) ReadConfig(configPath string) error { m.Lock() IsInitialSetup = true m.Unlock() - if c.PromptForConfigEncryption() { + if c.PromptForConfigEncryption(configPath, dryrun) { c.EncryptConfig = configFileEncryptionEnabled - return c.SaveConfig(defaultPath) + return c.SaveConfig(defaultPath, dryrun) } } } else { @@ -1552,7 +1566,11 @@ func (c *Config) ReadConfig(configPath string) error { } // SaveConfig saves your configuration to your desired path -func (c *Config) SaveConfig(configPath string) error { +func (c *Config) SaveConfig(configPath string, dryrun bool) error { + if dryrun { + return nil + } + defaultPath, err := GetFilePath(configPath) if err != nil { return err @@ -1666,8 +1684,8 @@ func (c *Config) CheckConfig() error { } // LoadConfig loads your configuration file into your configuration object -func (c *Config) LoadConfig(configPath string) error { - err := c.ReadConfig(configPath) +func (c *Config) LoadConfig(configPath string, dryrun bool) error { + err := c.ReadConfig(configPath, dryrun) if err != nil { return fmt.Errorf(ErrFailureOpeningConfig, configPath, err) } @@ -1676,7 +1694,7 @@ func (c *Config) LoadConfig(configPath string) error { } // UpdateConfig updates the config with a supplied config file -func (c *Config) UpdateConfig(configPath string, newCfg *Config) error { +func (c *Config) UpdateConfig(configPath string, newCfg *Config, dryrun bool) error { err := newCfg.CheckConfig() if err != nil { return err @@ -1691,12 +1709,12 @@ func (c *Config) UpdateConfig(configPath string, newCfg *Config) error { c.Webserver = newCfg.Webserver c.Exchanges = newCfg.Exchanges - err = c.SaveConfig(configPath) + err = c.SaveConfig(configPath, dryrun) if err != nil { return err } - return c.LoadConfig(configPath) + return c.LoadConfig(configPath, dryrun) } // GetConfig returns a pointer to a configuration object diff --git a/config/config_encryption.go b/config/config_encryption.go index 83688d98..c15fefdc 100644 --- a/config/config_encryption.go +++ b/config/config_encryption.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" + log "github.com/thrasher-corp/gocryptotrader/logger" "golang.org/x/crypto/scrypt" ) @@ -32,7 +33,7 @@ var ( ) // PromptForConfigEncryption asks for encryption key -func (c *Config) PromptForConfigEncryption() bool { +func (c *Config) PromptForConfigEncryption(configPath string, dryrun bool) bool { fmt.Println("Would you like to encrypt your config file (y/n)?") input := "" @@ -43,7 +44,10 @@ func (c *Config) PromptForConfigEncryption() bool { if !common.YesOrNo(input) { c.EncryptConfig = configFileEncryptionDisabled - c.SaveConfig("") + err := c.SaveConfig(configPath, dryrun) + if err != nil { + log.Errorf(log.ConfigMgr, "cannot save config %s", err) + } return false } return true diff --git a/config/config_encryption_test.go b/config/config_encryption_test.go index a907f1f2..0088c881 100644 --- a/config/config_encryption_test.go +++ b/config/config_encryption_test.go @@ -8,7 +8,7 @@ import ( func TestPromptForConfigEncryption(t *testing.T) { t.Parallel() - if Cfg.PromptForConfigEncryption() { + if Cfg.PromptForConfigEncryption("", true) { t.Error("Test failed. PromptForConfigEncryption return incorrect bool") } } diff --git a/config/config_test.go b/config/config_test.go index bae07ee1..419e1951 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -22,7 +22,7 @@ const ( func TestGetCurrencyConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. GetCurrencyConfig LoadConfig error", err) } @@ -31,7 +31,7 @@ func TestGetCurrencyConfig(t *testing.T) { func TestGetExchangeBankAccounts(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. GetExchangeBankAccounts LoadConfig error", err) } @@ -47,7 +47,7 @@ func TestGetExchangeBankAccounts(t *testing.T) { func TestUpdateExchangeBankAccounts(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. UpdateExchangeBankAccounts LoadConfig error", err) } @@ -77,7 +77,7 @@ func TestUpdateExchangeBankAccounts(t *testing.T) { func TestGetClientBankAccounts(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. GetClientBankAccounts LoadConfig error", err) } @@ -97,7 +97,7 @@ func TestGetClientBankAccounts(t *testing.T) { func TestUpdateClientBankAccounts(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. UpdateClientBankAccounts LoadConfig error", err) } @@ -127,7 +127,7 @@ func TestUpdateClientBankAccounts(t *testing.T) { func TestCheckClientBankAccounts(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. CheckClientBankAccounts LoadConfig error", err) } @@ -262,7 +262,7 @@ func TestPurgeExchangeCredentials(t *testing.T) { func TestGetCommunicationsConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. GetCommunicationsConfig LoadConfig error", err) } @@ -271,7 +271,7 @@ func TestGetCommunicationsConfig(t *testing.T) { func TestUpdateCommunicationsConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. UpdateCommunicationsConfig LoadConfig error", err) } @@ -283,7 +283,7 @@ func TestUpdateCommunicationsConfig(t *testing.T) { func TestGetCryptocurrencyProviderConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. GetCryptocurrencyProviderConfig LoadConfig error", err) } @@ -292,7 +292,7 @@ func TestGetCryptocurrencyProviderConfig(t *testing.T) { func TestUpdateCryptocurrencyProviderConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. UpdateCryptocurrencyProviderConfig LoadConfig error", err) } @@ -308,7 +308,7 @@ func TestUpdateCryptocurrencyProviderConfig(t *testing.T) { func TestCheckCommunicationsConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. CheckCommunicationsConfig LoadConfig error", err) } @@ -782,7 +782,7 @@ func TestCheckPairConsistency(t *testing.T) { func TestSupportsPair(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf( "Test failed. TestSupportsPair. LoadConfig Error: %s", err.Error(), @@ -979,7 +979,7 @@ func TestGetEnabledPairs(t *testing.T) { func TestGetEnabledExchanges(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf( "Test failed. TestGetEnabledExchanges. LoadConfig Error: %s", err.Error(), @@ -1002,7 +1002,7 @@ func TestGetEnabledExchanges(t *testing.T) { func TestGetDisabledExchanges(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf( "Test failed. TestGetDisabledExchanges. LoadConfig Error: %s", err.Error(), @@ -1040,7 +1040,7 @@ func TestGetDisabledExchanges(t *testing.T) { func TestCountEnabledExchanges(t *testing.T) { GetConfigEnabledExchanges := GetConfig() - err := GetConfigEnabledExchanges.LoadConfig(ConfigTestFile) + err := GetConfigEnabledExchanges.LoadConfig(ConfigTestFile, true) if err != nil { t.Error( "Test failed. GetConfigEnabledExchanges load config error: " + err.Error(), @@ -1054,7 +1054,7 @@ func TestCountEnabledExchanges(t *testing.T) { func TestGetConfigCurrencyPairFormat(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf( "Test failed. TestGetConfigCurrencyPairFormat. LoadConfig Error: %s", err.Error(), @@ -1080,7 +1080,7 @@ func TestGetConfigCurrencyPairFormat(t *testing.T) { func TestGetRequestCurrencyPairFormat(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf( "Test failed. TestGetRequestCurrencyPairFormat. LoadConfig Error: %s", err.Error(), @@ -1107,7 +1107,7 @@ func TestGetRequestCurrencyPairFormat(t *testing.T) { func TestGetCurrencyPairDisplayConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf( "Test failed. GetCurrencyPairDisplayConfig. LoadConfig Error: %s", err.Error(), @@ -1123,7 +1123,7 @@ func TestGetCurrencyPairDisplayConfig(t *testing.T) { func TestGetAllExchangeConfigs(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. GetAllExchangeConfigs. LoadConfig error", err) } @@ -1134,7 +1134,7 @@ func TestGetAllExchangeConfigs(t *testing.T) { func TestGetExchangeConfig(t *testing.T) { GetExchangeConfig := GetConfig() - err := GetExchangeConfig.LoadConfig(ConfigTestFile) + err := GetExchangeConfig.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf( "Test failed. GetExchangeConfig.LoadConfig Error: %s", err.Error(), @@ -1153,7 +1153,7 @@ func TestGetExchangeConfig(t *testing.T) { func TestGetForexProviderConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. GetForexProviderConfig. LoadConfig error", err) } @@ -1170,7 +1170,7 @@ func TestGetForexProviderConfig(t *testing.T) { func TestGetForexProvidersConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error(err) } @@ -1182,7 +1182,7 @@ func TestGetForexProvidersConfig(t *testing.T) { func TestGetPrimaryForexProvider(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. GetPrimaryForexProvider. LoadConfig error", err) } @@ -1202,7 +1202,7 @@ func TestGetPrimaryForexProvider(t *testing.T) { func TestUpdateExchangeConfig(t *testing.T) { c := GetConfig() - err := c.LoadConfig(ConfigTestFile) + err := c.LoadConfig(ConfigTestFile, true) if err != nil { t.Error(err) } @@ -1232,7 +1232,7 @@ func TestCheckExchangeConfigValues(t *testing.T) { t.Error("nil exchanges should throw an err") } - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Fatal(err) } @@ -1548,7 +1548,7 @@ func TestCheckExchangeConfigValues(t *testing.T) { func TestRetrieveConfigCurrencyPairs(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile) + err := cfg.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf( "Test failed. TestRetrieveConfigCurrencyPairs.LoadConfig: %s", err.Error(), @@ -1573,17 +1573,17 @@ func TestRetrieveConfigCurrencyPairs(t *testing.T) { func TestReadConfig(t *testing.T) { readConfig := GetConfig() - err := readConfig.ReadConfig(ConfigTestFile) + err := readConfig.ReadConfig(ConfigTestFile, true) if err != nil { t.Errorf("Test failed. TestReadConfig %s", err.Error()) } - err = readConfig.ReadConfig("bla") + err = readConfig.ReadConfig("bla", true) if err == nil { t.Error("Test failed. TestReadConfig error cannot be nil") } - err = readConfig.ReadConfig("") + err = readConfig.ReadConfig("", true) if err != nil { t.Error("Test failed. TestReadConfig error") } @@ -1591,12 +1591,12 @@ func TestReadConfig(t *testing.T) { func TestLoadConfig(t *testing.T) { loadConfig := GetConfig() - err := loadConfig.LoadConfig(ConfigTestFile) + err := loadConfig.LoadConfig(ConfigTestFile, true) if err != nil { t.Error("Test failed. TestLoadConfig " + err.Error()) } - err = loadConfig.LoadConfig("testy") + err = loadConfig.LoadConfig("testy", true) if err == nil { t.Error("Test failed. TestLoadConfig ") } @@ -1604,11 +1604,11 @@ func TestLoadConfig(t *testing.T) { func TestSaveConfig(t *testing.T) { saveConfig := GetConfig() - err := saveConfig.LoadConfig(ConfigTestFile) + err := saveConfig.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf("Test failed. TestSaveConfig.LoadConfig: %s", err.Error()) } - err2 := saveConfig.SaveConfig(ConfigTestFile) + err2 := saveConfig.SaveConfig(ConfigTestFile, true) if err2 != nil { t.Errorf("Test failed. TestSaveConfig.SaveConfig, %s", err2.Error()) } @@ -1687,7 +1687,7 @@ func TestCheckRemoteControlConfig(t *testing.T) { func TestCheckConfig(t *testing.T) { var c Config - err := c.LoadConfig(ConfigTestFile) + err := c.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf("Test failed. %s", err) } @@ -1700,24 +1700,24 @@ func TestCheckConfig(t *testing.T) { func TestUpdateConfig(t *testing.T) { var c Config - err := c.LoadConfig(ConfigTestFile) + err := c.LoadConfig(ConfigTestFile, true) if err != nil { t.Errorf("Test failed. %s", err) } newCfg := c - err = c.UpdateConfig(ConfigTestFile, &newCfg) + err = c.UpdateConfig(ConfigTestFile, &newCfg, true) if err != nil { t.Fatalf("Test failed. %s", err) } - err = c.UpdateConfig("//non-existantpath\\", &newCfg) + err = c.UpdateConfig("//non-existantpath\\", &newCfg, true) if err == nil { t.Fatalf("Test failed. Error should of been thrown for invalid path") } newCfg.Currency.Cryptocurrencies = currency.NewCurrenciesFromStringArray([]string{""}) - err = c.UpdateConfig(ConfigTestFile, &newCfg) + err = c.UpdateConfig(ConfigTestFile, &newCfg, true) if err != nil { t.Errorf("Test failed. %s", err) } @@ -1728,14 +1728,14 @@ func TestUpdateConfig(t *testing.T) { func BenchmarkUpdateConfig(b *testing.B) { var c Config - err := c.LoadConfig(ConfigTestFile) + err := c.LoadConfig(ConfigTestFile, true) if err != nil { b.Errorf("Unable to benchmark UpdateConfig(): %s", err) } newCfg := c for i := 0; i < b.N; i++ { - _ = c.UpdateConfig(ConfigTestFile, &newCfg) + _ = c.UpdateConfig(ConfigTestFile, &newCfg, true) } } @@ -1768,7 +1768,7 @@ func TestCheckLoggerConfig(t *testing.T) { t.Error("unexpected result") } - c.LoadConfig(ConfigTestFile) + c.LoadConfig(ConfigTestFile, true) err = c.CheckLoggerConfig() if err != nil { t.Errorf("Failed to create logger with user settings: reason: %v", err) @@ -1779,7 +1779,7 @@ func TestDisableNTPCheck(t *testing.T) { t.Parallel() c := GetConfig() - err := c.LoadConfig(ConfigTestFile) + err := c.LoadConfig(ConfigTestFile, true) if err != nil { t.Fatal(err) } diff --git a/engine/engine.go b/engine/engine.go index 88d0e611..c369be51 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -53,7 +53,7 @@ func New() (*Engine, error) { var b Engine b.Config = &config.Cfg - err := b.Config.LoadConfig("") + err := b.Config.LoadConfig("", false) if err != nil { return nil, fmt.Errorf("failed to load config. Err: %s", err) } @@ -69,9 +69,13 @@ func NewFromSettings(settings *Settings) (*Engine, error) { var b Engine b.Config = &config.Cfg + filePath, err := config.GetFilePath(settings.ConfigFile) + if err != nil { + return nil, err + } - log.Debugf(log.Global, "Loading config file %s..\n", settings.ConfigFile) - err := b.Config.LoadConfig(settings.ConfigFile) + log.Debugf(log.Global, "Loading config file %s..\n", filePath) + err = b.Config.LoadConfig(filePath, settings.EnableDryRun) if err != nil { return nil, fmt.Errorf("failed to load config. Err: %s", err) } @@ -86,7 +90,7 @@ func NewFromSettings(settings *Settings) (*Engine, error) { log.SetupSubLoggers(b.Config.Logging.SubLoggers) } - b.Settings.ConfigFile = settings.ConfigFile + b.Settings.ConfigFile = filePath b.Settings.DataDir = settings.DataDir err = utils.AdjustGoMaxProcs(settings.GoMaxProcs) @@ -433,7 +437,7 @@ func (e *Engine) Stop() { } if !e.Settings.EnableDryRun { - err := e.Config.SaveConfig(e.Settings.ConfigFile) + err := e.Config.SaveConfig(e.Settings.ConfigFile, false) if err != nil { log.Errorln(log.Global, "Unable to save config.") } else { diff --git a/engine/exchange_test.go b/engine/exchange_test.go index 53299eae..7dff0a20 100644 --- a/engine/exchange_test.go +++ b/engine/exchange_test.go @@ -14,7 +14,7 @@ func SetupTest(t *testing.T) { Bot = new(Engine) } Bot.Config = &config.Cfg - err := Bot.Config.LoadConfig("") + err := Bot.Config.LoadConfig("", true) if err != nil { t.Fatalf("Test failed. SetupTest: Failed to load config: %s", err) } diff --git a/engine/helpers_test.go b/engine/helpers_test.go index 8e82911b..085db1f5 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -29,7 +29,7 @@ func SetupTestHelpers(t *testing.T) { Bot = new(Engine) } Bot.Config = &config.Cfg - err := Bot.Config.LoadConfig("../testdata/configtest.json") + err := Bot.Config.LoadConfig("../testdata/configtest.json", true) if err != nil { t.Fatalf("Test failed. SetupTest: Failed to load config: %s", err) } diff --git a/engine/restful_server.go b/engine/restful_server.go index 1c169978..575178e9 100644 --- a/engine/restful_server.go +++ b/engine/restful_server.go @@ -42,7 +42,7 @@ func RESTSaveAllSettings(w http.ResponseWriter, r *http.Request) { RESTfulError(r.Method, err) } // Save change the settings - err = Bot.Config.UpdateConfig(Bot.Settings.ConfigFile, &responseData.Data) + err = Bot.Config.UpdateConfig(Bot.Settings.ConfigFile, &responseData.Data, false) if err != nil { RESTfulError(r.Method, err) } diff --git a/engine/restful_server_test.go b/engine/restful_server_test.go index 8c8479bd..5729dec8 100644 --- a/engine/restful_server_test.go +++ b/engine/restful_server_test.go @@ -13,7 +13,7 @@ import ( func loadConfig(t *testing.T) *config.Config { cfg := config.GetConfig() - err := cfg.LoadConfig("") + err := cfg.LoadConfig("", true) if err != nil { t.Error("Test failed. GetCurrencyConfig LoadConfig error", err) } diff --git a/engine/syncer_test.go b/engine/syncer_test.go index 134f5300..ed1bed2d 100644 --- a/engine/syncer_test.go +++ b/engine/syncer_test.go @@ -14,7 +14,7 @@ func TestNewCurrencyPairSyncer(t *testing.T) { Bot = new(Engine) } Bot.Config = &config.Cfg - err := Bot.Config.LoadConfig("") + err := Bot.Config.LoadConfig("", true) if err != nil { t.Fatalf("Test failed. TestNewExchangeSyncer: Failed to load config: %s", err) } diff --git a/engine/websocket.go b/engine/websocket.go index f42feb29..21613fd4 100644 --- a/engine/websocket.go +++ b/engine/websocket.go @@ -310,7 +310,7 @@ func wsSaveConfig(client *WebsocketClient, data interface{}) error { return err } - err = Bot.Config.UpdateConfig(Bot.Settings.ConfigFile, &cfg) + err = Bot.Config.UpdateConfig(Bot.Settings.ConfigFile, &cfg, Bot.Settings.EnableDryRun) if err != nil { wsResp.Error = err.Error() client.SendWebsocketMessage(wsResp) diff --git a/exchanges/anx/anx_live_test.go b/exchanges/anx/anx_live_test.go index 803916eb..4b0c6b07 100644 --- a/exchanges/anx/anx_live_test.go +++ b/exchanges/anx/anx_live_test.go @@ -17,7 +17,10 @@ var mockTests = false func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatalf("Test Failed - ANX Setup() load config error: %s", err) + } anxConfig, err := cfg.GetExchangeConfig("ANX") if err != nil { log.Fatalf("Test Failed - ANX Setup() init error: %s", err) @@ -26,7 +29,10 @@ func TestMain(m *testing.M) { anxConfig.API.Credentials.Key = apiKey anxConfig.API.Credentials.Secret = apiSecret a.SetDefaults() - a.Setup(anxConfig) + err = a.Setup(anxConfig) + if err != nil { + log.Fatal("Test Failed - ANX setup error", err) + } log.Printf(sharedtestvalues.LiveTesting, a.GetName(), a.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/anx/anx_mock_test.go b/exchanges/anx/anx_mock_test.go index 39d6c479..9ebc8768 100644 --- a/exchanges/anx/anx_mock_test.go +++ b/exchanges/anx/anx_mock_test.go @@ -20,7 +20,10 @@ var mockTests = true func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - ANX load config error", err) + } anxConfig, err := cfg.GetExchangeConfig("ANX") if err != nil { log.Fatal("Test Failed - Mock server error", err) @@ -30,7 +33,10 @@ func TestMain(m *testing.M) { anxConfig.API.Credentials.Key = apiKey anxConfig.API.Credentials.Secret = apiSecret a.SetDefaults() - a.Setup(anxConfig) + err = a.Setup(anxConfig) + if err != nil { + log.Fatal("Test Failed - ANX setup error", err) + } serverDetails, newClient, err := mock.NewVCRServer(mockFile) if err != nil { diff --git a/exchanges/binance/binance_live_test.go b/exchanges/binance/binance_live_test.go index 2d70cfb3..2f4a6f7e 100644 --- a/exchanges/binance/binance_live_test.go +++ b/exchanges/binance/binance_live_test.go @@ -17,7 +17,10 @@ var mockTests = false func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Binance load config error", err) + } binanceConfig, err := cfg.GetExchangeConfig("Binance") if err != nil { log.Fatal("Test Failed - Binance Setup() init error", err) @@ -26,7 +29,10 @@ func TestMain(m *testing.M) { binanceConfig.API.Credentials.Key = apiKey binanceConfig.API.Credentials.Secret = apiSecret b.SetDefaults() - b.Setup(binanceConfig) + err = b.Setup(binanceConfig) + if err != nil { + log.Fatal("Test Failed - Binance setup error", err) + } log.Printf(sharedtestvalues.LiveTesting, b.GetName(), b.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/binance/binance_mock_test.go b/exchanges/binance/binance_mock_test.go index b258744a..b19ecab8 100644 --- a/exchanges/binance/binance_mock_test.go +++ b/exchanges/binance/binance_mock_test.go @@ -20,7 +20,10 @@ var mockTests = true func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Binance load config error", err) + } binanceConfig, err := cfg.GetExchangeConfig("Binance") if err != nil { log.Fatal("Test Failed - Binance Setup() init error", err) @@ -30,7 +33,10 @@ func TestMain(m *testing.M) { binanceConfig.API.Credentials.Key = apiKey binanceConfig.API.Credentials.Secret = apiSecret b.SetDefaults() - b.Setup(binanceConfig) + err = b.Setup(binanceConfig) + if err != nil { + log.Fatal("Test Failed - Binance setup error", err) + } serverDetails, newClient, err := mock.NewVCRServer(mockfile) if err != nil { diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 3659ca83..2b48981b 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -28,12 +28,18 @@ var b Bitfinex func TestSetup(t *testing.T) { b.SetDefaults() cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - Bitfinex load config error", err) + } bfxConfig, err := cfg.GetExchangeConfig("Bitfinex") if err != nil { t.Error("Test Failed - Bitfinex Setup() init error") } - b.Setup(bfxConfig) + err = b.Setup(bfxConfig) + if err != nil { + t.Fatal("Test Failed - Bitfinex setup error", err) + } b.API.Credentials.Key = apiKey b.API.Credentials.Secret = apiSecret if !b.Enabled || b.API.AuthenticatedSupport || diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index 60eb3140..0bfe7848 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -26,7 +26,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - Bitflyer load config error", err) + } bitflyerConfig, err := cfg.GetExchangeConfig("Bitflyer") if err != nil { t.Error("Test Failed - bitflyer Setup() init error") @@ -36,7 +39,10 @@ func TestSetup(t *testing.T) { bitflyerConfig.API.Credentials.Key = apiKey bitflyerConfig.API.Credentials.Secret = apiSecret - b.Setup(bitflyerConfig) + err = b.Setup(bitflyerConfig) + if err != nil { + t.Fatal("Test Failed - Bitflyer setup error", err) + } } func TestGetLatestBlockCA(t *testing.T) { diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index d7701a00..fefef92d 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -24,7 +24,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - Bithumb load config error", err) + } bitConfig, err := cfg.GetExchangeConfig("Bithumb") if err != nil { t.Error("Test Failed - Bithumb Setup() init error") @@ -34,7 +37,10 @@ func TestSetup(t *testing.T) { bitConfig.API.Credentials.Key = apiKey bitConfig.API.Credentials.Secret = apiSecret - b.Setup(bitConfig) + err = b.Setup(bitConfig) + if err != nil { + t.Fatal("Test Failed - Bithumb setup error", err) + } } func TestGetTradablePairs(t *testing.T) { diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index db2c6c9d..ae0b726f 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -30,7 +30,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - Bitmex load config error", err) + } bitmexConfig, err := cfg.GetExchangeConfig("Bitmex") if err != nil { t.Error("Test Failed - Bitmex Setup() init error") @@ -41,7 +44,10 @@ func TestSetup(t *testing.T) { bitmexConfig.API.Credentials.Key = apiKey bitmexConfig.API.Credentials.Secret = apiSecret - b.Setup(bitmexConfig) + err = b.Setup(bitmexConfig) + if err != nil { + t.Fatal("Test Failed - Bitmex setup error", err) + } } func TestStart(t *testing.T) { diff --git a/exchanges/bitstamp/bitstamp_live_test.go b/exchanges/bitstamp/bitstamp_live_test.go index fd5e8df2..0e3c8091 100644 --- a/exchanges/bitstamp/bitstamp_live_test.go +++ b/exchanges/bitstamp/bitstamp_live_test.go @@ -17,7 +17,10 @@ var mockTests = false func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Bitstamp load config error", err) + } bitstampConfig, err := cfg.GetExchangeConfig("Bitstamp") if err != nil { log.Fatal("Test Failed - Bitstamp Setup() init error", err) @@ -27,7 +30,10 @@ func TestMain(m *testing.M) { bitstampConfig.API.Credentials.Secret = apiSecret bitstampConfig.API.Credentials.ClientID = customerID b.SetDefaults() - b.Setup(bitstampConfig) + err = b.Setup(bitstampConfig) + if err != nil { + log.Fatal("Test Failed - Bitstamp setup error", err) + } log.Printf(sharedtestvalues.LiveTesting, b.GetName(), b.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/bitstamp/bitstamp_mock_test.go b/exchanges/bitstamp/bitstamp_mock_test.go index 6075d94d..74d23cc5 100644 --- a/exchanges/bitstamp/bitstamp_mock_test.go +++ b/exchanges/bitstamp/bitstamp_mock_test.go @@ -20,7 +20,10 @@ var mockTests = true func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Bitstamp load config error", err) + } bitstampConfig, err := cfg.GetExchangeConfig("Bitstamp") if err != nil { log.Fatal("Test Failed - Bitstamp Setup() init error", err) @@ -31,7 +34,10 @@ func TestMain(m *testing.M) { bitstampConfig.API.Credentials.Secret = apiSecret bitstampConfig.API.Credentials.ClientID = customerID b.SetDefaults() - b.Setup(bitstampConfig) + err = b.Setup(bitstampConfig) + if err != nil { + log.Fatal("Test Failed - Bitstamp setup error", err) + } serverDetails, newClient, err := mock.NewVCRServer(mockfile) if err != nil { diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 90649763..7ba5e57c 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -27,7 +27,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - Bittrex load config error", err) + } bConfig, err := cfg.GetExchangeConfig("Bittrex") if err != nil { t.Error("Test Failed - Bittrex Setup() init error") @@ -36,7 +39,10 @@ func TestSetup(t *testing.T) { bConfig.API.Credentials.Secret = apiSecret bConfig.API.AuthenticatedSupport = true - b.Setup(bConfig) + err = b.Setup(bConfig) + if err != nil { + t.Fatal("Test Failed - Bittrex setup error", err) + } if !b.IsEnabled() || !b.API.AuthenticatedSupport || b.Verbose || len(b.BaseCurrencies) < 1 { diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index 79d911b2..29d34315 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -25,7 +25,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - BTC Markets load config error", err) + } bConfig, err := cfg.GetExchangeConfig("BTC Markets") if err != nil { t.Error("Test Failed - BTC Markets Setup() init error") @@ -34,7 +37,10 @@ func TestSetup(t *testing.T) { bConfig.API.Credentials.Secret = apiSecret bConfig.API.AuthenticatedSupport = true - b.Setup(bConfig) + err = b.Setup(bConfig) + if err != nil { + t.Fatal("Test Failed - BTC Markets setup error", err) + } } func TestGetMarkets(t *testing.T) { diff --git a/exchanges/btse/btse_test.go b/exchanges/btse/btse_test.go index d4093681..c394ccd5 100644 --- a/exchanges/btse/btse_test.go +++ b/exchanges/btse/btse_test.go @@ -1,6 +1,7 @@ package btse import ( + "log" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -24,7 +25,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - BTSE load config error", err) + } btseConfig, err := cfg.GetExchangeConfig("BTSE") if err != nil { t.Error("Test Failed - BTSE Setup() init error") @@ -34,7 +38,10 @@ func TestSetup(t *testing.T) { btseConfig.API.Credentials.Key = apiKey btseConfig.API.Credentials.Secret = apiSecret - b.Setup(btseConfig) + err = b.Setup(btseConfig) + if err != nil { + t.Fatal("Test Failed - BTSE setup error", err) + } } func TestGetMarkets(t *testing.T) { diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index c4a28389..1b851383 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -30,7 +30,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - coinbasepro load config error", err) + } gdxConfig, err := cfg.GetExchangeConfig("CoinbasePro") if err != nil { t.Error("Test Failed - coinbasepro Setup() init error") @@ -40,7 +43,10 @@ func TestSetup(t *testing.T) { gdxConfig.API.Credentials.ClientID = clientID gdxConfig.API.AuthenticatedSupport = true gdxConfig.API.AuthenticatedWebsocketSupport = true - c.Setup(gdxConfig) + err = c.Setup(gdxConfig) + if err != nil { + t.Fatal("Test Failed - CoinbasePro setup error", err) + } } func TestGetProducts(t *testing.T) { diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 54706e55..35ed6da6 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -29,7 +29,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - Coinut load config error", err) + } bConfig, err := cfg.GetExchangeConfig("COINUT") if err != nil { t.Error("Test Failed - Coinut Setup() init error") @@ -39,7 +42,10 @@ func TestSetup(t *testing.T) { bConfig.API.Credentials.Key = apiKey bConfig.API.Credentials.ClientID = clientID bConfig.Verbose = true - c.Setup(bConfig) + err = c.Setup(bConfig) + if err != nil { + t.Fatal("Test Failed - Coinut setup error", err) + } if !c.IsEnabled() || !c.Verbose || c.Websocket.IsEnabled() || len(c.BaseCurrencies) < 1 { diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index e07c8978..09be04b5 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -110,7 +110,7 @@ func TestSetClientProxyAddress(t *testing.T) { func TestSetAutoPairDefaults(t *testing.T) { cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) + err := cfg.LoadConfig(config.ConfigTestFile, true) if err != nil { t.Fatalf("Test failed. TestSetAutoPairDefaults failed to load config file. Error: %s", err) } @@ -163,7 +163,7 @@ func TestGetLastPairsUpdateTime(t *testing.T) { func TestSetAssetTypes(t *testing.T) { cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) + err := cfg.LoadConfig(config.ConfigTestFile, true) if err != nil { t.Fatalf("Test failed. TestSetAssetTypes failed to load config file. Error: %s", err) } @@ -222,7 +222,7 @@ func TestSetCurrencyPairFormat(t *testing.T) { t.Skip() // TO-DO cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) + err := cfg.LoadConfig(config.ConfigTestFile, true) if err != nil { t.Fatalf("Test failed. TestSetCurrencyPairFormat failed to load config file. Error: %s", err) } @@ -578,7 +578,7 @@ func TestSetPairs(t *testing.T) { t.Skip() // TO-DO cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) + err := cfg.LoadConfig(config.ConfigTestFile, true) if err != nil { t.Fatal("Test failed. TestSetPairs failed to load config") } @@ -621,7 +621,7 @@ func TestSetPairs(t *testing.T) { func TestUpdatePairs(t *testing.T) { cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) + err := cfg.LoadConfig(config.ConfigTestFile, true) if err != nil { t.Fatal("Test failed. TestUpdatePairs failed to load config") } diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index 65fac92e..3288c50b 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -1,6 +1,7 @@ package exmo import ( + "log" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -25,13 +26,19 @@ func TestDefault(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Exmo load config error", err) + } exmoConf, err := cfg.GetExchangeConfig("EXMO") if err != nil { - t.Error("Test Failed - OKCoin Setup() init error") + t.Error("Test Failed - Exmo Setup() init error") } - e.Setup(exmoConf) + err = e.Setup(exmoConf) + if err != nil { + t.Fatal("Test Failed - Exmo setup error", err) + } e.API.AuthenticatedSupport = true e.API.Credentials.Key = APIKey diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 94a7c6ec..cdeb8d19 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -30,7 +30,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - GateIO load config error", err) + } gateioConfig, err := cfg.GetExchangeConfig("GateIO") if err != nil { t.Error("Test Failed - GateIO Setup() init error") @@ -40,7 +43,10 @@ func TestSetup(t *testing.T) { gateioConfig.API.Credentials.Key = apiKey gateioConfig.API.Credentials.Secret = apiSecret - g.Setup(gateioConfig) + err = g.Setup(gateioConfig) + if err != nil { + t.Fatal("Test Failed - GateIO setup error", err) + } } func TestGetSymbols(t *testing.T) { diff --git a/exchanges/gemini/gemini_live_test.go b/exchanges/gemini/gemini_live_test.go index 2e1010c3..52721fff 100644 --- a/exchanges/gemini/gemini_live_test.go +++ b/exchanges/gemini/gemini_live_test.go @@ -17,7 +17,10 @@ var mockTests = false func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Gemini load config error", err) + } geminiConfig, err := cfg.GetExchangeConfig("Gemini") if err != nil { log.Fatal("Test Failed - Gemini Setup() init error", err) @@ -26,7 +29,10 @@ func TestMain(m *testing.M) { geminiConfig.API.Credentials.Key = apiKey geminiConfig.API.Credentials.Secret = apiSecret g.SetDefaults() - g.Setup(geminiConfig) + err = g.Setup(geminiConfig) + if err != nil { + log.Fatal("Test Failed - Gemini setup error", err) + } g.API.Endpoints.URL = geminiSandboxAPIURL log.Printf(sharedtestvalues.LiveTesting, g.GetName(), g.API.Endpoints.URL) os.Exit(m.Run()) diff --git a/exchanges/gemini/gemini_mock_test.go b/exchanges/gemini/gemini_mock_test.go index a334eff8..4fc36d59 100644 --- a/exchanges/gemini/gemini_mock_test.go +++ b/exchanges/gemini/gemini_mock_test.go @@ -20,7 +20,10 @@ var mockTests = true func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Gemini load config error", err) + } geminiConfig, err := cfg.GetExchangeConfig("Gemini") if err != nil { log.Fatal("Test Failed - Mock server error", err) @@ -30,7 +33,10 @@ func TestMain(m *testing.M) { geminiConfig.API.Credentials.Key = apiKey geminiConfig.API.Credentials.Secret = apiSecret g.SetDefaults() - g.Setup(geminiConfig) + err = g.Setup(geminiConfig) + if err != nil { + log.Fatal("Test Failed - Gemini setup error", err) + } serverDetails, newClient, err := mock.NewVCRServer(mockFile) if err != nil { diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index eca699be..ba39081a 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -31,7 +31,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - HitBTC load config error", err) + } hitbtcConfig, err := cfg.GetExchangeConfig("HitBTC") if err != nil { t.Error("Test Failed - HitBTC Setup() init error") @@ -41,7 +44,10 @@ func TestSetup(t *testing.T) { hitbtcConfig.API.Credentials.Key = apiKey hitbtcConfig.API.Credentials.Secret = apiSecret - h.Setup(hitbtcConfig) + err = h.Setup(hitbtcConfig) + if err != nil { + t.Fatal("Test Failed - HitBTC setup error", err) + } } func TestGetOrderbook(t *testing.T) { diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 7f4b401e..644471e4 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "encoding/pem" "io/ioutil" + "log" "strconv" "strings" "testing" @@ -37,7 +38,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Huobi load config error", err) + } hConfig, err := cfg.GetExchangeConfig("Huobi") if err != nil { t.Error("Test Failed - Huobi Setup() init error") @@ -47,7 +51,10 @@ func TestSetup(t *testing.T) { hConfig.API.Credentials.Key = apiKey hConfig.API.Credentials.Secret = apiSecret - h.Setup(hConfig) + err = h.Setup(hConfig) + if err != nil { + t.Fatal("Test Failed - Huobi setup error", err) + } } func setupWsTests(t *testing.T) { diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index 1a088ec0..b7d3dc95 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -26,17 +26,23 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - Itbit load config error", err) + } itbitConfig, err := cfg.GetExchangeConfig("ITBIT") if err != nil { - t.Error("Test Failed - Gemini Setup() init error") + t.Error("Test Failed - Itbit Setup() init error") } itbitConfig.API.AuthenticatedSupport = true itbitConfig.API.Credentials.Key = apiKey itbitConfig.API.Credentials.Secret = apiSecret itbitConfig.API.Credentials.ClientID = clientID - i.Setup(itbitConfig) + err = i.Setup(itbitConfig) + if err != nil { + t.Fatal("Test Failed - Itbit setup error", err) + } } func TestGetTicker(t *testing.T) { diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 2b4ce98f..be1b4954 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -1,6 +1,7 @@ package kraken import ( + "log" "net/http" "testing" @@ -31,7 +32,10 @@ func TestSetDefaults(t *testing.T) { // TestSetup setup func func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Kraken load config error", err) + } krakenConfig, err := cfg.GetExchangeConfig("Kraken") if err != nil { t.Error("Test Failed - kraken Setup() init error", err) @@ -43,7 +47,10 @@ func TestSetup(t *testing.T) { krakenConfig.API.Endpoints.WebsocketURL = k.API.Endpoints.WebsocketURL subscribeToDefaultChannels = false - k.Setup(krakenConfig) + err = k.Setup(krakenConfig) + if err != nil { + t.Fatal("Test Failed - Kraken setup error", err) + } } // TestGetServerTime API endpoint test diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index 0ddee837..8c18c824 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -2,6 +2,7 @@ package lakebtc import ( "fmt" + "log" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -32,7 +33,10 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { if !setupRan { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - LakeBTC load config error", err) + } lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC") if err != nil { t.Error("Test Failed - LakeBTC Setup() init error") @@ -41,7 +45,10 @@ func TestSetup(t *testing.T) { lakebtcConfig.API.Credentials.Key = apiKey lakebtcConfig.API.Credentials.Secret = apiSecret lakebtcConfig.Features.Enabled.Websocket = true - l.Setup(lakebtcConfig) + err = l.Setup(lakebtcConfig) + if err != nil { + t.Fatal("Test Failed - LakeBTC setup error", err) + } l.API.Endpoints.WebsocketURL = lakeBTCWSURL setupRan = true } diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go index 5d16b128..25c75f2c 100644 --- a/exchanges/lbank/lbank_test.go +++ b/exchanges/lbank/lbank_test.go @@ -32,18 +32,21 @@ func TestSetup(t *testing.T) { } l.SetDefaults() cfg := config.GetConfig() - err := cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Errorf("Test Failed - Lbank Setup() init error:, %v", err) + t.Fatalf("Test Failed - Lbank Setup() init error:, %v", err) } lbankConfig, err := cfg.GetExchangeConfig("Lbank") if err != nil { - t.Errorf("Test Failed - Lbank Setup() init error: %v", err) + t.Fatalf("Test Failed - Lbank Setup() init error: %v", err) } lbankConfig.API.AuthenticatedSupport = true lbankConfig.API.Credentials.Secret = testAPISecret lbankConfig.API.Credentials.Key = testAPIKey - l.Setup(lbankConfig) + err = l.Setup(lbankConfig) + if err != nil { + t.Fatal("Test Failed - LBank setup error", err) + } setupRan = true } diff --git a/exchanges/localbitcoins/localbitcoins_live_test.go b/exchanges/localbitcoins/localbitcoins_live_test.go index ac50b975..1fa86101 100644 --- a/exchanges/localbitcoins/localbitcoins_live_test.go +++ b/exchanges/localbitcoins/localbitcoins_live_test.go @@ -17,7 +17,10 @@ var mockTests = false func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - LocalBitcoins load config error", err) + } localbitcoinsConfig, err := cfg.GetExchangeConfig("LocalBitcoins") if err != nil { log.Fatal("Test Failed - LocalBitcoins Setup() init error", err) @@ -26,7 +29,10 @@ func TestMain(m *testing.M) { localbitcoinsConfig.API.Credentials.Key = apiKey localbitcoinsConfig.API.Credentials.Secret = apiSecret l.SetDefaults() - l.Setup(localbitcoinsConfig) + err = l.Setup(localbitcoinsConfig) + if err != nil { + log.Fatal("Test Failed - Localbitcoins setup error", err) + } log.Printf(sharedtestvalues.LiveTesting, l.GetName(), l.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/localbitcoins/localbitcoins_mock_test.go b/exchanges/localbitcoins/localbitcoins_mock_test.go index 996dd56e..a09aa423 100644 --- a/exchanges/localbitcoins/localbitcoins_mock_test.go +++ b/exchanges/localbitcoins/localbitcoins_mock_test.go @@ -20,7 +20,10 @@ var mockTests = true func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Localbitcoins load config error", err) + } localbitcoinsConfig, err := cfg.GetExchangeConfig("LocalBitcoins") if err != nil { log.Fatal("Test Failed - Localbitcoins Setup() init error", err) @@ -30,7 +33,10 @@ func TestMain(m *testing.M) { localbitcoinsConfig.API.Credentials.Key = apiKey localbitcoinsConfig.API.Credentials.Secret = apiSecret l.SetDefaults() - l.Setup(localbitcoinsConfig) + err = l.Setup(localbitcoinsConfig) + if err != nil { + log.Fatal("Test Failed - Localbitcoins setup error", err) + } serverDetails, newClient, err := mock.NewVCRServer(mockfile) if err != nil { diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 6b379782..8c35ed07 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -62,7 +62,10 @@ func TestSetup(t *testing.T) { } o.ExchangeName = OKGroupExchange cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - Okcoin load config error", err) + } okcoinConfig, err := cfg.GetExchangeConfig(OKGroupExchange) if err != nil { t.Fatalf("Test Failed - %v Setup() init error", OKGroupExchange) @@ -77,7 +80,10 @@ func TestSetup(t *testing.T) { okcoinConfig.API.Credentials.Secret = apiSecret okcoinConfig.API.Credentials.ClientID = passphrase okcoinConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL - o.Setup(okcoinConfig) + err = o.Setup(okcoinConfig) + if err != nil { + t.Fatal("Test Failed - OKCoin setup error", err) + } testSetupRan = true o.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() o.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index a8b5d911..506ec7ab 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -63,7 +63,10 @@ func TestSetup(t *testing.T) { } o.ExchangeName = OKGroupExchange cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - Okex load config error", err) + } okexConfig, err := cfg.GetExchangeConfig(OKGroupExchange) if err != nil { @@ -78,7 +81,10 @@ func TestSetup(t *testing.T) { okexConfig.API.Credentials.Secret = apiSecret okexConfig.API.Credentials.ClientID = passphrase okexConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL - o.Setup(okexConfig) + err = o.Setup(okexConfig) + if err != nil { + t.Fatal("Test Failed - Okex setup error", err) + } testSetupRan = true o.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() o.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() diff --git a/exchanges/poloniex/poloniex_live_test.go b/exchanges/poloniex/poloniex_live_test.go index 915f708c..7eb685c9 100644 --- a/exchanges/poloniex/poloniex_live_test.go +++ b/exchanges/poloniex/poloniex_live_test.go @@ -17,7 +17,10 @@ var mockTests = false func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Poloniex load config error", err) + } poloniexConfig, err := cfg.GetExchangeConfig("Poloniex") if err != nil { log.Fatal("Test Failed - Poloniex Setup() init error", err) @@ -26,7 +29,10 @@ func TestMain(m *testing.M) { poloniexConfig.API.Credentials.Key = apiKey poloniexConfig.API.Credentials.Secret = apiSecret p.SetDefaults() - p.Setup(poloniexConfig) + err = p.Setup(poloniexConfig) + if err != nil { + log.Fatal("Test Failed - Poloniex setup error", err) + } log.Printf(sharedtestvalues.LiveTesting, p.GetName(), p.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/poloniex/poloniex_mock_test.go b/exchanges/poloniex/poloniex_mock_test.go index 46c77719..96748300 100644 --- a/exchanges/poloniex/poloniex_mock_test.go +++ b/exchanges/poloniex/poloniex_mock_test.go @@ -20,7 +20,10 @@ var mockTests = true func TestMain(m *testing.M) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("Test Failed - Poloniex load config error", err) + } poloniexConfig, err := cfg.GetExchangeConfig("Poloniex") if err != nil { log.Fatal("Test Failed - Poloniex Setup() init error", err) @@ -30,7 +33,10 @@ func TestMain(m *testing.M) { poloniexConfig.API.Credentials.Key = apiKey poloniexConfig.API.Credentials.Secret = apiSecret p.SetDefaults() - p.Setup(poloniexConfig) + err = p.Setup(poloniexConfig) + if err != nil { + log.Fatal("Test Failed - Poloniex setup error", err) + } serverDetails, newClient, err := mock.NewVCRServer(mockfile) if err != nil { diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index 9ae869f7..20193af2 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -27,16 +27,22 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { yobitConfig := config.GetConfig() - yobitConfig.LoadConfig("../../testdata/configtest.json") + err := yobitConfig.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - Yobit load config error", err) + } conf, err := yobitConfig.GetExchangeConfig("Yobit") if err != nil { - t.Error("Test Failed - Yobit init error") + t.Fatal("Test Failed - Yobit init error", err) } conf.API.Credentials.Key = apiKey conf.API.Credentials.Secret = apiSecret conf.API.AuthenticatedSupport = true - y.Setup(conf) + err = y.Setup(conf) + if err != nil { + t.Fatal("Test Failed - Yobit setup error", err) + } } func TestFetchTradablePairs(t *testing.T) { diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index 2ad77c9f..1f0004cb 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -29,17 +29,23 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + t.Fatal("Test Failed - ZB load config error", err) + } zbConfig, err := cfg.GetExchangeConfig("ZB") if err != nil { - t.Error("Test Failed - ZB Setup() init error") + t.Fatal("Test Failed - ZB Setup() init error", err) } zbConfig.API.AuthenticatedSupport = true zbConfig.API.AuthenticatedWebsocketSupport = true zbConfig.API.Credentials.Key = apiKey zbConfig.API.Credentials.Secret = apiSecret - z.Setup(zbConfig) + err = z.Setup(zbConfig) + if err != nil { + t.Fatal("Test Failed - ZB setup error", err) + } } func setupWsAuth(t *testing.T) { diff --git a/main.go b/main.go index 4638fa38..e2f2e77f 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" mg "github.com/thrasher-corp/gocryptotrader/database/migration" "github.com/thrasher-corp/gocryptotrader/engine" @@ -18,18 +17,12 @@ import ( ) func main() { - defaultPath, err := config.GetFilePath("") - if err != nil { - log.Errorln(log.Global, err) - os.Exit(1) - } - // Handle flags var settings engine.Settings versionFlag := flag.Bool("version", false, "retrieves current GoCryptoTrader version") // Core settings - flag.StringVar(&settings.ConfigFile, "config", defaultPath, "config file to load") + flag.StringVar(&settings.ConfigFile, "config", "", "config file to load") flag.StringVar(&settings.DataDir, "datadir", common.GetDefaultDataDir(runtime.GOOS), "default data directory for GoCryptoTrader files") flag.StringVar(&settings.MigrationDir, "migrationdir", mg.MigrationDir, "override migration folder") flag.IntVar(&settings.GoMaxProcs, "gomaxprocs", runtime.NumCPU(), "sets the runtime GOMAXPROCS value") @@ -92,6 +85,7 @@ func main() { fmt.Println(core.Banner) fmt.Println(core.Version(false)) + var err error engine.Bot, err = engine.NewFromSettings(&settings) if engine.Bot == nil || err != nil { log.Errorf(log.Global, "Unable to initialise bot engine. Error: %s\n", err) From 6951b4c34216245555592d927b074671a4073dfd Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 30 Sep 2019 17:40:51 +1000 Subject: [PATCH 44/71] Start expanding test coverage for exchange.go and fix minor issues --- engine/syncer.go | 6 +- engine/timekeeper.go | 8 +- exchanges/exchange_test.go | 335 ++++++++++++++++++++++++++++++++----- signaler/signaler.go | 1 - 4 files changed, 300 insertions(+), 50 deletions(-) diff --git a/engine/syncer.go b/engine/syncer.go index 8490eb0f..3cb15be2 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -228,9 +228,9 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair e.CurrencyPairs[x].Ticker.HaveData = true e.CurrencyPairs[x].Ticker.IsProcessing = false if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { + removedCounter++ log.Debugf(log.SyncMgr, "%s ticker sync complete %v [%d/%d].\n", exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter) - removedCounter++ e.initSyncWG.Done() } @@ -243,9 +243,9 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair e.CurrencyPairs[x].Orderbook.HaveData = true e.CurrencyPairs[x].Orderbook.IsProcessing = false if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { + removedCounter++ log.Debugf(log.SyncMgr, "%s orderbook sync complete %v [%d/%d].\n", exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter) - removedCounter++ e.initSyncWG.Done() } @@ -258,9 +258,9 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair e.CurrencyPairs[x].Trade.HaveData = true e.CurrencyPairs[x].Trade.IsProcessing = false if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { + removedCounter++ log.Debugf(log.SyncMgr, "%s trade sync complete %v [%d/%d].\n", exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter) - removedCounter++ e.initSyncWG.Done() } } diff --git a/engine/timekeeper.go b/engine/timekeeper.go index bef2ecb9..8956ff6d 100644 --- a/engine/timekeeper.go +++ b/engine/timekeeper.go @@ -42,6 +42,11 @@ func (n *ntpManager) Start() (err error) { } }() + if Bot.Config.NTPClient.Level == -1 { + err = errors.New("NTP client disabled") + return + } + log.Debugln(log.TimeMgr, "NTP manager starting...") if Bot.Config.NTPClient.Level == 0 && *Bot.Config.Logging.Enabled { // Initial NTP check (prompts user on how we should proceed) @@ -101,9 +106,6 @@ func (n *ntpManager) run() { return case <-t.C: n.processTime() - if Bot.Config.NTPClient.Level == 0 { - close(n.shutdown) - } } } } diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 09be04b5..4ef7cde1 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -20,6 +20,8 @@ const ( ) func TestSupportsRESTTickerBatchUpdates(t *testing.T) { + t.Parallel() + b := Base{ Name: "RAWR", Features: Features{ @@ -38,6 +40,8 @@ func TestSupportsRESTTickerBatchUpdates(t *testing.T) { } func TestHTTPClient(t *testing.T) { + t.Parallel() + r := Base{Name: "asdf"} r.SetHTTPClientTimeout(time.Second * 5) @@ -77,18 +81,26 @@ func TestHTTPClient(t *testing.T) { if b.GetHTTPClient().Timeout != time.Second*10 { t.Fatalf("Test failed. TestHTTPClient unexpected value") } + + b.SetHTTPClientUserAgent("epicUserAgent") + if !strings.Contains(b.GetHTTPClientUserAgent(), "epicUserAgent") { + t.Error("user agent not set properly") + } } func TestSetClientProxyAddress(t *testing.T) { - requester := request.New("testicles", + t.Parallel() + + requester := request.New("rawr", &request.RateLimit{}, &request.RateLimit{}, &http.Client{}) - newBase := Base{Name: "Testicles", Requester: requester} + newBase := Base{ + Name: "rawr", + Requester: requester} newBase.Websocket = wshandler.New() - err := newBase.SetClientProxyAddress(":invalid") if err == nil { t.Error("Test failed. SetClientProxyAddress parsed invalid URL") @@ -142,16 +154,25 @@ func TestSetAutoPairDefaults(t *testing.T) { } func TestSupportsAutoPairUpdates(t *testing.T) { + t.Parallel() + b := Base{ Name: "TESTNAME", } if b.SupportsAutoPairUpdates() { - t.Fatal("Test failed. TestSupportsAutoPairUpdates Incorrect value") + t.Error("exchange shouldn't support auto pair updates") + } + + b.Features.Supports.RESTCapabilities.AutoPairUpdates = true + if !b.SupportsAutoPairUpdates() { + t.Error("exchange should support auto pair updates") } } func TestGetLastPairsUpdateTime(t *testing.T) { + t.Parallel() + testTime := time.Now().Unix() var b Base b.CurrencyPairs.LastUpdated = testTime @@ -202,6 +223,8 @@ func TestSetAssetTypes(t *testing.T) { } func TestGetAssetTypes(t *testing.T) { + t.Parallel() + testExchange := Base{ CurrencyPairs: currency.PairsManager{ AssetTypes: asset.Items{ @@ -266,6 +289,8 @@ func TestSetCurrencyPairFormat(t *testing.T) { // TestGetAuthenticatedAPISupport logic test func TestGetAuthenticatedAPISupport(t *testing.T) { + t.Parallel() + base := Base{ API: API{ AuthenticatedSupport: true, @@ -289,6 +314,8 @@ func TestGetAuthenticatedAPISupport(t *testing.T) { } func TestGetName(t *testing.T) { + t.Parallel() + GetName := Base{ Name: "TESTNAME", } @@ -300,6 +327,8 @@ func TestGetName(t *testing.T) { } func TestGetEnabledPairs(t *testing.T) { + t.Parallel() + b := Base{ Name: "TESTNAME", } @@ -374,6 +403,8 @@ func TestGetEnabledPairs(t *testing.T) { } func TestGetAvailablePairs(t *testing.T) { + t.Parallel() + b := Base{ Name: "TESTNAME", } @@ -448,6 +479,8 @@ func TestGetAvailablePairs(t *testing.T) { } func TestSupportsPair(t *testing.T) { + t.Parallel() + b := Base{ Name: "TESTNAME", } @@ -482,6 +515,8 @@ func TestSupportsPair(t *testing.T) { } func TestFormatExchangeCurrencies(t *testing.T) { + t.Parallel() + e := Base{ CurrencyPairs: currency.PairsManager{ UseGlobalFormat: true, @@ -509,14 +544,20 @@ func TestFormatExchangeCurrencies(t *testing.T) { t.Errorf("Test failed - Exchange TestFormatExchangeCurrencies error %s", err) } expected := "btc~usd^ltc~btc" - if actual != expected { t.Errorf("Test failed - Exchange TestFormatExchangeCurrencies %s != %s", actual, expected) } + + _, err = e.FormatExchangeCurrencies(nil, asset.Spot) + if err == nil { + t.Error("nil pairs should return an error") + } } func TestFormatExchangeCurrency(t *testing.T) { + t.Parallel() + var b Base b.CurrencyPairs.UseGlobalFormat = true b.CurrencyPairs.RequestFormat = ¤cy.PairFormat{ @@ -535,6 +576,8 @@ func TestFormatExchangeCurrency(t *testing.T) { } func TestSetEnabled(t *testing.T) { + t.Parallel() + SetEnabled := Base{ Name: "TESTNAME", Enabled: false, @@ -547,6 +590,8 @@ func TestSetEnabled(t *testing.T) { } func TestIsEnabled(t *testing.T) { + t.Parallel() + IsEnabled := Base{ Name: "TESTNAME", Enabled: false, @@ -559,6 +604,8 @@ func TestIsEnabled(t *testing.T) { // TestSetAPIKeys logic test func TestSetAPIKeys(t *testing.T) { + t.Parallel() + SetAPIKeys := Base{ Name: "TESTNAME", Enabled: false, @@ -574,48 +621,117 @@ func TestSetAPIKeys(t *testing.T) { } } +func TestAllowAuthenticatedRequest(t *testing.T) { + t.Parallel() + + b := Base{ + SkipAuthCheck: true, + } + + if r := b.AllowAuthenticatedRequest(); !r { + t.Error("skip auth check should allow authenticated requests") + } + + b.SkipAuthCheck = false + b.API.CredentialsValidator.RequiresKey = true + if r := b.AllowAuthenticatedRequest(); r { + t.Error("should fail with an empty key") + } + +} + +func TestValidateAPICredentials(t *testing.T) { + t.Parallel() + + var b Base + type tester struct { + Key string + Secret string + ClientID string + PEMKey string + RequiresPEM bool + RequiresKey bool + RequiresSecret bool + RequiresClientID bool + RequiresBase64DecodeSecret bool + Expected bool + Result bool + } + + tests := []tester{ + // test key + {RequiresKey: true}, + {RequiresKey: true, Key: "k3y", Expected: true}, + // test secret + {RequiresSecret: true}, + {RequiresSecret: true, Secret: "s3cr3t", Expected: true}, + // test pem + {RequiresPEM: true}, + {RequiresPEM: true, PEMKey: "p3mK3y", Expected: true}, + // test clientID + {RequiresClientID: true}, + {RequiresClientID: true, ClientID: "cli3nt1D", Expected: true}, + // test requires base64 decode secret + {RequiresBase64DecodeSecret: true, RequiresSecret: true}, + {RequiresBase64DecodeSecret: true, Secret: "%%", Expected: false}, + {RequiresBase64DecodeSecret: true, Secret: "aGVsbG8gd29ybGQ=", Expected: true}, + } + + for x := range tests { + setupBase := func(b *Base, tData tester) { + b.API.Credentials.Key = tData.Key + b.API.Credentials.Secret = tData.Secret + b.API.Credentials.ClientID = tData.ClientID + b.API.Credentials.PEMKey = tData.PEMKey + b.API.CredentialsValidator.RequiresKey = tData.RequiresKey + b.API.CredentialsValidator.RequiresSecret = tData.RequiresSecret + b.API.CredentialsValidator.RequiresPEM = tData.RequiresPEM + b.API.CredentialsValidator.RequiresClientID = tData.RequiresClientID + b.API.CredentialsValidator.RequiresBase64DecodeSecret = tData.RequiresBase64DecodeSecret + } + + setupBase(&b, tests[x]) + if r := b.ValidateAPICredentials(); r != tests[x].Expected { + t.Errorf("Test %d: expected: %v: got %v", x, tests[x].Expected, r) + } + } +} + func TestSetPairs(t *testing.T) { - t.Skip() - // TO-DO - cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile, true) + t.Parallel() + + b := Base{ + CurrencyPairs: currency.PairsManager{ + UseGlobalFormat: true, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + }, + Config: &config.ExchangeConfig{ + CurrencyPairs: ¤cy.PairsManager{ + UseGlobalFormat: true, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + Pairs: map[asset.Item]*currency.PairStore{}, + }, + }, + } + + if err := b.SetPairs(nil, asset.Spot, true); err == nil { + t.Error("nil pairs should throw an error") + } + + pairs := currency.Pairs{ + currency.NewPair(currency.BTC, currency.USD), + } + err := b.SetPairs(pairs, asset.Spot, true) if err != nil { - t.Fatal("Test failed. TestSetPairs failed to load config") + t.Error(err) } - anxCfg, err := cfg.GetExchangeConfig(defaultTestExchange) - if err != nil { - t.Fatal("Test failed. TestSetPairs failed to load config") - } - - newPair := currency.NewPairDelimiter("ETH_USDT", "_") - assetType := asset.Spot - - var UAC Base - UAC.Name = "ANX" - UAC.Config = anxCfg - err = UAC.SetupDefaults(anxCfg) - if err != nil { - t.Fatalf("Test failed. TestSetPairs unable to set defaults: %s", err) - } - - err = UAC.SetPairs([]currency.Pair{newPair}, asset.Spot, true) - if err != nil { - t.Fatalf("Test failed. TestSetPairs failed to set currencies: %s", err) - } - - if !UAC.GetEnabledPairs(assetType).Contains(newPair, true) { - t.Fatal("Test failed. TestSetPairs failed to set currencies") - } - - UAC.SetPairs([]currency.Pair{newPair}, asset.Spot, false) - if !UAC.GetAvailablePairs(assetType).Contains(newPair, true) { - t.Fatal("Test failed. TestSetPairs failed to set currencies") - } - - err = UAC.SetPairs(nil, asset.Spot, false) - if err == nil { - t.Fatal("Test failed. TestSetPairs should return an error when attempting to set an empty pairs array") + if p := b.GetEnabledPairs(asset.Spot); len(p) != 1 { + t.Error("pairs shouldn't be nil") } } @@ -686,9 +802,29 @@ func TestUpdatePairs(t *testing.T) { if err == nil { t.Errorf("Test failed - empty available pairs should return an error") } + + // Test empty pair + p := currency.NewPairDelimiter(defaultTestCurrencyPair, "-") + pairs := currency.Pairs{ + currency.Pair{}, + p, + } + err = UAC.UpdatePairs(pairs, asset.Spot, true, true) + if err != nil { + t.Errorf("Test Failed - Forced Exchange UpdatePairs() error: %s", err) + } + UAC.CurrencyPairs.UseGlobalFormat = true + UAC.CurrencyPairs.ConfigFormat = ¤cy.PairFormat{ + Delimiter: "-", + } + if !UAC.GetEnabledPairs(asset.Spot).Contains(p, true) { + t.Fatal("expected currency pair not found") + } } func TestSetAPIURL(t *testing.T) { + t.Parallel() + testURL := "https://api.something.com" testURLSecondary := "https://api.somethingelse.com" testURLDefault := "https://api.defaultsomething.com" @@ -751,7 +887,52 @@ func BenchmarkSetAPIURL(b *testing.B) { } } +func TestSupportsWebsocket(t *testing.T) { + t.Parallel() + + var b Base + if b.SupportsWebsocket() { + t.Error("exchange doesn't support websocket") + } + + b.Features.Supports.Websocket = true + if !b.SupportsWebsocket() { + t.Error("exchange supports websocket") + } +} + +func TestSupportsREST(t *testing.T) { + t.Parallel() + + var b Base + if b.SupportsREST() { + t.Error("exchange doesn't support REST") + } + + b.Features.Supports.REST = true + if !b.SupportsREST() { + t.Error("exchange supports REST") + } +} + +func TestIsWebsocketEnabled(t *testing.T) { + t.Parallel() + + var b Base + if b.IsWebsocketEnabled() { + t.Error("exchange doesn't support websocket") + } + + b.Websocket = wshandler.New() + b.Websocket.Setup(nil, nil, nil, "", true, false, "", "", false) + if !b.IsWebsocketEnabled() { + t.Error("websocket should be enabled") + } +} + func TestSupportsWithdrawPermissions(t *testing.T) { + t.Parallel() + UAC := Base{Name: defaultTestExchange} UAC.Features.Supports.WithdrawPermissions = AutoWithdrawCrypto | AutoWithdrawCryptoWithAPIPermission withdrawPermissions := UAC.SupportsWithdrawPermissions(AutoWithdrawCrypto) @@ -782,6 +963,8 @@ func TestSupportsWithdrawPermissions(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { + t.Parallel() + UAC := Base{Name: "ANX"} UAC.Features.Supports.WithdrawPermissions = AutoWithdrawCrypto | AutoWithdrawCryptoWithAPIPermission | @@ -817,6 +1000,8 @@ func TestFormatWithdrawPermissions(t *testing.T) { } func TestOrderSides(t *testing.T) { + t.Parallel() + var os = BuyOrderSide if os.ToString() != "BUY" { t.Errorf("test failed - unexpected string %s", os.ToString()) @@ -828,6 +1013,8 @@ func TestOrderSides(t *testing.T) { } func TestOrderTypes(t *testing.T) { + t.Parallel() + var ot OrderType = "Mo'Money" if ot.ToString() != "Mo'Money" { @@ -840,6 +1027,8 @@ func TestOrderTypes(t *testing.T) { } func TestFilterOrdersByType(t *testing.T) { + t.Parallel() + var orders = []OrderDetail{ { OrderType: ImmediateOrCancelOrderType, @@ -866,6 +1055,8 @@ func TestFilterOrdersByType(t *testing.T) { } func TestFilterOrdersBySide(t *testing.T) { + t.Parallel() + var orders = []OrderDetail{ { OrderSide: BuyOrderSide, @@ -893,6 +1084,8 @@ func TestFilterOrdersBySide(t *testing.T) { } func TestFilterOrdersByTickRange(t *testing.T) { + t.Parallel() + var orders = []OrderDetail{ { OrderDate: time.Unix(100, 0), @@ -927,6 +1120,8 @@ func TestFilterOrdersByTickRange(t *testing.T) { } func TestFilterOrdersByCurrencies(t *testing.T) { + t.Parallel() + var orders = []OrderDetail{ { CurrencyPair: currency.NewPair(currency.BTC, currency.USD), @@ -968,6 +1163,8 @@ func TestFilterOrdersByCurrencies(t *testing.T) { } func TestSortOrdersByPrice(t *testing.T) { + t.Parallel() + orders := []OrderDetail{ { Price: 100, @@ -990,6 +1187,8 @@ func TestSortOrdersByPrice(t *testing.T) { } func TestSortOrdersByDate(t *testing.T) { + t.Parallel() + orders := []OrderDetail{ { OrderDate: time.Unix(0, 0), @@ -1016,6 +1215,8 @@ func TestSortOrdersByDate(t *testing.T) { } func TestSortOrdersByCurrency(t *testing.T) { + t.Parallel() + orders := []OrderDetail{ { CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), @@ -1056,6 +1257,8 @@ func TestSortOrdersByCurrency(t *testing.T) { } func TestSortOrdersByOrderSide(t *testing.T) { + t.Parallel() + orders := []OrderDetail{ { OrderSide: BuyOrderSide, @@ -1084,6 +1287,8 @@ func TestSortOrdersByOrderSide(t *testing.T) { } func TestSortOrdersByOrderType(t *testing.T) { + t.Parallel() + orders := []OrderDetail{ { OrderType: MarketOrderType, @@ -1106,3 +1311,47 @@ func TestSortOrdersByOrderType(t *testing.T) { t.Errorf("Test failed. Expected: '%v', received: '%v'", TrailingStopOrderType, orders[0].OrderType) } } + +func TestIsAssetTypeSupported(t *testing.T) { + t.Parallel() + + var b Base + b.CurrencyPairs.AssetTypes = asset.Items{ + asset.Spot, + } + + if !b.IsAssetTypeSupported(asset.Spot) { + t.Error("spot should be supported") + } + if b.IsAssetTypeSupported(asset.Index) { + t.Error("index shouldn't be supported") + } +} + +func TestPrintEnabledPairs(t *testing.T) { + t.Parallel() + + var b Base + b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) + b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + Enabled: currency.Pairs{ + currency.NewPair(currency.BTC, currency.USD), + }, + } + + b.PrintEnabledPairs() +} +func TestGetBase(t *testing.T) { + t.Parallel() + + b := Base{ + Name: "MEOW", + } + + p := b.GetBase() + p.Name = "rawr" + + if b.Name != "rawr" { + t.Error("name should be rawr") + } +} diff --git a/signaler/signaler.go b/signaler/signaler.go index d2b70a07..036efde8 100644 --- a/signaler/signaler.go +++ b/signaler/signaler.go @@ -15,7 +15,6 @@ func init() { os.Interrupt, os.Kill, syscall.SIGTERM, - syscall.SIGQUIT, syscall.SIGABRT, } signal.Notify(s, sigs...) From 580190638cae18167648ded12b92ed8b3b6f0251 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 1 Oct 2019 16:12:49 +1000 Subject: [PATCH 45/71] Increase exchange.go test coverage to 100% --- exchanges/exchange.go | 12 +- exchanges/exchange_test.go | 455 +++++++++++++++++++++++++++++++------ 2 files changed, 386 insertions(+), 81 deletions(-) diff --git a/exchanges/exchange.go b/exchanges/exchange.go index d3b8d9a8..59f3a9f8 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -79,11 +79,9 @@ func (e *Base) SetClientProxyAddress(addr string) error { err) } - err = e.Requester.SetProxy(proxy) - if err != nil { - return fmt.Errorf("exchange.go - setting proxy address error %s", - err) - } + // No needs to check err here as the only err condition is an empty + // string which is already checked above + e.Requester.SetProxy(proxy) if e.Websocket != nil { err = e.Websocket.SetProxyAddress(addr) @@ -253,12 +251,10 @@ func (e *Base) SetCurrencyPairFormat() { } e.Config.CurrencyPairs.UseGlobalFormat = e.CurrencyPairs.UseGlobalFormat - if e.Config.CurrencyPairs.UseGlobalFormat { if e.Config.CurrencyPairs.ConfigFormat == nil { e.Config.CurrencyPairs.ConfigFormat = e.CurrencyPairs.ConfigFormat } - if e.Config.CurrencyPairs.RequestFormat == nil { e.Config.CurrencyPairs.RequestFormat = e.CurrencyPairs.RequestFormat } @@ -268,7 +264,6 @@ func (e *Base) SetCurrencyPairFormat() { if e.Config.CurrencyPairs.ConfigFormat != nil { e.Config.CurrencyPairs.ConfigFormat = nil } - if e.Config.CurrencyPairs.RequestFormat != nil { e.Config.CurrencyPairs.RequestFormat = nil } @@ -401,6 +396,7 @@ func (e *Base) SetAPIKeys(apiKey, apiSecret, clientID string) { e.API.AuthenticatedSupport = false e.API.AuthenticatedWebsocketSupport = false log.Warnf(log.ExchangeSys, warningBase64DecryptSecretKeyFailed, e.Name) + return } e.API.Credentials.Secret = string(result) } else { diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 4ef7cde1..96074091 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -115,11 +115,122 @@ func TestSetClientProxyAddress(t *testing.T) { t.Error("Test failed. SetClientProxyAddress error", err) } + // calling this again will cause the ws check to fail + err = newBase.SetClientProxyAddress("www.valid.com") + if err == nil { + t.Error("trying to set the same proxy addr should thrown an err for ws") + } + if newBase.Websocket.GetProxyAddress() != "www.valid.com" { t.Error("Test failed. SetClientProxyAddress error", err) } } +func TestSetFeatureDefaults(t *testing.T) { + t.Parallel() + + // Test nil features with basic support capabilities + b := Base{ + Config: &config.ExchangeConfig{ + CurrencyPairs: ¤cy.PairsManager{}, + }, + Features: Features{ + Supports: FeaturesSupported{ + REST: true, + RESTCapabilities: ProtocolFeatures{ + TickerBatching: true, + }, + Websocket: true, + }, + }, + } + b.SetFeatureDefaults() + if !b.Config.Features.Supports.REST && b.Config.CurrencyPairs.LastUpdated == 0 { + t.Error("incorrect values") + } + + // Test upgrade when SupportsAutoPairUpdates is enabled + bptr := func(a bool) *bool { return &a } + b.Config.Features = nil + b.Config.SupportsAutoPairUpdates = bptr(true) + b.SetFeatureDefaults() + if !b.Config.Features.Supports.RESTCapabilities.AutoPairUpdates && + !b.Features.Enabled.AutoPairUpdates { + t.Error("incorrect values") + } + + // Test non migrated features config + b.Config.Features.Supports.REST = false + b.Config.Features.Supports.RESTCapabilities.TickerBatching = false + b.Config.Features.Supports.Websocket = false + b.SetFeatureDefaults() + + if !b.Features.Supports.REST || + !b.Features.Supports.RESTCapabilities.TickerBatching || + !b.Features.Supports.Websocket { + t.Error("incorrect values") + } +} + +func TestSetAPICredentialDefaults(t *testing.T) { + t.Parallel() + + b := Base{ + Config: &config.ExchangeConfig{}, + } + b.API.CredentialsValidator.RequiresKey = true + b.API.CredentialsValidator.RequiresSecret = true + b.API.CredentialsValidator.RequiresBase64DecodeSecret = true + b.API.CredentialsValidator.RequiresClientID = true + b.API.CredentialsValidator.RequiresPEM = true + b.SetAPICredentialDefaults() + + if !b.Config.API.CredentialsValidator.RequiresKey || + !b.Config.API.CredentialsValidator.RequiresSecret || + !b.Config.API.CredentialsValidator.RequiresBase64DecodeSecret || + !b.Config.API.CredentialsValidator.RequiresClientID || + !b.Config.API.CredentialsValidator.RequiresPEM { + t.Error("incorrect values") + } +} + +func TestSetHTTPRateLimiter(t *testing.T) { + t.Parallel() + + b := Base{ + Config: &config.ExchangeConfig{}, + Requester: request.New("asdf", + request.NewRateLimit(time.Second*5, 10), + request.NewRateLimit(time.Second*10, 15), + common.NewHTTPClientWithTimeout(DefaultHTTPTimeout)), + } + b.SetHTTPRateLimiter() + if b.Requester.GetRateLimit(true).Duration.String() != "5s" && + b.Requester.GetRateLimit(true).Rate != 10 && + b.Requester.GetRateLimit(false).Duration.String() != "10s" && + b.Requester.GetRateLimit(false).Rate != 15 { + t.Error("rate limiter not set properly") + } + + b.Config.HTTPRateLimiter = &config.HTTPRateLimitConfig{ + Unauthenticated: config.HTTPRateConfig{ + Duration: time.Second * 100, + Rate: 100, + }, + Authenticated: config.HTTPRateConfig{ + Duration: time.Second * 110, + Rate: 150, + }, + } + b.SetHTTPRateLimiter() + if b.Requester.GetRateLimit(true).Duration.String() != "1m50s" && + b.Requester.GetRateLimit(true).Rate != 150 && + b.Requester.GetRateLimit(false).Duration.String() != "1m40s" && + b.Requester.GetRateLimit(false).Rate != 100 { + t.Error("rate limiter not set properly") + } +} + func TestSetAutoPairDefaults(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig(config.ConfigTestFile, true) @@ -183,42 +294,33 @@ func TestGetLastPairsUpdateTime(t *testing.T) { } func TestSetAssetTypes(t *testing.T) { - cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile, true) - if err != nil { - t.Fatalf("Test failed. TestSetAssetTypes failed to load config file. Error: %s", err) - } + t.Parallel() b := Base{ - Name: "TESTNAME", + Config: &config.ExchangeConfig{ + CurrencyPairs: ¤cy.PairsManager{}, + }, + CurrencyPairs: currency.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + asset.Binary, + asset.Futures, + }, + }, } - - b.Name = "ANX" - b.CurrencyPairs.AssetTypes = asset.Items{asset.Spot} - exch, err := cfg.GetExchangeConfig(b.Name) - if err != nil { - t.Fatalf("Test failed. TestSetAssetTypes load config failed. Error %s", err) - } - - exch.CurrencyPairs.AssetTypes = asset.New("") - 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) - } - b.Config = exch - - if exch.CurrencyPairs.AssetTypes.JoinToString(",") != "" { - t.Fatal("Test failed. TestSetAssetTypes assetTypes != ''") - } - b.SetAssetTypes() - if !common.StringDataCompare(b.CurrencyPairs.AssetTypes.Strings(), asset.Spot.String()) { - t.Fatal("Test failed. TestSetAssetTypes assetTypes is not set") + if len(b.GetAssetTypes()) != 3 { + t.Error("incorrect assets len") + } + + b.CurrencyPairs.AssetTypes = append(b.CurrencyPairs.AssetTypes, + asset.PerpetualSwap) + b.Config.CurrencyPairs.AssetTypes = asset.Items{ + asset.Index, + } + b.SetAssetTypes() + if len(b.GetAssetTypes()) != 4 { + t.Error("incorrect assets len") } } @@ -241,49 +343,99 @@ func TestGetAssetTypes(t *testing.T) { } } -func TestSetCurrencyPairFormat(t *testing.T) { - t.Skip() - // TO-DO +func TestGetClientBankAccounts(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig(config.ConfigTestFile, true) if err != nil { - t.Fatalf("Test failed. TestSetCurrencyPairFormat failed to load config file. Error: %s", err) + t.Fatal(err) } + var b Base + var r config.BankAccount + r, err = b.GetClientBankAccounts("Kraken", "USD") + if err != nil { + t.Error(err) + } + + if r.BankName != "test" { + t.Error("incorrect bank name") + } + + r, err = b.GetClientBankAccounts("MEOW", "USD") + if err == nil { + t.Error("an error should of been thrown for a non-existent exchange") + } +} + +func TestGetExchangeBankAccounts(t *testing.T) { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigTestFile, true) + if err != nil { + t.Fatal(err) + } + + var b Base + var r config.BankAccount + r, err = b.GetExchangeBankAccounts("Bitfinex", "USD") + if err != nil { + t.Error(err) + } + + if r.BankName != "Deutsche Bank Privat Und Geschaeftskunden AG" { + t.Error("incorrect bank name") + } + + _, err = b.GetExchangeBankAccounts("MEOW", "USD") + if err == nil { + t.Error("an error should of been thrown for a non-existent exchange") + } +} + +func TestSetCurrencyPairFormat(t *testing.T) { + t.Parallel() + b := Base{ - Name: "TESTNAME", + Config: &config.ExchangeConfig{}, } - - b.Name = "ANX" - exch, err := cfg.GetExchangeConfig(b.Name) - if err != nil { - t.Fatalf("Test failed. TestSetCurrencyPairFormat load config failed. Error %s", err) - } - b.Config = exch b.SetCurrencyPairFormat() - if exch.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter != "-" && !exch.CurrencyPairs.ConfigFormat.Uppercase { - t.Fatal("Test failed. TestSetCurrencyPairFormat exch values are not nil") + if b.Config.CurrencyPairs == nil { + t.Error("CurrencyPairs shouldn't be nil") } - exch.CurrencyPairs.ConfigFormat = nil - exch.CurrencyPairs.RequestFormat = nil - err = cfg.UpdateExchangeConfig(exch) - if err != nil { - t.Fatalf("Test failed. TestSetCurrencyPairFormat update config failed. Error %s", err) + // Test global format logic + b.Config.CurrencyPairs.UseGlobalFormat = true + b.CurrencyPairs.UseGlobalFormat = true + pFmt := ¤cy.PairFormat{ + Delimiter: "#", } - - exch, err = cfg.GetExchangeConfig(b.Name) - if err != nil { - t.Fatalf("Test failed. TestSetCurrencyPairFormat load config failed. Error %s", err) - } - - if exch.CurrencyPairs.ConfigFormat != nil && exch.CurrencyPairs.RequestFormat != nil { - t.Fatal("Test failed. TestSetCurrencyPairFormat exch values are not nil") - } - + b.CurrencyPairs.RequestFormat = pFmt + b.CurrencyPairs.ConfigFormat = pFmt b.SetCurrencyPairFormat() - if exch.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter != "-" && !exch.CurrencyPairs.ConfigFormat.Uppercase { - t.Fatal("Test failed. TestSetCurrencyPairFormat exch values are not nil") + if b.GetPairFormat(asset.Spot, true).Delimiter != "#" { + t.Error("incorrect pair format delimiter") + } + + // Test individual asset type formatting logic + b.CurrencyPairs.UseGlobalFormat = false + // This will generate a nil pair store + b.CurrencyPairs.AssetTypes = asset.Items{asset.Index} + // Store non-nil pair stores + b.CurrencyPairs.Store(asset.Spot, currency.PairStore{ + ConfigFormat: ¤cy.PairFormat{ + Delimiter: "~", + }, + }) + b.CurrencyPairs.Store(asset.Futures, currency.PairStore{ + ConfigFormat: ¤cy.PairFormat{ + Delimiter: ":)", + }, + }) + b.SetCurrencyPairFormat() + if b.GetPairFormat(asset.Spot, false).Delimiter != "~" { + t.Error("incorrect pair format delimiter") + } + if b.GetPairFormat(asset.Futures, false).Delimiter != ":)" { + t.Error("incorrect pair format delimiter") } } @@ -316,16 +468,79 @@ func TestGetAuthenticatedAPISupport(t *testing.T) { func TestGetName(t *testing.T) { t.Parallel() - GetName := Base{ + b := Base{ Name: "TESTNAME", } - name := GetName.GetName() + name := b.GetName() if name != "TESTNAME" { t.Error("Test Failed - Exchange GetName() returned incorrect name") } } +func TestGetFeatures(t *testing.T) { + t.Parallel() + + // Test GetEnabledFeatures + var b Base + if b.GetEnabledFeatures().AutoPairUpdates { + t.Error("auto pair updates should be disabled") + } + b.Features.Enabled.AutoPairUpdates = true + if !b.GetEnabledFeatures().AutoPairUpdates { + t.Error("auto pair updates should be enabled") + } + + // Test GetSupportedFeatures + b.Features.Supports.RESTCapabilities.AutoPairUpdates = true + if !b.GetSupportedFeatures().RESTCapabilities.AutoPairUpdates { + t.Error("auto pair updates should be supported") + } + if b.GetSupportedFeatures().RESTCapabilities.TickerBatching { + t.Error("ticker batching shouldn't be supported") + } +} + +func TestGetPairFormat(t *testing.T) { + t.Parallel() + + // Test global formatting + var b Base + b.CurrencyPairs.UseGlobalFormat = true + b.CurrencyPairs.ConfigFormat = ¤cy.PairFormat{ + Uppercase: true, + } + b.CurrencyPairs.RequestFormat = ¤cy.PairFormat{ + Delimiter: "~", + } + pFmt := b.GetPairFormat(asset.Spot, true) + if pFmt.Delimiter != "~" && !pFmt.Uppercase { + t.Error("incorrect pair format values") + } + pFmt = b.GetPairFormat(asset.Spot, false) + if pFmt.Delimiter != "" && pFmt.Uppercase { + t.Error("incorrect pair format values") + } + + // Test individual asset pair store formatting + b.CurrencyPairs.UseGlobalFormat = false + b.CurrencyPairs.Store(asset.Spot, currency.PairStore{ + ConfigFormat: &pFmt, + RequestFormat: ¤cy.PairFormat{ + Delimiter: "/", + Uppercase: true, + }, + }) + pFmt = b.GetPairFormat(asset.Spot, false) + if pFmt.Delimiter != "" && pFmt.Uppercase { + t.Error("incorrect pair format values") + } + pFmt = b.GetPairFormat(asset.Spot, true) + if pFmt.Delimiter != "~" && !pFmt.Uppercase { + t.Error("incorrect pair format values") + } +} + func TestGetEnabledPairs(t *testing.T) { t.Parallel() @@ -606,7 +821,7 @@ func TestIsEnabled(t *testing.T) { func TestSetAPIKeys(t *testing.T) { t.Parallel() - SetAPIKeys := Base{ + b := Base{ Name: "TESTNAME", Enabled: false, API: API{ @@ -615,9 +830,80 @@ func TestSetAPIKeys(t *testing.T) { }, } - SetAPIKeys.SetAPIKeys("RocketMan", "Digereedoo", "007") - if SetAPIKeys.API.Credentials.Key != "RocketMan" && SetAPIKeys.API.Credentials.Secret != "Digereedoo" && SetAPIKeys.API.Credentials.ClientID != "007" { - t.Error("Test Failed - SetAPIKeys() unable to set API credentials") + b.SetAPIKeys("RocketMan", "Digereedoo", "007") + if b.API.Credentials.Key != "RocketMan" && b.API.Credentials.Secret != "Digereedoo" && b.API.Credentials.ClientID != "007" { + t.Error("invalid API credentials") + } + + // Invalid secret + b.API.CredentialsValidator.RequiresBase64DecodeSecret = true + b.API.AuthenticatedSupport = true + b.SetAPIKeys("RocketMan", "%%", "007") + if b.API.AuthenticatedSupport || b.API.AuthenticatedWebsocketSupport { + t.Error("invalid secret should disable authenticated API support") + } + + // valid secret + b.API.CredentialsValidator.RequiresBase64DecodeSecret = true + b.API.AuthenticatedSupport = true + b.SetAPIKeys("RocketMan", "aGVsbG8gd29ybGQ=", "007") + if !b.API.AuthenticatedSupport && b.API.Credentials.Secret != "hello world" { + t.Error("invalid secret should disable authenticated API support") + } +} + +func TestSetupDefaults(t *testing.T) { + t.Parallel() + + var b Base + cfg := config.ExchangeConfig{ + HTTPTimeout: time.Duration(-1), + API: config.APIConfig{ + AuthenticatedSupport: true, + }, + } + if err := b.SetupDefaults(&cfg); err != nil { + t.Error(err) + } + if cfg.HTTPTimeout.String() != "15s" { + t.Error("HTTP timeout should be set to 15s") + } + + // Test custom HTTP timeout is set + cfg.HTTPTimeout = time.Second * 30 + if err := b.SetupDefaults(&cfg); err != nil { + t.Error(err) + } + if cfg.HTTPTimeout.String() != "30s" { + t.Error("HTTP timeout should be set to 30s") + } + + // Test asset types + p := currency.NewPairDelimiter(defaultTestCurrencyPair, "-") + b.CurrencyPairs.Store(asset.Spot, + currency.PairStore{ + Enabled: currency.Pairs{ + p, + }, + }, + ) + if err := b.SetupDefaults(&cfg); err != nil { + t.Error(err) + } + ps := cfg.CurrencyPairs.Get(asset.Spot) + if !ps.Enabled.Contains(p, true) { + t.Error("default pair should be stored in the configs pair store") + } + + // Test websocket support + b.Websocket = wshandler.New() + b.Features.Supports.Websocket = true + if err := b.SetupDefaults(&cfg); err != nil { + t.Error(err) + } + b.Websocket.Setup(nil, nil, nil, "", true, false, "", "", false) + if !b.IsWebsocketEnabled() { + t.Error("websocket should be enabled") } } @@ -628,16 +914,39 @@ func TestAllowAuthenticatedRequest(t *testing.T) { SkipAuthCheck: true, } + // Test SkipAuthCheck if r := b.AllowAuthenticatedRequest(); !r { t.Error("skip auth check should allow authenticated requests") } + // Test credentials failure b.SkipAuthCheck = false b.API.CredentialsValidator.RequiresKey = true if r := b.AllowAuthenticatedRequest(); r { t.Error("should fail with an empty key") } + // Test bot usage with authenticated API support disabled, but with + // valid credentials + b.LoadedByConfig = true + b.API.Credentials.Key = "k3y" + if r := b.AllowAuthenticatedRequest(); r { + t.Error("should fail when authenticated support is disabled") + } + + // Test enabled authenticated API support and loaded by config + // but invalid credentials + b.API.AuthenticatedSupport = true + b.API.Credentials.Key = "" + if r := b.AllowAuthenticatedRequest(); r { + t.Error("should fail with invalid credentials") + } + + // Finally a valid one + b.API.Credentials.Key = "k3y" + if r := b.AllowAuthenticatedRequest(); !r { + t.Error("show allow an authenticated request") + } } func TestValidateAPICredentials(t *testing.T) { @@ -742,12 +1051,12 @@ func TestUpdatePairs(t *testing.T) { t.Fatal("Test failed. TestUpdatePairs failed to load config") } - anxCfg, err := cfg.GetExchangeConfig("ANX") + anxCfg, err := cfg.GetExchangeConfig(defaultTestExchange) if err != nil { t.Fatal("Test failed. TestUpdatePairs failed to load config") } - UAC := Base{Name: "ANX"} + UAC := Base{Name: defaultTestExchange} UAC.Config = anxCfg exchangeProducts := currency.NewPairsFromStrings([]string{"ltc", "btc", "usd", "aud", ""}) err = UAC.UpdatePairs(exchangeProducts, asset.Spot, true, false) @@ -770,7 +1079,7 @@ func TestUpdatePairs(t *testing.T) { // Test updating exchange products exchangeProducts = currency.NewPairsFromStrings([]string{"ltc", "btc", "usd", "aud"}) - UAC.Name = "ANX" + UAC.Name = defaultTestExchange err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err != nil { t.Errorf("Test Failed - Exchange UpdatePairs() error: %s", err) @@ -965,7 +1274,7 @@ func TestSupportsWithdrawPermissions(t *testing.T) { func TestFormatWithdrawPermissions(t *testing.T) { t.Parallel() - UAC := Base{Name: "ANX"} + UAC := Base{Name: defaultTestExchange} UAC.Features.Supports.WithdrawPermissions = AutoWithdrawCrypto | AutoWithdrawCryptoWithAPIPermission | AutoWithdrawCryptoWithSetup | From c2a33300f5d183fe9b892c5ad329d4d9cc96b685 Mon Sep 17 00:00:00 2001 From: Scott Date: Wed, 2 Oct 2019 09:06:52 +1000 Subject: [PATCH 46/71] Feature+Bugfix: Engine websocket management (#360) * Initial commit tearing down the websocket connection management. The purpose is to remove the traffic monitoring and dropping as syncer.go is a better manager * Adds a readwrite mutex and helper functions to minimise inline lock/unlocks and prevent races * Creates new WebsocketType struct to contain all parameters required. Deletes WebsocketReset. Utilises ReadMessageErrors channel for all websocket readmessages to analyse when an error returned is due to a disconnect * Fixes issue with syncer trying to connect while connecting * Simplifies initialisation function for websocket. Reconnects and resubscribes after disconnection * Adds WebsocketTimeout config value to dictate when the websocket traffic monitor should die. Default to two minutes of no traffic activity. Increases test coverage and updates existing tests to work with new technologic. RE-ADDS TESTS I ACCIDENTALLY DELETED FROM PREVIOUS PR * Removes snapshot override as its always necessary when considering reconnections. Increases test coverage. Re-adds tests that were ACCIDENTALLY DELETED. Removes unused websocket channels. Bug fix for traffic monitor to shutdown via goroutine instead of killing itself * Fixes gateio bug for authentication errors when null. Adds little entry to syncer for when websocket is switched to rest and then back, you get a log notifying of the return. Fixes okgroup bug where ws message is sent on a disconnected ws, causing panic. Renames setConnectionStatus to setConnectedStatus. Puts connection monitor log behind verbose bool * Fixes lingering races. Fixes bug where websocket was enabled whether you liked it or not. Removes demonstration test * Fixes log message, renames unc, removes comments * Fixes data race * Removes verbosity, ensures shutdown sets connection status appropriately * Removes go routine causing CPU spike. Stops timers properly and resets timers properly * Renames `WsEnabled` to `Enabled`. Increases test coverage. Fixes typos. Handles unhandled errors * The forgotten lint * With using RWlocks, removes the channel nil check and relies on !w.IsConnected() to prevent a shutdown from recurring * Removes extra closure step in the defer as it causes all the issues * Prevents timer channel hangups. Minimises use of websocket Connect(). Expands disconnection error definition. Removes routine disconnection error handling. Ensures only one traffic monitor can ever be run. Renames subscriptionLock to subscriptionMutext for consistency * Extends timeout to 30 seconds to cover for non-popular exchanges and non-popular currencies * Updates test from rebase to use new websocket setup function * Fixes test to ensure it tests what it says it does --- config/config.go | 6 + config/config_test.go | 5 + config/config_types.go | 1 + engine/exchange.go | 1 - engine/routines.go | 36 +- engine/syncer.go | 13 +- exchanges/alphapoint/alphapoint_websocket.go | 1 + exchanges/binance/binance_websocket.go | 8 +- exchanges/binance/binance_wrapper.go | 21 +- exchanges/bitfinex/bitfinex_websocket.go | 5 +- exchanges/bitfinex/bitfinex_wrapper.go | 22 +- exchanges/bitmex/bitmex_websocket.go | 7 +- exchanges/bitmex/bitmex_wrapper.go | 22 +- exchanges/bitstamp/bitstamp_websocket.go | 6 +- exchanges/bitstamp/bitstamp_wrapper.go | 22 +- exchanges/btse/btse_websocket.go | 4 +- exchanges/btse/btse_wrapper.go | 22 +- .../coinbasepro/coinbasepro_websocket.go | 4 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 22 +- exchanges/coinut/coinut_websocket.go | 4 +- exchanges/coinut/coinut_wrapper.go | 22 +- exchanges/exchange.go | 2 +- exchanges/exchange_test.go | 5 +- exchanges/gateio/gateio_types.go | 2 +- exchanges/gateio/gateio_websocket.go | 5 +- exchanges/gateio/gateio_wrapper.go | 22 +- exchanges/gemini/gemini_websocket.go | 3 +- exchanges/gemini/gemini_wrapper.go | 20 +- exchanges/hitbtc/hitbtc_websocket.go | 4 +- exchanges/hitbtc/hitbtc_wrapper.go | 22 +- exchanges/huobi/huobi_websocket.go | 2 +- exchanges/huobi/huobi_wrapper.go | 22 +- exchanges/kraken/kraken_websocket.go | 10 +- exchanges/kraken/kraken_wrapper.go | 22 +- exchanges/lakebtc/lakebtc_websocket.go | 2 +- exchanges/lakebtc/lakebtc_wrapper.go | 21 +- exchanges/okgroup/okgroup_websocket.go | 7 +- exchanges/okgroup/okgroup_wrapper.go | 21 +- exchanges/poloniex/poloniex_websocket.go | 4 +- exchanges/poloniex/poloniex_wrapper.go | 22 +- exchanges/websocket/wshandler/wshandler.go | 527 +++++++-------- .../websocket/wshandler/wshandler_test.go | 608 ++++++++++++++---- .../websocket/wshandler/wshandler_types.go | 100 +-- .../websocket/wsorderbook/wsorderbook.go | 9 +- .../websocket/wsorderbook/wsorderbook_test.go | 27 +- exchanges/zb/zb_websocket.go | 5 +- exchanges/zb/zb_wrapper.go | 21 +- 47 files changed, 1080 insertions(+), 689 deletions(-) diff --git a/config/config.go b/config/config.go index 3bb42c56..d197ec35 100644 --- a/config/config.go +++ b/config/config.go @@ -41,6 +41,7 @@ const ( configDefaultWebsocketResponseCheckTimeout = time.Millisecond * 30 configDefaultWebsocketResponseMaxLimit = time.Second * 7 configDefaultWebsocketOrderbookBufferLimit = 5 + configDefaultWebsocketTrafficTimeout = time.Second * 30 configMaxAuthFailures = 3 defaultNTPAllowedDifference = 50000000 defaultNTPAllowedNegativeDifference = 50000000 @@ -1024,6 +1025,11 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].Name, configDefaultWebsocketResponseMaxLimit) c.Exchanges[i].WebsocketResponseMaxLimit = configDefaultWebsocketResponseMaxLimit } + if c.Exchanges[i].WebsocketTrafficTimeout <= 0 { + log.Warnf(log.ExchangeSys, "Exchange %s Websocket response traffic timeout value not set, defaulting to %v.", + c.Exchanges[i].Name, configDefaultWebsocketTrafficTimeout) + c.Exchanges[i].WebsocketTrafficTimeout = configDefaultWebsocketTrafficTimeout + } if c.Exchanges[i].WebsocketOrderbookBufferLimit <= 0 { log.Warnf(log.ExchangeSys, "Exchange %s Websocket orderbook buffer limit value not set, defaulting to %v.", c.Exchanges[i].Name, configDefaultWebsocketOrderbookBufferLimit) diff --git a/config/config_test.go b/config/config_test.go index 419e1951..65249bfd 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1451,6 +1451,7 @@ func TestCheckExchangeConfigValues(t *testing.T) { cfg.Exchanges[0].WebsocketResponseMaxLimit = 0 cfg.Exchanges[0].WebsocketResponseCheckTimeout = 0 cfg.Exchanges[0].WebsocketOrderbookBufferLimit = 0 + cfg.Exchanges[0].WebsocketTrafficTimeout = 0 cfg.Exchanges[0].HTTPTimeout = 0 err = cfg.CheckExchangeConfigValues() if err != nil { @@ -1465,6 +1466,10 @@ func TestCheckExchangeConfigValues(t *testing.T) { t.Errorf("expected exchange %s to have updated WebsocketOrderbookBufferLimit value", cfg.Exchanges[0].Name) } + if cfg.Exchanges[0].WebsocketTrafficTimeout == 0 { + t.Errorf("expected exchange %s to have updated WebsocketTrafficTimeout value", + cfg.Exchanges[0].Name) + } if cfg.Exchanges[0].HTTPTimeout == 0 { t.Errorf("expected exchange %s to have updated HTTPTimeout value", cfg.Exchanges[0].Name) diff --git a/config/config_types.go b/config/config_types.go index 33ea91f5..5c07f39e 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -59,6 +59,7 @@ type ExchangeConfig struct { HTTPRateLimiter *HTTPRateLimitConfig `json:"httpRateLimiter,omitempty"` WebsocketResponseCheckTimeout time.Duration `json:"websocketResponseCheckTimeout"` WebsocketResponseMaxLimit time.Duration `json:"websocketResponseMaxLimit"` + WebsocketTrafficTimeout time.Duration `json:"websocketTrafficTimeout"` WebsocketOrderbookBufferLimit int `json:"websocketOrderbookBufferLimit"` ProxyAddress string `json:"proxyAddress,omitempty"` BaseCurrencies currency.Currencies `json:"baseCurrencies"` diff --git a/engine/exchange.go b/engine/exchange.go index 3faaa836..5da34cdf 100644 --- a/engine/exchange.go +++ b/engine/exchange.go @@ -228,7 +228,6 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { if exchCfg.Features.Supports.RESTCapabilities.AutoPairUpdates { exchCfg.Features.Enabled.AutoPairUpdates = false } - } } diff --git a/engine/routines.go b/engine/routines.go index 29466566..bcbc6902 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -354,39 +354,12 @@ func Websocketshutdown(ws *wshandler.Websocket) error { } } -// streamDiversion is a diversion switch from websocket to REST or other -// alternative feed -func streamDiversion(ws *wshandler.Websocket) { - wg.Add(1) - defer wg.Done() - - for { - select { - case <-shutdowner: - return - - case <-ws.Connected: - if Bot.Settings.Verbose { - log.Debugf(log.WebsocketMgr, "exchange %s websocket feed connected\n", ws.GetName()) - } - - case <-ws.Disconnected: - if Bot.Settings.Verbose { - log.Debugf(log.WebsocketMgr, "exchange %s websocket feed disconnected, switching to REST functionality\n", - ws.GetName()) - } - } - } -} - // WebsocketDataHandler handles websocket data coming from a websocket feed // associated with an exchange func WebsocketDataHandler(ws *wshandler.Websocket) { wg.Add(1) defer wg.Done() - go streamDiversion(ws) - for { select { case <-shutdowner: @@ -407,14 +380,7 @@ func WebsocketDataHandler(ws *wshandler.Websocket) { } case error: - switch { - case strings.Contains(d.Error(), "close 1006"): - go ws.WebsocketReset() - continue - default: - log.Errorf(log.WebsocketMgr, "routines.go exchange %s websocket error - %s", ws.GetName(), data) - } - + log.Errorf(log.WebsocketMgr, "routines.go exchange %s websocket error - %s", ws.GetName(), data) case wshandler.TradeData: // Trade Data // if Bot.Settings.Verbose { diff --git a/engine/syncer.go b/engine/syncer.go index 3cb15be2..598661f5 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -286,7 +286,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { supportsRESTTickerBatching := Bot.Exchanges[x].SupportsRESTTickerBatchUpdates() var usingREST bool var usingWebsocket bool - + var switchedToRest bool if Bot.Exchanges[x].SupportsWebsocket() && Bot.Exchanges[x].IsWebsocketEnabled() { ws, err := Bot.Exchanges[x].GetWebsocket() if err != nil { @@ -346,7 +346,12 @@ func (e *ExchangeCurrencyPairSyncer) worker() { log.Errorf(log.SyncMgr, "failed to get item. Err: %s\n", err) continue } - + if switchedToRest && usingWebsocket { + log.Infof(log.SyncMgr, + "%s %s: Websocket re-enabled, switching from rest to websocket\n", + c.Exchange, FormatCurrency(p).String()) + switchedToRest = false + } if e.Cfg.SyncTicker { if !e.isProcessing(exchangeName, c.Pair, c.AssetType, SyncItemTicker) { if c.Ticker.LastUpdated.IsZero() || time.Since(c.Ticker.LastUpdated) > defaultSyncerTimeout { @@ -362,6 +367,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { log.Warnf(log.SyncMgr, "%s %s: No ticker update after 10 seconds, switching from websocket to rest\n", c.Exchange, FormatCurrency(p).String()) + switchedToRest = true e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, false) } } @@ -425,6 +431,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { log.Warnf(log.SyncMgr, "%s %s: No orderbook update after 15 seconds, switching from websocket to rest\n", c.Exchange, FormatCurrency(c.Pair).String()) + switchedToRest = true e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, false) } } @@ -491,7 +498,7 @@ func (e *ExchangeCurrencyPairSyncer) Start() { usingREST = true } - if !ws.IsConnected() { + if !ws.IsConnected() && !ws.IsConnecting() { go WebsocketDataHandler(ws) err = ws.Connect() diff --git a/exchanges/alphapoint/alphapoint_websocket.go b/exchanges/alphapoint/alphapoint_websocket.go index 58c6803a..05f389b2 100644 --- a/exchanges/alphapoint/alphapoint_websocket.go +++ b/exchanges/alphapoint/alphapoint_websocket.go @@ -38,6 +38,7 @@ func (a *Alphapoint) WebsocketClient() { for a.Enabled { msgType, resp, err := a.WebsocketConn.ReadMessage() if err != nil { + a.Websocket.ReadMessageErrors <- err log.Error(log.ExchangeSys, err) break } diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index e8b24c3b..ec48658b 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -21,8 +21,8 @@ const ( binanceDefaultWebsocketURL = "wss://stream.binance.com:9443" ) -// WSConnect intiates a websocket connection -func (b *Binance) WSConnect() error { +// WsConnect intiates a websocket connection +func (b *Binance) WsConnect() error { if !b.Websocket.IsEnabled() || !b.IsEnabled() { return errors.New(wshandler.WebsocketNotEnabled) } @@ -87,7 +87,7 @@ func (b *Binance) WsHandleData() { default: read, err := b.WebsocketConn.ReadMessage() if err != nil { - b.Websocket.DataHandler <- err + b.Websocket.ReadMessageErrors <- err return } b.Websocket.TrafficAlert <- struct{}{} @@ -248,7 +248,7 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error { newOrderBook.Pair = p newOrderBook.AssetType = asset.Spot - return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false) + return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } // UpdateLocalCache updates and returns the most recent iteration of the orderbook diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 707b9858..442dcfb8 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -113,15 +113,18 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup(b.WSConnect, - nil, - nil, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - binanceDefaultWebsocketURL, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = b.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: binanceDefaultWebsocketURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + }) + if err != nil { return err } diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 557553e5..b9b5f569 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -133,6 +133,7 @@ func (b *Bitfinex) WsConnect() error { resp, err := b.WebsocketConn.ReadMessage() if err != nil { + b.Websocket.ReadMessageErrors <- err return fmt.Errorf("%v unable to read from Websocket. Error: %s", b.Name, err) } b.Websocket.TrafficAlert <- struct{}{} @@ -177,7 +178,7 @@ func (b *Bitfinex) WsDataHandler() { default: stream, err := b.WebsocketConn.ReadMessage() if err != nil { - b.Websocket.DataHandler <- err + b.Websocket.ReadMessageErrors <- err return } b.Websocket.TrafficAlert <- struct{}{} @@ -481,7 +482,7 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books newOrderBook.AssetType = assetType newOrderBook.Bids = bid newOrderBook.Pair = p - err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false) + err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return fmt.Errorf("bitfinex.go error - %s", err) } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 4fbe2455..1a8229ed 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -114,15 +114,19 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup(b.WsConnect, - b.Subscribe, - b.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - bitfinexWebsocket, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = b.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: bitfinexWebsocket, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + UnSubscriber: b.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 4af5f0f9..3524550c 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -66,8 +66,8 @@ var ( pongChan = make(chan int, 1) ) -// WsConnector initiates a new websocket connection -func (b *Bitmex) WsConnector() error { +// WsConnect initiates a new websocket connection +func (b *Bitmex) WsConnect() error { if !b.Websocket.IsEnabled() || !b.IsEnabled() { return errors.New(wshandler.WebsocketNotEnabled) } @@ -79,6 +79,7 @@ func (b *Bitmex) WsConnector() error { p, err := b.WebsocketConn.ReadMessage() if err != nil { + b.Websocket.ReadMessageErrors <- err return err } b.Websocket.TrafficAlert <- struct{}{} @@ -360,7 +361,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai newOrderBook.Bids = bids newOrderBook.AssetType = assetType newOrderBook.Pair = currencyPair - err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false) + err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return fmt.Errorf("bitmex_websocket.go process orderbook error - %s", err) diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 167be7e4..68e0a016 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -137,15 +137,19 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup(b.WsConnector, - b.Subscribe, - b.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - bitmexWSURL, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = b.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: bitmexWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + UnSubscriber: b.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 09d06ac4..5260dfd2 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -62,7 +62,7 @@ func (b *Bitstamp) WsHandleData() { default: resp, err := b.WebsocketConn.ReadMessage() if err != nil { - b.Websocket.DataHandler <- err + b.Websocket.ReadMessageErrors <- err return } b.Websocket.TrafficAlert <- struct{}{} @@ -78,7 +78,7 @@ func (b *Bitstamp) WsHandleData() { if b.Verbose { log.Debugf(log.ExchangeSys, "%v - Websocket reconnection request received", b.GetName()) } - go b.Websocket.WebsocketReset() + go b.Websocket.Shutdown() // Connection monitor will reconnect case "data": wsOrderBookTemp := websocketOrderBookResponse{} @@ -248,7 +248,7 @@ func (b *Bitstamp) seedOrderBook() error { newOrderBook.Pair = p[x] newOrderBook.AssetType = asset.Spot - err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false) + err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return err } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 790ca785..5f8cdc9f 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -110,15 +110,19 @@ func (b *Bitstamp) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup(b.WsConnect, - b.Subscribe, - b.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - bitstampWSURL, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = b.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: bitstampWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + UnSubscriber: b.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index 1ce85e65..b5476a45 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -54,7 +54,7 @@ func (b *BTSE) WsHandleData() { default: resp, err := b.WebsocketConn.ReadMessage() if err != nil { - b.Websocket.DataHandler <- err + b.Websocket.ReadMessageErrors <- err return } b.Websocket.TrafficAlert <- struct{}{} @@ -162,7 +162,7 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error { base.LastUpdated = time.Now() base.ExchangeName = b.Name - err := b.Websocket.Orderbook.LoadSnapshot(&base, true) + err := b.Websocket.Orderbook.LoadSnapshot(&base) if err != nil { return err } diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 32a102be..3cec4218 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -109,15 +109,19 @@ func (b *BTSE) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup(b.WsConnect, - b.Subscribe, - b.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - btseWebsocket, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = b.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: btseWebsocket, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + UnSubscriber: b.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 74c3b9d0..ff34058e 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -54,7 +54,7 @@ func (c *CoinbasePro) WsHandleData() { default: resp, err := c.WebsocketConn.ReadMessage() if err != nil { - c.Websocket.DataHandler <- err + c.Websocket.ReadMessageErrors <- err return } c.Websocket.TrafficAlert <- struct{}{} @@ -217,7 +217,7 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro base.AssetType = asset.Spot base.Pair = pair - err := c.Websocket.Orderbook.LoadSnapshot(&base, false) + err := c.Websocket.Orderbook.LoadSnapshot(&base) if err != nil { return err } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 3c207972..3079db1d 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -115,15 +115,19 @@ func (c *CoinbasePro) Setup(exch *config.ExchangeConfig) error { return err } - err = c.Websocket.Setup(c.WsConnect, - c.Subscribe, - c.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - coinbaseproWebsocketURL, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = c.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: coinbaseproWebsocketURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: c.WsConnect, + Subscriber: c.Subscribe, + UnSubscriber: c.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index bae03986..d12e03aa 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -77,7 +77,7 @@ func (c *COINUT) WsHandleData() { default: resp, err := c.WebsocketConn.ReadMessage() if err != nil { - c.Websocket.DataHandler <- err + c.Websocket.ReadMessageErrors <- err return } c.Websocket.TrafficAlert <- struct{}{} @@ -289,7 +289,7 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { ) newOrderBook.AssetType = asset.Spot - return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false) + return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } // WsProcessOrderbookUpdate process an orderbook update diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 78e60142..08cc097c 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -116,15 +116,19 @@ func (c *COINUT) Setup(exch *config.ExchangeConfig) error { return err } - err = c.Websocket.Setup(c.WsConnect, - c.Subscribe, - c.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - coinutWebsocketURL, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = c.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: coinutWebsocketURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: c.WsConnect, + Subscriber: c.Subscribe, + UnSubscriber: c.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 59f3a9f8..f218aa4a 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -449,7 +449,7 @@ func (e *Base) SetupDefaults(exch *config.ExchangeConfig) error { } if e.Features.Supports.Websocket { - e.Websocket.SetWsStatusAndConnection(exch.Features.Enabled.Websocket) + e.Websocket.Initialise() } return nil } diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 96074091..ca3060da 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -1233,7 +1233,10 @@ func TestIsWebsocketEnabled(t *testing.T) { } b.Websocket = wshandler.New() - b.Websocket.Setup(nil, nil, nil, "", true, false, "", "", false) + err := b.Websocket.Setup(&wshandler.WebsocketSetup{Enabled: true}) + if err != nil { + t.Error(err) + } if !b.IsWebsocketEnabled() { t.Error("websocket should be enabled") } diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index 585bf48f..8336d8e5 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -462,7 +462,7 @@ type WebSocketOrderQueryRecords struct { // WebsocketAuthenticationResponse contains the result of a login request type WebsocketAuthenticationResponse struct { - Error string `json:"error"` + Error string `json:"error,omitempty"` Result struct { Status string `json:"status"` } `json:"result"` diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 60853d4a..8bcb8b76 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -92,7 +92,7 @@ func (g *Gateio) WsHandleData() { default: resp, err := g.WebsocketConn.ReadMessage() if err != nil { - g.Websocket.DataHandler <- err + g.Websocket.ReadMessageErrors <- err return } g.Websocket.TrafficAlert <- struct{}{} @@ -238,8 +238,7 @@ func (g *Gateio) WsHandleData() { newOrderBook.AssetType = asset.Spot newOrderBook.Pair = currency.NewPairFromString(c) - err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook, - true) + err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { g.Websocket.DataHandler <- err } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 7bd1db10..d3d602a0 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -117,15 +117,19 @@ func (g *Gateio) Setup(exch *config.ExchangeConfig) error { return err } - err = g.Websocket.Setup(g.WsConnect, - g.Subscribe, - g.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - gateioWebsocketEndpoint, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = g.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: gateioWebsocketEndpoint, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: g.WsConnect, + Subscriber: g.Subscribe, + UnSubscriber: g.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index fc64e050..9d47d9c2 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -282,8 +282,7 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = pair - err := g.Websocket.Orderbook.LoadSnapshot(&newOrderBook, - false) + err := g.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { g.Websocket.DataHandler <- err return diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 0e4d76f9..dd2338a9 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -116,15 +116,17 @@ func (g *Gemini) Setup(exch *config.ExchangeConfig) error { g.API.Endpoints.URL = geminiSandboxAPIURL } - err = g.Websocket.Setup(g.WsConnect, - nil, - nil, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - geminiWebsocketEndpoint, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = g.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: geminiWebsocketEndpoint, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: g.WsConnect, + }) if err != nil { return err } diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index 4e058a05..5cfda89a 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -65,7 +65,7 @@ func (h *HitBTC) WsHandleData() { default: resp, err := h.WebsocketConn.ReadMessage() if err != nil { - h.Websocket.DataHandler <- err + h.Websocket.ReadMessageErrors <- err return } h.Websocket.TrafficAlert <- struct{}{} @@ -251,7 +251,7 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { newOrderBook.AssetType = asset.Spot newOrderBook.Pair = p - err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false) + err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return err } diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index a7cc2534..f1fcaa90 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -115,15 +115,19 @@ func (h *HitBTC) Setup(exch *config.ExchangeConfig) error { return err } - err = h.Websocket.Setup(h.WsConnect, - h.Subscribe, - h.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - hitbtcWebsocketAddress, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = h.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: hitbtcWebsocketAddress, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: h.WsConnect, + Subscriber: h.Subscribe, + UnSubscriber: h.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index ce063bd5..fb5897ec 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -313,7 +313,7 @@ func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { newOrderBook.Asks = asks newOrderBook.Bids = bids newOrderBook.Pair = p - err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, true) + err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return err } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index f2678e9e..8aa6ccce 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -119,15 +119,19 @@ func (h *HUOBI) Setup(exch *config.ExchangeConfig) error { h.API.PEMKeySupport = exch.API.PEMKeySupport h.API.Credentials.PEMKey = exch.API.Credentials.PEMKey - err = h.Websocket.Setup(h.WsConnect, - h.Subscribe, - h.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - wsMarketURL, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = h.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: wsMarketURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: h.WsConnect, + Subscriber: h.Subscribe, + UnSubscriber: h.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index 7d855966..025d5aea 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -110,9 +110,7 @@ func (k *Kraken) WsHandleData() { default: resp, err := k.WebsocketConn.ReadMessage() if err != nil { - k.Websocket.DataHandler <- fmt.Errorf("%v WsHandleData: %v", - k.Name, - err) + k.Websocket.ReadMessageErrors <- err return } k.Websocket.TrafficAlert <- struct{}{} @@ -384,7 +382,7 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob } } base.LastUpdated = highestLastUpdate - err := k.Websocket.Orderbook.LoadSnapshot(&base, true) + err := k.Websocket.Orderbook.LoadSnapshot(&base) if err != nil { k.Websocket.DataHandler <- err return @@ -509,7 +507,7 @@ func (k *Kraken) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscrip Subscription: WebsocketSubscriptionData{ Name: channelToSubscribe.Channel, }, - RequestID: k.WebsocketConn.GenerateMessageID(true), + RequestID: k.WebsocketConn.GenerateMessageID(false), } _, err := k.WebsocketConn.SendMessageReturnResponse(resp.RequestID, resp) return err @@ -523,7 +521,7 @@ func (k *Kraken) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscr Subscription: WebsocketSubscriptionData{ Name: channelToSubscribe.Channel, }, - RequestID: k.WebsocketConn.GenerateMessageID(true), + RequestID: k.WebsocketConn.GenerateMessageID(false), } _, err := k.WebsocketConn.SendMessageReturnResponse(resp.RequestID, resp) return err diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 5d4912b6..3763a672 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -119,15 +119,19 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) error { return err } - err = k.Websocket.Setup(k.WsConnect, - k.Subscribe, - k.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - krakenWSURL, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = k.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: krakenWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: k.WsConnect, + Subscriber: k.Subscribe, + UnSubscriber: k.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/lakebtc/lakebtc_websocket.go b/exchanges/lakebtc/lakebtc_websocket.go index 690e2f41..3b1abae5 100644 --- a/exchanges/lakebtc/lakebtc_websocket.go +++ b/exchanges/lakebtc/lakebtc_websocket.go @@ -205,7 +205,7 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { Price: price, }) } - return l.Websocket.Orderbook.LoadSnapshot(&book, true) + return l.Websocket.Orderbook.LoadSnapshot(&book) } func (l *LakeBTC) getCurrencyFromChannel(channel string) currency.Pair { diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index f865d0e6..c7c9be68 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -110,15 +110,18 @@ func (l *LakeBTC) Setup(exch *config.ExchangeConfig) error { return err } - err = l.Websocket.Setup(l.WsConnect, - l.Subscribe, - nil, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - lakeBTCWSURL, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = l.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: lakeBTCWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: l.WsConnect, + Subscriber: l.Subscribe, + }) if err != nil { return err } diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index 558252d6..fe10fee8 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -193,6 +193,9 @@ func (o *OKGroup) wsPingHandler(wg *sync.WaitGroup) { return case <-ticker.C: + if !o.Websocket.IsConnected() { + continue + } err := o.WebsocketConn.Connection.WriteMessage(websocket.TextMessage, []byte("ping")) if o.Verbose { log.Debugf(log.ExchangeSys, "%v sending ping", o.GetName()) @@ -221,7 +224,7 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) { default: resp, err := o.WebsocketConn.ReadMessage() if err != nil { - o.Websocket.DataHandler <- err + o.Websocket.ReadMessageErrors <- err return } o.Websocket.TrafficAlert <- struct{}{} @@ -475,7 +478,7 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i ExchangeName: o.GetName(), } - err := o.Websocket.Orderbook.LoadSnapshot(&newOrderBook, true) + err := o.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return err } diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index 863af600..a0ae6828 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -31,15 +31,18 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) error { return err } - err = o.Websocket.Setup(o.WsConnect, - o.Subscribe, - o.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - o.API.Endpoints.WebsocketURL, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = o.Websocket.Setup(&wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: o.API.Endpoints.WebsocketURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: o.WsConnect, + Subscriber: o.Subscribe, + UnSubscriber: o.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index a6ef2ed1..50962417 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -88,7 +88,7 @@ func (p *Poloniex) WsHandleData() { default: resp, err := p.WebsocketConn.ReadMessage() if err != nil { - p.Websocket.DataHandler <- err + p.Websocket.ReadMessageErrors <- err return } p.Websocket.TrafficAlert <- struct{}{} @@ -330,7 +330,7 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e newOrderBook.AssetType = asset.Spot newOrderBook.Pair = currency.NewPairFromString(symbol) - return p.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false) + return p.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } // WsProcessOrderbookUpdate processes new orderbook updates diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 13611997..2febf687 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -113,15 +113,19 @@ func (p *Poloniex) Setup(exch *config.ExchangeConfig) error { return err } - err = p.Websocket.Setup(p.WsConnect, - p.Subscribe, - p.Unsubscribe, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - poloniexWebsocketAddress, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = p.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: poloniexWebsocketAddress, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: p.WsConnect, + Subscriber: p.Subscribe, + UnSubscriber: p.Unsubscribe, + }) if err != nil { return err } diff --git a/exchanges/websocket/wshandler/wshandler.go b/exchanges/websocket/wshandler/wshandler.go index 931ee5c8..1bc9502d 100644 --- a/exchanges/websocket/wshandler/wshandler.go +++ b/exchanges/websocket/wshandler/wshandler.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io/ioutil" + "net" "net/http" "net/url" "strings" @@ -31,42 +32,28 @@ func New() *Websocket { } // Setup sets main variables for websocket connection -func (w *Websocket) Setup(connector func() error, - subscriber func(channelToSubscribe WebsocketChannelSubscription) error, - unsubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error, - exchangeName string, - wsEnabled, - verbose bool, - defaultURL, - runningURL string, - authenticatedWebsocketAPISupport bool) error { - +func (w *Websocket) Setup(setupData *WebsocketSetup) error { w.DataHandler = make(chan interface{}, 1) - w.Connected = make(chan struct{}, 1) - w.Disconnected = make(chan struct{}, 1) w.TrafficAlert = make(chan struct{}, 1) - w.verbose = verbose - - w.SetChannelSubscriber(subscriber) - w.SetChannelUnsubscriber(unsubscriber) - err := w.SetWsStatusAndConnection(wsEnabled) + w.verbose = setupData.Verbose + w.SetChannelSubscriber(setupData.Subscriber) + w.SetChannelUnsubscriber(setupData.UnSubscriber) + w.enabled = setupData.Enabled + w.SetDefaultURL(setupData.DefaultURL) + w.SetConnector(setupData.Connector) + w.SetWebsocketURL(setupData.RunningURL) + w.SetExchangeName(setupData.ExchangeName) + w.SetCanUseAuthenticatedEndpoints(setupData.AuthenticatedWebsocketAPISupport) + w.trafficTimeout = setupData.WebsocketTimeout + err := w.Initialise() if err != nil { return err } - w.SetDefaultURL(defaultURL) - w.SetConnector(connector) - w.SetWebsocketURL(runningURL) - w.SetExchangeName(exchangeName) - w.SetCanUseAuthenticatedEndpoints(authenticatedWebsocketAPISupport) - - w.init = false - w.noConnectionCheckLimit = 5 - w.reconnectionLimit = 10 return nil } -// Connect intiates a websocket connection by using a package defined connection +// Connect initiates a websocket connection by using a package defined connection // function func (w *Websocket) Connect() error { w.m.Lock() @@ -75,32 +62,33 @@ func (w *Websocket) Connect() error { if !w.IsEnabled() { return errors.New(WebsocketNotEnabled) } - - if w.connected { - w.connecting = false - return errors.New("exchange_websocket.go error - already connected, cannot connect again") + if w.IsConnecting() { + return fmt.Errorf("%v Websocket already attempting to connect", + w.exchangeName) } - - w.connecting = true + if w.IsConnected() { + return fmt.Errorf("%v Websocket already connected", + w.exchangeName) + } + w.setConnectingStatus(true) w.ShutdownC = make(chan struct{}, 1) + w.ReadMessageErrors = make(chan error, 1) err := w.connector() if err != nil { - w.connecting = false - return fmt.Errorf("exchange_websocket.go connection error %s", - err) + w.setConnectingStatus(false) + return fmt.Errorf("%v Error connecting %s", + w.exchangeName, err) } - if !w.connected { - w.Connected <- struct{}{} - w.connected = true - w.connecting = false - } + w.setConnectedStatus(true) + w.setConnectingStatus(false) + w.setInit(true) var anotherWG sync.WaitGroup anotherWG.Add(1) go w.trafficMonitor(&anotherWG) anotherWG.Wait() - if !w.connectionMonitorRunning { + if !w.IsConnectionMonitorRunning() { go w.connectionMonitor() } if w.SupportsFunctionality(WebsocketSubscribeSupported) || w.SupportsFunctionality(WebsocketUnsubscribeSupported) { @@ -112,88 +100,82 @@ func (w *Websocket) Connect() error { // connectionMonitor ensures that the WS keeps connecting func (w *Websocket) connectionMonitor() { - w.m.Lock() - w.connectionMonitorRunning = true - w.m.Unlock() + if w.IsConnectionMonitorRunning() { + return + } + w.setConnectionMonitorRunning(true) + timer := time.NewTimer(connectionMonitorDelay) + defer func() { - w.connectionMonitorRunning = false + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + w.setConnectionMonitorRunning(false) + if w.verbose { + log.Debugf(log.WebsocketMgr, "%v websocket connection monitor exiting", + w.exchangeName) + } }() for { - time.Sleep(connectionMonitorDelay) - w.m.Lock() - if !w.enabled { - w.m.Unlock() - w.DataHandler <- fmt.Errorf("%v connectionMonitor: websocket disabled, shutting down", w.exchangeName) - err := w.Shutdown() - if err != nil { - log.Error(log.WebsocketMgr, err) + if w.verbose { + log.Debugf(log.WebsocketMgr, "%v running connection monitor cycle", + w.exchangeName) + } + if !w.IsEnabled() { + if w.verbose { + log.Debugf(log.WebsocketMgr, "%v connectionMonitor: websocket disabled, shutting down", w.exchangeName) + } + if w.IsConnected() { + err := w.Shutdown() + if err != nil { + log.Error(log.WebsocketMgr, err) + } } if w.verbose { - log.Debugf(log.WebsocketMgr, "%v connectionMonitor exiting", + log.Debugf(log.WebsocketMgr, "%v websocket connection monitor exiting", w.exchangeName) } return } - w.m.Unlock() - err := w.checkConnection() - if err != nil { - log.Error(log.WebsocketMgr, err) - } - } -} - -// checkConnection ensures the connection is maintained -// Will reconnect on disconnect -func (w *Websocket) checkConnection() error { - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v checking connection", w.exchangeName) - } - switch { - case !w.IsConnected() && !w.IsConnecting(): - w.m.Lock() - defer w.m.Unlock() - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v no connection. Attempt %v/%v", w.exchangeName, w.noConnectionChecks, w.noConnectionCheckLimit) - } - if w.noConnectionChecks >= w.noConnectionCheckLimit { - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v resetting connection", w.exchangeName) + select { + case err := <-w.ReadMessageErrors: + // check if this error is a disconnection error + if isDisconnectionError(err) { + w.setConnectedStatus(false) + w.setConnectingStatus(false) + w.setInit(false) + if w.verbose { + log.Debugf(log.WebsocketMgr, "%v websocket has been disconnected. Reason: %v", + w.exchangeName, err) + } + err = w.Connect() + if err != nil { + log.Error(log.WebsocketMgr, err) + } + } else { + // pass off non disconnect errors to datahandler to manage + w.DataHandler <- err } - w.connecting = true - go w.WebsocketReset() - w.noConnectionChecks = 0 + case <-timer.C: + if !w.IsConnecting() && !w.IsConnected() { + err := w.Connect() + if err != nil { + log.Error(log.WebsocketMgr, err) + } + } + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + timer.Reset(connectionMonitorDelay) } - w.noConnectionChecks++ - case w.IsConnecting(): - if w.reconnectionChecks >= w.reconnectionLimit { - return fmt.Errorf("%v websocket failed to reconnect after %v seconds", - w.exchangeName, - w.reconnectionLimit*int(connectionMonitorDelay.Seconds())) - } - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v Busy reconnecting", w.exchangeName) - } - w.reconnectionChecks++ - default: - w.noConnectionChecks = 0 - w.reconnectionChecks = 0 } - return nil -} - -// IsConnected exposes websocket connection status -func (w *Websocket) IsConnected() bool { - w.m.Lock() - defer w.m.Unlock() - return w.connected -} - -// IsConnecting checks whether websocket is busy connecting -func (w *Websocket) IsConnecting() bool { - w.m.Lock() - defer w.m.Unlock() - return w.connecting } // Shutdown attempts to shut down a websocket connection and associated routines @@ -204,124 +186,145 @@ func (w *Websocket) Shutdown() error { w.Orderbook.FlushCache() w.m.Unlock() }() - if !w.connected && w.ShutdownC == nil { + if !w.IsConnected() { return fmt.Errorf("%v cannot shutdown a disconnected websocket", w.exchangeName) } if w.verbose { log.Debugf(log.WebsocketMgr, "%v shutting down websocket channels", w.exchangeName) } - timer := time.NewTimer(15 * time.Second) - c := make(chan struct{}, 1) - - go func(c chan struct{}) { - close(w.ShutdownC) - w.Wg.Wait() - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v completed websocket channel shutdown", w.exchangeName) - } - c <- struct{}{} - }(c) - - select { - case <-c: - w.connected = false - return nil - case <-timer.C: - return fmt.Errorf("%s websocket routines failed to shutdown after 15 seconds", - w.GetName()) + close(w.ShutdownC) + w.Wg.Wait() + w.setConnectedStatus(false) + w.setConnectingStatus(false) + if w.verbose { + log.Debugf(log.WebsocketMgr, "%v completed websocket channel shutdown", w.exchangeName) } + return nil } -// WebsocketReset sends the shutdown command, waits for channel/func closure and then reconnects -func (w *Websocket) WebsocketReset() { - err := w.Shutdown() - if err != nil { - // does not return here to allow connection to be made if already shut down - w.DataHandler <- fmt.Errorf("%v shutdown error: %v", w.exchangeName, err) - } - log.Infof(log.WebsocketMgr, "%v reconnecting to websocket", w.exchangeName) - w.m.Lock() - w.init = true - w.m.Unlock() - err = w.Connect() - if err != nil { - w.DataHandler <- fmt.Errorf("%v connection error: %v", w.exchangeName, err) - } -} - -// trafficMonitor monitors traffic and switches connection modes for websocket +// trafficMonitor uses a timer of WebsocketTrafficLimitTime and once it expires +// Will reconnect if the TrafficAlert channel has not received any data +// The trafficTimer will reset on each traffic alert func (w *Websocket) trafficMonitor(wg *sync.WaitGroup) { w.Wg.Add(1) - wg.Done() // Makes sure we are unlocking after we add to waitgroup + wg.Done() + trafficTimer := time.NewTimer(w.trafficTimeout) defer func() { - if w.connected { - w.Disconnected <- struct{}{} + if !trafficTimer.Stop() { + select { + case <-trafficTimer.C: + default: + } } + w.setTrafficMonitorRunning(false) w.Wg.Done() }() - - // Define an initial traffic timer which will be a delay then fall over to - // WebsocketTrafficLimitTime after first response - trafficTimer := time.NewTimer(5 * time.Second) + if w.IsTrafficMonitorRunning() { + return + } + w.setTrafficMonitorRunning(true) for { select { - case <-w.ShutdownC: // Returns on shutdown channel close + case <-w.ShutdownC: if w.verbose { log.Debugf(log.WebsocketMgr, "%v trafficMonitor shutdown message received", w.exchangeName) } return - case <-w.TrafficAlert: // Resets timer on traffic - w.m.Lock() - if !w.connected { - w.Connected <- struct{}{} - w.connected = true + case <-w.TrafficAlert: + if !trafficTimer.Stop() { + select { + case <-trafficTimer.C: + default: + } } - w.m.Unlock() - trafficTimer.Reset(WebsocketTrafficLimitTime) + trafficTimer.Reset(w.trafficTimeout) case <-trafficTimer.C: // Falls through when timer runs out - newtimer := time.NewTimer(10 * time.Second) // New secondary timer set if w.verbose { - log.Debugf(log.WebsocketMgr, "%v has not received a traffic alert in 5 seconds.", w.exchangeName) - } - w.m.Lock() - if w.connected { - // If connected divert traffic to rest - w.Disconnected <- struct{}{} - w.connected = false - } - w.m.Unlock() - - select { - case <-w.ShutdownC: // Returns on shutdown channel close - w.m.Lock() - w.connected = false - w.m.Unlock() - return - - case <-newtimer.C: // If secondary timer runs state timeout is sent to the data handler - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v has not received a traffic alert in 15 seconds, exiting", w.exchangeName) - } - w.DataHandler <- fmt.Errorf("trafficMonitor %v", WebsocketStateTimeout) - return - - case <-w.TrafficAlert: // If in this time response traffic comes through - trafficTimer.Reset(WebsocketTrafficLimitTime) - w.m.Lock() - if !w.connected { - // If not connected dive rt traffic from REST to websocket - w.Connected <- struct{}{} - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v has received a traffic alert. Setting status to connected", w.exchangeName) - } - w.connected = true - } - w.m.Unlock() + log.Warnf(log.WebsocketMgr, "%v has not received a traffic alert in %v. Reconnecting", w.exchangeName, w.trafficTimeout) } + go w.Shutdown() } } } +func (w *Websocket) setConnectedStatus(b bool) { + w.connectionMutex.Lock() + w.connected = b + w.connectionMutex.Unlock() +} + +// IsConnected returns status of connection +func (w *Websocket) IsConnected() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.connected +} + +func (w *Websocket) setConnectingStatus(b bool) { + w.connectionMutex.Lock() + w.connecting = b + w.connectionMutex.Unlock() +} + +// IsConnecting returns status of connecting +func (w *Websocket) IsConnecting() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.connecting +} + +func (w *Websocket) setEnabled(b bool) { + w.connectionMutex.Lock() + w.enabled = b + w.connectionMutex.Unlock() +} + +// IsEnabled returns status of enabled +func (w *Websocket) IsEnabled() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.enabled +} + +func (w *Websocket) setInit(b bool) { + w.connectionMutex.Lock() + w.init = b + w.connectionMutex.Unlock() +} + +// IsInit returns status of init +func (w *Websocket) IsInit() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.init +} + +func (w *Websocket) setTrafficMonitorRunning(b bool) { + w.connectionMutex.Lock() + w.trafficMonitorRunning = b + w.connectionMutex.Unlock() +} + +// IsTrafficMonitorRunning returns status of the traffic monitor +func (w *Websocket) IsTrafficMonitorRunning() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.trafficMonitorRunning +} + +func (w *Websocket) setConnectionMonitorRunning(b bool) { + w.connectionMutex.Lock() + w.connectionMonitorRunning = b + w.connectionMutex.Unlock() +} + +// IsConnectionMonitorRunning returns status of connection monitor +func (w *Websocket) IsConnectionMonitorRunning() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.connectionMonitorRunning +} + // SetWebsocketURL sets websocket URL func (w *Websocket) SetWebsocketURL(websocketURL string) { if websocketURL == "" || websocketURL == config.WebsocketURLNonDefaultMessage { @@ -336,55 +339,28 @@ func (w *Websocket) GetWebsocketURL() string { return w.runningURL } -// SetWsStatusAndConnection sets if websocket is enabled -// it will also connect/disconnect the websocket connection -func (w *Websocket) SetWsStatusAndConnection(enabled bool) error { - w.m.Lock() - if w.enabled == enabled { - if w.init { - w.m.Unlock() +// Initialise verifies status and connects +func (w *Websocket) Initialise() error { + if w.IsEnabled() { + if w.IsInit() { return nil } - w.m.Unlock() - return fmt.Errorf("exchange_websocket.go error - already set as %t", - enabled) + return fmt.Errorf("%v Websocket already initialised", + w.exchangeName) } - w.enabled = enabled - if !w.init { - if enabled { - if w.connected { - w.m.Unlock() - return nil - } - w.m.Unlock() - return w.Connect() - } - - if !w.connected { - w.m.Unlock() - return nil - } - w.m.Unlock() - return w.Shutdown() - } - w.m.Unlock() + w.setEnabled(w.enabled) return nil } -// IsEnabled returns bool -func (w *Websocket) IsEnabled() bool { - return w.enabled -} - // SetProxyAddress sets websocket proxy address func (w *Websocket) SetProxyAddress(proxyAddr string) error { if w.proxyAddr == proxyAddr { - return errors.New("exchange_websocket.go error - Setting proxy address - same address") + return fmt.Errorf("%v Cannot set proxy address to the same address '%v'", w.exchangeName, w.proxyAddr) } w.proxyAddr = proxyAddr - if !w.init && w.enabled { - if w.connected { + if !w.IsInit() && w.IsEnabled() { + if w.IsConnected() { err := w.Shutdown() if err != nil { return err @@ -532,19 +508,28 @@ func (w *Websocket) manageSubscriptions() { for { select { case <-w.ShutdownC: + w.subscriptionMutex.Lock() w.subscribedChannels = []WebsocketChannelSubscription{} + w.subscriptionMutex.Unlock() if w.verbose { log.Debugf(log.WebsocketMgr, "%v shutdown manageSubscriptions", w.exchangeName) } return default: time.Sleep(manageSubscriptionsDelay) + if !w.IsConnected() { + w.subscriptionMutex.Lock() + w.subscribedChannels = []WebsocketChannelSubscription{} + w.subscriptionMutex.Unlock() + + continue + } if w.verbose { log.Debugf(log.WebsocketMgr, "%v checking subscriptions", w.exchangeName) } // Subscribe to channels Pending a subscription if w.SupportsFunctionality(WebsocketSubscribeSupported) { - err := w.subscribeToChannels() + err := w.appendSubscribedChannels() if err != nil { w.DataHandler <- err } @@ -559,11 +544,11 @@ func (w *Websocket) manageSubscriptions() { } } -// subscribeToChannels compares channelsToSubscribe to subscribedChannels +// appendSubscribedChannels compares channelsToSubscribe to subscribedChannels // and subscribes to any channels not present in subscribedChannels -func (w *Websocket) subscribeToChannels() error { - w.subscriptionLock.Lock() - defer w.subscriptionLock.Unlock() +func (w *Websocket) appendSubscribedChannels() error { + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() for i := 0; i < len(w.channelsToSubscribe); i++ { channelIsSubscribed := false for j := 0; j < len(w.subscribedChannels); j++ { @@ -589,8 +574,8 @@ func (w *Websocket) subscribeToChannels() error { // unsubscribeToChannels compares subscribedChannels to channelsToSubscribe // and unsubscribes to any channels not present in channelsToSubscribe func (w *Websocket) unsubscribeToChannels() error { - w.subscriptionLock.Lock() - defer w.subscriptionLock.Unlock() + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() for i := 0; i < len(w.subscribedChannels); i++ { subscriptionFound := false for j := 0; j < len(w.channelsToSubscribe); j++ { @@ -622,8 +607,8 @@ func (w *Websocket) RemoveSubscribedChannels(channels []WebsocketChannelSubscrip // removeChannelToSubscribe removes an entry from w.channelsToSubscribe // so an unsubscribe event can be triggered func (w *Websocket) removeChannelToSubscribe(subscribedChannel WebsocketChannelSubscription) { - w.subscriptionLock.Lock() - defer w.subscriptionLock.Unlock() + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() channelLength := len(w.channelsToSubscribe) i := 0 for j := 0; j < len(w.channelsToSubscribe); j++ { @@ -644,8 +629,8 @@ func (w *Websocket) removeChannelToSubscribe(subscribedChannel WebsocketChannelS // ResubscribeToChannel calls unsubscribe func and // removes it from subscribedChannels to trigger a subscribe event func (w *Websocket) ResubscribeToChannel(subscribedChannel WebsocketChannelSubscription) { - w.subscriptionLock.Lock() - defer w.subscriptionLock.Unlock() + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() err := w.channelUnsubscriber(subscribedChannel) if err != nil { w.DataHandler <- err @@ -675,7 +660,6 @@ func (w *Websocket) SubscribeToChannels(channels []WebsocketChannelSubscription) w.channelsToSubscribe = append(w.channelsToSubscribe, channels[i]) } } - w.noConnectionChecks = 0 } // Equal two WebsocketChannelSubscription to determine equality @@ -693,16 +677,16 @@ func (w *Websocket) GetSubscriptions() []WebsocketChannelSubscription { // SetCanUseAuthenticatedEndpoints sets canUseAuthenticatedEndpoints val in // a thread safe manner func (w *Websocket) SetCanUseAuthenticatedEndpoints(val bool) { - w.subscriptionLock.Lock() - defer w.subscriptionLock.Unlock() + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() w.canUseAuthenticatedEndpoints = val } // CanUseAuthenticatedEndpoints gets canUseAuthenticatedEndpoints val in // a thread safe manner func (w *Websocket) CanUseAuthenticatedEndpoints() bool { - w.subscriptionLock.Lock() - defer w.subscriptionLock.Unlock() + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() return w.canUseAuthenticatedEndpoints } @@ -735,6 +719,10 @@ func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header } return fmt.Errorf("%v Error: %v", w.URL, err) } + if w.Verbose { + log.Infof(log.WebsocketMgr, "%v Websocket connected", w.ExchangeName) + } + w.setConnectedStatus(true) return nil } @@ -742,6 +730,9 @@ func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header func (w *WebsocketConnection) SendMessage(data interface{}) error { w.Lock() defer w.Unlock() + if !w.IsConnected() { + return fmt.Errorf("%v cannot send message to a disconnected websocket", w.ExchangeName) + } json, err := common.JSONEncode(data) if err != nil { return err @@ -801,10 +792,26 @@ func (w *WebsocketConnection) WaitForResult(id int64, wg *sync.WaitGroup) { } } +func (w *WebsocketConnection) setConnectedStatus(b bool) { + w.connectionMutex.Lock() + w.connected = b + w.connectionMutex.Unlock() +} + +// IsConnected exposes websocket connection status +func (w *WebsocketConnection) IsConnected() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.connected +} + // ReadMessage reads messages, can handle text, gzip and binary func (w *WebsocketConnection) ReadMessage() (WebsocketResponse, error) { mType, resp, err := w.Connection.ReadMessage() if err != nil { + if isDisconnectionError(err) { + w.setConnectedStatus(false) + } return WebsocketResponse{}, err } var standardMessage []byte @@ -866,3 +873,15 @@ func (w *WebsocketConnection) GenerateMessageID(useNano bool) int64 { } return time.Now().Unix() } + +// isDisconnectionError Determines if the error sent over chan ReadMessageErrors is a disconnection error +func isDisconnectionError(err error) bool { + if websocket.IsUnexpectedCloseError(err) { + return true + } + switch err.(type) { + case *websocket.CloseError, *net.OpError: + return true + } + return false +} diff --git a/exchanges/websocket/wshandler/wshandler_test.go b/exchanges/websocket/wshandler/wshandler_test.go index f3699a8b..931ec661 100644 --- a/exchanges/websocket/wshandler/wshandler_test.go +++ b/exchanges/websocket/wshandler/wshandler_test.go @@ -1,38 +1,151 @@ package wshandler import ( - "fmt" + "bytes" + "compress/flate" + "compress/gzip" + "errors" + "net" + "net/http" + "os" "strings" + "sync" "testing" "time" + + "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/currency" ) -var ws *Websocket +func TestTrafficMonitorTimeout(t *testing.T) { + ws := New() + err := ws.Setup( + &WebsocketSetup{ + Enabled: true, + AuthenticatedWebsocketAPISupport: true, + WebsocketTimeout: 10000, + DefaultURL: "testDefaultURL", + ExchangeName: "exchangeName", + RunningURL: "testRunningURL", + Connector: func() error { return nil }, + Subscriber: func(test WebsocketChannelSubscription) error { return nil }, + UnSubscriber: func(test WebsocketChannelSubscription) error { return nil }, + }) + if err != nil { + t.Error(err) + } + ws.setConnectedStatus(true) + ws.TrafficAlert = make(chan struct{}, 2) + ws.ShutdownC = make(chan struct{}) + var anotherWG sync.WaitGroup + anotherWG.Add(1) + go ws.trafficMonitor(&anotherWG) + anotherWG.Wait() + ws.TrafficAlert <- struct{}{} + trafficTimer := time.NewTimer(5 * time.Second) + select { + case <-trafficTimer.C: + t.Error("should be exiting") + default: + ws.Wg.Wait() + } +} -func TestWebsocketInit(t *testing.T) { - ws = New() - if ws == nil { - t.Error("test failed - Websocket New() error") +func TestIsDisconnectionError(t *testing.T) { + isADisconnectionError := isDisconnectionError(errors.New("errorText")) + if isADisconnectionError { + t.Error("Its not") + } + isADisconnectionError = isDisconnectionError(&websocket.CloseError{ + Code: 1006, + Text: "errorText", + }) + if !isADisconnectionError { + t.Error("It is") + } + + isADisconnectionError = isDisconnectionError(&net.OpError{ + Op: "", + Net: "", + Source: nil, + Addr: nil, + Err: errors.New("errorText"), + }) + if !isADisconnectionError { + t.Error("It is") + } +} + +func TestConnectionMessageErrors(t *testing.T) { + ws := New() + ws.connected = true + ws.enabled = true + ws.ReadMessageErrors = make(chan error) + ws.DataHandler = make(chan interface{}) + ws.ShutdownC = make(chan struct{}) + ws.connector = func() error { return nil } + go ws.connectionMonitor() + timer := time.NewTimer(900 * time.Millisecond) + ws.ReadMessageErrors <- errors.New("errorText") + select { + case err := <-ws.DataHandler: + if err.(error).Error() != "errorText" { + t.Errorf("Expected 'errorText', received %v", err) + } + case <-timer.C: + t.Error("Timeout waiting for datahandler to receive error") + } + timer = time.NewTimer(900 * time.Millisecond) + ws.ReadMessageErrors <- &websocket.CloseError{ + Code: 1006, + Text: "errorText", + } +outer: + for { + select { + case <-ws.DataHandler: + t.Fatal("Error is a disconnection error") + case <-timer.C: + break outer + } } } func TestWebsocket(t *testing.T) { - if err := ws.SetProxyAddress("testProxy"); err != nil { + ws := Websocket{} + ws.setInit(true) + err := ws.Setup(&WebsocketSetup{ + ExchangeName: "test", + Enabled: true, + }) + if err != nil && err.Error() != "test Websocket already initialised" { + t.Errorf("Expected 'test Websocket already initialised', received %v", err) + } + + ws = *New() + err = ws.SetProxyAddress("testProxy") + if err != nil { t.Error("test failed - SetProxyAddress", err) } - ws.Setup(func() error { return nil }, - func(test WebsocketChannelSubscription) error { return nil }, - func(test WebsocketChannelSubscription) error { return nil }, - "testName", - true, - false, - "testDefaultURL", - "testRunningURL", - false) + err = ws.Setup( + &WebsocketSetup{ + Enabled: true, + AuthenticatedWebsocketAPISupport: true, + WebsocketTimeout: 2, + DefaultURL: "testDefaultURL", + ExchangeName: "exchangeName", + RunningURL: "testRunningURL", + Connector: func() error { return nil }, + Subscriber: func(test WebsocketChannelSubscription) error { return nil }, + UnSubscriber: func(test WebsocketChannelSubscription) error { return nil }, + }) + if err != nil { + t.Error(err) + } - // Test variable setting and retreival - if ws.GetName() != "testName" { + if ws.GetName() != "exchangeName" { t.Error("test failed - WebsocketSetup") } @@ -52,25 +165,11 @@ func TestWebsocket(t *testing.T) { t.Error("test failed - WebsocketSetup") } - // Test websocket connect and shutdown functions - comms := make(chan struct{}, 1) - go func() { - var count int - for { - if count == 4 { - close(comms) - return - } - select { - case <-ws.Connected: - count++ - case <-ws.Disconnected: - count++ - } - } - }() + if ws.trafficTimeout != time.Duration(2) { + t.Error("test failed - WebsocketSetup") + } // -- Not connected shutdown - err := ws.Shutdown() + err = ws.Shutdown() if err == nil { t.Fatal("test failed - should not be connected to able to shut down") } @@ -80,70 +179,61 @@ func TestWebsocket(t *testing.T) { if err != nil { t.Fatal("test failed - WebsocketSetup", err) } - + ws.SetWebsocketURL("ws://demos.kaazing.com/echo") // -- Already connected connect err = ws.Connect() if err == nil { t.Fatal("test failed - should not connect, already connected") } - - ws.SetWebsocketURL("") - - // -- Set true when already true - err = ws.SetWsStatusAndConnection(true) - if err == nil { - t.Fatal("test failed - setting enabled should not work") - } - - // -- Set false normal - err = ws.SetWsStatusAndConnection(false) - if err != nil { - t.Fatal("test failed - setting enabled should not work") - } - - // -- Set true normal - err = ws.SetWsStatusAndConnection(true) - if err != nil { - t.Fatal("test failed - setting enabled should not work") - } - // -- Normal shutdown err = ws.Shutdown() if err != nil { t.Fatal("test failed - WebsocketSetup", err) } - - timer := time.NewTimer(5 * time.Second) - select { - case <-comms: - case <-timer.C: - t.Fatal("test failed - WebsocketSetup - timeout") - } + ws.Wg.Wait() } func TestFunctionality(t *testing.T) { - var w Websocket - - if w.FormatFunctionality() != NoWebsocketSupportText { + ws := New() + if ws.FormatFunctionality() != NoWebsocketSupportText { t.Fatalf("Test Failed - FormatFunctionality error expected %s but received %s", - NoWebsocketSupportText, w.FormatFunctionality()) + NoWebsocketSupportText, ws.FormatFunctionality()) } - w.Functionality = 1 << 31 + ws.Functionality = 1 << 31 - if w.FormatFunctionality() != UnknownWebsocketFunctionality+"[1<<31]" { + if ws.FormatFunctionality() != UnknownWebsocketFunctionality+"[1<<31]" { t.Fatal("Test Failed - GetFunctionality error incorrect error returned") } - w.Functionality = WebsocketOrderbookSupported + ws.Functionality = WebsocketOrderbookSupported - if w.GetFunctionality() != WebsocketOrderbookSupported { + if ws.GetFunctionality() != WebsocketOrderbookSupported { t.Fatal("Test Failed - GetFunctionality error incorrect bitmask returned") } - if !w.SupportsFunctionality(WebsocketOrderbookSupported) { + if !ws.SupportsFunctionality(WebsocketOrderbookSupported) { t.Fatal("Test Failed - SupportsFunctionality error should be true") } + + ws.Functionality = WebsocketTickerSupported | WebsocketOrderbookSupported | WebsocketKlineSupported | + WebsocketTradeDataSupported | WebsocketAccountSupported | WebsocketAllowsRequests | + WebsocketSubscribeSupported | WebsocketUnsubscribeSupported | WebsocketAuthenticatedEndpointsSupported | + WebsocketAccountDataSupported | WebsocketSubmitOrderSupported | WebsocketCancelOrderSupported | + WebsocketWithdrawSupported | WebsocketMessageCorrelationSupported | WebsocketSequenceNumberSupported | + WebsocketDeadMansSwitchSupported + formatted := ws.FormatFunctionality() + + if !strings.Contains(formatted, WebsocketTickerSupportedText) || !strings.Contains(formatted, WebsocketOrderbookSupportedText) || + !strings.Contains(formatted, WebsocketKlineSupportedText) || !strings.Contains(formatted, WebsocketTradeDataSupportedText) || + !strings.Contains(formatted, WebsocketAccountSupportedText) || !strings.Contains(formatted, WebsocketAllowsRequestsText) || + !strings.Contains(formatted, WebsocketSubscribeSupportedText) || !strings.Contains(formatted, WebsocketUnsubscribeSupportedText) || + !strings.Contains(formatted, WebsocketAuthenticatedEndpointsSupportedText) || !strings.Contains(formatted, WebsocketAccountDataSupportedText) || + !strings.Contains(formatted, WebsocketSubmitOrderSupportedText) || !strings.Contains(formatted, WebsocketCancelOrderSupportedText) || + !strings.Contains(formatted, WebsocketWithdrawSupportedText) || !strings.Contains(formatted, WebsocketMessageCorrelationSupportedText) || + !strings.Contains(formatted, WebsocketSequenceNumberSupportedText) || !strings.Contains(formatted, WebsocketDeadMansSwitchSupportedText) { + t.Error("Failed to format and include supported websocket features") + } } // placeholderSubscriber basic function to test subscriptions @@ -162,12 +252,32 @@ func TestSubscribe(t *testing.T) { subscribedChannels: []WebsocketChannelSubscription{}, } w.SetChannelSubscriber(placeholderSubscriber) - w.subscribeToChannels() + err := w.appendSubscribedChannels() + if err != nil { + t.Error(err) + } if len(w.subscribedChannels) != 1 { t.Errorf("Subscription did not occur") } } +// TestSubscribe logic test +func TestSubscribeToChannels(t *testing.T) { + w := Websocket{ + channelsToSubscribe: []WebsocketChannelSubscription{ + { + Channel: "hello", + }, + }, + subscribedChannels: []WebsocketChannelSubscription{}, + } + w.SetChannelSubscriber(placeholderSubscriber) + w.SubscribeToChannels([]WebsocketChannelSubscription{{Channel: "hello"}, {Channel: "hello2"}}) + if len(w.channelsToSubscribe) != 2 { + t.Errorf("Subscription did not occur") + } +} + // TestUnsubscribe logic test func TestUnsubscribe(t *testing.T) { w := Websocket{ @@ -179,7 +289,10 @@ func TestUnsubscribe(t *testing.T) { }, } w.SetChannelUnsubscriber(placeholderSubscriber) - w.unsubscribeToChannels() + err := w.unsubscribeToChannels() + if err != nil { + t.Error(err) + } if len(w.subscribedChannels) != 0 { t.Errorf("Unsubscription did not occur") } @@ -200,7 +313,10 @@ func TestSubscriptionWithExistingEntry(t *testing.T) { }, } w.SetChannelSubscriber(placeholderSubscriber) - w.subscribeToChannels() + err := w.appendSubscribedChannels() + if err != nil { + t.Error(err) + } if len(w.subscribedChannels) != 1 { t.Errorf("Subscription should not have occurred") } @@ -221,7 +337,10 @@ func TestUnsubscriptionWithExistingEntry(t *testing.T) { }, } w.SetChannelUnsubscriber(placeholderSubscriber) - w.unsubscribeToChannels() + err := w.unsubscribeToChannels() + if err != nil { + t.Error(err) + } if len(w.subscribedChannels) != 1 { t.Errorf("Unsubscription should not have occurred") } @@ -230,67 +349,49 @@ func TestUnsubscriptionWithExistingEntry(t *testing.T) { // TestManageSubscriptionsStartStop logic test func TestManageSubscriptionsStartStop(t *testing.T) { w := Websocket{ - ShutdownC: make(chan struct{}, 1), + ShutdownC: make(chan struct{}), Functionality: WebsocketSubscribeSupported | WebsocketUnsubscribeSupported, } go w.manageSubscriptions() - time.Sleep(time.Second) close(w.ShutdownC) + w.Wg.Wait() +} + +// TestManageSubscriptionsStartStop logic test +func TestManageSubscriptions(t *testing.T) { + w := Websocket{ + ShutdownC: make(chan struct{}), + Functionality: WebsocketSubscribeSupported | WebsocketUnsubscribeSupported, + subscribedChannels: []WebsocketChannelSubscription{ + { + Channel: "hello", + }, + }, + } + w.SetChannelUnsubscriber(placeholderSubscriber) + w.SetChannelSubscriber(placeholderSubscriber) + w.setConnectedStatus(true) + go w.manageSubscriptions() + time.Sleep(8 * time.Second) + w.setConnectedStatus(false) + time.Sleep(manageSubscriptionsDelay) + w.subscriptionMutex.Lock() + if len(w.subscribedChannels) > 0 { + t.Error("Expected empty subscribed channels") + } + w.subscriptionMutex.Unlock() } // TestConnectionMonitorNoConnection logic test func TestConnectionMonitorNoConnection(t *testing.T) { - w := Websocket{} - w.DataHandler = make(chan interface{}, 1) - w.ShutdownC = make(chan struct{}, 1) - w.exchangeName = "hello" - go w.connectionMonitor() - err := <-w.DataHandler - if !strings.EqualFold(err.(error).Error(), - fmt.Sprintf("%v connectionMonitor: websocket disabled, shutting down", w.exchangeName)) { - t.Errorf("expecting error 'connectionMonitor: websocket disabled, shutting down', received '%v'", err) - } -} - -// TestWsNoConnectionTolerance logic test -func TestWsNoConnectionTolerance(t *testing.T) { - w := Websocket{} - w.DataHandler = make(chan interface{}, 1) - w.ShutdownC = make(chan struct{}, 1) - w.enabled = true - w.noConnectionCheckLimit = 500 - w.checkConnection() - if w.noConnectionChecks == 0 { - t.Errorf("Expected noConnectionTolerance to increment, received '%v'", w.noConnectionChecks) - } -} - -// TestConnecting logic test -func TestConnecting(t *testing.T) { - w := Websocket{} - w.DataHandler = make(chan interface{}, 1) - w.ShutdownC = make(chan struct{}, 1) - w.enabled = true - w.connecting = true - w.reconnectionLimit = 500 - w.checkConnection() - if w.reconnectionChecks != 1 { - t.Errorf("Expected reconnectionLimit to increment, received '%v'", w.reconnectionChecks) - } -} - -// TestReconnectionLimit logic test -func TestReconnectionLimit(t *testing.T) { - w := Websocket{} - w.DataHandler = make(chan interface{}, 1) - w.ShutdownC = make(chan struct{}, 1) - w.enabled = true - w.connecting = true - w.reconnectionChecks = 99 - w.reconnectionLimit = 1 - err := w.checkConnection() - if err == nil { - t.Error("Expected error") + ws := New() + ws.DataHandler = make(chan interface{}, 1) + ws.ShutdownC = make(chan struct{}, 1) + ws.exchangeName = "hello" + ws.trafficTimeout = 1 + go ws.connectionMonitor() + if ws.IsConnectionMonitorRunning() { + t.Fatal("Should have exited") } } @@ -360,26 +461,259 @@ func TestSliceCopyDoesntImpactBoth(t *testing.T) { }, } w.SetChannelUnsubscriber(placeholderSubscriber) - w.unsubscribeToChannels() + err := w.unsubscribeToChannels() + if err != nil { + t.Error(err) + } if len(w.subscribedChannels) != 2 { t.Errorf("Unsubscription did not occur") } w.subscribedChannels[0].Channel = "test" if strings.EqualFold(w.subscribedChannels[0].Channel, w.channelsToSubscribe[0].Channel) { - t.Errorf("Slice has not been copies appropriately") + t.Errorf("Slice has not been copied appropriately") + } +} + +// TestSliceCopyDoesntImpactBoth logic test +func TestGetSubscriptions(t *testing.T) { + w := Websocket{ + subscribedChannels: []WebsocketChannelSubscription{ + { + Channel: "hello3", + }, + }, + } + + subs := w.GetSubscriptions() + subs[0].Channel = "noHELLO" + if strings.EqualFold(w.subscribedChannels[0].Channel, subs[0].Channel) { + t.Error("Subscriptions was not copied properly") } } // TestSetCanUseAuthenticatedEndpoints logic test func TestSetCanUseAuthenticatedEndpoints(t *testing.T) { - w := Websocket{} - result := w.CanUseAuthenticatedEndpoints() + ws := New() + result := ws.CanUseAuthenticatedEndpoints() if result { t.Error("expected `canUseAuthenticatedEndpoints` to be false") } - w.SetCanUseAuthenticatedEndpoints(true) - result = w.CanUseAuthenticatedEndpoints() + ws.SetCanUseAuthenticatedEndpoints(true) + result = ws.CanUseAuthenticatedEndpoints() if !result { t.Error("expected `canUseAuthenticatedEndpoints` to be true") } } + +func TestRemoveSubscribedChannels(t *testing.T) { + w := Websocket{ + channelsToSubscribe: []WebsocketChannelSubscription{ + { + Channel: "hello3", + }, + }, + } + + w.RemoveSubscribedChannels([]WebsocketChannelSubscription{{Channel: "hello3"}}) + if len(w.channelsToSubscribe) == 1 { + t.Error("Did not remove subscription") + } +} + +const ( + websocketTestURL = "wss://www.bitmex.com/realtime" + returnResponseURL = "wss://ws.kraken.com" + useProxyTests = false // Disabled by default. Freely available proxy servers that work all the time are difficult to find + proxyURL = "http://212.186.171.4:80" // Replace with a usable proxy server +) + +var wc *WebsocketConnection +var dialer websocket.Dialer + +type testStruct struct { + Error error + WC WebsocketConnection +} + +type testRequest struct { + Event string `json:"event"` + RequestID int64 `json:"reqid,omitempty"` + Pairs []string `json:"pair"` + Subscription testRequestData `json:"subscription,omitempty"` +} + +// testRequestData contains details on WS channel +type testRequestData struct { + Name string `json:"name,omitempty"` + Interval int64 `json:"interval,omitempty"` + Depth int64 `json:"depth,omitempty"` +} + +type testResponse struct { + RequestID int64 `json:"reqid,omitempty"` +} + +// TestMain setup test +func TestMain(m *testing.M) { + wc = &WebsocketConnection{ + ExchangeName: "test", + URL: returnResponseURL, + ResponseMaxLimit: 7000000000, + ResponseCheckTimeout: 30000000, + } + os.Exit(m.Run()) +} + +// TestDial logic test +func TestDial(t *testing.T) { + var testCases = []testStruct{ + {Error: nil, WC: WebsocketConnection{ExchangeName: "test1", Verbose: true, URL: websocketTestURL, RateLimit: 10, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, + {Error: errors.New(" Error: malformed ws or wss URL"), WC: WebsocketConnection{ExchangeName: "test2", Verbose: true, URL: "", ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, + {Error: nil, WC: WebsocketConnection{ExchangeName: "test3", Verbose: true, URL: websocketTestURL, ProxyURL: proxyURL, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, + } + for i := 0; i < len(testCases); i++ { + testData := &testCases[i] + t.Run(testData.WC.ExchangeName, func(t *testing.T) { + if testData.WC.ProxyURL != "" && !useProxyTests { + t.Skip("Proxy testing not enabled, skipping") + } + err := testData.WC.Dial(&dialer, http.Header{}) + if err != nil { + if testData.Error != nil && err.Error() == testData.Error.Error() { + return + } + t.Fatal(err) + } + }) + } +} + +// TestSendMessage logic test +func TestSendMessage(t *testing.T) { + var testCases = []testStruct{ + {Error: nil, WC: WebsocketConnection{ExchangeName: "test1", Verbose: true, URL: websocketTestURL, RateLimit: 10, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, + {Error: errors.New(" Error: malformed ws or wss URL"), WC: WebsocketConnection{ExchangeName: "test2", Verbose: true, URL: "", ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, + {Error: nil, WC: WebsocketConnection{ExchangeName: "test3", Verbose: true, URL: websocketTestURL, ProxyURL: proxyURL, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, + } + for i := 0; i < len(testCases); i++ { + testData := &testCases[i] + t.Run(testData.WC.ExchangeName, func(t *testing.T) { + if testData.WC.ProxyURL != "" && !useProxyTests { + t.Skip("Proxy testing not enabled, skipping") + } + err := testData.WC.Dial(&dialer, http.Header{}) + if err != nil { + if testData.Error != nil && err.Error() == testData.Error.Error() { + return + } + t.Fatal(err) + } + err = testData.WC.SendMessage("ping") + if err != nil { + t.Error(err) + } + }) + } +} + +// TestSendMessageWithResponse logic test +func TestSendMessageWithResponse(t *testing.T) { + if wc.ProxyURL != "" && !useProxyTests { + t.Skip("Proxy testing not enabled, skipping") + } + err := wc.Dial(&dialer, http.Header{}) + if err != nil { + t.Fatal(err) + } + go readMessages(wc, t) + + request := testRequest{ + Event: "subscribe", + Pairs: []string{currency.NewPairWithDelimiter("XBT", "USD", "/").String()}, + Subscription: testRequestData{ + Name: "ticker", + }, + RequestID: wc.GenerateMessageID(false), + } + _, err = wc.SendMessageReturnResponse(request.RequestID, request) + if err != nil { + t.Error(err) + } +} + +// TestParseBinaryResponse logic test +func TestParseBinaryResponse(t *testing.T) { + var b bytes.Buffer + w := gzip.NewWriter(&b) + _, err := w.Write([]byte("hello")) + if err != nil { + t.Error(err) + } + err = w.Close() + if err != nil { + t.Error(err) + } + var resp []byte + resp, err = wc.parseBinaryResponse(b.Bytes()) + if err != nil { + t.Error(err) + } + if !strings.EqualFold(string(resp), "hello") { + t.Errorf("GZip conversion failed. Received: '%v', Expected: 'hello'", string(resp)) + } + + var b2 bytes.Buffer + w2, err2 := flate.NewWriter(&b2, 1) + if err2 != nil { + t.Error(err2) + } + _, err2 = w2.Write([]byte("hello")) + if err2 != nil { + t.Error(err) + } + err2 = w2.Close() + if err2 != nil { + t.Error(err) + } + resp2, err3 := wc.parseBinaryResponse(b2.Bytes()) + if err3 != nil { + t.Error(err3) + } + if !strings.EqualFold(string(resp2), "hello") { + t.Errorf("GZip conversion failed. Received: '%v', Expected: 'hello'", string(resp2)) + } +} + +// TestAddResponseWithID logic test +func TestAddResponseWithID(t *testing.T) { + wc.IDResponses = nil + wc.AddResponseWithID(0, []byte("hi")) + wc.AddResponseWithID(1, []byte("hi")) +} + +// readMessages helper func +func readMessages(wc *WebsocketConnection, t *testing.T) { + timer := time.NewTimer(20 * time.Second) + for { + select { + case <-timer.C: + return + default: + resp, err := wc.ReadMessage() + if err != nil { + t.Error(err) + return + } + var incoming testResponse + err = common.JSONDecode(resp.Raw, &incoming) + if err != nil { + t.Error(err) + return + } + if incoming.RequestID > 0 { + wc.AddResponseWithID(incoming.RequestID, resp.Raw) + return + } + } + } +} diff --git a/exchanges/websocket/wshandler/wshandler_types.go b/exchanges/websocket/wshandler/wshandler_types.go index 529c124d..6469efe4 100644 --- a/exchanges/websocket/wshandler/wshandler_types.go +++ b/exchanges/websocket/wshandler/wshandler_types.go @@ -48,51 +48,40 @@ const ( WebsocketMessageCorrelationSupportedText = "WEBSOCKET MESSAGE CORRELATION SUPPORTED" WebsocketSequenceNumberSupportedText = "WEBSOCKET SEQUENCE NUMBER SUPPORTED" WebsocketDeadMansSwitchSupportedText = "WEBSOCKET DEAD MANS SWITCH SUPPORTED" - // WebsocketNotEnabled alerts of a disabled websocket - WebsocketNotEnabled = "exchange_websocket_not_enabled" - // WebsocketTrafficLimitTime defines a standard time for no traffic from the - // websocket connection - WebsocketTrafficLimitTime = 5 * time.Second - websocketRestablishConnection = time.Second - manageSubscriptionsDelay = 5 * time.Second + WebsocketNotEnabled = "exchange_websocket_not_enabled" + manageSubscriptionsDelay = 5 * time.Second // connection monitor time delays and limits connectionMonitorDelay = 2 * time.Second - // WebsocketStateTimeout defines a const for when a websocket connection - // times out, will be handled by the routine management system - WebsocketStateTimeout = "TIMEOUT" ) // Websocket defines a return type for websocket connections via the interface // wrapper for routine processing in routines.go type Websocket struct { - proxyAddr string - defaultURL string - runningURL string - exchangeName string - enabled bool - init bool - connected bool - connecting bool - verbose bool - connector func() error - m sync.Mutex - subscriptionLock sync.Mutex - connectionMonitorRunning bool - reconnectionLimit int - noConnectionChecks int - reconnectionChecks int - noConnectionCheckLimit int - subscribedChannels []WebsocketChannelSubscription - channelsToSubscribe []WebsocketChannelSubscription - channelSubscriber func(channelToSubscribe WebsocketChannelSubscription) error - channelUnsubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error - // Connected denotes a channel switch for diversion of request flow - Connected chan struct{} - // Disconnected denotes a channel switch for diversion of request flow - Disconnected chan struct{} - // DataHandler pipes websocket data to an exchange websocket data handler - DataHandler chan interface{} + // Functionality defines websocket stream capabilities + Functionality uint32 + canUseAuthenticatedEndpoints bool + enabled bool + init bool + connected bool + connecting bool + trafficMonitorRunning bool + verbose bool + connectionMonitorRunning bool + trafficTimeout time.Duration + proxyAddr string + defaultURL string + runningURL string + exchangeName string + m sync.Mutex + subscriptionMutex sync.Mutex + connectionMutex sync.RWMutex + connector func() error + subscribedChannels []WebsocketChannelSubscription + channelsToSubscribe []WebsocketChannelSubscription + channelSubscriber func(channelToSubscribe WebsocketChannelSubscription) error + channelUnsubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error + DataHandler chan interface{} // ShutdownC is the main shutdown channel which controls all websocket go funcs ShutdownC chan struct{} // Orderbook is a local cache of orderbooks @@ -102,9 +91,21 @@ type Websocket struct { Wg sync.WaitGroup // TrafficAlert monitors if there is a halt in traffic throughput TrafficAlert chan struct{} - // Functionality defines websocket stream capabilities - Functionality uint32 - canUseAuthenticatedEndpoints bool + // ReadMessageErrors will received all errors from ws.ReadMessage() and verify if its a disconnection + ReadMessageErrors chan error +} + +type WebsocketSetup struct { + Enabled bool + Verbose bool + AuthenticatedWebsocketAPISupport bool + WebsocketTimeout time.Duration + DefaultURL string + ExchangeName string + RunningURL string + Connector func() error + Subscriber func(channelToSubscribe WebsocketChannelSubscription) error + UnSubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error } // WebsocketChannelSubscription container for websocket subscriptions @@ -187,16 +188,19 @@ type WebsocketPositionUpdated struct { // WebsocketConnection contains all the data needed to send a message to a WS type WebsocketConnection struct { sync.Mutex - Verbose bool - RateLimit float64 - ExchangeName string - URL string - ProxyURL string - Wg sync.WaitGroup - Connection *websocket.Conn - Shutdown chan struct{} + Verbose bool + connected bool + connectionMutex sync.RWMutex + RateLimit float64 + ExchangeName string + URL string + ProxyURL string + Wg sync.WaitGroup + Connection *websocket.Conn + Shutdown chan struct{} // These are the request IDs and the corresponding response JSON IDResponses map[int64][]byte ResponseCheckTimeout time.Duration ResponseMaxLimit time.Duration + TrafficTimeout time.Duration } diff --git a/exchanges/websocket/wsorderbook/wsorderbook.go b/exchanges/websocket/wsorderbook/wsorderbook.go index 66b59d13..3446ddbd 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook.go +++ b/exchanges/websocket/wsorderbook/wsorderbook.go @@ -201,7 +201,7 @@ func (w *WebsocketOrderbookLocal) updateByIDAndAction(orderbookUpdate *Websocket // LoadSnapshot loads initial snapshot of ob data, overwrite allows full // ob to be completely rewritten because the exchange is a doing a full // update not an incremental one -func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base, overwrite bool) error { +func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base) error { if len(newOrderbook.Asks) == 0 || len(newOrderbook.Bids) == 0 { return fmt.Errorf("%v snapshot ask and bids are nil", w.exchangeName) } @@ -216,11 +216,8 @@ func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base, ove if w.ob[newOrderbook.Pair][newOrderbook.AssetType] != nil && (len(w.ob[newOrderbook.Pair][newOrderbook.AssetType].Asks) > 0 || len(w.ob[newOrderbook.Pair][newOrderbook.AssetType].Bids) > 0) { - if overwrite { - w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook - return newOrderbook.Process() - } - return fmt.Errorf("%v snapshot instance already found", w.exchangeName) + w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook + return newOrderbook.Process() } w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook return newOrderbook.Process() diff --git a/exchanges/websocket/wsorderbook/wsorderbook_test.go b/exchanges/websocket/wsorderbook/wsorderbook_test.go index b3edf789..acc00095 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_test.go +++ b/exchanges/websocket/wsorderbook/wsorderbook_test.go @@ -38,7 +38,7 @@ func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, b snapShot1.AssetType = asset.Spot snapShot1.Pair = curr obl = &WebsocketOrderbookLocal{} - err = obl.LoadSnapshot(&snapShot1, false) + err = obl.LoadSnapshot(&snapShot1) return } @@ -382,8 +382,7 @@ func TestRunSnapshotWithNoData(t *testing.T) { snapShot1.Pair = curr snapShot1.ExchangeName = "test" obl.exchangeName = "test" - err := obl.LoadSnapshot(&snapShot1, - false) + err := obl.LoadSnapshot(&snapShot1) if err == nil { t.Fatal("expected an error loading a snapshot") } @@ -392,8 +391,8 @@ func TestRunSnapshotWithNoData(t *testing.T) { } } -// TestLoadSnapshotWithOverride logic test -func TestLoadSnapshotWithOverride(t *testing.T) { +// TestLoadSnapshot logic test +func TestLoadSnapshot(t *testing.T) { var obl WebsocketOrderbookLocal var snapShot1 orderbook.Base curr := currency.NewPairFromString("BTCUSD") @@ -407,21 +406,13 @@ func TestLoadSnapshotWithOverride(t *testing.T) { snapShot1.Bids = bids snapShot1.AssetType = asset.Spot snapShot1.Pair = curr - err := obl.LoadSnapshot(&snapShot1, false) - if err != nil { - t.Error(err) - } - err = obl.LoadSnapshot(&snapShot1, false) - if err == nil { - t.Error("expected error: 'snapshot instance already found'") - } - err = obl.LoadSnapshot(&snapShot1, true) + err := obl.LoadSnapshot(&snapShot1) if err != nil { t.Error(err) } } -// TestInsertWithIDs logic test +// TestFlushCache logic test func TestFlushCache(t *testing.T) { obl, curr, _, _, err := createSnapshot() if err != nil { @@ -473,7 +464,7 @@ func TestInsertingSnapShots(t *testing.T) { snapShot1.Bids = bids snapShot1.AssetType = asset.Spot snapShot1.Pair = currency.NewPairFromString("BTCUSD") - err := obl.LoadSnapshot(&snapShot1, false) + err := obl.LoadSnapshot(&snapShot1) if err != nil { t.Fatal(err) } @@ -510,7 +501,7 @@ func TestInsertingSnapShots(t *testing.T) { snapShot2.Bids = bids snapShot2.AssetType = asset.Spot snapShot2.Pair = currency.NewPairFromString("LTCUSD") - err = obl.LoadSnapshot(&snapShot2, false) + err = obl.LoadSnapshot(&snapShot2) if err != nil { t.Fatal(err) } @@ -547,7 +538,7 @@ func TestInsertingSnapShots(t *testing.T) { snapShot3.Bids = bids snapShot3.AssetType = "FUTURES" snapShot3.Pair = currency.NewPairFromString("LTCUSD") - err = obl.LoadSnapshot(&snapShot3, false) + err = obl.LoadSnapshot(&snapShot3) if err != nil { t.Fatal(err) } diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 7cd23866..f52c0aed 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -58,7 +58,7 @@ func (z *ZB) WsHandleData() { default: resp, err := z.WebsocketConn.ReadMessage() if err != nil { - z.Websocket.DataHandler <- err + z.Websocket.ReadMessageErrors <- err return } z.Websocket.TrafficAlert <- struct{}{} @@ -143,8 +143,7 @@ func (z *ZB) WsHandleData() { newOrderBook.AssetType = asset.Spot newOrderBook.Pair = cPair - err = z.Websocket.Orderbook.LoadSnapshot(&newOrderBook, - true) + err = z.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { z.Websocket.DataHandler <- err continue diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index e415043d..37a2a637 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -117,15 +117,18 @@ func (z *ZB) Setup(exch *config.ExchangeConfig) error { return err } - err = z.Websocket.Setup(z.WsConnect, - z.Subscribe, - nil, - exch.Name, - exch.Features.Enabled.Websocket, - exch.Verbose, - zbWebsocketAPI, - exch.API.Endpoints.WebsocketURL, - exch.API.AuthenticatedWebsocketSupport) + err = z.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: zbWebsocketAPI, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: z.WsConnect, + Subscriber: z.Subscribe, + }) if err != nil { return err } From 4a0fcc7f0f2ecddc7ad0314fecec45df76e0bb7c Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 2 Oct 2019 09:33:30 +1000 Subject: [PATCH 47/71] Fix exchange.go test after PR --- exchanges/exchange_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index ca3060da..7a10eab4 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -901,7 +901,9 @@ func TestSetupDefaults(t *testing.T) { if err := b.SetupDefaults(&cfg); err != nil { t.Error(err) } - b.Websocket.Setup(nil, nil, nil, "", true, false, "", "", false) + b.Websocket.Setup(&wshandler.WebsocketSetup{ + Enabled: true, + }) if !b.IsWebsocketEnabled() { t.Error("websocket should be enabled") } From db317a2447f6c8fbfcab17691d58ab3401301475 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Thu, 3 Oct 2019 09:47:37 +1000 Subject: [PATCH 48/71] Engine: Dispatch service (#346) * Added dispatch service * Added orderbook streaming capabilities * Assigned correct orderbook.base exchange name * Fixed Requested niterinos Add in cli orderbook QA tool to gctcli Add exchange orderbook streaming * Add ticker streaming support through dispatch package * Added in some more info on error returns for orderbook.go * fix linter issues * Fix some issues * Update * Fix requested * move dispatch out of exchanges folder to its own independant folder * Fix requested * change orderbook string to tickers * Limit orderbooks to 50 and made dispatch system more stateless in operation * lower cases for update/retrieve/sub exchange name * Adds in asset validation and lower case conversion on cli * Remove comment * Moved timer to a higher scope so its not constantly initialised just reset per instance and removed returning unused channel on error * Rm unused release function in dispatch.go Reset timer and bleed buffered timer chan if needed in dispatch.go Added in ticker.Stop() and timer.Stop() functions for worker routine return in dispatch.go Index aggregated bid and ask functions for orderbook.go Added in dummy slice for wsorderbook_test.go * Moved drain to above Reset so potential race would not occur in dispatch.go Fix various linter issues dispatch.go * Fix some issues * change to start/stop service, added in service state change via cli, updated logger * fix requested * Add worker amount init spawning * fix linter issues * Fix more linter issues * More fixes * Fix race issue on releasing pipe channel on a close after shutting down dispatcher system * Moved all types to dispatch_types.go && remove panic * Moved types into serperate file && improve test coverage * RM unnecessary select case for draining channel && fixed error string * Added orderbook_types file and improved code coverage * gofmt file * reinstated select cases on drain because I am silly * Remove error for drop worker * Added more test cases * not checking error issue fix * remove func causing race in test, this has required protection via an exported function * set Gemini websocket orderbook exchange name --- cmd/gctcli/commands.go | 448 +++++++ cmd/gctcli/main.go | 4 + cmd/gctcli/validation.go | 6 + dispatch/dispatch.go | 357 +++++ dispatch/dispatch_test.go | 307 +++++ dispatch/dispatch_types.go | 88 ++ dispatch/mux.go | 77 ++ engine/engine.go | 17 + engine/engine_types.go | 4 + engine/events_test.go | 12 - engine/helpers.go | 7 + engine/routines.go | 96 -- engine/rpcserver.go | 206 ++- exchanges/anx/anx_test.go | 4 +- exchanges/binance/binance_websocket.go | 1 + exchanges/bitfinex/bitfinex_websocket.go | 2 + exchanges/bitmex/bitmex_websocket.go | 2 + exchanges/bitstamp/bitstamp_websocket.go | 1 + .../coinbasepro/coinbasepro_websocket.go | 1 + exchanges/coinut/coinut_websocket.go | 1 + exchanges/gateio/gateio_websocket.go | 1 + exchanges/gemini/gemini_websocket.go | 1 + exchanges/hitbtc/hitbtc_websocket.go | 1 + exchanges/orderbook/orderbook.go | 360 ++--- exchanges/orderbook/orderbook_test.go | 245 ++-- exchanges/orderbook/orderbook_types.go | 69 + exchanges/poloniex/poloniex_websocket.go | 1 + exchanges/ticker/ticker.go | 304 +++-- exchanges/ticker/ticker_test.go | 280 ++-- exchanges/ticker/ticker_types.go | 57 + .../websocket/wsorderbook/wsorderbook.go | 14 + .../websocket/wsorderbook/wsorderbook_test.go | 81 +- exchanges/zb/zb_websocket.go | 1 + gctrpc/rpc.pb.go | 1162 +++++++++++++---- gctrpc/rpc.pb.gw.go | 331 ++++- gctrpc/rpc.proto | 44 + gctrpc/rpc.swagger.json | 207 +++ go.mod | 7 +- go.sum | 9 +- logger/logger_setup.go | 1 + logger/sublogger_types.go | 1 + main.go | 3 + 42 files changed, 3802 insertions(+), 1019 deletions(-) create mode 100644 dispatch/dispatch.go create mode 100644 dispatch/dispatch_test.go create mode 100644 dispatch/dispatch_types.go create mode 100644 dispatch/mux.go create mode 100644 exchanges/orderbook/orderbook_types.go create mode 100644 exchanges/ticker/ticker_types.go diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index 943009a0..ddcc043b 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -4,7 +4,11 @@ import ( "context" "errors" "fmt" + "os" + "os/exec" + "runtime" "strconv" + "strings" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" @@ -544,6 +548,11 @@ func getTicker(c *cli.Context) error { assetType = c.Args().Get(2) } + assetType = strings.ToLower(assetType) + if !validAsset(assetType) { + return errInvalidAsset + } + conn, err := setupClient() if err != nil { return err @@ -652,6 +661,11 @@ func getOrderbook(c *cli.Context) error { assetType = c.Args().Get(2) } + assetType = strings.ToLower(assetType) + if !validAsset(assetType) { + return errInvalidAsset + } + conn, err := setupClient() if err != nil { return err @@ -1066,6 +1080,11 @@ func getOrders(c *cli.Context) error { assetType = c.Args().Get(1) } + assetType = strings.ToLower(assetType) + if !validAsset(assetType) { + return errInvalidAsset + } + if c.IsSet("pair") { currencyPair = c.String("pair") } else { @@ -1559,6 +1578,11 @@ func cancelOrder(c *cli.Context) error { assetType = c.String("asset_type") } + assetType = strings.ToLower(assetType) + if !validAsset(assetType) { + return errInvalidAsset + } + if c.IsSet("wallet_address") { walletAddress = c.String("wallet_address") } @@ -1782,6 +1806,11 @@ func addEvent(c *cli.Context) error { assetType = c.String("asset_type") } + assetType = strings.ToLower(assetType) + if !validAsset(assetType) { + return errInvalidAsset + } + if c.IsSet("action") { action = c.String("action") } else { @@ -2172,6 +2201,11 @@ func getExchangePairs(c *cli.Context) error { asset = c.Args().Get(1) } + asset = strings.ToLower(asset) + if !validAsset(asset) { + return errInvalidAsset + } + conn, err := setupClient() if err != nil { return err @@ -2249,6 +2283,11 @@ func enableExchangePair(c *cli.Context) error { asset = c.Args().Get(2) } + asset = strings.ToLower(asset) + if !validAsset(asset) { + return errInvalidAsset + } + conn, err := setupClient() if err != nil { return err @@ -2332,6 +2371,11 @@ func disableExchangePair(c *cli.Context) error { asset = c.Args().Get(2) } + asset = strings.ToLower(asset) + if !validAsset(asset) { + return errInvalidAsset + } + conn, err := setupClient() if err != nil { return err @@ -2357,3 +2401,407 @@ func disableExchangePair(c *cli.Context) error { jsonOutput(result) return nil } + +var getOrderbookStreamCommand = cli.Command{ + Name: "getorderbookstream", + Usage: "gets the orderbook stream for a specific currency pair and exchange", + ArgsUsage: " ", + Action: getOrderbookStream, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the orderbook from", + }, + cli.StringFlag{ + Name: "pair", + Usage: "currency pair", + }, + cli.StringFlag{ + Name: "asset", + Usage: "the asset type of the currency pair", + }, + }, +} + +func getOrderbookStream(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getorderbookstream") + return nil + } + + var exchangeName string + var pair string + var assetType string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if !validExchange(exchangeName) { + return errInvalidExchange + } + + if c.IsSet("pair") { + pair = c.String("pair") + } else { + pair = c.Args().Get(1) + } + + if !validPair(pair) { + return errInvalidPair + } + + if c.IsSet("asset") { + assetType = c.String("asset") + } else { + assetType = c.Args().Get(2) + } + + assetType = strings.ToLower(assetType) + + if !validAsset(assetType) { + return errInvalidAsset + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + p := currency.NewPairDelimiter(pair, pairDelimiter) + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetOrderbookStream(context.Background(), + &gctrpc.GetOrderbookStreamRequest{ + Exchange: exchangeName, + Pair: &gctrpc.CurrencyPair{ + Base: p.Base.String(), + Quote: p.Quote.String(), + Delimiter: p.Delimiter, + }, + AssetType: assetType, + }, + ) + + if err != nil { + return err + } + + for { + resp, err := result.Recv() + if err != nil { + return err + } + + err = clearScreen() + if err != nil { + return err + } + + fmt.Printf("Orderbook stream for %s %s:\n\n", exchangeName, + resp.Pair.String()) + fmt.Println("\t\tBids\t\t\t\tAsks") + fmt.Println() + + bidLen := len(resp.Bids) - 1 + askLen := len(resp.Asks) - 1 + + var maxLen int + if bidLen >= askLen { + maxLen = bidLen + } else { + maxLen = askLen + } + + for i := 0; i < maxLen; i++ { + var bidAmount, bidPrice float64 + if i <= bidLen { + bidAmount = resp.Bids[i].Amount + bidPrice = resp.Bids[i].Price + } + + var askAmount, askPrice float64 + if i <= askLen { + askAmount = resp.Asks[i].Amount + askPrice = resp.Asks[i].Price + } + + fmt.Printf("%f %s @ %f %s\t\t%f %s @ %f %s\n", + bidAmount, + resp.Pair.Base, + bidPrice, + resp.Pair.Quote, + askAmount, + resp.Pair.Base, + askPrice, + resp.Pair.Quote) + + if i >= 49 { + // limits orderbook display output + break + } + } + } +} + +var getExchangeOrderbookStreamCommand = cli.Command{ + Name: "getexchangeorderbookstream", + Usage: "gets a stream for all orderbooks associated with an exchange", + ArgsUsage: "", + Action: getExchangeOrderbookStream, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the orderbook from", + }, + }, +} + +func getExchangeOrderbookStream(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getexchangeorderbookstream") + return nil + } + + var exchangeName string + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if !validExchange(exchangeName) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetExchangeOrderbookStream(context.Background(), + &gctrpc.GetExchangeOrderbookStreamRequest{ + Exchange: exchangeName, + }) + + if err != nil { + return err + } + + for { + resp, err := result.Recv() + if err != nil { + return err + } + + err = clearScreen() + if err != nil { + return err + } + + fmt.Printf("Orderbook streamed for %s %s", + exchangeName, + resp.Pair.String()) + } +} + +var getTickerStreamCommand = cli.Command{ + Name: "gettickerstream", + Usage: "gets the ticker stream for a specific currency pair and exchange", + ArgsUsage: " ", + Action: getTickerStream, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the ticker from", + }, + cli.StringFlag{ + Name: "pair", + Usage: "currency pair", + }, + cli.StringFlag{ + Name: "asset", + Usage: "the asset type of the currency pair", + }, + }, +} + +func getTickerStream(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "gettickerstream") + return nil + } + + var exchangeName string + var pair string + var assetType string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if !validExchange(exchangeName) { + return errInvalidExchange + } + + if c.IsSet("pair") { + pair = c.String("pair") + } else { + pair = c.Args().Get(1) + } + + if !validPair(pair) { + return errInvalidPair + } + + if c.IsSet("asset") { + assetType = c.String("asset") + } else { + assetType = c.Args().Get(2) + } + + assetType = strings.ToLower(assetType) + + if !validAsset(assetType) { + return errInvalidAsset + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + p := currency.NewPairDelimiter(pair, pairDelimiter) + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetTickerStream(context.Background(), + &gctrpc.GetTickerStreamRequest{ + Exchange: exchangeName, + Pair: &gctrpc.CurrencyPair{ + Base: p.Base.String(), + Quote: p.Quote.String(), + Delimiter: p.Delimiter, + }, + AssetType: assetType, + }, + ) + + if err != nil { + return err + } + + for { + resp, err := result.Recv() + if err != nil { + return err + } + + err = clearScreen() + if err != nil { + return err + } + + fmt.Printf("Ticker stream for %s %s:\n", exchangeName, + resp.Pair.String()) + fmt.Println() + + fmt.Printf("LAST: %f\n HIGH: %f\n LOW: %f\n BID: %f\n ASK: %f\n VOLUME: %f\n PRICEATH: %f\n LASTUPDATED: %d\n", + resp.Last, + resp.High, + resp.Low, + resp.Bid, + resp.Ask, + resp.Volume, + resp.PriceAth, + resp.LastUpdated) + } +} + +var getExchangeTickerStreamCommand = cli.Command{ + Name: "getexchangetickerstream", + Usage: "gets a stream for all tickers associated with an exchange", + ArgsUsage: "", + Action: getExchangeTickerStream, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the ticker from", + }, + }, +} + +func getExchangeTickerStream(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getexchangetickerstream") + return nil + } + + var exchangeName string + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if !validExchange(exchangeName) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetExchangeTickerStream(context.Background(), + &gctrpc.GetExchangeTickerStreamRequest{ + Exchange: exchangeName, + }) + + if err != nil { + return err + } + + for { + resp, err := result.Recv() + if err != nil { + return err + } + + fmt.Printf("Ticker stream for %s %s:\n", + exchangeName, + resp.Pair.String()) + + fmt.Printf("LAST: %f HIGH: %f LOW: %f BID: %f ASK: %f VOLUME: %f PRICEATH: %f LASTUPDATED: %d\n", + resp.Last, + resp.High, + resp.Low, + resp.Bid, + resp.Ask, + resp.Volume, + resp.PriceAth, + resp.LastUpdated) + } +} + +func clearScreen() error { + switch runtime.GOOS { + case "windows": + cmd := exec.Command("cmd", "/c", "cls") + cmd.Stdout = os.Stdout + return cmd.Run() + default: + cmd := exec.Command("clear") + cmd.Stdout = os.Stdout + return cmd.Run() + } +} diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index afe0316d..31e4fe17 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -128,6 +128,10 @@ func main() { getExchangePairsCommand, enableExchangePairCommand, disableExchangePairCommand, + getOrderbookStreamCommand, + getExchangeOrderbookStreamCommand, + getTickerStreamCommand, + getExchangeTickerStreamCommand, } err := app.Run(os.Args) diff --git a/cmd/gctcli/validation.go b/cmd/gctcli/validation.go index fd3202b0..fd308b06 100644 --- a/cmd/gctcli/validation.go +++ b/cmd/gctcli/validation.go @@ -5,11 +5,13 @@ import ( "strings" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) var ( errInvalidPair = errors.New("invalid currency pair supplied") errInvalidExchange = errors.New("invalid exchange supplied") + errInvalidAsset = errors.New("invalid asset supplied") ) func validPair(pair string) bool { @@ -19,3 +21,7 @@ func validPair(pair string) bool { func validExchange(exch string) bool { return exchange.IsSupported(exch) } + +func validAsset(i string) bool { + return asset.IsValid(asset.Item(i)) +} diff --git a/dispatch/dispatch.go b/dispatch/dispatch.go new file mode 100644 index 00000000..8fb8b096 --- /dev/null +++ b/dispatch/dispatch.go @@ -0,0 +1,357 @@ +package dispatch + +import ( + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/gofrs/uuid" + log "github.com/thrasher-corp/gocryptotrader/logger" +) + +func init() { + dispatcher = &Dispatcher{ + routes: make(map[uuid.UUID][]chan interface{}), + jobs: make(chan *job, DefaultJobBuffer), + outbound: sync.Pool{ + New: func() interface{} { + // Create unbuffered channel for data pass + return make(chan interface{}) + }, + }, + } +} + +// Start starts the dispatch system by spawning workers and allocating memory +func Start(workers int64) error { + if dispatcher == nil { + return errors.New(errNotInitialised) + } + + mtx.Lock() + defer mtx.Unlock() + return dispatcher.start(workers) +} + +// Stop attempts to stop the dispatch service, this will close all pipe channels +// flush job list and drop all workers +func Stop() error { + if dispatcher == nil { + return errors.New(errNotInitialised) + } + + log.Debugln(log.DispatchMgr, "Dispatch manager shutting down...") + + mtx.Lock() + defer mtx.Unlock() + return dispatcher.stop() +} + +// IsRunning checks to see if the dispatch service is running +func IsRunning() bool { + if dispatcher == nil { + return false + } + + return dispatcher.isRunning() +} + +// DropWorker drops a worker routine +func DropWorker() error { + if dispatcher == nil { + return errors.New(errNotInitialised) + } + + dispatcher.dropWorker() + return nil +} + +// SpawnWorker starts a new worker routine +func SpawnWorker() error { + if dispatcher == nil { + return errors.New(errNotInitialised) + } + return dispatcher.spawnWorker() +} + +// start compares atomic running value, sets defaults, overides with +// configuration, then spawns workers +func (d *Dispatcher) start(workers int64) error { + if atomic.LoadUint32(&d.running) == 1 { + return errors.New(errAlreadyStarted) + } + + if workers < 1 { + log.Warn(log.DispatchMgr, + "Dispatcher: workers cannot be zero using default values") + workers = DefaultMaxWorkers + } + + d.maxWorkers = workers + d.shutdown = make(chan *sync.WaitGroup) + + if atomic.LoadInt64(&d.count) != 0 { + return errors.New("dispatcher leaked workers found") + } + + for i := int64(0); i < d.maxWorkers; i++ { + err := d.spawnWorker() + if err != nil { + return err + } + } + + atomic.SwapUint32(&d.running, 1) + return nil +} + +// stop stops the service and shuts down all worker routines +func (d *Dispatcher) stop() error { + if !atomic.CompareAndSwapUint32(&d.running, 1, 0) { + return errors.New(errCannotShutdown) + } + close(d.shutdown) + ch := make(chan struct{}) + timer := time.NewTimer(1 * time.Second) + defer func() { + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + }() + go func(ch chan struct{}) { d.wg.Wait(); ch <- struct{}{} }(ch) + select { + case <-ch: + // close all routes + for key := range d.routes { + for i := range d.routes[key] { + close(d.routes[key][i]) + } + + d.routes[key] = nil + } + + for len(d.jobs) != 0 { // drain jobs channel for old data + <-d.jobs + } + + log.Debugln(log.DispatchMgr, "Dispatch manager shutdown.") + + return nil + case <-timer.C: + return errors.New(errShutdownRoutines) + } +} + +// isRunning returns if the dispatch system is running +func (d *Dispatcher) isRunning() bool { + return atomic.LoadUint32(&d.running) == 1 +} + +// dropWorker deallocates a worker routine +func (d *Dispatcher) dropWorker() { + wg := sync.WaitGroup{} + wg.Add(1) + d.shutdown <- &wg + wg.Wait() +} + +// spawnWorker allocates a new worker for job processing +func (d *Dispatcher) spawnWorker() error { + if atomic.LoadInt64(&d.count) >= d.maxWorkers { + return errors.New("dispatcher cannot spawn more workers; ceiling reached") + } + var spawnWg sync.WaitGroup + spawnWg.Add(1) + go d.relayer(&spawnWg) + spawnWg.Wait() + return nil +} + +// Relayer routine relays communications across the defined routes +func (d *Dispatcher) relayer(i *sync.WaitGroup) { + atomic.AddInt64(&d.count, 1) + d.wg.Add(1) + timeout := time.NewTimer(0) + i.Done() + for { + select { + case j := <-d.jobs: + d.rMtx.RLock() + if _, ok := d.routes[j.ID]; !ok { + d.rMtx.RUnlock() + continue + } + // Channel handshake timeout feature if a channel is blocked for any + // period of time due to an issue with the receiving routine. + // This will wait on channel then fall over to the next route when + // the timer actuates and continue over the route list. Have to + // iterate across full length of routes so every routine can get + // their new info, cannot be buffered as we dont want to have an old + // orderbook etc contained in a buffered channel when a routine + // actually is ready for a receive. + // TODO: Need to consider optimal timer length + for i := range d.routes[j.ID] { + if !timeout.Stop() { // Stop timer before reset + // Drain channel if timer has already actuated + select { + case <-timeout.C: + default: + } + } + + timeout.Reset(DefaultHandshakeTimeout) + select { + case d.routes[j.ID][i] <- j.Data: + case <-timeout.C: + } + } + d.rMtx.RUnlock() + + case v := <-d.shutdown: + if !timeout.Stop() { + select { + case <-timeout.C: + default: + } + } + atomic.AddInt64(&d.count, -1) + if v != nil { + v.Done() + } + d.wg.Done() + return + } + } +} + +// publish relays data to the subscribed subsystems +func (d *Dispatcher) publish(id uuid.UUID, data interface{}) error { + if data == nil { + return errors.New("dispatcher data cannot be nil") + } + + if id == (uuid.UUID{}) { + return errors.New("dispatcher uuid not set") + } + + if atomic.LoadUint32(&d.running) == 0 { + return nil + } + + // Create a new job to publish + newJob := &job{ + Data: data, + ID: id, + } + + // Push job on stack here + select { + case d.jobs <- newJob: + default: + return fmt.Errorf("dispatcher buffer at max capacity [%d] current worker count [%d], spawn more workers via --dispatchworkers=x", + len(d.jobs), + atomic.LoadInt64(&d.count)) + } + + return nil +} + +// Subscribe subscribes a system and returns a communication chan, this does not +// ensure initial push. If your routine is out of sync with heartbeat and the +// system does not get a change, its up to you to in turn get initial state. +func (d *Dispatcher) subscribe(id uuid.UUID) (chan interface{}, error) { + if atomic.LoadUint32(&d.running) == 0 { + return nil, errors.New(errNotInitialised) + } + + // Read lock to read route list + d.rMtx.RLock() + _, ok := d.routes[id] + d.rMtx.RUnlock() + if !ok { + return nil, errors.New("dispatcher uuid not found in route list") + } + + // Get an unused channel from the channel pool + unusedChan := d.outbound.Get().(chan interface{}) + + // Lock for writing to the route list + d.rMtx.Lock() + d.routes[id] = append(d.routes[id], unusedChan) + d.rMtx.Unlock() + + return unusedChan, nil +} + +// Unsubscribe unsubs a routine from the dispatcher +func (d *Dispatcher) unsubscribe(id uuid.UUID, usedChan chan interface{}) error { + if atomic.LoadUint32(&d.running) == 0 { + // reference will already be released in the stop function + return nil + } + + // Read lock to read route list + d.rMtx.RLock() + _, ok := d.routes[id] + d.rMtx.RUnlock() + if !ok { + return errors.New("dispatcher uuid does not reference any channels") + } + + // Lock for write to delete references + d.rMtx.Lock() + for i := range d.routes[id] { + if d.routes[id][i] != usedChan { + continue + } + // Delete individual reference + d.routes[id][i] = d.routes[id][len(d.routes[id])-1] + d.routes[id][len(d.routes[id])-1] = nil + d.routes[id] = d.routes[id][:len(d.routes[id])-1] + + d.rMtx.Unlock() + + // Drain and put the used chan back in pool; only if it is not closed. + select { + case _, ok := <-usedChan: + if !ok { + return nil + } + default: + } + + d.outbound.Put(usedChan) + return nil + } + d.rMtx.Unlock() + return errors.New("dispatcher channel not found in uuid reference slice") +} + +// GetNewID returns a new ID +func (d *Dispatcher) getNewID() (uuid.UUID, error) { + // Generate new uuid + newID, err := uuid.NewV4() + if err != nil { + return uuid.UUID{}, err + } + + // Check to see if it already exists + d.rMtx.RLock() + _, ok := d.routes[newID] + d.rMtx.RUnlock() + if ok { + return newID, errors.New("dispatcher collision detected, uuid already exists") + } + + // Write the key into system + d.rMtx.Lock() + d.routes[newID] = nil + d.rMtx.Unlock() + + return newID, nil +} diff --git a/dispatch/dispatch_test.go b/dispatch/dispatch_test.go new file mode 100644 index 00000000..e526e3e2 --- /dev/null +++ b/dispatch/dispatch_test.go @@ -0,0 +1,307 @@ +package dispatch + +import ( + "fmt" + "os" + "sync" + "testing" + + "github.com/gofrs/uuid" +) + +var mux *Mux + +func TestMain(m *testing.M) { + err := Start(DefaultMaxWorkers) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + cpyDispatch = dispatcher + mux = GetNewMux() + cpyMux = mux + os.Exit(m.Run()) +} + +var cpyDispatch *Dispatcher +var cpyMux *Mux + +func TestDispatcher(t *testing.T) { + dispatcher = nil + err := Stop() + if err == nil { + t.Error("error cannot be nil") + } + + err = Start(10) + if err == nil { + t.Error("error cannot be nil") + } + + if IsRunning() { + t.Error("should be false") + } + + err = DropWorker() + if err == nil { + t.Error("error cannot be nil") + } + + err = SpawnWorker() + if err == nil { + t.Error("error cannot be nil") + } + + dispatcher = cpyDispatch + + if !IsRunning() { + t.Error("should be true") + } + + err = Start(10) + if err == nil { + t.Error("error cannot be nil") + } + + err = DropWorker() + if err != nil { + t.Error(err) + } + + err = DropWorker() + if err != nil { + t.Error(err) + } + + err = SpawnWorker() + if err != nil { + t.Error(err) + } + + err = SpawnWorker() + if err != nil { + t.Error(err) + } + + err = SpawnWorker() + if err == nil { + t.Error("error cannot be nil") + } + + err = Stop() + if err != nil { + t.Error(err) + } + + err = Stop() + if err == nil { + t.Error("error cannot be nil") + } + + err = Start(0) + if err != nil { + t.Error(err) + } + + payload := "something" + + err = dispatcher.publish(uuid.UUID{}, &payload) + if err == nil { + t.Error("error cannot be nil") + } + + err = dispatcher.publish(uuid.UUID{}, nil) + if err == nil { + t.Error("error cannot be nil") + } + + id, errrrrrrrr := dispatcher.getNewID() + if errrrrrrrr != nil { + t.Error(errrrrrrrr) + } + + err = dispatcher.publish(id, &payload) + if err != nil { + t.Error(err) + } + + err = dispatcher.stop() + if err != nil { + t.Error(err) + } + + err = dispatcher.publish(id, &payload) + if err != nil { + t.Error(err) + } + + _, err = dispatcher.subscribe(id) + if err == nil { + t.Error("error cannot be nil") + } + + err = dispatcher.start(10) + if err != nil { + t.Error(err) + } + + someID, err := uuid.NewV4() + if err != nil { + t.Error(err) + } + + _, err = dispatcher.subscribe(someID) + if err == nil { + t.Error("error cannot be nil") + } + + randomChan := make(chan interface{}) + err = dispatcher.unsubscribe(someID, randomChan) + if err == nil { + t.Error(err) + } + + err = dispatcher.unsubscribe(id, randomChan) + if err == nil { + t.Error(err) + } + + close(randomChan) + err = dispatcher.unsubscribe(id, randomChan) + if err == nil { + t.Error(err) + } +} + +func TestMux(t *testing.T) { + mux = nil + _, err := mux.Subscribe(uuid.UUID{}) + if err == nil { + t.Error("error cannot be nil") + } + + err = mux.Unsubscribe(uuid.UUID{}, nil) + if err == nil { + t.Error("error cannot be nil") + } + + err = mux.Publish(nil, nil) + if err == nil { + t.Error("error cannot be nil") + } + + _, err = mux.GetID() + if err == nil { + t.Error("error cannot be nil") + } + mux = cpyMux + + err = mux.Publish(nil, nil) + if err == nil { + t.Error("error cannot be nil") + } + + payload := "string" + id, err := uuid.NewV4() + if err != nil { + t.Error(err) + } + + err = mux.Publish([]uuid.UUID{id}, &payload) + if err != nil { + t.Error(err) + } + + _, err = mux.Subscribe(uuid.UUID{}) + if err == nil { + t.Error("error cannot be nil") + } + + _, err = mux.Subscribe(id) + if err == nil { + t.Error("error cannot be nil") + } +} + +func TestSubscribe(t *testing.T) { + itemID, err := mux.GetID() + if err != nil { + t.Fatal(err) + } + + var pipes []Pipe + for i := 0; i < 1000; i++ { + newPipe, err := mux.Subscribe(itemID) + if err != nil { + t.Error(err) + } + pipes = append(pipes, newPipe) + } + + for i := range pipes { + err := pipes[i].Release() + if err != nil { + t.Error(err) + } + } +} + +func TestPublish(t *testing.T) { + itemID, err := mux.GetID() + if err != nil { + t.Fatal(err) + } + + pipe, err := mux.Subscribe(itemID) + if err != nil { + t.Error(err) + } + + var wg sync.WaitGroup + wg.Add(1) + go func(wg *sync.WaitGroup) { + wg.Done() + for { + _, ok := <-pipe.C + if !ok { + pErr := pipe.Release() + if pErr != nil { + t.Error(pErr) + } + wg.Done() + return + } + } + }(&wg) + wg.Wait() + wg.Add(1) + mainPayload := "PAYLOAD" + for i := 0; i < 100; i++ { + errMux := mux.Publish([]uuid.UUID{itemID}, &mainPayload) + if errMux != nil { + t.Error(errMux) + } + } + + // Shut down dispatch system + err = Stop() + if err != nil { + t.Fatal(err) + } + wg.Wait() +} + +func BenchmarkSubscribe(b *testing.B) { + newID, err := mux.GetID() + if err != nil { + b.Error(err) + } + + for n := 0; n < b.N; n++ { + _, err := mux.Subscribe(newID) + if err != nil { + b.Error(err) + } + } +} diff --git a/dispatch/dispatch_types.go b/dispatch/dispatch_types.go new file mode 100644 index 00000000..b52bb42f --- /dev/null +++ b/dispatch/dispatch_types.go @@ -0,0 +1,88 @@ +package dispatch + +import ( + "sync" + "time" + + "github.com/gofrs/uuid" +) + +const ( + // DefaultJobBuffer defines a maxiumum amount of jobs allowed in channel + DefaultJobBuffer = 100 + + // DefaultMaxWorkers is the package default worker ceiling amount + DefaultMaxWorkers = 10 + + // DefaultHandshakeTimeout defines a workers max length of time to wait on a + // an unbuffered channel for a receiver before moving on to next route + DefaultHandshakeTimeout = 200 * time.Nanosecond + + errNotInitialised = "dispatcher not initialised" + errAlreadyStarted = "dispatcher already started" + errCannotShutdown = "dispatcher cannot shutdown, already stopped" + errShutdownRoutines = "dispatcher did not shutdown properly, routines failed to close" +) + +// dispatcher is our main in memory instance with a stop/start mtx below +var dispatcher *Dispatcher +var mtx sync.Mutex + +// Dispatcher defines an internal subsystem communication/change state publisher +type Dispatcher struct { + // routes refers to a subystem uuid ticket map with associated publish + // channels, a relayer will be given a unique id through its job channel, + // then publish the data across the full registered channels for that uuid. + // See relayer() method below. + routes map[uuid.UUID][]chan interface{} + + // rMtx protects the routes variable ensuring acceptable read/write access + rMtx sync.RWMutex + + // Persistent buffered job queue for relayers + jobs chan *job + + // Dynamic channel pool; returns an unbuffered channel for routes map + outbound sync.Pool + + // MaxWorkers defines max worker ceiling + maxWorkers int64 + + // Atomic values ----------------------- + // Worker counter + count int64 + // Dispatch status + running uint32 + + // Unbufferd shutdown chan, sync wg for ensuring concurrency when only + // dropping a single relayer routine + shutdown chan *sync.WaitGroup + + // Relayer shutdown tracking + wg sync.WaitGroup +} + +// job defines a relaying job associated with a ticket which allows routing to +// routines that require specific data +type job struct { + Data interface{} + ID uuid.UUID +} + +// Mux defines a new multiplexor for the dispatch system, these a generated +// per subsystem +type Mux struct { + // Reference to the main running dispatch service + d *Dispatcher + sync.RWMutex +} + +// Pipe defines an outbound object to the desired routine +type Pipe struct { + // Channel to get all our lovely informations + C chan interface{} + // ID to tracked system + id uuid.UUID + // Reference to multiplexor + m *Mux +} diff --git a/dispatch/mux.go b/dispatch/mux.go new file mode 100644 index 00000000..1c741e67 --- /dev/null +++ b/dispatch/mux.go @@ -0,0 +1,77 @@ +package dispatch + +import ( + "errors" + "reflect" + + "github.com/gofrs/uuid" +) + +// GetNewMux returns a new multiplexor to track subsystem updates +func GetNewMux() *Mux { + return &Mux{d: dispatcher} +} + +// Subscribe takes in a package defined signature element pointing to an ID set +// and returns the associated pipe +func (m *Mux) Subscribe(id uuid.UUID) (Pipe, error) { + if m == nil { + return Pipe{}, errors.New("mux is nil") + } + + if id == (uuid.UUID{}) { + return Pipe{}, errors.New("id not set") + } + + ch, err := m.d.subscribe(id) + if err != nil { + return Pipe{}, err + } + + return Pipe{C: ch, id: id, m: m}, nil +} + +// Unsubscribe returns channel to the pool for the full signature set +func (m *Mux) Unsubscribe(id uuid.UUID, ch chan interface{}) error { + if m == nil { + return errors.New("mux is nil") + } + return m.d.unsubscribe(id, ch) +} + +// Publish takes in a persistent memory address and dispatches changes to +// required pipes. Data should be of *type. +func (m *Mux) Publish(ids []uuid.UUID, data interface{}) error { + if m == nil { + return errors.New("mux is nil") + } + + if data == nil { + return errors.New("data payload is nil") + } + + cpy := reflect.ValueOf(data).Elem().Interface() + + for i := range ids { + // Create copy to not interfere with stored value + err := m.d.publish(ids[i], &cpy) + if err != nil { + return err + } + } + return nil +} + +// GetID gets a lovely new ID +func (m *Mux) GetID() (uuid.UUID, error) { + if m == nil { + return uuid.UUID{}, errors.New("mux is nil") + } + + return m.d.getNewID() +} + +// Release returns the channel to the communications pool to be reused +func (p *Pipe) Release() error { + return p.m.Unsubscribe(p.id, p.C) +} diff --git a/engine/engine.go b/engine/engine.go index c369be51..a35ac444 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/currency/coinmarketcap" + "github.com/thrasher-corp/gocryptotrader/dispatch" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/request" log "github.com/thrasher-corp/gocryptotrader/logger" @@ -111,6 +112,7 @@ func ValidateSettings(b *Engine, s *Settings) { b.Settings.EnablePortfolioManager = s.EnablePortfolioManager b.Settings.EnableCoinmarketcapAnalysis = s.EnableCoinmarketcapAnalysis b.Settings.EnableDatabaseManager = s.EnableDatabaseManager + b.Settings.EnableDispatcher = s.EnableDispatcher // TO-DO: FIXME if flag.Lookup("grpc") != nil { @@ -203,6 +205,7 @@ func ValidateSettings(b *Engine, s *Settings) { } b.Settings.GlobalHTTPProxy = s.GlobalHTTPProxy + b.Settings.DispatchMaxWorkerAmount = s.DispatchMaxWorkerAmount } // PrintSettings returns the engine settings @@ -230,6 +233,8 @@ func PrintSettings(s *Settings) { log.Debugf(log.Global, "\t Enable orderbook syncing: %v", s.EnableOrderbookSyncing) log.Debugf(log.Global, "\t Enable websocket routine: %v\n", s.EnableWebsocketRoutine) log.Debugf(log.Global, "\t Enable NTP client: %v", s.EnableNTPClient) + log.Debugf(log.Global, "\t Enable dispatcher: %v", s.EnableDispatcher) + log.Debugf(log.Global, "\t Dispatch package max worker amount: %d", s.DispatchMaxWorkerAmount) log.Debugf(log.Global, "- FOREX SETTINGS:") log.Debugf(log.Global, "\t Enable currency conveter: %v", s.EnableCurrencyConverter) log.Debugf(log.Global, "\t Enable currency layer: %v", s.EnableCurrencyLayer) @@ -266,6 +271,12 @@ func (e *Engine) Start() error { } } + if e.Settings.EnableDispatcher { + if err := dispatch.Start(e.Settings.DispatchMaxWorkerAmount); err != nil { + log.Errorf(log.DispatchMgr, "Dispatcher unable to start: %v", err) + } + } + // Sets up internet connectivity monitor if e.Settings.EnableConnectivityMonitor { if err := e.ConnectionManager.Start(); err != nil { @@ -436,6 +447,12 @@ func (e *Engine) Stop() { } } + if dispatch.IsRunning() { + if err := dispatch.Stop(); err != nil { + log.Errorf(log.DispatchMgr, "Dispatch system unable to stop. Error: %v", err) + } + } + if !e.Settings.EnableDryRun { err := e.Config.SaveConfig(e.Settings.ConfigFile, false) if err != nil { diff --git a/engine/engine_types.go b/engine/engine_types.go index 42cffb37..0372df9a 100644 --- a/engine/engine_types.go +++ b/engine/engine_types.go @@ -61,4 +61,8 @@ type Settings struct { ExchangeHTTPTimeout time.Duration ExchangeHTTPUserAgent string ExchangeHTTPProxy string + + // Dispatch system settings + EnableDispatcher bool + DispatchMaxWorkerAmount int64 } diff --git a/engine/events_test.go b/engine/events_test.go index 9563d9cf..cc771af4 100644 --- a/engine/events_test.go +++ b/engine/events_test.go @@ -149,12 +149,6 @@ func TestProcessTicker(t *testing.T) { }, } - // this will throw an err with an unpopulated ticker - ticker.Tickers = nil - if r := e.processTicker(); r { - t.Error("unexpected result") - } - // now populate it with a 0 entry tick := ticker.Price{ Pair: currency.NewPair(currency.BTC, currency.USD), @@ -222,12 +216,6 @@ func TestProcessOrderbook(t *testing.T) { }, } - // this will throw an err with an unpopulated orderbook - orderbook.Orderbooks = nil - if r := e.processOrderbook(); r { - t.Error("unexpected result") - } - // now populate it with a 0 entry o := orderbook.Base{ Pair: currency.NewPair(currency.BTC, currency.USD), diff --git a/engine/helpers.go b/engine/helpers.go index b5865226..65b9822a 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -19,6 +19,7 @@ import ( "github.com/pquerna/otp/totp" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/dispatch" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -42,6 +43,7 @@ func GetSubsystemsStatus() map[string]bool { systems["grpc_proxy"] = Bot.Settings.EnableGRPCProxy systems["deprecated_rpc"] = Bot.Settings.EnableDeprecatedRPC systems["websocket_rpc"] = Bot.Settings.EnableWebsocketRPC + systems["dispatch"] = dispatch.IsRunning() return systems } @@ -106,6 +108,11 @@ func SetSubsystem(subsys string, enable bool) error { Bot.ExchangeCurrencyPairManager.Start() } Bot.ExchangeCurrencyPairManager.Stop() + case "dispatch": + if enable { + return dispatch.Start(Bot.Settings.DispatchMaxWorkerAmount) + } + return dispatch.Stop() } return errors.New("subsystem not found") } diff --git a/engine/routines.go b/engine/routines.go index bcbc6902..16320491 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -9,7 +9,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/stats" @@ -186,101 +185,6 @@ func relayWebsocketEvent(result interface{}, event, assetType, exchangeName stri } } -// TickerUpdaterRoutine fetches and updates the ticker for all enabled -// currency pairs and exchanges -func TickerUpdaterRoutine() { - log.Debugln(log.Ticker, "Starting ticker updater routine.") - var wg sync.WaitGroup - for { - wg.Add(len(Bot.Exchanges)) - for x := range Bot.Exchanges { - go func(x int, wg *sync.WaitGroup) { - defer wg.Done() - - if Bot.Exchanges[x] == nil || !Bot.Exchanges[x].SupportsREST() { - return - } - - exchangeName := Bot.Exchanges[x].GetName() - supportsBatching := Bot.Exchanges[x].SupportsRESTTickerBatchUpdates() - assetTypes := Bot.Exchanges[x].GetAssetTypes() - - processTicker := func(exch exchange.IBotExchange, update bool, c currency.Pair, assetType asset.Item) { - var result ticker.Price - var err error - if update { - result, err = exch.UpdateTicker(c, assetType) - } else { - result, err = exch.FetchTicker(c, assetType) - } - printTickerSummary(&result, c, assetType, exchangeName, err) - if err == nil { - if Bot.Config.RemoteControl.WebsocketRPC.Enabled { - relayWebsocketEvent(result, "ticker_update", assetType.String(), exchangeName) - } - } - } - - for y := range assetTypes { - enabledCurrencies := Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) - for z := range enabledCurrencies { - if supportsBatching && z > 0 { - processTicker(Bot.Exchanges[x], false, enabledCurrencies[z], assetTypes[y]) - continue - } - processTicker(Bot.Exchanges[x], true, enabledCurrencies[z], assetTypes[y]) - } - } - }(x, &wg) - } - wg.Wait() - log.Debugln(log.Ticker, "All enabled currency tickers fetched.") - time.Sleep(time.Second * 10) - } -} - -// OrderbookUpdaterRoutine fetches and updates the orderbooks for all enabled -// currency pairs and exchanges -func OrderbookUpdaterRoutine() { - log.Debugln(log.OrderBook, "Starting orderbook updater routine.") - var wg sync.WaitGroup - for { - wg.Add(len(Bot.Exchanges)) - for x := range Bot.Exchanges { - go func(x int, wg *sync.WaitGroup) { - defer wg.Done() - - if Bot.Exchanges[x] == nil || !Bot.Exchanges[x].SupportsREST() { - return - } - - exchangeName := Bot.Exchanges[x].GetName() - assetTypes := Bot.Exchanges[x].GetAssetTypes() - - processOrderbook := func(exch exchange.IBotExchange, c currency.Pair, assetType asset.Item) { - result, err := exch.UpdateOrderbook(c, assetType) - printOrderbookSummary(&result, c, assetType, exchangeName, err) - if err == nil { - if Bot.Config.RemoteControl.WebsocketRPC.Enabled { - relayWebsocketEvent(result, "orderbook_update", assetType.String(), exchangeName) - } - } - } - - for y := range assetTypes { - enabledCurrencies := Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) - for z := range enabledCurrencies { - processOrderbook(Bot.Exchanges[x], enabledCurrencies[z], assetTypes[y]) - } - } - }(x, &wg) - } - wg.Wait() - log.Debugln(log.OrderBook, "All enabled currency orderbooks fetched.") - time.Sleep(time.Second * 10) - } -} - // WebsocketRoutine Initial routine management system for websocket func WebsocketRoutine() { if Bot.Settings.Verbose { diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 79d79975..4e6cd048 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -17,6 +17,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/gctrpc" "github.com/thrasher-corp/gocryptotrader/gctrpc/auth" log "github.com/thrasher-corp/gocryptotrader/logger" @@ -27,6 +29,13 @@ import ( "google.golang.org/grpc/metadata" ) +const ( + errExchangeNameUnset = "exchange name unset" + errCurrencyPairUnset = "currency pair unset" + errAssetTypeUnset = "asset type unset" + errDispatchSystem = "dispatch system offline" +) + // RPCServer struct type RPCServer struct{} @@ -953,5 +962,200 @@ func (s *RPCServer) DisableExchangePair(ctx context.Context, r *gctrpc.ExchangeP err = GetExchangeByName(r.Exchange).GetBase().CurrencyPairs.DisablePair( asset.Item(r.AssetType), p) return &gctrpc.GenericExchangeNameResponse{}, err - +} + +// GetOrderbookStream streams the requested updated orderbook +func (s *RPCServer) GetOrderbookStream(r *gctrpc.GetOrderbookStreamRequest, stream gctrpc.GoCryptoTrader_GetOrderbookStreamServer) error { + if r.Exchange == "" { + return errors.New(errExchangeNameUnset) + } + + if r.Pair.String() == "" { + return errors.New(errCurrencyPairUnset) + } + + if r.AssetType == "" { + return errors.New(errAssetTypeUnset) + } + + p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + + pipe, err := orderbook.SubscribeOrderbook(r.Exchange, p, asset.Item(r.AssetType)) + if err != nil { + return err + } + + defer pipe.Release() + + for { + data, ok := <-pipe.C + if !ok { + return errors.New(errDispatchSystem) + } + + ob := (*data.(*interface{})).(orderbook.Base) + var bids, asks []*gctrpc.OrderbookItem + for i := range ob.Bids { + bids = append(bids, &gctrpc.OrderbookItem{ + Amount: ob.Bids[i].Amount, + Price: ob.Bids[i].Price, + Id: ob.Bids[i].ID, + }) + } + for i := range ob.Asks { + asks = append(asks, &gctrpc.OrderbookItem{ + Amount: ob.Asks[i].Amount, + Price: ob.Asks[i].Price, + Id: ob.Asks[i].ID, + }) + } + err := stream.Send(&gctrpc.OrderbookResponse{ + Pair: &gctrpc.CurrencyPair{Base: ob.Pair.Base.String(), + Quote: ob.Pair.Quote.String()}, + Bids: bids, + Asks: asks, + AssetType: ob.AssetType.String(), + }) + if err != nil { + return err + } + } +} + +// GetExchangeOrderbookStream streams all orderbooks associated with an exchange +func (s *RPCServer) GetExchangeOrderbookStream(r *gctrpc.GetExchangeOrderbookStreamRequest, stream gctrpc.GoCryptoTrader_GetExchangeOrderbookStreamServer) error { + if r.Exchange == "" { + return errors.New(errExchangeNameUnset) + } + + pipe, err := orderbook.SubscribeToExchangeOrderbooks(r.Exchange) + if err != nil { + return err + } + + defer pipe.Release() + + for { + data, ok := <-pipe.C + if !ok { + return errors.New(errDispatchSystem) + } + + ob := (*data.(*interface{})).(orderbook.Base) + var bids, asks []*gctrpc.OrderbookItem + for i := range ob.Bids { + bids = append(bids, &gctrpc.OrderbookItem{ + Amount: ob.Bids[i].Amount, + Price: ob.Bids[i].Price, + Id: ob.Bids[i].ID, + }) + } + for i := range ob.Asks { + asks = append(asks, &gctrpc.OrderbookItem{ + Amount: ob.Asks[i].Amount, + Price: ob.Asks[i].Price, + Id: ob.Asks[i].ID, + }) + } + err := stream.Send(&gctrpc.OrderbookResponse{ + Pair: &gctrpc.CurrencyPair{Base: ob.Pair.Base.String(), + Quote: ob.Pair.Quote.String()}, + Bids: bids, + Asks: asks, + AssetType: ob.AssetType.String(), + }) + if err != nil { + return err + } + } +} + +// GetTickerStream streams the requested updated ticker +func (s *RPCServer) GetTickerStream(r *gctrpc.GetTickerStreamRequest, stream gctrpc.GoCryptoTrader_GetTickerStreamServer) error { + if r.Exchange == "" { + return errors.New(errExchangeNameUnset) + } + + if r.Pair.String() == "" { + return errors.New(errCurrencyPairUnset) + } + + if r.AssetType == "" { + return errors.New(errAssetTypeUnset) + } + + p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + + pipe, err := ticker.SubscribeTicker(r.Exchange, p, asset.Item(r.AssetType)) + if err != nil { + return err + } + + defer pipe.Release() + + for { + data, ok := <-pipe.C + if !ok { + return errors.New(errDispatchSystem) + } + t := (*data.(*interface{})).(ticker.Price) + + err := stream.Send(&gctrpc.TickerResponse{ + Pair: &gctrpc.CurrencyPair{ + Base: t.Pair.Base.String(), + Quote: t.Pair.Quote.String(), + Delimiter: t.Pair.Delimiter}, + LastUpdated: t.LastUpdated.Unix(), + Last: t.Last, + High: t.High, + Low: t.Low, + Bid: t.Bid, + Ask: t.Ask, + Volume: t.Volume, + PriceAth: t.PriceATH, + }) + if err != nil { + return err + } + } +} + +// GetExchangeTickerStream streams all tickers associated with an exchange +func (s *RPCServer) GetExchangeTickerStream(r *gctrpc.GetExchangeTickerStreamRequest, stream gctrpc.GoCryptoTrader_GetExchangeTickerStreamServer) error { + if r.Exchange == "" { + return errors.New(errExchangeNameUnset) + } + + pipe, err := ticker.SubscribeToExchangeTickers(r.Exchange) + if err != nil { + return err + } + + defer pipe.Release() + + for { + data, ok := <-pipe.C + if !ok { + return errors.New(errDispatchSystem) + } + t := (*data.(*interface{})).(ticker.Price) + + err := stream.Send(&gctrpc.TickerResponse{ + Pair: &gctrpc.CurrencyPair{ + Base: t.Pair.Base.String(), + Quote: t.Pair.Quote.String(), + Delimiter: t.Pair.Delimiter}, + LastUpdated: t.LastUpdated.Unix(), + Last: t.Last, + High: t.High, + Low: t.Low, + Bid: t.Bid, + Ask: t.Ask, + Volume: t.Volume, + PriceAth: t.PriceATH, + }) + if err != nil { + return err + } + } } diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index a583247c..85519f1d 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -387,7 +387,7 @@ func TestUpdateOrderbook(t *testing.T) { Quote: currency.USD} _, err := a.UpdateOrderbook(q, "spot") - if err != nil { - t.Fatalf("Update for orderbook failed: %v", err) + if err == nil { + t.Fatalf("error cannot be nil as the endpoint returns no orderbook information") } } diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index ec48658b..72715f16 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -247,6 +247,7 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error { newOrderBook.LastUpdated = time.Unix(orderbookNew.LastUpdateID, 0) newOrderBook.Pair = p newOrderBook.AssetType = asset.Spot + newOrderBook.ExchangeName = b.GetName() return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index b9b5f569..b0381e36 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -482,6 +482,8 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books newOrderBook.AssetType = assetType newOrderBook.Bids = bid newOrderBook.Pair = p + newOrderBook.ExchangeName = b.GetName() + err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return fmt.Errorf("bitfinex.go error - %s", err) diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 3524550c..0c82fffc 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -361,6 +361,8 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai newOrderBook.Bids = bids newOrderBook.AssetType = assetType newOrderBook.Pair = currencyPair + newOrderBook.ExchangeName = b.GetName() + err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return fmt.Errorf("bitmex_websocket.go process orderbook error - %s", diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 5260dfd2..67d6c619 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -247,6 +247,7 @@ func (b *Bitstamp) seedOrderBook() error { newOrderBook.Bids = bids newOrderBook.Pair = p[x] newOrderBook.AssetType = asset.Spot + newOrderBook.ExchangeName = b.GetName() err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index ff34058e..f0a9764e 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -216,6 +216,7 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro pair := currency.NewPairFromString(snapshot.ProductID) base.AssetType = asset.Spot base.Pair = pair + base.ExchangeName = c.GetName() err := c.Websocket.Orderbook.LoadSnapshot(&base) if err != nil { diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index d12e03aa..ecb067c9 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -288,6 +288,7 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { c.GetPairFormat(asset.Spot, true), ) newOrderBook.AssetType = asset.Spot + newOrderBook.ExchangeName = c.GetName() return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 8bcb8b76..fe04587f 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -237,6 +237,7 @@ func (g *Gateio) WsHandleData() { newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = currency.NewPairFromString(c) + newOrderBook.ExchangeName = g.GetName() err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 9d47d9c2..42a2392e 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -282,6 +282,7 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = pair + newOrderBook.ExchangeName = g.GetName() err := g.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { g.Websocket.DataHandler <- err diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index 5cfda89a..f8cfe0c3 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -250,6 +250,7 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = p + newOrderBook.ExchangeName = h.GetName() err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index 8c471e91..2bdb4567 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -2,235 +2,259 @@ package orderbook import ( "errors" + "fmt" "sort" - "sync" + "strings" "time" + "github.com/gofrs/uuid" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/dispatch" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) -// const values for orderbook package -const ( - errExchangeOrderbookNotFound = "orderbook for exchange does not exist" - errPairNotSet = "orderbook currency pair not set" - errAssetTypeNotSet = "orderbook asset type not set" - errBaseCurrencyNotFound = "orderbook base currency not found" - errQuoteCurrencyNotFound = "orderbook quote currency not found" -) - -// Vars for the orderbook package -var ( - Orderbooks []Orderbook - m sync.Mutex -) - -// Item stores the amount and price values -type Item struct { - Amount float64 - Price float64 - ID int64 +// Get checks and returns the orderbook given an exchange name and currency pair +// if it exists +func Get(exchange string, p currency.Pair, a asset.Item) (Base, error) { + o, err := service.Retrieve(exchange, p, a) + if err != nil { + return Base{}, err + } + return *o, nil } -// Base holds the fields for the orderbook base -type Base struct { - Pair currency.Pair `json:"pair"` - Bids []Item `json:"bids"` - Asks []Item `json:"asks"` - LastUpdated time.Time `json:"lastUpdated"` - AssetType asset.Item `json:"assetType"` - ExchangeName string `json:"exchangeName"` +// SubscribeOrderbook subcribes to an orderbook and returns a communication +// channel to stream orderbook data updates +func SubscribeOrderbook(exchange string, p currency.Pair, a asset.Item) (dispatch.Pipe, error) { + exchange = strings.ToLower(exchange) + service.RLock() + defer service.RUnlock() + book, ok := service.Books[exchange][p.Base.Item][p.Quote.Item][a] + if !ok { + return dispatch.Pipe{}, fmt.Errorf("orderbook item not found for %s %s %s", + exchange, + p, + a) + } + + return service.mux.Subscribe(book.Main) } -// Orderbook holds the orderbook information for a currency pair and type -type Orderbook struct { - Orderbook map[*currency.Item]map[*currency.Item]map[asset.Item]Base - ExchangeName string +// SubscribeToExchangeOrderbooks subcribes to all orderbooks on an exchange +func SubscribeToExchangeOrderbooks(exchange string) (dispatch.Pipe, error) { + exchange = strings.ToLower(exchange) + service.RLock() + defer service.RUnlock() + id, ok := service.Exchange[exchange] + if !ok { + return dispatch.Pipe{}, fmt.Errorf("%s exchange orderbooks not found", + exchange) + } + + return service.mux.Subscribe(id) } -type byOBPrice []Item +// Update stores orderbook data +func (s *Service) Update(b *Base) error { + var ids []uuid.UUID -func (a byOBPrice) Len() int { return len(a) } -func (a byOBPrice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byOBPrice) Less(i, j int) bool { return a[i].Price < a[j].Price } - -// Verify ensures that the orderbook items are correctly sorted -// Bids should always go from a high price to a low price and -// asks should always go from a low price to a higher price -func (o *Base) Verify() { - var lastPrice float64 - var sortBids, sortAsks bool - for x := range o.Bids { - if lastPrice != 0 && o.Bids[x].Price >= lastPrice { - sortBids = true - break + s.Lock() + switch { + case s.Books[b.ExchangeName] == nil: + s.Books[b.ExchangeName] = make(map[*currency.Item]map[*currency.Item]map[asset.Item]*Book) + s.Books[b.ExchangeName][b.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Book) + s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item] = make(map[asset.Item]*Book) + err := s.SetNewData(b) + if err != nil { + s.Unlock() + return err } - lastPrice = o.Bids[x].Price - } - lastPrice = 0 - for x := range o.Asks { - if lastPrice != 0 && o.Asks[x].Price <= lastPrice { - sortAsks = true - break + case s.Books[b.ExchangeName][b.Pair.Base.Item] == nil: + s.Books[b.ExchangeName][b.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Book) + s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item] = make(map[asset.Item]*Book) + err := s.SetNewData(b) + if err != nil { + s.Unlock() + return err } - lastPrice = o.Asks[x].Price + + case s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item] == nil: + s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item] = make(map[asset.Item]*Book) + err := s.SetNewData(b) + if err != nil { + s.Unlock() + return err + } + + case s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] == nil: + err := s.SetNewData(b) + if err != nil { + s.Unlock() + return err + } + + default: + s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].b.Bids = b.Bids + s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].b.Asks = b.Asks + s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].b.LastUpdated = b.LastUpdated + ids = s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].Assoc + ids = append(ids, s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].Main) + } + s.Unlock() + return s.mux.Publish(ids, b) +} + +// SetNewData sets new data +func (s *Service) SetNewData(b *Base) error { + ids, err := s.GetAssociations(b) + if err != nil { + return err + } + singleID, err := s.mux.GetID() + if err != nil { + return err } - if sortBids { - sort.Sort(sort.Reverse(byOBPrice(o.Bids))) + s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] = &Book{b: b, + Main: singleID, + Assoc: ids} + return nil +} + +// GetAssociations links a singular book with it's dispatch associations +func (s *Service) GetAssociations(b *Base) ([]uuid.UUID, error) { + if b == nil { + return nil, errors.New("orderbook is nil") } - if sortAsks { - sort.Sort((byOBPrice(o.Asks))) + var ids []uuid.UUID + exchangeID, ok := s.Exchange[b.ExchangeName] + if !ok { + var err error + exchangeID, err = s.mux.GetID() + if err != nil { + return nil, err + } + s.Exchange[b.ExchangeName] = exchangeID } + ids = append(ids, exchangeID) + return ids, nil +} + +// Retrieve gets orderbook data from the slice +func (s *Service) Retrieve(exchange string, p currency.Pair, a asset.Item) (*Base, error) { + exchange = strings.ToLower(exchange) + s.RLock() + defer s.RUnlock() + if s.Books[exchange] == nil { + return nil, fmt.Errorf("no orderbooks for %s exchange", exchange) + } + + if s.Books[exchange][p.Base.Item] == nil { + return nil, fmt.Errorf("no orderbooks associated with base currency %s", + p.Base) + } + + if s.Books[exchange][p.Base.Item][p.Quote.Item] == nil { + return nil, fmt.Errorf("no orderbooks associated with quote currency %s", + p.Quote) + } + + if s.Books[exchange][p.Base.Item][p.Quote.Item][a] == nil { + return nil, fmt.Errorf("no orderbooks associated with asset type %s", + a) + } + + return s.Books[exchange][p.Base.Item][p.Quote.Item][a].b, nil } // TotalBidsAmount returns the total amount of bids and the total orderbook // bids value -func (o *Base) TotalBidsAmount() (amountCollated, total float64) { - for _, x := range o.Bids { - amountCollated += x.Amount - total += x.Amount * x.Price +func (b *Base) TotalBidsAmount() (amountCollated, total float64) { + for x := range b.Bids { + amountCollated += b.Bids[x].Amount + total += b.Bids[x].Amount * b.Bids[x].Price } return amountCollated, total } // TotalAsksAmount returns the total amount of asks and the total orderbook // asks value -func (o *Base) TotalAsksAmount() (amountCollated, total float64) { - for _, x := range o.Asks { - amountCollated += x.Amount - total += x.Amount * x.Price +func (b *Base) TotalAsksAmount() (amountCollated, total float64) { + for y := range b.Asks { + amountCollated += b.Asks[y].Amount + total += b.Asks[y].Amount * b.Asks[y].Price } return amountCollated, total } // Update updates the bids and asks -func (o *Base) Update(bids, asks []Item) { - o.Bids = bids - o.Asks = asks - o.LastUpdated = time.Now() +func (b *Base) Update(bids, asks []Item) { + b.Bids = bids + b.Asks = asks + b.LastUpdated = time.Now() } -// Get checks and returns the orderbook given an exchange name and currency pair -// if it exists -func Get(exchange string, p currency.Pair, orderbookType asset.Item) (Base, error) { - orderbook, err := GetByExchange(exchange) - if err != nil { - return Base{}, err - } - - if !BaseCurrencyExists(exchange, p.Base) { - return Base{}, errors.New(errBaseCurrencyNotFound) - } - - if !QuoteCurrencyExists(exchange, p) { - return Base{}, errors.New(errQuoteCurrencyNotFound) - } - - return orderbook.Orderbook[p.Base.Item][p.Quote.Item][orderbookType], nil -} - -// GetByExchange returns an exchange orderbook -func GetByExchange(exchange string) (*Orderbook, error) { - m.Lock() - defer m.Unlock() - for x := range Orderbooks { - if Orderbooks[x].ExchangeName == exchange { - return &Orderbooks[x], nil +// Verify ensures that the orderbook items are correctly sorted +// Bids should always go from a high price to a low price and +// asks should always go from a low price to a higher price +func (b *Base) Verify() { + var lastPrice float64 + var sortBids, sortAsks bool + for x := range b.Bids { + if lastPrice != 0 && b.Bids[x].Price >= lastPrice { + sortBids = true + break } + lastPrice = b.Bids[x].Price } - return nil, errors.New(errExchangeOrderbookNotFound) -} -// BaseCurrencyExists checks to see if the base currency of the orderbook map -// exists -func BaseCurrencyExists(exchange string, currency currency.Code) bool { - m.Lock() - defer m.Unlock() - for _, y := range Orderbooks { - if y.ExchangeName == exchange { - if _, ok := y.Orderbook[currency.Item]; ok { - return true - } + lastPrice = 0 + for x := range b.Asks { + if lastPrice != 0 && b.Asks[x].Price <= lastPrice { + sortAsks = true + break } + lastPrice = b.Asks[x].Price } - return false -} -// QuoteCurrencyExists checks to see if the quote currency of the orderbook -// map exists -func QuoteCurrencyExists(exchange string, p currency.Pair) bool { - m.Lock() - defer m.Unlock() - for _, y := range Orderbooks { - if y.ExchangeName == exchange { - if _, ok := y.Orderbook[p.Base.Item]; ok { - if _, ok := y.Orderbook[p.Base.Item][p.Quote.Item]; ok { - return true - } - } - } + if sortBids { + sort.Sort(sort.Reverse(byOBPrice(b.Bids))) } - return false -} -// CreateNewOrderbook creates a new orderbook -func CreateNewOrderbook(exchangeName string, orderbookNew *Base, orderbookType asset.Item) *Orderbook { - m.Lock() - defer m.Unlock() - orderbook := Orderbook{} - orderbook.ExchangeName = exchangeName - orderbook.Orderbook = make(map[*currency.Item]map[*currency.Item]map[asset.Item]Base) - a := make(map[*currency.Item]map[asset.Item]Base) - b := make(map[asset.Item]Base) - b[orderbookType] = *orderbookNew - a[orderbookNew.Pair.Quote.Item] = b - orderbook.Orderbook[orderbookNew.Pair.Base.Item] = a - Orderbooks = append(Orderbooks, orderbook) - return &orderbook + if sortAsks { + sort.Sort((byOBPrice(b.Asks))) + } } // Process processes incoming orderbooks, creating or updating the orderbook // list -func (o *Base) Process() error { - if o.Pair.IsEmpty() { +func (b *Base) Process() error { + if b.ExchangeName == "" { + return errors.New(errExchangeNameUnset) + } + + b.ExchangeName = strings.ToLower(b.ExchangeName) + + if b.Pair.IsEmpty() { return errors.New(errPairNotSet) } - if o.AssetType == "" { + if b.AssetType.String() == "" { return errors.New(errAssetTypeNotSet) } - if o.LastUpdated.IsZero() { - o.LastUpdated = time.Now() + if len(b.Asks) == 0 && len(b.Bids) == 0 { + return errors.New(errNoOrderbook) } - o.Verify() - - orderbook, err := GetByExchange(o.ExchangeName) - if err != nil { - CreateNewOrderbook(o.ExchangeName, o, o.AssetType) - return nil + if b.LastUpdated.IsZero() { + b.LastUpdated = time.Now() } - if BaseCurrencyExists(o.ExchangeName, o.Pair.Base) { - m.Lock() - a := make(map[asset.Item]Base) - a[o.AssetType] = *o - orderbook.Orderbook[o.Pair.Base.Item][o.Pair.Quote.Item] = a - m.Unlock() - return nil - } + b.Verify() - m.Lock() - a := make(map[*currency.Item]map[asset.Item]Base) - b := make(map[asset.Item]Base) - b[o.AssetType] = *o - a[o.Pair.Quote.Item] = b - orderbook.Orderbook[o.Pair.Base.Item] = a - m.Unlock() - return nil + return service.Update(b) } diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 4944a504..03db6595 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -1,16 +1,139 @@ package orderbook import ( + "log" "math/rand" + "os" "strconv" "sync" "testing" "time" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/dispatch" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) +func TestMain(m *testing.M) { + err := dispatch.Start(1) + if err != nil { + log.Fatal(err) + } + + cpyMux = service.mux + + os.Exit(m.Run()) +} + +var cpyMux *dispatch.Mux + +func TestSubscribeOrderbook(t *testing.T) { + _, err := SubscribeOrderbook("", currency.Pair{}, asset.Item("")) + if err == nil { + t.Error("error cannot be nil") + } + + p := currency.NewPair(currency.BTC, currency.USD) + + b := Base{ + Pair: p, + AssetType: asset.Spot, + } + + err = b.Process() + if err == nil { + t.Error("error cannot be nil") + } + + b.ExchangeName = "SubscribeOBTest" + + err = b.Process() + if err == nil { + t.Error("error cannot be nil") + } + + b.Bids = []Item{{}} + + err = b.Process() + if err != nil { + t.Error("test failed - process error", err) + } + + _, err = SubscribeOrderbook("SubscribeOBTest", p, asset.Spot) + if err != nil { + t.Error("error cannot be nil") + } + + // process redundant update + err = b.Process() + if err != nil { + t.Error("test failed - process error", err) + } +} + +func TestUpdateBooks(t *testing.T) { + p := currency.NewPair(currency.BTC, currency.USD) + + b := Base{ + Pair: p, + AssetType: asset.Spot, + ExchangeName: "UpdateTest", + } + + service.mux = nil + + err := service.Update(&b) + if err == nil { + t.Error("error cannot be nil") + } + + b.Pair.Base = currency.CYC + err = service.Update(&b) + if err == nil { + t.Error("error cannot be nil") + } + + b.Pair.Quote = currency.ENAU + err = service.Update(&b) + if err == nil { + t.Error("error cannot be nil") + } + + b.AssetType = "unicorns" + err = service.Update(&b) + if err == nil { + t.Error("error cannot be nil") + } + + service.mux = cpyMux +} + +func TestSubscribeToExchangeOrderbooks(t *testing.T) { + _, err := SubscribeToExchangeOrderbooks("") + if err == nil { + t.Error("error cannot be nil") + } + + p := currency.NewPair(currency.BTC, currency.USD) + + b := Base{ + Pair: p, + AssetType: asset.Spot, + ExchangeName: "SubscribeToExchangeOrderbooks", + Bids: []Item{{}}, + } + + err = b.Process() + if err != nil { + t.Error("test failed:", err) + } + + _, err = SubscribeToExchangeOrderbooks("SubscribeToExchangeOrderbooks") + if err != nil { + t.Error(err) + } +} + func TestVerify(t *testing.T) { t.Parallel() b := Base{ @@ -95,13 +218,18 @@ func TestUpdate(t *testing.T) { func TestGetOrderbook(t *testing.T) { c := currency.NewPairFromStrings("BTC", "USD") - base := Base{ - Pair: c, - Asks: []Item{{Price: 100, Amount: 10}}, - Bids: []Item{{Price: 200, Amount: 10}}, + base := &Base{ + Pair: c, + Asks: []Item{{Price: 100, Amount: 10}}, + Bids: []Item{{Price: 200, Amount: 10}}, + ExchangeName: "Exchange", + AssetType: asset.Spot, } - CreateNewOrderbook("Exchange", &base, asset.Spot) + err := base.Process() + if err != nil { + t.Fatal(err) + } result, err := Get("Exchange", c, asset.Spot) if err != nil { @@ -128,83 +256,37 @@ func TestGetOrderbook(t *testing.T) { if err == nil { t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook using invalid second currency") } -} -func TestGetOrderbookByExchange(t *testing.T) { - currency := currency.NewPairFromStrings("BTC", "USD") - base := Base{ - Pair: currency, - Asks: []Item{{Price: 100, Amount: 10}}, - Bids: []Item{{Price: 200, Amount: 10}}, - } - - CreateNewOrderbook("Exchange", &base, asset.Spot) - - _, err := GetByExchange("Exchange") + base.Pair = newCurrency + err = base.Process() if err != nil { - t.Fatalf("Test failed. TestGetOrderbookByExchange failed to get orderbook. Error %s", - err) + t.Error(err) } - _, err = GetByExchange("nonexistent") + _, err = Get("Exchange", newCurrency, "meowCats") if err == nil { - t.Fatal("Test failed. TestGetOrderbookByExchange retrieved non-existent orderbook") - } -} - -func TestFirstCurrencyExists(t *testing.T) { - c := currency.NewPairFromStrings("BTC", "AUD") - base := Base{ - Pair: c, - Asks: []Item{{Price: 100, Amount: 10}}, - Bids: []Item{{Price: 200, Amount: 10}}, - } - - CreateNewOrderbook("Exchange", &base, asset.Spot) - - if !BaseCurrencyExists("Exchange", c.Base) { - t.Fatal("Test failed. TestFirstCurrencyExists expected first currency doesn't exist") - } - - var item = currency.NewCode("blah") - if BaseCurrencyExists("Exchange", item) { - t.Fatal("Test failed. TestFirstCurrencyExists unexpected first currency exists") - } -} - -func TestSecondCurrencyExists(t *testing.T) { - c := currency.NewPairFromStrings("BTC", "USD") - base := Base{ - Pair: c, - Asks: []Item{{Price: 100, Amount: 10}}, - Bids: []Item{{Price: 200, Amount: 10}}, - } - - CreateNewOrderbook("Exchange", &base, asset.Spot) - - if !QuoteCurrencyExists("Exchange", c) { - t.Fatal("Test failed. TestSecondCurrencyExists expected first currency doesn't exist") - } - - c.Quote = currency.NewCode("blah") - if QuoteCurrencyExists("Exchange", c) { - t.Fatal("Test failed. TestSecondCurrencyExists unexpected first currency exists") + t.Error("error cannot be nil") } } func TestCreateNewOrderbook(t *testing.T) { c := currency.NewPairFromStrings("BTC", "USD") - base := Base{ - Pair: c, - Asks: []Item{{Price: 100, Amount: 10}}, - Bids: []Item{{Price: 200, Amount: 10}}, + base := &Base{ + Pair: c, + Asks: []Item{{Price: 100, Amount: 10}}, + Bids: []Item{{Price: 200, Amount: 10}}, + ExchangeName: "testCreateNewOrderbook", + AssetType: asset.Spot, } - CreateNewOrderbook("Exchange", &base, asset.Spot) - - result, err := Get("Exchange", c, asset.Spot) + err := base.Process() if err != nil { - t.Fatal("Test failed. TestCreateNewOrderbook failed to create new orderbook") + t.Fatal(err) + } + + result, err := Get("testCreateNewOrderbook", c, asset.Spot) + if err != nil { + t.Fatal("Test failed. TestCreateNewOrderbook failed to create new orderbook", err) } if !result.Pair.Equal(c) { @@ -223,12 +305,11 @@ func TestCreateNewOrderbook(t *testing.T) { } func TestProcessOrderbook(t *testing.T) { - Orderbooks = []Orderbook{} c := currency.NewPairFromStrings("BTC", "USD") base := Base{ Asks: []Item{{Price: 100, Amount: 10}}, Bids: []Item{{Price: 200, Amount: 10}}, - ExchangeName: "Exchange", + ExchangeName: "ProcessOrderbook", } // test for empty pair @@ -251,7 +332,7 @@ func TestProcessOrderbook(t *testing.T) { if err != nil { t.Error("unexpcted result: ", err) } - result, err := Get("Exchange", c, asset.Spot) + result, err := Get("ProcessOrderbook", c, asset.Spot) if err != nil { t.Fatal("Test failed. TestProcessOrderbook failed to create new orderbook") } @@ -266,7 +347,7 @@ func TestProcessOrderbook(t *testing.T) { if err != nil { t.Error("Test Failed - Process() error", err) } - result, err = Get("Exchange", c, asset.Spot) + result, err = Get("ProcessOrderbook", c, asset.Spot) if err != nil { t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") } @@ -281,7 +362,7 @@ func TestProcessOrderbook(t *testing.T) { if err != nil { t.Error("Test Failed - Process() error", err) } - result, err = Get("Exchange", c, asset.Spot) + result, err = Get("ProcessOrderbook", c, asset.Spot) if err != nil { t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") } @@ -296,7 +377,7 @@ func TestProcessOrderbook(t *testing.T) { t.Error("Test Failed - Process() error", err) } - result, err = Get("Exchange", c, "monthly") + result, err = Get("ProcessOrderbook", c, "monthly") if err != nil { t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") } @@ -408,3 +489,17 @@ func TestProcessOrderbook(t *testing.T) { wg.Wait() } + +func TestSetNewData(t *testing.T) { + err := service.SetNewData(nil) + if err == nil { + t.Error("error cannot be nil ") + } +} + +func TestGetAssociations(t *testing.T) { + _, err := service.GetAssociations(nil) + if err == nil { + t.Error("error cannot be nil ") + } +} diff --git a/exchanges/orderbook/orderbook_types.go b/exchanges/orderbook/orderbook_types.go new file mode 100644 index 00000000..56260dbe --- /dev/null +++ b/exchanges/orderbook/orderbook_types.go @@ -0,0 +1,69 @@ +package orderbook + +import ( + "sync" + "time" + + "github.com/gofrs/uuid" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/dispatch" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" +) + +// const values for orderbook package +const ( + errExchangeNameUnset = "orderbook exchange name not set" + errPairNotSet = "orderbook currency pair not set" + errAssetTypeNotSet = "orderbook asset type not set" + errNoOrderbook = "orderbook bids and asks are empty" +) + +// Vars for the orderbook package +var ( + service *Service +) + +func init() { + service = new(Service) + service.mux = dispatch.GetNewMux() + service.Books = make(map[string]map[*currency.Item]map[*currency.Item]map[asset.Item]*Book) + service.Exchange = make(map[string]uuid.UUID) +} + +// Book defines an orderbook with its links to different dispatch outputs +type Book struct { + b *Base + Main uuid.UUID + Assoc []uuid.UUID +} + +// Service holds orderbook information for each individual exchange +type Service struct { + Books map[string]map[*currency.Item]map[*currency.Item]map[asset.Item]*Book + Exchange map[string]uuid.UUID + mux *dispatch.Mux + sync.RWMutex +} + +// Item stores the amount and price values +type Item struct { + Amount float64 + Price float64 + ID int64 +} + +// Base holds the fields for the orderbook base +type Base struct { + Pair currency.Pair `json:"pair"` + Bids []Item `json:"bids"` + Asks []Item `json:"asks"` + LastUpdated time.Time `json:"lastUpdated"` + AssetType asset.Item `json:"assetType"` + ExchangeName string `json:"exchangeName"` +} + +type byOBPrice []Item + +func (a byOBPrice) Len() int { return len(a) } +func (a byOBPrice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byOBPrice) Less(i, j int) bool { return a[i].Price < a[j].Price } diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index 50962417..79008dda 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -329,6 +329,7 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = currency.NewPairFromString(symbol) + newOrderBook.ExchangeName = p.GetName() return p.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index 4087f31d..8b77f602 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -3,191 +3,205 @@ package ticker import ( "errors" "fmt" - "strconv" "strings" - "sync" "time" + "github.com/gofrs/uuid" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/dispatch" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) -// const values for the ticker package -const ( - errExchangeTickerNotFound = "ticker for exchange does not exist" - errPairNotSet = "ticker currency pair not set" - errAssetTypeNotSet = "ticker asset type not set" - errBaseCurrencyNotFound = "ticker base currency not found" - errQuoteCurrencyNotFound = "ticker quote currency not found" -) - -// Vars for the ticker package -var ( - Tickers []Ticker - m sync.Mutex -) - -// Price struct stores the currency pair and pricing information -type Price struct { - Last float64 `json:"Last"` - High float64 `json:"High"` - Low float64 `json:"Low"` - Bid float64 `json:"Bid"` - Ask float64 `json:"Ask"` - Volume float64 `json:"Volume"` - QuoteVolume float64 `json:"QuoteVolume"` - PriceATH float64 `json:"PriceATH"` - Open float64 `json:"Open"` - Close float64 `json:"Close"` - Pair currency.Pair `json:"Pair"` - LastUpdated time.Time +func init() { + service = new(Service) + service.Tickers = make(map[string]map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker) + service.Exchange = make(map[string]uuid.UUID) + service.mux = dispatch.GetNewMux() } -// Ticker struct holds the ticker information for a currency pair and type -type Ticker struct { - Price map[string]map[string]map[string]Price - ExchangeName string -} +// SubscribeTicker subcribes to a ticker and returns a communication channel to +// stream new ticker updates +func SubscribeTicker(exchange string, p currency.Pair, a asset.Item) (dispatch.Pipe, error) { + exchange = strings.ToLower(exchange) + service.RLock() + defer service.RUnlock() -// PriceToString returns the string version of a stored price field -func (t *Ticker) PriceToString(p currency.Pair, priceType string, tickerType asset.Item) string { - priceType = strings.ToLower(priceType) - - switch priceType { - case "last": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].Last, 'f', -1, 64) - case "high": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].High, 'f', -1, 64) - case "low": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].Low, 'f', -1, 64) - case "bid": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].Bid, 'f', -1, 64) - case "ask": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].Ask, 'f', -1, 64) - case "volume": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].Volume, 'f', -1, 64) - case "ath": - return strconv.FormatFloat(t.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()].PriceATH, 'f', -1, 64) - default: - return "" + tick, ok := service.Tickers[exchange][p.Base.Item][p.Quote.Item][a] + if !ok { + return dispatch.Pipe{}, fmt.Errorf("ticker item not found for %s %s %s", + exchange, + p, + a) } + + return service.mux.Subscribe(tick.Main) +} + +// SubscribeToExchangeTickers subcribes to all tickers on an exchange +func SubscribeToExchangeTickers(exchange string) (dispatch.Pipe, error) { + exchange = strings.ToLower(exchange) + service.RLock() + defer service.RUnlock() + id, ok := service.Exchange[exchange] + if !ok { + return dispatch.Pipe{}, fmt.Errorf("%s exchange tickers not found", + exchange) + } + + return service.mux.Subscribe(id) } // GetTicker checks and returns a requested ticker if it exists func GetTicker(exchange string, p currency.Pair, tickerType asset.Item) (Price, error) { - ticker, err := GetTickerByExchange(exchange) - if err != nil { - return Price{}, err + exchange = strings.ToLower(exchange) + service.RLock() + defer service.RUnlock() + if service.Tickers[exchange] == nil { + return Price{}, fmt.Errorf("no tickers for %s exchange", exchange) } - if !BaseCurrencyExists(exchange, p.Base) { - return Price{}, errors.New(errBaseCurrencyNotFound) + if service.Tickers[exchange][p.Base.Item] == nil { + return Price{}, fmt.Errorf("no tickers associated with base currency %s", + p.Base) } - if !QuoteCurrencyExists(exchange, p) { - return Price{}, errors.New(errQuoteCurrencyNotFound) + if service.Tickers[exchange][p.Base.Item][p.Quote.Item] == nil { + return Price{}, fmt.Errorf("no tickers associated with quote currency %s", + p.Quote) } - return ticker.Price[p.Base.Upper().String()][p.Quote.Upper().String()][tickerType.String()], nil -} - -// GetTickerByExchange returns an exchange Ticker -func GetTickerByExchange(exchange string) (*Ticker, error) { - m.Lock() - defer m.Unlock() - for x := range Tickers { - if Tickers[x].ExchangeName == exchange { - return &Tickers[x], nil - } + if service.Tickers[exchange][p.Base.Item][p.Quote.Item][tickerType] == nil { + return Price{}, fmt.Errorf("no tickers associated with asset type %s", + tickerType) } - return nil, errors.New(errExchangeTickerNotFound) -} -// BaseCurrencyExists checks to see if the base currency of the ticker map -// exists -func BaseCurrencyExists(exchange string, currency currency.Code) bool { - m.Lock() - defer m.Unlock() - for _, y := range Tickers { - if y.ExchangeName == exchange { - if _, ok := y.Price[currency.Upper().String()]; ok { - return true - } - } - } - return false -} - -// QuoteCurrencyExists checks to see if the quote currency of the ticker map -// exists -func QuoteCurrencyExists(exchange string, p currency.Pair) bool { - m.Lock() - defer m.Unlock() - for _, y := range Tickers { - if y.ExchangeName == exchange { - if _, ok := y.Price[p.Base.Upper().String()]; ok { - if _, ok := y.Price[p.Base.Upper().String()][p.Quote.Upper().String()]; ok { - return true - } - } - } - } - return false -} - -// CreateNewTicker creates a new Ticker -func CreateNewTicker(exchangeName string, tickerNew *Price, tickerType asset.Item) Ticker { - m.Lock() - defer m.Unlock() - ticker := Ticker{} - ticker.ExchangeName = exchangeName - ticker.Price = make(map[string]map[string]map[string]Price) - a := make(map[string]map[string]Price) - b := make(map[string]Price) - b[tickerType.String()] = *tickerNew - a[tickerNew.Pair.Quote.Upper().String()] = b - ticker.Price[tickerNew.Pair.Base.Upper().String()] = a - Tickers = append(Tickers, ticker) - return ticker + return service.Tickers[exchange][p.Base.Item][p.Quote.Item][tickerType].Price, nil } // ProcessTicker processes incoming tickers, creating or updating the Tickers // list func ProcessTicker(exchangeName string, tickerNew *Price, assetType asset.Item) error { + if exchangeName == "" { + return fmt.Errorf(errExchangeNameUnset) + } + + tickerNew.ExchangeName = strings.ToLower(exchangeName) + if tickerNew.Pair.IsEmpty() { - return fmt.Errorf("%v %v", exchangeName, errPairNotSet) + return fmt.Errorf("%s %s", exchangeName, errPairNotSet) } if assetType == "" { - return fmt.Errorf("%v %v %v", exchangeName, tickerNew.Pair.String(), errAssetTypeNotSet) + return fmt.Errorf("%s %s %s", exchangeName, + tickerNew.Pair, + errAssetTypeNotSet) } + tickerNew.AssetType = assetType + if tickerNew.LastUpdated.IsZero() { tickerNew.LastUpdated = time.Now() } - ticker, err := GetTickerByExchange(exchangeName) + return service.Update(tickerNew) +} + +// Update updates ticker price +func (s *Service) Update(p *Price) error { + var ids []uuid.UUID + + s.Lock() + switch { + case s.Tickers[p.ExchangeName] == nil: + s.Tickers[p.ExchangeName] = make(map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker) + s.Tickers[p.ExchangeName][p.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Ticker) + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] = make(map[asset.Item]*Ticker) + err := s.SetItemID(p) + if err != nil { + s.Unlock() + return err + } + + case s.Tickers[p.ExchangeName][p.Pair.Base.Item] == nil: + s.Tickers[p.ExchangeName][p.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Ticker) + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] = make(map[asset.Item]*Ticker) + err := s.SetItemID(p) + if err != nil { + s.Unlock() + return err + } + + case s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] == nil: + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] = make(map[asset.Item]*Ticker) + err := s.SetItemID(p) + if err != nil { + s.Unlock() + return err + } + + case s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] == nil: + err := s.SetItemID(p) + if err != nil { + s.Unlock() + return err + } + + default: + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Last = p.Last + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].High = p.High + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Low = p.Low + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Bid = p.Bid + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Ask = p.Ask + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Volume = p.Volume + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].QuoteVolume = p.QuoteVolume + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].PriceATH = p.PriceATH + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Open = p.Open + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Close = p.Close + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].LastUpdated = p.LastUpdated + ids = s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Assoc + ids = append(ids, s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Main) + } + s.Unlock() + return s.mux.Publish(ids, p) +} + +// SetItemID retrieves and sets dispatch mux publish IDs +func (s *Service) SetItemID(p *Price) error { + if p == nil { + return errors.New(errTickerPriceIsNil) + } + + ids, err := s.GetAssociations(p) if err != nil { - CreateNewTicker(exchangeName, tickerNew, assetType) - return nil + return err + } + singleID, err := s.mux.GetID() + if err != nil { + return err } - if BaseCurrencyExists(exchangeName, tickerNew.Pair.Base) { - m.Lock() - a := make(map[string]Price) - a[assetType.String()] = *tickerNew - ticker.Price[tickerNew.Pair.Base.Upper().String()][tickerNew.Pair.Quote.Upper().String()] = a - m.Unlock() - return nil - } - - m.Lock() - - a := make(map[string]map[string]Price) - b := make(map[string]Price) - b[assetType.String()] = *tickerNew - a[tickerNew.Pair.Quote.Upper().String()] = b - ticker.Price[tickerNew.Pair.Base.Upper().String()] = a - m.Unlock() + s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] = &Ticker{Price: *p, + Main: singleID, + Assoc: ids} return nil } + +// GetAssociations links a singular book with it's dispatch associations +func (s *Service) GetAssociations(p *Price) ([]uuid.UUID, error) { + if p == nil || *p == (Price{}) { + return nil, errors.New(errTickerPriceIsNil) + } + var ids []uuid.UUID + exchangeID, ok := s.Exchange[p.ExchangeName] + if !ok { + var err error + exchangeID, err = s.mux.GetID() + if err != nil { + return nil, err + } + s.Exchange[p.ExchangeName] = exchangeID + } + + ids = append(ids, exchangeID) + return ids, nil +} diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index 29d1e7c2..91e6c9ab 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -1,55 +1,94 @@ package ticker import ( + "log" "math/rand" - "reflect" + "os" "strconv" "sync" "testing" "time" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/dispatch" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) -func TestPriceToString(t *testing.T) { - newPair := currency.NewPairFromStrings("BTC", "USD") - priceStruct := Price{ - Pair: newPair, - Last: 1200, - High: 1298, - Low: 1148, - Bid: 1195, - Ask: 1220, - Volume: 5, - PriceATH: 1337, +func TestMain(m *testing.M) { + err := dispatch.Start(1) + if err != nil { + log.Fatal(err) } - newTicker := CreateNewTicker("ANX", &priceStruct, asset.Spot) + cpyMux = service.mux - if newTicker.PriceToString(newPair, "last", asset.Spot) != "1200" { - t.Error("Test Failed - ticker PriceToString last value is incorrect") + os.Exit(m.Run()) +} + +var cpyMux *dispatch.Mux + +func TestSubscribeTicker(t *testing.T) { + _, err := SubscribeTicker("", currency.Pair{}, asset.Item("")) + if err == nil { + t.Error("error cannot be nil") } - if newTicker.PriceToString(newPair, "high", asset.Spot) != "1298" { - t.Error("Test Failed - ticker PriceToString high value is incorrect") + + p := currency.NewPair(currency.BTC, currency.USD) + + // force error + service.mux = nil + err = ProcessTicker("subscribetest", &Price{Pair: p}, asset.Spot) + if err == nil { + t.Error("error cannot be nil") } - if newTicker.PriceToString(newPair, "low", asset.Spot) != "1148" { - t.Error("Test Failed - ticker PriceToString low value is incorrect") + + sillyP := p + sillyP.Base = currency.GALA_NEO + err = ProcessTicker("subscribetest", &Price{Pair: sillyP}, asset.Spot) + if err == nil { + t.Error("error cannot be nil") } - if newTicker.PriceToString(newPair, "bid", asset.Spot) != "1195" { - t.Error("Test Failed - ticker PriceToString bid value is incorrect") + + sillyP.Quote = currency.AAA + err = ProcessTicker("subscribetest", &Price{Pair: sillyP}, asset.Spot) + if err == nil { + t.Error("error cannot be nil") } - if newTicker.PriceToString(newPair, "ask", asset.Spot) != "1220" { - t.Error("Test Failed - ticker PriceToString ask value is incorrect") + + err = ProcessTicker("subscribetest", &Price{Pair: sillyP}, "silly") + if err == nil { + t.Error("error cannot be nil") } - if newTicker.PriceToString(newPair, "volume", asset.Spot) != "5" { - t.Error("Test Failed - ticker PriceToString volume value is incorrect") + // reinstate mux + service.mux = cpyMux + + err = ProcessTicker("subscribetest", &Price{Pair: p}, asset.Spot) + if err != nil { + t.Error("error cannot be nil") } - if newTicker.PriceToString(newPair, "ath", asset.Spot) != "1337" { - t.Error("Test Failed - ticker PriceToString ath value is incorrect") + + _, err = SubscribeTicker("subscribetest", p, asset.Spot) + if err != nil { + t.Error("cannot subscribe to ticker", err) } - if newTicker.PriceToString(newPair, "obtuse", asset.Spot) != "" { - t.Error("Test Failed - ticker PriceToString obtuse value is incorrect") +} + +func TestSubscribeToExchangeTickers(t *testing.T) { + _, err := SubscribeToExchangeTickers("") + if err == nil { + t.Error("error cannot be nil") + } + + p := currency.NewPair(currency.BTC, currency.USD) + + err = ProcessTicker("subscribeExchangeTest", &Price{Pair: p}, asset.Spot) + if err != nil { + t.Error(err) + } + + _, err = SubscribeToExchangeTickers("subscribeExchangeTest") + if err != nil { + t.Error("error cannot be nil", err) } } @@ -111,142 +150,25 @@ func TestGetTicker(t *testing.T) { if tickerPrice.PriceATH != 9001 { t.Error("Test Failed - ticker tickerPrice.PriceATH value is incorrect") } -} -func TestGetTickerByExchange(t *testing.T) { - newPair := currency.NewPairFromStrings("BTC", "USD") - priceStruct := Price{ - Pair: newPair, - Last: 1200, - High: 1298, - Low: 1148, - Bid: 1195, - Ask: 1220, - Volume: 5, - PriceATH: 1337, + _, err = GetTicker("bitfinex", newPair, "meowCats") + if err == nil { + t.Error("Test Failed - Ticker GetTicker error cannot be nil") } - anxTicker := CreateNewTicker("ANX", &priceStruct, asset.Spot) - Tickers = append(Tickers, anxTicker) - - tickerPtr, err := GetTickerByExchange("ANX") + err = ProcessTicker("bitfinex", &priceStruct, "meowCats") if err != nil { - t.Errorf("Test Failed - GetTickerByExchange init error: %s", err) - } - if tickerPtr.ExchangeName != "ANX" { - t.Error("Test Failed - GetTickerByExchange ExchangeName value is incorrect") - } -} - -func TestBaseCurrencyExists(t *testing.T) { - newPair := currency.NewPairFromStrings("BTC", "USD") - priceStruct := Price{ - Pair: newPair, - Last: 1200, - High: 1298, - Low: 1148, - Bid: 1195, - Ask: 1220, - Volume: 5, - PriceATH: 1337, + t.Fatal("Test failed. ProcessTicker error", err) } - alphaTicker := CreateNewTicker("alphapoint", &priceStruct, asset.Spot) - Tickers = append(Tickers, alphaTicker) - - if !BaseCurrencyExists("alphapoint", currency.BTC) { - t.Error("Test Failed - BaseCurrencyExists1 value return is incorrect") - } - if BaseCurrencyExists("alphapoint", currency.NewCode("CATS")) { - t.Error("Test Failed - BaseCurrencyExists2 value return is incorrect") - } -} - -func TestQuoteCurrencyExists(t *testing.T) { - t.Parallel() - - newPair := currency.NewPairFromStrings("BTC", "USD") - priceStruct := Price{ - Pair: newPair, - Last: 1200, - High: 1298, - Low: 1148, - Bid: 1195, - Ask: 1220, - Volume: 5, - PriceATH: 1337, - } - - bitstampTicker := CreateNewTicker("bitstamp", &priceStruct, asset.Spot) - Tickers = append(Tickers, bitstampTicker) - - if !QuoteCurrencyExists("bitstamp", newPair) { - t.Error("Test Failed - QuoteCurrencyExists1 value return is incorrect") - } - - newPair.Quote = currency.NewCode("DOGS") - if QuoteCurrencyExists("bitstamp", newPair) { - t.Error("Test Failed - QuoteCurrencyExists2 value return is incorrect") - } -} - -func TestCreateNewTicker(t *testing.T) { - const float64Type = "float64" - newPair := currency.NewPairFromStrings("BTC", "USD") - priceStruct := Price{ - Pair: newPair, - Last: 1200, - High: 1298, - Low: 1148, - Bid: 1195, - Ask: 1220, - Volume: 5, - PriceATH: 1337, - } - - newTicker := CreateNewTicker("ANX", &priceStruct, asset.Spot) - - if reflect.ValueOf(newTicker).NumField() != 2 { - t.Error("Test Failed - ticker CreateNewTicker struct change/or updated") - } - if reflect.TypeOf(newTicker.ExchangeName).String() != "string" { - t.Error("Test Failed - ticker CreateNewTicker.ExchangeName value is not a string") - } - if newTicker.ExchangeName != "ANX" { - t.Error("Test Failed - ticker CreateNewTicker.ExchangeName value is not ANX") - } - - if !newTicker.Price[currency.BTC.Upper().String()][currency.USD.Upper().String()][asset.Spot.String()].Pair.Equal(newPair) { - t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Pair.Pair().String() value is not expected 'BTCUSD'") - } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Ask).String() != float64Type { - t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Ask value is not a float64") - } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Bid).String() != float64Type { - t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Bid value is not a float64") - } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Pair).String() != "currency.Pair" { - t.Error("Test Failed - ticker newTicker.Price[BTC][USD].CurrencyPair value is not a currency.Pair") - } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].High).String() != float64Type { - t.Error("Test Failed - ticker newTicker.Price[BTC][USD].High value is not a float64") - } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Last).String() != float64Type { - t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Last value is not a float64") - } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Low).String() != float64Type { - t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Low value is not a float64") - } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].PriceATH).String() != float64Type { - t.Error("Test Failed - ticker newTicker.Price[BTC][USD].PriceATH value is not a float64") - } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"][asset.Spot.String()].Volume).String() != float64Type { - t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Volume value is not a float64") + // process update again + err = ProcessTicker("bitfinex", &priceStruct, "meowCats") + if err != nil { + t.Fatal("Test failed. ProcessTicker error", err) } } func TestProcessTicker(t *testing.T) { // non-appending function to tickers - Tickers = []Ticker{} exchName := "bitstamp" newPair := currency.NewPairFromStrings("BTC", "USD") priceStruct := Price{ @@ -259,8 +181,13 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers PriceATH: 1337, } + err := ProcessTicker("", &priceStruct, asset.Spot) + if err == nil { + t.Fatal("empty exchange should throw an err") + } + // test for empty pair - err := ProcessTicker(exchName, &priceStruct, asset.Spot) + err = ProcessTicker(exchName, &priceStruct, asset.Spot) if err == nil { t.Fatal("empty pair should throw an err") } @@ -389,5 +316,44 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers } } wg.Wait() - +} + +func TestSetItemID(t *testing.T) { + err := service.SetItemID(nil) + if err == nil { + t.Error("error cannot be nil") + } + + err = service.SetItemID(&Price{}) + if err == nil { + t.Error("error cannot be nil") + } + + p := currency.NewPair(currency.CYC, currency.CYG) + + service.mux = nil + err = service.SetItemID(&Price{Pair: p, ExchangeName: "SetItemID"}) + if err == nil { + t.Error("error cannot be nil") + } + + service.mux = cpyMux +} + +func TestGetAssociation(t *testing.T) { + _, err := service.GetAssociations(nil) + if err == nil { + t.Error("error cannot be nil ") + } + + p := currency.NewPair(currency.CYC, currency.CYG) + + service.mux = nil + + _, err = service.GetAssociations(&Price{Pair: p, ExchangeName: "GetAssociation"}) + if err == nil { + t.Error("error cannot be nil ") + } + + service.mux = cpyMux } diff --git a/exchanges/ticker/ticker_types.go b/exchanges/ticker/ticker_types.go new file mode 100644 index 00000000..ba37ed11 --- /dev/null +++ b/exchanges/ticker/ticker_types.go @@ -0,0 +1,57 @@ +package ticker + +import ( + "sync" + "time" + + "github.com/gofrs/uuid" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/dispatch" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" +) + +// const values for the ticker package +const ( + errExchangeNameUnset = "ticker exchange name not set" + errPairNotSet = "ticker currency pair not set" + errAssetTypeNotSet = "ticker asset type not set" + errTickerPriceIsNil = "ticker price is nil" +) + +// Vars for the ticker package +var ( + service *Service +) + +// Service holds ticker information for each individual exchange +type Service struct { + Tickers map[string]map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker + Exchange map[string]uuid.UUID + mux *dispatch.Mux + sync.RWMutex +} + +// Price struct stores the currency pair and pricing information +type Price struct { + Last float64 `json:"Last"` + High float64 `json:"High"` + Low float64 `json:"Low"` + Bid float64 `json:"Bid"` + Ask float64 `json:"Ask"` + Volume float64 `json:"Volume"` + QuoteVolume float64 `json:"QuoteVolume"` + PriceATH float64 `json:"PriceATH"` + Open float64 `json:"Open"` + Close float64 `json:"Close"` + Pair currency.Pair `json:"Pair"` + ExchangeName string `json:"exchangeName"` + AssetType asset.Item `json:"assetType"` + LastUpdated time.Time +} + +// Ticker struct holds the ticker information for a currency pair and type +type Ticker struct { + Price + Main uuid.UUID + Assoc []uuid.UUID +} diff --git a/exchanges/websocket/wsorderbook/wsorderbook.go b/exchanges/websocket/wsorderbook/wsorderbook.go index 3446ddbd..2422b921 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook.go +++ b/exchanges/websocket/wsorderbook/wsorderbook.go @@ -1,6 +1,7 @@ package wsorderbook import ( + "errors" "fmt" "sort" "sync" @@ -205,6 +206,19 @@ func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base) err if len(newOrderbook.Asks) == 0 || len(newOrderbook.Bids) == 0 { return fmt.Errorf("%v snapshot ask and bids are nil", w.exchangeName) } + + if newOrderbook.Pair.IsEmpty() { + return errors.New("websocket orderbook pair unset") + } + + if newOrderbook.AssetType.String() == "" { + return errors.New("websocket orderbook asset type unset") + } + + if newOrderbook.ExchangeName == "" { + return errors.New("websocket orderbook exchange name unset") + } + w.m.Lock() defer w.m.Unlock() if w.ob == nil { diff --git a/exchanges/websocket/wsorderbook/wsorderbook_test.go b/exchanges/websocket/wsorderbook/wsorderbook_test.go index acc00095..8c0c09db 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_test.go +++ b/exchanges/websocket/wsorderbook/wsorderbook_test.go @@ -26,6 +26,7 @@ const ( func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, bids []orderbook.Item, err error) { var snapShot1 orderbook.Base + snapShot1.ExchangeName = exchangeName curr = currency.NewPairFromString("BTCUSD") asks = []orderbook.Item{ {Price: 4000, Amount: 1, ID: 6}, @@ -37,19 +38,28 @@ func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, b snapShot1.Bids = bids snapShot1.AssetType = asset.Spot snapShot1.Pair = curr - obl = &WebsocketOrderbookLocal{} + obl = &WebsocketOrderbookLocal{exchangeName: exchangeName} err = obl.LoadSnapshot(&snapShot1) return } -// BenchmarkBufferPerformance demonstrates buffer more performant than multi process calls +// BenchmarkBufferPerformance demonstrates buffer more performant than multi +// process calls func BenchmarkBufferPerformance(b *testing.B) { obl, curr, asks, bids, err := createSnapshot() if err != nil { b.Fatal(err) } - obl.exchangeName = exchangeName obl.sortBuffer = true + cp := currency.NewPairFromString("BTCUSD") + // This is to ensure we do not send in zero orderbook info to our main book + // in orderbook.go, orderbooks should not be zero even after an update. + dummyItem := orderbook.Item{ + Amount: 1333337, + Price: 1337.1337, + ID: 1337, + } + obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) update := &WebsocketOrderbookUpdate{ Bids: bids, Asks: asks, @@ -74,7 +84,6 @@ func BenchmarkBufferSortingPerformance(b *testing.B) { if err != nil { b.Fatal(err) } - obl.exchangeName = exchangeName obl.sortBuffer = true obl.bufferEnabled = true obl.obBufferLimit = 5 @@ -96,13 +105,22 @@ func BenchmarkBufferSortingPerformance(b *testing.B) { } } -// BenchmarkNoBufferPerformance demonstrates orderbook process less performant than buffer +// BenchmarkNoBufferPerformance demonstrates orderbook process less performant +// than buffer func BenchmarkNoBufferPerformance(b *testing.B) { obl, curr, asks, bids, err := createSnapshot() if err != nil { b.Fatal(err) } - obl.exchangeName = exchangeName + cp := currency.NewPairFromString("BTCUSD") + // This is to ensure we do not send in zero orderbook info to our main book + // in orderbook.go, orderbooks should not be zero even after an update. + dummyItem := orderbook.Item{ + Amount: 1333337, + Price: 1337.1337, + ID: 1337, + } + obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) update := &WebsocketOrderbookUpdate{ Bids: bids, Asks: asks, @@ -127,7 +145,6 @@ func TestHittingTheBuffer(t *testing.T) { if err != nil { t.Fatal(err) } - obl.exchangeName = exchangeName obl.bufferEnabled = true obl.obBufferLimit = 5 for i := 0; i < len(itemArray); i++ { @@ -146,10 +163,12 @@ func TestHittingTheBuffer(t *testing.T) { } if len(obl.ob[curr][asset.Spot].Asks) != 3 { t.Log(obl.ob[curr][asset.Spot]) - t.Errorf("expected 3 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks)) + t.Errorf("expected 3 entries, received: %v", + len(obl.ob[curr][asset.Spot].Asks)) } if len(obl.ob[curr][asset.Spot].Bids) != 3 { - t.Errorf("expected 3 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids)) + t.Errorf("expected 3 entries, received: %v", + len(obl.ob[curr][asset.Spot].Bids)) } } @@ -159,7 +178,6 @@ func TestInsertWithIDs(t *testing.T) { if err != nil { t.Fatal(err) } - obl.exchangeName = exchangeName obl.bufferEnabled = true obl.updateEntriesByID = true obl.obBufferLimit = 5 @@ -179,10 +197,12 @@ func TestInsertWithIDs(t *testing.T) { } } if len(obl.ob[curr][asset.Spot].Asks) != 6 { - t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks)) + t.Errorf("expected 6 entries, received: %v", + len(obl.ob[curr][asset.Spot].Asks)) } if len(obl.ob[curr][asset.Spot].Bids) != 6 { - t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids)) + t.Errorf("expected 6 entries, received: %v", + len(obl.ob[curr][asset.Spot].Bids)) } } @@ -192,7 +212,6 @@ func TestSortIDs(t *testing.T) { if err != nil { t.Fatal(err) } - obl.exchangeName = exchangeName obl.bufferEnabled = true obl.sortBufferByUpdateIDs = true obl.sortBuffer = true @@ -212,10 +231,12 @@ func TestSortIDs(t *testing.T) { } } if len(obl.ob[curr][asset.Spot].Asks) != 3 { - t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks)) + t.Errorf("expected 3 entries, received: %v", + len(obl.ob[curr][asset.Spot].Asks)) } if len(obl.ob[curr][asset.Spot].Bids) != 3 { - t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids)) + t.Errorf("expected 3 entries, received: %v", + len(obl.ob[curr][asset.Spot].Bids)) } } @@ -225,7 +246,16 @@ func TestDeleteWithIDs(t *testing.T) { if err != nil { t.Fatal(err) } - obl.exchangeName = exchangeName + + cp := currency.NewPairFromString("BTCUSD") + // This is to ensure we do not send in zero orderbook info to our main book + // in orderbook.go, orderbooks should not be zero even after an update. + dummyItem := orderbook.Item{ + Amount: 1333337, + Price: 1337.1337, + ID: 1337, + } + obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) obl.updateEntriesByID = true for i := 0; i < len(itemArray); i++ { asks := itemArray[i] @@ -243,10 +273,12 @@ func TestDeleteWithIDs(t *testing.T) { } } if len(obl.ob[curr][asset.Spot].Asks) != 0 { - t.Errorf("expected 0 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks)) + t.Errorf("expected 0 entries, received: %v", + len(obl.ob[curr][asset.Spot].Asks)) } - if len(obl.ob[curr][asset.Spot].Bids) != 0 { - t.Errorf("expected 0 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids)) + if len(obl.ob[curr][asset.Spot].Bids) != 1 { + t.Errorf("expected 1 entries, received: %v", + len(obl.ob[curr][asset.Spot].Bids)) } } @@ -256,7 +288,6 @@ func TestUpdateWithIDs(t *testing.T) { if err != nil { t.Fatal(err) } - obl.exchangeName = exchangeName obl.updateEntriesByID = true for i := 0; i < len(itemArray); i++ { asks := itemArray[i] @@ -288,11 +319,10 @@ func TestOutOfOrderIDs(t *testing.T) { if err != nil { t.Fatal(err) } - outOFOrderIDs := []int64{2, 1, 5, 3, 4, 6} + outOFOrderIDs := []int64{2, 1, 5, 3, 4, 6, 7} if itemArray[0][0].Price != 1000 { t.Errorf("expected sorted price to be 3000, received: %v", itemArray[1][0].Price) } - obl.exchangeName = exchangeName obl.bufferEnabled = true obl.sortBuffer = true obl.obBufferLimit = 5 @@ -366,7 +396,8 @@ func TestRunUpdateWithoutAnyUpdates(t *testing.T) { if err == nil { t.Fatal("expected an error running update with no snapshot loaded") } - if err.Error() != fmt.Sprintf("%v cannot have bids and ask targets both nil", exchangeName) { + if err.Error() != fmt.Sprintf("%v cannot have bids and ask targets both nil", + exchangeName) { t.Fatal("expected nil asks and bids error") } } @@ -395,6 +426,7 @@ func TestRunSnapshotWithNoData(t *testing.T) { func TestLoadSnapshot(t *testing.T) { var obl WebsocketOrderbookLocal var snapShot1 orderbook.Base + snapShot1.ExchangeName = "SnapshotWithOverride" curr := currency.NewPairFromString("BTCUSD") asks := []orderbook.Item{ {Price: 4000, Amount: 1, ID: 8}, @@ -432,6 +464,7 @@ func TestFlushCache(t *testing.T) { func TestInsertingSnapShots(t *testing.T) { var obl WebsocketOrderbookLocal var snapShot1 orderbook.Base + snapShot1.ExchangeName = "WSORDERBOOKTEST1" asks := []orderbook.Item{ {Price: 6000, Amount: 1, ID: 1}, {Price: 6001, Amount: 0.5, ID: 2}, @@ -469,6 +502,7 @@ func TestInsertingSnapShots(t *testing.T) { t.Fatal(err) } var snapShot2 orderbook.Base + snapShot2.ExchangeName = "WSORDERBOOKTEST2" asks = []orderbook.Item{ {Price: 51, Amount: 1, ID: 1}, {Price: 52, Amount: 0.5, ID: 2}, @@ -506,6 +540,7 @@ func TestInsertingSnapShots(t *testing.T) { t.Fatal(err) } var snapShot3 orderbook.Base + snapShot3.ExchangeName = "WSORDERBOOKTEST3" asks = []orderbook.Item{ {Price: 511, Amount: 1, ID: 1}, {Price: 52, Amount: 0.5, ID: 2}, diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index f52c0aed..0ac6d2ec 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -142,6 +142,7 @@ func (z *ZB) WsHandleData() { newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = cPair + newOrderBook.ExchangeName = z.GetName() err = z.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index 71453f7f..b44758ea 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -9,6 +9,8 @@ import ( proto "github.com/golang/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" math "math" ) @@ -4714,6 +4716,194 @@ func (m *ExchangePairRequest) GetPair() *CurrencyPair { return nil } +type GetOrderbookStreamRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderbookStreamRequest) Reset() { *m = GetOrderbookStreamRequest{} } +func (m *GetOrderbookStreamRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderbookStreamRequest) ProtoMessage() {} +func (*GetOrderbookStreamRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{92} +} + +func (m *GetOrderbookStreamRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderbookStreamRequest.Unmarshal(m, b) +} +func (m *GetOrderbookStreamRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderbookStreamRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderbookStreamRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderbookStreamRequest.Merge(m, src) +} +func (m *GetOrderbookStreamRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderbookStreamRequest.Size(m) +} +func (m *GetOrderbookStreamRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderbookStreamRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderbookStreamRequest proto.InternalMessageInfo + +func (m *GetOrderbookStreamRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *GetOrderbookStreamRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *GetOrderbookStreamRequest) GetAssetType() string { + if m != nil { + return m.AssetType + } + return "" +} + +type GetExchangeOrderbookStreamRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangeOrderbookStreamRequest) Reset() { *m = GetExchangeOrderbookStreamRequest{} } +func (m *GetExchangeOrderbookStreamRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangeOrderbookStreamRequest) ProtoMessage() {} +func (*GetExchangeOrderbookStreamRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{93} +} + +func (m *GetExchangeOrderbookStreamRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeOrderbookStreamRequest.Unmarshal(m, b) +} +func (m *GetExchangeOrderbookStreamRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeOrderbookStreamRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangeOrderbookStreamRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeOrderbookStreamRequest.Merge(m, src) +} +func (m *GetExchangeOrderbookStreamRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangeOrderbookStreamRequest.Size(m) +} +func (m *GetExchangeOrderbookStreamRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeOrderbookStreamRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeOrderbookStreamRequest proto.InternalMessageInfo + +func (m *GetExchangeOrderbookStreamRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +type GetTickerStreamRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetTickerStreamRequest) Reset() { *m = GetTickerStreamRequest{} } +func (m *GetTickerStreamRequest) String() string { return proto.CompactTextString(m) } +func (*GetTickerStreamRequest) ProtoMessage() {} +func (*GetTickerStreamRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{94} +} + +func (m *GetTickerStreamRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetTickerStreamRequest.Unmarshal(m, b) +} +func (m *GetTickerStreamRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetTickerStreamRequest.Marshal(b, m, deterministic) +} +func (m *GetTickerStreamRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetTickerStreamRequest.Merge(m, src) +} +func (m *GetTickerStreamRequest) XXX_Size() int { + return xxx_messageInfo_GetTickerStreamRequest.Size(m) +} +func (m *GetTickerStreamRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetTickerStreamRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetTickerStreamRequest proto.InternalMessageInfo + +func (m *GetTickerStreamRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *GetTickerStreamRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair + } + return nil +} + +func (m *GetTickerStreamRequest) GetAssetType() string { + if m != nil { + return m.AssetType + } + return "" +} + +type GetExchangeTickerStreamRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangeTickerStreamRequest) Reset() { *m = GetExchangeTickerStreamRequest{} } +func (m *GetExchangeTickerStreamRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangeTickerStreamRequest) ProtoMessage() {} +func (*GetExchangeTickerStreamRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{95} +} + +func (m *GetExchangeTickerStreamRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeTickerStreamRequest.Unmarshal(m, b) +} +func (m *GetExchangeTickerStreamRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeTickerStreamRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangeTickerStreamRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeTickerStreamRequest.Merge(m, src) +} +func (m *GetExchangeTickerStreamRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangeTickerStreamRequest.Size(m) +} +func (m *GetExchangeTickerStreamRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeTickerStreamRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeTickerStreamRequest proto.InternalMessageInfo + +func (m *GetExchangeTickerStreamRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + func init() { proto.RegisterType((*GetInfoRequest)(nil), "gctrpc.GetInfoRequest") proto.RegisterType((*GetInfoResponse)(nil), "gctrpc.GetInfoResponse") @@ -4821,296 +5011,309 @@ func init() { proto.RegisterType((*GetExchangePairsResponse)(nil), "gctrpc.GetExchangePairsResponse") proto.RegisterMapType((map[string]*PairsSupported)(nil), "gctrpc.GetExchangePairsResponse.SupportedAssetsEntry") proto.RegisterType((*ExchangePairRequest)(nil), "gctrpc.ExchangePairRequest") + proto.RegisterType((*GetOrderbookStreamRequest)(nil), "gctrpc.GetOrderbookStreamRequest") + proto.RegisterType((*GetExchangeOrderbookStreamRequest)(nil), "gctrpc.GetExchangeOrderbookStreamRequest") + proto.RegisterType((*GetTickerStreamRequest)(nil), "gctrpc.GetTickerStreamRequest") + proto.RegisterType((*GetExchangeTickerStreamRequest)(nil), "gctrpc.GetExchangeTickerStreamRequest") } func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 4537 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7b, 0xcd, 0x6f, 0x1c, 0x57, - 0x72, 0x38, 0x66, 0x38, 0x22, 0x39, 0x35, 0x43, 0x72, 0xf8, 0xf8, 0x35, 0x1a, 0x92, 0xa2, 0xd4, - 0x5e, 0xc9, 0x92, 0xd7, 0xa6, 0x6c, 0x59, 0xbf, 0xdf, 0x3a, 0xeb, 0xcd, 0x26, 0x34, 0x2d, 0x73, - 0x15, 0x7b, 0x2d, 0xa6, 0xa9, 0x95, 0x00, 0x6f, 0xe0, 0x4e, 0x73, 0xfa, 0x71, 0xd8, 0x51, 0x4f, - 0x77, 0xbb, 0xbb, 0x87, 0xd4, 0x38, 0x01, 0x02, 0x18, 0x48, 0x90, 0x53, 0x72, 0x58, 0x04, 0xd8, - 0x43, 0x4e, 0x39, 0x06, 0xc8, 0x25, 0xc8, 0x29, 0x87, 0x45, 0xae, 0x41, 0x8e, 0xb9, 0xe4, 0x0f, - 0x08, 0x72, 0x4b, 0x02, 0x04, 0xc8, 0x25, 0xa7, 0xe0, 0xd5, 0xfb, 0xe8, 0xf7, 0xba, 0x7b, 0x86, - 0xa3, 0x5d, 0xad, 0x2f, 0xd2, 0x74, 0xbd, 0x7a, 0x55, 0xf5, 0xea, 0xd5, 0xab, 0x57, 0x55, 0xaf, - 0x08, 0xcd, 0x24, 0xee, 0xef, 0xc7, 0x49, 0x94, 0x45, 0x64, 0x7e, 0xd0, 0xcf, 0x92, 0xb8, 0xdf, - 0xdb, 0x19, 0x44, 0xd1, 0x20, 0xa0, 0xf7, 0xdd, 0xd8, 0xbf, 0xef, 0x86, 0x61, 0x94, 0xb9, 0x99, - 0x1f, 0x85, 0x29, 0xc7, 0xb2, 0x3a, 0xb0, 0x7c, 0x44, 0xb3, 0xc7, 0xe1, 0x59, 0x64, 0xd3, 0xaf, - 0x46, 0x34, 0xcd, 0xac, 0xbf, 0x6f, 0xc0, 0x8a, 0x02, 0xa5, 0x71, 0x14, 0xa6, 0x94, 0x6c, 0xc2, - 0xfc, 0x28, 0xce, 0xfc, 0x21, 0xed, 0xd6, 0x6e, 0xd6, 0xee, 0x36, 0x6d, 0xf1, 0x45, 0xee, 0xc3, - 0x9a, 0x7b, 0xe1, 0xfa, 0x81, 0x7b, 0x1a, 0x50, 0x87, 0xbe, 0xec, 0x9f, 0xbb, 0xe1, 0x80, 0xa6, - 0xdd, 0xfa, 0xcd, 0xda, 0xdd, 0x39, 0x9b, 0xa8, 0xa1, 0x47, 0x72, 0x84, 0x7c, 0x17, 0x56, 0x69, - 0xc8, 0x40, 0x9e, 0x86, 0x3e, 0x87, 0xe8, 0x1d, 0x31, 0x90, 0x23, 0x3f, 0x84, 0x4d, 0x8f, 0x9e, - 0xb9, 0xa3, 0x20, 0x73, 0xce, 0xa2, 0x84, 0xbe, 0x74, 0xe2, 0x24, 0xba, 0xf0, 0x3d, 0x9a, 0x74, - 0x1b, 0x28, 0xc5, 0xba, 0x18, 0xfd, 0x84, 0x0d, 0x1e, 0x8b, 0x31, 0xf2, 0x00, 0x36, 0xd4, 0x2c, - 0xdf, 0xcd, 0x9c, 0xfe, 0x28, 0x49, 0x68, 0xd8, 0x1f, 0x77, 0xaf, 0xe1, 0xa4, 0x35, 0x39, 0xc9, - 0x77, 0xb3, 0x43, 0x31, 0x44, 0x9e, 0x43, 0x27, 0x1d, 0x9d, 0xa6, 0xe3, 0x34, 0xa3, 0x43, 0x27, - 0xcd, 0xdc, 0x6c, 0x94, 0x76, 0xe7, 0x6f, 0xce, 0xdd, 0x6d, 0x3d, 0x78, 0x7b, 0x9f, 0xab, 0x71, - 0xbf, 0xa0, 0x92, 0xfd, 0x13, 0x89, 0x7f, 0x82, 0xe8, 0x8f, 0xc2, 0x2c, 0x19, 0xdb, 0x2b, 0xa9, - 0x09, 0x25, 0x9f, 0xc3, 0x52, 0x12, 0xf7, 0x1d, 0x1a, 0x7a, 0x71, 0xe4, 0x87, 0x59, 0xda, 0x5d, - 0x40, 0xaa, 0xf7, 0x26, 0x51, 0xb5, 0xe3, 0xfe, 0x23, 0x89, 0xcb, 0x49, 0xb6, 0x13, 0x0d, 0xd4, - 0xfb, 0x08, 0xd6, 0xab, 0x18, 0x93, 0x0e, 0xcc, 0xbd, 0xa0, 0x63, 0xb1, 0x3b, 0xec, 0x27, 0x59, - 0x87, 0x6b, 0x17, 0x6e, 0x30, 0xa2, 0xb8, 0x19, 0x8b, 0x36, 0xff, 0xf8, 0x7e, 0xfd, 0x83, 0x5a, - 0xef, 0x29, 0xac, 0x96, 0xd8, 0x54, 0x10, 0xb8, 0xa7, 0x13, 0x68, 0x3d, 0x58, 0x93, 0x22, 0xdb, - 0xc7, 0x87, 0x72, 0xae, 0x46, 0xd5, 0xba, 0x05, 0x7b, 0x47, 0x34, 0x3b, 0x8c, 0x86, 0xc3, 0x51, - 0xe8, 0xf7, 0xd1, 0xc6, 0x6c, 0x1a, 0xb8, 0x63, 0x9a, 0xa4, 0xd2, 0xb2, 0x3e, 0x87, 0xf5, 0xaa, - 0x71, 0xd2, 0x85, 0x05, 0xb1, 0xf7, 0xc8, 0x7f, 0xd1, 0x96, 0x9f, 0x64, 0x07, 0x9a, 0xfd, 0x28, - 0x0c, 0x69, 0x3f, 0xa3, 0x9e, 0x58, 0x48, 0x0e, 0xb0, 0xfe, 0xb4, 0x0e, 0x37, 0x27, 0xf3, 0x14, - 0xa6, 0xfb, 0x35, 0x6c, 0xf6, 0x75, 0x04, 0x27, 0x11, 0x18, 0xdd, 0x1a, 0x6e, 0xc5, 0xa1, 0xb6, - 0x15, 0x53, 0x29, 0xed, 0x57, 0x8e, 0xf2, 0x4d, 0xda, 0xe8, 0x57, 0x8d, 0xf5, 0xce, 0xa0, 0x37, - 0x79, 0x52, 0x85, 0xca, 0x1f, 0x98, 0x2a, 0xdf, 0x91, 0xa2, 0x55, 0x11, 0xd1, 0x75, 0xff, 0x3d, - 0xd8, 0x3a, 0xa2, 0x21, 0x4d, 0xfc, 0xbe, 0x32, 0x0e, 0xa1, 0x73, 0xa6, 0x41, 0x65, 0x93, 0x82, - 0x55, 0x0e, 0xb0, 0x7a, 0xd0, 0x2d, 0x4f, 0xe4, 0xcb, 0xb5, 0x36, 0x61, 0xfd, 0x88, 0x66, 0x0a, - 0xae, 0x76, 0xf1, 0x17, 0x35, 0xd8, 0xc0, 0x81, 0xf4, 0x34, 0x1d, 0xf3, 0x01, 0xa1, 0xea, 0xdf, - 0x87, 0x55, 0x45, 0x3a, 0x95, 0xc7, 0x88, 0x6b, 0xf9, 0x7d, 0x4d, 0xcb, 0xe5, 0x99, 0xf9, 0x61, - 0x4a, 0xf5, 0xd3, 0x94, 0x9f, 0x49, 0x01, 0xee, 0x1d, 0xc2, 0x46, 0x25, 0xea, 0xab, 0xd8, 0xbf, - 0xd5, 0x85, 0xcd, 0x23, 0x9a, 0x69, 0x66, 0xac, 0x19, 0x68, 0x4b, 0x03, 0x33, 0xbb, 0x4c, 0x33, - 0x37, 0xc9, 0x72, 0xbb, 0x14, 0x9f, 0xe4, 0x36, 0x2c, 0x07, 0x7e, 0x9a, 0xd1, 0xd0, 0x71, 0x3d, - 0x2f, 0xa1, 0x29, 0x77, 0x79, 0x4d, 0x7b, 0x89, 0x43, 0x0f, 0x38, 0xd0, 0xfa, 0x87, 0x1a, 0xdb, - 0x98, 0x02, 0x2b, 0xa1, 0xac, 0xcf, 0xa0, 0x99, 0x7b, 0x05, 0xae, 0xa4, 0x7d, 0x4d, 0x49, 0x55, - 0x73, 0xf6, 0x0b, 0xae, 0x21, 0x27, 0xd0, 0xfb, 0x5d, 0x58, 0x7e, 0xdd, 0x07, 0xfa, 0x03, 0xe8, - 0x09, 0xdb, 0x90, 0x1e, 0xf9, 0x73, 0x77, 0x48, 0xa5, 0x5d, 0xf5, 0x60, 0x51, 0x3a, 0x70, 0xc1, - 0x43, 0x7d, 0x5b, 0xbb, 0xb0, 0x5d, 0x39, 0x53, 0x18, 0xd6, 0x7d, 0x58, 0x3b, 0xa2, 0x99, 0x72, - 0xf3, 0x92, 0xe2, 0x44, 0x2f, 0x60, 0x3d, 0x44, 0x4b, 0xd4, 0x26, 0x08, 0x15, 0xee, 0x40, 0x33, - 0xbf, 0x44, 0x84, 0x6d, 0x2b, 0x80, 0xf5, 0x00, 0xcd, 0x54, 0xce, 0x7a, 0xf2, 0xf4, 0xd8, 0xa6, - 0x7c, 0xda, 0x75, 0x58, 0x8c, 0xb2, 0xd8, 0xe9, 0x47, 0x9e, 0x14, 0x7d, 0x21, 0xca, 0xe2, 0xc3, - 0xc8, 0xa3, 0xc2, 0x34, 0xb4, 0x39, 0xca, 0x34, 0xfe, 0x9a, 0x6f, 0xa5, 0x39, 0x24, 0xe4, 0xf8, - 0x1d, 0x68, 0x4a, 0x82, 0x72, 0x2b, 0xdf, 0xd1, 0xb6, 0xb2, 0x6a, 0xce, 0xfe, 0x13, 0xce, 0x51, - 0xec, 0xe4, 0xa2, 0x10, 0x20, 0xed, 0x7d, 0x08, 0x4b, 0xc6, 0xd0, 0x55, 0x96, 0xdd, 0xd4, 0xb7, - 0xec, 0x21, 0x6c, 0x7e, 0xec, 0xa7, 0xfa, 0x8d, 0x3b, 0xcb, 0x76, 0x7d, 0x09, 0xcb, 0xc7, 0xae, - 0x9f, 0xa4, 0x27, 0xa3, 0x38, 0x8e, 0xd0, 0xbc, 0xdf, 0x84, 0x95, 0xfc, 0x5a, 0x8f, 0xd9, 0x98, - 0x98, 0xb4, 0xac, 0xc0, 0x38, 0x83, 0xbc, 0x01, 0x4b, 0xf2, 0x3a, 0xe7, 0x68, 0x5c, 0xa4, 0xb6, - 0x00, 0x22, 0x92, 0xf5, 0x4d, 0xc3, 0x50, 0x9d, 0x11, 0x58, 0x10, 0x68, 0x84, 0xae, 0x0a, 0x2b, - 0xf0, 0xb7, 0x6e, 0x08, 0x75, 0xf3, 0x3a, 0xe8, 0xc2, 0xc2, 0x05, 0x4d, 0x4e, 0xa3, 0x94, 0x62, - 0xcc, 0xb0, 0x68, 0xcb, 0x4f, 0x26, 0xc8, 0x28, 0xf5, 0xc3, 0x81, 0x93, 0xba, 0xa1, 0x77, 0x1a, - 0xbd, 0xc4, 0x08, 0x61, 0xd1, 0x6e, 0x23, 0xf0, 0x84, 0xc3, 0xc8, 0x2d, 0x68, 0x9f, 0x67, 0x59, - 0xec, 0xb0, 0xd0, 0x25, 0x1a, 0x65, 0x22, 0x20, 0x68, 0x31, 0xd8, 0x53, 0x0e, 0x62, 0x07, 0x1b, - 0x51, 0x46, 0x29, 0x4d, 0xdc, 0x01, 0x0d, 0xb3, 0xee, 0x3c, 0x3f, 0xd8, 0x0c, 0xfa, 0x13, 0x09, - 0x24, 0xbb, 0x00, 0x88, 0x16, 0x27, 0xd1, 0xcb, 0x71, 0x77, 0x81, 0x9b, 0x1e, 0x83, 0x1c, 0x33, - 0x00, 0xd3, 0xdf, 0xa9, 0x9b, 0x52, 0x19, 0x7a, 0xf8, 0x34, 0xed, 0x2e, 0x72, 0xfd, 0x31, 0xf0, - 0xa1, 0x82, 0x12, 0x87, 0xc5, 0x1d, 0x42, 0xeb, 0x8e, 0x9b, 0xa6, 0x34, 0x4b, 0xbb, 0x4d, 0x34, - 0xa0, 0x87, 0x15, 0x06, 0x54, 0x88, 0x3f, 0xc4, 0xbc, 0x03, 0x9c, 0xa6, 0xe2, 0x0f, 0x03, 0xca, - 0xe2, 0x2d, 0x77, 0x94, 0x9d, 0xd3, 0x30, 0x63, 0xb7, 0x07, 0x63, 0x12, 0xfb, 0x5d, 0x40, 0xdd, - 0x74, 0x8c, 0x81, 0x83, 0xd8, 0xef, 0x7d, 0xc1, 0x82, 0x8b, 0x32, 0xd5, 0x0a, 0x13, 0x7c, 0xdb, - 0x74, 0x25, 0x9b, 0x52, 0x58, 0xd3, 0x8e, 0x74, 0xd3, 0xbc, 0x84, 0xce, 0x11, 0xcd, 0x9e, 0xfa, - 0xfd, 0x17, 0x34, 0x99, 0xc1, 0x28, 0xc9, 0x5d, 0x68, 0x30, 0x8b, 0x12, 0x0c, 0xd6, 0xd5, 0x4d, - 0x28, 0x22, 0x36, 0xc6, 0xc8, 0x46, 0x0c, 0xb6, 0x17, 0xa8, 0x39, 0x27, 0x1b, 0xc7, 0xdc, 0x2e, - 0x9a, 0x76, 0x13, 0x21, 0x4f, 0xc7, 0x31, 0xb5, 0x9e, 0x41, 0x5b, 0x9f, 0xc4, 0x9c, 0x86, 0x47, - 0x03, 0x7f, 0xe8, 0x67, 0x34, 0x91, 0x4e, 0x43, 0x01, 0x98, 0x3d, 0xb2, 0x2d, 0x12, 0x76, 0x8c, - 0xbf, 0xd9, 0x79, 0xfb, 0x6a, 0x14, 0x65, 0x92, 0x36, 0xff, 0xb0, 0xfe, 0xb2, 0x0e, 0xcb, 0x72, - 0x39, 0xc2, 0x98, 0xa5, 0xcc, 0xb5, 0x2b, 0x65, 0xbe, 0x05, 0xed, 0xc0, 0x4d, 0x33, 0x67, 0x14, - 0x7b, 0xae, 0x0c, 0x6d, 0xe6, 0xec, 0x16, 0x83, 0xfd, 0x84, 0x83, 0x98, 0x45, 0xcb, 0xc8, 0x15, - 0xcf, 0x96, 0xe0, 0xde, 0xee, 0xeb, 0x8b, 0x21, 0xd0, 0x60, 0x73, 0xd0, 0xda, 0x6b, 0x36, 0xfe, - 0x66, 0xb0, 0x73, 0x7f, 0x70, 0x8e, 0xd6, 0x5d, 0xb3, 0xf1, 0x37, 0xdb, 0xc1, 0x20, 0xba, 0x44, - 0x5b, 0xae, 0xd9, 0xec, 0x27, 0x83, 0x9c, 0xfa, 0x1e, 0x9a, 0x6e, 0xcd, 0x66, 0x3f, 0x19, 0xc4, - 0x4d, 0x5f, 0xa0, 0xa1, 0xd6, 0x6c, 0xf6, 0x93, 0x45, 0xfd, 0x17, 0x51, 0x30, 0x1a, 0xd2, 0x6e, - 0x13, 0x81, 0xe2, 0x8b, 0x6c, 0x43, 0x33, 0x4e, 0xfc, 0x3e, 0x75, 0xdc, 0xec, 0x1c, 0x8d, 0xa9, - 0x66, 0x2f, 0x22, 0xe0, 0x20, 0x3b, 0xb7, 0xd6, 0x60, 0x55, 0x6d, 0xb4, 0xf2, 0x9e, 0xcf, 0x61, - 0x41, 0x40, 0xa6, 0x6e, 0xfa, 0xbb, 0xb0, 0x90, 0x71, 0xb4, 0x6e, 0x1d, 0x4f, 0x81, 0x32, 0x2c, - 0x53, 0xd3, 0xb6, 0x44, 0xb3, 0x7e, 0x0b, 0x88, 0xce, 0x4d, 0x6c, 0xc4, 0xbd, 0x9c, 0x0e, 0x77, - 0xc7, 0x2b, 0x26, 0x9d, 0x34, 0x27, 0xf0, 0x35, 0x5e, 0x46, 0x4f, 0x12, 0x8f, 0x39, 0x92, 0xe8, - 0xc5, 0xb7, 0x6a, 0x9a, 0x3f, 0x86, 0x25, 0xc5, 0xf8, 0x71, 0x46, 0x87, 0x4c, 0xe1, 0xee, 0x30, - 0x1a, 0x85, 0x19, 0xf2, 0xac, 0xd9, 0xe2, 0x8b, 0x59, 0x20, 0xea, 0x17, 0x59, 0xd6, 0x6c, 0xfe, - 0x41, 0x96, 0xa1, 0xee, 0x7b, 0x22, 0x79, 0xaa, 0xfb, 0x9e, 0xf5, 0xbf, 0x35, 0x58, 0xd5, 0x16, - 0xf2, 0xca, 0x46, 0x59, 0xb2, 0xb8, 0x7a, 0x85, 0xc5, 0xdd, 0x83, 0xc6, 0xa9, 0xef, 0xb1, 0x9c, - 0x8d, 0xe9, 0x75, 0x43, 0x92, 0x33, 0xd6, 0x61, 0x23, 0x0a, 0x43, 0x75, 0xd3, 0x17, 0x69, 0xb7, - 0x31, 0x15, 0x95, 0xa1, 0x94, 0xce, 0xc3, 0xb5, 0xf2, 0x79, 0x30, 0x75, 0x39, 0x5f, 0xd4, 0x25, - 0x8f, 0x56, 0x15, 0x6d, 0x65, 0x79, 0x7d, 0x80, 0x1c, 0x38, 0x75, 0x5b, 0x7f, 0x03, 0x20, 0x52, - 0x98, 0xc2, 0xfe, 0xae, 0x97, 0x84, 0x56, 0x26, 0xa8, 0x21, 0x5b, 0x9f, 0x62, 0xa8, 0xa1, 0x33, - 0x17, 0xca, 0x7f, 0x60, 0xd0, 0xe4, 0xb6, 0x48, 0x4a, 0x34, 0x53, 0x83, 0xd8, 0xfb, 0x48, 0xec, - 0xa0, 0xdf, 0x67, 0x5b, 0xaf, 0x25, 0xe6, 0x53, 0xef, 0xf0, 0x67, 0xb0, 0x20, 0x66, 0x08, 0xb3, - 0xe0, 0x08, 0x75, 0xdf, 0x23, 0x1f, 0x02, 0x68, 0xf7, 0x10, 0x5f, 0xd7, 0xb6, 0x94, 0x41, 0x4c, - 0x92, 0xd6, 0x80, 0xec, 0x34, 0x74, 0xeb, 0x0c, 0xd6, 0x2a, 0x50, 0x98, 0x28, 0x2a, 0xad, 0x16, - 0xa2, 0xc8, 0x6f, 0xb2, 0x07, 0xad, 0x2c, 0xca, 0xdc, 0xc0, 0xc9, 0x6f, 0x88, 0x9a, 0x0d, 0x08, - 0x7a, 0xc6, 0x20, 0xe8, 0xa0, 0xa2, 0x80, 0x5b, 0x2e, 0x73, 0x50, 0x51, 0xe0, 0x59, 0x2e, 0x06, - 0x5e, 0xc6, 0xa2, 0x85, 0x0a, 0xa7, 0x6d, 0xd9, 0x77, 0x61, 0xd1, 0xe5, 0x53, 0xe4, 0xc2, 0x56, - 0x0a, 0x0b, 0xb3, 0x15, 0x82, 0x45, 0xf0, 0x06, 0x3a, 0x8c, 0xc2, 0x33, 0x7f, 0x20, 0xad, 0xe3, - 0x4d, 0x74, 0x56, 0x12, 0x96, 0xc7, 0x24, 0x9e, 0x9b, 0xb9, 0xc8, 0xad, 0x6d, 0xe3, 0x6f, 0xeb, - 0x4f, 0x6a, 0xd0, 0x39, 0x8e, 0x92, 0xec, 0x2c, 0x0a, 0xfc, 0x48, 0x84, 0xf7, 0x2c, 0x1c, 0x91, - 0xe1, 0xbf, 0x88, 0x23, 0xc5, 0x27, 0xf3, 0x90, 0xfd, 0xc8, 0x0f, 0xb9, 0xad, 0xd6, 0x85, 0x82, - 0x22, 0x3f, 0x64, 0xa6, 0x4a, 0x6e, 0x42, 0xcb, 0xa3, 0x69, 0x3f, 0xf1, 0x63, 0x96, 0xce, 0x09, - 0xb7, 0xa0, 0x83, 0x18, 0xe1, 0x53, 0x37, 0x70, 0xc3, 0x3e, 0x15, 0x9e, 0x5d, 0x7e, 0x5a, 0x1b, - 0xe8, 0xae, 0x94, 0x24, 0x5a, 0x66, 0x6d, 0x82, 0xc5, 0x52, 0xfe, 0x3f, 0x34, 0x63, 0x09, 0x14, - 0xe6, 0xd7, 0x55, 0x77, 0x75, 0x61, 0x39, 0x76, 0x8e, 0x6a, 0xed, 0xb0, 0xd8, 0x3f, 0xa7, 0x77, - 0x32, 0x1a, 0x0e, 0xdd, 0x64, 0x2c, 0xb9, 0x85, 0xd0, 0x38, 0x8c, 0xfc, 0x90, 0x29, 0x8a, 0x2d, - 0x4a, 0x06, 0x6f, 0xec, 0xb7, 0x2e, 0x7a, 0xdd, 0x10, 0x5d, 0xd7, 0xd6, 0x9c, 0xa9, 0xad, 0x1b, - 0x00, 0x31, 0x4d, 0xfa, 0x34, 0xcc, 0xdc, 0x81, 0x5c, 0xb1, 0x06, 0xb1, 0xce, 0x81, 0x3c, 0x39, - 0x3b, 0x0b, 0xfc, 0x90, 0x32, 0xb6, 0x42, 0x98, 0x29, 0xda, 0x9f, 0x2c, 0x83, 0xc9, 0x69, 0xae, - 0xc4, 0xe9, 0xc7, 0xb0, 0xfa, 0x24, 0xac, 0x60, 0x24, 0xc9, 0xd5, 0xa6, 0x91, 0xab, 0x97, 0xc8, - 0xfd, 0x08, 0xda, 0x9a, 0xe0, 0x29, 0xf9, 0x00, 0x9a, 0x42, 0x46, 0x95, 0x28, 0xf4, 0x94, 0x37, - 0x28, 0xad, 0xd0, 0xce, 0x91, 0xad, 0x9f, 0xd7, 0xa0, 0x95, 0x4b, 0x96, 0x92, 0x87, 0x70, 0x8d, - 0xa9, 0x5b, 0x52, 0xb9, 0xa1, 0xa8, 0xe4, 0x38, 0xfb, 0xf8, 0x2f, 0x8f, 0x0b, 0x39, 0x72, 0xef, - 0x04, 0x20, 0x07, 0x56, 0x84, 0x75, 0xf7, 0xcd, 0xb0, 0xee, 0x7a, 0x99, 0xaa, 0x14, 0x4d, 0x8b, - 0xec, 0xfe, 0xb9, 0xc1, 0xd2, 0xbd, 0x0a, 0x63, 0x11, 0x36, 0xf8, 0x0e, 0xb4, 0xf8, 0x59, 0x60, - 0x1e, 0x40, 0x0a, 0xdc, 0xce, 0x4b, 0x1b, 0x7e, 0x68, 0x03, 0x9e, 0x0d, 0x1c, 0x27, 0xef, 0xc1, - 0x12, 0x0a, 0xeb, 0x44, 0x5c, 0x21, 0xe2, 0x60, 0x9b, 0x13, 0xda, 0x88, 0x22, 0x54, 0x46, 0x62, - 0xd8, 0x30, 0xa6, 0x38, 0x29, 0x17, 0x41, 0x5c, 0x52, 0x3f, 0xd0, 0x42, 0xe9, 0x49, 0x52, 0x72, - 0x65, 0x09, 0x82, 0x62, 0x8c, 0xab, 0x6e, 0xad, 0x5f, 0x1e, 0x21, 0xf7, 0xa1, 0x2d, 0x38, 0xa2, - 0x66, 0xc4, 0x15, 0x67, 0xca, 0xd8, 0xe2, 0x13, 0x11, 0x81, 0x0c, 0x61, 0x5d, 0x9f, 0xa0, 0x24, - 0xbc, 0x86, 0x13, 0x3f, 0x9c, 0x5d, 0xc2, 0xb0, 0x24, 0x20, 0xe9, 0x97, 0x06, 0x7a, 0xbf, 0x07, - 0xdd, 0x49, 0x0b, 0xaa, 0xd8, 0xf6, 0xb7, 0xcc, 0x6d, 0x5f, 0xaf, 0x30, 0xc9, 0x54, 0x2f, 0x20, - 0x7e, 0x01, 0x5b, 0x13, 0x84, 0x79, 0x85, 0xaa, 0x83, 0x66, 0xa9, 0xba, 0x35, 0xfd, 0x45, 0x0d, - 0x7a, 0x07, 0x9e, 0x57, 0x72, 0x4e, 0x79, 0x91, 0xe0, 0xdb, 0x76, 0xb9, 0xbb, 0xb0, 0x5d, 0x29, - 0x90, 0xa8, 0x66, 0xbc, 0x84, 0x5d, 0x9b, 0x0e, 0xa3, 0x0b, 0xfa, 0x6d, 0x8b, 0x6c, 0xdd, 0x84, - 0x1b, 0x93, 0x38, 0x0b, 0xd9, 0xb0, 0xbc, 0x67, 0x96, 0xc7, 0x55, 0x60, 0xf4, 0x1f, 0x35, 0x58, - 0x32, 0x0b, 0xe7, 0xaf, 0x2b, 0x17, 0x7f, 0x1b, 0x48, 0x42, 0xd3, 0xcc, 0x49, 0xa2, 0x20, 0x60, - 0x29, 0xb9, 0x47, 0x03, 0x77, 0x2c, 0x4a, 0xf6, 0x1d, 0x36, 0x62, 0xf3, 0x81, 0x8f, 0x19, 0x9c, - 0x6c, 0xc1, 0x82, 0x1b, 0xfb, 0x0e, 0xb3, 0x1a, 0x9e, 0x8f, 0xcf, 0xbb, 0xb1, 0xff, 0x29, 0x1d, - 0x13, 0x0b, 0x96, 0xc4, 0x80, 0x13, 0xd0, 0x0b, 0x1a, 0x60, 0xcc, 0x37, 0x67, 0xb7, 0xf8, 0xf0, - 0x67, 0x0c, 0x44, 0xee, 0x41, 0x27, 0x4e, 0x7c, 0x66, 0x7e, 0xf9, 0xdb, 0xc0, 0x02, 0x4a, 0xb3, - 0x22, 0xe0, 0x72, 0x75, 0xd6, 0x4f, 0xe1, 0x7a, 0x85, 0x2e, 0x84, 0x8f, 0xfa, 0x21, 0xac, 0x98, - 0x2f, 0x0c, 0xd2, 0x4f, 0xa9, 0xa8, 0xd5, 0x98, 0x68, 0x2f, 0x9f, 0x19, 0x74, 0x44, 0xf4, 0x89, - 0x38, 0xb6, 0x9b, 0xa9, 0x9a, 0x96, 0xf5, 0x15, 0xac, 0xe7, 0xc0, 0xc3, 0x28, 0xbc, 0xa0, 0x49, - 0xca, 0xac, 0x8d, 0x40, 0xe3, 0x2c, 0x89, 0x64, 0x41, 0x16, 0x7f, 0xb3, 0xb8, 0x2d, 0x8b, 0x84, - 0x19, 0xd4, 0xb3, 0x88, 0xe1, 0x24, 0x6e, 0x26, 0x6f, 0x29, 0xfc, 0xcd, 0xe2, 0x64, 0x1f, 0x89, - 0x50, 0x07, 0xc7, 0xb8, 0xa9, 0xb6, 0x04, 0x8c, 0x71, 0xb1, 0x9e, 0x61, 0xf8, 0xa8, 0x8b, 0x22, - 0xd6, 0xf8, 0x9b, 0xd0, 0xe2, 0x6b, 0x64, 0x33, 0xe5, 0xfa, 0x76, 0x8c, 0xf5, 0x15, 0xc4, 0xb4, - 0xe1, 0x4c, 0x41, 0xad, 0xff, 0xaa, 0x43, 0x1b, 0x23, 0xd6, 0x8f, 0x69, 0xe6, 0xfa, 0xc1, 0xf4, - 0x58, 0x9a, 0xc7, 0xa0, 0x75, 0x15, 0x83, 0xbe, 0x01, 0x4b, 0x7a, 0x41, 0x64, 0x2c, 0x93, 0x59, - 0xad, 0x1c, 0x32, 0x26, 0xb7, 0x61, 0x19, 0x53, 0xeb, 0x1c, 0x8b, 0xdb, 0xcc, 0x12, 0x42, 0x15, - 0x9a, 0x99, 0x08, 0x5c, 0x2b, 0x24, 0x02, 0x6c, 0x18, 0x83, 0x69, 0x27, 0xf5, 0x3d, 0x95, 0x27, - 0x20, 0xe4, 0xc4, 0xf7, 0xb4, 0x61, 0x9c, 0xbd, 0xa0, 0x0d, 0xe3, 0x6c, 0x96, 0x03, 0x25, 0x94, - 0x3f, 0x14, 0xe0, 0x7b, 0xd7, 0x22, 0x1a, 0x5d, 0x5b, 0x02, 0x9f, 0xfa, 0x43, 0x7c, 0x0d, 0x13, - 0xc5, 0xed, 0x26, 0xb7, 0x58, 0xfe, 0x95, 0xa7, 0x69, 0xa0, 0xa7, 0x69, 0x79, 0x52, 0xd7, 0x32, - 0x92, 0xba, 0x3d, 0x68, 0x45, 0x31, 0x0d, 0x1d, 0x91, 0x62, 0xb7, 0x79, 0xf4, 0xc0, 0x40, 0xcf, - 0x10, 0x22, 0x4a, 0x26, 0xa8, 0xf3, 0x74, 0x96, 0xbc, 0xd4, 0x54, 0x4c, 0xbd, 0xa8, 0x18, 0x99, - 0x08, 0xce, 0x5d, 0x95, 0x08, 0x5a, 0x07, 0x18, 0x15, 0x4b, 0xc6, 0xc2, 0x7c, 0xde, 0x86, 0x79, - 0x54, 0x93, 0xb4, 0x9c, 0x75, 0x23, 0x8d, 0x11, 0x46, 0x61, 0x0b, 0x1c, 0xeb, 0x47, 0xf8, 0x86, - 0x88, 0x43, 0xb3, 0x88, 0x7e, 0x1d, 0x16, 0xf9, 0xae, 0x28, 0xab, 0x59, 0xc0, 0xef, 0xc7, 0x9e, - 0xf5, 0xaf, 0x35, 0x20, 0x27, 0xa3, 0xd3, 0xa1, 0x3f, 0x3b, 0xb5, 0xd9, 0x13, 0x74, 0x02, 0x0d, - 0x34, 0x13, 0x6e, 0x8e, 0xf8, 0xbb, 0x60, 0x21, 0x8d, 0xa2, 0x85, 0xe4, 0xdb, 0x79, 0xad, 0x3a, - 0x47, 0x9f, 0xd7, 0x37, 0x9f, 0xb9, 0xf8, 0xc0, 0xa7, 0x61, 0xe6, 0x88, 0x62, 0x0b, 0x73, 0xf1, - 0x08, 0x78, 0xec, 0x59, 0x27, 0xb0, 0x66, 0xac, 0x4c, 0x68, 0xfa, 0x16, 0xb4, 0xb9, 0x00, 0x71, - 0xe0, 0xf6, 0x55, 0x35, 0xbc, 0x85, 0xb0, 0x63, 0x04, 0x4d, 0xd3, 0xd7, 0x9f, 0xd5, 0x60, 0xfd, - 0xc4, 0x1f, 0x8e, 0x02, 0x37, 0xa3, 0xbf, 0x06, 0x8d, 0xe5, 0xcb, 0x9f, 0x33, 0x96, 0x2f, 0x35, - 0xd9, 0xc8, 0x35, 0x69, 0xfd, 0x77, 0x0d, 0x36, 0x0a, 0xa2, 0xa8, 0x98, 0xd0, 0x34, 0xa6, 0x09, - 0xc5, 0x01, 0x81, 0xa4, 0x31, 0xad, 0x1b, 0x4c, 0xdf, 0x80, 0xa5, 0xa1, 0x1f, 0xfa, 0xc3, 0xd1, - 0xd0, 0xe1, 0xba, 0xe7, 0x32, 0xb5, 0x05, 0xf0, 0x18, 0xb7, 0x80, 0x21, 0xb9, 0x2f, 0x35, 0xa4, - 0x86, 0x40, 0xe2, 0x40, 0x8e, 0xf4, 0x2e, 0xac, 0xe7, 0x71, 0xbb, 0x33, 0x70, 0xfd, 0xd0, 0x09, - 0xa2, 0x34, 0x15, 0x7b, 0x4c, 0xf2, 0xb1, 0x23, 0xd7, 0x0f, 0x3f, 0x8b, 0xd2, 0x54, 0x73, 0x02, - 0xf3, 0xba, 0x13, 0x60, 0x01, 0x4c, 0xe7, 0xf9, 0xb9, 0x1b, 0xd0, 0x8f, 0xa2, 0xe1, 0xe9, 0xeb, - 0xd5, 0xfd, 0x2d, 0x68, 0xf3, 0xba, 0x5b, 0xe6, 0x26, 0x03, 0x2a, 0x77, 0xa0, 0x85, 0xb0, 0xa7, - 0x08, 0xaa, 0xdc, 0x86, 0xff, 0xac, 0x01, 0x39, 0x64, 0xa1, 0x4c, 0x30, 0xb3, 0x3d, 0x30, 0x57, - 0xc2, 0xf3, 0xe6, 0xdc, 0xc2, 0x9a, 0x02, 0xf2, 0xd8, 0x34, 0xbf, 0x39, 0xc3, 0xfc, 0xd4, 0x6a, - 0x1a, 0xaf, 0x58, 0x1c, 0x2b, 0xf9, 0xf1, 0xdb, 0xb0, 0x7c, 0xe9, 0x06, 0x01, 0xcd, 0xd4, 0x13, - 0x9b, 0xa8, 0xc4, 0x73, 0xa8, 0xcc, 0xc1, 0xe5, 0x82, 0x17, 0xb4, 0x05, 0x6f, 0xc0, 0x9a, 0xb1, - 0x5e, 0x11, 0x0d, 0x3d, 0x84, 0x4d, 0x0e, 0x3e, 0x08, 0x82, 0x99, 0xbd, 0xaa, 0xf5, 0x57, 0x75, - 0xd8, 0x2a, 0x4d, 0x53, 0x61, 0x83, 0x69, 0xc6, 0x77, 0xd4, 0x72, 0xab, 0x27, 0xec, 0x8b, 0x4f, - 0x31, 0xab, 0xf7, 0x8f, 0x35, 0x98, 0xe7, 0xa0, 0xa9, 0xbb, 0xf1, 0x85, 0x74, 0x08, 0xc2, 0xe0, - 0x78, 0x46, 0xf4, 0xbd, 0xd9, 0x98, 0xf1, 0xff, 0xf4, 0x67, 0x55, 0xee, 0x49, 0xc4, 0x8b, 0xea, - 0x0f, 0xa1, 0x53, 0x44, 0x78, 0xa5, 0x27, 0x27, 0x5e, 0x55, 0x79, 0x74, 0x41, 0xb5, 0x67, 0xd4, - 0x5f, 0xd4, 0x60, 0xe5, 0x30, 0x0a, 0x3d, 0x9f, 0xdd, 0x98, 0xc7, 0x6e, 0xe2, 0x0e, 0x53, 0xf1, - 0x92, 0xcf, 0x41, 0xb2, 0xec, 0xae, 0x00, 0x13, 0x0a, 0x9c, 0xbb, 0x00, 0xfd, 0x73, 0xda, 0x7f, - 0xe1, 0x88, 0x8a, 0x23, 0x7f, 0xfe, 0x67, 0x90, 0x8f, 0x7c, 0x2f, 0x25, 0xef, 0xc0, 0x5a, 0x3e, - 0xec, 0xb8, 0xa1, 0xe7, 0x88, 0x72, 0x23, 0xbe, 0x6e, 0x28, 0xbc, 0x83, 0xd0, 0x3b, 0x48, 0x5f, - 0xa4, 0x2c, 0x56, 0x54, 0x55, 0x36, 0xc7, 0x70, 0xe1, 0x2b, 0x0a, 0x7e, 0x80, 0x60, 0xeb, 0x7f, - 0x6a, 0x78, 0x03, 0xca, 0x55, 0x89, 0xdd, 0xce, 0x0b, 0x6b, 0x58, 0x6f, 0x35, 0xb6, 0xac, 0x5e, - 0xd8, 0x32, 0x02, 0x0d, 0x3f, 0xa3, 0x43, 0x79, 0xb1, 0xb0, 0xdf, 0xe4, 0x23, 0xe8, 0xa8, 0x15, - 0x3b, 0x31, 0xaa, 0x45, 0x1c, 0x93, 0xad, 0x3c, 0x71, 0x34, 0xb4, 0x66, 0xaf, 0xf4, 0x0b, 0x6a, - 0x94, 0xc7, 0xeb, 0xda, 0x4c, 0x8e, 0xba, 0x8f, 0xda, 0x16, 0xfe, 0x89, 0x7f, 0x71, 0xa9, 0x69, - 0x7f, 0x94, 0x51, 0x4f, 0x84, 0xca, 0xea, 0xdb, 0xfa, 0xf7, 0x1a, 0xac, 0x1c, 0x78, 0x1e, 0xae, - 0x7b, 0x16, 0x37, 0x21, 0x57, 0x59, 0xbf, 0x62, 0x95, 0x73, 0xbf, 0xe4, 0x2a, 0x7f, 0x65, 0x27, - 0x32, 0x41, 0x09, 0x96, 0x05, 0x9d, 0x7c, 0x9d, 0xd5, 0xdb, 0x6b, 0x7d, 0x07, 0x08, 0x4f, 0xaf, - 0x0c, 0x75, 0x14, 0xb1, 0x36, 0x60, 0xcd, 0xc0, 0x12, 0xbe, 0xe6, 0x13, 0xb8, 0x7b, 0x44, 0xb3, - 0xc3, 0x64, 0x1c, 0x67, 0x91, 0x0c, 0x67, 0x3f, 0xa6, 0x71, 0x94, 0xfa, 0xd2, 0x73, 0xd1, 0x99, - 0xbc, 0xcf, 0x3f, 0xd5, 0xe0, 0xde, 0x0c, 0x84, 0xc4, 0x12, 0xbe, 0x2c, 0xd7, 0x97, 0x7e, 0x5b, - 0x6f, 0x6f, 0x99, 0x89, 0xca, 0xbe, 0x82, 0x88, 0x2e, 0x03, 0x45, 0xb2, 0xf7, 0x03, 0x58, 0x36, - 0x07, 0x5f, 0xc9, 0x55, 0x04, 0x70, 0xe7, 0x0a, 0x21, 0x66, 0xb1, 0xb9, 0x3b, 0xb0, 0xdc, 0x37, - 0x48, 0x08, 0x46, 0x05, 0xa8, 0x75, 0x08, 0x6f, 0x5e, 0xc9, 0x4d, 0xa8, 0x6d, 0x62, 0x86, 0x6e, - 0xfd, 0x6d, 0x03, 0xb6, 0x9e, 0xfb, 0xd9, 0xb9, 0x97, 0xb8, 0x97, 0xd2, 0xfa, 0x66, 0x11, 0xb2, - 0x90, 0xbc, 0xd7, 0xcb, 0xf5, 0x86, 0xb7, 0x60, 0x35, 0x0a, 0x29, 0xe6, 0x18, 0x4e, 0xec, 0xa6, - 0xe9, 0x65, 0x94, 0xc8, 0xbb, 0x74, 0x25, 0x0a, 0x29, 0xcb, 0x33, 0x8e, 0x05, 0xb8, 0x70, 0x1b, - 0x37, 0x8a, 0xb7, 0x71, 0x07, 0xe6, 0x62, 0x3f, 0x14, 0x6f, 0x26, 0xec, 0x27, 0xbb, 0x3b, 0xb3, - 0xc4, 0xf5, 0x34, 0xca, 0xe2, 0xee, 0x44, 0xa8, 0xa2, 0xab, 0x57, 0xf1, 0x17, 0x0a, 0x55, 0x7c, - 0x4d, 0x27, 0x8b, 0x66, 0xd5, 0x62, 0x0f, 0x5a, 0xe2, 0xa7, 0x93, 0xb9, 0x03, 0x91, 0x02, 0x81, - 0x00, 0x3d, 0x75, 0x07, 0x5a, 0xb4, 0x06, 0x46, 0xb4, 0xb6, 0x0b, 0x70, 0x46, 0xa9, 0x63, 0x24, - 0x43, 0xcd, 0x33, 0x4a, 0xb9, 0xd3, 0x65, 0xa1, 0xf2, 0xa9, 0x1b, 0xbe, 0x70, 0xb0, 0x06, 0xd1, - 0xe6, 0xe2, 0x30, 0xc0, 0xe7, 0xee, 0x10, 0x63, 0x62, 0x1c, 0x94, 0x32, 0x2d, 0x71, 0x8d, 0x32, - 0xd8, 0x41, 0x5e, 0x4d, 0x41, 0x94, 0xbe, 0x9f, 0x8d, 0xbb, 0xcb, 0xf9, 0xfc, 0x43, 0x3f, 0x1b, - 0xab, 0xf9, 0xa8, 0xb3, 0x64, 0xdc, 0x5d, 0xc9, 0xe7, 0x1f, 0x72, 0x10, 0x13, 0x2f, 0xbd, 0xf4, - 0xcf, 0x28, 0x6f, 0x0c, 0xe9, 0x88, 0x56, 0x29, 0x06, 0x39, 0x8c, 0x3c, 0x0c, 0x23, 0x2f, 0xfd, - 0x44, 0x4b, 0x4e, 0x57, 0x79, 0x0a, 0xcb, 0x80, 0xd2, 0x34, 0xac, 0xb7, 0xa0, 0x23, 0xcd, 0x45, - 0xef, 0x9d, 0x4c, 0x68, 0x3a, 0x0a, 0x32, 0xd9, 0x3b, 0xc9, 0xbf, 0xac, 0xf7, 0xb0, 0x2b, 0xe2, - 0xb3, 0x68, 0x30, 0xc8, 0xd3, 0x27, 0x61, 0x5a, 0x9b, 0x30, 0x1f, 0x20, 0x5c, 0x4e, 0xe1, 0x5f, - 0x56, 0x88, 0xf5, 0x9c, 0xc2, 0x94, 0xfc, 0xd5, 0xc2, 0x0f, 0xcf, 0x22, 0x91, 0x2d, 0xe0, 0x6f, - 0x76, 0x16, 0x3d, 0x7a, 0x3a, 0x1a, 0xc8, 0x1e, 0x28, 0xfc, 0x60, 0x98, 0x97, 0x6e, 0x12, 0x8a, - 0x0b, 0x15, 0x7f, 0x33, 0x4c, 0x9a, 0x24, 0x51, 0x22, 0x6e, 0x4f, 0xfe, 0x61, 0x1d, 0xc1, 0xd6, - 0xc9, 0xab, 0x89, 0xc8, 0x08, 0xf1, 0x6a, 0x8d, 0x38, 0xfe, 0xf8, 0x61, 0x7d, 0x6a, 0x74, 0x80, - 0x60, 0x97, 0xc0, 0x2c, 0xc7, 0x68, 0x1d, 0xae, 0xa1, 0x2f, 0x97, 0xc4, 0xf0, 0x83, 0x65, 0x84, - 0xdd, 0x32, 0x35, 0xd5, 0x83, 0x56, 0xee, 0xa8, 0xe0, 0x9e, 0xf0, 0xff, 0x55, 0x74, 0x54, 0x18, - 0x73, 0x67, 0x6b, 0xa9, 0xf8, 0xb5, 0x76, 0x49, 0x7c, 0x0d, 0x6b, 0xba, 0x68, 0xdf, 0x66, 0xd6, - 0xff, 0xe0, 0xe7, 0x77, 0x60, 0xf9, 0x28, 0xe2, 0x0e, 0xf3, 0x29, 0xf3, 0x13, 0x09, 0x79, 0x02, - 0x0b, 0xa2, 0x41, 0x95, 0x6c, 0x96, 0x3a, 0x56, 0x51, 0xb4, 0xde, 0xd6, 0x84, 0x4e, 0x56, 0x6b, - 0xed, 0x9b, 0x7f, 0xf9, 0xb7, 0x9f, 0xd5, 0x97, 0x48, 0xeb, 0xfe, 0xc5, 0x7b, 0xf7, 0x07, 0x34, - 0x43, 0x83, 0x3c, 0x87, 0x25, 0xa3, 0xa7, 0x90, 0xec, 0x18, 0x7d, 0x81, 0x85, 0x56, 0xc3, 0xde, - 0xee, 0xd4, 0xae, 0x41, 0xab, 0x87, 0x2c, 0xd6, 0x09, 0x11, 0x2c, 0x52, 0x44, 0xe1, 0x84, 0xbf, - 0x82, 0x95, 0x47, 0x58, 0xa9, 0x54, 0x54, 0xc9, 0x5e, 0x4e, 0xad, 0xb2, 0x57, 0xb2, 0x77, 0x73, - 0x32, 0x82, 0xe0, 0xb8, 0x8d, 0x1c, 0x37, 0xc8, 0x1a, 0xe3, 0xc8, 0x2b, 0xa1, 0xaa, 0x47, 0x91, - 0xa4, 0xd0, 0x11, 0xdd, 0x57, 0xaf, 0x95, 0xe7, 0x0e, 0xf2, 0xdc, 0x24, 0xeb, 0x8c, 0xa7, 0xc7, - 0x19, 0xe4, 0x4c, 0x23, 0x2c, 0xb4, 0xe8, 0xdd, 0x82, 0xe4, 0xc6, 0xc4, 0x36, 0x42, 0xce, 0x72, - 0xef, 0x8a, 0x36, 0x43, 0x73, 0x95, 0x03, 0xca, 0x70, 0x55, 0xa7, 0x21, 0xf9, 0x19, 0x3f, 0x7d, - 0x95, 0x7d, 0xad, 0xe4, 0xcd, 0xab, 0x9b, 0x69, 0xb9, 0x0c, 0x77, 0x67, 0xed, 0xba, 0xb5, 0xbe, - 0x83, 0xc2, 0xdc, 0x20, 0x3b, 0x42, 0x18, 0xa3, 0xd3, 0x56, 0xf6, 0xf2, 0x92, 0x3e, 0xb4, 0xf5, - 0x16, 0x41, 0xb2, 0x5d, 0x71, 0xd8, 0x15, 0xf3, 0x9d, 0xea, 0x41, 0xc1, 0xb0, 0x8b, 0x0c, 0x09, - 0xe9, 0x08, 0x86, 0xaa, 0xa3, 0x90, 0x7c, 0x0d, 0x2b, 0x85, 0xf6, 0x3a, 0x62, 0x15, 0xb6, 0xaf, - 0xa2, 0x55, 0xb2, 0xf7, 0xc6, 0x54, 0x1c, 0xc1, 0xf5, 0x06, 0x72, 0xed, 0x5a, 0x6b, 0xda, 0x2e, - 0x4b, 0xce, 0xdf, 0xaf, 0xbd, 0x45, 0x52, 0xdc, 0x67, 0xbd, 0x13, 0x6c, 0x26, 0xde, 0x7b, 0x57, - 0xb4, 0x91, 0x95, 0xf6, 0x5a, 0xf2, 0xc4, 0xe3, 0x9a, 0x62, 0x77, 0x8d, 0xd6, 0xbf, 0x88, 0x37, - 0xe1, 0x2c, 0x7c, 0x77, 0xab, 0xfb, 0x1f, 0x45, 0x0b, 0x66, 0xe9, 0xe4, 0x4a, 0xae, 0x51, 0x16, - 0x93, 0xd4, 0x68, 0x0f, 0x15, 0x4c, 0x4d, 0xab, 0xae, 0x68, 0xd0, 0xac, 0x5c, 0xa9, 0xde, 0x71, - 0x39, 0x71, 0xa5, 0x51, 0x16, 0xa7, 0xe4, 0x25, 0x2c, 0x73, 0x77, 0xf1, 0xfa, 0x77, 0x76, 0x17, - 0xf9, 0x6e, 0x59, 0x24, 0xf7, 0x19, 0xfa, 0xc6, 0x3e, 0x87, 0xa6, 0xea, 0x60, 0x22, 0x5d, 0x6d, - 0x11, 0x46, 0xaf, 0x5c, 0x6f, 0x42, 0x27, 0x94, 0xb4, 0x56, 0x6b, 0x49, 0xac, 0x8a, 0xf7, 0x35, - 0x31, 0xc2, 0x3f, 0x05, 0xc8, 0x5b, 0xa3, 0xc8, 0xf5, 0x12, 0x65, 0xa5, 0xb9, 0x5e, 0xd5, 0x90, - 0x6c, 0x02, 0x47, 0xf2, 0x1d, 0xb2, 0x6c, 0x90, 0x97, 0xe7, 0x4d, 0x55, 0xeb, 0x8c, 0xf3, 0x56, - 0x6c, 0xa6, 0xea, 0x4d, 0xee, 0xa2, 0x91, 0x9b, 0x62, 0xc9, 0xc3, 0xa6, 0x32, 0x71, 0xb6, 0x82, - 0x01, 0xde, 0x16, 0x5a, 0xfb, 0xce, 0x4e, 0x15, 0x97, 0xca, 0xdb, 0xa2, 0xdc, 0x8b, 0x63, 0x5d, - 0x47, 0x56, 0x6b, 0x64, 0xb5, 0xc8, 0x2a, 0x25, 0x2f, 0xf0, 0x8f, 0x60, 0xb4, 0xee, 0x13, 0xa2, - 0xd3, 0x2a, 0xb7, 0xe2, 0xf4, 0x6e, 0x4c, 0x1a, 0x9e, 0x70, 0x33, 0x89, 0x60, 0x1d, 0x0f, 0x15, - 0xdf, 0x70, 0xde, 0x73, 0x62, 0x6c, 0xb8, 0xd1, 0x9a, 0xd2, 0xbb, 0x5e, 0x31, 0x22, 0xa8, 0x6f, - 0x20, 0xf5, 0x15, 0xb2, 0xa4, 0x5c, 0x22, 0xd2, 0xe2, 0x7b, 0xa2, 0x1e, 0x03, 0x8d, 0x3d, 0x29, - 0x76, 0x8c, 0x18, 0x3e, 0xb0, 0xd4, 0x37, 0x52, 0xf2, 0x81, 0xaa, 0x33, 0x84, 0xfc, 0xb1, 0xd9, - 0x80, 0x22, 0x1f, 0xc4, 0xad, 0xa9, 0x2f, 0xd8, 0xa5, 0xd3, 0x32, 0xf1, 0x95, 0xdb, 0xda, 0x43, - 0xce, 0xd7, 0xc9, 0x56, 0x91, 0xb3, 0x78, 0x31, 0x27, 0xdf, 0xd4, 0x60, 0xad, 0xe2, 0x3d, 0x36, - 0x97, 0x60, 0xf2, 0xeb, 0x71, 0x2e, 0xc1, 0xb4, 0x07, 0x5d, 0x0b, 0x25, 0xd8, 0xb1, 0x50, 0x02, - 0xd7, 0xf3, 0x94, 0x04, 0x22, 0xf7, 0x60, 0x96, 0xf9, 0xe7, 0x35, 0xd8, 0xac, 0x7e, 0x7b, 0x25, - 0xb7, 0x55, 0x5b, 0xfd, 0xb4, 0x57, 0xe1, 0xde, 0x9d, 0xab, 0xd0, 0x84, 0x34, 0xb7, 0x51, 0x9a, - 0x3d, 0xab, 0xc7, 0xa4, 0x49, 0x10, 0xb7, 0x4a, 0xa0, 0x4b, 0x2c, 0x58, 0x99, 0xaf, 0x9b, 0x44, - 0x8b, 0x2d, 0xaa, 0x1f, 0x81, 0x7b, 0xb7, 0xa6, 0x60, 0x98, 0xee, 0x8b, 0x6c, 0x88, 0x0d, 0xc1, - 0x27, 0x41, 0xf5, 0x4c, 0x2a, 0xce, 0x68, 0xfe, 0x7a, 0x68, 0x9c, 0xd1, 0xd2, 0x83, 0xa8, 0x71, - 0x46, 0xcb, 0x6f, 0x94, 0xa5, 0x33, 0x8a, 0xcc, 0xf0, 0xbd, 0x92, 0x7c, 0x81, 0xc7, 0x46, 0x54, - 0x4b, 0xbb, 0xc5, 0xa3, 0x9e, 0x56, 0x1d, 0x1b, 0xb3, 0x1e, 0x5a, 0x72, 0x95, 0xbc, 0x08, 0xcb, - 0xb4, 0x67, 0xc3, 0xa2, 0x44, 0x27, 0x5b, 0x45, 0x02, 0x92, 0x72, 0xe5, 0x83, 0x97, 0xb5, 0x85, - 0x44, 0x57, 0xad, 0xb6, 0x4e, 0x94, 0xd1, 0x3c, 0x85, 0x96, 0xf6, 0xb8, 0x43, 0x94, 0x93, 0x2d, - 0xbf, 0x65, 0xf5, 0xb6, 0x2b, 0xc7, 0x4c, 0x57, 0x62, 0xad, 0x30, 0x06, 0x29, 0x22, 0x28, 0x1e, - 0x7f, 0x00, 0x4b, 0xc6, 0xfb, 0x4a, 0xae, 0xfc, 0xaa, 0x17, 0xa0, 0x5c, 0xf9, 0x95, 0x8f, 0x32, - 0x32, 0xd0, 0xb4, 0x50, 0xf9, 0xa9, 0x40, 0x51, 0xbc, 0xbe, 0x84, 0xa6, 0x7a, 0xd6, 0xc8, 0xf5, - 0x5f, 0x7c, 0xe9, 0xb8, 0x8a, 0x87, 0xb1, 0x07, 0x97, 0x6c, 0xf2, 0x69, 0x34, 0x3c, 0x15, 0xfa, - 0xd2, 0x8a, 0xf6, 0xb9, 0xbe, 0xca, 0x2f, 0x17, 0xb9, 0xbe, 0xaa, 0xaa, 0xfc, 0x86, 0xbe, 0xfa, - 0x88, 0xa0, 0xd6, 0x90, 0xc0, 0x4a, 0xa1, 0x58, 0x9e, 0x87, 0x15, 0xd5, 0x4f, 0x03, 0x79, 0x58, - 0x31, 0xa1, 0xca, 0x6e, 0x06, 0x6e, 0x9c, 0x9f, 0x1b, 0x04, 0xb9, 0x6d, 0x71, 0x77, 0xcf, 0x4b, - 0xc9, 0x86, 0xdd, 0x1a, 0x35, 0x73, 0xc3, 0x6e, 0xcd, 0xba, 0x73, 0xc9, 0xdd, 0x53, 0x4e, 0xeb, - 0x19, 0x2c, 0xca, 0x1a, 0x66, 0x6e, 0xb4, 0x85, 0xea, 0x6d, 0xaf, 0x5b, 0x1e, 0x10, 0x54, 0x0d, - 0xc3, 0x75, 0x3d, 0x0f, 0xa9, 0x8a, 0x8d, 0xd0, 0x2a, 0x9a, 0xf9, 0x46, 0x94, 0x8b, 0xa1, 0xf9, - 0x46, 0x54, 0x95, 0x40, 0x8d, 0x8d, 0xe0, 0x9e, 0x4b, 0xf1, 0xf8, 0xbb, 0x1a, 0xdc, 0xba, 0xb2, - 0x20, 0x49, 0xde, 0x7d, 0x85, 0xda, 0x25, 0x17, 0xe8, 0xbd, 0x57, 0xae, 0x76, 0x5a, 0x77, 0x51, - 0x4c, 0xcb, 0xda, 0x95, 0x97, 0x29, 0x4e, 0xf3, 0x38, 0xba, 0x2a, 0x7d, 0x32, 0xa1, 0xff, 0xa6, - 0xc6, 0xff, 0xc4, 0x71, 0x0a, 0x5d, 0xb2, 0x3f, 0xa3, 0x00, 0x52, 0xe0, 0xfb, 0x33, 0xe3, 0x0b, - 0x71, 0xef, 0xa0, 0xb8, 0x37, 0xad, 0xed, 0x29, 0xe2, 0x32, 0x61, 0xff, 0x08, 0xb6, 0x55, 0xe1, - 0xd2, 0xa0, 0xfb, 0xc9, 0x28, 0xf4, 0xd2, 0x3c, 0x2f, 0x9d, 0x50, 0xdd, 0xcc, 0x0d, 0xa7, 0x58, - 0xcf, 0x32, 0xef, 0xc7, 0x4b, 0x31, 0xca, 0xc5, 0x38, 0x63, 0xb4, 0x19, 0xf7, 0x18, 0x56, 0xe5, - 0xbc, 0x4f, 0x7c, 0x37, 0xfb, 0x95, 0x79, 0xde, 0x44, 0x9e, 0x3d, 0x6b, 0x43, 0xe7, 0x79, 0xe6, - 0xbb, 0x99, 0xe2, 0x98, 0xe2, 0x3b, 0x94, 0x51, 0xaa, 0xd2, 0x93, 0xef, 0xca, 0x22, 0x96, 0x9e, - 0x7c, 0x57, 0x57, 0xd5, 0xcc, 0xe4, 0x7b, 0x40, 0x33, 0x5e, 0xe5, 0xf2, 0x04, 0x83, 0x0b, 0xe8, - 0x9c, 0x4c, 0x64, 0x7a, 0xf2, 0x4b, 0x33, 0x15, 0x31, 0x90, 0x85, 0x4c, 0xd3, 0x02, 0x53, 0xb6, - 0xd8, 0x0b, 0xfe, 0xe8, 0xa6, 0x17, 0xb1, 0xc8, 0xde, 0xe4, 0xf2, 0x56, 0x99, 0x6f, 0x65, 0xfd, - 0xcb, 0xe4, 0xab, 0x65, 0x48, 0xf8, 0xa7, 0x5d, 0x8c, 0xef, 0x18, 0x88, 0x99, 0x25, 0xe1, 0x9f, - 0x04, 0x28, 0x2f, 0x50, 0x51, 0xba, 0x9a, 0x2d, 0x45, 0xba, 0x85, 0x8c, 0xb7, 0xad, 0xcd, 0x72, - 0x8a, 0xc4, 0x78, 0x33, 0xd6, 0x7f, 0x08, 0x6b, 0x85, 0xdc, 0xfb, 0x35, 0xf1, 0x36, 0xcc, 0xb9, - 0x90, 0x78, 0x0b, 0xe6, 0xa7, 0xf3, 0xf8, 0xb7, 0xf2, 0xef, 0xff, 0x5f, 0x00, 0x00, 0x00, 0xff, - 0xff, 0xcb, 0x7f, 0xc6, 0x31, 0x5e, 0x3f, 0x00, 0x00, + // 4676 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3c, 0x4d, 0x6f, 0x1c, 0x57, + 0x72, 0xe8, 0x21, 0x45, 0x72, 0x6a, 0x86, 0xe4, 0xf0, 0xf1, 0x6b, 0x34, 0x24, 0x45, 0xa9, 0xb5, + 0x92, 0x25, 0xad, 0x4d, 0xd9, 0xb2, 0x92, 0x75, 0xd6, 0xce, 0x6e, 0x68, 0x5a, 0xe6, 0x2a, 0xf6, + 0x5a, 0x4c, 0x53, 0x2b, 0x01, 0xde, 0xc0, 0x93, 0xe6, 0xf4, 0xe3, 0xb0, 0xa3, 0x9e, 0xee, 0x76, + 0x77, 0x0f, 0x29, 0x3a, 0x09, 0xb2, 0x30, 0x90, 0x20, 0x08, 0x82, 0xe4, 0xb0, 0x08, 0x90, 0x43, + 0x4e, 0x39, 0x06, 0xc8, 0x25, 0xc8, 0x29, 0x87, 0x45, 0xae, 0x41, 0x8e, 0xb9, 0xe4, 0x07, 0x04, + 0xb9, 0x25, 0x01, 0x02, 0xe4, 0x92, 0x53, 0xf0, 0xea, 0x7d, 0xf4, 0x7b, 0xdd, 0x3d, 0xc3, 0xe1, + 0xae, 0xac, 0xbd, 0xd8, 0xd3, 0xf5, 0xea, 0x55, 0xd5, 0xab, 0x57, 0xaf, 0x5e, 0x55, 0xbd, 0xa2, + 0xa0, 0x9e, 0xc4, 0xbd, 0x9d, 0x38, 0x89, 0xb2, 0x88, 0xcc, 0xf4, 0x7b, 0x59, 0x12, 0xf7, 0x3a, + 0x9b, 0xfd, 0x28, 0xea, 0x07, 0xf4, 0xbe, 0x1b, 0xfb, 0xf7, 0xdd, 0x30, 0x8c, 0x32, 0x37, 0xf3, + 0xa3, 0x30, 0xe5, 0x58, 0x76, 0x0b, 0x16, 0xf6, 0x69, 0xf6, 0x38, 0x3c, 0x8e, 0x1c, 0xfa, 0xe5, + 0x90, 0xa6, 0x99, 0xfd, 0x0f, 0xd3, 0xb0, 0xa8, 0x40, 0x69, 0x1c, 0x85, 0x29, 0x25, 0x6b, 0x30, + 0x33, 0x8c, 0x33, 0x7f, 0x40, 0xdb, 0xd6, 0x75, 0xeb, 0x4e, 0xdd, 0x11, 0x5f, 0xe4, 0x3e, 0x2c, + 0xbb, 0xa7, 0xae, 0x1f, 0xb8, 0x47, 0x01, 0xed, 0xd2, 0x97, 0xbd, 0x13, 0x37, 0xec, 0xd3, 0xb4, + 0x5d, 0xbb, 0x6e, 0xdd, 0x99, 0x72, 0x88, 0x1a, 0x7a, 0x24, 0x47, 0xc8, 0xb7, 0x61, 0x89, 0x86, + 0x0c, 0xe4, 0x69, 0xe8, 0x53, 0x88, 0xde, 0x12, 0x03, 0x39, 0xf2, 0x43, 0x58, 0xf3, 0xe8, 0xb1, + 0x3b, 0x0c, 0xb2, 0xee, 0x71, 0x94, 0xd0, 0x97, 0xdd, 0x38, 0x89, 0x4e, 0x7d, 0x8f, 0x26, 0xed, + 0x69, 0x94, 0x62, 0x45, 0x8c, 0x7e, 0xcc, 0x06, 0x0f, 0xc4, 0x18, 0x79, 0x00, 0xab, 0x6a, 0x96, + 0xef, 0x66, 0xdd, 0xde, 0x30, 0x49, 0x68, 0xd8, 0x3b, 0x6f, 0x5f, 0xc1, 0x49, 0xcb, 0x72, 0x92, + 0xef, 0x66, 0x7b, 0x62, 0x88, 0x3c, 0x87, 0x56, 0x3a, 0x3c, 0x4a, 0xcf, 0xd3, 0x8c, 0x0e, 0xba, + 0x69, 0xe6, 0x66, 0xc3, 0xb4, 0x3d, 0x73, 0x7d, 0xea, 0x4e, 0xe3, 0xc1, 0x9b, 0x3b, 0x5c, 0x8d, + 0x3b, 0x05, 0x95, 0xec, 0x1c, 0x4a, 0xfc, 0x43, 0x44, 0x7f, 0x14, 0x66, 0xc9, 0xb9, 0xb3, 0x98, + 0x9a, 0x50, 0xf2, 0x19, 0xcc, 0x27, 0x71, 0xaf, 0x4b, 0x43, 0x2f, 0x8e, 0xfc, 0x30, 0x4b, 0xdb, + 0xb3, 0x48, 0xf5, 0xee, 0x28, 0xaa, 0x4e, 0xdc, 0x7b, 0x24, 0x71, 0x39, 0xc9, 0x66, 0xa2, 0x81, + 0x3a, 0x1f, 0xc2, 0x4a, 0x15, 0x63, 0xd2, 0x82, 0xa9, 0x17, 0xf4, 0x5c, 0xec, 0x0e, 0xfb, 0x49, + 0x56, 0xe0, 0xca, 0xa9, 0x1b, 0x0c, 0x29, 0x6e, 0xc6, 0x9c, 0xc3, 0x3f, 0xbe, 0x5b, 0x7b, 0xcf, + 0xea, 0x3c, 0x85, 0xa5, 0x12, 0x9b, 0x0a, 0x02, 0x77, 0x75, 0x02, 0x8d, 0x07, 0xcb, 0x52, 0x64, + 0xe7, 0x60, 0x4f, 0xce, 0xd5, 0xa8, 0xda, 0x37, 0x60, 0x7b, 0x9f, 0x66, 0x7b, 0xd1, 0x60, 0x30, + 0x0c, 0xfd, 0x1e, 0xda, 0x98, 0x43, 0x03, 0xf7, 0x9c, 0x26, 0xa9, 0xb4, 0xac, 0xcf, 0x60, 0xa5, + 0x6a, 0x9c, 0xb4, 0x61, 0x56, 0xec, 0x3d, 0xf2, 0x9f, 0x73, 0xe4, 0x27, 0xd9, 0x84, 0x7a, 0x2f, + 0x0a, 0x43, 0xda, 0xcb, 0xa8, 0x27, 0x16, 0x92, 0x03, 0xec, 0x3f, 0xae, 0xc1, 0xf5, 0xd1, 0x3c, + 0x85, 0xe9, 0x7e, 0x05, 0x6b, 0x3d, 0x1d, 0xa1, 0x9b, 0x08, 0x8c, 0xb6, 0x85, 0x5b, 0xb1, 0xa7, + 0x6d, 0xc5, 0x58, 0x4a, 0x3b, 0x95, 0xa3, 0x7c, 0x93, 0x56, 0x7b, 0x55, 0x63, 0x9d, 0x63, 0xe8, + 0x8c, 0x9e, 0x54, 0xa1, 0xf2, 0x07, 0xa6, 0xca, 0x37, 0xa5, 0x68, 0x55, 0x44, 0x74, 0xdd, 0x7f, + 0x07, 0xd6, 0xf7, 0x69, 0x48, 0x13, 0xbf, 0xa7, 0x8c, 0x43, 0xe8, 0x9c, 0x69, 0x50, 0xd9, 0xa4, + 0x60, 0x95, 0x03, 0xec, 0x0e, 0xb4, 0xcb, 0x13, 0xf9, 0x72, 0xed, 0x35, 0x58, 0xd9, 0xa7, 0x99, + 0x82, 0xab, 0x5d, 0xfc, 0x99, 0x05, 0xab, 0x38, 0x90, 0x1e, 0xa5, 0xe7, 0x7c, 0x40, 0xa8, 0xfa, + 0x77, 0x60, 0x49, 0x91, 0x4e, 0xe5, 0x31, 0xe2, 0x5a, 0x7e, 0x57, 0xd3, 0x72, 0x79, 0x66, 0x7e, + 0x98, 0x52, 0xfd, 0x34, 0xe5, 0x67, 0x52, 0x80, 0x3b, 0x7b, 0xb0, 0x5a, 0x89, 0x7a, 0x19, 0xfb, + 0xb7, 0xdb, 0xb0, 0xb6, 0x4f, 0x33, 0xcd, 0x8c, 0x35, 0x03, 0x6d, 0x68, 0x60, 0x66, 0x97, 0x69, + 0xe6, 0x26, 0x59, 0x6e, 0x97, 0xe2, 0x93, 0xdc, 0x82, 0x85, 0xc0, 0x4f, 0x33, 0x1a, 0x76, 0x5d, + 0xcf, 0x4b, 0x68, 0xca, 0x5d, 0x5e, 0xdd, 0x99, 0xe7, 0xd0, 0x5d, 0x0e, 0xb4, 0xff, 0xd1, 0x62, + 0x1b, 0x53, 0x60, 0x25, 0x94, 0xf5, 0x29, 0xd4, 0x73, 0xaf, 0xc0, 0x95, 0xb4, 0xa3, 0x29, 0xa9, + 0x6a, 0xce, 0x4e, 0xc1, 0x35, 0xe4, 0x04, 0x3a, 0xbf, 0x05, 0x0b, 0xaf, 0xfa, 0x40, 0xbf, 0x07, + 0x1d, 0x61, 0x1b, 0xd2, 0x23, 0x7f, 0xe6, 0x0e, 0xa8, 0xb4, 0xab, 0x0e, 0xcc, 0x49, 0x07, 0x2e, + 0x78, 0xa8, 0x6f, 0x7b, 0x0b, 0x36, 0x2a, 0x67, 0x0a, 0xc3, 0xba, 0x0f, 0xcb, 0xfb, 0x34, 0x53, + 0x6e, 0x5e, 0x52, 0x1c, 0xe9, 0x05, 0xec, 0x87, 0x68, 0x89, 0xda, 0x04, 0xa1, 0xc2, 0x4d, 0xa8, + 0xe7, 0x97, 0x88, 0xb0, 0x6d, 0x05, 0xb0, 0x1f, 0xa0, 0x99, 0xca, 0x59, 0x4f, 0x9e, 0x1e, 0x38, + 0x94, 0x4f, 0xbb, 0x0a, 0x73, 0x51, 0x16, 0x77, 0x7b, 0x91, 0x27, 0x45, 0x9f, 0x8d, 0xb2, 0x78, + 0x2f, 0xf2, 0xa8, 0x30, 0x0d, 0x6d, 0x8e, 0x32, 0x8d, 0xbf, 0xe1, 0x5b, 0x69, 0x0e, 0x09, 0x39, + 0x7e, 0x13, 0xea, 0x92, 0xa0, 0xdc, 0xca, 0xb7, 0xb4, 0xad, 0xac, 0x9a, 0xb3, 0xf3, 0x84, 0x73, + 0x14, 0x3b, 0x39, 0x27, 0x04, 0x48, 0x3b, 0xef, 0xc3, 0xbc, 0x31, 0x74, 0x91, 0x65, 0xd7, 0xf5, + 0x2d, 0x7b, 0x08, 0x6b, 0x1f, 0xf9, 0xa9, 0x7e, 0xe3, 0x4e, 0xb2, 0x5d, 0x5f, 0xc0, 0xc2, 0x81, + 0xeb, 0x27, 0xe9, 0xe1, 0x30, 0x8e, 0x23, 0x34, 0xef, 0x37, 0x60, 0x31, 0xbf, 0xd6, 0x63, 0x36, + 0x26, 0x26, 0x2d, 0x28, 0x30, 0xce, 0x20, 0x37, 0x61, 0x5e, 0x5e, 0xe7, 0x1c, 0x8d, 0x8b, 0xd4, + 0x14, 0x40, 0x44, 0xb2, 0xbf, 0x9e, 0x36, 0x54, 0x67, 0x04, 0x16, 0x04, 0xa6, 0x43, 0x57, 0x85, + 0x15, 0xf8, 0x5b, 0x37, 0x84, 0x9a, 0x79, 0x1d, 0xb4, 0x61, 0xf6, 0x94, 0x26, 0x47, 0x51, 0x4a, + 0x31, 0x66, 0x98, 0x73, 0xe4, 0x27, 0x13, 0x64, 0x98, 0xfa, 0x61, 0xbf, 0x9b, 0xba, 0xa1, 0x77, + 0x14, 0xbd, 0xc4, 0x08, 0x61, 0xce, 0x69, 0x22, 0xf0, 0x90, 0xc3, 0xc8, 0x0d, 0x68, 0x9e, 0x64, + 0x59, 0xdc, 0x65, 0xa1, 0x4b, 0x34, 0xcc, 0x44, 0x40, 0xd0, 0x60, 0xb0, 0xa7, 0x1c, 0xc4, 0x0e, + 0x36, 0xa2, 0x0c, 0x53, 0x9a, 0xb8, 0x7d, 0x1a, 0x66, 0xed, 0x19, 0x7e, 0xb0, 0x19, 0xf4, 0x47, + 0x12, 0x48, 0xb6, 0x00, 0x10, 0x2d, 0x4e, 0xa2, 0x97, 0xe7, 0xed, 0x59, 0x6e, 0x7a, 0x0c, 0x72, + 0xc0, 0x00, 0x4c, 0x7f, 0x47, 0x6e, 0x4a, 0x65, 0xe8, 0xe1, 0xd3, 0xb4, 0x3d, 0xc7, 0xf5, 0xc7, + 0xc0, 0x7b, 0x0a, 0x4a, 0xba, 0x2c, 0xee, 0x10, 0x5a, 0xef, 0xba, 0x69, 0x4a, 0xb3, 0xb4, 0x5d, + 0x47, 0x03, 0x7a, 0x58, 0x61, 0x40, 0x85, 0xf8, 0x43, 0xcc, 0xdb, 0xc5, 0x69, 0x2a, 0xfe, 0x30, + 0xa0, 0x2c, 0xde, 0x72, 0x87, 0xd9, 0x09, 0x0d, 0x33, 0x76, 0x7b, 0x30, 0x26, 0xb1, 0xdf, 0x06, + 0xd4, 0x4d, 0xcb, 0x18, 0xd8, 0x8d, 0xfd, 0xce, 0xe7, 0x2c, 0xb8, 0x28, 0x53, 0xad, 0x30, 0xc1, + 0x37, 0x4d, 0x57, 0xb2, 0x26, 0x85, 0x35, 0xed, 0x48, 0x37, 0xcd, 0x33, 0x68, 0xed, 0xd3, 0xec, + 0xa9, 0xdf, 0x7b, 0x41, 0x93, 0x09, 0x8c, 0x92, 0xdc, 0x81, 0x69, 0x66, 0x51, 0x82, 0xc1, 0x8a, + 0xba, 0x09, 0x45, 0xc4, 0xc6, 0x18, 0x39, 0x88, 0xc1, 0xf6, 0x02, 0x35, 0xd7, 0xcd, 0xce, 0x63, + 0x6e, 0x17, 0x75, 0xa7, 0x8e, 0x90, 0xa7, 0xe7, 0x31, 0xb5, 0x9f, 0x41, 0x53, 0x9f, 0xc4, 0x9c, + 0x86, 0x47, 0x03, 0x7f, 0xe0, 0x67, 0x34, 0x91, 0x4e, 0x43, 0x01, 0x98, 0x3d, 0xb2, 0x2d, 0x12, + 0x76, 0x8c, 0xbf, 0xd9, 0x79, 0xfb, 0x72, 0x18, 0x65, 0x92, 0x36, 0xff, 0xb0, 0xff, 0xb2, 0x06, + 0x0b, 0x72, 0x39, 0xc2, 0x98, 0xa5, 0xcc, 0xd6, 0x85, 0x32, 0xdf, 0x80, 0x66, 0xe0, 0xa6, 0x59, + 0x77, 0x18, 0x7b, 0xae, 0x0c, 0x6d, 0xa6, 0x9c, 0x06, 0x83, 0xfd, 0x88, 0x83, 0x98, 0x45, 0xcb, + 0xc8, 0x15, 0xcf, 0x96, 0xe0, 0xde, 0xec, 0xe9, 0x8b, 0x21, 0x30, 0xcd, 0xe6, 0xa0, 0xb5, 0x5b, + 0x0e, 0xfe, 0x66, 0xb0, 0x13, 0xbf, 0x7f, 0x82, 0xd6, 0x6d, 0x39, 0xf8, 0x9b, 0xed, 0x60, 0x10, + 0x9d, 0xa1, 0x2d, 0x5b, 0x0e, 0xfb, 0xc9, 0x20, 0x47, 0xbe, 0x87, 0xa6, 0x6b, 0x39, 0xec, 0x27, + 0x83, 0xb8, 0xe9, 0x0b, 0x34, 0x54, 0xcb, 0x61, 0x3f, 0x59, 0xd4, 0x7f, 0x1a, 0x05, 0xc3, 0x01, + 0x6d, 0xd7, 0x11, 0x28, 0xbe, 0xc8, 0x06, 0xd4, 0xe3, 0xc4, 0xef, 0xd1, 0xae, 0x9b, 0x9d, 0xa0, + 0x31, 0x59, 0xce, 0x1c, 0x02, 0x76, 0xb3, 0x13, 0x7b, 0x19, 0x96, 0xd4, 0x46, 0x2b, 0xef, 0xf9, + 0x1c, 0x66, 0x05, 0x64, 0xec, 0xa6, 0xbf, 0x0d, 0xb3, 0x19, 0x47, 0x6b, 0xd7, 0xf0, 0x14, 0x28, + 0xc3, 0x32, 0x35, 0xed, 0x48, 0x34, 0xfb, 0xfb, 0x40, 0x74, 0x6e, 0x62, 0x23, 0xee, 0xe6, 0x74, + 0xb8, 0x3b, 0x5e, 0x34, 0xe9, 0xa4, 0x39, 0x81, 0xaf, 0xf0, 0x32, 0x7a, 0x92, 0x78, 0xcc, 0x91, + 0x44, 0x2f, 0x5e, 0xab, 0x69, 0xfe, 0x10, 0xe6, 0x15, 0xe3, 0xc7, 0x19, 0x1d, 0x30, 0x85, 0xbb, + 0x83, 0x68, 0x18, 0x66, 0xc8, 0xd3, 0x72, 0xc4, 0x17, 0xb3, 0x40, 0xd4, 0x2f, 0xb2, 0xb4, 0x1c, + 0xfe, 0x41, 0x16, 0xa0, 0xe6, 0x7b, 0x22, 0x79, 0xaa, 0xf9, 0x9e, 0xfd, 0x7f, 0x16, 0x2c, 0x69, + 0x0b, 0xb9, 0xb4, 0x51, 0x96, 0x2c, 0xae, 0x56, 0x61, 0x71, 0x77, 0x61, 0xfa, 0xc8, 0xf7, 0x58, + 0xce, 0xc6, 0xf4, 0xba, 0x2a, 0xc9, 0x19, 0xeb, 0x70, 0x10, 0x85, 0xa1, 0xba, 0xe9, 0x8b, 0xb4, + 0x3d, 0x3d, 0x16, 0x95, 0xa1, 0x94, 0xce, 0xc3, 0x95, 0xf2, 0x79, 0x30, 0x75, 0x39, 0x53, 0xd4, + 0x25, 0x8f, 0x56, 0x15, 0x6d, 0x65, 0x79, 0x3d, 0x80, 0x1c, 0x38, 0x76, 0x5b, 0x7f, 0x0d, 0x20, + 0x52, 0x98, 0xc2, 0xfe, 0xae, 0x96, 0x84, 0x56, 0x26, 0xa8, 0x21, 0xdb, 0x9f, 0x60, 0xa8, 0xa1, + 0x33, 0x17, 0xca, 0x7f, 0x60, 0xd0, 0xe4, 0xb6, 0x48, 0x4a, 0x34, 0x53, 0x83, 0xd8, 0xbb, 0x48, + 0x6c, 0xb7, 0xd7, 0x63, 0x5b, 0xaf, 0x25, 0xe6, 0x63, 0xef, 0xf0, 0x67, 0x30, 0x2b, 0x66, 0x08, + 0xb3, 0xe0, 0x08, 0x35, 0xdf, 0x23, 0xef, 0x03, 0x68, 0xf7, 0x10, 0x5f, 0xd7, 0x86, 0x94, 0x41, + 0x4c, 0x92, 0xd6, 0x80, 0xec, 0x34, 0x74, 0xfb, 0x18, 0x96, 0x2b, 0x50, 0x98, 0x28, 0x2a, 0xad, + 0x16, 0xa2, 0xc8, 0x6f, 0xb2, 0x0d, 0x8d, 0x2c, 0xca, 0xdc, 0xa0, 0x9b, 0xdf, 0x10, 0x96, 0x03, + 0x08, 0x7a, 0xc6, 0x20, 0xe8, 0xa0, 0xa2, 0x80, 0x5b, 0x2e, 0x73, 0x50, 0x51, 0xe0, 0xd9, 0x2e, + 0x06, 0x5e, 0xc6, 0xa2, 0x85, 0x0a, 0xc7, 0x6d, 0xd9, 0xb7, 0x61, 0xce, 0xe5, 0x53, 0xe4, 0xc2, + 0x16, 0x0b, 0x0b, 0x73, 0x14, 0x82, 0x4d, 0xf0, 0x06, 0xda, 0x8b, 0xc2, 0x63, 0xbf, 0x2f, 0xad, + 0xe3, 0x0d, 0x74, 0x56, 0x12, 0x96, 0xc7, 0x24, 0x9e, 0x9b, 0xb9, 0xc8, 0xad, 0xe9, 0xe0, 0x6f, + 0xfb, 0x8f, 0x2c, 0x68, 0x1d, 0x44, 0x49, 0x76, 0x1c, 0x05, 0x7e, 0x24, 0xc2, 0x7b, 0x16, 0x8e, + 0xc8, 0xf0, 0x5f, 0xc4, 0x91, 0xe2, 0x93, 0x79, 0xc8, 0x5e, 0xe4, 0x87, 0xdc, 0x56, 0x6b, 0x42, + 0x41, 0x91, 0x1f, 0x32, 0x53, 0x25, 0xd7, 0xa1, 0xe1, 0xd1, 0xb4, 0x97, 0xf8, 0x31, 0x4b, 0xe7, + 0x84, 0x5b, 0xd0, 0x41, 0x8c, 0xf0, 0x91, 0x1b, 0xb8, 0x61, 0x8f, 0x0a, 0xcf, 0x2e, 0x3f, 0xed, + 0x55, 0x74, 0x57, 0x4a, 0x12, 0x2d, 0xb3, 0x36, 0xc1, 0x62, 0x29, 0xbf, 0x0a, 0xf5, 0x58, 0x02, + 0x85, 0xf9, 0xb5, 0xd5, 0x5d, 0x5d, 0x58, 0x8e, 0x93, 0xa3, 0xda, 0x9b, 0x2c, 0xf6, 0xcf, 0xe9, + 0x1d, 0x0e, 0x07, 0x03, 0x37, 0x39, 0x97, 0xdc, 0x42, 0x98, 0xde, 0x8b, 0xfc, 0x90, 0x29, 0x8a, + 0x2d, 0x4a, 0x06, 0x6f, 0xec, 0xb7, 0x2e, 0x7a, 0xcd, 0x10, 0x5d, 0xd7, 0xd6, 0x94, 0xa9, 0xad, + 0x6b, 0x00, 0x31, 0x4d, 0x7a, 0x34, 0xcc, 0xdc, 0xbe, 0x5c, 0xb1, 0x06, 0xb1, 0x4f, 0x80, 0x3c, + 0x39, 0x3e, 0x0e, 0xfc, 0x90, 0x32, 0xb6, 0x42, 0x98, 0x31, 0xda, 0x1f, 0x2d, 0x83, 0xc9, 0x69, + 0xaa, 0xc4, 0xe9, 0x87, 0xb0, 0xf4, 0x24, 0xac, 0x60, 0x24, 0xc9, 0x59, 0xe3, 0xc8, 0xd5, 0x4a, + 0xe4, 0x7e, 0x00, 0x4d, 0x4d, 0xf0, 0x94, 0xbc, 0x07, 0x75, 0x21, 0xa3, 0x4a, 0x14, 0x3a, 0xca, + 0x1b, 0x94, 0x56, 0xe8, 0xe4, 0xc8, 0xf6, 0x5f, 0x59, 0xd0, 0xc8, 0x25, 0x4b, 0xc9, 0x43, 0xb8, + 0xc2, 0xd4, 0x2d, 0xa9, 0x5c, 0x53, 0x54, 0x72, 0x9c, 0x1d, 0xfc, 0x2f, 0x8f, 0x0b, 0x39, 0x72, + 0xe7, 0x10, 0x20, 0x07, 0x56, 0x84, 0x75, 0xf7, 0xcd, 0xb0, 0xee, 0x6a, 0x99, 0xaa, 0x14, 0x4d, + 0x8b, 0xec, 0xfe, 0x65, 0x9a, 0xa5, 0x7b, 0x15, 0xc6, 0x22, 0x6c, 0xf0, 0x2d, 0x68, 0xf0, 0xb3, + 0xc0, 0x3c, 0x80, 0x14, 0xb8, 0x99, 0x97, 0x36, 0xfc, 0xd0, 0x01, 0x3c, 0x1b, 0x38, 0x4e, 0xde, + 0x81, 0x79, 0x14, 0xb6, 0x1b, 0x71, 0x85, 0x88, 0x83, 0x6d, 0x4e, 0x68, 0x22, 0x8a, 0x50, 0x19, + 0x89, 0x61, 0xd5, 0x98, 0xd2, 0x4d, 0xb9, 0x08, 0xe2, 0x92, 0xfa, 0x40, 0x0b, 0xa5, 0x47, 0x49, + 0xc9, 0x95, 0x25, 0x08, 0x8a, 0x31, 0xae, 0xba, 0xe5, 0x5e, 0x79, 0x84, 0xdc, 0x87, 0xa6, 0xe0, + 0x88, 0x9a, 0x11, 0x57, 0x9c, 0x29, 0x63, 0x83, 0x4f, 0x44, 0x04, 0x32, 0x80, 0x15, 0x7d, 0x82, + 0x92, 0xf0, 0x0a, 0x4e, 0x7c, 0x7f, 0x72, 0x09, 0xc3, 0x92, 0x80, 0xa4, 0x57, 0x1a, 0xe8, 0xfc, + 0x36, 0xb4, 0x47, 0x2d, 0xa8, 0x62, 0xdb, 0xef, 0x99, 0xdb, 0xbe, 0x52, 0x61, 0x92, 0xa9, 0x5e, + 0x40, 0xfc, 0x1c, 0xd6, 0x47, 0x08, 0x73, 0x89, 0xaa, 0x83, 0x66, 0xa9, 0xba, 0x35, 0xfd, 0x85, + 0x05, 0x9d, 0x5d, 0xcf, 0x2b, 0x39, 0xa7, 0xbc, 0x48, 0xf0, 0xba, 0x5d, 0xee, 0x16, 0x6c, 0x54, + 0x0a, 0x24, 0xaa, 0x19, 0x2f, 0x61, 0xcb, 0xa1, 0x83, 0xe8, 0x94, 0xbe, 0x6e, 0x91, 0xed, 0xeb, + 0x70, 0x6d, 0x14, 0x67, 0x21, 0x1b, 0x96, 0xf7, 0xcc, 0xf2, 0xb8, 0x0a, 0x8c, 0xfe, 0xd3, 0x82, + 0x79, 0xb3, 0x70, 0xfe, 0xaa, 0x72, 0xf1, 0x37, 0x81, 0x24, 0x34, 0xcd, 0xba, 0x49, 0x14, 0x04, + 0x2c, 0x25, 0xf7, 0x68, 0xe0, 0x9e, 0x8b, 0x92, 0x7d, 0x8b, 0x8d, 0x38, 0x7c, 0xe0, 0x23, 0x06, + 0x27, 0xeb, 0x30, 0xeb, 0xc6, 0x7e, 0x97, 0x59, 0x0d, 0xcf, 0xc7, 0x67, 0xdc, 0xd8, 0xff, 0x84, + 0x9e, 0x13, 0x1b, 0xe6, 0xc5, 0x40, 0x37, 0xa0, 0xa7, 0x34, 0xc0, 0x98, 0x6f, 0xca, 0x69, 0xf0, + 0xe1, 0x4f, 0x19, 0x88, 0xdc, 0x85, 0x56, 0x9c, 0xf8, 0xcc, 0xfc, 0xf2, 0xb7, 0x81, 0x59, 0x94, + 0x66, 0x51, 0xc0, 0xe5, 0xea, 0xec, 0x1f, 0xc3, 0xd5, 0x0a, 0x5d, 0x08, 0x1f, 0xf5, 0x3d, 0x58, + 0x34, 0x5f, 0x18, 0xa4, 0x9f, 0x52, 0x51, 0xab, 0x31, 0xd1, 0x59, 0x38, 0x36, 0xe8, 0x88, 0xe8, + 0x13, 0x71, 0x1c, 0x37, 0x53, 0x35, 0x2d, 0xfb, 0x4b, 0x58, 0xc9, 0x81, 0x7b, 0x51, 0x78, 0x4a, + 0x93, 0x94, 0x59, 0x1b, 0x81, 0xe9, 0xe3, 0x24, 0x92, 0x05, 0x59, 0xfc, 0xcd, 0xe2, 0xb6, 0x2c, + 0x12, 0x66, 0x50, 0xcb, 0x22, 0x86, 0x93, 0xb8, 0x99, 0xbc, 0xa5, 0xf0, 0x37, 0x8b, 0x93, 0x7d, + 0x24, 0x42, 0xbb, 0x38, 0xc6, 0x4d, 0xb5, 0x21, 0x60, 0x8c, 0x8b, 0xfd, 0x0c, 0xc3, 0x47, 0x5d, + 0x14, 0xb1, 0xc6, 0x5f, 0x87, 0x06, 0x5f, 0x23, 0x9b, 0x29, 0xd7, 0xb7, 0x69, 0xac, 0xaf, 0x20, + 0xa6, 0x03, 0xc7, 0x0a, 0x6a, 0xff, 0x77, 0x0d, 0x9a, 0x18, 0xb1, 0x7e, 0x44, 0x33, 0xd7, 0x0f, + 0xc6, 0xc7, 0xd2, 0x3c, 0x06, 0xad, 0xa9, 0x18, 0xf4, 0x26, 0xcc, 0xeb, 0x05, 0x91, 0x73, 0x99, + 0xcc, 0x6a, 0xe5, 0x90, 0x73, 0x72, 0x0b, 0x16, 0x30, 0xb5, 0xce, 0xb1, 0xb8, 0xcd, 0xcc, 0x23, + 0x54, 0xa1, 0x99, 0x89, 0xc0, 0x95, 0x42, 0x22, 0xc0, 0x86, 0x31, 0x98, 0xee, 0xa6, 0xbe, 0xa7, + 0xf2, 0x04, 0x84, 0x1c, 0xfa, 0x9e, 0x36, 0x8c, 0xb3, 0x67, 0xb5, 0x61, 0x9c, 0xcd, 0x72, 0xa0, + 0x84, 0xf2, 0x87, 0x02, 0x7c, 0xef, 0x9a, 0x43, 0xa3, 0x6b, 0x4a, 0xe0, 0x53, 0x7f, 0x80, 0xaf, + 0x61, 0xa2, 0xb8, 0x5d, 0xe7, 0x16, 0xcb, 0xbf, 0xf2, 0x34, 0x0d, 0xf4, 0x34, 0x2d, 0x4f, 0xea, + 0x1a, 0x46, 0x52, 0xb7, 0x0d, 0x8d, 0x28, 0xa6, 0x61, 0x57, 0xa4, 0xd8, 0x4d, 0x1e, 0x3d, 0x30, + 0xd0, 0x33, 0x84, 0x88, 0x92, 0x09, 0xea, 0x3c, 0x9d, 0x24, 0x2f, 0x35, 0x15, 0x53, 0x2b, 0x2a, + 0x46, 0x26, 0x82, 0x53, 0x17, 0x25, 0x82, 0xf6, 0x2e, 0x46, 0xc5, 0x92, 0xb1, 0x30, 0x9f, 0x37, + 0x61, 0x06, 0xd5, 0x24, 0x2d, 0x67, 0xc5, 0x48, 0x63, 0x84, 0x51, 0x38, 0x02, 0xc7, 0xfe, 0x01, + 0xbe, 0x21, 0xe2, 0xd0, 0x24, 0xa2, 0x5f, 0x85, 0x39, 0xbe, 0x2b, 0xca, 0x6a, 0x66, 0xf1, 0xfb, + 0xb1, 0x67, 0xff, 0x9b, 0x05, 0xe4, 0x70, 0x78, 0x34, 0xf0, 0x27, 0xa7, 0x36, 0x79, 0x82, 0x4e, + 0x60, 0x1a, 0xcd, 0x84, 0x9b, 0x23, 0xfe, 0x2e, 0x58, 0xc8, 0x74, 0xd1, 0x42, 0xf2, 0xed, 0xbc, + 0x52, 0x9d, 0xa3, 0xcf, 0xe8, 0x9b, 0xcf, 0x5c, 0x7c, 0xe0, 0xd3, 0x30, 0xeb, 0x8a, 0x62, 0x0b, + 0x73, 0xf1, 0x08, 0x78, 0xec, 0xd9, 0x87, 0xb0, 0x6c, 0xac, 0x4c, 0x68, 0xfa, 0x06, 0x34, 0xb9, + 0x00, 0x71, 0xe0, 0xf6, 0x54, 0x35, 0xbc, 0x81, 0xb0, 0x03, 0x04, 0x8d, 0xd3, 0xd7, 0x9f, 0x58, + 0xb0, 0x72, 0xe8, 0x0f, 0x86, 0x81, 0x9b, 0xd1, 0x6f, 0x40, 0x63, 0xf9, 0xf2, 0xa7, 0x8c, 0xe5, + 0x4b, 0x4d, 0x4e, 0xe7, 0x9a, 0xb4, 0xff, 0xc7, 0x82, 0xd5, 0x82, 0x28, 0x2a, 0x26, 0x34, 0x8d, + 0x69, 0x44, 0x71, 0x40, 0x20, 0x69, 0x4c, 0x6b, 0x06, 0xd3, 0x9b, 0x30, 0x3f, 0xf0, 0x43, 0x7f, + 0x30, 0x1c, 0x74, 0xb9, 0xee, 0xb9, 0x4c, 0x4d, 0x01, 0x3c, 0xc0, 0x2d, 0x60, 0x48, 0xee, 0x4b, + 0x0d, 0x69, 0x5a, 0x20, 0x71, 0x20, 0x47, 0x7a, 0x1b, 0x56, 0xf2, 0xb8, 0xbd, 0xdb, 0x77, 0xfd, + 0xb0, 0x1b, 0x44, 0x69, 0x2a, 0xf6, 0x98, 0xe4, 0x63, 0xfb, 0xae, 0x1f, 0x7e, 0x1a, 0xa5, 0xa9, + 0xe6, 0x04, 0x66, 0x74, 0x27, 0xc0, 0x02, 0x98, 0xd6, 0xf3, 0x13, 0x37, 0xa0, 0x1f, 0x46, 0x83, + 0xa3, 0x57, 0xab, 0xfb, 0x1b, 0xd0, 0xe4, 0x75, 0xb7, 0xcc, 0x4d, 0xfa, 0x54, 0xee, 0x40, 0x03, + 0x61, 0x4f, 0x11, 0x54, 0xb9, 0x0d, 0xff, 0x65, 0x01, 0xd9, 0x63, 0xa1, 0x4c, 0x30, 0xb1, 0x3d, + 0x30, 0x57, 0xc2, 0xf3, 0xe6, 0xdc, 0xc2, 0xea, 0x02, 0xf2, 0xd8, 0x34, 0xbf, 0x29, 0xc3, 0xfc, + 0xd4, 0x6a, 0xa6, 0x2f, 0x59, 0x1c, 0x2b, 0xf9, 0xf1, 0x5b, 0xb0, 0x70, 0xe6, 0x06, 0x01, 0xcd, + 0xd4, 0x13, 0x9b, 0xa8, 0xc4, 0x73, 0xa8, 0xcc, 0xc1, 0xe5, 0x82, 0x67, 0xb5, 0x05, 0xaf, 0xc2, + 0xb2, 0xb1, 0x5e, 0x11, 0x0d, 0x3d, 0x84, 0x35, 0x0e, 0xde, 0x0d, 0x82, 0x89, 0xbd, 0xaa, 0xfd, + 0xd7, 0x35, 0x58, 0x2f, 0x4d, 0x53, 0x61, 0x83, 0x69, 0xc6, 0xb7, 0xd5, 0x72, 0xab, 0x27, 0xec, + 0x88, 0x4f, 0x31, 0xab, 0xf3, 0x4f, 0x16, 0xcc, 0x70, 0xd0, 0xd8, 0xdd, 0xf8, 0x5c, 0x3a, 0x04, + 0x61, 0x70, 0x3c, 0x23, 0xfa, 0xce, 0x64, 0xcc, 0xf8, 0xff, 0xf4, 0x67, 0x55, 0xee, 0x49, 0xc4, + 0x8b, 0xea, 0xf7, 0xa0, 0x55, 0x44, 0xb8, 0xd4, 0x93, 0x13, 0xaf, 0xaa, 0x3c, 0x3a, 0xa5, 0xda, + 0x33, 0xea, 0xcf, 0x2c, 0x58, 0xdc, 0x8b, 0x42, 0xcf, 0x67, 0x37, 0xe6, 0x81, 0x9b, 0xb8, 0x83, + 0x54, 0xbc, 0xe4, 0x73, 0x90, 0x2c, 0xbb, 0x2b, 0xc0, 0x88, 0x02, 0xe7, 0x16, 0x40, 0xef, 0x84, + 0xf6, 0x5e, 0x74, 0x45, 0xc5, 0x91, 0x3f, 0xff, 0x33, 0xc8, 0x87, 0xbe, 0x97, 0x92, 0xb7, 0x60, + 0x39, 0x1f, 0xee, 0xba, 0xa1, 0xd7, 0x15, 0xe5, 0x46, 0x7c, 0xdd, 0x50, 0x78, 0xbb, 0xa1, 0xb7, + 0x9b, 0xbe, 0x48, 0x59, 0xac, 0xa8, 0xaa, 0x6c, 0x5d, 0xc3, 0x85, 0x2f, 0x2a, 0xf8, 0x2e, 0x82, + 0xed, 0xff, 0xb5, 0xf0, 0x06, 0x94, 0xab, 0x12, 0xbb, 0x9d, 0x17, 0xd6, 0xb0, 0xde, 0x6a, 0x6c, + 0x59, 0xad, 0xb0, 0x65, 0x04, 0xa6, 0xfd, 0x8c, 0x0e, 0xe4, 0xc5, 0xc2, 0x7e, 0x93, 0x0f, 0xa1, + 0xa5, 0x56, 0xdc, 0x8d, 0x51, 0x2d, 0xe2, 0x98, 0xac, 0xe7, 0x89, 0xa3, 0xa1, 0x35, 0x67, 0xb1, + 0x57, 0x50, 0xa3, 0x3c, 0x5e, 0x57, 0x26, 0x72, 0xd4, 0x3d, 0xd4, 0xb6, 0xf0, 0x4f, 0xfc, 0x8b, + 0x4b, 0x4d, 0x7b, 0xc3, 0x8c, 0x7a, 0x22, 0x54, 0x56, 0xdf, 0xf6, 0x7f, 0x58, 0xb0, 0xb8, 0xeb, + 0x79, 0xb8, 0xee, 0x49, 0xdc, 0x84, 0x5c, 0x65, 0xed, 0x82, 0x55, 0x4e, 0xfd, 0x9c, 0xab, 0xfc, + 0x85, 0x9d, 0xc8, 0x08, 0x25, 0xd8, 0x36, 0xb4, 0xf2, 0x75, 0x56, 0x6f, 0xaf, 0xfd, 0x2d, 0x20, + 0x3c, 0xbd, 0x32, 0xd4, 0x51, 0xc4, 0x5a, 0x85, 0x65, 0x03, 0x4b, 0xf8, 0x9a, 0x8f, 0xe1, 0xce, + 0x3e, 0xcd, 0xf6, 0x92, 0xf3, 0x38, 0x8b, 0x64, 0x38, 0xfb, 0x11, 0x8d, 0xa3, 0xd4, 0x97, 0x9e, + 0x8b, 0x4e, 0xe4, 0x7d, 0xfe, 0xd9, 0x82, 0xbb, 0x13, 0x10, 0x12, 0x4b, 0xf8, 0xa2, 0x5c, 0x5f, + 0xfa, 0x0d, 0xbd, 0xbd, 0x65, 0x22, 0x2a, 0x3b, 0x0a, 0x22, 0xba, 0x0c, 0x14, 0xc9, 0xce, 0x07, + 0xb0, 0x60, 0x0e, 0x5e, 0xca, 0x55, 0x04, 0x70, 0xfb, 0x02, 0x21, 0x26, 0xb1, 0xb9, 0xdb, 0xb0, + 0xd0, 0x33, 0x48, 0x08, 0x46, 0x05, 0xa8, 0xbd, 0x07, 0x6f, 0x5c, 0xc8, 0x4d, 0xa8, 0x6d, 0x64, + 0x86, 0x6e, 0xff, 0xdd, 0x34, 0xac, 0x3f, 0xf7, 0xb3, 0x13, 0x2f, 0x71, 0xcf, 0xa4, 0xf5, 0x4d, + 0x22, 0x64, 0x21, 0x79, 0xaf, 0x95, 0xeb, 0x0d, 0xf7, 0x60, 0x29, 0x0a, 0x29, 0xe6, 0x18, 0xdd, + 0xd8, 0x4d, 0xd3, 0xb3, 0x28, 0x91, 0x77, 0xe9, 0x62, 0x14, 0x52, 0x96, 0x67, 0x1c, 0x08, 0x70, + 0xe1, 0x36, 0x9e, 0x2e, 0xde, 0xc6, 0x2d, 0x98, 0x8a, 0xfd, 0x50, 0xbc, 0x99, 0xb0, 0x9f, 0xec, + 0xee, 0xcc, 0x12, 0xd7, 0xd3, 0x28, 0x8b, 0xbb, 0x13, 0xa1, 0x8a, 0xae, 0x5e, 0xc5, 0x9f, 0x2d, + 0x54, 0xf1, 0x35, 0x9d, 0xcc, 0x99, 0x55, 0x8b, 0x6d, 0x68, 0x88, 0x9f, 0xdd, 0xcc, 0xed, 0x8b, + 0x14, 0x08, 0x04, 0xe8, 0xa9, 0xdb, 0xd7, 0xa2, 0x35, 0x30, 0xa2, 0xb5, 0x2d, 0x80, 0x63, 0x4a, + 0xbb, 0x46, 0x32, 0x54, 0x3f, 0xa6, 0x94, 0x3b, 0x5d, 0x16, 0x2a, 0x1f, 0xb9, 0xe1, 0x8b, 0x2e, + 0xd6, 0x20, 0x9a, 0x5c, 0x1c, 0x06, 0xf8, 0xcc, 0x1d, 0x60, 0x4c, 0x8c, 0x83, 0x52, 0xa6, 0x79, + 0xae, 0x51, 0x06, 0xdb, 0xcd, 0xab, 0x29, 0x88, 0xd2, 0xf3, 0xb3, 0xf3, 0xf6, 0x42, 0x3e, 0x7f, + 0xcf, 0xcf, 0xce, 0xd5, 0x7c, 0xd4, 0x59, 0x72, 0xde, 0x5e, 0xcc, 0xe7, 0xef, 0x71, 0x10, 0x13, + 0x2f, 0x3d, 0xf3, 0x8f, 0x29, 0x6f, 0x0c, 0x69, 0x89, 0x56, 0x29, 0x06, 0xd9, 0x8b, 0x3c, 0x0c, + 0x23, 0xcf, 0xfc, 0x44, 0x4b, 0x4e, 0x97, 0x78, 0x0a, 0xcb, 0x80, 0xd2, 0x34, 0xec, 0x7b, 0xd0, + 0x92, 0xe6, 0xa2, 0xf7, 0x4e, 0x26, 0x34, 0x1d, 0x06, 0x99, 0xec, 0x9d, 0xe4, 0x5f, 0xf6, 0x3b, + 0xd8, 0x15, 0xf1, 0x69, 0xd4, 0xef, 0xe7, 0xe9, 0x93, 0x30, 0xad, 0x35, 0x98, 0x09, 0x10, 0x2e, + 0xa7, 0xf0, 0x2f, 0x3b, 0xc4, 0x7a, 0x4e, 0x61, 0x4a, 0xfe, 0x6a, 0xe1, 0x87, 0xc7, 0x91, 0xc8, + 0x16, 0xf0, 0x37, 0x3b, 0x8b, 0x1e, 0x3d, 0x1a, 0xf6, 0x65, 0x0f, 0x14, 0x7e, 0x30, 0xcc, 0x33, + 0x37, 0x09, 0xc5, 0x85, 0x8a, 0xbf, 0x19, 0x26, 0x4d, 0x92, 0x28, 0x11, 0xb7, 0x27, 0xff, 0xb0, + 0xf7, 0x61, 0xfd, 0xf0, 0x72, 0x22, 0x32, 0x42, 0xbc, 0x5a, 0x23, 0x8e, 0x3f, 0x7e, 0xd8, 0x9f, + 0x18, 0x1d, 0x20, 0xd8, 0x25, 0x30, 0xc9, 0x31, 0x5a, 0x81, 0x2b, 0xe8, 0xcb, 0x25, 0x31, 0xfc, + 0x60, 0x19, 0x61, 0xbb, 0x4c, 0x4d, 0xf5, 0xa0, 0x95, 0x3b, 0x2a, 0xb8, 0x27, 0xfc, 0x95, 0x8a, + 0x8e, 0x0a, 0x63, 0xee, 0x64, 0x2d, 0x15, 0xdf, 0x68, 0x97, 0xc4, 0x57, 0xb0, 0xac, 0x8b, 0xf6, + 0x5a, 0xb3, 0xfe, 0x9f, 0x58, 0x58, 0x21, 0x53, 0x19, 0xd8, 0x61, 0x96, 0x50, 0x77, 0xf0, 0x5a, + 0x1f, 0xc4, 0xbf, 0x0f, 0x37, 0xf4, 0x7e, 0xa9, 0x4b, 0x4b, 0x62, 0xff, 0x01, 0x3e, 0x23, 0xf2, + 0x47, 0xfe, 0x5f, 0x82, 0xfc, 0x1f, 0xc0, 0x35, 0x4d, 0xfe, 0x4b, 0x8a, 0xf1, 0xe0, 0x4f, 0xef, + 0xc1, 0xc2, 0x7e, 0xc4, 0x6f, 0xac, 0xa7, 0xcc, 0x51, 0x27, 0xe4, 0x09, 0xcc, 0x8a, 0x0e, 0x61, + 0xb2, 0x56, 0x6a, 0x19, 0x46, 0x8a, 0x9d, 0xf5, 0x11, 0xad, 0xc4, 0xf6, 0xf2, 0xd7, 0xff, 0xfa, + 0xef, 0x3f, 0xad, 0xcd, 0x93, 0xc6, 0xfd, 0xd3, 0x77, 0xee, 0xf7, 0x69, 0x86, 0x1e, 0xe1, 0x04, + 0xe6, 0x8d, 0xa6, 0x4e, 0xb2, 0x69, 0x34, 0x66, 0x16, 0x7a, 0x3d, 0x3b, 0x5b, 0x63, 0xdb, 0x36, + 0xed, 0x0e, 0xb2, 0x58, 0x21, 0x44, 0xb0, 0x48, 0x11, 0x85, 0x13, 0xfe, 0x12, 0x16, 0x1f, 0x61, + 0xa9, 0x58, 0x51, 0x25, 0xdb, 0x39, 0xb5, 0xca, 0x66, 0xd5, 0xce, 0xf5, 0xd1, 0x08, 0x82, 0xe3, + 0x06, 0x72, 0x5c, 0x25, 0xcb, 0x8c, 0x23, 0x2f, 0x45, 0xab, 0x26, 0x51, 0x92, 0x42, 0x4b, 0xb4, + 0xbf, 0xbd, 0x52, 0x9e, 0x9b, 0xc8, 0x73, 0x8d, 0xac, 0x30, 0x9e, 0x1e, 0x67, 0x90, 0x33, 0x8d, + 0xb0, 0xd2, 0xa5, 0xb7, 0x6b, 0x92, 0x6b, 0x23, 0xfb, 0x38, 0x39, 0xcb, 0xed, 0x0b, 0xfa, 0x3c, + 0xcd, 0x55, 0xf6, 0x29, 0xc3, 0x55, 0xad, 0x9e, 0xe4, 0xa7, 0xdc, 0xfd, 0x55, 0x36, 0x16, 0x93, + 0x37, 0x2e, 0xee, 0x66, 0xe6, 0x32, 0xdc, 0x99, 0xb4, 0xed, 0xd9, 0xfe, 0x16, 0x0a, 0x73, 0x8d, + 0x6c, 0x0a, 0x61, 0x8c, 0x56, 0x67, 0xd9, 0x4c, 0x4d, 0x7a, 0xd0, 0xd4, 0x7b, 0x34, 0xc9, 0x46, + 0x85, 0xb7, 0x55, 0xcc, 0x37, 0xab, 0x07, 0x05, 0xc3, 0x36, 0x32, 0x24, 0xa4, 0x25, 0x18, 0xaa, + 0x96, 0x4e, 0xf2, 0x15, 0x2c, 0x16, 0xfa, 0x1b, 0x89, 0x5d, 0xd8, 0xbe, 0x8a, 0x5e, 0xd5, 0xce, + 0xcd, 0xb1, 0x38, 0x82, 0xeb, 0x35, 0xe4, 0xda, 0xb6, 0x97, 0xb5, 0x5d, 0x96, 0x9c, 0xbf, 0x6b, + 0xdd, 0x23, 0x29, 0xee, 0xb3, 0xde, 0x8a, 0x37, 0x11, 0xef, 0xed, 0x0b, 0xfa, 0xf8, 0x4a, 0x7b, + 0x2d, 0x79, 0xe2, 0x71, 0x4d, 0xb1, 0xbd, 0x49, 0x6b, 0x20, 0xc5, 0x50, 0x64, 0x12, 0xbe, 0x5b, + 0xd5, 0x0d, 0xa8, 0xa2, 0x07, 0xb6, 0x74, 0x72, 0x25, 0xd7, 0x28, 0x8b, 0x49, 0x6a, 0xf4, 0xe7, + 0x0a, 0xa6, 0xa6, 0x55, 0x57, 0x74, 0xc8, 0x56, 0xae, 0x54, 0x6f, 0x79, 0x1d, 0xb9, 0xd2, 0x28, + 0x8b, 0x53, 0xf2, 0x12, 0x16, 0xb8, 0xbb, 0x78, 0xf5, 0x3b, 0xbb, 0x85, 0x7c, 0xd7, 0x6d, 0x92, + 0xfb, 0x0c, 0x7d, 0x63, 0x9f, 0x43, 0x5d, 0xdd, 0x19, 0xa4, 0xad, 0x2d, 0xc2, 0x68, 0x56, 0xec, + 0x8c, 0x68, 0x45, 0x93, 0xd6, 0x6a, 0xcf, 0x8b, 0x55, 0xf1, 0xc6, 0x32, 0x46, 0xf8, 0xc7, 0x00, + 0x79, 0x6f, 0x1a, 0xb9, 0x5a, 0xa2, 0xac, 0x34, 0xd7, 0xa9, 0x1a, 0x92, 0x5d, 0xf8, 0x48, 0xbe, + 0x45, 0x16, 0x0c, 0xf2, 0xf2, 0xbc, 0xa9, 0x2b, 0xd2, 0x38, 0x6f, 0xc5, 0x6e, 0xb6, 0xce, 0xe8, + 0x36, 0x26, 0xb9, 0x29, 0xb6, 0x3c, 0x6c, 0xaa, 0x14, 0xc2, 0x56, 0xd0, 0xc7, 0xdb, 0x42, 0xeb, + 0x9f, 0xda, 0xac, 0xe2, 0x52, 0x79, 0x5b, 0x94, 0x9b, 0xa1, 0xec, 0xab, 0xc8, 0x6a, 0x99, 0x2c, + 0x15, 0x59, 0xa5, 0xe4, 0x05, 0xfe, 0x15, 0x92, 0xd6, 0xfe, 0x43, 0x74, 0x5a, 0xe5, 0x5e, 0xa8, + 0xce, 0xb5, 0x51, 0xc3, 0x23, 0x6e, 0x26, 0x91, 0x2d, 0xe1, 0xa1, 0xe2, 0x1b, 0xce, 0x9b, 0x7e, + 0x8c, 0x0d, 0x37, 0x7a, 0x83, 0x3a, 0x57, 0x2b, 0x46, 0x04, 0xf5, 0x55, 0xa4, 0xbe, 0x48, 0xe6, + 0x95, 0x4b, 0x44, 0x5a, 0x7c, 0x4f, 0xd4, 0x6b, 0xac, 0xb1, 0x27, 0xc5, 0x96, 0x1d, 0xc3, 0x07, + 0x96, 0x1a, 0x77, 0x4a, 0x3e, 0x50, 0xb5, 0xe6, 0x90, 0x3f, 0x34, 0x3b, 0x80, 0x64, 0x47, 0x82, + 0x3d, 0xb6, 0x85, 0xa0, 0x74, 0x5a, 0x46, 0xb6, 0x19, 0xd8, 0xdb, 0xc8, 0xf9, 0x2a, 0x59, 0x2f, + 0x72, 0x16, 0x2d, 0x0b, 0xe4, 0x6b, 0x0b, 0x96, 0x2b, 0x1e, 0xc4, 0x73, 0x09, 0x46, 0x3f, 0xdf, + 0xe7, 0x12, 0x8c, 0x7b, 0x51, 0xb7, 0x51, 0x82, 0x4d, 0x1b, 0x25, 0x70, 0x3d, 0x4f, 0x49, 0x20, + 0x92, 0x3f, 0x66, 0x99, 0x7f, 0x6e, 0xc1, 0x5a, 0xf5, 0xe3, 0x37, 0xb9, 0xa5, 0xfe, 0xae, 0x61, + 0xdc, 0xb3, 0x7c, 0xe7, 0xf6, 0x45, 0x68, 0x42, 0x9a, 0x5b, 0x28, 0xcd, 0xb6, 0xdd, 0x61, 0xd2, + 0x24, 0x88, 0x5b, 0x25, 0xd0, 0x19, 0x56, 0x0c, 0xcd, 0xe7, 0x65, 0xa2, 0xc5, 0x16, 0xd5, 0xaf, + 0xf0, 0x9d, 0x1b, 0x63, 0x30, 0x4c, 0xf7, 0x45, 0x56, 0xc5, 0x86, 0xe0, 0x9b, 0xac, 0x7a, 0xa7, + 0x16, 0x67, 0x34, 0x7f, 0xbe, 0x35, 0xce, 0x68, 0xe9, 0x45, 0xda, 0x38, 0xa3, 0xe5, 0x47, 0xe2, + 0xd2, 0x19, 0x45, 0x66, 0xf8, 0x60, 0x4c, 0x3e, 0xc7, 0x63, 0x23, 0xca, 0xd5, 0xed, 0xe2, 0x51, + 0x4f, 0xab, 0x8e, 0x8d, 0x59, 0x90, 0x2e, 0xb9, 0x4a, 0x5e, 0x05, 0x67, 0xda, 0x73, 0x60, 0x4e, + 0xa2, 0x93, 0xf5, 0x22, 0x01, 0x49, 0xb9, 0xf2, 0xc5, 0xd1, 0x5e, 0x47, 0xa2, 0x4b, 0x76, 0x53, + 0x27, 0xca, 0x68, 0x1e, 0x41, 0x43, 0x7b, 0x5d, 0x23, 0xca, 0xc9, 0x96, 0x1f, 0x13, 0x3b, 0x1b, + 0x95, 0x63, 0xa6, 0x2b, 0xb1, 0x17, 0x19, 0x83, 0x14, 0x11, 0x14, 0x8f, 0xdf, 0x85, 0x79, 0xe3, + 0x81, 0x2b, 0x57, 0x7e, 0xd5, 0x13, 0x5c, 0xae, 0xfc, 0xca, 0x57, 0x31, 0x19, 0x68, 0xda, 0xa8, + 0xfc, 0x54, 0xa0, 0x28, 0x5e, 0x5f, 0x40, 0x5d, 0xbd, 0x2b, 0xe5, 0xfa, 0x2f, 0x3e, 0x35, 0x5d, + 0xc4, 0xc3, 0xd8, 0x83, 0x33, 0x36, 0xf9, 0x28, 0x1a, 0x1c, 0x09, 0x7d, 0x69, 0xaf, 0x26, 0xb9, + 0xbe, 0xca, 0x4f, 0x47, 0xb9, 0xbe, 0xaa, 0x9e, 0x59, 0x0c, 0x7d, 0xf5, 0x10, 0x41, 0xad, 0x21, + 0x81, 0xc5, 0xc2, 0x6b, 0x45, 0x1e, 0x56, 0x54, 0xbf, 0xcd, 0xe4, 0x61, 0xc5, 0x88, 0x67, 0x0e, + 0x33, 0x70, 0xe3, 0xfc, 0xdc, 0x20, 0xc8, 0x6d, 0x8b, 0xbb, 0x7b, 0x5e, 0xcb, 0x37, 0xec, 0xd6, + 0x78, 0xb4, 0x30, 0xec, 0xd6, 0x2c, 0xfc, 0x97, 0xdc, 0x3d, 0xe5, 0xb4, 0x9e, 0xc1, 0x9c, 0x2c, + 0x22, 0xe7, 0x46, 0x5b, 0x28, 0x9f, 0x77, 0xda, 0xe5, 0x01, 0x41, 0xd5, 0x30, 0x5c, 0xd7, 0xf3, + 0x90, 0xaa, 0xd8, 0x08, 0xad, 0xa4, 0x9c, 0x6f, 0x44, 0xb9, 0x1a, 0x9d, 0x6f, 0x44, 0x55, 0x0d, + 0xda, 0xd8, 0x08, 0xee, 0xb9, 0x14, 0x8f, 0xbf, 0xb7, 0x30, 0xd5, 0x1e, 0x5f, 0x11, 0x26, 0x6f, + 0x5f, 0xa2, 0x78, 0xcc, 0x05, 0x7a, 0xe7, 0xd2, 0xe5, 0x66, 0xfb, 0x0e, 0x8a, 0x69, 0xdb, 0x5b, + 0xf2, 0x32, 0xc5, 0x69, 0x1e, 0x47, 0x57, 0xb5, 0x67, 0x26, 0xf4, 0xdf, 0x5a, 0xfc, 0x6f, 0x4c, + 0xc7, 0xd0, 0x25, 0x3b, 0x13, 0x0a, 0x20, 0x05, 0xbe, 0x3f, 0x31, 0xbe, 0x10, 0xf7, 0x36, 0x8a, + 0x7b, 0xdd, 0xde, 0x18, 0x23, 0x2e, 0x13, 0xf6, 0xf7, 0x61, 0x43, 0x55, 0x8e, 0x0d, 0xba, 0x1f, + 0x0f, 0x43, 0x2f, 0xcd, 0xf3, 0xd2, 0x11, 0xe5, 0xe5, 0xdc, 0x70, 0x8a, 0x05, 0x45, 0xf3, 0x7e, + 0x3c, 0x13, 0xa3, 0x5c, 0x8c, 0x63, 0x46, 0x9b, 0x71, 0x8f, 0x61, 0x49, 0xce, 0xfb, 0xd8, 0x77, + 0xb3, 0x5f, 0x98, 0xe7, 0x75, 0xe4, 0xd9, 0xb1, 0x57, 0x75, 0x9e, 0xc7, 0xbe, 0x9b, 0x29, 0x8e, + 0x29, 0x3e, 0x04, 0x1a, 0xb5, 0x42, 0x3d, 0xf9, 0xae, 0xac, 0x22, 0xea, 0xc9, 0x77, 0x75, 0x59, + 0xd3, 0x4c, 0xbe, 0xfb, 0x34, 0xe3, 0x65, 0x46, 0x4f, 0x30, 0x38, 0x85, 0xd6, 0xe1, 0x48, 0xa6, + 0x87, 0x3f, 0x37, 0x53, 0x11, 0x03, 0xd9, 0xc8, 0x34, 0x2d, 0x30, 0x65, 0x8b, 0x3d, 0xe5, 0xaf, + 0x9e, 0x7a, 0x15, 0x91, 0x6c, 0x8f, 0xae, 0x2f, 0x96, 0xf9, 0x56, 0x16, 0x20, 0x4d, 0xbe, 0x5a, + 0x86, 0x84, 0x7f, 0x5b, 0xc7, 0xf8, 0x9e, 0x03, 0x31, 0xb3, 0x24, 0xfc, 0x9b, 0x0c, 0xe5, 0x05, + 0x2a, 0x6a, 0x87, 0x93, 0xa5, 0x48, 0x37, 0x90, 0xf1, 0x86, 0xbd, 0x56, 0x4e, 0x91, 0x18, 0x6f, + 0xc6, 0xfa, 0xf7, 0x60, 0xb9, 0x90, 0x7b, 0xbf, 0x22, 0xde, 0x86, 0x39, 0x17, 0x12, 0x6f, 0xc9, + 0x3c, 0xc3, 0x3c, 0xb8, 0x50, 0x10, 0x24, 0x37, 0xaa, 0xf2, 0x0d, 0xa3, 0xde, 0x36, 0x2e, 0xf3, + 0x11, 0xf7, 0x06, 0x59, 0x2b, 0xa5, 0x23, 0x48, 0xe1, 0x6d, 0x8b, 0xfc, 0x99, 0x85, 0x6d, 0xf0, + 0x23, 0xea, 0x91, 0xe4, 0x6e, 0x55, 0xc2, 0x7b, 0x69, 0x31, 0x84, 0x3f, 0x21, 0xd7, 0x8a, 0x59, + 0x71, 0x49, 0x9c, 0x13, 0xac, 0x40, 0xe8, 0x55, 0x45, 0x23, 0x27, 0xaf, 0x28, 0x37, 0x8e, 0x4c, + 0x5a, 0x8b, 0xa9, 0xb8, 0xc8, 0x3a, 0x25, 0xa7, 0x9f, 0x98, 0x7f, 0xec, 0x6a, 0xb0, 0xbc, 0x5d, + 0xb1, 0xea, 0xcb, 0xb0, 0xbe, 0x89, 0xac, 0xb7, 0xc8, 0x46, 0x61, 0xbd, 0x99, 0x21, 0xc2, 0xd1, + 0x0c, 0xfe, 0xf3, 0x14, 0xef, 0xfe, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc2, 0xa4, 0xbf, 0x47, + 0xd1, 0x42, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -5168,6 +5371,10 @@ type GoCryptoTraderClient interface { GetExchangePairs(ctx context.Context, in *GetExchangePairsRequest, opts ...grpc.CallOption) (*GetExchangePairsResponse, error) EnableExchangePair(ctx context.Context, in *ExchangePairRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) DisableExchangePair(ctx context.Context, in *ExchangePairRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) + GetOrderbookStream(ctx context.Context, in *GetOrderbookStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetOrderbookStreamClient, error) + GetExchangeOrderbookStream(ctx context.Context, in *GetExchangeOrderbookStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetExchangeOrderbookStreamClient, error) + GetTickerStream(ctx context.Context, in *GetTickerStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetTickerStreamClient, error) + GetExchangeTickerStream(ctx context.Context, in *GetExchangeTickerStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetExchangeTickerStreamClient, error) } type goCryptoTraderClient struct { @@ -5565,6 +5772,134 @@ func (c *goCryptoTraderClient) DisableExchangePair(ctx context.Context, in *Exch return out, nil } +func (c *goCryptoTraderClient) GetOrderbookStream(ctx context.Context, in *GetOrderbookStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetOrderbookStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[0], "/gctrpc.GoCryptoTrader/GetOrderbookStream", opts...) + if err != nil { + return nil, err + } + x := &goCryptoTraderGetOrderbookStreamClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type GoCryptoTrader_GetOrderbookStreamClient interface { + Recv() (*OrderbookResponse, error) + grpc.ClientStream +} + +type goCryptoTraderGetOrderbookStreamClient struct { + grpc.ClientStream +} + +func (x *goCryptoTraderGetOrderbookStreamClient) Recv() (*OrderbookResponse, error) { + m := new(OrderbookResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *goCryptoTraderClient) GetExchangeOrderbookStream(ctx context.Context, in *GetExchangeOrderbookStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetExchangeOrderbookStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[1], "/gctrpc.GoCryptoTrader/GetExchangeOrderbookStream", opts...) + if err != nil { + return nil, err + } + x := &goCryptoTraderGetExchangeOrderbookStreamClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type GoCryptoTrader_GetExchangeOrderbookStreamClient interface { + Recv() (*OrderbookResponse, error) + grpc.ClientStream +} + +type goCryptoTraderGetExchangeOrderbookStreamClient struct { + grpc.ClientStream +} + +func (x *goCryptoTraderGetExchangeOrderbookStreamClient) Recv() (*OrderbookResponse, error) { + m := new(OrderbookResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *goCryptoTraderClient) GetTickerStream(ctx context.Context, in *GetTickerStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetTickerStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[2], "/gctrpc.GoCryptoTrader/GetTickerStream", opts...) + if err != nil { + return nil, err + } + x := &goCryptoTraderGetTickerStreamClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type GoCryptoTrader_GetTickerStreamClient interface { + Recv() (*TickerResponse, error) + grpc.ClientStream +} + +type goCryptoTraderGetTickerStreamClient struct { + grpc.ClientStream +} + +func (x *goCryptoTraderGetTickerStreamClient) Recv() (*TickerResponse, error) { + m := new(TickerResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *goCryptoTraderClient) GetExchangeTickerStream(ctx context.Context, in *GetExchangeTickerStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetExchangeTickerStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[3], "/gctrpc.GoCryptoTrader/GetExchangeTickerStream", opts...) + if err != nil { + return nil, err + } + x := &goCryptoTraderGetExchangeTickerStreamClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type GoCryptoTrader_GetExchangeTickerStreamClient interface { + Recv() (*TickerResponse, error) + grpc.ClientStream +} + +type goCryptoTraderGetExchangeTickerStreamClient struct { + grpc.ClientStream +} + +func (x *goCryptoTraderGetExchangeTickerStreamClient) Recv() (*TickerResponse, error) { + m := new(TickerResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // GoCryptoTraderServer is the server API for GoCryptoTrader service. type GoCryptoTraderServer interface { GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) @@ -5610,6 +5945,156 @@ type GoCryptoTraderServer interface { GetExchangePairs(context.Context, *GetExchangePairsRequest) (*GetExchangePairsResponse, error) EnableExchangePair(context.Context, *ExchangePairRequest) (*GenericExchangeNameResponse, error) DisableExchangePair(context.Context, *ExchangePairRequest) (*GenericExchangeNameResponse, error) + GetOrderbookStream(*GetOrderbookStreamRequest, GoCryptoTrader_GetOrderbookStreamServer) error + GetExchangeOrderbookStream(*GetExchangeOrderbookStreamRequest, GoCryptoTrader_GetExchangeOrderbookStreamServer) error + GetTickerStream(*GetTickerStreamRequest, GoCryptoTrader_GetTickerStreamServer) error + GetExchangeTickerStream(*GetExchangeTickerStreamRequest, GoCryptoTrader_GetExchangeTickerStreamServer) error +} + +// UnimplementedGoCryptoTraderServer can be embedded to have forward compatible implementations. +type UnimplementedGoCryptoTraderServer struct { +} + +func (*UnimplementedGoCryptoTraderServer) GetInfo(ctx context.Context, req *GetInfoRequest) (*GetInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetSubsystems(ctx context.Context, req *GetSubsystemsRequest) (*GetSusbsytemsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSubsystems not implemented") +} +func (*UnimplementedGoCryptoTraderServer) EnableSubsystem(ctx context.Context, req *GenericSubsystemRequest) (*GenericSubsystemResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EnableSubsystem not implemented") +} +func (*UnimplementedGoCryptoTraderServer) DisableSubsystem(ctx context.Context, req *GenericSubsystemRequest) (*GenericSubsystemResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DisableSubsystem not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetRPCEndpoints(ctx context.Context, req *GetRPCEndpointsRequest) (*GetRPCEndpointsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRPCEndpoints not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetCommunicationRelayers(ctx context.Context, req *GetCommunicationRelayersRequest) (*GetCommunicationRelayersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCommunicationRelayers not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchanges(ctx context.Context, req *GetExchangesRequest) (*GetExchangesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchanges not implemented") +} +func (*UnimplementedGoCryptoTraderServer) DisableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DisableExchange not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeInfo(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangeInfo not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCode(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeOTPReponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangeOTPCode not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCodes(ctx context.Context, req *GetExchangeOTPsRequest) (*GetExchangeOTPsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangeOTPCodes not implemented") +} +func (*UnimplementedGoCryptoTraderServer) EnableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EnableExchange not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetTicker(ctx context.Context, req *GetTickerRequest) (*TickerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTicker not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetTickers(ctx context.Context, req *GetTickersRequest) (*GetTickersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTickers not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrderbook(ctx context.Context, req *GetOrderbookRequest) (*OrderbookResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrderbook not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrderbooks(ctx context.Context, req *GetOrderbooksRequest) (*GetOrderbooksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrderbooks not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetAccountInfo(ctx context.Context, req *GetAccountInfoRequest) (*GetAccountInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAccountInfo not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetConfig(ctx context.Context, req *GetConfigRequest) (*GetConfigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetPortfolio(ctx context.Context, req *GetPortfolioRequest) (*GetPortfolioResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPortfolio not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetPortfolioSummary(ctx context.Context, req *GetPortfolioSummaryRequest) (*GetPortfolioSummaryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPortfolioSummary not implemented") +} +func (*UnimplementedGoCryptoTraderServer) AddPortfolioAddress(ctx context.Context, req *AddPortfolioAddressRequest) (*AddPortfolioAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddPortfolioAddress not implemented") +} +func (*UnimplementedGoCryptoTraderServer) RemovePortfolioAddress(ctx context.Context, req *RemovePortfolioAddressRequest) (*RemovePortfolioAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemovePortfolioAddress not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetForexProviders(ctx context.Context, req *GetForexProvidersRequest) (*GetForexProvidersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetForexProviders not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetForexRates(ctx context.Context, req *GetForexRatesRequest) (*GetForexRatesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetForexRates not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrders(ctx context.Context, req *GetOrdersRequest) (*GetOrdersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrders not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrder(ctx context.Context, req *GetOrderRequest) (*OrderDetails, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) SubmitOrder(ctx context.Context, req *SubmitOrderRequest) (*SubmitOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) SimulateOrder(ctx context.Context, req *SimulateOrderRequest) (*SimulateOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SimulateOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WhaleBomb(ctx context.Context, req *WhaleBombRequest) (*SimulateOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WhaleBomb not implemented") +} +func (*UnimplementedGoCryptoTraderServer) CancelOrder(ctx context.Context, req *CancelOrderRequest) (*CancelOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CancelOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) CancelAllOrders(ctx context.Context, req *CancelAllOrdersRequest) (*CancelAllOrdersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CancelAllOrders not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetEvents(ctx context.Context, req *GetEventsRequest) (*GetEventsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented") +} +func (*UnimplementedGoCryptoTraderServer) AddEvent(ctx context.Context, req *AddEventRequest) (*AddEventResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddEvent not implemented") +} +func (*UnimplementedGoCryptoTraderServer) RemoveEvent(ctx context.Context, req *RemoveEventRequest) (*RemoveEventResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveEvent not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddresses(ctx context.Context, req *GetCryptocurrencyDepositAddressesRequest) (*GetCryptocurrencyDepositAddressesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddresses not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddress(ctx context.Context, req *GetCryptocurrencyDepositAddressRequest) (*GetCryptocurrencyDepositAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddress not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WithdrawCryptocurrencyFunds(ctx context.Context, req *WithdrawCurrencyRequest) (*WithdrawResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WithdrawCryptocurrencyFunds not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WithdrawFiatFunds(ctx context.Context, req *WithdrawCurrencyRequest) (*WithdrawResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WithdrawFiatFunds not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetLoggerDetails(ctx context.Context, req *GetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetLoggerDetails not implemented") +} +func (*UnimplementedGoCryptoTraderServer) SetLoggerDetails(ctx context.Context, req *SetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetLoggerDetails not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangePairs(ctx context.Context, req *GetExchangePairsRequest) (*GetExchangePairsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangePairs not implemented") +} +func (*UnimplementedGoCryptoTraderServer) EnableExchangePair(ctx context.Context, req *ExchangePairRequest) (*GenericExchangeNameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EnableExchangePair not implemented") +} +func (*UnimplementedGoCryptoTraderServer) DisableExchangePair(ctx context.Context, req *ExchangePairRequest) (*GenericExchangeNameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DisableExchangePair not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrderbookStream(req *GetOrderbookStreamRequest, srv GoCryptoTrader_GetOrderbookStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetOrderbookStream not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeOrderbookStream(req *GetExchangeOrderbookStreamRequest, srv GoCryptoTrader_GetExchangeOrderbookStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetExchangeOrderbookStream not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetTickerStream(req *GetTickerStreamRequest, srv GoCryptoTrader_GetTickerStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetTickerStream not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeTickerStream(req *GetExchangeTickerStreamRequest, srv GoCryptoTrader_GetExchangeTickerStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetExchangeTickerStream not implemented") } func RegisterGoCryptoTraderServer(s *grpc.Server, srv GoCryptoTraderServer) { @@ -6390,6 +6875,90 @@ func _GoCryptoTrader_DisableExchangePair_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _GoCryptoTrader_GetOrderbookStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetOrderbookStreamRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(GoCryptoTraderServer).GetOrderbookStream(m, &goCryptoTraderGetOrderbookStreamServer{stream}) +} + +type GoCryptoTrader_GetOrderbookStreamServer interface { + Send(*OrderbookResponse) error + grpc.ServerStream +} + +type goCryptoTraderGetOrderbookStreamServer struct { + grpc.ServerStream +} + +func (x *goCryptoTraderGetOrderbookStreamServer) Send(m *OrderbookResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _GoCryptoTrader_GetExchangeOrderbookStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetExchangeOrderbookStreamRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(GoCryptoTraderServer).GetExchangeOrderbookStream(m, &goCryptoTraderGetExchangeOrderbookStreamServer{stream}) +} + +type GoCryptoTrader_GetExchangeOrderbookStreamServer interface { + Send(*OrderbookResponse) error + grpc.ServerStream +} + +type goCryptoTraderGetExchangeOrderbookStreamServer struct { + grpc.ServerStream +} + +func (x *goCryptoTraderGetExchangeOrderbookStreamServer) Send(m *OrderbookResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _GoCryptoTrader_GetTickerStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetTickerStreamRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(GoCryptoTraderServer).GetTickerStream(m, &goCryptoTraderGetTickerStreamServer{stream}) +} + +type GoCryptoTrader_GetTickerStreamServer interface { + Send(*TickerResponse) error + grpc.ServerStream +} + +type goCryptoTraderGetTickerStreamServer struct { + grpc.ServerStream +} + +func (x *goCryptoTraderGetTickerStreamServer) Send(m *TickerResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _GoCryptoTrader_GetExchangeTickerStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetExchangeTickerStreamRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(GoCryptoTraderServer).GetExchangeTickerStream(m, &goCryptoTraderGetExchangeTickerStreamServer{stream}) +} + +type GoCryptoTrader_GetExchangeTickerStreamServer interface { + Send(*TickerResponse) error + grpc.ServerStream +} + +type goCryptoTraderGetExchangeTickerStreamServer struct { + grpc.ServerStream +} + +func (x *goCryptoTraderGetExchangeTickerStreamServer) Send(m *TickerResponse) error { + return x.ServerStream.SendMsg(m) +} + var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ ServiceName: "gctrpc.GoCryptoTrader", HandlerType: (*GoCryptoTraderServer)(nil), @@ -6567,6 +7136,27 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ Handler: _GoCryptoTrader_DisableExchangePair_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "GetOrderbookStream", + Handler: _GoCryptoTrader_GetOrderbookStream_Handler, + ServerStreams: true, + }, + { + StreamName: "GetExchangeOrderbookStream", + Handler: _GoCryptoTrader_GetExchangeOrderbookStream_Handler, + ServerStreams: true, + }, + { + StreamName: "GetTickerStream", + Handler: _GoCryptoTrader_GetTickerStream_Handler, + ServerStreams: true, + }, + { + StreamName: "GetExchangeTickerStream", + Handler: _GoCryptoTrader_GetExchangeTickerStream_Handler, + ServerStreams: true, + }, + }, Metadata: "rpc.proto", } diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 37957646..958df2af 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -9,13 +9,13 @@ It translates gRPC into RESTful JSON APIs. package gctrpc import ( + "context" "io" "net/http" "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/grpc-ecosystem/grpc-gateway/utilities" - "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/grpclog" @@ -54,7 +54,10 @@ func request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshaler run var protoReq GenericSubsystemRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_EnableSubsystem_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_EnableSubsystem_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -71,7 +74,10 @@ func request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marshaler ru var protoReq GenericSubsystemRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_DisableSubsystem_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_DisableSubsystem_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -106,7 +112,10 @@ func request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtim var protoReq GetExchangesRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchanges_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -140,7 +149,10 @@ func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler run var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -157,7 +169,10 @@ func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -252,7 +267,10 @@ func request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runt var protoReq GetAccountInfoRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetAccountInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -578,7 +596,10 @@ func request_GoCryptoTrader_GetLoggerDetails_0(ctx context.Context, marshaler ru var protoReq GetLoggerDetailsRequest var metadata runtime.ServerMetadata - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetLoggerDetails_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetLoggerDetails_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -655,6 +676,118 @@ func request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler } +var ( + filter_GoCryptoTrader_GetOrderbookStream_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetOrderbookStream_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (GoCryptoTrader_GetOrderbookStreamClient, runtime.ServerMetadata, error) { + var protoReq GetOrderbookStreamRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetOrderbookStream_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + stream, err := client.GetOrderbookStream(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + +var ( + filter_GoCryptoTrader_GetExchangeOrderbookStream_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetExchangeOrderbookStream_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (GoCryptoTrader_GetExchangeOrderbookStreamClient, runtime.ServerMetadata, error) { + var protoReq GetExchangeOrderbookStreamRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeOrderbookStream_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + stream, err := client.GetExchangeOrderbookStream(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + +var ( + filter_GoCryptoTrader_GetTickerStream_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetTickerStream_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (GoCryptoTrader_GetTickerStreamClient, runtime.ServerMetadata, error) { + var protoReq GetTickerStreamRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetTickerStream_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + stream, err := client.GetTickerStream(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + +var ( + filter_GoCryptoTrader_GetExchangeTickerStream_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetExchangeTickerStream_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (GoCryptoTrader_GetExchangeTickerStreamClient, runtime.ServerMetadata, error) { + var protoReq GetExchangeTickerStreamRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeTickerStream_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + stream, err := client.GetExchangeTickerStream(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + // RegisterGoCryptoTraderHandlerFromEndpoint is same as RegisterGoCryptoTraderHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterGoCryptoTraderHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { @@ -1553,95 +1686,183 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_GetOrderbookStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetOrderbookStream_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrderbookStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOrderbookStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetExchangeOrderbookStream_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeOrderbookStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetTickerStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetTickerStream_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetTickerStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeTickerStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetExchangeTickerStream_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeTickerStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + return nil } var ( - pattern_GoCryptoTrader_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, "")) + pattern_GoCryptoTrader_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetSubsystems_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getsusbsystems"}, "")) + pattern_GoCryptoTrader_GetSubsystems_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getsusbsystems"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_EnableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enablesubsystem"}, "")) + pattern_GoCryptoTrader_EnableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enablesubsystem"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_DisableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disablesubsystem"}, "")) + pattern_GoCryptoTrader_DisableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disablesubsystem"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetRPCEndpoints_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getrpcendpoints"}, "")) + pattern_GoCryptoTrader_GetRPCEndpoints_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getrpcendpoints"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetCommunicationRelayers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcommunicationrelayers"}, "")) + pattern_GoCryptoTrader_GetCommunicationRelayers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcommunicationrelayers"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetExchanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchanges"}, "")) + pattern_GoCryptoTrader_GetExchanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchanges"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_DisableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchange"}, "")) + pattern_GoCryptoTrader_DisableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchange"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetExchangeInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeinfo"}, "")) + pattern_GoCryptoTrader_GetExchangeInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeinfo"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotp"}, "")) + pattern_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotp"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetExchangeOTPCodes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotps"}, "")) + pattern_GoCryptoTrader_GetExchangeOTPCodes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotps"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_EnableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchange"}, "")) + pattern_GoCryptoTrader_EnableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchange"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetTicker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getticker"}, "")) + pattern_GoCryptoTrader_GetTicker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getticker"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetTickers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "gettickers"}, "")) + pattern_GoCryptoTrader_GetTickers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "gettickers"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetOrderbook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbook"}, "")) + pattern_GoCryptoTrader_GetOrderbook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbook"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetOrderbooks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbooks"}, "")) + pattern_GoCryptoTrader_GetOrderbooks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbooks"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetAccountInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getaccountinfo"}, "")) + pattern_GoCryptoTrader_GetAccountInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getaccountinfo"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getconfig"}, "")) + pattern_GoCryptoTrader_GetConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getconfig"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetPortfolio_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfolio"}, "")) + pattern_GoCryptoTrader_GetPortfolio_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfolio"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetPortfolioSummary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfoliosummary"}, "")) + pattern_GoCryptoTrader_GetPortfolioSummary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfoliosummary"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_AddPortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addportfolioaddress"}, "")) + pattern_GoCryptoTrader_AddPortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addportfolioaddress"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_RemovePortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeportfolioaddress"}, "")) + pattern_GoCryptoTrader_RemovePortfolioAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeportfolioaddress"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetForexProviders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexproviders"}, "")) + pattern_GoCryptoTrader_GetForexProviders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexproviders"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetForexRates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexrates"}, "")) + pattern_GoCryptoTrader_GetForexRates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getforexrates"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorders"}, "")) + pattern_GoCryptoTrader_GetOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorders"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorder"}, "")) + pattern_GoCryptoTrader_GetOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorder"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_SubmitOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "submitorder"}, "")) + pattern_GoCryptoTrader_SubmitOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "submitorder"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_SimulateOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "simulateorder"}, "")) + pattern_GoCryptoTrader_SimulateOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "simulateorder"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_WhaleBomb_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "whalebomb"}, "")) + pattern_GoCryptoTrader_WhaleBomb_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "whalebomb"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_CancelOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelorder"}, "")) + pattern_GoCryptoTrader_CancelOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelorder"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_CancelAllOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelallorders"}, "")) + pattern_GoCryptoTrader_CancelAllOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cancelallorders"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getevents"}, "")) + pattern_GoCryptoTrader_GetEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getevents"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_AddEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addevent"}, "")) + pattern_GoCryptoTrader_AddEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "addevent"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_RemoveEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeevent"}, "")) + pattern_GoCryptoTrader_RemoveEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "removeevent"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddresses"}, "")) + pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddresses"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddress"}, "")) + pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getcryptodepositaddress"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawcryptofunds"}, "")) + pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawcryptofunds"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_WithdrawFiatFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawfiatfunds"}, "")) + pattern_GoCryptoTrader_WithdrawFiatFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "withdrawfiatfunds"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getloggerdetails"}, "")) + pattern_GoCryptoTrader_GetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getloggerdetails"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_SetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "setloggerdetails"}, "")) + pattern_GoCryptoTrader_SetLoggerDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "setloggerdetails"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetExchangePairs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangepairs"}, "")) + pattern_GoCryptoTrader_GetExchangePairs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangepairs"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_EnableExchangePair_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchangepair"}, "")) + pattern_GoCryptoTrader_EnableExchangePair_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchangepair"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_DisableExchangePair_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchangepair"}, "")) + pattern_GoCryptoTrader_DisableExchangePair_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchangepair"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_GetOrderbookStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbookstream"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_GetExchangeOrderbookStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeorderbookstream"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_GetTickerStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getTickerstream"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_GetExchangeTickerStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangetickerstream"}, "", runtime.AssumeColonVerbOpt(true))) ) var ( @@ -1730,4 +1951,12 @@ var ( forward_GoCryptoTrader_EnableExchangePair_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_DisableExchangePair_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetOrderbookStream_0 = runtime.ForwardResponseStream + + forward_GoCryptoTrader_GetExchangeOrderbookStream_0 = runtime.ForwardResponseStream + + forward_GoCryptoTrader_GetTickerStream_0 = runtime.ForwardResponseStream + + forward_GoCryptoTrader_GetExchangeTickerStream_0 = runtime.ForwardResponseStream ) diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 33c4cbdb..9e25a8f9 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -489,6 +489,26 @@ message ExchangePairRequest { CurrencyPair pair = 3; } +message GetOrderbookStreamRequest { + string exchange = 1; + CurrencyPair pair = 2; + string asset_type = 3; +} + +message GetExchangeOrderbookStreamRequest { + string exchange = 1; +} + +message GetTickerStreamRequest { + string exchange = 1; + CurrencyPair pair = 2; + string asset_type = 3; +} + +message GetExchangeTickerStreamRequest { + string exchange = 1; +} + service GoCryptoTrader { rpc GetInfo (GetInfoRequest) returns (GetInfoResponse) { option (google.api.http) = { @@ -771,4 +791,28 @@ service GoCryptoTrader { body: "*" }; } + + rpc GetOrderbookStream(GetOrderbookStreamRequest) returns (stream OrderbookResponse) { + option (google.api.http) = { + get: "/v1/getorderbookstream" + }; + } + + rpc GetExchangeOrderbookStream(GetExchangeOrderbookStreamRequest) returns (stream OrderbookResponse) { + option (google.api.http) = { + get: "/v1/getexchangeorderbookstream" + }; + } + + rpc GetTickerStream(GetTickerStreamRequest) returns (stream TickerResponse) { + option (google.api.http) = { + get: "/v1/getTickerstream" + }; + } + + rpc GetExchangeTickerStream(GetExchangeTickerStreamRequest) returns (stream TickerResponse) { + option (google.api.http) = { + get: "/v1/getexchangetickerstream" + }; + } } diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index f1759936..ac1504b8 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -271,6 +271,54 @@ ] } }, + "/v1/getTickerstream": { + "get": { + "operationId": "GetTickerStream", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "$ref": "#/x-stream-definitions/gctrpcTickerResponse" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pair.delimiter", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pair.base", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pair.quote", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "asset_type", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getaccountinfo": { "get": { "operationId": "GetAccountInfo", @@ -419,6 +467,30 @@ ] } }, + "/v1/getexchangeorderbookstream": { + "get": { + "operationId": "GetExchangeOrderbookStream", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "$ref": "#/x-stream-definitions/gctrpcOrderbookResponse" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getexchangeotp": { "get": { "operationId": "GetExchangeOTPCode", @@ -510,6 +582,30 @@ ] } }, + "/v1/getexchangetickerstream": { + "get": { + "operationId": "GetExchangeTickerStream", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "$ref": "#/x-stream-definitions/gctrpcTickerResponse" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getforexproviders": { "get": { "operationId": "GetForexProviders", @@ -650,6 +746,54 @@ ] } }, + "/v1/getorderbookstream": { + "get": { + "operationId": "GetOrderbookStream", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "$ref": "#/x-stream-definitions/gctrpcOrderbookResponse" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pair.delimiter", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pair.base", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pair.quote", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "asset_type", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getorders": { "post": { "operationId": "GetOrders", @@ -2175,6 +2319,69 @@ "type": "string" } } + }, + "protobufAny": { + "type": "object", + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "runtimeStreamError": { + "type": "object", + "properties": { + "grpc_code": { + "type": "integer", + "format": "int32" + }, + "http_code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "http_status": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + } + }, + "x-stream-definitions": { + "gctrpcOrderbookResponse": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/gctrpcOrderbookResponse" + }, + "error": { + "$ref": "#/definitions/runtimeStreamError" + } + }, + "title": "Stream result of gctrpcOrderbookResponse" + }, + "gctrpcTickerResponse": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/gctrpcTickerResponse" + }, + "error": { + "$ref": "#/definitions/runtimeStreamError" + } + }, + "title": "Stream result of gctrpcTickerResponse" } } } diff --git a/go.mod b/go.mod index 38ee1c2f..21c7d742 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/thrasher-corp/gocryptotrader go 1.12 require ( + github.com/cockroachdb/apd v1.1.0 // indirect github.com/gofrs/uuid v3.2.0+incompatible github.com/gogo/protobuf v1.2.1 // indirect github.com/golang/protobuf v1.3.1 @@ -11,16 +12,18 @@ require ( github.com/gorilla/websocket v1.4.0 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/grpc-ecosystem/grpc-gateway v1.9.2 + github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect github.com/jackc/pgx v3.5.0+incompatible github.com/jmoiron/sqlx v1.2.0 github.com/mattn/go-sqlite3 v1.11.0 github.com/pkg/errors v0.8.1 // indirect github.com/pquerna/otp v1.2.0 - github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b + github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect + github.com/shopspring/decimal v0.0.0-20190905144223-a36b5d85f337 // indirect github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb github.com/urfave/cli v1.20.0 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 - golang.org/x/net v0.0.0-20190606173856-1492cefac77f + golang.org/x/net v0.0.0-20190606173856-1492cefac77f // indirect google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6 google.golang.org/grpc v1.21.1 ) diff --git a/go.sum b/go.sum index 9473d911..89e90902 100644 --- a/go.sum +++ b/go.sum @@ -4,9 +4,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -29,6 +32,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmo github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/grpc-gateway v1.9.2 h1:S+ef0492XaIknb8LMjcwgW2i3cNTzDYMmDrOThOJNWc= github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.5.0+incompatible h1:BRJ4G3UPtvml5R1ey0biqqGuYUGayMYekm3woO75orY= github.com/jackc/pgx v3.5.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= @@ -40,6 +45,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= @@ -53,6 +59,8 @@ github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20190905144223-a36b5d85f337 h1:Da9XEUfFxgyDOqUfwgoTDcWzmnlOnCGi6i4iPS+8Fbw= +github.com/shopspring/decimal v0.0.0-20190905144223-a36b5d85f337/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -61,7 +69,6 @@ github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb/go.mod h1:VTLqNCX github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= diff --git a/logger/logger_setup.go b/logger/logger_setup.go index 14ac5a07..05887001 100644 --- a/logger/logger_setup.go +++ b/logger/logger_setup.go @@ -146,6 +146,7 @@ func init() { TimeMgr = registerNewSubLogger("timekeeper") WebsocketMgr = registerNewSubLogger("websocket") EventMgr = registerNewSubLogger("event") + DispatchMgr = registerNewSubLogger("dispatch") ExchangeSys = registerNewSubLogger("exchange") GRPCSys = registerNewSubLogger("grpc") diff --git a/logger/sublogger_types.go b/logger/sublogger_types.go index 362e34d5..7bbb027f 100644 --- a/logger/sublogger_types.go +++ b/logger/sublogger_types.go @@ -15,6 +15,7 @@ var ( TimeMgr *subLogger WebsocketMgr *subLogger EventMgr *subLogger + DispatchMgr *subLogger ExchangeSys *subLogger GRPCSys *subLogger diff --git a/main.go b/main.go index e2f2e77f..f30243d8 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/core" mg "github.com/thrasher-corp/gocryptotrader/database/migration" + "github.com/thrasher-corp/gocryptotrader/dispatch" "github.com/thrasher-corp/gocryptotrader/engine" "github.com/thrasher-corp/gocryptotrader/exchanges/request" log "github.com/thrasher-corp/gocryptotrader/logger" @@ -48,6 +49,8 @@ func main() { flag.BoolVar(&settings.EnableDatabaseManager, "databasemanager", true, "enables database manager") flag.DurationVar(&settings.EventManagerDelay, "eventmanagerdelay", time.Duration(0), "sets the event managers sleep delay between event checking") flag.BoolVar(&settings.EnableNTPClient, "ntpclient", true, "enables the NTP client to check system clock drift") + flag.BoolVar(&settings.EnableDispatcher, "dispatch", true, "enables the dispatch system") + flag.Int64Var(&settings.DispatchMaxWorkerAmount, "dispatchworkers", dispatch.DefaultMaxWorkers, "sets the dispatch package max worker generation limit") // Forex provider settings flag.BoolVar(&settings.EnableCurrencyConverter, "currencyconverter", false, "overrides config and sets up foreign exchange Currency Converter") From 8ca1e5c36f4f26ec47bd2cddbc96e9d653a8e98c Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 3 Oct 2019 10:12:44 +1000 Subject: [PATCH 49/71] Fix utils.AdjustGoMaxProcs Since Go 1.5, Go will use the total number of logical processers that the system has available. Caveats to this are if someone has set the GOMAXPROCS env var set or wish to limit usage of the number of logical processers between a range from 1 to NumCPUs --- engine/engine.go | 4 ++++ main.go | 2 +- utils/utils.go | 23 ++++++++++++++----- utils/utils_test.go | 55 ++++++++++++++++++++++++++++++++++++--------- 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index a35ac444..e41bb44a 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -4,6 +4,7 @@ import ( "errors" "flag" "fmt" + "runtime" "sync" "time" @@ -293,6 +294,9 @@ func (e *Engine) Start() error { e.Uptime = time.Now() log.Debugf(log.Global, "Bot '%s' started.\n", e.Config.Name) log.Debugf(log.Global, "Using data dir: %s\n", e.Settings.DataDir) + log.Debugf(log.Global, + "Using %d out of %d logical processors for runtime performance\n", + runtime.GOMAXPROCS(-1), runtime.NumCPU()) enabledExchanges := e.Config.CountEnabledExchanges() if e.Settings.EnableAllExchanges { diff --git a/main.go b/main.go index f30243d8..d677b637 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ func main() { flag.StringVar(&settings.ConfigFile, "config", "", "config file to load") flag.StringVar(&settings.DataDir, "datadir", common.GetDefaultDataDir(runtime.GOOS), "default data directory for GoCryptoTrader files") flag.StringVar(&settings.MigrationDir, "migrationdir", mg.MigrationDir, "override migration folder") - flag.IntVar(&settings.GoMaxProcs, "gomaxprocs", runtime.NumCPU(), "sets the runtime GOMAXPROCS value") + flag.IntVar(&settings.GoMaxProcs, "gomaxprocs", runtime.GOMAXPROCS(-1), "sets the runtime GOMAXPROCS value") flag.BoolVar(&settings.EnableDryRun, "dryrun", false, "dry runs bot, doesn't save config file") flag.BoolVar(&settings.EnableAllExchanges, "enableallexchanges", false, "enables all exchanges") flag.BoolVar(&settings.EnableAllPairs, "enableallpairs", false, "enables all pairs for enabled exchanges") diff --git a/utils/utils.go b/utils/utils.go index 7334aa92..6b7f6b9b 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -16,16 +16,29 @@ var ( ) // AdjustGoMaxProcs sets the runtime GOMAXPROCS val -func AdjustGoMaxProcs(maxProcs int) error { +// Since Go 1.5, Go will use the total number of logical processers that the +// system has available. Caveats to this are if someone has set the GOMAXPROCS +// env var set or wish to limit usage of the number of logical processers +// between a range from 1 to NumCPUs +func AdjustGoMaxProcs(procs int) error { + // Check for default settings, plus respecting GOMAXPROCS env but + // don't allow for values which will cause thread contention n := runtime.NumCPU() - if maxProcs < 0 || maxProcs > n { - maxProcs = n + if procs == runtime.GOMAXPROCS(-1) { + if procs <= n { + return nil + } } - if i := runtime.GOMAXPROCS(maxProcs); i != maxProcs { + // Sanitise the procs value (defaults to NumCPUs) + if procs < 1 || procs > n { + procs = n + } + + runtime.GOMAXPROCS(procs) + if i := runtime.GOMAXPROCS(procs); i != procs { return ErrGoMaxProcsFailure } - return nil } diff --git a/utils/utils_test.go b/utils/utils_test.go index 12189173..8a51da64 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -1,22 +1,57 @@ package utils import ( + "fmt" "runtime" "testing" ) func TestAdjustGoMaxProcs(t *testing.T) { - // Ensure that a supplied crazy number is set to a valid one and doesn't - // return an error - err := AdjustGoMaxProcs(1000) - if err != nil { - t.Fatalf("TestAdjustGoMaxProcs returned err: %v", err) + // Test default settings + curr := runtime.GOMAXPROCS(-1) + numCPUs := runtime.NumCPU() + + // This func both checks for an error of AdjustGoMaxProcs, plus + // ensures that the value it sets is the one that is expected + checker := func(setting, expected int) error { + if err := AdjustGoMaxProcs(setting); err != nil { + return err + } + if i := runtime.GOMAXPROCS(expected); i != expected { + return fmt.Errorf("expected %d, got %d", expected, i) + } + return nil } - // This time use the num of logical CPU's and ensure it doesn't - // return an error - err = AdjustGoMaxProcs(runtime.NumCPU()) - if err != nil { - t.Fatalf("TestAdjustGoMaxProcs returned err: %v", err) + tester := []struct { + Setting int + Expected int + }{ + { + // Test setting to current runtime val + Setting: curr, + Expected: curr, + }, + { + // Test setting to num of logical CPUs + Setting: numCPUs, + Expected: numCPUs, + }, + { + // Test crazy value and make sure it defaults to numCPUs + Setting: 1000, + Expected: numCPUs, + }, + { + // Test another crazy value and make sure it defaults to numCPUs + Setting: -1, + Expected: numCPUs, + }, + } + + for x := range tester { + if err := checker(tester[x].Setting, tester[x].Expected); err != nil { + t.Errorf("%d failed. %s", tester[x], err) + } } } From 62a528a064c8ff927917dede4ed0a9ec3638f670 Mon Sep 17 00:00:00 2001 From: Scott Date: Fri, 4 Oct 2019 16:24:29 +1000 Subject: [PATCH 50/71] Bugfix/improvement: Orderbook/ticker update processing (#364) * Greatly increases efficiency of web socket orderbook processing by minimising multiple map lookups. Changes buffer to use pointers. Ensures orderbook benchmarks are on equal footing and set correctly. Removes data race by not setting waitgroup adds inside go routine causing wait and add to happen simultaneously. Updates ticker and orderbook service to use a pointer var instead of map lookups when setting data. * Removes misguided comment * Removes waitgroups and goroutines for updating bids and asks for non-id orderbook updates via websocket. Updates benchmarks to be consistent --- exchanges/orderbook/orderbook.go | 11 +- exchanges/ticker/ticker.go | 27 ++-- exchanges/websocket/wshandler/wshandler.go | 2 +- .../websocket/wshandler/wshandler_test.go | 3 +- .../websocket/wsorderbook/wsorderbook.go | 129 +++++++++--------- .../websocket/wsorderbook/wsorderbook_test.go | 115 +++++++++++++++- .../wsorderbook/wsorderbook_types.go | 2 +- 7 files changed, 197 insertions(+), 92 deletions(-) diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index 2bdb4567..6396148f 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -95,11 +95,12 @@ func (s *Service) Update(b *Base) error { } default: - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].b.Bids = b.Bids - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].b.Asks = b.Asks - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].b.LastUpdated = b.LastUpdated - ids = s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].Assoc - ids = append(ids, s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].Main) + book := s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] + book.b.Bids = b.Bids + book.b.Asks = b.Asks + book.b.LastUpdated = b.LastUpdated + ids = book.Assoc + ids = append(ids, book.Main) } s.Unlock() return s.mux.Publish(ids, b) diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index 8b77f602..4ff779b6 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -147,19 +147,20 @@ func (s *Service) Update(p *Price) error { } default: - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Last = p.Last - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].High = p.High - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Low = p.Low - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Bid = p.Bid - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Ask = p.Ask - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Volume = p.Volume - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].QuoteVolume = p.QuoteVolume - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].PriceATH = p.PriceATH - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Open = p.Open - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Close = p.Close - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].LastUpdated = p.LastUpdated - ids = s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Assoc - ids = append(ids, s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Main) + ticker := s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] + ticker.Last = p.Last + ticker.High = p.High + ticker.Low = p.Low + ticker.Bid = p.Bid + ticker.Ask = p.Ask + ticker.Volume = p.Volume + ticker.QuoteVolume = p.QuoteVolume + ticker.PriceATH = p.PriceATH + ticker.Open = p.Open + ticker.Close = p.Close + ticker.LastUpdated = p.LastUpdated + ids = ticker.Assoc + ids = append(ids, ticker.Main) } s.Unlock() return s.mux.Publish(ids, p) diff --git a/exchanges/websocket/wshandler/wshandler.go b/exchanges/websocket/wshandler/wshandler.go index 1bc9502d..caa29869 100644 --- a/exchanges/websocket/wshandler/wshandler.go +++ b/exchanges/websocket/wshandler/wshandler.go @@ -92,6 +92,7 @@ func (w *Websocket) Connect() error { go w.connectionMonitor() } if w.SupportsFunctionality(WebsocketSubscribeSupported) || w.SupportsFunctionality(WebsocketUnsubscribeSupported) { + w.Wg.Add(1) go w.manageSubscriptions() } @@ -498,7 +499,6 @@ func (w *Websocket) manageSubscriptions() { w.DataHandler <- fmt.Errorf("%v does not support channel subscriptions, exiting ManageSubscriptions()", w.exchangeName) return } - w.Wg.Add(1) defer func() { if w.verbose { log.Debugf(log.WebsocketMgr, "%v ManageSubscriptions exiting", w.exchangeName) diff --git a/exchanges/websocket/wshandler/wshandler_test.go b/exchanges/websocket/wshandler/wshandler_test.go index 931ec661..dc293cf1 100644 --- a/exchanges/websocket/wshandler/wshandler_test.go +++ b/exchanges/websocket/wshandler/wshandler_test.go @@ -352,12 +352,13 @@ func TestManageSubscriptionsStartStop(t *testing.T) { ShutdownC: make(chan struct{}), Functionality: WebsocketSubscribeSupported | WebsocketUnsubscribeSupported, } + w.Wg.Add(1) go w.manageSubscriptions() close(w.ShutdownC) w.Wg.Wait() } -// TestManageSubscriptionsStartStop logic test +// TestManageSubscriptions logic test func TestManageSubscriptions(t *testing.T) { w := Websocket{ ShutdownC: make(chan struct{}), diff --git a/exchanges/websocket/wsorderbook/wsorderbook.go b/exchanges/websocket/wsorderbook/wsorderbook.go index 2422b921..e24a93ea 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook.go +++ b/exchanges/websocket/wsorderbook/wsorderbook.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "sort" - "sync" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -33,21 +32,22 @@ func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpda } w.m.Lock() defer w.m.Unlock() - if _, ok := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]; !ok { + obLookup, ok := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] + if !ok { return fmt.Errorf("ob.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s", w.exchangeName, orderbookUpdate.CurrencyPair.String(), orderbookUpdate.AssetType) } if w.bufferEnabled { - overBufferLimit := w.processBufferUpdate(orderbookUpdate) + overBufferLimit := w.processBufferUpdate(obLookup, orderbookUpdate) if !overBufferLimit { return nil } } else { - w.processObUpdate(orderbookUpdate) + w.processObUpdate(obLookup, orderbookUpdate) } - err := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Process() + err := obLookup.Process() if err != nil { return err } @@ -58,144 +58,142 @@ func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpda return nil } -func (w *WebsocketOrderbookLocal) processBufferUpdate(orderbookUpdate *WebsocketOrderbookUpdate) bool { +func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) bool { if w.buffer == nil { - w.buffer = make(map[currency.Pair]map[asset.Item][]WebsocketOrderbookUpdate) + w.buffer = make(map[currency.Pair]map[asset.Item][]*WebsocketOrderbookUpdate) } if w.buffer[orderbookUpdate.CurrencyPair] == nil { - w.buffer[orderbookUpdate.CurrencyPair] = make(map[asset.Item][]WebsocketOrderbookUpdate) + w.buffer[orderbookUpdate.CurrencyPair] = make(map[asset.Item][]*WebsocketOrderbookUpdate) } - if len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]) <= w.obBufferLimit { - w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = append(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], *orderbookUpdate) - if len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]) < w.obBufferLimit { + bufferLookup := w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] + if len(bufferLookup) <= w.obBufferLimit { + bufferLookup = append(bufferLookup, orderbookUpdate) + if len(bufferLookup) < w.obBufferLimit { + w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = bufferLookup return false } } if w.sortBuffer { // sort by last updated to ensure each update is in order if w.sortBufferByUpdateIDs { - sort.Slice(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], func(i, j int) bool { - return w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i].UpdateID < w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][j].UpdateID + sort.Slice(bufferLookup, func(i, j int) bool { + return bufferLookup[i].UpdateID < bufferLookup[j].UpdateID }) } else { - sort.Slice(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], func(i, j int) bool { - return w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i].UpdateTime.Before(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][j].UpdateTime) + sort.Slice(bufferLookup, func(i, j int) bool { + return bufferLookup[i].UpdateTime.Before(bufferLookup[j].UpdateTime) }) } } - for i := 0; i < len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]); i++ { - w.processObUpdate(&w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i]) + for i := 0; i < len(bufferLookup); i++ { + w.processObUpdate(o, bufferLookup[i]) } + w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = bufferLookup return true } -func (w *WebsocketOrderbookLocal) processObUpdate(orderbookUpdate *WebsocketOrderbookUpdate) { +func (w *WebsocketOrderbookLocal) processObUpdate(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) { if w.updateEntriesByID { - w.updateByIDAndAction(orderbookUpdate) + w.updateByIDAndAction(o, orderbookUpdate) } else { - var wg sync.WaitGroup - wg.Add(2) - go w.updateAsksByPrice(orderbookUpdate, &wg) - go w.updateBidsByPrice(orderbookUpdate, &wg) - wg.Wait() + w.updateAsksByPrice(o, orderbookUpdate) + w.updateBidsByPrice(o, orderbookUpdate) } } -func (w *WebsocketOrderbookLocal) updateAsksByPrice(base *WebsocketOrderbookUpdate, wg *sync.WaitGroup) { +func (w *WebsocketOrderbookLocal) updateAsksByPrice(o *orderbook.Base, base *WebsocketOrderbookUpdate) { for j := 0; j < len(base.Asks); j++ { found := false - for k := 0; k < len(w.ob[base.CurrencyPair][base.AssetType].Asks); k++ { - if w.ob[base.CurrencyPair][base.AssetType].Asks[k].Price == base.Asks[j].Price { + for k := 0; k < len(o.Asks); k++ { + if o.Asks[k].Price == base.Asks[j].Price { found = true if base.Asks[j].Amount == 0 { - w.ob[base.CurrencyPair][base.AssetType].Asks = append(w.ob[base.CurrencyPair][base.AssetType].Asks[:k], - w.ob[base.CurrencyPair][base.AssetType].Asks[k+1:]...) + o.Asks = append(o.Asks[:k], + o.Asks[k+1:]...) break } - w.ob[base.CurrencyPair][base.AssetType].Asks[k].Amount = base.Asks[j].Amount + o.Asks[k].Amount = base.Asks[j].Amount break } } if !found { - w.ob[base.CurrencyPair][base.AssetType].Asks = append(w.ob[base.CurrencyPair][base.AssetType].Asks, base.Asks[j]) + o.Asks = append(o.Asks, base.Asks[j]) } } - sort.Slice(w.ob[base.CurrencyPair][base.AssetType].Asks, func(i, j int) bool { - return w.ob[base.CurrencyPair][base.AssetType].Asks[i].Price < w.ob[base.CurrencyPair][base.AssetType].Asks[j].Price + sort.Slice(o.Asks, func(i, j int) bool { + return o.Asks[i].Price < o.Asks[j].Price }) - wg.Done() } -func (w *WebsocketOrderbookLocal) updateBidsByPrice(base *WebsocketOrderbookUpdate, wg *sync.WaitGroup) { +func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, base *WebsocketOrderbookUpdate) { for j := 0; j < len(base.Bids); j++ { found := false - for k := 0; k < len(w.ob[base.CurrencyPair][base.AssetType].Bids); k++ { - if w.ob[base.CurrencyPair][base.AssetType].Bids[k].Price == base.Bids[j].Price { + for k := 0; k < len(o.Bids); k++ { + if o.Bids[k].Price == base.Bids[j].Price { found = true if base.Bids[j].Amount == 0 { - w.ob[base.CurrencyPair][base.AssetType].Bids = append(w.ob[base.CurrencyPair][base.AssetType].Bids[:k], - w.ob[base.CurrencyPair][base.AssetType].Bids[k+1:]...) + o.Bids = append(o.Bids[:k], + o.Bids[k+1:]...) break } - w.ob[base.CurrencyPair][base.AssetType].Bids[k].Amount = base.Bids[j].Amount + o.Bids[k].Amount = base.Bids[j].Amount break } } if !found { - w.ob[base.CurrencyPair][base.AssetType].Bids = append(w.ob[base.CurrencyPair][base.AssetType].Bids, base.Bids[j]) + o.Bids = append(o.Bids, base.Bids[j]) } } - sort.Slice(w.ob[base.CurrencyPair][base.AssetType].Bids, func(i, j int) bool { - return w.ob[base.CurrencyPair][base.AssetType].Bids[i].Price > w.ob[base.CurrencyPair][base.AssetType].Bids[j].Price + sort.Slice(o.Bids, func(i, j int) bool { + return o.Bids[i].Price > o.Bids[j].Price }) - wg.Done() } // updateByIDAndAction will receive an action to execute against the orderbook // it will then match by IDs instead of price to perform the action -func (w *WebsocketOrderbookLocal) updateByIDAndAction(orderbookUpdate *WebsocketOrderbookUpdate) { +func (w *WebsocketOrderbookLocal) updateByIDAndAction(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) { switch orderbookUpdate.Action { case "update": for _, target := range orderbookUpdate.Bids { - for i := range w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids { - if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].ID == target.ID { - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].Amount = target.Amount + for i := range o.Bids { + if o.Bids[i].ID == target.ID { + o.Bids[i].Amount = target.Amount break } } } for _, target := range orderbookUpdate.Asks { - for i := range w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks { - if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].ID == target.ID { - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].Amount = target.Amount + for i := range o.Asks { + if o.Asks[i].ID == target.ID { + o.Asks[i].Amount = target.Amount break } } } case "delete": for _, target := range orderbookUpdate.Bids { - for i := 0; i < len(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids); i++ { - if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].ID == target.ID { - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[:i], - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i+1:]...) + for i := 0; i < len(o.Bids); i++ { + if o.Bids[i].ID == target.ID { + o.Bids = append(o.Bids[:i], + o.Bids[i+1:]...) i-- break } } } for _, target := range orderbookUpdate.Asks { - for i := 0; i < len(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks); i++ { - if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].ID == target.ID { - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[:i], - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i+1:]...) + for i := 0; i < len(o.Asks); i++ { + if o.Asks[i].ID == target.ID { + o.Asks = append(o.Asks[:i], + o.Asks[i+1:]...) i-- break } } } case "insert": - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids, orderbookUpdate.Bids...) - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks, orderbookUpdate.Asks...) + o.Bids = append(o.Bids, orderbookUpdate.Bids...) + o.Asks = append(o.Asks, orderbookUpdate.Asks...) } } @@ -227,10 +225,11 @@ func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base) err if w.ob[newOrderbook.Pair] == nil { w.ob[newOrderbook.Pair] = make(map[asset.Item]*orderbook.Base) } - if w.ob[newOrderbook.Pair][newOrderbook.AssetType] != nil && - (len(w.ob[newOrderbook.Pair][newOrderbook.AssetType].Asks) > 0 || - len(w.ob[newOrderbook.Pair][newOrderbook.AssetType].Bids) > 0) { - w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook + fullObLookup := w.ob[newOrderbook.Pair][newOrderbook.AssetType] + if fullObLookup != nil && + (len(fullObLookup.Asks) > 0 || + len(fullObLookup.Bids) > 0) { + fullObLookup = newOrderbook return newOrderbook.Process() } w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook diff --git a/exchanges/websocket/wsorderbook/wsorderbook_test.go b/exchanges/websocket/wsorderbook/wsorderbook_test.go index 8c0c09db..a60fd067 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_test.go +++ b/exchanges/websocket/wsorderbook/wsorderbook_test.go @@ -43,6 +43,61 @@ func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, b return } +func bidAskGenerator() []orderbook.Item { + var response []orderbook.Item + randIterator := 100 + for i := 0; i < randIterator; i++ { + price := float64(rand.Intn(1000)) + if price == 0 { + price = 1 + } + response = append(response, orderbook.Item{ + Amount: float64(rand.Intn(1)), + Price: price, + ID: int64(i), + }) + } + return response +} + +func BenchmarkUpdateBidsByPrice(b *testing.B) { + ob, curr, _, _, err := createSnapshot() + if err != nil { + b.Error(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + bidAsks := bidAskGenerator() + update := &WebsocketOrderbookUpdate{ + Bids: bidAsks, + Asks: bidAsks, + CurrencyPair: curr, + UpdateTime: time.Now(), + AssetType: asset.Spot, + } + ob.updateBidsByPrice(ob.ob[curr][asset.Spot], update) + } +} + +func BenchmarkUpdateAsksByPrice(b *testing.B) { + ob, curr, _, _, err := createSnapshot() + if err != nil { + b.Error(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + bidAsks := bidAskGenerator() + update := &WebsocketOrderbookUpdate{ + Bids: bidAsks, + Asks: bidAsks, + CurrencyPair: curr, + UpdateTime: time.Now(), + AssetType: asset.Spot, + } + ob.updateAsksByPrice(ob.ob[curr][asset.Spot], update) + } +} + // BenchmarkBufferPerformance demonstrates buffer more performant than multi // process calls func BenchmarkBufferPerformance(b *testing.B) { @@ -50,6 +105,42 @@ func BenchmarkBufferPerformance(b *testing.B) { if err != nil { b.Fatal(err) } + obl.bufferEnabled = true + cp := currency.NewPairFromString("BTCUSD") + // This is to ensure we do not send in zero orderbook info to our main book + // in orderbook.go, orderbooks should not be zero even after an update. + dummyItem := orderbook.Item{ + Amount: 1333337, + Price: 1337.1337, + ID: 1337, + } + obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) + update := &WebsocketOrderbookUpdate{ + Bids: bids, + Asks: asks, + CurrencyPair: curr, + UpdateTime: time.Now(), + AssetType: asset.Spot, + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + randomIndex := rand.Intn(4) + update.Asks = itemArray[randomIndex] + update.Bids = itemArray[randomIndex] + err = obl.Update(update) + if err != nil { + b.Fatal(err) + } + } +} + +// BenchmarkBufferSortingPerformance benchmark +func BenchmarkBufferSortingPerformance(b *testing.B) { + obl, curr, asks, bids, err := createSnapshot() + if err != nil { + b.Fatal(err) + } + obl.bufferEnabled = true obl.sortBuffer = true cp := currency.NewPairFromString("BTCUSD") // This is to ensure we do not send in zero orderbook info to our main book @@ -67,8 +158,9 @@ func BenchmarkBufferPerformance(b *testing.B) { UpdateTime: time.Now(), AssetType: asset.Spot, } + b.ResetTimer() for i := 0; i < b.N; i++ { - randomIndex := rand.Intn(5) + randomIndex := rand.Intn(4) update.Asks = itemArray[randomIndex] update.Bids = itemArray[randomIndex] err = obl.Update(update) @@ -79,14 +171,23 @@ func BenchmarkBufferPerformance(b *testing.B) { } // BenchmarkBufferSortingPerformance benchmark -func BenchmarkBufferSortingPerformance(b *testing.B) { +func BenchmarkBufferSortingByIDPerformance(b *testing.B) { obl, curr, asks, bids, err := createSnapshot() if err != nil { b.Fatal(err) } - obl.sortBuffer = true obl.bufferEnabled = true - obl.obBufferLimit = 5 + obl.sortBuffer = true + obl.sortBufferByUpdateIDs = true + cp := currency.NewPairFromString("BTCUSD") + // This is to ensure we do not send in zero orderbook info to our main book + // in orderbook.go, orderbooks should not be zero even after an update. + dummyItem := orderbook.Item{ + Amount: 1333337, + Price: 1337.1337, + ID: 1337, + } + obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) update := &WebsocketOrderbookUpdate{ Bids: bids, Asks: asks, @@ -94,8 +195,9 @@ func BenchmarkBufferSortingPerformance(b *testing.B) { UpdateTime: time.Now(), AssetType: asset.Spot, } + b.ResetTimer() for i := 0; i < b.N; i++ { - randomIndex := rand.Intn(5) + randomIndex := rand.Intn(4) update.Asks = itemArray[randomIndex] update.Bids = itemArray[randomIndex] err = obl.Update(update) @@ -128,8 +230,9 @@ func BenchmarkNoBufferPerformance(b *testing.B) { UpdateTime: time.Now(), AssetType: asset.Spot, } + b.ResetTimer() for i := 0; i < b.N; i++ { - randomIndex := rand.Intn(5) + randomIndex := rand.Intn(4) update.Asks = itemArray[randomIndex] update.Bids = itemArray[randomIndex] err = obl.Update(update) diff --git a/exchanges/websocket/wsorderbook/wsorderbook_types.go b/exchanges/websocket/wsorderbook/wsorderbook_types.go index 8fa4f35c..6766c797 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_types.go +++ b/exchanges/websocket/wsorderbook/wsorderbook_types.go @@ -13,7 +13,7 @@ import ( // appending and deleting changes and updates the main store in wsorderbook.go type WebsocketOrderbookLocal struct { ob map[currency.Pair]map[asset.Item]*orderbook.Base - buffer map[currency.Pair]map[asset.Item][]WebsocketOrderbookUpdate + buffer map[currency.Pair]map[asset.Item][]*WebsocketOrderbookUpdate obBufferLimit int bufferEnabled bool sortBuffer bool From 2a13551dd1a9d1caf138ac5ffcabafd178a5dfd6 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 4 Oct 2019 19:31:04 +1000 Subject: [PATCH 51/71] Minor QA changes 1) Add ability to enable all pairs using the new pair manager 2) Add abiity to enable/disable database subsystem via gRPC 3) Fix spelling mistakes --- engine/exchange.go | 8 +- engine/helpers.go | 6 + engine/rpcserver.go | 9 +- exchanges/zb/zb_websocket_types.go | 2 +- gctrpc/rpc.pb.go | 340 +++---- gctrpc/rpc.pb.gw.go | 1502 +++++++++++++++++++++++++++- gctrpc/rpc.proto | 6 +- gctrpc/rpc.swagger.json | 100 +- go.mod | 9 +- go.sum | 11 + utils/utils.go | 4 +- utils/utils_test.go | 2 +- 12 files changed, 1760 insertions(+), 239 deletions(-) diff --git a/engine/exchange.go b/engine/exchange.go index 5da34cdf..f52e9214 100644 --- a/engine/exchange.go +++ b/engine/exchange.go @@ -200,7 +200,13 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { } if Bot.Settings.EnableAllPairs { - exchCfg.EnabledPairs = exchCfg.AvailablePairs + if exchCfg.CurrencyPairs != nil { + assets := exchCfg.CurrencyPairs.GetAssetTypes() + for x := range assets { + pairs := exchCfg.CurrencyPairs.GetPairs(assets[x], false) + exchCfg.CurrencyPairs.StorePairs(assets[x], pairs, true) + } + } } if Bot.Settings.EnableExchangeVerbose { diff --git a/engine/helpers.go b/engine/helpers.go index 65b9822a..4ef42aa5 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -38,6 +38,7 @@ func GetSubsystemsStatus() map[string]bool { systems["orders"] = Bot.OrderManager.Started() systems["portfolio"] = Bot.PortfolioManager.Started() systems["ntp_timekeeper"] = Bot.NTPManager.Started() + systems["database"] = Bot.DatabaseManager.Started() systems["exchange_syncer"] = Bot.Settings.EnableExchangeSyncManager systems["grpc"] = Bot.Settings.EnableGRPC systems["grpc_proxy"] = Bot.Settings.EnableGRPCProxy @@ -103,6 +104,11 @@ func SetSubsystem(subsys string, enable bool) error { return Bot.NTPManager.Start() } return Bot.NTPManager.Stop() + case "database": + if enable { + return Bot.DatabaseManager.Start() + } + return Bot.DatabaseManager.Stop() case "exchange_syncer": if enable { Bot.ExchangeCurrencyPairManager.Start() diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 4e6cd048..160660a4 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -116,9 +116,6 @@ func StartRPCServer() { // StartRPCRESTProxy starts a gRPC proxy func StartRPCRESTProxy() { log.Debugf(log.GRPCSys, "gRPC proxy server support enabled. Starting gRPC proxy server on http://%v.\n", Bot.Config.RemoteControl.GRPC.GRPCProxyListenAddress) - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - defer cancel() targetDir := utils.GetTLSDir(Bot.Settings.DataDir) creds, err := credentials.NewClientTLSFromFile(filepath.Join(targetDir, "cert.pem"), "") @@ -134,9 +131,11 @@ func StartRPCRESTProxy() { Password: Bot.Config.RemoteControl.Password, }), } - err = gctrpc.RegisterGoCryptoTraderHandlerFromEndpoint(ctx, mux, Bot.Config.RemoteControl.GRPC.ListenAddress, opts) + err = gctrpc.RegisterGoCryptoTraderHandlerFromEndpoint(context.Background(), + mux, Bot.Config.RemoteControl.GRPC.ListenAddress, opts) if err != nil { log.Errorf(log.GRPCSys, "Failed to register gRPC proxy. Err: %s\n", err) + return } go func() { @@ -568,7 +567,7 @@ func (s *RPCServer) GetForexProviders(ctx context.Context, r *gctrpc.GetForexPro Name: providers[x].Name, Enabled: providers[x].Enabled, Verbose: providers[x].Verbose, - RestRollingDelay: providers[x].RESTPollingDelay.String(), + RestPollingDelay: providers[x].RESTPollingDelay.String(), ApiKey: providers[x].APIKey, ApiKeyLevel: int64(providers[x].APIKeyLvl), PrimaryProvider: providers[x].PrimaryProvider, diff --git a/exchanges/zb/zb_websocket_types.go b/exchanges/zb/zb_websocket_types.go index ea95b9bf..4faef0a0 100644 --- a/exchanges/zb/zb_websocket_types.go +++ b/exchanges/zb/zb_websocket_types.go @@ -77,7 +77,7 @@ type WsAddSubUserRequest struct { Memo string `json:"memo"` Password string `json:"password"` SubUserName string `json:"subUserName"` - No int64 `json:"no,string,omtempty"` + No int64 `json:"no,string,omitempty"` Sign string `json:"sign,omitempty"` } diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index b44758ea..470ea91c 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -2566,7 +2566,7 @@ type ForexProvider struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` Verbose bool `protobuf:"varint,3,opt,name=verbose,proto3" json:"verbose,omitempty"` - RestRollingDelay string `protobuf:"bytes,4,opt,name=rest_rolling_delay,json=restRollingDelay,proto3" json:"rest_rolling_delay,omitempty"` + RestPollingDelay string `protobuf:"bytes,4,opt,name=rest_polling_delay,json=restPollingDelay,proto3" json:"rest_polling_delay,omitempty"` ApiKey string `protobuf:"bytes,5,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` ApiKeyLevel int64 `protobuf:"varint,6,opt,name=api_key_level,json=apiKeyLevel,proto3" json:"api_key_level,omitempty"` PrimaryProvider bool `protobuf:"varint,7,opt,name=primary_provider,json=primaryProvider,proto3" json:"primary_provider,omitempty"` @@ -2621,9 +2621,9 @@ func (m *ForexProvider) GetVerbose() bool { return false } -func (m *ForexProvider) GetRestRollingDelay() string { +func (m *ForexProvider) GetRestPollingDelay() string { if m != nil { - return m.RestRollingDelay + return m.RestPollingDelay } return "" } @@ -5020,12 +5020,12 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 4676 bytes of a gzipped FileDescriptorProto + // 4673 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3c, 0x4d, 0x6f, 0x1c, 0x57, 0x72, 0xe8, 0x21, 0x45, 0x72, 0x6a, 0x86, 0xe4, 0xf0, 0xf1, 0x6b, 0x34, 0x24, 0x45, 0xa9, 0xb5, 0x92, 0x25, 0xad, 0x4d, 0xd9, 0xb2, 0x92, 0x75, 0xd6, 0xce, 0x6e, 0x68, 0x5a, 0xe6, 0x2a, 0xf6, 0x5a, 0x4c, 0x53, 0x2b, 0x01, 0xde, 0xc0, 0x93, 0xe6, 0xf4, 0xe3, 0xb0, 0xa3, 0x9e, 0xee, 0x76, - 0x77, 0x0f, 0x29, 0x3a, 0x09, 0xb2, 0x30, 0x90, 0x20, 0x08, 0x82, 0xe4, 0xb0, 0x08, 0x90, 0x43, + 0x77, 0x0f, 0x29, 0x3a, 0x09, 0xb2, 0x30, 0x90, 0x20, 0x01, 0x82, 0xe4, 0xb0, 0x08, 0x90, 0x43, 0x4e, 0x39, 0x06, 0xc8, 0x25, 0xc8, 0x29, 0x87, 0x45, 0xae, 0x41, 0x8e, 0xb9, 0xe4, 0x07, 0x04, 0xb9, 0x25, 0x01, 0x02, 0xe4, 0x92, 0x53, 0xf0, 0xea, 0x7d, 0xf4, 0x7b, 0xdd, 0x3d, 0xc3, 0xe1, 0xae, 0xac, 0xbd, 0xd8, 0xd3, 0xf5, 0xea, 0x55, 0xd5, 0xab, 0x57, 0xaf, 0x5e, 0x55, 0xbd, 0xa2, @@ -5149,171 +5149,171 @@ var fileDescriptor_77a6da22d6a3feb1 = []byte{ 0x05, 0x9d, 0x5d, 0xcf, 0x2b, 0x39, 0xa7, 0xbc, 0x48, 0xf0, 0xba, 0x5d, 0xee, 0x16, 0x6c, 0x54, 0x0a, 0x24, 0xaa, 0x19, 0x2f, 0x61, 0xcb, 0xa1, 0x83, 0xe8, 0x94, 0xbe, 0x6e, 0x91, 0xed, 0xeb, 0x70, 0x6d, 0x14, 0x67, 0x21, 0x1b, 0x96, 0xf7, 0xcc, 0xf2, 0xb8, 0x0a, 0x8c, 0xfe, 0xd3, 0x82, - 0x79, 0xb3, 0x70, 0xfe, 0xaa, 0x72, 0xf1, 0x37, 0x81, 0x24, 0x34, 0xcd, 0xba, 0x49, 0x14, 0x04, - 0x2c, 0x25, 0xf7, 0x68, 0xe0, 0x9e, 0x8b, 0x92, 0x7d, 0x8b, 0x8d, 0x38, 0x7c, 0xe0, 0x23, 0x06, - 0x27, 0xeb, 0x30, 0xeb, 0xc6, 0x7e, 0x97, 0x59, 0x0d, 0xcf, 0xc7, 0x67, 0xdc, 0xd8, 0xff, 0x84, - 0x9e, 0x13, 0x1b, 0xe6, 0xc5, 0x40, 0x37, 0xa0, 0xa7, 0x34, 0xc0, 0x98, 0x6f, 0xca, 0x69, 0xf0, - 0xe1, 0x4f, 0x19, 0x88, 0xdc, 0x85, 0x56, 0x9c, 0xf8, 0xcc, 0xfc, 0xf2, 0xb7, 0x81, 0x59, 0x94, - 0x66, 0x51, 0xc0, 0xe5, 0xea, 0xec, 0x1f, 0xc3, 0xd5, 0x0a, 0x5d, 0x08, 0x1f, 0xf5, 0x3d, 0x58, - 0x34, 0x5f, 0x18, 0xa4, 0x9f, 0x52, 0x51, 0xab, 0x31, 0xd1, 0x59, 0x38, 0x36, 0xe8, 0x88, 0xe8, - 0x13, 0x71, 0x1c, 0x37, 0x53, 0x35, 0x2d, 0xfb, 0x4b, 0x58, 0xc9, 0x81, 0x7b, 0x51, 0x78, 0x4a, - 0x93, 0x94, 0x59, 0x1b, 0x81, 0xe9, 0xe3, 0x24, 0x92, 0x05, 0x59, 0xfc, 0xcd, 0xe2, 0xb6, 0x2c, - 0x12, 0x66, 0x50, 0xcb, 0x22, 0x86, 0x93, 0xb8, 0x99, 0xbc, 0xa5, 0xf0, 0x37, 0x8b, 0x93, 0x7d, - 0x24, 0x42, 0xbb, 0x38, 0xc6, 0x4d, 0xb5, 0x21, 0x60, 0x8c, 0x8b, 0xfd, 0x0c, 0xc3, 0x47, 0x5d, - 0x14, 0xb1, 0xc6, 0x5f, 0x87, 0x06, 0x5f, 0x23, 0x9b, 0x29, 0xd7, 0xb7, 0x69, 0xac, 0xaf, 0x20, - 0xa6, 0x03, 0xc7, 0x0a, 0x6a, 0xff, 0x77, 0x0d, 0x9a, 0x18, 0xb1, 0x7e, 0x44, 0x33, 0xd7, 0x0f, - 0xc6, 0xc7, 0xd2, 0x3c, 0x06, 0xad, 0xa9, 0x18, 0xf4, 0x26, 0xcc, 0xeb, 0x05, 0x91, 0x73, 0x99, - 0xcc, 0x6a, 0xe5, 0x90, 0x73, 0x72, 0x0b, 0x16, 0x30, 0xb5, 0xce, 0xb1, 0xb8, 0xcd, 0xcc, 0x23, - 0x54, 0xa1, 0x99, 0x89, 0xc0, 0x95, 0x42, 0x22, 0xc0, 0x86, 0x31, 0x98, 0xee, 0xa6, 0xbe, 0xa7, - 0xf2, 0x04, 0x84, 0x1c, 0xfa, 0x9e, 0x36, 0x8c, 0xb3, 0x67, 0xb5, 0x61, 0x9c, 0xcd, 0x72, 0xa0, - 0x84, 0xf2, 0x87, 0x02, 0x7c, 0xef, 0x9a, 0x43, 0xa3, 0x6b, 0x4a, 0xe0, 0x53, 0x7f, 0x80, 0xaf, - 0x61, 0xa2, 0xb8, 0x5d, 0xe7, 0x16, 0xcb, 0xbf, 0xf2, 0x34, 0x0d, 0xf4, 0x34, 0x2d, 0x4f, 0xea, - 0x1a, 0x46, 0x52, 0xb7, 0x0d, 0x8d, 0x28, 0xa6, 0x61, 0x57, 0xa4, 0xd8, 0x4d, 0x1e, 0x3d, 0x30, - 0xd0, 0x33, 0x84, 0x88, 0x92, 0x09, 0xea, 0x3c, 0x9d, 0x24, 0x2f, 0x35, 0x15, 0x53, 0x2b, 0x2a, - 0x46, 0x26, 0x82, 0x53, 0x17, 0x25, 0x82, 0xf6, 0x2e, 0x46, 0xc5, 0x92, 0xb1, 0x30, 0x9f, 0x37, - 0x61, 0x06, 0xd5, 0x24, 0x2d, 0x67, 0xc5, 0x48, 0x63, 0x84, 0x51, 0x38, 0x02, 0xc7, 0xfe, 0x01, - 0xbe, 0x21, 0xe2, 0xd0, 0x24, 0xa2, 0x5f, 0x85, 0x39, 0xbe, 0x2b, 0xca, 0x6a, 0x66, 0xf1, 0xfb, - 0xb1, 0x67, 0xff, 0x9b, 0x05, 0xe4, 0x70, 0x78, 0x34, 0xf0, 0x27, 0xa7, 0x36, 0x79, 0x82, 0x4e, - 0x60, 0x1a, 0xcd, 0x84, 0x9b, 0x23, 0xfe, 0x2e, 0x58, 0xc8, 0x74, 0xd1, 0x42, 0xf2, 0xed, 0xbc, - 0x52, 0x9d, 0xa3, 0xcf, 0xe8, 0x9b, 0xcf, 0x5c, 0x7c, 0xe0, 0xd3, 0x30, 0xeb, 0x8a, 0x62, 0x0b, - 0x73, 0xf1, 0x08, 0x78, 0xec, 0xd9, 0x87, 0xb0, 0x6c, 0xac, 0x4c, 0x68, 0xfa, 0x06, 0x34, 0xb9, - 0x00, 0x71, 0xe0, 0xf6, 0x54, 0x35, 0xbc, 0x81, 0xb0, 0x03, 0x04, 0x8d, 0xd3, 0xd7, 0x9f, 0x58, - 0xb0, 0x72, 0xe8, 0x0f, 0x86, 0x81, 0x9b, 0xd1, 0x6f, 0x40, 0x63, 0xf9, 0xf2, 0xa7, 0x8c, 0xe5, - 0x4b, 0x4d, 0x4e, 0xe7, 0x9a, 0xb4, 0xff, 0xc7, 0x82, 0xd5, 0x82, 0x28, 0x2a, 0x26, 0x34, 0x8d, - 0x69, 0x44, 0x71, 0x40, 0x20, 0x69, 0x4c, 0x6b, 0x06, 0xd3, 0x9b, 0x30, 0x3f, 0xf0, 0x43, 0x7f, - 0x30, 0x1c, 0x74, 0xb9, 0xee, 0xb9, 0x4c, 0x4d, 0x01, 0x3c, 0xc0, 0x2d, 0x60, 0x48, 0xee, 0x4b, - 0x0d, 0x69, 0x5a, 0x20, 0x71, 0x20, 0x47, 0x7a, 0x1b, 0x56, 0xf2, 0xb8, 0xbd, 0xdb, 0x77, 0xfd, - 0xb0, 0x1b, 0x44, 0x69, 0x2a, 0xf6, 0x98, 0xe4, 0x63, 0xfb, 0xae, 0x1f, 0x7e, 0x1a, 0xa5, 0xa9, - 0xe6, 0x04, 0x66, 0x74, 0x27, 0xc0, 0x02, 0x98, 0xd6, 0xf3, 0x13, 0x37, 0xa0, 0x1f, 0x46, 0x83, - 0xa3, 0x57, 0xab, 0xfb, 0x1b, 0xd0, 0xe4, 0x75, 0xb7, 0xcc, 0x4d, 0xfa, 0x54, 0xee, 0x40, 0x03, - 0x61, 0x4f, 0x11, 0x54, 0xb9, 0x0d, 0xff, 0x65, 0x01, 0xd9, 0x63, 0xa1, 0x4c, 0x30, 0xb1, 0x3d, - 0x30, 0x57, 0xc2, 0xf3, 0xe6, 0xdc, 0xc2, 0xea, 0x02, 0xf2, 0xd8, 0x34, 0xbf, 0x29, 0xc3, 0xfc, - 0xd4, 0x6a, 0xa6, 0x2f, 0x59, 0x1c, 0x2b, 0xf9, 0xf1, 0x5b, 0xb0, 0x70, 0xe6, 0x06, 0x01, 0xcd, - 0xd4, 0x13, 0x9b, 0xa8, 0xc4, 0x73, 0xa8, 0xcc, 0xc1, 0xe5, 0x82, 0x67, 0xb5, 0x05, 0xaf, 0xc2, - 0xb2, 0xb1, 0x5e, 0x11, 0x0d, 0x3d, 0x84, 0x35, 0x0e, 0xde, 0x0d, 0x82, 0x89, 0xbd, 0xaa, 0xfd, - 0xd7, 0x35, 0x58, 0x2f, 0x4d, 0x53, 0x61, 0x83, 0x69, 0xc6, 0xb7, 0xd5, 0x72, 0xab, 0x27, 0xec, - 0x88, 0x4f, 0x31, 0xab, 0xf3, 0x4f, 0x16, 0xcc, 0x70, 0xd0, 0xd8, 0xdd, 0xf8, 0x5c, 0x3a, 0x04, - 0x61, 0x70, 0x3c, 0x23, 0xfa, 0xce, 0x64, 0xcc, 0xf8, 0xff, 0xf4, 0x67, 0x55, 0xee, 0x49, 0xc4, - 0x8b, 0xea, 0xf7, 0xa0, 0x55, 0x44, 0xb8, 0xd4, 0x93, 0x13, 0xaf, 0xaa, 0x3c, 0x3a, 0xa5, 0xda, - 0x33, 0xea, 0xcf, 0x2c, 0x58, 0xdc, 0x8b, 0x42, 0xcf, 0x67, 0x37, 0xe6, 0x81, 0x9b, 0xb8, 0x83, - 0x54, 0xbc, 0xe4, 0x73, 0x90, 0x2c, 0xbb, 0x2b, 0xc0, 0x88, 0x02, 0xe7, 0x16, 0x40, 0xef, 0x84, - 0xf6, 0x5e, 0x74, 0x45, 0xc5, 0x91, 0x3f, 0xff, 0x33, 0xc8, 0x87, 0xbe, 0x97, 0x92, 0xb7, 0x60, - 0x39, 0x1f, 0xee, 0xba, 0xa1, 0xd7, 0x15, 0xe5, 0x46, 0x7c, 0xdd, 0x50, 0x78, 0xbb, 0xa1, 0xb7, - 0x9b, 0xbe, 0x48, 0x59, 0xac, 0xa8, 0xaa, 0x6c, 0x5d, 0xc3, 0x85, 0x2f, 0x2a, 0xf8, 0x2e, 0x82, - 0xed, 0xff, 0xb5, 0xf0, 0x06, 0x94, 0xab, 0x12, 0xbb, 0x9d, 0x17, 0xd6, 0xb0, 0xde, 0x6a, 0x6c, - 0x59, 0xad, 0xb0, 0x65, 0x04, 0xa6, 0xfd, 0x8c, 0x0e, 0xe4, 0xc5, 0xc2, 0x7e, 0x93, 0x0f, 0xa1, - 0xa5, 0x56, 0xdc, 0x8d, 0x51, 0x2d, 0xe2, 0x98, 0xac, 0xe7, 0x89, 0xa3, 0xa1, 0x35, 0x67, 0xb1, - 0x57, 0x50, 0xa3, 0x3c, 0x5e, 0x57, 0x26, 0x72, 0xd4, 0x3d, 0xd4, 0xb6, 0xf0, 0x4f, 0xfc, 0x8b, - 0x4b, 0x4d, 0x7b, 0xc3, 0x8c, 0x7a, 0x22, 0x54, 0x56, 0xdf, 0xf6, 0x7f, 0x58, 0xb0, 0xb8, 0xeb, - 0x79, 0xb8, 0xee, 0x49, 0xdc, 0x84, 0x5c, 0x65, 0xed, 0x82, 0x55, 0x4e, 0xfd, 0x9c, 0xab, 0xfc, - 0x85, 0x9d, 0xc8, 0x08, 0x25, 0xd8, 0x36, 0xb4, 0xf2, 0x75, 0x56, 0x6f, 0xaf, 0xfd, 0x2d, 0x20, - 0x3c, 0xbd, 0x32, 0xd4, 0x51, 0xc4, 0x5a, 0x85, 0x65, 0x03, 0x4b, 0xf8, 0x9a, 0x8f, 0xe1, 0xce, - 0x3e, 0xcd, 0xf6, 0x92, 0xf3, 0x38, 0x8b, 0x64, 0x38, 0xfb, 0x11, 0x8d, 0xa3, 0xd4, 0x97, 0x9e, - 0x8b, 0x4e, 0xe4, 0x7d, 0xfe, 0xd9, 0x82, 0xbb, 0x13, 0x10, 0x12, 0x4b, 0xf8, 0xa2, 0x5c, 0x5f, - 0xfa, 0x0d, 0xbd, 0xbd, 0x65, 0x22, 0x2a, 0x3b, 0x0a, 0x22, 0xba, 0x0c, 0x14, 0xc9, 0xce, 0x07, - 0xb0, 0x60, 0x0e, 0x5e, 0xca, 0x55, 0x04, 0x70, 0xfb, 0x02, 0x21, 0x26, 0xb1, 0xb9, 0xdb, 0xb0, - 0xd0, 0x33, 0x48, 0x08, 0x46, 0x05, 0xa8, 0xbd, 0x07, 0x6f, 0x5c, 0xc8, 0x4d, 0xa8, 0x6d, 0x64, - 0x86, 0x6e, 0xff, 0xdd, 0x34, 0xac, 0x3f, 0xf7, 0xb3, 0x13, 0x2f, 0x71, 0xcf, 0xa4, 0xf5, 0x4d, - 0x22, 0x64, 0x21, 0x79, 0xaf, 0x95, 0xeb, 0x0d, 0xf7, 0x60, 0x29, 0x0a, 0x29, 0xe6, 0x18, 0xdd, - 0xd8, 0x4d, 0xd3, 0xb3, 0x28, 0x91, 0x77, 0xe9, 0x62, 0x14, 0x52, 0x96, 0x67, 0x1c, 0x08, 0x70, - 0xe1, 0x36, 0x9e, 0x2e, 0xde, 0xc6, 0x2d, 0x98, 0x8a, 0xfd, 0x50, 0xbc, 0x99, 0xb0, 0x9f, 0xec, - 0xee, 0xcc, 0x12, 0xd7, 0xd3, 0x28, 0x8b, 0xbb, 0x13, 0xa1, 0x8a, 0xae, 0x5e, 0xc5, 0x9f, 0x2d, - 0x54, 0xf1, 0x35, 0x9d, 0xcc, 0x99, 0x55, 0x8b, 0x6d, 0x68, 0x88, 0x9f, 0xdd, 0xcc, 0xed, 0x8b, - 0x14, 0x08, 0x04, 0xe8, 0xa9, 0xdb, 0xd7, 0xa2, 0x35, 0x30, 0xa2, 0xb5, 0x2d, 0x80, 0x63, 0x4a, - 0xbb, 0x46, 0x32, 0x54, 0x3f, 0xa6, 0x94, 0x3b, 0x5d, 0x16, 0x2a, 0x1f, 0xb9, 0xe1, 0x8b, 0x2e, - 0xd6, 0x20, 0x9a, 0x5c, 0x1c, 0x06, 0xf8, 0xcc, 0x1d, 0x60, 0x4c, 0x8c, 0x83, 0x52, 0xa6, 0x79, - 0xae, 0x51, 0x06, 0xdb, 0xcd, 0xab, 0x29, 0x88, 0xd2, 0xf3, 0xb3, 0xf3, 0xf6, 0x42, 0x3e, 0x7f, - 0xcf, 0xcf, 0xce, 0xd5, 0x7c, 0xd4, 0x59, 0x72, 0xde, 0x5e, 0xcc, 0xe7, 0xef, 0x71, 0x10, 0x13, - 0x2f, 0x3d, 0xf3, 0x8f, 0x29, 0x6f, 0x0c, 0x69, 0x89, 0x56, 0x29, 0x06, 0xd9, 0x8b, 0x3c, 0x0c, - 0x23, 0xcf, 0xfc, 0x44, 0x4b, 0x4e, 0x97, 0x78, 0x0a, 0xcb, 0x80, 0xd2, 0x34, 0xec, 0x7b, 0xd0, - 0x92, 0xe6, 0xa2, 0xf7, 0x4e, 0x26, 0x34, 0x1d, 0x06, 0x99, 0xec, 0x9d, 0xe4, 0x5f, 0xf6, 0x3b, - 0xd8, 0x15, 0xf1, 0x69, 0xd4, 0xef, 0xe7, 0xe9, 0x93, 0x30, 0xad, 0x35, 0x98, 0x09, 0x10, 0x2e, - 0xa7, 0xf0, 0x2f, 0x3b, 0xc4, 0x7a, 0x4e, 0x61, 0x4a, 0xfe, 0x6a, 0xe1, 0x87, 0xc7, 0x91, 0xc8, - 0x16, 0xf0, 0x37, 0x3b, 0x8b, 0x1e, 0x3d, 0x1a, 0xf6, 0x65, 0x0f, 0x14, 0x7e, 0x30, 0xcc, 0x33, - 0x37, 0x09, 0xc5, 0x85, 0x8a, 0xbf, 0x19, 0x26, 0x4d, 0x92, 0x28, 0x11, 0xb7, 0x27, 0xff, 0xb0, - 0xf7, 0x61, 0xfd, 0xf0, 0x72, 0x22, 0x32, 0x42, 0xbc, 0x5a, 0x23, 0x8e, 0x3f, 0x7e, 0xd8, 0x9f, - 0x18, 0x1d, 0x20, 0xd8, 0x25, 0x30, 0xc9, 0x31, 0x5a, 0x81, 0x2b, 0xe8, 0xcb, 0x25, 0x31, 0xfc, - 0x60, 0x19, 0x61, 0xbb, 0x4c, 0x4d, 0xf5, 0xa0, 0x95, 0x3b, 0x2a, 0xb8, 0x27, 0xfc, 0x95, 0x8a, - 0x8e, 0x0a, 0x63, 0xee, 0x64, 0x2d, 0x15, 0xdf, 0x68, 0x97, 0xc4, 0x57, 0xb0, 0xac, 0x8b, 0xf6, - 0x5a, 0xb3, 0xfe, 0x9f, 0x58, 0x58, 0x21, 0x53, 0x19, 0xd8, 0x61, 0x96, 0x50, 0x77, 0xf0, 0x5a, - 0x1f, 0xc4, 0xbf, 0x0f, 0x37, 0xf4, 0x7e, 0xa9, 0x4b, 0x4b, 0x62, 0xff, 0x01, 0x3e, 0x23, 0xf2, - 0x47, 0xfe, 0x5f, 0x82, 0xfc, 0x1f, 0xc0, 0x35, 0x4d, 0xfe, 0x4b, 0x8a, 0xf1, 0xe0, 0x4f, 0xef, - 0xc1, 0xc2, 0x7e, 0xc4, 0x6f, 0xac, 0xa7, 0xcc, 0x51, 0x27, 0xe4, 0x09, 0xcc, 0x8a, 0x0e, 0x61, - 0xb2, 0x56, 0x6a, 0x19, 0x46, 0x8a, 0x9d, 0xf5, 0x11, 0xad, 0xc4, 0xf6, 0xf2, 0xd7, 0xff, 0xfa, - 0xef, 0x3f, 0xad, 0xcd, 0x93, 0xc6, 0xfd, 0xd3, 0x77, 0xee, 0xf7, 0x69, 0x86, 0x1e, 0xe1, 0x04, - 0xe6, 0x8d, 0xa6, 0x4e, 0xb2, 0x69, 0x34, 0x66, 0x16, 0x7a, 0x3d, 0x3b, 0x5b, 0x63, 0xdb, 0x36, - 0xed, 0x0e, 0xb2, 0x58, 0x21, 0x44, 0xb0, 0x48, 0x11, 0x85, 0x13, 0xfe, 0x12, 0x16, 0x1f, 0x61, - 0xa9, 0x58, 0x51, 0x25, 0xdb, 0x39, 0xb5, 0xca, 0x66, 0xd5, 0xce, 0xf5, 0xd1, 0x08, 0x82, 0xe3, - 0x06, 0x72, 0x5c, 0x25, 0xcb, 0x8c, 0x23, 0x2f, 0x45, 0xab, 0x26, 0x51, 0x92, 0x42, 0x4b, 0xb4, - 0xbf, 0xbd, 0x52, 0x9e, 0x9b, 0xc8, 0x73, 0x8d, 0xac, 0x30, 0x9e, 0x1e, 0x67, 0x90, 0x33, 0x8d, - 0xb0, 0xd2, 0xa5, 0xb7, 0x6b, 0x92, 0x6b, 0x23, 0xfb, 0x38, 0x39, 0xcb, 0xed, 0x0b, 0xfa, 0x3c, - 0xcd, 0x55, 0xf6, 0x29, 0xc3, 0x55, 0xad, 0x9e, 0xe4, 0xa7, 0xdc, 0xfd, 0x55, 0x36, 0x16, 0x93, - 0x37, 0x2e, 0xee, 0x66, 0xe6, 0x32, 0xdc, 0x99, 0xb4, 0xed, 0xd9, 0xfe, 0x16, 0x0a, 0x73, 0x8d, - 0x6c, 0x0a, 0x61, 0x8c, 0x56, 0x67, 0xd9, 0x4c, 0x4d, 0x7a, 0xd0, 0xd4, 0x7b, 0x34, 0xc9, 0x46, - 0x85, 0xb7, 0x55, 0xcc, 0x37, 0xab, 0x07, 0x05, 0xc3, 0x36, 0x32, 0x24, 0xa4, 0x25, 0x18, 0xaa, - 0x96, 0x4e, 0xf2, 0x15, 0x2c, 0x16, 0xfa, 0x1b, 0x89, 0x5d, 0xd8, 0xbe, 0x8a, 0x5e, 0xd5, 0xce, - 0xcd, 0xb1, 0x38, 0x82, 0xeb, 0x35, 0xe4, 0xda, 0xb6, 0x97, 0xb5, 0x5d, 0x96, 0x9c, 0xbf, 0x6b, - 0xdd, 0x23, 0x29, 0xee, 0xb3, 0xde, 0x8a, 0x37, 0x11, 0xef, 0xed, 0x0b, 0xfa, 0xf8, 0x4a, 0x7b, - 0x2d, 0x79, 0xe2, 0x71, 0x4d, 0xb1, 0xbd, 0x49, 0x6b, 0x20, 0xc5, 0x50, 0x64, 0x12, 0xbe, 0x5b, - 0xd5, 0x0d, 0xa8, 0xa2, 0x07, 0xb6, 0x74, 0x72, 0x25, 0xd7, 0x28, 0x8b, 0x49, 0x6a, 0xf4, 0xe7, - 0x0a, 0xa6, 0xa6, 0x55, 0x57, 0x74, 0xc8, 0x56, 0xae, 0x54, 0x6f, 0x79, 0x1d, 0xb9, 0xd2, 0x28, - 0x8b, 0x53, 0xf2, 0x12, 0x16, 0xb8, 0xbb, 0x78, 0xf5, 0x3b, 0xbb, 0x85, 0x7c, 0xd7, 0x6d, 0x92, - 0xfb, 0x0c, 0x7d, 0x63, 0x9f, 0x43, 0x5d, 0xdd, 0x19, 0xa4, 0xad, 0x2d, 0xc2, 0x68, 0x56, 0xec, - 0x8c, 0x68, 0x45, 0x93, 0xd6, 0x6a, 0xcf, 0x8b, 0x55, 0xf1, 0xc6, 0x32, 0x46, 0xf8, 0xc7, 0x00, - 0x79, 0x6f, 0x1a, 0xb9, 0x5a, 0xa2, 0xac, 0x34, 0xd7, 0xa9, 0x1a, 0x92, 0x5d, 0xf8, 0x48, 0xbe, - 0x45, 0x16, 0x0c, 0xf2, 0xf2, 0xbc, 0xa9, 0x2b, 0xd2, 0x38, 0x6f, 0xc5, 0x6e, 0xb6, 0xce, 0xe8, - 0x36, 0x26, 0xb9, 0x29, 0xb6, 0x3c, 0x6c, 0xaa, 0x14, 0xc2, 0x56, 0xd0, 0xc7, 0xdb, 0x42, 0xeb, - 0x9f, 0xda, 0xac, 0xe2, 0x52, 0x79, 0x5b, 0x94, 0x9b, 0xa1, 0xec, 0xab, 0xc8, 0x6a, 0x99, 0x2c, - 0x15, 0x59, 0xa5, 0xe4, 0x05, 0xfe, 0x15, 0x92, 0xd6, 0xfe, 0x43, 0x74, 0x5a, 0xe5, 0x5e, 0xa8, - 0xce, 0xb5, 0x51, 0xc3, 0x23, 0x6e, 0x26, 0x91, 0x2d, 0xe1, 0xa1, 0xe2, 0x1b, 0xce, 0x9b, 0x7e, - 0x8c, 0x0d, 0x37, 0x7a, 0x83, 0x3a, 0x57, 0x2b, 0x46, 0x04, 0xf5, 0x55, 0xa4, 0xbe, 0x48, 0xe6, - 0x95, 0x4b, 0x44, 0x5a, 0x7c, 0x4f, 0xd4, 0x6b, 0xac, 0xb1, 0x27, 0xc5, 0x96, 0x1d, 0xc3, 0x07, - 0x96, 0x1a, 0x77, 0x4a, 0x3e, 0x50, 0xb5, 0xe6, 0x90, 0x3f, 0x34, 0x3b, 0x80, 0x64, 0x47, 0x82, - 0x3d, 0xb6, 0x85, 0xa0, 0x74, 0x5a, 0x46, 0xb6, 0x19, 0xd8, 0xdb, 0xc8, 0xf9, 0x2a, 0x59, 0x2f, - 0x72, 0x16, 0x2d, 0x0b, 0xe4, 0x6b, 0x0b, 0x96, 0x2b, 0x1e, 0xc4, 0x73, 0x09, 0x46, 0x3f, 0xdf, - 0xe7, 0x12, 0x8c, 0x7b, 0x51, 0xb7, 0x51, 0x82, 0x4d, 0x1b, 0x25, 0x70, 0x3d, 0x4f, 0x49, 0x20, - 0x92, 0x3f, 0x66, 0x99, 0x7f, 0x6e, 0xc1, 0x5a, 0xf5, 0xe3, 0x37, 0xb9, 0xa5, 0xfe, 0xae, 0x61, - 0xdc, 0xb3, 0x7c, 0xe7, 0xf6, 0x45, 0x68, 0x42, 0x9a, 0x5b, 0x28, 0xcd, 0xb6, 0xdd, 0x61, 0xd2, - 0x24, 0x88, 0x5b, 0x25, 0xd0, 0x19, 0x56, 0x0c, 0xcd, 0xe7, 0x65, 0xa2, 0xc5, 0x16, 0xd5, 0xaf, - 0xf0, 0x9d, 0x1b, 0x63, 0x30, 0x4c, 0xf7, 0x45, 0x56, 0xc5, 0x86, 0xe0, 0x9b, 0xac, 0x7a, 0xa7, - 0x16, 0x67, 0x34, 0x7f, 0xbe, 0x35, 0xce, 0x68, 0xe9, 0x45, 0xda, 0x38, 0xa3, 0xe5, 0x47, 0xe2, - 0xd2, 0x19, 0x45, 0x66, 0xf8, 0x60, 0x4c, 0x3e, 0xc7, 0x63, 0x23, 0xca, 0xd5, 0xed, 0xe2, 0x51, - 0x4f, 0xab, 0x8e, 0x8d, 0x59, 0x90, 0x2e, 0xb9, 0x4a, 0x5e, 0x05, 0x67, 0xda, 0x73, 0x60, 0x4e, - 0xa2, 0x93, 0xf5, 0x22, 0x01, 0x49, 0xb9, 0xf2, 0xc5, 0xd1, 0x5e, 0x47, 0xa2, 0x4b, 0x76, 0x53, - 0x27, 0xca, 0x68, 0x1e, 0x41, 0x43, 0x7b, 0x5d, 0x23, 0xca, 0xc9, 0x96, 0x1f, 0x13, 0x3b, 0x1b, - 0x95, 0x63, 0xa6, 0x2b, 0xb1, 0x17, 0x19, 0x83, 0x14, 0x11, 0x14, 0x8f, 0xdf, 0x85, 0x79, 0xe3, - 0x81, 0x2b, 0x57, 0x7e, 0xd5, 0x13, 0x5c, 0xae, 0xfc, 0xca, 0x57, 0x31, 0x19, 0x68, 0xda, 0xa8, - 0xfc, 0x54, 0xa0, 0x28, 0x5e, 0x5f, 0x40, 0x5d, 0xbd, 0x2b, 0xe5, 0xfa, 0x2f, 0x3e, 0x35, 0x5d, - 0xc4, 0xc3, 0xd8, 0x83, 0x33, 0x36, 0xf9, 0x28, 0x1a, 0x1c, 0x09, 0x7d, 0x69, 0xaf, 0x26, 0xb9, - 0xbe, 0xca, 0x4f, 0x47, 0xb9, 0xbe, 0xaa, 0x9e, 0x59, 0x0c, 0x7d, 0xf5, 0x10, 0x41, 0xad, 0x21, - 0x81, 0xc5, 0xc2, 0x6b, 0x45, 0x1e, 0x56, 0x54, 0xbf, 0xcd, 0xe4, 0x61, 0xc5, 0x88, 0x67, 0x0e, - 0x33, 0x70, 0xe3, 0xfc, 0xdc, 0x20, 0xc8, 0x6d, 0x8b, 0xbb, 0x7b, 0x5e, 0xcb, 0x37, 0xec, 0xd6, - 0x78, 0xb4, 0x30, 0xec, 0xd6, 0x2c, 0xfc, 0x97, 0xdc, 0x3d, 0xe5, 0xb4, 0x9e, 0xc1, 0x9c, 0x2c, - 0x22, 0xe7, 0x46, 0x5b, 0x28, 0x9f, 0x77, 0xda, 0xe5, 0x01, 0x41, 0xd5, 0x30, 0x5c, 0xd7, 0xf3, - 0x90, 0xaa, 0xd8, 0x08, 0xad, 0xa4, 0x9c, 0x6f, 0x44, 0xb9, 0x1a, 0x9d, 0x6f, 0x44, 0x55, 0x0d, - 0xda, 0xd8, 0x08, 0xee, 0xb9, 0x14, 0x8f, 0xbf, 0xb7, 0x30, 0xd5, 0x1e, 0x5f, 0x11, 0x26, 0x6f, - 0x5f, 0xa2, 0x78, 0xcc, 0x05, 0x7a, 0xe7, 0xd2, 0xe5, 0x66, 0xfb, 0x0e, 0x8a, 0x69, 0xdb, 0x5b, - 0xf2, 0x32, 0xc5, 0x69, 0x1e, 0x47, 0x57, 0xb5, 0x67, 0x26, 0xf4, 0xdf, 0x5a, 0xfc, 0x6f, 0x4c, - 0xc7, 0xd0, 0x25, 0x3b, 0x13, 0x0a, 0x20, 0x05, 0xbe, 0x3f, 0x31, 0xbe, 0x10, 0xf7, 0x36, 0x8a, - 0x7b, 0xdd, 0xde, 0x18, 0x23, 0x2e, 0x13, 0xf6, 0xf7, 0x61, 0x43, 0x55, 0x8e, 0x0d, 0xba, 0x1f, - 0x0f, 0x43, 0x2f, 0xcd, 0xf3, 0xd2, 0x11, 0xe5, 0xe5, 0xdc, 0x70, 0x8a, 0x05, 0x45, 0xf3, 0x7e, - 0x3c, 0x13, 0xa3, 0x5c, 0x8c, 0x63, 0x46, 0x9b, 0x71, 0x8f, 0x61, 0x49, 0xce, 0xfb, 0xd8, 0x77, - 0xb3, 0x5f, 0x98, 0xe7, 0x75, 0xe4, 0xd9, 0xb1, 0x57, 0x75, 0x9e, 0xc7, 0xbe, 0x9b, 0x29, 0x8e, - 0x29, 0x3e, 0x04, 0x1a, 0xb5, 0x42, 0x3d, 0xf9, 0xae, 0xac, 0x22, 0xea, 0xc9, 0x77, 0x75, 0x59, - 0xd3, 0x4c, 0xbe, 0xfb, 0x34, 0xe3, 0x65, 0x46, 0x4f, 0x30, 0x38, 0x85, 0xd6, 0xe1, 0x48, 0xa6, - 0x87, 0x3f, 0x37, 0x53, 0x11, 0x03, 0xd9, 0xc8, 0x34, 0x2d, 0x30, 0x65, 0x8b, 0x3d, 0xe5, 0xaf, - 0x9e, 0x7a, 0x15, 0x91, 0x6c, 0x8f, 0xae, 0x2f, 0x96, 0xf9, 0x56, 0x16, 0x20, 0x4d, 0xbe, 0x5a, - 0x86, 0x84, 0x7f, 0x5b, 0xc7, 0xf8, 0x9e, 0x03, 0x31, 0xb3, 0x24, 0xfc, 0x9b, 0x0c, 0xe5, 0x05, - 0x2a, 0x6a, 0x87, 0x93, 0xa5, 0x48, 0x37, 0x90, 0xf1, 0x86, 0xbd, 0x56, 0x4e, 0x91, 0x18, 0x6f, - 0xc6, 0xfa, 0xf7, 0x60, 0xb9, 0x90, 0x7b, 0xbf, 0x22, 0xde, 0x86, 0x39, 0x17, 0x12, 0x6f, 0xc9, - 0x3c, 0xc3, 0x3c, 0xb8, 0x50, 0x10, 0x24, 0x37, 0xaa, 0xf2, 0x0d, 0xa3, 0xde, 0x36, 0x2e, 0xf3, - 0x11, 0xf7, 0x06, 0x59, 0x2b, 0xa5, 0x23, 0x48, 0xe1, 0x6d, 0x8b, 0xfc, 0x99, 0x85, 0x6d, 0xf0, - 0x23, 0xea, 0x91, 0xe4, 0x6e, 0x55, 0xc2, 0x7b, 0x69, 0x31, 0x84, 0x3f, 0x21, 0xd7, 0x8a, 0x59, - 0x71, 0x49, 0x9c, 0x13, 0xac, 0x40, 0xe8, 0x55, 0x45, 0x23, 0x27, 0xaf, 0x28, 0x37, 0x8e, 0x4c, - 0x5a, 0x8b, 0xa9, 0xb8, 0xc8, 0x3a, 0x25, 0xa7, 0x9f, 0x98, 0x7f, 0xec, 0x6a, 0xb0, 0xbc, 0x5d, - 0xb1, 0xea, 0xcb, 0xb0, 0xbe, 0x89, 0xac, 0xb7, 0xc8, 0x46, 0x61, 0xbd, 0x99, 0x21, 0xc2, 0xd1, - 0x0c, 0xfe, 0xf3, 0x14, 0xef, 0xfe, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc2, 0xa4, 0xbf, 0x47, - 0xd1, 0x42, 0x00, 0x00, + 0x79, 0xb3, 0x70, 0xfe, 0xaa, 0x72, 0xf1, 0x37, 0x81, 0x24, 0x34, 0xcd, 0xba, 0x71, 0x14, 0x04, + 0x2c, 0x25, 0xf7, 0x68, 0xe0, 0x9e, 0x8b, 0x92, 0x7d, 0x8b, 0x8d, 0x1c, 0xf0, 0x81, 0x8f, 0x18, + 0x9c, 0xac, 0xc3, 0xac, 0x1b, 0xfb, 0x5d, 0x66, 0x35, 0x3c, 0x1f, 0x9f, 0x71, 0x63, 0xff, 0x13, + 0x7a, 0x4e, 0x6c, 0x98, 0x17, 0x03, 0xdd, 0x80, 0x9e, 0xd2, 0x00, 0x63, 0xbe, 0x29, 0xa7, 0xc1, + 0x87, 0x3f, 0x65, 0x20, 0x72, 0x17, 0x5a, 0x71, 0xe2, 0x33, 0xf3, 0xcb, 0xdf, 0x06, 0x66, 0x51, + 0x9a, 0x45, 0x01, 0x97, 0xab, 0xb3, 0x7f, 0x0c, 0x57, 0x2b, 0x74, 0x21, 0x7c, 0xd4, 0xf7, 0x60, + 0xd1, 0x7c, 0x61, 0x90, 0x7e, 0x4a, 0x45, 0xad, 0xc6, 0x44, 0x67, 0xe1, 0xd8, 0xa0, 0x23, 0xa2, + 0x4f, 0xc4, 0x71, 0xdc, 0x4c, 0xd5, 0xb4, 0xec, 0x2f, 0x61, 0x25, 0x07, 0xee, 0x45, 0xe1, 0x29, + 0x4d, 0x52, 0x66, 0x6d, 0x04, 0xa6, 0x8f, 0x93, 0x48, 0x16, 0x64, 0xf1, 0x37, 0x8b, 0xdb, 0xb2, + 0x48, 0x98, 0x41, 0x2d, 0x8b, 0x18, 0x4e, 0xe2, 0x66, 0xf2, 0x96, 0xc2, 0xdf, 0x2c, 0x4e, 0xf6, + 0x91, 0x08, 0xed, 0xe2, 0x18, 0x37, 0xd5, 0x86, 0x80, 0x31, 0x2e, 0xf6, 0x33, 0x0c, 0x1f, 0x75, + 0x51, 0xc4, 0x1a, 0x7f, 0x1d, 0x1a, 0x7c, 0x8d, 0x6c, 0xa6, 0x5c, 0xdf, 0xa6, 0xb1, 0xbe, 0x82, + 0x98, 0x0e, 0x1c, 0x2b, 0xa8, 0xfd, 0xdf, 0x35, 0x68, 0x62, 0xc4, 0xfa, 0x11, 0xcd, 0x5c, 0x3f, + 0x18, 0x1f, 0x4b, 0xf3, 0x18, 0xb4, 0xa6, 0x62, 0xd0, 0x9b, 0x30, 0xaf, 0x17, 0x44, 0xce, 0x65, + 0x32, 0xab, 0x95, 0x43, 0xce, 0xc9, 0x2d, 0x58, 0xc0, 0xd4, 0x3a, 0xc7, 0xe2, 0x36, 0x33, 0x8f, + 0x50, 0x85, 0x66, 0x26, 0x02, 0x57, 0x0a, 0x89, 0x00, 0x1b, 0xc6, 0x60, 0xba, 0x9b, 0xfa, 0x9e, + 0xca, 0x13, 0x10, 0x72, 0xe8, 0x7b, 0xda, 0x30, 0xce, 0x9e, 0xd5, 0x86, 0x71, 0x36, 0xcb, 0x81, + 0x12, 0xca, 0x1f, 0x0a, 0xf0, 0xbd, 0x6b, 0x0e, 0x8d, 0xae, 0x29, 0x81, 0x4f, 0xfd, 0x01, 0xbe, + 0x86, 0x89, 0xe2, 0x76, 0x9d, 0x5b, 0x2c, 0xff, 0xca, 0xd3, 0x34, 0xd0, 0xd3, 0xb4, 0x3c, 0xa9, + 0x6b, 0x18, 0x49, 0xdd, 0x36, 0x34, 0xa2, 0x98, 0x86, 0x5d, 0x91, 0x62, 0x37, 0x79, 0xf4, 0xc0, + 0x40, 0xcf, 0x10, 0x22, 0x4a, 0x26, 0xa8, 0xf3, 0x74, 0x92, 0xbc, 0xd4, 0x54, 0x4c, 0xad, 0xa8, + 0x18, 0x99, 0x08, 0x4e, 0x5d, 0x94, 0x08, 0xda, 0xbb, 0x18, 0x15, 0x4b, 0xc6, 0xc2, 0x7c, 0xde, + 0x84, 0x19, 0x54, 0x93, 0xb4, 0x9c, 0x15, 0x23, 0x8d, 0x11, 0x46, 0xe1, 0x08, 0x1c, 0xfb, 0x07, + 0xf8, 0x86, 0x88, 0x43, 0x93, 0x88, 0x7e, 0x15, 0xe6, 0xf8, 0xae, 0x28, 0xab, 0x99, 0xc5, 0xef, + 0xc7, 0x9e, 0xfd, 0x6f, 0x16, 0x90, 0xc3, 0xe1, 0xd1, 0xc0, 0x9f, 0x9c, 0xda, 0xe4, 0x09, 0x3a, + 0x81, 0x69, 0x34, 0x13, 0x6e, 0x8e, 0xf8, 0xbb, 0x60, 0x21, 0xd3, 0x45, 0x0b, 0xc9, 0xb7, 0xf3, + 0x4a, 0x75, 0x8e, 0x3e, 0xa3, 0x6f, 0x3e, 0x73, 0xf1, 0x81, 0x4f, 0xc3, 0xac, 0x2b, 0x8a, 0x2d, + 0xcc, 0xc5, 0x23, 0xe0, 0xb1, 0x67, 0x1f, 0xc2, 0xb2, 0xb1, 0x32, 0xa1, 0xe9, 0x1b, 0xd0, 0xe4, + 0x02, 0xc4, 0x81, 0xdb, 0x53, 0xd5, 0xf0, 0x06, 0xc2, 0x0e, 0x10, 0x34, 0x4e, 0x5f, 0x7f, 0x62, + 0xc1, 0xca, 0xa1, 0x3f, 0x18, 0x06, 0x6e, 0x46, 0xbf, 0x01, 0x8d, 0xe5, 0xcb, 0x9f, 0x32, 0x96, + 0x2f, 0x35, 0x39, 0x9d, 0x6b, 0xd2, 0xfe, 0x1f, 0x0b, 0x56, 0x0b, 0xa2, 0xa8, 0x98, 0xd0, 0x34, + 0xa6, 0x11, 0xc5, 0x01, 0x81, 0xa4, 0x31, 0xad, 0x19, 0x4c, 0x6f, 0xc2, 0xfc, 0xc0, 0x0f, 0xfd, + 0xc1, 0x70, 0xd0, 0xe5, 0xba, 0xe7, 0x32, 0x35, 0x05, 0xf0, 0x00, 0xb7, 0x80, 0x21, 0xb9, 0x2f, + 0x35, 0xa4, 0x69, 0x81, 0xc4, 0x81, 0x1c, 0xe9, 0x6d, 0x58, 0xc9, 0xe3, 0xf6, 0x6e, 0xdf, 0xf5, + 0xc3, 0x6e, 0x10, 0xa5, 0xa9, 0xd8, 0x63, 0x92, 0x8f, 0xed, 0xbb, 0x7e, 0xf8, 0x69, 0x94, 0xa6, + 0x9a, 0x13, 0x98, 0xd1, 0x9d, 0x00, 0x0b, 0x60, 0x5a, 0xcf, 0x4f, 0xdc, 0x80, 0x7e, 0x18, 0x0d, + 0x8e, 0x5e, 0xad, 0xee, 0x6f, 0x40, 0x93, 0xd7, 0xdd, 0x32, 0x37, 0xe9, 0x53, 0xb9, 0x03, 0x0d, + 0x84, 0x3d, 0x45, 0x50, 0xe5, 0x36, 0xfc, 0x97, 0x05, 0x64, 0x8f, 0x85, 0x32, 0xc1, 0xc4, 0xf6, + 0xc0, 0x5c, 0x09, 0xcf, 0x9b, 0x73, 0x0b, 0xab, 0x0b, 0xc8, 0x63, 0xd3, 0xfc, 0xa6, 0x0c, 0xf3, + 0x53, 0xab, 0x99, 0xbe, 0x64, 0x71, 0xac, 0xe4, 0xc7, 0x6f, 0xc1, 0xc2, 0x99, 0x1b, 0x04, 0x34, + 0x53, 0x4f, 0x6c, 0xa2, 0x12, 0xcf, 0xa1, 0x32, 0x07, 0x97, 0x0b, 0x9e, 0xd5, 0x16, 0xbc, 0x0a, + 0xcb, 0xc6, 0x7a, 0x45, 0x34, 0xf4, 0x10, 0xd6, 0x38, 0x78, 0x37, 0x08, 0x26, 0xf6, 0xaa, 0xf6, + 0x5f, 0xd7, 0x60, 0xbd, 0x34, 0x4d, 0x85, 0x0d, 0xa6, 0x19, 0xdf, 0x56, 0xcb, 0xad, 0x9e, 0xb0, + 0x23, 0x3e, 0xc5, 0xac, 0xce, 0x3f, 0x59, 0x30, 0xc3, 0x41, 0x63, 0x77, 0xe3, 0x73, 0xe9, 0x10, + 0x84, 0xc1, 0xf1, 0x8c, 0xe8, 0x3b, 0x93, 0x31, 0xe3, 0xff, 0xd3, 0x9f, 0x55, 0xb9, 0x27, 0x11, + 0x2f, 0xaa, 0xdf, 0x83, 0x56, 0x11, 0xe1, 0x52, 0x4f, 0x4e, 0xbc, 0xaa, 0xf2, 0xe8, 0x94, 0x6a, + 0xcf, 0xa8, 0x3f, 0xb3, 0x60, 0x71, 0x2f, 0x0a, 0x3d, 0x9f, 0xdd, 0x98, 0x07, 0x6e, 0xe2, 0x0e, + 0x52, 0xf1, 0x92, 0xcf, 0x41, 0xb2, 0xec, 0xae, 0x00, 0x23, 0x0a, 0x9c, 0x5b, 0x00, 0xbd, 0x13, + 0xda, 0x7b, 0xd1, 0x15, 0x15, 0x47, 0xfe, 0xfc, 0xcf, 0x20, 0x1f, 0xfa, 0x5e, 0x4a, 0xde, 0x82, + 0xe5, 0x7c, 0xb8, 0xeb, 0x86, 0x5e, 0x57, 0x94, 0x1b, 0xf1, 0x75, 0x43, 0xe1, 0xed, 0x86, 0xde, + 0x6e, 0xfa, 0x22, 0x65, 0xb1, 0xa2, 0xaa, 0xb2, 0x75, 0x0d, 0x17, 0xbe, 0xa8, 0xe0, 0xbb, 0x08, + 0xb6, 0xff, 0xd7, 0xc2, 0x1b, 0x50, 0xae, 0x4a, 0xec, 0x76, 0x5e, 0x58, 0xc3, 0x7a, 0xab, 0xb1, + 0x65, 0xb5, 0xc2, 0x96, 0x11, 0x98, 0xf6, 0x33, 0x3a, 0x90, 0x17, 0x0b, 0xfb, 0x4d, 0x3e, 0x84, + 0x96, 0x5a, 0x71, 0x37, 0x46, 0xb5, 0x88, 0x63, 0xb2, 0x9e, 0x27, 0x8e, 0x86, 0xd6, 0x9c, 0xc5, + 0x5e, 0x41, 0x8d, 0xf2, 0x78, 0x5d, 0x99, 0xc8, 0x51, 0xf7, 0x50, 0xdb, 0xc2, 0x3f, 0xf1, 0x2f, + 0x2e, 0x35, 0xed, 0x0d, 0x33, 0xea, 0x89, 0x50, 0x59, 0x7d, 0xdb, 0xff, 0x61, 0xc1, 0xe2, 0xae, + 0xe7, 0xe1, 0xba, 0x27, 0x71, 0x13, 0x72, 0x95, 0xb5, 0x0b, 0x56, 0x39, 0xf5, 0x73, 0xae, 0xf2, + 0x17, 0x76, 0x22, 0x23, 0x94, 0x60, 0xdb, 0xd0, 0xca, 0xd7, 0x59, 0xbd, 0xbd, 0xf6, 0xb7, 0x80, + 0xf0, 0xf4, 0xca, 0x50, 0x47, 0x11, 0x6b, 0x15, 0x96, 0x0d, 0x2c, 0xe1, 0x6b, 0x3e, 0x86, 0x3b, + 0xfb, 0x34, 0xdb, 0x4b, 0xce, 0xe3, 0x2c, 0x92, 0xe1, 0xec, 0x47, 0x34, 0x8e, 0x52, 0x5f, 0x7a, + 0x2e, 0x3a, 0x91, 0xf7, 0xf9, 0x67, 0x0b, 0xee, 0x4e, 0x40, 0x48, 0x2c, 0xe1, 0x8b, 0x72, 0x7d, + 0xe9, 0x37, 0xf4, 0xf6, 0x96, 0x89, 0xa8, 0xec, 0x28, 0x88, 0xe8, 0x32, 0x50, 0x24, 0x3b, 0x1f, + 0xc0, 0x82, 0x39, 0x78, 0x29, 0x57, 0x11, 0xc0, 0xed, 0x0b, 0x84, 0x98, 0xc4, 0xe6, 0x6e, 0xc3, + 0x42, 0xcf, 0x20, 0x21, 0x18, 0x15, 0xa0, 0xf6, 0x1e, 0xbc, 0x71, 0x21, 0x37, 0xa1, 0xb6, 0x91, + 0x19, 0xba, 0xfd, 0x77, 0xd3, 0xb0, 0xfe, 0xdc, 0xcf, 0x4e, 0xbc, 0xc4, 0x3d, 0x93, 0xd6, 0x37, + 0x89, 0x90, 0x85, 0xe4, 0xbd, 0x56, 0xae, 0x37, 0xdc, 0x83, 0xa5, 0x28, 0xa4, 0x98, 0x63, 0x74, + 0x63, 0x37, 0x4d, 0xcf, 0xa2, 0x44, 0xde, 0xa5, 0x8b, 0x51, 0x48, 0x59, 0x9e, 0x71, 0x20, 0xc0, + 0x85, 0xdb, 0x78, 0xba, 0x78, 0x1b, 0xb7, 0x60, 0x2a, 0xf6, 0x43, 0xf1, 0x66, 0xc2, 0x7e, 0xb2, + 0xbb, 0x33, 0x4b, 0x5c, 0x4f, 0xa3, 0x2c, 0xee, 0x4e, 0x84, 0x2a, 0xba, 0x7a, 0x15, 0x7f, 0xb6, + 0x50, 0xc5, 0xd7, 0x74, 0x32, 0x67, 0x56, 0x2d, 0xb6, 0xa1, 0x21, 0x7e, 0x76, 0x33, 0xb7, 0x2f, + 0x52, 0x20, 0x10, 0xa0, 0xa7, 0x6e, 0x5f, 0x8b, 0xd6, 0xc0, 0x88, 0xd6, 0xb6, 0x00, 0x8e, 0x29, + 0xed, 0x1a, 0xc9, 0x50, 0xfd, 0x98, 0x52, 0xee, 0x74, 0x59, 0xa8, 0x7c, 0xe4, 0x86, 0x2f, 0xba, + 0x58, 0x83, 0x68, 0x72, 0x71, 0x18, 0xe0, 0x33, 0x77, 0x80, 0x31, 0x31, 0x0e, 0x4a, 0x99, 0xe6, + 0xb9, 0x46, 0x19, 0x6c, 0x37, 0xaf, 0xa6, 0x20, 0x4a, 0xcf, 0xcf, 0xce, 0xdb, 0x0b, 0xf9, 0xfc, + 0x3d, 0x3f, 0x3b, 0x57, 0xf3, 0x51, 0x67, 0xc9, 0x79, 0x7b, 0x31, 0x9f, 0xbf, 0xc7, 0x41, 0x4c, + 0xbc, 0xf4, 0xcc, 0x3f, 0xa6, 0xbc, 0x31, 0xa4, 0x25, 0x5a, 0xa5, 0x18, 0x64, 0x2f, 0xf2, 0x30, + 0x8c, 0x3c, 0xf3, 0x13, 0x2d, 0x39, 0x5d, 0xe2, 0x29, 0x2c, 0x03, 0x4a, 0xd3, 0xb0, 0xef, 0x41, + 0x4b, 0x9a, 0x8b, 0xde, 0x3b, 0x99, 0xd0, 0x74, 0x18, 0x64, 0xb2, 0x77, 0x92, 0x7f, 0xd9, 0xef, + 0x60, 0x57, 0xc4, 0xa7, 0x51, 0xbf, 0x9f, 0xa7, 0x4f, 0xc2, 0xb4, 0xd6, 0x60, 0x26, 0x40, 0xb8, + 0x9c, 0xc2, 0xbf, 0xec, 0x10, 0xeb, 0x39, 0x85, 0x29, 0xf9, 0xab, 0x85, 0x1f, 0x1e, 0x47, 0x22, + 0x5b, 0xc0, 0xdf, 0xec, 0x2c, 0x7a, 0xf4, 0x68, 0xd8, 0x97, 0x3d, 0x50, 0xf8, 0xc1, 0x30, 0xcf, + 0xdc, 0x24, 0x14, 0x17, 0x2a, 0xfe, 0x66, 0x98, 0x34, 0x49, 0xa2, 0x44, 0xdc, 0x9e, 0xfc, 0xc3, + 0xde, 0x87, 0xf5, 0xc3, 0xcb, 0x89, 0xc8, 0x08, 0xf1, 0x6a, 0x8d, 0x38, 0xfe, 0xf8, 0x61, 0x7f, + 0x62, 0x74, 0x80, 0x60, 0x97, 0xc0, 0x24, 0xc7, 0x68, 0x05, 0xae, 0xa0, 0x2f, 0x97, 0xc4, 0xf0, + 0x83, 0x65, 0x84, 0xed, 0x32, 0x35, 0xd5, 0x83, 0x56, 0xee, 0xa8, 0xe0, 0x9e, 0xf0, 0x57, 0x2a, + 0x3a, 0x2a, 0x8c, 0xb9, 0x93, 0xb5, 0x54, 0x7c, 0xa3, 0x5d, 0x12, 0x5f, 0xc1, 0xb2, 0x2e, 0xda, + 0x6b, 0xcd, 0xfa, 0x7f, 0x62, 0x61, 0x85, 0x4c, 0x65, 0x60, 0x87, 0x59, 0x42, 0xdd, 0xc1, 0x6b, + 0x7d, 0x10, 0xff, 0x3e, 0xdc, 0xd0, 0xfb, 0xa5, 0x2e, 0x2d, 0x89, 0xfd, 0x07, 0xf8, 0x8c, 0xc8, + 0x1f, 0xf9, 0x7f, 0x09, 0xf2, 0x7f, 0x00, 0xd7, 0x34, 0xf9, 0x2f, 0x29, 0xc6, 0x83, 0x3f, 0xbd, + 0x07, 0x0b, 0xfb, 0x11, 0xbf, 0xb1, 0x9e, 0x32, 0x47, 0x9d, 0x90, 0x27, 0x30, 0x2b, 0x3a, 0x84, + 0xc9, 0x5a, 0xa9, 0x65, 0x18, 0x29, 0x76, 0xd6, 0x47, 0xb4, 0x12, 0xdb, 0xcb, 0x5f, 0xff, 0xeb, + 0xbf, 0xff, 0xb4, 0x36, 0x4f, 0x1a, 0xf7, 0x4f, 0xdf, 0xb9, 0xdf, 0xa7, 0x19, 0x7a, 0x84, 0x3e, + 0xcc, 0x1b, 0x4d, 0x9d, 0x64, 0xd3, 0x68, 0xcc, 0x2c, 0xf4, 0x7a, 0x76, 0xb6, 0xc6, 0xb6, 0x6d, + 0xda, 0x57, 0x91, 0xc5, 0x32, 0x59, 0x12, 0x2c, 0xf2, 0x7e, 0x4d, 0xf2, 0x25, 0x2c, 0x3e, 0xc2, + 0x4a, 0xb1, 0x22, 0x4a, 0xb6, 0x73, 0x62, 0x95, 0xbd, 0xaa, 0x9d, 0xeb, 0xa3, 0x11, 0x04, 0xc3, + 0x0d, 0x64, 0xb8, 0x4a, 0x96, 0x19, 0x43, 0x5e, 0x89, 0x56, 0x3c, 0x49, 0x0a, 0x2d, 0xd1, 0xfd, + 0xf6, 0x4a, 0x79, 0x6e, 0x22, 0xcf, 0x35, 0xb2, 0xc2, 0x78, 0x7a, 0x9c, 0x41, 0xce, 0x34, 0xc2, + 0x42, 0x97, 0xde, 0xad, 0x49, 0xae, 0x8d, 0x6c, 0xe3, 0xe4, 0x2c, 0xb7, 0x2f, 0x68, 0xf3, 0x34, + 0x57, 0xd9, 0xa7, 0x0c, 0x57, 0x75, 0x7a, 0x92, 0x9f, 0x72, 0xef, 0x57, 0xd9, 0x57, 0x4c, 0xde, + 0xb8, 0xb8, 0x99, 0x99, 0xcb, 0x70, 0x67, 0xd2, 0xae, 0x67, 0xfb, 0x5b, 0x28, 0xcc, 0x35, 0xb2, + 0x29, 0x84, 0x31, 0x3a, 0x9d, 0x65, 0x2f, 0x35, 0xe9, 0x41, 0x53, 0x6f, 0xd1, 0x24, 0x1b, 0x15, + 0xce, 0x56, 0x31, 0xdf, 0xac, 0x1e, 0x14, 0x0c, 0xdb, 0xc8, 0x90, 0x90, 0x96, 0x60, 0xa8, 0x3a, + 0x3a, 0xc9, 0x57, 0xb0, 0x58, 0x68, 0x6f, 0x24, 0x76, 0x61, 0xfb, 0x2a, 0x5a, 0x55, 0x3b, 0x37, + 0xc7, 0xe2, 0x08, 0xae, 0xd7, 0x90, 0x6b, 0xdb, 0x5e, 0xd6, 0x76, 0x59, 0x72, 0xfe, 0xae, 0x75, + 0x8f, 0xa4, 0xb8, 0xcf, 0x7a, 0x27, 0xde, 0x44, 0xbc, 0xb7, 0x2f, 0x68, 0xe3, 0x2b, 0xed, 0xb5, + 0xe4, 0x89, 0xa7, 0x35, 0xc5, 0xee, 0x26, 0xad, 0x7f, 0x14, 0x23, 0x91, 0x49, 0xf8, 0x6e, 0x55, + 0xf7, 0x9f, 0x8a, 0x16, 0x58, 0xbb, 0x83, 0x5c, 0x57, 0x08, 0x29, 0x70, 0x8d, 0xb2, 0x98, 0xa4, + 0x46, 0x7b, 0xae, 0x60, 0x6a, 0x5a, 0x75, 0x45, 0x83, 0x6c, 0xe5, 0x4a, 0xf5, 0x8e, 0xd7, 0x91, + 0x2b, 0x8d, 0xb2, 0x38, 0x25, 0x2f, 0x61, 0x81, 0xbb, 0x8b, 0x57, 0xbf, 0xb3, 0x5b, 0xc8, 0x77, + 0xdd, 0x26, 0xb9, 0xcf, 0xd0, 0x37, 0xf6, 0x39, 0xd4, 0xd5, 0x95, 0x41, 0xda, 0xda, 0x22, 0x8c, + 0x5e, 0xc5, 0xce, 0x88, 0x4e, 0x34, 0x69, 0xad, 0xf6, 0xbc, 0x58, 0x15, 0xef, 0x2b, 0x63, 0x84, + 0x7f, 0x0c, 0x90, 0xb7, 0xa6, 0x91, 0xab, 0x25, 0xca, 0x4a, 0x73, 0x9d, 0xaa, 0x21, 0xd9, 0x84, + 0x8f, 0xe4, 0x5b, 0x64, 0xc1, 0x20, 0x2f, 0xcf, 0x9b, 0xba, 0x21, 0x8d, 0xf3, 0x56, 0x6c, 0x66, + 0xeb, 0x8c, 0xee, 0x62, 0x92, 0x9b, 0x62, 0xcb, 0xc3, 0xa6, 0x2a, 0x21, 0x6c, 0x05, 0xfc, 0xb2, + 0xd0, 0xda, 0xa7, 0x36, 0xab, 0xb8, 0x54, 0x5e, 0x16, 0xe5, 0x5e, 0xa8, 0xd2, 0x65, 0x91, 0xb7, + 0x3c, 0x91, 0x17, 0xf8, 0x47, 0x48, 0x5a, 0xf7, 0x0f, 0xd1, 0x69, 0x95, 0x5b, 0xa1, 0x3a, 0xd7, + 0x46, 0x0d, 0xa7, 0xd5, 0xf6, 0x2d, 0x92, 0x25, 0x3c, 0x54, 0x7c, 0xc3, 0x79, 0xcf, 0x8f, 0xb1, + 0xe1, 0x46, 0x6b, 0x50, 0xe7, 0x6a, 0xc5, 0x88, 0xa0, 0xbe, 0x8a, 0xd4, 0x17, 0xc9, 0xbc, 0x72, + 0x89, 0x48, 0x8b, 0xef, 0x89, 0x7a, 0x8c, 0x35, 0xf6, 0xa4, 0xd8, 0xb1, 0x63, 0xf8, 0xc0, 0x52, + 0xdf, 0x4e, 0xc9, 0x07, 0xaa, 0xce, 0x1c, 0xf2, 0x87, 0x66, 0x03, 0x90, 0x6c, 0x48, 0xb0, 0xc7, + 0x76, 0x10, 0x94, 0x4e, 0xcb, 0xc8, 0x2e, 0x03, 0x7b, 0x1b, 0x39, 0x5f, 0x25, 0xeb, 0x45, 0xce, + 0xa2, 0x63, 0x81, 0x7c, 0x6d, 0xc1, 0x72, 0xc5, 0x7b, 0x78, 0x2e, 0xc1, 0xe8, 0xd7, 0xfb, 0x5c, + 0x82, 0x71, 0x0f, 0xea, 0x36, 0x4a, 0xb0, 0x69, 0xa3, 0x04, 0xae, 0xe7, 0x29, 0x09, 0x44, 0xee, + 0xc7, 0x2c, 0xf3, 0xcf, 0x2d, 0x58, 0xab, 0x7e, 0xfb, 0x26, 0xb7, 0xd4, 0x9f, 0x35, 0x8c, 0x7b, + 0x95, 0xef, 0xdc, 0xbe, 0x08, 0x4d, 0x48, 0x73, 0x0b, 0xa5, 0xd9, 0xb6, 0x3b, 0x4c, 0x9a, 0x04, + 0x71, 0xab, 0x04, 0x3a, 0xc3, 0x82, 0xa1, 0xf9, 0xba, 0x4c, 0xb4, 0xd8, 0xa2, 0xfa, 0x11, 0xbe, + 0x73, 0x63, 0x0c, 0x86, 0xe9, 0xbe, 0xc8, 0xaa, 0xd8, 0x10, 0x7c, 0x92, 0x55, 0xcf, 0xd4, 0xe2, + 0x8c, 0xe6, 0xaf, 0xb7, 0xc6, 0x19, 0x2d, 0x3d, 0x48, 0x1b, 0x67, 0xb4, 0xfc, 0x46, 0x5c, 0x3a, + 0xa3, 0xc8, 0x0c, 0xdf, 0x8b, 0xc9, 0xe7, 0x78, 0x6c, 0x44, 0xb5, 0xba, 0x5d, 0x3c, 0xea, 0x69, + 0xd5, 0xb1, 0x31, 0xeb, 0xd1, 0x25, 0x57, 0xc9, 0x8b, 0xe0, 0x4c, 0x7b, 0x0e, 0xcc, 0x49, 0x74, + 0xb2, 0x5e, 0x24, 0x20, 0x29, 0x57, 0x3e, 0x38, 0xda, 0xeb, 0x48, 0x74, 0xc9, 0x6e, 0xea, 0x44, + 0x19, 0xcd, 0x23, 0x68, 0x68, 0x8f, 0x6b, 0x44, 0x39, 0xd9, 0xf2, 0x5b, 0x62, 0x67, 0xa3, 0x72, + 0xcc, 0x74, 0x25, 0xf6, 0x22, 0x63, 0x90, 0x22, 0x82, 0xe2, 0xf1, 0xbb, 0x30, 0x6f, 0xbc, 0x6f, + 0xe5, 0xca, 0xaf, 0x7a, 0x81, 0xcb, 0x95, 0x5f, 0xf9, 0x28, 0x26, 0x03, 0x4d, 0x1b, 0x95, 0x9f, + 0x0a, 0x14, 0xc5, 0xeb, 0x0b, 0xa8, 0xab, 0x67, 0xa5, 0x5c, 0xff, 0xc5, 0x97, 0xa6, 0x8b, 0x78, + 0x18, 0x7b, 0x70, 0xc6, 0x26, 0x1f, 0x45, 0x83, 0x23, 0xa1, 0x2f, 0xed, 0xd1, 0x24, 0xd7, 0x57, + 0xf9, 0xe5, 0x28, 0xd7, 0x57, 0xd5, 0x2b, 0x8b, 0xa1, 0xaf, 0x1e, 0x22, 0xa8, 0x35, 0x24, 0xb0, + 0x58, 0x78, 0xac, 0xc8, 0xc3, 0x8a, 0xea, 0xa7, 0x99, 0x3c, 0xac, 0x18, 0xf1, 0xca, 0x61, 0x06, + 0x6e, 0x9c, 0x9f, 0x1b, 0x04, 0xb9, 0x6d, 0x71, 0x77, 0xcf, 0x4b, 0xf9, 0x86, 0xdd, 0x1a, 0x6f, + 0x16, 0x86, 0xdd, 0x9a, 0x75, 0xff, 0x92, 0xbb, 0xa7, 0x9c, 0xd6, 0x33, 0x98, 0x93, 0x35, 0xe4, + 0xdc, 0x68, 0x0b, 0xd5, 0xf3, 0x4e, 0xbb, 0x3c, 0x20, 0xa8, 0x1a, 0x86, 0xeb, 0x7a, 0x1e, 0x52, + 0x15, 0x1b, 0xa1, 0x55, 0x94, 0xf3, 0x8d, 0x28, 0x17, 0xa3, 0xf3, 0x8d, 0xa8, 0x2a, 0x41, 0x1b, + 0x1b, 0xc1, 0x3d, 0x97, 0xe2, 0xf1, 0xf7, 0x16, 0x66, 0xda, 0xe3, 0x0b, 0xc2, 0xe4, 0xed, 0x4b, + 0xd4, 0x8e, 0xb9, 0x40, 0xef, 0x5c, 0xba, 0xda, 0x6c, 0xdf, 0x41, 0x31, 0x6d, 0x7b, 0x4b, 0x5e, + 0xa6, 0x38, 0xcd, 0xe3, 0xe8, 0xaa, 0xf4, 0xcc, 0x84, 0xfe, 0x5b, 0x8b, 0xff, 0x89, 0xe9, 0x18, + 0xba, 0x64, 0x67, 0x42, 0x01, 0xa4, 0xc0, 0xf7, 0x27, 0xc6, 0x17, 0xe2, 0xde, 0x46, 0x71, 0xaf, + 0xdb, 0x1b, 0x63, 0xc4, 0x65, 0xc2, 0xfe, 0x3e, 0x6c, 0xa8, 0xc2, 0xb1, 0x41, 0xf7, 0xe3, 0x61, + 0xe8, 0xa5, 0x79, 0x5e, 0x3a, 0xa2, 0xba, 0x9c, 0x1b, 0x4e, 0xb1, 0x9e, 0x68, 0xde, 0x8f, 0x67, + 0x62, 0x94, 0x8b, 0x71, 0xcc, 0x68, 0x33, 0xee, 0x31, 0x2c, 0xc9, 0x79, 0x1f, 0xfb, 0x6e, 0xf6, + 0x0b, 0xf3, 0xbc, 0x8e, 0x3c, 0x3b, 0xf6, 0xaa, 0xce, 0xf3, 0xd8, 0x77, 0x33, 0xc5, 0x31, 0xc5, + 0x77, 0x40, 0xa3, 0x54, 0xa8, 0x27, 0xdf, 0x95, 0x45, 0x44, 0x3d, 0xf9, 0xae, 0xae, 0x6a, 0x9a, + 0xc9, 0x77, 0x9f, 0x66, 0xbc, 0xca, 0xe8, 0x09, 0x06, 0xa7, 0xd0, 0x3a, 0x1c, 0xc9, 0xf4, 0xf0, + 0xe7, 0x66, 0x2a, 0x62, 0x20, 0x1b, 0x99, 0xa6, 0x05, 0xa6, 0x6c, 0xb1, 0xa7, 0xfc, 0xd1, 0x53, + 0x2f, 0x22, 0x92, 0xed, 0xd1, 0xe5, 0xc5, 0x32, 0xdf, 0xca, 0xfa, 0xa3, 0xc9, 0x57, 0xcb, 0x90, + 0xf0, 0x4f, 0xeb, 0x18, 0xdf, 0x73, 0x20, 0x66, 0x96, 0x84, 0x7f, 0x92, 0xa1, 0xbc, 0x40, 0x45, + 0xe9, 0x70, 0xb2, 0x14, 0xe9, 0x06, 0x32, 0xde, 0xb0, 0xd7, 0xca, 0x29, 0x12, 0xe3, 0xcd, 0x58, + 0xff, 0x1e, 0x2c, 0x17, 0x72, 0xef, 0x57, 0xc4, 0xdb, 0x30, 0xe7, 0x42, 0xe2, 0x2d, 0x99, 0x67, + 0x98, 0x07, 0x17, 0xea, 0x81, 0xe4, 0x46, 0x55, 0xbe, 0x61, 0x94, 0xdb, 0xc6, 0x65, 0x3e, 0xe2, + 0xde, 0x20, 0x6b, 0xa5, 0x74, 0x04, 0x29, 0xbc, 0x6d, 0x91, 0x3f, 0xb3, 0xb0, 0x0b, 0x7e, 0x44, + 0x39, 0x92, 0xdc, 0xad, 0x4a, 0x78, 0x2f, 0x2d, 0x86, 0xf0, 0x27, 0xe4, 0x5a, 0x31, 0x2b, 0x2e, + 0x89, 0x73, 0x82, 0x15, 0x08, 0xbd, 0xa8, 0x68, 0xe4, 0xe4, 0x15, 0xd5, 0xc6, 0x91, 0x49, 0x6b, + 0x31, 0x15, 0x17, 0x59, 0xa5, 0xe4, 0xf4, 0x13, 0xf3, 0x6f, 0x5d, 0x0d, 0x96, 0xb7, 0x2b, 0x56, + 0x7d, 0x19, 0xd6, 0x37, 0x91, 0xf5, 0x16, 0xd9, 0x28, 0xac, 0xd7, 0x14, 0xe1, 0x68, 0x06, 0xff, + 0x75, 0x8a, 0x77, 0xff, 0x3f, 0x00, 0x00, 0xff, 0xff, 0xdb, 0x60, 0x0b, 0x6d, 0xd0, 0x42, 0x00, + 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 958df2af..0332dec2 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -13,6 +13,7 @@ import ( "io" "net/http" + "github.com/golang/protobuf/descriptor" "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/grpc-ecosystem/grpc-gateway/utilities" @@ -22,11 +23,13 @@ import ( "google.golang.org/grpc/status" ) +// Suppress "imported and not used" errors var _ codes.Code var _ io.Reader var _ status.Status var _ = runtime.String var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage func request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetInfoRequest @@ -37,6 +40,15 @@ func request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Mar } +func local_request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetInfoRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetInfo(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetSubsystems_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetSubsystemsRequest var metadata runtime.ServerMetadata @@ -46,6 +58,15 @@ func request_GoCryptoTrader_GetSubsystems_0(ctx context.Context, marshaler runti } +func local_request_GoCryptoTrader_GetSubsystems_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetSubsystemsRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetSubsystems(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_EnableSubsystem_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -66,6 +87,19 @@ func request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshaler run } +func local_request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericSubsystemRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_EnableSubsystem_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.EnableSubsystem(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_DisableSubsystem_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -86,6 +120,19 @@ func request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marshaler ru } +func local_request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericSubsystemRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_DisableSubsystem_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DisableSubsystem(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetRPCEndpointsRequest var metadata runtime.ServerMetadata @@ -95,6 +142,15 @@ func request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler run } +func local_request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetRPCEndpointsRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetRPCEndpoints(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetCommunicationRelayers_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetCommunicationRelayersRequest var metadata runtime.ServerMetadata @@ -104,6 +160,15 @@ func request_GoCryptoTrader_GetCommunicationRelayers_0(ctx context.Context, mars } +func local_request_GoCryptoTrader_GetCommunicationRelayers_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCommunicationRelayersRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetCommunicationRelayers(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetExchanges_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -124,6 +189,19 @@ func request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtim } +func local_request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangesRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetExchanges(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_DisableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata @@ -141,6 +219,23 @@ func request_GoCryptoTrader_DisableExchange_0(ctx context.Context, marshaler run } +func local_request_GoCryptoTrader_DisableExchange_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DisableExchange(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetExchangeInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -161,6 +256,19 @@ func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler run } +func local_request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetExchangeInfo(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetExchangeOTPCode_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -181,6 +289,19 @@ func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetExchangeOTPCode(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetExchangeOTPsRequest var metadata runtime.ServerMetadata @@ -190,6 +311,15 @@ func request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangeOTPsRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetExchangeOTPCodes(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata @@ -207,6 +337,23 @@ func request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runt } +func local_request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.EnableExchange(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetTicker_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetTickerRequest var metadata runtime.ServerMetadata @@ -224,6 +371,23 @@ func request_GoCryptoTrader_GetTicker_0(ctx context.Context, marshaler runtime.M } +func local_request_GoCryptoTrader_GetTicker_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetTickerRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetTicker(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetTickers_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetTickersRequest var metadata runtime.ServerMetadata @@ -233,6 +397,15 @@ func request_GoCryptoTrader_GetTickers_0(ctx context.Context, marshaler runtime. } +func local_request_GoCryptoTrader_GetTickers_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetTickersRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetTickers(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetOrderbook_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrderbookRequest var metadata runtime.ServerMetadata @@ -250,6 +423,23 @@ func request_GoCryptoTrader_GetOrderbook_0(ctx context.Context, marshaler runtim } +func local_request_GoCryptoTrader_GetOrderbook_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrderbookRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetOrderbook(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetOrderbooks_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrderbooksRequest var metadata runtime.ServerMetadata @@ -259,6 +449,15 @@ func request_GoCryptoTrader_GetOrderbooks_0(ctx context.Context, marshaler runti } +func local_request_GoCryptoTrader_GetOrderbooks_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrderbooksRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetOrderbooks(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetAccountInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -279,6 +478,19 @@ func request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runt } +func local_request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetAccountInfoRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetAccountInfo(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetConfigRequest var metadata runtime.ServerMetadata @@ -288,6 +500,15 @@ func request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.M } +func local_request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetConfigRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetConfig(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetPortfolio_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetPortfolioRequest var metadata runtime.ServerMetadata @@ -297,6 +518,15 @@ func request_GoCryptoTrader_GetPortfolio_0(ctx context.Context, marshaler runtim } +func local_request_GoCryptoTrader_GetPortfolio_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetPortfolioRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetPortfolio(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetPortfolioSummary_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetPortfolioSummaryRequest var metadata runtime.ServerMetadata @@ -306,6 +536,15 @@ func request_GoCryptoTrader_GetPortfolioSummary_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_GetPortfolioSummary_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetPortfolioSummaryRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetPortfolioSummary(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_AddPortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq AddPortfolioAddressRequest var metadata runtime.ServerMetadata @@ -323,6 +562,23 @@ func request_GoCryptoTrader_AddPortfolioAddress_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_AddPortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddPortfolioAddressRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.AddPortfolioAddress(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_RemovePortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq RemovePortfolioAddressRequest var metadata runtime.ServerMetadata @@ -340,6 +596,23 @@ func request_GoCryptoTrader_RemovePortfolioAddress_0(ctx context.Context, marsha } +func local_request_GoCryptoTrader_RemovePortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RemovePortfolioAddressRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.RemovePortfolioAddress(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetForexProviders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetForexProvidersRequest var metadata runtime.ServerMetadata @@ -349,6 +622,15 @@ func request_GoCryptoTrader_GetForexProviders_0(ctx context.Context, marshaler r } +func local_request_GoCryptoTrader_GetForexProviders_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetForexProvidersRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetForexProviders(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetForexRates_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetForexRatesRequest var metadata runtime.ServerMetadata @@ -358,6 +640,15 @@ func request_GoCryptoTrader_GetForexRates_0(ctx context.Context, marshaler runti } +func local_request_GoCryptoTrader_GetForexRates_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetForexRatesRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetForexRates(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetOrders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrdersRequest var metadata runtime.ServerMetadata @@ -375,6 +666,23 @@ func request_GoCryptoTrader_GetOrders_0(ctx context.Context, marshaler runtime.M } +func local_request_GoCryptoTrader_GetOrders_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrdersRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetOrders(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrderRequest var metadata runtime.ServerMetadata @@ -392,6 +700,23 @@ func request_GoCryptoTrader_GetOrder_0(ctx context.Context, marshaler runtime.Ma } +func local_request_GoCryptoTrader_GetOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetOrder(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq SubmitOrderRequest var metadata runtime.ServerMetadata @@ -409,6 +734,23 @@ func request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime } +func local_request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SubmitOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SubmitOrder(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_SimulateOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq SimulateOrderRequest var metadata runtime.ServerMetadata @@ -426,6 +768,23 @@ func request_GoCryptoTrader_SimulateOrder_0(ctx context.Context, marshaler runti } +func local_request_GoCryptoTrader_SimulateOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SimulateOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SimulateOrder(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_WhaleBomb_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq WhaleBombRequest var metadata runtime.ServerMetadata @@ -443,6 +802,23 @@ func request_GoCryptoTrader_WhaleBomb_0(ctx context.Context, marshaler runtime.M } +func local_request_GoCryptoTrader_WhaleBomb_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WhaleBombRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WhaleBomb(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq CancelOrderRequest var metadata runtime.ServerMetadata @@ -460,6 +836,23 @@ func request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime } +func local_request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CancelOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.CancelOrder(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_CancelAllOrders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq CancelAllOrdersRequest var metadata runtime.ServerMetadata @@ -477,6 +870,23 @@ func request_GoCryptoTrader_CancelAllOrders_0(ctx context.Context, marshaler run } +func local_request_GoCryptoTrader_CancelAllOrders_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CancelAllOrdersRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.CancelAllOrders(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetEvents_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetEventsRequest var metadata runtime.ServerMetadata @@ -486,6 +896,15 @@ func request_GoCryptoTrader_GetEvents_0(ctx context.Context, marshaler runtime.M } +func local_request_GoCryptoTrader_GetEvents_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetEventsRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetEvents(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_AddEvent_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq AddEventRequest var metadata runtime.ServerMetadata @@ -503,6 +922,23 @@ func request_GoCryptoTrader_AddEvent_0(ctx context.Context, marshaler runtime.Ma } +func local_request_GoCryptoTrader_AddEvent_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddEventRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.AddEvent(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_RemoveEvent_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq RemoveEventRequest var metadata runtime.ServerMetadata @@ -520,6 +956,23 @@ func request_GoCryptoTrader_RemoveEvent_0(ctx context.Context, marshaler runtime } +func local_request_GoCryptoTrader_RemoveEvent_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RemoveEventRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.RemoveEvent(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetCryptocurrencyDepositAddressesRequest var metadata runtime.ServerMetadata @@ -537,6 +990,23 @@ func request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx context.Cont } +func local_request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCryptocurrencyDepositAddressesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetCryptocurrencyDepositAddresses(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetCryptocurrencyDepositAddressRequest var metadata runtime.ServerMetadata @@ -554,6 +1024,23 @@ func request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx context.Contex } +func local_request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCryptocurrencyDepositAddressRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetCryptocurrencyDepositAddress(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq WithdrawCurrencyRequest var metadata runtime.ServerMetadata @@ -571,6 +1058,23 @@ func request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx context.Context, m } +func local_request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WithdrawCurrencyRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WithdrawCryptocurrencyFunds(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq WithdrawCurrencyRequest var metadata runtime.ServerMetadata @@ -588,6 +1092,23 @@ func request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler r } +func local_request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WithdrawCurrencyRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WithdrawFiatFunds(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetLoggerDetails_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -608,6 +1129,19 @@ func request_GoCryptoTrader_GetLoggerDetails_0(ctx context.Context, marshaler ru } +func local_request_GoCryptoTrader_GetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetLoggerDetailsRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetLoggerDetails_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetLoggerDetails(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq SetLoggerDetailsRequest var metadata runtime.ServerMetadata @@ -625,6 +1159,23 @@ func request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler ru } +func local_request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetLoggerDetailsRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SetLoggerDetails(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetExchangePairsRequest var metadata runtime.ServerMetadata @@ -642,6 +1193,23 @@ func request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marshaler ru } +func local_request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangePairsRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetExchangePairs(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ExchangePairRequest var metadata runtime.ServerMetadata @@ -659,6 +1227,23 @@ func request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExchangePairRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.EnableExchangePair(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ExchangePairRequest var metadata runtime.ServerMetadata @@ -676,6 +1261,23 @@ func request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExchangePairRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DisableExchangePair(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetOrderbookStream_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -788,6 +1390,902 @@ func request_GoCryptoTrader_GetExchangeTickerStream_0(ctx context.Context, marsh } +// RegisterGoCryptoTraderHandlerServer registers the http handlers for service GoCryptoTrader to "mux". +// UnaryRPC :call GoCryptoTraderServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +func RegisterGoCryptoTraderHandlerServer(ctx context.Context, mux *runtime.ServeMux, server GoCryptoTraderServer) error { + + mux.Handle("GET", pattern_GoCryptoTrader_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetInfo_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetSubsystems_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetSubsystems_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetSubsystems_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_EnableSubsystem_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_EnableSubsystem_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_EnableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_DisableSubsystem_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_DisableSubsystem_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_DisableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetRPCEndpoints_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetRPCEndpoints_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetRPCEndpoints_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetCommunicationRelayers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetCommunicationRelayers_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetCommunicationRelayers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchanges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchanges_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchanges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_DisableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_DisableExchange_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_DisableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchangeInfo_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOTPCode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchangeOTPCode_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeOTPCode_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOTPCodes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchangeOTPCodes_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeOTPCodes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_EnableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_EnableExchange_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_EnableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetTicker_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetTicker_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetTicker_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetTickers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetTickers_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetTickers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetOrderbook_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetOrderbook_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrderbook_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetOrderbooks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetOrderbooks_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrderbooks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetAccountInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetAccountInfo_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetAccountInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetConfig_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetConfig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetPortfolio_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetPortfolio_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetPortfolio_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetPortfolioSummary_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetPortfolioSummary_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetPortfolioSummary_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_AddPortfolioAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_AddPortfolioAddress_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_AddPortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_RemovePortfolioAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_RemovePortfolioAddress_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_RemovePortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetForexProviders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetForexProviders_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetForexProviders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetForexRates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetForexRates_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetForexRates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetOrders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetOrders_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetOrder_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_SubmitOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_SubmitOrder_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SubmitOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_SimulateOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_SimulateOrder_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SimulateOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_WhaleBomb_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WhaleBomb_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WhaleBomb_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_CancelOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_CancelOrder_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_CancelOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_CancelAllOrders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_CancelAllOrders_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_CancelAllOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetEvents_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetEvents_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_AddEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_AddEvent_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_AddEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_RemoveEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_RemoveEvent_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_RemoveEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_WithdrawFiatFunds_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WithdrawFiatFunds_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WithdrawFiatFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetLoggerDetails_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetLoggerDetails_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_SetLoggerDetails_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_SetLoggerDetails_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetExchangePairs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchangePairs_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangePairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_EnableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_EnableExchangePair_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_EnableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_DisableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_DisableExchangePair_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_DisableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetOrderbookStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOrderbookStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetTickerStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeTickerStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + return nil +} + // RegisterGoCryptoTraderHandlerFromEndpoint is same as RegisterGoCryptoTraderHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterGoCryptoTraderHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { @@ -1772,7 +3270,7 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve var ( pattern_GoCryptoTrader_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetSubsystems_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getsusbsystems"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetSubsystems_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getsubsystems"}, "", runtime.AssumeColonVerbOpt(true))) pattern_GoCryptoTrader_EnableSubsystem_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enablesubsystem"}, "", runtime.AssumeColonVerbOpt(true))) @@ -1860,7 +3358,7 @@ var ( pattern_GoCryptoTrader_GetExchangeOrderbookStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeorderbookstream"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_GetTickerStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getTickerstream"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetTickerStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "gettickerstream"}, "", runtime.AssumeColonVerbOpt(true))) pattern_GoCryptoTrader_GetExchangeTickerStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangetickerstream"}, "", runtime.AssumeColonVerbOpt(true))) ) diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 9e25a8f9..3f00479a 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -262,7 +262,7 @@ message ForexProvider { string name = 1; bool enabled = 2; bool verbose = 3; - string rest_rolling_delay = 4; + string rest_polling_delay = 4; string api_key = 5; int64 api_key_level =6; bool primary_provider = 7; @@ -518,7 +518,7 @@ service GoCryptoTrader { rpc GetSubsystems (GetSubsystemsRequest) returns (GetSusbsytemsResponse) { option (google.api.http) = { - get: "/v1/getsusbsystems" + get: "/v1/getsubsystems" }; } @@ -806,7 +806,7 @@ service GoCryptoTrader { rpc GetTickerStream(GetTickerStreamRequest) returns (stream TickerResponse) { option (google.api.http) = { - get: "/v1/getTickerstream" + get: "/v1/gettickerstream" }; } diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index ac1504b8..39eede80 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -271,54 +271,6 @@ ] } }, - "/v1/getTickerstream": { - "get": { - "operationId": "GetTickerStream", - "responses": { - "200": { - "description": "A successful response.(streaming responses)", - "schema": { - "$ref": "#/x-stream-definitions/gctrpcTickerResponse" - } - } - }, - "parameters": [ - { - "name": "exchange", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "pair.delimiter", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "pair.base", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "pair.quote", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "asset_type", - "in": "query", - "required": false, - "type": "string" - } - ], - "tags": [ - "GoCryptoTrader" - ] - } - }, "/v1/getaccountinfo": { "get": { "operationId": "GetAccountInfo", @@ -868,7 +820,7 @@ ] } }, - "/v1/getsusbsystems": { + "/v1/getsubsystems": { "get": { "operationId": "GetSubsystems", "responses": { @@ -926,6 +878,54 @@ ] } }, + "/v1/gettickerstream": { + "get": { + "operationId": "GetTickerStream", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "$ref": "#/x-stream-definitions/gctrpcTickerResponse" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pair.delimiter", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pair.base", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pair.quote", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "asset_type", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/removeevent": { "post": { "operationId": "RemoveEvent", @@ -1379,7 +1379,7 @@ "type": "boolean", "format": "boolean" }, - "rest_rolling_delay": { + "rest_polling_delay": { "type": "string" }, "api_key": { diff --git a/go.mod b/go.mod index 21c7d742..0c8db7f2 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,12 @@ require ( github.com/cockroachdb/apd v1.1.0 // indirect github.com/gofrs/uuid v3.2.0+incompatible github.com/gogo/protobuf v1.2.1 // indirect - github.com/golang/protobuf v1.3.1 + github.com/golang/protobuf v1.3.2 github.com/google/go-querystring v1.0.0 github.com/gorilla/mux v1.7.3 github.com/gorilla/websocket v1.4.0 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 - github.com/grpc-ecosystem/grpc-gateway v1.9.2 + github.com/grpc-ecosystem/grpc-gateway v1.11.3 github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect github.com/jackc/pgx v3.5.0+incompatible github.com/jmoiron/sqlx v1.2.0 @@ -23,7 +23,8 @@ require ( github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb github.com/urfave/cli v1.20.0 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 - golang.org/x/net v0.0.0-20190606173856-1492cefac77f // indirect - google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6 + golang.org/x/net v0.0.0-20190606173856-1492cefac77f + google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143 google.golang.org/grpc v1.21.1 + gopkg.in/yaml.v2 v2.2.4 // indirect ) diff --git a/go.sum b/go.sum index 89e90902..6021c43a 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,7 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -21,6 +22,8 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -32,6 +35,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmo github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/grpc-gateway v1.9.2 h1:S+ef0492XaIknb8LMjcwgW2i3cNTzDYMmDrOThOJNWc= github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.11.3 h1:h8+NsYENhxNTuq+dobk3+ODoJtwY4Fu0WQXsxJfL8aM= +github.com/grpc-ecosystem/grpc-gateway v1.11.3/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.5.0+incompatible h1:BRJ4G3UPtvml5R1ey0biqqGuYUGayMYekm3woO75orY= @@ -104,11 +109,17 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6 h1:XRqWpmQ5ACYxWuYX495S0sHawhPGOVrh62WzgXsQnWs= google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143 h1:tikhlQEJeezbnu0Zcblj7g5vm/L7xt6g1vnfq8mRCS4= +google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7 h1:+t9dhfO+GNOIGJof6kPOAenx7YgrZMTdRPV+EsnPabk= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/utils/utils.go b/utils/utils.go index 6b7f6b9b..40e96ef7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -16,9 +16,9 @@ var ( ) // AdjustGoMaxProcs sets the runtime GOMAXPROCS val -// Since Go 1.5, Go will use the total number of logical processers that the +// Since Go 1.5, Go will use the total number of logical processors that the // system has available. Caveats to this are if someone has set the GOMAXPROCS -// env var set or wish to limit usage of the number of logical processers +// env var set or wish to limit usage of the number of logical processors // between a range from 1 to NumCPUs func AdjustGoMaxProcs(procs int) error { // Check for default settings, plus respecting GOMAXPROCS env but diff --git a/utils/utils_test.go b/utils/utils_test.go index 8a51da64..3bbf1569 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -51,7 +51,7 @@ func TestAdjustGoMaxProcs(t *testing.T) { for x := range tester { if err := checker(tester[x].Setting, tester[x].Expected); err != nil { - t.Errorf("%d failed. %s", tester[x], err) + t.Errorf("%d failed. %s", x, err) } } } From 92147cdc5f18d4bea5fdddbb71467d3fc1ca999c Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 8 Oct 2019 15:28:31 +1100 Subject: [PATCH 52/71] (Engine): Database system improvements (#358) * Migrated to goose & sqlboiler * create tests with sqlboiler * code clean up * Added gct -> sqlboiler config gen * dropped pgx support * dropped pgx support because who needs connection pools * reenable sqlite audit tests * first pass of migration changes * stuff is broken :D * sqlboiler :D * end of date commit * Added comments code clean up * revert go module files back to upstream * bug fix * pushed go.mod update to use correc goose version * renamed sqlite to sqlite3 for consistency across codebase and PR feedback changes * makefile updates * things are broken end of day commit * added postgresql test * use correct database name * travis fixes for env vars * travis fixes for env vars * test fixes * run migration on test setup * test adding postgres support to appveyor * Skip tests on appveyor due to issues with missing binaries * oh yeah i have to support windows don't i * bumped goose version up * add postgres to osx * fix travis config as osx does not support services move spin up to before_script * added PGDATA path fix * pass PG_DATA to pg_ctl * added initdb to before install * fixes to wording and bumps up goose version * who needs ssl anyway * moved ssl to correct section :D * bumped goose version up * unbreak travis * unbreak travis * fix if database is disabled in config * move strings to consts * converted more strings to const * improvements to sqlboiler mmodel gen * Added contrib\sqlboiler file * sqlboiler windows contrib fixes * bumped goose version up * :D whoops * further fixes to sql models * further fixes to sql models * database type fix for config gen * README update * go.mod clean up * added config details for appveyor * appveyor ordering fix * force psql9.6 * appveyor config changes * all the environmen vars * model changes for psql * model changes for psql * sqlite model fixes * attempt at osx fix * added error check for migration * typos and check against goose error instead of string :D * updated sqlboiler commit id * bump sqlboiler version again * set decimal package to @0bb1631 * readme and makefile updates * bump goose version update readme and add override flag to config gen * README typo fix and lowered inserts in test down to 20 as we are only testing that inserts work running 200 was unnecessary * added gctcli command for audit event * Added debug output toggle to config added both postgres & sqlite support to gctcli command * Wording changes on errors * set sqlite to 1 connection to stop locke database issues * Usage update for order * README updates with config examples * go.mod/sum tidy * removed lines in import second * removed lines in imports * convert local time to utc for database and display output * go mod clean up and error checking to time * renamed all packages to sqlite3 * added windows command output for sql model gen * time conversion fix * time conversion on gctcli --- .appveyor.yml | 20 + .gitignore | 2 + .travis.yml | 22 +- Makefile | 9 +- cmd/dbmigrate/main.go | 143 +- cmd/gctcli/commands.go | 110 ++ cmd/gctcli/main.go | 1 + cmd/gen_sqlboiler_config/main.go | 107 ++ codelingo.yaml | 2 +- config/config.go | 8 +- config/config_test.go | 4 +- contrib/sqlboiler.cmd | 20 + currency/code.go | 2 +- database/README.md | 127 +- database/database_logger.go | 12 + database/database_types.go | 54 + database/db_types.go | 41 - database/drivers/drivers_type.go | 12 +- database/drivers/postgres/postgres.go | 43 + database/drivers/postgres/postgresql.go | 41 - database/drivers/sqlite/sqlite.go | 28 - database/drivers/sqlite3/sqlite3.go | 29 + database/migration/migrate.go | 180 -- database/migration/migrate_type.go | 37 - database/migration/migration_logger.go | 25 - .../1565657999_create_audit_event_table.sql | 11 - .../20190916104959_audit_event/postgres.sql | 13 + .../20190916104959_audit_event/sqlite3.sql | 13 + database/models/audit.go | 8 - database/models/postgres/audit_event.go | 925 ++++++++++ database/models/postgres/audit_event_test.go | 732 ++++++++ database/models/postgres/boil_main_test.go | 119 ++ database/models/postgres/boil_queries.go | 33 + database/models/postgres/boil_queries_test.go | 52 + database/models/postgres/boil_suites_test.go | 121 ++ database/models/postgres/boil_table_names.go | 10 + database/models/postgres/boil_types.go | 52 + database/models/postgres/psql_main_test.go | 243 +++ database/models/postgres/psql_suites_test.go | 10 + database/models/postgres/psql_upsert.go | 61 + database/models/sqlite3/audit_event.go | 816 +++++++++ database/models/sqlite3/audit_event_test.go | 684 ++++++++ database/models/sqlite3/boil_main_test.go | 119 ++ database/models/sqlite3/boil_queries.go | 33 + database/models/sqlite3/boil_queries_test.go | 52 + database/models/sqlite3/boil_suites_test.go | 121 ++ database/models/sqlite3/boil_table_names.go | 10 + database/models/sqlite3/boil_types.go | 52 + database/models/sqlite3/sqlite3_main_test.go | 63 + database/repository/audit/audit.go | 115 +- database/repository/audit/postgres/audit.go | 52 - database/repository/audit/sqlite/audit.go | 53 - database/repository/repository.go | 16 + database/tests/audit_test.go | 92 +- .../tests/{db_test.go => database_test.go} | 86 +- engine/database.go | 71 +- engine/engine.go | 1 + engine/rpcserver.go | 51 + gctrpc/rpc.pb.go | 955 +++++----- gctrpc/rpc.pb.gw.go | 1530 +---------------- gctrpc/rpc.proto | 25 + gctrpc/rpc.swagger.json | 78 + go.mod | 17 +- go.sum | 153 +- main.go | 2 - sqlboiler_example.json | 34 + 66 files changed, 6018 insertions(+), 2745 deletions(-) create mode 100644 cmd/gen_sqlboiler_config/main.go create mode 100644 contrib/sqlboiler.cmd create mode 100644 database/database_logger.go create mode 100644 database/database_types.go delete mode 100644 database/db_types.go create mode 100644 database/drivers/postgres/postgres.go delete mode 100644 database/drivers/postgres/postgresql.go delete mode 100644 database/drivers/sqlite/sqlite.go create mode 100644 database/drivers/sqlite3/sqlite3.go delete mode 100644 database/migration/migrate.go delete mode 100644 database/migration/migrate_type.go delete mode 100644 database/migration/migration_logger.go delete mode 100755 database/migration/migrations/1565657999_create_audit_event_table.sql create mode 100644 database/migrations/20190916104959_audit_event/postgres.sql create mode 100644 database/migrations/20190916104959_audit_event/sqlite3.sql delete mode 100644 database/models/audit.go create mode 100644 database/models/postgres/audit_event.go create mode 100644 database/models/postgres/audit_event_test.go create mode 100644 database/models/postgres/boil_main_test.go create mode 100644 database/models/postgres/boil_queries.go create mode 100644 database/models/postgres/boil_queries_test.go create mode 100644 database/models/postgres/boil_suites_test.go create mode 100644 database/models/postgres/boil_table_names.go create mode 100644 database/models/postgres/boil_types.go create mode 100644 database/models/postgres/psql_main_test.go create mode 100644 database/models/postgres/psql_suites_test.go create mode 100644 database/models/postgres/psql_upsert.go create mode 100644 database/models/sqlite3/audit_event.go create mode 100644 database/models/sqlite3/audit_event_test.go create mode 100644 database/models/sqlite3/boil_main_test.go create mode 100644 database/models/sqlite3/boil_queries.go create mode 100644 database/models/sqlite3/boil_queries_test.go create mode 100644 database/models/sqlite3/boil_suites_test.go create mode 100644 database/models/sqlite3/boil_table_names.go create mode 100644 database/models/sqlite3/boil_types.go create mode 100644 database/models/sqlite3/sqlite3_main_test.go delete mode 100644 database/repository/audit/postgres/audit.go delete mode 100644 database/repository/audit/sqlite/audit.go create mode 100644 database/repository/repository.go rename database/tests/{db_test.go => database_test.go} (53%) create mode 100644 sqlboiler_example.json diff --git a/.appveyor.yml b/.appveyor.yml index 3aef67cb..742e5aea 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,15 +13,35 @@ environment: GO111MODULE: on NODEJS_VER: 10.15.3 APPVEYOR_SAVE_CACHE_ON_ERROR: true + POSTGRES_PATH: C:\Program Files\PostgreSQL\9.6 + PGUSER: postgres + PGPASSWORD: Password12! + POSTGRES_ENV_POSTGRES_USER: postgres + POSTGRES_ENV_POSTGRES_PASSWORD: Password12! + POSTGRES_ENV_POSTGRES_DB: gct_dev_ci + PSQL_USER: postgres + PSQL_HOST: localhost + PSQL_PASS: Password12! + PSQL_DBNAME: gct_dev_ci + PSQL_SSLMODE: disable stack: go 1.12.3 +services: + - postgresql96 + +init: + - SET PATH=%POSTGRES_PATH%\bin;%PATH% + install: - set Path=C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;%Path% - ps: Install-Product node $env:NODEJS_VER - cd c:\gopath\src\github.com\thrasher-corp\gocryptotrader\web - npm install +build_script: + - createdb gct_dev_ci + before_test: - cd c:\gopath\src\github.com\thrasher-corp\gocryptotrader - go get diff --git a/.gitignore b/.gitignore index c3baa137..ba7a3bae 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ gocryptotrader # Output of the go coverage tool, specifically when used with LiteIDE *.out +sqlboiler.toml +sqlboiler.json diff --git a/.travis.yml b/.travis.yml index e04d4cf7..209562ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ matrix: script: - npm run lint - npm run build - - language: go dist: xenial name: 'GoCryptoTrader [back-end] [linux]' @@ -21,11 +20,17 @@ matrix: - 1.13.x env: - GO111MODULE=on + - PSQL_USER=postgres + - PSQL_HOST=localhost + - PSQL_DBNAME=gct_dev_ci install: true cache: directories: - $GOPATH/pkg/mod - + services: + - postgresql + before_script: + - psql -c 'create database gct_dev_ci;' -U postgres script: - make check after_success: @@ -38,11 +43,22 @@ matrix: - 1.13.x env: - GO111MODULE=on + - PSQL_USER=postgres + - PSQL_HOST=localhost + - PSQL_DBNAME=gct_dev_ci + - PSQL_SSLMODE=disable + - PSQL_SKIPSQLCMD=true + - PSQL_TESTDBNAME=gct_dev_ci install: true cache: directories: - $GOPATH/pkg/mod - + before_install: + - rm -rf /usr/local/var/postgres + - initdb /usr/local/var/postgres + - pg_ctl start --pgdata /usr/local/var/postgres + - createuser -s postgres + - psql -c 'create database gct_dev_ci;' -U postgres script: - make check after_success: diff --git a/Makefile b/Makefile index 05ff8891..68473ff6 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ LINTBIN = $(GOPATH)/bin/golangci-lint GCTLISTENPORT=9050 GCTPROFILERLISTENPORT=8085 CRON = $(TRAVIS_EVENT_TYPE) +DRIVER ?= psql get: GO111MODULE=on go get $(GCTPKG) @@ -46,5 +47,9 @@ profile_heap: profile_cpu: go tool pprof -http "localhost:$(GCTPROFILERLISTENPORT)" 'http://localhost:$(GCTLISTENPORT)/debug/pprof/profile' -db_migrate: - go run ./cmd/dbmigrate +gen_db_models: +ifeq ($(DRIVER), psql) + sqlboiler -o database/models/postgres -p postgres --no-auto-timestamps --wipe $(DRIVER) +else + sqlboiler -o database/models/sqlite3 -p sqlite3 --no-auto-timestamps --wipe $(DRIVER) +endif diff --git a/cmd/dbmigrate/main.go b/cmd/dbmigrate/main.go index 0cb733b3..496daadd 100644 --- a/cmd/dbmigrate/main.go +++ b/cmd/dbmigrate/main.go @@ -1,74 +1,46 @@ package main import ( + "errors" "flag" "fmt" "os" - "path/filepath" "runtime" - "strconv" - "time" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/database" - db "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres" - dbsqlite3 "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite" - mg "github.com/thrasher-corp/gocryptotrader/database/migration" + dbPSQL "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres" + dbsqlite3 "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite3" + "github.com/thrasher-corp/gocryptotrader/database/repository" + "github.com/thrasher-corp/goose" ) var ( - dbConn *database.Database - configFile string - defaultDataDir string - createMigration string - migrationDir string + dbConn *database.Db + configFile string + defaultDataDir string + migrationDir string + command string + args string ) -var defaultMigration = []byte(`-- up --- down -`) - func openDbConnection(driver string) (err error) { - if driver == "postgres" { - dbConn, err = db.Connect() + if driver == database.DBPostgreSQL { + dbConn, err = dbPSQL.Connect() if err != nil { return fmt.Errorf("database failed to connect: %v Some features that utilise a database will be unavailable", err) } - - dbConn.SQL.SetMaxOpenConns(2) - dbConn.SQL.SetMaxIdleConns(1) - dbConn.SQL.SetConnMaxLifetime(time.Hour) - - } else if driver == "sqlite" { + return nil + } else if driver == database.DBSQLite || driver == database.DBSQLite3 { dbConn, err = dbsqlite3.Connect() - if err != nil { return fmt.Errorf("database failed to connect: %v Some features that utilise a database will be unavailable", err) } + return nil } - return nil -} - -type tmpLogger struct{} - -// Printf implantation of migration Logger interface -// Passes directly to Printf from fmt package -func (t tmpLogger) Printf(format string, v ...interface{}) { - fmt.Printf(format, v...) -} - -// Println implantation of migration Logger interface -// Passes directly to Println from fmt package -func (t tmpLogger) Println(v ...interface{}) { - fmt.Println(v...) -} - -// Errorf implantation of migration Logger interface -// Passes directly to Printf from fmt package -func (t tmpLogger) Errorf(format string, v ...interface{}) { - fmt.Printf(format, v...) + return errors.New("no connection established") } func main() { @@ -82,35 +54,15 @@ func main() { os.Exit(1) } + flag.StringVar(&command, "command", "", "command to run status|up|up-by-one|up-to|down|create") + flag.StringVar(&args, "args", "", "arguments to pass to goose") + flag.StringVar(&configFile, "config", defaultPath, "config file to load") flag.StringVar(&defaultDataDir, "datadir", common.GetDefaultDataDir(runtime.GOOS), "default data directory for GoCryptoTrader files") - flag.StringVar(&createMigration, "create", "", "create a new empty migration file") - flag.StringVar(&migrationDir, "migrationdir", mg.MigrationDir, "override migration folder") + flag.StringVar(&migrationDir, "migrationdir", database.MigrationDir, "override migration folder") flag.Parse() - if createMigration != "" { - err = newMigrationFile(createMigration) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - fmt.Println("Migration created successfully") - os.Exit(0) - } - - tempLogger := tmpLogger{} - - temp := mg.Migrator{ - Log: tempLogger, - } - - err = temp.LoadMigrations() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - conf := config.GetConfig() err = conf.LoadConfig(configFile, true) @@ -119,49 +71,32 @@ func main() { os.Exit(1) } + if !conf.Database.Enabled { + fmt.Println("Database support is disabled") + os.Exit(1) + } err = openDbConnection(conf.Database.Driver) if err != nil { fmt.Println(err) os.Exit(1) } - fmt.Printf("Connected to: %s\n", conf.Database.Host) + drv := repository.GetSQLDialect() - temp.Conn = dbConn + if drv == database.DBSQLite || drv == database.DBSQLite3 { + fmt.Printf("Database file: %s\n", conf.Database.Database) + } else { + fmt.Printf("Connected to: %s\n", conf.Database.Host) + } - err = temp.RunMigration() - if err != nil { + if command == "" { + _ = goose.Run("status", dbConn.SQL, drv, migrationDir, "") + fmt.Println() + flag.Usage() + return + } + + if err = goose.Run(command, dbConn.SQL, drv, migrationDir, args); err != nil { fmt.Println(err) - os.Exit(1) - } - - if dbConn.SQL != nil { - err = dbConn.SQL.Close() - if err != nil { - fmt.Println(err) - } } } - -func newMigrationFile(filename string) error { - curTime := strconv.FormatInt(time.Now().Unix(), 10) - path := filepath.Join(migrationDir, curTime+"_"+filename+".sql") - err := common.CreateDir(migrationDir) - if err != nil { - return err - } - fmt.Printf("Creating new empty migration: %v\n", path) - f, err := os.Create(path) - - if err != nil { - return err - } - - _, err = f.Write(defaultMigration) - - if err != nil { - return err - } - - return f.Close() -} diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index ddcc043b..1b596368 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -9,6 +9,7 @@ import ( "runtime" "strconv" "strings" + "time" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" @@ -2805,3 +2806,112 @@ func clearScreen() error { return cmd.Run() } } + +const timeFormat = "2006-01-02 15:04:05" + +var startTime, endTime, order string +var limit int + +var getAuditEventCommand = cli.Command{ + Name: "getauditevent", + Usage: "gets audit events matching query parameters", + ArgsUsage: " ", + Action: getAuditEvent, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "start, s", + Usage: "start date to search", + Value: time.Now().Add(-time.Hour).Format(timeFormat), + Destination: &startTime, + }, + cli.StringFlag{ + Name: "end, e", + Usage: "end time to search", + Value: time.Now().Format(timeFormat), + Destination: &endTime, + }, + cli.StringFlag{ + Name: "order, o", + Usage: "order results by ascending/descending", + Value: "asc", + Destination: &order, + }, + cli.IntFlag{ + Name: "limit, l", + Usage: "how many results to retrieve", + Value: 100, + Destination: &limit, + }, + }, +} + +func getAuditEvent(c *cli.Context) error { + if !c.IsSet("start") { + if c.Args().Get(0) != "" { + startTime = c.Args().Get(0) + } + } + + if !c.IsSet("end") { + if c.Args().Get(1) != "" { + endTime = c.Args().Get(1) + } + } + + if !c.IsSet("order") { + if c.Args().Get(2) != "" { + order = c.Args().Get(2) + } + } + + if !c.IsSet("limit") { + if c.Args().Get(3) != "" { + limitStr, err := strconv.ParseInt(c.Args().Get(3), 10, 32) + if err == nil { + limit = int(limitStr) + } + } + } + + s, err := time.Parse(timeFormat, startTime) + if err != nil { + return fmt.Errorf("invalid time format for start: %v", err) + } + + e, err := time.Parse(timeFormat, endTime) + if err != nil { + return fmt.Errorf("invalid time format for end: %v", err) + } + + if e.Before(s) { + return errors.New("start cannot be after before") + } + + conn, err := setupClient() + if err != nil { + return err + } + + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + + _, offset := time.Now().Zone() + loc := time.FixedZone("", -offset) + + result, err := client.GetAuditEvent(context.Background(), + &gctrpc.GetAuditEventRequest{ + StartDate: s.In(loc).Format(timeFormat), + EndDate: e.In(loc).Format(timeFormat), + Limit: int32(limit), + OrderBy: order, + Offset: int32(offset), + }) + + if err != nil { + return err + } + + jsonOutput(result) + return nil +} diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index 31e4fe17..d16aa5e1 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -132,6 +132,7 @@ func main() { getExchangeOrderbookStreamCommand, getTickerStreamCommand, getExchangeTickerStreamCommand, + getAuditEventCommand, } err := app.Run(os.Args) diff --git a/cmd/gen_sqlboiler_config/main.go b/cmd/gen_sqlboiler_config/main.go new file mode 100644 index 00000000..c5198315 --- /dev/null +++ b/cmd/gen_sqlboiler_config/main.go @@ -0,0 +1,107 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/core" + "github.com/thrasher-corp/gocryptotrader/database" + "github.com/thrasher-corp/gocryptotrader/database/repository" +) + +var ( + configFile string + defaultDataDir string + outputFolder string +) + +var sqlboilerConfig map[string]driverConfig + +type driverConfig struct { + DBName string `json:"dbname,omitempty"` + Host string `json:"host,omitempty"` + Port uint16 `json:"port,omitempty"` + User string `json:"user,omitempty"` + Pass string `json:"pass,omitempty"` + Schema string `json:"schema,omitempty"` + SSLMode string `json:"sslmode,omitempty"` + Blacklist []string `json:"blacklist,omitempty"` +} + +func main() { + fmt.Println("GoCryptoTrader SQLBoiler config generation tool") + fmt.Println(core.Copyright) + fmt.Println() + + defaultPath, err := config.GetFilePath("") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + flag.StringVar(&configFile, "config", defaultPath, "config file to load") + flag.StringVar(&defaultDataDir, "datadir", common.GetDefaultDataDir(runtime.GOOS), "default data directory for GoCryptoTrader files") + flag.StringVar(&outputFolder, "outdir", "", "overwrite default output folder") + flag.Parse() + + conf := config.GetConfig() + + err = conf.LoadConfig(configFile, true) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + convertGCTtoSQLBoilerConfig(&conf.Database) + + jsonOutput, err := json.MarshalIndent(sqlboilerConfig, "", " ") + if err != nil { + fmt.Printf("Marshal failed: %v", err) + os.Exit(1) + } + + path := filepath.Join(outputFolder, "sqlboiler.json") + err = ioutil.WriteFile(path, jsonOutput, 0644) + if err != nil { + fmt.Printf("Write failed: %v", err) + os.Exit(1) + } + fmt.Println("sqlboiler.json file created") +} + +func convertGCTtoSQLBoilerConfig(c *database.Config) { + tempConfig := driverConfig{ + Blacklist: []string{"goose_db_version"}, + } + + sqlboilerConfig = make(map[string]driverConfig) + + dbType := repository.GetSQLDialect() + + if dbType == database.DBPostgreSQL { + dbType = "psql" + } + if dbType == database.DBSQLite || dbType == database.DBSQLite3 { + tempConfig.DBName = convertDBName(c.Database) + } else { + tempConfig.User = c.Username + tempConfig.Pass = c.Password + tempConfig.Port = c.Port + tempConfig.Host = c.Host + tempConfig.DBName = c.Database + tempConfig.SSLMode = c.SSLMode + } + + sqlboilerConfig[dbType] = tempConfig +} + +func convertDBName(in string) string { + return filepath.Join(common.GetDefaultDataDir(runtime.GOOS), "/database", in) +} diff --git a/codelingo.yaml b/codelingo.yaml index 4e839223..03c1f23e 100644 --- a/codelingo.yaml +++ b/codelingo.yaml @@ -8,7 +8,7 @@ tenets: - import: codelingo/effective-go/unnecessary-else - import: codelingo/code-review-comments/declare-empty-slice - import: codelingo/effective-go/defer-close-file - - import: codelingo/effective-go/comment-first-word-when-empty + # - import: codelingo/effective-go/comment-first-word-when-empty # this has been disabled temporarily - name: missing-stop-ticker actions: codelingo/review: diff --git a/config/config.go b/config/config.go index d197ec35..27cdaf02 100644 --- a/config/config.go +++ b/config/config.go @@ -1293,7 +1293,7 @@ func (c *Config) checkDatabaseConfig() error { defer m.Unlock() if (c.Database == database.Config{}) { - c.Database.Driver = "sqlite" + c.Database.Driver = database.DBSQLite3 c.Database.Database = database.DefaultSQLiteDatabase } @@ -1306,16 +1306,16 @@ func (c *Config) checkDatabaseConfig() error { return fmt.Errorf("unsupported database driver %v, database disabled", c.Database.Driver) } - if c.Database.Driver == "sqlite" { + if c.Database.Driver == database.DBSQLite || c.Database.Driver == database.DBSQLite3 { databaseDir := filepath.Join(common.GetDefaultDataDir(runtime.GOOS), "/database") err := common.CreateDir(databaseDir) if err != nil { return err } - database.Conn.DataPath = databaseDir + database.DB.DataPath = databaseDir } - database.Conn.Config = &c.Database + database.DB.Config = &c.Database return nil } diff --git a/config/config_test.go b/config/config_test.go index 65249bfd..073ea166 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1821,7 +1821,7 @@ func TestCheckDatabaseConfig(t *testing.T) { t.Error(err) } - if c.Database.Driver != "sqlite" || + if c.Database.Driver != database.DBSQLite3 || c.Database.Database != database.DefaultSQLiteDatabase || c.Database.Enabled { t.Error("unexpected results") @@ -1833,7 +1833,7 @@ func TestCheckDatabaseConfig(t *testing.T) { t.Error("unexpected result") } - c.Database.Driver = "sqlite" + c.Database.Driver = database.DBSQLite3 c.Database.Enabled = true if err := c.checkDatabaseConfig(); err != nil { t.Error(err) diff --git a/contrib/sqlboiler.cmd b/contrib/sqlboiler.cmd new file mode 100644 index 00000000..95f5d29c --- /dev/null +++ b/contrib/sqlboiler.cmd @@ -0,0 +1,20 @@ +@echo off +title GoCryptoTrader Database Model Generation +IF NOT DEFINED GOPATH ( + echo "GOPATH not set" + exit +) + +IF NOT DEFINED DRIVER ( + SET DRIVER=psql +) + +IF %DRIVER%==psql ( + IF NOT DEFINED MODEL (SET MODEL=postgres) +) ELSE ( + IF NOT DEFINED MODEL (SET MODEL=sqlite3) +) +cd ..\ +start %GOPATH%\\bin\\sqlboiler -o database\\models\\%MODEL% -p %MODEL% --no-auto-timestamps --wipe %DRIVER% + +pause \ No newline at end of file diff --git a/currency/code.go b/currency/code.go index a1902769..90f19ba6 100644 --- a/currency/code.go +++ b/currency/code.go @@ -804,7 +804,7 @@ var ( STQ = NewCode("STQ") INK = NewCode("INK") HBZ = NewCode("HBZ") - USDT_ETH = NewCode("USDT_ETH") // nolint: golint + USDT_ETH = NewCode("USDT_ETH") // nolint: golint,stylecheck QTUM_ETH = NewCode("QTUM_ETH") // nolint: golint BTM_ETH = NewCode("BTM_ETH") // nolint: golint FIL = NewCode("FIL") diff --git a/database/README.md b/database/README.md index e3bd676b..89b35f48 100644 --- a/database/README.md +++ b/database/README.md @@ -14,46 +14,135 @@ This database package is part of the GoCryptoTrader codebase. ## This is still in active development -You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). +You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) ## Current Features for database package + Establishes & Maintains database connection across program life cycle -+ Multiple database support via simple repository model -+ Run migration on connection to assure database is at correct version ++ Migration handed by [Goose](https://github.com/thrasher-corp/goose) ++ Model generation handled by [SQLBoiler](https://github.com/thrasher-corp/sqlboiler) ## How to use -##### To Manually migrate to the latest database you can run the "dbmigrate" helper in the cmd folder +##### Prerequisites -This will parse and run all migration files in your $GoCryptoTrader/database/migrations - -_This is also run from the bot when a connection is established to the database_ - -```sh -go run ./cmd/dbmigrate -``` -A Makefile command has also been added for this -```sh -make db_migrate +[SQLBoiler](https://github.com/thrasher-corp/sqlboiler) +```shell script +go get -u github.com/thrasher-corp/sqlboiler ``` -##### To create a new migrate file you can also run the same command with the -create "migration name" flag +[Postgres Driver](https://github.com/thrasher-corp/sqlboiler/drivers/sqlboiler-psql) +```shell script +go get -u github.com/thrasher-corp/sqlboiler/drivers/sqlboiler-psql +``` + +[SQLite Driver](https://github.com/thrasher-corp/sqlboiler-sqlite3) +```shell script +go get -u github.com/thrasher-corp/sqlboiler-sqlite3 +``` + +##### Configuration + +The database configuration struct is currently: +```shell script +type Config struct { + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + Driver string `json:"driver"` + drivers.ConnectionDetails `json:"connectionDetails"` +} +``` +And Connection Details: +```sh +type ConnectionDetails struct { + Host string `json:"host"` + Port uint16 `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + Database string `json:"database"` + SSLMode string `json:"sslmode"` +} +``` + +With an example configuration being: ```sh -go run ./cmd/dbmigrate -create "alter some table" + "database": { + "enabled": true, + "verbose": true, + "driver": "postgres", + "connectionDetails": { + "host": "localhost", + "port": 5432, + "username": "gct-dev", + "password": "gct-dev", + "database": "gct-dev", + "sslmode": "disable" + } + }, ``` +##### Create and Run migrations + Migrations are created using a modified version of [Goose](https://github.com/thrasher-corp/goose) + + A helper tool sits in the ./cmd/dbmigrate folder that includes the following features: + ++ Check current database version with the "status" command +```shell script +dbmigrate -command status +``` + ++ Create a new migration +```sh +dbmigrate -command "create" -args "model" +``` +_This will create a folder in the ./database/migration folder that contains postgres.sql and sqlite.sql files_ + + Run dbmigrate command with -command up +```shell script +dbmigrate -command "up" +``` + +dbmigrate provides a -migrationdir flag override to tell it what path to look in for migrations + ##### Adding a new model +Model's are generated using [SQLBoiler](https://github.com/thrasher-corp/sqlboiler) +A helper tool has been made located in gen_sqlboiler_config that will parse your GoCryptoTrader config and output a SQLBoiler config -+ Create Model in github.com/thrasher-corp/gocryptotrader/database/models directory +```sh +gen_sqlboiler_config +``` + +By default this will look in your gocryptotrader data folder and default config, these can be overwritten +along with the location of the sqlboiler generated config + +```shell script +-config "configname.json" +-datadir "~/.gocryptotrader/" +-outdir "~/.gocryptotrader/" +``` + + +Generate a new model that gets placed in ./database/models/ folder + +Linux: +```shell script +sqlboiler -o database/models/postgres -p postgres --no-auto-timestamps --wipe psql +``` +Windows: +```sh +sqlboiler -o database\\models\\postgres -p postgres --no-auto-timestamps --wipe psql +``` + +Helpers have been provided in the Makefile for linux users +``` +make gen_db_models +``` +And in the contrib/sqlboiler.cmd for windows users ##### Adding a Repository + Create Repository directory in github.com/thrasher-corp/gocryptotrader/database/repository/ -+ Create a base Repository interface with any required Methods -+ Create a per driver implementation of the Repository that implement all required methods to match the interface ## Contribution diff --git a/database/database_logger.go b/database/database_logger.go new file mode 100644 index 00000000..2d038ce3 --- /dev/null +++ b/database/database_logger.go @@ -0,0 +1,12 @@ +package database + +import log "github.com/thrasher-corp/gocryptotrader/logger" + +// Logger implements io.Writer interface to redirect SQLBoiler debug output to GCT logger +type Logger struct{} + +// Write takes input and sends to GCT logger +func (l Logger) Write(p []byte) (n int, err error) { + log.Debugf(log.DatabaseMgr, "SQL: %s", p) + return 0, nil +} diff --git a/database/database_types.go b/database/database_types.go new file mode 100644 index 00000000..3fce1a44 --- /dev/null +++ b/database/database_types.go @@ -0,0 +1,54 @@ +package database + +import ( + "database/sql" + "errors" + "path/filepath" + "sync" + + "github.com/thrasher-corp/gocryptotrader/database/drivers" +) + +// Db holds all information for a database instance +type Db struct { + SQL *sql.DB + DataPath string + Config *Config + + Connected bool + Mu sync.RWMutex +} + +// Config holds all database configurable options including enable/disabled & DSN settings +type Config struct { + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + Driver string `json:"driver"` + drivers.ConnectionDetails `json:"connectionDetails"` +} + +var ( + // DB Global Database Connection + DB = &Db{} + + // MigrationDir which folder to look in for current migrations + MigrationDir = filepath.Join("..", "..", "database", "migrations") + + // ErrNoDatabaseProvided error to display when no database is provided + ErrNoDatabaseProvided = errors.New("no database provided") + + // SupportedDrivers slice of supported database driver types + SupportedDrivers = []string{DBSQLite, DBSQLite3, DBPostgreSQL} + + // DefaultSQLiteDatabase is the default sqlite3 database name to use + DefaultSQLiteDatabase = "gocryptotrader.db" +) + +const ( + // DBSQLite const string for sqlite across code base + DBSQLite = "sqlite" + // DBSQLite3 const string for sqlite3 across code base + DBSQLite3 = "sqlite3" + // DBPostgreSQL const string for PostgreSQL across code base + DBPostgreSQL = "postgres" +) diff --git a/database/db_types.go b/database/db_types.go deleted file mode 100644 index 235bef0c..00000000 --- a/database/db_types.go +++ /dev/null @@ -1,41 +0,0 @@ -package database - -import ( - "errors" - "sync" - - "github.com/jmoiron/sqlx" - "github.com/thrasher-corp/gocryptotrader/database/drivers" -) - -// Database holds a pointer to sql connection, DataPath which is used for file based databases -// and a pointer to a Config struct -type Database struct { - Config *Config - DataPath string - SQL *sqlx.DB - - Connected bool - Mu sync.RWMutex -} - -// Config holds connection information about the database what the driver type is and if its enabled or not -type Config struct { - Enabled bool `json:"enabled"` - Driver string `json:"driver"` - drivers.ConnectionDetails `json:"connectionDetails"` -} - -// Conn is a global copy of Database{} struct -var Conn = &Database{} - -var ( - // ErrNoDatabaseProvided error to display when no database is provided - ErrNoDatabaseProvided = errors.New("no database provided") - - // SupportedDrivers slice of supported database driver types - SupportedDrivers = []string{"sqlite", "postgres"} - - // DefaultSQLiteDatabase is the default sqlite database name to use - DefaultSQLiteDatabase = "gocryptotrader.db" -) diff --git a/database/drivers/drivers_type.go b/database/drivers/drivers_type.go index ec0498a6..31656167 100644 --- a/database/drivers/drivers_type.go +++ b/database/drivers/drivers_type.go @@ -2,10 +2,10 @@ package drivers // ConnectionDetails holds DSN information type ConnectionDetails struct { - Host string - Port uint16 - Username string - Password string - Database string - SSLMode string + Host string `json:"host"` + Port uint16 `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + Database string `json:"database"` + SSLMode string `json:"sslmode"` } diff --git a/database/drivers/postgres/postgres.go b/database/drivers/postgres/postgres.go new file mode 100644 index 00000000..9ca54560 --- /dev/null +++ b/database/drivers/postgres/postgres.go @@ -0,0 +1,43 @@ +package postgres + +import ( + "database/sql" + "fmt" + "time" + + // import go libpq driver package + _ "github.com/lib/pq" + "github.com/thrasher-corp/gocryptotrader/database" +) + +// Connect opens a connection to Postgres database and returns a pointer to database.DB +func Connect() (*database.Db, error) { + if database.DB.Config.SSLMode == "" { + database.DB.Config.SSLMode = "disable" + } + + configDSN := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s", + database.DB.Config.Username, + database.DB.Config.Password, + database.DB.Config.Host, + database.DB.Config.Port, + database.DB.Config.Database, + database.DB.Config.SSLMode) + + db, err := sql.Open(database.DBPostgreSQL, configDSN) + if err != nil { + return nil, err + } + + err = db.Ping() + if err != nil { + return nil, err + } + + database.DB.SQL = db + database.DB.SQL.SetMaxOpenConns(2) + database.DB.SQL.SetMaxIdleConns(1) + database.DB.SQL.SetConnMaxLifetime(time.Hour) + + return database.DB, nil +} diff --git a/database/drivers/postgres/postgresql.go b/database/drivers/postgres/postgresql.go deleted file mode 100644 index 3041f52d..00000000 --- a/database/drivers/postgres/postgresql.go +++ /dev/null @@ -1,41 +0,0 @@ -package postgres - -import ( - "fmt" - "time" - - "github.com/jackc/pgx" - "github.com/jackc/pgx/stdlib" - "github.com/jmoiron/sqlx" - "github.com/thrasher-corp/gocryptotrader/database" -) - -// Connect establishes a connection pool to the database -func Connect() (*database.Database, error) { - configDSN := fmt.Sprintf("host=%s port=%d user=%s password=%s database=%s sslmode=%s", - database.Conn.Config.Host, - database.Conn.Config.Port, - database.Conn.Config.Username, - database.Conn.Config.Password, - database.Conn.Config.Database, - database.Conn.Config.SSLMode) - - connConfig, err := pgx.ParseDSN(configDSN) - if err != nil { - return nil, err - } - - connPool, err := pgx.NewConnPool(pgx.ConnPoolConfig{ - ConnConfig: connConfig, - AfterConnect: nil, - MaxConnections: 20, - AcquireTimeout: 30 * time.Second, - }) - if err != nil { - return nil, err - } - - sqlxDB := stdlib.OpenDBFromPool(connPool) - database.Conn.SQL = sqlx.NewDb(sqlxDB, "pgx") - return database.Conn, nil -} diff --git a/database/drivers/sqlite/sqlite.go b/database/drivers/sqlite/sqlite.go deleted file mode 100644 index 6dad1161..00000000 --- a/database/drivers/sqlite/sqlite.go +++ /dev/null @@ -1,28 +0,0 @@ -package sqlite - -import ( - "path/filepath" - - "github.com/jmoiron/sqlx" - // import sqlite3 driver - _ "github.com/mattn/go-sqlite3" - "github.com/thrasher-corp/gocryptotrader/database" -) - -// Connect creates a connection to the entered database -// With SQLite the database is not created until first read/write - -func Connect() (*database.Database, error) { - if database.Conn.Config.Database == "" { - return nil, database.ErrNoDatabaseProvided - } - - databaseFullLocation := filepath.Join(database.Conn.DataPath, database.Conn.Config.Database) - dbConn, err := sqlx.Open("sqlite3", databaseFullLocation) - if err != nil { - return nil, err - } - - database.Conn.SQL = dbConn - return database.Conn, nil -} diff --git a/database/drivers/sqlite3/sqlite3.go b/database/drivers/sqlite3/sqlite3.go new file mode 100644 index 00000000..51354549 --- /dev/null +++ b/database/drivers/sqlite3/sqlite3.go @@ -0,0 +1,29 @@ +package sqlite + +import ( + "database/sql" + "path/filepath" + + // import sqlite3 driver + _ "github.com/mattn/go-sqlite3" + "github.com/thrasher-corp/gocryptotrader/database" +) + +// Connect opens a connection to sqlite database and returns a pointer to database.DB +func Connect() (*database.Db, error) { + if database.DB.Config.Database == "" { + return nil, database.ErrNoDatabaseProvided + } + + databaseFullLocation := filepath.Join(database.DB.DataPath, database.DB.Config.Database) + + dbConn, err := sql.Open("sqlite3", databaseFullLocation) + if err != nil { + return nil, err + } + + database.DB.SQL = dbConn + database.DB.SQL.SetMaxOpenConns(1) + + return database.DB, nil +} diff --git a/database/migration/migrate.go b/database/migration/migrate.go deleted file mode 100644 index ae47e2d8..00000000 --- a/database/migration/migrate.go +++ /dev/null @@ -1,180 +0,0 @@ -package migrations - -import ( - "bytes" - "database/sql" - "errors" - "flag" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "sort" - "strconv" - "strings" -) - -// LoadMigrations will load all migrations in the ./database/migration/migrations folder -func (m *Migrator) LoadMigrations() error { - flag.Visit(func(f *flag.Flag) { - if f.Name == "migrationdir" { - MigrationDir = flag.Lookup("migrationdir").Value.String() - } - }) - - m.Log.Printf("Using migration folder %s\n", MigrationDir) - - migration, err := filepath.Glob(MigrationDir + "/*.sql") - - if err != nil { - return errors.New("failed to load migrations") - } - - if len(migration) == 0 { - return errors.New("no migration files found") - } - - sort.Strings(migration) - - for x := range migration { - err = m.loadMigration(migration[x]) - if err != nil { - return err - } - } - return nil -} - -func (m *Migrator) loadMigration(migration string) error { - file, err := os.Open(migration) - if err != nil { - return err - } - - fileData := strings.Trim(file.Name(), MigrationDir) - fileSeq := strings.Split(fileData, "_") - seq, _ := strconv.Atoi(fileSeq[0]) - - b, err := ioutil.ReadAll(file) - if err != nil { - return err - } - - up := bytes.Split(b, []byte("-- up")) - - if len(up) == 1 { - return fmt.Errorf("invalid migration file %v", file.Name()) - } - - down := strings.Split(string(up[1]), "-- down") - - temp := Migration{ - Sequence: seq, - UpSQL: down[0], - DownSQL: down[1], - } - - m.Migrations = append(m.Migrations, temp) - - return nil -} - -// RunMigration attempts to run current migrations against a database -func (m *Migrator) RunMigration() (err error) { - v, err := m.getCurrentVersion() - if err != nil { - return - } - m.Log.Printf("Current database version: %v\n", v) - - latestSeq := m.Migrations[len(m.Migrations)-1].Sequence - - if v > latestSeq { - return errors.New("current database version is greater than latest migration halting further migrations") - } - - if v == latestSeq { - m.Log.Println("no migrations to be run") - return - } - - tx, err := m.Conn.SQL.Begin() - if err != nil { - return - } - - for y := 0; y < len(m.Migrations); y++ { - if m.Migrations[y].Sequence <= v { - continue - } - - err = m.txBegin(tx, m.checkConvert(m.Migrations[y].UpSQL)) - if err != nil { - return tx.Rollback() - } - - _, err = tx.Exec("update version set version=$1", m.Migrations[y].Sequence) - if err != nil { - return tx.Rollback() - } - } - - err = tx.Commit() - if err != nil { - return tx.Rollback() - } - - m.Log.Println("Migration completed") - m.Log.Printf("New database version: %v\n", latestSeq) - return nil -} - -func (m *Migrator) txBegin(tx *sql.Tx, input string) error { - _, err := tx.Exec(input) - if err != nil { - m.Log.Errorf("%v", err) - return tx.Rollback() - } - return nil -} - -func (m *Migrator) getCurrentVersion() (v int, err error) { - err = m.checkVersionTableExists() - if err != nil { - return - } - err = m.Conn.SQL.QueryRow("select version from version").Scan(&v) - return -} - -func (m *Migrator) checkVersionTableExists() error { - query := ` - CREATE TABLE IF NOT EXISTS version( - version int not null - ); - - INSERT INTO version SELECT 0 WHERE 0=(SELECT COUNT(*) from version); -` - - _, err := m.Conn.SQL.Exec(m.checkConvert(query)) - if err != nil { - return err - } - return nil -} - -func (m *Migrator) checkConvert(input string) string { - if m.Conn.Config.Driver != "sqlite" { - return input - } - - // Common PSQL -> SQLITE conversion - // TODO: Find a better way to handle this list - - r := strings.NewReplacer( - "bigserial", "integer", - "int", "integer", - "now()", "CURRENT_TIMESTAMP") - - return r.Replace(input) -} diff --git a/database/migration/migrate_type.go b/database/migration/migrate_type.go deleted file mode 100644 index 16a13bdf..00000000 --- a/database/migration/migrate_type.go +++ /dev/null @@ -1,37 +0,0 @@ -package migrations - -import ( - "path/filepath" - - "github.com/thrasher-corp/gocryptotrader/database" -) - -var ( - // MigrationDir Default folder to look for migrations to apply - MigrationDir = filepath.Join("./database", "migration", "migrations") -) - -// Migration holds all information passes from a migration file -// Includes: Sequence(version), SQL queries to run on up & down -type Migration struct { - Sequence int - Name string - UpSQL string - DownSQL string -} - -// Migrator holds pointer to database struct slice of Migrations and logger -type Migrator struct { - Conn *database.Database - Migrations []Migration - Log Logger -} - -// Logger interface implementation -// Allows you to BYO Logging/Printing - -type Logger interface { - Printf(format string, v ...interface{}) - Println(v ...interface{}) - Errorf(format string, v ...interface{}) -} diff --git a/database/migration/migration_logger.go b/database/migration/migration_logger.go deleted file mode 100644 index 56b4ebd5..00000000 --- a/database/migration/migration_logger.go +++ /dev/null @@ -1,25 +0,0 @@ -package migrations - -import ( - log "github.com/thrasher-corp/gocryptotrader/logger" -) - -type MLogger struct{} - -// Printf implantation of migration Logger interface -// Passes off to log.Infof -func (t MLogger) Printf(format string, v ...interface{}) { - log.Infof(log.DatabaseMgr, format, v...) -} - -// Println implantation of migration Logger interface -// Passes off to log.Infoln -func (t MLogger) Println(v ...interface{}) { - log.Infoln(log.DatabaseMgr, v...) -} - -// Errorf implantation of migration Logger interface -// Passes off to log.Errorf -func (t MLogger) Errorf(format string, v ...interface{}) { - log.Errorf(log.DatabaseMgr, format, v...) -} diff --git a/database/migration/migrations/1565657999_create_audit_event_table.sql b/database/migration/migrations/1565657999_create_audit_event_table.sql deleted file mode 100755 index 1021c0e7..00000000 --- a/database/migration/migrations/1565657999_create_audit_event_table.sql +++ /dev/null @@ -1,11 +0,0 @@ --- up -CREATE TABLE IF NOT EXISTS audit_event -( - id bigserial PRIMARY KEY NOT NULL, - Type varchar(255) NOT NULL, - Identifier varchar(255) NOT NULL, - Message text NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT now() -); --- down -DROP TABLE audit_event; diff --git a/database/migrations/20190916104959_audit_event/postgres.sql b/database/migrations/20190916104959_audit_event/postgres.sql new file mode 100644 index 00000000..1f69f4a9 --- /dev/null +++ b/database/migrations/20190916104959_audit_event/postgres.sql @@ -0,0 +1,13 @@ +-- +goose Up +-- SQL in this section is executed when the migration is applied. +CREATE TABLE IF NOT EXISTS audit_event +( + id bigserial PRIMARY KEY NOT NULL, + type varchar(255) NOT NULL, + identifier varchar(255) NOT NULL, + message text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT (now() at time zone 'utc') +); +-- +goose Down +-- SQL in this section is executed when the migration is rolled back. +DROP TABLE audit_event; \ No newline at end of file diff --git a/database/migrations/20190916104959_audit_event/sqlite3.sql b/database/migrations/20190916104959_audit_event/sqlite3.sql new file mode 100644 index 00000000..43988cc3 --- /dev/null +++ b/database/migrations/20190916104959_audit_event/sqlite3.sql @@ -0,0 +1,13 @@ +-- +goose Up +-- SQL in this section is executed when the migration is applied. +CREATE TABLE "audit_event" ( + id integer not null primary key, + type text not null, + identifier text not null, + message text not null, + created_at timestamp not null default CURRENT_TIMESTAMP + +); +-- +goose Down +-- SQL in this section is executed when the migration is rolled back. +DROP TABLE audit_event; \ No newline at end of file diff --git a/database/models/audit.go b/database/models/audit.go deleted file mode 100644 index 878b1feb..00000000 --- a/database/models/audit.go +++ /dev/null @@ -1,8 +0,0 @@ -package models - -// AuditEvent is a model of how the data is represented in a database -type AuditEvent struct { - Type string - Identifier string - Message string -} diff --git a/database/models/postgres/audit_event.go b/database/models/postgres/audit_event.go new file mode 100644 index 00000000..eafc9193 --- /dev/null +++ b/database/models/postgres/audit_event.go @@ -0,0 +1,925 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/pkg/errors" + "github.com/thrasher-corp/sqlboiler/boil" + "github.com/thrasher-corp/sqlboiler/queries" + "github.com/thrasher-corp/sqlboiler/queries/qm" + "github.com/thrasher-corp/sqlboiler/queries/qmhelper" + "github.com/thrasher-corp/sqlboiler/strmangle" +) + +// AuditEvent is an object representing the database table. +type AuditEvent struct { + ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"` + Type string `boil:"type" json:"type" toml:"type" yaml:"type"` + Identifier string `boil:"identifier" json:"identifier" toml:"identifier" yaml:"identifier"` + Message string `boil:"message" json:"message" toml:"message" yaml:"message"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + + R *auditEventR `boil:"-" json:"-" toml:"-" yaml:"-"` + L auditEventL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var AuditEventColumns = struct { + ID string + Type string + Identifier string + Message string + CreatedAt string +}{ + ID: "id", + Type: "type", + Identifier: "identifier", + Message: "message", + CreatedAt: "created_at", +} + +// Generated where + +type whereHelperint64 struct{ field string } + +func (w whereHelperint64) EQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint64) NEQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint64) LT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint64) LTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint64) GT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint64) GTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } + +type whereHelperstring struct{ field string } + +func (w whereHelperstring) EQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperstring) NEQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperstring) LT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperstring) LTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperstring) GT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperstring) GTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperstring) IN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} + +type whereHelpertime_Time struct{ field string } + +func (w whereHelpertime_Time) EQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.EQ, x) +} +func (w whereHelpertime_Time) NEQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.NEQ, x) +} +func (w whereHelpertime_Time) LT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpertime_Time) LTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpertime_Time) GT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpertime_Time) GTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +var AuditEventWhere = struct { + ID whereHelperint64 + Type whereHelperstring + Identifier whereHelperstring + Message whereHelperstring + CreatedAt whereHelpertime_Time +}{ + ID: whereHelperint64{field: "\"audit_event\".\"id\""}, + Type: whereHelperstring{field: "\"audit_event\".\"type\""}, + Identifier: whereHelperstring{field: "\"audit_event\".\"identifier\""}, + Message: whereHelperstring{field: "\"audit_event\".\"message\""}, + CreatedAt: whereHelpertime_Time{field: "\"audit_event\".\"created_at\""}, +} + +// AuditEventRels is where relationship names are stored. +var AuditEventRels = struct { +}{} + +// auditEventR is where relationships are stored. +type auditEventR struct { +} + +// NewStruct creates a new relationship struct +func (*auditEventR) NewStruct() *auditEventR { + return &auditEventR{} +} + +// auditEventL is where Load methods for each relationship are stored. +type auditEventL struct{} + +var ( + auditEventAllColumns = []string{"id", "type", "identifier", "message", "created_at"} + auditEventColumnsWithoutDefault = []string{"type", "identifier", "message"} + auditEventColumnsWithDefault = []string{"id", "created_at"} + auditEventPrimaryKeyColumns = []string{"id"} +) + +type ( + // AuditEventSlice is an alias for a slice of pointers to AuditEvent. + // This should generally be used opposed to []AuditEvent. + AuditEventSlice []*AuditEvent + // AuditEventHook is the signature for custom AuditEvent hook methods + AuditEventHook func(context.Context, boil.ContextExecutor, *AuditEvent) error + + auditEventQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + auditEventType = reflect.TypeOf(&AuditEvent{}) + auditEventMapping = queries.MakeStructMapping(auditEventType) + auditEventPrimaryKeyMapping, _ = queries.BindMapping(auditEventType, auditEventMapping, auditEventPrimaryKeyColumns) + auditEventInsertCacheMut sync.RWMutex + auditEventInsertCache = make(map[string]insertCache) + auditEventUpdateCacheMut sync.RWMutex + auditEventUpdateCache = make(map[string]updateCache) + auditEventUpsertCacheMut sync.RWMutex + auditEventUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +var auditEventBeforeInsertHooks []AuditEventHook +var auditEventBeforeUpdateHooks []AuditEventHook +var auditEventBeforeDeleteHooks []AuditEventHook +var auditEventBeforeUpsertHooks []AuditEventHook + +var auditEventAfterInsertHooks []AuditEventHook +var auditEventAfterSelectHooks []AuditEventHook +var auditEventAfterUpdateHooks []AuditEventHook +var auditEventAfterDeleteHooks []AuditEventHook +var auditEventAfterUpsertHooks []AuditEventHook + +// doBeforeInsertHooks executes all "before insert" hooks. +func (o *AuditEvent) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventBeforeInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpdateHooks executes all "before Update" hooks. +func (o *AuditEvent) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventBeforeUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeDeleteHooks executes all "before Delete" hooks. +func (o *AuditEvent) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventBeforeDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpsertHooks executes all "before Upsert" hooks. +func (o *AuditEvent) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventBeforeUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterInsertHooks executes all "after Insert" hooks. +func (o *AuditEvent) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventAfterInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterSelectHooks executes all "after Select" hooks. +func (o *AuditEvent) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventAfterSelectHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpdateHooks executes all "after Update" hooks. +func (o *AuditEvent) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventAfterUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterDeleteHooks executes all "after Delete" hooks. +func (o *AuditEvent) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventAfterDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpsertHooks executes all "after Upsert" hooks. +func (o *AuditEvent) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventAfterUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// AddAuditEventHook registers your hook function for all future operations. +func AddAuditEventHook(hookPoint boil.HookPoint, auditEventHook AuditEventHook) { + switch hookPoint { + case boil.BeforeInsertHook: + auditEventBeforeInsertHooks = append(auditEventBeforeInsertHooks, auditEventHook) + case boil.BeforeUpdateHook: + auditEventBeforeUpdateHooks = append(auditEventBeforeUpdateHooks, auditEventHook) + case boil.BeforeDeleteHook: + auditEventBeforeDeleteHooks = append(auditEventBeforeDeleteHooks, auditEventHook) + case boil.BeforeUpsertHook: + auditEventBeforeUpsertHooks = append(auditEventBeforeUpsertHooks, auditEventHook) + case boil.AfterInsertHook: + auditEventAfterInsertHooks = append(auditEventAfterInsertHooks, auditEventHook) + case boil.AfterSelectHook: + auditEventAfterSelectHooks = append(auditEventAfterSelectHooks, auditEventHook) + case boil.AfterUpdateHook: + auditEventAfterUpdateHooks = append(auditEventAfterUpdateHooks, auditEventHook) + case boil.AfterDeleteHook: + auditEventAfterDeleteHooks = append(auditEventAfterDeleteHooks, auditEventHook) + case boil.AfterUpsertHook: + auditEventAfterUpsertHooks = append(auditEventAfterUpsertHooks, auditEventHook) + } +} + +// One returns a single auditEvent record from the query. +func (q auditEventQuery) One(ctx context.Context, exec boil.ContextExecutor) (*AuditEvent, error) { + o := &AuditEvent{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "postgres: failed to execute a one query for audit_event") + } + + if err := o.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + + return o, nil +} + +// All returns all AuditEvent records from the query. +func (q auditEventQuery) All(ctx context.Context, exec boil.ContextExecutor) (AuditEventSlice, error) { + var o []*AuditEvent + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "postgres: failed to assign all query results to AuditEvent slice") + } + + if len(auditEventAfterSelectHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + } + } + + return o, nil +} + +// Count returns the count of all AuditEvent records in the query. +func (q auditEventQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "postgres: failed to count audit_event rows") + } + + return count, nil +} + +// Exists checks if the row exists in the table. +func (q auditEventQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "postgres: failed to check if audit_event exists") + } + + return count > 0, nil +} + +// AuditEvents retrieves all the records using an executor. +func AuditEvents(mods ...qm.QueryMod) auditEventQuery { + mods = append(mods, qm.From("\"audit_event\"")) + return auditEventQuery{NewQuery(mods...)} +} + +// FindAuditEvent retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindAuditEvent(ctx context.Context, exec boil.ContextExecutor, iD int64, selectCols ...string) (*AuditEvent, error) { + auditEventObj := &AuditEvent{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"audit_event\" where \"id\"=$1", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, auditEventObj) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "postgres: unable to select from audit_event") + } + + return auditEventObj, nil +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *AuditEvent) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("postgres: no audit_event provided for insertion") + } + + var err error + + if err := o.doBeforeInsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(auditEventColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + auditEventInsertCacheMut.RLock() + cache, cached := auditEventInsertCache[key] + auditEventInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + auditEventAllColumns, + auditEventColumnsWithDefault, + auditEventColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(auditEventType, auditEventMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(auditEventType, auditEventMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"audit_event\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"audit_event\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, cache.query) + fmt.Fprintln(boil.DebugWriter, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "postgres: unable to insert into audit_event") + } + + if !cached { + auditEventInsertCacheMut.Lock() + auditEventInsertCache[key] = cache + auditEventInsertCacheMut.Unlock() + } + + return o.doAfterInsertHooks(ctx, exec) +} + +// Update uses an executor to update the AuditEvent. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *AuditEvent) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + var err error + if err = o.doBeforeUpdateHooks(ctx, exec); err != nil { + return 0, err + } + key := makeCacheKey(columns, nil) + auditEventUpdateCacheMut.RLock() + cache, cached := auditEventUpdateCache[key] + auditEventUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + auditEventAllColumns, + auditEventPrimaryKeyColumns, + ) + + if len(wl) == 0 { + return 0, errors.New("postgres: unable to update audit_event, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"audit_event\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, auditEventPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(auditEventType, auditEventMapping, append(wl, auditEventPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, cache.query) + fmt.Fprintln(boil.DebugWriter, values) + } + + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "postgres: unable to update audit_event row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "postgres: failed to get rows affected by update for audit_event") + } + + if !cached { + auditEventUpdateCacheMut.Lock() + auditEventUpdateCache[key] = cache + auditEventUpdateCacheMut.Unlock() + } + + return rowsAff, o.doAfterUpdateHooks(ctx, exec) +} + +// UpdateAll updates all rows with the specified column values. +func (q auditEventQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "postgres: unable to update all for audit_event") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "postgres: unable to retrieve rows affected for audit_event") + } + + return rowsAff, nil +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o AuditEventSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("postgres: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), auditEventPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"audit_event\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, auditEventPrimaryKeyColumns, len(o))) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, args...) + } + + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "postgres: unable to update all in auditEvent slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "postgres: unable to retrieve rows affected all in update all auditEvent") + } + return rowsAff, nil +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *AuditEvent) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns) error { + if o == nil { + return errors.New("postgres: no audit_event provided for upsert") + } + + if err := o.doBeforeUpsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(auditEventColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + auditEventUpsertCacheMut.RLock() + cache, cached := auditEventUpsertCache[key] + auditEventUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, ret := insertColumns.InsertColumnSet( + auditEventAllColumns, + auditEventColumnsWithDefault, + auditEventColumnsWithoutDefault, + nzDefaults, + ) + update := updateColumns.UpdateColumnSet( + auditEventAllColumns, + auditEventPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("postgres: unable to upsert audit_event, could not build update column list") + } + + conflict := conflictColumns + if len(conflict) == 0 { + conflict = make([]string, len(auditEventPrimaryKeyColumns)) + copy(conflict, auditEventPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"audit_event\"", updateOnConflict, ret, update, conflict, insert) + + cache.valueMapping, err = queries.BindMapping(auditEventType, auditEventMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(auditEventType, auditEventMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, cache.query) + fmt.Fprintln(boil.DebugWriter, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if err == sql.ErrNoRows { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "postgres: unable to upsert audit_event") + } + + if !cached { + auditEventUpsertCacheMut.Lock() + auditEventUpsertCache[key] = cache + auditEventUpsertCacheMut.Unlock() + } + + return o.doAfterUpsertHooks(ctx, exec) +} + +// Delete deletes a single AuditEvent record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *AuditEvent) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("postgres: no AuditEvent provided for delete") + } + + if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), auditEventPrimaryKeyMapping) + sql := "DELETE FROM \"audit_event\" WHERE \"id\"=$1" + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, args...) + } + + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "postgres: unable to delete from audit_event") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "postgres: failed to get rows affected by delete for audit_event") + } + + if err := o.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + return rowsAff, nil +} + +// DeleteAll deletes all matching rows. +func (q auditEventQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("postgres: no auditEventQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "postgres: unable to delete all from audit_event") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "postgres: failed to get rows affected by deleteall for audit_event") + } + + return rowsAff, nil +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o AuditEventSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + if len(auditEventBeforeDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), auditEventPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"audit_event\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, auditEventPrimaryKeyColumns, len(o)) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, args) + } + + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "postgres: unable to delete all from auditEvent slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "postgres: failed to get rows affected by deleteall for audit_event") + } + + if len(auditEventAfterDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + return rowsAff, nil +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *AuditEvent) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindAuditEvent(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *AuditEventSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := AuditEventSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), auditEventPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"audit_event\".* FROM \"audit_event\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, auditEventPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "postgres: unable to reload all in AuditEventSlice") + } + + *o = slice + + return nil +} + +// AuditEventExists checks if the AuditEvent row exists. +func AuditEventExists(ctx context.Context, exec boil.ContextExecutor, iD int64) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"audit_event\" where \"id\"=$1 limit 1)" + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, iD) + } + + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "postgres: unable to check if audit_event exists") + } + + return exists, nil +} diff --git a/database/models/postgres/audit_event_test.go b/database/models/postgres/audit_event_test.go new file mode 100644 index 00000000..c9a403ec --- /dev/null +++ b/database/models/postgres/audit_event_test.go @@ -0,0 +1,732 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +import ( + "bytes" + "context" + "reflect" + "testing" + + "github.com/thrasher-corp/sqlboiler/boil" + "github.com/thrasher-corp/sqlboiler/queries" + "github.com/thrasher-corp/sqlboiler/randomize" + "github.com/thrasher-corp/sqlboiler/strmangle" +) + +var ( + // Relationships sometimes use the reflection helper queries.Equal/queries.Assign + // so force a package dependency in case they don't. + _ = queries.Equal +) + +func testAuditEvents(t *testing.T) { + t.Parallel() + + query := AuditEvents() + + if query.Query == nil { + t.Error("expected a query, got nothing") + } +} + +func testAuditEventsDelete(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if rowsAff, err := o.Delete(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testAuditEventsQueryDeleteAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if rowsAff, err := AuditEvents().DeleteAll(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testAuditEventsSliceDeleteAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice := AuditEventSlice{o} + + if rowsAff, err := slice.DeleteAll(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testAuditEventsExists(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + e, err := AuditEventExists(ctx, tx, o.ID) + if err != nil { + t.Errorf("Unable to check if AuditEvent exists: %s", err) + } + if !e { + t.Errorf("Expected AuditEventExists to return true, but got false.") + } +} + +func testAuditEventsFind(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + auditEventFound, err := FindAuditEvent(ctx, tx, o.ID) + if err != nil { + t.Error(err) + } + + if auditEventFound == nil { + t.Error("want a record, got nil") + } +} + +func testAuditEventsBind(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if err = AuditEvents().Bind(ctx, tx, o); err != nil { + t.Error(err) + } +} + +func testAuditEventsOne(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if x, err := AuditEvents().One(ctx, tx); err != nil { + t.Error(err) + } else if x == nil { + t.Error("expected to get a non nil record") + } +} + +func testAuditEventsAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + auditEventOne := &AuditEvent{} + auditEventTwo := &AuditEvent{} + if err = randomize.Struct(seed, auditEventOne, auditEventDBTypes, false, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + if err = randomize.Struct(seed, auditEventTwo, auditEventDBTypes, false, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = auditEventOne.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + if err = auditEventTwo.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice, err := AuditEvents().All(ctx, tx) + if err != nil { + t.Error(err) + } + + if len(slice) != 2 { + t.Error("want 2 records, got:", len(slice)) + } +} + +func testAuditEventsCount(t *testing.T) { + t.Parallel() + + var err error + seed := randomize.NewSeed() + auditEventOne := &AuditEvent{} + auditEventTwo := &AuditEvent{} + if err = randomize.Struct(seed, auditEventOne, auditEventDBTypes, false, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + if err = randomize.Struct(seed, auditEventTwo, auditEventDBTypes, false, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = auditEventOne.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + if err = auditEventTwo.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 2 { + t.Error("want 2 records, got:", count) + } +} + +func auditEventBeforeInsertHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventAfterInsertHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventAfterSelectHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventBeforeUpdateHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventAfterUpdateHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventBeforeDeleteHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventAfterDeleteHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventBeforeUpsertHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventAfterUpsertHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func testAuditEventsHooks(t *testing.T) { + t.Parallel() + + var err error + + ctx := context.Background() + empty := &AuditEvent{} + o := &AuditEvent{} + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, o, auditEventDBTypes, false); err != nil { + t.Errorf("Unable to randomize AuditEvent object: %s", err) + } + + AddAuditEventHook(boil.BeforeInsertHook, auditEventBeforeInsertHook) + if err = o.doBeforeInsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeInsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeInsertHook function to empty object, but got: %#v", o) + } + auditEventBeforeInsertHooks = []AuditEventHook{} + + AddAuditEventHook(boil.AfterInsertHook, auditEventAfterInsertHook) + if err = o.doAfterInsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterInsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterInsertHook function to empty object, but got: %#v", o) + } + auditEventAfterInsertHooks = []AuditEventHook{} + + AddAuditEventHook(boil.AfterSelectHook, auditEventAfterSelectHook) + if err = o.doAfterSelectHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterSelectHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterSelectHook function to empty object, but got: %#v", o) + } + auditEventAfterSelectHooks = []AuditEventHook{} + + AddAuditEventHook(boil.BeforeUpdateHook, auditEventBeforeUpdateHook) + if err = o.doBeforeUpdateHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeUpdateHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeUpdateHook function to empty object, but got: %#v", o) + } + auditEventBeforeUpdateHooks = []AuditEventHook{} + + AddAuditEventHook(boil.AfterUpdateHook, auditEventAfterUpdateHook) + if err = o.doAfterUpdateHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterUpdateHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterUpdateHook function to empty object, but got: %#v", o) + } + auditEventAfterUpdateHooks = []AuditEventHook{} + + AddAuditEventHook(boil.BeforeDeleteHook, auditEventBeforeDeleteHook) + if err = o.doBeforeDeleteHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeDeleteHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeDeleteHook function to empty object, but got: %#v", o) + } + auditEventBeforeDeleteHooks = []AuditEventHook{} + + AddAuditEventHook(boil.AfterDeleteHook, auditEventAfterDeleteHook) + if err = o.doAfterDeleteHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterDeleteHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterDeleteHook function to empty object, but got: %#v", o) + } + auditEventAfterDeleteHooks = []AuditEventHook{} + + AddAuditEventHook(boil.BeforeUpsertHook, auditEventBeforeUpsertHook) + if err = o.doBeforeUpsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeUpsertHook function to empty object, but got: %#v", o) + } + auditEventBeforeUpsertHooks = []AuditEventHook{} + + AddAuditEventHook(boil.AfterUpsertHook, auditEventAfterUpsertHook) + if err = o.doAfterUpsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterUpsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o) + } + auditEventAfterUpsertHooks = []AuditEventHook{} +} + +func testAuditEventsInsert(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } +} + +func testAuditEventsInsertWhitelist(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Whitelist(auditEventColumnsWithoutDefault...)); err != nil { + t.Error(err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } +} + +func testAuditEventsReload(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if err = o.Reload(ctx, tx); err != nil { + t.Error(err) + } +} + +func testAuditEventsReloadAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice := AuditEventSlice{o} + + if err = slice.ReloadAll(ctx, tx); err != nil { + t.Error(err) + } +} + +func testAuditEventsSelect(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice, err := AuditEvents().All(ctx, tx) + if err != nil { + t.Error(err) + } + + if len(slice) != 1 { + t.Error("want one record, got:", len(slice)) + } +} + +var ( + auditEventDBTypes = map[string]string{`ID`: `bigint`, `Type`: `character varying`, `Identifier`: `character varying`, `Message`: `text`, `CreatedAt`: `timestamp with time zone`} + _ = bytes.MinRead +) + +func testAuditEventsUpdate(t *testing.T) { + t.Parallel() + + if 0 == len(auditEventPrimaryKeyColumns) { + t.Skip("Skipping table with no primary key columns") + } + if len(auditEventAllColumns) == len(auditEventPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } + + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + if rowsAff, err := o.Update(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only affect one row but affected", rowsAff) + } +} + +func testAuditEventsSliceUpdateAll(t *testing.T) { + t.Parallel() + + if len(auditEventAllColumns) == len(auditEventPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } + + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + // Remove Primary keys and unique columns from what we plan to update + var fields []string + if strmangle.StringSliceMatch(auditEventAllColumns, auditEventPrimaryKeyColumns) { + fields = auditEventAllColumns + } else { + fields = strmangle.SetComplement( + auditEventAllColumns, + auditEventPrimaryKeyColumns, + ) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + typ := reflect.TypeOf(o).Elem() + n := typ.NumField() + + updateMap := M{} + for _, col := range fields { + for i := 0; i < n; i++ { + f := typ.Field(i) + if f.Tag.Get("boil") == col { + updateMap[col] = value.Field(i).Interface() + } + } + } + + slice := AuditEventSlice{o} + if rowsAff, err := slice.UpdateAll(ctx, tx, updateMap); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("wanted one record updated but got", rowsAff) + } +} + +func testAuditEventsUpsert(t *testing.T) { + t.Parallel() + + if len(auditEventAllColumns) == len(auditEventPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + // Attempt the INSERT side of an UPSERT + o := AuditEvent{} + if err = randomize.Struct(seed, &o, auditEventDBTypes, true); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Upsert(ctx, tx, false, nil, boil.Infer(), boil.Infer()); err != nil { + t.Errorf("Unable to upsert AuditEvent: %s", err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + if count != 1 { + t.Error("want one record, got:", count) + } + + // Attempt the UPDATE side of an UPSERT + if err = randomize.Struct(seed, &o, auditEventDBTypes, false, auditEventPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + if err = o.Upsert(ctx, tx, true, nil, boil.Infer(), boil.Infer()); err != nil { + t.Errorf("Unable to upsert AuditEvent: %s", err) + } + + count, err = AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + if count != 1 { + t.Error("want one record, got:", count) + } +} diff --git a/database/models/postgres/boil_main_test.go b/database/models/postgres/boil_main_test.go new file mode 100644 index 00000000..e1bbd228 --- /dev/null +++ b/database/models/postgres/boil_main_test.go @@ -0,0 +1,119 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +import ( + "database/sql" + "flag" + "fmt" + "math/rand" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/spf13/viper" + "github.com/thrasher-corp/sqlboiler/boil" +) + +var flagDebugMode = flag.Bool("test.sqldebug", false, "Turns on debug mode for SQL statements") +var flagConfigFile = flag.String("test.config", "", "Overrides the default config") + +const outputDirDepth = 3 + +var ( + dbMain tester +) + +type tester interface { + setup() error + conn() (*sql.DB, error) + teardown() error +} + +func TestMain(m *testing.M) { + if dbMain == nil { + fmt.Println("no dbMain tester interface was ready") + os.Exit(-1) + } + + rand.Seed(time.Now().UnixNano()) + + flag.Parse() + + var err error + + // Load configuration + err = initViper() + if err != nil { + fmt.Println("unable to load config file") + os.Exit(-2) + } + + // Set DebugMode so we can see generated sql statements + boil.DebugMode = *flagDebugMode + + if err = dbMain.setup(); err != nil { + fmt.Println("Unable to execute setup:", err) + os.Exit(-4) + } + + conn, err := dbMain.conn() + if err != nil { + fmt.Println("failed to get connection:", err) + } + + var code int + boil.SetDB(conn) + code = m.Run() + + if err = dbMain.teardown(); err != nil { + fmt.Println("Unable to execute teardown:", err) + os.Exit(-5) + } + + os.Exit(code) +} + +func initViper() error { + if flagConfigFile != nil && *flagConfigFile != "" { + viper.SetConfigFile(*flagConfigFile) + if err := viper.ReadInConfig(); err != nil { + return err + } + return nil + } + + var err error + + viper.SetConfigName("sqlboiler") + + configHome := os.Getenv("XDG_CONFIG_HOME") + homePath := os.Getenv("HOME") + wd, err := os.Getwd() + if err != nil { + wd = strings.Repeat("../", outputDirDepth) + } else { + wd = wd + strings.Repeat("/..", outputDirDepth) + } + + configPaths := []string{wd} + if len(configHome) > 0 { + configPaths = append(configPaths, filepath.Join(configHome, "sqlboiler")) + } else { + configPaths = append(configPaths, filepath.Join(homePath, ".config/sqlboiler")) + } + + for _, p := range configPaths { + viper.AddConfigPath(p) + } + + // Ignore errors here, fall back to defaults and validation to provide errs + _ = viper.ReadInConfig() + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + + return nil +} diff --git a/database/models/postgres/boil_queries.go b/database/models/postgres/boil_queries.go new file mode 100644 index 00000000..00fb6f9b --- /dev/null +++ b/database/models/postgres/boil_queries.go @@ -0,0 +1,33 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +import ( + "github.com/thrasher-corp/sqlboiler/drivers" + "github.com/thrasher-corp/sqlboiler/queries" + "github.com/thrasher-corp/sqlboiler/queries/qm" +) + +var dialect = drivers.Dialect{ + LQ: 0x22, + RQ: 0x22, + + UseIndexPlaceholders: true, + UseLastInsertID: false, + UseSchema: false, + UseDefaultKeyword: true, + UseAutoColumns: false, + UseTopClause: false, + UseOutputClause: false, + UseCaseWhenExistsClause: false, +} + +// NewQuery initializes a new Query using the passed in QueryMods +func NewQuery(mods ...qm.QueryMod) *queries.Query { + q := &queries.Query{} + queries.SetDialect(q, &dialect) + qm.Apply(q, mods...) + + return q +} diff --git a/database/models/postgres/boil_queries_test.go b/database/models/postgres/boil_queries_test.go new file mode 100644 index 00000000..1986576b --- /dev/null +++ b/database/models/postgres/boil_queries_test.go @@ -0,0 +1,52 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "math/rand" + "regexp" + + "github.com/thrasher-corp/sqlboiler/boil" +) + +var dbNameRand *rand.Rand + +func MustTx(transactor boil.ContextTransactor, err error) boil.ContextTransactor { + if err != nil { + panic(fmt.Sprintf("Cannot create a transactor: %s", err)) + } + return transactor +} + +func newFKeyDestroyer(regex *regexp.Regexp, reader io.Reader) io.Reader { + return &fKeyDestroyer{ + reader: reader, + rgx: regex, + } +} + +type fKeyDestroyer struct { + reader io.Reader + buf *bytes.Buffer + rgx *regexp.Regexp +} + +func (f *fKeyDestroyer) Read(b []byte) (int, error) { + if f.buf == nil { + all, err := ioutil.ReadAll(f.reader) + if err != nil { + return 0, err + } + + all = bytes.Replace(all, []byte{'\r', '\n'}, []byte{'\n'}, -1) + all = f.rgx.ReplaceAll(all, []byte{}) + f.buf = bytes.NewBuffer(all) + } + + return f.buf.Read(b) +} diff --git a/database/models/postgres/boil_suites_test.go b/database/models/postgres/boil_suites_test.go new file mode 100644 index 00000000..d4e1c166 --- /dev/null +++ b/database/models/postgres/boil_suites_test.go @@ -0,0 +1,121 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +import "testing" + +// This test suite runs each operation test in parallel. +// Example, if your database has 3 tables, the suite will run: +// table1, table2 and table3 Delete in parallel +// table1, table2 and table3 Insert in parallel, and so forth. +// It does NOT run each operation group in parallel. +// Separating the tests thusly grants avoidance of Postgres deadlocks. +func TestParent(t *testing.T) { + t.Run("AuditEvents", testAuditEvents) +} + +func TestDelete(t *testing.T) { + t.Run("AuditEvents", testAuditEventsDelete) +} + +func TestQueryDeleteAll(t *testing.T) { + t.Run("AuditEvents", testAuditEventsQueryDeleteAll) +} + +func TestSliceDeleteAll(t *testing.T) { + t.Run("AuditEvents", testAuditEventsSliceDeleteAll) +} + +func TestExists(t *testing.T) { + t.Run("AuditEvents", testAuditEventsExists) +} + +func TestFind(t *testing.T) { + t.Run("AuditEvents", testAuditEventsFind) +} + +func TestBind(t *testing.T) { + t.Run("AuditEvents", testAuditEventsBind) +} + +func TestOne(t *testing.T) { + t.Run("AuditEvents", testAuditEventsOne) +} + +func TestAll(t *testing.T) { + t.Run("AuditEvents", testAuditEventsAll) +} + +func TestCount(t *testing.T) { + t.Run("AuditEvents", testAuditEventsCount) +} + +func TestHooks(t *testing.T) { + t.Run("AuditEvents", testAuditEventsHooks) +} + +func TestInsert(t *testing.T) { + t.Run("AuditEvents", testAuditEventsInsert) + t.Run("AuditEvents", testAuditEventsInsertWhitelist) +} + +// TestToOne tests cannot be run in parallel +// or deadlocks can occur. +func TestToOne(t *testing.T) {} + +// TestOneToOne tests cannot be run in parallel +// or deadlocks can occur. +func TestOneToOne(t *testing.T) {} + +// TestToMany tests cannot be run in parallel +// or deadlocks can occur. +func TestToMany(t *testing.T) {} + +// TestToOneSet tests cannot be run in parallel +// or deadlocks can occur. +func TestToOneSet(t *testing.T) {} + +// TestToOneRemove tests cannot be run in parallel +// or deadlocks can occur. +func TestToOneRemove(t *testing.T) {} + +// TestOneToOneSet tests cannot be run in parallel +// or deadlocks can occur. +func TestOneToOneSet(t *testing.T) {} + +// TestOneToOneRemove tests cannot be run in parallel +// or deadlocks can occur. +func TestOneToOneRemove(t *testing.T) {} + +// TestToManyAdd tests cannot be run in parallel +// or deadlocks can occur. +func TestToManyAdd(t *testing.T) {} + +// TestToManySet tests cannot be run in parallel +// or deadlocks can occur. +func TestToManySet(t *testing.T) {} + +// TestToManyRemove tests cannot be run in parallel +// or deadlocks can occur. +func TestToManyRemove(t *testing.T) {} + +func TestReload(t *testing.T) { + t.Run("AuditEvents", testAuditEventsReload) +} + +func TestReloadAll(t *testing.T) { + t.Run("AuditEvents", testAuditEventsReloadAll) +} + +func TestSelect(t *testing.T) { + t.Run("AuditEvents", testAuditEventsSelect) +} + +func TestUpdate(t *testing.T) { + t.Run("AuditEvents", testAuditEventsUpdate) +} + +func TestSliceUpdateAll(t *testing.T) { + t.Run("AuditEvents", testAuditEventsSliceUpdateAll) +} diff --git a/database/models/postgres/boil_table_names.go b/database/models/postgres/boil_table_names.go new file mode 100644 index 00000000..c506138f --- /dev/null +++ b/database/models/postgres/boil_table_names.go @@ -0,0 +1,10 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +var TableNames = struct { + AuditEvent string +}{ + AuditEvent: "audit_event", +} diff --git a/database/models/postgres/boil_types.go b/database/models/postgres/boil_types.go new file mode 100644 index 00000000..7ca491aa --- /dev/null +++ b/database/models/postgres/boil_types.go @@ -0,0 +1,52 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +import ( + "strconv" + + "github.com/pkg/errors" + "github.com/thrasher-corp/sqlboiler/boil" + "github.com/thrasher-corp/sqlboiler/strmangle" +) + +// M type is for providing columns and column values to UpdateAll. +type M map[string]interface{} + +// ErrSyncFail occurs during insert when the record could not be retrieved in +// order to populate default value information. This usually happens when LastInsertId +// fails or there was a primary key configuration that was not resolvable. +var ErrSyncFail = errors.New("postgres: failed to synchronize data after insert") + +type insertCache struct { + query string + retQuery string + valueMapping []uint64 + retMapping []uint64 +} + +type updateCache struct { + query string + valueMapping []uint64 +} + +func makeCacheKey(cols boil.Columns, nzDefaults []string) string { + buf := strmangle.GetBuffer() + + buf.WriteString(strconv.Itoa(cols.Kind)) + for _, w := range cols.Cols { + buf.WriteString(w) + } + + if len(nzDefaults) != 0 { + buf.WriteByte('.') + } + for _, nz := range nzDefaults { + buf.WriteString(nz) + } + + str := buf.String() + strmangle.PutBuffer(buf) + return str +} diff --git a/database/models/postgres/psql_main_test.go b/database/models/postgres/psql_main_test.go new file mode 100644 index 00000000..5149dc31 --- /dev/null +++ b/database/models/postgres/psql_main_test.go @@ -0,0 +1,243 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +import ( + "bytes" + "database/sql" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "github.com/kat-co/vala" + _ "github.com/lib/pq" + "github.com/pkg/errors" + "github.com/spf13/viper" + "github.com/thrasher-corp/goose" + "github.com/thrasher-corp/sqlboiler/drivers/sqlboiler-psql/driver" + "github.com/thrasher-corp/sqlboiler/randomize" +) + +var rgxPGFkey = regexp.MustCompile(`(?m)^ALTER TABLE ONLY .*\n\s+ADD CONSTRAINT .*? FOREIGN KEY .*?;\n`) + +type pgTester struct { + dbConn *sql.DB + + dbName string + host string + user string + pass string + sslmode string + port int + + pgPassFile string + + testDBName string + skipSQLCmd bool +} + +func init() { + dbMain = &pgTester{} +} + +// setup dumps the database schema and imports it into a temporary randomly +// generated test database so that tests can be run against it using the +// generated sqlboiler ORM package. +func (p *pgTester) setup() error { + var err error + + viper.SetDefault("psql.schema", "public") + viper.SetDefault("psql.port", 5432) + viper.SetDefault("psql.sslmode", "require") + + p.dbName = viper.GetString("psql.dbname") + p.host = viper.GetString("psql.host") + p.user = viper.GetString("psql.user") + p.pass = viper.GetString("psql.pass") + p.port = viper.GetInt("psql.port") + p.sslmode = viper.GetString("psql.sslmode") + p.testDBName = viper.GetString("psql.testdbname") + p.skipSQLCmd = viper.GetBool("psql.skipsqlcmd") + + err = vala.BeginValidation().Validate( + vala.StringNotEmpty(p.user, "psql.user"), + vala.StringNotEmpty(p.host, "psql.host"), + vala.Not(vala.Equals(p.port, 0, "psql.port")), + vala.StringNotEmpty(p.dbName, "psql.dbname"), + vala.StringNotEmpty(p.sslmode, "psql.sslmode"), + ).Check() + + if err != nil { + return err + } + + // if no testing DB passed + if len(p.testDBName) == 0 { + // Create a randomized db name. + p.testDBName = randomize.StableDBName(p.dbName) + } + + if err = p.makePGPassFile(); err != nil { + return err + } + + if !p.skipSQLCmd { + if err = p.dropTestDB(); err != nil { + return err + } + if err = p.createTestDB(); err != nil { + return err + } + + dumpCmd := exec.Command("pg_dump", "--schema-only", p.dbName) + dumpCmd.Env = append(os.Environ(), p.pgEnv()...) + createCmd := exec.Command("psql", p.testDBName) + createCmd.Env = append(os.Environ(), p.pgEnv()...) + + r, w := io.Pipe() + dumpCmdStderr := &bytes.Buffer{} + createCmdStderr := &bytes.Buffer{} + + dumpCmd.Stdout = w + dumpCmd.Stderr = dumpCmdStderr + + createCmd.Stdin = newFKeyDestroyer(rgxPGFkey, r) + createCmd.Stderr = createCmdStderr + + if err = dumpCmd.Start(); err != nil { + return errors.Wrap(err, "failed to start pg_dump command") + } + if err = createCmd.Start(); err != nil { + return errors.Wrap(err, "failed to start psql command") + } + + if err = dumpCmd.Wait(); err != nil { + fmt.Println(err) + fmt.Println(dumpCmdStderr.String()) + return errors.Wrap(err, "failed to wait for pg_dump command") + } + + _ = w.Close() // After dumpCmd is done, close the write end of the pipe + + if err = createCmd.Wait(); err != nil { + fmt.Println(err) + fmt.Println(createCmdStderr.String()) + return errors.Wrap(err, "failed to wait for psql command") + } + } + + return nil +} + +func (p *pgTester) runCmd(stdin, command string, args ...string) error { + cmd := exec.Command(command, args...) + cmd.Env = append(os.Environ(), p.pgEnv()...) + + if len(stdin) != 0 { + cmd.Stdin = strings.NewReader(stdin) + } + + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + cmd.Stdout = stdout + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { + fmt.Println("failed running:", command, args) + fmt.Println(stdout.String()) + fmt.Println(stderr.String()) + return err + } + + return nil +} + +func (p *pgTester) pgEnv() []string { + return []string{ + fmt.Sprintf("PGHOST=%s", p.host), + fmt.Sprintf("PGPORT=%d", p.port), + fmt.Sprintf("PGUSER=%s", p.user), + fmt.Sprintf("PGPASSFILE=%s", p.pgPassFile), + } +} + +func (p *pgTester) makePGPassFile() error { + tmp, err := ioutil.TempFile("", "pgpass") + if err != nil { + return errors.Wrap(err, "failed to create option file") + } + + fmt.Fprintf(tmp, "%s:%d:postgres:%s", p.host, p.port, p.user) + if len(p.pass) != 0 { + fmt.Fprintf(tmp, ":%s", p.pass) + } + fmt.Fprintln(tmp) + + fmt.Fprintf(tmp, "%s:%d:%s:%s", p.host, p.port, p.dbName, p.user) + if len(p.pass) != 0 { + fmt.Fprintf(tmp, ":%s", p.pass) + } + fmt.Fprintln(tmp) + + fmt.Fprintf(tmp, "%s:%d:%s:%s", p.host, p.port, p.testDBName, p.user) + if len(p.pass) != 0 { + fmt.Fprintf(tmp, ":%s", p.pass) + } + fmt.Fprintln(tmp) + + p.pgPassFile = tmp.Name() + return tmp.Close() +} + +func (p *pgTester) createTestDB() error { + return p.runCmd("", "createdb", p.testDBName) +} + +func (p *pgTester) dropTestDB() error { + return p.runCmd("", "dropdb", "--if-exists", p.testDBName) +} + +// teardown executes cleanup tasks when the tests finish running +func (p *pgTester) teardown() error { + var err error + if err = p.dbConn.Close(); err != nil { + return err + } + p.dbConn = nil + + if !p.skipSQLCmd { + if err = p.dropTestDB(); err != nil { + return err + } + } + + return os.Remove(p.pgPassFile) +} + +func (p *pgTester) conn() (*sql.DB, error) { + if p.dbConn != nil { + return p.dbConn, nil + } + + var err error + p.dbConn, err = sql.Open("postgres", driver.PSQLBuildQueryString(p.user, p.pass, p.testDBName, p.host, p.port, p.sslmode)) + if err != nil { + return nil, err + } + + path := filepath.Join("..", "..", "migrations") + err = goose.Run("up", p.dbConn, "postgres", path, "") + if err != nil { + if err == goose.ErrNoNextVersion { + return p.dbConn, nil + } + return nil, err + } + + return p.dbConn, nil +} diff --git a/database/models/postgres/psql_suites_test.go b/database/models/postgres/psql_suites_test.go new file mode 100644 index 00000000..0dac7a27 --- /dev/null +++ b/database/models/postgres/psql_suites_test.go @@ -0,0 +1,10 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +import "testing" + +func TestUpsert(t *testing.T) { + t.Run("AuditEvents", testAuditEventsUpsert) +} diff --git a/database/models/postgres/psql_upsert.go b/database/models/postgres/psql_upsert.go new file mode 100644 index 00000000..2d362318 --- /dev/null +++ b/database/models/postgres/psql_upsert.go @@ -0,0 +1,61 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package postgres + +import ( + "fmt" + "strings" + + "github.com/thrasher-corp/sqlboiler/drivers" + "github.com/thrasher-corp/sqlboiler/strmangle" +) + +// buildUpsertQueryPostgres builds a SQL statement string using the upsertData provided. +func buildUpsertQueryPostgres(dia drivers.Dialect, tableName string, updateOnConflict bool, ret, update, conflict, whitelist []string) string { + conflict = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, conflict) + whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist) + ret = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, ret) + + buf := strmangle.GetBuffer() + defer strmangle.PutBuffer(buf) + + columns := "DEFAULT VALUES" + if len(whitelist) != 0 { + columns = fmt.Sprintf("(%s) VALUES (%s)", + strings.Join(whitelist, ", "), + strmangle.Placeholders(dia.UseIndexPlaceholders, len(whitelist), 1, 1)) + } + + fmt.Fprintf( + buf, + "INSERT INTO %s %s ON CONFLICT ", + tableName, + columns, + ) + + if !updateOnConflict || len(update) == 0 { + buf.WriteString("DO NOTHING") + } else { + buf.WriteByte('(') + buf.WriteString(strings.Join(conflict, ", ")) + buf.WriteString(") DO UPDATE SET ") + + for i, v := range update { + if i != 0 { + buf.WriteByte(',') + } + quoted := strmangle.IdentQuote(dia.LQ, dia.RQ, v) + buf.WriteString(quoted) + buf.WriteString(" = EXCLUDED.") + buf.WriteString(quoted) + } + } + + if len(ret) != 0 { + buf.WriteString(" RETURNING ") + buf.WriteString(strings.Join(ret, ", ")) + } + + return buf.String() +} diff --git a/database/models/sqlite3/audit_event.go b/database/models/sqlite3/audit_event.go new file mode 100644 index 00000000..428c7cc6 --- /dev/null +++ b/database/models/sqlite3/audit_event.go @@ -0,0 +1,816 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package sqlite3 + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strings" + "sync" + "time" + + "github.com/pkg/errors" + "github.com/thrasher-corp/sqlboiler/boil" + "github.com/thrasher-corp/sqlboiler/queries" + "github.com/thrasher-corp/sqlboiler/queries/qm" + "github.com/thrasher-corp/sqlboiler/queries/qmhelper" + "github.com/thrasher-corp/sqlboiler/strmangle" +) + +// AuditEvent is an object representing the database table. +type AuditEvent struct { + ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"` + Type string `boil:"type" json:"type" toml:"type" yaml:"type"` + Identifier string `boil:"identifier" json:"identifier" toml:"identifier" yaml:"identifier"` + Message string `boil:"message" json:"message" toml:"message" yaml:"message"` + CreatedAt string `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + + R *auditEventR `boil:"-" json:"-" toml:"-" yaml:"-"` + L auditEventL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var AuditEventColumns = struct { + ID string + Type string + Identifier string + Message string + CreatedAt string +}{ + ID: "id", + Type: "type", + Identifier: "identifier", + Message: "message", + CreatedAt: "created_at", +} + +// Generated where + +type whereHelperint64 struct{ field string } + +func (w whereHelperint64) EQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint64) NEQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint64) LT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint64) LTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint64) GT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint64) GTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } + +type whereHelperstring struct{ field string } + +func (w whereHelperstring) EQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperstring) NEQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperstring) LT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperstring) LTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperstring) GT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperstring) GTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperstring) IN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} + +var AuditEventWhere = struct { + ID whereHelperint64 + Type whereHelperstring + Identifier whereHelperstring + Message whereHelperstring + CreatedAt whereHelperstring +}{ + ID: whereHelperint64{field: "\"audit_event\".\"id\""}, + Type: whereHelperstring{field: "\"audit_event\".\"type\""}, + Identifier: whereHelperstring{field: "\"audit_event\".\"identifier\""}, + Message: whereHelperstring{field: "\"audit_event\".\"message\""}, + CreatedAt: whereHelperstring{field: "\"audit_event\".\"created_at\""}, +} + +// AuditEventRels is where relationship names are stored. +var AuditEventRels = struct { +}{} + +// auditEventR is where relationships are stored. +type auditEventR struct { +} + +// NewStruct creates a new relationship struct +func (*auditEventR) NewStruct() *auditEventR { + return &auditEventR{} +} + +// auditEventL is where Load methods for each relationship are stored. +type auditEventL struct{} + +var ( + auditEventAllColumns = []string{"id", "type", "identifier", "message", "created_at"} + auditEventColumnsWithoutDefault = []string{"type", "identifier", "message"} + auditEventColumnsWithDefault = []string{"id", "created_at"} + auditEventPrimaryKeyColumns = []string{"id"} +) + +type ( + // AuditEventSlice is an alias for a slice of pointers to AuditEvent. + // This should generally be used opposed to []AuditEvent. + AuditEventSlice []*AuditEvent + // AuditEventHook is the signature for custom AuditEvent hook methods + AuditEventHook func(context.Context, boil.ContextExecutor, *AuditEvent) error + + auditEventQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + auditEventType = reflect.TypeOf(&AuditEvent{}) + auditEventMapping = queries.MakeStructMapping(auditEventType) + auditEventPrimaryKeyMapping, _ = queries.BindMapping(auditEventType, auditEventMapping, auditEventPrimaryKeyColumns) + auditEventInsertCacheMut sync.RWMutex + auditEventInsertCache = make(map[string]insertCache) + auditEventUpdateCacheMut sync.RWMutex + auditEventUpdateCache = make(map[string]updateCache) + auditEventUpsertCacheMut sync.RWMutex + auditEventUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +var auditEventBeforeInsertHooks []AuditEventHook +var auditEventBeforeUpdateHooks []AuditEventHook +var auditEventBeforeDeleteHooks []AuditEventHook +var auditEventBeforeUpsertHooks []AuditEventHook + +var auditEventAfterInsertHooks []AuditEventHook +var auditEventAfterSelectHooks []AuditEventHook +var auditEventAfterUpdateHooks []AuditEventHook +var auditEventAfterDeleteHooks []AuditEventHook +var auditEventAfterUpsertHooks []AuditEventHook + +// doBeforeInsertHooks executes all "before insert" hooks. +func (o *AuditEvent) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventBeforeInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpdateHooks executes all "before Update" hooks. +func (o *AuditEvent) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventBeforeUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeDeleteHooks executes all "before Delete" hooks. +func (o *AuditEvent) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventBeforeDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpsertHooks executes all "before Upsert" hooks. +func (o *AuditEvent) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventBeforeUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterInsertHooks executes all "after Insert" hooks. +func (o *AuditEvent) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventAfterInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterSelectHooks executes all "after Select" hooks. +func (o *AuditEvent) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventAfterSelectHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpdateHooks executes all "after Update" hooks. +func (o *AuditEvent) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventAfterUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterDeleteHooks executes all "after Delete" hooks. +func (o *AuditEvent) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventAfterDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpsertHooks executes all "after Upsert" hooks. +func (o *AuditEvent) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range auditEventAfterUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// AddAuditEventHook registers your hook function for all future operations. +func AddAuditEventHook(hookPoint boil.HookPoint, auditEventHook AuditEventHook) { + switch hookPoint { + case boil.BeforeInsertHook: + auditEventBeforeInsertHooks = append(auditEventBeforeInsertHooks, auditEventHook) + case boil.BeforeUpdateHook: + auditEventBeforeUpdateHooks = append(auditEventBeforeUpdateHooks, auditEventHook) + case boil.BeforeDeleteHook: + auditEventBeforeDeleteHooks = append(auditEventBeforeDeleteHooks, auditEventHook) + case boil.BeforeUpsertHook: + auditEventBeforeUpsertHooks = append(auditEventBeforeUpsertHooks, auditEventHook) + case boil.AfterInsertHook: + auditEventAfterInsertHooks = append(auditEventAfterInsertHooks, auditEventHook) + case boil.AfterSelectHook: + auditEventAfterSelectHooks = append(auditEventAfterSelectHooks, auditEventHook) + case boil.AfterUpdateHook: + auditEventAfterUpdateHooks = append(auditEventAfterUpdateHooks, auditEventHook) + case boil.AfterDeleteHook: + auditEventAfterDeleteHooks = append(auditEventAfterDeleteHooks, auditEventHook) + case boil.AfterUpsertHook: + auditEventAfterUpsertHooks = append(auditEventAfterUpsertHooks, auditEventHook) + } +} + +// One returns a single auditEvent record from the query. +func (q auditEventQuery) One(ctx context.Context, exec boil.ContextExecutor) (*AuditEvent, error) { + o := &AuditEvent{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "sqlite3: failed to execute a one query for audit_event") + } + + if err := o.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + + return o, nil +} + +// All returns all AuditEvent records from the query. +func (q auditEventQuery) All(ctx context.Context, exec boil.ContextExecutor) (AuditEventSlice, error) { + var o []*AuditEvent + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "sqlite3: failed to assign all query results to AuditEvent slice") + } + + if len(auditEventAfterSelectHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + } + } + + return o, nil +} + +// Count returns the count of all AuditEvent records in the query. +func (q auditEventQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "sqlite3: failed to count audit_event rows") + } + + return count, nil +} + +// Exists checks if the row exists in the table. +func (q auditEventQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "sqlite3: failed to check if audit_event exists") + } + + return count > 0, nil +} + +// AuditEvents retrieves all the records using an executor. +func AuditEvents(mods ...qm.QueryMod) auditEventQuery { + mods = append(mods, qm.From("\"audit_event\"")) + return auditEventQuery{NewQuery(mods...)} +} + +// FindAuditEvent retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindAuditEvent(ctx context.Context, exec boil.ContextExecutor, iD int64, selectCols ...string) (*AuditEvent, error) { + auditEventObj := &AuditEvent{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"audit_event\" where \"id\"=?", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, auditEventObj) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "sqlite3: unable to select from audit_event") + } + + return auditEventObj, nil +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *AuditEvent) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("sqlite3: no audit_event provided for insertion") + } + + var err error + + if err := o.doBeforeInsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(auditEventColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + auditEventInsertCacheMut.RLock() + cache, cached := auditEventInsertCache[key] + auditEventInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + auditEventAllColumns, + auditEventColumnsWithDefault, + auditEventColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(auditEventType, auditEventMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(auditEventType, auditEventMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"audit_event\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"audit_event\" () VALUES ()%s%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + cache.retQuery = fmt.Sprintf("SELECT \"%s\" FROM \"audit_event\" WHERE %s", strings.Join(returnColumns, "\",\""), strmangle.WhereClause("\"", "\"", 0, auditEventPrimaryKeyColumns)) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, cache.query) + fmt.Fprintln(boil.DebugWriter, vals) + } + + result, err := exec.ExecContext(ctx, cache.query, vals...) + + if err != nil { + return errors.Wrap(err, "sqlite3: unable to insert into audit_event") + } + + var lastID int64 + var identifierCols []interface{} + + if len(cache.retMapping) == 0 { + goto CacheNoHooks + } + + lastID, err = result.LastInsertId() + if err != nil { + return ErrSyncFail + } + + o.ID = int64(lastID) + if lastID != 0 && len(cache.retMapping) == 1 && cache.retMapping[0] == auditEventMapping["ID"] { + goto CacheNoHooks + } + + identifierCols = []interface{}{ + o.ID, + } + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, cache.retQuery) + fmt.Fprintln(boil.DebugWriter, identifierCols...) + } + + err = exec.QueryRowContext(ctx, cache.retQuery, identifierCols...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + if err != nil { + return errors.Wrap(err, "sqlite3: unable to populate default values for audit_event") + } + +CacheNoHooks: + if !cached { + auditEventInsertCacheMut.Lock() + auditEventInsertCache[key] = cache + auditEventInsertCacheMut.Unlock() + } + + return o.doAfterInsertHooks(ctx, exec) +} + +// Update uses an executor to update the AuditEvent. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *AuditEvent) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + var err error + if err = o.doBeforeUpdateHooks(ctx, exec); err != nil { + return 0, err + } + key := makeCacheKey(columns, nil) + auditEventUpdateCacheMut.RLock() + cache, cached := auditEventUpdateCache[key] + auditEventUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + auditEventAllColumns, + auditEventPrimaryKeyColumns, + ) + + if len(wl) == 0 { + return 0, errors.New("sqlite3: unable to update audit_event, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"audit_event\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 0, wl), + strmangle.WhereClause("\"", "\"", 0, auditEventPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(auditEventType, auditEventMapping, append(wl, auditEventPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, cache.query) + fmt.Fprintln(boil.DebugWriter, values) + } + + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "sqlite3: unable to update audit_event row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "sqlite3: failed to get rows affected by update for audit_event") + } + + if !cached { + auditEventUpdateCacheMut.Lock() + auditEventUpdateCache[key] = cache + auditEventUpdateCacheMut.Unlock() + } + + return rowsAff, o.doAfterUpdateHooks(ctx, exec) +} + +// UpdateAll updates all rows with the specified column values. +func (q auditEventQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "sqlite3: unable to update all for audit_event") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "sqlite3: unable to retrieve rows affected for audit_event") + } + + return rowsAff, nil +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o AuditEventSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("sqlite3: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), auditEventPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"audit_event\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 0, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, auditEventPrimaryKeyColumns, len(o))) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, args...) + } + + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "sqlite3: unable to update all in auditEvent slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "sqlite3: unable to retrieve rows affected all in update all auditEvent") + } + return rowsAff, nil +} + +// Delete deletes a single AuditEvent record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *AuditEvent) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("sqlite3: no AuditEvent provided for delete") + } + + if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), auditEventPrimaryKeyMapping) + sql := "DELETE FROM \"audit_event\" WHERE \"id\"=?" + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, args...) + } + + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "sqlite3: unable to delete from audit_event") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "sqlite3: failed to get rows affected by delete for audit_event") + } + + if err := o.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + return rowsAff, nil +} + +// DeleteAll deletes all matching rows. +func (q auditEventQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("sqlite3: no auditEventQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "sqlite3: unable to delete all from audit_event") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "sqlite3: failed to get rows affected by deleteall for audit_event") + } + + return rowsAff, nil +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o AuditEventSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + if len(auditEventBeforeDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), auditEventPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"audit_event\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, auditEventPrimaryKeyColumns, len(o)) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, args) + } + + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "sqlite3: unable to delete all from auditEvent slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "sqlite3: failed to get rows affected by deleteall for audit_event") + } + + if len(auditEventAfterDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + return rowsAff, nil +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *AuditEvent) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindAuditEvent(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *AuditEventSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := AuditEventSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), auditEventPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"audit_event\".* FROM \"audit_event\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, auditEventPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "sqlite3: unable to reload all in AuditEventSlice") + } + + *o = slice + + return nil +} + +// AuditEventExists checks if the AuditEvent row exists. +func AuditEventExists(ctx context.Context, exec boil.ContextExecutor, iD int64) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"audit_event\" where \"id\"=? limit 1)" + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, iD) + } + + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "sqlite3: unable to check if audit_event exists") + } + + return exists, nil +} diff --git a/database/models/sqlite3/audit_event_test.go b/database/models/sqlite3/audit_event_test.go new file mode 100644 index 00000000..25e1e473 --- /dev/null +++ b/database/models/sqlite3/audit_event_test.go @@ -0,0 +1,684 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package sqlite3 + +import ( + "bytes" + "context" + "reflect" + "testing" + + "github.com/thrasher-corp/sqlboiler/boil" + "github.com/thrasher-corp/sqlboiler/queries" + "github.com/thrasher-corp/sqlboiler/randomize" + "github.com/thrasher-corp/sqlboiler/strmangle" +) + +var ( + // Relationships sometimes use the reflection helper queries.Equal/queries.Assign + // so force a package dependency in case they don't. + _ = queries.Equal +) + +func testAuditEvents(t *testing.T) { + t.Parallel() + + query := AuditEvents() + + if query.Query == nil { + t.Error("expected a query, got nothing") + } +} + +func testAuditEventsDelete(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if rowsAff, err := o.Delete(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testAuditEventsQueryDeleteAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if rowsAff, err := AuditEvents().DeleteAll(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testAuditEventsSliceDeleteAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice := AuditEventSlice{o} + + if rowsAff, err := slice.DeleteAll(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testAuditEventsExists(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + e, err := AuditEventExists(ctx, tx, o.ID) + if err != nil { + t.Errorf("Unable to check if AuditEvent exists: %s", err) + } + if !e { + t.Errorf("Expected AuditEventExists to return true, but got false.") + } +} + +func testAuditEventsFind(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + auditEventFound, err := FindAuditEvent(ctx, tx, o.ID) + if err != nil { + t.Error(err) + } + + if auditEventFound == nil { + t.Error("want a record, got nil") + } +} + +func testAuditEventsBind(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if err = AuditEvents().Bind(ctx, tx, o); err != nil { + t.Error(err) + } +} + +func testAuditEventsOne(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if x, err := AuditEvents().One(ctx, tx); err != nil { + t.Error(err) + } else if x == nil { + t.Error("expected to get a non nil record") + } +} + +func testAuditEventsAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + auditEventOne := &AuditEvent{} + auditEventTwo := &AuditEvent{} + if err = randomize.Struct(seed, auditEventOne, auditEventDBTypes, false, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + if err = randomize.Struct(seed, auditEventTwo, auditEventDBTypes, false, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = auditEventOne.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + if err = auditEventTwo.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice, err := AuditEvents().All(ctx, tx) + if err != nil { + t.Error(err) + } + + if len(slice) != 2 { + t.Error("want 2 records, got:", len(slice)) + } +} + +func testAuditEventsCount(t *testing.T) { + t.Parallel() + + var err error + seed := randomize.NewSeed() + auditEventOne := &AuditEvent{} + auditEventTwo := &AuditEvent{} + if err = randomize.Struct(seed, auditEventOne, auditEventDBTypes, false, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + if err = randomize.Struct(seed, auditEventTwo, auditEventDBTypes, false, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = auditEventOne.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + if err = auditEventTwo.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 2 { + t.Error("want 2 records, got:", count) + } +} + +func auditEventBeforeInsertHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventAfterInsertHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventAfterSelectHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventBeforeUpdateHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventAfterUpdateHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventBeforeDeleteHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventAfterDeleteHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventBeforeUpsertHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func auditEventAfterUpsertHook(ctx context.Context, e boil.ContextExecutor, o *AuditEvent) error { + *o = AuditEvent{} + return nil +} + +func testAuditEventsHooks(t *testing.T) { + t.Parallel() + + var err error + + ctx := context.Background() + empty := &AuditEvent{} + o := &AuditEvent{} + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, o, auditEventDBTypes, false); err != nil { + t.Errorf("Unable to randomize AuditEvent object: %s", err) + } + + AddAuditEventHook(boil.BeforeInsertHook, auditEventBeforeInsertHook) + if err = o.doBeforeInsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeInsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeInsertHook function to empty object, but got: %#v", o) + } + auditEventBeforeInsertHooks = []AuditEventHook{} + + AddAuditEventHook(boil.AfterInsertHook, auditEventAfterInsertHook) + if err = o.doAfterInsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterInsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterInsertHook function to empty object, but got: %#v", o) + } + auditEventAfterInsertHooks = []AuditEventHook{} + + AddAuditEventHook(boil.AfterSelectHook, auditEventAfterSelectHook) + if err = o.doAfterSelectHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterSelectHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterSelectHook function to empty object, but got: %#v", o) + } + auditEventAfterSelectHooks = []AuditEventHook{} + + AddAuditEventHook(boil.BeforeUpdateHook, auditEventBeforeUpdateHook) + if err = o.doBeforeUpdateHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeUpdateHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeUpdateHook function to empty object, but got: %#v", o) + } + auditEventBeforeUpdateHooks = []AuditEventHook{} + + AddAuditEventHook(boil.AfterUpdateHook, auditEventAfterUpdateHook) + if err = o.doAfterUpdateHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterUpdateHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterUpdateHook function to empty object, but got: %#v", o) + } + auditEventAfterUpdateHooks = []AuditEventHook{} + + AddAuditEventHook(boil.BeforeDeleteHook, auditEventBeforeDeleteHook) + if err = o.doBeforeDeleteHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeDeleteHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeDeleteHook function to empty object, but got: %#v", o) + } + auditEventBeforeDeleteHooks = []AuditEventHook{} + + AddAuditEventHook(boil.AfterDeleteHook, auditEventAfterDeleteHook) + if err = o.doAfterDeleteHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterDeleteHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterDeleteHook function to empty object, but got: %#v", o) + } + auditEventAfterDeleteHooks = []AuditEventHook{} + + AddAuditEventHook(boil.BeforeUpsertHook, auditEventBeforeUpsertHook) + if err = o.doBeforeUpsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeUpsertHook function to empty object, but got: %#v", o) + } + auditEventBeforeUpsertHooks = []AuditEventHook{} + + AddAuditEventHook(boil.AfterUpsertHook, auditEventAfterUpsertHook) + if err = o.doAfterUpsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterUpsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o) + } + auditEventAfterUpsertHooks = []AuditEventHook{} +} + +func testAuditEventsInsert(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } +} + +func testAuditEventsInsertWhitelist(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Whitelist(auditEventColumnsWithoutDefault...)); err != nil { + t.Error(err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } +} + +func testAuditEventsReload(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if err = o.Reload(ctx, tx); err != nil { + t.Error(err) + } +} + +func testAuditEventsReloadAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice := AuditEventSlice{o} + + if err = slice.ReloadAll(ctx, tx); err != nil { + t.Error(err) + } +} + +func testAuditEventsSelect(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice, err := AuditEvents().All(ctx, tx) + if err != nil { + t.Error(err) + } + + if len(slice) != 1 { + t.Error("want one record, got:", len(slice)) + } +} + +var ( + auditEventDBTypes = map[string]string{`ID`: `INTEGER`, `Type`: `TEXT`, `Identifier`: `TEXT`, `Message`: `TEXT`, `CreatedAt`: `TIMESTAMP`} + _ = bytes.MinRead +) + +func testAuditEventsUpdate(t *testing.T) { + t.Parallel() + + if 0 == len(auditEventPrimaryKeyColumns) { + t.Skip("Skipping table with no primary key columns") + } + if len(auditEventAllColumns) == len(auditEventPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } + + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + if rowsAff, err := o.Update(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only affect one row but affected", rowsAff) + } +} + +func testAuditEventsSliceUpdateAll(t *testing.T) { + t.Parallel() + + if len(auditEventAllColumns) == len(auditEventPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + o := &AuditEvent{} + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := AuditEvents().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } + + if err = randomize.Struct(seed, o, auditEventDBTypes, true, auditEventPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize AuditEvent struct: %s", err) + } + + // Remove Primary keys and unique columns from what we plan to update + var fields []string + if strmangle.StringSliceMatch(auditEventAllColumns, auditEventPrimaryKeyColumns) { + fields = auditEventAllColumns + } else { + fields = strmangle.SetComplement( + auditEventAllColumns, + auditEventPrimaryKeyColumns, + ) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + typ := reflect.TypeOf(o).Elem() + n := typ.NumField() + + updateMap := M{} + for _, col := range fields { + for i := 0; i < n; i++ { + f := typ.Field(i) + if f.Tag.Get("boil") == col { + updateMap[col] = value.Field(i).Interface() + } + } + } + + slice := AuditEventSlice{o} + if rowsAff, err := slice.UpdateAll(ctx, tx, updateMap); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("wanted one record updated but got", rowsAff) + } +} diff --git a/database/models/sqlite3/boil_main_test.go b/database/models/sqlite3/boil_main_test.go new file mode 100644 index 00000000..57ff6348 --- /dev/null +++ b/database/models/sqlite3/boil_main_test.go @@ -0,0 +1,119 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package sqlite3 + +import ( + "database/sql" + "flag" + "fmt" + "math/rand" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/spf13/viper" + "github.com/thrasher-corp/sqlboiler/boil" +) + +var flagDebugMode = flag.Bool("test.sqldebug", false, "Turns on debug mode for SQL statements") +var flagConfigFile = flag.String("test.config", "", "Overrides the default config") + +const outputDirDepth = 3 + +var ( + dbMain tester +) + +type tester interface { + setup() error + conn() (*sql.DB, error) + teardown() error +} + +func TestMain(m *testing.M) { + if dbMain == nil { + fmt.Println("no dbMain tester interface was ready") + os.Exit(-1) + } + + rand.Seed(time.Now().UnixNano()) + + flag.Parse() + + var err error + + // Load configuration + err = initViper() + if err != nil { + fmt.Println("unable to load config file") + os.Exit(-2) + } + + // Set DebugMode so we can see generated sql statements + boil.DebugMode = *flagDebugMode + + if err = dbMain.setup(); err != nil { + fmt.Println("Unable to execute setup:", err) + os.Exit(-4) + } + + conn, err := dbMain.conn() + if err != nil { + fmt.Println("failed to get connection:", err) + } + + var code int + boil.SetDB(conn) + code = m.Run() + + if err = dbMain.teardown(); err != nil { + fmt.Println("Unable to execute teardown:", err) + os.Exit(-5) + } + + os.Exit(code) +} + +func initViper() error { + if flagConfigFile != nil && *flagConfigFile != "" { + viper.SetConfigFile(*flagConfigFile) + if err := viper.ReadInConfig(); err != nil { + return err + } + return nil + } + + var err error + + viper.SetConfigName("sqlboiler") + + configHome := os.Getenv("XDG_CONFIG_HOME") + homePath := os.Getenv("HOME") + wd, err := os.Getwd() + if err != nil { + wd = strings.Repeat("../", outputDirDepth) + } else { + wd = wd + strings.Repeat("/..", outputDirDepth) + } + + configPaths := []string{wd} + if len(configHome) > 0 { + configPaths = append(configPaths, filepath.Join(configHome, "sqlboiler")) + } else { + configPaths = append(configPaths, filepath.Join(homePath, ".config/sqlboiler")) + } + + for _, p := range configPaths { + viper.AddConfigPath(p) + } + + // Ignore errors here, fall back to defaults and validation to provide errs + _ = viper.ReadInConfig() + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + + return nil +} diff --git a/database/models/sqlite3/boil_queries.go b/database/models/sqlite3/boil_queries.go new file mode 100644 index 00000000..daec99d8 --- /dev/null +++ b/database/models/sqlite3/boil_queries.go @@ -0,0 +1,33 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package sqlite3 + +import ( + "github.com/thrasher-corp/sqlboiler/drivers" + "github.com/thrasher-corp/sqlboiler/queries" + "github.com/thrasher-corp/sqlboiler/queries/qm" +) + +var dialect = drivers.Dialect{ + LQ: 0x22, + RQ: 0x22, + + UseIndexPlaceholders: false, + UseLastInsertID: true, + UseSchema: false, + UseDefaultKeyword: false, + UseAutoColumns: false, + UseTopClause: false, + UseOutputClause: false, + UseCaseWhenExistsClause: false, +} + +// NewQuery initializes a new Query using the passed in QueryMods +func NewQuery(mods ...qm.QueryMod) *queries.Query { + q := &queries.Query{} + queries.SetDialect(q, &dialect) + qm.Apply(q, mods...) + + return q +} diff --git a/database/models/sqlite3/boil_queries_test.go b/database/models/sqlite3/boil_queries_test.go new file mode 100644 index 00000000..d10c3425 --- /dev/null +++ b/database/models/sqlite3/boil_queries_test.go @@ -0,0 +1,52 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package sqlite3 + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "math/rand" + "regexp" + + "github.com/thrasher-corp/sqlboiler/boil" +) + +var dbNameRand *rand.Rand + +func MustTx(transactor boil.ContextTransactor, err error) boil.ContextTransactor { + if err != nil { + panic(fmt.Sprintf("Cannot create a transactor: %s", err)) + } + return transactor +} + +func newFKeyDestroyer(regex *regexp.Regexp, reader io.Reader) io.Reader { + return &fKeyDestroyer{ + reader: reader, + rgx: regex, + } +} + +type fKeyDestroyer struct { + reader io.Reader + buf *bytes.Buffer + rgx *regexp.Regexp +} + +func (f *fKeyDestroyer) Read(b []byte) (int, error) { + if f.buf == nil { + all, err := ioutil.ReadAll(f.reader) + if err != nil { + return 0, err + } + + all = bytes.Replace(all, []byte{'\r', '\n'}, []byte{'\n'}, -1) + all = f.rgx.ReplaceAll(all, []byte{}) + f.buf = bytes.NewBuffer(all) + } + + return f.buf.Read(b) +} diff --git a/database/models/sqlite3/boil_suites_test.go b/database/models/sqlite3/boil_suites_test.go new file mode 100644 index 00000000..5b53a446 --- /dev/null +++ b/database/models/sqlite3/boil_suites_test.go @@ -0,0 +1,121 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package sqlite3 + +import "testing" + +// This test suite runs each operation test in parallel. +// Example, if your database has 3 tables, the suite will run: +// table1, table2 and table3 Delete in parallel +// table1, table2 and table3 Insert in parallel, and so forth. +// It does NOT run each operation group in parallel. +// Separating the tests thusly grants avoidance of Postgres deadlocks. +func TestParent(t *testing.T) { + t.Run("AuditEvents", testAuditEvents) +} + +func TestDelete(t *testing.T) { + t.Run("AuditEvents", testAuditEventsDelete) +} + +func TestQueryDeleteAll(t *testing.T) { + t.Run("AuditEvents", testAuditEventsQueryDeleteAll) +} + +func TestSliceDeleteAll(t *testing.T) { + t.Run("AuditEvents", testAuditEventsSliceDeleteAll) +} + +func TestExists(t *testing.T) { + t.Run("AuditEvents", testAuditEventsExists) +} + +func TestFind(t *testing.T) { + t.Run("AuditEvents", testAuditEventsFind) +} + +func TestBind(t *testing.T) { + t.Run("AuditEvents", testAuditEventsBind) +} + +func TestOne(t *testing.T) { + t.Run("AuditEvents", testAuditEventsOne) +} + +func TestAll(t *testing.T) { + t.Run("AuditEvents", testAuditEventsAll) +} + +func TestCount(t *testing.T) { + t.Run("AuditEvents", testAuditEventsCount) +} + +func TestHooks(t *testing.T) { + t.Run("AuditEvents", testAuditEventsHooks) +} + +func TestInsert(t *testing.T) { + t.Run("AuditEvents", testAuditEventsInsert) + t.Run("AuditEvents", testAuditEventsInsertWhitelist) +} + +// TestToOne tests cannot be run in parallel +// or deadlocks can occur. +func TestToOne(t *testing.T) {} + +// TestOneToOne tests cannot be run in parallel +// or deadlocks can occur. +func TestOneToOne(t *testing.T) {} + +// TestToMany tests cannot be run in parallel +// or deadlocks can occur. +func TestToMany(t *testing.T) {} + +// TestToOneSet tests cannot be run in parallel +// or deadlocks can occur. +func TestToOneSet(t *testing.T) {} + +// TestToOneRemove tests cannot be run in parallel +// or deadlocks can occur. +func TestToOneRemove(t *testing.T) {} + +// TestOneToOneSet tests cannot be run in parallel +// or deadlocks can occur. +func TestOneToOneSet(t *testing.T) {} + +// TestOneToOneRemove tests cannot be run in parallel +// or deadlocks can occur. +func TestOneToOneRemove(t *testing.T) {} + +// TestToManyAdd tests cannot be run in parallel +// or deadlocks can occur. +func TestToManyAdd(t *testing.T) {} + +// TestToManySet tests cannot be run in parallel +// or deadlocks can occur. +func TestToManySet(t *testing.T) {} + +// TestToManyRemove tests cannot be run in parallel +// or deadlocks can occur. +func TestToManyRemove(t *testing.T) {} + +func TestReload(t *testing.T) { + t.Run("AuditEvents", testAuditEventsReload) +} + +func TestReloadAll(t *testing.T) { + t.Run("AuditEvents", testAuditEventsReloadAll) +} + +func TestSelect(t *testing.T) { + t.Run("AuditEvents", testAuditEventsSelect) +} + +func TestUpdate(t *testing.T) { + t.Run("AuditEvents", testAuditEventsUpdate) +} + +func TestSliceUpdateAll(t *testing.T) { + t.Run("AuditEvents", testAuditEventsSliceUpdateAll) +} diff --git a/database/models/sqlite3/boil_table_names.go b/database/models/sqlite3/boil_table_names.go new file mode 100644 index 00000000..17af4b3c --- /dev/null +++ b/database/models/sqlite3/boil_table_names.go @@ -0,0 +1,10 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package sqlite3 + +var TableNames = struct { + AuditEvent string +}{ + AuditEvent: "audit_event", +} diff --git a/database/models/sqlite3/boil_types.go b/database/models/sqlite3/boil_types.go new file mode 100644 index 00000000..68f4ae1c --- /dev/null +++ b/database/models/sqlite3/boil_types.go @@ -0,0 +1,52 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package sqlite3 + +import ( + "strconv" + + "github.com/pkg/errors" + "github.com/thrasher-corp/sqlboiler/boil" + "github.com/thrasher-corp/sqlboiler/strmangle" +) + +// M type is for providing columns and column values to UpdateAll. +type M map[string]interface{} + +// ErrSyncFail occurs during insert when the record could not be retrieved in +// order to populate default value information. This usually happens when LastInsertId +// fails or there was a primary key configuration that was not resolvable. +var ErrSyncFail = errors.New("sqlite3: failed to synchronize data after insert") + +type insertCache struct { + query string + retQuery string + valueMapping []uint64 + retMapping []uint64 +} + +type updateCache struct { + query string + valueMapping []uint64 +} + +func makeCacheKey(cols boil.Columns, nzDefaults []string) string { + buf := strmangle.GetBuffer() + + buf.WriteString(strconv.Itoa(cols.Kind)) + for _, w := range cols.Cols { + buf.WriteString(w) + } + + if len(nzDefaults) != 0 { + buf.WriteByte('.') + } + for _, nz := range nzDefaults { + buf.WriteString(nz) + } + + str := buf.String() + strmangle.PutBuffer(buf) + return str +} diff --git a/database/models/sqlite3/sqlite3_main_test.go b/database/models/sqlite3/sqlite3_main_test.go new file mode 100644 index 00000000..96605f7a --- /dev/null +++ b/database/models/sqlite3/sqlite3_main_test.go @@ -0,0 +1,63 @@ +// Code generated by SQLBoiler 3.5.0-gct (https://github.com/thrasher-corp/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package sqlite3 + +import ( + "database/sql" + "fmt" + "math/rand" + "os" + "path/filepath" + "regexp" + + _ "github.com/mattn/go-sqlite3" + "github.com/thrasher-corp/goose" +) + +var rgxSQLitekey = regexp.MustCompile(`(?mi)((,\n)?\s+foreign key.*?\n)+`) + +type sqliteTester struct { + dbConn *sql.DB + + dbName string + testDBName string +} + +func init() { + dbMain = &sqliteTester{} +} + +func (s *sqliteTester) setup() error { + s.testDBName = filepath.Join(os.TempDir(), fmt.Sprintf("boil-sqlite3-%d.sql", rand.Int())) + + return nil +} + +func (s *sqliteTester) teardown() error { + if s.dbConn != nil { + s.dbConn.Close() + } + + return os.Remove(s.testDBName) +} + +func (s *sqliteTester) conn() (*sql.DB, error) { + if s.dbConn != nil { + return s.dbConn, nil + } + + var err error + s.dbConn, err = sql.Open("sqlite3", fmt.Sprintf("file:%s?_loc=UTC", s.testDBName)) + if err != nil { + return nil, err + } + + path := filepath.Join("..", "..", "migrations") + err = goose.Run("up", s.dbConn, "sqlite3", path, "") + if err != nil { + return nil, err + } + + return s.dbConn, nil +} diff --git a/database/repository/audit/audit.go b/database/repository/audit/audit.go index e2ea17dd..b78cbfd4 100644 --- a/database/repository/audit/audit.go +++ b/database/repository/audit/audit.go @@ -1,62 +1,93 @@ package audit import ( - "sync" + "context" + "errors" + "time" "github.com/thrasher-corp/gocryptotrader/database" - "github.com/thrasher-corp/gocryptotrader/database/models" + modelPSQL "github.com/thrasher-corp/gocryptotrader/database/models/postgres" + modelSQLite "github.com/thrasher-corp/gocryptotrader/database/models/sqlite3" + "github.com/thrasher-corp/gocryptotrader/database/repository" log "github.com/thrasher-corp/gocryptotrader/logger" + "github.com/thrasher-corp/sqlboiler/boil" + "github.com/thrasher-corp/sqlboiler/queries/qm" ) -// Repository that is required for each driver type to implement -type Repository interface { - AddEventTx(event []*models.AuditEvent) -} +// TableTimeFormat Go Time format conversion +const TableTimeFormat = "2006-01-02 15:04:05" -var ( - // Audit repository initialise copy of Audit Repository - Audit Repository -) - -type eventPool struct { - events []*models.AuditEvent - eventMu sync.Mutex -} - -var ep eventPool - -// Event allows you to call audit.Event() as long as the audit repository package without the need to include each driver -func Event(msgType, identifier, message string) { - if database.Conn.SQL == nil { +// Event inserts a new audit event to database +func Event(id, msgtype, message string) { + if database.DB.SQL == nil { return } - if Audit == nil { + ctx := context.Background() + ctx = boil.SkipTimestamps(ctx) + + tx, err := database.DB.SQL.BeginTx(ctx, nil) + if err != nil { + log.Errorf(log.Global, "Event transaction begin failed: %v", err) return } - tempEvent := models.AuditEvent{ - Type: msgType, - Identifier: identifier, - Message: message} + if repository.GetSQLDialect() == database.DBSQLite3 { + var tempEvent = modelSQLite.AuditEvent{ + Type: msgtype, + Identifier: id, + Message: message, + } + err = tempEvent.Insert(ctx, tx, boil.Blacklist("created_at")) + } else { + var tempEvent = modelPSQL.AuditEvent{ + Type: msgtype, + Identifier: id, + Message: message, + } + err = tempEvent.Insert(ctx, tx, boil.Blacklist("created_at")) + } - ep.poolEvents(&tempEvent) -} - -func (e *eventPool) poolEvents(event *models.AuditEvent) { - e.eventMu.Lock() - defer e.eventMu.Unlock() - - e.events = append(e.events, event) - - database.Conn.Mu.RLock() - defer database.Conn.Mu.RUnlock() - - if !database.Conn.Connected { - log.Warnln(log.DatabaseMgr, "connection to database interrupted pooling database writes") + if err != nil { + log.Errorf(log.Global, "Event insert failed: %v", err) + err = tx.Rollback() + if err != nil { + log.Errorf(log.Global, "Event Transaction rollback failed: %v", err) + } return } - Audit.AddEventTx(e.events) - e.events = nil + err = tx.Commit() + if err != nil { + log.Errorf(log.Global, "Event Transaction commit failed: %v", err) + err = tx.Rollback() + if err != nil { + log.Errorf(log.Global, "Event Transaction rollback failed: %v", err) + } + return + } +} + +// GetEvent () returns list of order events matching query +func GetEvent(startTime, endTime time.Time, order string, limit int) (interface{}, error) { + if database.DB.SQL == nil { + return nil, errors.New("database is nil") + } + + query := qm.Where("created_at BETWEEN ? AND ?", startTime, endTime) + + orderByQueryString := "id" + if order == "desc" { + orderByQueryString += " desc" + } + + orderByQuery := qm.OrderBy(orderByQueryString) + limitQuery := qm.Limit(limit) + + ctx := context.Background() + if repository.GetSQLDialect() == database.DBSQLite3 { + return modelSQLite.AuditEvents(query, orderByQuery, limitQuery).All(ctx, database.DB.SQL) + } + + return modelPSQL.AuditEvents(query, orderByQuery, limitQuery).All(ctx, database.DB.SQL) } diff --git a/database/repository/audit/postgres/audit.go b/database/repository/audit/postgres/audit.go deleted file mode 100644 index 98fb2d8a..00000000 --- a/database/repository/audit/postgres/audit.go +++ /dev/null @@ -1,52 +0,0 @@ -package audit - -import ( - "github.com/thrasher-corp/gocryptotrader/database" - "github.com/thrasher-corp/gocryptotrader/database/models" - "github.com/thrasher-corp/gocryptotrader/database/repository/audit" - log "github.com/thrasher-corp/gocryptotrader/logger" -) - -type auditRepo struct{} - -// Audit returns a new instance of auditRepo -func Audit() audit.Repository { - return &auditRepo{} -} - -// AddEventTx writes multiple events to database -// writes are done using a transaction with a rollback on error -func (pg *auditRepo) AddEventTx(event []*models.AuditEvent) { - if pg == nil { - return - } - - tx, err := database.Conn.SQL.Begin() - if err != nil { - log.Errorf(log.Global, "Failed to create transaction: %v\n", err) - return - } - - query := `INSERT INTO audit_event (type, identifier, message) VALUES($1, $2, $3)` - - for x := range event { - _, err = tx.Exec(query, &event[x].Type, &event[x].Identifier, &event[x].Message) - - if err != nil { - err = tx.Rollback() - if err != nil { - log.Errorf(log.Global, "Tx Rollback has failed: %v", err) - } - return - } - } - - err = tx.Commit() - if err != nil { - err = tx.Rollback() - if err != nil { - log.Errorf(log.Global, "Tx Rollback has failed: %v", err) - } - return - } -} diff --git a/database/repository/audit/sqlite/audit.go b/database/repository/audit/sqlite/audit.go deleted file mode 100644 index c69c4079..00000000 --- a/database/repository/audit/sqlite/audit.go +++ /dev/null @@ -1,53 +0,0 @@ -package audit - -import ( - "github.com/thrasher-corp/gocryptotrader/database" - "github.com/thrasher-corp/gocryptotrader/database/models" - "github.com/thrasher-corp/gocryptotrader/database/repository/audit" - log "github.com/thrasher-corp/gocryptotrader/logger" -) - -type auditRepo struct{} - -// Audit returns a new instance of auditRepo -func Audit() audit.Repository { - return &auditRepo{} -} - -// AddEventTx writes multiple event to database -// writes are done using a transaction with a rollback on error -func (pg *auditRepo) AddEventTx(event []*models.AuditEvent) { - if pg == nil { - return - } - - tx, err := database.Conn.SQL.Begin() - if err != nil { - log.Errorf(log.Global, "Failed to create transaction: %v\n", err) - return - } - - query := `INSERT INTO audit_event (type, identifier, message) VALUES($1, $2, $3)` - - for x := range event { - _, err = tx.Exec(query, &event[x].Type, &event[x].Identifier, &event[x].Message) - - if err != nil { - err = tx.Rollback() - if err != nil { - log.Errorf(log.Global, "Tx Rollback has failed: %v", err) - } - return - } - } - - err = tx.Commit() - if err != nil { - err = tx.Rollback() - if err != nil { - log.Errorf(log.Global, "Tx Rollback has failed: %v", err) - } - return - } - -} diff --git a/database/repository/repository.go b/database/repository/repository.go new file mode 100644 index 00000000..ed2fab36 --- /dev/null +++ b/database/repository/repository.go @@ -0,0 +1,16 @@ +package repository + +import ( + "github.com/thrasher-corp/gocryptotrader/database" +) + +// GetSQLDialect returns current SQL Dialect based on enabled driver +func GetSQLDialect() string { + switch database.DB.Config.Driver { + case "sqlite", "sqlite3": + return database.DBSQLite3 + case "psql", "postgres", "postgresql": + return database.DBPostgreSQL + } + return "invalid driver" +} diff --git a/database/tests/audit_test.go b/database/tests/audit_test.go index 4d6267cf..59c4d38b 100644 --- a/database/tests/audit_test.go +++ b/database/tests/audit_test.go @@ -2,102 +2,87 @@ package tests import ( "fmt" - "path" "path/filepath" "sync" "testing" + "time" "github.com/thrasher-corp/gocryptotrader/database" "github.com/thrasher-corp/gocryptotrader/database/drivers" - mg "github.com/thrasher-corp/gocryptotrader/database/migration" + "github.com/thrasher-corp/gocryptotrader/database/repository" "github.com/thrasher-corp/gocryptotrader/database/repository/audit" - auditPSQL "github.com/thrasher-corp/gocryptotrader/database/repository/audit/postgres" - auditSQlite "github.com/thrasher-corp/gocryptotrader/database/repository/audit/sqlite" + "github.com/thrasher-corp/goose" ) func TestAudit(t *testing.T) { testCases := []struct { name string - config database.Config - audit audit.Repository + config *database.Config runner func(t *testing.T) - closer func(t *testing.T, dbConn *database.Database) error + closer func(t *testing.T, dbConn *database.Db) error output interface{} }{ { - "SQLite", - database.Config{ - Driver: "sqlite", - ConnectionDetails: drivers.ConnectionDetails{Database: path.Join(tempDir, "./testdb.db")}, + "SQLite-Write", + &database.Config{ + Driver: database.DBSQLite3, + ConnectionDetails: drivers.ConnectionDetails{Database: "./testdb"}, }, - auditSQlite.Audit(), + writeAudit, closeDatabase, nil, }, { - "Postgres", + "SQLite-Read", + &database.Config{ + Driver: database.DBSQLite3, + ConnectionDetails: drivers.ConnectionDetails{Database: "./testdb"}, + }, + + readHelper, + closeDatabase, + nil, + }, + { + "Postgres-Write", postgresTestDatabase, - auditPSQL.Audit(), writeAudit, nil, nil, }, + { + "Postgres-Read", + postgresTestDatabase, + readHelper, + nil, + nil, + }, } for _, tests := range testCases { test := tests t.Run(test.name, func(t *testing.T) { - - mg.MigrationDir = filepath.Join("../migration", "migrations") - if !checkValidConfig(t, &test.config.ConnectionDetails) { t.Skip("database not configured skipping test") } - dbConn, err := connectToDatabase(t, &test.config) + dbConn, err := connectToDatabase(t, test.config) if err != nil { t.Fatal(err) } - - mLogger := mg.MLogger{} - migrations := mg.Migrator{ - Log: mLogger, - } - - migrations.Conn = dbConn - - err = migrations.LoadMigrations() + path := filepath.Join("..", "migrations") + err = goose.Run("up", dbConn.SQL, repository.GetSQLDialect(), path, "") if err != nil { - t.Fatal(err) - } - - err = migrations.RunMigration() - if err != nil { - t.Fatal(err) - } - - if test.audit != nil { - audit.Audit = test.audit + t.Fatalf("failed to run migrations %v", err) } if test.runner != nil { test.runner(t) } - switch v := test.output.(type) { - - case error: - if v.Error() != test.output.(error).Error() { - t.Fatal(err) - } - return - default: - break - } - if test.closer != nil { err = test.closer(t, dbConn) if err != nil { @@ -112,7 +97,7 @@ func writeAudit(t *testing.T) { t.Helper() var wg sync.WaitGroup - for x := 0; x < 200; x++ { + for x := 0; x < 20; x++ { wg.Add(1) go func(x int) { @@ -124,3 +109,12 @@ func writeAudit(t *testing.T) { wg.Wait() } + +func readHelper(t *testing.T) { + t.Helper() + + _, err := audit.GetEvent(time.Now().Add(-time.Hour*60), time.Now(), "asc", 1) + if err != nil { + t.Error(err) + } +} diff --git a/database/tests/db_test.go b/database/tests/database_test.go similarity index 53% rename from database/tests/db_test.go rename to database/tests/database_test.go index 1e4e6610..6c35a64d 100644 --- a/database/tests/db_test.go +++ b/database/tests/database_test.go @@ -4,20 +4,54 @@ import ( "fmt" "io/ioutil" "os" - "path" "reflect" "testing" "github.com/thrasher-corp/gocryptotrader/database" "github.com/thrasher-corp/gocryptotrader/database/drivers" - dbpsql "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres" - dbsqlite "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite" + psqlConn "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres" + sqliteConn "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite3" ) var ( - tempDir string + tempDir string + postgresTestDatabase *database.Config +) - postgresTestDatabase = database.Config{ +func getConnectionDetails() *database.Config { + _, exists := os.LookupEnv("TRAVIS") + if exists { + return &database.Config{ + Enabled: true, + Driver: "postgres", + ConnectionDetails: drivers.ConnectionDetails{ + Host: "localhost", + Port: 5432, + Username: "postgres", + Password: "", + Database: "gct_dev_ci", + SSLMode: "", + }, + } + } + + _, exists = os.LookupEnv("APPVEYOR") + if exists { + return &database.Config{ + Enabled: true, + Driver: "postgres", + ConnectionDetails: drivers.ConnectionDetails{ + Host: "localhost", + Port: 5432, + Username: "postgres", + Password: "Password12!", + Database: "gct_dev_ci", + SSLMode: "", + }, + } + } + + return &database.Config{ Enabled: true, Driver: "postgres", ConnectionDetails: drivers.ConnectionDetails{ @@ -29,10 +63,12 @@ var ( //SSLMode: "", }, } -) +} func TestMain(m *testing.M) { var err error + postgresTestDatabase = getConnectionDetails() + tempDir, err = ioutil.TempDir("", "gct-temp") if err != nil { fmt.Printf("failed to create temp file: %v", err) @@ -52,23 +88,23 @@ func TestMain(m *testing.M) { func TestDatabaseConnect(t *testing.T) { testCases := []struct { name string - config database.Config - closer func(t *testing.T, dbConn *database.Database) error + config *database.Config + closer func(t *testing.T, dbConn *database.Db) error output interface{} }{ { "SQLite", - database.Config{ - Driver: "sqlite", - ConnectionDetails: drivers.ConnectionDetails{Database: path.Join(tempDir, "./testdb.db")}, + &database.Config{ + Driver: database.DBSQLite3, + ConnectionDetails: drivers.ConnectionDetails{Database: "./testdb.db"}, }, closeDatabase, nil, }, { "SQliteNoDatabase", - database.Config{ - Driver: "sqlite", + &database.Config{ + Driver: database.DBSQLite3, ConnectionDetails: drivers.ConnectionDetails{ Host: "localhost", }, @@ -90,7 +126,7 @@ func TestDatabaseConnect(t *testing.T) { t.Skip("database not configured skipping test") } - dbConn, err := connectToDatabase(t, &test.config) + dbConn, err := connectToDatabase(t, test.config) if err != nil { switch v := test.output.(type) { case error: @@ -113,26 +149,28 @@ func TestDatabaseConnect(t *testing.T) { } } -func connectToDatabase(t *testing.T, conn *database.Config) (dbConn *database.Database, err error) { +func connectToDatabase(t *testing.T, conn *database.Config) (dbConn *database.Db, err error) { t.Helper() - database.Conn.Config = conn + database.DB.Config = conn - if conn.Driver == "postgres" { - dbConn, err = dbpsql.Connect() + if conn.Driver == database.DBPostgreSQL { + dbConn, err = psqlConn.Connect() if err != nil { - return + return nil, err } - } else if conn.Driver == "sqlite" { - dbConn, err = dbsqlite.Connect() + } else if conn.Driver == database.DBSQLite3 || conn.Driver == database.DBSQLite { + database.DB.DataPath = tempDir + dbConn, err = sqliteConn.Connect() + if err != nil { - return + return nil, err } } - database.Conn.Connected = true + database.DB.Connected = true return } -func closeDatabase(t *testing.T, conn *database.Database) (err error) { +func closeDatabase(t *testing.T, conn *database.Db) (err error) { t.Helper() if conn != nil { diff --git a/engine/database.go b/engine/database.go index b521ed8b..72f9e7ce 100644 --- a/engine/database.go +++ b/engine/database.go @@ -7,17 +7,14 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/database" - db "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres" - dbsqlite3 "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite" - mg "github.com/thrasher-corp/gocryptotrader/database/migration" - "github.com/thrasher-corp/gocryptotrader/database/repository/audit" - auditPSQL "github.com/thrasher-corp/gocryptotrader/database/repository/audit/postgres" - auditSQLite "github.com/thrasher-corp/gocryptotrader/database/repository/audit/sqlite" + dbpsql "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres" + dbsqlite3 "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite3" log "github.com/thrasher-corp/gocryptotrader/logger" + "github.com/thrasher-corp/sqlboiler/boil" ) var ( - dbConn *database.Database + dbConn *database.Db ) type databaseManager struct { @@ -39,53 +36,24 @@ func (a *databaseManager) Start() (err error) { a.shutdown = make(chan struct{}) if Bot.Config.Database.Enabled { - if Bot.Config.Database.Driver == "postgres" { - dbConn, err = db.Connect() - if err != nil { - return fmt.Errorf("database failed to connect: %v Some features that utilise a database will be unavailable", err) - } - - dbConn.SQL.SetMaxOpenConns(2) - dbConn.SQL.SetMaxIdleConns(1) - dbConn.SQL.SetConnMaxLifetime(time.Hour) - - audit.Audit = auditPSQL.Audit() - } else if Bot.Config.Database.Driver == "sqlite" { + if Bot.Config.Database.Driver == database.DBPostgreSQL { + log.Debugf(log.DatabaseMgr, "Attempting to establish database connection to host %s/%s utilising %s driver\n", + Bot.Config.Database.Host, Bot.Config.Database.Database, Bot.Config.Database.Driver) + dbConn, err = dbpsql.Connect() + } else if Bot.Config.Database.Driver == database.DBSQLite || Bot.Config.Database.Driver == database.DBSQLite3 { + log.Debugf(log.DatabaseMgr, "Attempting to establish database connection to %s utilising %s driver\n", + Bot.Config.Database.Database, Bot.Config.Database.Driver) dbConn, err = dbsqlite3.Connect() - - if err != nil { - return fmt.Errorf("database failed to connect: %v Some features that utilise a database will be unavailable", err) - } - - audit.Audit = auditSQLite.Audit() + } + if err != nil { + return fmt.Errorf("database failed to connect: %v Some features that utilise a database will be unavailable", err) } dbConn.Connected = true - if Bot.Config.Database.Driver == "postgres" { - log.Debugf(log.DatabaseMgr, - "Database connection established to host: %s. Using postgres driver\n", - dbConn.Config.Host) - } else { - log.Debugf(log.DatabaseMgr, - "Database connection established to file database: %s. Using sqlite driver\n", - dbConn.Config.Database) - } - - mLogger := mg.MLogger{} - migrations := mg.Migrator{ - Log: mLogger, - } - - migrations.Conn = dbConn - - err := migrations.LoadMigrations() - if err != nil { - return err - } - - err = migrations.RunMigration() - if err != nil { - return err + DBLogger := database.Logger{} + if Bot.Config.Database.Verbose { + boil.DebugMode = true + boil.DebugWriter = DBLogger } go a.run() @@ -101,10 +69,12 @@ func (a *databaseManager) Stop() error { } log.Debugln(log.DatabaseMgr, "Database manager shutting down...") + err := dbConn.SQL.Close() if err != nil { log.Errorf(log.DatabaseMgr, "Failed to close database: %v", err) } + close(a.shutdown) return nil } @@ -114,6 +84,7 @@ func (a *databaseManager) run() { Bot.ServicesWG.Add(1) t := time.NewTicker(time.Second * 2) + a.running.Store(true) defer func() { diff --git a/engine/engine.go b/engine/engine.go index e41bb44a..c97153db 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -234,6 +234,7 @@ func PrintSettings(s *Settings) { log.Debugf(log.Global, "\t Enable orderbook syncing: %v", s.EnableOrderbookSyncing) log.Debugf(log.Global, "\t Enable websocket routine: %v\n", s.EnableWebsocketRoutine) log.Debugf(log.Global, "\t Enable NTP client: %v", s.EnableNTPClient) + log.Debugf(log.Global, "\t Enable Database manager: %v", s.EnableDatabaseManager) log.Debugf(log.Global, "\t Enable dispatcher: %v", s.EnableDispatcher) log.Debugf(log.Global, "\t Dispatch package max worker amount: %d", s.DispatchMaxWorkerAmount) log.Debugf(log.Global, "- FOREX SETTINGS:") diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 160660a4..1936aae6 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -15,6 +15,9 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/database/models/postgres" + "github.com/thrasher-corp/gocryptotrader/database/models/sqlite3" + "github.com/thrasher-corp/gocryptotrader/database/repository/audit" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -1158,3 +1161,51 @@ func (s *RPCServer) GetExchangeTickerStream(r *gctrpc.GetExchangeTickerStreamReq } } } + +// GetAuditEvent returns matching audit events from database +func (s *RPCServer) GetAuditEvent(ctx context.Context, r *gctrpc.GetAuditEventRequest) (*gctrpc.GetAuditEventResponse, error) { + UTCStartTime, err := time.Parse(audit.TableTimeFormat, r.StartDate) + if err != nil { + return nil, err + } + + UTCSEndTime, err := time.Parse(audit.TableTimeFormat, r.EndDate) + if err != nil { + return nil, err + } + + loc := time.FixedZone("", int(r.Offset)) + + events, err := audit.GetEvent(UTCStartTime, UTCSEndTime, r.OrderBy, int(r.Limit)) + if err != nil { + return nil, err + } + + resp := gctrpc.GetAuditEventResponse{} + + switch v := events.(type) { + case postgres.AuditEventSlice: + for x := range v { + tempEvent := &gctrpc.AuditEvent{ + Type: v[x].Type, + Identifier: v[x].Identifier, + Message: v[x].Message, + Timestamp: v[x].CreatedAt.In(loc).Format(audit.TableTimeFormat), + } + + resp.Events = append(resp.Events, tempEvent) + } + case sqlite3.AuditEventSlice: + for x := range v { + tempEvent := &gctrpc.AuditEvent{ + Type: v[x].Type, + Identifier: v[x].Identifier, + Message: v[x].Message, + Timestamp: v[x].CreatedAt, + } + resp.Events = append(resp.Events, tempEvent) + } + } + + return &resp, nil +} diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index 470ea91c..00cbb677 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -9,8 +9,6 @@ import ( proto "github.com/golang/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" math "math" ) @@ -4904,6 +4902,179 @@ func (m *GetExchangeTickerStreamRequest) GetExchange() string { return "" } +type GetAuditEventRequest struct { + StartDate string `protobuf:"bytes,1,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"` + EndDate string `protobuf:"bytes,2,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"` + OrderBy string `protobuf:"bytes,3,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"` + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + Offset int32 `protobuf:"varint,5,opt,name=offset,proto3" json:"offset,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetAuditEventRequest) Reset() { *m = GetAuditEventRequest{} } +func (m *GetAuditEventRequest) String() string { return proto.CompactTextString(m) } +func (*GetAuditEventRequest) ProtoMessage() {} +func (*GetAuditEventRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{96} +} + +func (m *GetAuditEventRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAuditEventRequest.Unmarshal(m, b) +} +func (m *GetAuditEventRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAuditEventRequest.Marshal(b, m, deterministic) +} +func (m *GetAuditEventRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAuditEventRequest.Merge(m, src) +} +func (m *GetAuditEventRequest) XXX_Size() int { + return xxx_messageInfo_GetAuditEventRequest.Size(m) +} +func (m *GetAuditEventRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetAuditEventRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAuditEventRequest proto.InternalMessageInfo + +func (m *GetAuditEventRequest) GetStartDate() string { + if m != nil { + return m.StartDate + } + return "" +} + +func (m *GetAuditEventRequest) GetEndDate() string { + if m != nil { + return m.EndDate + } + return "" +} + +func (m *GetAuditEventRequest) GetOrderBy() string { + if m != nil { + return m.OrderBy + } + return "" +} + +func (m *GetAuditEventRequest) GetLimit() int32 { + if m != nil { + return m.Limit + } + return 0 +} + +func (m *GetAuditEventRequest) GetOffset() int32 { + if m != nil { + return m.Offset + } + return 0 +} + +type GetAuditEventResponse struct { + Events []*AuditEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetAuditEventResponse) Reset() { *m = GetAuditEventResponse{} } +func (m *GetAuditEventResponse) String() string { return proto.CompactTextString(m) } +func (*GetAuditEventResponse) ProtoMessage() {} +func (*GetAuditEventResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{97} +} + +func (m *GetAuditEventResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAuditEventResponse.Unmarshal(m, b) +} +func (m *GetAuditEventResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAuditEventResponse.Marshal(b, m, deterministic) +} +func (m *GetAuditEventResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAuditEventResponse.Merge(m, src) +} +func (m *GetAuditEventResponse) XXX_Size() int { + return xxx_messageInfo_GetAuditEventResponse.Size(m) +} +func (m *GetAuditEventResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetAuditEventResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAuditEventResponse proto.InternalMessageInfo + +func (m *GetAuditEventResponse) GetEvents() []*AuditEvent { + if m != nil { + return m.Events + } + return nil +} + +type AuditEvent struct { + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Identifier string `protobuf:"bytes,2,opt,name=identifier,proto3" json:"identifier,omitempty"` + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + Timestamp string `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AuditEvent) Reset() { *m = AuditEvent{} } +func (m *AuditEvent) String() string { return proto.CompactTextString(m) } +func (*AuditEvent) ProtoMessage() {} +func (*AuditEvent) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{98} +} + +func (m *AuditEvent) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AuditEvent.Unmarshal(m, b) +} +func (m *AuditEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AuditEvent.Marshal(b, m, deterministic) +} +func (m *AuditEvent) XXX_Merge(src proto.Message) { + xxx_messageInfo_AuditEvent.Merge(m, src) +} +func (m *AuditEvent) XXX_Size() int { + return xxx_messageInfo_AuditEvent.Size(m) +} +func (m *AuditEvent) XXX_DiscardUnknown() { + xxx_messageInfo_AuditEvent.DiscardUnknown(m) +} + +var xxx_messageInfo_AuditEvent proto.InternalMessageInfo + +func (m *AuditEvent) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *AuditEvent) GetIdentifier() string { + if m != nil { + return m.Identifier + } + return "" +} + +func (m *AuditEvent) GetMessage() string { + if m != nil { + return m.Message + } + return "" +} + +func (m *AuditEvent) GetTimestamp() string { + if m != nil { + return m.Timestamp + } + return "" +} + func init() { proto.RegisterType((*GetInfoRequest)(nil), "gctrpc.GetInfoRequest") proto.RegisterType((*GetInfoResponse)(nil), "gctrpc.GetInfoResponse") @@ -5015,305 +5186,318 @@ func init() { proto.RegisterType((*GetExchangeOrderbookStreamRequest)(nil), "gctrpc.GetExchangeOrderbookStreamRequest") proto.RegisterType((*GetTickerStreamRequest)(nil), "gctrpc.GetTickerStreamRequest") proto.RegisterType((*GetExchangeTickerStreamRequest)(nil), "gctrpc.GetExchangeTickerStreamRequest") + proto.RegisterType((*GetAuditEventRequest)(nil), "gctrpc.GetAuditEventRequest") + proto.RegisterType((*GetAuditEventResponse)(nil), "gctrpc.GetAuditEventResponse") + proto.RegisterType((*AuditEvent)(nil), "gctrpc.audit_event") } func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 4673 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3c, 0x4d, 0x6f, 0x1c, 0x57, - 0x72, 0xe8, 0x21, 0x45, 0x72, 0x6a, 0x86, 0xe4, 0xf0, 0xf1, 0x6b, 0x34, 0x24, 0x45, 0xa9, 0xb5, - 0x92, 0x25, 0xad, 0x4d, 0xd9, 0xb2, 0x92, 0x75, 0xd6, 0xce, 0x6e, 0x68, 0x5a, 0xe6, 0x2a, 0xf6, - 0x5a, 0x4c, 0x53, 0x2b, 0x01, 0xde, 0xc0, 0x93, 0xe6, 0xf4, 0xe3, 0xb0, 0xa3, 0x9e, 0xee, 0x76, - 0x77, 0x0f, 0x29, 0x3a, 0x09, 0xb2, 0x30, 0x90, 0x20, 0x01, 0x82, 0xe4, 0xb0, 0x08, 0x90, 0x43, - 0x4e, 0x39, 0x06, 0xc8, 0x25, 0xc8, 0x29, 0x87, 0x45, 0xae, 0x41, 0x8e, 0xb9, 0xe4, 0x07, 0x04, - 0xb9, 0x25, 0x01, 0x02, 0xe4, 0x92, 0x53, 0xf0, 0xea, 0x7d, 0xf4, 0x7b, 0xdd, 0x3d, 0xc3, 0xe1, - 0xae, 0xac, 0xbd, 0xd8, 0xd3, 0xf5, 0xea, 0x55, 0xd5, 0xab, 0x57, 0xaf, 0x5e, 0x55, 0xbd, 0xa2, - 0xa0, 0x9e, 0xc4, 0xbd, 0x9d, 0x38, 0x89, 0xb2, 0x88, 0xcc, 0xf4, 0x7b, 0x59, 0x12, 0xf7, 0x3a, - 0x9b, 0xfd, 0x28, 0xea, 0x07, 0xf4, 0xbe, 0x1b, 0xfb, 0xf7, 0xdd, 0x30, 0x8c, 0x32, 0x37, 0xf3, - 0xa3, 0x30, 0xe5, 0x58, 0x76, 0x0b, 0x16, 0xf6, 0x69, 0xf6, 0x38, 0x3c, 0x8e, 0x1c, 0xfa, 0xe5, - 0x90, 0xa6, 0x99, 0xfd, 0x0f, 0xd3, 0xb0, 0xa8, 0x40, 0x69, 0x1c, 0x85, 0x29, 0x25, 0x6b, 0x30, - 0x33, 0x8c, 0x33, 0x7f, 0x40, 0xdb, 0xd6, 0x75, 0xeb, 0x4e, 0xdd, 0x11, 0x5f, 0xe4, 0x3e, 0x2c, - 0xbb, 0xa7, 0xae, 0x1f, 0xb8, 0x47, 0x01, 0xed, 0xd2, 0x97, 0xbd, 0x13, 0x37, 0xec, 0xd3, 0xb4, - 0x5d, 0xbb, 0x6e, 0xdd, 0x99, 0x72, 0x88, 0x1a, 0x7a, 0x24, 0x47, 0xc8, 0xb7, 0x61, 0x89, 0x86, - 0x0c, 0xe4, 0x69, 0xe8, 0x53, 0x88, 0xde, 0x12, 0x03, 0x39, 0xf2, 0x43, 0x58, 0xf3, 0xe8, 0xb1, - 0x3b, 0x0c, 0xb2, 0xee, 0x71, 0x94, 0xd0, 0x97, 0xdd, 0x38, 0x89, 0x4e, 0x7d, 0x8f, 0x26, 0xed, - 0x69, 0x94, 0x62, 0x45, 0x8c, 0x7e, 0xcc, 0x06, 0x0f, 0xc4, 0x18, 0x79, 0x00, 0xab, 0x6a, 0x96, - 0xef, 0x66, 0xdd, 0xde, 0x30, 0x49, 0x68, 0xd8, 0x3b, 0x6f, 0x5f, 0xc1, 0x49, 0xcb, 0x72, 0x92, - 0xef, 0x66, 0x7b, 0x62, 0x88, 0x3c, 0x87, 0x56, 0x3a, 0x3c, 0x4a, 0xcf, 0xd3, 0x8c, 0x0e, 0xba, - 0x69, 0xe6, 0x66, 0xc3, 0xb4, 0x3d, 0x73, 0x7d, 0xea, 0x4e, 0xe3, 0xc1, 0x9b, 0x3b, 0x5c, 0x8d, - 0x3b, 0x05, 0x95, 0xec, 0x1c, 0x4a, 0xfc, 0x43, 0x44, 0x7f, 0x14, 0x66, 0xc9, 0xb9, 0xb3, 0x98, - 0x9a, 0x50, 0xf2, 0x19, 0xcc, 0x27, 0x71, 0xaf, 0x4b, 0x43, 0x2f, 0x8e, 0xfc, 0x30, 0x4b, 0xdb, - 0xb3, 0x48, 0xf5, 0xee, 0x28, 0xaa, 0x4e, 0xdc, 0x7b, 0x24, 0x71, 0x39, 0xc9, 0x66, 0xa2, 0x81, - 0x3a, 0x1f, 0xc2, 0x4a, 0x15, 0x63, 0xd2, 0x82, 0xa9, 0x17, 0xf4, 0x5c, 0xec, 0x0e, 0xfb, 0x49, - 0x56, 0xe0, 0xca, 0xa9, 0x1b, 0x0c, 0x29, 0x6e, 0xc6, 0x9c, 0xc3, 0x3f, 0xbe, 0x5b, 0x7b, 0xcf, - 0xea, 0x3c, 0x85, 0xa5, 0x12, 0x9b, 0x0a, 0x02, 0x77, 0x75, 0x02, 0x8d, 0x07, 0xcb, 0x52, 0x64, - 0xe7, 0x60, 0x4f, 0xce, 0xd5, 0xa8, 0xda, 0x37, 0x60, 0x7b, 0x9f, 0x66, 0x7b, 0xd1, 0x60, 0x30, - 0x0c, 0xfd, 0x1e, 0xda, 0x98, 0x43, 0x03, 0xf7, 0x9c, 0x26, 0xa9, 0xb4, 0xac, 0xcf, 0x60, 0xa5, - 0x6a, 0x9c, 0xb4, 0x61, 0x56, 0xec, 0x3d, 0xf2, 0x9f, 0x73, 0xe4, 0x27, 0xd9, 0x84, 0x7a, 0x2f, - 0x0a, 0x43, 0xda, 0xcb, 0xa8, 0x27, 0x16, 0x92, 0x03, 0xec, 0x3f, 0xae, 0xc1, 0xf5, 0xd1, 0x3c, - 0x85, 0xe9, 0x7e, 0x05, 0x6b, 0x3d, 0x1d, 0xa1, 0x9b, 0x08, 0x8c, 0xb6, 0x85, 0x5b, 0xb1, 0xa7, - 0x6d, 0xc5, 0x58, 0x4a, 0x3b, 0x95, 0xa3, 0x7c, 0x93, 0x56, 0x7b, 0x55, 0x63, 0x9d, 0x63, 0xe8, - 0x8c, 0x9e, 0x54, 0xa1, 0xf2, 0x07, 0xa6, 0xca, 0x37, 0xa5, 0x68, 0x55, 0x44, 0x74, 0xdd, 0x7f, - 0x07, 0xd6, 0xf7, 0x69, 0x48, 0x13, 0xbf, 0xa7, 0x8c, 0x43, 0xe8, 0x9c, 0x69, 0x50, 0xd9, 0xa4, - 0x60, 0x95, 0x03, 0xec, 0x0e, 0xb4, 0xcb, 0x13, 0xf9, 0x72, 0xed, 0x35, 0x58, 0xd9, 0xa7, 0x99, - 0x82, 0xab, 0x5d, 0xfc, 0x99, 0x05, 0xab, 0x38, 0x90, 0x1e, 0xa5, 0xe7, 0x7c, 0x40, 0xa8, 0xfa, - 0x77, 0x60, 0x49, 0x91, 0x4e, 0xe5, 0x31, 0xe2, 0x5a, 0x7e, 0x57, 0xd3, 0x72, 0x79, 0x66, 0x7e, - 0x98, 0x52, 0xfd, 0x34, 0xe5, 0x67, 0x52, 0x80, 0x3b, 0x7b, 0xb0, 0x5a, 0x89, 0x7a, 0x19, 0xfb, - 0xb7, 0xdb, 0xb0, 0xb6, 0x4f, 0x33, 0xcd, 0x8c, 0x35, 0x03, 0x6d, 0x68, 0x60, 0x66, 0x97, 0x69, - 0xe6, 0x26, 0x59, 0x6e, 0x97, 0xe2, 0x93, 0xdc, 0x82, 0x85, 0xc0, 0x4f, 0x33, 0x1a, 0x76, 0x5d, - 0xcf, 0x4b, 0x68, 0xca, 0x5d, 0x5e, 0xdd, 0x99, 0xe7, 0xd0, 0x5d, 0x0e, 0xb4, 0xff, 0xd1, 0x62, - 0x1b, 0x53, 0x60, 0x25, 0x94, 0xf5, 0x29, 0xd4, 0x73, 0xaf, 0xc0, 0x95, 0xb4, 0xa3, 0x29, 0xa9, - 0x6a, 0xce, 0x4e, 0xc1, 0x35, 0xe4, 0x04, 0x3a, 0xbf, 0x05, 0x0b, 0xaf, 0xfa, 0x40, 0xbf, 0x07, - 0x1d, 0x61, 0x1b, 0xd2, 0x23, 0x7f, 0xe6, 0x0e, 0xa8, 0xb4, 0xab, 0x0e, 0xcc, 0x49, 0x07, 0x2e, - 0x78, 0xa8, 0x6f, 0x7b, 0x0b, 0x36, 0x2a, 0x67, 0x0a, 0xc3, 0xba, 0x0f, 0xcb, 0xfb, 0x34, 0x53, - 0x6e, 0x5e, 0x52, 0x1c, 0xe9, 0x05, 0xec, 0x87, 0x68, 0x89, 0xda, 0x04, 0xa1, 0xc2, 0x4d, 0xa8, - 0xe7, 0x97, 0x88, 0xb0, 0x6d, 0x05, 0xb0, 0x1f, 0xa0, 0x99, 0xca, 0x59, 0x4f, 0x9e, 0x1e, 0x38, - 0x94, 0x4f, 0xbb, 0x0a, 0x73, 0x51, 0x16, 0x77, 0x7b, 0x91, 0x27, 0x45, 0x9f, 0x8d, 0xb2, 0x78, - 0x2f, 0xf2, 0xa8, 0x30, 0x0d, 0x6d, 0x8e, 0x32, 0x8d, 0xbf, 0xe1, 0x5b, 0x69, 0x0e, 0x09, 0x39, - 0x7e, 0x13, 0xea, 0x92, 0xa0, 0xdc, 0xca, 0xb7, 0xb4, 0xad, 0xac, 0x9a, 0xb3, 0xf3, 0x84, 0x73, - 0x14, 0x3b, 0x39, 0x27, 0x04, 0x48, 0x3b, 0xef, 0xc3, 0xbc, 0x31, 0x74, 0x91, 0x65, 0xd7, 0xf5, - 0x2d, 0x7b, 0x08, 0x6b, 0x1f, 0xf9, 0xa9, 0x7e, 0xe3, 0x4e, 0xb2, 0x5d, 0x5f, 0xc0, 0xc2, 0x81, - 0xeb, 0x27, 0xe9, 0xe1, 0x30, 0x8e, 0x23, 0x34, 0xef, 0x37, 0x60, 0x31, 0xbf, 0xd6, 0x63, 0x36, - 0x26, 0x26, 0x2d, 0x28, 0x30, 0xce, 0x20, 0x37, 0x61, 0x5e, 0x5e, 0xe7, 0x1c, 0x8d, 0x8b, 0xd4, - 0x14, 0x40, 0x44, 0xb2, 0xbf, 0x9e, 0x36, 0x54, 0x67, 0x04, 0x16, 0x04, 0xa6, 0x43, 0x57, 0x85, - 0x15, 0xf8, 0x5b, 0x37, 0x84, 0x9a, 0x79, 0x1d, 0xb4, 0x61, 0xf6, 0x94, 0x26, 0x47, 0x51, 0x4a, - 0x31, 0x66, 0x98, 0x73, 0xe4, 0x27, 0x13, 0x64, 0x98, 0xfa, 0x61, 0xbf, 0x9b, 0xba, 0xa1, 0x77, - 0x14, 0xbd, 0xc4, 0x08, 0x61, 0xce, 0x69, 0x22, 0xf0, 0x90, 0xc3, 0xc8, 0x0d, 0x68, 0x9e, 0x64, - 0x59, 0xdc, 0x65, 0xa1, 0x4b, 0x34, 0xcc, 0x44, 0x40, 0xd0, 0x60, 0xb0, 0xa7, 0x1c, 0xc4, 0x0e, - 0x36, 0xa2, 0x0c, 0x53, 0x9a, 0xb8, 0x7d, 0x1a, 0x66, 0xed, 0x19, 0x7e, 0xb0, 0x19, 0xf4, 0x47, - 0x12, 0x48, 0xb6, 0x00, 0x10, 0x2d, 0x4e, 0xa2, 0x97, 0xe7, 0xed, 0x59, 0x6e, 0x7a, 0x0c, 0x72, - 0xc0, 0x00, 0x4c, 0x7f, 0x47, 0x6e, 0x4a, 0x65, 0xe8, 0xe1, 0xd3, 0xb4, 0x3d, 0xc7, 0xf5, 0xc7, - 0xc0, 0x7b, 0x0a, 0x4a, 0xba, 0x2c, 0xee, 0x10, 0x5a, 0xef, 0xba, 0x69, 0x4a, 0xb3, 0xb4, 0x5d, - 0x47, 0x03, 0x7a, 0x58, 0x61, 0x40, 0x85, 0xf8, 0x43, 0xcc, 0xdb, 0xc5, 0x69, 0x2a, 0xfe, 0x30, - 0xa0, 0x2c, 0xde, 0x72, 0x87, 0xd9, 0x09, 0x0d, 0x33, 0x76, 0x7b, 0x30, 0x26, 0xb1, 0xdf, 0x06, - 0xd4, 0x4d, 0xcb, 0x18, 0xd8, 0x8d, 0xfd, 0xce, 0xe7, 0x2c, 0xb8, 0x28, 0x53, 0xad, 0x30, 0xc1, - 0x37, 0x4d, 0x57, 0xb2, 0x26, 0x85, 0x35, 0xed, 0x48, 0x37, 0xcd, 0x33, 0x68, 0xed, 0xd3, 0xec, - 0xa9, 0xdf, 0x7b, 0x41, 0x93, 0x09, 0x8c, 0x92, 0xdc, 0x81, 0x69, 0x66, 0x51, 0x82, 0xc1, 0x8a, - 0xba, 0x09, 0x45, 0xc4, 0xc6, 0x18, 0x39, 0x88, 0xc1, 0xf6, 0x02, 0x35, 0xd7, 0xcd, 0xce, 0x63, - 0x6e, 0x17, 0x75, 0xa7, 0x8e, 0x90, 0xa7, 0xe7, 0x31, 0xb5, 0x9f, 0x41, 0x53, 0x9f, 0xc4, 0x9c, - 0x86, 0x47, 0x03, 0x7f, 0xe0, 0x67, 0x34, 0x91, 0x4e, 0x43, 0x01, 0x98, 0x3d, 0xb2, 0x2d, 0x12, - 0x76, 0x8c, 0xbf, 0xd9, 0x79, 0xfb, 0x72, 0x18, 0x65, 0x92, 0x36, 0xff, 0xb0, 0xff, 0xb2, 0x06, - 0x0b, 0x72, 0x39, 0xc2, 0x98, 0xa5, 0xcc, 0xd6, 0x85, 0x32, 0xdf, 0x80, 0x66, 0xe0, 0xa6, 0x59, - 0x77, 0x18, 0x7b, 0xae, 0x0c, 0x6d, 0xa6, 0x9c, 0x06, 0x83, 0xfd, 0x88, 0x83, 0x98, 0x45, 0xcb, - 0xc8, 0x15, 0xcf, 0x96, 0xe0, 0xde, 0xec, 0xe9, 0x8b, 0x21, 0x30, 0xcd, 0xe6, 0xa0, 0xb5, 0x5b, - 0x0e, 0xfe, 0x66, 0xb0, 0x13, 0xbf, 0x7f, 0x82, 0xd6, 0x6d, 0x39, 0xf8, 0x9b, 0xed, 0x60, 0x10, - 0x9d, 0xa1, 0x2d, 0x5b, 0x0e, 0xfb, 0xc9, 0x20, 0x47, 0xbe, 0x87, 0xa6, 0x6b, 0x39, 0xec, 0x27, - 0x83, 0xb8, 0xe9, 0x0b, 0x34, 0x54, 0xcb, 0x61, 0x3f, 0x59, 0xd4, 0x7f, 0x1a, 0x05, 0xc3, 0x01, - 0x6d, 0xd7, 0x11, 0x28, 0xbe, 0xc8, 0x06, 0xd4, 0xe3, 0xc4, 0xef, 0xd1, 0xae, 0x9b, 0x9d, 0xa0, - 0x31, 0x59, 0xce, 0x1c, 0x02, 0x76, 0xb3, 0x13, 0x7b, 0x19, 0x96, 0xd4, 0x46, 0x2b, 0xef, 0xf9, - 0x1c, 0x66, 0x05, 0x64, 0xec, 0xa6, 0xbf, 0x0d, 0xb3, 0x19, 0x47, 0x6b, 0xd7, 0xf0, 0x14, 0x28, - 0xc3, 0x32, 0x35, 0xed, 0x48, 0x34, 0xfb, 0xfb, 0x40, 0x74, 0x6e, 0x62, 0x23, 0xee, 0xe6, 0x74, - 0xb8, 0x3b, 0x5e, 0x34, 0xe9, 0xa4, 0x39, 0x81, 0xaf, 0xf0, 0x32, 0x7a, 0x92, 0x78, 0xcc, 0x91, - 0x44, 0x2f, 0x5e, 0xab, 0x69, 0xfe, 0x10, 0xe6, 0x15, 0xe3, 0xc7, 0x19, 0x1d, 0x30, 0x85, 0xbb, - 0x83, 0x68, 0x18, 0x66, 0xc8, 0xd3, 0x72, 0xc4, 0x17, 0xb3, 0x40, 0xd4, 0x2f, 0xb2, 0xb4, 0x1c, - 0xfe, 0x41, 0x16, 0xa0, 0xe6, 0x7b, 0x22, 0x79, 0xaa, 0xf9, 0x9e, 0xfd, 0x7f, 0x16, 0x2c, 0x69, - 0x0b, 0xb9, 0xb4, 0x51, 0x96, 0x2c, 0xae, 0x56, 0x61, 0x71, 0x77, 0x61, 0xfa, 0xc8, 0xf7, 0x58, - 0xce, 0xc6, 0xf4, 0xba, 0x2a, 0xc9, 0x19, 0xeb, 0x70, 0x10, 0x85, 0xa1, 0xba, 0xe9, 0x8b, 0xb4, - 0x3d, 0x3d, 0x16, 0x95, 0xa1, 0x94, 0xce, 0xc3, 0x95, 0xf2, 0x79, 0x30, 0x75, 0x39, 0x53, 0xd4, - 0x25, 0x8f, 0x56, 0x15, 0x6d, 0x65, 0x79, 0x3d, 0x80, 0x1c, 0x38, 0x76, 0x5b, 0x7f, 0x0d, 0x20, - 0x52, 0x98, 0xc2, 0xfe, 0xae, 0x96, 0x84, 0x56, 0x26, 0xa8, 0x21, 0xdb, 0x9f, 0x60, 0xa8, 0xa1, - 0x33, 0x17, 0xca, 0x7f, 0x60, 0xd0, 0xe4, 0xb6, 0x48, 0x4a, 0x34, 0x53, 0x83, 0xd8, 0xbb, 0x48, - 0x6c, 0xb7, 0xd7, 0x63, 0x5b, 0xaf, 0x25, 0xe6, 0x63, 0xef, 0xf0, 0x67, 0x30, 0x2b, 0x66, 0x08, - 0xb3, 0xe0, 0x08, 0x35, 0xdf, 0x23, 0xef, 0x03, 0x68, 0xf7, 0x10, 0x5f, 0xd7, 0x86, 0x94, 0x41, - 0x4c, 0x92, 0xd6, 0x80, 0xec, 0x34, 0x74, 0xfb, 0x18, 0x96, 0x2b, 0x50, 0x98, 0x28, 0x2a, 0xad, - 0x16, 0xa2, 0xc8, 0x6f, 0xb2, 0x0d, 0x8d, 0x2c, 0xca, 0xdc, 0xa0, 0x9b, 0xdf, 0x10, 0x96, 0x03, - 0x08, 0x7a, 0xc6, 0x20, 0xe8, 0xa0, 0xa2, 0x80, 0x5b, 0x2e, 0x73, 0x50, 0x51, 0xe0, 0xd9, 0x2e, - 0x06, 0x5e, 0xc6, 0xa2, 0x85, 0x0a, 0xc7, 0x6d, 0xd9, 0xb7, 0x61, 0xce, 0xe5, 0x53, 0xe4, 0xc2, - 0x16, 0x0b, 0x0b, 0x73, 0x14, 0x82, 0x4d, 0xf0, 0x06, 0xda, 0x8b, 0xc2, 0x63, 0xbf, 0x2f, 0xad, - 0xe3, 0x0d, 0x74, 0x56, 0x12, 0x96, 0xc7, 0x24, 0x9e, 0x9b, 0xb9, 0xc8, 0xad, 0xe9, 0xe0, 0x6f, - 0xfb, 0x8f, 0x2c, 0x68, 0x1d, 0x44, 0x49, 0x76, 0x1c, 0x05, 0x7e, 0x24, 0xc2, 0x7b, 0x16, 0x8e, - 0xc8, 0xf0, 0x5f, 0xc4, 0x91, 0xe2, 0x93, 0x79, 0xc8, 0x5e, 0xe4, 0x87, 0xdc, 0x56, 0x6b, 0x42, - 0x41, 0x91, 0x1f, 0x32, 0x53, 0x25, 0xd7, 0xa1, 0xe1, 0xd1, 0xb4, 0x97, 0xf8, 0x31, 0x4b, 0xe7, - 0x84, 0x5b, 0xd0, 0x41, 0x8c, 0xf0, 0x91, 0x1b, 0xb8, 0x61, 0x8f, 0x0a, 0xcf, 0x2e, 0x3f, 0xed, - 0x55, 0x74, 0x57, 0x4a, 0x12, 0x2d, 0xb3, 0x36, 0xc1, 0x62, 0x29, 0xbf, 0x0a, 0xf5, 0x58, 0x02, - 0x85, 0xf9, 0xb5, 0xd5, 0x5d, 0x5d, 0x58, 0x8e, 0x93, 0xa3, 0xda, 0x9b, 0x2c, 0xf6, 0xcf, 0xe9, - 0x1d, 0x0e, 0x07, 0x03, 0x37, 0x39, 0x97, 0xdc, 0x42, 0x98, 0xde, 0x8b, 0xfc, 0x90, 0x29, 0x8a, - 0x2d, 0x4a, 0x06, 0x6f, 0xec, 0xb7, 0x2e, 0x7a, 0xcd, 0x10, 0x5d, 0xd7, 0xd6, 0x94, 0xa9, 0xad, - 0x6b, 0x00, 0x31, 0x4d, 0x7a, 0x34, 0xcc, 0xdc, 0xbe, 0x5c, 0xb1, 0x06, 0xb1, 0x4f, 0x80, 0x3c, - 0x39, 0x3e, 0x0e, 0xfc, 0x90, 0x32, 0xb6, 0x42, 0x98, 0x31, 0xda, 0x1f, 0x2d, 0x83, 0xc9, 0x69, - 0xaa, 0xc4, 0xe9, 0x87, 0xb0, 0xf4, 0x24, 0xac, 0x60, 0x24, 0xc9, 0x59, 0xe3, 0xc8, 0xd5, 0x4a, - 0xe4, 0x7e, 0x00, 0x4d, 0x4d, 0xf0, 0x94, 0xbc, 0x07, 0x75, 0x21, 0xa3, 0x4a, 0x14, 0x3a, 0xca, - 0x1b, 0x94, 0x56, 0xe8, 0xe4, 0xc8, 0xf6, 0x5f, 0x59, 0xd0, 0xc8, 0x25, 0x4b, 0xc9, 0x43, 0xb8, - 0xc2, 0xd4, 0x2d, 0xa9, 0x5c, 0x53, 0x54, 0x72, 0x9c, 0x1d, 0xfc, 0x2f, 0x8f, 0x0b, 0x39, 0x72, - 0xe7, 0x10, 0x20, 0x07, 0x56, 0x84, 0x75, 0xf7, 0xcd, 0xb0, 0xee, 0x6a, 0x99, 0xaa, 0x14, 0x4d, - 0x8b, 0xec, 0xfe, 0x65, 0x9a, 0xa5, 0x7b, 0x15, 0xc6, 0x22, 0x6c, 0xf0, 0x2d, 0x68, 0xf0, 0xb3, - 0xc0, 0x3c, 0x80, 0x14, 0xb8, 0x99, 0x97, 0x36, 0xfc, 0xd0, 0x01, 0x3c, 0x1b, 0x38, 0x4e, 0xde, - 0x81, 0x79, 0x14, 0xb6, 0x1b, 0x71, 0x85, 0x88, 0x83, 0x6d, 0x4e, 0x68, 0x22, 0x8a, 0x50, 0x19, - 0x89, 0x61, 0xd5, 0x98, 0xd2, 0x4d, 0xb9, 0x08, 0xe2, 0x92, 0xfa, 0x40, 0x0b, 0xa5, 0x47, 0x49, - 0xc9, 0x95, 0x25, 0x08, 0x8a, 0x31, 0xae, 0xba, 0xe5, 0x5e, 0x79, 0x84, 0xdc, 0x87, 0xa6, 0xe0, - 0x88, 0x9a, 0x11, 0x57, 0x9c, 0x29, 0x63, 0x83, 0x4f, 0x44, 0x04, 0x32, 0x80, 0x15, 0x7d, 0x82, - 0x92, 0xf0, 0x0a, 0x4e, 0x7c, 0x7f, 0x72, 0x09, 0xc3, 0x92, 0x80, 0xa4, 0x57, 0x1a, 0xe8, 0xfc, - 0x36, 0xb4, 0x47, 0x2d, 0xa8, 0x62, 0xdb, 0xef, 0x99, 0xdb, 0xbe, 0x52, 0x61, 0x92, 0xa9, 0x5e, - 0x40, 0xfc, 0x1c, 0xd6, 0x47, 0x08, 0x73, 0x89, 0xaa, 0x83, 0x66, 0xa9, 0xba, 0x35, 0xfd, 0x85, - 0x05, 0x9d, 0x5d, 0xcf, 0x2b, 0x39, 0xa7, 0xbc, 0x48, 0xf0, 0xba, 0x5d, 0xee, 0x16, 0x6c, 0x54, - 0x0a, 0x24, 0xaa, 0x19, 0x2f, 0x61, 0xcb, 0xa1, 0x83, 0xe8, 0x94, 0xbe, 0x6e, 0x91, 0xed, 0xeb, - 0x70, 0x6d, 0x14, 0x67, 0x21, 0x1b, 0x96, 0xf7, 0xcc, 0xf2, 0xb8, 0x0a, 0x8c, 0xfe, 0xd3, 0x82, - 0x79, 0xb3, 0x70, 0xfe, 0xaa, 0x72, 0xf1, 0x37, 0x81, 0x24, 0x34, 0xcd, 0xba, 0x71, 0x14, 0x04, - 0x2c, 0x25, 0xf7, 0x68, 0xe0, 0x9e, 0x8b, 0x92, 0x7d, 0x8b, 0x8d, 0x1c, 0xf0, 0x81, 0x8f, 0x18, - 0x9c, 0xac, 0xc3, 0xac, 0x1b, 0xfb, 0x5d, 0x66, 0x35, 0x3c, 0x1f, 0x9f, 0x71, 0x63, 0xff, 0x13, - 0x7a, 0x4e, 0x6c, 0x98, 0x17, 0x03, 0xdd, 0x80, 0x9e, 0xd2, 0x00, 0x63, 0xbe, 0x29, 0xa7, 0xc1, - 0x87, 0x3f, 0x65, 0x20, 0x72, 0x17, 0x5a, 0x71, 0xe2, 0x33, 0xf3, 0xcb, 0xdf, 0x06, 0x66, 0x51, - 0x9a, 0x45, 0x01, 0x97, 0xab, 0xb3, 0x7f, 0x0c, 0x57, 0x2b, 0x74, 0x21, 0x7c, 0xd4, 0xf7, 0x60, - 0xd1, 0x7c, 0x61, 0x90, 0x7e, 0x4a, 0x45, 0xad, 0xc6, 0x44, 0x67, 0xe1, 0xd8, 0xa0, 0x23, 0xa2, - 0x4f, 0xc4, 0x71, 0xdc, 0x4c, 0xd5, 0xb4, 0xec, 0x2f, 0x61, 0x25, 0x07, 0xee, 0x45, 0xe1, 0x29, - 0x4d, 0x52, 0x66, 0x6d, 0x04, 0xa6, 0x8f, 0x93, 0x48, 0x16, 0x64, 0xf1, 0x37, 0x8b, 0xdb, 0xb2, - 0x48, 0x98, 0x41, 0x2d, 0x8b, 0x18, 0x4e, 0xe2, 0x66, 0xf2, 0x96, 0xc2, 0xdf, 0x2c, 0x4e, 0xf6, - 0x91, 0x08, 0xed, 0xe2, 0x18, 0x37, 0xd5, 0x86, 0x80, 0x31, 0x2e, 0xf6, 0x33, 0x0c, 0x1f, 0x75, - 0x51, 0xc4, 0x1a, 0x7f, 0x1d, 0x1a, 0x7c, 0x8d, 0x6c, 0xa6, 0x5c, 0xdf, 0xa6, 0xb1, 0xbe, 0x82, - 0x98, 0x0e, 0x1c, 0x2b, 0xa8, 0xfd, 0xdf, 0x35, 0x68, 0x62, 0xc4, 0xfa, 0x11, 0xcd, 0x5c, 0x3f, - 0x18, 0x1f, 0x4b, 0xf3, 0x18, 0xb4, 0xa6, 0x62, 0xd0, 0x9b, 0x30, 0xaf, 0x17, 0x44, 0xce, 0x65, - 0x32, 0xab, 0x95, 0x43, 0xce, 0xc9, 0x2d, 0x58, 0xc0, 0xd4, 0x3a, 0xc7, 0xe2, 0x36, 0x33, 0x8f, - 0x50, 0x85, 0x66, 0x26, 0x02, 0x57, 0x0a, 0x89, 0x00, 0x1b, 0xc6, 0x60, 0xba, 0x9b, 0xfa, 0x9e, - 0xca, 0x13, 0x10, 0x72, 0xe8, 0x7b, 0xda, 0x30, 0xce, 0x9e, 0xd5, 0x86, 0x71, 0x36, 0xcb, 0x81, - 0x12, 0xca, 0x1f, 0x0a, 0xf0, 0xbd, 0x6b, 0x0e, 0x8d, 0xae, 0x29, 0x81, 0x4f, 0xfd, 0x01, 0xbe, - 0x86, 0x89, 0xe2, 0x76, 0x9d, 0x5b, 0x2c, 0xff, 0xca, 0xd3, 0x34, 0xd0, 0xd3, 0xb4, 0x3c, 0xa9, - 0x6b, 0x18, 0x49, 0xdd, 0x36, 0x34, 0xa2, 0x98, 0x86, 0x5d, 0x91, 0x62, 0x37, 0x79, 0xf4, 0xc0, - 0x40, 0xcf, 0x10, 0x22, 0x4a, 0x26, 0xa8, 0xf3, 0x74, 0x92, 0xbc, 0xd4, 0x54, 0x4c, 0xad, 0xa8, - 0x18, 0x99, 0x08, 0x4e, 0x5d, 0x94, 0x08, 0xda, 0xbb, 0x18, 0x15, 0x4b, 0xc6, 0xc2, 0x7c, 0xde, - 0x84, 0x19, 0x54, 0x93, 0xb4, 0x9c, 0x15, 0x23, 0x8d, 0x11, 0x46, 0xe1, 0x08, 0x1c, 0xfb, 0x07, - 0xf8, 0x86, 0x88, 0x43, 0x93, 0x88, 0x7e, 0x15, 0xe6, 0xf8, 0xae, 0x28, 0xab, 0x99, 0xc5, 0xef, - 0xc7, 0x9e, 0xfd, 0x6f, 0x16, 0x90, 0xc3, 0xe1, 0xd1, 0xc0, 0x9f, 0x9c, 0xda, 0xe4, 0x09, 0x3a, - 0x81, 0x69, 0x34, 0x13, 0x6e, 0x8e, 0xf8, 0xbb, 0x60, 0x21, 0xd3, 0x45, 0x0b, 0xc9, 0xb7, 0xf3, - 0x4a, 0x75, 0x8e, 0x3e, 0xa3, 0x6f, 0x3e, 0x73, 0xf1, 0x81, 0x4f, 0xc3, 0xac, 0x2b, 0x8a, 0x2d, - 0xcc, 0xc5, 0x23, 0xe0, 0xb1, 0x67, 0x1f, 0xc2, 0xb2, 0xb1, 0x32, 0xa1, 0xe9, 0x1b, 0xd0, 0xe4, - 0x02, 0xc4, 0x81, 0xdb, 0x53, 0xd5, 0xf0, 0x06, 0xc2, 0x0e, 0x10, 0x34, 0x4e, 0x5f, 0x7f, 0x62, - 0xc1, 0xca, 0xa1, 0x3f, 0x18, 0x06, 0x6e, 0x46, 0xbf, 0x01, 0x8d, 0xe5, 0xcb, 0x9f, 0x32, 0x96, - 0x2f, 0x35, 0x39, 0x9d, 0x6b, 0xd2, 0xfe, 0x1f, 0x0b, 0x56, 0x0b, 0xa2, 0xa8, 0x98, 0xd0, 0x34, - 0xa6, 0x11, 0xc5, 0x01, 0x81, 0xa4, 0x31, 0xad, 0x19, 0x4c, 0x6f, 0xc2, 0xfc, 0xc0, 0x0f, 0xfd, - 0xc1, 0x70, 0xd0, 0xe5, 0xba, 0xe7, 0x32, 0x35, 0x05, 0xf0, 0x00, 0xb7, 0x80, 0x21, 0xb9, 0x2f, - 0x35, 0xa4, 0x69, 0x81, 0xc4, 0x81, 0x1c, 0xe9, 0x6d, 0x58, 0xc9, 0xe3, 0xf6, 0x6e, 0xdf, 0xf5, - 0xc3, 0x6e, 0x10, 0xa5, 0xa9, 0xd8, 0x63, 0x92, 0x8f, 0xed, 0xbb, 0x7e, 0xf8, 0x69, 0x94, 0xa6, - 0x9a, 0x13, 0x98, 0xd1, 0x9d, 0x00, 0x0b, 0x60, 0x5a, 0xcf, 0x4f, 0xdc, 0x80, 0x7e, 0x18, 0x0d, - 0x8e, 0x5e, 0xad, 0xee, 0x6f, 0x40, 0x93, 0xd7, 0xdd, 0x32, 0x37, 0xe9, 0x53, 0xb9, 0x03, 0x0d, - 0x84, 0x3d, 0x45, 0x50, 0xe5, 0x36, 0xfc, 0x97, 0x05, 0x64, 0x8f, 0x85, 0x32, 0xc1, 0xc4, 0xf6, - 0xc0, 0x5c, 0x09, 0xcf, 0x9b, 0x73, 0x0b, 0xab, 0x0b, 0xc8, 0x63, 0xd3, 0xfc, 0xa6, 0x0c, 0xf3, - 0x53, 0xab, 0x99, 0xbe, 0x64, 0x71, 0xac, 0xe4, 0xc7, 0x6f, 0xc1, 0xc2, 0x99, 0x1b, 0x04, 0x34, - 0x53, 0x4f, 0x6c, 0xa2, 0x12, 0xcf, 0xa1, 0x32, 0x07, 0x97, 0x0b, 0x9e, 0xd5, 0x16, 0xbc, 0x0a, - 0xcb, 0xc6, 0x7a, 0x45, 0x34, 0xf4, 0x10, 0xd6, 0x38, 0x78, 0x37, 0x08, 0x26, 0xf6, 0xaa, 0xf6, - 0x5f, 0xd7, 0x60, 0xbd, 0x34, 0x4d, 0x85, 0x0d, 0xa6, 0x19, 0xdf, 0x56, 0xcb, 0xad, 0x9e, 0xb0, - 0x23, 0x3e, 0xc5, 0xac, 0xce, 0x3f, 0x59, 0x30, 0xc3, 0x41, 0x63, 0x77, 0xe3, 0x73, 0xe9, 0x10, - 0x84, 0xc1, 0xf1, 0x8c, 0xe8, 0x3b, 0x93, 0x31, 0xe3, 0xff, 0xd3, 0x9f, 0x55, 0xb9, 0x27, 0x11, - 0x2f, 0xaa, 0xdf, 0x83, 0x56, 0x11, 0xe1, 0x52, 0x4f, 0x4e, 0xbc, 0xaa, 0xf2, 0xe8, 0x94, 0x6a, - 0xcf, 0xa8, 0x3f, 0xb3, 0x60, 0x71, 0x2f, 0x0a, 0x3d, 0x9f, 0xdd, 0x98, 0x07, 0x6e, 0xe2, 0x0e, - 0x52, 0xf1, 0x92, 0xcf, 0x41, 0xb2, 0xec, 0xae, 0x00, 0x23, 0x0a, 0x9c, 0x5b, 0x00, 0xbd, 0x13, - 0xda, 0x7b, 0xd1, 0x15, 0x15, 0x47, 0xfe, 0xfc, 0xcf, 0x20, 0x1f, 0xfa, 0x5e, 0x4a, 0xde, 0x82, - 0xe5, 0x7c, 0xb8, 0xeb, 0x86, 0x5e, 0x57, 0x94, 0x1b, 0xf1, 0x75, 0x43, 0xe1, 0xed, 0x86, 0xde, - 0x6e, 0xfa, 0x22, 0x65, 0xb1, 0xa2, 0xaa, 0xb2, 0x75, 0x0d, 0x17, 0xbe, 0xa8, 0xe0, 0xbb, 0x08, - 0xb6, 0xff, 0xd7, 0xc2, 0x1b, 0x50, 0xae, 0x4a, 0xec, 0x76, 0x5e, 0x58, 0xc3, 0x7a, 0xab, 0xb1, - 0x65, 0xb5, 0xc2, 0x96, 0x11, 0x98, 0xf6, 0x33, 0x3a, 0x90, 0x17, 0x0b, 0xfb, 0x4d, 0x3e, 0x84, - 0x96, 0x5a, 0x71, 0x37, 0x46, 0xb5, 0x88, 0x63, 0xb2, 0x9e, 0x27, 0x8e, 0x86, 0xd6, 0x9c, 0xc5, - 0x5e, 0x41, 0x8d, 0xf2, 0x78, 0x5d, 0x99, 0xc8, 0x51, 0xf7, 0x50, 0xdb, 0xc2, 0x3f, 0xf1, 0x2f, - 0x2e, 0x35, 0xed, 0x0d, 0x33, 0xea, 0x89, 0x50, 0x59, 0x7d, 0xdb, 0xff, 0x61, 0xc1, 0xe2, 0xae, - 0xe7, 0xe1, 0xba, 0x27, 0x71, 0x13, 0x72, 0x95, 0xb5, 0x0b, 0x56, 0x39, 0xf5, 0x73, 0xae, 0xf2, - 0x17, 0x76, 0x22, 0x23, 0x94, 0x60, 0xdb, 0xd0, 0xca, 0xd7, 0x59, 0xbd, 0xbd, 0xf6, 0xb7, 0x80, - 0xf0, 0xf4, 0xca, 0x50, 0x47, 0x11, 0x6b, 0x15, 0x96, 0x0d, 0x2c, 0xe1, 0x6b, 0x3e, 0x86, 0x3b, - 0xfb, 0x34, 0xdb, 0x4b, 0xce, 0xe3, 0x2c, 0x92, 0xe1, 0xec, 0x47, 0x34, 0x8e, 0x52, 0x5f, 0x7a, - 0x2e, 0x3a, 0x91, 0xf7, 0xf9, 0x67, 0x0b, 0xee, 0x4e, 0x40, 0x48, 0x2c, 0xe1, 0x8b, 0x72, 0x7d, - 0xe9, 0x37, 0xf4, 0xf6, 0x96, 0x89, 0xa8, 0xec, 0x28, 0x88, 0xe8, 0x32, 0x50, 0x24, 0x3b, 0x1f, - 0xc0, 0x82, 0x39, 0x78, 0x29, 0x57, 0x11, 0xc0, 0xed, 0x0b, 0x84, 0x98, 0xc4, 0xe6, 0x6e, 0xc3, - 0x42, 0xcf, 0x20, 0x21, 0x18, 0x15, 0xa0, 0xf6, 0x1e, 0xbc, 0x71, 0x21, 0x37, 0xa1, 0xb6, 0x91, - 0x19, 0xba, 0xfd, 0x77, 0xd3, 0xb0, 0xfe, 0xdc, 0xcf, 0x4e, 0xbc, 0xc4, 0x3d, 0x93, 0xd6, 0x37, - 0x89, 0x90, 0x85, 0xe4, 0xbd, 0x56, 0xae, 0x37, 0xdc, 0x83, 0xa5, 0x28, 0xa4, 0x98, 0x63, 0x74, - 0x63, 0x37, 0x4d, 0xcf, 0xa2, 0x44, 0xde, 0xa5, 0x8b, 0x51, 0x48, 0x59, 0x9e, 0x71, 0x20, 0xc0, - 0x85, 0xdb, 0x78, 0xba, 0x78, 0x1b, 0xb7, 0x60, 0x2a, 0xf6, 0x43, 0xf1, 0x66, 0xc2, 0x7e, 0xb2, - 0xbb, 0x33, 0x4b, 0x5c, 0x4f, 0xa3, 0x2c, 0xee, 0x4e, 0x84, 0x2a, 0xba, 0x7a, 0x15, 0x7f, 0xb6, - 0x50, 0xc5, 0xd7, 0x74, 0x32, 0x67, 0x56, 0x2d, 0xb6, 0xa1, 0x21, 0x7e, 0x76, 0x33, 0xb7, 0x2f, - 0x52, 0x20, 0x10, 0xa0, 0xa7, 0x6e, 0x5f, 0x8b, 0xd6, 0xc0, 0x88, 0xd6, 0xb6, 0x00, 0x8e, 0x29, - 0xed, 0x1a, 0xc9, 0x50, 0xfd, 0x98, 0x52, 0xee, 0x74, 0x59, 0xa8, 0x7c, 0xe4, 0x86, 0x2f, 0xba, - 0x58, 0x83, 0x68, 0x72, 0x71, 0x18, 0xe0, 0x33, 0x77, 0x80, 0x31, 0x31, 0x0e, 0x4a, 0x99, 0xe6, - 0xb9, 0x46, 0x19, 0x6c, 0x37, 0xaf, 0xa6, 0x20, 0x4a, 0xcf, 0xcf, 0xce, 0xdb, 0x0b, 0xf9, 0xfc, - 0x3d, 0x3f, 0x3b, 0x57, 0xf3, 0x51, 0x67, 0xc9, 0x79, 0x7b, 0x31, 0x9f, 0xbf, 0xc7, 0x41, 0x4c, - 0xbc, 0xf4, 0xcc, 0x3f, 0xa6, 0xbc, 0x31, 0xa4, 0x25, 0x5a, 0xa5, 0x18, 0x64, 0x2f, 0xf2, 0x30, - 0x8c, 0x3c, 0xf3, 0x13, 0x2d, 0x39, 0x5d, 0xe2, 0x29, 0x2c, 0x03, 0x4a, 0xd3, 0xb0, 0xef, 0x41, - 0x4b, 0x9a, 0x8b, 0xde, 0x3b, 0x99, 0xd0, 0x74, 0x18, 0x64, 0xb2, 0x77, 0x92, 0x7f, 0xd9, 0xef, - 0x60, 0x57, 0xc4, 0xa7, 0x51, 0xbf, 0x9f, 0xa7, 0x4f, 0xc2, 0xb4, 0xd6, 0x60, 0x26, 0x40, 0xb8, - 0x9c, 0xc2, 0xbf, 0xec, 0x10, 0xeb, 0x39, 0x85, 0x29, 0xf9, 0xab, 0x85, 0x1f, 0x1e, 0x47, 0x22, - 0x5b, 0xc0, 0xdf, 0xec, 0x2c, 0x7a, 0xf4, 0x68, 0xd8, 0x97, 0x3d, 0x50, 0xf8, 0xc1, 0x30, 0xcf, - 0xdc, 0x24, 0x14, 0x17, 0x2a, 0xfe, 0x66, 0x98, 0x34, 0x49, 0xa2, 0x44, 0xdc, 0x9e, 0xfc, 0xc3, - 0xde, 0x87, 0xf5, 0xc3, 0xcb, 0x89, 0xc8, 0x08, 0xf1, 0x6a, 0x8d, 0x38, 0xfe, 0xf8, 0x61, 0x7f, - 0x62, 0x74, 0x80, 0x60, 0x97, 0xc0, 0x24, 0xc7, 0x68, 0x05, 0xae, 0xa0, 0x2f, 0x97, 0xc4, 0xf0, - 0x83, 0x65, 0x84, 0xed, 0x32, 0x35, 0xd5, 0x83, 0x56, 0xee, 0xa8, 0xe0, 0x9e, 0xf0, 0x57, 0x2a, - 0x3a, 0x2a, 0x8c, 0xb9, 0x93, 0xb5, 0x54, 0x7c, 0xa3, 0x5d, 0x12, 0x5f, 0xc1, 0xb2, 0x2e, 0xda, - 0x6b, 0xcd, 0xfa, 0x7f, 0x62, 0x61, 0x85, 0x4c, 0x65, 0x60, 0x87, 0x59, 0x42, 0xdd, 0xc1, 0x6b, - 0x7d, 0x10, 0xff, 0x3e, 0xdc, 0xd0, 0xfb, 0xa5, 0x2e, 0x2d, 0x89, 0xfd, 0x07, 0xf8, 0x8c, 0xc8, - 0x1f, 0xf9, 0x7f, 0x09, 0xf2, 0x7f, 0x00, 0xd7, 0x34, 0xf9, 0x2f, 0x29, 0xc6, 0x83, 0x3f, 0xbd, - 0x07, 0x0b, 0xfb, 0x11, 0xbf, 0xb1, 0x9e, 0x32, 0x47, 0x9d, 0x90, 0x27, 0x30, 0x2b, 0x3a, 0x84, - 0xc9, 0x5a, 0xa9, 0x65, 0x18, 0x29, 0x76, 0xd6, 0x47, 0xb4, 0x12, 0xdb, 0xcb, 0x5f, 0xff, 0xeb, - 0xbf, 0xff, 0xb4, 0x36, 0x4f, 0x1a, 0xf7, 0x4f, 0xdf, 0xb9, 0xdf, 0xa7, 0x19, 0x7a, 0x84, 0x3e, - 0xcc, 0x1b, 0x4d, 0x9d, 0x64, 0xd3, 0x68, 0xcc, 0x2c, 0xf4, 0x7a, 0x76, 0xb6, 0xc6, 0xb6, 0x6d, - 0xda, 0x57, 0x91, 0xc5, 0x32, 0x59, 0x12, 0x2c, 0xf2, 0x7e, 0x4d, 0xf2, 0x25, 0x2c, 0x3e, 0xc2, - 0x4a, 0xb1, 0x22, 0x4a, 0xb6, 0x73, 0x62, 0x95, 0xbd, 0xaa, 0x9d, 0xeb, 0xa3, 0x11, 0x04, 0xc3, - 0x0d, 0x64, 0xb8, 0x4a, 0x96, 0x19, 0x43, 0x5e, 0x89, 0x56, 0x3c, 0x49, 0x0a, 0x2d, 0xd1, 0xfd, - 0xf6, 0x4a, 0x79, 0x6e, 0x22, 0xcf, 0x35, 0xb2, 0xc2, 0x78, 0x7a, 0x9c, 0x41, 0xce, 0x34, 0xc2, - 0x42, 0x97, 0xde, 0xad, 0x49, 0xae, 0x8d, 0x6c, 0xe3, 0xe4, 0x2c, 0xb7, 0x2f, 0x68, 0xf3, 0x34, - 0x57, 0xd9, 0xa7, 0x0c, 0x57, 0x75, 0x7a, 0x92, 0x9f, 0x72, 0xef, 0x57, 0xd9, 0x57, 0x4c, 0xde, - 0xb8, 0xb8, 0x99, 0x99, 0xcb, 0x70, 0x67, 0xd2, 0xae, 0x67, 0xfb, 0x5b, 0x28, 0xcc, 0x35, 0xb2, - 0x29, 0x84, 0x31, 0x3a, 0x9d, 0x65, 0x2f, 0x35, 0xe9, 0x41, 0x53, 0x6f, 0xd1, 0x24, 0x1b, 0x15, - 0xce, 0x56, 0x31, 0xdf, 0xac, 0x1e, 0x14, 0x0c, 0xdb, 0xc8, 0x90, 0x90, 0x96, 0x60, 0xa8, 0x3a, - 0x3a, 0xc9, 0x57, 0xb0, 0x58, 0x68, 0x6f, 0x24, 0x76, 0x61, 0xfb, 0x2a, 0x5a, 0x55, 0x3b, 0x37, - 0xc7, 0xe2, 0x08, 0xae, 0xd7, 0x90, 0x6b, 0xdb, 0x5e, 0xd6, 0x76, 0x59, 0x72, 0xfe, 0xae, 0x75, - 0x8f, 0xa4, 0xb8, 0xcf, 0x7a, 0x27, 0xde, 0x44, 0xbc, 0xb7, 0x2f, 0x68, 0xe3, 0x2b, 0xed, 0xb5, - 0xe4, 0x89, 0xa7, 0x35, 0xc5, 0xee, 0x26, 0xad, 0x7f, 0x14, 0x23, 0x91, 0x49, 0xf8, 0x6e, 0x55, - 0xf7, 0x9f, 0x8a, 0x16, 0x58, 0xbb, 0x83, 0x5c, 0x57, 0x08, 0x29, 0x70, 0x8d, 0xb2, 0x98, 0xa4, - 0x46, 0x7b, 0xae, 0x60, 0x6a, 0x5a, 0x75, 0x45, 0x83, 0x6c, 0xe5, 0x4a, 0xf5, 0x8e, 0xd7, 0x91, - 0x2b, 0x8d, 0xb2, 0x38, 0x25, 0x2f, 0x61, 0x81, 0xbb, 0x8b, 0x57, 0xbf, 0xb3, 0x5b, 0xc8, 0x77, - 0xdd, 0x26, 0xb9, 0xcf, 0xd0, 0x37, 0xf6, 0x39, 0xd4, 0xd5, 0x95, 0x41, 0xda, 0xda, 0x22, 0x8c, - 0x5e, 0xc5, 0xce, 0x88, 0x4e, 0x34, 0x69, 0xad, 0xf6, 0xbc, 0x58, 0x15, 0xef, 0x2b, 0x63, 0x84, - 0x7f, 0x0c, 0x90, 0xb7, 0xa6, 0x91, 0xab, 0x25, 0xca, 0x4a, 0x73, 0x9d, 0xaa, 0x21, 0xd9, 0x84, - 0x8f, 0xe4, 0x5b, 0x64, 0xc1, 0x20, 0x2f, 0xcf, 0x9b, 0xba, 0x21, 0x8d, 0xf3, 0x56, 0x6c, 0x66, - 0xeb, 0x8c, 0xee, 0x62, 0x92, 0x9b, 0x62, 0xcb, 0xc3, 0xa6, 0x2a, 0x21, 0x6c, 0x05, 0xfc, 0xb2, - 0xd0, 0xda, 0xa7, 0x36, 0xab, 0xb8, 0x54, 0x5e, 0x16, 0xe5, 0x5e, 0xa8, 0xd2, 0x65, 0x91, 0xb7, - 0x3c, 0x91, 0x17, 0xf8, 0x47, 0x48, 0x5a, 0xf7, 0x0f, 0xd1, 0x69, 0x95, 0x5b, 0xa1, 0x3a, 0xd7, - 0x46, 0x0d, 0xa7, 0xd5, 0xf6, 0x2d, 0x92, 0x25, 0x3c, 0x54, 0x7c, 0xc3, 0x79, 0xcf, 0x8f, 0xb1, - 0xe1, 0x46, 0x6b, 0x50, 0xe7, 0x6a, 0xc5, 0x88, 0xa0, 0xbe, 0x8a, 0xd4, 0x17, 0xc9, 0xbc, 0x72, - 0x89, 0x48, 0x8b, 0xef, 0x89, 0x7a, 0x8c, 0x35, 0xf6, 0xa4, 0xd8, 0xb1, 0x63, 0xf8, 0xc0, 0x52, - 0xdf, 0x4e, 0xc9, 0x07, 0xaa, 0xce, 0x1c, 0xf2, 0x87, 0x66, 0x03, 0x90, 0x6c, 0x48, 0xb0, 0xc7, - 0x76, 0x10, 0x94, 0x4e, 0xcb, 0xc8, 0x2e, 0x03, 0x7b, 0x1b, 0x39, 0x5f, 0x25, 0xeb, 0x45, 0xce, - 0xa2, 0x63, 0x81, 0x7c, 0x6d, 0xc1, 0x72, 0xc5, 0x7b, 0x78, 0x2e, 0xc1, 0xe8, 0xd7, 0xfb, 0x5c, - 0x82, 0x71, 0x0f, 0xea, 0x36, 0x4a, 0xb0, 0x69, 0xa3, 0x04, 0xae, 0xe7, 0x29, 0x09, 0x44, 0xee, - 0xc7, 0x2c, 0xf3, 0xcf, 0x2d, 0x58, 0xab, 0x7e, 0xfb, 0x26, 0xb7, 0xd4, 0x9f, 0x35, 0x8c, 0x7b, - 0x95, 0xef, 0xdc, 0xbe, 0x08, 0x4d, 0x48, 0x73, 0x0b, 0xa5, 0xd9, 0xb6, 0x3b, 0x4c, 0x9a, 0x04, - 0x71, 0xab, 0x04, 0x3a, 0xc3, 0x82, 0xa1, 0xf9, 0xba, 0x4c, 0xb4, 0xd8, 0xa2, 0xfa, 0x11, 0xbe, - 0x73, 0x63, 0x0c, 0x86, 0xe9, 0xbe, 0xc8, 0xaa, 0xd8, 0x10, 0x7c, 0x92, 0x55, 0xcf, 0xd4, 0xe2, - 0x8c, 0xe6, 0xaf, 0xb7, 0xc6, 0x19, 0x2d, 0x3d, 0x48, 0x1b, 0x67, 0xb4, 0xfc, 0x46, 0x5c, 0x3a, - 0xa3, 0xc8, 0x0c, 0xdf, 0x8b, 0xc9, 0xe7, 0x78, 0x6c, 0x44, 0xb5, 0xba, 0x5d, 0x3c, 0xea, 0x69, - 0xd5, 0xb1, 0x31, 0xeb, 0xd1, 0x25, 0x57, 0xc9, 0x8b, 0xe0, 0x4c, 0x7b, 0x0e, 0xcc, 0x49, 0x74, - 0xb2, 0x5e, 0x24, 0x20, 0x29, 0x57, 0x3e, 0x38, 0xda, 0xeb, 0x48, 0x74, 0xc9, 0x6e, 0xea, 0x44, - 0x19, 0xcd, 0x23, 0x68, 0x68, 0x8f, 0x6b, 0x44, 0x39, 0xd9, 0xf2, 0x5b, 0x62, 0x67, 0xa3, 0x72, - 0xcc, 0x74, 0x25, 0xf6, 0x22, 0x63, 0x90, 0x22, 0x82, 0xe2, 0xf1, 0xbb, 0x30, 0x6f, 0xbc, 0x6f, - 0xe5, 0xca, 0xaf, 0x7a, 0x81, 0xcb, 0x95, 0x5f, 0xf9, 0x28, 0x26, 0x03, 0x4d, 0x1b, 0x95, 0x9f, - 0x0a, 0x14, 0xc5, 0xeb, 0x0b, 0xa8, 0xab, 0x67, 0xa5, 0x5c, 0xff, 0xc5, 0x97, 0xa6, 0x8b, 0x78, - 0x18, 0x7b, 0x70, 0xc6, 0x26, 0x1f, 0x45, 0x83, 0x23, 0xa1, 0x2f, 0xed, 0xd1, 0x24, 0xd7, 0x57, - 0xf9, 0xe5, 0x28, 0xd7, 0x57, 0xd5, 0x2b, 0x8b, 0xa1, 0xaf, 0x1e, 0x22, 0xa8, 0x35, 0x24, 0xb0, - 0x58, 0x78, 0xac, 0xc8, 0xc3, 0x8a, 0xea, 0xa7, 0x99, 0x3c, 0xac, 0x18, 0xf1, 0xca, 0x61, 0x06, - 0x6e, 0x9c, 0x9f, 0x1b, 0x04, 0xb9, 0x6d, 0x71, 0x77, 0xcf, 0x4b, 0xf9, 0x86, 0xdd, 0x1a, 0x6f, - 0x16, 0x86, 0xdd, 0x9a, 0x75, 0xff, 0x92, 0xbb, 0xa7, 0x9c, 0xd6, 0x33, 0x98, 0x93, 0x35, 0xe4, - 0xdc, 0x68, 0x0b, 0xd5, 0xf3, 0x4e, 0xbb, 0x3c, 0x20, 0xa8, 0x1a, 0x86, 0xeb, 0x7a, 0x1e, 0x52, - 0x15, 0x1b, 0xa1, 0x55, 0x94, 0xf3, 0x8d, 0x28, 0x17, 0xa3, 0xf3, 0x8d, 0xa8, 0x2a, 0x41, 0x1b, - 0x1b, 0xc1, 0x3d, 0x97, 0xe2, 0xf1, 0xf7, 0x16, 0x66, 0xda, 0xe3, 0x0b, 0xc2, 0xe4, 0xed, 0x4b, - 0xd4, 0x8e, 0xb9, 0x40, 0xef, 0x5c, 0xba, 0xda, 0x6c, 0xdf, 0x41, 0x31, 0x6d, 0x7b, 0x4b, 0x5e, - 0xa6, 0x38, 0xcd, 0xe3, 0xe8, 0xaa, 0xf4, 0xcc, 0x84, 0xfe, 0x5b, 0x8b, 0xff, 0x89, 0xe9, 0x18, - 0xba, 0x64, 0x67, 0x42, 0x01, 0xa4, 0xc0, 0xf7, 0x27, 0xc6, 0x17, 0xe2, 0xde, 0x46, 0x71, 0xaf, - 0xdb, 0x1b, 0x63, 0xc4, 0x65, 0xc2, 0xfe, 0x3e, 0x6c, 0xa8, 0xc2, 0xb1, 0x41, 0xf7, 0xe3, 0x61, - 0xe8, 0xa5, 0x79, 0x5e, 0x3a, 0xa2, 0xba, 0x9c, 0x1b, 0x4e, 0xb1, 0x9e, 0x68, 0xde, 0x8f, 0x67, - 0x62, 0x94, 0x8b, 0x71, 0xcc, 0x68, 0x33, 0xee, 0x31, 0x2c, 0xc9, 0x79, 0x1f, 0xfb, 0x6e, 0xf6, - 0x0b, 0xf3, 0xbc, 0x8e, 0x3c, 0x3b, 0xf6, 0xaa, 0xce, 0xf3, 0xd8, 0x77, 0x33, 0xc5, 0x31, 0xc5, - 0x77, 0x40, 0xa3, 0x54, 0xa8, 0x27, 0xdf, 0x95, 0x45, 0x44, 0x3d, 0xf9, 0xae, 0xae, 0x6a, 0x9a, - 0xc9, 0x77, 0x9f, 0x66, 0xbc, 0xca, 0xe8, 0x09, 0x06, 0xa7, 0xd0, 0x3a, 0x1c, 0xc9, 0xf4, 0xf0, - 0xe7, 0x66, 0x2a, 0x62, 0x20, 0x1b, 0x99, 0xa6, 0x05, 0xa6, 0x6c, 0xb1, 0xa7, 0xfc, 0xd1, 0x53, - 0x2f, 0x22, 0x92, 0xed, 0xd1, 0xe5, 0xc5, 0x32, 0xdf, 0xca, 0xfa, 0xa3, 0xc9, 0x57, 0xcb, 0x90, - 0xf0, 0x4f, 0xeb, 0x18, 0xdf, 0x73, 0x20, 0x66, 0x96, 0x84, 0x7f, 0x92, 0xa1, 0xbc, 0x40, 0x45, - 0xe9, 0x70, 0xb2, 0x14, 0xe9, 0x06, 0x32, 0xde, 0xb0, 0xd7, 0xca, 0x29, 0x12, 0xe3, 0xcd, 0x58, - 0xff, 0x1e, 0x2c, 0x17, 0x72, 0xef, 0x57, 0xc4, 0xdb, 0x30, 0xe7, 0x42, 0xe2, 0x2d, 0x99, 0x67, - 0x98, 0x07, 0x17, 0xea, 0x81, 0xe4, 0x46, 0x55, 0xbe, 0x61, 0x94, 0xdb, 0xc6, 0x65, 0x3e, 0xe2, - 0xde, 0x20, 0x6b, 0xa5, 0x74, 0x04, 0x29, 0xbc, 0x6d, 0x91, 0x3f, 0xb3, 0xb0, 0x0b, 0x7e, 0x44, - 0x39, 0x92, 0xdc, 0xad, 0x4a, 0x78, 0x2f, 0x2d, 0x86, 0xf0, 0x27, 0xe4, 0x5a, 0x31, 0x2b, 0x2e, - 0x89, 0x73, 0x82, 0x15, 0x08, 0xbd, 0xa8, 0x68, 0xe4, 0xe4, 0x15, 0xd5, 0xc6, 0x91, 0x49, 0x6b, - 0x31, 0x15, 0x17, 0x59, 0xa5, 0xe4, 0xf4, 0x13, 0xf3, 0x6f, 0x5d, 0x0d, 0x96, 0xb7, 0x2b, 0x56, - 0x7d, 0x19, 0xd6, 0x37, 0x91, 0xf5, 0x16, 0xd9, 0x28, 0xac, 0xd7, 0x14, 0xe1, 0x68, 0x06, 0xff, - 0x75, 0x8a, 0x77, 0xff, 0x3f, 0x00, 0x00, 0xff, 0xff, 0xdb, 0x60, 0x0b, 0x6d, 0xd0, 0x42, 0x00, - 0x00, + // 4844 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3c, 0x4d, 0x8f, 0x24, 0x47, + 0x56, 0xca, 0xea, 0x9e, 0xee, 0xae, 0x57, 0xd5, 0x5f, 0xd1, 0x5f, 0x35, 0xd5, 0xdd, 0xf3, 0x91, + 0x5e, 0x8f, 0x67, 0xfc, 0xd1, 0x63, 0x8f, 0x07, 0xd6, 0xac, 0xcd, 0x2e, 0xed, 0x1e, 0xbb, 0xd7, + 0xd8, 0xeb, 0x69, 0xb2, 0x67, 0xc7, 0x92, 0x17, 0xb9, 0xc8, 0xae, 0x8c, 0xea, 0x4e, 0xa6, 0x2a, + 0x33, 0x9d, 0x19, 0xd5, 0x3d, 0x65, 0x40, 0xac, 0x2c, 0x81, 0x38, 0x20, 0x38, 0xac, 0x90, 0x40, + 0xe2, 0xc4, 0x11, 0x89, 0x0b, 0xe2, 0xc4, 0x61, 0xc5, 0x15, 0x71, 0xe4, 0xc2, 0x0f, 0x40, 0xdc, + 0x00, 0x69, 0x25, 0x2e, 0x9c, 0x50, 0xbc, 0xf8, 0xc8, 0x88, 0xcc, 0xac, 0xea, 0xea, 0xdd, 0x59, + 0x73, 0xb1, 0x2b, 0x5f, 0xbc, 0x78, 0xef, 0xc5, 0x8b, 0x17, 0x2f, 0xde, 0x7b, 0xf1, 0x7a, 0xa0, + 0x9e, 0x26, 0xdd, 0xbd, 0x24, 0x8d, 0x59, 0x4c, 0xe6, 0x4e, 0xbb, 0x2c, 0x4d, 0xba, 0xed, 0x9d, + 0xd3, 0x38, 0x3e, 0xed, 0xd3, 0xfb, 0x7e, 0x12, 0xde, 0xf7, 0xa3, 0x28, 0x66, 0x3e, 0x0b, 0xe3, + 0x28, 0x13, 0x58, 0xee, 0x0a, 0x2c, 0x1d, 0x52, 0xf6, 0x51, 0xd4, 0x8b, 0x3d, 0xfa, 0xe5, 0x90, + 0x66, 0xcc, 0xfd, 0x87, 0x59, 0x58, 0xd6, 0xa0, 0x2c, 0x89, 0xa3, 0x8c, 0x92, 0x4d, 0x98, 0x1b, + 0x26, 0x2c, 0x1c, 0xd0, 0x96, 0x73, 0xcb, 0xb9, 0x5b, 0xf7, 0xe4, 0x17, 0xb9, 0x0f, 0x6b, 0xfe, + 0xb9, 0x1f, 0xf6, 0xfd, 0x93, 0x3e, 0xed, 0xd0, 0xe7, 0xdd, 0x33, 0x3f, 0x3a, 0xa5, 0x59, 0xab, + 0x76, 0xcb, 0xb9, 0x3b, 0xe3, 0x11, 0x3d, 0xf4, 0x81, 0x1a, 0x21, 0xaf, 0xc1, 0x2a, 0x8d, 0x38, + 0x28, 0x30, 0xd0, 0x67, 0x10, 0x7d, 0x45, 0x0e, 0xe4, 0xc8, 0x0f, 0x61, 0x33, 0xa0, 0x3d, 0x7f, + 0xd8, 0x67, 0x9d, 0x5e, 0x9c, 0xd2, 0xe7, 0x9d, 0x24, 0x8d, 0xcf, 0xc3, 0x80, 0xa6, 0xad, 0x59, + 0x94, 0x62, 0x5d, 0x8e, 0x7e, 0xc8, 0x07, 0x8f, 0xe4, 0x18, 0x79, 0x00, 0x1b, 0x7a, 0x56, 0xe8, + 0xb3, 0x4e, 0x77, 0x98, 0xa6, 0x34, 0xea, 0x8e, 0x5a, 0xd7, 0x70, 0xd2, 0x9a, 0x9a, 0x14, 0xfa, + 0xec, 0x40, 0x0e, 0x91, 0xcf, 0x60, 0x25, 0x1b, 0x9e, 0x64, 0xa3, 0x8c, 0xd1, 0x41, 0x27, 0x63, + 0x3e, 0x1b, 0x66, 0xad, 0xb9, 0x5b, 0x33, 0x77, 0x1b, 0x0f, 0x5e, 0xdf, 0x13, 0x6a, 0xdc, 0x2b, + 0xa8, 0x64, 0xef, 0x58, 0xe1, 0x1f, 0x23, 0xfa, 0x07, 0x11, 0x4b, 0x47, 0xde, 0x72, 0x66, 0x43, + 0xc9, 0xa7, 0xb0, 0x98, 0x26, 0xdd, 0x0e, 0x8d, 0x82, 0x24, 0x0e, 0x23, 0x96, 0xb5, 0xe6, 0x91, + 0xea, 0xbd, 0x71, 0x54, 0xbd, 0xa4, 0xfb, 0x81, 0xc2, 0x15, 0x24, 0x9b, 0xa9, 0x01, 0x6a, 0xbf, + 0x0f, 0xeb, 0x55, 0x8c, 0xc9, 0x0a, 0xcc, 0x3c, 0xa3, 0x23, 0xb9, 0x3b, 0xfc, 0x27, 0x59, 0x87, + 0x6b, 0xe7, 0x7e, 0x7f, 0x48, 0x71, 0x33, 0x16, 0x3c, 0xf1, 0xf1, 0x9d, 0xda, 0x3b, 0x4e, 0xfb, + 0x09, 0xac, 0x96, 0xd8, 0x54, 0x10, 0xb8, 0x67, 0x12, 0x68, 0x3c, 0x58, 0x53, 0x22, 0x7b, 0x47, + 0x07, 0x6a, 0xae, 0x41, 0xd5, 0xbd, 0x0d, 0x37, 0x0f, 0x29, 0x3b, 0x88, 0x07, 0x83, 0x61, 0x14, + 0x76, 0xd1, 0xc6, 0x3c, 0xda, 0xf7, 0x47, 0x34, 0xcd, 0x94, 0x65, 0x7d, 0x0a, 0xeb, 0x55, 0xe3, + 0xa4, 0x05, 0xf3, 0x72, 0xef, 0x91, 0xff, 0x82, 0xa7, 0x3e, 0xc9, 0x0e, 0xd4, 0xbb, 0x71, 0x14, + 0xd1, 0x2e, 0xa3, 0x81, 0x5c, 0x48, 0x0e, 0x70, 0xff, 0xb8, 0x06, 0xb7, 0xc6, 0xf3, 0x94, 0xa6, + 0xfb, 0x15, 0x6c, 0x76, 0x4d, 0x84, 0x4e, 0x2a, 0x31, 0x5a, 0x0e, 0x6e, 0xc5, 0x81, 0xb1, 0x15, + 0x13, 0x29, 0xed, 0x55, 0x8e, 0x8a, 0x4d, 0xda, 0xe8, 0x56, 0x8d, 0xb5, 0x7b, 0xd0, 0x1e, 0x3f, + 0xa9, 0x42, 0xe5, 0x0f, 0x6c, 0x95, 0xef, 0x28, 0xd1, 0xaa, 0x88, 0x98, 0xba, 0xff, 0x36, 0x6c, + 0x1d, 0xd2, 0x88, 0xa6, 0x61, 0x57, 0x1b, 0x87, 0xd4, 0x39, 0xd7, 0xa0, 0xb6, 0x49, 0xc9, 0x2a, + 0x07, 0xb8, 0x6d, 0x68, 0x95, 0x27, 0x8a, 0xe5, 0xba, 0x9b, 0xb0, 0x7e, 0x48, 0x99, 0x86, 0xeb, + 0x5d, 0xfc, 0xa9, 0x03, 0x1b, 0x38, 0x90, 0x9d, 0x64, 0x23, 0x31, 0x20, 0x55, 0xfd, 0x3b, 0xb0, + 0xaa, 0x49, 0x67, 0xea, 0x18, 0x09, 0x2d, 0xbf, 0x6d, 0x68, 0xb9, 0x3c, 0x33, 0x3f, 0x4c, 0x99, + 0x79, 0x9a, 0xf2, 0x33, 0x29, 0xc1, 0xed, 0x03, 0xd8, 0xa8, 0x44, 0xbd, 0x8a, 0xfd, 0xbb, 0x2d, + 0xd8, 0x3c, 0xa4, 0xcc, 0x30, 0x63, 0xc3, 0x40, 0x1b, 0x06, 0x98, 0xdb, 0x65, 0xc6, 0xfc, 0x94, + 0xe5, 0x76, 0x29, 0x3f, 0xc9, 0xcb, 0xb0, 0xd4, 0x0f, 0x33, 0x46, 0xa3, 0x8e, 0x1f, 0x04, 0x29, + 0xcd, 0x84, 0xcb, 0xab, 0x7b, 0x8b, 0x02, 0xba, 0x2f, 0x80, 0xee, 0x3f, 0x3a, 0x7c, 0x63, 0x0a, + 0xac, 0xa4, 0xb2, 0x3e, 0x81, 0x7a, 0xee, 0x15, 0x84, 0x92, 0xf6, 0x0c, 0x25, 0x55, 0xcd, 0xd9, + 0x2b, 0xb8, 0x86, 0x9c, 0x40, 0xfb, 0xb7, 0x60, 0xe9, 0x45, 0x1f, 0xe8, 0x77, 0xa0, 0x2d, 0x6d, + 0x43, 0x79, 0xe4, 0x4f, 0xfd, 0x01, 0x55, 0x76, 0xd5, 0x86, 0x05, 0xe5, 0xc0, 0x25, 0x0f, 0xfd, + 0xed, 0xee, 0xc2, 0x76, 0xe5, 0x4c, 0x69, 0x58, 0xf7, 0x61, 0xed, 0x90, 0x32, 0xed, 0xe6, 0x15, + 0xc5, 0xb1, 0x5e, 0xc0, 0x7d, 0x88, 0x96, 0x68, 0x4c, 0x90, 0x2a, 0xdc, 0x81, 0x7a, 0x7e, 0x89, + 0x48, 0xdb, 0xd6, 0x00, 0xf7, 0x01, 0x9a, 0xa9, 0x9a, 0xf5, 0xf8, 0xc9, 0x91, 0x47, 0xc5, 0xb4, + 0xeb, 0xb0, 0x10, 0xb3, 0xa4, 0xd3, 0x8d, 0x03, 0x25, 0xfa, 0x7c, 0xcc, 0x92, 0x83, 0x38, 0xa0, + 0xd2, 0x34, 0x8c, 0x39, 0xda, 0x34, 0xfe, 0x46, 0x6c, 0xa5, 0x3d, 0x24, 0xe5, 0xf8, 0x4d, 0xa8, + 0x2b, 0x82, 0x6a, 0x2b, 0xdf, 0x30, 0xb6, 0xb2, 0x6a, 0xce, 0xde, 0x63, 0xc1, 0x51, 0xee, 0xe4, + 0x82, 0x14, 0x20, 0x6b, 0xbf, 0x0b, 0x8b, 0xd6, 0xd0, 0x65, 0x96, 0x5d, 0x37, 0xb7, 0xec, 0x21, + 0x6c, 0x3e, 0x0a, 0x33, 0xf3, 0xc6, 0x9d, 0x66, 0xbb, 0xbe, 0x80, 0xa5, 0x23, 0x3f, 0x4c, 0xb3, + 0xe3, 0x61, 0x92, 0xc4, 0x68, 0xde, 0xaf, 0xc0, 0x72, 0x7e, 0xad, 0x27, 0x7c, 0x4c, 0x4e, 0x5a, + 0xd2, 0x60, 0x9c, 0x41, 0x5e, 0x82, 0x45, 0x75, 0x9d, 0x0b, 0x34, 0x21, 0x52, 0x53, 0x02, 0x11, + 0xc9, 0xfd, 0x7a, 0xd6, 0x52, 0x9d, 0x15, 0x58, 0x10, 0x98, 0x8d, 0x7c, 0x1d, 0x56, 0xe0, 0x6f, + 0xd3, 0x10, 0x6a, 0xf6, 0x75, 0xd0, 0x82, 0xf9, 0x73, 0x9a, 0x9e, 0xc4, 0x19, 0xc5, 0x98, 0x61, + 0xc1, 0x53, 0x9f, 0x5c, 0x90, 0x61, 0x16, 0x46, 0xa7, 0x9d, 0xcc, 0x8f, 0x82, 0x93, 0xf8, 0x39, + 0x46, 0x08, 0x0b, 0x5e, 0x13, 0x81, 0xc7, 0x02, 0x46, 0x6e, 0x43, 0xf3, 0x8c, 0xb1, 0xa4, 0xc3, + 0x43, 0x97, 0x78, 0xc8, 0x64, 0x40, 0xd0, 0xe0, 0xb0, 0x27, 0x02, 0xc4, 0x0f, 0x36, 0xa2, 0x0c, + 0x33, 0x9a, 0xfa, 0xa7, 0x34, 0x62, 0xad, 0x39, 0x71, 0xb0, 0x39, 0xf4, 0x87, 0x0a, 0x48, 0x76, + 0x01, 0x10, 0x2d, 0x49, 0xe3, 0xe7, 0xa3, 0xd6, 0xbc, 0x30, 0x3d, 0x0e, 0x39, 0xe2, 0x00, 0xae, + 0xbf, 0x13, 0x3f, 0xa3, 0x2a, 0xf4, 0x08, 0x69, 0xd6, 0x5a, 0x10, 0xfa, 0xe3, 0xe0, 0x03, 0x0d, + 0x25, 0x1d, 0x1e, 0x77, 0x48, 0xad, 0x77, 0xfc, 0x2c, 0xa3, 0x2c, 0x6b, 0xd5, 0xd1, 0x80, 0x1e, + 0x56, 0x18, 0x50, 0x21, 0xfe, 0x90, 0xf3, 0xf6, 0x71, 0x9a, 0x8e, 0x3f, 0x2c, 0x28, 0x8f, 0xb7, + 0xfc, 0x21, 0x3b, 0xa3, 0x11, 0xe3, 0xb7, 0x07, 0x67, 0x92, 0x84, 0x2d, 0x40, 0xdd, 0xac, 0x58, + 0x03, 0xfb, 0x49, 0xd8, 0xfe, 0x9c, 0x07, 0x17, 0x65, 0xaa, 0x15, 0x26, 0xf8, 0xba, 0xed, 0x4a, + 0x36, 0x95, 0xb0, 0xb6, 0x1d, 0x99, 0xa6, 0x79, 0x01, 0x2b, 0x87, 0x94, 0x3d, 0x09, 0xbb, 0xcf, + 0x68, 0x3a, 0x85, 0x51, 0x92, 0xbb, 0x30, 0xcb, 0x2d, 0x4a, 0x32, 0x58, 0xd7, 0x37, 0xa1, 0x8c, + 0xd8, 0x38, 0x23, 0x0f, 0x31, 0xf8, 0x5e, 0xa0, 0xe6, 0x3a, 0x6c, 0x94, 0x08, 0xbb, 0xa8, 0x7b, + 0x75, 0x84, 0x3c, 0x19, 0x25, 0xd4, 0x7d, 0x0a, 0x4d, 0x73, 0x12, 0x77, 0x1a, 0x01, 0xed, 0x87, + 0x83, 0x90, 0xd1, 0x54, 0x39, 0x0d, 0x0d, 0xe0, 0xf6, 0xc8, 0xb7, 0x48, 0xda, 0x31, 0xfe, 0xe6, + 0xe7, 0xed, 0xcb, 0x61, 0xcc, 0x14, 0x6d, 0xf1, 0xe1, 0xfe, 0x45, 0x0d, 0x96, 0xd4, 0x72, 0xa4, + 0x31, 0x2b, 0x99, 0x9d, 0x4b, 0x65, 0xbe, 0x0d, 0xcd, 0xbe, 0x9f, 0xb1, 0xce, 0x30, 0x09, 0x7c, + 0x15, 0xda, 0xcc, 0x78, 0x0d, 0x0e, 0xfb, 0xa1, 0x00, 0x71, 0x8b, 0x56, 0x91, 0x2b, 0x9e, 0x2d, + 0xc9, 0xbd, 0xd9, 0x35, 0x17, 0x43, 0x60, 0x96, 0xcf, 0x41, 0x6b, 0x77, 0x3c, 0xfc, 0xcd, 0x61, + 0x67, 0xe1, 0xe9, 0x19, 0x5a, 0xb7, 0xe3, 0xe1, 0x6f, 0xbe, 0x83, 0xfd, 0xf8, 0x02, 0x6d, 0xd9, + 0xf1, 0xf8, 0x4f, 0x0e, 0x39, 0x09, 0x03, 0x34, 0x5d, 0xc7, 0xe3, 0x3f, 0x39, 0xc4, 0xcf, 0x9e, + 0xa1, 0xa1, 0x3a, 0x1e, 0xff, 0xc9, 0xa3, 0xfe, 0xf3, 0xb8, 0x3f, 0x1c, 0xd0, 0x56, 0x1d, 0x81, + 0xf2, 0x8b, 0x6c, 0x43, 0x3d, 0x49, 0xc3, 0x2e, 0xed, 0xf8, 0xec, 0x0c, 0x8d, 0xc9, 0xf1, 0x16, + 0x10, 0xb0, 0xcf, 0xce, 0xdc, 0x35, 0x58, 0xd5, 0x1b, 0xad, 0xbd, 0xe7, 0x67, 0x30, 0x2f, 0x21, + 0x13, 0x37, 0xfd, 0x4d, 0x98, 0x67, 0x02, 0xad, 0x55, 0xc3, 0x53, 0xa0, 0x0d, 0xcb, 0xd6, 0xb4, + 0xa7, 0xd0, 0xdc, 0xef, 0x01, 0x31, 0xb9, 0xc9, 0x8d, 0xb8, 0x97, 0xd3, 0x11, 0xee, 0x78, 0xd9, + 0xa6, 0x93, 0xe5, 0x04, 0xbe, 0xc2, 0xcb, 0xe8, 0x71, 0x1a, 0x70, 0x47, 0x12, 0x3f, 0xfb, 0x46, + 0x4d, 0xf3, 0x07, 0xb0, 0xa8, 0x19, 0x7f, 0xc4, 0xe8, 0x80, 0x2b, 0xdc, 0x1f, 0xc4, 0xc3, 0x88, + 0x21, 0x4f, 0xc7, 0x93, 0x5f, 0xdc, 0x02, 0x51, 0xbf, 0xc8, 0xd2, 0xf1, 0xc4, 0x07, 0x59, 0x82, + 0x5a, 0x18, 0xc8, 0xe4, 0xa9, 0x16, 0x06, 0xee, 0xff, 0x3a, 0xb0, 0x6a, 0x2c, 0xe4, 0xca, 0x46, + 0x59, 0xb2, 0xb8, 0x5a, 0x85, 0xc5, 0xdd, 0x83, 0xd9, 0x93, 0x30, 0xe0, 0x39, 0x1b, 0xd7, 0xeb, + 0x86, 0x22, 0x67, 0xad, 0xc3, 0x43, 0x14, 0x8e, 0xea, 0x67, 0xcf, 0xb2, 0xd6, 0xec, 0x44, 0x54, + 0x8e, 0x52, 0x3a, 0x0f, 0xd7, 0xca, 0xe7, 0xc1, 0xd6, 0xe5, 0x5c, 0x51, 0x97, 0x22, 0x5a, 0xd5, + 0xb4, 0xb5, 0xe5, 0x75, 0x01, 0x72, 0xe0, 0xc4, 0x6d, 0xfd, 0x35, 0x80, 0x58, 0x63, 0x4a, 0xfb, + 0xbb, 0x5e, 0x12, 0x5a, 0x9b, 0xa0, 0x81, 0xec, 0x7e, 0x8c, 0xa1, 0x86, 0xc9, 0x5c, 0x2a, 0xff, + 0x81, 0x45, 0x53, 0xd8, 0x22, 0x29, 0xd1, 0xcc, 0x2c, 0x62, 0x6f, 0x23, 0xb1, 0xfd, 0x6e, 0x97, + 0x6f, 0xbd, 0x91, 0x98, 0x4f, 0xbc, 0xc3, 0x9f, 0xc2, 0xbc, 0x9c, 0x21, 0xcd, 0x42, 0x20, 0xd4, + 0xc2, 0x80, 0xbc, 0x0b, 0x60, 0xdc, 0x43, 0x62, 0x5d, 0xdb, 0x4a, 0x06, 0x39, 0x49, 0x59, 0x03, + 0xb2, 0x33, 0xd0, 0xdd, 0x1e, 0xac, 0x55, 0xa0, 0x70, 0x51, 0x74, 0x5a, 0x2d, 0x45, 0x51, 0xdf, + 0xe4, 0x26, 0x34, 0x58, 0xcc, 0xfc, 0x7e, 0x27, 0xbf, 0x21, 0x1c, 0x0f, 0x10, 0xf4, 0x94, 0x43, + 0xd0, 0x41, 0xc5, 0x7d, 0x61, 0xb9, 0xdc, 0x41, 0xc5, 0xfd, 0xc0, 0xf5, 0x31, 0xf0, 0xb2, 0x16, + 0x2d, 0x55, 0x38, 0x69, 0xcb, 0x5e, 0x83, 0x05, 0x5f, 0x4c, 0x51, 0x0b, 0x5b, 0x2e, 0x2c, 0xcc, + 0xd3, 0x08, 0x2e, 0xc1, 0x1b, 0xe8, 0x20, 0x8e, 0x7a, 0xe1, 0xa9, 0xb2, 0x8e, 0x57, 0xd0, 0x59, + 0x29, 0x58, 0x1e, 0x93, 0x04, 0x3e, 0xf3, 0x91, 0x5b, 0xd3, 0xc3, 0xdf, 0xee, 0x1f, 0x39, 0xb0, + 0x72, 0x14, 0xa7, 0xac, 0x17, 0xf7, 0xc3, 0x58, 0x86, 0xf7, 0x3c, 0x1c, 0x51, 0xe1, 0xbf, 0x8c, + 0x23, 0xe5, 0x27, 0xf7, 0x90, 0xdd, 0x38, 0x8c, 0x84, 0xad, 0xd6, 0xa4, 0x82, 0xe2, 0x30, 0xe2, + 0xa6, 0x4a, 0x6e, 0x41, 0x23, 0xa0, 0x59, 0x37, 0x0d, 0x13, 0x9e, 0xce, 0x49, 0xb7, 0x60, 0x82, + 0x38, 0xe1, 0x13, 0xbf, 0xef, 0x47, 0x5d, 0x2a, 0x3d, 0xbb, 0xfa, 0x74, 0x37, 0xd0, 0x5d, 0x69, + 0x49, 0x8c, 0xcc, 0xda, 0x06, 0xcb, 0xa5, 0xfc, 0x2a, 0xd4, 0x13, 0x05, 0x94, 0xe6, 0xd7, 0xd2, + 0x77, 0x75, 0x61, 0x39, 0x5e, 0x8e, 0xea, 0xee, 0xf0, 0xd8, 0x3f, 0xa7, 0x77, 0x3c, 0x1c, 0x0c, + 0xfc, 0x74, 0xa4, 0xb8, 0x45, 0x30, 0x7b, 0x10, 0x87, 0x11, 0x57, 0x14, 0x5f, 0x94, 0x0a, 0xde, + 0xf8, 0x6f, 0x53, 0xf4, 0x9a, 0x25, 0xba, 0xa9, 0xad, 0x19, 0x5b, 0x5b, 0x37, 0x00, 0x12, 0x9a, + 0x76, 0x69, 0xc4, 0xfc, 0x53, 0xb5, 0x62, 0x03, 0xe2, 0x9e, 0x01, 0x79, 0xdc, 0xeb, 0xf5, 0xc3, + 0x88, 0x72, 0xb6, 0x52, 0x98, 0x09, 0xda, 0x1f, 0x2f, 0x83, 0xcd, 0x69, 0xa6, 0xc4, 0xe9, 0x07, + 0xb0, 0xfa, 0x38, 0xaa, 0x60, 0xa4, 0xc8, 0x39, 0x93, 0xc8, 0xd5, 0x4a, 0xe4, 0xbe, 0x0f, 0x4d, + 0x43, 0xf0, 0x8c, 0xbc, 0x03, 0x75, 0x29, 0xa3, 0x4e, 0x14, 0xda, 0xda, 0x1b, 0x94, 0x56, 0xe8, + 0xe5, 0xc8, 0xee, 0x5f, 0x3a, 0xd0, 0xc8, 0x25, 0xcb, 0xc8, 0x43, 0xb8, 0xc6, 0xd5, 0xad, 0xa8, + 0xdc, 0xd0, 0x54, 0x72, 0x9c, 0x3d, 0xfc, 0xaf, 0x88, 0x0b, 0x05, 0x72, 0xfb, 0x18, 0x20, 0x07, + 0x56, 0x84, 0x75, 0xf7, 0xed, 0xb0, 0xee, 0x7a, 0x99, 0xaa, 0x12, 0xcd, 0x88, 0xec, 0xfe, 0x65, + 0x96, 0xa7, 0x7b, 0x15, 0xc6, 0x22, 0x6d, 0xf0, 0x0d, 0x68, 0x88, 0xb3, 0xc0, 0x3d, 0x80, 0x12, + 0xb8, 0x99, 0x97, 0x36, 0xc2, 0xc8, 0x03, 0x3c, 0x1b, 0x38, 0x4e, 0xde, 0x82, 0x45, 0x14, 0xb6, + 0x13, 0x0b, 0x85, 0xc8, 0x83, 0x6d, 0x4f, 0x68, 0x22, 0x8a, 0x54, 0x19, 0x49, 0x60, 0xc3, 0x9a, + 0xd2, 0xc9, 0x84, 0x08, 0xf2, 0x92, 0x7a, 0xcf, 0x08, 0xa5, 0xc7, 0x49, 0x29, 0x94, 0x25, 0x09, + 0xca, 0x31, 0xa1, 0xba, 0xb5, 0x6e, 0x79, 0x84, 0xdc, 0x87, 0xa6, 0xe4, 0x88, 0x9a, 0x91, 0x57, + 0x9c, 0x2d, 0x63, 0x43, 0x4c, 0x44, 0x04, 0x32, 0x80, 0x75, 0x73, 0x82, 0x96, 0xf0, 0x1a, 0x4e, + 0x7c, 0x77, 0x7a, 0x09, 0xa3, 0x92, 0x80, 0xa4, 0x5b, 0x1a, 0x68, 0xff, 0x36, 0xb4, 0xc6, 0x2d, + 0xa8, 0x62, 0xdb, 0x5f, 0xb5, 0xb7, 0x7d, 0xbd, 0xc2, 0x24, 0x33, 0xb3, 0x80, 0xf8, 0x39, 0x6c, + 0x8d, 0x11, 0xe6, 0x0a, 0x55, 0x07, 0xc3, 0x52, 0x4d, 0x6b, 0xfa, 0x73, 0x07, 0xda, 0xfb, 0x41, + 0x50, 0x72, 0x4e, 0x79, 0x91, 0xe0, 0x9b, 0x76, 0xb9, 0xbb, 0xb0, 0x5d, 0x29, 0x90, 0xac, 0x66, + 0x3c, 0x87, 0x5d, 0x8f, 0x0e, 0xe2, 0x73, 0xfa, 0x4d, 0x8b, 0xec, 0xde, 0x82, 0x1b, 0xe3, 0x38, + 0x4b, 0xd9, 0xb0, 0xbc, 0x67, 0x97, 0xc7, 0x75, 0x60, 0xf4, 0x9f, 0x0e, 0x2c, 0xda, 0x85, 0xf3, + 0x17, 0x95, 0x8b, 0xbf, 0x0e, 0x24, 0xa5, 0x19, 0xeb, 0x24, 0x71, 0xbf, 0xcf, 0x53, 0xf2, 0x80, + 0xf6, 0xfd, 0x91, 0x2c, 0xd9, 0xaf, 0xf0, 0x91, 0x23, 0x31, 0xf0, 0x88, 0xc3, 0xc9, 0x16, 0xcc, + 0xfb, 0x49, 0xd8, 0xe1, 0x56, 0x23, 0xf2, 0xf1, 0x39, 0x3f, 0x09, 0x3f, 0xa6, 0x23, 0xe2, 0xc2, + 0xa2, 0x1c, 0xe8, 0xf4, 0xe9, 0x39, 0xed, 0x63, 0xcc, 0x37, 0xe3, 0x35, 0xc4, 0xf0, 0x27, 0x1c, + 0x44, 0xee, 0xc1, 0x4a, 0x92, 0x86, 0xdc, 0xfc, 0xf2, 0xb7, 0x81, 0x79, 0x94, 0x66, 0x59, 0xc2, + 0xd5, 0xea, 0xdc, 0x1f, 0xc1, 0xf5, 0x0a, 0x5d, 0x48, 0x1f, 0xf5, 0x5d, 0x58, 0xb6, 0x5f, 0x18, + 0x94, 0x9f, 0xd2, 0x51, 0xab, 0x35, 0xd1, 0x5b, 0xea, 0x59, 0x74, 0x64, 0xf4, 0x89, 0x38, 0x9e, + 0xcf, 0x74, 0x4d, 0xcb, 0xfd, 0x12, 0xd6, 0x73, 0xe0, 0x41, 0x1c, 0x9d, 0xd3, 0x34, 0xe3, 0xd6, + 0x46, 0x60, 0xb6, 0x97, 0xc6, 0xaa, 0x20, 0x8b, 0xbf, 0x79, 0xdc, 0xc6, 0x62, 0x69, 0x06, 0x35, + 0x16, 0x73, 0x9c, 0xd4, 0x67, 0xea, 0x96, 0xc2, 0xdf, 0x3c, 0x4e, 0x0e, 0x91, 0x08, 0xed, 0xe0, + 0x98, 0x30, 0xd5, 0x86, 0x84, 0x71, 0x2e, 0xee, 0x53, 0x0c, 0x1f, 0x4d, 0x51, 0xe4, 0x1a, 0x7f, + 0x1d, 0x1a, 0x62, 0x8d, 0x7c, 0xa6, 0x5a, 0xdf, 0x8e, 0xb5, 0xbe, 0x82, 0x98, 0x1e, 0xf4, 0x34, + 0xd4, 0xfd, 0xef, 0x1a, 0x34, 0x31, 0x62, 0x7d, 0x44, 0x99, 0x1f, 0xf6, 0x27, 0xc7, 0xd2, 0x22, + 0x06, 0xad, 0xe9, 0x18, 0xf4, 0x25, 0x58, 0x34, 0x0b, 0x22, 0x23, 0x95, 0xcc, 0x1a, 0xe5, 0x90, + 0x11, 0x79, 0x19, 0x96, 0x30, 0xb5, 0xce, 0xb1, 0x84, 0xcd, 0x2c, 0x22, 0x54, 0xa3, 0xd9, 0x89, + 0xc0, 0xb5, 0x42, 0x22, 0xc0, 0x87, 0x31, 0x98, 0xee, 0x64, 0x61, 0xa0, 0xf3, 0x04, 0x84, 0x1c, + 0x87, 0x81, 0x31, 0x8c, 0xb3, 0xe7, 0x8d, 0x61, 0x9c, 0xcd, 0x73, 0xa0, 0x94, 0x8a, 0x87, 0x02, + 0x7c, 0xef, 0x5a, 0x40, 0xa3, 0x6b, 0x2a, 0xe0, 0x93, 0x70, 0x80, 0xaf, 0x61, 0xb2, 0xb8, 0x5d, + 0x17, 0x16, 0x2b, 0xbe, 0xf2, 0x34, 0x0d, 0xcc, 0x34, 0x2d, 0x4f, 0xea, 0x1a, 0x56, 0x52, 0x77, + 0x13, 0x1a, 0x71, 0x42, 0xa3, 0x8e, 0x4c, 0xb1, 0x9b, 0x22, 0x7a, 0xe0, 0xa0, 0xa7, 0x08, 0x91, + 0x25, 0x13, 0xd4, 0x79, 0x36, 0x4d, 0x5e, 0x6a, 0x2b, 0xa6, 0x56, 0x54, 0x8c, 0x4a, 0x04, 0x67, + 0x2e, 0x4b, 0x04, 0xdd, 0x7d, 0x8c, 0x8a, 0x15, 0x63, 0x69, 0x3e, 0xaf, 0xc3, 0x1c, 0xaa, 0x49, + 0x59, 0xce, 0xba, 0x95, 0xc6, 0x48, 0xa3, 0xf0, 0x24, 0x8e, 0xfb, 0x7d, 0x7c, 0x43, 0xc4, 0xa1, + 0x69, 0x44, 0xbf, 0x0e, 0x0b, 0x62, 0x57, 0xb4, 0xd5, 0xcc, 0xe3, 0xf7, 0x47, 0x81, 0xfb, 0x6f, + 0x0e, 0x90, 0xe3, 0xe1, 0xc9, 0x20, 0x9c, 0x9e, 0xda, 0xf4, 0x09, 0x3a, 0x81, 0x59, 0x34, 0x13, + 0x61, 0x8e, 0xf8, 0xbb, 0x60, 0x21, 0xb3, 0x45, 0x0b, 0xc9, 0xb7, 0xf3, 0x5a, 0x75, 0x8e, 0x3e, + 0x67, 0x6e, 0x3e, 0x77, 0xf1, 0xfd, 0x90, 0x46, 0xac, 0x23, 0x8b, 0x2d, 0xdc, 0xc5, 0x23, 0xe0, + 0xa3, 0xc0, 0x3d, 0x86, 0x35, 0x6b, 0x65, 0x52, 0xd3, 0xb7, 0xa1, 0x29, 0x04, 0x48, 0xfa, 0x7e, + 0x57, 0x57, 0xc3, 0x1b, 0x08, 0x3b, 0x42, 0xd0, 0x24, 0x7d, 0xfd, 0x89, 0x03, 0xeb, 0xc7, 0xe1, + 0x60, 0xd8, 0xf7, 0x19, 0xfd, 0x25, 0x68, 0x2c, 0x5f, 0xfe, 0x8c, 0xb5, 0x7c, 0xa5, 0xc9, 0xd9, + 0x5c, 0x93, 0xee, 0xcf, 0x1c, 0xd8, 0x28, 0x88, 0xa2, 0x63, 0x42, 0xdb, 0x98, 0xc6, 0x14, 0x07, + 0x24, 0x92, 0xc1, 0xb4, 0x66, 0x31, 0x7d, 0x09, 0x16, 0x07, 0x61, 0x14, 0x0e, 0x86, 0x83, 0x8e, + 0xd0, 0xbd, 0x90, 0xa9, 0x29, 0x81, 0x47, 0xb8, 0x05, 0x1c, 0xc9, 0x7f, 0x6e, 0x20, 0xcd, 0x4a, + 0x24, 0x01, 0x14, 0x48, 0x6f, 0xc2, 0x7a, 0x1e, 0xb7, 0x77, 0x4e, 0xfd, 0x30, 0xea, 0xf4, 0xe3, + 0x2c, 0x93, 0x7b, 0x4c, 0xf2, 0xb1, 0x43, 0x3f, 0x8c, 0x3e, 0x89, 0xb3, 0xcc, 0x70, 0x02, 0x73, + 0xa6, 0x13, 0xe0, 0x01, 0xcc, 0xca, 0x67, 0x67, 0x7e, 0x9f, 0xbe, 0x1f, 0x0f, 0x4e, 0x5e, 0xac, + 0xee, 0x6f, 0x43, 0x53, 0xd4, 0xdd, 0x98, 0x9f, 0x9e, 0x52, 0xb5, 0x03, 0x0d, 0x84, 0x3d, 0x41, + 0x50, 0xe5, 0x36, 0xfc, 0x97, 0x03, 0xe4, 0x80, 0x87, 0x32, 0xfd, 0xa9, 0xed, 0x81, 0xbb, 0x12, + 0x91, 0x37, 0xe7, 0x16, 0x56, 0x97, 0x90, 0x8f, 0x6c, 0xf3, 0x9b, 0xb1, 0xcc, 0x4f, 0xaf, 0x66, + 0xf6, 0x8a, 0xc5, 0xb1, 0x92, 0x1f, 0x7f, 0x19, 0x96, 0x2e, 0xfc, 0x7e, 0x9f, 0x32, 0xfd, 0xc4, + 0x26, 0x2b, 0xf1, 0x02, 0xaa, 0x72, 0x70, 0xb5, 0xe0, 0x79, 0x63, 0xc1, 0x1b, 0xb0, 0x66, 0xad, + 0x57, 0x46, 0x43, 0x0f, 0x61, 0x53, 0x80, 0xf7, 0xfb, 0xfd, 0xa9, 0xbd, 0xaa, 0xfb, 0xd7, 0x35, + 0xd8, 0x2a, 0x4d, 0xd3, 0x61, 0x83, 0x6d, 0xc6, 0x77, 0xf4, 0x72, 0xab, 0x27, 0xec, 0xc9, 0x4f, + 0x39, 0xab, 0xfd, 0x4f, 0x0e, 0xcc, 0x09, 0xd0, 0xc4, 0xdd, 0xf8, 0x5c, 0x39, 0x04, 0x69, 0x70, + 0x22, 0x23, 0xfa, 0xf6, 0x74, 0xcc, 0xc4, 0xff, 0xcc, 0x67, 0x55, 0xe1, 0x49, 0xe4, 0x8b, 0xea, + 0x77, 0x61, 0xa5, 0x88, 0x70, 0xa5, 0x27, 0x27, 0x51, 0x55, 0xf9, 0xe0, 0x9c, 0x1a, 0xcf, 0xa8, + 0x3f, 0x75, 0x60, 0xf9, 0x20, 0x8e, 0x82, 0x90, 0xdf, 0x98, 0x47, 0x7e, 0xea, 0x0f, 0x32, 0xf9, + 0x92, 0x2f, 0x40, 0xaa, 0xec, 0xae, 0x01, 0x63, 0x0a, 0x9c, 0xbb, 0x00, 0xdd, 0x33, 0xda, 0x7d, + 0xd6, 0x91, 0x15, 0x47, 0xf1, 0xfc, 0xcf, 0x21, 0xef, 0x87, 0x41, 0x46, 0xde, 0x80, 0xb5, 0x7c, + 0xb8, 0xe3, 0x47, 0x41, 0x47, 0x96, 0x1b, 0xf1, 0x75, 0x43, 0xe3, 0xed, 0x47, 0xc1, 0x7e, 0xf6, + 0x2c, 0xe3, 0xb1, 0xa2, 0xae, 0xb2, 0x75, 0x2c, 0x17, 0xbe, 0xac, 0xe1, 0xfb, 0x08, 0x76, 0xff, + 0xc7, 0xc1, 0x1b, 0x50, 0xad, 0x4a, 0xee, 0x76, 0x5e, 0x58, 0xc3, 0x7a, 0xab, 0xb5, 0x65, 0xb5, + 0xc2, 0x96, 0x11, 0x98, 0x0d, 0x19, 0x1d, 0xa8, 0x8b, 0x85, 0xff, 0x26, 0xef, 0xc3, 0x8a, 0x5e, + 0x71, 0x27, 0x41, 0xb5, 0xc8, 0x63, 0xb2, 0x95, 0x27, 0x8e, 0x96, 0xd6, 0xbc, 0xe5, 0x6e, 0x41, + 0x8d, 0xea, 0x78, 0x5d, 0x9b, 0xca, 0x51, 0x77, 0x51, 0xdb, 0xd2, 0x3f, 0x89, 0x2f, 0x21, 0x35, + 0xed, 0x0e, 0x19, 0x0d, 0x64, 0xa8, 0xac, 0xbf, 0xdd, 0xff, 0x70, 0x60, 0x79, 0x3f, 0x08, 0x70, + 0xdd, 0xd3, 0xb8, 0x09, 0xb5, 0xca, 0xda, 0x25, 0xab, 0x9c, 0xf9, 0x39, 0x57, 0xf9, 0x0b, 0x3b, + 0x91, 0x31, 0x4a, 0x70, 0x5d, 0x58, 0xc9, 0xd7, 0x59, 0xbd, 0xbd, 0xee, 0xb7, 0x80, 0x88, 0xf4, + 0xca, 0x52, 0x47, 0x11, 0x6b, 0x03, 0xd6, 0x2c, 0x2c, 0xe9, 0x6b, 0x3e, 0x84, 0xbb, 0x87, 0x94, + 0x1d, 0xa4, 0xa3, 0x84, 0xc5, 0x2a, 0x9c, 0x7d, 0x44, 0x93, 0x38, 0x0b, 0x95, 0xe7, 0xa2, 0x53, + 0x79, 0x9f, 0x7f, 0x76, 0xe0, 0xde, 0x14, 0x84, 0xe4, 0x12, 0xbe, 0x28, 0xd7, 0x97, 0x7e, 0xc3, + 0x6c, 0x6f, 0x99, 0x8a, 0xca, 0x9e, 0x86, 0xc8, 0x2e, 0x03, 0x4d, 0xb2, 0xfd, 0x1e, 0x2c, 0xd9, + 0x83, 0x57, 0x72, 0x15, 0x7d, 0xb8, 0x73, 0x89, 0x10, 0xd3, 0xd8, 0xdc, 0x1d, 0x58, 0xea, 0x5a, + 0x24, 0x24, 0xa3, 0x02, 0xd4, 0x3d, 0x80, 0x57, 0x2e, 0xe5, 0x26, 0xd5, 0x36, 0x36, 0x43, 0x77, + 0xff, 0x6e, 0x16, 0xb6, 0x3e, 0x0b, 0xd9, 0x59, 0x90, 0xfa, 0x17, 0xca, 0xfa, 0xa6, 0x11, 0xb2, + 0x90, 0xbc, 0xd7, 0xca, 0xf5, 0x86, 0x57, 0x61, 0x35, 0x8e, 0x28, 0xe6, 0x18, 0x9d, 0xc4, 0xcf, + 0xb2, 0x8b, 0x38, 0x55, 0x77, 0xe9, 0x72, 0x1c, 0x51, 0x9e, 0x67, 0x1c, 0x49, 0x70, 0xe1, 0x36, + 0x9e, 0x2d, 0xde, 0xc6, 0x2b, 0x30, 0x93, 0x84, 0x91, 0x7c, 0x33, 0xe1, 0x3f, 0xf9, 0xdd, 0xc9, + 0x52, 0x3f, 0x30, 0x28, 0xcb, 0xbb, 0x13, 0xa1, 0x9a, 0xae, 0x59, 0xc5, 0x9f, 0x2f, 0x54, 0xf1, + 0x0d, 0x9d, 0x2c, 0xd8, 0x55, 0x8b, 0x9b, 0xd0, 0x90, 0x3f, 0x3b, 0xcc, 0x3f, 0x95, 0x29, 0x10, + 0x48, 0xd0, 0x13, 0xff, 0xd4, 0x88, 0xd6, 0xc0, 0x8a, 0xd6, 0x76, 0x01, 0x7a, 0x94, 0x76, 0xac, + 0x64, 0xa8, 0xde, 0xa3, 0x54, 0x38, 0x5d, 0x1e, 0x2a, 0x9f, 0xf8, 0xd1, 0xb3, 0x0e, 0xd6, 0x20, + 0x9a, 0x42, 0x1c, 0x0e, 0xf8, 0xd4, 0x1f, 0x60, 0x4c, 0x8c, 0x83, 0x4a, 0xa6, 0x45, 0xa1, 0x51, + 0x0e, 0xdb, 0xcf, 0xab, 0x29, 0x88, 0xd2, 0x0d, 0xd9, 0xa8, 0xb5, 0x94, 0xcf, 0x3f, 0x08, 0xd9, + 0x48, 0xcf, 0x47, 0x9d, 0xa5, 0xa3, 0xd6, 0x72, 0x3e, 0xff, 0x40, 0x80, 0xb8, 0x78, 0xd9, 0x45, + 0xd8, 0xa3, 0xa2, 0x31, 0x64, 0x45, 0xb6, 0x4a, 0x71, 0xc8, 0x41, 0x1c, 0x60, 0x18, 0x79, 0x11, + 0xa6, 0x46, 0x72, 0xba, 0x2a, 0x52, 0x58, 0x0e, 0x54, 0xa6, 0xe1, 0xbe, 0x0a, 0x2b, 0xca, 0x5c, + 0xcc, 0xde, 0xc9, 0x94, 0x66, 0xc3, 0x3e, 0x53, 0xbd, 0x93, 0xe2, 0xcb, 0x7d, 0x0b, 0xbb, 0x22, + 0x3e, 0x89, 0x4f, 0x4f, 0xf3, 0xf4, 0x49, 0x9a, 0xd6, 0x26, 0xcc, 0xf5, 0x11, 0xae, 0xa6, 0x88, + 0x2f, 0x37, 0xc2, 0x7a, 0x4e, 0x61, 0x4a, 0xfe, 0x6a, 0x11, 0x46, 0xbd, 0x58, 0x66, 0x0b, 0xf8, + 0x9b, 0x9f, 0xc5, 0x80, 0x9e, 0x0c, 0x4f, 0x55, 0x0f, 0x14, 0x7e, 0x70, 0xcc, 0x0b, 0x3f, 0x8d, + 0xe4, 0x85, 0x8a, 0xbf, 0x39, 0x26, 0x4d, 0xd3, 0x38, 0x95, 0xb7, 0xa7, 0xf8, 0x70, 0x0f, 0x61, + 0xeb, 0xf8, 0x6a, 0x22, 0x72, 0x42, 0xa2, 0x5a, 0x23, 0x8f, 0x3f, 0x7e, 0xb8, 0x1f, 0x5b, 0x1d, + 0x20, 0xd8, 0x25, 0x30, 0xcd, 0x31, 0x5a, 0x87, 0x6b, 0xe8, 0xcb, 0x15, 0x31, 0xfc, 0xe0, 0x19, + 0x61, 0xab, 0x4c, 0x4d, 0xf7, 0xa0, 0x95, 0x3b, 0x2a, 0x84, 0x27, 0xfc, 0x95, 0x8a, 0x8e, 0x0a, + 0x6b, 0xee, 0x74, 0x2d, 0x15, 0xbf, 0xd4, 0x2e, 0x89, 0xaf, 0x60, 0xcd, 0x14, 0xed, 0x1b, 0xcd, + 0xfa, 0x7f, 0xec, 0x60, 0x85, 0x4c, 0x67, 0x60, 0xc7, 0x2c, 0xa5, 0xfe, 0xe0, 0x1b, 0x7d, 0x10, + 0xff, 0x1e, 0xdc, 0x36, 0xfb, 0xa5, 0xae, 0x2c, 0x89, 0xfb, 0x07, 0xf8, 0x8c, 0x28, 0x1e, 0xf9, + 0xff, 0x1f, 0xe4, 0x7f, 0x0f, 0x6e, 0x18, 0xf2, 0x5f, 0x51, 0x0c, 0xf7, 0xaf, 0x1c, 0xac, 0x22, + 0xee, 0x0f, 0x83, 0x90, 0x59, 0x31, 0x07, 0xf7, 0x4c, 0xcc, 0x4f, 0x59, 0x27, 0xf0, 0x19, 0xd5, + 0x4d, 0x9c, 0x1c, 0xf2, 0xc8, 0x67, 0x58, 0x3c, 0xa1, 0x51, 0x20, 0x06, 0x65, 0x31, 0x80, 0x46, + 0x81, 0x1a, 0x12, 0x99, 0xc3, 0xc9, 0xc8, 0x4a, 0xd4, 0xde, 0xc7, 0x7b, 0x1a, 0x9b, 0x5e, 0xf0, + 0xc4, 0x5f, 0xf3, 0xc4, 0x07, 0x3f, 0xd6, 0x71, 0xaf, 0xc7, 0x8f, 0xdc, 0x35, 0x04, 0xcb, 0x2f, + 0xf7, 0x91, 0x78, 0x94, 0x36, 0x44, 0x93, 0xe7, 0xed, 0x35, 0x98, 0xa3, 0x18, 0x26, 0xcb, 0x53, + 0xa6, 0xeb, 0xfb, 0x3e, 0xc7, 0xed, 0xe0, 0x98, 0x27, 0x51, 0xdc, 0x11, 0x34, 0x0c, 0x30, 0x77, + 0x44, 0xa8, 0x47, 0x59, 0x05, 0xe5, 0xbf, 0xc9, 0x0d, 0x80, 0x30, 0xa0, 0x11, 0x0b, 0x7b, 0x21, + 0x55, 0x1d, 0x08, 0x06, 0x84, 0xdf, 0x4b, 0x03, 0x9a, 0x65, 0xea, 0xf9, 0xae, 0xee, 0xa9, 0x4f, + 0x9e, 0x61, 0xf0, 0xdb, 0x34, 0x63, 0xfe, 0x20, 0x51, 0x97, 0xa4, 0x06, 0x3c, 0xf8, 0xd9, 0xab, + 0xb0, 0x74, 0x18, 0x8b, 0x70, 0xe0, 0x09, 0xbf, 0x05, 0x53, 0xf2, 0x18, 0xe6, 0x65, 0xfb, 0x35, + 0xd9, 0x2c, 0xf5, 0x63, 0xa3, 0xe6, 0xdb, 0x5b, 0x63, 0xfa, 0xb4, 0xdd, 0xb5, 0xaf, 0xff, 0xf5, + 0xdf, 0x7f, 0x52, 0x5b, 0x24, 0x8d, 0xfb, 0xe7, 0x6f, 0xdd, 0x3f, 0xa5, 0x0c, 0xdd, 0xed, 0x29, + 0x2c, 0x5a, 0x1d, 0xb3, 0x64, 0xc7, 0xea, 0x7a, 0x2d, 0x34, 0xd2, 0xb6, 0x77, 0x27, 0xf6, 0xc4, + 0xba, 0xd7, 0x91, 0xc5, 0x1a, 0x59, 0x95, 0x2c, 0xf2, 0x66, 0x58, 0xf2, 0x25, 0x2c, 0x7f, 0x80, + 0x65, 0x78, 0x4d, 0x94, 0xdc, 0xcc, 0x89, 0x55, 0x36, 0x02, 0xb7, 0x6f, 0x8d, 0x47, 0x90, 0x0c, + 0xb7, 0x91, 0xe1, 0x06, 0x59, 0xe3, 0x0c, 0x45, 0x99, 0x5f, 0xf3, 0x24, 0x19, 0xac, 0xc8, 0xd6, + 0xc2, 0x17, 0xca, 0x73, 0x07, 0x79, 0x6e, 0x92, 0x75, 0xce, 0x33, 0x10, 0x0c, 0x72, 0xa6, 0x31, + 0x56, 0x11, 0xcd, 0x56, 0x58, 0x72, 0x63, 0x6c, 0x8f, 0xac, 0x60, 0x79, 0xf3, 0x92, 0x1e, 0x5a, + 0x7b, 0x95, 0xa7, 0x94, 0xe3, 0xea, 0x36, 0x5a, 0xf2, 0x13, 0x71, 0xb5, 0x54, 0x36, 0x6d, 0x93, + 0x57, 0x2e, 0xef, 0x14, 0x17, 0x32, 0xdc, 0x9d, 0xb6, 0xa5, 0xdc, 0xfd, 0x16, 0x0a, 0x73, 0x83, + 0xec, 0x48, 0x61, 0xac, 0x36, 0x72, 0xd5, 0xa8, 0x4e, 0xba, 0xd0, 0x34, 0xfb, 0x5f, 0xc9, 0x76, + 0xc5, 0x4d, 0xa6, 0x99, 0xef, 0x54, 0x0f, 0x4a, 0x86, 0x2d, 0x64, 0x48, 0xc8, 0x8a, 0x64, 0xa8, + 0xdb, 0x65, 0xc9, 0x57, 0xb0, 0x5c, 0xe8, 0x1d, 0x25, 0x6e, 0x61, 0xfb, 0x2a, 0xfa, 0x80, 0xdb, + 0x2f, 0x4d, 0xc4, 0x91, 0x5c, 0x6f, 0x20, 0xd7, 0x96, 0xbb, 0x66, 0xec, 0xb2, 0xe2, 0xfc, 0x1d, + 0xe7, 0x55, 0x92, 0xe1, 0x3e, 0x9b, 0x6d, 0x8e, 0x53, 0xf1, 0xbe, 0x79, 0x49, 0x8f, 0x64, 0x69, + 0xaf, 0x15, 0x4f, 0x3c, 0xad, 0x19, 0xb6, 0x8e, 0x19, 0xcd, 0xb9, 0x18, 0xe6, 0x4d, 0xc3, 0x77, + 0xb7, 0xba, 0xb9, 0x57, 0xf6, 0x17, 0xbb, 0x6d, 0xe4, 0xba, 0x4e, 0x48, 0x81, 0x6b, 0xcc, 0x12, + 0x92, 0x59, 0xbd, 0xcf, 0x92, 0xa9, 0x6d, 0xd5, 0x15, 0xdd, 0xc7, 0x95, 0x2b, 0x35, 0xdb, 0x89, + 0xc7, 0xae, 0x34, 0x66, 0x49, 0x46, 0x9e, 0xc3, 0x92, 0x70, 0x17, 0x2f, 0x7e, 0x67, 0x77, 0x91, + 0xef, 0x96, 0x4b, 0x72, 0x9f, 0x61, 0x6e, 0xec, 0x67, 0x50, 0xd7, 0xf7, 0x31, 0x69, 0x19, 0x8b, + 0xb0, 0x1a, 0x41, 0xdb, 0x63, 0xda, 0xfc, 0x94, 0xb5, 0xba, 0x8b, 0x72, 0x55, 0xa2, 0x69, 0x8f, + 0x13, 0xfe, 0x11, 0x40, 0xde, 0xf7, 0x47, 0xae, 0x97, 0x28, 0x6b, 0xcd, 0xb5, 0xab, 0x86, 0xd4, + 0x5f, 0x38, 0x20, 0xf9, 0x15, 0xb2, 0x64, 0x91, 0x57, 0xe7, 0x4d, 0x87, 0x1f, 0xd6, 0x79, 0x2b, + 0x76, 0x0a, 0xb6, 0xc7, 0xb7, 0x88, 0xa9, 0x4d, 0x71, 0xd5, 0x61, 0xd3, 0x65, 0x26, 0xbe, 0x02, + 0x71, 0x59, 0x18, 0xbd, 0x69, 0x3b, 0x55, 0x5c, 0x2a, 0x2f, 0x8b, 0x72, 0xa3, 0x59, 0xe9, 0xb2, + 0xc8, 0xfb, 0xc9, 0xc8, 0x33, 0xfc, 0x0b, 0x2f, 0xa3, 0xb5, 0x8a, 0x98, 0xb4, 0xca, 0x7d, 0x66, + 0xed, 0x1b, 0xe3, 0x86, 0xb3, 0x6a, 0xfb, 0x96, 0x99, 0x28, 0x1e, 0x2a, 0xb1, 0xe1, 0xa2, 0xa1, + 0xca, 0xda, 0x70, 0xab, 0xef, 0xaa, 0x7d, 0xbd, 0x62, 0x44, 0x52, 0xdf, 0x40, 0xea, 0xcb, 0x64, + 0x51, 0xbb, 0x44, 0xa4, 0x25, 0xf6, 0x44, 0xbf, 0x74, 0x5b, 0x7b, 0x52, 0x6c, 0x87, 0xb2, 0x7c, + 0x60, 0xa9, 0x29, 0xaa, 0xe4, 0x03, 0x75, 0xdb, 0x13, 0xf9, 0x43, 0xbb, 0xbb, 0x4a, 0x75, 0x7b, + 0xb8, 0x13, 0xdb, 0x33, 0x4a, 0xa7, 0x65, 0x6c, 0x0b, 0x87, 0x7b, 0x13, 0x39, 0x5f, 0x27, 0x5b, + 0x45, 0xce, 0xb2, 0x1d, 0x84, 0x7c, 0xed, 0xc0, 0x5a, 0x45, 0xb3, 0x41, 0x2e, 0xc1, 0xf8, 0xd6, + 0x88, 0x5c, 0x82, 0x49, 0xdd, 0x0a, 0x2e, 0x4a, 0xb0, 0xe3, 0xa2, 0x04, 0x7e, 0x10, 0x68, 0x09, + 0x64, 0x62, 0xcd, 0x2d, 0xf3, 0xcf, 0x1c, 0xd8, 0xac, 0x6e, 0x2c, 0x20, 0x2f, 0xeb, 0xbf, 0x19, + 0x99, 0xd4, 0xf2, 0xd0, 0xbe, 0x73, 0x19, 0x9a, 0x94, 0xe6, 0x65, 0x94, 0xe6, 0xa6, 0xdb, 0xe6, + 0xd2, 0xa4, 0x88, 0x5b, 0x25, 0xd0, 0x05, 0x56, 0x63, 0xed, 0xa7, 0x7b, 0x62, 0xc4, 0x16, 0xd5, + 0x1d, 0x0e, 0xed, 0xdb, 0x13, 0x30, 0x6c, 0xf7, 0x45, 0x36, 0xe4, 0x86, 0xe0, 0x7b, 0xb7, 0xee, + 0x01, 0x90, 0x67, 0x34, 0x7f, 0x1a, 0xb7, 0xce, 0x68, 0xe9, 0xb5, 0xdf, 0x3a, 0xa3, 0xe5, 0x07, + 0xf8, 0xd2, 0x19, 0x45, 0x66, 0xf8, 0x18, 0x4f, 0x3e, 0xc7, 0x63, 0x23, 0x9f, 0x02, 0x5a, 0xc5, + 0xa3, 0x9e, 0x55, 0x1d, 0x1b, 0xbb, 0xd8, 0x5f, 0x72, 0x95, 0xe2, 0x85, 0x81, 0x6b, 0xcf, 0x83, + 0x05, 0x85, 0x4e, 0xb6, 0x8a, 0x04, 0x14, 0xe5, 0xca, 0xd7, 0x5c, 0x77, 0x0b, 0x89, 0xae, 0xba, + 0x4d, 0x93, 0x28, 0xa7, 0x79, 0x02, 0x0d, 0xe3, 0xe5, 0x92, 0x68, 0x27, 0x5b, 0x7e, 0xa8, 0x6d, + 0x6f, 0x57, 0x8e, 0xd9, 0xae, 0xc4, 0x5d, 0xe6, 0x0c, 0x32, 0x44, 0xd0, 0x3c, 0x7e, 0x17, 0x16, + 0xad, 0xc7, 0xc3, 0x5c, 0xf9, 0x55, 0xcf, 0x9b, 0xb9, 0xf2, 0x2b, 0x5f, 0x1c, 0x55, 0xa0, 0xe9, + 0xa2, 0xf2, 0x33, 0x89, 0xa2, 0x79, 0x7d, 0x01, 0x75, 0xfd, 0x66, 0x97, 0xeb, 0xbf, 0xf8, 0x8c, + 0x77, 0x19, 0x0f, 0x6b, 0x0f, 0x2e, 0xf8, 0xe4, 0x93, 0x78, 0x70, 0x22, 0xf5, 0x65, 0xbc, 0x48, + 0xe5, 0xfa, 0x2a, 0x3f, 0xcb, 0xe5, 0xfa, 0xaa, 0x7a, 0xc2, 0xb2, 0xf4, 0xd5, 0x45, 0x04, 0xbd, + 0x86, 0x14, 0x96, 0x0b, 0x2f, 0x41, 0x79, 0x58, 0x51, 0xfd, 0xee, 0x95, 0x87, 0x15, 0x63, 0x9e, + 0x90, 0xec, 0xc0, 0x4d, 0xf0, 0xf3, 0xfb, 0xfd, 0xdc, 0xb6, 0x84, 0xbb, 0x17, 0xef, 0x24, 0x96, + 0xdd, 0x5a, 0x0f, 0x42, 0x96, 0xdd, 0xda, 0x8f, 0x2a, 0x25, 0x77, 0x2f, 0x32, 0x45, 0xf2, 0x14, + 0x16, 0x54, 0x81, 0x3e, 0x37, 0xda, 0xc2, 0xd3, 0x44, 0xbb, 0x55, 0x1e, 0x90, 0x54, 0x2d, 0xc3, + 0xf5, 0x83, 0x00, 0xa9, 0xca, 0x8d, 0x30, 0xca, 0xf5, 0xf9, 0x46, 0x94, 0x2b, 0xfd, 0xf9, 0x46, + 0x54, 0xd5, 0xf7, 0xad, 0x8d, 0x10, 0x9e, 0x4b, 0xf3, 0xf8, 0x7b, 0x07, 0xcb, 0x18, 0x93, 0xab, + 0xed, 0xe4, 0xcd, 0x2b, 0x14, 0xe6, 0x85, 0x40, 0x6f, 0x5d, 0xb9, 0x94, 0xef, 0xde, 0x45, 0x31, + 0x5d, 0x77, 0x57, 0x5d, 0xa6, 0x38, 0x2d, 0x10, 0xe8, 0xba, 0xae, 0xcf, 0x85, 0xfe, 0x5b, 0x47, + 0xfc, 0xfd, 0xee, 0x04, 0xba, 0x64, 0x6f, 0x4a, 0x01, 0x94, 0xc0, 0xf7, 0xa7, 0xc6, 0x97, 0xe2, + 0xde, 0x41, 0x71, 0x6f, 0xb9, 0xdb, 0x13, 0xc4, 0xe5, 0xc2, 0xfe, 0x3e, 0x6c, 0xeb, 0xaa, 0xbc, + 0x45, 0xf7, 0xc3, 0x61, 0x14, 0x64, 0x79, 0x5e, 0x3a, 0xa6, 0x74, 0x9f, 0x1b, 0x4e, 0xb1, 0x58, + 0x6b, 0xdf, 0x8f, 0x17, 0x72, 0x54, 0x88, 0xd1, 0xe3, 0xb4, 0x39, 0xf7, 0x04, 0x56, 0xd5, 0xbc, + 0x0f, 0x43, 0x9f, 0xfd, 0xc2, 0x3c, 0x6f, 0x21, 0xcf, 0xb6, 0xbb, 0x61, 0xf2, 0xec, 0x85, 0x3e, + 0xd3, 0x1c, 0x33, 0x7c, 0x64, 0xb5, 0xea, 0xb0, 0x66, 0xf2, 0x5d, 0x59, 0xa1, 0x35, 0x93, 0xef, + 0xea, 0x92, 0xb1, 0x9d, 0x7c, 0x9f, 0x52, 0x26, 0x4a, 0xb8, 0x81, 0x64, 0x70, 0x0e, 0x2b, 0xc7, + 0x63, 0x99, 0x1e, 0xff, 0xdc, 0x4c, 0x65, 0x0c, 0xe4, 0x22, 0xd3, 0xac, 0xc0, 0x94, 0x2f, 0xf6, + 0x5c, 0xbc, 0x28, 0x9b, 0x15, 0x5a, 0x72, 0x73, 0x7c, 0xed, 0xb6, 0xcc, 0xb7, 0xb2, 0xb8, 0x6b, + 0xf3, 0x35, 0x32, 0x24, 0xfc, 0xbb, 0x45, 0xce, 0x77, 0x04, 0xc4, 0xce, 0x92, 0xf0, 0xef, 0x5d, + 0xb4, 0x17, 0xa8, 0xa8, 0xcb, 0x4e, 0x97, 0x22, 0xdd, 0x46, 0xc6, 0xdb, 0xee, 0x66, 0x39, 0x45, + 0xe2, 0xbc, 0x39, 0xeb, 0xdf, 0x83, 0xb5, 0x42, 0xee, 0xfd, 0x82, 0x78, 0x5b, 0xe6, 0x5c, 0x48, + 0xbc, 0x15, 0x73, 0x86, 0x79, 0x70, 0xa1, 0xd8, 0x4a, 0x6e, 0x57, 0xe5, 0x1b, 0x56, 0x2d, 0x73, + 0x52, 0xe6, 0x23, 0xef, 0x0d, 0xb2, 0x59, 0x4a, 0x47, 0x90, 0xc2, 0x9b, 0x0e, 0xf9, 0x53, 0x07, + 0xff, 0xc4, 0x60, 0x4c, 0xad, 0x97, 0xdc, 0xab, 0x4a, 0x78, 0xaf, 0x2c, 0x86, 0xf4, 0x27, 0xe4, + 0x46, 0x31, 0x2b, 0x2e, 0x89, 0x73, 0x86, 0x15, 0x08, 0xb3, 0x62, 0x6b, 0xe5, 0xe4, 0x15, 0xa5, + 0xdc, 0xb1, 0x49, 0x6b, 0x31, 0x15, 0x97, 0x59, 0xa5, 0xe2, 0xf4, 0x63, 0xfb, 0x0f, 0x89, 0x2d, + 0x96, 0x77, 0x2a, 0x56, 0x7d, 0x15, 0xd6, 0x2f, 0x21, 0xeb, 0x5d, 0xb2, 0x5d, 0x58, 0x6f, 0x41, + 0x04, 0x11, 0xd6, 0xe6, 0xc5, 0x5c, 0x2b, 0xac, 0x2d, 0x95, 0x9f, 0xad, 0xb0, 0xb6, 0x5c, 0x01, + 0x2e, 0x85, 0xb5, 0x58, 0xe1, 0xc5, 0xcb, 0xf0, 0x64, 0x0e, 0xff, 0x8d, 0x91, 0xb7, 0xff, 0x2f, + 0x00, 0x00, 0xff, 0xff, 0xb9, 0x45, 0x93, 0xa0, 0x96, 0x44, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -5375,6 +5559,7 @@ type GoCryptoTraderClient interface { GetExchangeOrderbookStream(ctx context.Context, in *GetExchangeOrderbookStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetExchangeOrderbookStreamClient, error) GetTickerStream(ctx context.Context, in *GetTickerStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetTickerStreamClient, error) GetExchangeTickerStream(ctx context.Context, in *GetExchangeTickerStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetExchangeTickerStreamClient, error) + GetAuditEvent(ctx context.Context, in *GetAuditEventRequest, opts ...grpc.CallOption) (*GetAuditEventResponse, error) } type goCryptoTraderClient struct { @@ -5900,6 +6085,15 @@ func (x *goCryptoTraderGetExchangeTickerStreamClient) Recv() (*TickerResponse, e return m, nil } +func (c *goCryptoTraderClient) GetAuditEvent(ctx context.Context, in *GetAuditEventRequest, opts ...grpc.CallOption) (*GetAuditEventResponse, error) { + out := new(GetAuditEventResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetAuditEvent", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // GoCryptoTraderServer is the server API for GoCryptoTrader service. type GoCryptoTraderServer interface { GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) @@ -5949,152 +6143,7 @@ type GoCryptoTraderServer interface { GetExchangeOrderbookStream(*GetExchangeOrderbookStreamRequest, GoCryptoTrader_GetExchangeOrderbookStreamServer) error GetTickerStream(*GetTickerStreamRequest, GoCryptoTrader_GetTickerStreamServer) error GetExchangeTickerStream(*GetExchangeTickerStreamRequest, GoCryptoTrader_GetExchangeTickerStreamServer) error -} - -// UnimplementedGoCryptoTraderServer can be embedded to have forward compatible implementations. -type UnimplementedGoCryptoTraderServer struct { -} - -func (*UnimplementedGoCryptoTraderServer) GetInfo(ctx context.Context, req *GetInfoRequest) (*GetInfoResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetSubsystems(ctx context.Context, req *GetSubsystemsRequest) (*GetSusbsytemsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetSubsystems not implemented") -} -func (*UnimplementedGoCryptoTraderServer) EnableSubsystem(ctx context.Context, req *GenericSubsystemRequest) (*GenericSubsystemResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method EnableSubsystem not implemented") -} -func (*UnimplementedGoCryptoTraderServer) DisableSubsystem(ctx context.Context, req *GenericSubsystemRequest) (*GenericSubsystemResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DisableSubsystem not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetRPCEndpoints(ctx context.Context, req *GetRPCEndpointsRequest) (*GetRPCEndpointsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetRPCEndpoints not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetCommunicationRelayers(ctx context.Context, req *GetCommunicationRelayersRequest) (*GetCommunicationRelayersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetCommunicationRelayers not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetExchanges(ctx context.Context, req *GetExchangesRequest) (*GetExchangesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetExchanges not implemented") -} -func (*UnimplementedGoCryptoTraderServer) DisableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DisableExchange not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetExchangeInfo(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetExchangeInfo not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCode(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeOTPReponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetExchangeOTPCode not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCodes(ctx context.Context, req *GetExchangeOTPsRequest) (*GetExchangeOTPsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetExchangeOTPCodes not implemented") -} -func (*UnimplementedGoCryptoTraderServer) EnableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method EnableExchange not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetTicker(ctx context.Context, req *GetTickerRequest) (*TickerResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetTicker not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetTickers(ctx context.Context, req *GetTickersRequest) (*GetTickersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetTickers not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetOrderbook(ctx context.Context, req *GetOrderbookRequest) (*OrderbookResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOrderbook not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetOrderbooks(ctx context.Context, req *GetOrderbooksRequest) (*GetOrderbooksResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOrderbooks not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetAccountInfo(ctx context.Context, req *GetAccountInfoRequest) (*GetAccountInfoResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetAccountInfo not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetConfig(ctx context.Context, req *GetConfigRequest) (*GetConfigResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetPortfolio(ctx context.Context, req *GetPortfolioRequest) (*GetPortfolioResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetPortfolio not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetPortfolioSummary(ctx context.Context, req *GetPortfolioSummaryRequest) (*GetPortfolioSummaryResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetPortfolioSummary not implemented") -} -func (*UnimplementedGoCryptoTraderServer) AddPortfolioAddress(ctx context.Context, req *AddPortfolioAddressRequest) (*AddPortfolioAddressResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddPortfolioAddress not implemented") -} -func (*UnimplementedGoCryptoTraderServer) RemovePortfolioAddress(ctx context.Context, req *RemovePortfolioAddressRequest) (*RemovePortfolioAddressResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method RemovePortfolioAddress not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetForexProviders(ctx context.Context, req *GetForexProvidersRequest) (*GetForexProvidersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetForexProviders not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetForexRates(ctx context.Context, req *GetForexRatesRequest) (*GetForexRatesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetForexRates not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetOrders(ctx context.Context, req *GetOrdersRequest) (*GetOrdersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOrders not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetOrder(ctx context.Context, req *GetOrderRequest) (*OrderDetails, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOrder not implemented") -} -func (*UnimplementedGoCryptoTraderServer) SubmitOrder(ctx context.Context, req *SubmitOrderRequest) (*SubmitOrderResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SubmitOrder not implemented") -} -func (*UnimplementedGoCryptoTraderServer) SimulateOrder(ctx context.Context, req *SimulateOrderRequest) (*SimulateOrderResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SimulateOrder not implemented") -} -func (*UnimplementedGoCryptoTraderServer) WhaleBomb(ctx context.Context, req *WhaleBombRequest) (*SimulateOrderResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method WhaleBomb not implemented") -} -func (*UnimplementedGoCryptoTraderServer) CancelOrder(ctx context.Context, req *CancelOrderRequest) (*CancelOrderResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CancelOrder not implemented") -} -func (*UnimplementedGoCryptoTraderServer) CancelAllOrders(ctx context.Context, req *CancelAllOrdersRequest) (*CancelAllOrdersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CancelAllOrders not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetEvents(ctx context.Context, req *GetEventsRequest) (*GetEventsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented") -} -func (*UnimplementedGoCryptoTraderServer) AddEvent(ctx context.Context, req *AddEventRequest) (*AddEventResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddEvent not implemented") -} -func (*UnimplementedGoCryptoTraderServer) RemoveEvent(ctx context.Context, req *RemoveEventRequest) (*RemoveEventResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method RemoveEvent not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddresses(ctx context.Context, req *GetCryptocurrencyDepositAddressesRequest) (*GetCryptocurrencyDepositAddressesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddresses not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddress(ctx context.Context, req *GetCryptocurrencyDepositAddressRequest) (*GetCryptocurrencyDepositAddressResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddress not implemented") -} -func (*UnimplementedGoCryptoTraderServer) WithdrawCryptocurrencyFunds(ctx context.Context, req *WithdrawCurrencyRequest) (*WithdrawResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method WithdrawCryptocurrencyFunds not implemented") -} -func (*UnimplementedGoCryptoTraderServer) WithdrawFiatFunds(ctx context.Context, req *WithdrawCurrencyRequest) (*WithdrawResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method WithdrawFiatFunds not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetLoggerDetails(ctx context.Context, req *GetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetLoggerDetails not implemented") -} -func (*UnimplementedGoCryptoTraderServer) SetLoggerDetails(ctx context.Context, req *SetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetLoggerDetails not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetExchangePairs(ctx context.Context, req *GetExchangePairsRequest) (*GetExchangePairsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetExchangePairs not implemented") -} -func (*UnimplementedGoCryptoTraderServer) EnableExchangePair(ctx context.Context, req *ExchangePairRequest) (*GenericExchangeNameResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method EnableExchangePair not implemented") -} -func (*UnimplementedGoCryptoTraderServer) DisableExchangePair(ctx context.Context, req *ExchangePairRequest) (*GenericExchangeNameResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DisableExchangePair not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetOrderbookStream(req *GetOrderbookStreamRequest, srv GoCryptoTrader_GetOrderbookStreamServer) error { - return status.Errorf(codes.Unimplemented, "method GetOrderbookStream not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetExchangeOrderbookStream(req *GetExchangeOrderbookStreamRequest, srv GoCryptoTrader_GetExchangeOrderbookStreamServer) error { - return status.Errorf(codes.Unimplemented, "method GetExchangeOrderbookStream not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetTickerStream(req *GetTickerStreamRequest, srv GoCryptoTrader_GetTickerStreamServer) error { - return status.Errorf(codes.Unimplemented, "method GetTickerStream not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetExchangeTickerStream(req *GetExchangeTickerStreamRequest, srv GoCryptoTrader_GetExchangeTickerStreamServer) error { - return status.Errorf(codes.Unimplemented, "method GetExchangeTickerStream not implemented") + GetAuditEvent(context.Context, *GetAuditEventRequest) (*GetAuditEventResponse, error) } func RegisterGoCryptoTraderServer(s *grpc.Server, srv GoCryptoTraderServer) { @@ -6959,6 +7008,24 @@ func (x *goCryptoTraderGetExchangeTickerStreamServer) Send(m *TickerResponse) er return x.ServerStream.SendMsg(m) } +func _GoCryptoTrader_GetAuditEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAuditEventRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetAuditEvent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetAuditEvent", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetAuditEvent(ctx, req.(*GetAuditEventRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ ServiceName: "gctrpc.GoCryptoTrader", HandlerType: (*GoCryptoTraderServer)(nil), @@ -7135,6 +7202,10 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ MethodName: "DisableExchangePair", Handler: _GoCryptoTrader_DisableExchangePair_Handler, }, + { + MethodName: "GetAuditEvent", + Handler: _GoCryptoTrader_GetAuditEvent_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 0332dec2..1aacb63d 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -13,7 +13,6 @@ import ( "io" "net/http" - "github.com/golang/protobuf/descriptor" "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/grpc-ecosystem/grpc-gateway/utilities" @@ -23,13 +22,11 @@ import ( "google.golang.org/grpc/status" ) -// Suppress "imported and not used" errors var _ codes.Code var _ io.Reader var _ status.Status var _ = runtime.String var _ = utilities.NewDoubleArray -var _ = descriptor.ForMessage func request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetInfoRequest @@ -40,15 +37,6 @@ func request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Mar } -func local_request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetInfoRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetInfo(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetSubsystems_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetSubsystemsRequest var metadata runtime.ServerMetadata @@ -58,15 +46,6 @@ func request_GoCryptoTrader_GetSubsystems_0(ctx context.Context, marshaler runti } -func local_request_GoCryptoTrader_GetSubsystems_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetSubsystemsRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetSubsystems(ctx, &protoReq) - return msg, metadata, err - -} - var ( filter_GoCryptoTrader_EnableSubsystem_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -87,19 +66,6 @@ func request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshaler run } -func local_request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GenericSubsystemRequest - var metadata runtime.ServerMetadata - - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_EnableSubsystem_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.EnableSubsystem(ctx, &protoReq) - return msg, metadata, err - -} - var ( filter_GoCryptoTrader_DisableSubsystem_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -120,19 +86,6 @@ func request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marshaler ru } -func local_request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GenericSubsystemRequest - var metadata runtime.ServerMetadata - - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_DisableSubsystem_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.DisableSubsystem(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetRPCEndpointsRequest var metadata runtime.ServerMetadata @@ -142,15 +95,6 @@ func request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler run } -func local_request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetRPCEndpointsRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetRPCEndpoints(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetCommunicationRelayers_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetCommunicationRelayersRequest var metadata runtime.ServerMetadata @@ -160,15 +104,6 @@ func request_GoCryptoTrader_GetCommunicationRelayers_0(ctx context.Context, mars } -func local_request_GoCryptoTrader_GetCommunicationRelayers_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetCommunicationRelayersRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetCommunicationRelayers(ctx, &protoReq) - return msg, metadata, err - -} - var ( filter_GoCryptoTrader_GetExchanges_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -189,19 +124,6 @@ func request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtim } -func local_request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetExchangesRequest - var metadata runtime.ServerMetadata - - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetExchanges(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_DisableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata @@ -219,23 +141,6 @@ func request_GoCryptoTrader_DisableExchange_0(ctx context.Context, marshaler run } -func local_request_GoCryptoTrader_DisableExchange_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GenericExchangeNameRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.DisableExchange(ctx, &protoReq) - return msg, metadata, err - -} - var ( filter_GoCryptoTrader_GetExchangeInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -256,19 +161,6 @@ func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler run } -func local_request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GenericExchangeNameRequest - var metadata runtime.ServerMetadata - - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetExchangeInfo(ctx, &protoReq) - return msg, metadata, err - -} - var ( filter_GoCryptoTrader_GetExchangeOTPCode_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -289,19 +181,6 @@ func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler } -func local_request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GenericExchangeNameRequest - var metadata runtime.ServerMetadata - - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetExchangeOTPCode(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetExchangeOTPsRequest var metadata runtime.ServerMetadata @@ -311,15 +190,6 @@ func request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler } -func local_request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetExchangeOTPsRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetExchangeOTPCodes(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata @@ -337,23 +207,6 @@ func request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runt } -func local_request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GenericExchangeNameRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.EnableExchange(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetTicker_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetTickerRequest var metadata runtime.ServerMetadata @@ -371,23 +224,6 @@ func request_GoCryptoTrader_GetTicker_0(ctx context.Context, marshaler runtime.M } -func local_request_GoCryptoTrader_GetTicker_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetTickerRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetTicker(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetTickers_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetTickersRequest var metadata runtime.ServerMetadata @@ -397,15 +233,6 @@ func request_GoCryptoTrader_GetTickers_0(ctx context.Context, marshaler runtime. } -func local_request_GoCryptoTrader_GetTickers_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetTickersRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetTickers(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetOrderbook_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrderbookRequest var metadata runtime.ServerMetadata @@ -423,23 +250,6 @@ func request_GoCryptoTrader_GetOrderbook_0(ctx context.Context, marshaler runtim } -func local_request_GoCryptoTrader_GetOrderbook_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetOrderbookRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetOrderbook(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetOrderbooks_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrderbooksRequest var metadata runtime.ServerMetadata @@ -449,15 +259,6 @@ func request_GoCryptoTrader_GetOrderbooks_0(ctx context.Context, marshaler runti } -func local_request_GoCryptoTrader_GetOrderbooks_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetOrderbooksRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetOrderbooks(ctx, &protoReq) - return msg, metadata, err - -} - var ( filter_GoCryptoTrader_GetAccountInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -478,19 +279,6 @@ func request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runt } -func local_request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetAccountInfoRequest - var metadata runtime.ServerMetadata - - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetAccountInfo(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetConfigRequest var metadata runtime.ServerMetadata @@ -500,15 +288,6 @@ func request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.M } -func local_request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetConfigRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetConfig(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetPortfolio_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetPortfolioRequest var metadata runtime.ServerMetadata @@ -518,15 +297,6 @@ func request_GoCryptoTrader_GetPortfolio_0(ctx context.Context, marshaler runtim } -func local_request_GoCryptoTrader_GetPortfolio_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetPortfolioRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetPortfolio(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetPortfolioSummary_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetPortfolioSummaryRequest var metadata runtime.ServerMetadata @@ -536,15 +306,6 @@ func request_GoCryptoTrader_GetPortfolioSummary_0(ctx context.Context, marshaler } -func local_request_GoCryptoTrader_GetPortfolioSummary_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetPortfolioSummaryRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetPortfolioSummary(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_AddPortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq AddPortfolioAddressRequest var metadata runtime.ServerMetadata @@ -562,23 +323,6 @@ func request_GoCryptoTrader_AddPortfolioAddress_0(ctx context.Context, marshaler } -func local_request_GoCryptoTrader_AddPortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq AddPortfolioAddressRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.AddPortfolioAddress(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_RemovePortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq RemovePortfolioAddressRequest var metadata runtime.ServerMetadata @@ -596,23 +340,6 @@ func request_GoCryptoTrader_RemovePortfolioAddress_0(ctx context.Context, marsha } -func local_request_GoCryptoTrader_RemovePortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq RemovePortfolioAddressRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.RemovePortfolioAddress(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetForexProviders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetForexProvidersRequest var metadata runtime.ServerMetadata @@ -622,15 +349,6 @@ func request_GoCryptoTrader_GetForexProviders_0(ctx context.Context, marshaler r } -func local_request_GoCryptoTrader_GetForexProviders_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetForexProvidersRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetForexProviders(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetForexRates_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetForexRatesRequest var metadata runtime.ServerMetadata @@ -640,15 +358,6 @@ func request_GoCryptoTrader_GetForexRates_0(ctx context.Context, marshaler runti } -func local_request_GoCryptoTrader_GetForexRates_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetForexRatesRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetForexRates(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetOrders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrdersRequest var metadata runtime.ServerMetadata @@ -666,23 +375,6 @@ func request_GoCryptoTrader_GetOrders_0(ctx context.Context, marshaler runtime.M } -func local_request_GoCryptoTrader_GetOrders_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetOrdersRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetOrders(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrderRequest var metadata runtime.ServerMetadata @@ -700,23 +392,6 @@ func request_GoCryptoTrader_GetOrder_0(ctx context.Context, marshaler runtime.Ma } -func local_request_GoCryptoTrader_GetOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetOrderRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetOrder(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq SubmitOrderRequest var metadata runtime.ServerMetadata @@ -734,23 +409,6 @@ func request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime } -func local_request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq SubmitOrderRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.SubmitOrder(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_SimulateOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq SimulateOrderRequest var metadata runtime.ServerMetadata @@ -768,23 +426,6 @@ func request_GoCryptoTrader_SimulateOrder_0(ctx context.Context, marshaler runti } -func local_request_GoCryptoTrader_SimulateOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq SimulateOrderRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.SimulateOrder(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_WhaleBomb_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq WhaleBombRequest var metadata runtime.ServerMetadata @@ -802,23 +443,6 @@ func request_GoCryptoTrader_WhaleBomb_0(ctx context.Context, marshaler runtime.M } -func local_request_GoCryptoTrader_WhaleBomb_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq WhaleBombRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.WhaleBomb(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq CancelOrderRequest var metadata runtime.ServerMetadata @@ -836,23 +460,6 @@ func request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime } -func local_request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq CancelOrderRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.CancelOrder(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_CancelAllOrders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq CancelAllOrdersRequest var metadata runtime.ServerMetadata @@ -870,23 +477,6 @@ func request_GoCryptoTrader_CancelAllOrders_0(ctx context.Context, marshaler run } -func local_request_GoCryptoTrader_CancelAllOrders_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq CancelAllOrdersRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.CancelAllOrders(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetEvents_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetEventsRequest var metadata runtime.ServerMetadata @@ -896,15 +486,6 @@ func request_GoCryptoTrader_GetEvents_0(ctx context.Context, marshaler runtime.M } -func local_request_GoCryptoTrader_GetEvents_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetEventsRequest - var metadata runtime.ServerMetadata - - msg, err := server.GetEvents(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_AddEvent_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq AddEventRequest var metadata runtime.ServerMetadata @@ -922,23 +503,6 @@ func request_GoCryptoTrader_AddEvent_0(ctx context.Context, marshaler runtime.Ma } -func local_request_GoCryptoTrader_AddEvent_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq AddEventRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.AddEvent(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_RemoveEvent_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq RemoveEventRequest var metadata runtime.ServerMetadata @@ -956,23 +520,6 @@ func request_GoCryptoTrader_RemoveEvent_0(ctx context.Context, marshaler runtime } -func local_request_GoCryptoTrader_RemoveEvent_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq RemoveEventRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.RemoveEvent(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetCryptocurrencyDepositAddressesRequest var metadata runtime.ServerMetadata @@ -990,23 +537,6 @@ func request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx context.Cont } -func local_request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetCryptocurrencyDepositAddressesRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetCryptocurrencyDepositAddresses(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetCryptocurrencyDepositAddressRequest var metadata runtime.ServerMetadata @@ -1024,23 +554,6 @@ func request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx context.Contex } -func local_request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetCryptocurrencyDepositAddressRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetCryptocurrencyDepositAddress(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq WithdrawCurrencyRequest var metadata runtime.ServerMetadata @@ -1058,23 +571,6 @@ func request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx context.Context, m } -func local_request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq WithdrawCurrencyRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.WithdrawCryptocurrencyFunds(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq WithdrawCurrencyRequest var metadata runtime.ServerMetadata @@ -1092,23 +588,6 @@ func request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler r } -func local_request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq WithdrawCurrencyRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.WithdrawFiatFunds(ctx, &protoReq) - return msg, metadata, err - -} - var ( filter_GoCryptoTrader_GetLoggerDetails_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -1129,19 +608,6 @@ func request_GoCryptoTrader_GetLoggerDetails_0(ctx context.Context, marshaler ru } -func local_request_GoCryptoTrader_GetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetLoggerDetailsRequest - var metadata runtime.ServerMetadata - - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetLoggerDetails_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetLoggerDetails(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq SetLoggerDetailsRequest var metadata runtime.ServerMetadata @@ -1159,23 +625,6 @@ func request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler ru } -func local_request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq SetLoggerDetailsRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.SetLoggerDetails(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetExchangePairsRequest var metadata runtime.ServerMetadata @@ -1193,23 +642,6 @@ func request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marshaler ru } -func local_request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetExchangePairsRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetExchangePairs(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ExchangePairRequest var metadata runtime.ServerMetadata @@ -1227,23 +659,6 @@ func request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler } -func local_request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ExchangePairRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.EnableExchangePair(ctx, &protoReq) - return msg, metadata, err - -} - func request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ExchangePairRequest var metadata runtime.ServerMetadata @@ -1261,23 +676,6 @@ func request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler } -func local_request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ExchangePairRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.DisableExchangePair(ctx, &protoReq) - return msg, metadata, err - -} - var ( filter_GoCryptoTrader_GetOrderbookStream_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -1390,900 +788,24 @@ func request_GoCryptoTrader_GetExchangeTickerStream_0(ctx context.Context, marsh } -// RegisterGoCryptoTraderHandlerServer registers the http handlers for service GoCryptoTrader to "mux". -// UnaryRPC :call GoCryptoTraderServer directly. -// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. -func RegisterGoCryptoTraderHandlerServer(ctx context.Context, mux *runtime.ServeMux, server GoCryptoTraderServer) error { +var ( + filter_GoCryptoTrader_GetAuditEvent_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) - mux.Handle("GET", pattern_GoCryptoTrader_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetInfo_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } +func request_GoCryptoTrader_GetAuditEvent_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetAuditEventRequest + var metadata runtime.ServerMetadata - forward_GoCryptoTrader_GetInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetAuditEvent_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } - }) + msg, err := client.GetAuditEvent(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err - mux.Handle("GET", pattern_GoCryptoTrader_GetSubsystems_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetSubsystems_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetSubsystems_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_EnableSubsystem_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_EnableSubsystem_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_EnableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_DisableSubsystem_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_DisableSubsystem_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_DisableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetRPCEndpoints_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetRPCEndpoints_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetRPCEndpoints_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetCommunicationRelayers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetCommunicationRelayers_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetCommunicationRelayers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetExchanges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetExchanges_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetExchanges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_DisableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_DisableExchange_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_DisableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetExchangeInfo_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetExchangeInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOTPCode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetExchangeOTPCode_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetExchangeOTPCode_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOTPCodes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetExchangeOTPCodes_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetExchangeOTPCodes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_EnableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_EnableExchange_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_EnableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_GetTicker_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetTicker_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetTicker_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetTickers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetTickers_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetTickers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_GetOrderbook_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetOrderbook_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetOrderbook_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetOrderbooks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetOrderbooks_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetOrderbooks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetAccountInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetAccountInfo_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetAccountInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetConfig_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetConfig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetPortfolio_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetPortfolio_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetPortfolio_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetPortfolioSummary_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetPortfolioSummary_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetPortfolioSummary_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_AddPortfolioAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_AddPortfolioAddress_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_AddPortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_RemovePortfolioAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_RemovePortfolioAddress_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_RemovePortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetForexProviders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetForexProviders_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetForexProviders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetForexRates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetForexRates_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetForexRates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_GetOrders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetOrders_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_GetOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetOrder_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_SubmitOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_SubmitOrder_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_SubmitOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_SimulateOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_SimulateOrder_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_SimulateOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_WhaleBomb_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_WhaleBomb_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_WhaleBomb_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_CancelOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_CancelOrder_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_CancelOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_CancelAllOrders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_CancelAllOrders_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_CancelAllOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetEvents_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetEvents_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_AddEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_AddEvent_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_AddEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_RemoveEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_RemoveEvent_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_RemoveEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_WithdrawFiatFunds_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_WithdrawFiatFunds_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_WithdrawFiatFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetLoggerDetails_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetLoggerDetails_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_SetLoggerDetails_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_SetLoggerDetails_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_SetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_GetExchangePairs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_GetExchangePairs_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_GetExchangePairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_EnableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_EnableExchangePair_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_EnableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_DisableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_DisableExchangePair_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_DisableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetOrderbookStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") - _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOrderbookStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") - _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetTickerStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") - _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - }) - - mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeTickerStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") - _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - }) - - return nil } // RegisterGoCryptoTraderHandlerFromEndpoint is same as RegisterGoCryptoTraderHandler but @@ -3264,6 +1786,26 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_GetAuditEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetAuditEvent_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetAuditEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -3361,6 +1903,8 @@ var ( pattern_GoCryptoTrader_GetTickerStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "gettickerstream"}, "", runtime.AssumeColonVerbOpt(true))) pattern_GoCryptoTrader_GetExchangeTickerStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangetickerstream"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_GetAuditEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getauditevent"}, "", runtime.AssumeColonVerbOpt(true))) ) var ( @@ -3457,4 +2001,6 @@ var ( forward_GoCryptoTrader_GetTickerStream_0 = runtime.ForwardResponseStream forward_GoCryptoTrader_GetExchangeTickerStream_0 = runtime.ForwardResponseStream + + forward_GoCryptoTrader_GetAuditEvent_0 = runtime.ForwardResponseMessage ) diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 3f00479a..7e42319f 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -509,6 +509,25 @@ message GetExchangeTickerStreamRequest { string exchange = 1; } +message GetAuditEventRequest { + string start_date = 1; + string end_date = 2; + string order_by = 3; + int32 limit = 4; + int32 offset = 5; +} + +message GetAuditEventResponse { + repeated audit_event events = 1; +} + +message audit_event { + string type = 1 ; + string identifier = 2; + string message = 3; + string timestamp = 4; +} + service GoCryptoTrader { rpc GetInfo (GetInfoRequest) returns (GetInfoResponse) { option (google.api.http) = { @@ -815,4 +834,10 @@ service GoCryptoTrader { get: "/v1/getexchangetickerstream" }; } + + rpc GetAuditEvent(GetAuditEventRequest) returns (GetAuditEventResponse) { + option (google.api.http) = { + get: "/v1/getauditevent" + }; + } } diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index 39eede80..c6687dea 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -295,6 +295,56 @@ ] } }, + "/v1/getauditevent": { + "get": { + "operationId": "GetAuditEvent", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetAuditEventResponse" + } + } + }, + "parameters": [ + { + "name": "start_date", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "end_date", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "order_by", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getcommunicationrelayers": { "get": { "operationId": "GetCommunicationRelayers", @@ -1442,6 +1492,17 @@ } } }, + "gctrpcGetAuditEventResponse": { + "type": "object", + "properties": { + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcaudit_event" + } + } + } + }, "gctrpcGetCommunicationRelayersResponse": { "type": "object", "properties": { @@ -2320,6 +2381,23 @@ } } }, + "gctrpcaudit_event": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "message": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, "protobufAny": { "type": "object", "properties": { diff --git a/go.mod b/go.mod index 0c8db7f2..a412732a 100644 --- a/go.mod +++ b/go.mod @@ -3,27 +3,26 @@ module github.com/thrasher-corp/gocryptotrader go 1.12 require ( - github.com/cockroachdb/apd v1.1.0 // indirect github.com/gofrs/uuid v3.2.0+incompatible - github.com/gogo/protobuf v1.2.1 // indirect github.com/golang/protobuf v1.3.2 github.com/google/go-querystring v1.0.0 github.com/gorilla/mux v1.7.3 github.com/gorilla/websocket v1.4.0 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/grpc-ecosystem/grpc-gateway v1.11.3 - github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect - github.com/jackc/pgx v3.5.0+incompatible - github.com/jmoiron/sqlx v1.2.0 + github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12 + github.com/lib/pq v1.2.0 github.com/mattn/go-sqlite3 v1.11.0 - github.com/pkg/errors v0.8.1 // indirect + github.com/pkg/errors v0.8.1 github.com/pquerna/otp v1.2.0 - github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect - github.com/shopspring/decimal v0.0.0-20190905144223-a36b5d85f337 // indirect + github.com/spf13/viper v1.4.0 + github.com/thrasher-corp/goose v2.7.0-rc4.0.20191002032028-0f2c2a27abdb+incompatible + github.com/thrasher-corp/sqlboiler v1.0.1-0.20191001234224-71e17f37a85e github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb github.com/urfave/cli v1.20.0 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 - golang.org/x/net v0.0.0-20190606173856-1492cefac77f + golang.org/x/net v0.0.0-20190606173856-1492cefac77f // indirect + golang.org/x/sys v0.0.0-20191003212358-c178f38b412c // indirect google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143 google.golang.org/grpc v1.21.1 gopkg.in/yaml.v2 v2.2.4 // indirect diff --git a/go.sum b/go.sum index 6021c43a..9d1076b0 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,63 @@ cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apmckinlay/gsuneido v0.0.0-20180907175622-1f10244968e3/go.mod h1:hJnaqxrCRgMCTWtpNz9XUFkBCREiQdlcyK6YNmOfroM= +github.com/apmckinlay/gsuneido v0.0.0-20190404155041-0b6cd442a18f/go.mod h1:JU2DOj5Fc6rol0yaT79Csr47QR0vONGwJtBNGRD7jmc= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/ericlagergren/decimal v0.0.0-20180907214518-0bb163153a5d/go.mod h1:1yj25TwtUlJ+pfOu9apAVaM1RWfZGg+aFpd4hPQZekQ= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -33,18 +67,22 @@ github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/grpc-gateway v1.9.2 h1:S+ef0492XaIknb8LMjcwgW2i3cNTzDYMmDrOThOJNWc= -github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.11.3 h1:h8+NsYENhxNTuq+dobk3+ODoJtwY4Fu0WQXsxJfL8aM= github.com/grpc-ecosystem/grpc-gateway v1.11.3/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgx v3.5.0+incompatible h1:BRJ4G3UPtvml5R1ey0biqqGuYUGayMYekm3woO75orY= -github.com/jackc/pgx v3.5.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= -github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12 h1:DQVOxR9qdYEybJUr/c7ku34r3PfajaMYXZwgDM7KuSk= +github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12/go.mod h1:u9MdXq/QageOOSGp7qG4XAQsYUMP+V5zEel/Vrl6OOc= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -52,28 +90,92 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shopspring/decimal v0.0.0-20190905144223-a36b5d85f337 h1:Da9XEUfFxgyDOqUfwgoTDcWzmnlOnCGi6i4iPS+8Fbw= -github.com/shopspring/decimal v0.0.0-20190905144223-a36b5d85f337/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/thrasher-corp/goose v2.7.0-rc4.0.20191002032028-0f2c2a27abdb+incompatible h1:SPqQlzFu3g4P9wK2iwJaWVLJWcQ5rYc43rvXBJ8RSCY= +github.com/thrasher-corp/goose v2.7.0-rc4.0.20191002032028-0f2c2a27abdb+incompatible/go.mod h1:2Bb/y0SpnUWOlPU5kDz+ctvb3w/mzuAVqxy7JPfBzgw= +github.com/thrasher-corp/sqlboiler v1.0.1-0.20191001234224-71e17f37a85e h1:4kYBo2YhqqFY7aZPPEhrtPTMoAq4iCsoDITd3jseRbY= +github.com/thrasher-corp/sqlboiler v1.0.1-0.20191001234224-71e17f37a85e/go.mod h1:JfJE+3gijF30ZJbUCzxGkU0+ymQxBfBOVp4XDObmJBE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb h1:9kcmLvQdiIecpgVEL3/+J5QIP/ElRBJDljOay0SvqnA= github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb/go.mod h1:VTLqNCX1tXrur6pdIRCl8Q90FR7nw/mEBdyMkWMcsb0= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/volatiletech/inflect v0.0.0-20170731032912-e7201282ae8d h1:gI4/tqP6lCY5k6Sg+4k9qSoBXmPwG+xXgMpK7jivD4M= +github.com/volatiletech/inflect v0.0.0-20170731032912-e7201282ae8d/go.mod h1:jspfvgf53t5NLUT4o9L1IX0kIBNKamGq1tWc/MgWK9Q= +github.com/volatiletech/null v8.0.0+incompatible h1:7wP8m5d/gZ6kW/9GnrLtMCRre2dlEnaQ9Km5OXlK4zg= +github.com/volatiletech/null v8.0.0+incompatible/go.mod h1:0wD98JzdqB+rLyZ70fN05VDbXbafIb0KU0MdVhCzmOQ= +github.com/volatiletech/sqlboiler v3.5.0+incompatible h1:n160O7UQLpZVRnJY6VH5eRNkt7sQdQBZGCCZ3CUy1+g= +github.com/volatiletech/sqlboiler v3.5.0+incompatible/go.mod h1:jLfDkkHWPbS2cWRLkyC20vQWaIQsASEY7gM7zSo11Yw= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -82,24 +184,38 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190606173856-1492cefac77f h1:IWHgpgFqnL5AhBUBZSgBdjl2vkQUEzcY+JNKWfcgAU0= golang.org/x/net v0.0.0-20190606173856-1492cefac77f/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190927073244-c990c680b611 h1:q9u40nxWT5zRClI/uU9dHCiYGottAg6Nzz4YUQyHxdA= +golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191003212358-c178f38b412c h1:6Zx7DRlKXf79yfxuQ/7GqV3w2y7aDsk6bGg0MzF5RVU= +golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -107,19 +223,22 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6 h1:XRqWpmQ5ACYxWuYX495S0sHawhPGOVrh62WzgXsQnWs= -google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143 h1:tikhlQEJeezbnu0Zcblj7g5vm/L7xt6g1vnfq8mRCS4= google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7 h1:+t9dhfO+GNOIGJof6kPOAenx7YgrZMTdRPV+EsnPabk= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index d677b637..99b1fd1d 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/core" - mg "github.com/thrasher-corp/gocryptotrader/database/migration" "github.com/thrasher-corp/gocryptotrader/dispatch" "github.com/thrasher-corp/gocryptotrader/engine" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -25,7 +24,6 @@ func main() { // Core settings flag.StringVar(&settings.ConfigFile, "config", "", "config file to load") flag.StringVar(&settings.DataDir, "datadir", common.GetDefaultDataDir(runtime.GOOS), "default data directory for GoCryptoTrader files") - flag.StringVar(&settings.MigrationDir, "migrationdir", mg.MigrationDir, "override migration folder") flag.IntVar(&settings.GoMaxProcs, "gomaxprocs", runtime.GOMAXPROCS(-1), "sets the runtime GOMAXPROCS value") flag.BoolVar(&settings.EnableDryRun, "dryrun", false, "dry runs bot, doesn't save config file") flag.BoolVar(&settings.EnableAllExchanges, "enableallexchanges", false, "enables all exchanges") diff --git a/sqlboiler_example.json b/sqlboiler_example.json new file mode 100644 index 00000000..e6415e62 --- /dev/null +++ b/sqlboiler_example.json @@ -0,0 +1,34 @@ +{ + "psql": { + "dbname": "", + "host": "", + "port": 5432, + "user": "", + "pass": "", + "schema": "public", + "sslmode": "disable", + "blacklist": [ + "goose_db_version" + ] + }, + "mysql": { + "dbname": "", + "host": "", + "port": 3306, + "user": "", + "pass": "", + "sslmode": "false" + }, + "mssql": { + "dbname": "", + "host": "", + "port": 1433, + "user": "", + "pass": "", + "sslmode": "disable", + "schema": "" + }, + "sqlite": { + "dbname": "/.gocryptotrader/database/gocryptotrader.db" + } +} \ No newline at end of file From ec0ed1c1e5d53da7683ad4ceb6fd228ed05a8794 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 21 Oct 2019 13:09:56 +1100 Subject: [PATCH 53/71] Basic documentation update for engine branch (#369) * Add basic docs for gRPC/gctcli/unified API and a few markdown fixes * Update patherinos and spacing fixes * Consistent namerinos * Fix spelling mistakes * Add fancy headers * Uperaterinos * Fix feedback nitterinos --- CONTRIBUTORS | 2 +- README.md | 87 ++++++++++--------- .../root_templates/root_readme.tmpl | 13 ++- .../sub_templates/contributors.tmpl | 6 +- cmd/gctcli/README.md | 38 ++++++++ docs/EXCHANGE_API.md | 72 +++++++++++++++ docs/FILES.md | 48 ++++++++++ docs/README.md | 31 +++++++ gctrpc/README.md | 63 ++++++++++++++ 9 files changed, 313 insertions(+), 47 deletions(-) create mode 100644 cmd/gctcli/README.md create mode 100644 docs/EXCHANGE_API.md create mode 100644 docs/FILES.md create mode 100644 docs/README.md create mode 100644 gctrpc/README.md diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ae80db0c..c8958923 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -9,9 +9,9 @@ vadimzhukck | https://github.com/vadimzhukck 140am | https://github.com/140am marcofranssen | https://github.com/marcofranssen cranktakular | https://github.com/cranktakular +MadCozBadd | https://github.com/MadCozBadd leilaes | https://github.com/leilaes crackcomm | https://github.com/crackcomm -MadCozBadd | https://github.com/MadCozBadd andreygrehov | https://github.com/andreygrehov bretep | https://github.com/bretep woshidama323 | https://github.com/woshidama323 diff --git a/README.md b/README.md index 8f04d8dc..d255409b 100644 --- a/README.md +++ b/README.md @@ -53,19 +53,26 @@ We are aiming to support the top 20 highest volume exchanges based off the [Coin ## Current Features -+ Support for all Exchange fiat and digital currencies, with the ability to individually toggle them on/off. ++ Support for all exchange fiat and digital currencies, with the ability to individually toggle them on/off. + AES256 encrypted config file. + REST API support for all exchanges. + Websocket support for applicable exchanges. + Ability to turn off/on certain exchanges. -+ Ability to adjust manual polling timer for exchanges. + Communication packages (Slack, SMS via SMSGlobal, Telegram and SMTP) + HTTP rate limiter package. ++ Unified API for exchange usage. ++ Customisation of HTTP client features including setting a proxy, user agent and adjusting transport settings. ++ NTP client package. ++ Database support (Postgres and SQLite3). See [database](/database/README.md). ++ OTP generation tool. See [gen otp](/cmd/gen_otp). ++ Connection monitor package. ++ gRPC service and JSON RPC proxy. See [gRPC service](/gctrpc/README.md). ++ gRPC client. See [gctcli](/cmd/gctcli/README.md). + Forex currency converter packages (CurrencyConverterAPI, CurrencyLayer, Fixer.io, OpenExchangeRates) + Packages for handling currency pairs, tickers and orderbooks. + Portfolio management tool; fetches balances from supported exchanges and allows for custom address tracking. + Basic event trigger system. -+ WebGUI. ++ WebGUI (discontinued). ## Planned Features @@ -128,40 +135,40 @@ Binaries will be published once the codebase reaches a stable condition. ### A very special thank you to all who have contributed to this program: -|User|Github|Contribution Amount| -|--|--|--| -| thrasher- | https://github.com/thrasher- | 543 | -| shazbert | https://github.com/shazbert | 174 | -| gloriousCode | https://github.com/gloriousCode | 154 | -| xtda | https://github.com/xtda | 18 | -| ermalguni | https://github.com/ermalguni | 14 | -| vadimzhukck | https://github.com/vadimzhukck | 10 | -| 140am | https://github.com/140am | 8 | -| marcofranssen | https://github.com/marcofranssen | 8 | -| cranktakular | https://github.com/cranktakular | 5 | -| leilaes | https://github.com/leilaes | 3 | -| crackcomm | https://github.com/crackcomm | 3 | -| MadCozBadd | https://github.com/MadCozBadd | 2 | -| andreygrehov | https://github.com/andreygrehov | 2 | -| bretep | https://github.com/bretep | 2 | -| woshidama323 | https://github.com/woshidama323 | 2 | -| gam-phon | https://github.com/gam-phon | 2 | -| cornelk | https://github.com/cornelk | 2 | -| if1live | https://github.com/if1live | 2 | -| soxipy | https://github.com/soxipy | 2 | -| herenow | https://github.com/herenow | 2 | -| blombard | https://github.com/blombard | 1 | -| CodeLingoBot | https://github.com/CodeLingoBot | 1 | -| CodeLingoTeam | https://github.com/CodeLingoTeam | 1 | -| Daanikus | https://github.com/Daanikus | 1 | -| daniel-cohen | https://github.com/daniel-cohen | 1 | -| DirectX | https://github.com/DirectX | 1 | -| frankzougc | https://github.com/frankzougc | 1 | -| starit | https://github.com/starit | 1 | -| Jimexist | https://github.com/Jimexist | 1 | -| lookfirst | https://github.com/lookfirst | 1 | -| mattkanwisher | https://github.com/mattkanwisher | 1 | -| mKurrels | https://github.com/mKurrels | 1 | -| m1kola | https://github.com/m1kola | 1 | -| cavapoo2 | https://github.com/cavapoo2 | 1 | -| zeldrinn | https://github.com/zeldrinn | 1 | +|User|Contribution Amount| +|--|--| +| [thrasher-](https://github.com/thrasher-) | 548 | +| [shazbert](https://github.com/shazbert) | 176 | +| [gloriousCode](https://github.com/gloriousCode) | 155 | +| [xtda](https://github.com/xtda) | 18 | +| [ermalguni](https://github.com/ermalguni) | 14 | +| [vadimzhukck](https://github.com/vadimzhukck) | 10 | +| [140am](https://github.com/140am) | 8 | +| [marcofranssen](https://github.com/marcofranssen) | 8 | +| [cranktakular](https://github.com/cranktakular) | 5 | +| [MadCozBadd](https://github.com/MadCozBadd) | 3 | +| [leilaes](https://github.com/leilaes) | 3 | +| [crackcomm](https://github.com/crackcomm) | 3 | +| [andreygrehov](https://github.com/andreygrehov) | 2 | +| [bretep](https://github.com/bretep) | 2 | +| [woshidama323](https://github.com/woshidama323) | 2 | +| [gam-phon](https://github.com/gam-phon) | 2 | +| [cornelk](https://github.com/cornelk) | 2 | +| [if1live](https://github.com/if1live) | 2 | +| [soxipy](https://github.com/soxipy) | 2 | +| [herenow](https://github.com/herenow) | 2 | +| [blombard](https://github.com/blombard) | 1 | +| [CodeLingoBot](https://github.com/CodeLingoBot) | 1 | +| [CodeLingoTeam](https://github.com/CodeLingoTeam) | 1 | +| [Daanikus](https://github.com/Daanikus) | 1 | +| [daniel-cohen](https://github.com/daniel-cohen) | 1 | +| [DirectX](https://github.com/DirectX) | 1 | +| [frankzougc](https://github.com/frankzougc) | 1 | +| [starit](https://github.com/starit) | 1 | +| [Jimexist](https://github.com/Jimexist) | 1 | +| [lookfirst](https://github.com/lookfirst) | 1 | +| [mattkanwisher](https://github.com/mattkanwisher) | 1 | +| [mKurrels](https://github.com/mKurrels) | 1 | +| [m1kola](https://github.com/m1kola) | 1 | +| [cavapoo2](https://github.com/cavapoo2) | 1 | +| [zeldrinn](https://github.com/zeldrinn )| 1 | diff --git a/cmd/documentation/root_templates/root_readme.tmpl b/cmd/documentation/root_templates/root_readme.tmpl index 92c1c039..9666a073 100644 --- a/cmd/documentation/root_templates/root_readme.tmpl +++ b/cmd/documentation/root_templates/root_readme.tmpl @@ -54,19 +54,26 @@ We are aiming to support the top 20 highest volume exchanges based off the [Coin ## Current Features -+ Support for all Exchange fiat and digital currencies, with the ability to individually toggle them on/off. ++ Support for all exchange fiat and digital currencies, with the ability to individually toggle them on/off. + AES256 encrypted config file. + REST API support for all exchanges. + Websocket support for applicable exchanges. + Ability to turn off/on certain exchanges. -+ Ability to adjust manual polling timer for exchanges. + Communication packages (Slack, SMS via SMSGlobal, Telegram and SMTP) + HTTP rate limiter package. ++ Unified API for exchange usage. ++ Customisation of HTTP client features including setting a proxy, user agent and adjusting transport settings. ++ NTP client package. ++ Database support (Postgres and SQLite3). See [database](/database/README.md). ++ OTP generation tool. See [gen otp](/cmd/gen_otp). ++ Connection monitor package. ++ gRPC service and JSON RPC proxy. See [gRPC service](/gctrpc/README.md). ++ gRPC client. See [gctcli](/cmd/gctcli/README.md). + Forex currency converter packages (CurrencyConverterAPI, CurrencyLayer, Fixer.io, OpenExchangeRates) + Packages for handling currency pairs, tickers and orderbooks. + Portfolio management tool; fetches balances from supported exchanges and allows for custom address tracking. + Basic event trigger system. -+ WebGUI. ++ WebGUI (discontinued). ## Planned Features diff --git a/cmd/documentation/sub_templates/contributors.tmpl b/cmd/documentation/sub_templates/contributors.tmpl index e08f5609..1051ea3b 100644 --- a/cmd/documentation/sub_templates/contributors.tmpl +++ b/cmd/documentation/sub_templates/contributors.tmpl @@ -3,9 +3,9 @@ ### A very special thank you to all who have contributed to this program: -|User|Github|Contribution Amount| -|--|--|--| +|User|Contribution Amount| +|--|--| {{- range $contributor := .Contributors -}} -| {{$contributor.Login}} | {{$contributor.URL}} | {{$contributor.Contributions}} | +| [{{$contributor.Login}}]({{$contributor.URL}}) | {{$contributor.Contributions}} | {{end}} {{end}} diff --git a/cmd/gctcli/README.md b/cmd/gctcli/README.md new file mode 100644 index 00000000..6a22e7ae --- /dev/null +++ b/cmd/gctcli/README.md @@ -0,0 +1,38 @@ +# GoCryptoTrader gRPC client + + + +[![Build Status](https://travis-ci.com/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.com/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + +A cryptocurrency trading bot supporting multiple exchanges written in Golang. + +**Please note that this bot is under development and is not ready for production!** + +## Community + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) + +## Background + +GoCryptoTrader utilises gRPC for client/server interaction. Authentication is done +by a self signed TLS cert, which only supports connections from localhost and also +through basic authorisation specified by the users config file. + +## Usage + +GoCryptoTrader must be running with gRPC enabled in order to use the client features. + +```bash +go build or go run . +``` + +For a full list of commands, you can run `gctcli --help`. Alternatively, you can also +visit our [GoCryptoTrader API reference.](https://api.gocryptotrader.app/) + +## Autocomplete + +Bash/ZSH autocomplete entries can be found [here](/contrib). diff --git a/docs/EXCHANGE_API.md b/docs/EXCHANGE_API.md new file mode 100644 index 00000000..1448fc5c --- /dev/null +++ b/docs/EXCHANGE_API.md @@ -0,0 +1,72 @@ +# GoCryptoTrader Unified API + + + +[![Build Status](https://travis-ci.com/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.com/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + +A cryptocurrency trading bot supporting multiple exchanges written in Golang. + +**Please note that this bot is under development and is not ready for production!** + +## Community + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) + +## Unified API + +GoCryptoTrader supports a unified API for dealing with exchanges. Each exchange +has its own wrapper file which maps the exchanges own RESTful endpoints into a +standardised way for bot and standalone application usage. + +A full breakdown of all the supported wrapper funcs can be found [here.](https://github.com/thrasher-corp/gocryptotrader/blob/engine/exchanges/interfaces.go#L16) +Please note that these change on a regular basis as GoCryptoTrader is undergoing +rapid development. + +Each exchange supports public API endpoints which don't require any authentication +(fetching ticker, orderbook, trade data) and also private API endpoints (which +require authentication). Some examples include submitting, cancelling and fetching +open orders). To use the authenticated API endpoints, you'll need to set your API +credentials in either the `config.json` file or when you initialise an exchange in +your application, and also have the appropriate key permissions set for the exchange. +Each exchange has a credentials validator which ensures that the API credentials +supplied meet the requirements to make an authenticated request. + +## Public API Ticker Example + +```go + var b bitstamp.Bitstamp + b.SetDefaults() + ticker, err := b.FetchTicker(currency.NewPair(currency.BTC, currency.USD), asset.Spot) + if err != nil { + // Handle error + } + fmt.Println(ticker.Last) +``` + +## Private API Submit Order Example + +```go + var b bitstamp.Bitstamp + b.SetDefaults() + + b.API.Credentials.Key = "your_key" + b.API.Credentials.Secret = "your_secret" + b.API.Credentials.ClientID = "your_clientid" + + order := &exchange.OrderSubmission{ + Pair: currency.NewPair(currency.BTC, currency.USD), + OrderSide: exchange.SellOrderSide, + OrderType: exchange.LimitOrderType, + Price: 1000000, + Amount: 0.1, + } + resp, err := b.SubmitOrder(order) + if err != nil { + // Handle error + } + fmt.Println(resp.OrderID) +``` diff --git a/docs/FILES.md b/docs/FILES.md new file mode 100644 index 00000000..f456f7f7 --- /dev/null +++ b/docs/FILES.md @@ -0,0 +1,48 @@ +# GoCryptoTrader File Hierarchy + + + +[![Build Status](https://travis-ci.com/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.com/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + +A cryptocurrency trading bot supporting multiple exchanges written in Golang. + +**Please note that this bot is under development and is not ready for production!** + +## Community + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) + +## Default data directory + +By default, GoCryptoTrader uses the following data directores: + +Operating System | Path | Translated +--- | --- | ---- +| Windows | %APPDATA%\GoCryptoTrader | C:\Users\User\AppData\Roaming\GoCryptoTrader +| Linux | ~/.gocryptotrader | /home/user/.gocryptotrader +| macOS | ~/.gocryptotrader | /Users/User/.gocryptotrader + +This can be overridden by running GoCryptoTrader with the `-datadir` command line +parameter. + +## Subdirectories + +Depending on the features enabled, you'll see the following directories created +inside the data directory: + +Directory | Reason +--- | --- +| database | Used to store the database file (if using SQLite3) and sqlboiler config files +| logs | Used to store the debug log file (`log.txt` by default), if file output and logging is enabled +| tls | Used to store the generated self-signed certificate and key for gRPC authentication + +## Files + +File | Reason +--- | --- +config.json or config.dat (encrypted config) | Config file which GoCryptoTrader loads from (can be overridden by the `-config` command line parameter). +currency.json | Cached list of fiat and digital currencies diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..c45f6c7e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,31 @@ +# GoCryptoTrader Documentation + + + +[![Build Status](https://travis-ci.com/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.com/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + +A cryptocurrency trading bot supporting multiple exchanges written in Golang. + +**Please note that this bot is under development and is not ready for production!** + +## Community + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) + +## Documentation + +See below for feature documentation: + ++ [Exchange unified API documentation](EXCHANGE_API.md) ++ [File hierarchy documentation](FILES.md) ++ [Config documentation](/config/README.md) ++ [gRPC service documentation](/gctrpc/README.md) ++ [gctcli documentation](/cmd/gctcli/README.md) ++ [Database documentation](/database/README.md) ++ [Currency documentation](/currency/README.md) ++ [Exchange documentation](/exchanges/README.md) ++ [Portfolio documentation](/portfolio/README.md) diff --git a/gctrpc/README.md b/gctrpc/README.md new file mode 100644 index 00000000..b7f0ee9d --- /dev/null +++ b/gctrpc/README.md @@ -0,0 +1,63 @@ +# GoCryptoTrader gRPC Service + + + +[![Build Status](https://travis-ci.com/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.com/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + +A cryptocurrency trading bot supporting multiple exchanges written in Golang. + +**Please note that this bot is under development and is not ready for production!** + +## Community + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) + +## Background + +GoCryptoTrader utilises gRPC for client/server interaction. Authentication is done +by a self signed TLS cert, which only supports connections from localhost and also +through basic authorisation specified by the users config file. + +GoCryptoTrader also supports a gRPC JSON proxy service for applications which can +be toggled on or off depending on the users preference. + +## Installation + +GoCryptoTrader requires a local installation of the Google protocol buffers +compiler `protoc` v3.0.0 or above. Please install this via your local package +manager or by downloading one of the releases from the official repository: + +[protoc releases](https://github.com/protocolbuffers/protobuf/releases) + +Then use `go get -u` to download the following packages: + +```bash +go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway +go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger +go get -u github.com/golang/protobuf/protoc-gen-go +``` + +This will place three binaries in your `$GOBIN`; + +* `protoc-gen-grpc-gateway` +* `protoc-gen-swagger` +* `protoc-gen-go` + +Make sure that your `$GOBIN` is in your `$PATH`. + +## Usage + +After the above dependencies are required, make necessary changes to the `rpc.proto` +spec file and run the generation scripts: + +### Windows + +Run `gen_pb_win.bat` + +### Linux and macOS + +Run `./gen_pb_linux.sh` From ccfcdf26aa2b2f358d1d459e17b6d9b03de2099d Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 22 Oct 2019 10:56:20 +1100 Subject: [PATCH 54/71] Engine: Protocol Features, coverage, types, BTC markets websocket (#368) * Attempts to update orderbook so it doesn't need to sort * Reverts the ws ob stuff. Gets rid of sorting because it happens later. Adds some exchange features * update existing feature lists. Expands list definition to match my emotions * Adds bithumb bitmex and bitstamp. adds a couple more types * Features for you, features for me, features for bittrex, btcmarkets, btse, coinbasepro, coinut, exmo, gateio and gemini * Features for hitbtc, huobi, itbit, kraken, lakebtc, lbank, localbitcoins, okcoin, okex, poloniex, yobit, zb * Who can forget good old alphapoint? * Adds btcmarksets websocket :glitch_crab: fixes alphapoint features * Adds extra data not in the documentation :/ * Replaces websocket features by using protocol features. However, it breaks it due to import cycles. I'm not sure what I'll do just yet * Removes import cycle via duplicate structs. * Increases coverage of config with `TestCheckCurrencyConfigValues`. Moves all currency pair package types into their own files or places it at the bottom of files if necessary * Increase coverage in code.go * One way of determining a test has failed, is when to it fails. Removed redundant explanation * Increases code coverage of conversion * Lint fixes * Fixes orderbook tests * Re-adds sorting because its important to still have the internal pre-processed orderbook to be representative of a real orderbook * Secret lints that did not show up via Windows linting * Adds protocol package to contain exchange features * Fixes protocol implementation * Fixes ws tests * Addresses the following: Removes st-st-stutters in config types, changes GetAvailableForexProviders -> GetSupportedForexProviders, removes errors from tests where error is nil, removes orderbook setup when not necessary, removes import newlines, removes false bools from declaration, changes should of to should have * imports and casing * Fixes two more nil error checks --- cmd/config/config_test.go | 4 +- cmd/exchange_template/test_file.tmpl | 2 +- cmd/websocket_client/main.go | 2 +- common/common_test.go | 94 +- common/convert/convert_test.go | 32 +- common/crypto/crypto_test.go | 32 +- common/math/math_test.go | 12 +- communications/base/base_test.go | 6 +- communications/communications_test.go | 2 +- communications/slack/slack_test.go | 60 +- communications/smsglobal/smsglobal_test.go | 28 +- .../smtpservice/smtpservice_test.go | 8 +- communications/telegram/telegram_test.go | 26 +- config/config.go | 145 +- config/config_encryption.go | 2 +- config/config_encryption_test.go | 28 +- config/config_test.go | 373 ++-- config/config_types.go | 97 +- connchecker/connchecker_test.go | 8 +- currency/code.go | 1655 +---------------- currency/code_test.go | 202 +- currency/code_types.go | 1655 +++++++++++++++++ currency/coinmarketcap/coinmarketcap.go | 47 - currency/coinmarketcap/coinmarketcap_test.go | 98 +- currency/coinmarketcap/coinmarketcap_types.go | 53 +- currency/conversion.go | 7 +- currency/conversion_test.go | 111 +- currency/currencies.go | 5 - currency/currencies_test.go | 12 +- currency/currency_test.go | 26 +- currency/forexprovider/base/base.go | 30 +- .../currencyconverterapi.go | 23 - .../currencyconverterapi_test.go | 2 +- .../currencyconverterapi_types.go | 28 + .../currencylayer/currencylayer.go | 28 - .../currencylayer/currencylayer_test.go | 14 +- .../currencylayer/currencylayer_types.go | 33 + .../exchangeratesapi.io/exchangeratesapi.go | 18 - .../exchangeratesapi_types.go | 23 + currency/forexprovider/fixer.io/fixer.go | 26 - currency/forexprovider/fixer.io/fixer_test.go | 14 +- .../forexprovider/fixer.io/fixer_types.go | 31 + currency/forexprovider/forexprovider.go | 14 +- .../openexchangerates/openexchangerates.go | 39 - .../openexchangerates_test.go | 16 +- .../openexchangerates_types.go | 44 + currency/manager_test.go | 14 +- currency/pair.go | 14 +- currency/pair_test.go | 130 +- currency/pairs.go | 6 +- currency/pairs_test.go | 38 +- currency/storage.go | 66 - currency/storage_test.go | 6 +- currency/storage_types.go | 62 + currency/symbol.go | 18 +- currency/symbol_test.go | 6 +- currency/translation.go | 20 +- dispatch/dispatch_test.go | 12 +- engine/exchange_test.go | 32 +- engine/helpers_test.go | 6 +- engine/restful_server_test.go | 14 +- engine/syncer_test.go | 2 +- exchanges/alphapoint/alphapoint_test.go | 112 +- exchanges/alphapoint/alphapoint_wrapper.go | 21 +- exchanges/anx/anx_live_test.go | 6 +- exchanges/anx/anx_mock_test.go | 8 +- exchanges/anx/anx_test.go | 48 +- exchanges/anx/anx_wrapper.go | 22 +- exchanges/asset/asset_test.go | 28 +- exchanges/binance/binance_live_test.go | 6 +- exchanges/binance/binance_mock_test.go | 8 +- exchanges/binance/binance_test.go | 108 +- exchanges/binance/binance_wrapper.go | 35 +- exchanges/bitfinex/bitfinex_test.go | 96 +- exchanges/bitfinex/bitfinex_wrapper.go | 44 +- exchanges/bitflyer/bitflyer_test.go | 48 +- exchanges/bitflyer/bitflyer_wrapper.go | 11 +- exchanges/bithumb/bithumb_test.go | 68 +- exchanges/bithumb/bithumb_wrapper.go | 25 +- exchanges/bitmex/bitmex_test.go | 120 +- exchanges/bitmex/bitmex_wrapper.go | 42 +- exchanges/bitstamp/bitstamp_live_test.go | 6 +- exchanges/bitstamp/bitstamp_mock_test.go | 8 +- exchanges/bitstamp/bitstamp_test.go | 66 +- exchanges/bitstamp/bitstamp_wrapper.go | 35 +- exchanges/bittrex/bittrex_test.go | 76 +- exchanges/bittrex/bittrex_wrapper.go | 21 +- exchanges/btcmarkets/btcmarkets.go | 2 + exchanges/btcmarkets/btcmarkets_test.go | 62 +- exchanges/btcmarkets/btcmarkets_types.go | 57 +- exchanges/btcmarkets/btcmarkets_websocket.go | 207 +++ exchanges/btcmarkets/btcmarkets_wrapper.go | 71 +- exchanges/btse/btse_test.go | 38 +- exchanges/btse/btse_wrapper.go | 30 +- exchanges/coinbasepro/coinbasepro_test.go | 60 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 41 +- exchanges/coinut/coinut_test.go | 44 +- exchanges/coinut/coinut_wrapper.go | 41 +- exchanges/exchange.go | 3 +- exchanges/exchange_test.go | 169 +- exchanges/exchange_types.go | 34 +- exchanges/exmo/exmo_test.go | 52 +- exchanges/exmo/exmo_wrapper.go | 25 +- exchanges/gateio/gateio_test.go | 50 +- exchanges/gateio/gateio_wrapper.go | 41 +- exchanges/gemini/gemini_live_test.go | 6 +- exchanges/gemini/gemini_mock_test.go | 8 +- exchanges/gemini/gemini_test.go | 80 +- exchanges/gemini/gemini_wrapper.go | 31 +- exchanges/hitbtc/hitbtc_test.go | 30 +- exchanges/hitbtc/hitbtc_wrapper.go | 43 +- exchanges/huobi/huobi_test.go | 80 +- exchanges/huobi/huobi_wrapper.go | 38 +- exchanges/itbit/itbit_test.go | 58 +- exchanges/itbit/itbit_wrapper.go | 19 +- exchanges/kraken/kraken_test.go | 86 +- exchanges/kraken/kraken_wrapper.go | 43 +- exchanges/lakebtc/lakebtc_test.go | 50 +- exchanges/lakebtc/lakebtc_wrapper.go | 29 +- exchanges/lbank/lbank_test.go | 38 +- exchanges/lbank/lbank_wrapper.go | 21 +- .../localbitcoins/localbitcoins_live_test.go | 6 +- .../localbitcoins/localbitcoins_mock_test.go | 8 +- exchanges/localbitcoins/localbitcoins_test.go | 30 +- .../localbitcoins/localbitcoins_wrapper.go | 17 +- exchanges/mock/README.md | 6 +- exchanges/mock/common_test.go | 28 +- exchanges/mock/recording_test.go | 24 +- exchanges/mock/server_test.go | 28 +- exchanges/nonce/nonce_test.go | 14 +- exchanges/okcoin/okcoin_test.go | 22 +- exchanges/okcoin/okcoin_wrapper.go | 43 +- exchanges/okex/okex_test.go | 22 +- exchanges/okex/okex_wrapper.go | 43 +- exchanges/okgroup/okgroup_wrapper.go | 1 + exchanges/orderbook/orderbook_test.go | 74 +- exchanges/orders/orders_test.go | 12 +- exchanges/poloniex/poloniex_live_test.go | 6 +- exchanges/poloniex/poloniex_mock_test.go | 8 +- exchanges/poloniex/poloniex_test.go | 44 +- exchanges/poloniex/poloniex_wrapper.go | 39 +- exchanges/protocol/features.go | 40 + exchanges/request/request_test.go | 25 +- exchanges/stats/stats_test.go | 40 +- exchanges/ticker/ticker_test.go | 54 +- exchanges/websocket/wshandler/wshandler.go | 90 +- .../websocket/wshandler/wshandler_test.go | 76 +- .../websocket/wshandler/wshandler_types.go | 41 +- .../websocket/wsorderbook/wsorderbook_test.go | 33 + exchanges/yobit/yobit_test.go | 68 +- exchanges/yobit/yobit_wrapper.go | 21 +- exchanges/zb/zb_test.go | 50 +- exchanges/zb/zb_wrapper.go | 40 +- logger/logger_test.go | 4 +- ntpclient/ntpclient_test.go | 2 +- portfolio/portfolio_test.go | 90 +- 156 files changed, 5228 insertions(+), 4337 deletions(-) create mode 100644 currency/code_types.go create mode 100644 currency/storage_types.go create mode 100644 exchanges/btcmarkets/btcmarkets_websocket.go create mode 100644 exchanges/protocol/features.go diff --git a/cmd/config/config_test.go b/cmd/config/config_test.go index cd266ae5..7542cc09 100644 --- a/cmd/config/config_test.go +++ b/cmd/config/config_test.go @@ -6,13 +6,13 @@ func TestEncryptOrDecrypt(t *testing.T) { reValue := EncryptOrDecrypt(true) if reValue != "encrypted" { t.Error( - "Test failed - Tools/Config/Config_test.go - EncryptOrDecrypt Error", + "Tools/Config/Config_test.go - EncryptOrDecrypt Error", ) } reValue = EncryptOrDecrypt(false) if reValue != "decrypted" { t.Error( - "Test failed - Tools/Config/Config_test.go - EncryptOrDecrypt Error", + "Tools/Config/Config_test.go - EncryptOrDecrypt Error", ) } } diff --git a/cmd/exchange_template/test_file.tmpl b/cmd/exchange_template/test_file.tmpl index 38e4db70..87ee2bdc 100644 --- a/cmd/exchange_template/test_file.tmpl +++ b/cmd/exchange_template/test_file.tmpl @@ -24,7 +24,7 @@ func TestSetup(t *testing.T) { cfg.LoadConfig("../../testdata/configtest.json") {{.Name}}Config, err := cfg.GetExchangeConfig("{{.CapitalName}}") if err != nil { - t.Error("Test Failed - {{.CapitalName}} Setup() init error") + t.Error("{{.CapitalName}} Setup() init error") } {{.Name}}Config.API.AuthenticatedSupport = true diff --git a/cmd/websocket_client/main.go b/cmd/websocket_client/main.go index 7eba7875..c71b8876 100644 --- a/cmd/websocket_client/main.go +++ b/cmd/websocket_client/main.go @@ -76,7 +76,7 @@ func SendWebsocketEvent(event string, reqData interface{}, result *WebsocketEven func main() { cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigFile, true) + err := cfg.LoadConfig(config.File, true) if err != nil { log.Fatalf("Failed to load config file: %s", err) } diff --git a/common/common_test.go b/common/common_test.go index 41f7e35d..d8bf9f84 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -16,13 +16,13 @@ func TestIsEnabled(t *testing.T) { expected := "Enabled" actual := IsEnabled(true) if actual != expected { - t.Errorf("Test failed. Expected %s. Actual %s", expected, actual) + t.Errorf("Expected %s. Actual %s", expected, actual) } expected = "Disabled" actual = IsEnabled(false) if actual != expected { - t.Errorf("Test failed. Expected %s. Actual %s", expected, actual) + t.Errorf("Expected %s. Actual %s", expected, actual) } } @@ -30,44 +30,44 @@ func TestIsValidCryptoAddress(t *testing.T) { t.Parallel() b, err := IsValidCryptoAddress("1Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX", "bTC") if err != nil && !b { - t.Errorf("Test Failed - Common IsValidCryptoAddress error: %s", err) + t.Errorf("Common IsValidCryptoAddress error: %s", err) } b, err = IsValidCryptoAddress("0Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX", "btc") if err == nil && b { - t.Error("Test Failed - Common IsValidCryptoAddress error") + t.Error("Common IsValidCryptoAddress error") } b, err = IsValidCryptoAddress("1Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX", "lTc") if err == nil && b { - t.Error("Test Failed - Common IsValidCryptoAddress error") + t.Error("Common IsValidCryptoAddress error") } b, err = IsValidCryptoAddress("3CDJNfdWX8m2NwuGUV3nhXHXEeLygMXoAj", "ltc") if err != nil && !b { - t.Errorf("Test Failed - Common IsValidCryptoAddress error: %s", err) + t.Errorf("Common IsValidCryptoAddress error: %s", err) } b, err = IsValidCryptoAddress("NCDJNfdWX8m2NwuGUV3nhXHXEeLygMXoAj", "lTc") if err == nil && b { - t.Error("Test Failed - Common IsValidCryptoAddress error") + t.Error("Common IsValidCryptoAddress error") } b, err = IsValidCryptoAddress( "0xb794f5ea0ba39494ce839613fffba74279579268", "eth", ) if err != nil && b { - t.Errorf("Test Failed - Common IsValidCryptoAddress error: %s", err) + t.Errorf("Common IsValidCryptoAddress error: %s", err) } b, err = IsValidCryptoAddress( "xxb794f5ea0ba39494ce839613fffba74279579268", "eTh", ) if err == nil && b { - t.Error("Test Failed - Common IsValidCryptoAddress error") + t.Error("Common IsValidCryptoAddress error") } b, err = IsValidCryptoAddress( "xxb794f5ea0ba39494ce839613fffba74279579268", "ding", ) if err == nil && b { - t.Error("Test Failed - Common IsValidCryptoAddress error") + t.Error("Common IsValidCryptoAddress error") } } @@ -78,7 +78,7 @@ func TestStringSliceDifference(t *testing.T) { expectedOutput := []string{"hello moto"} actualResult := StringSliceDifference(originalInputOne, originalInputTwo) if reflect.DeepEqual(expectedOutput, actualResult) { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", + t.Errorf("Expected '%s'. Actual '%s'", expectedOutput, actualResult) } } @@ -92,12 +92,12 @@ func TestStringDataContains(t *testing.T) { expectedOutputTwo := false actualResult := StringDataContains(originalHaystack, originalNeedle) if actualResult != expectedOutput { - t.Errorf("Test failed. Expected '%v'. Actual '%v'", + t.Errorf("Expected '%v'. Actual '%v'", expectedOutput, actualResult) } actualResult = StringDataContains(originalHaystack, anotherNeedle) if actualResult != expectedOutputTwo { - t.Errorf("Test failed. Expected '%v'. Actual '%v'", + t.Errorf("Expected '%v'. Actual '%v'", expectedOutput, actualResult) } } @@ -111,12 +111,12 @@ func TestStringDataCompare(t *testing.T) { expectedOutputTwo := false actualResult := StringDataCompare(originalHaystack, originalNeedle) if actualResult != expectedOutput { - t.Errorf("Test failed. Expected '%v'. Actual '%v'", + t.Errorf("Expected '%v'. Actual '%v'", expectedOutput, actualResult) } actualResult = StringDataCompare(originalHaystack, anotherNeedle) if actualResult != expectedOutputTwo { - t.Errorf("Test failed. Expected '%v'. Actual '%v'", + t.Errorf("Expected '%v'. Actual '%v'", expectedOutput, actualResult) } } @@ -130,13 +130,13 @@ func TestStringDataCompareUpper(t *testing.T) { expectedOutputTwo := false actualResult := StringDataCompareInsensitive(originalHaystack, originalNeedle) if actualResult != expectedOutput { - t.Errorf("Test failed. Expected '%v'. Actual '%v'", + t.Errorf("Expected '%v'. Actual '%v'", expectedOutput, actualResult) } actualResult = StringDataCompareInsensitive(originalHaystack, anotherNeedle) if actualResult != expectedOutputTwo { - t.Errorf("Test failed. Expected '%v'. Actual '%v'", + t.Errorf("Expected '%v'. Actual '%v'", expectedOutput, actualResult) } } @@ -150,12 +150,12 @@ func TestStringDataContainsUpper(t *testing.T) { expectedOutputTwo := false actualResult := StringDataContainsInsensitive(originalHaystack, originalNeedle) if actualResult != expectedOutput { - t.Errorf("Test failed. Expected '%v'. Actual '%v'", + t.Errorf("Expected '%v'. Actual '%v'", expectedOutput, actualResult) } actualResult = StringDataContainsInsensitive(originalHaystack, anotherNeedle) if actualResult != expectedOutputTwo { - t.Errorf("Test failed. Expected '%v'. Actual '%v'", + t.Errorf("Expected '%v'. Actual '%v'", expectedOutput, actualResult) } } @@ -163,13 +163,13 @@ func TestStringDataContainsUpper(t *testing.T) { func TestYesOrNo(t *testing.T) { t.Parallel() if !YesOrNo("y") { - t.Error("Test failed - Common YesOrNo Error.") + t.Error("Common YesOrNo Error.") } if !YesOrNo("yes") { - t.Error("Test failed - Common YesOrNo Error.") + t.Error("Common YesOrNo Error.") } if YesOrNo("ding") { - t.Error("Test failed - Common YesOrNo Error.") + t.Error("Common YesOrNo Error.") } } @@ -187,42 +187,42 @@ func TestSendHTTPRequest(t *testing.T) { strings.NewReader(""), ) if err == nil { - t.Error("Test failed. ") + t.Error("Expected error 'invalid HTTP method specified'") } _, err = SendHTTPRequest( methodPost, "https://www.google.com", headers, strings.NewReader(""), ) if err != nil { - t.Errorf("Test failed. %s ", err) + t.Error(err) } _, err = SendHTTPRequest( methodGet, "https://www.google.com", headers, strings.NewReader(""), ) if err != nil { - t.Errorf("Test failed. %s ", err) + t.Error(err) } _, err = SendHTTPRequest( methodDelete, "https://www.google.com", headers, strings.NewReader(""), ) if err != nil { - t.Errorf("Test failed. %s ", err) + t.Error(err) } _, err = SendHTTPRequest( methodGet, ":missingprotocolscheme", headers, strings.NewReader(""), ) if err == nil { - t.Error("Test failed. Common HTTPRequest accepted missing protocol") + t.Error("Common HTTPRequest accepted missing protocol") } _, err = SendHTTPRequest( methodGet, "test://unsupportedprotocolscheme", headers, strings.NewReader(""), ) if err == nil { - t.Error("Test failed. Common HTTPRequest accepted invalid protocol") + t.Error("Common HTTPRequest accepted invalid protocol") } } @@ -243,23 +243,23 @@ func TestSendHTTPGetRequest(t *testing.T) { err := SendHTTPGetRequest(ethURL, true, true, &result) if err != nil { - t.Errorf("Test failed - common SendHTTPGetRequest error: %s", err) + t.Errorf("common SendHTTPGetRequest error: %s", err) } err = SendHTTPGetRequest("DINGDONG", true, false, &result) if err == nil { - t.Error("Test failed - common SendHTTPGetRequest error") + t.Error("common SendHTTPGetRequest error") } err = SendHTTPGetRequest(ethURL, false, false, &result) if err != nil { - t.Errorf("Test failed - common SendHTTPGetRequest error: %s", err) + t.Errorf("common SendHTTPGetRequest error: %s", err) } err = SendHTTPGetRequest("https://httpstat.us/202", false, false, &result) if err == nil { - t.Error("Test failed = common SendHTTPGetRequest error: Ignored unexpected status code") + t.Error("= common SendHTTPGetRequest error: Ignored unexpected status code") } err = SendHTTPGetRequest(ethURL, true, false, &badresult) if err == nil { - t.Error("Test failed - common SendHTTPGetRequest error: Unmarshalled into bad type") + t.Error("common SendHTTPGetRequest error: Unmarshalled into bad type") } } @@ -282,14 +282,14 @@ func TestJSONEncode(t *testing.T) { bitey, err := JSONEncode(v) if err != nil { - t.Errorf("Test failed - common JSONEncode error: %s", err) + t.Errorf("common JSONEncode error: %s", err) } if string(bitey) != expectOutputString { - t.Error("Test failed - common JSONEncode error") + t.Error("common JSONEncode error") } _, err = JSONEncode("WigWham") if err != nil { - t.Errorf("Test failed - common JSONEncode error: %s", err) + t.Errorf("common JSONEncode error: %s", err) } } @@ -299,7 +299,7 @@ func TestJSONDecode(t *testing.T) { result := "Not a memory address" err := JSONDecode(data, result) if err == nil { - t.Error("Test failed. Common JSONDecode, unmarshalled when address not supplied") + t.Error("Common JSONDecode, unmarshalled when address not supplied") } type test struct { @@ -314,7 +314,7 @@ func TestJSONDecode(t *testing.T) { data = []byte(`{"status":1,"data":null}`) err = JSONDecode(data, &v) if err != nil || v.Status != 1 { - t.Errorf("Test failed. Common JSONDecode. Data: %v \nError: %s", + t.Errorf("Common JSONDecode. Data: %v \nError: %s", v, err) } } @@ -329,7 +329,7 @@ func TestEncodeURLValues(t *testing.T) { output := EncodeURLValues(urlstring, values) if output != expectedOutput { - t.Error("Test Failed - common EncodeURLValues error") + t.Error("common EncodeURLValues error") } } @@ -341,12 +341,12 @@ func TestExtractHost(t *testing.T) { actualResult := ExtractHost(address) if expectedOutput != actualResult { t.Errorf( - "Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult) + "Expected '%s'. Actual '%s'.", expectedOutput, actualResult) } actualResultTwo := ExtractHost(addresstwo) if expectedOutput != actualResultTwo { t.Errorf( - "Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult) + "Expected '%s'. Actual '%s'.", expectedOutput, actualResult) } address = "192.168.1.100:1337" @@ -354,7 +354,7 @@ func TestExtractHost(t *testing.T) { actualResult = ExtractHost(address) if expectedOutput != actualResult { t.Errorf( - "Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult) + "Expected '%s'. Actual '%s'.", expectedOutput, actualResult) } } @@ -365,7 +365,7 @@ func TestExtractPort(t *testing.T) { actualResult := ExtractPort(address) if expectedOutput != actualResult { t.Errorf( - "Test failed. Expected '%d'. Actual '%d'.", expectedOutput, actualResult) + "Expected '%d'. Actual '%d'.", expectedOutput, actualResult) } } @@ -378,11 +378,11 @@ func TestOutputCSV(t *testing.T) { err := OutputCSV(path, data) if err != nil { - t.Errorf("Test failed - common OutputCSV error: %s", err) + t.Errorf("common OutputCSV error: %s", err) } err = OutputCSV("/:::notapath:::", data) if err == nil { - t.Error("Test failed - common OutputCSV, tried writing to invalid path") + t.Error("common OutputCSV, tried writing to invalid path") } } @@ -397,7 +397,7 @@ func TestGetURIPath(t *testing.T) { for testInput, expectedOutput := range testTable { actualOutput := GetURIPath(testInput) if actualOutput != expectedOutput { - t.Errorf("Test failed. Expected '%s'. Actual '%s'.", + t.Errorf("Expected '%s'. Actual '%s'.", expectedOutput, actualOutput) } } @@ -407,7 +407,7 @@ func TestGetExecutablePath(t *testing.T) { t.Parallel() _, err := GetExecutablePath() if err != nil { - t.Errorf("Test failed. Common GetExecutablePath. Error: %s", err) + t.Errorf("Common GetExecutablePath. Error: %s", err) } } diff --git a/common/convert/convert_test.go b/common/convert/convert_test.go index e4fe11a5..03aabb3d 100644 --- a/common/convert/convert_test.go +++ b/common/convert/convert_test.go @@ -12,20 +12,20 @@ func TestFloatFromString(t *testing.T) { actualOutput, err := FloatFromString(testString) if actualOutput != expectedOutput || err != nil { - t.Errorf("Test failed. Common FloatFromString. Expected '%v'. Actual '%v'. Error: %s", + t.Errorf("Common FloatFromString. Expected '%v'. Actual '%v'. Error: %s", expectedOutput, actualOutput, err) } var testByte []byte _, err = FloatFromString(testByte) if err == nil { - t.Error("Test failed. Common FloatFromString. Converted non-string.") + t.Error("Common FloatFromString. Converted non-string.") } testString = " something unconvertible " _, err = FloatFromString(testString) if err == nil { - t.Error("Test failed. Common FloatFromString. Converted invalid syntax.") + t.Error("Common FloatFromString. Converted invalid syntax.") } } @@ -36,20 +36,20 @@ func TestIntFromString(t *testing.T) { actualOutput, err := IntFromString(testString) if actualOutput != expectedOutput || err != nil { - t.Errorf("Test failed. Common IntFromString. Expected '%v'. Actual '%v'. Error: %s", + t.Errorf("Common IntFromString. Expected '%v'. Actual '%v'. Error: %s", expectedOutput, actualOutput, err) } var testByte []byte _, err = IntFromString(testByte) if err == nil { - t.Error("Test failed. Common IntFromString. Converted non-string.") + t.Error("Common IntFromString. Converted non-string.") } testString = "1.41421356237" _, err = IntFromString(testString) if err == nil { - t.Error("Test failed. Common IntFromString. Converted invalid syntax.") + t.Error("Common IntFromString. Converted invalid syntax.") } } @@ -60,20 +60,20 @@ func TestInt64FromString(t *testing.T) { actualOutput, err := Int64FromString(testString) if actualOutput != expectedOutput || err != nil { - t.Errorf("Test failed. Common Int64FromString. Expected '%v'. Actual '%v'. Error: %s", + t.Errorf("Common Int64FromString. Expected '%v'. Actual '%v'. Error: %s", expectedOutput, actualOutput, err) } var testByte []byte _, err = Int64FromString(testByte) if err == nil { - t.Error("Test failed. Common Int64FromString. Converted non-string.") + t.Error("Common Int64FromString. Converted non-string.") } testString = "1.41421356237" _, err = Int64FromString(testString) if err == nil { - t.Error("Test failed. Common Int64FromString. Converted invalid syntax.") + t.Error("Common Int64FromString. Converted invalid syntax.") } } @@ -84,14 +84,14 @@ func TestTimeFromUnixTimestampFloat(t *testing.T) { actualOutput, err := TimeFromUnixTimestampFloat(testTimestamp) if actualOutput.UTC().String() != expectedOutput.UTC().String() || err != nil { - t.Errorf("Test failed. Common TimeFromUnixTimestampFloat. Expected '%v'. Actual '%v'. Error: %s", + t.Errorf("Common TimeFromUnixTimestampFloat. Expected '%v'. Actual '%v'. Error: %s", expectedOutput, actualOutput, err) } testString := "Time" _, err = TimeFromUnixTimestampFloat(testString) if err == nil { - t.Error("Test failed. Common TimeFromUnixTimestampFloat. Converted invalid syntax.") + t.Error("Common TimeFromUnixTimestampFloat. Converted invalid syntax.") } } @@ -103,7 +103,7 @@ func TestUnixTimestampToTime(t *testing.T) { actualResult := UnixTimestampToTime(testTime) if tm.String() != actualResult.String() { t.Errorf( - "Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult) + "Expected '%s'. Actual '%s'.", expectedOutput, actualResult) } } @@ -118,11 +118,11 @@ func TestUnixTimestampStrToTime(t *testing.T) { } if actualResult.UTC().String() != expectedOutput { t.Errorf( - "Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult) + "Expected '%s'. Actual '%s'.", expectedOutput, actualResult) } actualResult, err = UnixTimestampStrToTime(incorrectTime) if err == nil { - t.Error("Test failed. Common UnixTimestampStrToTime error") + t.Error("Common UnixTimestampStrToTime error") } } @@ -133,7 +133,7 @@ func TestUnixMillis(t *testing.T) { actualOutput := UnixMillis(testTime) if actualOutput != expectedOutput { - t.Errorf("Test failed. Common UnixMillis. Expected '%d'. Actual '%d'.", + t.Errorf("Common UnixMillis. Expected '%d'. Actual '%d'.", expectedOutput, actualOutput) } } @@ -145,7 +145,7 @@ func TestRecvWindow(t *testing.T) { actualOutput := RecvWindow(testTime) if actualOutput != expectedOutput { - t.Errorf("Test failed. Common RecvWindow. Expected '%d'. Actual '%d'", + t.Errorf("Common RecvWindow. Expected '%d'. Actual '%d'", expectedOutput, actualOutput) } } diff --git a/common/crypto/crypto_test.go b/common/crypto/crypto_test.go index 38f929e7..2db54953 100644 --- a/common/crypto/crypto_test.go +++ b/common/crypto/crypto_test.go @@ -11,7 +11,7 @@ func TestHexEncodeToString(t *testing.T) { expectedOutput := "737472696e67" actualResult := HexEncodeToString(originalInput) if actualResult != expectedOutput { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", + t.Errorf("Expected '%s'. Actual '%s'", expectedOutput, actualResult) } } @@ -22,13 +22,13 @@ func TestBase64Decode(t *testing.T) { expectedOutput := []byte("hello") actualResult, err := Base64Decode(originalInput) if !bytes.Equal(actualResult, expectedOutput) { - t.Errorf("Test failed. Expected '%s'. Actual '%s'. Error: %s", + t.Errorf("Expected '%s'. Actual '%s'. Error: %s", expectedOutput, actualResult, err) } _, err = Base64Decode("-") if err == nil { - t.Error("Test failed. Bad base64 string failed returned nil error") + t.Error("Bad base64 string failed returned nil error") } } @@ -38,7 +38,7 @@ func TestBase64Encode(t *testing.T) { expectedOutput := "aGVsbG8=" actualResult := Base64Encode(originalInput) if actualResult != expectedOutput { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", + t.Errorf("Expected '%s'. Actual '%s'", expectedOutput, actualResult) } } @@ -48,7 +48,7 @@ func TestGetRandomSalt(t *testing.T) { _, err := GetRandomSalt(nil, -1) if err == nil { - t.Fatal("Test failed. Expected err on negative salt length") + t.Fatal("Expected err on negative salt length") } salt, err := GetRandomSalt(nil, 10) @@ -57,7 +57,7 @@ func TestGetRandomSalt(t *testing.T) { } if len(salt) != 10 { - t.Fatal("Test failed. Expected salt of len=10") + t.Fatal("Expected salt of len=10") } salt, err = GetRandomSalt([]byte("RAWR"), 12) @@ -66,7 +66,7 @@ func TestGetRandomSalt(t *testing.T) { } if len(salt) != 16 { - t.Fatal("Test failed. Expected salt of len=16") + t.Fatal("Expected salt of len=16") } } @@ -77,7 +77,7 @@ func TestGetMD5(t *testing.T) { actualOutput := GetMD5(originalString) actualStr := HexEncodeToString(actualOutput) if !bytes.Equal(expectedOutput, []byte(actualStr)) { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", + t.Errorf("Expected '%s'. Actual '%s'", expectedOutput, []byte(actualStr)) } @@ -92,7 +92,7 @@ func TestGetSHA512(t *testing.T) { actualOutput := GetSHA512(originalString) actualStr := HexEncodeToString(actualOutput) if !bytes.Equal(expectedOutput, []byte(actualStr)) { - t.Errorf("Test failed. Expected '%x'. Actual '%x'", + t.Errorf("Expected '%x'. Actual '%x'", expectedOutput, []byte(actualStr)) } } @@ -106,7 +106,7 @@ func TestGetSHA256(t *testing.T) { actualOutput := GetSHA256(originalString) actualStr := HexEncodeToString(actualOutput) if !bytes.Equal(expectedOutput, []byte(actualStr)) { - t.Errorf("Test failed. Expected '%x'. Actual '%x'", expectedOutput, + t.Errorf("Expected '%x'. Actual '%x'", expectedOutput, []byte(actualStr)) } } @@ -138,31 +138,31 @@ func TestGetHMAC(t *testing.T) { sha1 := GetHMAC(HashSHA1, []byte("Hello,World"), []byte("1234")) if string(sha1) != string(expectedSha1) { - t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", + t.Errorf("Common GetHMAC error: Expected '%x'. Actual '%x'", expectedSha1, sha1, ) } sha256 := GetHMAC(HashSHA256, []byte("Hello,World"), []byte("1234")) if string(sha256) != string(expectedsha256) { - t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", + t.Errorf("Common GetHMAC error: Expected '%x'. Actual '%x'", expectedsha256, sha256, ) } sha512 := GetHMAC(HashSHA512, []byte("Hello,World"), []byte("1234")) if string(sha512) != string(expectedsha512) { - t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", + t.Errorf("Common GetHMAC error: Expected '%x'. Actual '%x'", expectedsha512, sha512, ) } sha512384 := GetHMAC(HashSHA512_384, []byte("Hello,World"), []byte("1234")) if string(sha512384) != string(expectedsha512384) { - t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", + t.Errorf("Common GetHMAC error: Expected '%x'. Actual '%x'", expectedsha512384, sha512384, ) } md5 := GetHMAC(HashMD5, []byte("Hello World"), []byte("1234")) if string(md5) != string(expectedmd5) { - t.Errorf("Test failed. Common GetHMAC error: Expected '%x'. Actual '%x'", + t.Errorf("Common GetHMAC error: Expected '%x'. Actual '%x'", expectedmd5, md5, ) } @@ -174,7 +174,7 @@ func TestSha1Tohex(t *testing.T) { expectedResult := "fcfbfcd7d31d994ef660f6972399ab5d7a890149" actualResult := Sha1ToHex("Testing Sha1ToHex") if actualResult != expectedResult { - t.Errorf("Test failed. Expected '%s'. Actual '%s'", + t.Errorf("Expected '%s'. Actual '%s'", expectedResult, actualResult) } } diff --git a/common/math/math_test.go b/common/math/math_test.go index 93c43a21..37de7cdf 100644 --- a/common/math/math_test.go +++ b/common/math/math_test.go @@ -10,7 +10,7 @@ func TestCalculateFee(t *testing.T) { actualResult := CalculateFee(originalInput, fee) if expectedOutput != actualResult { t.Errorf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) + "Expected '%f'. Actual '%f'.", expectedOutput, actualResult) } } @@ -22,7 +22,7 @@ func TestCalculateAmountWithFee(t *testing.T) { actualResult := CalculateAmountWithFee(originalInput, fee) if expectedOutput != actualResult { t.Errorf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) + "Expected '%f'. Actual '%f'.", expectedOutput, actualResult) } } @@ -34,7 +34,7 @@ func TestCalculatePercentageGainOrLoss(t *testing.T) { actualResult := CalculatePercentageGainOrLoss(originalInput, secondInput) if expectedOutput != actualResult { t.Errorf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) + "Expected '%f'. Actual '%f'.", expectedOutput, actualResult) } } @@ -46,7 +46,7 @@ func TestCalculatePercentageDifference(t *testing.T) { actualResult := CalculatePercentageDifference(originalInput, secondAmount) if expectedOutput != actualResult { t.Errorf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) + "Expected '%f'. Actual '%f'.", expectedOutput, actualResult) } } @@ -60,7 +60,7 @@ func TestCalculateNetProfit(t *testing.T) { actualResult := CalculateNetProfit(amount, priceThen, priceNow, costs) if expectedOutput != actualResult { t.Errorf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult) + "Expected '%f'. Actual '%f'.", expectedOutput, actualResult) } } @@ -74,7 +74,7 @@ func TestRoundFloat(t *testing.T) { for testInput, expectedOutput := range testTable { actualOutput := RoundFloat(testInput, 2) if actualOutput != expectedOutput { - t.Errorf("Test failed. RoundFloat Expected '%f'. Actual '%f'.", + t.Errorf("RoundFloat Expected '%f'. Actual '%f'.", expectedOutput, actualOutput) } } diff --git a/communications/base/base_test.go b/communications/base/base_test.go index 71cb86cb..37f9613f 100644 --- a/communications/base/base_test.go +++ b/communications/base/base_test.go @@ -19,19 +19,19 @@ func TestStart(t *testing.T) { func TestIsEnabled(t *testing.T) { if !b.IsEnabled() { - t.Error("test failed - base IsEnabled() error") + t.Error("base IsEnabled() error") } } func TestIsConnected(t *testing.T) { if !b.IsConnected() { - t.Error("test failed - base IsConnected() error") + t.Error("base IsConnected() error") } } func TestGetName(t *testing.T) { if b.GetName() != "test" { - t.Error("test failed - base GetName() error") + t.Error("base GetName() error") } } diff --git a/communications/communications_test.go b/communications/communications_test.go index 2a145aab..cbc90dc5 100644 --- a/communications/communications_test.go +++ b/communications/communications_test.go @@ -23,7 +23,7 @@ func TestNewComm(t *testing.T) { } if len(communications.IComm) != 4 { - t.Errorf("Test failed, communications NewComm, expected len 4, got len %d", + t.Errorf("communications NewComm, expected len 4, got len %d", len(communications.IComm)) } } diff --git a/communications/slack/slack_test.go b/communications/slack/slack_test.go index 73eb850d..2e954165 100644 --- a/communications/slack/slack_test.go +++ b/communications/slack/slack_test.go @@ -54,7 +54,7 @@ func TestSetup(t *testing.T) { func TestConnect(t *testing.T) { err := s.Connect() if err == nil { - t.Error("test failed - slack Connect() error cannot be nil") + t.Error("slack Connect() error cannot be nil") } } @@ -62,7 +62,7 @@ func TestPushEvent(t *testing.T) { t.Parallel() err := s.PushEvent(base.Event{}) if err == nil { - t.Error("test failed - slack PushEvent() error cannot be nil") + t.Error("slack PushEvent() error cannot be nil") } } @@ -70,7 +70,7 @@ func TestBuildURL(t *testing.T) { t.Parallel() v := s.BuildURL("lol123") if v != "https://slack.com/api/rtm.start?token=lol123" { - t.Error("test failed - slack BuildURL() error") + t.Error("slack BuildURL() error") } } @@ -101,14 +101,14 @@ func TestGetChannelsString(t *testing.T) { } } if !testpassed { - t.Error("test failed - slack GetChannelsString() error") + t.Error("slack GetChannelsString() error") } } func TestGetUsernameByID(t *testing.T) { username := s.GetUsernameByID("1337") if username != "" { - t.Error("test failed - slack GetUsernameByID() error") + t.Error("slack GetUsernameByID() error") } s.Details.Users = append(s.Details.Users, struct { @@ -141,7 +141,7 @@ func TestGetUsernameByID(t *testing.T) { username = s.GetUsernameByID("1337") if username != "cranktakular" { - t.Error("test failed - slack GetUsernameByID() error") + t.Error("slack GetUsernameByID() error") } } @@ -149,7 +149,7 @@ func TestGetUsernameByID(t *testing.T) { func TestGetIDByName(t *testing.T) { id, err := s.GetIDByName("batman") if err == nil || id != "" { - t.Error("test failed - slack GetIDByName() error") + t.Error("slack GetIDByName() error") } s.Details.Groups = append(s.Details.Groups, group{ @@ -158,7 +158,7 @@ func TestGetIDByName(t *testing.T) { }) id, err = s.GetIDByName("this is a group") if err != nil || id != "210314" { - t.Errorf("test failed - slack GetIDByName() Expected '210314' Actual '%s' Error: %s", + t.Errorf("slack GetIDByName() Expected '210314' Actual '%s' Error: %s", id, err) } } @@ -166,7 +166,7 @@ func TestGetIDByName(t *testing.T) { func TestGetGroupIDByName(t *testing.T) { id, err := s.GetGroupIDByName("batman") if err == nil || id != "" { - t.Error("test failed - slack GetGroupIDByName() error") + t.Error("slack GetGroupIDByName() error") } s.Details.Groups = append(s.Details.Groups, group{ @@ -175,7 +175,7 @@ func TestGetGroupIDByName(t *testing.T) { }) id, err = s.GetGroupIDByName("another group") if err != nil || id != "11223344" { - t.Errorf("test failed - slack GetGroupIDByName() Expected '11223344' Actual '%s' Error: %s", + t.Errorf("slack GetGroupIDByName() Expected '11223344' Actual '%s' Error: %s", id, err) } @@ -184,7 +184,7 @@ func TestGetGroupIDByName(t *testing.T) { func TestGetChannelIDByName(t *testing.T) { id, err := s.GetChannelIDByName("1337") if err == nil || id != "" { - t.Error("test failed - slack GetChannelIDByName() error") + t.Error("slack GetChannelIDByName() error") } s.Details.Channels = append(s.Details.Channels, struct { @@ -208,7 +208,7 @@ func TestGetChannelIDByName(t *testing.T) { id, err = s.GetChannelIDByName("Slack Test") if err != nil || id != "2048" { - t.Errorf("test failed - slack GetChannelIDByName() Expected '2048' Actual '%s' Error: %s", + t.Errorf("slack GetChannelIDByName() Expected '2048' Actual '%s' Error: %s", id, err) } } @@ -216,7 +216,7 @@ func TestGetChannelIDByName(t *testing.T) { func TestGetUsersInGroup(t *testing.T) { username := s.GetUsersInGroup("supergroup") if len(username) != 0 { - t.Error("test failed - slack GetUsersInGroup() error") + t.Error("slack GetUsersInGroup() error") } s.Details.Groups = append(s.Details.Groups, group{ @@ -227,7 +227,7 @@ func TestGetUsersInGroup(t *testing.T) { username = s.GetUsersInGroup("three guys") if len(username) != 3 { - t.Errorf("test failed - slack GetUsersInGroup() Expected '3' Actual '%s'", + t.Errorf("slack GetUsersInGroup() Expected '3' Actual '%s'", username) } } @@ -235,14 +235,14 @@ func TestGetUsersInGroup(t *testing.T) { func TestNewConnection(t *testing.T) { err := s.NewConnection() if err == nil { - t.Error("test failed - slack NewConnection() error") + t.Error("slack NewConnection() error") } } func TestWebsocketConnect(t *testing.T) { err := s.WebsocketConnect() if err == nil { - t.Error("test failed - slack WebsocketConnect() error") + t.Error("slack WebsocketConnect() error") } } @@ -253,13 +253,13 @@ func TestHandlePresenceChange(t *testing.T) { err := s.handlePresenceChange([]byte(`{"malformedjson}`)) if err == nil { - t.Error("test failed - slack handlePresenceChange(), unmarshalled malformed json") + t.Error("slack handlePresenceChange(), unmarshalled malformed json") } data, _ := common.JSONEncode(pres) err = s.handlePresenceChange(data) if err != nil { - t.Errorf("test failed - slack handlePresenceChange() Error: %s", err) + t.Errorf("slack handlePresenceChange() Error: %s", err) } } @@ -269,7 +269,7 @@ func TestHandleMessageResponse(t *testing.T) { err := s.handleMessageResponse(nil, data) if err.Error() != "reply to is != 0" { - t.Errorf("test failed - slack handleMessageResponse(), Incorrect Error: %s", + t.Errorf("slack handleMessageResponse(), Incorrect Error: %s", err) } @@ -277,7 +277,7 @@ func TestHandleMessageResponse(t *testing.T) { err = s.handleMessageResponse([]byte(`{"malformedjson}`), data) if err == nil { - t.Error("test failed - slack handleMessageResponse(), unmarshalled malformed json") + t.Error("slack handleMessageResponse(), unmarshalled malformed json") } var msg Message @@ -287,7 +287,7 @@ func TestHandleMessageResponse(t *testing.T) { err = s.handleMessageResponse(resp, data) if err != nil { - t.Error("test failed - slack HandleMessage(), Sent message through nil websocket") + t.Error("slack HandleMessage(), Sent message through nil websocket") } msg.Text = "!notacommand" @@ -295,7 +295,7 @@ func TestHandleMessageResponse(t *testing.T) { err = s.handleMessageResponse(resp, data) if err == nil { - t.Errorf("test failed - slack handleMessageResponse() Error: %s", err) + t.Errorf("slack handleMessageResponse() Expected error") } } @@ -303,13 +303,13 @@ func TestHandleErrorResponse(t *testing.T) { var data WebsocketResponse err := s.handleErrorResponse(data) if err == nil { - t.Error("test failed - slack handleErrorResponse() Ignored strange input") + t.Error("slack handleErrorResponse() Ignored strange input") } data.Error.Msg = "Socket URL has expired" err = s.handleErrorResponse(data) if err == nil { - t.Error("test failed - slack handleErrorResponse() Didn't error on nil websocket") + t.Error("slack handleErrorResponse() Didn't error on nil websocket") } } @@ -320,7 +320,7 @@ func TestHandleHelloResponse(t *testing.T) { func TestHandleReconnectResponse(t *testing.T) { err := s.handleReconnectResponse([]byte(`{"malformedjson}`)) if err == nil { - t.Error("test failed - slack handleReconnectResponse(), unmarshalled malformed json") + t.Error("slack handleReconnectResponse(), unmarshalled malformed json") } var testURL struct { @@ -332,7 +332,7 @@ func TestHandleReconnectResponse(t *testing.T) { err = s.handleReconnectResponse(data) if err != nil || s.ReconnectURL != "https://www.thrasher.io" { - t.Errorf("test failed - slack handleReconnectResponse() Expected 'https://www.thrasher.io' Actual '%s' Error: %s", + t.Errorf("slack handleReconnectResponse() Expected 'https://www.thrasher.io' Actual '%s' Error: %s", s.ReconnectURL, err) } } @@ -340,7 +340,7 @@ func TestHandleReconnectResponse(t *testing.T) { func TestWebsocketSend(t *testing.T) { err := s.WebsocketSend("test", "Hello World!") if err == nil { - t.Error("test failed - slack WebsocketSend(), Sent message through nil websocket") + t.Error("slack WebsocketSend(), Sent message through nil websocket") } } @@ -348,16 +348,16 @@ func TestHandleMessage(t *testing.T) { msg := &Message{} err := s.HandleMessage(msg) if err == nil { - t.Error("test failed - slack HandleMessage(), Sent message through nil websocket") + t.Error("slack HandleMessage(), Sent message through nil websocket") } msg.Text = cmdStatus err = s.HandleMessage(msg) if err == nil { - t.Error("test failed - slack HandleMessage(), Sent message through nil websocket") + t.Error("slack HandleMessage(), Sent message through nil websocket") } msg.Text = cmdHelp err = s.HandleMessage(msg) if err == nil { - t.Error("test failed - slack HandleMessage(), Sent message through nil websocket") + t.Error("slack HandleMessage(), Sent message through nil websocket") } } diff --git a/communications/smsglobal/smsglobal_test.go b/communications/smsglobal/smsglobal_test.go index 2ffef201..0db1298f 100644 --- a/communications/smsglobal/smsglobal_test.go +++ b/communications/smsglobal/smsglobal_test.go @@ -22,82 +22,82 @@ func TestSetup(t *testing.T) { func TestConnect(t *testing.T) { err := s.Connect() if err != nil { - t.Error("test failed - SMSGlobal Connect() error", err) + t.Error("SMSGlobal Connect() error", err) } } func TestPushEvent(t *testing.T) { err := s.PushEvent(base.Event{}) if err != nil { - t.Error("test failed - SMSGlobal PushEvent() error", err) + t.Error("SMSGlobal PushEvent() error", err) } } func TestGetEnabledContacts(t *testing.T) { v := s.GetEnabledContacts() if v != 1 { - t.Error("test failed - SMSGlobal GetEnabledContacts() error") + t.Error("SMSGlobal GetEnabledContacts() error") } } func TestGetContactByNumber(t *testing.T) { _, err := s.GetContactByNumber("1231424") if err != nil { - t.Error("test failed - SMSGlobal GetContactByNumber() error", err) + t.Error("SMSGlobal GetContactByNumber() error", err) } _, err = s.GetContactByNumber("basketball") if err == nil { - t.Error("test failed - SMSGlobal GetContactByNumber() error") + t.Error("SMSGlobal GetContactByNumber() error") } } func TestGetContactByName(t *testing.T) { _, err := s.GetContactByName("StyleGherkin") if err != nil { - t.Error("test failed - SMSGlobal GetContactByName() error", err) + t.Error("SMSGlobal GetContactByName() error", err) } _, err = s.GetContactByName("blah") if err == nil { - t.Error("test failed - SMSGlobal GetContactByName() error") + t.Error("SMSGlobal GetContactByName() error") } } func TestAddContact(t *testing.T) { err := s.AddContact(Contact{Name: "bra", Number: "2876", Enabled: true}) if err != nil { - t.Error("test failed - SMSGlobal AddContact() error", err) + t.Error("SMSGlobal AddContact() error", err) } err = s.AddContact(Contact{Name: "StyleGherkin", Number: "1231424", Enabled: true}) if err == nil { - t.Error("test failed - SMSGlobal AddContact() error") + t.Error("SMSGlobal AddContact() error") } err = s.AddContact(Contact{Name: "", Number: "", Enabled: true}) if err == nil { - t.Error("test failed - SMSGlobal AddContact() error") + t.Error("SMSGlobal AddContact() error") } } func TestRemoveContact(t *testing.T) { err := s.RemoveContact(Contact{Name: "StyleGherkin", Number: "1231424", Enabled: true}) if err != nil { - t.Error("test failed - SMSGlobal RemoveContact() error", err) + t.Error("SMSGlobal RemoveContact() error", err) } err = s.RemoveContact(Contact{Name: "frieda", Number: "243453", Enabled: true}) if err == nil { - t.Error("test failed - SMSGlobal RemoveContact() error", err) + t.Error("SMSGlobal RemoveContact() Expected error") } } func TestSendMessageToAll(t *testing.T) { err := s.SendMessageToAll("Hello,World!") if err != nil { - t.Error("test failed - SMSGlobal SendMessageToAll() error", err) + t.Error("SMSGlobal SendMessageToAll() error", err) } } func TestSendMessage(t *testing.T) { err := s.SendMessage("1337", "Hello!") if err != nil { - t.Error("test failed - SMSGlobal SendMessage() error", err) + t.Error("SMSGlobal SendMessage() error", err) } } diff --git a/communications/smtpservice/smtpservice_test.go b/communications/smtpservice/smtpservice_test.go index 10124690..f1996348 100644 --- a/communications/smtpservice/smtpservice_test.go +++ b/communications/smtpservice/smtpservice_test.go @@ -22,24 +22,24 @@ func TestSetup(t *testing.T) { func TestConnect(t *testing.T) { err := s.Connect() if err != nil { - t.Error("test failed - smtpservice Connect() error", err) + t.Error("smtpservice Connect() error", err) } } func TestPushEvent(t *testing.T) { err := s.PushEvent(base.Event{}) if err == nil { - t.Error("test failed - smtpservice PushEvent() error cannot be nil") + t.Error("smtpservice PushEvent() error cannot be nil") } } func TestSend(t *testing.T) { err := s.Send("", "") if err == nil { - t.Error("test failed - smtpservice Send() error cannot be nil") + t.Error("smtpservice Send() error cannot be nil") } err = s.Send("subject", "alertmessage") if err == nil { - t.Error("test failed - smtpservice Send() error cannot be nil") + t.Error("smtpservice Send() error cannot be nil") } } diff --git a/communications/telegram/telegram_test.go b/communications/telegram/telegram_test.go index 0643037e..d50ba75e 100644 --- a/communications/telegram/telegram_test.go +++ b/communications/telegram/telegram_test.go @@ -22,7 +22,7 @@ func TestSetup(t *testing.T) { commsCfg := cfg.GetCommunicationsConfig() T.Setup(&commsCfg) if T.Name != "Telegram" || T.Enabled || T.Token != "testest" || T.Verbose { - t.Error("test failed - telegram Setup() error, unexpected setup values", + t.Error("telegram Setup() error, unexpected setup values", T.Name, T.Enabled, T.Token, @@ -33,19 +33,19 @@ func TestSetup(t *testing.T) { func TestConnect(t *testing.T) { err := T.Connect() if err == nil { - t.Error("test failed - telegram Connect() error") + t.Error("telegram Connect() error") } } func TestPushEvent(t *testing.T) { err := T.PushEvent(base.Event{}) if err != nil { - t.Error("test failed - telegram PushEvent() error", err) + t.Error("telegram PushEvent() error", err) } T.AuthorisedClients = append(T.AuthorisedClients, 1337) err = T.PushEvent(base.Event{}) if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram PushEvent() error, expected 'Not found' got '%s'", + t.Errorf("telegram PushEvent() error, expected 'Not found' got '%s'", err) } } @@ -55,27 +55,27 @@ func TestHandleMessages(t *testing.T) { chatID := int64(1337) err := T.HandleMessages(cmdHelp, chatID) if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", + t.Errorf("telegram HandleMessages() error, expected 'Not found' got '%s'", err) } err = T.HandleMessages(cmdStart, chatID) if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", + t.Errorf("telegram HandleMessages() error, expected 'Not found' got '%s'", err) } err = T.HandleMessages(cmdStatus, chatID) if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", + t.Errorf("telegram HandleMessages() error, expected 'Not found' got '%s'", err) } err = T.HandleMessages(cmdSettings, chatID) if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", + t.Errorf("telegram HandleMessages() error, expected 'Not found' got '%s'", err) } err = T.HandleMessages("Not a command", chatID) if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram HandleMessages() error, expected 'Not found' got '%s'", + t.Errorf("telegram HandleMessages() error, expected 'Not found' got '%s'", err) } } @@ -84,7 +84,7 @@ func TestGetUpdates(t *testing.T) { t.Parallel() _, err := T.GetUpdates() if err != nil { - t.Error("test failed - telegram GetUpdates() error", err) + t.Error("telegram GetUpdates() error", err) } } @@ -92,7 +92,7 @@ func TestTestConnection(t *testing.T) { t.Parallel() err := T.TestConnection() if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram TestConnection() error, expected 'Not found' got '%s'", + t.Errorf("telegram TestConnection() error, expected 'Not found' got '%s'", err) } } @@ -101,7 +101,7 @@ func TestSendMessage(t *testing.T) { t.Parallel() err := T.SendMessage("Test message", int64(1337)) if err.Error() != testErrNotFound { - t.Errorf("test failed - telegram SendMessage() error, expected 'Not found' got '%s'", + t.Errorf("telegram SendMessage() error, expected 'Not found' got '%s'", err) } } @@ -110,6 +110,6 @@ func TestSendHTTPRequest(t *testing.T) { t.Parallel() err := T.SendHTTPRequest("0.0.0.0", nil, nil) if err == nil { - t.Error("test failed - telegram SendHTTPRequest() error") + t.Error("telegram SendHTTPRequest() error") } } diff --git a/config/config.go b/config/config.go index 27cdaf02..52373774 100644 --- a/config/config.go +++ b/config/config.go @@ -13,7 +13,6 @@ import ( "runtime" "strconv" "strings" - "sync" "time" "github.com/thrasher-corp/gocryptotrader/common" @@ -27,67 +26,6 @@ import ( log "github.com/thrasher-corp/gocryptotrader/logger" ) -// Constants declared here are filename strings and test strings -const ( - FXProviderFixer = "fixer" - EncryptedConfigFile = "config.dat" - ConfigFile = "config.json" - ConfigTestFile = "../testdata/configtest.json" - configFileEncryptionPrompt = 0 - configFileEncryptionEnabled = 1 - configFileEncryptionDisabled = -1 - configPairsLastUpdatedWarningThreshold = 30 // 30 days - configDefaultHTTPTimeout = time.Second * 15 - configDefaultWebsocketResponseCheckTimeout = time.Millisecond * 30 - configDefaultWebsocketResponseMaxLimit = time.Second * 7 - configDefaultWebsocketOrderbookBufferLimit = 5 - configDefaultWebsocketTrafficTimeout = time.Second * 30 - configMaxAuthFailures = 3 - defaultNTPAllowedDifference = 50000000 - defaultNTPAllowedNegativeDifference = 50000000 - - DefaultAPIKey = "Key" - DefaultAPISecret = "Secret" - DefaultAPIClientID = "ClientID" -) - -// Constants here hold some messages -const ( - ErrExchangeNameEmpty = "exchange #%d name is empty" - ErrExchangeAvailablePairsEmpty = "exchange %s available pairs is empty" - ErrExchangeEnabledPairsEmpty = "exchange %s enabled pairs is empty" - ErrExchangeBaseCurrenciesEmpty = "exchange %s base currencies is empty" - ErrExchangeNotFound = "exchange %s not found" - ErrNoEnabledExchanges = "no exchanges enabled" - ErrCryptocurrenciesEmpty = "cryptocurrencies variable is empty" - ErrFailureOpeningConfig = "fatal error opening %s file. Error: %s" - ErrCheckingConfigValues = "fatal error checking config values. Error: %s" - ErrSavingConfigBytesMismatch = "config file %q bytes comparison doesn't match, read %s expected %s" - WarningWebserverCredentialValuesEmpty = "webserver support disabled due to empty Username/Password values" - WarningWebserverListenAddressInvalid = "webserver support disabled due to invalid listen address" - WarningExchangeAuthAPIDefaultOrEmptyValues = "exchange %s authenticated API support disabled due to default/empty APIKey/Secret/ClientID values" - WarningPairsLastUpdatedThresholdExceeded = "exchange %s last manual update of available currency pairs has exceeded %d days. Manual update required!" -) - -// Constants here define unset default values displayed in the config.json -// file -const ( - APIURLNonDefaultMessage = "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API" - WebsocketURLNonDefaultMessage = "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - DefaultUnsetAPIKey = "Key" - DefaultUnsetAPISecret = "Secret" - DefaultUnsetAccountPlan = "accountPlan" - DefaultForexProviderExchangeRatesAPI = "ExchangeRates" -) - -// Variables here are used for configuration -var ( - Cfg Config - IsInitialSetup bool - testBypass bool - m sync.Mutex -) - // GetCurrencyConfig returns currency configurations func (c *Config) GetCurrencyConfig() CurrencyConfig { return c.Currency @@ -982,14 +920,14 @@ func (c *Config) CheckExchangeConfigValues() error { } if !c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates && !c.Exchanges[i].Features.Supports.WebsocketCapabilities.AutoPairUpdates { lastUpdated := convert.UnixTimestampToTime(c.Exchanges[i].CurrencyPairs.LastUpdated) - lastUpdated = lastUpdated.AddDate(0, 0, configPairsLastUpdatedWarningThreshold) + lastUpdated = lastUpdated.AddDate(0, 0, pairsLastUpdatedWarningThreshold) if lastUpdated.Unix() <= time.Now().Unix() { - log.Warnf(log.ExchangeSys, WarningPairsLastUpdatedThresholdExceeded, c.Exchanges[i].Name, configPairsLastUpdatedWarningThreshold) + log.Warnf(log.ExchangeSys, WarningPairsLastUpdatedThresholdExceeded, c.Exchanges[i].Name, pairsLastUpdatedWarningThreshold) } } if c.Exchanges[i].HTTPTimeout <= 0 { - log.Warnf(log.ExchangeSys, "Exchange %s HTTP Timeout value not set, defaulting to %v.\n", c.Exchanges[i].Name, configDefaultHTTPTimeout) - c.Exchanges[i].HTTPTimeout = configDefaultHTTPTimeout + log.Warnf(log.ExchangeSys, "Exchange %s HTTP Timeout value not set, defaulting to %v.\n", c.Exchanges[i].Name, defaultHTTPTimeout) + c.Exchanges[i].HTTPTimeout = defaultHTTPTimeout } if c.Exchanges[i].HTTPRateLimiter != nil { @@ -1016,24 +954,24 @@ func (c *Config) CheckExchangeConfigValues() error { if c.Exchanges[i].WebsocketResponseCheckTimeout <= 0 { log.Warnf(log.ExchangeSys, "Exchange %s Websocket response check timeout value not set, defaulting to %v.", - c.Exchanges[i].Name, configDefaultWebsocketResponseCheckTimeout) - c.Exchanges[i].WebsocketResponseCheckTimeout = configDefaultWebsocketResponseCheckTimeout + c.Exchanges[i].Name, defaultWebsocketResponseCheckTimeout) + c.Exchanges[i].WebsocketResponseCheckTimeout = defaultWebsocketResponseCheckTimeout } if c.Exchanges[i].WebsocketResponseMaxLimit <= 0 { log.Warnf(log.ExchangeSys, "Exchange %s Websocket response max limit value not set, defaulting to %v.", - c.Exchanges[i].Name, configDefaultWebsocketResponseMaxLimit) - c.Exchanges[i].WebsocketResponseMaxLimit = configDefaultWebsocketResponseMaxLimit + c.Exchanges[i].Name, defaultWebsocketResponseMaxLimit) + c.Exchanges[i].WebsocketResponseMaxLimit = defaultWebsocketResponseMaxLimit } if c.Exchanges[i].WebsocketTrafficTimeout <= 0 { log.Warnf(log.ExchangeSys, "Exchange %s Websocket response traffic timeout value not set, defaulting to %v.", - c.Exchanges[i].Name, configDefaultWebsocketTrafficTimeout) - c.Exchanges[i].WebsocketTrafficTimeout = configDefaultWebsocketTrafficTimeout + c.Exchanges[i].Name, defaultWebsocketTrafficTimeout) + c.Exchanges[i].WebsocketTrafficTimeout = defaultWebsocketTrafficTimeout } if c.Exchanges[i].WebsocketOrderbookBufferLimit <= 0 { log.Warnf(log.ExchangeSys, "Exchange %s Websocket orderbook buffer limit value not set, defaulting to %v.", - c.Exchanges[i].Name, configDefaultWebsocketOrderbookBufferLimit) - c.Exchanges[i].WebsocketOrderbookBufferLimit = configDefaultWebsocketOrderbookBufferLimit + c.Exchanges[i].Name, defaultWebsocketOrderbookBufferLimit) + c.Exchanges[i].WebsocketOrderbookBufferLimit = defaultWebsocketOrderbookBufferLimit } err := c.CheckPairConsistency(c.Exchanges[i].Name) if err != nil { @@ -1065,10 +1003,7 @@ func (c *Config) CheckExchangeConfigValues() error { // CheckCurrencyConfigValues checks to see if the currency config values are correct or not func (c *Config) CheckCurrencyConfigValues() error { - fxProviders := forexprovider.GetAvailableForexProviders() - if len(fxProviders) == 0 { - return errors.New("no forex providers available") - } + fxProviders := forexprovider.GetSupportedForexProviders() if len(fxProviders) != len(c.Currency.ForexProviders) { for x := range fxProviders { @@ -1088,27 +1023,25 @@ func (c *Config) CheckCurrencyConfigValues() error { count := 0 for i := range c.Currency.ForexProviders { if c.Currency.ForexProviders[i].Enabled { - if c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey && c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI { + if c.Currency.ForexProviders[i].Name == "CurrencyConverter" && + c.Currency.ForexProviders[i].PrimaryProvider && + (c.Currency.ForexProviders[i].APIKey == "" || + c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey) { + log.Warnln(log.Global, "CurrencyConverter forex provider no longer supports unset API key requests. Switching to ExchangeRates FX provider..") + c.Currency.ForexProviders[i].Enabled = false + c.Currency.ForexProviders[i].PrimaryProvider = false + c.Currency.ForexProviders[i].APIKey = DefaultUnsetAPIKey + c.Currency.ForexProviders[i].APIKeyLvl = -1 + continue + } + if c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey && + c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI { log.Warnf(log.Global, "%s enabled forex provider API key not set. Please set this in your config.json file\n", c.Currency.ForexProviders[i].Name) c.Currency.ForexProviders[i].Enabled = false c.Currency.ForexProviders[i].PrimaryProvider = false continue } - if c.Currency.ForexProviders[i].Name == "CurrencyConverter" { - if c.Currency.ForexProviders[i].Enabled && - c.Currency.ForexProviders[i].PrimaryProvider && - (c.Currency.ForexProviders[i].APIKey == "" || - c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey) { - log.Warnln(log.Global, "CurrencyConverter forex provider no longer supports unset API key requests. Switching to ExchangeRates FX provider..") - c.Currency.ForexProviders[i].Enabled = false - c.Currency.ForexProviders[i].PrimaryProvider = false - c.Currency.ForexProviders[i].APIKey = DefaultUnsetAPIKey - c.Currency.ForexProviders[i].APIKeyLvl = -1 - continue - } - } - if c.Currency.ForexProviders[i].APIKeyLvl == -1 && c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI { log.Warnf(log.Global, "%s APIKey Level not set, functions limited. Please set this in your config.json file\n", c.Currency.ForexProviders[i].Name) @@ -1406,7 +1339,7 @@ func GetFilePath(file string) (string, error) { } if flag.Lookup("test.v") != nil && !testBypass { - return ConfigTestFile, nil + return TestFile, nil } exePath, err := common.GetExecutablePath() @@ -1415,8 +1348,8 @@ func GetFilePath(file string) (string, error) { } oldDirs := []string{ - filepath.Join(exePath, ConfigFile), - filepath.Join(exePath, EncryptedConfigFile), + filepath.Join(exePath, File), + filepath.Join(exePath, EncryptedFile), } newDir := common.GetDefaultDataDir(runtime.GOOS) @@ -1425,8 +1358,8 @@ func GetFilePath(file string) (string, error) { return "", err } newDirs := []string{ - filepath.Join(newDir, ConfigFile), - filepath.Join(newDir, EncryptedConfigFile), + filepath.Join(newDir, File), + filepath.Join(newDir, EncryptedFile), } // First upgrade the old dir config file if it exists to the corresponding @@ -1522,23 +1455,23 @@ func (c *Config) ReadConfig(configPath string, dryrun bool) error { return err } - if c.EncryptConfig == configFileEncryptionDisabled { + if c.EncryptConfig == fileEncryptionDisabled { return nil } - if c.EncryptConfig == configFileEncryptionPrompt { + if c.EncryptConfig == fileEncryptionPrompt { m.Lock() IsInitialSetup = true m.Unlock() if c.PromptForConfigEncryption(configPath, dryrun) { - c.EncryptConfig = configFileEncryptionEnabled + c.EncryptConfig = fileEncryptionEnabled return c.SaveConfig(defaultPath, dryrun) } } } else { errCounter := 0 for { - if errCounter >= configMaxAuthFailures { + if errCounter >= maxAuthFailures { return errors.New("failed to decrypt config after 3 attempts") } key, err := PromptForConfigKey(IsInitialSetup) @@ -1559,7 +1492,7 @@ func (c *Config) ReadConfig(configPath string, dryrun bool) error { err = ConfirmConfigJSON(data, &c) if err != nil { - if errCounter < configMaxAuthFailures { + if errCounter < maxAuthFailures { log.Error(log.ConfigMgr, "Invalid password.") } errCounter++ @@ -1587,7 +1520,7 @@ func (c *Config) SaveConfig(configPath string, dryrun bool) error { return err } - if c.EncryptConfig == configFileEncryptionEnabled { + if c.EncryptConfig == fileEncryptionEnabled { var key []byte if IsInitialSetup { @@ -1678,8 +1611,8 @@ func (c *Config) CheckConfig() error { } if c.GlobalHTTPTimeout <= 0 { - log.Warnf(log.ConfigMgr, "Global HTTP Timeout value not set, defaulting to %v.\n", configDefaultHTTPTimeout) - c.GlobalHTTPTimeout = configDefaultHTTPTimeout + log.Warnf(log.ConfigMgr, "Global HTTP Timeout value not set, defaulting to %v.\n", defaultHTTPTimeout) + c.GlobalHTTPTimeout = defaultHTTPTimeout } if c.NTPClient.Level != 0 { diff --git a/config/config_encryption.go b/config/config_encryption.go index c15fefdc..6ec7d3d4 100644 --- a/config/config_encryption.go +++ b/config/config_encryption.go @@ -43,7 +43,7 @@ func (c *Config) PromptForConfigEncryption(configPath string, dryrun bool) bool } if !common.YesOrNo(input) { - c.EncryptConfig = configFileEncryptionDisabled + c.EncryptConfig = fileEncryptionDisabled err := c.SaveConfig(configPath, dryrun) if err != nil { log.Errorf(log.ConfigMgr, "cannot save config %s", err) diff --git a/config/config_encryption_test.go b/config/config_encryption_test.go index 0088c881..3eabe392 100644 --- a/config/config_encryption_test.go +++ b/config/config_encryption_test.go @@ -9,7 +9,7 @@ func TestPromptForConfigEncryption(t *testing.T) { t.Parallel() if Cfg.PromptForConfigEncryption("", true) { - t.Error("Test failed. PromptForConfigEncryption return incorrect bool") + t.Error("PromptForConfigEncryption return incorrect bool") } } @@ -18,25 +18,25 @@ func TestPromptForConfigKey(t *testing.T) { byteyBite, err := PromptForConfigKey(true) if err == nil && len(byteyBite) > 1 { - t.Errorf("Test failed. PromptForConfigKey: %s", err) + t.Errorf("PromptForConfigKey: %s", err) } _, err = PromptForConfigKey(false) if err == nil { - t.Fatal(err) + t.Error("Expected error") } } func TestEncryptConfigFile(t *testing.T) { _, err := EncryptConfigFile([]byte("test"), nil) if err == nil { - t.Fatal("Test failed. Expected different result") + t.Fatal("Expected error") } sessionDK = []byte("a") _, err = EncryptConfigFile([]byte("test"), nil) if err == nil { - t.Fatal("Test failed. Expected different result") + t.Fatal("Expected error") } sessionDK, err = makeNewSessionDK([]byte("asdf")) @@ -60,17 +60,17 @@ func TestDecryptConfigFile(t *testing.T) { _, err = DecryptConfigFile(result, nil) if err == nil { - t.Fatal("Test failed. Expected different result") + t.Fatal("Expected error") } _, err = DecryptConfigFile([]byte("test"), nil) if err == nil { - t.Fatal("Test failed. Expected different result") + t.Fatal("Expected error") } _, err = DecryptConfigFile([]byte("test"), []byte("AAAAAAAAAAAAAAAA")) if err == nil { - t.Fatalf("Test failed. Expected %s", errAESBlockSize) + t.Fatalf("Expected %s", errAESBlockSize) } result, err = EncryptConfigFile([]byte("test"), []byte("key")) @@ -86,14 +86,14 @@ func TestDecryptConfigFile(t *testing.T) { func TestConfirmConfigJSON(t *testing.T) { var result interface{} - testConfirmJSON, err := ioutil.ReadFile(ConfigTestFile) + testConfirmJSON, err := ioutil.ReadFile(TestFile) if err != nil { - t.Errorf("Test failed. testConfirmJSON: %s", err) + t.Errorf("testConfirmJSON: %s", err) } err = ConfirmConfigJSON(testConfirmJSON, &result) if err != nil || result == nil { - t.Errorf("Test failed. testConfirmJSON: %s", err) + t.Errorf("testConfirmJSON: %s", err) } } @@ -102,7 +102,7 @@ func TestConfirmECS(t *testing.T) { ECStest := []byte(EncryptConfirmString) if !ConfirmECS(ECStest) { - t.Errorf("Test failed. TestConfirmECS: Error finding ECS.") + t.Errorf("TestConfirmECS: Error finding ECS.") } } @@ -113,7 +113,7 @@ func TestRemoveECS(t *testing.T) { isremoved := RemoveECS(ECStest) if string(isremoved) != "" { - t.Errorf("Test failed. TestConfirmECS: Error ECS not deleted.") + t.Errorf("TestConfirmECS: Error ECS not deleted.") } } @@ -122,6 +122,6 @@ func TestMakeNewSessionDK(t *testing.T) { _, err := makeNewSessionDK(nil) if err == nil { - t.Fatal("Test failed. makeNewSessionDK passed with nil key") + t.Fatal("makeNewSessionDK passed with nil key") } } diff --git a/config/config_test.go b/config/config_test.go index 073ea166..984f013e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -22,40 +22,40 @@ const ( func TestGetCurrencyConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. GetCurrencyConfig LoadConfig error", err) + t.Error("GetCurrencyConfig LoadConfig error", err) } _ = cfg.GetCurrencyConfig() } func TestGetExchangeBankAccounts(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. GetExchangeBankAccounts LoadConfig error", err) + t.Error("GetExchangeBankAccounts LoadConfig error", err) } _, err = cfg.GetExchangeBankAccounts("Bitfinex", "USD") if err != nil { - t.Error("Test failed. GetExchangeBankAccounts error", err) + t.Error("GetExchangeBankAccounts error", err) } _, err = cfg.GetExchangeBankAccounts("Not an exchange", "Not a currency") if err == nil { - t.Error("Test failed. GetExchangeBankAccounts, no error returned for invalid exchange") + t.Error("GetExchangeBankAccounts, no error returned for invalid exchange") } } func TestUpdateExchangeBankAccounts(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. UpdateExchangeBankAccounts LoadConfig error", err) + t.Error("UpdateExchangeBankAccounts LoadConfig error", err) } b := []BankAccount{{Enabled: false}} err = cfg.UpdateExchangeBankAccounts("Bitfinex", b) if err != nil { - t.Error("Test failed. UpdateExchangeBankAccounts error", err) + t.Error("UpdateExchangeBankAccounts error", err) } var count int for _, exch := range cfg.Exchanges { @@ -66,50 +66,50 @@ func TestUpdateExchangeBankAccounts(t *testing.T) { } } if count != 1 { - t.Error("Test failed. UpdateExchangeBankAccounts error") + t.Error("UpdateExchangeBankAccounts error") } err = cfg.UpdateExchangeBankAccounts("Not an exchange", b) if err == nil { - t.Error("Test failed. UpdateExchangeBankAccounts, no error returned for invalid exchange") + t.Error("UpdateExchangeBankAccounts, no error returned for invalid exchange") } } func TestGetClientBankAccounts(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. GetClientBankAccounts LoadConfig error", err) + t.Error("GetClientBankAccounts LoadConfig error", err) } _, err = cfg.GetClientBankAccounts("Kraken", "USD") if err != nil { - t.Error("Test failed. GetClientBankAccounts error", err) + t.Error("GetClientBankAccounts error", err) } _, err = cfg.GetClientBankAccounts("Bla", "USD") if err == nil { - t.Error("Test failed. GetClientBankAccounts error") + t.Error("GetClientBankAccounts error") } _, err = cfg.GetClientBankAccounts("Kraken", "JPY") if err == nil { - t.Error("Test failed. GetClientBankAccounts error", err) + t.Error("GetClientBankAccounts Expected error") } } func TestUpdateClientBankAccounts(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. UpdateClientBankAccounts LoadConfig error", err) + t.Error("UpdateClientBankAccounts LoadConfig error", err) } b := BankAccount{Enabled: false, BankName: "test", AccountNumber: "0234"} err = cfg.UpdateClientBankAccounts(&b) if err != nil { - t.Error("Test failed. UpdateClientBankAccounts error", err) + t.Error("UpdateClientBankAccounts error", err) } err = cfg.UpdateClientBankAccounts(&BankAccount{}) if err == nil { - t.Error("Test failed. UpdateClientBankAccounts error") + t.Error("UpdateClientBankAccounts error") } var count int @@ -121,21 +121,21 @@ func TestUpdateClientBankAccounts(t *testing.T) { } } if count != 1 { - t.Error("Test failed. UpdateClientBankAccounts error") + t.Error("UpdateClientBankAccounts error") } } func TestCheckClientBankAccounts(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. CheckClientBankAccounts LoadConfig error", err) + t.Error("CheckClientBankAccounts LoadConfig error", err) } cfg.BankAccounts = nil cfg.CheckClientBankAccounts() if len(cfg.BankAccounts) == 0 { - t.Error("Test failed. CheckClientBankAccounts error:", err) + t.Error("CheckClientBankAccounts error:", err) } cfg.BankAccounts = nil @@ -262,45 +262,45 @@ func TestPurgeExchangeCredentials(t *testing.T) { func TestGetCommunicationsConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. GetCommunicationsConfig LoadConfig error", err) + t.Error("GetCommunicationsConfig LoadConfig error", err) } _ = cfg.GetCommunicationsConfig() } func TestUpdateCommunicationsConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. UpdateCommunicationsConfig LoadConfig error", err) + t.Error("UpdateCommunicationsConfig LoadConfig error", err) } cfg.UpdateCommunicationsConfig(&CommunicationsConfig{SlackConfig: SlackConfig{Name: "TEST"}}) if cfg.Communications.SlackConfig.Name != "TEST" { - t.Error("Test failed. UpdateCommunicationsConfig LoadConfig error") + t.Error("UpdateCommunicationsConfig LoadConfig error") } } func TestGetCryptocurrencyProviderConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. GetCryptocurrencyProviderConfig LoadConfig error", err) + t.Error("GetCryptocurrencyProviderConfig LoadConfig error", err) } _ = cfg.GetCryptocurrencyProviderConfig() } func TestUpdateCryptocurrencyProviderConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. UpdateCryptocurrencyProviderConfig LoadConfig error", err) + t.Error("UpdateCryptocurrencyProviderConfig LoadConfig error", err) } orig := cfg.GetCryptocurrencyProviderConfig() cfg.UpdateCryptocurrencyProviderConfig(CryptocurrencyProvider{Name: "SERIOUS TESTING PROCEDURE!"}) if cfg.Currency.CryptocurrencyProvider.Name != "SERIOUS TESTING PROCEDURE!" { - t.Error("Test failed. UpdateCurrencyProviderConfig LoadConfig error") + t.Error("UpdateCurrencyProviderConfig LoadConfig error") } cfg.UpdateCryptocurrencyProviderConfig(orig) @@ -308,9 +308,9 @@ func TestUpdateCryptocurrencyProviderConfig(t *testing.T) { func TestCheckCommunicationsConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. CheckCommunicationsConfig LoadConfig error", err) + t.Error("CheckCommunicationsConfig LoadConfig error", err) } cfg.Communications = CommunicationsConfig{} @@ -319,7 +319,7 @@ func TestCheckCommunicationsConfig(t *testing.T) { cfg.Communications.SMSGlobalConfig.Name != "SMSGlobal" || cfg.Communications.SMTPConfig.Name != "SMTP" || cfg.Communications.TelegramConfig.Name != "Telegram" { - t.Error("Test failed. CheckCommunicationsConfig unexpected data:", + t.Error("CheckCommunicationsConfig unexpected data:", cfg.Communications) } @@ -327,7 +327,7 @@ func TestCheckCommunicationsConfig(t *testing.T) { cfg.Communications.SMSGlobalConfig.Name = "" cfg.CheckCommunicationsConfig() if cfg.Communications.SMSGlobalConfig.Password != "test" { - t.Error("Test failed. CheckCommunicationsConfig error:", err) + t.Error("CheckCommunicationsConfig error:", err) } cfg.SMS.Contacts = append(cfg.SMS.Contacts, SMSContact{ @@ -338,25 +338,25 @@ func TestCheckCommunicationsConfig(t *testing.T) { cfg.Communications.SMSGlobalConfig.Name = "" cfg.CheckCommunicationsConfig() if cfg.Communications.SMSGlobalConfig.Contacts[0].Name != "Bobby" { - t.Error("Test failed. CheckCommunicationsConfig error:", err) + t.Error("CheckCommunicationsConfig error:", err) } cfg.Communications.SMSGlobalConfig.From = "" cfg.CheckCommunicationsConfig() if cfg.Communications.SMSGlobalConfig.From != cfg.Name { - t.Error("Test failed. CheckCommunicationsConfig From value should of been set to the config name") + t.Error("CheckCommunicationsConfig From value should have been set to the config name") } cfg.Communications.SMSGlobalConfig.From = "aaaaaaaaaaaaaaaaaaa" cfg.CheckCommunicationsConfig() if cfg.Communications.SMSGlobalConfig.From != "aaaaaaaaaaa" { - t.Error("Test failed. CheckCommunicationsConfig From value should of been trimmed to 11 characters") + t.Error("CheckCommunicationsConfig From value should have been trimmed to 11 characters") } cfg.SMS = &SMSGlobalConfig{} cfg.CheckCommunicationsConfig() if cfg.SMS != nil { - t.Error("Test failed. CheckCommunicationsConfig unexpected data:", + t.Error("CheckCommunicationsConfig unexpected data:", cfg.SMS) } @@ -367,7 +367,7 @@ func TestCheckCommunicationsConfig(t *testing.T) { cfg.Communications.SlackConfig.Enabled = true cfg.CheckCommunicationsConfig() if cfg.Communications.SlackConfig.Enabled { - t.Error("Test failed. CheckCommunicationsConfig Slack is enabled when it shouldn't be.") + t.Error("CheckCommunicationsConfig Slack is enabled when it shouldn't be.") } cfg.Communications.SlackConfig.Enabled = false @@ -375,7 +375,7 @@ func TestCheckCommunicationsConfig(t *testing.T) { cfg.Communications.SMSGlobalConfig.Password = "" cfg.CheckCommunicationsConfig() if cfg.Communications.SlackConfig.Enabled { - t.Error("Test failed. CheckCommunicationsConfig SMSGlobal is enabled when it shouldn't be.") + t.Error("CheckCommunicationsConfig SMSGlobal is enabled when it shouldn't be.") } cfg.Communications.SMSGlobalConfig.Enabled = false @@ -383,7 +383,7 @@ func TestCheckCommunicationsConfig(t *testing.T) { cfg.Communications.SMTPConfig.AccountPassword = "" cfg.CheckCommunicationsConfig() if cfg.Communications.SlackConfig.Enabled { - t.Error("Test failed. CheckCommunicationsConfig SMTPConfig is enabled when it shouldn't be.") + t.Error("CheckCommunicationsConfig SMTPConfig is enabled when it shouldn't be.") } cfg.Communications.SMTPConfig.Enabled = false @@ -391,7 +391,7 @@ func TestCheckCommunicationsConfig(t *testing.T) { cfg.Communications.TelegramConfig.VerificationToken = "" cfg.CheckCommunicationsConfig() if cfg.Communications.TelegramConfig.Enabled { - t.Error("Test failed. CheckCommunicationsConfig TelegramConfig is enabled when it shouldn't be.") + t.Error("CheckCommunicationsConfig TelegramConfig is enabled when it shouldn't be.") } } @@ -400,7 +400,7 @@ func TestGetExchangeAssetTypes(t *testing.T) { var c Config _, err := c.GetExchangeAssetTypes("void") if err == nil { - t.Error("err should of been thrown on a non-existent exchange") + t.Error("err should have been thrown on a non-existent exchange") } c.Exchanges = append(c.Exchanges, @@ -428,7 +428,7 @@ func TestGetExchangeAssetTypes(t *testing.T) { c.Exchanges[0].CurrencyPairs = nil _, err = c.GetExchangeAssetTypes(testFakeExchangeName) if err == nil { - t.Error("a nil pair manager should throw an error") + t.Error("Expected error from nil currency pair") } } @@ -437,7 +437,7 @@ func TestSupportsExchangeAssetType(t *testing.T) { var c Config _, err := c.SupportsExchangeAssetType("void", asset.Spot) if err == nil { - t.Error("unexpected result for non-existent exchange") + t.Error("Expected error for non-existent exchange") } c.Exchanges = append(c.Exchanges, @@ -463,13 +463,13 @@ func TestSupportsExchangeAssetType(t *testing.T) { _, err = c.SupportsExchangeAssetType(testFakeExchangeName, "asdf") if err == nil { - t.Error("invalid asset item should throw an error") + t.Error("Expected error from invalid asset item") } c.Exchanges[0].CurrencyPairs = nil _, err = c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot) if err == nil { - t.Error("a nil pair manager should throw an error") + t.Error("Expected error from nil pair manager") } } @@ -507,7 +507,7 @@ func TestCheckExchangeAssetsConsistency(t *testing.T) { } if supports { - t.Error("perpetual contract should of been removed from the pair manager") + t.Error("perpetual contract should have been removed from the pair manager") } } @@ -522,12 +522,12 @@ func TestSetPairs(t *testing.T) { err := c.SetPairs("asdf", asset.Spot, true, nil) if err == nil { - t.Error("nil pairs should throw an error") + t.Error("Expected error from nil pairs") } err = c.SetPairs("asdf", asset.Spot, true, pairs) if err == nil { - t.Error("non-existent exchange should throw an error") + t.Error("Expected error from non-existent exchange") } c.Exchanges = append(c.Exchanges, @@ -538,7 +538,7 @@ func TestSetPairs(t *testing.T) { err = c.SetPairs(testFakeExchangeName, asset.Index, true, pairs) if err == nil { - t.Error("non initialised pair manager should throw an error") + t.Error("Expected error from non initialised pair manager") } c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ @@ -550,7 +550,7 @@ func TestSetPairs(t *testing.T) { err = c.SetPairs(testFakeExchangeName, asset.Index, true, pairs) if err == nil { - t.Error("non supported asset type should throw an error") + t.Error("Expected error from non supported asset type") } err = c.SetPairs(testFakeExchangeName, asset.Spot, true, pairs) @@ -565,7 +565,7 @@ func TestGetCurrencyPairConfig(t *testing.T) { var c Config _, err := c.GetCurrencyPairConfig("asdfg", asset.Spot) if err == nil { - t.Error("expected error with non-existent exchange") + t.Error("Expected error with non-existent exchange") } c.Exchanges = append(c.Exchanges, @@ -576,7 +576,7 @@ func TestGetCurrencyPairConfig(t *testing.T) { _, err = c.GetCurrencyPairConfig(testFakeExchangeName, asset.Index) if err == nil { - t.Error("expected error with nil currency pair store") + t.Error("Expected error with nil currency pair store") } pm := ¤cy.PairsManager{ @@ -601,7 +601,7 @@ func TestGetCurrencyPairConfig(t *testing.T) { c.Exchanges[0].CurrencyPairs = pm _, err = c.GetCurrencyPairConfig(testFakeExchangeName, asset.Index) if err == nil { - t.Error("expected error with unsupported asset") + t.Error("Expected error with unsupported asset") } var p *currency.PairStore @@ -782,10 +782,10 @@ func TestCheckPairConsistency(t *testing.T) { func TestSupportsPair(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { t.Errorf( - "Test failed. TestSupportsPair. LoadConfig Error: %s", err.Error(), + "TestSupportsPair. LoadConfig Error: %s", err.Error(), ) } @@ -794,7 +794,7 @@ func TestSupportsPair(t *testing.T) { currency.NewPair(currency.BTC, currency.USD), assetType) if err == nil { t.Error( - "Test failed. TestSupportsPair. Non-existent exchange returned nil error", + "TestSupportsPair. Expected error from Non-existent exchange", ) } @@ -802,7 +802,7 @@ func TestSupportsPair(t *testing.T) { currency.NewPair(currency.BTC, currency.USD), assetType) if err != nil { t.Errorf( - "Test failed. TestSupportsPair. Incorrect values. Err: %s", err, + "TestSupportsPair. Incorrect values. Err: %s", err, ) } } @@ -813,7 +813,7 @@ func TestGetPairFormat(t *testing.T) { var c Config _, err := c.GetPairFormat("meow", asset.Spot) if err == nil { - t.Error("non-existent exchange should throw an error") + t.Error("Expected error from non-existent exchange") } c.Exchanges = append(c.Exchanges, @@ -823,7 +823,7 @@ func TestGetPairFormat(t *testing.T) { ) _, err = c.GetPairFormat(testFakeExchangeName, asset.Spot) if err == nil { - t.Error("nil pair manager should throw an error") + t.Error("Expected error from nil pair manager") } c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ @@ -840,12 +840,12 @@ func TestGetPairFormat(t *testing.T) { } _, err = c.GetPairFormat(testFakeExchangeName, asset.Item("invalid")) if err == nil { - t.Error("non-existent asset item should throw an error") + t.Error("Expected error from non-existent asset item") } _, err = c.GetPairFormat(testFakeExchangeName, asset.Futures) if err == nil { - t.Error("valid but non supported asset type should throw an error") + t.Error("Expected error from valid but non supported asset type") } var p currency.PairFormat @@ -862,7 +862,7 @@ func TestGetPairFormat(t *testing.T) { c.Exchanges[0].CurrencyPairs.UseGlobalFormat = false _, err = c.GetPairFormat(testFakeExchangeName, asset.Spot) if err == nil { - t.Error(err) + t.Error("Expected error") } c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ @@ -889,7 +889,7 @@ func TestGetAvailablePairs(t *testing.T) { var c Config _, err := c.GetAvailablePairs("asdf", asset.Spot) if err == nil { - t.Error("non-existent exchange should throw an error") + t.Error("Expected error from non-existent exchange") } c.Exchanges = append(c.Exchanges, @@ -905,7 +905,7 @@ func TestGetAvailablePairs(t *testing.T) { _, err = c.GetAvailablePairs(testFakeExchangeName, asset.Spot) if err == nil { - t.Error("nil pair manager should throw an error") + t.Error("Expected error from nil pair manager") } c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ @@ -918,7 +918,7 @@ func TestGetAvailablePairs(t *testing.T) { } _, err = c.GetAvailablePairs(testFakeExchangeName, asset.Spot) if err != nil { - t.Error("nil pairs should return a nil error") + t.Error("Expected error from nil pairs") } c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{ @@ -936,7 +936,7 @@ func TestGetEnabledPairs(t *testing.T) { var c Config _, err := c.GetEnabledPairs("asdf", asset.Spot) if err == nil { - t.Error("non-existent exchange should throw an error") + t.Error("Expected error from non-existent exchange") } c.Exchanges = append(c.Exchanges, @@ -952,7 +952,7 @@ func TestGetEnabledPairs(t *testing.T) { _, err = c.GetEnabledPairs(testFakeExchangeName, asset.Spot) if err == nil { - t.Error("nil pair manager should throw an error") + t.Error("Expected error from nil pair manager") } c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ @@ -979,47 +979,47 @@ func TestGetEnabledPairs(t *testing.T) { func TestGetEnabledExchanges(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { t.Errorf( - "Test failed. TestGetEnabledExchanges. LoadConfig Error: %s", err.Error(), + "TestGetEnabledExchanges. LoadConfig Error: %s", err.Error(), ) } exchanges := cfg.GetEnabledExchanges() if len(exchanges) != defaultEnabledExchanges { t.Error( - "Test failed. TestGetEnabledExchanges. Enabled exchanges value mismatch", + "TestGetEnabledExchanges. Enabled exchanges value mismatch", ) } if !common.StringDataCompare(exchanges, "Bitfinex") { t.Error( - "Test failed. TestGetEnabledExchanges. Expected exchange Bitfinex not found", + "TestGetEnabledExchanges. Expected exchange Bitfinex not found", ) } } func TestGetDisabledExchanges(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { t.Errorf( - "Test failed. TestGetDisabledExchanges. LoadConfig Error: %s", err.Error(), + "TestGetDisabledExchanges. LoadConfig Error: %s", err.Error(), ) } exchanges := cfg.GetDisabledExchanges() if len(exchanges) != 0 { t.Error( - "Test failed. TestGetDisabledExchanges. Enabled exchanges value mismatch", + "TestGetDisabledExchanges. Enabled exchanges value mismatch", ) } exchCfg, err := cfg.GetExchangeConfig("Bitfinex") if err != nil { t.Errorf( - "Test failed. TestGetDisabledExchanges. GetExchangeConfig Error: %s", err.Error(), + "TestGetDisabledExchanges. GetExchangeConfig Error: %s", err.Error(), ) } @@ -1027,150 +1027,150 @@ func TestGetDisabledExchanges(t *testing.T) { err = cfg.UpdateExchangeConfig(exchCfg) if err != nil { t.Errorf( - "Test failed. TestGetDisabledExchanges. UpdateExchangeConfig Error: %s", err.Error(), + "TestGetDisabledExchanges. UpdateExchangeConfig Error: %s", err.Error(), ) } if len(cfg.GetDisabledExchanges()) != 1 { t.Error( - "Test failed. TestGetDisabledExchanges. Enabled exchanges value mismatch", + "TestGetDisabledExchanges. Enabled exchanges value mismatch", ) } } func TestCountEnabledExchanges(t *testing.T) { GetConfigEnabledExchanges := GetConfig() - err := GetConfigEnabledExchanges.LoadConfig(ConfigTestFile, true) + err := GetConfigEnabledExchanges.LoadConfig(TestFile, true) if err != nil { t.Error( - "Test failed. GetConfigEnabledExchanges load config error: " + err.Error(), + "GetConfigEnabledExchanges load config error: " + err.Error(), ) } enabledExch := GetConfigEnabledExchanges.CountEnabledExchanges() if enabledExch != defaultEnabledExchanges { - t.Errorf("Test failed. Expected %v, Received %v", defaultEnabledExchanges, enabledExch) + t.Errorf("Expected %v, Received %v", defaultEnabledExchanges, enabledExch) } } func TestGetConfigCurrencyPairFormat(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { t.Errorf( - "Test failed. TestGetConfigCurrencyPairFormat. LoadConfig Error: %s", err.Error(), + "TestGetConfigCurrencyPairFormat. LoadConfig Error: %s", err.Error(), ) } _, err = cfg.GetConfigCurrencyPairFormat("asdasdasd") if err == nil { t.Errorf( - "Test failed. TestGetRequestCurrencyPairFormat. Non-existent exchange returned nil error", + "TestGetRequestCurrencyPairFormat. Non-existent exchange returned nil error", ) } exchFmt, err := cfg.GetConfigCurrencyPairFormat("Yobit") if err != nil { - t.Errorf("Test failed. TestGetConfigCurrencyPairFormat err: %s", err) + t.Errorf("TestGetConfigCurrencyPairFormat err: %s", err) } if !exchFmt.Uppercase || exchFmt.Delimiter != "_" { t.Errorf( - "Test failed. TestGetConfigCurrencyPairFormat. Invalid values", + "TestGetConfigCurrencyPairFormat. Invalid values", ) } } func TestGetRequestCurrencyPairFormat(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { t.Errorf( - "Test failed. TestGetRequestCurrencyPairFormat. LoadConfig Error: %s", err.Error(), + "TestGetRequestCurrencyPairFormat. LoadConfig Error: %s", err.Error(), ) } _, err = cfg.GetRequestCurrencyPairFormat("asdasdasd") if err == nil { t.Errorf( - "Test failed. TestGetRequestCurrencyPairFormat. Non-existent exchange returned nil error", + "TestGetRequestCurrencyPairFormat. Non-existent exchange returned nil error", ) } exchFmt, err := cfg.GetRequestCurrencyPairFormat("Yobit") if err != nil { - t.Errorf("Test failed. TestGetRequestCurrencyPairFormat. Err: %s", err) + t.Errorf("TestGetRequestCurrencyPairFormat. Err: %s", err) } if exchFmt.Uppercase || exchFmt.Delimiter != "_" || exchFmt.Separator != "-" { t.Errorf( - "Test failed. TestGetRequestCurrencyPairFormat. Invalid values", + "TestGetRequestCurrencyPairFormat. Invalid values", ) } } func TestGetCurrencyPairDisplayConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { t.Errorf( - "Test failed. GetCurrencyPairDisplayConfig. LoadConfig Error: %s", err.Error(), + "GetCurrencyPairDisplayConfig. LoadConfig Error: %s", err.Error(), ) } settings := cfg.GetCurrencyPairDisplayConfig() if settings.Delimiter != "-" || !settings.Uppercase { t.Errorf( - "Test failed. GetCurrencyPairDisplayConfi. Invalid values", + "GetCurrencyPairDisplayConfi. Invalid values", ) } } func TestGetAllExchangeConfigs(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. GetAllExchangeConfigs. LoadConfig error", err) + t.Error("GetAllExchangeConfigs. LoadConfig error", err) } if len(cfg.GetAllExchangeConfigs()) < 26 { - t.Error("Test failed. GetAllExchangeConfigs error") + t.Error("GetAllExchangeConfigs error") } } func TestGetExchangeConfig(t *testing.T) { GetExchangeConfig := GetConfig() - err := GetExchangeConfig.LoadConfig(ConfigTestFile, true) + err := GetExchangeConfig.LoadConfig(TestFile, true) if err != nil { t.Errorf( - "Test failed. GetExchangeConfig.LoadConfig Error: %s", err.Error(), + "GetExchangeConfig.LoadConfig Error: %s", err.Error(), ) } _, err = GetExchangeConfig.GetExchangeConfig("ANX") if err != nil { - t.Errorf("Test failed. GetExchangeConfig.GetExchangeConfig Error: %s", + t.Errorf("GetExchangeConfig.GetExchangeConfig Error: %s", err.Error()) } _, err = GetExchangeConfig.GetExchangeConfig("Testy") if err == nil { - t.Error("Test failed. GetExchangeConfig.GetExchangeConfig Error") + t.Error("GetExchangeConfig.GetExchangeConfig Expected error") } } func TestGetForexProviderConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. GetForexProviderConfig. LoadConfig error", err) + t.Error("GetForexProviderConfig. LoadConfig error", err) } _, err = cfg.GetForexProviderConfig("Fixer") if err != nil { - t.Error("Test failed. GetForexProviderConfig error", err) + t.Error("GetForexProviderConfig error", err) } _, err = cfg.GetForexProviderConfig("this is not a forex provider") if err == nil { - t.Error("Test failed. GetForexProviderConfig no error for invalid provider") + t.Error("GetForexProviderConfig no error for invalid provider") } } func TestGetForexProvidersConfig(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { t.Error(err) } @@ -1182,13 +1182,13 @@ func TestGetForexProvidersConfig(t *testing.T) { func TestGetPrimaryForexProvider(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. GetPrimaryForexProvider. LoadConfig error", err) + t.Error("GetPrimaryForexProvider. LoadConfig error", err) } primary := cfg.GetPrimaryForexProvider() if primary == "" { - t.Error("Test failed. GetPrimaryForexProvider error") + t.Error("GetPrimaryForexProvider error") } for i := range cfg.Currency.ForexProviders { @@ -1196,13 +1196,13 @@ func TestGetPrimaryForexProvider(t *testing.T) { } primary = cfg.GetPrimaryForexProvider() if primary != "" { - t.Error("Test failed. GetPrimaryForexProvider error, expected nil got:", primary) + t.Error("GetPrimaryForexProvider error, expected nil got:", primary) } } func TestUpdateExchangeConfig(t *testing.T) { c := GetConfig() - err := c.LoadConfig(ConfigTestFile, true) + err := c.LoadConfig(TestFile, true) if err != nil { t.Error(err) } @@ -1210,7 +1210,7 @@ func TestUpdateExchangeConfig(t *testing.T) { e := &ExchangeConfig{} err = c.UpdateExchangeConfig(e) if err == nil { - t.Error("non-existent exchange should throw an error") + t.Error("Expected error from non-existent exchange") } e, err = c.GetExchangeConfig("ANX") @@ -1232,7 +1232,7 @@ func TestCheckExchangeConfigValues(t *testing.T) { t.Error("nil exchanges should throw an err") } - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { t.Fatal(err) } @@ -1538,7 +1538,7 @@ func TestCheckExchangeConfigValues(t *testing.T) { cfg.CheckExchangeConfigValues() if cfg.Exchanges[0].Enabled { t.Errorf( - "Test failed. Exchange with no name should be empty", + "Exchange with no name should be empty", ) } @@ -1547,22 +1547,22 @@ func TestCheckExchangeConfigValues(t *testing.T) { cfg.Exchanges[0].Enabled = false err = cfg.CheckExchangeConfigValues() if err == nil { - t.Error("no enabled exchanges should throw an error") + t.Error("Expected error from no enabled exchanges") } } func TestRetrieveConfigCurrencyPairs(t *testing.T) { cfg := GetConfig() - err := cfg.LoadConfig(ConfigTestFile, true) + err := cfg.LoadConfig(TestFile, true) if err != nil { t.Errorf( - "Test failed. TestRetrieveConfigCurrencyPairs.LoadConfig: %s", err.Error(), + "TestRetrieveConfigCurrencyPairs.LoadConfig: %s", err.Error(), ) } err = cfg.RetrieveConfigCurrencyPairs(true, asset.Spot) if err != nil { t.Errorf( - "Test failed. TestRetrieveConfigCurrencyPairs.RetrieveConfigCurrencyPairs: %s", + "TestRetrieveConfigCurrencyPairs.RetrieveConfigCurrencyPairs: %s", err.Error(), ) } @@ -1570,7 +1570,7 @@ func TestRetrieveConfigCurrencyPairs(t *testing.T) { err = cfg.RetrieveConfigCurrencyPairs(false, asset.Spot) if err != nil { t.Errorf( - "Test failed. TestRetrieveConfigCurrencyPairs.RetrieveConfigCurrencyPairs: %s", + "TestRetrieveConfigCurrencyPairs.RetrieveConfigCurrencyPairs: %s", err.Error(), ) } @@ -1578,44 +1578,44 @@ func TestRetrieveConfigCurrencyPairs(t *testing.T) { func TestReadConfig(t *testing.T) { readConfig := GetConfig() - err := readConfig.ReadConfig(ConfigTestFile, true) + err := readConfig.ReadConfig(TestFile, true) if err != nil { - t.Errorf("Test failed. TestReadConfig %s", err.Error()) + t.Errorf("TestReadConfig %s", err.Error()) } err = readConfig.ReadConfig("bla", true) if err == nil { - t.Error("Test failed. TestReadConfig error cannot be nil") + t.Error("TestReadConfig error cannot be nil") } err = readConfig.ReadConfig("", true) if err != nil { - t.Error("Test failed. TestReadConfig error") + t.Error("TestReadConfig error") } } func TestLoadConfig(t *testing.T) { loadConfig := GetConfig() - err := loadConfig.LoadConfig(ConfigTestFile, true) + err := loadConfig.LoadConfig(TestFile, true) if err != nil { - t.Error("Test failed. TestLoadConfig " + err.Error()) + t.Error("TestLoadConfig " + err.Error()) } err = loadConfig.LoadConfig("testy", true) if err == nil { - t.Error("Test failed. TestLoadConfig ") + t.Error("TestLoadConfig Expected error") } } func TestSaveConfig(t *testing.T) { saveConfig := GetConfig() - err := saveConfig.LoadConfig(ConfigTestFile, true) + err := saveConfig.LoadConfig(TestFile, true) if err != nil { - t.Errorf("Test failed. TestSaveConfig.LoadConfig: %s", err.Error()) + t.Errorf("TestSaveConfig.LoadConfig: %s", err.Error()) } - err2 := saveConfig.SaveConfig(ConfigTestFile, true) + err2 := saveConfig.SaveConfig(TestFile, true) if err2 != nil { - t.Errorf("Test failed. TestSaveConfig.SaveConfig, %s", err2.Error()) + t.Errorf("TestSaveConfig.SaveConfig, %s", err2.Error()) } } @@ -1641,13 +1641,13 @@ func TestGetFilePath(t *testing.T) { expected := "blah.json" result, _ := GetFilePath("blah.json") if result != "blah.json" { - t.Errorf("Test failed. TestGetFilePath: expected %s got %s", expected, result) + t.Errorf("TestGetFilePath: expected %s got %s", expected, result) } - expected = ConfigTestFile + expected = TestFile result, _ = GetFilePath("") if result != expected { - t.Errorf("Test failed. TestGetFilePath: expected %s got %s", expected, result) + t.Errorf("TestGetFilePath: expected %s got %s", expected, result) } testBypass = true } @@ -1692,9 +1692,9 @@ func TestCheckRemoteControlConfig(t *testing.T) { func TestCheckConfig(t *testing.T) { var c Config - err := c.LoadConfig(ConfigTestFile, true) + err := c.LoadConfig(TestFile, true) if err != nil { - t.Errorf("Test failed. %s", err) + t.Errorf("%s", err) } err = c.CheckConfig() @@ -1705,42 +1705,42 @@ func TestCheckConfig(t *testing.T) { func TestUpdateConfig(t *testing.T) { var c Config - err := c.LoadConfig(ConfigTestFile, true) + err := c.LoadConfig(TestFile, true) if err != nil { - t.Errorf("Test failed. %s", err) + t.Errorf("%s", err) } newCfg := c - err = c.UpdateConfig(ConfigTestFile, &newCfg, true) + err = c.UpdateConfig(TestFile, &newCfg, true) if err != nil { - t.Fatalf("Test failed. %s", err) + t.Fatalf("%s", err) } err = c.UpdateConfig("//non-existantpath\\", &newCfg, true) if err == nil { - t.Fatalf("Test failed. Error should of been thrown for invalid path") + t.Fatalf("Error should have been thrown for invalid path") } newCfg.Currency.Cryptocurrencies = currency.NewCurrenciesFromStringArray([]string{""}) - err = c.UpdateConfig(ConfigTestFile, &newCfg, true) + err = c.UpdateConfig(TestFile, &newCfg, true) if err != nil { - t.Errorf("Test failed. %s", err) + t.Errorf("%s", err) } if c.Currency.Cryptocurrencies.Join() == "" { - t.Fatalf("Test failed. Cryptocurrencies should have been repopulated") + t.Fatalf("Cryptocurrencies should have been repopulated") } } func BenchmarkUpdateConfig(b *testing.B) { var c Config - err := c.LoadConfig(ConfigTestFile, true) + err := c.LoadConfig(TestFile, true) if err != nil { b.Errorf("Unable to benchmark UpdateConfig(): %s", err) } newCfg := c for i := 0; i < b.N; i++ { - _ = c.UpdateConfig(ConfigTestFile, &newCfg, true) + _ = c.UpdateConfig(TestFile, &newCfg, true) } } @@ -1773,7 +1773,7 @@ func TestCheckLoggerConfig(t *testing.T) { t.Error("unexpected result") } - c.LoadConfig(ConfigTestFile, true) + c.LoadConfig(TestFile, true) err = c.CheckLoggerConfig() if err != nil { t.Errorf("Failed to create logger with user settings: reason: %v", err) @@ -1784,14 +1784,14 @@ func TestDisableNTPCheck(t *testing.T) { t.Parallel() c := GetConfig() - err := c.LoadConfig(ConfigTestFile, true) + err := c.LoadConfig(TestFile, true) if err != nil { t.Fatal(err) } warn, err := c.DisableNTPCheck(strings.NewReader("w\n")) if err != nil { - t.Fatalf("test failed to create ntpclient failed reason: %v", err) + t.Fatalf("to create ntpclient failed reason: %v", err) } if warn != "Time sync has been set to warn only" { @@ -1851,11 +1851,11 @@ func TestCheckNTPConfig(t *testing.T) { c.CheckNTPConfig() _, err := ntpclient.NTPClient(c.NTPClient.Pool) if err != nil { - t.Fatalf("test failed to create ntpclient failed reason: %v", err) + t.Fatalf("to create ntpclient failed reason: %v", err) } if c.NTPClient.Pool[0] != "pool.ntp.org:123" { - t.Error("ntpclient with no valid pool should default to pool.ntp.org ") + t.Error("ntpclient with no valid pool should default to pool.ntp.org") } if c.NTPClient.AllowedDifference == nil { @@ -1866,3 +1866,66 @@ func TestCheckNTPConfig(t *testing.T) { t.Error("ntpclient with nil allowednegativedifference should default to sane value") } } + +func TestCheckCurrencyConfigValues(t *testing.T) { + c := GetConfig() + c.Currency.ForexProviders = nil + c.Currency.CryptocurrencyProvider = CryptocurrencyProvider{} + err := c.CheckCurrencyConfigValues() + if err != nil { + t.Error(err) + } + if c.Currency.ForexProviders == nil { + t.Error("Failed to populate c.Currency.ForexProviders") + } + if c.Currency.CryptocurrencyProvider.APIkey != DefaultUnsetAPIKey { + t.Error("Failed to set the api key to the default key") + } + if c.Currency.CryptocurrencyProvider.Name != "CoinMarketCap" { + t.Error("Failed to set the c.Currency.CryptocurrencyProvider.Name") + } + + c.Currency.ForexProviders[0].Enabled = true + c.Currency.ForexProviders[0].Name = "CurrencyConverter" + c.Currency.ForexProviders[0].PrimaryProvider = true + c.Currency.Cryptocurrencies = nil + c.Cryptocurrencies = nil + c.Currency.CurrencyPairFormat = nil + c.CurrencyPairFormat = &CurrencyPairFormatConfig{ + Uppercase: true, + } + c.Currency.FiatDisplayCurrency = currency.Code{} + c.FiatDisplayCurrency = ¤cy.BTC + c.Currency.CryptocurrencyProvider.Enabled = true + err = c.CheckCurrencyConfigValues() + if err != nil { + t.Error(err) + } + if c.Currency.ForexProviders[0].Enabled { + t.Error("Failed to disable invalid forex provider") + } + if !c.Currency.CurrencyPairFormat.Uppercase { + t.Error("Failed to apply c.CurrencyPairFormat format to c.Currency.CurrencyPairFormat") + } + + c.Currency.CryptocurrencyProvider.Enabled = false + c.Currency.CryptocurrencyProvider.APIkey = "" + c.Currency.CryptocurrencyProvider.AccountPlan = "" + c.FiatDisplayCurrency = ¤cy.BTC + c.Currency.ForexProviders[0].Enabled = true + c.Currency.ForexProviders[0].Name = "Name" + c.Currency.ForexProviders[0].PrimaryProvider = true + c.Currency.Cryptocurrencies = currency.Currencies{} + c.Cryptocurrencies = ¤cy.Currencies{} + err = c.CheckCurrencyConfigValues() + if err != nil { + t.Error(err) + } + if c.FiatDisplayCurrency != nil { + t.Error("Failed to clear c.FiatDisplayCurrency") + } + if c.Currency.CryptocurrencyProvider.APIkey != DefaultUnsetAPIKey || + c.Currency.CryptocurrencyProvider.AccountPlan != DefaultUnsetAccountPlan { + t.Error("Failed to set CryptocurrencyProvider.APIkey and AccountPlan") + } +} diff --git a/config/config_types.go b/config/config_types.go index 5c07f39e..6e629610 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -3,15 +3,77 @@ package config import ( "fmt" "strings" + "sync" "time" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" "github.com/thrasher-corp/gocryptotrader/database" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" log "github.com/thrasher-corp/gocryptotrader/logger" "github.com/thrasher-corp/gocryptotrader/portfolio" ) +// Constants declared here are filename strings and test strings +const ( + FXProviderFixer = "fixer" + EncryptedFile = "config.dat" + File = "config.json" + TestFile = "../testdata/configtest.json" + fileEncryptionPrompt = 0 + fileEncryptionEnabled = 1 + fileEncryptionDisabled = -1 + pairsLastUpdatedWarningThreshold = 30 // 30 days + defaultHTTPTimeout = time.Second * 15 + defaultWebsocketResponseCheckTimeout = time.Millisecond * 30 + defaultWebsocketResponseMaxLimit = time.Second * 7 + defaultWebsocketOrderbookBufferLimit = 5 + defaultWebsocketTrafficTimeout = time.Second * 30 + maxAuthFailures = 3 + defaultNTPAllowedDifference = 50000000 + defaultNTPAllowedNegativeDifference = 50000000 + DefaultAPIKey = "Key" + DefaultAPISecret = "Secret" + DefaultAPIClientID = "ClientID" +) + +// Constants here hold some messages +const ( + ErrExchangeNameEmpty = "exchange #%d name is empty" + ErrExchangeAvailablePairsEmpty = "exchange %s available pairs is empty" + ErrExchangeEnabledPairsEmpty = "exchange %s enabled pairs is empty" + ErrExchangeBaseCurrenciesEmpty = "exchange %s base currencies is empty" + ErrExchangeNotFound = "exchange %s not found" + ErrNoEnabledExchanges = "no exchanges enabled" + ErrCryptocurrenciesEmpty = "cryptocurrencies variable is empty" + ErrFailureOpeningConfig = "fatal error opening %s file. Error: %s" + ErrCheckingConfigValues = "fatal error checking config values. Error: %s" + ErrSavingConfigBytesMismatch = "config file %q bytes comparison doesn't match, read %s expected %s" + WarningWebserverCredentialValuesEmpty = "webserver support disabled due to empty Username/Password values" + WarningWebserverListenAddressInvalid = "webserver support disabled due to invalid listen address" + WarningExchangeAuthAPIDefaultOrEmptyValues = "exchange %s authenticated API support disabled due to default/empty APIKey/Secret/ClientID values" + WarningPairsLastUpdatedThresholdExceeded = "exchange %s last manual update of available currency pairs has exceeded %d days. Manual update required!" +) + +// Constants here define unset default values displayed in the config.json +// file +const ( + APIURLNonDefaultMessage = "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API" + WebsocketURLNonDefaultMessage = "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + DefaultUnsetAPIKey = "Key" + DefaultUnsetAPISecret = "Secret" + DefaultUnsetAccountPlan = "accountPlan" + DefaultForexProviderExchangeRatesAPI = "ExchangeRates" +) + +// Variables here are used for configuration +var ( + Cfg Config + IsInitialSetup bool + testBypass bool + m sync.Mutex +) + // Config is the overarching object that holds all the information for // prestart management of Portfolio, Communications, Webserver and Enabled // Exchanges @@ -313,39 +375,12 @@ type TelegramConfig struct { VerificationToken string `json:"verificationToken"` } -// ProtocolFeaturesConfig holds all variables for the exchanges supported features -// for a protocol (e.g REST or Websocket) -type ProtocolFeaturesConfig struct { - TickerBatching bool `json:"tickerBatching,omitempty"` - AutoPairUpdates bool `json:"autoPairUpdates,omitempty"` - AccountBalance bool `json:"accountBalance,omitempty"` - CryptoDeposit bool `json:"cryptoDeposit,omitempty"` - CryptoWithdrawal uint32 `json:"cryptoWithdrawal,omitempty"` - FiatWithdraw bool `json:"fiatWithdraw,omitempty"` - GetOrder bool `json:"getOrder,omitempty"` - GetOrders bool `json:"getOrders,omitempty"` - CancelOrders bool `json:"cancelOrders,omitempty"` - CancelOrder bool `json:"cancelOrder,omitempty"` - SubmitOrder bool `json:"submitOrder,omitempty"` - SubmitOrders bool `json:"submitOrders,omitempty"` - ModifyOrder bool `json:"modifyOrder,omitempty"` - DepositHistory bool `json:"depositHistory,omitempty"` - WithdrawalHistory bool `json:"withdrawalHistory,omitempty"` - TradeHistory bool `json:"tradeHistory,omitempty"` - UserTradeHistory bool `json:"userTradeHistory,omitempty"` - TradeFee bool `json:"tradeFee,omitempty"` - FiatDepositFee bool `json:"fiatDepositFee,omitempty"` - FiatWithdrawalFee bool `json:"fiatWithdrawalFee,omitempty"` - CryptoDepositFee bool `json:"cryptoDepositFee,omitempty"` - CryptoWithdrawalFee bool `json:"cryptoWithdrawalFee,omitempty"` -} - // FeaturesSupportedConfig stores the exchanges supported features type FeaturesSupportedConfig struct { - REST bool `json:"restAPI"` - RESTCapabilities ProtocolFeaturesConfig `json:"restCapabilities,omitempty"` - Websocket bool `json:"websocketAPI"` - WebsocketCapabilities ProtocolFeaturesConfig `json:"websocketCapabilities,omitempty"` + REST bool `json:"restAPI"` + RESTCapabilities protocol.Features `json:"restCapabilities,omitempty"` + Websocket bool `json:"websocketAPI"` + WebsocketCapabilities protocol.Features `json:"websocketCapabilities,omitempty"` } // FeaturesEnabledConfig stores the exchanges enabled features diff --git a/connchecker/connchecker_test.go b/connchecker/connchecker_test.go index 7b1b27b6..1ff3dfd7 100644 --- a/connchecker/connchecker_test.go +++ b/connchecker/connchecker_test.go @@ -9,22 +9,22 @@ func TestConnection(t *testing.T) { faultyHost := []string{"faultyHost"} _, err := New(faultyDomain, nil, 100000) if err == nil { - t.Fatal("Test Failed - New error cannot be nil") + t.Fatal("New error cannot be nil") } _, err = New(DefaultDNSList, nil, 100000) if err != nil { - t.Fatal("Test Failed - New error", err) + t.Fatal("New error", err) } _, err = New(nil, faultyHost, 100000) if err != nil { - t.Fatal("Test Failed - New error cannot be nil", err) + t.Fatal("New error cannot be nil", err) } c, err := New(nil, nil, 0) if err != nil { - t.Fatal("Test Failed - New error", err) + t.Fatal("New error", err) } if !c.IsConnected() { diff --git a/currency/code.go b/currency/code.go index 90f19ba6..e1991530 100644 --- a/currency/code.go +++ b/currency/code.go @@ -4,31 +4,10 @@ import ( "errors" "fmt" "strings" - "sync" - "time" "github.com/thrasher-corp/gocryptotrader/common" ) -// Bitmasks const for currency rolls -const ( - Unset Role = 0 - Fiat Role = 1 << (iota - 1) - Cryptocurrency - Token - Contract - - UnsetRollString = "roleUnset" - FiatCurrencyString = "fiatCurrency" - CryptocurrencyString = "cryptocurrency" - TokenString = "token" - ContractString = "contract" -) - -// Role defines a bitmask for the full currency rolls either; fiat, -// cryptocurrency, token, or contract -type Role uint8 - func (r Role) String() string { switch r { case Unset: @@ -46,7 +25,7 @@ func (r Role) String() string { } } -// MarshalJSON conforms Roll to the marshaler interface +// MarshalJSON conforms Roll to the marshaller interface func (r Role) MarshalJSON() ([]byte, error) { return common.JSONEncode(r.String()) } @@ -77,13 +56,6 @@ func (r *Role) UnmarshalJSON(d []byte) error { return nil } -// BaseCodes defines a basket of bare currency codes -type BaseCodes struct { - Items []*Item - LastMainUpdate time.Time - mtx sync.Mutex -} - // HasData returns true if the type contains data func (b *BaseCodes) HasData() bool { b.mtx.Lock() @@ -107,7 +79,7 @@ func (b *BaseCodes) GetFullCurrencyData() (File, error) { case Contract: file.Contracts = append(file.Contracts, *i) default: - return file, errors.New("roll undefined") + return file, errors.New("role undefined") } } @@ -396,24 +368,6 @@ func NewCode(c string) Code { return storage.ValidateCode(c) } -// Code defines an ISO 4217 fiat currency or unofficial cryptocurrency code -// string -type Code struct { - Item *Item - UpperCase bool -} - -// Item defines a sub type containing the main attributes of a designated -// currency code pointer -type Item struct { - ID int `json:"id"` - FullName string `json:"fullName"` - Symbol string `json:"symbol"` - Role Role `json:"role"` - AssocChain string `json:"associatedBlockchain"` - AssocExchange []string `json:"associatedExchanges"` -} - // String conforms to the stringer interface func (i *Item) String() string { return i.FullName @@ -496,1608 +450,3 @@ func (c Code) IsFiatCurrency() bool { func (c Code) IsCryptocurrency() bool { return storage.IsCryptocurrency(c) } - -// Const declarations for individual currencies/tokens/fiat -// An ever growing list. Cares not for equivalence, just is -var ( - BTC = NewCode("BTC") - LTC = NewCode("LTC") - ETH = NewCode("ETH") - XRP = NewCode("XRP") - BCH = NewCode("BCH") - EOS = NewCode("EOS") - XLM = NewCode("XLM") - USDT = NewCode("USDT") - ADA = NewCode("ADA") - XMR = NewCode("XMR") - TRX = NewCode("TRX") - MIOTA = NewCode("MIOTA") - DASH = NewCode("DASH") - BNB = NewCode("BNB") - NEO = NewCode("NEO") - ETC = NewCode("ETC") - XEM = NewCode("XEM") - XTZ = NewCode("XTZ") - VET = NewCode("VET") - DOGE = NewCode("DOGE") - ZEC = NewCode("ZEC") - OMG = NewCode("OMG") - BTG = NewCode("BTG") - MKR = NewCode("MKR") - BCN = NewCode("BCN") - ONT = NewCode("ONT") - ZRX = NewCode("ZRX") - LSK = NewCode("LSK") - DCR = NewCode("DCR") - QTUM = NewCode("QTUM") - BCD = NewCode("BCD") - BTS = NewCode("BTS") - NANO = NewCode("NANO") - ZIL = NewCode("ZIL") - SC = NewCode("SC") - DGB = NewCode("DGB") - ICX = NewCode("ICX") - STEEM = NewCode("STEEM") - AE = NewCode("AE") - XVG = NewCode("XVG") - WAVES = NewCode("WAVES") - NPXS = NewCode("NPXS") - ETN = NewCode("ETN") - BTM = NewCode("BTM") - BAT = NewCode("BAT") - ETP = NewCode("ETP") - HOT = NewCode("HOT") - STRAT = NewCode("STRAT") // nolint: misspell - GNT = NewCode("GNT") - REP = NewCode("REP") - SNT = NewCode("SNT") - PPT = NewCode("PPT") - KMD = NewCode("KMD") - TUSD = NewCode("TUSD") - CNX = NewCode("CNX") - LINK = NewCode("LINK") - WTC = NewCode("WTC") - ARDR = NewCode("ARDR") - WAN = NewCode("WAN") - MITH = NewCode("MITH") - RDD = NewCode("RDD") - IOST = NewCode("IOST") - IOT = NewCode("IOT") - KCS = NewCode("KCS") - MAID = NewCode("MAID") - XET = NewCode("XET") - MOAC = NewCode("MOAC") - HC = NewCode("HC") - AION = NewCode("AION") - AOA = NewCode("AOA") - HT = NewCode("HT") - ELF = NewCode("ELF") - LRC = NewCode("LRC") - BNT = NewCode("BNT") - CMT = NewCode("CMT") - DGD = NewCode("DGD") - DCN = NewCode("DCN") - FUN = NewCode("FUN") - GXS = NewCode("GXS") - DROP = NewCode("DROP") - MANA = NewCode("MANA") - PAY = NewCode("PAY") - MCO = NewCode("MCO") - THETA = NewCode("THETA") - NXT = NewCode("NXT") - NOAH = NewCode("NOAH") - LOOM = NewCode("LOOM") - POWR = NewCode("POWR") - WAX = NewCode("WAX") - ELA = NewCode("ELA") - PIVX = NewCode("PIVX") - XIN = NewCode("XIN") - DAI = NewCode("DAI") - BTCP = NewCode("BTCP") - NEXO = NewCode("NEXO") - XBT = NewCode("XBT") - SAN = NewCode("SAN") - GAS = NewCode("GAS") - BCC = NewCode("BCC") - HCC = NewCode("HCC") - OAX = NewCode("OAX") - DNT = NewCode("DNT") - ICN = NewCode("ICN") - LLT = NewCode("LLT") - YOYO = NewCode("YOYO") - SNGLS = NewCode("SNGLS") - BQX = NewCode("BQX") - KNC = NewCode("KNC") - SNM = NewCode("SNM") - CTR = NewCode("CTR") - SALT = NewCode("SALT") - MDA = NewCode("MDA") - IOTA = NewCode("IOTA") - SUB = NewCode("SUB") - MTL = NewCode("MTL") - MTH = NewCode("MTH") - ENG = NewCode("ENG") - AST = NewCode("AST") - CLN = NewCode("CLN") - EDG = NewCode("EDG") - FIRST = NewCode("1ST") - GOLOS = NewCode("GOLOS") - ANT = NewCode("ANT") - GBG = NewCode("GBG") - HMQ = NewCode("HMQ") - INCNT = NewCode("INCNT") - ACE = NewCode("ACE") - ACT = NewCode("ACT") - AAC = NewCode("AAC") - AIDOC = NewCode("AIDOC") - SOC = NewCode("SOC") - ATL = NewCode("ATL") - AVT = NewCode("AVT") - BKX = NewCode("BKX") - BEC = NewCode("BEC") - VEE = NewCode("VEE") - PTOY = NewCode("PTOY") - CAG = NewCode("CAG") - CIC = NewCode("CIC") - CBT = NewCode("CBT") - CAN = NewCode("CAN") - DAT = NewCode("DAT") - DNA = NewCode("DNA") - INT = NewCode("INT") - IPC = NewCode("IPC") - ILA = NewCode("ILA") - LIGHT = NewCode("LIGHT") - MAG = NewCode("MAG") - AMM = NewCode("AMM") - MOF = NewCode("MOF") - MGC = NewCode("MGC") - OF = NewCode("OF") - LA = NewCode("LA") - LEV = NewCode("LEV") - NGC = NewCode("NGC") - OKB = NewCode("OKB") - MOT = NewCode("MOT") - PRA = NewCode("PRA") - R = NewCode("R") - SSC = NewCode("SSC") - SHOW = NewCode("SHOW") - SPF = NewCode("SPF") - SNC = NewCode("SNC") - SWFTC = NewCode("SWFTC") - TRA = NewCode("TRA") - TOPC = NewCode("TOPC") - TRIO = NewCode("TRIO") - QVT = NewCode("QVT") - UCT = NewCode("UCT") - UKG = NewCode("UKG") - UTK = NewCode("UTK") - VIU = NewCode("VIU") - WFEE = NewCode("WFEE") - WRC = NewCode("WRC") - UGC = NewCode("UGC") - YEE = NewCode("YEE") - YOYOW = NewCode("YOYOW") - ZIP = NewCode("ZIP") - READ = NewCode("READ") - RCT = NewCode("RCT") - REF = NewCode("REF") - XUC = NewCode("XUC") - FAIR = NewCode("FAIR") - GSC = NewCode("GSC") - HMC = NewCode("HMC") - PLU = NewCode("PLU") - PRO = NewCode("PRO") - QRL = NewCode("QRL") - REN = NewCode("REN") - ROUND = NewCode("ROUND") - SRN = NewCode("SRN") - XID = NewCode("XID") - SBD = NewCode("SBD") - TAAS = NewCode("TAAS") - TKN = NewCode("TKN") - VEN = NewCode("VEN") - VSL = NewCode("VSL") - TRST = NewCode("TRST") - XXX = NewCode("XXX") - IND = NewCode("IND") - LDC = NewCode("LDC") - GUP = NewCode("GUP") - MGO = NewCode("MGO") - MYST = NewCode("MYST") - NEU = NewCode("NEU") - NET = NewCode("NET") - BMC = NewCode("BMC") - BCAP = NewCode("BCAP") - TIME = NewCode("TIME") - CFI = NewCode("CFI") - EVX = NewCode("EVX") - REQ = NewCode("REQ") - VIB = NewCode("VIB") - ARK = NewCode("ARK") - MOD = NewCode("MOD") - ENJ = NewCode("ENJ") - STORJ = NewCode("STORJ") - RCN = NewCode("RCN") - NULS = NewCode("NULS") - RDN = NewCode("RDN") - DLT = NewCode("DLT") - AMB = NewCode("AMB") - BCPT = NewCode("BCPT") - ARN = NewCode("ARN") - GVT = NewCode("GVT") - CDT = NewCode("CDT") - POE = NewCode("POE") - QSP = NewCode("QSP") - XZC = NewCode("XZC") - TNT = NewCode("TNT") - FUEL = NewCode("FUEL") - ADX = NewCode("ADX") - CND = NewCode("CND") - LEND = NewCode("LEND") - WABI = NewCode("WABI") - SBTC = NewCode("SBTC") - BCX = NewCode("BCX") - TNB = NewCode("TNB") - GTO = NewCode("GTO") - OST = NewCode("OST") - CVC = NewCode("CVC") - DATA = NewCode("DATA") - ETF = NewCode("ETF") - BRD = NewCode("BRD") - NEBL = NewCode("NEBL") - VIBE = NewCode("VIBE") - LUN = NewCode("LUN") - CHAT = NewCode("CHAT") - RLC = NewCode("RLC") - INS = NewCode("INS") - VIA = NewCode("VIA") - BLZ = NewCode("BLZ") - SYS = NewCode("SYS") - NCASH = NewCode("NCASH") - POA = NewCode("POA") - STORM = NewCode("STORM") - WPR = NewCode("WPR") - QLC = NewCode("QLC") - GRS = NewCode("GRS") - CLOAK = NewCode("CLOAK") - ZEN = NewCode("ZEN") - SKY = NewCode("SKY") - IOTX = NewCode("IOTX") - QKC = NewCode("QKC") - AGI = NewCode("AGI") - NXS = NewCode("NXS") - EON = NewCode("EON") - KEY = NewCode("KEY") - NAS = NewCode("NAS") - ADD = NewCode("ADD") - MEETONE = NewCode("MEETONE") - ATD = NewCode("ATD") - MFT = NewCode("MFT") - EOP = NewCode("EOP") - DENT = NewCode("DENT") - IQ = NewCode("IQ") - DOCK = NewCode("DOCK") - POLY = NewCode("POLY") - VTHO = NewCode("VTHO") - ONG = NewCode("ONG") - PHX = NewCode("PHX") - GO = NewCode("GO") - PAX = NewCode("PAX") - EDO = NewCode("EDO") - WINGS = NewCode("WINGS") - NAV = NewCode("NAV") - TRIG = NewCode("TRIG") - APPC = NewCode("APPC") - KRW = NewCode("KRW") - HSR = NewCode("HSR") - ETHOS = NewCode("ETHOS") - CTXC = NewCode("CTXC") - ITC = NewCode("ITC") - TRUE = NewCode("TRUE") - ABT = NewCode("ABT") - RNT = NewCode("RNT") - PLY = NewCode("PLY") - PST = NewCode("PST") - KICK = NewCode("KICK") - BTCZ = NewCode("BTCZ") - DXT = NewCode("DXT") - STQ = NewCode("STQ") - INK = NewCode("INK") - HBZ = NewCode("HBZ") - USDT_ETH = NewCode("USDT_ETH") // nolint: golint,stylecheck - QTUM_ETH = NewCode("QTUM_ETH") // nolint: golint - BTM_ETH = NewCode("BTM_ETH") // nolint: golint - FIL = NewCode("FIL") - STX = NewCode("STX") - BOT = NewCode("BOT") - VERI = NewCode("VERI") - ZSC = NewCode("ZSC") - QBT = NewCode("QBT") - MED = NewCode("MED") - QASH = NewCode("QASH") - MDS = NewCode("MDS") - GOD = NewCode("GOD") - SMT = NewCode("SMT") - BTF = NewCode("BTF") - NAS_ETH = NewCode("NAS_ETH") // nolint: golint - TSL = NewCode("TSL") - BIFI = NewCode("BIFI") - BNTY = NewCode("BNTY") - DRGN = NewCode("DRGN") - GTC = NewCode("GTC") - MDT = NewCode("MDT") - QUN = NewCode("QUN") - GNX = NewCode("GNX") - DDD = NewCode("DDD") - BTO = NewCode("BTO") - TIO = NewCode("TIO") - OCN = NewCode("OCN") - RUFF = NewCode("RUFF") - TNC = NewCode("TNC") - SNET = NewCode("SNET") - COFI = NewCode("COFI") - ZPT = NewCode("ZPT") - JNT = NewCode("JNT") - MTN = NewCode("MTN") - GEM = NewCode("GEM") - DADI = NewCode("DADI") - RFR = NewCode("RFR") - MOBI = NewCode("MOBI") - LEDU = NewCode("LEDU") - DBC = NewCode("DBC") - MKR_OLD = NewCode("MKR_OLD") // nolint: golint - DPY = NewCode("DPY") - BCDN = NewCode("BCDN") - EOSDAC = NewCode("EOSDAC") // nolint: golint - TIPS = NewCode("TIPS") - XMC = NewCode("XMC") - PPS = NewCode("PPS") - BOE = NewCode("BOE") - MEDX = NewCode("MEDX") - SMT_ETH = NewCode("SMT_ETH") // nolint: golint - CS = NewCode("CS") - MAN = NewCode("MAN") - REM = NewCode("REM") - LYM = NewCode("LYM") - INSTAR = NewCode("INSTAR") // nolint: golint - BFT = NewCode("BFT") - IHT = NewCode("IHT") - SENC = NewCode("SENC") - TOMO = NewCode("TOMO") - ELEC = NewCode("ELEC") - SHIP = NewCode("SHIP") - TFD = NewCode("TFD") - HAV = NewCode("HAV") - HUR = NewCode("HUR") - LST = NewCode("LST") - LINO = NewCode("LINO") - SWTH = NewCode("SWTH") - NKN = NewCode("NKN") - SOUL = NewCode("SOUL") - GALA_NEO = NewCode("GALA_NEO") // nolint: golint - LRN = NewCode("LRN") - GSE = NewCode("GSE") - RATING = NewCode("RATING") - HSC = NewCode("HSC") - HIT = NewCode("HIT") - DX = NewCode("DX") - BXC = NewCode("BXC") - GARD = NewCode("GARD") - FTI = NewCode("FTI") - SOP = NewCode("SOP") - LEMO = NewCode("LEMO") - RED = NewCode("RED") - LBA = NewCode("LBA") - KAN = NewCode("KAN") - OPEN = NewCode("OPEN") - SKM = NewCode("SKM") - NBAI = NewCode("NBAI") - UPP = NewCode("UPP") - ATMI = NewCode("ATMI") - TMT = NewCode("TMT") - BBK = NewCode("BBK") - EDR = NewCode("EDR") - MET = NewCode("MET") - TCT = NewCode("TCT") - EXC = NewCode("EXC") - CNC = NewCode("CNC") - TIX = NewCode("TIX") - XTC = NewCode("XTC") - BU = NewCode("BU") - GNO = NewCode("GNO") - MLN = NewCode("MLN") - XBC = NewCode("XBC") - BTCD = NewCode("BTCD") - BURST = NewCode("BURST") - CLAM = NewCode("CLAM") - XCP = NewCode("XCP") - EMC2 = NewCode("EMC2") - EXP = NewCode("EXP") - FCT = NewCode("FCT") - GAME = NewCode("GAME") - GRC = NewCode("GRC") - HUC = NewCode("HUC") - LBC = NewCode("LBC") - NMC = NewCode("NMC") - NEOS = NewCode("NEOS") - OMNI = NewCode("OMNI") - PASC = NewCode("PASC") - PPC = NewCode("PPC") - DSH = NewCode("DSH") - GML = NewCode("GML") - GSY = NewCode("GSY") - POT = NewCode("POT") - XPM = NewCode("XPM") - AMP = NewCode("AMP") - VRC = NewCode("VRC") - VTC = NewCode("VTC") - ZERO07 = NewCode("007") - BIT16 = NewCode("BIT16") - TWO015 = NewCode("2015") - TWO56 = NewCode("256") - TWOBACCO = NewCode("2BACCO") - TWOGIVE = NewCode("2GIVE") - THIRTY2BIT = NewCode("32BIT") - THREE65 = NewCode("365") - FOUR04 = NewCode("404") - SEVEN00 = NewCode("700") - EIGHTBIT = NewCode("8BIT") - ACLR = NewCode("ACLR") - ACES = NewCode("ACES") - ACPR = NewCode("ACPR") - ACID = NewCode("ACID") - ACOIN = NewCode("ACOIN") - ACRN = NewCode("ACRN") - ADAM = NewCode("ADAM") - ADT = NewCode("ADT") - AIB = NewCode("AIB") - ADZ = NewCode("ADZ") - AECC = NewCode("AECC") - AM = NewCode("AM") - AGRI = NewCode("AGRI") - AGT = NewCode("AGT") - AIR = NewCode("AIR") - ALEX = NewCode("ALEX") - AUM = NewCode("AUM") - ALIEN = NewCode("ALIEN") - ALIS = NewCode("ALIS") - ALL = NewCode("ALL") - ASAFE = NewCode("ASAFE") - AMBER = NewCode("AMBER") - AMS = NewCode("AMS") - ANAL = NewCode("ANAL") - ACP = NewCode("ACP") - ANI = NewCode("ANI") - ANTI = NewCode("ANTI") - ALTC = NewCode("ALTC") - APT = NewCode("APT") - ARCO = NewCode("ARCO") - ALC = NewCode("ALC") - ARB = NewCode("ARB") - ARCT = NewCode("ARCT") - ARCX = NewCode("ARCX") - ARGUS = NewCode("ARGUS") - ARH = NewCode("ARH") - ARM = NewCode("ARM") - ARNA = NewCode("ARNA") - ARPA = NewCode("ARPA") - ARTA = NewCode("ARTA") - ABY = NewCode("ABY") - ARTC = NewCode("ARTC") - AL = NewCode("AL") - ASN = NewCode("ASN") - ADCN = NewCode("ADCN") - ATB = NewCode("ATB") - ATM = NewCode("ATM") - ATMCHA = NewCode("ATMCHA") - ATOM = NewCode("ATOM") - ADC = NewCode("ADC") - ARE = NewCode("ARE") - AUR = NewCode("AUR") - AV = NewCode("AV") - AXIOM = NewCode("AXIOM") - B2B = NewCode("B2B") - B2 = NewCode("B2") - B3 = NewCode("B3") - BAB = NewCode("BAB") - BAN = NewCode("BAN") - BamitCoin = NewCode("BamitCoin") - NANAS = NewCode("NANAS") - BBCC = NewCode("BBCC") - BTA = NewCode("BTA") - BSTK = NewCode("BSTK") - BATL = NewCode("BATL") - BBH = NewCode("BBH") - BITB = NewCode("BITB") - BRDD = NewCode("BRDD") - XBTS = NewCode("XBTS") - BVC = NewCode("BVC") - CHATX = NewCode("CHATX") - BEEP = NewCode("BEEP") - BEEZ = NewCode("BEEZ") - BENJI = NewCode("BENJI") - BERN = NewCode("BERN") - PROFIT = NewCode("PROFIT") - BEST = NewCode("BEST") - BGF = NewCode("BGF") - BIGUP = NewCode("BIGUP") - BLRY = NewCode("BLRY") - BILL = NewCode("BILL") - BIOB = NewCode("BIOB") - BIO = NewCode("BIO") - BIOS = NewCode("BIOS") - BPTN = NewCode("BPTN") - BTCA = NewCode("BTCA") - BA = NewCode("BA") - BAC = NewCode("BAC") - BBT = NewCode("BBT") - BOSS = NewCode("BOSS") - BRONZ = NewCode("BRONZ") - CAT = NewCode("CAT") - BTD = NewCode("BTD") - XBTC21 = NewCode("XBTC21") - BCA = NewCode("BCA") - BCP = NewCode("BCP") - BTDOLL = NewCode("BTDOLL") - LIZA = NewCode("LIZA") - BTCRED = NewCode("BTCRED") - BTCS = NewCode("BTCS") - BTU = NewCode("BTU") - BUM = NewCode("BUM") - LITE = NewCode("LITE") - BCM = NewCode("BCM") - BCS = NewCode("BCS") - BTCU = NewCode("BTCU") - BM = NewCode("BM") - BTCRY = NewCode("BTCRY") - BTCR = NewCode("BTCR") - HIRE = NewCode("HIRE") - STU = NewCode("STU") - BITOK = NewCode("BITOK") - BITON = NewCode("BITON") - BPC = NewCode("BPC") - BPOK = NewCode("BPOK") - BTP = NewCode("BTP") - BITCNY = NewCode("bitCNY") - RNTB = NewCode("RNTB") - BSH = NewCode("BSH") - XBS = NewCode("XBS") - BITS = NewCode("BITS") - BST = NewCode("BST") - BXT = NewCode("BXT") - VEG = NewCode("VEG") - VOLT = NewCode("VOLT") - BTV = NewCode("BTV") - BITZ = NewCode("BITZ") - BTZ = NewCode("BTZ") - BHC = NewCode("BHC") - BDC = NewCode("BDC") - JACK = NewCode("JACK") - BS = NewCode("BS") - BSTAR = NewCode("BSTAR") - BLAZR = NewCode("BLAZR") - BOD = NewCode("BOD") - BLUE = NewCode("BLUE") - BLU = NewCode("BLU") - BLUS = NewCode("BLUS") - BMT = NewCode("BMT") - BOLI = NewCode("BOLI") - BOMB = NewCode("BOMB") - BON = NewCode("BON") - BOOM = NewCode("BOOM") - BOSON = NewCode("BOSON") - BSC = NewCode("BSC") - BRH = NewCode("BRH") - BRAIN = NewCode("BRAIN") - BRE = NewCode("BRE") - BTCM = NewCode("BTCM") - BTCO = NewCode("BTCO") - TALK = NewCode("TALK") - BUB = NewCode("BUB") - BUY = NewCode("BUY") - BUZZ = NewCode("BUZZ") - BTH = NewCode("BTH") - C0C0 = NewCode("C0C0") - CAB = NewCode("CAB") - CF = NewCode("CF") - CLO = NewCode("CLO") - CAM = NewCode("CAM") - CD = NewCode("CD") - CANN = NewCode("CANN") - CNNC = NewCode("CNNC") - CPC = NewCode("CPC") - CST = NewCode("CST") - CAPT = NewCode("CAPT") - CARBON = NewCode("CARBON") - CME = NewCode("CME") - CTK = NewCode("CTK") - CBD = NewCode("CBD") - CCC = NewCode("CCC") - CNT = NewCode("CNT") - XCE = NewCode("XCE") - CHRG = NewCode("CHRG") - CHEMX = NewCode("CHEMX") - CHESS = NewCode("CHESS") - CKS = NewCode("CKS") - CHILL = NewCode("CHILL") - CHIP = NewCode("CHIP") - CHOOF = NewCode("CHOOF") - CRX = NewCode("CRX") - CIN = NewCode("CIN") - POLL = NewCode("POLL") - CLICK = NewCode("CLICK") - CLINT = NewCode("CLINT") - CLUB = NewCode("CLUB") - CLUD = NewCode("CLUD") - COX = NewCode("COX") - COXST = NewCode("COXST") - CFC = NewCode("CFC") - CTIC2 = NewCode("CTIC2") - COIN = NewCode("COIN") - BTTF = NewCode("BTTF") - C2 = NewCode("C2") - CAID = NewCode("CAID") - CL = NewCode("CL") - CTIC = NewCode("CTIC") - CXT = NewCode("CXT") - CHP = NewCode("CHP") - CV2 = NewCode("CV2") - COC = NewCode("COC") - COMP = NewCode("COMP") - CMS = NewCode("CMS") - CONX = NewCode("CONX") - CCX = NewCode("CCX") - CLR = NewCode("CLR") - CORAL = NewCode("CORAL") - CORG = NewCode("CORG") - CSMIC = NewCode("CSMIC") - CMC = NewCode("CMC") - COV = NewCode("COV") - COVX = NewCode("COVX") - CRAB = NewCode("CRAB") - CRAFT = NewCode("CRAFT") - CRNK = NewCode("CRNK") - CRAVE = NewCode("CRAVE") - CRM = NewCode("CRM") - XCRE = NewCode("XCRE") - CREDIT = NewCode("CREDIT") - CREVA = NewCode("CREVA") - CRIME = NewCode("CRIME") - CROC = NewCode("CROC") - CRC = NewCode("CRC") - CRW = NewCode("CRW") - CRY = NewCode("CRY") - CBX = NewCode("CBX") - TKTX = NewCode("TKTX") - CB = NewCode("CB") - CIRC = NewCode("CIRC") - CCB = NewCode("CCB") - CDO = NewCode("CDO") - CG = NewCode("CG") - CJ = NewCode("CJ") - CJC = NewCode("CJC") - CYT = NewCode("CYT") - CRPS = NewCode("CRPS") - PING = NewCode("PING") - CWXT = NewCode("CWXT") - CCT = NewCode("CCT") - CTL = NewCode("CTL") - CURVES = NewCode("CURVES") - CC = NewCode("CC") - CYC = NewCode("CYC") - CYG = NewCode("CYG") - CYP = NewCode("CYP") - FUNK = NewCode("FUNK") - CZECO = NewCode("CZECO") - DALC = NewCode("DALC") - DLISK = NewCode("DLISK") - MOOND = NewCode("MOOND") - DB = NewCode("DB") - DCC = NewCode("DCC") - DCYP = NewCode("DCYP") - DETH = NewCode("DETH") - DKC = NewCode("DKC") - DISK = NewCode("DISK") - DRKT = NewCode("DRKT") - DTT = NewCode("DTT") - DASHS = NewCode("DASHS") - DBTC = NewCode("DBTC") - DCT = NewCode("DCT") - DBET = NewCode("DBET") - DEC = NewCode("DEC") - DECR = NewCode("DECR") - DEA = NewCode("DEA") - DPAY = NewCode("DPAY") - DCRE = NewCode("DCRE") - DC = NewCode("DC") - DES = NewCode("DES") - DEM = NewCode("DEM") - DXC = NewCode("DXC") - DCK = NewCode("DCK") - CUBE = NewCode("CUBE") - DGMS = NewCode("DGMS") - DBG = NewCode("DBG") - DGCS = NewCode("DGCS") - DBLK = NewCode("DBLK") - DIME = NewCode("DIME") - DIRT = NewCode("DIRT") - DVD = NewCode("DVD") - DMT = NewCode("DMT") - NOTE = NewCode("NOTE") - DGORE = NewCode("DGORE") - DLC = NewCode("DLC") - DRT = NewCode("DRT") - DOTA = NewCode("DOTA") - DOX = NewCode("DOX") - DRA = NewCode("DRA") - DFT = NewCode("DFT") - XDB = NewCode("XDB") - DRM = NewCode("DRM") - DRZ = NewCode("DRZ") - DRACO = NewCode("DRACO") - DBIC = NewCode("DBIC") - DUB = NewCode("DUB") - GUM = NewCode("GUM") - DUR = NewCode("DUR") - DUST = NewCode("DUST") - DUX = NewCode("DUX") - DXO = NewCode("DXO") - ECN = NewCode("ECN") - EDR2 = NewCode("EDR2") - EA = NewCode("EA") - EAGS = NewCode("EAGS") - EMT = NewCode("EMT") - EBONUS = NewCode("EBONUS") - ECCHI = NewCode("ECCHI") - EKO = NewCode("EKO") - ECLI = NewCode("ECLI") - ECOB = NewCode("ECOB") - ECO = NewCode("ECO") - EDIT = NewCode("EDIT") - EDRC = NewCode("EDRC") - EDC = NewCode("EDC") - EGAME = NewCode("EGAME") - EGG = NewCode("EGG") - EGO = NewCode("EGO") - ELC = NewCode("ELC") - ELCO = NewCode("ELCO") - ECA = NewCode("ECA") - EPC = NewCode("EPC") - ELE = NewCode("ELE") - ONE337 = NewCode("1337") - EMB = NewCode("EMB") - EMC = NewCode("EMC") - EPY = NewCode("EPY") - EMPC = NewCode("EMPC") - EMP = NewCode("EMP") - ENE = NewCode("ENE") - EET = NewCode("EET") - XNG = NewCode("XNG") - EGMA = NewCode("EGMA") - ENTER = NewCode("ENTER") - ETRUST = NewCode("ETRUST") - EQL = NewCode("EQL") - EQM = NewCode("EQM") - EQT = NewCode("EQT") - ERR = NewCode("ERR") - ESC = NewCode("ESC") - ESP = NewCode("ESP") - ENT = NewCode("ENT") - ETCO = NewCode("ETCO") - DOGETH = NewCode("DOGETH") - ECASH = NewCode("ECASH") - ELITE = NewCode("ELITE") - ETHS = NewCode("ETHS") - ETL = NewCode("ETL") - ETZ = NewCode("ETZ") - EUC = NewCode("EUC") - EURC = NewCode("EURC") - EUROPE = NewCode("EUROPE") - EVA = NewCode("EVA") - EGC = NewCode("EGC") - EOC = NewCode("EOC") - EVIL = NewCode("EVIL") - EVO = NewCode("EVO") - EXB = NewCode("EXB") - EXIT = NewCode("EXIT") - XT = NewCode("XT") - F16 = NewCode("F16") - FADE = NewCode("FADE") - FAZZ = NewCode("FAZZ") - FX = NewCode("FX") - FIDEL = NewCode("FIDEL") - FIDGT = NewCode("FIDGT") - FIND = NewCode("FIND") - FPC = NewCode("FPC") - FIRE = NewCode("FIRE") - FFC = NewCode("FFC") - FRST = NewCode("FRST") - FIST = NewCode("FIST") - FIT = NewCode("FIT") - FLX = NewCode("FLX") - FLVR = NewCode("FLVR") - FLY = NewCode("FLY") - FONZ = NewCode("FONZ") - XFCX = NewCode("XFCX") - FOREX = NewCode("FOREX") - FRN = NewCode("FRN") - FRK = NewCode("FRK") - FRWC = NewCode("FRWC") - FGZ = NewCode("FGZ") - FRE = NewCode("FRE") - FRDC = NewCode("FRDC") - FJC = NewCode("FJC") - FURY = NewCode("FURY") - FSN = NewCode("FSN") - FCASH = NewCode("FCASH") - FTO = NewCode("FTO") - FUZZ = NewCode("FUZZ") - GAKH = NewCode("GAKH") - GBT = NewCode("GBT") - UNITS = NewCode("UNITS") - FOUR20G = NewCode("420G") - GENIUS = NewCode("GENIUS") - GEN = NewCode("GEN") - GEO = NewCode("GEO") - GER = NewCode("GER") - GSR = NewCode("GSR") - SPKTR = NewCode("SPKTR") - GIFT = NewCode("GIFT") - WTT = NewCode("WTT") - GHS = NewCode("GHS") - GIG = NewCode("GIG") - GOT = NewCode("GOT") - XGTC = NewCode("XGTC") - GIZ = NewCode("GIZ") - GLO = NewCode("GLO") - GCR = NewCode("GCR") - BSTY = NewCode("BSTY") - GLC = NewCode("GLC") - GSX = NewCode("GSX") - GOAT = NewCode("GOAT") - GB = NewCode("GB") - GFL = NewCode("GFL") - MNTP = NewCode("MNTP") - GP = NewCode("GP") - GLUCK = NewCode("GLUCK") - GOON = NewCode("GOON") - GTFO = NewCode("GTFO") - GOTX = NewCode("GOTX") - GPU = NewCode("GPU") - GRF = NewCode("GRF") - GRAM = NewCode("GRAM") - GRAV = NewCode("GRAV") - GBIT = NewCode("GBIT") - GREED = NewCode("GREED") - GE = NewCode("GE") - GREENF = NewCode("GREENF") - GRE = NewCode("GRE") - GREXIT = NewCode("GREXIT") - GMCX = NewCode("GMCX") - GROW = NewCode("GROW") - GSM = NewCode("GSM") - GT = NewCode("GT") - NLG = NewCode("NLG") - HKN = NewCode("HKN") - HAC = NewCode("HAC") - HALLO = NewCode("HALLO") - HAMS = NewCode("HAMS") - HPC = NewCode("HPC") - HAWK = NewCode("HAWK") - HAZE = NewCode("HAZE") - HZT = NewCode("HZT") - HDG = NewCode("HDG") - HEDG = NewCode("HEDG") - HEEL = NewCode("HEEL") - HMP = NewCode("HMP") - PLAY = NewCode("PLAY") - HXX = NewCode("HXX") - XHI = NewCode("XHI") - HVCO = NewCode("HVCO") - HTC = NewCode("HTC") - MINH = NewCode("MINH") - HODL = NewCode("HODL") - HON = NewCode("HON") - HOPE = NewCode("HOPE") - HQX = NewCode("HQX") - HSP = NewCode("HSP") - HTML5 = NewCode("HTML5") - HYPERX = NewCode("HYPERX") - HPS = NewCode("HPS") - IOC = NewCode("IOC") - IBANK = NewCode("IBANK") - IBITS = NewCode("IBITS") - ICASH = NewCode("ICASH") - ICOB = NewCode("ICOB") - ICON = NewCode("ICON") - IETH = NewCode("IETH") - ILM = NewCode("ILM") - IMPS = NewCode("IMPS") - NKA = NewCode("NKA") - INCP = NewCode("INCP") - IN = NewCode("IN") - INC = NewCode("INC") - IMS = NewCode("IMS") - IFLT = NewCode("IFLT") - INFX = NewCode("INFX") - INGT = NewCode("INGT") - INPAY = NewCode("INPAY") - INSANE = NewCode("INSANE") - INXT = NewCode("INXT") - IFT = NewCode("IFT") - INV = NewCode("INV") - IVZ = NewCode("IVZ") - ILT = NewCode("ILT") - IONX = NewCode("IONX") - ISL = NewCode("ISL") - ITI = NewCode("ITI") - ING = NewCode("ING") - IEC = NewCode("IEC") - IW = NewCode("IW") - IXC = NewCode("IXC") - IXT = NewCode("IXT") - JPC = NewCode("JPC") - JANE = NewCode("JANE") - JWL = NewCode("JWL") - JIF = NewCode("JIF") - JOBS = NewCode("JOBS") - JOCKER = NewCode("JOCKER") - JW = NewCode("JW") - JOK = NewCode("JOK") - XJO = NewCode("XJO") - KGB = NewCode("KGB") - KARMC = NewCode("KARMC") - KARMA = NewCode("KARMA") - KASHH = NewCode("KASHH") - KAT = NewCode("KAT") - KC = NewCode("KC") - KIDS = NewCode("KIDS") - KIN = NewCode("KIN") - KISS = NewCode("KISS") - KOBO = NewCode("KOBO") - TP1 = NewCode("TP1") - KRAK = NewCode("KRAK") - KGC = NewCode("KGC") - KTK = NewCode("KTK") - KR = NewCode("KR") - KUBO = NewCode("KUBO") - KURT = NewCode("KURT") - KUSH = NewCode("KUSH") - LANA = NewCode("LANA") - LTH = NewCode("LTH") - LAZ = NewCode("LAZ") - LEA = NewCode("LEA") - LEAF = NewCode("LEAF") - LENIN = NewCode("LENIN") - LEPEN = NewCode("LEPEN") - LIR = NewCode("LIR") - LVG = NewCode("LVG") - LGBTQ = NewCode("LGBTQ") - LHC = NewCode("LHC") - EXT = NewCode("EXT") - LBTC = NewCode("LBTC") - LSD = NewCode("LSD") - LIMX = NewCode("LIMX") - LTD = NewCode("LTD") - LINDA = NewCode("LINDA") - LKC = NewCode("LKC") - LBTCX = NewCode("LBTCX") - LCC = NewCode("LCC") - LTCU = NewCode("LTCU") - LTCR = NewCode("LTCR") - LDOGE = NewCode("LDOGE") - LTS = NewCode("LTS") - LIV = NewCode("LIV") - LIZI = NewCode("LIZI") - LOC = NewCode("LOC") - LOCX = NewCode("LOCX") - LOOK = NewCode("LOOK") - LOOT = NewCode("LOOT") - XLTCG = NewCode("XLTCG") - BASH = NewCode("BASH") - LUCKY = NewCode("LUCKY") - L7S = NewCode("L7S") - LDM = NewCode("LDM") - LUMI = NewCode("LUMI") - LUNA = NewCode("LUNA") - LC = NewCode("LC") - LUX = NewCode("LUX") - MCRN = NewCode("MCRN") - XMG = NewCode("XMG") - MMXIV = NewCode("MMXIV") - MAT = NewCode("MAT") - MAO = NewCode("MAO") - MAPC = NewCode("MAPC") - MRB = NewCode("MRB") - MXT = NewCode("MXT") - MARV = NewCode("MARV") - MARX = NewCode("MARX") - MCAR = NewCode("MCAR") - MM = NewCode("MM") - MVC = NewCode("MVC") - MAVRO = NewCode("MAVRO") - MAX = NewCode("MAX") - MAZE = NewCode("MAZE") - MBIT = NewCode("MBIT") - MCOIN = NewCode("MCOIN") - MPRO = NewCode("MPRO") - XMS = NewCode("XMS") - MLITE = NewCode("MLITE") - MLNC = NewCode("MLNC") - MENTAL = NewCode("MENTAL") - MERGEC = NewCode("MERGEC") - MTLMC3 = NewCode("MTLMC3") - METAL = NewCode("METAL") - MUU = NewCode("MUU") - MILO = NewCode("MILO") - MND = NewCode("MND") - XMINE = NewCode("XMINE") - MNM = NewCode("MNM") - XNM = NewCode("XNM") - MIRO = NewCode("MIRO") - MIS = NewCode("MIS") - MMXVI = NewCode("MMXVI") - MOIN = NewCode("MOIN") - MOJO = NewCode("MOJO") - TAB = NewCode("TAB") - MONETA = NewCode("MONETA") - MUE = NewCode("MUE") - MONEY = NewCode("MONEY") - MRP = NewCode("MRP") - MOTO = NewCode("MOTO") - MULTI = NewCode("MULTI") - MST = NewCode("MST") - MVR = NewCode("MVR") - MYSTIC = NewCode("MYSTIC") - WISH = NewCode("WISH") - NKT = NewCode("NKT") - NAT = NewCode("NAT") - ENAU = NewCode("ENAU") - NEBU = NewCode("NEBU") - NEF = NewCode("NEF") - NBIT = NewCode("NBIT") - NETKO = NewCode("NETKO") - NTM = NewCode("NTM") - NETC = NewCode("NETC") - NRC = NewCode("NRC") - NTK = NewCode("NTK") - NTRN = NewCode("NTRN") - NEVA = NewCode("NEVA") - NIC = NewCode("NIC") - NKC = NewCode("NKC") - NYC = NewCode("NYC") - NZC = NewCode("NZC") - NICE = NewCode("NICE") - NDOGE = NewCode("NDOGE") - XTR = NewCode("XTR") - N2O = NewCode("N2O") - NIXON = NewCode("NIXON") - NOC = NewCode("NOC") - NODC = NewCode("NODC") - NODES = NewCode("NODES") - NODX = NewCode("NODX") - NLC = NewCode("NLC") - NLC2 = NewCode("NLC2") - NOO = NewCode("NOO") - NVC = NewCode("NVC") - NPC = NewCode("NPC") - NUBIS = NewCode("NUBIS") - NUKE = NewCode("NUKE") - N7 = NewCode("N7") - NUM = NewCode("NUM") - NMR = NewCode("NMR") - NXE = NewCode("NXE") - OBS = NewCode("OBS") - OCEAN = NewCode("OCEAN") - OCOW = NewCode("OCOW") - EIGHT88 = NewCode("888") - OCC = NewCode("OCC") - OK = NewCode("OK") - ODNT = NewCode("ODNT") - FLAV = NewCode("FLAV") - OLIT = NewCode("OLIT") - OLYMP = NewCode("OLYMP") - OMA = NewCode("OMA") - OMC = NewCode("OMC") - ONEK = NewCode("ONEK") - ONX = NewCode("ONX") - XPO = NewCode("XPO") - OPAL = NewCode("OPAL") - OTN = NewCode("OTN") - OP = NewCode("OP") - OPES = NewCode("OPES") - OPTION = NewCode("OPTION") - ORLY = NewCode("ORLY") - OS76 = NewCode("OS76") - OZC = NewCode("OZC") - P7C = NewCode("P7C") - PAC = NewCode("PAC") - PAK = NewCode("PAK") - PAL = NewCode("PAL") - PND = NewCode("PND") - PINKX = NewCode("PINKX") - POPPY = NewCode("POPPY") - DUO = NewCode("DUO") - PARA = NewCode("PARA") - PKB = NewCode("PKB") - GENE = NewCode("GENE") - PARTY = NewCode("PARTY") - PYN = NewCode("PYN") - XPY = NewCode("XPY") - CON = NewCode("CON") - PAYP = NewCode("PAYP") - GUESS = NewCode("GUESS") - PEN = NewCode("PEN") - PTA = NewCode("PTA") - PEO = NewCode("PEO") - PSB = NewCode("PSB") - XPD = NewCode("XPD") - PXL = NewCode("PXL") - PHR = NewCode("PHR") - PIE = NewCode("PIE") - PIO = NewCode("PIO") - PIPR = NewCode("PIPR") - SKULL = NewCode("SKULL") - PLANET = NewCode("PLANET") - PNC = NewCode("PNC") - XPTX = NewCode("XPTX") - PLNC = NewCode("PLNC") - XPS = NewCode("XPS") - POKE = NewCode("POKE") - PLBT = NewCode("PLBT") - POM = NewCode("POM") - PONZ2 = NewCode("PONZ2") - PONZI = NewCode("PONZI") - XSP = NewCode("XSP") - XPC = NewCode("XPC") - PEX = NewCode("PEX") - TRON = NewCode("TRON") - POST = NewCode("POST") - POSW = NewCode("POSW") - PWR = NewCode("PWR") - POWER = NewCode("POWER") - PRE = NewCode("PRE") - PRS = NewCode("PRS") - PXI = NewCode("PXI") - PEXT = NewCode("PEXT") - PRIMU = NewCode("PRIMU") - PRX = NewCode("PRX") - PRM = NewCode("PRM") - PRIX = NewCode("PRIX") - XPRO = NewCode("XPRO") - PCM = NewCode("PCM") - PROC = NewCode("PROC") - NANOX = NewCode("NANOX") - VRP = NewCode("VRP") - PTY = NewCode("PTY") - PSI = NewCode("PSI") - PSY = NewCode("PSY") - PULSE = NewCode("PULSE") - PUPA = NewCode("PUPA") - PURE = NewCode("PURE") - VIDZ = NewCode("VIDZ") - PUTIN = NewCode("PUTIN") - PX = NewCode("PX") - QTM = NewCode("QTM") - QTZ = NewCode("QTZ") - QBC = NewCode("QBC") - XQN = NewCode("XQN") - RBBT = NewCode("RBBT") - RAC = NewCode("RAC") - RADI = NewCode("RADI") - RAD = NewCode("RAD") - RAI = NewCode("RAI") - XRA = NewCode("XRA") - RATIO = NewCode("RATIO") - REA = NewCode("REA") - RCX = NewCode("RCX") - REE = NewCode("REE") - REC = NewCode("REC") - RMS = NewCode("RMS") - RBIT = NewCode("RBIT") - RNC = NewCode("RNC") - REV = NewCode("REV") - RH = NewCode("RH") - XRL = NewCode("XRL") - RICE = NewCode("RICE") - RICHX = NewCode("RICHX") - RID = NewCode("RID") - RIDE = NewCode("RIDE") - RBT = NewCode("RBT") - RING = NewCode("RING") - RIO = NewCode("RIO") - RISE = NewCode("RISE") - ROCKET = NewCode("ROCKET") - RPC = NewCode("RPC") - ROS = NewCode("ROS") - ROYAL = NewCode("ROYAL") - RSGP = NewCode("RSGP") - RBIES = NewCode("RBIES") - RUBIT = NewCode("RUBIT") - RBY = NewCode("RBY") - RUC = NewCode("RUC") - RUPX = NewCode("RUPX") - RUP = NewCode("RUP") - RUST = NewCode("RUST") - SFE = NewCode("SFE") - SLS = NewCode("SLS") - SMSR = NewCode("SMSR") - RONIN = NewCode("RONIN") - STV = NewCode("STV") - HIFUN = NewCode("HIFUN") - MAD = NewCode("MAD") - SANDG = NewCode("SANDG") - STO = NewCode("STO") - SCAN = NewCode("SCAN") - SCITW = NewCode("SCITW") - SCRPT = NewCode("SCRPT") - SCRT = NewCode("SCRT") - SED = NewCode("SED") - SEEDS = NewCode("SEEDS") - B2X = NewCode("B2X") - SEL = NewCode("SEL") - SLFI = NewCode("SLFI") - SMBR = NewCode("SMBR") - SEN = NewCode("SEN") - SENT = NewCode("SENT") - SRNT = NewCode("SRNT") - SEV = NewCode("SEV") - SP = NewCode("SP") - SXC = NewCode("SXC") - GELD = NewCode("GELD") - SHDW = NewCode("SHDW") - SDC = NewCode("SDC") - SAK = NewCode("SAK") - SHRP = NewCode("SHRP") - SHELL = NewCode("SHELL") - SH = NewCode("SH") - SHORTY = NewCode("SHORTY") - SHREK = NewCode("SHREK") - SHRM = NewCode("SHRM") - SIB = NewCode("SIB") - SIGT = NewCode("SIGT") - SLCO = NewCode("SLCO") - SIGU = NewCode("SIGU") - SIX = NewCode("SIX") - SJW = NewCode("SJW") - SKB = NewCode("SKB") - SW = NewCode("SW") - SLEEP = NewCode("SLEEP") - SLING = NewCode("SLING") - SMART = NewCode("SMART") - SMC = NewCode("SMC") - SMF = NewCode("SMF") - SOCC = NewCode("SOCC") - SCL = NewCode("SCL") - SDAO = NewCode("SDAO") - SOLAR = NewCode("SOLAR") - SOLO = NewCode("SOLO") - SCT = NewCode("SCT") - SONG = NewCode("SONG") - ALTCOM = NewCode("ALTCOM") - SPHTX = NewCode("SPHTX") - SPC = NewCode("SPC") - SPACE = NewCode("SPACE") - SBT = NewCode("SBT") - SPEC = NewCode("SPEC") - SPX = NewCode("SPX") - SCS = NewCode("SCS") - SPORT = NewCode("SPORT") - SPT = NewCode("SPT") - SPR = NewCode("SPR") - SPEX = NewCode("SPEX") - SQL = NewCode("SQL") - SBIT = NewCode("SBIT") - STHR = NewCode("STHR") - STALIN = NewCode("STALIN") - STAR = NewCode("STAR") - STA = NewCode("STA") - START = NewCode("START") - STP = NewCode("STP") - PNK = NewCode("PNK") - STEPS = NewCode("STEPS") - STK = NewCode("STK") - STONK = NewCode("STONK") - STS = NewCode("STS") - STRP = NewCode("STRP") - STY = NewCode("STY") - XMT = NewCode("XMT") - SSTC = NewCode("SSTC") - SUPER = NewCode("SUPER") - SRND = NewCode("SRND") - STRB = NewCode("STRB") - M1 = NewCode("M1") - SPM = NewCode("SPM") - BUCKS = NewCode("BUCKS") - TOKEN = NewCode("TOKEN") - SWT = NewCode("SWT") - SWEET = NewCode("SWEET") - SWING = NewCode("SWING") - CHSB = NewCode("CHSB") - SIC = NewCode("SIC") - SDP = NewCode("SDP") - XSY = NewCode("XSY") - SYNX = NewCode("SYNX") - SNRG = NewCode("SNRG") - TAG = NewCode("TAG") - TAGR = NewCode("TAGR") - TAJ = NewCode("TAJ") - TAK = NewCode("TAK") - TAKE = NewCode("TAKE") - TAM = NewCode("TAM") - XTO = NewCode("XTO") - TAP = NewCode("TAP") - TLE = NewCode("TLE") - TSE = NewCode("TSE") - TLEX = NewCode("TLEX") - TAXI = NewCode("TAXI") - TCN = NewCode("TCN") - TDFB = NewCode("TDFB") - TEAM = NewCode("TEAM") - TECH = NewCode("TECH") - TEC = NewCode("TEC") - TEK = NewCode("TEK") - TB = NewCode("TB") - TLX = NewCode("TLX") - TELL = NewCode("TELL") - TENNET = NewCode("TENNET") - TES = NewCode("TES") - TGS = NewCode("TGS") - XVE = NewCode("XVE") - TCR = NewCode("TCR") - GCC = NewCode("GCC") - MAY = NewCode("MAY") - THOM = NewCode("THOM") - TIA = NewCode("TIA") - TIDE = NewCode("TIDE") - TIE = NewCode("TIE") - TIT = NewCode("TIT") - TTC = NewCode("TTC") - TODAY = NewCode("TODAY") - TBX = NewCode("TBX") - TDS = NewCode("TDS") - TLOSH = NewCode("TLOSH") - TOKC = NewCode("TOKC") - TMRW = NewCode("TMRW") - TOOL = NewCode("TOOL") - TCX = NewCode("TCX") - TOT = NewCode("TOT") - TX = NewCode("TX") - TRANSF = NewCode("TRANSF") - TRAP = NewCode("TRAP") - TBCX = NewCode("TBCX") - TRICK = NewCode("TRICK") - TPG = NewCode("TPG") - TFL = NewCode("TFL") - TRUMP = NewCode("TRUMP") - TNG = NewCode("TNG") - TUR = NewCode("TUR") - TWERK = NewCode("TWERK") - TWIST = NewCode("TWIST") - TWO = NewCode("TWO") - UCASH = NewCode("UCASH") - UAE = NewCode("UAE") - XBU = NewCode("XBU") - UBQ = NewCode("UBQ") - U = NewCode("U") - UDOWN = NewCode("UDOWN") - GAIN = NewCode("GAIN") - USC = NewCode("USC") - UMC = NewCode("UMC") - UNF = NewCode("UNF") - UNIFY = NewCode("UNIFY") - USDE = NewCode("USDE") - UBTC = NewCode("UBTC") - UIS = NewCode("UIS") - UNIT = NewCode("UNIT") - UNI = NewCode("UNI") - UXC = NewCode("UXC") - URC = NewCode("URC") - XUP = NewCode("XUP") - UFR = NewCode("UFR") - URO = NewCode("URO") - UTLE = NewCode("UTLE") - VAL = NewCode("VAL") - VPRC = NewCode("VPRC") - VAPOR = NewCode("VAPOR") - VCOIN = NewCode("VCOIN") - VEC = NewCode("VEC") - VEC2 = NewCode("VEC2") - VLT = NewCode("VLT") - VENE = NewCode("VENE") - VNTX = NewCode("VNTX") - VTN = NewCode("VTN") - CRED = NewCode("CRED") - VERS = NewCode("VERS") - VTX = NewCode("VTX") - VTY = NewCode("VTY") - VIP = NewCode("VIP") - VISIO = NewCode("VISIO") - VK = NewCode("VK") - VOL = NewCode("VOL") - VOYA = NewCode("VOYA") - VPN = NewCode("VPN") - XVS = NewCode("XVS") - VTL = NewCode("VTL") - VULC = NewCode("VULC") - VVI = NewCode("VVI") - WGR = NewCode("WGR") - WAM = NewCode("WAM") - WARP = NewCode("WARP") - WASH = NewCode("WASH") - WGO = NewCode("WGO") - WAY = NewCode("WAY") - WCASH = NewCode("WCASH") - WEALTH = NewCode("WEALTH") - WEEK = NewCode("WEEK") - WHO = NewCode("WHO") - WIC = NewCode("WIC") - WBB = NewCode("WBB") - WINE = NewCode("WINE") - WINK = NewCode("WINK") - WISC = NewCode("WISC") - WITCH = NewCode("WITCH") - WMC = NewCode("WMC") - WOMEN = NewCode("WOMEN") - WOK = NewCode("WOK") - WRT = NewCode("WRT") - XCO = NewCode("XCO") - X2 = NewCode("X2") - XNX = NewCode("XNX") - XAU = NewCode("XAU") - XAV = NewCode("XAV") - XDE2 = NewCode("XDE2") - XDE = NewCode("XDE") - XIOS = NewCode("XIOS") - XOC = NewCode("XOC") - XSSX = NewCode("XSSX") - XBY = NewCode("XBY") - YAC = NewCode("YAC") - YMC = NewCode("YMC") - YAY = NewCode("YAY") - YBC = NewCode("YBC") - YES = NewCode("YES") - YOB2X = NewCode("YOB2X") - YOVI = NewCode("YOVI") - ZYD = NewCode("ZYD") - ZECD = NewCode("ZECD") - ZEIT = NewCode("ZEIT") - ZENI = NewCode("ZENI") - ZET2 = NewCode("ZET2") - ZET = NewCode("ZET") - ZMC = NewCode("ZMC") - ZIRK = NewCode("ZIRK") - ZLQ = NewCode("ZLQ") - ZNE = NewCode("ZNE") - ZONTO = NewCode("ZONTO") - ZOOM = NewCode("ZOOM") - ZRC = NewCode("ZRC") - ZUR = NewCode("ZUR") - ZB = NewCode("ZB") - QC = NewCode("QC") - HLC = NewCode("HLC") - SAFE = NewCode("SAFE") - BTN = NewCode("BTN") - CDC = NewCode("CDC") - DDM = NewCode("DDM") - HOTC = NewCode("HOTC") - BDS = NewCode("BDS") - AAA = NewCode("AAA") - XWC = NewCode("XWC") - PDX = NewCode("PDX") - SLT = NewCode("SLT") - HPY = NewCode("HPY") - XXBT = NewCode("XXBT") // BTC, but XXBT instead - XDG = NewCode("XDG") // DOGE - HKD = NewCode("HKD") // Hong Kong Dollar - AUD = NewCode("AUD") // Australian Dollar - USD = NewCode("USD") // United States Dollar - ZUSD = NewCode("ZUSD") // United States Dollar, but with a Z in front of it - EUR = NewCode("EUR") // Euro - ZEUR = NewCode("ZEUR") // Euro, but with a Z in front of it - CAD = NewCode("CAD") // Canadaian Dollar - ZCAD = NewCode("ZCAD") // Canadaian Dollar, but with a Z in front of it - SGD = NewCode("SGD") // Singapore Dollar - RUB = NewCode("RUB") // RUssian ruBle - RUR = NewCode("RUR") // RUssian Ruble - PLN = NewCode("PLN") // Polish złoty - TRY = NewCode("TRY") // Turkish lira - UAH = NewCode("UAH") // Ukrainian hryvnia - JPY = NewCode("JPY") // Japanese yen - ZJPY = NewCode("ZJPY") // Japanese yen, but with a Z in front of it - LCH = NewCode("LCH") - MYR = NewCode("MYR") - AFN = NewCode("AFN") - ARS = NewCode("ARS") - AWG = NewCode("AWG") - AZN = NewCode("AZN") - BSD = NewCode("BSD") - BBD = NewCode("BBD") - BYN = NewCode("BYN") - BZD = NewCode("BZD") - BMD = NewCode("BMD") - BOB = NewCode("BOB") - BAM = NewCode("BAM") - BWP = NewCode("BWP") - BGN = NewCode("BGN") - BRL = NewCode("BRL") - BND = NewCode("BND") - KHR = NewCode("KHR") - KYD = NewCode("KYD") - CLP = NewCode("CLP") - CNY = NewCode("CNY") - COP = NewCode("COP") - HRK = NewCode("HRK") - CUP = NewCode("CUP") - CZK = NewCode("CZK") - DKK = NewCode("DKK") - DOP = NewCode("DOP") - XCD = NewCode("XCD") - EGP = NewCode("EGP") - SVC = NewCode("SVC") - FKP = NewCode("FKP") - FJD = NewCode("FJD") - GIP = NewCode("GIP") - GTQ = NewCode("GTQ") - GGP = NewCode("GGP") - GYD = NewCode("GYD") - HNL = NewCode("HNL") - HUF = NewCode("HUF") - ISK = NewCode("ISK") - INR = NewCode("INR") - IDR = NewCode("IDR") - IRR = NewCode("IRR") - IMP = NewCode("IMP") - ILS = NewCode("ILS") - JMD = NewCode("JMD") - JEP = NewCode("JEP") - KZT = NewCode("KZT") - KPW = NewCode("KPW") - KGS = NewCode("KGS") - LAK = NewCode("LAK") - LBP = NewCode("LBP") - LRD = NewCode("LRD") - MKD = NewCode("MKD") - MUR = NewCode("MUR") - MXN = NewCode("MXN") - MNT = NewCode("MNT") - MZN = NewCode("MZN") - NAD = NewCode("NAD") - NPR = NewCode("NPR") - ANG = NewCode("ANG") - NZD = NewCode("NZD") - NIO = NewCode("NIO") - NGN = NewCode("NGN") - NOK = NewCode("NOK") - OMR = NewCode("OMR") - PKR = NewCode("PKR") - PAB = NewCode("PAB") - PYG = NewCode("PYG") - PHP = NewCode("PHP") - QAR = NewCode("QAR") - RON = NewCode("RON") - SHP = NewCode("SHP") - SAR = NewCode("SAR") - RSD = NewCode("RSD") - SCR = NewCode("SCR") - SOS = NewCode("SOS") - ZAR = NewCode("ZAR") - LKR = NewCode("LKR") - SEK = NewCode("SEK") - CHF = NewCode("CHF") - SRD = NewCode("SRD") - SYP = NewCode("SYP") - TWD = NewCode("TWD") - THB = NewCode("THB") - TTD = NewCode("TTD") - TVD = NewCode("TVD") - GBP = NewCode("GBP") - UYU = NewCode("UYU") - UZS = NewCode("UZS") - VEF = NewCode("VEF") - VND = NewCode("VND") - YER = NewCode("YER") - ZWD = NewCode("ZWD") - XETH = NewCode("XETH") - FX_BTC = NewCode("FX_BTC") // nolint: golint -) diff --git a/currency/code_test.go b/currency/code_test.go index b5d7c599..e27dc4c6 100644 --- a/currency/code_test.go +++ b/currency/code_test.go @@ -8,31 +8,31 @@ import ( func TestRoleString(t *testing.T) { if Unset.String() != UnsetRollString { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", UnsetRollString, Unset) } if Fiat.String() != FiatCurrencyString { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", FiatCurrencyString, Fiat) } if Cryptocurrency.String() != CryptocurrencyString { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", CryptocurrencyString, Cryptocurrency) } if Token.String() != TokenString { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", TokenString, Token) } if Contract.String() != ContractString { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", ContractString, Contract) } @@ -40,7 +40,7 @@ func TestRoleString(t *testing.T) { var random Role = 1 << 7 if random.String() != "UNKNOWN" { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", "UNKNOWN", random) } @@ -49,17 +49,18 @@ func TestRoleString(t *testing.T) { func TestRoleMarshalJSON(t *testing.T) { d, err := common.JSONEncode(Fiat) if err != nil { - t.Error("Test Failed - Role MarshalJSON() error", err) + t.Error("Role MarshalJSON() error", err) } expected := `"fiatCurrency"` if string(d) != expected { - t.Errorf("Test Failed - Role MarshalJSON() error expected %s but received %s", + t.Errorf("Role MarshalJSON() error expected %s but received %s", expected, string(d)) } } +// TestRoleUnmarshalJSON logic test func TestRoleUnmarshalJSON(t *testing.T) { type AllTheRoles struct { RoleOne Role `json:"RoleOne"` @@ -80,136 +81,158 @@ func TestRoleUnmarshalJSON(t *testing.T) { e, err := common.JSONEncode(1337) if err != nil { - t.Fatal("Test Failed - Role UnmarshalJSON() error", err) + t.Fatal("Role UnmarshalJSON() error", err) } var incoming AllTheRoles err = common.JSONDecode(e, &incoming) if err == nil { - t.Fatal("Test Failed - Role UnmarshalJSON() error", err) + t.Fatal("Role UnmarshalJSON() Expected error") } e, err = common.JSONEncode(outgoing) if err != nil { - t.Fatal("Test Failed - Role UnmarshalJSON() error", err) + t.Fatal("Role UnmarshalJSON() error", err) } err = common.JSONDecode(e, &incoming) if err != nil { - t.Fatal("Test Failed - Role UnmarshalJSON() error", err) + t.Fatal("Role UnmarshalJSON() error", err) } if incoming.RoleOne != Unset { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", Unset, incoming.RoleOne) } if incoming.RoleTwo != Cryptocurrency { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", Cryptocurrency, incoming.RoleTwo) } if incoming.RoleThree != Fiat { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", Fiat, incoming.RoleThree) } if incoming.RoleFour != Token { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", Token, incoming.RoleFour) } if incoming.RoleFive != Contract { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", Contract, incoming.RoleFive) } if incoming.RoleUnknown != Unset { - t.Errorf("Test Failed - Role String() error expected %s but received %s", + t.Errorf("Role String() error expected %s but received %s", incoming.RoleFive, incoming.RoleUnknown) } + var unhandled Role + err = unhandled.UnmarshalJSON([]byte("\"ThisIsntReal\"")) + if err == nil { + t.Error("Expected unmarshall error") + } } func TestBaseCode(t *testing.T) { var main BaseCodes if main.HasData() { - t.Errorf("Test Failed - BaseCode HasData() error expected false but received %v", + t.Errorf("BaseCode HasData() error expected false but received %v", main.HasData()) } catsCode := main.Register("CATS") if !main.HasData() { - t.Errorf("Test Failed - BaseCode HasData() error expected true but received %v", + t.Errorf("BaseCode HasData() error expected true but received %v", main.HasData()) } if !main.Register("CATS").Match(catsCode) { - t.Errorf("Test Failed - BaseCode Match() error expected true but received %v", + t.Errorf("BaseCode Match() error expected true but received %v", false) } if main.Register("DOGS").Match(catsCode) { - t.Errorf("Test Failed - BaseCode Match() error expected false but received %v", + t.Errorf("BaseCode Match() error expected false but received %v", true) } loadedCurrencies := main.GetCurrencies() if loadedCurrencies.Contains(main.Register("OWLS")) { - t.Errorf("Test Failed - BaseCode Contains() error expected false but received %v", + t.Errorf("BaseCode Contains() error expected false but received %v", true) } if !loadedCurrencies.Contains(catsCode) { - t.Errorf("Test Failed - BaseCode Contains() error expected true but received %v", + t.Errorf("BaseCode Contains() error expected true but received %v", false) } err := main.UpdateContract("Bitcoin Perpetual", "XBTUSD", "Bitmex") if err != nil { - t.Error("Test Failed - BaseCode UpdateContract error", err) + t.Error("BaseCode UpdateContract error", err) } err = main.UpdateCryptocurrency("Bitcoin", "BTC", 1337) if err != nil { - t.Error("Test Failed - BaseCode UpdateContract error", err) + t.Error("BaseCode UpdateContract error", err) + } + + err = main.UpdateFiatCurrency("Unreal Dollar", "AUD", 1111) + if err != nil { + t.Error("BaseCode UpdateContract error", err) + } + if main.Items[5].FullName != "Unreal Dollar" { + t.Error("Expected fullname to update for AUD") } err = main.UpdateFiatCurrency("Australian Dollar", "AUD", 1336) if err != nil { - t.Error("Test Failed - BaseCode UpdateContract error", err) + t.Error("BaseCode UpdateContract error", err) + } + + main.Items[5].Role = Unset + err = main.UpdateFiatCurrency("Australian Dollar", "AUD", 1336) + if err != nil { + t.Error("BaseCode UpdateContract error", err) + } + if main.Items[5].Role != Fiat { + t.Error("Expected role to change to Fiat") } err = main.UpdateToken("Populous", "PPT", "ETH", 1335) if err != nil { - t.Error("Test Failed - BaseCode UpdateContract error", err) + t.Error("BaseCode UpdateContract error", err) } contract := main.Register("XBTUSD") if contract.IsFiatCurrency() { - t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but received %v", + t.Errorf("BaseCode IsFiatCurrency() error expected false but received %v", true) } if contract.IsCryptocurrency() { - t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but received %v", + t.Errorf("BaseCode IsFiatCurrency() error expected false but received %v", true) } if contract.IsDefaultFiatCurrency() { - t.Errorf("Test Failed - BaseCode IsDefaultFiatCurrency() error expected false but received %v", + t.Errorf("BaseCode IsDefaultFiatCurrency() error expected false but received %v", true) } if contract.IsDefaultFiatCurrency() { - t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but received %v", + t.Errorf("BaseCode IsFiatCurrency() error expected false but received %v", true) } @@ -220,50 +243,101 @@ func TestBaseCode(t *testing.T) { Symbol: "ADA", }) if err != nil { - t.Error("Test Failed - BaseCode LoadItem() error", err) + t.Error("BaseCode LoadItem() error", err) } full, err := main.GetFullCurrencyData() if err != nil { - t.Error("Test Failed - BaseCode GetFullCurrencyData error", err) + t.Error("BaseCode GetFullCurrencyData error", err) } if len(full.Contracts) != 1 { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but received %v", + t.Errorf("BaseCode GetFullCurrencyData() error expected 1 but received %v", len(full.Contracts)) } if len(full.Cryptocurrency) != 2 { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but received %v", + t.Errorf("BaseCode GetFullCurrencyData() error expected 1 but received %v", len(full.Cryptocurrency)) } if len(full.FiatCurrency) != 1 { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but received %v", + t.Errorf("BaseCode GetFullCurrencyData() error expected 1 but received %v", len(full.FiatCurrency)) } if len(full.Token) != 1 { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but received %v", + t.Errorf("BaseCode GetFullCurrencyData() error expected 1 but received %v", len(full.Token)) } if len(full.UnsetCurrency) != 3 { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 3 but received %v", + t.Errorf("BaseCode GetFullCurrencyData() error expected 3 but received %v", len(full.UnsetCurrency)) } if !full.LastMainUpdate.IsZero() { - t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 0 but received %s", + t.Errorf("BaseCode GetFullCurrencyData() error expected 0 but received %s", full.LastMainUpdate) } + + err = main.LoadItem(&Item{ + ID: 0, + FullName: "Cardano", + Role: Role(99), + Symbol: "ADA", + }) + if err != nil { + t.Error("BaseCode LoadItem() error", err) + } + _, err = main.GetFullCurrencyData() + if err == nil { + t.Error("Expected 'Role undefined'") + } + + main.Items[0].FullName = "Hello" + err = main.UpdateCryptocurrency("MEWOW", "CATS", 1338) + if err != nil { + t.Error("BaseCode UpdateContract error", err) + } + if main.Items[0].FullName != "MEWOW" { + t.Error("Fullname not updated") + } + err = main.UpdateCryptocurrency("MEWOW", "CATS", 1338) + if err != nil { + t.Error("BaseCode UpdateContract error", err) + } + + main.Items[0].Role = Cryptocurrency + err = main.UpdateCryptocurrency("MEWOW", "CATS", 3) + if err != nil { + t.Error("BaseCode UpdateContract error", err) + } + if main.Items[0].ID != 3 { + t.Error("ID not updated") + } + + main.Items[0].Role = Unset + err = main.UpdateCryptocurrency("MEWOW", "CATS", 1338) + if err != nil { + t.Error("BaseCode UpdateContract error", err) + } + if main.Items[0].ID != 1338 { + t.Error("ID not updated") + } + + main.Items[0].Role = Token + err = main.UpdateCryptocurrency("MEWOW", "CATS", 3) + if err == nil { + t.Error("Expecting cryptocurrency to already exist") + } } func TestCodeString(t *testing.T) { expected := "TEST" cc := NewCode("TEST") if cc.String() != expected { - t.Errorf("Test Failed - Currency Code String() error expected %s but received %s", + t.Errorf("Currency Code String() error expected %s but received %s", expected, cc) } } @@ -272,7 +346,7 @@ func TestCodeLower(t *testing.T) { expected := "test" cc := NewCode("TEST") if cc.Lower().String() != expected { - t.Errorf("Test Failed - Currency Code Lower() error expected %s but received %s", + t.Errorf("Currency Code Lower() error expected %s but received %s", expected, cc.Lower()) } @@ -282,7 +356,7 @@ func TestCodeUpper(t *testing.T) { expected := "TEST" cc := NewCode("test") if cc.Upper().String() != expected { - t.Errorf("Test Failed - Currency Code Upper() error expected %s but received %s", + t.Errorf("Currency Code Upper() error expected %s but received %s", expected, cc.Upper()) } @@ -293,21 +367,21 @@ func TestCodeUnmarshalJSON(t *testing.T) { expected := "BRO" encoded, err := common.JSONEncode(expected) if err != nil { - t.Fatal("Test Failed - Currency Code UnmarshalJSON error", err) + t.Fatal("Currency Code UnmarshalJSON error", err) } err = common.JSONDecode(encoded, &unmarshalHere) if err != nil { - t.Fatal("Test Failed - Currency Code UnmarshalJSON error", err) + t.Fatal("Currency Code UnmarshalJSON error", err) } err = common.JSONDecode(encoded, &unmarshalHere) if err != nil { - t.Fatal("Test Failed - Currency Code UnmarshalJSON error", err) + t.Fatal("Currency Code UnmarshalJSON error", err) } if unmarshalHere.String() != expected { - t.Errorf("Test Failed - Currency Code Upper() error expected %s but received %s", + t.Errorf("Currency Code Upper() error expected %s but received %s", expected, unmarshalHere) } @@ -324,11 +398,11 @@ func TestCodeMarshalJSON(t *testing.T) { encoded, err := common.JSONEncode(quickstruct) if err != nil { - t.Fatal("Test Failed - Currency Code UnmarshalJSON error", err) + t.Fatal("Currency Code UnmarshalJSON error", err) } if string(encoded) != expectedJSON { - t.Errorf("Test Failed - Currency Code Upper() error expected %s but received %s", + t.Errorf("Currency Code Upper() error expected %s but received %s", expectedJSON, string(encoded)) } @@ -341,42 +415,42 @@ func TestCodeMarshalJSON(t *testing.T) { encoded, err = common.JSONEncode(quickstruct) if err != nil { - t.Fatal("Test Failed - Currency Code UnmarshalJSON error", err) + t.Fatal("Currency Code UnmarshalJSON error", err) } newExpectedJSON := `{"sweetCodes":""}` if string(encoded) != newExpectedJSON { - t.Errorf("Test Failed - Currency Code Upper() error expected %s but received %s", + t.Errorf("Currency Code Upper() error expected %s but received %s", newExpectedJSON, string(encoded)) } } func TestIsDefaultCurrency(t *testing.T) { if !USD.IsDefaultFiatCurrency() { - t.Errorf("Test Failed. TestIsDefaultCurrency Cannot match currency %s.", + t.Errorf("TestIsDefaultCurrency Cannot match currency %s.", USD) } if !AUD.IsDefaultFiatCurrency() { - t.Errorf("Test Failed. TestIsDefaultCurrency Cannot match currency, %s.", + t.Errorf("TestIsDefaultCurrency Cannot match currency, %s.", AUD) } if LTC.IsDefaultFiatCurrency() { - t.Errorf("Test Failed. TestIsDefaultCurrency Function return is incorrect with, %s.", + t.Errorf("TestIsDefaultCurrency Function return is incorrect with, %s.", LTC) } } func TestIsDefaultCryptocurrency(t *testing.T) { if !BTC.IsDefaultCryptocurrency() { - t.Errorf("Test Failed. TestIsDefaultCryptocurrency cannot match currency, %s.", + t.Errorf("TestIsDefaultCryptocurrency cannot match currency, %s.", BTC) } if !LTC.IsDefaultCryptocurrency() { - t.Errorf("Test Failed. TestIsDefaultCryptocurrency cannot match currency, %s.", + t.Errorf("TestIsDefaultCryptocurrency cannot match currency, %s.", LTC) } if AUD.IsDefaultCryptocurrency() { - t.Errorf("Test Failed. TestIsDefaultCryptocurrency function return is incorrect with, %s.", + t.Errorf("TestIsDefaultCryptocurrency function return is incorrect with, %s.", AUD) } } @@ -384,30 +458,30 @@ func TestIsDefaultCryptocurrency(t *testing.T) { func TestIsFiatCurrency(t *testing.T) { if !USD.IsFiatCurrency() { t.Errorf( - "Test Failed. TestIsFiatCurrency cannot match currency, %s.", USD) + "TestIsFiatCurrency cannot match currency, %s.", USD) } if !CNY.IsFiatCurrency() { t.Errorf( - "Test Failed. TestIsFiatCurrency cannot match currency, %s.", CNY) + "TestIsFiatCurrency cannot match currency, %s.", CNY) } if LINO.IsFiatCurrency() { t.Errorf( - "Test Failed. TestIsFiatCurrency cannot match currency, %s.", LINO, + "TestIsFiatCurrency cannot match currency, %s.", LINO, ) } } func TestIsCryptocurrency(t *testing.T) { if !BTC.IsCryptocurrency() { - t.Errorf("Test Failed. TestIsFiatCurrency cannot match currency, %s.", + t.Errorf("TestIsFiatCurrency cannot match currency, %s.", BTC) } if !LTC.IsCryptocurrency() { - t.Errorf("Test Failed. TestIsFiatCurrency cannot match currency, %s.", + t.Errorf("TestIsFiatCurrency cannot match currency, %s.", LTC) } if AUD.IsCryptocurrency() { - t.Errorf("Test Failed. TestIsFiatCurrency cannot match currency, %s.", + t.Errorf("TestIsFiatCurrency cannot match currency, %s.", AUD) } } @@ -419,7 +493,7 @@ func TestItemString(t *testing.T) { } if newItem.String() != expected { - t.Errorf("Test Failed - Item String() error expected %s but received %s", + t.Errorf("Item String() error expected %s but received %s", expected, &newItem) } diff --git a/currency/code_types.go b/currency/code_types.go new file mode 100644 index 00000000..d0aa6b12 --- /dev/null +++ b/currency/code_types.go @@ -0,0 +1,1655 @@ +package currency + +import ( + "sync" + "time" +) + +// Bitmasks const for currency rolls +const ( + Unset Role = 0 + Fiat Role = 1 << (iota - 1) + Cryptocurrency + Token + Contract + + UnsetRollString = "roleUnset" + FiatCurrencyString = "fiatCurrency" + CryptocurrencyString = "cryptocurrency" + TokenString = "token" + ContractString = "contract" +) + +// Role defines a bitmask for the full currency rolls either; fiat, +// cryptocurrency, token, or contract +type Role uint8 + +// BaseCodes defines a basket of bare currency codes +type BaseCodes struct { + Items []*Item + LastMainUpdate time.Time + mtx sync.Mutex +} + +// Code defines an ISO 4217 fiat currency or unofficial cryptocurrency code +// string +type Code struct { + Item *Item + UpperCase bool +} + +// Item defines a sub type containing the main attributes of a designated +// currency code pointer +type Item struct { + ID int `json:"id"` + FullName string `json:"fullName"` + Symbol string `json:"symbol"` + Role Role `json:"role"` + AssocChain string `json:"associatedBlockchain"` + AssocExchange []string `json:"associatedExchanges"` +} + +// Const declarations for individual currencies/tokens/fiat +// An ever growing list. Cares not for equivalence, just is +var ( + BTC = NewCode("BTC") + LTC = NewCode("LTC") + ETH = NewCode("ETH") + XRP = NewCode("XRP") + BCH = NewCode("BCH") + EOS = NewCode("EOS") + XLM = NewCode("XLM") + USDT = NewCode("USDT") + ADA = NewCode("ADA") + XMR = NewCode("XMR") + TRX = NewCode("TRX") + MIOTA = NewCode("MIOTA") + DASH = NewCode("DASH") + BNB = NewCode("BNB") + NEO = NewCode("NEO") + ETC = NewCode("ETC") + XEM = NewCode("XEM") + XTZ = NewCode("XTZ") + VET = NewCode("VET") + DOGE = NewCode("DOGE") + ZEC = NewCode("ZEC") + OMG = NewCode("OMG") + BTG = NewCode("BTG") + MKR = NewCode("MKR") + BCN = NewCode("BCN") + ONT = NewCode("ONT") + ZRX = NewCode("ZRX") + LSK = NewCode("LSK") + DCR = NewCode("DCR") + QTUM = NewCode("QTUM") + BCD = NewCode("BCD") + BTS = NewCode("BTS") + NANO = NewCode("NANO") + ZIL = NewCode("ZIL") + SC = NewCode("SC") + DGB = NewCode("DGB") + ICX = NewCode("ICX") + STEEM = NewCode("STEEM") + AE = NewCode("AE") + XVG = NewCode("XVG") + WAVES = NewCode("WAVES") + NPXS = NewCode("NPXS") + ETN = NewCode("ETN") + BTM = NewCode("BTM") + BAT = NewCode("BAT") + ETP = NewCode("ETP") + HOT = NewCode("HOT") + STRAT = NewCode("STRAT") // nolint: misspell + GNT = NewCode("GNT") + REP = NewCode("REP") + SNT = NewCode("SNT") + PPT = NewCode("PPT") + KMD = NewCode("KMD") + TUSD = NewCode("TUSD") + CNX = NewCode("CNX") + LINK = NewCode("LINK") + WTC = NewCode("WTC") + ARDR = NewCode("ARDR") + WAN = NewCode("WAN") + MITH = NewCode("MITH") + RDD = NewCode("RDD") + IOST = NewCode("IOST") + IOT = NewCode("IOT") + KCS = NewCode("KCS") + MAID = NewCode("MAID") + XET = NewCode("XET") + MOAC = NewCode("MOAC") + HC = NewCode("HC") + AION = NewCode("AION") + AOA = NewCode("AOA") + HT = NewCode("HT") + ELF = NewCode("ELF") + LRC = NewCode("LRC") + BNT = NewCode("BNT") + CMT = NewCode("CMT") + DGD = NewCode("DGD") + DCN = NewCode("DCN") + FUN = NewCode("FUN") + GXS = NewCode("GXS") + DROP = NewCode("DROP") + MANA = NewCode("MANA") + PAY = NewCode("PAY") + MCO = NewCode("MCO") + THETA = NewCode("THETA") + NXT = NewCode("NXT") + NOAH = NewCode("NOAH") + LOOM = NewCode("LOOM") + POWR = NewCode("POWR") + WAX = NewCode("WAX") + ELA = NewCode("ELA") + PIVX = NewCode("PIVX") + XIN = NewCode("XIN") + DAI = NewCode("DAI") + BTCP = NewCode("BTCP") + NEXO = NewCode("NEXO") + XBT = NewCode("XBT") + SAN = NewCode("SAN") + GAS = NewCode("GAS") + BCC = NewCode("BCC") + HCC = NewCode("HCC") + OAX = NewCode("OAX") + DNT = NewCode("DNT") + ICN = NewCode("ICN") + LLT = NewCode("LLT") + YOYO = NewCode("YOYO") + SNGLS = NewCode("SNGLS") + BQX = NewCode("BQX") + KNC = NewCode("KNC") + SNM = NewCode("SNM") + CTR = NewCode("CTR") + SALT = NewCode("SALT") + MDA = NewCode("MDA") + IOTA = NewCode("IOTA") + SUB = NewCode("SUB") + MTL = NewCode("MTL") + MTH = NewCode("MTH") + ENG = NewCode("ENG") + AST = NewCode("AST") + CLN = NewCode("CLN") + EDG = NewCode("EDG") + FIRST = NewCode("1ST") + GOLOS = NewCode("GOLOS") + ANT = NewCode("ANT") + GBG = NewCode("GBG") + HMQ = NewCode("HMQ") + INCNT = NewCode("INCNT") + ACE = NewCode("ACE") + ACT = NewCode("ACT") + AAC = NewCode("AAC") + AIDOC = NewCode("AIDOC") + SOC = NewCode("SOC") + ATL = NewCode("ATL") + AVT = NewCode("AVT") + BKX = NewCode("BKX") + BEC = NewCode("BEC") + VEE = NewCode("VEE") + PTOY = NewCode("PTOY") + CAG = NewCode("CAG") + CIC = NewCode("CIC") + CBT = NewCode("CBT") + CAN = NewCode("CAN") + DAT = NewCode("DAT") + DNA = NewCode("DNA") + INT = NewCode("INT") + IPC = NewCode("IPC") + ILA = NewCode("ILA") + LIGHT = NewCode("LIGHT") + MAG = NewCode("MAG") + AMM = NewCode("AMM") + MOF = NewCode("MOF") + MGC = NewCode("MGC") + OF = NewCode("OF") + LA = NewCode("LA") + LEV = NewCode("LEV") + NGC = NewCode("NGC") + OKB = NewCode("OKB") + MOT = NewCode("MOT") + PRA = NewCode("PRA") + R = NewCode("R") + SSC = NewCode("SSC") + SHOW = NewCode("SHOW") + SPF = NewCode("SPF") + SNC = NewCode("SNC") + SWFTC = NewCode("SWFTC") + TRA = NewCode("TRA") + TOPC = NewCode("TOPC") + TRIO = NewCode("TRIO") + QVT = NewCode("QVT") + UCT = NewCode("UCT") + UKG = NewCode("UKG") + UTK = NewCode("UTK") + VIU = NewCode("VIU") + WFEE = NewCode("WFEE") + WRC = NewCode("WRC") + UGC = NewCode("UGC") + YEE = NewCode("YEE") + YOYOW = NewCode("YOYOW") + ZIP = NewCode("ZIP") + READ = NewCode("READ") + RCT = NewCode("RCT") + REF = NewCode("REF") + XUC = NewCode("XUC") + FAIR = NewCode("FAIR") + GSC = NewCode("GSC") + HMC = NewCode("HMC") + PLU = NewCode("PLU") + PRO = NewCode("PRO") + QRL = NewCode("QRL") + REN = NewCode("REN") + ROUND = NewCode("ROUND") + SRN = NewCode("SRN") + XID = NewCode("XID") + SBD = NewCode("SBD") + TAAS = NewCode("TAAS") + TKN = NewCode("TKN") + VEN = NewCode("VEN") + VSL = NewCode("VSL") + TRST = NewCode("TRST") + XXX = NewCode("XXX") + IND = NewCode("IND") + LDC = NewCode("LDC") + GUP = NewCode("GUP") + MGO = NewCode("MGO") + MYST = NewCode("MYST") + NEU = NewCode("NEU") + NET = NewCode("NET") + BMC = NewCode("BMC") + BCAP = NewCode("BCAP") + TIME = NewCode("TIME") + CFI = NewCode("CFI") + EVX = NewCode("EVX") + REQ = NewCode("REQ") + VIB = NewCode("VIB") + ARK = NewCode("ARK") + MOD = NewCode("MOD") + ENJ = NewCode("ENJ") + STORJ = NewCode("STORJ") + RCN = NewCode("RCN") + NULS = NewCode("NULS") + RDN = NewCode("RDN") + DLT = NewCode("DLT") + AMB = NewCode("AMB") + BCPT = NewCode("BCPT") + ARN = NewCode("ARN") + GVT = NewCode("GVT") + CDT = NewCode("CDT") + POE = NewCode("POE") + QSP = NewCode("QSP") + XZC = NewCode("XZC") + TNT = NewCode("TNT") + FUEL = NewCode("FUEL") + ADX = NewCode("ADX") + CND = NewCode("CND") + LEND = NewCode("LEND") + WABI = NewCode("WABI") + SBTC = NewCode("SBTC") + BCX = NewCode("BCX") + TNB = NewCode("TNB") + GTO = NewCode("GTO") + OST = NewCode("OST") + CVC = NewCode("CVC") + DATA = NewCode("DATA") + ETF = NewCode("ETF") + BRD = NewCode("BRD") + NEBL = NewCode("NEBL") + VIBE = NewCode("VIBE") + LUN = NewCode("LUN") + CHAT = NewCode("CHAT") + RLC = NewCode("RLC") + INS = NewCode("INS") + VIA = NewCode("VIA") + BLZ = NewCode("BLZ") + SYS = NewCode("SYS") + NCASH = NewCode("NCASH") + POA = NewCode("POA") + STORM = NewCode("STORM") + WPR = NewCode("WPR") + QLC = NewCode("QLC") + GRS = NewCode("GRS") + CLOAK = NewCode("CLOAK") + ZEN = NewCode("ZEN") + SKY = NewCode("SKY") + IOTX = NewCode("IOTX") + QKC = NewCode("QKC") + AGI = NewCode("AGI") + NXS = NewCode("NXS") + EON = NewCode("EON") + KEY = NewCode("KEY") + NAS = NewCode("NAS") + ADD = NewCode("ADD") + MEETONE = NewCode("MEETONE") + ATD = NewCode("ATD") + MFT = NewCode("MFT") + EOP = NewCode("EOP") + DENT = NewCode("DENT") + IQ = NewCode("IQ") + DOCK = NewCode("DOCK") + POLY = NewCode("POLY") + VTHO = NewCode("VTHO") + ONG = NewCode("ONG") + PHX = NewCode("PHX") + GO = NewCode("GO") + PAX = NewCode("PAX") + EDO = NewCode("EDO") + WINGS = NewCode("WINGS") + NAV = NewCode("NAV") + TRIG = NewCode("TRIG") + APPC = NewCode("APPC") + KRW = NewCode("KRW") + HSR = NewCode("HSR") + ETHOS = NewCode("ETHOS") + CTXC = NewCode("CTXC") + ITC = NewCode("ITC") + TRUE = NewCode("TRUE") + ABT = NewCode("ABT") + RNT = NewCode("RNT") + PLY = NewCode("PLY") + PST = NewCode("PST") + KICK = NewCode("KICK") + BTCZ = NewCode("BTCZ") + DXT = NewCode("DXT") + STQ = NewCode("STQ") + INK = NewCode("INK") + HBZ = NewCode("HBZ") + USDT_ETH = NewCode("USDT_ETH") // nolint: golint,stylecheck + QTUM_ETH = NewCode("QTUM_ETH") // nolint: golint + BTM_ETH = NewCode("BTM_ETH") // nolint: golint + FIL = NewCode("FIL") + STX = NewCode("STX") + BOT = NewCode("BOT") + VERI = NewCode("VERI") + ZSC = NewCode("ZSC") + QBT = NewCode("QBT") + MED = NewCode("MED") + QASH = NewCode("QASH") + MDS = NewCode("MDS") + GOD = NewCode("GOD") + SMT = NewCode("SMT") + BTF = NewCode("BTF") + NAS_ETH = NewCode("NAS_ETH") // nolint: golint + TSL = NewCode("TSL") + BIFI = NewCode("BIFI") + BNTY = NewCode("BNTY") + DRGN = NewCode("DRGN") + GTC = NewCode("GTC") + MDT = NewCode("MDT") + QUN = NewCode("QUN") + GNX = NewCode("GNX") + DDD = NewCode("DDD") + BTO = NewCode("BTO") + TIO = NewCode("TIO") + OCN = NewCode("OCN") + RUFF = NewCode("RUFF") + TNC = NewCode("TNC") + SNET = NewCode("SNET") + COFI = NewCode("COFI") + ZPT = NewCode("ZPT") + JNT = NewCode("JNT") + MTN = NewCode("MTN") + GEM = NewCode("GEM") + DADI = NewCode("DADI") + RFR = NewCode("RFR") + MOBI = NewCode("MOBI") + LEDU = NewCode("LEDU") + DBC = NewCode("DBC") + MKR_OLD = NewCode("MKR_OLD") // nolint: golint + DPY = NewCode("DPY") + BCDN = NewCode("BCDN") + EOSDAC = NewCode("EOSDAC") // nolint: golint + TIPS = NewCode("TIPS") + XMC = NewCode("XMC") + PPS = NewCode("PPS") + BOE = NewCode("BOE") + MEDX = NewCode("MEDX") + SMT_ETH = NewCode("SMT_ETH") // nolint: golint + CS = NewCode("CS") + MAN = NewCode("MAN") + REM = NewCode("REM") + LYM = NewCode("LYM") + INSTAR = NewCode("INSTAR") // nolint: golint + BFT = NewCode("BFT") + IHT = NewCode("IHT") + SENC = NewCode("SENC") + TOMO = NewCode("TOMO") + ELEC = NewCode("ELEC") + SHIP = NewCode("SHIP") + TFD = NewCode("TFD") + HAV = NewCode("HAV") + HUR = NewCode("HUR") + LST = NewCode("LST") + LINO = NewCode("LINO") + SWTH = NewCode("SWTH") + NKN = NewCode("NKN") + SOUL = NewCode("SOUL") + GALA_NEO = NewCode("GALA_NEO") // nolint: golint + LRN = NewCode("LRN") + GSE = NewCode("GSE") + RATING = NewCode("RATING") + HSC = NewCode("HSC") + HIT = NewCode("HIT") + DX = NewCode("DX") + BXC = NewCode("BXC") + GARD = NewCode("GARD") + FTI = NewCode("FTI") + SOP = NewCode("SOP") + LEMO = NewCode("LEMO") + RED = NewCode("RED") + LBA = NewCode("LBA") + KAN = NewCode("KAN") + OPEN = NewCode("OPEN") + SKM = NewCode("SKM") + NBAI = NewCode("NBAI") + UPP = NewCode("UPP") + ATMI = NewCode("ATMI") + TMT = NewCode("TMT") + BBK = NewCode("BBK") + EDR = NewCode("EDR") + MET = NewCode("MET") + TCT = NewCode("TCT") + EXC = NewCode("EXC") + CNC = NewCode("CNC") + TIX = NewCode("TIX") + XTC = NewCode("XTC") + BU = NewCode("BU") + GNO = NewCode("GNO") + MLN = NewCode("MLN") + XBC = NewCode("XBC") + BTCD = NewCode("BTCD") + BURST = NewCode("BURST") + CLAM = NewCode("CLAM") + XCP = NewCode("XCP") + EMC2 = NewCode("EMC2") + EXP = NewCode("EXP") + FCT = NewCode("FCT") + GAME = NewCode("GAME") + GRC = NewCode("GRC") + HUC = NewCode("HUC") + LBC = NewCode("LBC") + NMC = NewCode("NMC") + NEOS = NewCode("NEOS") + OMNI = NewCode("OMNI") + PASC = NewCode("PASC") + PPC = NewCode("PPC") + DSH = NewCode("DSH") + GML = NewCode("GML") + GSY = NewCode("GSY") + POT = NewCode("POT") + XPM = NewCode("XPM") + AMP = NewCode("AMP") + VRC = NewCode("VRC") + VTC = NewCode("VTC") + ZERO07 = NewCode("007") + BIT16 = NewCode("BIT16") + TWO015 = NewCode("2015") + TWO56 = NewCode("256") + TWOBACCO = NewCode("2BACCO") + TWOGIVE = NewCode("2GIVE") + THIRTY2BIT = NewCode("32BIT") + THREE65 = NewCode("365") + FOUR04 = NewCode("404") + SEVEN00 = NewCode("700") + EIGHTBIT = NewCode("8BIT") + ACLR = NewCode("ACLR") + ACES = NewCode("ACES") + ACPR = NewCode("ACPR") + ACID = NewCode("ACID") + ACOIN = NewCode("ACOIN") + ACRN = NewCode("ACRN") + ADAM = NewCode("ADAM") + ADT = NewCode("ADT") + AIB = NewCode("AIB") + ADZ = NewCode("ADZ") + AECC = NewCode("AECC") + AM = NewCode("AM") + AGRI = NewCode("AGRI") + AGT = NewCode("AGT") + AIR = NewCode("AIR") + ALEX = NewCode("ALEX") + AUM = NewCode("AUM") + ALIEN = NewCode("ALIEN") + ALIS = NewCode("ALIS") + ALL = NewCode("ALL") + ASAFE = NewCode("ASAFE") + AMBER = NewCode("AMBER") + AMS = NewCode("AMS") + ANAL = NewCode("ANAL") + ACP = NewCode("ACP") + ANI = NewCode("ANI") + ANTI = NewCode("ANTI") + ALTC = NewCode("ALTC") + APT = NewCode("APT") + ARCO = NewCode("ARCO") + ALC = NewCode("ALC") + ARB = NewCode("ARB") + ARCT = NewCode("ARCT") + ARCX = NewCode("ARCX") + ARGUS = NewCode("ARGUS") + ARH = NewCode("ARH") + ARM = NewCode("ARM") + ARNA = NewCode("ARNA") + ARPA = NewCode("ARPA") + ARTA = NewCode("ARTA") + ABY = NewCode("ABY") + ARTC = NewCode("ARTC") + AL = NewCode("AL") + ASN = NewCode("ASN") + ADCN = NewCode("ADCN") + ATB = NewCode("ATB") + ATM = NewCode("ATM") + ATMCHA = NewCode("ATMCHA") + ATOM = NewCode("ATOM") + ADC = NewCode("ADC") + ARE = NewCode("ARE") + AUR = NewCode("AUR") + AV = NewCode("AV") + AXIOM = NewCode("AXIOM") + B2B = NewCode("B2B") + B2 = NewCode("B2") + B3 = NewCode("B3") + BAB = NewCode("BAB") + BAN = NewCode("BAN") + BamitCoin = NewCode("BamitCoin") + NANAS = NewCode("NANAS") + BBCC = NewCode("BBCC") + BTA = NewCode("BTA") + BSTK = NewCode("BSTK") + BATL = NewCode("BATL") + BBH = NewCode("BBH") + BITB = NewCode("BITB") + BRDD = NewCode("BRDD") + XBTS = NewCode("XBTS") + BVC = NewCode("BVC") + CHATX = NewCode("CHATX") + BEEP = NewCode("BEEP") + BEEZ = NewCode("BEEZ") + BENJI = NewCode("BENJI") + BERN = NewCode("BERN") + PROFIT = NewCode("PROFIT") + BEST = NewCode("BEST") + BGF = NewCode("BGF") + BIGUP = NewCode("BIGUP") + BLRY = NewCode("BLRY") + BILL = NewCode("BILL") + BIOB = NewCode("BIOB") + BIO = NewCode("BIO") + BIOS = NewCode("BIOS") + BPTN = NewCode("BPTN") + BTCA = NewCode("BTCA") + BA = NewCode("BA") + BAC = NewCode("BAC") + BBT = NewCode("BBT") + BOSS = NewCode("BOSS") + BRONZ = NewCode("BRONZ") + CAT = NewCode("CAT") + BTD = NewCode("BTD") + XBTC21 = NewCode("XBTC21") + BCA = NewCode("BCA") + BCP = NewCode("BCP") + BTDOLL = NewCode("BTDOLL") + LIZA = NewCode("LIZA") + BTCRED = NewCode("BTCRED") + BTCS = NewCode("BTCS") + BTU = NewCode("BTU") + BUM = NewCode("BUM") + LITE = NewCode("LITE") + BCM = NewCode("BCM") + BCS = NewCode("BCS") + BTCU = NewCode("BTCU") + BM = NewCode("BM") + BTCRY = NewCode("BTCRY") + BTCR = NewCode("BTCR") + HIRE = NewCode("HIRE") + STU = NewCode("STU") + BITOK = NewCode("BITOK") + BITON = NewCode("BITON") + BPC = NewCode("BPC") + BPOK = NewCode("BPOK") + BTP = NewCode("BTP") + BITCNY = NewCode("bitCNY") + RNTB = NewCode("RNTB") + BSH = NewCode("BSH") + XBS = NewCode("XBS") + BITS = NewCode("BITS") + BST = NewCode("BST") + BXT = NewCode("BXT") + VEG = NewCode("VEG") + VOLT = NewCode("VOLT") + BTV = NewCode("BTV") + BITZ = NewCode("BITZ") + BTZ = NewCode("BTZ") + BHC = NewCode("BHC") + BDC = NewCode("BDC") + JACK = NewCode("JACK") + BS = NewCode("BS") + BSTAR = NewCode("BSTAR") + BLAZR = NewCode("BLAZR") + BOD = NewCode("BOD") + BLUE = NewCode("BLUE") + BLU = NewCode("BLU") + BLUS = NewCode("BLUS") + BMT = NewCode("BMT") + BOLI = NewCode("BOLI") + BOMB = NewCode("BOMB") + BON = NewCode("BON") + BOOM = NewCode("BOOM") + BOSON = NewCode("BOSON") + BSC = NewCode("BSC") + BRH = NewCode("BRH") + BRAIN = NewCode("BRAIN") + BRE = NewCode("BRE") + BTCM = NewCode("BTCM") + BTCO = NewCode("BTCO") + TALK = NewCode("TALK") + BUB = NewCode("BUB") + BUY = NewCode("BUY") + BUZZ = NewCode("BUZZ") + BTH = NewCode("BTH") + C0C0 = NewCode("C0C0") + CAB = NewCode("CAB") + CF = NewCode("CF") + CLO = NewCode("CLO") + CAM = NewCode("CAM") + CD = NewCode("CD") + CANN = NewCode("CANN") + CNNC = NewCode("CNNC") + CPC = NewCode("CPC") + CST = NewCode("CST") + CAPT = NewCode("CAPT") + CARBON = NewCode("CARBON") + CME = NewCode("CME") + CTK = NewCode("CTK") + CBD = NewCode("CBD") + CCC = NewCode("CCC") + CNT = NewCode("CNT") + XCE = NewCode("XCE") + CHRG = NewCode("CHRG") + CHEMX = NewCode("CHEMX") + CHESS = NewCode("CHESS") + CKS = NewCode("CKS") + CHILL = NewCode("CHILL") + CHIP = NewCode("CHIP") + CHOOF = NewCode("CHOOF") + CRX = NewCode("CRX") + CIN = NewCode("CIN") + POLL = NewCode("POLL") + CLICK = NewCode("CLICK") + CLINT = NewCode("CLINT") + CLUB = NewCode("CLUB") + CLUD = NewCode("CLUD") + COX = NewCode("COX") + COXST = NewCode("COXST") + CFC = NewCode("CFC") + CTIC2 = NewCode("CTIC2") + COIN = NewCode("COIN") + BTTF = NewCode("BTTF") + C2 = NewCode("C2") + CAID = NewCode("CAID") + CL = NewCode("CL") + CTIC = NewCode("CTIC") + CXT = NewCode("CXT") + CHP = NewCode("CHP") + CV2 = NewCode("CV2") + COC = NewCode("COC") + COMP = NewCode("COMP") + CMS = NewCode("CMS") + CONX = NewCode("CONX") + CCX = NewCode("CCX") + CLR = NewCode("CLR") + CORAL = NewCode("CORAL") + CORG = NewCode("CORG") + CSMIC = NewCode("CSMIC") + CMC = NewCode("CMC") + COV = NewCode("COV") + COVX = NewCode("COVX") + CRAB = NewCode("CRAB") + CRAFT = NewCode("CRAFT") + CRNK = NewCode("CRNK") + CRAVE = NewCode("CRAVE") + CRM = NewCode("CRM") + XCRE = NewCode("XCRE") + CREDIT = NewCode("CREDIT") + CREVA = NewCode("CREVA") + CRIME = NewCode("CRIME") + CROC = NewCode("CROC") + CRC = NewCode("CRC") + CRW = NewCode("CRW") + CRY = NewCode("CRY") + CBX = NewCode("CBX") + TKTX = NewCode("TKTX") + CB = NewCode("CB") + CIRC = NewCode("CIRC") + CCB = NewCode("CCB") + CDO = NewCode("CDO") + CG = NewCode("CG") + CJ = NewCode("CJ") + CJC = NewCode("CJC") + CYT = NewCode("CYT") + CRPS = NewCode("CRPS") + PING = NewCode("PING") + CWXT = NewCode("CWXT") + CCT = NewCode("CCT") + CTL = NewCode("CTL") + CURVES = NewCode("CURVES") + CC = NewCode("CC") + CYC = NewCode("CYC") + CYG = NewCode("CYG") + CYP = NewCode("CYP") + FUNK = NewCode("FUNK") + CZECO = NewCode("CZECO") + DALC = NewCode("DALC") + DLISK = NewCode("DLISK") + MOOND = NewCode("MOOND") + DB = NewCode("DB") + DCC = NewCode("DCC") + DCYP = NewCode("DCYP") + DETH = NewCode("DETH") + DKC = NewCode("DKC") + DISK = NewCode("DISK") + DRKT = NewCode("DRKT") + DTT = NewCode("DTT") + DASHS = NewCode("DASHS") + DBTC = NewCode("DBTC") + DCT = NewCode("DCT") + DBET = NewCode("DBET") + DEC = NewCode("DEC") + DECR = NewCode("DECR") + DEA = NewCode("DEA") + DPAY = NewCode("DPAY") + DCRE = NewCode("DCRE") + DC = NewCode("DC") + DES = NewCode("DES") + DEM = NewCode("DEM") + DXC = NewCode("DXC") + DCK = NewCode("DCK") + CUBE = NewCode("CUBE") + DGMS = NewCode("DGMS") + DBG = NewCode("DBG") + DGCS = NewCode("DGCS") + DBLK = NewCode("DBLK") + DIME = NewCode("DIME") + DIRT = NewCode("DIRT") + DVD = NewCode("DVD") + DMT = NewCode("DMT") + NOTE = NewCode("NOTE") + DGORE = NewCode("DGORE") + DLC = NewCode("DLC") + DRT = NewCode("DRT") + DOTA = NewCode("DOTA") + DOX = NewCode("DOX") + DRA = NewCode("DRA") + DFT = NewCode("DFT") + XDB = NewCode("XDB") + DRM = NewCode("DRM") + DRZ = NewCode("DRZ") + DRACO = NewCode("DRACO") + DBIC = NewCode("DBIC") + DUB = NewCode("DUB") + GUM = NewCode("GUM") + DUR = NewCode("DUR") + DUST = NewCode("DUST") + DUX = NewCode("DUX") + DXO = NewCode("DXO") + ECN = NewCode("ECN") + EDR2 = NewCode("EDR2") + EA = NewCode("EA") + EAGS = NewCode("EAGS") + EMT = NewCode("EMT") + EBONUS = NewCode("EBONUS") + ECCHI = NewCode("ECCHI") + EKO = NewCode("EKO") + ECLI = NewCode("ECLI") + ECOB = NewCode("ECOB") + ECO = NewCode("ECO") + EDIT = NewCode("EDIT") + EDRC = NewCode("EDRC") + EDC = NewCode("EDC") + EGAME = NewCode("EGAME") + EGG = NewCode("EGG") + EGO = NewCode("EGO") + ELC = NewCode("ELC") + ELCO = NewCode("ELCO") + ECA = NewCode("ECA") + EPC = NewCode("EPC") + ELE = NewCode("ELE") + ONE337 = NewCode("1337") + EMB = NewCode("EMB") + EMC = NewCode("EMC") + EPY = NewCode("EPY") + EMPC = NewCode("EMPC") + EMP = NewCode("EMP") + ENE = NewCode("ENE") + EET = NewCode("EET") + XNG = NewCode("XNG") + EGMA = NewCode("EGMA") + ENTER = NewCode("ENTER") + ETRUST = NewCode("ETRUST") + EQL = NewCode("EQL") + EQM = NewCode("EQM") + EQT = NewCode("EQT") + ERR = NewCode("ERR") + ESC = NewCode("ESC") + ESP = NewCode("ESP") + ENT = NewCode("ENT") + ETCO = NewCode("ETCO") + DOGETH = NewCode("DOGETH") + ECASH = NewCode("ECASH") + ELITE = NewCode("ELITE") + ETHS = NewCode("ETHS") + ETL = NewCode("ETL") + ETZ = NewCode("ETZ") + EUC = NewCode("EUC") + EURC = NewCode("EURC") + EUROPE = NewCode("EUROPE") + EVA = NewCode("EVA") + EGC = NewCode("EGC") + EOC = NewCode("EOC") + EVIL = NewCode("EVIL") + EVO = NewCode("EVO") + EXB = NewCode("EXB") + EXIT = NewCode("EXIT") + XT = NewCode("XT") + F16 = NewCode("F16") + FADE = NewCode("FADE") + FAZZ = NewCode("FAZZ") + FX = NewCode("FX") + FIDEL = NewCode("FIDEL") + FIDGT = NewCode("FIDGT") + FIND = NewCode("FIND") + FPC = NewCode("FPC") + FIRE = NewCode("FIRE") + FFC = NewCode("FFC") + FRST = NewCode("FRST") + FIST = NewCode("FIST") + FIT = NewCode("FIT") + FLX = NewCode("FLX") + FLVR = NewCode("FLVR") + FLY = NewCode("FLY") + FONZ = NewCode("FONZ") + XFCX = NewCode("XFCX") + FOREX = NewCode("FOREX") + FRN = NewCode("FRN") + FRK = NewCode("FRK") + FRWC = NewCode("FRWC") + FGZ = NewCode("FGZ") + FRE = NewCode("FRE") + FRDC = NewCode("FRDC") + FJC = NewCode("FJC") + FURY = NewCode("FURY") + FSN = NewCode("FSN") + FCASH = NewCode("FCASH") + FTO = NewCode("FTO") + FUZZ = NewCode("FUZZ") + GAKH = NewCode("GAKH") + GBT = NewCode("GBT") + UNITS = NewCode("UNITS") + FOUR20G = NewCode("420G") + GENIUS = NewCode("GENIUS") + GEN = NewCode("GEN") + GEO = NewCode("GEO") + GER = NewCode("GER") + GSR = NewCode("GSR") + SPKTR = NewCode("SPKTR") + GIFT = NewCode("GIFT") + WTT = NewCode("WTT") + GHS = NewCode("GHS") + GIG = NewCode("GIG") + GOT = NewCode("GOT") + XGTC = NewCode("XGTC") + GIZ = NewCode("GIZ") + GLO = NewCode("GLO") + GCR = NewCode("GCR") + BSTY = NewCode("BSTY") + GLC = NewCode("GLC") + GSX = NewCode("GSX") + GOAT = NewCode("GOAT") + GB = NewCode("GB") + GFL = NewCode("GFL") + MNTP = NewCode("MNTP") + GP = NewCode("GP") + GLUCK = NewCode("GLUCK") + GOON = NewCode("GOON") + GTFO = NewCode("GTFO") + GOTX = NewCode("GOTX") + GPU = NewCode("GPU") + GRF = NewCode("GRF") + GRAM = NewCode("GRAM") + GRAV = NewCode("GRAV") + GBIT = NewCode("GBIT") + GREED = NewCode("GREED") + GE = NewCode("GE") + GREENF = NewCode("GREENF") + GRE = NewCode("GRE") + GREXIT = NewCode("GREXIT") + GMCX = NewCode("GMCX") + GROW = NewCode("GROW") + GSM = NewCode("GSM") + GT = NewCode("GT") + NLG = NewCode("NLG") + HKN = NewCode("HKN") + HAC = NewCode("HAC") + HALLO = NewCode("HALLO") + HAMS = NewCode("HAMS") + HPC = NewCode("HPC") + HAWK = NewCode("HAWK") + HAZE = NewCode("HAZE") + HZT = NewCode("HZT") + HDG = NewCode("HDG") + HEDG = NewCode("HEDG") + HEEL = NewCode("HEEL") + HMP = NewCode("HMP") + PLAY = NewCode("PLAY") + HXX = NewCode("HXX") + XHI = NewCode("XHI") + HVCO = NewCode("HVCO") + HTC = NewCode("HTC") + MINH = NewCode("MINH") + HODL = NewCode("HODL") + HON = NewCode("HON") + HOPE = NewCode("HOPE") + HQX = NewCode("HQX") + HSP = NewCode("HSP") + HTML5 = NewCode("HTML5") + HYPERX = NewCode("HYPERX") + HPS = NewCode("HPS") + IOC = NewCode("IOC") + IBANK = NewCode("IBANK") + IBITS = NewCode("IBITS") + ICASH = NewCode("ICASH") + ICOB = NewCode("ICOB") + ICON = NewCode("ICON") + IETH = NewCode("IETH") + ILM = NewCode("ILM") + IMPS = NewCode("IMPS") + NKA = NewCode("NKA") + INCP = NewCode("INCP") + IN = NewCode("IN") + INC = NewCode("INC") + IMS = NewCode("IMS") + IFLT = NewCode("IFLT") + INFX = NewCode("INFX") + INGT = NewCode("INGT") + INPAY = NewCode("INPAY") + INSANE = NewCode("INSANE") + INXT = NewCode("INXT") + IFT = NewCode("IFT") + INV = NewCode("INV") + IVZ = NewCode("IVZ") + ILT = NewCode("ILT") + IONX = NewCode("IONX") + ISL = NewCode("ISL") + ITI = NewCode("ITI") + ING = NewCode("ING") + IEC = NewCode("IEC") + IW = NewCode("IW") + IXC = NewCode("IXC") + IXT = NewCode("IXT") + JPC = NewCode("JPC") + JANE = NewCode("JANE") + JWL = NewCode("JWL") + JIF = NewCode("JIF") + JOBS = NewCode("JOBS") + JOCKER = NewCode("JOCKER") + JW = NewCode("JW") + JOK = NewCode("JOK") + XJO = NewCode("XJO") + KGB = NewCode("KGB") + KARMC = NewCode("KARMC") + KARMA = NewCode("KARMA") + KASHH = NewCode("KASHH") + KAT = NewCode("KAT") + KC = NewCode("KC") + KIDS = NewCode("KIDS") + KIN = NewCode("KIN") + KISS = NewCode("KISS") + KOBO = NewCode("KOBO") + TP1 = NewCode("TP1") + KRAK = NewCode("KRAK") + KGC = NewCode("KGC") + KTK = NewCode("KTK") + KR = NewCode("KR") + KUBO = NewCode("KUBO") + KURT = NewCode("KURT") + KUSH = NewCode("KUSH") + LANA = NewCode("LANA") + LTH = NewCode("LTH") + LAZ = NewCode("LAZ") + LEA = NewCode("LEA") + LEAF = NewCode("LEAF") + LENIN = NewCode("LENIN") + LEPEN = NewCode("LEPEN") + LIR = NewCode("LIR") + LVG = NewCode("LVG") + LGBTQ = NewCode("LGBTQ") + LHC = NewCode("LHC") + EXT = NewCode("EXT") + LBTC = NewCode("LBTC") + LSD = NewCode("LSD") + LIMX = NewCode("LIMX") + LTD = NewCode("LTD") + LINDA = NewCode("LINDA") + LKC = NewCode("LKC") + LBTCX = NewCode("LBTCX") + LCC = NewCode("LCC") + LTCU = NewCode("LTCU") + LTCR = NewCode("LTCR") + LDOGE = NewCode("LDOGE") + LTS = NewCode("LTS") + LIV = NewCode("LIV") + LIZI = NewCode("LIZI") + LOC = NewCode("LOC") + LOCX = NewCode("LOCX") + LOOK = NewCode("LOOK") + LOOT = NewCode("LOOT") + XLTCG = NewCode("XLTCG") + BASH = NewCode("BASH") + LUCKY = NewCode("LUCKY") + L7S = NewCode("L7S") + LDM = NewCode("LDM") + LUMI = NewCode("LUMI") + LUNA = NewCode("LUNA") + LC = NewCode("LC") + LUX = NewCode("LUX") + MCRN = NewCode("MCRN") + XMG = NewCode("XMG") + MMXIV = NewCode("MMXIV") + MAT = NewCode("MAT") + MAO = NewCode("MAO") + MAPC = NewCode("MAPC") + MRB = NewCode("MRB") + MXT = NewCode("MXT") + MARV = NewCode("MARV") + MARX = NewCode("MARX") + MCAR = NewCode("MCAR") + MM = NewCode("MM") + MVC = NewCode("MVC") + MAVRO = NewCode("MAVRO") + MAX = NewCode("MAX") + MAZE = NewCode("MAZE") + MBIT = NewCode("MBIT") + MCOIN = NewCode("MCOIN") + MPRO = NewCode("MPRO") + XMS = NewCode("XMS") + MLITE = NewCode("MLITE") + MLNC = NewCode("MLNC") + MENTAL = NewCode("MENTAL") + MERGEC = NewCode("MERGEC") + MTLMC3 = NewCode("MTLMC3") + METAL = NewCode("METAL") + MUU = NewCode("MUU") + MILO = NewCode("MILO") + MND = NewCode("MND") + XMINE = NewCode("XMINE") + MNM = NewCode("MNM") + XNM = NewCode("XNM") + MIRO = NewCode("MIRO") + MIS = NewCode("MIS") + MMXVI = NewCode("MMXVI") + MOIN = NewCode("MOIN") + MOJO = NewCode("MOJO") + TAB = NewCode("TAB") + MONETA = NewCode("MONETA") + MUE = NewCode("MUE") + MONEY = NewCode("MONEY") + MRP = NewCode("MRP") + MOTO = NewCode("MOTO") + MULTI = NewCode("MULTI") + MST = NewCode("MST") + MVR = NewCode("MVR") + MYSTIC = NewCode("MYSTIC") + WISH = NewCode("WISH") + NKT = NewCode("NKT") + NAT = NewCode("NAT") + ENAU = NewCode("ENAU") + NEBU = NewCode("NEBU") + NEF = NewCode("NEF") + NBIT = NewCode("NBIT") + NETKO = NewCode("NETKO") + NTM = NewCode("NTM") + NETC = NewCode("NETC") + NRC = NewCode("NRC") + NTK = NewCode("NTK") + NTRN = NewCode("NTRN") + NEVA = NewCode("NEVA") + NIC = NewCode("NIC") + NKC = NewCode("NKC") + NYC = NewCode("NYC") + NZC = NewCode("NZC") + NICE = NewCode("NICE") + NDOGE = NewCode("NDOGE") + XTR = NewCode("XTR") + N2O = NewCode("N2O") + NIXON = NewCode("NIXON") + NOC = NewCode("NOC") + NODC = NewCode("NODC") + NODES = NewCode("NODES") + NODX = NewCode("NODX") + NLC = NewCode("NLC") + NLC2 = NewCode("NLC2") + NOO = NewCode("NOO") + NVC = NewCode("NVC") + NPC = NewCode("NPC") + NUBIS = NewCode("NUBIS") + NUKE = NewCode("NUKE") + N7 = NewCode("N7") + NUM = NewCode("NUM") + NMR = NewCode("NMR") + NXE = NewCode("NXE") + OBS = NewCode("OBS") + OCEAN = NewCode("OCEAN") + OCOW = NewCode("OCOW") + EIGHT88 = NewCode("888") + OCC = NewCode("OCC") + OK = NewCode("OK") + ODNT = NewCode("ODNT") + FLAV = NewCode("FLAV") + OLIT = NewCode("OLIT") + OLYMP = NewCode("OLYMP") + OMA = NewCode("OMA") + OMC = NewCode("OMC") + ONEK = NewCode("ONEK") + ONX = NewCode("ONX") + XPO = NewCode("XPO") + OPAL = NewCode("OPAL") + OTN = NewCode("OTN") + OP = NewCode("OP") + OPES = NewCode("OPES") + OPTION = NewCode("OPTION") + ORLY = NewCode("ORLY") + OS76 = NewCode("OS76") + OZC = NewCode("OZC") + P7C = NewCode("P7C") + PAC = NewCode("PAC") + PAK = NewCode("PAK") + PAL = NewCode("PAL") + PND = NewCode("PND") + PINKX = NewCode("PINKX") + POPPY = NewCode("POPPY") + DUO = NewCode("DUO") + PARA = NewCode("PARA") + PKB = NewCode("PKB") + GENE = NewCode("GENE") + PARTY = NewCode("PARTY") + PYN = NewCode("PYN") + XPY = NewCode("XPY") + CON = NewCode("CON") + PAYP = NewCode("PAYP") + GUESS = NewCode("GUESS") + PEN = NewCode("PEN") + PTA = NewCode("PTA") + PEO = NewCode("PEO") + PSB = NewCode("PSB") + XPD = NewCode("XPD") + PXL = NewCode("PXL") + PHR = NewCode("PHR") + PIE = NewCode("PIE") + PIO = NewCode("PIO") + PIPR = NewCode("PIPR") + SKULL = NewCode("SKULL") + PLANET = NewCode("PLANET") + PNC = NewCode("PNC") + XPTX = NewCode("XPTX") + PLNC = NewCode("PLNC") + XPS = NewCode("XPS") + POKE = NewCode("POKE") + PLBT = NewCode("PLBT") + POM = NewCode("POM") + PONZ2 = NewCode("PONZ2") + PONZI = NewCode("PONZI") + XSP = NewCode("XSP") + XPC = NewCode("XPC") + PEX = NewCode("PEX") + TRON = NewCode("TRON") + POST = NewCode("POST") + POSW = NewCode("POSW") + PWR = NewCode("PWR") + POWER = NewCode("POWER") + PRE = NewCode("PRE") + PRS = NewCode("PRS") + PXI = NewCode("PXI") + PEXT = NewCode("PEXT") + PRIMU = NewCode("PRIMU") + PRX = NewCode("PRX") + PRM = NewCode("PRM") + PRIX = NewCode("PRIX") + XPRO = NewCode("XPRO") + PCM = NewCode("PCM") + PROC = NewCode("PROC") + NANOX = NewCode("NANOX") + VRP = NewCode("VRP") + PTY = NewCode("PTY") + PSI = NewCode("PSI") + PSY = NewCode("PSY") + PULSE = NewCode("PULSE") + PUPA = NewCode("PUPA") + PURE = NewCode("PURE") + VIDZ = NewCode("VIDZ") + PUTIN = NewCode("PUTIN") + PX = NewCode("PX") + QTM = NewCode("QTM") + QTZ = NewCode("QTZ") + QBC = NewCode("QBC") + XQN = NewCode("XQN") + RBBT = NewCode("RBBT") + RAC = NewCode("RAC") + RADI = NewCode("RADI") + RAD = NewCode("RAD") + RAI = NewCode("RAI") + XRA = NewCode("XRA") + RATIO = NewCode("RATIO") + REA = NewCode("REA") + RCX = NewCode("RCX") + REE = NewCode("REE") + REC = NewCode("REC") + RMS = NewCode("RMS") + RBIT = NewCode("RBIT") + RNC = NewCode("RNC") + REV = NewCode("REV") + RH = NewCode("RH") + XRL = NewCode("XRL") + RICE = NewCode("RICE") + RICHX = NewCode("RICHX") + RID = NewCode("RID") + RIDE = NewCode("RIDE") + RBT = NewCode("RBT") + RING = NewCode("RING") + RIO = NewCode("RIO") + RISE = NewCode("RISE") + ROCKET = NewCode("ROCKET") + RPC = NewCode("RPC") + ROS = NewCode("ROS") + ROYAL = NewCode("ROYAL") + RSGP = NewCode("RSGP") + RBIES = NewCode("RBIES") + RUBIT = NewCode("RUBIT") + RBY = NewCode("RBY") + RUC = NewCode("RUC") + RUPX = NewCode("RUPX") + RUP = NewCode("RUP") + RUST = NewCode("RUST") + SFE = NewCode("SFE") + SLS = NewCode("SLS") + SMSR = NewCode("SMSR") + RONIN = NewCode("RONIN") + STV = NewCode("STV") + HIFUN = NewCode("HIFUN") + MAD = NewCode("MAD") + SANDG = NewCode("SANDG") + STO = NewCode("STO") + SCAN = NewCode("SCAN") + SCITW = NewCode("SCITW") + SCRPT = NewCode("SCRPT") + SCRT = NewCode("SCRT") + SED = NewCode("SED") + SEEDS = NewCode("SEEDS") + B2X = NewCode("B2X") + SEL = NewCode("SEL") + SLFI = NewCode("SLFI") + SMBR = NewCode("SMBR") + SEN = NewCode("SEN") + SENT = NewCode("SENT") + SRNT = NewCode("SRNT") + SEV = NewCode("SEV") + SP = NewCode("SP") + SXC = NewCode("SXC") + GELD = NewCode("GELD") + SHDW = NewCode("SHDW") + SDC = NewCode("SDC") + SAK = NewCode("SAK") + SHRP = NewCode("SHRP") + SHELL = NewCode("SHELL") + SH = NewCode("SH") + SHORTY = NewCode("SHORTY") + SHREK = NewCode("SHREK") + SHRM = NewCode("SHRM") + SIB = NewCode("SIB") + SIGT = NewCode("SIGT") + SLCO = NewCode("SLCO") + SIGU = NewCode("SIGU") + SIX = NewCode("SIX") + SJW = NewCode("SJW") + SKB = NewCode("SKB") + SW = NewCode("SW") + SLEEP = NewCode("SLEEP") + SLING = NewCode("SLING") + SMART = NewCode("SMART") + SMC = NewCode("SMC") + SMF = NewCode("SMF") + SOCC = NewCode("SOCC") + SCL = NewCode("SCL") + SDAO = NewCode("SDAO") + SOLAR = NewCode("SOLAR") + SOLO = NewCode("SOLO") + SCT = NewCode("SCT") + SONG = NewCode("SONG") + ALTCOM = NewCode("ALTCOM") + SPHTX = NewCode("SPHTX") + SPC = NewCode("SPC") + SPACE = NewCode("SPACE") + SBT = NewCode("SBT") + SPEC = NewCode("SPEC") + SPX = NewCode("SPX") + SCS = NewCode("SCS") + SPORT = NewCode("SPORT") + SPT = NewCode("SPT") + SPR = NewCode("SPR") + SPEX = NewCode("SPEX") + SQL = NewCode("SQL") + SBIT = NewCode("SBIT") + STHR = NewCode("STHR") + STALIN = NewCode("STALIN") + STAR = NewCode("STAR") + STA = NewCode("STA") + START = NewCode("START") + STP = NewCode("STP") + PNK = NewCode("PNK") + STEPS = NewCode("STEPS") + STK = NewCode("STK") + STONK = NewCode("STONK") + STS = NewCode("STS") + STRP = NewCode("STRP") + STY = NewCode("STY") + XMT = NewCode("XMT") + SSTC = NewCode("SSTC") + SUPER = NewCode("SUPER") + SRND = NewCode("SRND") + STRB = NewCode("STRB") + M1 = NewCode("M1") + SPM = NewCode("SPM") + BUCKS = NewCode("BUCKS") + TOKEN = NewCode("TOKEN") + SWT = NewCode("SWT") + SWEET = NewCode("SWEET") + SWING = NewCode("SWING") + CHSB = NewCode("CHSB") + SIC = NewCode("SIC") + SDP = NewCode("SDP") + XSY = NewCode("XSY") + SYNX = NewCode("SYNX") + SNRG = NewCode("SNRG") + TAG = NewCode("TAG") + TAGR = NewCode("TAGR") + TAJ = NewCode("TAJ") + TAK = NewCode("TAK") + TAKE = NewCode("TAKE") + TAM = NewCode("TAM") + XTO = NewCode("XTO") + TAP = NewCode("TAP") + TLE = NewCode("TLE") + TSE = NewCode("TSE") + TLEX = NewCode("TLEX") + TAXI = NewCode("TAXI") + TCN = NewCode("TCN") + TDFB = NewCode("TDFB") + TEAM = NewCode("TEAM") + TECH = NewCode("TECH") + TEC = NewCode("TEC") + TEK = NewCode("TEK") + TB = NewCode("TB") + TLX = NewCode("TLX") + TELL = NewCode("TELL") + TENNET = NewCode("TENNET") + TES = NewCode("TES") + TGS = NewCode("TGS") + XVE = NewCode("XVE") + TCR = NewCode("TCR") + GCC = NewCode("GCC") + MAY = NewCode("MAY") + THOM = NewCode("THOM") + TIA = NewCode("TIA") + TIDE = NewCode("TIDE") + TIE = NewCode("TIE") + TIT = NewCode("TIT") + TTC = NewCode("TTC") + TODAY = NewCode("TODAY") + TBX = NewCode("TBX") + TDS = NewCode("TDS") + TLOSH = NewCode("TLOSH") + TOKC = NewCode("TOKC") + TMRW = NewCode("TMRW") + TOOL = NewCode("TOOL") + TCX = NewCode("TCX") + TOT = NewCode("TOT") + TX = NewCode("TX") + TRANSF = NewCode("TRANSF") + TRAP = NewCode("TRAP") + TBCX = NewCode("TBCX") + TRICK = NewCode("TRICK") + TPG = NewCode("TPG") + TFL = NewCode("TFL") + TRUMP = NewCode("TRUMP") + TNG = NewCode("TNG") + TUR = NewCode("TUR") + TWERK = NewCode("TWERK") + TWIST = NewCode("TWIST") + TWO = NewCode("TWO") + UCASH = NewCode("UCASH") + UAE = NewCode("UAE") + XBU = NewCode("XBU") + UBQ = NewCode("UBQ") + U = NewCode("U") + UDOWN = NewCode("UDOWN") + GAIN = NewCode("GAIN") + USC = NewCode("USC") + UMC = NewCode("UMC") + UNF = NewCode("UNF") + UNIFY = NewCode("UNIFY") + USDE = NewCode("USDE") + UBTC = NewCode("UBTC") + UIS = NewCode("UIS") + UNIT = NewCode("UNIT") + UNI = NewCode("UNI") + UXC = NewCode("UXC") + URC = NewCode("URC") + XUP = NewCode("XUP") + UFR = NewCode("UFR") + URO = NewCode("URO") + UTLE = NewCode("UTLE") + VAL = NewCode("VAL") + VPRC = NewCode("VPRC") + VAPOR = NewCode("VAPOR") + VCOIN = NewCode("VCOIN") + VEC = NewCode("VEC") + VEC2 = NewCode("VEC2") + VLT = NewCode("VLT") + VENE = NewCode("VENE") + VNTX = NewCode("VNTX") + VTN = NewCode("VTN") + CRED = NewCode("CRED") + VERS = NewCode("VERS") + VTX = NewCode("VTX") + VTY = NewCode("VTY") + VIP = NewCode("VIP") + VISIO = NewCode("VISIO") + VK = NewCode("VK") + VOL = NewCode("VOL") + VOYA = NewCode("VOYA") + VPN = NewCode("VPN") + XVS = NewCode("XVS") + VTL = NewCode("VTL") + VULC = NewCode("VULC") + VVI = NewCode("VVI") + WGR = NewCode("WGR") + WAM = NewCode("WAM") + WARP = NewCode("WARP") + WASH = NewCode("WASH") + WGO = NewCode("WGO") + WAY = NewCode("WAY") + WCASH = NewCode("WCASH") + WEALTH = NewCode("WEALTH") + WEEK = NewCode("WEEK") + WHO = NewCode("WHO") + WIC = NewCode("WIC") + WBB = NewCode("WBB") + WINE = NewCode("WINE") + WINK = NewCode("WINK") + WISC = NewCode("WISC") + WITCH = NewCode("WITCH") + WMC = NewCode("WMC") + WOMEN = NewCode("WOMEN") + WOK = NewCode("WOK") + WRT = NewCode("WRT") + XCO = NewCode("XCO") + X2 = NewCode("X2") + XNX = NewCode("XNX") + XAU = NewCode("XAU") + XAV = NewCode("XAV") + XDE2 = NewCode("XDE2") + XDE = NewCode("XDE") + XIOS = NewCode("XIOS") + XOC = NewCode("XOC") + XSSX = NewCode("XSSX") + XBY = NewCode("XBY") + YAC = NewCode("YAC") + YMC = NewCode("YMC") + YAY = NewCode("YAY") + YBC = NewCode("YBC") + YES = NewCode("YES") + YOB2X = NewCode("YOB2X") + YOVI = NewCode("YOVI") + ZYD = NewCode("ZYD") + ZECD = NewCode("ZECD") + ZEIT = NewCode("ZEIT") + ZENI = NewCode("ZENI") + ZET2 = NewCode("ZET2") + ZET = NewCode("ZET") + ZMC = NewCode("ZMC") + ZIRK = NewCode("ZIRK") + ZLQ = NewCode("ZLQ") + ZNE = NewCode("ZNE") + ZONTO = NewCode("ZONTO") + ZOOM = NewCode("ZOOM") + ZRC = NewCode("ZRC") + ZUR = NewCode("ZUR") + ZB = NewCode("ZB") + QC = NewCode("QC") + HLC = NewCode("HLC") + SAFE = NewCode("SAFE") + BTN = NewCode("BTN") + CDC = NewCode("CDC") + DDM = NewCode("DDM") + HOTC = NewCode("HOTC") + BDS = NewCode("BDS") + AAA = NewCode("AAA") + XWC = NewCode("XWC") + PDX = NewCode("PDX") + SLT = NewCode("SLT") + HPY = NewCode("HPY") + XXBT = NewCode("XXBT") // BTC, but XXBT instead + XDG = NewCode("XDG") // DOGE + HKD = NewCode("HKD") // Hong Kong Dollar + AUD = NewCode("AUD") // Australian Dollar + USD = NewCode("USD") // United States Dollar + ZUSD = NewCode("ZUSD") // United States Dollar, but with a Z in front of it + EUR = NewCode("EUR") // Euro + ZEUR = NewCode("ZEUR") // Euro, but with a Z in front of it + CAD = NewCode("CAD") // Canadaian Dollar + ZCAD = NewCode("ZCAD") // Canadaian Dollar, but with a Z in front of it + SGD = NewCode("SGD") // Singapore Dollar + RUB = NewCode("RUB") // RUssian ruBle + RUR = NewCode("RUR") // RUssian Ruble + PLN = NewCode("PLN") // Polish złoty + TRY = NewCode("TRY") // Turkish lira + UAH = NewCode("UAH") // Ukrainian hryvnia + JPY = NewCode("JPY") // Japanese yen + ZJPY = NewCode("ZJPY") // Japanese yen, but with a Z in front of it + LCH = NewCode("LCH") + MYR = NewCode("MYR") + AFN = NewCode("AFN") + ARS = NewCode("ARS") + AWG = NewCode("AWG") + AZN = NewCode("AZN") + BSD = NewCode("BSD") + BBD = NewCode("BBD") + BYN = NewCode("BYN") + BZD = NewCode("BZD") + BMD = NewCode("BMD") + BOB = NewCode("BOB") + BAM = NewCode("BAM") + BWP = NewCode("BWP") + BGN = NewCode("BGN") + BRL = NewCode("BRL") + BND = NewCode("BND") + KHR = NewCode("KHR") + KYD = NewCode("KYD") + CLP = NewCode("CLP") + CNY = NewCode("CNY") + COP = NewCode("COP") + HRK = NewCode("HRK") + CUP = NewCode("CUP") + CZK = NewCode("CZK") + DKK = NewCode("DKK") + DOP = NewCode("DOP") + XCD = NewCode("XCD") + EGP = NewCode("EGP") + SVC = NewCode("SVC") + FKP = NewCode("FKP") + FJD = NewCode("FJD") + GIP = NewCode("GIP") + GTQ = NewCode("GTQ") + GGP = NewCode("GGP") + GYD = NewCode("GYD") + HNL = NewCode("HNL") + HUF = NewCode("HUF") + ISK = NewCode("ISK") + INR = NewCode("INR") + IDR = NewCode("IDR") + IRR = NewCode("IRR") + IMP = NewCode("IMP") + ILS = NewCode("ILS") + JMD = NewCode("JMD") + JEP = NewCode("JEP") + KZT = NewCode("KZT") + KPW = NewCode("KPW") + KGS = NewCode("KGS") + LAK = NewCode("LAK") + LBP = NewCode("LBP") + LRD = NewCode("LRD") + MKD = NewCode("MKD") + MUR = NewCode("MUR") + MXN = NewCode("MXN") + MNT = NewCode("MNT") + MZN = NewCode("MZN") + NAD = NewCode("NAD") + NPR = NewCode("NPR") + ANG = NewCode("ANG") + NZD = NewCode("NZD") + NIO = NewCode("NIO") + NGN = NewCode("NGN") + NOK = NewCode("NOK") + OMR = NewCode("OMR") + PKR = NewCode("PKR") + PAB = NewCode("PAB") + PYG = NewCode("PYG") + PHP = NewCode("PHP") + QAR = NewCode("QAR") + RON = NewCode("RON") + SHP = NewCode("SHP") + SAR = NewCode("SAR") + RSD = NewCode("RSD") + SCR = NewCode("SCR") + SOS = NewCode("SOS") + ZAR = NewCode("ZAR") + LKR = NewCode("LKR") + SEK = NewCode("SEK") + CHF = NewCode("CHF") + SRD = NewCode("SRD") + SYP = NewCode("SYP") + TWD = NewCode("TWD") + THB = NewCode("THB") + TTD = NewCode("TTD") + TVD = NewCode("TVD") + GBP = NewCode("GBP") + UYU = NewCode("UYU") + UZS = NewCode("UZS") + VEF = NewCode("VEF") + VND = NewCode("VND") + YER = NewCode("YER") + ZWD = NewCode("ZWD") + XETH = NewCode("XETH") + FX_BTC = NewCode("FX_BTC") // nolint: golint +) diff --git a/currency/coinmarketcap/coinmarketcap.go b/currency/coinmarketcap/coinmarketcap.go index b676f4ed..5e562989 100644 --- a/currency/coinmarketcap/coinmarketcap.go +++ b/currency/coinmarketcap/coinmarketcap.go @@ -18,53 +18,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/request" ) -// Coinmarketcap account plan bitmasks, url and enpoint consts -const ( - Basic uint8 = 1 << iota - Hobbyist - Startup - Standard - Professional - Enterprise - - baseURL = "https://pro-api.coinmarketcap.com" - sandboxURL = "https://sandbox-api.coinmarketcap.com" - version = "/v1/" - - endpointCryptocurrencyInfo = "cryptocurrency/info" - endpointCryptocurrencyMap = "cryptocurrency/map" - endpointCryptocurrencyHistoricalListings = "cryptocurrency/listings/historical" - endpointCryptocurrencyLatestListings = "cryptocurrency/listings/latest" - endpointCryptocurrencyMarketPairs = "cryptocurrency/market-pairs/latest" - endpointOHLCVHistorical = "cryptocurrency/ohlcv/historical" - endpointOHLCVLatest = "cryptocurrency/ohlcv/latest" - endpointGetMarketQuotesHistorical = "cryptocurrency/quotes/historical" - endpointGetMarketQuotesLatest = "cryptocurrency/quotes/latest" - endpointExchangeInfo = "exchange/info" - endpointExchangeMap = "exchange/map" - endpointExchangeMarketPairsLatest = "exchange/market-pairs/latest" - endpointExchangeMarketQuoteHistorical = "exchange/quotes/historical" - endpointExchangeMarketQuoteLatest = "exchange/quotes/latest" - endpointGlobalQuoteHistorical = "global-metrics/quotes/historical" - endpointGlobalQuoteLatest = "global-metrics/quotes/latest" - endpointPriceConversion = "tools/price-conversion" - - authrate = 0 - defaultTimeOut = time.Second * 15 -) - -// Coinmarketcap is the overarching type across this package -type Coinmarketcap struct { - Verbose bool - Enabled bool - Name string - APIkey string - APIUrl string - APIVersion string - Plan uint8 - Requester *request.Requester -} - // SetDefaults sets default values for the exchange func (c *Coinmarketcap) SetDefaults() { c.Name = "CoinMarketCap" diff --git a/currency/coinmarketcap/coinmarketcap_test.go b/currency/coinmarketcap/coinmarketcap_test.go index 580fa909..e81f107f 100644 --- a/currency/coinmarketcap/coinmarketcap_test.go +++ b/currency/coinmarketcap/coinmarketcap_test.go @@ -58,32 +58,32 @@ func TestCheckAccountPlan(t *testing.T) { if areAPICredtionalsSet(Basic) { err := c.CheckAccountPlan(Enterprise) if err == nil { - t.Error("Test Failed - CheckAccountPlan() error cannot be nil") + t.Error("CheckAccountPlan() error cannot be nil") } err = c.CheckAccountPlan(Professional) if err == nil { - t.Error("Test Failed - CheckAccountPlan() error cannot be nil") + t.Error("CheckAccountPlan() error cannot be nil") } err = c.CheckAccountPlan(Standard) if err == nil { - t.Error("Test Failed - CheckAccountPlan() error cannot be nil") + t.Error("CheckAccountPlan() error cannot be nil") } err = c.CheckAccountPlan(Hobbyist) if err == nil { - t.Error("Test Failed - CheckAccountPlan() error cannot be nil") + t.Error("CheckAccountPlan() error cannot be nil") } err = c.CheckAccountPlan(Startup) if err == nil { - t.Error("Test Failed - CheckAccountPlan() error cannot be nil") + t.Error("CheckAccountPlan() error cannot be nil") } err = c.CheckAccountPlan(Basic) if err != nil { - t.Error("Test Failed - CheckAccountPlan() error", err) + t.Error("CheckAccountPlan() error", err) } } } @@ -94,11 +94,11 @@ func TestGetCryptocurrencyInfo(t *testing.T) { _, err := c.GetCryptocurrencyInfo(1) if areAPICredtionalsSet(Basic) { if err != nil { - t.Error("Test Failed - GetCryptocurrencyInfo() error", err) + t.Error("GetCryptocurrencyInfo() error", err) } } else { if err == nil { - t.Error("Test Failed - GetCryptocurrencyInfo() error cannot be nil") + t.Error("GetCryptocurrencyInfo() error cannot be nil") } } } @@ -109,11 +109,11 @@ func TestGetCryptocurrencyIDMap(t *testing.T) { _, err := c.GetCryptocurrencyIDMap() if areAPICredtionalsSet(Basic) { if err != nil { - t.Error("Test Failed - GetCryptocurrencyIDMap() error", err) + t.Error("GetCryptocurrencyIDMap() error", err) } } else { if err == nil { - t.Error("Test Failed - GetCryptocurrencyIDMap() error cannot be nil") + t.Error("GetCryptocurrencyIDMap() error cannot be nil") } } } @@ -123,7 +123,7 @@ func TestGetCryptocurrencyHistoricalListings(t *testing.T) { TestSetup(t) _, err := c.GetCryptocurrencyHistoricalListings() if err == nil { - t.Error("Test Failed - GetCryptocurrencyHistoricalListings() error cannot be nil") + t.Error("GetCryptocurrencyHistoricalListings() error cannot be nil") } } @@ -133,11 +133,11 @@ func TestGetCryptocurrencyLatestListing(t *testing.T) { _, err := c.GetCryptocurrencyLatestListing(0, 0) if areAPICredtionalsSet(Basic) { if err != nil { - t.Error("Test Failed - GetCryptocurrencyLatestListing() error", err) + t.Error("GetCryptocurrencyLatestListing() error", err) } } else { if err == nil { - t.Error("Test Failed - GetCryptocurrencyLatestListing() error cannot be nil") + t.Error("GetCryptocurrencyLatestListing() error cannot be nil") } } } @@ -148,12 +148,12 @@ func TestGetCryptocurrencyLatestMarketPairs(t *testing.T) { _, err := c.GetCryptocurrencyLatestMarketPairs(1, 0, 0) if areAPICredtionalsSet(Standard) { if err != nil { - t.Error("Test Failed - GetCryptocurrencyLatestMarketPairs() error", + t.Error("GetCryptocurrencyLatestMarketPairs() error", err) } } else { if err == nil { - t.Error("Test Failed - GetCryptocurrencyLatestMarketPairs() error cannot be nil") + t.Error("GetCryptocurrencyLatestMarketPairs() error cannot be nil") } } } @@ -164,12 +164,12 @@ func TestGetCryptocurrencyOHLCHistorical(t *testing.T) { _, err := c.GetCryptocurrencyOHLCHistorical(1, time.Now(), time.Now()) if areAPICredtionalsSet(Standard) { if err != nil { - t.Error("Test Failed - GetCryptocurrencyOHLCHistorical() error", + t.Error("GetCryptocurrencyOHLCHistorical() error", err) } } else { if err == nil { - t.Error("Test Failed - GetCryptocurrencyOHLCHistorical() error cannot be nil") + t.Error("GetCryptocurrencyOHLCHistorical() error cannot be nil") } } } @@ -180,12 +180,12 @@ func TestGetCryptocurrencyOHLCLatest(t *testing.T) { _, err := c.GetCryptocurrencyOHLCLatest(1) if areAPICredtionalsSet(Startup) { if err != nil { - t.Error("Test Failed - GetCryptocurrencyOHLCLatest() error", + t.Error("GetCryptocurrencyOHLCLatest() error", err) } } else { if err == nil { - t.Error("Test Failed - GetCryptocurrencyOHLCLatest() error cannot be nil") + t.Error("GetCryptocurrencyOHLCLatest() error cannot be nil") } } } @@ -196,12 +196,12 @@ func TestGetCryptocurrencyLatestQuotes(t *testing.T) { _, err := c.GetCryptocurrencyLatestQuotes(1) if areAPICredtionalsSet(Basic) { if err != nil { - t.Error("Test Failed - GetCryptocurrencyLatestQuotes() error", + t.Error("GetCryptocurrencyLatestQuotes() error", err) } } else { if err == nil { - t.Error("Test Failed - GetCryptocurrencyLatestQuotes() error cannot be nil") + t.Error("GetCryptocurrencyLatestQuotes() error cannot be nil") } } } @@ -212,12 +212,12 @@ func TestGetCryptocurrencyHistoricalQuotes(t *testing.T) { _, err := c.GetCryptocurrencyHistoricalQuotes(1, time.Now(), time.Now()) if areAPICredtionalsSet(Standard) { if err != nil { - t.Error("Test Failed - GetCryptocurrencyHistoricalQuotes() error", + t.Error("GetCryptocurrencyHistoricalQuotes() error", err) } } else { if err == nil { - t.Error("Test Failed - GetCryptocurrencyHistoricalQuotes() error cannot be nil") + t.Error("GetCryptocurrencyHistoricalQuotes() error cannot be nil") } } } @@ -228,12 +228,12 @@ func TestGetExchangeInfo(t *testing.T) { _, err := c.GetExchangeInfo(1) if areAPICredtionalsSet(Startup) { if err != nil { - t.Error("Test Failed - GetExchangeInfo() error", + t.Error("GetExchangeInfo() error", err) } } else { if err == nil { - t.Error("Test Failed - GetExchangeInfo() error cannot be nil") + t.Error("GetExchangeInfo() error cannot be nil") } } } @@ -244,12 +244,12 @@ func TestGetExchangeMap(t *testing.T) { _, err := c.GetExchangeMap(0, 0) if areAPICredtionalsSet(Startup) { if err != nil { - t.Error("Test Failed - GetExchangeMap() error", + t.Error("GetExchangeMap() error", err) } } else { if err == nil { - t.Error("Test Failed - GetExchangeMap() error cannot be nil") + t.Error("GetExchangeMap() error cannot be nil") } } } @@ -260,7 +260,7 @@ func TestGetExchangeHistoricalListings(t *testing.T) { _, err := c.GetExchangeHistoricalListings() if err == nil { // TODO: update this once the feature above is implemented - t.Error("Test Failed - GetExchangeHistoricalListings() error cannot be nil") + t.Error("GetExchangeHistoricalListings() error cannot be nil") } } @@ -270,7 +270,7 @@ func TestGetExchangeLatestListings(t *testing.T) { _, err := c.GetExchangeLatestListings() if err == nil { // TODO: update this once the feature above is implemented - t.Error("Test Failed - GetExchangeHistoricalListings() error cannot be nil") + t.Error("GetExchangeHistoricalListings() error cannot be nil") } } @@ -280,12 +280,12 @@ func TestGetExchangeLatestMarketPairs(t *testing.T) { _, err := c.GetExchangeLatestMarketPairs(1, 0, 0) if areAPICredtionalsSet(Standard) { if err != nil { - t.Error("Test Failed - GetExchangeLatestMarketPairs() error", + t.Error("GetExchangeLatestMarketPairs() error", err) } } else { if err == nil { - t.Error("Test Failed - GetExchangeLatestMarketPairs() error cannot be nil") + t.Error("GetExchangeLatestMarketPairs() error cannot be nil") } } } @@ -296,12 +296,12 @@ func TestGetExchangeLatestQuotes(t *testing.T) { _, err := c.GetExchangeLatestQuotes(1) if areAPICredtionalsSet(Standard) { if err != nil { - t.Error("Test Failed - GetExchangeLatestQuotes() error", + t.Error("GetExchangeLatestQuotes() error", err) } } else { if err == nil { - t.Error("Test Failed - GetExchangeLatestQuotes() error cannot be nil") + t.Error("GetExchangeLatestQuotes() error cannot be nil") } } } @@ -312,12 +312,12 @@ func TestGetExchangeHistoricalQuotes(t *testing.T) { _, err := c.GetExchangeHistoricalQuotes(1, time.Now(), time.Now()) if areAPICredtionalsSet(Standard) { if err != nil { - t.Error("Test Failed - GetExchangeHistoricalQuotes() error", + t.Error("GetExchangeHistoricalQuotes() error", err) } } else { if err == nil { - t.Error("Test Failed - GetExchangeHistoricalQuotes() error cannot be nil") + t.Error("GetExchangeHistoricalQuotes() error cannot be nil") } } } @@ -328,12 +328,12 @@ func TestGetGlobalMeticLatestQuotes(t *testing.T) { _, err := c.GetGlobalMeticLatestQuotes() if areAPICredtionalsSet(Basic) { if err != nil { - t.Error("Test Failed - GetGlobalMeticLatestQuotes() error", + t.Error("GetGlobalMeticLatestQuotes() error", err) } } else { if err == nil { - t.Error("Test Failed - GetGlobalMeticLatestQuotes() error cannot be nil") + t.Error("GetGlobalMeticLatestQuotes() error cannot be nil") } } } @@ -344,12 +344,12 @@ func TestGetGlobalMeticHistoricalQuotes(t *testing.T) { _, err := c.GetGlobalMeticHistoricalQuotes(time.Now(), time.Now()) if areAPICredtionalsSet(Standard) { if err != nil { - t.Error("Test Failed - GetGlobalMeticHistoricalQuotes() error", + t.Error("GetGlobalMeticHistoricalQuotes() error", err) } } else { if err == nil { - t.Error("Test Failed - GetGlobalMeticHistoricalQuotes() error cannot be nil") + t.Error("GetGlobalMeticHistoricalQuotes() error cannot be nil") } } } @@ -360,12 +360,12 @@ func TestGetPriceConversion(t *testing.T) { _, err := c.GetPriceConversion(0, 1, time.Now()) if areAPICredtionalsSet(Hobbyist) { if err != nil { - t.Error("Test Failed - GetPriceConversion() error", + t.Error("GetPriceConversion() error", err) } } else { if err == nil { - t.Error("Test Failed - GetPriceConversion() error cannot be nil") + t.Error("GetPriceConversion() error cannot be nil") } } } @@ -375,38 +375,38 @@ func TestSetAccountPlan(t *testing.T) { for _, plan := range accPlans { err := c.SetAccountPlan(plan) if err != nil { - t.Error("Test Failed - SetAccountPlan() error", err) + t.Error("SetAccountPlan() error", err) } switch plan { case "basic": if c.Plan != Basic { - t.Error("Test Failed - SetAccountPlan() error basic plan not set correctly") + t.Error("SetAccountPlan() error basic plan not set correctly") } case "startup": if c.Plan != Startup { - t.Error("Test Failed - SetAccountPlan() error startup plan not set correctly") + t.Error("SetAccountPlan() error startup plan not set correctly") } case "hobbyist": if c.Plan != Hobbyist { - t.Error("Test Failed - SetAccountPlan() error hobbyist plan not set correctly") + t.Error("SetAccountPlan() error hobbyist plan not set correctly") } case "standard": if c.Plan != Standard { - t.Error("Test Failed - SetAccountPlan() error standard plan not set correctly") + t.Error("SetAccountPlan() error standard plan not set correctly") } case "professional": if c.Plan != Professional { - t.Error("Test Failed - SetAccountPlan() error professional plan not set correctly") + t.Error("SetAccountPlan() error professional plan not set correctly") } case "enterprise": if c.Plan != Enterprise { - t.Error("Test Failed - SetAccountPlan() error enterprise plan not set correctly") + t.Error("SetAccountPlan() error enterprise plan not set correctly") } } } if err := c.SetAccountPlan("bra"); err == nil { - t.Error("Test Failed - SetAccountPlan() error cannot be nil") + t.Error("SetAccountPlan() error cannot be nil") } } diff --git a/currency/coinmarketcap/coinmarketcap_types.go b/currency/coinmarketcap/coinmarketcap_types.go index 95890a9f..7481165e 100644 --- a/currency/coinmarketcap/coinmarketcap_types.go +++ b/currency/coinmarketcap/coinmarketcap_types.go @@ -1,6 +1,57 @@ package coinmarketcap -import "time" +import ( + "time" + + "github.com/thrasher-corp/gocryptotrader/exchanges/request" +) + +// Coinmarketcap account plan bitmasks, url and enpoint consts +const ( + Basic uint8 = 1 << iota + Hobbyist + Startup + Standard + Professional + Enterprise + + baseURL = "https://pro-api.coinmarketcap.com" + sandboxURL = "https://sandbox-api.coinmarketcap.com" + version = "/v1/" + + endpointCryptocurrencyInfo = "cryptocurrency/info" + endpointCryptocurrencyMap = "cryptocurrency/map" + endpointCryptocurrencyHistoricalListings = "cryptocurrency/listings/historical" + endpointCryptocurrencyLatestListings = "cryptocurrency/listings/latest" + endpointCryptocurrencyMarketPairs = "cryptocurrency/market-pairs/latest" + endpointOHLCVHistorical = "cryptocurrency/ohlcv/historical" + endpointOHLCVLatest = "cryptocurrency/ohlcv/latest" + endpointGetMarketQuotesHistorical = "cryptocurrency/quotes/historical" + endpointGetMarketQuotesLatest = "cryptocurrency/quotes/latest" + endpointExchangeInfo = "exchange/info" + endpointExchangeMap = "exchange/map" + endpointExchangeMarketPairsLatest = "exchange/market-pairs/latest" + endpointExchangeMarketQuoteHistorical = "exchange/quotes/historical" + endpointExchangeMarketQuoteLatest = "exchange/quotes/latest" + endpointGlobalQuoteHistorical = "global-metrics/quotes/historical" + endpointGlobalQuoteLatest = "global-metrics/quotes/latest" + endpointPriceConversion = "tools/price-conversion" + + authrate = 0 + defaultTimeOut = time.Second * 15 +) + +// Coinmarketcap is the overarching type across this package +type Coinmarketcap struct { + Verbose bool + Enabled bool + Name string + APIkey string + APIUrl string + APIVersion string + Plan uint8 + Requester *request.Requester +} // Settings defines the current settings from configuration file type Settings struct { diff --git a/currency/conversion.go b/currency/conversion.go index cb04532c..5032e244 100644 --- a/currency/conversion.go +++ b/currency/conversion.go @@ -254,11 +254,6 @@ func (c *ConversionRates) GetFullRates() Conversions { // Conversions define a list of conversion data type Conversions []Conversion -// Slice exposes the underlying Conversion slice type -func (c Conversions) Slice() []Conversion { - return c -} - // NewConversionFromString splits a string from a foreign exchange provider func NewConversionFromString(p string) (Conversion, error) { return NewConversionFromStrings(p[:3], p[3:]) @@ -302,7 +297,7 @@ func (c Conversion) String() string { return c.From.String() + c.To.String() } -// GetRate returns system rate if availabled +// GetRate returns system rate if available func (c Conversion) GetRate() (float64, error) { c.mtx.Lock() defer c.mtx.Unlock() diff --git a/currency/conversion_test.go b/currency/conversion_test.go index 22774a0a..83b859af 100644 --- a/currency/conversion_test.go +++ b/currency/conversion_test.go @@ -1,6 +1,7 @@ package currency import ( + "fmt" "strings" "testing" ) @@ -9,10 +10,10 @@ func TestNewConversionFromString(t *testing.T) { expected := "AUDUSD" conv, err := NewConversionFromString(expected) if err != nil { - t.Error("Test Failed - NewConversionFromString() error", err) + t.Error(err) } if conv.String() != expected { - t.Errorf("Test Failed - NewConversion() error expected %s but received %s", + t.Errorf("NewConversion() error expected %s but received %s", expected, conv) } @@ -20,10 +21,10 @@ func TestNewConversionFromString(t *testing.T) { newexpected := strings.ToLower(expected) conv, err = NewConversionFromString(newexpected) if err != nil { - t.Error("Test Failed - NewConversionFromString() error", err) + t.Error(err) } if conv.String() != newexpected { - t.Errorf("Test Failed - NewConversion() error expected %s but received %s", + t.Errorf("NewConversion() error expected %s but received %s", newexpected, conv) } @@ -36,11 +37,11 @@ func TestNewConversionFromStrings(t *testing.T) { conv, err := NewConversionFromStrings(from, to) if err != nil { - t.Error("Test Failed - NewConversionFromString() error", err) + t.Error(err) } if conv.String() != expected { - t.Errorf("Test Failed - NewConversion() error expected %s but received %s", + t.Errorf("NewConversion() error expected %s but received %s", expected, conv) } @@ -53,11 +54,11 @@ func TestNewConversion(t *testing.T) { conv, err := NewConversion(from, to) if err != nil { - t.Error("Test Failed - NewConversionFromCode() error", err) + t.Error(err) } if conv.String() != expected { - t.Errorf("Test Failed - NewConversion() error expected %s but received %s", + t.Errorf("NewConversion() error expected %s but received %s", expected, conv) } @@ -69,18 +70,18 @@ func TestConversionIsInvalid(t *testing.T) { conv, err := NewConversion(from, to) if err != nil { - t.Fatal("Test Failed - NewConversion() error", err) + t.Fatal(err) } if conv.IsInvalid() { - t.Errorf("Test Failed - IsInvalid() error expected false but received %v", + t.Errorf("IsInvalid() error expected false but received %v", conv.IsInvalid()) } to = AUD conv, err = NewConversion(from, to) if err == nil { - t.Fatal("Test Failed - NewConversion() error", err) + t.Error("Expected error") } } @@ -90,18 +91,18 @@ func TestConversionIsFiatPair(t *testing.T) { conv, err := NewConversion(from, to) if err != nil { - t.Fatal("Test Failed - NewConversion() error", err) + t.Fatal(err) } if !conv.IsFiat() { - t.Errorf("Test Failed - IsFiatPair() error expected true but received %v", + t.Errorf("IsFiatPair() error expected true but received %v", conv.IsFiat()) } to = LTC conv, err = NewConversion(from, to) if err == nil { - t.Fatal("Test Failed - NewConversion() error", err) + t.Error("Expected error") } } @@ -109,7 +110,7 @@ func TestConversionsRatesSystem(t *testing.T) { var SuperDuperConversionSystem ConversionRates if SuperDuperConversionSystem.HasData() { - t.Fatalf("Test Failed - HasData() error expected false but received %v", + t.Fatalf("HasData() error expected false but received %v", SuperDuperConversionSystem.HasData()) } @@ -141,16 +142,16 @@ func TestConversionsRatesSystem(t *testing.T) { err := SuperDuperConversionSystem.Update(testmap) if err != nil { - t.Fatal("Test Failed - Update() error", err) + t.Fatal(err) } err = SuperDuperConversionSystem.Update(nil) if err == nil { - t.Fatal("Test Failed - Update() error cannot be nil") + t.Fatal("Update() error cannot be nil") } if !SuperDuperConversionSystem.HasData() { - t.Fatalf("Test Failed - HasData() error expected true but received %v", + t.Fatalf("HasData() error expected true but received %v", SuperDuperConversionSystem.HasData()) } @@ -161,7 +162,7 @@ func TestConversionsRatesSystem(t *testing.T) { r := *p * 1000 expectedRate := 1396.9317581 if r != expectedRate { - t.Errorf("Test Failed - Convert() error expected %.13f but received %.13f", + t.Errorf("Convert() error expected %.13f but received %.13f", expectedRate, r) } @@ -169,8 +170,78 @@ func TestConversionsRatesSystem(t *testing.T) { inverseR := *pi * expectedRate expectedInverseRate := float64(1000) if inverseR != expectedInverseRate { - t.Errorf("Test Failed - Convert() error expected %.13f but received %.13f", + t.Errorf("Convert() error expected %.13f but received %.13f", expectedInverseRate, inverseR) } } + +func TestGetRate(t *testing.T) { + from := NewCode("AUD") + to := NewCode("USD") + + c, err := NewConversion(from, to) + if err != nil { + t.Error(err) + } + rate, err := c.GetRate() + if err != nil { + t.Error(err) + } + if rate == 0 { + t.Error("Rate not set") + } + inv, err := c.GetInversionRate() + if err != nil { + t.Error(err) + } + if inv == 0 { + t.Error("Inverted rate not set") + } + conv, err := c.Convert(1) + if err != nil { + t.Error(err) + } + if rate != conv { + t.Errorf("Incorrect rate %v %v", rate, conv) + } + invConv, err := c.ConvertInverse(1) + if err != nil { + t.Error(err) + } + if inv != invConv { + t.Errorf("Incorrect rate %v %v", conv, invConv) + } + + var convs ConversionRates + var convRate float64 + _, err = convs.GetRate(BTC, USDT) + if err == nil { + t.Errorf("Expected %s", fmt.Errorf("rate not found for from %s to %s conversion", + BTC, + USD)) + } + convRate, err = convs.GetRate(USDT, USD) + if err != nil { + t.Error(err) + } + if convRate != 1 { + t.Errorf("Expected rate to be 1") + } + + convRate, err = convs.GetRate(RUR, RUB) + if err != nil { + t.Error(err) + } + if convRate != 1 { + t.Errorf("Expected rate to be 1") + } + + convRate, err = convs.GetRate(RUB, RUR) + if err != nil { + t.Error(err) + } + if convRate != 1 { + t.Errorf("Expected rate to be 1") + } +} diff --git a/currency/currencies.go b/currency/currencies.go index 86a6c6ff..7c3b5338 100644 --- a/currency/currencies.go +++ b/currency/currencies.go @@ -88,11 +88,6 @@ func (c Currencies) Match(other Currencies) bool { return true } -// Slice exposes the underlying type -func (c Currencies) Slice() []Code { - return c -} - // HasData checks to see if Currencies type has actual currencies func (c Currencies) HasData() bool { return len(c) != 0 diff --git a/currency/currencies_test.go b/currency/currencies_test.go index 981f97d5..6805ebf1 100644 --- a/currency/currencies_test.go +++ b/currency/currencies_test.go @@ -11,21 +11,21 @@ func TestCurrenciesUnmarshalJSON(t *testing.T) { expected := "btc,usd,ltc,bro,things" encoded, err := common.JSONEncode(expected) if err != nil { - t.Fatal("Test Failed - Currencies UnmarshalJSON() error", err) + t.Fatal("Currencies UnmarshalJSON() error", err) } err = common.JSONDecode(encoded, &unmarshalHere) if err != nil { - t.Fatal("Test Failed - Currencies UnmarshalJSON() error", err) + t.Fatal("Currencies UnmarshalJSON() error", err) } err = common.JSONDecode(encoded, &unmarshalHere) if err != nil { - t.Fatal("Test Failed - Currencies UnmarshalJSON() error", err) + t.Fatal("Currencies UnmarshalJSON() error", err) } if unmarshalHere.Join() != expected { - t.Errorf("Test Failed - Currencies UnmarshalJSON() error expected %s but received %s", + t.Errorf("Currencies UnmarshalJSON() error expected %s but received %s", expected, unmarshalHere.Join()) } } @@ -39,12 +39,12 @@ func TestCurrenciesMarshalJSON(t *testing.T) { encoded, err := common.JSONEncode(quickStruct) if err != nil { - t.Fatal("Test Failed - Currencies MarshalJSON() error", err) + t.Fatal("Currencies MarshalJSON() error", err) } expected := `{"amazingCurrencies":"btc,usd,ltc,bro,things"}` if string(encoded) != expected { - t.Errorf("Test Failed - Currencies MarshalJSON() error expected %s but received %s", + t.Errorf("Currencies MarshalJSON() error expected %s but received %s", expected, string(encoded)) } } diff --git a/currency/currency_test.go b/currency/currency_test.go index f6e3dae7..73568ba5 100644 --- a/currency/currency_test.go +++ b/currency/currency_test.go @@ -7,12 +7,12 @@ import ( func TestGetDefaultExchangeRates(t *testing.T) { rates, err := GetDefaultExchangeRates() if err != nil { - t.Error("Test failed - GetDefaultExchangeRates() err", err) + t.Error("GetDefaultExchangeRates() err", err) } for _, val := range rates { if !val.IsFiat() { - t.Errorf("Test failed - GetDefaultExchangeRates() %s is not fiat pair", + t.Errorf("GetDefaultExchangeRates() %s is not fiat pair", val) } } @@ -21,12 +21,12 @@ func TestGetDefaultExchangeRates(t *testing.T) { func TestGetExchangeRates(t *testing.T) { rates, err := GetExchangeRates() if err != nil { - t.Error("Test failed - GetExchangeRates() err", err) + t.Error("GetExchangeRates() err", err) } for _, val := range rates { if !val.IsFiat() { - t.Errorf("Test failed - GetExchangeRates() %s is not fiat pair", + t.Errorf("GetExchangeRates() %s is not fiat pair", val) } } @@ -35,23 +35,23 @@ func TestGetExchangeRates(t *testing.T) { func TestUpdateBaseCurrency(t *testing.T) { err := UpdateBaseCurrency(AUD) if err != nil { - t.Error("Test failed - UpdateBaseCurrency() err", err) + t.Error("UpdateBaseCurrency() err", err) } err = UpdateBaseCurrency(LTC) if err == nil { - t.Error("Test failed - UpdateBaseCurrency() cannot be nil") + t.Error("UpdateBaseCurrency() error cannot be nil") } if GetBaseCurrency() != AUD { - t.Errorf("Test failed - GetBaseCurrency() expected %s but received %s", + t.Errorf("GetBaseCurrency() expected %s but received %s", AUD, GetBaseCurrency()) } } func TestGetDefaultBaseCurrency(t *testing.T) { if GetDefaultBaseCurrency() != USD { - t.Errorf("Test failed - GetDefaultBaseCurrency() expected %s but received %s", + t.Errorf("GetDefaultBaseCurrency() expected %s but received %s", USD, GetDefaultBaseCurrency()) } } @@ -59,7 +59,7 @@ func TestGetDefaultBaseCurrency(t *testing.T) { func TestGetDefaulCryptoCurrencies(t *testing.T) { expected := Currencies{BTC, LTC, ETH, DOGE, DASH, XRP, XMR} if !GetDefaultCryptocurrencies().Match(expected) { - t.Errorf("Test failed - GetDefaultCryptocurrencies() expected %s but received %s", + t.Errorf("GetDefaultCryptocurrencies() expected %s but received %s", expected, GetDefaultCryptocurrencies()) } } @@ -67,7 +67,7 @@ func TestGetDefaulCryptoCurrencies(t *testing.T) { func TestGetDefaultFiatCurrencies(t *testing.T) { expected := Currencies{USD, AUD, EUR, CNY} if !GetDefaultFiatCurrencies().Match(expected) { - t.Errorf("Test failed - GetDefaultFiatCurrencies() expected %s but received %s", + t.Errorf("GetDefaultFiatCurrencies() expected %s but received %s", expected, GetDefaultFiatCurrencies()) } } @@ -77,14 +77,14 @@ func TestUpdateCurrencies(t *testing.T) { UpdateCurrencies(fiat, false) rFiat := GetFiatCurrencies() if !rFiat.Contains(HKN) || !rFiat.Contains(JPY) { - t.Error("Test failed - UpdateCurrencies() currencies did not update") + t.Error("UpdateCurrencies() currencies did not update") } crypto := Currencies{ZAR, ZCAD, B2} UpdateCurrencies(crypto, true) rCrypto := GetCryptocurrencies() if !rCrypto.Contains(ZAR) || !rCrypto.Contains(ZCAD) || !rCrypto.Contains(B2) { - t.Error("Test failed - UpdateCurrencies() currencies did not update") + t.Error("UpdateCurrencies() currencies did not update") } } @@ -100,7 +100,7 @@ func TestConvertCurrency(t *testing.T) { } if r != 100 { - t.Errorf("Test Failed - ConvertCurrency error, incorrect rate return %2.f but received %2.f", + t.Errorf("ConvertCurrency error, incorrect rate return %2.f but received %2.f", 100.00, r) } diff --git a/currency/forexprovider/base/base.go b/currency/forexprovider/base/base.go index 6d8e3347..ff037934 100644 --- a/currency/forexprovider/base/base.go +++ b/currency/forexprovider/base/base.go @@ -4,6 +4,21 @@ import ( "time" ) +// GetName returns name of provider +func (b *Base) GetName() string { + return b.Name +} + +// IsEnabled returns true if enabled +func (b *Base) IsEnabled() bool { + return b.Enabled +} + +// IsPrimaryProvider returns true if primary provider +func (b *Base) IsPrimaryProvider() bool { + return b.PrimaryProvider +} + // DefaultTimeOut is the default timeout for foreign exchange providers const DefaultTimeOut = time.Second * 15 @@ -22,18 +37,3 @@ type Settings struct { type Base struct { Settings `json:"settings"` } - -// GetName returns name of provider -func (b *Base) GetName() string { - return b.Name -} - -// IsEnabled returns true if enabled -func (b *Base) IsEnabled() bool { - return b.Enabled -} - -// IsPrimaryProvider returns true if primary provider -func (b *Base) IsPrimaryProvider() bool { - return b.PrimaryProvider -} diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go index df867e21..47e21e11 100644 --- a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go @@ -14,29 +14,6 @@ import ( log "github.com/thrasher-corp/gocryptotrader/logger" ) -// const declarations consist of endpoints -const ( - APIEndpointURL = "https://currencyconverterapi.com/api/" - APIEndpointFreeURL = "https://free.currencyconverterapi.com/api/" - APIEndpointVersion = "v5" - - APIEndpointConvert = "convert" - APIEndpointCurrencies = "currencies" - APIEndpointCountries = "countries" - APIEndpointUsage = "usage" - - defaultAPIKey = "Key" - - authRate = 0 - unAuthRate = 0 -) - -// CurrencyConverter stores the struct for the CurrencyConverter API -type CurrencyConverter struct { - base.Base - Requester *request.Requester -} - // Setup sets appropriate values for CurrencyLayer func (c *CurrencyConverter) Setup(config base.Settings) error { c.Name = config.Name diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi_test.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi_test.go index a15491c7..b322ef07 100644 --- a/currency/forexprovider/currencyconverterapi/currencyconverterapi_test.go +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi_test.go @@ -65,7 +65,7 @@ func TestConvertMany(t *testing.T) { currencies = []string{"USD_AUD", "USD_EUR", "USD_GBP"} _, err = c.ConvertMany(currencies) if err == nil { - t.Fatal("non error on supplying 3 or more currencies using the free API") + t.Fatal("Expected error from on supplying 3 or more currencies using the free API") } } diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi_types.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi_types.go index 6c310176..f44f2053 100644 --- a/currency/forexprovider/currencyconverterapi/currencyconverterapi_types.go +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi_types.go @@ -1,5 +1,33 @@ package currencyconverter +import ( + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" +) + +// const declarations consist of endpoints +const ( + APIEndpointURL = "https://currencyconverterapi.com/api/" + APIEndpointFreeURL = "https://free.currencyconverterapi.com/api/" + APIEndpointVersion = "v5" + + APIEndpointConvert = "convert" + APIEndpointCurrencies = "currencies" + APIEndpointCountries = "countries" + APIEndpointUsage = "usage" + + defaultAPIKey = "Key" + + authRate = 0 + unAuthRate = 0 +) + +// CurrencyConverter stores the struct for the CurrencyConverter API +type CurrencyConverter struct { + base.Base + Requester *request.Requester +} + // Error stores the error message type Error struct { Status int `json:"status"` diff --git a/currency/forexprovider/currencylayer/currencylayer.go b/currency/forexprovider/currencylayer/currencylayer.go index e79f5469..59c85a31 100644 --- a/currency/forexprovider/currencylayer/currencylayer.go +++ b/currency/forexprovider/currencylayer/currencylayer.go @@ -26,34 +26,6 @@ import ( log "github.com/thrasher-corp/gocryptotrader/logger" ) -// const declarations consist of endpoints and APIKey privileges -const ( - AccountFree = iota - AccountBasic - AccountPro - AccountEnterprise - - APIEndpointURL = "http://apilayer.net/api/" - APIEndpointURLSSL = "https://apilayer.net/api/" - APIEndpointList = "list" - APIEndpointLive = "live" - APIEndpointHistorical = "historical" - APIEndpointConversion = "convert" - APIEndpointTimeframe = "timeframe" - APIEndpointChange = "change" - - authRate = 0 - unAuthRate = 0 -) - -// CurrencyLayer is a foreign exchange rate provider at -// https://currencylayer.com NOTE default base currency is USD when using a free -// account. Has automatic upgrade to a SSL connection. -type CurrencyLayer struct { - base.Base - Requester *request.Requester -} - // Setup sets appropriate values for CurrencyLayer func (c *CurrencyLayer) Setup(config base.Settings) error { if config.APIKeyLvl < 0 || config.APIKeyLvl > 3 { diff --git a/currency/forexprovider/currencylayer/currencylayer_test.go b/currency/forexprovider/currencylayer/currencylayer_test.go index 3c6186f0..1105a306 100644 --- a/currency/forexprovider/currencylayer/currencylayer_test.go +++ b/currency/forexprovider/currencylayer/currencylayer_test.go @@ -48,7 +48,7 @@ func areAPIKeysSet() bool { func TestGetRates(t *testing.T) { err := setup() if err != nil { - t.Skip("Test Failed - CurrencyLayer GetRates error", err) + t.Skip("CurrencyLayer GetRates error", err) } _, err = c.GetRates("USD", "AUD") if areAPIKeysSet() && err != nil { @@ -61,7 +61,7 @@ func TestGetRates(t *testing.T) { func TestGetSupportedCurrencies(t *testing.T) { err := setup() if err != nil { - t.Fatal("Test Failed - CurrencyLayer GetSupportedCurrencies error", err) + t.Fatal("CurrencyLayer GetSupportedCurrencies error", err) } _, err = c.GetSupportedCurrencies() if areAPIKeysSet() && err != nil { @@ -74,7 +74,7 @@ func TestGetSupportedCurrencies(t *testing.T) { func TestGetliveData(t *testing.T) { err := setup() if err != nil { - t.Fatal("Test Failed - CurrencyLayer GetliveData error", err) + t.Fatal("CurrencyLayer GetliveData error", err) } _, err = c.GetliveData("AUD", "USD") if areAPIKeysSet() && err != nil { @@ -87,7 +87,7 @@ func TestGetliveData(t *testing.T) { func TestGetHistoricalData(t *testing.T) { err := setup() if err != nil { - t.Fatal("Test Failed - CurrencyLayer GetHistoricalData error", err) + t.Fatal("CurrencyLayer GetHistoricalData error", err) } _, err = c.GetHistoricalData("2016-12-15", []string{"AUD"}, "USD") if areAPIKeysSet() && err != nil { @@ -100,7 +100,7 @@ func TestGetHistoricalData(t *testing.T) { func TestConvert(t *testing.T) { err := setup() if err != nil { - t.Fatal("Test Failed - CurrencyLayer Convert error", err) + t.Fatal("CurrencyLayer Convert error", err) } _, err = c.Convert("USD", "AUD", "", 1) if areAPIKeysSet() && err != nil && c.APIKeyLvl >= AccountBasic { @@ -113,7 +113,7 @@ func TestConvert(t *testing.T) { func TestQueryTimeFrame(t *testing.T) { err := setup() if err != nil { - t.Fatal("Test Failed - CurrencyLayer QueryTimeFrame error", err) + t.Fatal("CurrencyLayer QueryTimeFrame error", err) } _, err = c.QueryTimeFrame("2010-12-0", "2010-12-5", "USD", []string{"AUD"}) if areAPIKeysSet() && err != nil && c.APIKeyLvl >= AccountPro { @@ -126,7 +126,7 @@ func TestQueryTimeFrame(t *testing.T) { func TestQueryCurrencyChange(t *testing.T) { err := setup() if err != nil { - t.Fatal("Test Failed - CurrencyLayer QueryCurrencyChange() error", err) + t.Fatal("CurrencyLayer QueryCurrencyChange() error", err) } _, err = c.QueryCurrencyChange("2010-12-0", "2010-12-5", "USD", []string{"AUD"}) if areAPIKeysSet() && err != nil && c.APIKeyLvl == AccountEnterprise { diff --git a/currency/forexprovider/currencylayer/currencylayer_types.go b/currency/forexprovider/currencylayer/currencylayer_types.go index 3d8fbd86..16753123 100644 --- a/currency/forexprovider/currencylayer/currencylayer_types.go +++ b/currency/forexprovider/currencylayer/currencylayer_types.go @@ -1,5 +1,38 @@ package currencylayer +import ( + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" +) + +// const declarations consist of endpoints and APIKey privileges +const ( + AccountFree = iota + AccountBasic + AccountPro + AccountEnterprise + + APIEndpointURL = "http://apilayer.net/api/" + APIEndpointURLSSL = "https://apilayer.net/api/" + APIEndpointList = "list" + APIEndpointLive = "live" + APIEndpointHistorical = "historical" + APIEndpointConversion = "convert" + APIEndpointTimeframe = "timeframe" + APIEndpointChange = "change" + + authRate = 0 + unAuthRate = 0 +) + +// CurrencyLayer is a foreign exchange rate provider at +// https://currencylayer.com NOTE default base currency is USD when using a free +// account. Has automatic upgrade to a SSL connection. +type CurrencyLayer struct { + base.Base + Requester *request.Requester +} + // Error Defines the response error if an error occurred type Error struct { Code int `json:"code"` diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go index 85724a55..6c7b2815 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go @@ -14,24 +14,6 @@ import ( log "github.com/thrasher-corp/gocryptotrader/logger" ) -const ( - exchangeRatesAPI = "https://api.exchangeratesapi.io" - exchangeRatesLatest = "latest" - exchangeRatesHistory = "history" - exchangeRatesSupportedCurrencies = "EUR,CHF,USD,BRL,ISK,PHP,KRW,BGN,MXN," + - "RON,CAD,SGD,NZD,THB,HKD,JPY,NOK,HRK,ILS,GBP,DKK,HUF,MYR,RUB,TRY,IDR," + - "ZAR,INR,AUD,CZK,SEK,CNY,PLN" - - authRate = 0 - unAuthRate = 0 -) - -// ExchangeRates stores the struct for the ExchangeRatesAPI API -type ExchangeRates struct { - base.Base - Requester *request.Requester -} - // Setup sets appropriate values for CurrencyLayer func (e *ExchangeRates) Setup(config base.Settings) error { e.Name = config.Name diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_types.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_types.go index b8c3e3f6..3c589f15 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_types.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_types.go @@ -1,5 +1,28 @@ package exchangerates +import ( + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" +) + +const ( + exchangeRatesAPI = "https://api.exchangeratesapi.io" + exchangeRatesLatest = "latest" + exchangeRatesHistory = "history" + exchangeRatesSupportedCurrencies = "EUR,CHF,USD,BRL,ISK,PHP,KRW,BGN,MXN," + + "RON,CAD,SGD,NZD,THB,HKD,JPY,NOK,HRK,ILS,GBP,DKK,HUF,MYR,RUB,TRY,IDR," + + "ZAR,INR,AUD,CZK,SEK,CNY,PLN" + + authRate = 0 + unAuthRate = 0 +) + +// ExchangeRates stores the struct for the ExchangeRatesAPI API +type ExchangeRates struct { + base.Base + Requester *request.Requester +} + // Rates holds the latest forex rates info type Rates struct { Base string `json:"base"` diff --git a/currency/forexprovider/fixer.io/fixer.go b/currency/forexprovider/fixer.io/fixer.go index 5f6f9201..9816b9df 100644 --- a/currency/forexprovider/fixer.io/fixer.go +++ b/currency/forexprovider/fixer.io/fixer.go @@ -22,32 +22,6 @@ import ( log "github.com/thrasher-corp/gocryptotrader/logger" ) -const ( - fixerAPIFree = iota - fixerAPIBasic - fixerAPIProfessional - fixerAPIProfessionalPlus - fixerAPIEnterprise - - fixerAPI = "http://data.fixer.io/api/" - fixerAPISSL = "https://data.fixer.io/api/" - fixerAPILatest = "latest" - fixerAPIConvert = "convert" - fixerAPITimeSeries = "timeseries" - fixerAPIFluctuation = "fluctuation" - fixerSupportedCurrencies = "symbols" - - authRate = 0 - unAuthRate = 0 -) - -// Fixer is a foreign exchange rate provider at https://fixer.io/ -// NOTE DEFAULT BASE CURRENCY IS EUR upgrade to basic to change -type Fixer struct { - base.Base - Requester *request.Requester -} - // Setup sets appropriate values for fixer object func (f *Fixer) Setup(config base.Settings) error { if config.APIKeyLvl < 0 || config.APIKeyLvl > 4 { diff --git a/currency/forexprovider/fixer.io/fixer_test.go b/currency/forexprovider/fixer.io/fixer_test.go index c85f7314..45752552 100644 --- a/currency/forexprovider/fixer.io/fixer_test.go +++ b/currency/forexprovider/fixer.io/fixer_test.go @@ -22,7 +22,7 @@ func setup(t *testing.T) { if !isSetup { err := f.Setup(base.Settings{}) if err != nil { - t.Fatal("Test Failed - Setup error", err) + t.Fatal("Setup error", err) } isSetup = true } @@ -32,7 +32,7 @@ func TestGetRates(t *testing.T) { setup(t) _, err := f.GetRates("EUR", "AUD") if err == nil { - t.Error("test failed - fixer GetRates() error", err) + t.Error("fixer GetRates() Expected error") } } @@ -40,7 +40,7 @@ func TestGetLatestRates(t *testing.T) { setup(t) _, err := f.GetLatestRates("EUR", "AUD") if err == nil { - t.Error("test failed - fixer GetLatestRates() error", err) + t.Error("fixer GetLatestRates() Expected error") } } @@ -48,7 +48,7 @@ func TestGetHistoricalRates(t *testing.T) { setup(t) _, err := f.GetHistoricalRates("2013-12-24", "EUR", []string{"AUD,KRW"}) if err == nil { - t.Error("test failed - fixer GetHistoricalRates() error", err) + t.Error("fixer GetHistoricalRates() Expected error") } } @@ -56,7 +56,7 @@ func TestConvertCurrency(t *testing.T) { setup(t) _, err := f.ConvertCurrency("AUD", "EUR", "", 1337) if err == nil { - t.Error("test failed - fixer ConvertCurrency() error", err) + t.Error("fixer ConvertCurrency() Expected error") } } @@ -64,7 +64,7 @@ func TestGetTimeSeriesData(t *testing.T) { setup(t) _, err := f.GetTimeSeriesData("2013-12-24", "2013-12-25", "EUR", []string{"AUD,KRW"}) if err == nil { - t.Error("test failed - fixer GetTimeSeriesData() error", err) + t.Error("fixer GetTimeSeriesData() Expected error") } } @@ -72,6 +72,6 @@ func TestGetFluctuationData(t *testing.T) { setup(t) _, err := f.GetFluctuationData("2013-12-24", "2013-12-25", "EUR", []string{"AUD,KRW"}) if err == nil { - t.Error("test failed - fixer GetFluctuationData() error", err) + t.Error("fixer GetFluctuationData() Expected error") } } diff --git a/currency/forexprovider/fixer.io/fixer_types.go b/currency/forexprovider/fixer.io/fixer_types.go index 8238b16d..116750bb 100644 --- a/currency/forexprovider/fixer.io/fixer_types.go +++ b/currency/forexprovider/fixer.io/fixer_types.go @@ -1,5 +1,36 @@ package fixer +import ( + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" +) + +const ( + fixerAPIFree = iota + fixerAPIBasic + fixerAPIProfessional + fixerAPIProfessionalPlus + fixerAPIEnterprise + + fixerAPI = "http://data.fixer.io/api/" + fixerAPISSL = "https://data.fixer.io/api/" + fixerAPILatest = "latest" + fixerAPIConvert = "convert" + fixerAPITimeSeries = "timeseries" + fixerAPIFluctuation = "fluctuation" + fixerSupportedCurrencies = "symbols" + + authRate = 0 + unAuthRate = 0 +) + +// Fixer is a foreign exchange rate provider at https://fixer.io/ +// NOTE DEFAULT BASE CURRENCY IS EUR upgrade to basic to change +type Fixer struct { + base.Base + Requester *request.Requester +} + // Rates contains the data fields for the currencies you have requested. type Rates struct { Success bool `json:"success"` diff --git a/currency/forexprovider/forexprovider.go b/currency/forexprovider/forexprovider.go index 8b11152f..ab415bbc 100644 --- a/currency/forexprovider/forexprovider.go +++ b/currency/forexprovider/forexprovider.go @@ -13,13 +13,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/openexchangerates" ) -// ForexProviders is a foreign exchange handler type -type ForexProviders struct { - base.FXHandler -} - -// GetAvailableForexProviders returns a list of supported forex providers -func GetAvailableForexProviders() []string { +// GetSupportedForexProviders returns a list of supported forex providers +func GetSupportedForexProviders() []string { return []string{"CurrencyConverter", "CurrencyLayer", "ExchangeRates", @@ -135,3 +130,8 @@ func StartFXService(fxProviders []base.Settings) (*ForexProviders, error) { return handler, nil } + +// ForexProviders is a foreign exchange handler type +type ForexProviders struct { + base.FXHandler +} diff --git a/currency/forexprovider/openexchangerates/openexchangerates.go b/currency/forexprovider/openexchangerates/openexchangerates.go index 4a02eb78..27ef1aac 100644 --- a/currency/forexprovider/openexchangerates/openexchangerates.go +++ b/currency/forexprovider/openexchangerates/openexchangerates.go @@ -23,45 +23,6 @@ import ( log "github.com/thrasher-corp/gocryptotrader/logger" ) -// These consts contain endpoint information -const ( - APIDeveloperAccess = iota - APIEnterpriseAccess - APIUnlimitedAccess - - APIURL = "https://openexchangerates.org/api/" - APIEndpointLatest = "latest.json" - APIEndpointHistorical = "historical/%s.json" - APIEndpointCurrencies = "currencies.json" - APIEndpointTimeSeries = "time-series.json" - APIEndpointConvert = "convert/%s/%s/%s" - APIEndpointOHLC = "ohlc.json" - APIEndpointUsage = "usage.json" - - oxrSupportedCurrencies = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD," + - "BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTC,BTN,BWP,BYN,BYR,BZD,CAD,CDF," + - "CHF,CLF,CLP,CNH,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EEK,EGP," + - "ERN,ETB,EUR,FJD,FKP,GBP,GEL,GGP,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK," + - "HTG,HUF,IDR,ILS,IMP,INR,IQD,IRR,ISK,JEP,JMD,JOD,JPY,KES,KGS,KHR,KMF," + - "KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT," + - "MOP,MRO,MRU,MTL,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR," + - "PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK," + - "SGD,SHP,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY," + - "TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VEF,VND,VUV,WST,XAF,XAG,XAU,XCD,XDR," + - "XOF,XPD,XPF,XPT,YER,ZAR,ZMK,ZMW" - - authRate = 0 - unAuthRate = 0 -) - -// OXR is a foreign exchange rate provider at https://openexchangerates.org/ -// this is the overarching type across this package -// DOCs : https://docs.openexchangerates.org/docs -type OXR struct { - base.Base - Requester *request.Requester -} - // Setup sets values for the OXR object func (o *OXR) Setup(config base.Settings) error { if config.APIKeyLvl < 0 || config.APIKeyLvl > 2 { diff --git a/currency/forexprovider/openexchangerates/openexchangerates_test.go b/currency/forexprovider/openexchangerates/openexchangerates_test.go index 69093c33..184c9c88 100644 --- a/currency/forexprovider/openexchangerates/openexchangerates_test.go +++ b/currency/forexprovider/openexchangerates/openexchangerates_test.go @@ -31,7 +31,7 @@ func TestGetRates(t *testing.T) { } _, err := o.GetRates("USD", "AUD") if err == nil { - t.Error("test failed - GetRates() error", err) + t.Error("GetRates() Expected error") } } @@ -41,7 +41,7 @@ func TestGetLatest(t *testing.T) { } _, err := o.GetLatest("USD", "AUD", false, false) if err == nil { - t.Error("test failed - GetLatest() error", err) + t.Error("GetLatest() Expected error") } } @@ -51,7 +51,7 @@ func TestGetHistoricalRates(t *testing.T) { } _, err := o.GetHistoricalRates("2017-12-01", "USD", []string{"CNH", "AUD", "ANG"}, false, false) if err == nil { - t.Error("test failed - GetRates() error", err) + t.Error("GetRates() Expected error") } } @@ -61,7 +61,7 @@ func TestGetCurrencies(t *testing.T) { } _, err := o.GetCurrencies(true, true, true) if err != nil { - t.Error("test failed - GetCurrencies() error", err) + t.Error("GetCurrencies() error", err) } } @@ -71,7 +71,7 @@ func TestGetTimeSeries(t *testing.T) { } _, err := o.GetTimeSeries("USD", "2017-12-01", "2017-12-02", []string{"CNH", "AUD", "ANG"}, false, false) if err == nil { - t.Error("test failed - GetTimeSeries() error", err) + t.Error("GetTimeSeries() Expected error") } } @@ -81,7 +81,7 @@ func TestConvertCurrency(t *testing.T) { } _, err := o.ConvertCurrency(1337, "USD", "AUD") if err == nil { - t.Error("test failed - ConvertCurrency() error", err) + t.Error("ConvertCurrency() Expected error") } } @@ -91,7 +91,7 @@ func TestGetOHLC(t *testing.T) { } _, err := o.GetOHLC("2017-07-17T08:30:00Z", "1m", "USD", []string{"AUD"}, false) if err == nil { - t.Error("test failed - GetOHLC() error", err) + t.Error("GetOHLC() Expected error") } } @@ -101,6 +101,6 @@ func TestGetUsageStats(t *testing.T) { } _, err := o.GetUsageStats(false) if err == nil { - t.Error("test failed - GetUsageStats() error", err) + t.Error("GetUsageStats() Expected error") } } diff --git a/currency/forexprovider/openexchangerates/openexchangerates_types.go b/currency/forexprovider/openexchangerates/openexchangerates_types.go index 8c2d6e18..438fd0a4 100644 --- a/currency/forexprovider/openexchangerates/openexchangerates_types.go +++ b/currency/forexprovider/openexchangerates/openexchangerates_types.go @@ -1,5 +1,49 @@ package openexchangerates +import ( + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" +) + +// These consts contain endpoint information +const ( + APIDeveloperAccess = iota + APIEnterpriseAccess + APIUnlimitedAccess + + APIURL = "https://openexchangerates.org/api/" + APIEndpointLatest = "latest.json" + APIEndpointHistorical = "historical/%s.json" + APIEndpointCurrencies = "currencies.json" + APIEndpointTimeSeries = "time-series.json" + APIEndpointConvert = "convert/%s/%s/%s" + APIEndpointOHLC = "ohlc.json" + APIEndpointUsage = "usage.json" + + oxrSupportedCurrencies = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD," + + "BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTC,BTN,BWP,BYN,BYR,BZD,CAD,CDF," + + "CHF,CLF,CLP,CNH,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EEK,EGP," + + "ERN,ETB,EUR,FJD,FKP,GBP,GEL,GGP,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK," + + "HTG,HUF,IDR,ILS,IMP,INR,IQD,IRR,ISK,JEP,JMD,JOD,JPY,KES,KGS,KHR,KMF," + + "KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT," + + "MOP,MRO,MRU,MTL,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR," + + "PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK," + + "SGD,SHP,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY," + + "TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VEF,VND,VUV,WST,XAF,XAG,XAU,XCD,XDR," + + "XOF,XPD,XPF,XPT,YER,ZAR,ZMK,ZMW" + + authRate = 0 + unAuthRate = 0 +) + +// OXR is a foreign exchange rate provider at https://openexchangerates.org/ +// this is the overarching type across this package +// DOCs : https://docs.openexchangerates.org/docs +type OXR struct { + base.Base + Requester *request.Requester +} + // Latest holds latest rate data type Latest struct { Disclaimer string `json:"disclaimer"` diff --git a/currency/manager_test.go b/currency/manager_test.go index 67a58490..80f32075 100644 --- a/currency/manager_test.go +++ b/currency/manager_test.go @@ -29,11 +29,11 @@ func TestGetAssetTypes(t *testing.T) { a := p.GetAssetTypes() if len(a) == 0 { - t.Errorf("Test failed. GetAssetTypes shouldn't be nil") + t.Errorf("GetAssetTypes shouldn't be nil") } if !a.Contains(asset.Spot) { - t.Errorf("Test failed. AssetTypeSpot should be in the assets list") + t.Errorf("AssetTypeSpot should be in the assets list") } } @@ -41,11 +41,11 @@ func TestGet(t *testing.T) { initTest() if p.Get(asset.Spot) == nil { - t.Error("Test failed. Spot assets shouldn't be nil") + t.Error("Spot assets shouldn't be nil") } if p.Get(asset.Futures) != nil { - t.Error("Test Failed. Futures should be nil") + t.Error("Futures should be nil") } } @@ -65,7 +65,7 @@ func TestStore(t *testing.T) { ) if p.Get(asset.Futures) == nil { - t.Error("Test failed. Futures assets shouldn't be nil") + t.Error("Futures assets shouldn't be nil") } } @@ -80,12 +80,12 @@ func TestDelete(t *testing.T) { ) p.Delete(asset.UpsideProfitContract) if p.Get(asset.Spot) == nil { - t.Error("Test failed. AssetTypeSpot should exist") + t.Error("AssetTypeSpot should exist") } p.Delete(asset.Spot) if p.Get(asset.Spot) != nil { - t.Error("Test failed. Delete should have deleted AssetTypeSpot") + t.Error("Delete should have deleted AssetTypeSpot") } } diff --git a/currency/pair.go b/currency/pair.go index e83ee8e2..e3c6fcdd 100644 --- a/currency/pair.go +++ b/currency/pair.go @@ -87,13 +87,6 @@ func NewPairFromFormattedPairs(currencyPair string, pairs Pairs, pairFmt PairFor return NewPairFromString(currencyPair) } -// Pair holds currency pair information -type Pair struct { - Delimiter string `json:"delimiter"` - Base Code `json:"base"` - Quote Code `json:"quote"` -} - // String returns a currency pair string func (p Pair) String() string { return p.Base.String() + p.Delimiter + p.Quote.String() @@ -203,3 +196,10 @@ func (p Pair) IsEmpty() bool { func (p Pair) ContainsCurrency(c Code) bool { return p.Base.Item == c.Item || p.Quote.Item == c.Item } + +// Pair holds currency pair information +type Pair struct { + Delimiter string `json:"delimiter"` + Base Code `json:"base"` + Quote Code `json:"quote"` +} diff --git a/currency/pair_test.go b/currency/pair_test.go index 07e9f789..87b7c5ff 100644 --- a/currency/pair_test.go +++ b/currency/pair_test.go @@ -17,7 +17,7 @@ func TestLower(t *testing.T) { actual := pair.Lower() expected := NewPairFromString(defaultPair).Lower() if actual != expected { - t.Errorf("Test failed. Lower(): %s was not equal to expected value: %s", + t.Errorf("Lower(): %s was not equal to expected value: %s", actual, expected) } } @@ -28,7 +28,7 @@ func TestUpper(t *testing.T) { actual := pair.Upper() expected := NewPairFromString(defaultPair) if actual != expected { - t.Errorf("Test failed. Upper(): %s was not equal to expected value: %s", + t.Errorf("Upper(): %s was not equal to expected value: %s", actual, expected) } } @@ -39,21 +39,21 @@ func TestPairUnmarshalJSON(t *testing.T) { encoded, err := common.JSONEncode(configPair) if err != nil { - t.Fatal("Test Failed - Pair UnmarshalJSON() error", err) + t.Fatal("Pair UnmarshalJSON() error", err) } err = common.JSONDecode(encoded, &unmarshalHere) if err != nil { - t.Fatal("Test Failed - Pair UnmarshalJSON() error", err) + t.Fatal("Pair UnmarshalJSON() error", err) } err = common.JSONDecode(encoded, &unmarshalHere) if err != nil { - t.Fatal("Test Failed - Pair UnmarshalJSON() error", err) + t.Fatal("Pair UnmarshalJSON() error", err) } if !unmarshalHere.Equal(configPair) { - t.Errorf("Test Failed - Pairs UnmarshalJSON() error expected %s but received %s", + t.Errorf("Pairs UnmarshalJSON() error expected %s but received %s", configPair, unmarshalHere) } } @@ -67,43 +67,43 @@ func TestPairMarshalJSON(t *testing.T) { encoded, err := common.JSONEncode(quickstruct) if err != nil { - t.Fatal("Test Failed - Pair MarshalJSON() error", err) + t.Fatal("Pair MarshalJSON() error", err) } expected := `{"superPair":"BTC-USD"}` if string(encoded) != expected { - t.Errorf("Test Failed - Pair MarshalJSON() error expected %s but received %s", + t.Errorf("Pair MarshalJSON() error expected %s but received %s", expected, string(encoded)) } } func TestIsCryptoPair(t *testing.T) { if !NewPair(BTC, LTC).IsCryptoPair() { - t.Error("Test Failed. TestIsCryptoPair. Expected true result") + t.Error("TestIsCryptoPair. Expected true result") } if NewPair(BTC, USD).IsCryptoPair() { - t.Error("Test Failed. TestIsCryptoPair. Expected false result") + t.Error("TestIsCryptoPair. Expected false result") } } func TestIsCryptoFiatPair(t *testing.T) { if !NewPair(BTC, USD).IsCryptoFiatPair() { - t.Error("Test Failed. TestIsCryptoPair. Expected true result") + t.Error("TestIsCryptoPair. Expected true result") } if NewPair(BTC, LTC).IsCryptoFiatPair() { - t.Error("Test Failed. TestIsCryptoPair. Expected false result") + t.Error("TestIsCryptoPair. Expected false result") } } func TestIsFiatPair(t *testing.T) { if !NewPair(AUD, USD).IsFiatPair() { - t.Error("Test Failed. TestIsFiatPair. Expected true result") + t.Error("TestIsFiatPair. Expected true result") } if NewPair(BTC, AUD).IsFiatPair() { - t.Error("Test Failed. TestIsFiatPair. Expected false result") + t.Error("TestIsFiatPair. Expected false result") } } @@ -113,7 +113,7 @@ func TestString(t *testing.T) { actual := defaultPair expected := pair.String() if actual != expected { - t.Errorf("Test failed. String(): %s was not equal to expected value: %s", + t.Errorf("String(): %s was not equal to expected value: %s", actual, expected) } } @@ -125,7 +125,7 @@ func TestFirstCurrency(t *testing.T) { expected := BTC if actual != expected { t.Errorf( - "Test failed. GetFirstCurrency(): %s was not equal to expected value: %s", + "GetFirstCurrency(): %s was not equal to expected value: %s", actual, expected, ) } @@ -138,7 +138,7 @@ func TestSecondCurrency(t *testing.T) { expected := USD if actual != expected { t.Errorf( - "Test failed. GetSecondCurrency(): %s was not equal to expected value: %s", + "GetSecondCurrency(): %s was not equal to expected value: %s", actual, expected, ) } @@ -151,7 +151,7 @@ func TestPair(t *testing.T) { expected := defaultPair if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -164,7 +164,7 @@ func TestDisplay(t *testing.T) { expected := defaultPairWDelimiter if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -173,7 +173,7 @@ func TestDisplay(t *testing.T) { expected = "btcusd" if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -182,7 +182,7 @@ func TestDisplay(t *testing.T) { expected = "BTC~USD" if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -196,7 +196,7 @@ func TestEquall(t *testing.T) { expected := true if actual != expected { t.Errorf( - "Test failed. Equal(): %v was not equal to expected value: %v", + "Equal(): %v was not equal to expected value: %v", actual, expected, ) } @@ -206,7 +206,7 @@ func TestEquall(t *testing.T) { expected = false if actual != expected { t.Errorf( - "Test failed. Equal(): %v was not equal to expected value: %v", + "Equal(): %v was not equal to expected value: %v", actual, expected, ) } @@ -216,7 +216,7 @@ func TestEquall(t *testing.T) { expected = false if actual != expected { t.Errorf( - "Test failed. Equal(): %v was not equal to expected value: %v", + "Equal(): %v was not equal to expected value: %v", actual, expected, ) } @@ -230,7 +230,7 @@ func TestEqualIncludeReciprocal(t *testing.T) { expected := true if actual != expected { t.Errorf( - "Test failed. Equal(): %v was not equal to expected value: %v", + "Equal(): %v was not equal to expected value: %v", actual, expected, ) } @@ -240,7 +240,7 @@ func TestEqualIncludeReciprocal(t *testing.T) { expected = false if actual != expected { t.Errorf( - "Test failed. Equal(): %v was not equal to expected value: %v", + "Equal(): %v was not equal to expected value: %v", actual, expected, ) } @@ -250,7 +250,7 @@ func TestEqualIncludeReciprocal(t *testing.T) { expected = true if actual != expected { t.Errorf( - "Test failed. Equal(): %v was not equal to expected value: %v", + "Equal(): %v was not equal to expected value: %v", actual, expected, ) } @@ -263,7 +263,7 @@ func TestSwap(t *testing.T) { expected := "USDBTC" if actual != expected { t.Errorf( - "Test failed. TestSwap: %s was not equal to expected value: %s", + "TestSwap: %s was not equal to expected value: %s", actual, expected, ) } @@ -273,12 +273,12 @@ func TestEmpty(t *testing.T) { t.Parallel() pair := NewPair(BTC, USD) if pair.IsEmpty() { - t.Error("Test failed. Empty() returned true when the pair was initialised") + t.Error("Empty() returned true when the pair was initialised") } p := NewPair(NewCode(""), NewCode("")) if !p.IsEmpty() { - t.Error("Test failed. Empty() returned true when the pair wasn't initialised") + t.Error("Empty() returned true when the pair wasn't initialised") } } @@ -289,7 +289,7 @@ func TestNewPair(t *testing.T) { expected := defaultPair if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -302,7 +302,7 @@ func TestNewPairWithDelimiter(t *testing.T) { expected := "BTC-test-USD" if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -312,7 +312,7 @@ func TestNewPairWithDelimiter(t *testing.T) { expected = defaultPair if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -325,7 +325,7 @@ func TestNewPairDelimiter(t *testing.T) { expected := defaultPairWDelimiter if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -334,7 +334,7 @@ func TestNewPairDelimiter(t *testing.T) { expected = "-" if actual != expected { t.Errorf( - "Test failed. Delmiter: %s was not equal to expected value: %s", + "Delmiter: %s was not equal to expected value: %s", actual, expected, ) } @@ -349,7 +349,7 @@ func TestNewPairFromIndex(t *testing.T) { pair, err := NewPairFromIndex(currency, index) if err != nil { - t.Error("test failed - NewPairFromIndex() error", err) + t.Error("NewPairFromIndex() error", err) } pair.Delimiter = "-" @@ -358,7 +358,7 @@ func TestNewPairFromIndex(t *testing.T) { expected := defaultPairWDelimiter if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -367,7 +367,7 @@ func TestNewPairFromIndex(t *testing.T) { pair, err = NewPairFromIndex(currency, index) if err != nil { - t.Error("test failed - NewPairFromIndex() error", err) + t.Error("NewPairFromIndex() error", err) } pair.Delimiter = "-" @@ -376,7 +376,7 @@ func TestNewPairFromIndex(t *testing.T) { expected = "DOGE-BTC" if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -390,7 +390,7 @@ func TestNewPairFromString(t *testing.T) { expected := defaultPairWDelimiter if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -401,7 +401,7 @@ func TestNewPairFromString(t *testing.T) { expected = defaultPair if actual != expected { t.Errorf( - "Test failed. Pair(): %s was not equal to expected value: %s", + "Pair(): %s was not equal to expected value: %s", actual, expected, ) } @@ -416,18 +416,18 @@ func TestNewPairFromFormattedPairs(t *testing.T) { p := NewPairFromFormattedPairs("BTCUSDT", pairs, PairFormat{Uppercase: true}) if p.String() != "BTC-USDT" { - t.Error("Test failed. TestNewPairFromFormattedPairs: Expected currency was not found") + t.Error("TestNewPairFromFormattedPairs: Expected currency was not found") } p = NewPairFromFormattedPairs("btcusdt", pairs, PairFormat{Uppercase: false}) if p.String() != "BTC-USDT" { - t.Error("Test failed. TestNewPairFromFormattedPairs: Expected currency was not found") + t.Error("TestNewPairFromFormattedPairs: Expected currency was not found") } // Now a wrong one, will default to NewPairFromString p = NewPairFromFormattedPairs("ethusdt", pairs, PairFormat{}) if p.String() != "ethusdt" && p.Base.String() != "eth" { - t.Error("Test failed. TestNewPairFromFormattedPairs: Expected currency was not found") + t.Error("TestNewPairFromFormattedPairs: Expected currency was not found") } } @@ -435,48 +435,48 @@ func TestContainsCurrency(t *testing.T) { p := NewPair(BTC, USD) if !p.ContainsCurrency(BTC) { - t.Error("Test failed. TestContainsCurrency: Expected currency was not found") + t.Error("TestContainsCurrency: Expected currency was not found") } if p.ContainsCurrency(ETH) { - t.Error("Test failed. TestContainsCurrency: Non-existent currency was found") + t.Error("TestContainsCurrency: Non-existent currency was found") } } func TestFormatPairs(t *testing.T) { newP, err := FormatPairs([]string{""}, "-", "") if err != nil { - t.Error("Test Failed - FormatPairs() error", err) + t.Error("FormatPairs() error", err) } if len(newP) > 0 { - t.Error("Test failed. TestFormatPairs: Empty string returned a valid pair") + t.Error("TestFormatPairs: Empty string returned a valid pair") } newP, err = FormatPairs([]string{defaultPairWDelimiter}, "-", "") if err != nil { - t.Error("Test Failed - FormatPairs() error", err) + t.Error("FormatPairs() error", err) } if newP[0].String() != defaultPairWDelimiter { - t.Error("Test failed. TestFormatPairs: Expected pair was not found") + t.Error("TestFormatPairs: Expected pair was not found") } newP, err = FormatPairs([]string{defaultPair}, "", "BTC") if err != nil { - t.Error("Test Failed - FormatPairs() error", err) + t.Error("FormatPairs() error", err) } if newP[0].String() != defaultPair { - t.Error("Test failed. TestFormatPairs: Expected pair was not found") + t.Error("TestFormatPairs: Expected pair was not found") } newP, err = FormatPairs([]string{"ETHUSD"}, "", "") if err != nil { - t.Error("Test Failed - FormatPairs() error", err) + t.Error("FormatPairs() error", err) } if newP[0].String() != "ETHUSD" { - t.Error("Test failed. TestFormatPairs: Expected pair was not found") + t.Error("TestFormatPairs: Expected pair was not found") } } @@ -492,12 +492,12 @@ func TestCopyPairFormat(t *testing.T) { result := CopyPairFormat(testPair, pairs, false) if result.String() != defaultPairWDelimiter { - t.Error("Test failed. TestCopyPairFormat: Expected pair was not found") + t.Error("TestCopyPairFormat: Expected pair was not found") } result = CopyPairFormat(NewPair(ETH, USD), pairs, true) if result.String() != "" { - t.Error("Test failed. TestCopyPairFormat: Unexpected non empty pair returned") + t.Error("TestCopyPairFormat: Unexpected non empty pair returned") } } @@ -507,26 +507,26 @@ func TestFindPairDifferences(t *testing.T) { // Test new pair update newPairs, removedPairs := pairList.FindDifferences(NewPairsFromStrings([]string{"DASH-USD"})) if len(newPairs) != 1 && len(removedPairs) != 3 { - t.Error("Test failed. TestFindPairDifferences: Unexpected values") + t.Error("TestFindPairDifferences: Unexpected values") } // Test that we don't allow empty strings for new pairs newPairs, removedPairs = pairList.FindDifferences(NewPairsFromStrings([]string{""})) if len(newPairs) != 0 && len(removedPairs) != 3 { - t.Error("Test failed. TestFindPairDifferences: Unexpected values") + t.Error("TestFindPairDifferences: Unexpected values") } // Test that we don't allow empty strings for new pairs newPairs, removedPairs = NewPairsFromStrings([]string{""}).FindDifferences(pairList) if len(newPairs) != 3 && len(removedPairs) != 0 { - t.Error("Test failed. TestFindPairDifferences: Unexpected values") + t.Error("TestFindPairDifferences: Unexpected values") } // Test that the supplied pair lists are the same, so // no newPairs or removedPairs newPairs, removedPairs = pairList.FindDifferences(pairList) if len(newPairs) != 0 && len(removedPairs) != 0 { - t.Error("Test failed. TestFindPairDifferences: Unexpected values") + t.Error("TestFindPairDifferences: Unexpected values") } } @@ -538,7 +538,7 @@ func TestPairsToStringArray(t *testing.T) { actual := pairs.Strings() if actual[0] != expected[0] { - t.Error("Test failed. TestPairsToStringArray: Unexpected values") + t.Error("TestPairsToStringArray: Unexpected values") } } @@ -547,7 +547,7 @@ func TestRandomPairFromPairs(t *testing.T) { var emptyPairs Pairs result := emptyPairs.GetRandomPair() if !result.IsEmpty() { - t.Error("Test failed. TestRandomPairFromPairs: Unexpected values") + t.Error("TestRandomPairFromPairs: Unexpected values") } // Test that a populated pairs array returns a non-empty currency pair @@ -556,7 +556,7 @@ func TestRandomPairFromPairs(t *testing.T) { result = pairs.GetRandomPair() if result.IsEmpty() { - t.Error("Test failed. TestRandomPairFromPairs: Unexpected values") + t.Error("TestRandomPairFromPairs: Unexpected values") } // Test that a populated pairs array over a number of attempts returns ALL @@ -574,7 +574,7 @@ func TestRandomPairFromPairs(t *testing.T) { for x := range pairs { _, ok := expectedResults[pairs[x].String()] if !ok { - t.Error("Test failed. TestRandomPairFromPairs: Unexpected values") + t.Error("TestRandomPairFromPairs: Unexpected values") } } } @@ -582,6 +582,6 @@ func TestRandomPairFromPairs(t *testing.T) { func TestIsInvalid(t *testing.T) { p := NewPair(LTC, LTC) if !p.IsInvalid() { - t.Error("Test Failed - IsInvalid() error expect true but received false") + t.Error("IsInvalid() error expect true but received false") } } diff --git a/currency/pairs.go b/currency/pairs.go index 06cdedd1..a6855e87 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -22,9 +22,6 @@ func NewPairsFromStrings(pairs []string) Pairs { return ps } -// Pairs defines a list of pairs -type Pairs []Pair - // Strings returns a slice of strings referring to each currency pair func (p Pairs) Strings() []string { var list []string @@ -186,3 +183,6 @@ func (p Pairs) GetRandomPair() Pair { return p[rand.Intn(pairsLen)] } + +// Pairs defines a list of pairs +type Pairs []Pair diff --git a/currency/pairs_test.go b/currency/pairs_test.go index e8d7e1e5..bd52814d 100644 --- a/currency/pairs_test.go +++ b/currency/pairs_test.go @@ -11,7 +11,7 @@ func TestPairsUpper(t *testing.T) { expected := "BTC_USD,BTC_AUD,BTC_LTC" if pairs.Upper().Join() != expected { - t.Errorf("Test Failed - Pairs Join() error expected %s but received %s", + t.Errorf("Pairs Join() error expected %s but received %s", expected, pairs.Join()) } } @@ -22,7 +22,7 @@ func TestPairsString(t *testing.T) { for i, p := range pairs { if p.String() != expected[i] { - t.Errorf("Test Failed - Pairs String() error expected %s but received %s", + t.Errorf("Pairs String() error expected %s but received %s", expected, p.String()) } } @@ -33,7 +33,7 @@ func TestPairsJoin(t *testing.T) { expected := "btc_usd,btc_aud,btc_ltc" if pairs.Join() != expected { - t.Errorf("Test Failed - Pairs Join() error expected %s but received %s", + t.Errorf("Pairs Join() error expected %s but received %s", expected, pairs.Join()) } } @@ -43,25 +43,25 @@ func TestPairsFormat(t *testing.T) { expected := "BTC-USD,BTC-AUD,BTC-LTC" if pairs.Format("-", "", true).Join() != expected { - t.Errorf("Test Failed - Pairs Join() error expected %s but received %s", + t.Errorf("Pairs Join() error expected %s but received %s", expected, pairs.Format("-", "", true).Join()) } expected = "btc:usd,btc:aud,btc:ltc" if pairs.Format(":", "", false).Join() != expected { - t.Errorf("Test Failed - Pairs Join() error expected %s but received %s", + t.Errorf("Pairs Join() error expected %s but received %s", expected, pairs.Format(":", "", false).Join()) } if pairs.Format(":", "KRW", false).Join() != "" { - t.Errorf("Test Failed - Pairs Join() error expected %s but received %s", + t.Errorf("Pairs Join() error expected %s but received %s", expected, pairs.Format(":", "KRW", true).Join()) } pairs = NewPairsFromStrings([]string{"DASHKRW", "BTCKRW"}) expected = "dash-krw,btc-krw" if pairs.Format("-", "KRW", false).Join() != expected { - t.Errorf("Test Failed - Pairs Join() error expected %s but received %s", + t.Errorf("Pairs Join() error expected %s but received %s", expected, pairs.Format("-", "KRW", false).Join()) } } @@ -72,21 +72,21 @@ func TestPairsUnmarshalJSON(t *testing.T) { encoded, err := common.JSONEncode(configPairs) if err != nil { - t.Fatal("Test Failed - Pairs UnmarshalJSON() error", err) + t.Fatal("Pairs UnmarshalJSON() error", err) } err = common.JSONDecode(encoded, &unmarshalHere) if err != nil { - t.Fatal("Test Failed - Pairs UnmarshalJSON() error", err) + t.Fatal("Pairs UnmarshalJSON() error", err) } err = common.JSONDecode(encoded, &unmarshalHere) if err != nil { - t.Fatal("Test Failed - Pairs UnmarshalJSON() error", err) + t.Fatal("Pairs UnmarshalJSON() error", err) } if unmarshalHere.Join() != configPairs { - t.Errorf("Test Failed - Pairs UnmarshalJSON() error expected %s but received %s", + t.Errorf("Pairs UnmarshalJSON() error expected %s but received %s", configPairs, unmarshalHere.Join()) } } @@ -100,12 +100,12 @@ func TestPairsMarshalJSON(t *testing.T) { encoded, err := common.JSONEncode(quickstruct) if err != nil { - t.Fatal("Test Failed - Pairs MarshalJSON() error", err) + t.Fatal("Pairs MarshalJSON() error", err) } expected := `{"soManyPairs":"btc_usd,btc_aud,btc_ltc"}` if string(encoded) != expected { - t.Errorf("Test Failed - Pairs MarshalJSON() error expected %s but received %s", + t.Errorf("Pairs MarshalJSON() error expected %s but received %s", expected, string(encoded)) } } @@ -119,7 +119,7 @@ func TestRemovePairsByFilter(t *testing.T) { pairs = pairs.RemovePairsByFilter(USDT) if pairs.Contains(NewPair(LTC, USDT), true) { - t.Error("Test failed. TestRemovePairsByFilter unexpected result") + t.Error("TestRemovePairsByFilter unexpected result") } } @@ -133,7 +133,7 @@ func TestRemove(t *testing.T) { p := NewPair(BTC, USD) pairs = pairs.Remove(p) if pairs.Contains(p, true) || len(pairs) != 2 { - t.Error("Test failed. TestRemove unexpected result") + t.Error("TestRemove unexpected result") } } @@ -148,13 +148,13 @@ func TestAdd(t *testing.T) { p := NewPair(BTC, USDT) pairs = pairs.Add(p) if !pairs.Contains(p, true) || len(pairs) != 4 { - t.Error("Test failed. TestAdd unexpected result") + t.Error("TestAdd unexpected result") } // Now test adding a pair which already exists pairs = pairs.Add(p) if len(pairs) != 4 { - t.Error("Test failed. TestAdd unexpected result") + t.Error("TestAdd unexpected result") } } @@ -165,10 +165,10 @@ func TestContains(t *testing.T) { } if !pairs.Contains(NewPair(BTC, USD), true) { - t.Errorf("Test failed. TestContains: Expected pair was not found") + t.Errorf("TestContains: Expected pair was not found") } if pairs.Contains(NewPair(ETH, USD), false) { - t.Errorf("Test failed. TestContains: Non-existent pair was found") + t.Errorf("TestContains: Non-existent pair was found") } } diff --git a/currency/storage.go b/currency/storage.go index a74947ab..f3c9f636 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -6,7 +6,6 @@ import ( "fmt" "io/ioutil" "path/filepath" - "sync" "time" "github.com/thrasher-corp/gocryptotrader/common" @@ -16,75 +15,10 @@ import ( log "github.com/thrasher-corp/gocryptotrader/logger" ) -// CurrencyFileUpdateDelay defines the rate at which the currency.json file is -// updated -const ( - DefaultCurrencyFileDelay = 168 * time.Hour - DefaultForeignExchangeDelay = 1 * time.Minute - DefaultStorageFile = "currency.json" -) - func init() { storage.SetDefaults() } -// storage is an overarching type that keeps track of and updates currency, -// currency exchange rates and pairs -var storage Storage - -// Storage contains the loaded storage currencies supported on available crypto -// or fiat marketplaces -// NOTE: All internal currencies are upper case -type Storage struct { - // FiatCurrencies defines the running fiat currencies in the currency - // storage - fiatCurrencies Currencies - - // Cryptocurrencies defines the running cryptocurrencies in the currency - // storage - cryptocurrencies Currencies - - // CurrencyCodes is a full basket of currencies either crypto, fiat, ico or - // contract being tracked by the currency storage system - currencyCodes BaseCodes - - // Main converting currency - baseCurrency Code - - // FXRates defines a protected conversion rate map - fxRates ConversionRates - - // DefaultBaseCurrency is the base currency used for conversion - defaultBaseCurrency Code - - // DefaultFiatCurrencies has the default minimum of FIAT values - defaultFiatCurrencies Currencies - - // DefaultCryptoCurrencies has the default minimum of crytpocurrency values - defaultCryptoCurrencies Currencies - - // FiatExchangeMarkets defines an interface to access FX data for fiat - // currency rates - fiatExchangeMarkets *forexprovider.ForexProviders - - // CurrencyAnalysis defines a full market analysis suite to receieve and - // define different fiat currencies, cryptocurrencies and markets - currencyAnalysis *coinmarketcap.Coinmarketcap - - // Path defines the main folder to dump and find currency JSON - path string - - // Update delay variables - currencyFileUpdateDelay time.Duration - foreignExchangeUpdateDelay time.Duration - - mtx sync.Mutex - wg sync.WaitGroup - shutdownC chan struct{} - updaterRunning bool - Verbose bool -} - // SetDefaults sets storage defaults for basic package functionality func (s *Storage) SetDefaults() { s.defaultBaseCurrency = USD diff --git a/currency/storage_test.go b/currency/storage_test.go index 2ed70a61..b9e562bf 100644 --- a/currency/storage_test.go +++ b/currency/storage_test.go @@ -8,7 +8,7 @@ func TestRunUpdater(t *testing.T) { emptyMainConfig := MainConfiguration{} err := newStorage.RunUpdater(BotOverrides{}, &emptyMainConfig, "", false) if err == nil { - t.Fatal("Test Failed storage RunUpdater() error cannot be nil") + t.Fatal("storage RunUpdater() error cannot be nil") } mainConfig := MainConfiguration{ @@ -18,11 +18,11 @@ func TestRunUpdater(t *testing.T) { err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "", false) if err == nil { - t.Fatal("Test Failed storage RunUpdater() error cannot be nil") + t.Fatal("storage RunUpdater() error cannot be nil") } err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "/bla", false) if err != nil { - t.Fatal("Test Failed storage RunUpdater() error", err) + t.Fatal("storage RunUpdater() error", err) } } diff --git a/currency/storage_types.go b/currency/storage_types.go new file mode 100644 index 00000000..032be5db --- /dev/null +++ b/currency/storage_types.go @@ -0,0 +1,62 @@ +package currency + +import ( + "sync" + "time" + + "github.com/thrasher-corp/gocryptotrader/currency/coinmarketcap" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider" +) + +// CurrencyFileUpdateDelay defines the rate at which the currency.json file is +// updated +const ( + DefaultCurrencyFileDelay = 168 * time.Hour + DefaultForeignExchangeDelay = 1 * time.Minute + DefaultStorageFile = "currency.json" +) + +// storage is an overarching type that keeps track of and updates currency, +// currency exchange rates and pairs +var storage Storage + +// Storage contains the loaded storage currencies supported on available crypto +// or fiat marketplaces +// NOTE: All internal currencies are upper case +type Storage struct { + // FiatCurrencies defines the running fiat currencies in the currency + // storage + fiatCurrencies Currencies + // Cryptocurrencies defines the running cryptocurrencies in the currency + // storage + cryptocurrencies Currencies + // CurrencyCodes is a full basket of currencies either crypto, fiat, ico or + // contract being tracked by the currency storage system + currencyCodes BaseCodes + // Main converting currency + baseCurrency Code + // FXRates defines a protected conversion rate map + fxRates ConversionRates + // DefaultBaseCurrency is the base currency used for conversion + defaultBaseCurrency Code + // DefaultFiatCurrencies has the default minimum of FIAT values + defaultFiatCurrencies Currencies + // DefaultCryptoCurrencies has the default minimum of crytpocurrency values + defaultCryptoCurrencies Currencies + // FiatExchangeMarkets defines an interface to access FX data for fiat + // currency rates + fiatExchangeMarkets *forexprovider.ForexProviders + // CurrencyAnalysis defines a full market analysis suite to receieve and + // define different fiat currencies, cryptocurrencies and markets + currencyAnalysis *coinmarketcap.Coinmarketcap + // Path defines the main folder to dump and find currency JSON + path string + // Update delay variables + currencyFileUpdateDelay time.Duration + foreignExchangeUpdateDelay time.Duration + mtx sync.Mutex + wg sync.WaitGroup + shutdownC chan struct{} + updaterRunning bool + Verbose bool +} diff --git a/currency/symbol.go b/currency/symbol.go index 28f8221e..e677384a 100644 --- a/currency/symbol.go +++ b/currency/symbol.go @@ -2,6 +2,15 @@ package currency import "errors" +// GetSymbolByCurrencyName returns a currency symbol +func GetSymbolByCurrencyName(currency Code) (string, error) { + result, ok := symbols[currency.Item] + if !ok { + return "", errors.New("currency symbol not found") + } + return result, nil +} + // symbols map holds the currency name and symbol mappings var symbols = map[*Item]string{ ALL.Item: "Lek", @@ -116,12 +125,3 @@ var symbols = map[*Item]string{ YER.Item: "﷼", ZWD.Item: "Z$", } - -// GetSymbolByCurrencyName returns a currency symbol -func GetSymbolByCurrencyName(currency Code) (string, error) { - result, ok := symbols[currency.Item] - if !ok { - return "", errors.New("currency symbol not found") - } - return result, nil -} diff --git a/currency/symbol_test.go b/currency/symbol_test.go index 2fe3ae6a..4c496f21 100644 --- a/currency/symbol_test.go +++ b/currency/symbol_test.go @@ -6,16 +6,16 @@ func TestGetSymbolByCurrencyName(t *testing.T) { expected := "₩" actual, err := GetSymbolByCurrencyName(KPW) if err != nil { - t.Errorf("Test failed. TestGetSymbolByCurrencyName error: %s", err) + t.Errorf("TestGetSymbolByCurrencyName error: %s", err) } if actual != expected { - t.Errorf("Test failed. TestGetSymbolByCurrencyName differing values") + t.Errorf("TestGetSymbolByCurrencyName differing values") } _, err = GetSymbolByCurrencyName(Code{}) if err == nil { - t.Errorf("Test failed. TestGetSymbolByCurrencyNam returned nil on non-existent currency") + t.Errorf("TestGetSymbolByCurrencyNam returned nil on non-existent currency") } } diff --git a/currency/translation.go b/currency/translation.go index 3a0632b3..c2db7430 100644 --- a/currency/translation.go +++ b/currency/translation.go @@ -1,5 +1,15 @@ package currency +// GetTranslation returns similar strings for a particular currency if not found +// returns the code back +func GetTranslation(currency Code) (Code, bool) { + val, ok := translations[currency] + if !ok { + return currency, ok + } + return val, ok +} + var translations = map[Code]Code{ BTC: XBT, ETH: XETH, @@ -10,13 +20,3 @@ var translations = map[Code]Code{ XDG: DOGE, USDT: USD, } - -// GetTranslation returns similar strings for a particular currency if not found -// returns the code back -func GetTranslation(currency Code) (Code, bool) { - val, ok := translations[currency] - if !ok { - return currency, ok - } - return val, ok -} diff --git a/dispatch/dispatch_test.go b/dispatch/dispatch_test.go index e526e3e2..cdbf1aaa 100644 --- a/dispatch/dispatch_test.go +++ b/dispatch/dispatch_test.go @@ -116,9 +116,9 @@ func TestDispatcher(t *testing.T) { t.Error("error cannot be nil") } - id, errrrrrrrr := dispatcher.getNewID() - if errrrrrrrr != nil { - t.Error(errrrrrrrr) + id, err := dispatcher.getNewID() + if err != nil { + t.Error(err) } err = dispatcher.publish(id, &payload) @@ -159,18 +159,18 @@ func TestDispatcher(t *testing.T) { randomChan := make(chan interface{}) err = dispatcher.unsubscribe(someID, randomChan) if err == nil { - t.Error(err) + t.Error("Expected error") } err = dispatcher.unsubscribe(id, randomChan) if err == nil { - t.Error(err) + t.Error("Expected error") } close(randomChan) err = dispatcher.unsubscribe(id, randomChan) if err == nil { - t.Error(err) + t.Error("Expected error") } } diff --git a/engine/exchange_test.go b/engine/exchange_test.go index 7dff0a20..b7713b82 100644 --- a/engine/exchange_test.go +++ b/engine/exchange_test.go @@ -16,7 +16,7 @@ func SetupTest(t *testing.T) { Bot.Config = &config.Cfg err := Bot.Config.LoadConfig("", true) if err != nil { - t.Fatalf("Test failed. SetupTest: Failed to load config: %s", err) + t.Fatalf("SetupTest: Failed to load config: %s", err) } testSetup = true } @@ -26,7 +26,7 @@ func SetupTest(t *testing.T) { } err := LoadExchange("Bitfinex", false, nil) if err != nil { - t.Errorf("Test failed. SetupTest: Failed to load exchange: %s", err) + t.Errorf("SetupTest: Failed to load exchange: %s", err) } } @@ -37,7 +37,7 @@ func CleanupTest(t *testing.T) { err := UnloadExchange("Bitfinex") if err != nil { - t.Fatalf("Test failed. CleanupTest: Failed to unload exchange: %s", + t.Fatalf("CleanupTest: Failed to unload exchange: %s", err) } } @@ -46,11 +46,11 @@ func TestCheckExchangeExists(t *testing.T) { SetupTest(t) if !CheckExchangeExists("Bitfinex") { - t.Errorf("Test failed. TestGetExchangeExists: Unable to find exchange") + t.Errorf("TestGetExchangeExists: Unable to find exchange") } if CheckExchangeExists("Asdsad") { - t.Errorf("Test failed. TestGetExchangeExists: Non-existent exchange found") + t.Errorf("TestGetExchangeExists: Non-existent exchange found") } CleanupTest(t) @@ -61,26 +61,26 @@ func TestGetExchangeByName(t *testing.T) { exch := GetExchangeByName("Bitfinex") if exch == nil { - t.Errorf("Test failed. TestGetExchangeByName: Failed to get exchange") + t.Errorf("TestGetExchangeByName: Failed to get exchange") } if !exch.IsEnabled() { - t.Errorf("Test failed. TestGetExchangeByName: Unexpected result") + t.Errorf("TestGetExchangeByName: Unexpected result") } exch.SetEnabled(false) bfx := GetExchangeByName("Bitfinex") if bfx.IsEnabled() { - t.Errorf("Test failed. TestGetExchangeByName: Unexpected result") + t.Errorf("TestGetExchangeByName: Unexpected result") } if exch.GetName() != "Bitfinex" { - t.Errorf("Test failed. TestGetExchangeByName: Unexpected result") + t.Errorf("TestGetExchangeByName: Unexpected result") } exch = GetExchangeByName("Asdasd") if exch != nil { - t.Errorf("Test failed. TestGetExchangeByName: Non-existent exchange found") + t.Errorf("TestGetExchangeByName: Non-existent exchange found") } CleanupTest(t) @@ -91,13 +91,13 @@ func TestReloadExchange(t *testing.T) { err := ReloadExchange("asdf") if err != ErrExchangeNotFound { - t.Errorf("Test failed. TestReloadExchange: Incorrect result: %s", + t.Errorf("TestReloadExchange: Incorrect result: %s", err) } err = ReloadExchange("Bitfinex") if err != nil { - t.Errorf("Test failed. TestReloadExchange: Incorrect result: %s", + t.Errorf("TestReloadExchange: Incorrect result: %s", err) } @@ -105,7 +105,7 @@ func TestReloadExchange(t *testing.T) { err = ReloadExchange("asdf") if err != ErrNoExchangesLoaded { - t.Errorf("Test failed. TestReloadExchange: Incorrect result: %s", + t.Errorf("TestReloadExchange: Incorrect result: %s", err) } } @@ -115,19 +115,19 @@ func TestUnloadExchange(t *testing.T) { err := UnloadExchange("asdf") if err != ErrExchangeNotFound { - t.Errorf("Test failed. TestUnloadExchange: Incorrect result: %s", + t.Errorf("TestUnloadExchange: Incorrect result: %s", err) } err = UnloadExchange("Bitfinex") if err != nil { - t.Errorf("Test failed. TestUnloadExchange: Failed to get exchange. %s", + t.Errorf("TestUnloadExchange: Failed to get exchange. %s", err) } err = UnloadExchange("asdf") if err != ErrNoExchangesLoaded { - t.Errorf("Test failed. TestUnloadExchange: Incorrect result: %s", + t.Errorf("TestUnloadExchange: Incorrect result: %s", err) } diff --git a/engine/helpers_test.go b/engine/helpers_test.go index 085db1f5..8bf23ab3 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -31,7 +31,7 @@ func SetupTestHelpers(t *testing.T) { Bot.Config = &config.Cfg err := Bot.Config.LoadConfig("../testdata/configtest.json", true) if err != nil { - t.Fatalf("Test failed. SetupTest: Failed to load config: %s", err) + t.Fatalf("SetupTest: Failed to load config: %s", err) } testSetup = true } @@ -428,7 +428,7 @@ func TestGetSpecificTicker(t *testing.T) { &ticker.Price{Pair: p, Last: 1000}, asset.Spot) if err != nil { - t.Fatal("Test failed. ProcessTicker error", err) + t.Fatal("ProcessTicker error", err) } tick, err := GetSpecificTicker(currency.NewPairFromStrings("BTC", "USD"), "Bitstamp", @@ -587,6 +587,6 @@ func TestGetCryptocurrenciesByExchange(t *testing.T) { _, err := GetCryptocurrenciesByExchange("Bitfinex", false, false, asset.Spot) if err != nil { - t.Fatalf("Test failed. Err %s", err) + t.Fatalf("Err %s", err) } } diff --git a/engine/restful_server_test.go b/engine/restful_server_test.go index 5729dec8..631ecbe2 100644 --- a/engine/restful_server_test.go +++ b/engine/restful_server_test.go @@ -15,7 +15,7 @@ func loadConfig(t *testing.T) *config.Config { cfg := config.GetConfig() err := cfg.LoadConfig("", true) if err != nil { - t.Error("Test failed. GetCurrencyConfig LoadConfig error", err) + t.Error("GetCurrencyConfig LoadConfig error", err) } return cfg } @@ -25,7 +25,7 @@ func makeHTTPGetRequest(t *testing.T, response interface{}) *http.Response { err := RESTfulJSONResponse(w, response) if err != nil { - t.Error("Test failed. Failed to make response.", err) + t.Error("Failed to make response.", err) } return w.Result() } @@ -37,17 +37,17 @@ func TestConfigAllJsonResponse(t *testing.T) { body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { - t.Error("Test failed. Body not readable", err) + t.Error("Body not readable", err) } var responseConfig config.Config jsonErr := json.Unmarshal(body, &responseConfig) if jsonErr != nil { - t.Error("Test failed. Response not parseable as json", err) + t.Error("Response not parseable as json", err) } if reflect.DeepEqual(responseConfig, cfg) { - t.Error("Test failed. Json not equal to config") + t.Error("Json not equal to config") } } @@ -62,7 +62,7 @@ func TestInvalidHostRequest(t *testing.T) { newRouter(true).ServeHTTP(resp, req) if status := resp.Code; status != http.StatusNotFound { - t.Errorf("Test failed. Response returned wrong status code expected %v got %v", http.StatusNotFound, status) + t.Errorf("Response returned wrong status code expected %v got %v", http.StatusNotFound, status) } } @@ -77,6 +77,6 @@ func TestValidHostRequest(t *testing.T) { newRouter(true).ServeHTTP(resp, req) if status := resp.Code; status != http.StatusOK { - t.Errorf("Test failed. Response returned wrong status code expected %v got %v", http.StatusOK, status) + t.Errorf("Response returned wrong status code expected %v got %v", http.StatusOK, status) } } diff --git a/engine/syncer_test.go b/engine/syncer_test.go index ed1bed2d..8546e21e 100644 --- a/engine/syncer_test.go +++ b/engine/syncer_test.go @@ -16,7 +16,7 @@ func TestNewCurrencyPairSyncer(t *testing.T) { Bot.Config = &config.Cfg err := Bot.Config.LoadConfig("", true) if err != nil { - t.Fatalf("Test failed. TestNewExchangeSyncer: Failed to load config: %s", err) + t.Fatalf("TestNewExchangeSyncer: Failed to load config: %s", err) } Bot.Settings.DisableExchangeAutoPairUpdates = true diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index 303a6e9b..f76b1630 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -21,10 +21,10 @@ func TestSetDefaults(t *testing.T) { SetDefaults.SetDefaults() if SetDefaults.API.Endpoints.URL != "https://sim3.alphapoint.com:8400" { - t.Error("Test Failed - SetDefaults: String Incorrect -", SetDefaults.API.Endpoints.URL) + t.Error("SetDefaults: String Incorrect -", SetDefaults.API.Endpoints.URL) } if SetDefaults.API.Endpoints.WebsocketURL != "wss://sim3.alphapoint.com:8401/v1/GetTicker/" { - t.Error("Test Failed - SetDefaults: String Incorrect -", SetDefaults.API.Endpoints.WebsocketURL) + t.Error("SetDefaults: String Incorrect -", SetDefaults.API.Endpoints.WebsocketURL) } } @@ -50,12 +50,12 @@ func TestGetTicker(t *testing.T) { if onlineTest { ticker, err = alpha.GetTicker("BTCUSD") if err != nil { - t.Fatal("Test Failed - Alphapoint GetTicker init error: ", err) + t.Fatal("Alphapoint GetTicker init error: ", err) } _, err = alpha.GetTicker("wigwham") if err == nil { - t.Error("Test Failed - Alphapoint GetTicker error") + t.Error("Alphapoint GetTicker Expected error") } } else { mockResp := []byte( @@ -64,16 +64,16 @@ func TestGetTicker(t *testing.T) { err = common.JSONDecode(mockResp, &ticker) if err != nil { - t.Fatal("Test Failed - Alphapoint GetTicker unmarshalling error: ", err) + t.Fatal("Alphapoint GetTicker unmarshalling error: ", err) } if ticker.Last != 249.76 { - t.Error("Test failed - Alphapoint GetTicker expected last = 249.76") + t.Error("Alphapoint GetTicker expected last = 249.76") } } if ticker.Last < 0 { - t.Error("Test failed - Alphapoint GetTicker last < 0") + t.Error("Alphapoint GetTicker last < 0") } } @@ -87,12 +87,12 @@ func TestGetTrades(t *testing.T) { if onlineTest { trades, err = alpha.GetTrades("BTCUSD", 0, 10) if err != nil { - t.Fatalf("Test Failed - Init error: %s", err) + t.Fatalf("Init error: %s", err) } _, err = alpha.GetTrades("wigwham", 0, 10) if err == nil { - t.Fatal("Test Failed - GetTrades error") + t.Fatal("GetTrades Expected error") } } else { mockResp := []byte( @@ -101,20 +101,20 @@ func TestGetTrades(t *testing.T) { err = common.JSONDecode(mockResp, &trades) if err != nil { - t.Fatal("Test Failed - GetTrades unmarshalling error: ", err) + t.Fatal("GetTrades unmarshalling error: ", err) } } if !trades.IsAccepted { - t.Error("Test Failed - GetTrades IsAccepted failed") + t.Error("GetTrades IsAccepted failed") } if trades.Count <= 0 { - t.Error("Test failed - GetTrades trades count is <= 0") + t.Error("GetTrades trades count is <= 0") } if trades.Instrument != "BTCUSD" { - t.Error("Test failed - GetTrades instrument is != BTCUSD") + t.Error("GetTrades instrument is != BTCUSD") } } @@ -128,11 +128,11 @@ func TestGetTradesByDate(t *testing.T) { if onlineTest { trades, err = alpha.GetTradesByDate("BTCUSD", 1414799400, 1414800000) if err != nil { - t.Errorf("Test Failed - Init error: %s", err) + t.Errorf("Init error: %s", err) } _, err = alpha.GetTradesByDate("wigwham", 1414799400, 1414800000) if err == nil { - t.Error("Test Failed - GetTradesByDate error") + t.Error("GetTradesByDate Expected error") } } else { mockResp := []byte( @@ -141,27 +141,27 @@ func TestGetTradesByDate(t *testing.T) { err = common.JSONDecode(mockResp, &trades) if err != nil { - t.Fatal("Test Failed - GetTradesByDate unmarshalling error: ", err) + t.Fatal("GetTradesByDate unmarshalling error: ", err) } } if trades.DateTimeUTC < 0 { - t.Error("Test Failed - Alphapoint trades.Count value is negative") + t.Error("Alphapoint trades.Count value is negative") } if trades.EndDate < 0 { - t.Error("Test Failed - Alphapoint trades.DateTimeUTC value is negative") + t.Error("Alphapoint trades.DateTimeUTC value is negative") } if trades.Instrument != "BTCUSD" { - t.Error("Test Failed - Alphapoint trades.Instrument value is incorrect") + t.Error("Alphapoint trades.Instrument value is incorrect") } if !trades.IsAccepted { - t.Error("Test Failed - Alphapoint trades.IsAccepted value is true") + t.Error("Alphapoint trades.IsAccepted value is true") } if len(trades.RejectReason) > 0 { - t.Error("Test Failed - Alphapoint trades.IsAccepted value has been returned") + t.Error("Alphapoint trades.IsAccepted value has been returned") } if trades.StartDate < 0 { - t.Error("Test Failed - Alphapoint trades.StartIndex value is negative") + t.Error("Alphapoint trades.StartIndex value is negative") } } @@ -175,12 +175,12 @@ func TestGetOrderbook(t *testing.T) { if onlineTest { orderBook, err = alpha.GetOrderbook("BTCUSD") if err != nil { - t.Errorf("Test Failed - Init error: %s", err) + t.Errorf("Init error: %s", err) } _, err = alpha.GetOrderbook("wigwham") if err == nil { - t.Error("Test Failed - GetOrderbook() error") + t.Error("GetOrderbook() Expected error") } } else { mockResp := []byte( @@ -189,24 +189,24 @@ func TestGetOrderbook(t *testing.T) { err = common.JSONDecode(mockResp, &orderBook) if err != nil { - t.Fatal("Test Failed - TestGetOrderbook unmarshalling error: ", err) + t.Fatal("TestGetOrderbook unmarshalling error: ", err) } if orderBook.Bids[0].Quantity != 725 { - t.Error("Test Failed - TestGetOrderbook Bids[0].Quantity != 725") + t.Error("TestGetOrderbook Bids[0].Quantity != 725") } } if !orderBook.IsAccepted { - t.Error("Test Failed - Alphapoint orderBook.IsAccepted value is negative") + t.Error("Alphapoint orderBook.IsAccepted value is negative") } if len(orderBook.Asks) == 0 { - t.Error("Test Failed - Alphapoint orderBook.Asks has len 0") + t.Error("Alphapoint orderBook.Asks has len 0") } if len(orderBook.Bids) == 0 { - t.Error("Test Failed - Alphapoint orderBook.Bids has len 0") + t.Error("Alphapoint orderBook.Bids has len 0") } } @@ -220,7 +220,7 @@ func TestGetProductPairs(t *testing.T) { if onlineTest { products, err = alpha.GetProductPairs() if err != nil { - t.Errorf("Test Failed - Init error: %s", err) + t.Errorf("Init error: %s", err) } } else { mockResp := []byte( @@ -229,24 +229,24 @@ func TestGetProductPairs(t *testing.T) { err = common.JSONDecode(mockResp, &products) if err != nil { - t.Fatal("Test Failed - TestGetProductPairs unmarshalling error: ", err) + t.Fatal("TestGetProductPairs unmarshalling error: ", err) } if products.ProductPairs[0].Name != "LTCUSD" { - t.Error("Test Failed - Alphapoint ProductPairs 0 != LTCUSD") + t.Error("Alphapoint ProductPairs 0 != LTCUSD") } if products.ProductPairs[1].Product1Label != "BTC" { - t.Error("Test Failed - Alphapoint ProductPairs 1 != BTC") + t.Error("Alphapoint ProductPairs 1 != BTC") } } if !products.IsAccepted { - t.Error("Test Failed - Alphapoint ProductPairs.IsAccepted value is negative") + t.Error("Alphapoint ProductPairs.IsAccepted value is negative") } if len(products.ProductPairs) == 0 { - t.Error("Test Failed - Alphapoint ProductPairs len is 0") + t.Error("Alphapoint ProductPairs len is 0") } } @@ -260,7 +260,7 @@ func TestGetProducts(t *testing.T) { if onlineTest { products, err = alpha.GetProducts() if err != nil { - t.Errorf("Test Failed - Init error: %s", err) + t.Errorf("Init error: %s", err) } } else { mockResp := []byte( @@ -269,24 +269,24 @@ func TestGetProducts(t *testing.T) { err = common.JSONDecode(mockResp, &products) if err != nil { - t.Fatal("Test Failed - TestGetProducts unmarshalling error: ", err) + t.Fatal("TestGetProducts unmarshalling error: ", err) } if products.Products[0].Name != "USD" { - t.Error("Test Failed - Alphapoint Products 0 != USD") + t.Error("Alphapoint Products 0 != USD") } if products.Products[1].ProductCode != 1 { - t.Error("Test Failed - Alphapoint Products 1 product code != 1") + t.Error("Alphapoint Products 1 product code != 1") } } if !products.IsAccepted { - t.Error("Test Failed - Alphapoint Products.IsAccepted value is negative") + t.Error("Alphapoint Products.IsAccepted value is negative") } if len(products.Products) == 0 { - t.Error("Test Failed - Alphapoint Products len is 0") + t.Error("Alphapoint Products len is 0") } } @@ -301,15 +301,15 @@ func TestCreateAccount(t *testing.T) { err := a.CreateAccount("test", "account", "something@something.com", "0292383745", "lolcat123") if err != nil { - t.Errorf("Test Failed - Init error: %s", err) + t.Errorf("Init error: %s", err) } err = a.CreateAccount("test", "account", "something@something.com", "0292383745", "bla") if err == nil { - t.Errorf("Test Failed - CreateAccount() error") + t.Errorf("CreateAccount() Expected error") } err = a.CreateAccount("", "", "", "", "lolcat123") if err == nil { - t.Errorf("Test Failed - CreateAccount() error") + t.Errorf("CreateAccount() Expected error") } } @@ -324,7 +324,7 @@ func TestGetUserInfo(t *testing.T) { _, err := a.GetUserInfo() if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -339,7 +339,7 @@ func TestSetUserInfo(t *testing.T) { _, err := a.SetUserInfo("bla", "bla", "1", "meh", true, true) if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -354,7 +354,7 @@ func TestGetAccountInfo(t *testing.T) { _, err := a.GetAccountInfo() if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -369,7 +369,7 @@ func TestGetAccountTrades(t *testing.T) { _, err := a.GetAccountTrades("", 1, 2) if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -384,7 +384,7 @@ func TestGetDepositAddresses(t *testing.T) { _, err := a.GetDepositAddresses() if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -399,7 +399,7 @@ func TestWithdrawCoins(t *testing.T) { err := a.WithdrawCoins("", "", "", 0.01) if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -414,7 +414,7 @@ func TestCreateOrder(t *testing.T) { _, err := a.CreateOrder("", "", exchange.LimitOrderType.ToString(), 0.01, 0) if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -429,7 +429,7 @@ func TestModifyExistingOrder(t *testing.T) { _, err := a.ModifyExistingOrder("", 1, 1) if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -444,7 +444,7 @@ func TestCancelAllExistingOrders(t *testing.T) { err := a.CancelAllExistingOrders("") if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -459,7 +459,7 @@ func TestGetOrders(t *testing.T) { _, err := a.GetOrders() if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -474,7 +474,7 @@ func TestGetOrderFee(t *testing.T) { _, err := a.GetOrderFee("", "", 1, 1) if err == nil { - t.Error("Test Failed - GetUserInfo() error") + t.Error("GetUserInfo() Expected error") } } @@ -626,7 +626,7 @@ func TestModifyOrder(t *testing.T) { _, err := a.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 38bccd13..e92a2ad8 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -12,6 +12,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -40,12 +41,24 @@ func (a *Alphapoint) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AccountInfo: true, + RESTCapabilities: protocol.Features{ + AccountInfo: true, + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + GetOrders: true, + CancelOrder: true, + CancelOrders: true, + SubmitOrder: true, + ModifyOrder: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, }, - WebsocketCapabilities: exchange.ProtocolFeatures{ - TickerFetching: true, + WebsocketCapabilities: protocol.Features{ + AccountInfo: true, }, WithdrawPermissions: exchange.WithdrawCryptoWith2FA | diff --git a/exchanges/anx/anx_live_test.go b/exchanges/anx/anx_live_test.go index 4b0c6b07..f1d0d3cf 100644 --- a/exchanges/anx/anx_live_test.go +++ b/exchanges/anx/anx_live_test.go @@ -19,11 +19,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatalf("Test Failed - ANX Setup() load config error: %s", err) + log.Fatalf("ANX Setup() load config error: %s", err) } anxConfig, err := cfg.GetExchangeConfig("ANX") if err != nil { - log.Fatalf("Test Failed - ANX Setup() init error: %s", err) + log.Fatalf("ANX Setup() init error: %s", err) } anxConfig.API.AuthenticatedSupport = true anxConfig.API.Credentials.Key = apiKey @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { a.SetDefaults() err = a.Setup(anxConfig) if err != nil { - log.Fatal("Test Failed - ANX setup error", err) + log.Fatal("ANX setup error", err) } log.Printf(sharedtestvalues.LiveTesting, a.GetName(), a.API.Endpoints.URL) os.Exit(m.Run()) diff --git a/exchanges/anx/anx_mock_test.go b/exchanges/anx/anx_mock_test.go index 9ebc8768..f4b426b8 100644 --- a/exchanges/anx/anx_mock_test.go +++ b/exchanges/anx/anx_mock_test.go @@ -22,11 +22,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - ANX load config error", err) + log.Fatal("ANX load config error", err) } anxConfig, err := cfg.GetExchangeConfig("ANX") if err != nil { - log.Fatal("Test Failed - Mock server error", err) + log.Fatal("Mock server error", err) } a.SkipAuthCheck = true anxConfig.API.AuthenticatedSupport = true @@ -35,12 +35,12 @@ func TestMain(m *testing.M) { a.SetDefaults() err = a.Setup(anxConfig) if err != nil { - log.Fatal("Test Failed - ANX setup error", err) + log.Fatal("ANX setup error", err) } serverDetails, newClient, err := mock.NewVCRServer(mockFile) if err != nil { - log.Fatalf("Test Failed - Mock server error %s", err) + log.Fatalf("Mock server error %s", err) } a.HTTPClient = newClient diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index 85519f1d..16aa053c 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -22,7 +22,7 @@ func TestGetCurrencies(t *testing.T) { t.Parallel() _, err := a.GetCurrencies() if err != nil { - t.Fatalf("Test failed. TestGetCurrencies failed. Err: %s", err) + t.Fatalf("TestGetCurrencies failed. Err: %s", err) } } @@ -30,7 +30,7 @@ func TestGetTradablePairs(t *testing.T) { t.Parallel() _, err := a.FetchTradablePairs(asset.Spot) if err != nil { - t.Fatalf("Test failed. TestGetTradablePairs failed. Err: %s", err) + t.Fatalf("TestGetTradablePairs failed. Err: %s", err) } } @@ -38,10 +38,10 @@ func TestGetTicker(t *testing.T) { t.Parallel() ticker, err := a.GetTicker("BTCUSD") if err != nil { - t.Errorf("Test Failed - ANX GetTicker() error: %s", err) + t.Errorf("ANX GetTicker() error: %s", err) } if ticker.Result != "success" { - t.Error("Test Failed - ANX GetTicker() unsuccessful") + t.Error("ANX GetTicker() unsuccessful") } } @@ -49,10 +49,10 @@ func TestGetDepth(t *testing.T) { t.Parallel() depth, err := a.GetDepth("BTCUSD") if err != nil { - t.Errorf("Test Failed - ANX GetDepth() error: %s", err) + t.Errorf("ANX GetDepth() error: %s", err) } if depth.Result != "success" { - t.Error("Test Failed - ANX GetDepth() unsuccessful") + t.Error("ANX GetDepth() unsuccessful") } } @@ -60,13 +60,13 @@ func TestGetAPIKey(t *testing.T) { t.Parallel() apiKey, apiSecret, err := a.GetAPIKey("userName", "passWord", "", "1337") if err == nil { - t.Error("Test Failed - ANX GetAPIKey() Incorrect") + t.Error("ANX GetAPIKey() Expected error") } if apiKey != "" { - t.Error("Test Failed - ANX GetAPIKey() Incorrect") + t.Error("ANX GetAPIKey() Expected error") } if apiSecret != "" { - t.Error("Test Failed - ANX GetAPIKey() Incorrect") + t.Error("ANX GetAPIKey() Expected error") } } @@ -103,7 +103,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := a.GetFee(feeBuilder); resp != float64(0.02) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) } // CryptocurrencyTradeFee High quantity @@ -111,7 +111,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := a.GetFee(feeBuilder); resp != float64(20000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(20000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(20000), resp) t.Error(err) } @@ -119,7 +119,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := a.GetFee(feeBuilder); resp != float64(0.01) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.01), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.01), resp) t.Error(err) } @@ -127,7 +127,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := a.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -135,7 +135,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := a.GetFee(feeBuilder); resp != float64(0.002) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -143,7 +143,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := a.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -152,7 +152,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.HKD if resp, err := a.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -161,7 +161,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.HKD if resp, err := a.GetFee(feeBuilder); resp != float64(250.01) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(250.01), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(250.01), resp) t.Error(err) } } @@ -208,7 +208,7 @@ func TestGetOrderHistory(t *testing.T) { case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - GetBalance() error", err) + t.Error("GetBalance() error", err) } } @@ -307,11 +307,11 @@ func TestGetAccountInfo(t *testing.T) { _, err := a.GetAccountInfo() switch { case areTestAPIKeysSet() && err != nil && !mockTests: - t.Error("test failed - GetAccountInfo() error:", err) + t.Error("GetAccountInfo() error:", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("test failed - GetAccountInfo() error") + t.Error("GetAccountInfo() error") case mockTests && err != nil: - t.Error("test failed - GetAccountInfo() error:", err) + t.Error("GetAccountInfo() error:", err) } } @@ -319,7 +319,7 @@ func TestModifyOrder(t *testing.T) { t.Parallel() _, err := a.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -373,9 +373,9 @@ func TestGetDepositAddress(t *testing.T) { t.Parallel() _, err := a.GetDepositAddress(currency.BTC, "") if areTestAPIKeysSet() && err != nil && !mockTests { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } else if !areTestAPIKeysSet() && err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 6dcaab39..9735e61a 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -13,6 +13,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -80,9 +81,24 @@ func (a *ANX) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: false, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: false, + RESTCapabilities: protocol.Features{ + TickerFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + TradeFee: true, + FiatWithdrawalFee: true, + CryptoWithdrawalFee: true, }, WithdrawPermissions: exchange.WithdrawCryptoWithEmail | exchange.AutoWithdrawCryptoWithSetup | diff --git a/exchanges/asset/asset_test.go b/exchanges/asset/asset_test.go index 475058c5..e1db02ba 100644 --- a/exchanges/asset/asset_test.go +++ b/exchanges/asset/asset_test.go @@ -9,7 +9,7 @@ import ( func TestString(t *testing.T) { a := Spot if a.String() != "spot" { - t.Fatal("Test failed - TestString returned an unexpected result") + t.Fatal("TestString returned an unexpected result") } } @@ -18,7 +18,7 @@ func TestToStringArray(t *testing.T) { result := a.Strings() for x := range a { if !common.StringDataCompare(result, a[x].String()) { - t.Fatal("Test failed - TestToStringArray returned an unexpected result") + t.Fatal("TestToStringArray returned an unexpected result") } } } @@ -26,60 +26,60 @@ func TestToStringArray(t *testing.T) { func TestContains(t *testing.T) { a := Items{Spot, Futures} if a.Contains("meow") { - t.Fatal("Test failed - TestContains returned an unexpected result") + t.Fatal("TestContains returned an unexpected result") } if !a.Contains(Spot) { - t.Fatal("Test failed - TestContains returned an unexpected result") + t.Fatal("TestContains returned an unexpected result") } if a.Contains(Binary) { - t.Fatal("Test failed - TestContains returned an unexpected result") + t.Fatal("TestContains returned an unexpected result") } if !a.Contains("SpOt") { - t.Error("Test failed - TestContains returned an unexpected result") + t.Error("TestContains returned an unexpected result") } } func TestJoinToString(t *testing.T) { a := Items{Spot, Futures} if a.JoinToString(",") != "spot,futures" { - t.Fatal("Test failed - TestJoinToString returned an unexpected result") + t.Fatal("TestJoinToString returned an unexpected result") } } func TestIsValid(t *testing.T) { if IsValid("rawr") { - t.Fatal("Test failed - TestIsValid returned an unexpected result") + t.Fatal("TestIsValid returned an unexpected result") } if !IsValid(Spot) { - t.Fatal("Test failed - TestIsValid returned an unexpected result") + t.Fatal("TestIsValid returned an unexpected result") } } func TestNew(t *testing.T) { a := New("Spota") if a != nil { - t.Fatal("Test failed - TestNew returned an unexpected result") + t.Fatal("TestNew returned an unexpected result") } a = New("SpOt") if a == nil { - t.Fatal("Test failed - TestNew returned an unexpected result") + t.Fatal("TestNew returned an unexpected result") } a = New("spot,futures") if a.JoinToString(",") != "spot,futures" { - t.Fatal("Test failed - TestNew returned an unexpected result") + t.Fatal("TestNew returned an unexpected result") } if a := New("Spot_rawr"); a != nil { - t.Fatal("Test failed - TestNew returned an unexpected result") + t.Fatal("TestNew returned an unexpected result") } if a := New("Spot,Rawr"); a != nil { - t.Fatal("Test failed - TestNew returned an unexpected result") + t.Fatal("TestNew returned an unexpected result") } } diff --git a/exchanges/binance/binance_live_test.go b/exchanges/binance/binance_live_test.go index 2f4a6f7e..f0dce783 100644 --- a/exchanges/binance/binance_live_test.go +++ b/exchanges/binance/binance_live_test.go @@ -19,11 +19,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Binance load config error", err) + log.Fatal("Binance load config error", err) } binanceConfig, err := cfg.GetExchangeConfig("Binance") if err != nil { - log.Fatal("Test Failed - Binance Setup() init error", err) + log.Fatal("Binance Setup() init error", err) } binanceConfig.API.AuthenticatedSupport = true binanceConfig.API.Credentials.Key = apiKey @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { b.SetDefaults() err = b.Setup(binanceConfig) if err != nil { - log.Fatal("Test Failed - Binance setup error", err) + log.Fatal("Binance setup error", err) } log.Printf(sharedtestvalues.LiveTesting, b.GetName(), b.API.Endpoints.URL) os.Exit(m.Run()) diff --git a/exchanges/binance/binance_mock_test.go b/exchanges/binance/binance_mock_test.go index b19ecab8..dccf08fd 100644 --- a/exchanges/binance/binance_mock_test.go +++ b/exchanges/binance/binance_mock_test.go @@ -22,11 +22,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Binance load config error", err) + log.Fatal("Binance load config error", err) } binanceConfig, err := cfg.GetExchangeConfig("Binance") if err != nil { - log.Fatal("Test Failed - Binance Setup() init error", err) + log.Fatal("Binance Setup() init error", err) } b.SkipAuthCheck = true binanceConfig.API.AuthenticatedSupport = true @@ -35,12 +35,12 @@ func TestMain(m *testing.M) { b.SetDefaults() err = b.Setup(binanceConfig) if err != nil { - log.Fatal("Test Failed - Binance setup error", err) + log.Fatal("Binance setup error", err) } serverDetails, newClient, err := mock.NewVCRServer(mockfile) if err != nil { - log.Fatalf("Test Failed - Mock server error %s", err) + log.Fatalf("Mock server error %s", err) } b.HTTPClient = newClient diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 9e815e2a..f0426906 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -36,7 +36,7 @@ func TestFetchTradablePairs(t *testing.T) { _, err := b.FetchTradablePairs(asset.Spot) if err != nil { - t.Error("Test Failed - Binance FetchTradablePairs(asset asets.AssetType) error", err) + t.Error("Binance FetchTradablePairs(asset asets.AssetType) error", err) } } @@ -49,7 +49,7 @@ func TestGetOrderBook(t *testing.T) { }) if err != nil { - t.Error("Test Failed - Binance GetOrderBook() error", err) + t.Error("Binance GetOrderBook() error", err) } } @@ -62,7 +62,7 @@ func TestGetRecentTrades(t *testing.T) { }) if err != nil { - t.Error("Test Failed - Binance GetRecentTrades() error", err) + t.Error("Binance GetRecentTrades() error", err) } } @@ -71,10 +71,10 @@ func TestGetHistoricalTrades(t *testing.T) { _, err := b.GetHistoricalTrades("BTCUSDT", 5, 0) if !mockTests && err == nil { - t.Error("Test Failed - Binance GetHistoricalTrades() expecting error") + t.Error("Binance GetHistoricalTrades() expecting error") } if mockTests && err == nil { - t.Error("Test Failed - Binance GetHistoricalTrades() error", err) + t.Error("Binance GetHistoricalTrades() error", err) } } @@ -83,7 +83,7 @@ func TestGetAggregatedTrades(t *testing.T) { _, err := b.GetAggregatedTrades("BTCUSDT", 5) if err != nil { - t.Error("Test Failed - Binance GetAggregatedTrades() error", err) + t.Error("Binance GetAggregatedTrades() error", err) } } @@ -96,7 +96,7 @@ func TestGetSpotKline(t *testing.T) { Limit: 24, }) if err != nil { - t.Error("Test Failed - Binance GetSpotKline() error", err) + t.Error("Binance GetSpotKline() error", err) } } @@ -105,7 +105,7 @@ func TestGetAveragePrice(t *testing.T) { _, err := b.GetAveragePrice("BTCUSDT") if err != nil { - t.Error("Test Failed - Binance GetAveragePrice() error", err) + t.Error("Binance GetAveragePrice() error", err) } } @@ -114,7 +114,7 @@ func TestGetPriceChangeStats(t *testing.T) { _, err := b.GetPriceChangeStats("BTCUSDT") if err != nil { - t.Error("Test Failed - Binance GetPriceChangeStats() error", err) + t.Error("Binance GetPriceChangeStats() error", err) } } @@ -123,7 +123,7 @@ func TestGetTickers(t *testing.T) { _, err := b.GetTickers() if err != nil { - t.Error("Test Failed - Binance TestGetTickers error", err) + t.Error("Binance TestGetTickers error", err) } } @@ -132,7 +132,7 @@ func TestGetLatestSpotPrice(t *testing.T) { _, err := b.GetLatestSpotPrice("BTCUSDT") if err != nil { - t.Error("Test Failed - Binance GetLatestSpotPrice() error", err) + t.Error("Binance GetLatestSpotPrice() error", err) } } @@ -141,7 +141,7 @@ func TestGetBestPrice(t *testing.T) { _, err := b.GetBestPrice("BTCUSDT") if err != nil { - t.Error("Test Failed - Binance GetBestPrice() error", err) + t.Error("Binance GetBestPrice() error", err) } } @@ -151,11 +151,11 @@ func TestQueryOrder(t *testing.T) { _, err := b.QueryOrder("BTCUSDT", "", 1337) switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - QueryOrder() error", err) + t.Error("QueryOrder() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - QueryOrder() expecting an error when no keys are set") + t.Error("QueryOrder() expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock QueryOrder() error", err) + t.Error("Mock QueryOrder() error", err) } } @@ -165,11 +165,11 @@ func TestOpenOrders(t *testing.T) { _, err := b.OpenOrders("BTCUSDT") switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - OpenOrders() error", err) + t.Error("OpenOrders() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - OpenOrders() expecting an error when no keys are set") + t.Error("OpenOrders() expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock OpenOrders() error", err) + t.Error("Mock OpenOrders() error", err) } } @@ -179,11 +179,11 @@ func TestAllOrders(t *testing.T) { _, err := b.AllOrders("BTCUSDT", "", "") switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - AllOrders() error", err) + t.Error("AllOrders() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - AllOrders() expecting an error when no keys are set") + t.Error("AllOrders() expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock AllOrders() error", err) + t.Error("Mock AllOrders() error", err) } } @@ -213,7 +213,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.1) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) } // CryptocurrencyTradeFee High quantity @@ -221,7 +221,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := b.GetFee(feeBuilder); resp != float64(100000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(100000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(100000), resp) t.Error(err) } @@ -229,7 +229,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := b.GetFee(feeBuilder); resp != float64(0.1) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.1), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.1), resp) t.Error(err) } @@ -237,7 +237,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -247,7 +247,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != float64(0.0005) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) t.Error(err) } @@ -255,7 +255,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -264,7 +264,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -273,7 +273,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -307,11 +307,11 @@ func TestGetActiveOrders(t *testing.T) { _, err = b.GetActiveOrders(&getOrdersRequest) switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - GetActiveOrders() error", err) + t.Error("GetActiveOrders() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - GetActiveOrders() expecting an error when no keys are set") + t.Error("GetActiveOrders() expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock GetActiveOrders() error", err) + t.Error("Mock GetActiveOrders() error", err) } } @@ -334,11 +334,11 @@ func TestGetOrderHistory(t *testing.T) { _, err = b.GetOrderHistory(&getOrdersRequest) switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - GetOrderHistory() error", err) + t.Error("GetOrderHistory() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - GetOrderHistory() expecting an error when no keys are set") + t.Error("GetOrderHistory() expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock GetOrderHistory() error", err) + t.Error("Mock GetOrderHistory() error", err) } } @@ -368,11 +368,11 @@ func TestSubmitOrder(t *testing.T) { _, err := b.SubmitOrder(orderSubmission) switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - SubmitOrder() error", err) + t.Error("SubmitOrder() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - SubmitOrder() expecting an error when no keys are set") + t.Error("SubmitOrder() expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock SubmitOrder() error", err) + t.Error("Mock SubmitOrder() error", err) } } @@ -393,11 +393,11 @@ func TestCancelExchangeOrder(t *testing.T) { err := b.CancelOrder(orderCancellation) switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - CancelExchangeOrder() error", err) + t.Error("CancelExchangeOrder() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - CancelExchangeOrder() expecting an error when no keys are set") + t.Error("CancelExchangeOrder() expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock CancelExchangeOrder() error", err) + t.Error("Mock CancelExchangeOrder() error", err) } } @@ -418,11 +418,11 @@ func TestCancelAllExchangeOrders(t *testing.T) { _, err := b.CancelAllOrders(orderCancellation) switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - CancelAllExchangeOrders() error", err) + t.Error("CancelAllExchangeOrders() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - CancelAllExchangeOrders() expecting an error when no keys are set") + t.Error("CancelAllExchangeOrders() expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock CancelAllExchangeOrders() error", err) + t.Error("Mock CancelAllExchangeOrders() error", err) } } @@ -432,11 +432,11 @@ func TestGetAccountInfo(t *testing.T) { _, err := b.GetAccountInfo() switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - GetAccountInfo() error", err) + t.Error("GetAccountInfo() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - GetAccountInfo() expecting an error when no keys are set") + t.Error("GetAccountInfo() expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock GetAccountInfo() error", err) + t.Error("Mock GetAccountInfo() error", err) } } @@ -445,7 +445,7 @@ func TestModifyOrder(t *testing.T) { _, err := b.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error cannot be nil") + t.Error("ModifyOrder() error cannot be nil") } } @@ -468,11 +468,11 @@ func TestWithdraw(t *testing.T) { _, err := b.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - Withdraw() error", err) + t.Error("Withdraw() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - Withdraw() expecting an error when no keys are set") + t.Error("Withdraw() expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock Withdraw() error", err) + t.Error("Mock Withdraw() error", err) } } @@ -502,10 +502,10 @@ func TestGetDepositAddress(t *testing.T) { _, err := b.GetDepositAddress(currency.BTC, "") switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") case mockTests && err != nil: - t.Error("Test Failed - Mock GetDepositAddress() error", err) + t.Error("Mock GetDepositAddress() error", err) } } diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 442dcfb8..47d6ecd5 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -71,9 +72,32 @@ func (b *Binance) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + TradeFetching: true, + UserTradeHistory: true, + TradeFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TradeFetching: true, + TickerFetching: true, + KlineFetching: true, + OrderbookFetching: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.NoFiatWithdrawals, @@ -92,10 +116,6 @@ func (b *Binance) SetDefaults() { b.API.Endpoints.URL = b.API.Endpoints.URLDefault b.Websocket = wshandler.New() b.API.Endpoints.WebsocketURL = binanceDefaultWebsocketURL - b.Websocket.Functionality = wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketTickerSupported | - wshandler.WebsocketKlineSupported | - wshandler.WebsocketOrderbookSupported b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -123,6 +143,7 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) error { ExchangeName: exch.Name, RunningURL: exch.API.Endpoints.WebsocketURL, Connector: b.WsConnect, + Features: &b.Features.Supports.WebsocketCapabilities, }) if err != nil { diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 2b48981b..2119f97f 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -30,21 +30,21 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - Bitfinex load config error", err) + t.Fatal("Bitfinex load config error", err) } bfxConfig, err := cfg.GetExchangeConfig("Bitfinex") if err != nil { - t.Error("Test Failed - Bitfinex Setup() init error") + t.Error("Bitfinex Setup() init error") } err = b.Setup(bfxConfig) if err != nil { - t.Fatal("Test Failed - Bitfinex setup error", err) + t.Fatal("Bitfinex setup error", err) } b.API.Credentials.Key = apiKey b.API.Credentials.Secret = apiSecret if !b.Enabled || b.API.AuthenticatedSupport || b.Verbose || b.Websocket.IsEnabled() || len(b.BaseCurrencies) < 1 { - t.Error("Test Failed - Bitfinex Setup values not set correctly") + t.Error("Bitfinex Setup values not set correctly") } b.API.AuthenticatedSupport = true @@ -98,7 +98,7 @@ func TestGetTicker(t *testing.T) { _, err = b.GetTicker("wigwham") if err == nil { - t.Error("Test Failed - GetTicker() error") + t.Error("GetTicker() Expected error") } } @@ -132,7 +132,7 @@ func TestGetStats(t *testing.T) { _, err = b.GetStats("wigwham") if err == nil { - t.Error("Test Failed - GetStats() error") + t.Error("GetStats() Expected error") } } @@ -144,7 +144,7 @@ func TestGetFundingBook(t *testing.T) { } _, err = b.GetFundingBook("wigwham") if err == nil { - t.Error("Testing Failed - GetFundingBook() error") + t.Error("Testing Failed - GetFundingBook() Expected error") } } @@ -271,7 +271,7 @@ func TestGetAccountInfo(t *testing.T) { _, err := b.GetAccountInfo() if err != nil { - t.Error("Test Failed - GetAccountInfo error", err) + t.Error("GetAccountInfo error", err) } } @@ -283,7 +283,7 @@ func TestGetAccountFees(t *testing.T) { _, err := b.GetAccountFees() if err == nil { - t.Error("Test Failed - GetAccountFees error") + t.Error("GetAccountFees Expected error") } } @@ -295,7 +295,7 @@ func TestGetAccountSummary(t *testing.T) { _, err := b.GetAccountSummary() if err == nil { - t.Error("Test Failed - GetAccountSummary() error:") + t.Error("GetAccountSummary() Expected error") } } @@ -307,7 +307,7 @@ func TestNewDeposit(t *testing.T) { _, err := b.NewDeposit("blabla", "testwallet", 1) if err == nil { - t.Error("Test Failed - NewDeposit() error:", err) + t.Error("NewDeposit() Expected error") } } @@ -319,7 +319,7 @@ func TestGetKeyPermissions(t *testing.T) { _, err := b.GetKeyPermissions() if err == nil { - t.Error("Test Failed - GetKeyPermissions() error:") + t.Error("GetKeyPermissions() Expected error") } } @@ -331,7 +331,7 @@ func TestGetMarginInfo(t *testing.T) { _, err := b.GetMarginInfo() if err == nil { - t.Error("Test Failed - GetMarginInfo() error") + t.Error("GetMarginInfo() Expected error") } } @@ -343,7 +343,7 @@ func TestGetAccountBalance(t *testing.T) { _, err := b.GetAccountBalance() if err == nil { - t.Error("Test Failed - GetAccountBalance() error") + t.Error("GetAccountBalance() Expected error") } } @@ -355,7 +355,7 @@ func TestWalletTransfer(t *testing.T) { _, err := b.WalletTransfer(0.01, "bla", "bla", "bla") if err == nil { - t.Error("Test Failed - WalletTransfer() error") + t.Error("WalletTransfer() Expected error") } } @@ -368,7 +368,7 @@ func TestNewOrder(t *testing.T) { _, err := b.NewOrder("BTCUSD", 1, 2, true, exchange.LimitOrderType.ToLower().ToString(), false) if err == nil { - t.Error("Test Failed - NewOrder() error") + t.Error("NewOrder() Expected error") } } @@ -391,7 +391,7 @@ func TestNewOrderMulti(t *testing.T) { _, err := b.NewOrderMulti(newOrder) if err == nil { - t.Error("Test Failed - NewOrderMulti() error") + t.Error("NewOrderMulti() Expected error") } } @@ -403,7 +403,7 @@ func TestCancelOrder(t *testing.T) { _, err := b.CancelExistingOrder(1337) if err == nil { - t.Error("Test Failed - CancelExistingOrder() error") + t.Error("CancelExistingOrder() Expected error") } } @@ -415,7 +415,7 @@ func TestCancelMultipleOrders(t *testing.T) { _, err := b.CancelMultipleOrders([]int64{1337, 1336}) if err == nil { - t.Error("Test Failed - CancelMultipleOrders() error") + t.Error("CancelMultipleOrders() Expected error") } } @@ -427,7 +427,7 @@ func TestCancelAllOrders(t *testing.T) { _, err := b.CancelAllExistingOrders() if err == nil { - t.Error("Test Failed - CancelAllExistingOrders() error") + t.Error("CancelAllExistingOrders() Expected error") } } @@ -440,7 +440,7 @@ func TestReplaceOrder(t *testing.T) { _, err := b.ReplaceOrder(1337, "BTCUSD", 1, 1, true, exchange.LimitOrderType.ToLower().ToString(), false) if err == nil { - t.Error("Test Failed - ReplaceOrder() error") + t.Error("ReplaceOrder() Expected error") } } @@ -452,7 +452,7 @@ func TestGetOrderStatus(t *testing.T) { _, err := b.GetOrderStatus(1337) if err == nil { - t.Error("Test Failed - GetOrderStatus() error") + t.Error("GetOrderStatus() Expected error") } } @@ -464,7 +464,7 @@ func TestGetOpenOrders(t *testing.T) { _, err := b.GetOpenOrders() if err == nil { - t.Error("Test Failed - GetOpenOrders() error") + t.Error("GetOpenOrders() Expectederror") } } @@ -476,7 +476,7 @@ func TestGetActivePositions(t *testing.T) { _, err := b.GetActivePositions() if err == nil { - t.Error("Test Failed - GetActivePositions() error") + t.Error("GetActivePositions() Expected error") } } @@ -488,7 +488,7 @@ func TestClaimPosition(t *testing.T) { _, err := b.ClaimPosition(1337) if err == nil { - t.Error("Test Failed - ClaimPosition() error") + t.Error("ClaimPosition() Expected error") } } @@ -500,7 +500,7 @@ func TestGetBalanceHistory(t *testing.T) { _, err := b.GetBalanceHistory("USD", time.Time{}, time.Time{}, 1, "deposit") if err == nil { - t.Error("Test Failed - GetBalanceHistory() error") + t.Error("GetBalanceHistory() Expected error") } } @@ -512,7 +512,7 @@ func TestGetMovementHistory(t *testing.T) { _, err := b.GetMovementHistory("USD", "bitcoin", time.Time{}, time.Time{}, 1) if err == nil { - t.Error("Test Failed - GetMovementHistory() error") + t.Error("GetMovementHistory() Expected error") } } @@ -524,7 +524,7 @@ func TestGetTradeHistory(t *testing.T) { _, err := b.GetTradeHistory("BTCUSD", time.Time{}, time.Time{}, 1, 0) if err == nil { - t.Error("Test Failed - GetTradeHistory() error") + t.Error("GetTradeHistory() Expected error") } } @@ -536,7 +536,7 @@ func TestNewOffer(t *testing.T) { _, err := b.NewOffer("BTC", 1, 1, 1, "loan") if err == nil { - t.Error("Test Failed - NewOffer() error") + t.Error("NewOffer() Expected error") } } @@ -548,7 +548,7 @@ func TestCancelOffer(t *testing.T) { _, err := b.CancelOffer(1337) if err == nil { - t.Error("Test Failed - CancelOffer() error") + t.Error("CancelOffer() Expected error") } } @@ -560,7 +560,7 @@ func TestGetOfferStatus(t *testing.T) { _, err := b.GetOfferStatus(1337) if err == nil { - t.Error("Test Failed - NewOffer() error") + t.Error("NewOffer() Expected error") } } @@ -572,7 +572,7 @@ func TestGetActiveCredits(t *testing.T) { _, err := b.GetActiveCredits() if err == nil { - t.Error("Test Failed - GetActiveCredits() error", err) + t.Error("GetActiveCredits() Expected error") } } @@ -584,7 +584,7 @@ func TestGetActiveOffers(t *testing.T) { _, err := b.GetActiveOffers() if err == nil { - t.Error("Test Failed - GetActiveOffers() error", err) + t.Error("GetActiveOffers() Expected error") } } @@ -596,7 +596,7 @@ func TestGetActiveMarginFunding(t *testing.T) { _, err := b.GetActiveMarginFunding() if err == nil { - t.Error("Test Failed - GetActiveMarginFunding() error", err) + t.Error("GetActiveMarginFunding() Expected error") } } @@ -608,7 +608,7 @@ func TestGetUnusedMarginFunds(t *testing.T) { _, err := b.GetUnusedMarginFunds() if err == nil { - t.Error("Test Failed - GetUnusedMarginFunds() error", err) + t.Error("GetUnusedMarginFunds() Expected error") } } @@ -620,7 +620,7 @@ func TestGetMarginTotalTakenFunds(t *testing.T) { _, err := b.GetMarginTotalTakenFunds() if err == nil { - t.Error("Test Failed - GetMarginTotalTakenFunds() error", err) + t.Error("GetMarginTotalTakenFunds() Expected error") } } @@ -632,7 +632,7 @@ func TestCloseMarginFunding(t *testing.T) { _, err := b.CloseMarginFunding(1337) if err == nil { - t.Error("Test Failed - CloseMarginFunding() error") + t.Error("CloseMarginFunding() Expected error") } } @@ -669,7 +669,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) } // CryptocurrencyTradeFee High quantity @@ -677,7 +677,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := b.GetFee(feeBuilder); resp != float64(2000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2000), resp) t.Error(err) } @@ -685,7 +685,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := b.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) t.Error(err) } @@ -693,7 +693,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -701,7 +701,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != float64(0.0004) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0004), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0004), resp) t.Error(err) } } @@ -710,7 +710,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -719,7 +719,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) t.Error(err) } @@ -728,7 +728,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) t.Error(err) } } @@ -869,7 +869,7 @@ func TestCancelAllExchangeOrdera(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := b.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -978,12 +978,12 @@ func TestGetDepositAddress(t *testing.T) { if areTestAPIKeysSet() { _, err := b.GetDepositAddress(currency.BTC, "deposit") if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } } else { _, err := b.GetDepositAddress(currency.BTC, "deposit") if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 1a8229ed..cb79bd66 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -15,6 +15,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -70,9 +71,39 @@ func (b *Bitfinex) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + FiatWithdraw: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + SubmitOrders: true, + ModifyOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + TradeFetching: true, + UserTradeHistory: true, + TradeFee: true, + FiatDepositFee: true, + FiatWithdrawalFee: true, + CryptoDepositFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + Subscribe: true, + Unsubscribe: true, + AuthenticatedEndpoints: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.AutoWithdrawFiatWithAPIPermission, @@ -91,12 +122,6 @@ func (b *Bitfinex) SetDefaults() { b.API.Endpoints.URL = b.API.Endpoints.URLDefault b.API.Endpoints.WebsocketURL = bitfinexWebsocket b.Websocket = wshandler.New() - b.Websocket.Functionality = wshandler.WebsocketTickerSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -126,6 +151,7 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error { Connector: b.WsConnect, Subscriber: b.Subscribe, UnSubscriber: b.Unsubscribe, + Features: &b.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index 0bfe7848..83690b9c 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -28,11 +28,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - Bitflyer load config error", err) + t.Fatal("Bitflyer load config error", err) } bitflyerConfig, err := cfg.GetExchangeConfig("Bitflyer") if err != nil { - t.Error("Test Failed - bitflyer Setup() init error") + t.Error("bitflyer Setup() init error") } bitflyerConfig.API.AuthenticatedSupport = true @@ -41,7 +41,7 @@ func TestSetup(t *testing.T) { err = b.Setup(bitflyerConfig) if err != nil { - t.Fatal("Test Failed - Bitflyer setup error", err) + t.Fatal("Bitflyer setup error", err) } } @@ -49,7 +49,7 @@ func TestGetLatestBlockCA(t *testing.T) { t.Parallel() _, err := b.GetLatestBlockCA() if err != nil { - t.Error("test failed - Bitflyer - GetLatestBlockCA() error:", err) + t.Error("Bitflyer - GetLatestBlockCA() error:", err) } } @@ -57,7 +57,7 @@ func TestGetBlockCA(t *testing.T) { t.Parallel() _, err := b.GetBlockCA("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") if err != nil { - t.Error("test failed - Bitflyer - GetBlockCA() error:", err) + t.Error("Bitflyer - GetBlockCA() error:", err) } } @@ -65,7 +65,7 @@ func TestGetBlockbyHeightCA(t *testing.T) { t.Parallel() _, err := b.GetBlockbyHeightCA(0) if err != nil { - t.Error("test failed - Bitflyer - GetBlockbyHeightCA() error:", err) + t.Error("Bitflyer - GetBlockbyHeightCA() error:", err) } } @@ -73,7 +73,7 @@ func TestGetTransactionByHashCA(t *testing.T) { t.Parallel() _, err := b.GetTransactionByHashCA("0562d1f063cd4127053d838b165630445af5e480ceb24e1fd9ecea52903cb772") if err != nil { - t.Error("test failed - Bitflyer - GetTransactionByHashCA() error:", err) + t.Error("Bitflyer - GetTransactionByHashCA() error:", err) } } @@ -81,7 +81,7 @@ func TestGetAddressInfoCA(t *testing.T) { t.Parallel() v, err := b.GetAddressInfoCA("1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB") if err != nil { - t.Error("test failed - Bitflyer - GetAddressInfoCA() error:", err) + t.Error("Bitflyer - GetAddressInfoCA() error:", err) } if v.UnconfirmedBalance == 0 || v.ConfirmedBalance == 0 { log.Warn(log.ExchangeSys, "Donation wallet is empty :( - please consider donating") @@ -92,7 +92,7 @@ func TestGetMarkets(t *testing.T) { t.Parallel() _, err := b.GetMarkets() if err != nil { - t.Error("test failed - Bitflyer - GetMarkets() error:", err) + t.Error("Bitflyer - GetMarkets() error:", err) } } @@ -100,7 +100,7 @@ func TestGetOrderBook(t *testing.T) { t.Parallel() _, err := b.GetOrderBook("BTC_JPY") if err != nil { - t.Error("test failed - Bitflyer - GetOrderBook() error:", err) + t.Error("Bitflyer - GetOrderBook() error:", err) } } @@ -108,7 +108,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := b.GetTicker("BTC_JPY") if err != nil { - t.Error("test failed - Bitflyer - GetTicker() error:", err) + t.Error("Bitflyer - GetTicker() error:", err) } } @@ -116,7 +116,7 @@ func TestGetExecutionHistory(t *testing.T) { t.Parallel() _, err := b.GetExecutionHistory("BTC_JPY") if err != nil { - t.Error("test failed - Bitflyer - GetExecutionHistory() error:", err) + t.Error("Bitflyer - GetExecutionHistory() error:", err) } } @@ -124,7 +124,7 @@ func TestGetExchangeStatus(t *testing.T) { t.Parallel() _, err := b.GetExchangeStatus() if err != nil { - t.Error("test failed - Bitflyer - GetExchangeStatus() error:", err) + t.Error("Bitflyer - GetExchangeStatus() error:", err) } } @@ -133,7 +133,7 @@ func TestCheckFXString(t *testing.T) { p := currency.NewPairDelimiter("FXBTC_JPY", "_") p = b.CheckFXString(p) if p.Base.String() != "FX_BTC" { - t.Error("test failed - Bitflyer - CheckFXString() error") + t.Error("Bitflyer - CheckFXString() error") } } @@ -151,7 +151,7 @@ func TestFetchTicker(t *testing.T) { _, err := b.FetchTicker(p, asset.Spot) if err != nil { - t.Error("test failed - Bitflyer - FetchTicker() error", err) + t.Error("Bitflyer - FetchTicker() error", err) } } @@ -190,7 +190,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) } // CryptocurrencyTradeFee High quantity @@ -198,7 +198,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -206,7 +206,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := b.GetFee(feeBuilder); resp != float64(0.1) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.1), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.1), resp) t.Error(err) } @@ -214,7 +214,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -222,7 +222,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -231,7 +231,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -240,7 +240,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.JPY if resp, err := b.GetFee(feeBuilder); resp != float64(324) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(324), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(324), resp) t.Error(err) } @@ -249,7 +249,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.JPY if resp, err := b.GetFee(feeBuilder); resp != float64(540) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(540), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(540), resp) t.Error(err) } } @@ -397,7 +397,7 @@ func TestWithdraw(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := b.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index 0ca12330..145fab0f 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -11,6 +11,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -68,9 +69,13 @@ func (b *Bitflyer) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: false, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: false, + RESTCapabilities: protocol.Features{ + TickerFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + TradeFee: true, + FiatDepositFee: true, + FiatWithdrawalFee: true, }, WithdrawPermissions: exchange.WithdrawCryptoViaWebsiteOnly | exchange.AutoWithdrawFiat, diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index fefef92d..7ec45d63 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -26,11 +26,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - Bithumb load config error", err) + t.Fatal("Bithumb load config error", err) } bitConfig, err := cfg.GetExchangeConfig("Bithumb") if err != nil { - t.Error("Test Failed - Bithumb Setup() init error") + t.Error("Bithumb Setup() init error") } bitConfig.API.AuthenticatedSupport = true @@ -39,7 +39,7 @@ func TestSetup(t *testing.T) { err = b.Setup(bitConfig) if err != nil { - t.Fatal("Test Failed - Bithumb setup error", err) + t.Fatal("Bithumb setup error", err) } } @@ -47,7 +47,7 @@ func TestGetTradablePairs(t *testing.T) { t.Parallel() _, err := b.GetTradablePairs() if err != nil { - t.Error("test failed - Bithumb GetTradablePairs() error", err) + t.Error("Bithumb GetTradablePairs() error", err) } } @@ -55,7 +55,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := b.GetTicker("btc") if err != nil { - t.Error("test failed - Bithumb GetTicker() error", err) + t.Error("Bithumb GetTicker() error", err) } } @@ -63,7 +63,7 @@ func TestGetAllTickers(t *testing.T) { t.Parallel() _, err := b.GetAllTickers() if err != nil { - t.Error("test failed - Bithumb GetAllTickers() error", err) + t.Error("Bithumb GetAllTickers() error", err) } } @@ -71,7 +71,7 @@ func TestGetOrderBook(t *testing.T) { t.Parallel() _, err := b.GetOrderBook("btc") if err != nil { - t.Error("test failed - Bithumb GetOrderBook() error", err) + t.Error("Bithumb GetOrderBook() error", err) } } @@ -79,7 +79,7 @@ func TestGetTransactionHistory(t *testing.T) { t.Parallel() _, err := b.GetTransactionHistory("btc") if err != nil { - t.Error("test failed - Bithumb GetTransactionHistory() error", err) + t.Error("Bithumb GetTransactionHistory() error", err) } } @@ -91,7 +91,7 @@ func TestGetAccountBalance(t *testing.T) { _, err := b.GetAccountBalance("BTC") if err == nil { - t.Error("test failed - Bithumb GetAccountBalance() error", err) + t.Error("Bithumb GetAccountBalance() Expected error") } } @@ -103,7 +103,7 @@ func TestGetWalletAddress(t *testing.T) { t.Parallel() _, err := b.GetWalletAddress("") if err == nil { - t.Error("test failed - Bithumb GetWalletAddress() error", err) + t.Error("Bithumb GetWalletAddress() Expected error") } } @@ -111,7 +111,7 @@ func TestGetLastTransaction(t *testing.T) { t.Parallel() _, err := b.GetLastTransaction() if err == nil { - t.Error("test failed - Bithumb GetLastTransaction() error", err) + t.Error("Bithumb GetLastTransaction() Expected error") } } @@ -119,7 +119,7 @@ func TestGetOrders(t *testing.T) { t.Parallel() _, err := b.GetOrders("1337", "bid", "100", "", "BTC") if err == nil { - t.Error("test failed - Bithumb GetOrders() error", err) + t.Error("Bithumb GetOrders() Expected error") } } @@ -127,7 +127,7 @@ func TestGetUserTransactions(t *testing.T) { t.Parallel() _, err := b.GetUserTransactions() if err == nil { - t.Error("test failed - Bithumb GetUserTransactions() error", err) + t.Error("Bithumb GetUserTransactions() Expected error") } } @@ -135,7 +135,7 @@ func TestPlaceTrade(t *testing.T) { t.Parallel() _, err := b.PlaceTrade("btc", "bid", 0, 0) if err == nil { - t.Error("test failed - Bithumb PlaceTrade() error", err) + t.Error("Bithumb PlaceTrade() Expected error") } } @@ -143,7 +143,7 @@ func TestGetOrderDetails(t *testing.T) { t.Parallel() _, err := b.GetOrderDetails("1337", "bid", "btc") if err == nil { - t.Error("test failed - Bithumb GetOrderDetails() error", err) + t.Error("Bithumb GetOrderDetails() Expected error") } } @@ -151,7 +151,7 @@ func TestCancelTrade(t *testing.T) { t.Parallel() _, err := b.CancelTrade("", "", "") if err == nil { - t.Error("test failed - Bithumb CancelTrade() error", err) + t.Error("Bithumb CancelTrade() Expected error") } } @@ -159,7 +159,7 @@ func TestWithdrawCrypto(t *testing.T) { t.Parallel() _, err := b.WithdrawCrypto("LQxiDhKU7idKiWQhx4ALKYkBx8xKEQVxJR", "", "ltc", 0) if err == nil { - t.Error("test failed - Bithumb WithdrawCrypto() error", err) + t.Error("Bithumb WithdrawCrypto() Expected error") } } @@ -170,7 +170,7 @@ func TestRequestKRWDepositDetails(t *testing.T) { } _, err := b.RequestKRWDepositDetails() if err == nil { - t.Error("test failed - Bithumb RequestKRWDepositDetails() error", err) + t.Error("Bithumb RequestKRWDepositDetails() Expected error") } } @@ -178,7 +178,7 @@ func TestRequestKRWWithdraw(t *testing.T) { t.Parallel() _, err := b.RequestKRWWithdraw("102_bank", "1337", 1000) if err == nil { - t.Error("test failed - Bithumb RequestKRWWithdraw() error", err) + t.Error("Bithumb RequestKRWWithdraw() Expected error") } } @@ -186,7 +186,7 @@ func TestMarketBuyOrder(t *testing.T) { t.Parallel() _, err := b.MarketBuyOrder("btc", 0) if err == nil { - t.Error("test failed - Bithumb MarketBuyOrder() error", err) + t.Error("Bithumb MarketBuyOrder() Expected error") } } @@ -194,7 +194,7 @@ func TestMarketSellOrder(t *testing.T) { t.Parallel() _, err := b.MarketSellOrder("btc", 0) if err == nil { - t.Error("test failed - Bithumb MarketSellOrder() error", err) + t.Error("Bithumb MarketSellOrder() Expected error") } } @@ -230,7 +230,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.0025) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0025), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0025), resp) } // CryptocurrencyTradeFee High quantity @@ -238,7 +238,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := b.GetFee(feeBuilder); resp != float64(2500) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2500), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2500), resp) t.Error(err) } @@ -246,7 +246,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := b.GetFee(feeBuilder); resp != float64(0.0025) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0025), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0025), resp) t.Error(err) } @@ -254,7 +254,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -262,7 +262,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) t.Error(err) } @@ -270,7 +270,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -279,7 +279,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -288,7 +288,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -432,12 +432,12 @@ func TestGetAccountInfo(t *testing.T) { if apiKey != "" || apiSecret != "" { _, err := b.GetAccountInfo() if err != nil { - t.Error("test failed - Bithumb GetAccountInfo() error", err) + t.Error("Bithumb GetAccountInfo() error", err) } } else { _, err := b.GetAccountInfo() if err == nil { - t.Error("test failed - Bithumb GetAccountInfo() error") + t.Error("Bithumb GetAccountInfo() Expected error") } } } @@ -450,7 +450,7 @@ func TestModifyOrder(t *testing.T) { OrderSide: exchange.SellOrderSide, CurrencyPair: curr}) if err == nil { - t.Error("Test Failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -534,12 +534,12 @@ func TestGetDepositAddress(t *testing.T) { if apiKey != "" && apiSecret != "" { _, err := b.GetDepositAddress(currency.BTC, "") if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } } else { _, err := b.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } } diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 7871622a..7a577543 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -15,6 +15,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -70,9 +71,27 @@ func (b *Bithumb) SetDefaults() { b.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + CryptoWithdrawal: true, + FiatDeposit: true, + FiatWithdraw: true, + GetOrder: true, + CancelOrder: true, + SubmitOrder: true, + ModifyOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + TradeFee: true, + FiatWithdrawalFee: true, + CryptoDepositFee: true, + CryptoWithdrawalFee: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.AutoWithdrawFiat, diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index ae0b726f..36a93e78 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -32,11 +32,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - Bitmex load config error", err) + t.Fatal("Bitmex load config error", err) } bitmexConfig, err := cfg.GetExchangeConfig("Bitmex") if err != nil { - t.Error("Test Failed - Bitmex Setup() init error") + t.Error("Bitmex Setup() init error") } bitmexConfig.API.AuthenticatedSupport = true @@ -46,7 +46,7 @@ func TestSetup(t *testing.T) { err = b.Setup(bitmexConfig) if err != nil { - t.Fatal("Test Failed - Bitmex setup error", err) + t.Fatal("Bitmex setup error", err) } } @@ -59,42 +59,42 @@ func TestStart(t *testing.T) { func TestGetUrgentAnnouncement(t *testing.T) { _, err := b.GetUrgentAnnouncement() if err == nil { - t.Error("test failed - GetUrgentAnnouncement() error", err) + t.Error("GetUrgentAnnouncement() Expected error") } } func TestGetAPIKeys(t *testing.T) { _, err := b.GetAPIKeys() if err == nil { - t.Error("test failed - GetAPIKeys() error", err) + t.Error("GetAPIKeys() Expected error") } } func TestRemoveAPIKey(t *testing.T) { _, err := b.RemoveAPIKey(APIKeyParams{APIKeyID: "1337"}) if err == nil { - t.Error("test failed - RemoveAPIKey() error", err) + t.Error("RemoveAPIKey() Expected error") } } func TestDisableAPIKey(t *testing.T) { _, err := b.DisableAPIKey(APIKeyParams{APIKeyID: "1337"}) if err == nil { - t.Error("test failed - DisableAPIKey() error", err) + t.Error("DisableAPIKey() Expected error") } } func TestEnableAPIKey(t *testing.T) { _, err := b.EnableAPIKey(APIKeyParams{APIKeyID: "1337"}) if err == nil { - t.Error("test failed - EnableAPIKey() error", err) + t.Error("EnableAPIKey() Expected error") } } func TestGetTrollboxMessages(t *testing.T) { _, err := b.GetTrollboxMessages(ChatGetParams{Count: 5}) if err != nil { - t.Error("test failed - GetTrollboxMessages() error", err) + t.Error("GetTrollboxMessages() error", err) } } @@ -103,126 +103,126 @@ func TestSendTrollboxMessage(t *testing.T) { ChannelID: 1337, Message: "Hello,World!"}) if err == nil { - t.Error("test failed - SendTrollboxMessage() error", err) + t.Error("SendTrollboxMessage() Expected error") } } func TestGetTrollboxChannels(t *testing.T) { _, err := b.GetTrollboxChannels() if err != nil { - t.Error("test failed - GetTrollboxChannels() error", err) + t.Error("GetTrollboxChannels() error", err) } } func TestGetTrollboxConnectedUsers(t *testing.T) { _, err := b.GetTrollboxConnectedUsers() if err == nil { - t.Error("test failed - GetTrollboxConnectedUsers() error", err) + t.Error("GetTrollboxConnectedUsers() Expected error") } } func TestGetAccountExecutions(t *testing.T) { _, err := b.GetAccountExecutions(&GenericRequestParams{}) if err == nil { - t.Error("test failed - GetAccountExecutions() error", err) + t.Error("GetAccountExecutions() Expected error") } } func TestGetAccountExecutionTradeHistory(t *testing.T) { _, err := b.GetAccountExecutionTradeHistory(&GenericRequestParams{}) if err == nil { - t.Error("test failed - GetAccountExecutionTradeHistory() error", err) + t.Error("GetAccountExecutionTradeHistory() Expected error") } } func TestGetFundingHistory(t *testing.T) { _, err := b.GetFundingHistory() if err == nil { - t.Error("test failed - GetFundingHistory() error", err) + t.Error("GetFundingHistory() Expected error") } } func TestGetInstruments(t *testing.T) { _, err := b.GetInstruments(&GenericRequestParams{}) if err != nil { - t.Error("test failed - GetInstruments() error", err) + t.Error("GetInstruments() error", err) } } func TestGetActiveInstruments(t *testing.T) { _, err := b.GetActiveInstruments(&GenericRequestParams{}) if err != nil { - t.Error("test failed - GetActiveInstruments() error", err) + t.Error("GetActiveInstruments() error", err) } } func TestGetActiveAndIndexInstruments(t *testing.T) { _, err := b.GetActiveAndIndexInstruments() if err != nil { - t.Error("test failed - GetActiveAndIndexInstruments() error", err) + t.Error("GetActiveAndIndexInstruments() error", err) } } func TestGetActiveIntervals(t *testing.T) { _, err := b.GetActiveIntervals() if err == nil { - t.Error("test failed - GetActiveIntervals() error", err) + t.Error("GetActiveIntervals() Expected error") } } func TestGetCompositeIndex(t *testing.T) { _, err := b.GetCompositeIndex(&GenericRequestParams{}) if err == nil { - t.Error("test failed - GetCompositeIndex() error", err) + t.Error("GetCompositeIndex() Expected error") } } func TestGetIndices(t *testing.T) { _, err := b.GetIndices() if err != nil { - t.Error("test failed - GetIndices() error", err) + t.Error("GetIndices() error", err) } } func TestGetInsuranceFundHistory(t *testing.T) { _, err := b.GetInsuranceFundHistory(&GenericRequestParams{}) if err != nil { - t.Error("test failed - GetInsuranceFundHistory() error", err) + t.Error("GetInsuranceFundHistory() error", err) } } func TestGetLeaderboard(t *testing.T) { _, err := b.GetLeaderboard(LeaderboardGetParams{}) if err != nil { - t.Error("test failed - GetLeaderboard() error", err) + t.Error("GetLeaderboard() error", err) } } func TestGetAliasOnLeaderboard(t *testing.T) { _, err := b.GetAliasOnLeaderboard() if err == nil { - t.Error("test failed - GetAliasOnLeaderboard() error", err) + t.Error("GetAliasOnLeaderboard() Expected error") } } func TestGetLiquidationOrders(t *testing.T) { _, err := b.GetLiquidationOrders(&GenericRequestParams{}) if err != nil { - t.Error("test failed - GetLiquidationOrders() error", err) + t.Error("GetLiquidationOrders() error", err) } } func TestGetCurrentNotifications(t *testing.T) { _, err := b.GetCurrentNotifications() if err == nil { - t.Error("test failed - GetCurrentNotifications() error", err) + t.Error("GetCurrentNotifications() Expected error") } } func TestAmendOrder(t *testing.T) { _, err := b.AmendOrder(&OrderAmendParams{}) if err == nil { - t.Error("test failed - AmendOrder() error", err) + t.Error("AmendOrder() Expected error") } } @@ -232,126 +232,126 @@ func TestCreateOrder(t *testing.T) { ClOrdID: "mm_bitmex_1a/oemUeQ4CAJZgP3fjHsA", OrderQty: 98}) if err == nil { - t.Error("test failed - CreateOrder() error", err) + t.Error("CreateOrder() Expected error") } } func TestCancelOrders(t *testing.T) { _, err := b.CancelOrders(&OrderCancelParams{}) if err == nil { - t.Error("test failed - CancelOrders() error", err) + t.Error("CancelOrders() Expected error") } } func TestCancelAllOrders(t *testing.T) { _, err := b.CancelAllExistingOrders(OrderCancelAllParams{}) if err == nil { - t.Error("test failed - CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error)", err) + t.Error("CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error)", err) } } func TestAmendBulkOrders(t *testing.T) { _, err := b.AmendBulkOrders(OrderAmendBulkParams{}) if err == nil { - t.Error("test failed - AmendBulkOrders() error", err) + t.Error("AmendBulkOrders() Expected error") } } func TestCreateBulkOrders(t *testing.T) { _, err := b.CreateBulkOrders(OrderNewBulkParams{}) if err == nil { - t.Error("test failed - CreateBulkOrders() error", err) + t.Error("CreateBulkOrders() Expected error") } } func TestCancelAllOrdersAfterTime(t *testing.T) { _, err := b.CancelAllOrdersAfterTime(OrderCancelAllAfterParams{}) if err == nil { - t.Error("test failed - CancelAllOrdersAfterTime() error", err) + t.Error("CancelAllOrdersAfterTime() Expected error") } } func TestClosePosition(t *testing.T) { _, err := b.ClosePosition(OrderClosePositionParams{}) if err == nil { - t.Error("test failed - ClosePosition() error", err) + t.Error("ClosePosition() Expected error") } } func TestGetOrderbook(t *testing.T) { _, err := b.GetOrderbook(OrderBookGetL2Params{Symbol: "XBT"}) if err != nil { - t.Error("test failed - GetOrderbook() error", err) + t.Error("GetOrderbook() error", err) } } func TestGetPositions(t *testing.T) { _, err := b.GetPositions(PositionGetParams{}) if err == nil { - t.Error("test failed - GetPositions() error", err) + t.Error("GetPositions() Expected error") } } func TestIsolatePosition(t *testing.T) { _, err := b.IsolatePosition(PositionIsolateMarginParams{Symbol: "XBT"}) if err == nil { - t.Error("test failed - IsolatePosition() error", err) + t.Error("IsolatePosition() Expected error") } } func TestLeveragePosition(t *testing.T) { _, err := b.LeveragePosition(PositionUpdateLeverageParams{}) if err == nil { - t.Error("test failed - LeveragePosition() error", err) + t.Error("LeveragePosition() Expected error") } } func TestUpdateRiskLimit(t *testing.T) { _, err := b.UpdateRiskLimit(PositionUpdateRiskLimitParams{}) if err == nil { - t.Error("test failed - UpdateRiskLimit() error", err) + t.Error("UpdateRiskLimit() Expected error") } } func TestTransferMargin(t *testing.T) { _, err := b.TransferMargin(PositionTransferIsolatedMarginParams{}) if err == nil { - t.Error("test failed - TransferMargin() error", err) + t.Error("TransferMargin() Expected error") } } func TestGetQuotesByBuckets(t *testing.T) { _, err := b.GetQuotesByBuckets(&QuoteGetBucketedParams{}) if err == nil { - t.Error("test failed - GetQuotesByBuckets() error", err) + t.Error("GetQuotesByBuckets() Expected error") } } func TestGetSettlementHistory(t *testing.T) { _, err := b.GetSettlementHistory(&GenericRequestParams{}) if err != nil { - t.Error("test failed - GetSettlementHistory() error", err) + t.Error("GetSettlementHistory() error", err) } } func TestGetStats(t *testing.T) { _, err := b.GetStats() if err != nil { - t.Error("test failed - GetStats() error", err) + t.Error("GetStats() error", err) } } func TestGetStatsHistorical(t *testing.T) { _, err := b.GetStatsHistorical() if err != nil { - t.Error("test failed - GetStatsHistorical() error", err) + t.Error("GetStatsHistorical() error", err) } } func TestGetStatSummary(t *testing.T) { _, err := b.GetStatSummary() if err != nil { - t.Error("test failed - GetStatSummary() error", err) + t.Error("GetStatSummary() error", err) } } @@ -361,14 +361,14 @@ func TestGetTrade(t *testing.T) { StartTime: time.Now().Format(time.RFC3339), Reverse: true}) if err != nil { - t.Error("test failed - GetTrade() error", err) + t.Error("GetTrade() error", err) } } func TestGetPreviousTrades(t *testing.T) { _, err := b.GetPreviousTrades(&TradeGetBucketedParams{}) if err == nil { - t.Error("test failed - GetPreviousTrades() error", err) + t.Error("GetPreviousTrades() Expected error") } } @@ -404,7 +404,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.00075) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.00075), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.00075), resp) } // CryptocurrencyTradeFee High quantity @@ -412,7 +412,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := b.GetFee(feeBuilder); resp != float64(750) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(750), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(750), resp) t.Error(err) } @@ -420,7 +420,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := b.GetFee(feeBuilder); resp != float64(0.0005) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) t.Error(err) } @@ -428,7 +428,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -436,7 +436,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -444,7 +444,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -453,7 +453,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -462,7 +462,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -607,12 +607,12 @@ func TestGetAccountInfo(t *testing.T) { if apiKey != "" || apiSecret != "" { _, err := b.GetAccountInfo() if err != nil { - t.Error("Test Failed - GetAccountInfo() error", err) + t.Error("GetAccountInfo() error", err) } } else { _, err := b.GetAccountInfo() if err == nil { - t.Error("Test Failed - GetAccountInfo() error") + t.Error("GetAccountInfo() error") } } } @@ -620,7 +620,7 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := b.ModifyOrder(&exchange.ModifyOrder{OrderID: "1337"}) if err == nil { - t.Error("Test Failed - ModifyOrder() error") + t.Error("ModifyOrder() error") } } @@ -685,12 +685,12 @@ func TestGetDepositAddress(t *testing.T) { if areTestAPIKeysSet() { _, err := b.GetDepositAddress(currency.BTC, "") if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } } else { _, err := b.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } } diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 68e0a016..d91adaa2 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -13,6 +13,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -90,9 +91,36 @@ func (b *Bitmex) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + SubmitOrders: true, + ModifyOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TradeFetching: true, + OrderbookFetching: true, + Subscribe: true, + Unsubscribe: true, + AuthenticatedEndpoints: true, + AccountInfo: true, + DeadMansSwitch: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.WithdrawCryptoWithEmail | @@ -113,13 +141,6 @@ func (b *Bitmex) SetDefaults() { b.API.Endpoints.URL = b.API.Endpoints.URLDefault b.API.Endpoints.WebsocketURL = bitmexWSURL b.Websocket = wshandler.New() - b.Websocket.Functionality = wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported | - wshandler.WebsocketAccountDataSupported | - wshandler.WebsocketDeadMansSwitchSupported b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -149,6 +170,7 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) error { Connector: b.WsConnect, Subscriber: b.Subscribe, UnSubscriber: b.Unsubscribe, + Features: &b.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/bitstamp/bitstamp_live_test.go b/exchanges/bitstamp/bitstamp_live_test.go index 0e3c8091..7fe346d1 100644 --- a/exchanges/bitstamp/bitstamp_live_test.go +++ b/exchanges/bitstamp/bitstamp_live_test.go @@ -19,11 +19,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Bitstamp load config error", err) + log.Fatal("Bitstamp load config error", err) } bitstampConfig, err := cfg.GetExchangeConfig("Bitstamp") if err != nil { - log.Fatal("Test Failed - Bitstamp Setup() init error", err) + log.Fatal("Bitstamp Setup() init error", err) } bitstampConfig.API.AuthenticatedSupport = true bitstampConfig.API.Credentials.Key = apiKey @@ -32,7 +32,7 @@ func TestMain(m *testing.M) { b.SetDefaults() err = b.Setup(bitstampConfig) if err != nil { - log.Fatal("Test Failed - Bitstamp setup error", err) + log.Fatal("Bitstamp setup error", err) } log.Printf(sharedtestvalues.LiveTesting, b.GetName(), b.API.Endpoints.URL) os.Exit(m.Run()) diff --git a/exchanges/bitstamp/bitstamp_mock_test.go b/exchanges/bitstamp/bitstamp_mock_test.go index 74d23cc5..d8b46e59 100644 --- a/exchanges/bitstamp/bitstamp_mock_test.go +++ b/exchanges/bitstamp/bitstamp_mock_test.go @@ -22,11 +22,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Bitstamp load config error", err) + log.Fatal("Bitstamp load config error", err) } bitstampConfig, err := cfg.GetExchangeConfig("Bitstamp") if err != nil { - log.Fatal("Test Failed - Bitstamp Setup() init error", err) + log.Fatal("Bitstamp Setup() init error", err) } b.SkipAuthCheck = true bitstampConfig.API.AuthenticatedSupport = true @@ -36,12 +36,12 @@ func TestMain(m *testing.M) { b.SetDefaults() err = b.Setup(bitstampConfig) if err != nil { - log.Fatal("Test Failed - Bitstamp setup error", err) + log.Fatal("Bitstamp setup error", err) } serverDetails, newClient, err := mock.NewVCRServer(mockfile) if err != nil { - log.Fatalf("Test Failed - Mock server error %s", err) + log.Fatalf("Mock server error %s", err) } b.HTTPClient = newClient diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 43bee320..1ddbfaaa 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -60,7 +60,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0) || (areTestAPIKeysSet() && err != nil) { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) } @@ -70,7 +70,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || (areTestAPIKeysSet() && err != nil) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -80,7 +80,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := b.GetFee(feeBuilder); resp != float64(0) || (areTestAPIKeysSet() && err != nil) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -90,7 +90,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || (areTestAPIKeysSet() && err != nil) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -100,7 +100,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -110,7 +110,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -121,7 +121,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(7.5) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(7.5), resp) t.Error(err) @@ -132,7 +132,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(15) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(15), resp) t.Error(err) @@ -147,18 +147,18 @@ func TestCalculateTradingFee(t *testing.T) { newBalance.BTCEURFee = 0 if resp := b.CalculateTradingFee(currency.BTC, currency.USD, 0, 0, newBalance); resp != 0 { - t.Error("Test Failed - GetFee() error") + t.Error("GetFee() error") } if resp := b.CalculateTradingFee(currency.BTC, currency.USD, 2, 2, newBalance); resp != float64(4) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(4), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(4), resp) } if resp := b.CalculateTradingFee(currency.BTC, currency.EUR, 2, 2, newBalance); resp != float64(0) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) } dummy1, dummy2 := currency.NewCode(""), currency.NewCode("") if resp := b.CalculateTradingFee(dummy1, dummy2, 0, 0, newBalance); resp != 0 { - t.Error("Test Failed - GetFee() error") + t.Error("GetFee() error") } } @@ -167,7 +167,7 @@ func TestGetTicker(t *testing.T) { _, err := b.GetTicker(currency.BTC.String()+currency.USD.String(), false) if err != nil { - t.Error("Test Failed - GetTicker() error", err) + t.Error("GetTicker() error", err) } } @@ -176,7 +176,7 @@ func TestGetOrderbook(t *testing.T) { _, err := b.GetOrderbook(currency.BTC.String() + currency.USD.String()) if err != nil { - t.Error("Test Failed - GetOrderbook() error", err) + t.Error("GetOrderbook() error", err) } } @@ -185,7 +185,7 @@ func TestGetTradingPairs(t *testing.T) { _, err := b.GetTradingPairs() if err != nil { - t.Error("Test Failed - GetTradingPairs() error", err) + t.Error("GetTradingPairs() error", err) } } @@ -197,7 +197,7 @@ func TestGetTransactions(t *testing.T) { _, err := b.GetTransactions(currency.BTC.String()+currency.USD.String(), value) if err != nil { - t.Error("Test Failed - GetTransactions() error", err) + t.Error("GetTransactions() error", err) } } @@ -206,7 +206,7 @@ func TestGetEURUSDConversionRate(t *testing.T) { _, err := b.GetEURUSDConversionRate() if err != nil { - t.Error("Test Failed - GetEURUSDConversionRate() error", err) + t.Error("GetEURUSDConversionRate() error", err) } } @@ -216,11 +216,11 @@ func TestGetBalance(t *testing.T) { _, err := b.GetBalance() switch { case areTestAPIKeysSet() && err != nil && !mockTests: - t.Error("Test Failed - GetBalance() error", err) + t.Error("GetBalance() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - GetBalance() error", err) + t.Error("GetBalance() error", err) } } @@ -230,11 +230,11 @@ func TestGetUserTransactions(t *testing.T) { _, err := b.GetUserTransactions("btcusd") switch { case areTestAPIKeysSet() && err != nil && !mockTests: - t.Error("Test Failed - GetUserTransactions() error", err) + t.Error("GetUserTransactions() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - GetUserTransactions() error", err) + t.Error("GetUserTransactions() error", err) } } @@ -244,11 +244,11 @@ func TestGetOpenOrders(t *testing.T) { _, err := b.GetOpenOrders("btcusd") switch { case areTestAPIKeysSet() && err != nil && !mockTests: - t.Error("Test Failed - GetOpenOrders() error", err) + t.Error("GetOpenOrders() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - GetOpenOrders() error", err) + t.Error("GetOpenOrders() error", err) } } @@ -258,7 +258,7 @@ func TestGetOrderStatus(t *testing.T) { _, err := b.GetOrderStatus(1337) switch { case areTestAPIKeysSet() && err != nil && !mockTests: - t.Error("Test Failed - GetOrderStatus() error", err) + t.Error("GetOrderStatus() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") case mockTests && err == nil: @@ -272,11 +272,11 @@ func TestGetWithdrawalRequests(t *testing.T) { _, err := b.GetWithdrawalRequests(0) switch { case areTestAPIKeysSet() && err != nil && !mockTests: - t.Error("Test Failed - GetWithdrawalRequests() error", err) + t.Error("GetWithdrawalRequests() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - GetWithdrawalRequests() error", err) + t.Error("GetWithdrawalRequests() error", err) } } @@ -286,11 +286,11 @@ func TestGetUnconfirmedBitcoinDeposits(t *testing.T) { _, err := b.GetUnconfirmedBitcoinDeposits() switch { case areTestAPIKeysSet() && err != nil && !mockTests: - t.Error("Test Failed - GetUnconfirmedBitcoinDeposits() error", err) + t.Error("GetUnconfirmedBitcoinDeposits() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - GetUnconfirmedBitcoinDeposits() error", err) + t.Error("GetUnconfirmedBitcoinDeposits() error", err) } } @@ -303,7 +303,7 @@ func TestTransferAccountBalance(t *testing.T) { err := b.TransferAccountBalance(0.01, "btc", "testAccount", true) if !mockTests && err != nil { - t.Error("Test Failed - TransferAccountBalance() error", err) + t.Error("TransferAccountBalance() error", err) } if mockTests && err == nil { t.Error("Expecting an error until a QA pass can be completed") @@ -456,7 +456,7 @@ func TestModifyOrder(t *testing.T) { _, err := b.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -575,10 +575,10 @@ func TestGetDepositAddress(t *testing.T) { _, err := b.GetDepositAddress(currency.BTC, "") switch { case areTestAPIKeysSet() && customerID != "" && err != nil && !mockTests: - t.Error("Test Failed - GetDepositAddress error", err) + t.Error("GetDepositAddress error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - GetDepositAddress error cannot be nil") + t.Error("GetDepositAddress error cannot be nil") case mockTests && err != nil: - t.Error("Test Failed - GetDepositAddress error", err) + t.Error("GetDepositAddress error", err) } } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 5f8cdc9f..48fe98a4 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -69,8 +70,33 @@ func (b *Bitstamp) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, + RESTCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + FiatDeposit: true, + FiatWithdraw: true, + TradeFee: true, + FiatDepositFee: true, + FiatWithdrawalFee: true, + CryptoDepositFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TradeFetching: true, + OrderbookFetching: true, + Subscribe: true, + Unsubscribe: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.AutoWithdrawFiat, @@ -89,10 +115,6 @@ func (b *Bitstamp) SetDefaults() { b.API.Endpoints.URL = b.API.Endpoints.URLDefault b.API.Endpoints.WebsocketURL = bitstampWSURL b.Websocket = wshandler.New() - b.Websocket.Functionality = wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -122,6 +144,7 @@ func (b *Bitstamp) Setup(exch *config.ExchangeConfig) error { Connector: b.WsConnect, Subscriber: b.Subscribe, UnSubscriber: b.Unsubscribe, + Features: &b.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 7ba5e57c..6b82573a 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -21,7 +21,7 @@ var b Bittrex func TestSetDefaults(t *testing.T) { b.SetDefaults() if b.GetName() != "Bittrex" { - t.Error("Test Failed - Bittrex - SetDefaults() error") + t.Error("Bittrex - SetDefaults() error") } } @@ -29,11 +29,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - Bittrex load config error", err) + t.Fatal("Bittrex load config error", err) } bConfig, err := cfg.GetExchangeConfig("Bittrex") if err != nil { - t.Error("Test Failed - Bittrex Setup() init error") + t.Error("Bittrex Setup() init error") } bConfig.API.Credentials.Key = apiKey bConfig.API.Credentials.Secret = apiSecret @@ -41,12 +41,12 @@ func TestSetup(t *testing.T) { err = b.Setup(bConfig) if err != nil { - t.Fatal("Test Failed - Bittrex setup error", err) + t.Fatal("Bittrex setup error", err) } if !b.IsEnabled() || !b.API.AuthenticatedSupport || b.Verbose || len(b.BaseCurrencies) < 1 { - t.Error("Test Failed - Bittrex Setup values not set correctly") + t.Error("Bittrex Setup values not set correctly") } } @@ -54,7 +54,7 @@ func TestGetMarkets(t *testing.T) { t.Parallel() _, err := b.GetMarkets() if err != nil { - t.Errorf("Test Failed - Bittrex - GetMarkets() error: %s", err) + t.Errorf("Bittrex - GetMarkets() error: %s", err) } } @@ -62,7 +62,7 @@ func TestGetCurrencies(t *testing.T) { t.Parallel() _, err := b.GetCurrencies() if err != nil { - t.Errorf("Test Failed - Bittrex - GetCurrencies() error: %s", err) + t.Errorf("Bittrex - GetCurrencies() error: %s", err) } } @@ -72,7 +72,7 @@ func TestGetTicker(t *testing.T) { _, err := b.GetTicker(btc) if err != nil { - t.Errorf("Test Failed - Bittrex - GetTicker() error: %s", err) + t.Errorf("Bittrex - GetTicker() error: %s", err) } } @@ -80,7 +80,7 @@ func TestGetMarketSummaries(t *testing.T) { t.Parallel() _, err := b.GetMarketSummaries() if err != nil { - t.Errorf("Test Failed - Bittrex - GetMarketSummaries() error: %s", err) + t.Errorf("Bittrex - GetMarketSummaries() error: %s", err) } } @@ -90,7 +90,7 @@ func TestGetMarketSummary(t *testing.T) { _, err := b.GetMarketSummary(pairOne) if err != nil { - t.Errorf("Test Failed - Bittrex - GetMarketSummary() error: %s", err) + t.Errorf("Bittrex - GetMarketSummary() error: %s", err) } } @@ -99,7 +99,7 @@ func TestGetOrderbook(t *testing.T) { _, err := b.GetOrderbook("btc-ltc") if err != nil { - t.Errorf("Test Failed - Bittrex - GetOrderbook() error: %s", err) + t.Errorf("Bittrex - GetOrderbook() error: %s", err) } } @@ -108,7 +108,7 @@ func TestGetMarketHistory(t *testing.T) { _, err := b.GetMarketHistory("btc-ltc") if err != nil { - t.Errorf("Test Failed - Bittrex - GetMarketHistory() error: %s", err) + t.Errorf("Bittrex - GetMarketHistory() error: %s", err) } } @@ -117,7 +117,7 @@ func TestPlaceBuyLimit(t *testing.T) { _, err := b.PlaceBuyLimit("btc-ltc", 1, 1) if err == nil { - t.Error("Test Failed - Bittrex - PlaceBuyLimit() error") + t.Error("Bittrex - PlaceBuyLimit() Expected error") } } @@ -126,7 +126,7 @@ func TestPlaceSellLimit(t *testing.T) { _, err := b.PlaceSellLimit("btc-ltc", 1, 1) if err == nil { - t.Error("Test Failed - Bittrex - PlaceSellLimit() error") + t.Error("Bittrex - PlaceSellLimit() Expected error") } } @@ -135,11 +135,11 @@ func TestGetOpenOrders(t *testing.T) { _, err := b.GetOpenOrders("") if err == nil { - t.Error("Test Failed - Bittrex - GetOrder() error") + t.Error("Bittrex - GetOrder() Expected error") } _, err = b.GetOpenOrders("btc-ltc") if err == nil { - t.Error("Test Failed - Bittrex - GetOrder() error") + t.Error("Bittrex - GetOrder() Expected error") } } @@ -148,7 +148,7 @@ func TestCancelExistingOrder(t *testing.T) { _, err := b.CancelExistingOrder("blaaaaaaa") if err == nil { - t.Error("Test Failed - Bittrex - CancelExistingOrder() error") + t.Error("Bittrex - CancelExistingOrder() Expected error") } } @@ -157,7 +157,7 @@ func TestGetAccountBalances(t *testing.T) { _, err := b.GetAccountBalances() if err == nil { - t.Error("Test Failed - Bittrex - GetAccountBalances() error") + t.Error("Bittrex - GetAccountBalances() Expected error") } } @@ -166,7 +166,7 @@ func TestGetAccountBalanceByCurrency(t *testing.T) { _, err := b.GetAccountBalanceByCurrency("btc") if err == nil { - t.Error("Test Failed - Bittrex - GetAccountBalanceByCurrency() error") + t.Error("Bittrex - GetAccountBalanceByCurrency() Expected error") } } @@ -175,11 +175,11 @@ func TestGetOrder(t *testing.T) { _, err := b.GetOrder("0cb4c4e4-bdc7-4e13-8c13-430e587d2cc1") if err == nil { - t.Error("Test Failed - Bittrex - GetOrder() error") + t.Error("Bittrex - GetOrder() Expected error") } _, err = b.GetOrder("") if err == nil { - t.Error("Test Failed - Bittrex - GetOrder() error") + t.Error("Bittrex - GetOrder() Expected error") } } @@ -188,11 +188,11 @@ func TestGetOrderHistoryForCurrency(t *testing.T) { _, err := b.GetOrderHistoryForCurrency("") if err == nil { - t.Error("Test Failed - Bittrex - GetOrderHistory() error") + t.Error("Bittrex - GetOrderHistory() Expected error") } _, err = b.GetOrderHistoryForCurrency("btc-ltc") if err == nil { - t.Error("Test Failed - Bittrex - GetOrderHistory() error") + t.Error("Bittrex - GetOrderHistory() Expected error") } } @@ -201,11 +201,11 @@ func TestGetwithdrawalHistory(t *testing.T) { _, err := b.GetWithdrawalHistory("") if err == nil { - t.Error("Test Failed - Bittrex - GetWithdrawalHistory() error") + t.Error("Bittrex - GetWithdrawalHistory() Expected error") } _, err = b.GetWithdrawalHistory("btc-ltc") if err == nil { - t.Error("Test Failed - Bittrex - GetWithdrawalHistory() error") + t.Error("Bittrex - GetWithdrawalHistory() Expected error") } } @@ -214,11 +214,11 @@ func TestGetDepositHistory(t *testing.T) { _, err := b.GetDepositHistory("") if err == nil { - t.Error("Test Failed - Bittrex - GetDepositHistory() error") + t.Error("Bittrex - GetDepositHistory() Expected error") } _, err = b.GetDepositHistory("btc-ltc") if err == nil { - t.Error("Test Failed - Bittrex - GetDepositHistory() error") + t.Error("Bittrex - GetDepositHistory() Expected error") } } @@ -255,7 +255,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.0025) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0025), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0025), resp) } // CryptocurrencyTradeFee High quantity @@ -263,7 +263,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := b.GetFee(feeBuilder); resp != float64(2500) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2500), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2500), resp) t.Error(err) } @@ -271,7 +271,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := b.GetFee(feeBuilder); resp != float64(0.0025) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0025), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0025), resp) t.Error(err) } @@ -279,7 +279,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -287,7 +287,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != float64(0.0005) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) t.Error(err) } @@ -295,7 +295,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -304,7 +304,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -313,7 +313,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.HKD if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -459,7 +459,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := b.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -524,12 +524,12 @@ func TestGetDepositAddress(t *testing.T) { if areTestAPIKeysSet() { _, err := b.GetDepositAddress(currency.BTC, "") if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } } else { _, err := b.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } } diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 6985f74f..3cb3d48b 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -13,6 +13,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -69,9 +70,23 @@ func (b *Bittrex) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: false, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + GetOrders: true, + CancelOrder: true, + SubmitOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + CryptoWithdrawalFee: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.NoFiatWithdrawals, diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 3105c5b0..ff6ac360 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -46,6 +47,7 @@ const ( // BTCMarkets is the overarching type across the BTCMarkets package type BTCMarkets struct { exchange.Base + WebsocketConn *wshandler.WebsocketConnection } // GetMarkets returns the BTCMarkets instruments diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index 29d34315..24384ef1 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -27,11 +27,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - BTC Markets load config error", err) + t.Fatal("BTC Markets load config error", err) } bConfig, err := cfg.GetExchangeConfig("BTC Markets") if err != nil { - t.Error("Test Failed - BTC Markets Setup() init error") + t.Error("BTC Markets Setup() init error") } bConfig.API.Credentials.Key = apiKey bConfig.API.Credentials.Secret = apiSecret @@ -39,7 +39,7 @@ func TestSetup(t *testing.T) { err = b.Setup(bConfig) if err != nil { - t.Fatal("Test Failed - BTC Markets setup error", err) + t.Fatal("BTC Markets setup error", err) } } @@ -47,7 +47,7 @@ func TestGetMarkets(t *testing.T) { t.Parallel() _, err := b.GetMarkets() if err != nil { - t.Error("Test failed - GetMarkets() error", err) + t.Error("GetMarkets() error", err) } } @@ -55,7 +55,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := b.GetTicker("BTC", "AUD") if err != nil { - t.Error("Test failed - GetTicker() error", err) + t.Error("GetTicker() error", err) } } @@ -63,7 +63,7 @@ func TestGetOrderbook(t *testing.T) { t.Parallel() _, err := b.GetOrderbook("BTC", "AUD") if err != nil { - t.Error("Test failed - GetOrderbook() error", err) + t.Error("GetOrderbook() error", err) } } @@ -71,14 +71,14 @@ func TestGetTrades(t *testing.T) { t.Parallel() _, err := b.GetTrades("BTC", "AUD", nil) if err != nil { - t.Error("Test failed - GetTrades() error", err) + t.Error("GetTrades() error", err) } val := url.Values{} val.Set("since", "0") _, err = b.GetTrades("BTC", "AUD", val) if err != nil { - t.Error("Test failed - GetTrades() error", err) + t.Error("GetTrades() error", err) } } @@ -87,7 +87,7 @@ func TestNewOrder(t *testing.T) { _, err := b.NewOrder("AUD", "BTC", 0, 0, "Bid", exchange.LimitOrderType.ToLower().ToString(), "testTest") if err == nil { - t.Error("Test failed - NewOrder() error", err) + t.Error("NewOrder() Expected error") } } @@ -95,7 +95,7 @@ func TestCancelExistingOrder(t *testing.T) { t.Parallel() _, err := b.CancelExistingOrder([]int64{1337}) if err == nil { - t.Error("Test failed - CancelExistingOrder() error", err) + t.Error("CancelExistingOrder() Expected error") } } @@ -103,11 +103,11 @@ func TestGetOrders(t *testing.T) { t.Parallel() _, err := b.GetOrders("AUD", "BTC", 10, 0, false) if err == nil { - t.Error("Test failed - GetOrders() error", err) + t.Error("GetOrders() Expected error") } _, err = b.GetOrders("AUD", "BTC", 10, 0, true) if err == nil { - t.Error("Test failed - GetOrders() error", err) + t.Error("GetOrders() Expected error") } } @@ -115,7 +115,7 @@ func TestGetOrderDetail(t *testing.T) { t.Parallel() _, err := b.GetOrderDetail([]int64{1337}) if err == nil { - t.Error("Test failed - GetOrderDetail() error", err) + t.Error("GetOrderDetail() Expected error") } } @@ -123,7 +123,7 @@ func TestGetAccountBalance(t *testing.T) { t.Parallel() _, err := b.GetAccountBalance() if err == nil { - t.Error("Test failed - GetAccountBalance() error", err) + t.Error("GetAccountBalance() Expected error") } } @@ -131,7 +131,7 @@ func TestWithdrawCrypto(t *testing.T) { t.Parallel() _, err := b.WithdrawCrypto(0, "BTC", "LOLOLOL") if err == nil { - t.Error("Test failed - WithdrawCrypto() error", err) + t.Error("WithdrawCrypto() Expected error") } } @@ -139,21 +139,21 @@ func TestWithdrawAUD(t *testing.T) { t.Parallel() _, err := b.WithdrawAUD("BLA", "1337", "blawest", "1336", 10000000) if err == nil { - t.Error("Test failed - WithdrawAUD() error", err) + t.Error("WithdrawAUD() Expected error") } } func TestGetAccountInfo(t *testing.T) { _, err := b.GetAccountInfo() if err == nil { - t.Error("Test failed - GetAccountInfo() error", err) + t.Error("GetAccountInfo() Expected error") } } func TestGetFundingHistory(t *testing.T) { _, err := b.GetFundingHistory() if err == nil { - t.Error("Test failed - GetAccountInfo() error", err) + t.Error("GetAccountInfo() Expected error") } } @@ -161,14 +161,14 @@ func TestCancelOrder(t *testing.T) { _, err := b.CancelExistingOrder([]int64{1337}) if err == nil { - t.Error("Test failed - CancelgOrder() error", err) + t.Error("CancelgOrder() Expected error") } } func TestGetOrderInfo(t *testing.T) { _, err := b.GetOrderInfo("1337") if err == nil { - t.Error("Test failed - GetOrderInfo() error", err) + t.Error("GetOrderInfo() Expected error") } } @@ -208,14 +208,14 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Quote = currency.USD if resp, err := b.GetFee(feeBuilder); resp != float64(0.00849999) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.00849999), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.00849999), resp) } // CryptocurrencyTradeFee Basic feeBuilder = setFeeBuilder() if resp, err := b.GetFee(feeBuilder); resp != float64(0.0022) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0022), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0022), resp) } // CryptocurrencyTradeFee High quantity @@ -223,7 +223,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := b.GetFee(feeBuilder); resp != float64(2200) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(22000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(22000), resp) t.Error(err) } @@ -231,7 +231,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := b.GetFee(feeBuilder); resp != float64(0.0022) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0022), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0022), resp) t.Error(err) } @@ -239,7 +239,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -248,7 +248,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) t.Error(err) } @@ -256,7 +256,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -265,7 +265,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.AUD if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -274,7 +274,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.AUD if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -418,7 +418,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := b.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -500,6 +500,6 @@ func TestWithdrawInternationalBank(t *testing.T) { func TestGetDepositAddress(t *testing.T) { _, err := b.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } diff --git a/exchanges/btcmarkets/btcmarkets_types.go b/exchanges/btcmarkets/btcmarkets_types.go index 4f23cfe4..33e61d78 100644 --- a/exchanges/btcmarkets/btcmarkets_types.go +++ b/exchanges/btcmarkets/btcmarkets_types.go @@ -1,6 +1,10 @@ package btcmarkets -import "github.com/thrasher-corp/gocryptotrader/currency" +import ( + "time" + + "github.com/thrasher-corp/gocryptotrader/currency" +) // Response is the genralized response type type Response struct { @@ -143,3 +147,54 @@ var WithdrawalFees = map[currency.Code]float64{ currency.OMG: 0.15, currency.POWR: 5, } + +// WsSubscribe message sent via ws to subscribe +type WsSubscribe struct { + MarketIDs []string `json:"marketIds,omitempty"` + Channels []string `json:"channels"` + MessageType string `json:"messageType"` +} + +// WsMessageType message sent via ws to determine type +type WsMessageType struct { + MessageType string `json:"messageType"` +} + +// WsTick message received for ticker data +type WsTick struct { + Currency string `json:"marketId"` + Timestamp time.Time `json:"timestamp"` + Bid float64 `json:"bestBid,string"` + Ask float64 `json:"bestAsk,string"` + Last float64 `json:"lastPrice,string"` + Volume float64 `json:"volume24h,string"` + Price24h float64 `json:"price24h,string"` + Low24h float64 `json:"low24h,string"` + High24 float64 `json:"high24h,string"` + MessageType string `json:"messageType"` +} + +// WsTrade message received for trade data +type WsTrade struct { + Currency string `json:"marketId"` + Timestamp time.Time `json:"timestamp"` + TradeID int64 `json:"tradeId"` + Price float64 `json:"price,string"` + Volume float64 `json:"volume,string"` + MessageType string `json:"messageType"` +} + +// WsOrderbook message received for orderbook data +type WsOrderbook struct { + Currency string `json:"marketId"` + Timestamp time.Time `json:"timestamp"` + Bids [][]string `json:"bids"` + Asks [][]string `json:"asks"` + MessageType string `json:"messageType"` +} + +type WsError struct { + MessageType string `json:"messageType"` + Code int64 `json:"code"` + Message string `json:"message"` +} diff --git a/exchanges/btcmarkets/btcmarkets_websocket.go b/exchanges/btcmarkets/btcmarkets_websocket.go new file mode 100644 index 00000000..840a25c3 --- /dev/null +++ b/exchanges/btcmarkets/btcmarkets_websocket.go @@ -0,0 +1,207 @@ +package btcmarkets + +import ( + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + log "github.com/thrasher-corp/gocryptotrader/logger" +) + +const ( + btcMarketsWSURL = "wss://socket.btcmarkets.net/v2" +) + +// WsConnect connects to a websocket feed +func (b *BTCMarkets) WsConnect() error { + if !b.Websocket.IsEnabled() || !b.IsEnabled() { + return errors.New(wshandler.WebsocketNotEnabled) + } + var dialer websocket.Dialer + err := b.WebsocketConn.Dial(&dialer, http.Header{}) + if err != nil { + return err + } + if b.Verbose { + log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.GetName()) + } + + b.generateDefaultSubscriptions() + go b.WsHandleData() + + return nil +} + +// WsHandleData handles websocket data from WsReadData +func (b *BTCMarkets) WsHandleData() { + b.Websocket.Wg.Add(1) + defer func() { + b.Websocket.Wg.Done() + }() + + for { + select { + case <-b.Websocket.ShutdownC: + return + default: + resp, err := b.WebsocketConn.ReadMessage() + if err != nil { + b.Websocket.ReadMessageErrors <- err + return + } + b.Websocket.TrafficAlert <- struct{}{} + var wsResponse WsMessageType + err = common.JSONDecode(resp.Raw, &wsResponse) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + switch wsResponse.MessageType { + case "heartbeat": + if b.Verbose { + log.Debugf(log.ExchangeSys, "%v - Websocket heartbeat received %s", b.GetName(), resp.Raw) + } + case "orderbook": + var ob WsOrderbook + err := common.JSONDecode(resp.Raw, &ob) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + + p := currency.NewPairFromString(ob.Currency) + var bids, asks []orderbook.Item + for x := range ob.Bids { + var price, amount float64 + price, err = strconv.ParseFloat(ob.Bids[x][0], 64) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + amount, err = strconv.ParseFloat(ob.Bids[x][1], 64) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + bids = append(bids, orderbook.Item{ + Amount: amount, + Price: price, + }) + } + for x := range ob.Asks { + var price, amount float64 + price, err = strconv.ParseFloat(ob.Asks[x][0], 64) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + amount, err = strconv.ParseFloat(ob.Asks[x][1], 64) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + asks = append(asks, orderbook.Item{ + Amount: amount, + Price: price, + }) + } + err = b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{ + Pair: p, + Bids: bids, + Asks: asks, + LastUpdated: ob.Timestamp, + AssetType: asset.Spot, + ExchangeName: b.Name, + }) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ + Pair: p, + Asset: asset.Spot, + Exchange: b.GetName(), + } + case "trade": + var trade WsTrade + err := common.JSONDecode(resp.Raw, &trade) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + p := currency.NewPairFromString(trade.Currency) + b.Websocket.DataHandler <- wshandler.TradeData{ + Timestamp: trade.Timestamp, + CurrencyPair: p, + AssetType: asset.Spot, + Exchange: b.GetName(), + Price: trade.Price, + Amount: trade.Volume, + } + case "tick": + var tick WsTick + err := common.JSONDecode(resp.Raw, &tick) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + + p := currency.NewPairFromString(tick.Currency) + b.Websocket.DataHandler <- wshandler.TickerData{ + Exchange: b.GetName(), + Volume: tick.Volume, + High: tick.High24, + Low: tick.Low24h, + Bid: tick.Bid, + Ask: tick.Ask, + Last: tick.Last, + Timestamp: tick.Timestamp, + AssetType: asset.Spot, + Pair: p, + } + case "error": + var wsErr WsError + err := common.JSONDecode(resp.Raw, &wsErr) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + b.Websocket.DataHandler <- fmt.Errorf("%v websocket error. Code: %v Message: %v", b.Name, wsErr.Code, wsErr.Message) + default: + b.Websocket.DataHandler <- fmt.Errorf("%v Unhandled websocket message %s", b.Name, resp.Raw) + } + } + } +} + +func (b *BTCMarkets) generateDefaultSubscriptions() { + var channels = []string{"tick", "trade", "orderbook"} + enabledCurrencies := b.GetEnabledPairs(asset.Spot) + var subscriptions []wshandler.WebsocketChannelSubscription + for i := range channels { + for j := range enabledCurrencies { + subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + Channel: channels[i], + Currency: enabledCurrencies[j], + }) + } + } + b.Websocket.SubscribeToChannels(subscriptions) +} + +// Subscribe sends a websocket message to receive data from the channel +func (b *BTCMarkets) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { + req := WsSubscribe{ + MarketIDs: []string{channelToSubscribe.Currency.String()}, + Channels: []string{channelToSubscribe.Channel}, + MessageType: "subscribe", + } + return b.WebsocketConn.SendMessage(req) +} diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index b8d0b116..98164cb7 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -71,10 +72,31 @@ func (b *BTCMarkets) SetDefaults() { b.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, - Websocket: false, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: false, + Websocket: true, + RESTCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrder: true, + SubmitOrder: true, + UserTradeHistory: true, + CryptoWithdrawal: true, + FiatWithdraw: true, + TradeFee: true, + FiatWithdrawalFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AccountInfo: true, + Subscribe: true, + AuthenticatedEndpoints: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.AutoWithdrawFiat, @@ -88,6 +110,12 @@ func (b *BTCMarkets) SetDefaults() { request.NewRateLimit(time.Second*10, btcmarketsAuthLimit), request.NewRateLimit(time.Second*10, btcmarketsUnauthLimit), common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + + b.API.Endpoints.WebsocketURL = btcMarketsWSURL + b.Websocket = wshandler.New() + b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit + b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout + b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit } // Setup takes in an exchange configuration and sets all parameters @@ -97,7 +125,38 @@ func (b *BTCMarkets) Setup(exch *config.ExchangeConfig) error { return nil } - return b.SetupDefaults(exch) + err := b.SetupDefaults(exch) + if err != nil { + return err + } + + err = b.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: btcMarketsWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + Features: &b.Features.Supports.WebsocketCapabilities, + }) + if err != nil { + return err + } + + b.WebsocketConn = &wshandler.WebsocketConnection{ + ExchangeName: b.Name, + URL: b.Websocket.GetWebsocketURL(), + ProxyURL: b.Websocket.GetProxyAddress(), + Verbose: b.Verbose, + ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, + ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + } + + return nil } // Start starts the BTC Markets go routine @@ -440,7 +499,7 @@ func (b *BTCMarkets) WithdrawFiatFundsToInternationalBank(withdrawRequest *excha // GetWebsocket returns a pointer to the exchange websocket func (b *BTCMarkets) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrNotYetImplemented + return b.Websocket, nil } // GetFeeByType returns an estimate of fee based on type of transaction diff --git a/exchanges/btse/btse_test.go b/exchanges/btse/btse_test.go index c394ccd5..bc90899d 100644 --- a/exchanges/btse/btse_test.go +++ b/exchanges/btse/btse_test.go @@ -27,11 +27,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - BTSE load config error", err) + log.Fatal("BTSE load config error", err) } btseConfig, err := cfg.GetExchangeConfig("BTSE") if err != nil { - t.Error("Test Failed - BTSE Setup() init error") + t.Error("BTSE Setup() init error") } btseConfig.API.AuthenticatedSupport = true @@ -40,7 +40,7 @@ func TestSetup(t *testing.T) { err = b.Setup(btseConfig) if err != nil { - t.Fatal("Test Failed - BTSE setup error", err) + t.Fatal("BTSE setup error", err) } } @@ -48,7 +48,7 @@ func TestGetMarkets(t *testing.T) { b.SetDefaults() _, err := b.GetMarkets() if err != nil { - t.Fatalf("Test failed. Err: %s", err) + t.Fatalf("Err: %s", err) } } @@ -56,7 +56,7 @@ func TestGetTrades(t *testing.T) { b.SetDefaults() _, err := b.GetTrades("BTC-USD") if err != nil { - t.Fatalf("Test failed. Err: %s", err) + t.Fatalf("Err: %s", err) } } @@ -64,7 +64,7 @@ func TestGetTicker(t *testing.T) { b.SetDefaults() _, err := b.GetTicker("BTC-USD") if err != nil { - t.Fatalf("Test failed. Err: %s", err) + t.Fatalf("Err: %s", err) } } @@ -72,7 +72,7 @@ func TestGetOrderbook(t *testing.T) { b.SetDefaults() _, err := b.GetOrderbook("BTC-USD", 0, 0, 0) if err != nil { - t.Fatalf("Test failed. Err: %s", err) + t.Fatalf("Err: %s", err) } } @@ -80,7 +80,7 @@ func TestGetMarketStatistics(t *testing.T) { b.SetDefaults() _, err := b.GetMarketStatistics("BTC-USD") if err != nil { - t.Fatalf("Test failed. Err: %s", err) + t.Fatalf("Err: %s", err) } } @@ -88,7 +88,7 @@ func TestGetServerTime(t *testing.T) { b.SetDefaults() _, err := b.GetServerTime() if err != nil { - t.Fatalf("Test failed. Err: %s", err) + t.Fatalf("Err: %s", err) } } @@ -142,7 +142,7 @@ func TestGetOrderHistory(t *testing.T) { _, err := b.GetOrderHistory(&getOrdersRequest) if err != common.ErrFunctionNotSupported { - t.Fatal("Test failed. Expected different result") + t.Fatal("Expected different result") } } @@ -188,49 +188,49 @@ func TestGetFee(t *testing.T) { } if resp, err := b.GetFee(feeBuilder); resp != 0.00050 || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", 0.00050, resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", 0.00050, resp) t.Error(err) } feeBuilder.IsMaker = false if resp, err := b.GetFee(feeBuilder); resp != 0.0015 || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", 0.0015, resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", 0.0015, resp) t.Error(err) } feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != 0.0005 || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", 0.0005, resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", 0.0005, resp) t.Error(err) } feeBuilder.Pair.Base = currency.USDT if resp, err := b.GetFee(feeBuilder); resp != float64(5) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(5), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(5), resp) t.Error(err) } feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := b.GetFee(feeBuilder); resp != float64(3) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(3), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(3), resp) t.Error(err) } feeBuilder.Amount = 1000000 if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee if resp, err := b.GetFee(feeBuilder); resp != float64(1000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(1000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(1000), resp) t.Error(err) } feeBuilder.Amount = 1000 if resp, err := b.GetFee(feeBuilder); resp != float64(25) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(25), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(25), resp) t.Error(err) } } @@ -239,7 +239,7 @@ func TestParseOrderTime(t *testing.T) { expected := int64(1534794360) actual := parseOrderTime("2018-08-20 19:20:46").Unix() if expected != actual { - t.Errorf("Test Failed. TestParseOrderTime expected: %d, got %d", expected, actual) + t.Errorf("TestParseOrderTime expected: %d, got %d", expected, actual) } } diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 3cec4218..49e83964 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -13,6 +13,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -69,8 +70,28 @@ func (b *BTSE) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, + RESTCapabilities: protocol.Features{ + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + TradeFee: true, + FiatDepositFee: true, + FiatWithdrawalFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + OrderbookFetching: true, + Subscribe: true, + Unsubscribe: true, }, WithdrawPermissions: exchange.NoAPIWithdrawalMethods, }, @@ -87,10 +108,6 @@ func (b *BTSE) SetDefaults() { b.API.Endpoints.URLDefault = btseAPIURL b.API.Endpoints.URL = b.API.Endpoints.URLDefault b.Websocket = wshandler.New() - b.Websocket.Functionality = wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketTickerSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -121,6 +138,7 @@ func (b *BTSE) Setup(exch *config.ExchangeConfig) error { Connector: b.WsConnect, Subscriber: b.Subscribe, UnSubscriber: b.Unsubscribe, + Features: &b.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 1b851383..017f7d33 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -32,11 +32,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - coinbasepro load config error", err) + t.Fatal("coinbasepro load config error", err) } gdxConfig, err := cfg.GetExchangeConfig("CoinbasePro") if err != nil { - t.Error("Test Failed - coinbasepro Setup() init error") + t.Error("coinbasepro Setup() init error") } gdxConfig.API.Credentials.Key = apiKey gdxConfig.API.Credentials.Secret = apiSecret @@ -45,56 +45,56 @@ func TestSetup(t *testing.T) { gdxConfig.API.AuthenticatedWebsocketSupport = true err = c.Setup(gdxConfig) if err != nil { - t.Fatal("Test Failed - CoinbasePro setup error", err) + t.Fatal("CoinbasePro setup error", err) } } func TestGetProducts(t *testing.T) { _, err := c.GetProducts() if err != nil { - t.Errorf("Test failed - Coinbase, GetProducts() Error: %s", err) + t.Errorf("Coinbase, GetProducts() Error: %s", err) } } func TestGetTicker(t *testing.T) { _, err := c.GetTicker("BTC-USD") if err != nil { - t.Error("Test failed - GetTicker() error", err) + t.Error("GetTicker() error", err) } } func TestGetTrades(t *testing.T) { _, err := c.GetTrades("BTC-USD") if err != nil { - t.Error("Test failed - GetTrades() error", err) + t.Error("GetTrades() error", err) } } func TestGetHistoricRates(t *testing.T) { _, err := c.GetHistoricRates("BTC-USD", 0, 0, 0) if err != nil { - t.Error("Test failed - GetHistoricRates() error", err) + t.Error("GetHistoricRates() error", err) } } func TestGetStats(t *testing.T) { _, err := c.GetStats("BTC-USD") if err != nil { - t.Error("Test failed - GetStats() error", err) + t.Error("GetStats() error", err) } } func TestGetCurrencies(t *testing.T) { _, err := c.GetCurrencies() if err != nil { - t.Error("Test failed - GetCurrencies() error", err) + t.Error("GetCurrencies() error", err) } } func TestGetServerTime(t *testing.T) { _, err := c.GetServerTime() if err != nil { - t.Error("Test failed - GetServerTime() error", err) + t.Error("GetServerTime() error", err) } } @@ -104,7 +104,7 @@ func TestAuthRequests(t *testing.T) { } _, err := c.GetAccounts() if err != nil { - t.Error("Test failed - GetAccounts() error", err) + t.Error("GetAccounts() error", err) } accountResponse, err := c.GetAccount("13371337-1337-1337-1337-133713371337") if accountResponse.ID != "" { @@ -173,11 +173,11 @@ func TestAuthRequests(t *testing.T) { } _, err = c.GetPayMethods() if err != nil { - t.Error("Test failed - GetPayMethods() error", err) + t.Error("GetPayMethods() error", err) } _, err = c.GetCoinbaseAccounts() if err != nil { - t.Error("Test failed - GetCoinbaseAccounts() error", err) + t.Error("GetCoinbaseAccounts() error", err) } } @@ -215,7 +215,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := c.GetFee(feeBuilder); resp != float64(0.003) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) } // CryptocurrencyTradeFee High quantity @@ -223,7 +223,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := c.GetFee(feeBuilder); resp != float64(3000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(3000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(3000), resp) t.Error(err) } @@ -231,7 +231,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := c.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.01), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.01), resp) t.Error(err) } @@ -239,7 +239,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := c.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -248,7 +248,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := c.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -256,7 +256,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := c.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -265,7 +265,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.EUR if resp, err := c.GetFee(feeBuilder); resp != float64(0.15) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -274,7 +274,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := c.GetFee(feeBuilder); resp != float64(25) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -290,7 +290,7 @@ func TestCalculateTradingFee(t *testing.T) { } if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) } // lowercase @@ -302,7 +302,7 @@ func TestCalculateTradingFee(t *testing.T) { } if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) } // mixedCase @@ -314,7 +314,7 @@ func TestCalculateTradingFee(t *testing.T) { } if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) } // medium volume @@ -326,7 +326,7 @@ func TestCalculateTradingFee(t *testing.T) { } if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.002) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) } // high volume @@ -338,7 +338,7 @@ func TestCalculateTradingFee(t *testing.T) { } if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.001) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) } // no match @@ -350,7 +350,7 @@ func TestCalculateTradingFee(t *testing.T) { } if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) } // taker @@ -362,7 +362,7 @@ func TestCalculateTradingFee(t *testing.T) { } if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, true); resp != float64(0) { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) } } @@ -510,7 +510,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := c.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -592,7 +592,7 @@ func TestWithdrawInternationalBank(t *testing.T) { func TestGetDepositAddress(t *testing.T) { _, err := c.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 3079db1d..f81c78ee 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -13,6 +13,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -71,9 +72,36 @@ func (c *CoinbasePro) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: false, + RESTCapabilities: protocol.Features{ + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + FiatDeposit: true, + FiatWithdraw: true, + TradeFee: true, + FiatDepositFee: true, + FiatWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + OrderbookFetching: true, + Subscribe: true, + Unsubscribe: true, + AuthenticatedEndpoints: true, + MessageSequenceNumbers: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.AutoWithdrawFiatWithAPIPermission, @@ -92,12 +120,6 @@ func (c *CoinbasePro) SetDefaults() { c.API.Endpoints.URL = c.API.Endpoints.URLDefault c.API.Endpoints.WebsocketURL = coinbaseproWebsocketURL c.Websocket = wshandler.New() - c.Websocket.Functionality = wshandler.WebsocketTickerSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported | - wshandler.WebsocketSequenceNumberSupported c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout c.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -127,6 +149,7 @@ func (c *CoinbasePro) Setup(exch *config.ExchangeConfig) error { Connector: c.WsConnect, Subscriber: c.Subscribe, UnSubscriber: c.Unsubscribe, + Features: &c.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 35ed6da6..1fb1bcee 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -31,11 +31,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - Coinut load config error", err) + t.Fatal("Coinut load config error", err) } bConfig, err := cfg.GetExchangeConfig("COINUT") if err != nil { - t.Error("Test Failed - Coinut Setup() init error") + t.Error("Coinut Setup() init error") } bConfig.API.AuthenticatedSupport = true bConfig.API.AuthenticatedWebsocketSupport = true @@ -44,12 +44,12 @@ func TestSetup(t *testing.T) { bConfig.Verbose = true err = c.Setup(bConfig) if err != nil { - t.Fatal("Test Failed - Coinut setup error", err) + t.Fatal("Coinut setup error", err) } if !c.IsEnabled() || !c.Verbose || c.Websocket.IsEnabled() || len(c.BaseCurrencies) < 1 { - t.Error("Test Failed - Coinut Setup values not set correctly") + t.Error("Coinut Setup values not set correctly") } } @@ -88,7 +88,7 @@ func setupWSTestAuth(t *testing.T) { func TestGetInstruments(t *testing.T) { _, err := c.GetInstruments() if err != nil { - t.Error("Test failed - GetInstruments() error", err) + t.Error("GetInstruments() error", err) } } @@ -138,7 +138,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := c.GetFee(feeBuilder); resp != float64(0.001) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0010), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0010), resp) } // CryptocurrencyTradeFee High quantity @@ -146,7 +146,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := c.GetFee(feeBuilder); resp != float64(1000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(1000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(1000), resp) t.Error(err) } @@ -154,7 +154,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := c.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -162,7 +162,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := c.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -170,7 +170,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := c.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -178,7 +178,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := c.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -187,7 +187,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.EUR if resp, err := c.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -196,7 +196,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.USD if resp, err := c.GetFee(feeBuilder); resp != float64(10) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(10), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(10), resp) t.Error(err) } @@ -205,7 +205,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.SGD if resp, err := c.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -214,7 +214,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := c.GetFee(feeBuilder); resp != float64(10) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(10), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(10), resp) t.Error(err) } @@ -223,7 +223,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.CAD if resp, err := c.GetFee(feeBuilder); resp != float64(2) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2), resp) t.Error(err) } @@ -232,7 +232,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.SGD if resp, err := c.GetFee(feeBuilder); resp != float64(10) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(10), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(10), resp) t.Error(err) } @@ -241,7 +241,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.CAD if resp, err := c.GetFee(feeBuilder); resp != float64(2) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2), resp) t.Error(err) } } @@ -384,12 +384,12 @@ func TestGetAccountInfo(t *testing.T) { if apiKey != "" || clientID != "" { _, err := c.GetAccountInfo() if err != nil { - t.Error("Test Failed - GetAccountInfo() error", err) + t.Error("GetAccountInfo() error", err) } } else { _, err := c.GetAccountInfo() if err == nil { - t.Error("Test Failed - GetAccountInfo() error") + t.Error("GetAccountInfo() Expected error") } } } @@ -397,7 +397,7 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := c.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -456,7 +456,7 @@ func TestWithdrawInternationalBank(t *testing.T) { func TestGetDepositAddress(t *testing.T) { _, err := c.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() function unsupported cannot be nil") + t.Error("GetDepositAddress() function unsupported cannot be nil") } } diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 08cc097c..687833c7 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -69,9 +70,33 @@ func (c *COINUT) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: false, + RESTCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + SubmitOrders: true, + UserTradeHistory: true, + TradeFee: true, + FiatDepositFee: true, + FiatWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + OrderbookFetching: true, + TradeFetching: true, + Subscribe: true, + Unsubscribe: true, + AuthenticatedEndpoints: true, + SubmitOrder: true, + SubmitOrders: true, + CancelOrder: true, + MessageCorrelation: true, }, WithdrawPermissions: exchange.WithdrawCryptoViaWebsiteOnly | exchange.WithdrawFiatViaWebsiteOnly, @@ -90,15 +115,6 @@ func (c *COINUT) SetDefaults() { c.API.Endpoints.URL = c.API.Endpoints.URLDefault c.API.Endpoints.WebsocketURL = coinutWebsocketURL c.Websocket = wshandler.New() - c.Websocket.Functionality = wshandler.WebsocketTickerSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported | - wshandler.WebsocketSubmitOrderSupported | - wshandler.WebsocketCancelOrderSupported | - wshandler.WebsocketMessageCorrelationSupported c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout c.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -128,6 +144,7 @@ func (c *COINUT) Setup(exch *config.ExchangeConfig) error { Connector: c.WsConnect, Subscriber: c.Subscribe, UnSubscriber: c.Unsubscribe, + Features: &c.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/exchange.go b/exchanges/exchange.go index f218aa4a..a5376011 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -101,7 +102,7 @@ func (e *Base) SetFeatureDefaults() { Supports: config.FeaturesSupportedConfig{ Websocket: e.Features.Supports.Websocket, REST: e.Features.Supports.REST, - RESTCapabilities: config.ProtocolFeaturesConfig{ + RESTCapabilities: protocol.Features{ AutoPairUpdates: e.Features.Supports.RESTCapabilities.AutoPairUpdates, }, }, diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 7a10eab4..3d5beb28 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -27,7 +28,7 @@ func TestSupportsRESTTickerBatchUpdates(t *testing.T) { Features: Features{ Supports: FeaturesSupported{ REST: true, - RESTCapabilities: ProtocolFeatures{ + RESTCapabilities: protocol.Features{ TickerBatching: true, }, }, @@ -35,7 +36,7 @@ func TestSupportsRESTTickerBatchUpdates(t *testing.T) { } if !b.SupportsRESTTickerBatchUpdates() { - t.Fatal("Test failed. TestSupportsRESTTickerBatchUpdates returned false") + t.Fatal("TestSupportsRESTTickerBatchUpdates returned false") } } @@ -46,7 +47,7 @@ func TestHTTPClient(t *testing.T) { r.SetHTTPClientTimeout(time.Second * 5) if r.GetHTTPClient().Timeout != time.Second*5 { - t.Fatalf("Test failed. TestHTTPClient unexpected value") + t.Fatalf("TestHTTPClient unexpected value") } r.Requester = nil @@ -55,12 +56,12 @@ func TestHTTPClient(t *testing.T) { r.SetHTTPClient(newClient) if r.GetHTTPClient().Timeout != time.Second*10 { - t.Fatalf("Test failed. TestHTTPClient unexpected value") + t.Fatalf("TestHTTPClient unexpected value") } r.Requester = nil if r.GetHTTPClient() == nil { - t.Fatalf("Test failed. TestHTTPClient unexpected value") + t.Fatalf("TestHTTPClient unexpected value") } b := Base{Name: "RAWR"} @@ -71,7 +72,7 @@ func TestHTTPClient(t *testing.T) { b.SetHTTPClientTimeout(time.Second * 5) if b.GetHTTPClient().Timeout != time.Second*5 { - t.Fatalf("Test failed. TestHTTPClient unexpected value") + t.Fatalf("TestHTTPClient unexpected value") } newClient = new(http.Client) @@ -79,7 +80,7 @@ func TestHTTPClient(t *testing.T) { b.SetHTTPClient(newClient) if b.GetHTTPClient().Timeout != time.Second*10 { - t.Fatalf("Test failed. TestHTTPClient unexpected value") + t.Fatalf("TestHTTPClient unexpected value") } b.SetHTTPClientUserAgent("epicUserAgent") @@ -103,16 +104,16 @@ func TestSetClientProxyAddress(t *testing.T) { newBase.Websocket = wshandler.New() err := newBase.SetClientProxyAddress(":invalid") if err == nil { - t.Error("Test failed. SetClientProxyAddress parsed invalid URL") + t.Error("SetClientProxyAddress parsed invalid URL") } if newBase.Websocket.GetProxyAddress() != "" { - t.Error("Test failed. SetClientProxyAddress error", err) + t.Error("SetClientProxyAddress error", err) } err = newBase.SetClientProxyAddress("www.valid.com") if err != nil { - t.Error("Test failed. SetClientProxyAddress error", err) + t.Error("SetClientProxyAddress error", err) } // calling this again will cause the ws check to fail @@ -122,7 +123,7 @@ func TestSetClientProxyAddress(t *testing.T) { } if newBase.Websocket.GetProxyAddress() != "www.valid.com" { - t.Error("Test failed. SetClientProxyAddress error", err) + t.Error("SetClientProxyAddress error", err) } } @@ -137,7 +138,7 @@ func TestSetFeatureDefaults(t *testing.T) { Features: Features{ Supports: FeaturesSupported{ REST: true, - RESTCapabilities: ProtocolFeatures{ + RESTCapabilities: protocol.Features{ TickerBatching: true, }, Websocket: true, @@ -233,22 +234,22 @@ func TestSetHTTPRateLimiter(t *testing.T) { func TestSetAutoPairDefaults(t *testing.T) { cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile, true) + err := cfg.LoadConfig(config.TestFile, true) if err != nil { - t.Fatalf("Test failed. TestSetAutoPairDefaults failed to load config file. Error: %s", err) + t.Fatalf("TestSetAutoPairDefaults failed to load config file. Error: %s", err) } exch, err := cfg.GetExchangeConfig("Bitstamp") if err != nil { - t.Fatalf("Test failed. TestSetAutoPairDefaults load config failed. Error %s", err) + t.Fatalf("TestSetAutoPairDefaults load config failed. Error %s", err) } if !exch.Features.Supports.RESTCapabilities.AutoPairUpdates { - t.Fatalf("Test failed. TestSetAutoPairDefaults Incorrect value") + t.Fatalf("TestSetAutoPairDefaults Incorrect value") } if exch.CurrencyPairs.LastUpdated != 0 { - t.Fatalf("Test failed. TestSetAutoPairDefaults Incorrect value") + t.Fatalf("TestSetAutoPairDefaults Incorrect value") } exch.Features.Supports.RESTCapabilities.AutoPairUpdates = false @@ -256,11 +257,11 @@ func TestSetAutoPairDefaults(t *testing.T) { exch, err = cfg.GetExchangeConfig("Bitstamp") if err != nil { - t.Fatalf("Test failed. TestSetAutoPairDefaults load config failed. Error %s", err) + t.Fatalf("TestSetAutoPairDefaults load config failed. Error %s", err) } if exch.Features.Supports.RESTCapabilities.AutoPairUpdates { - t.Fatal("Test failed. TestSetAutoPairDefaults Incorrect value") + t.Fatal("TestSetAutoPairDefaults Incorrect value") } } @@ -289,7 +290,7 @@ func TestGetLastPairsUpdateTime(t *testing.T) { b.CurrencyPairs.LastUpdated = testTime if b.GetLastPairsUpdateTime() != testTime { - t.Fatal("Test failed. TestGetLastPairsUpdateTim Incorrect value") + t.Fatal("TestGetLastPairsUpdateTim Incorrect value") } } @@ -339,13 +340,13 @@ func TestGetAssetTypes(t *testing.T) { aT := testExchange.GetAssetTypes() if len(aT) != 3 { - t.Error("Test failed. TestGetAssetTypes failed") + t.Error("TestGetAssetTypes failed") } } func TestGetClientBankAccounts(t *testing.T) { cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile, true) + err := cfg.LoadConfig(config.TestFile, true) if err != nil { t.Fatal(err) } @@ -363,13 +364,13 @@ func TestGetClientBankAccounts(t *testing.T) { r, err = b.GetClientBankAccounts("MEOW", "USD") if err == nil { - t.Error("an error should of been thrown for a non-existent exchange") + t.Error("an error should have been thrown for a non-existent exchange") } } func TestGetExchangeBankAccounts(t *testing.T) { cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile, true) + err := cfg.LoadConfig(config.TestFile, true) if err != nil { t.Fatal(err) } @@ -387,7 +388,7 @@ func TestGetExchangeBankAccounts(t *testing.T) { _, err = b.GetExchangeBankAccounts("MEOW", "USD") if err == nil { - t.Error("an error should of been thrown for a non-existent exchange") + t.Error("an error should have been thrown for a non-existent exchange") } } @@ -451,17 +452,17 @@ func TestGetAuthenticatedAPISupport(t *testing.T) { } if !base.GetAuthenticatedAPISupport(RestAuthentication) { - t.Fatal("Test failed. Expected RestAuthentication to return true") + t.Fatal("Expected RestAuthentication to return true") } if base.GetAuthenticatedAPISupport(WebsocketAuthentication) { - t.Fatal("Test failed. Expected WebsocketAuthentication to return false") + t.Fatal("Expected WebsocketAuthentication to return false") } base.API.AuthenticatedWebsocketSupport = true if !base.GetAuthenticatedAPISupport(WebsocketAuthentication) { - t.Fatal("Test failed. Expected WebsocketAuthentication to return true") + t.Fatal("Expected WebsocketAuthentication to return true") } if base.GetAuthenticatedAPISupport(2) { - t.Fatal("Test failed. Expected default case of 'false' to be returned") + t.Fatal("Expected default case of 'false' to be returned") } } @@ -474,7 +475,7 @@ func TestGetName(t *testing.T) { name := b.GetName() if name != "TESTNAME" { - t.Error("Test Failed - Exchange GetName() returned incorrect name") + t.Error("Exchange GetName() returned incorrect name") } } @@ -563,21 +564,21 @@ func TestGetEnabledPairs(t *testing.T) { c := b.GetEnabledPairs(assetType) if c[0].String() != defaultTestCurrencyPair { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } format.Delimiter = "~" b.CurrencyPairs.RequestFormat = &format c = b.GetEnabledPairs(assetType) if c[0].String() != "BTC~USD" { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } format.Delimiter = "" b.CurrencyPairs.ConfigFormat = &format c = b.GetEnabledPairs(assetType) if c[0].String() != "BTCUSD" { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } b.CurrencyPairs.StorePairs(asset.Spot, @@ -586,7 +587,7 @@ func TestGetEnabledPairs(t *testing.T) { b.CurrencyPairs.ConfigFormat = &format c = b.GetEnabledPairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } b.CurrencyPairs.StorePairs(asset.Spot, @@ -595,7 +596,7 @@ func TestGetEnabledPairs(t *testing.T) { b.CurrencyPairs.ConfigFormat.Delimiter = "_" c = b.GetEnabledPairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.USD { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } b.CurrencyPairs.StorePairs(asset.Spot, @@ -605,7 +606,7 @@ func TestGetEnabledPairs(t *testing.T) { b.CurrencyPairs.ConfigFormat.Index = currency.BTC.String() c = b.GetEnabledPairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } b.CurrencyPairs.StorePairs(asset.Spot, @@ -613,7 +614,7 @@ func TestGetEnabledPairs(t *testing.T) { b.CurrencyPairs.ConfigFormat.Index = "" c = b.GetEnabledPairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.USD { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } } @@ -639,21 +640,21 @@ func TestGetAvailablePairs(t *testing.T) { c := b.GetAvailablePairs(assetType) if c[0].String() != defaultTestCurrencyPair { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } format.Delimiter = "~" b.CurrencyPairs.RequestFormat = &format c = b.GetAvailablePairs(assetType) if c[0].String() != "BTC~USD" { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } format.Delimiter = "" b.CurrencyPairs.ConfigFormat = &format c = b.GetAvailablePairs(assetType) if c[0].String() != "BTCUSD" { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } b.CurrencyPairs.StorePairs(asset.Spot, @@ -662,7 +663,7 @@ func TestGetAvailablePairs(t *testing.T) { b.CurrencyPairs.ConfigFormat = &format c = b.GetAvailablePairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } b.CurrencyPairs.StorePairs(asset.Spot, @@ -671,7 +672,7 @@ func TestGetAvailablePairs(t *testing.T) { b.CurrencyPairs.ConfigFormat.Delimiter = "_" c = b.GetAvailablePairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.USD { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } b.CurrencyPairs.StorePairs(asset.Spot, @@ -681,7 +682,7 @@ func TestGetAvailablePairs(t *testing.T) { b.CurrencyPairs.ConfigFormat.Index = currency.BTC.String() c = b.GetAvailablePairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } b.CurrencyPairs.StorePairs(asset.Spot, @@ -689,7 +690,7 @@ func TestGetAvailablePairs(t *testing.T) { b.CurrencyPairs.ConfigFormat.Index = "" c = b.GetAvailablePairs(assetType) if c[0].Base != currency.BTC && c[0].Quote != currency.USD { - t.Error("Test Failed - Exchange GetAvailablePairs() incorrect string") + t.Error("Exchange GetAvailablePairs() incorrect string") } } @@ -717,15 +718,15 @@ func TestSupportsPair(t *testing.T) { assetType := asset.Spot if !b.SupportsPair(currency.NewPair(currency.BTC, currency.USD), true, assetType) { - t.Error("Test Failed - Exchange SupportsPair() incorrect value") + t.Error("Exchange SupportsPair() incorrect value") } if !b.SupportsPair(currency.NewPair(currency.ETH, currency.USD), false, assetType) { - t.Error("Test Failed - Exchange SupportsPair() incorrect value") + t.Error("Exchange SupportsPair() incorrect value") } if b.SupportsPair(currency.NewPairFromStrings("ASD", "ASDF"), true, assetType) { - t.Error("Test Failed - Exchange SupportsPair() incorrect value") + t.Error("Exchange SupportsPair() incorrect value") } } @@ -756,11 +757,11 @@ func TestFormatExchangeCurrencies(t *testing.T) { actual, err := e.FormatExchangeCurrencies(pairs, asset.Spot) if err != nil { - t.Errorf("Test failed - Exchange TestFormatExchangeCurrencies error %s", err) + t.Errorf("Exchange TestFormatExchangeCurrencies error %s", err) } expected := "btc~usd^ltc~btc" if actual != expected { - t.Errorf("Test failed - Exchange TestFormatExchangeCurrencies %s != %s", + t.Errorf("Exchange TestFormatExchangeCurrencies %s != %s", actual, expected) } @@ -785,7 +786,7 @@ func TestFormatExchangeCurrency(t *testing.T) { actual := b.FormatExchangeCurrency(p, asset.Spot) if actual.String() != expected { - t.Errorf("Test failed - Exchange TestFormatExchangeCurrency %s != %s", + t.Errorf("Exchange TestFormatExchangeCurrency %s != %s", actual, expected) } } @@ -800,7 +801,7 @@ func TestSetEnabled(t *testing.T) { SetEnabled.SetEnabled(true) if !SetEnabled.Enabled { - t.Error("Test Failed - Exchange SetEnabled(true) did not set boolean") + t.Error("Exchange SetEnabled(true) did not set boolean") } } @@ -813,7 +814,7 @@ func TestIsEnabled(t *testing.T) { } if IsEnabled.IsEnabled() { - t.Error("Test Failed - Exchange IsEnabled() did not return correct boolean") + t.Error("Exchange IsEnabled() did not return correct boolean") } } @@ -1048,14 +1049,14 @@ func TestSetPairs(t *testing.T) { func TestUpdatePairs(t *testing.T) { cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile, true) + err := cfg.LoadConfig(config.TestFile, true) if err != nil { - t.Fatal("Test failed. TestUpdatePairs failed to load config") + t.Fatal("TestUpdatePairs failed to load config") } anxCfg, err := cfg.GetExchangeConfig(defaultTestExchange) if err != nil { - t.Fatal("Test failed. TestUpdatePairs failed to load config") + t.Fatal("TestUpdatePairs failed to load config") } UAC := Base{Name: defaultTestExchange} @@ -1063,20 +1064,20 @@ func TestUpdatePairs(t *testing.T) { exchangeProducts := currency.NewPairsFromStrings([]string{"ltc", "btc", "usd", "aud", ""}) err = UAC.UpdatePairs(exchangeProducts, asset.Spot, true, false) if err != nil { - t.Errorf("Test Failed - TestUpdatePairs error: %s", err) + t.Errorf("TestUpdatePairs error: %s", err) } // Test updating the same new products, diff should be 0 err = UAC.UpdatePairs(exchangeProducts, asset.Spot, true, false) if err != nil { - t.Errorf("Test Failed - TestUpdatePairs error: %s", err) + t.Errorf("TestUpdatePairs error: %s", err) } // Test force updating to only one product exchangeProducts = currency.NewPairsFromStrings([]string{"btc"}) err = UAC.UpdatePairs(exchangeProducts, asset.Spot, true, true) if err != nil { - t.Errorf("Test Failed - TestUpdatePairs error: %s", err) + t.Errorf("TestUpdatePairs error: %s", err) } // Test updating exchange products @@ -1084,34 +1085,34 @@ func TestUpdatePairs(t *testing.T) { UAC.Name = defaultTestExchange err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err != nil { - t.Errorf("Test Failed - Exchange UpdatePairs() error: %s", err) + t.Errorf("Exchange UpdatePairs() error: %s", err) } // Test updating the same new products, diff should be 0 err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err != nil { - t.Errorf("Test Failed - Exchange UpdatePairs() error: %s", err) + t.Errorf("Exchange UpdatePairs() error: %s", err) } // Test force updating to only one product exchangeProducts = currency.NewPairsFromStrings([]string{"btc"}) err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, true) if err != nil { - t.Errorf("Test Failed - Forced Exchange UpdatePairs() error: %s", err) + t.Errorf("Forced Exchange UpdatePairs() error: %s", err) } // Test update currency pairs with btc excluded exchangeProducts = currency.NewPairsFromStrings([]string{"ltc", "eth"}) err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err != nil { - t.Errorf("Test Failed - Forced Exchange UpdatePairs() error: %s", err) + t.Errorf("Forced Exchange UpdatePairs() error: %s", err) } // Test that empty exchange products should return an error exchangeProducts = nil err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err == nil { - t.Errorf("Test failed - empty available pairs should return an error") + t.Errorf("empty available pairs should return an error") } // Test empty pair @@ -1122,7 +1123,7 @@ func TestUpdatePairs(t *testing.T) { } err = UAC.UpdatePairs(pairs, asset.Spot, true, true) if err != nil { - t.Errorf("Test Failed - Forced Exchange UpdatePairs() error: %s", err) + t.Errorf("Forced Exchange UpdatePairs() error: %s", err) } UAC.CurrencyPairs.UseGlobalFormat = true UAC.CurrencyPairs.ConfigFormat = ¤cy.PairFormat{ @@ -1146,7 +1147,7 @@ func TestSetAPIURL(t *testing.T) { err := tester.SetAPIURL() if err == nil { - t.Error("test failed - setting zero value config") + t.Error("setting zero value config") } tester.Config.API.Endpoints.URL = testURL @@ -1157,23 +1158,23 @@ func TestSetAPIURL(t *testing.T) { err = tester.SetAPIURL() if err != nil { - t.Error("test failed", err) + t.Error(err) } if tester.GetAPIURL() != testURL { - t.Error("test failed - incorrect return URL") + t.Error("incorrect return URL") } if tester.GetSecondaryAPIURL() != testURLSecondary { - t.Error("test failed - incorrect return URL") + t.Error("incorrect return URL") } if tester.GetAPIURLDefault() != testURLDefault { - t.Error("test failed - incorrect return URL") + t.Error("incorrect return URL") } if tester.GetAPIURLSecondaryDefault() != testURLSecondaryDefault { - t.Error("test failed - incorrect return URL") + t.Error("incorrect return URL") } } @@ -1318,11 +1319,11 @@ func TestOrderSides(t *testing.T) { var os = BuyOrderSide if os.ToString() != "BUY" { - t.Errorf("test failed - unexpected string %s", os.ToString()) + t.Errorf("unexpected string %s", os.ToString()) } if os.ToLower() != "buy" { - t.Errorf("test failed - unexpected string %s", os.ToString()) + t.Errorf("unexpected string %s", os.ToString()) } } @@ -1332,11 +1333,11 @@ func TestOrderTypes(t *testing.T) { var ot OrderType = "Mo'Money" if ot.ToString() != "Mo'Money" { - t.Errorf("test failed - unexpected string %s", ot.ToString()) + t.Errorf("unexpected string %s", ot.ToString()) } if ot.ToLower() != "mo'money" { - t.Errorf("test failed - unexpected string %s", ot.ToString()) + t.Errorf("unexpected string %s", ot.ToString()) } } @@ -1491,12 +1492,12 @@ func TestSortOrdersByPrice(t *testing.T) { SortOrdersByPrice(&orders, false) if orders[0].Price != 0 { - t.Errorf("Test failed. Expected: '%v', received: '%v'", 0, orders[0].Price) + t.Errorf("Expected: '%v', received: '%v'", 0, orders[0].Price) } SortOrdersByPrice(&orders, true) if orders[0].Price != 100 { - t.Errorf("Test failed. Expected: '%v', received: '%v'", 100, orders[0].Price) + t.Errorf("Expected: '%v', received: '%v'", 100, orders[0].Price) } } @@ -1515,14 +1516,14 @@ func TestSortOrdersByDate(t *testing.T) { SortOrdersByDate(&orders, false) if orders[0].OrderDate.Unix() != time.Unix(0, 0).Unix() { - t.Errorf("Test failed. Expected: '%v', received: '%v'", + t.Errorf("Expected: '%v', received: '%v'", time.Unix(0, 0).Unix(), orders[0].OrderDate.Unix()) } SortOrdersByDate(&orders, true) if orders[0].OrderDate.Unix() != time.Unix(2, 0).Unix() { - t.Errorf("Test failed. Expected: '%v', received: '%v'", + t.Errorf("Expected: '%v', received: '%v'", time.Unix(2, 0).Unix(), orders[0].OrderDate.Unix()) } @@ -1557,14 +1558,14 @@ func TestSortOrdersByCurrency(t *testing.T) { SortOrdersByCurrency(&orders, false) if orders[0].CurrencyPair.String() != currency.BTC.String()+"-"+currency.RUB.String() { - t.Errorf("Test failed. Expected: '%v', received: '%v'", + t.Errorf("Expected: '%v', received: '%v'", currency.BTC.String()+"-"+currency.RUB.String(), orders[0].CurrencyPair.String()) } SortOrdersByCurrency(&orders, true) if orders[0].CurrencyPair.String() != currency.LTC.String()+"-"+currency.EUR.String() { - t.Errorf("Test failed. Expected: '%v', received: '%v'", + t.Errorf("Expected: '%v', received: '%v'", currency.LTC.String()+"-"+currency.EUR.String(), orders[0].CurrencyPair.String()) } @@ -1587,14 +1588,14 @@ func TestSortOrdersByOrderSide(t *testing.T) { SortOrdersBySide(&orders, false) if !strings.EqualFold(orders[0].OrderSide.ToString(), BuyOrderSide.ToString()) { - t.Errorf("Test failed. Expected: '%v', received: '%v'", + t.Errorf("Expected: '%v', received: '%v'", BuyOrderSide, orders[0].OrderSide) } SortOrdersBySide(&orders, true) if !strings.EqualFold(orders[0].OrderSide.ToString(), SellOrderSide.ToString()) { - t.Errorf("Test failed. Expected: '%v', received: '%v'", + t.Errorf("Expected: '%v', received: '%v'", SellOrderSide, orders[0].OrderSide) } @@ -1617,12 +1618,12 @@ func TestSortOrdersByOrderType(t *testing.T) { SortOrdersByType(&orders, false) if !strings.EqualFold(orders[0].OrderType.ToString(), ImmediateOrCancelOrderType.ToString()) { - t.Errorf("Test failed. Expected: '%v', received: '%v'", ImmediateOrCancelOrderType, orders[0].OrderType) + t.Errorf("Expected: '%v', received: '%v'", ImmediateOrCancelOrderType, orders[0].OrderType) } SortOrdersByType(&orders, true) if !strings.EqualFold(orders[0].OrderType.ToString(), TrailingStopOrderType.ToString()) { - t.Errorf("Test failed. Expected: '%v', received: '%v'", TrailingStopOrderType, orders[0].OrderType) + t.Errorf("Expected: '%v', received: '%v'", TrailingStopOrderType, orders[0].OrderType) } } diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index 8e9ec272..344e8e6c 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -5,6 +5,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -228,41 +229,12 @@ type FeaturesEnabled struct { AutoPairUpdates bool } -// ProtocolFeatures holds all variables for the exchanges supported features -// for a protocol (e.g REST or Websocket) -type ProtocolFeatures struct { - TickerBatching bool - TickerFetching bool - OrderbookFetching bool - AutoPairUpdates bool - AccountInfo bool - CryptoDeposit bool - CryptoWithdrawal uint32 - FiatWithdraw bool - GetOrder bool - GetOrders bool - CancelOrders bool - CancelOrder bool - SubmitOrder bool - SubmitOrders bool - ModifyOrder bool - DepositHistory bool - WithdrawalHistory bool - TradeHistory bool - UserTradeHistory bool - TradeFee bool - FiatDepositFee bool - FiatWithdrawalFee bool - CryptoDepositFee bool - CryptoWithdrawalFee bool -} - // FeaturesSupported stores the exchanges supported features type FeaturesSupported struct { REST bool - RESTCapabilities ProtocolFeatures + RESTCapabilities protocol.Features Websocket bool - WebsocketCapabilities ProtocolFeatures + WebsocketCapabilities protocol.Features WithdrawPermissions uint32 } diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index 3288c50b..d78d0822 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -28,16 +28,16 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Exmo load config error", err) + log.Fatal("Exmo load config error", err) } exmoConf, err := cfg.GetExchangeConfig("EXMO") if err != nil { - t.Error("Test Failed - Exmo Setup() init error") + t.Error("Exmo Setup() init error") } err = e.Setup(exmoConf) if err != nil { - t.Fatal("Test Failed - Exmo setup error", err) + t.Fatal("Exmo setup error", err) } e.API.AuthenticatedSupport = true @@ -49,7 +49,7 @@ func TestGetTrades(t *testing.T) { t.Parallel() _, err := e.GetTrades("BTC_USD") if err != nil { - t.Errorf("Test failed. Err: %s", err) + t.Errorf("Err: %s", err) } } @@ -57,7 +57,7 @@ func TestGetOrderbook(t *testing.T) { t.Parallel() _, err := e.GetOrderbook("BTC_USD") if err != nil { - t.Errorf("Test failed. Err: %s", err) + t.Errorf("Err: %s", err) } } @@ -65,7 +65,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := e.GetTicker() if err != nil { - t.Errorf("Test failed. Err: %s", err) + t.Errorf("Err: %s", err) } } @@ -73,7 +73,7 @@ func TestGetPairSettings(t *testing.T) { t.Parallel() _, err := e.GetPairSettings() if err != nil { - t.Errorf("Test failed. Err: %s", err) + t.Errorf("Err: %s", err) } } @@ -81,7 +81,7 @@ func TestGetCurrency(t *testing.T) { t.Parallel() _, err := e.GetCurrency() if err != nil { - t.Errorf("Test failed. Err: %s", err) + t.Errorf("Err: %s", err) } } @@ -93,7 +93,7 @@ func TestGetUserInfo(t *testing.T) { TestSetup(t) _, err := e.GetUserInfo() if err != nil { - t.Errorf("Test failed. Err: %s", err) + t.Errorf("Err: %s", err) } } @@ -105,7 +105,7 @@ func TestGetRequiredAmount(t *testing.T) { TestSetup(t) _, err := e.GetRequiredAmount("BTC_USD", 100) if err != nil { - t.Errorf("Test failed. Err: %s", err) + t.Errorf("Err: %s", err) } } @@ -145,7 +145,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := e.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) } // CryptocurrencyTradeFee High quantity @@ -153,7 +153,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := e.GetFee(feeBuilder); resp != float64(2000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2000), resp) t.Error(err) } @@ -161,7 +161,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := e.GetFee(feeBuilder); resp != float64(0.002) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) t.Error(err) } @@ -169,7 +169,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := e.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -177,7 +177,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := e.GetFee(feeBuilder); resp != float64(0.0005) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) t.Error(err) } @@ -186,7 +186,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := e.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -194,7 +194,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := e.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -203,7 +203,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.RUB if resp, err := e.GetFee(feeBuilder); resp != float64(1600) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(1600), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(1600), resp) t.Error(err) } @@ -212,7 +212,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankDepositFee feeBuilder.FiatCurrency = currency.PLN if resp, err := e.GetFee(feeBuilder); resp != float64(30) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(30), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(30), resp) t.Error(err) } @@ -221,7 +221,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.PLN if resp, err := e.GetFee(feeBuilder); resp != float64(125) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(125), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(125), resp) t.Error(err) } @@ -230,7 +230,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.TRY if resp, err := e.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -239,7 +239,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.EUR if resp, err := e.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -248,7 +248,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.RUB if resp, err := e.GetFee(feeBuilder); resp != float64(3200) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(3200), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(3200), resp) t.Error(err) } } @@ -393,7 +393,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := e.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -456,12 +456,12 @@ func TestGetDepositAddress(t *testing.T) { if areTestAPIKeysSet() { _, err := e.GetDepositAddress(currency.LTC, "") if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } } else { _, err := e.GetDepositAddress(currency.LTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } } diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index abccfa43..6aa4fcb6 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -71,9 +72,27 @@ func (e *EXMO) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: false, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrder: true, + SubmitOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + FiatDepositFee: true, + FiatWithdrawalFee: true, + CryptoDepositFee: true, + CryptoWithdrawalFee: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | exchange.NoFiatWithdrawals, diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index cdeb8d19..6ba9a5ab 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -32,11 +32,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - GateIO load config error", err) + t.Fatal("GateIO load config error", err) } gateioConfig, err := cfg.GetExchangeConfig("GateIO") if err != nil { - t.Error("Test Failed - GateIO Setup() init error") + t.Error("GateIO Setup() init error") } gateioConfig.API.AuthenticatedSupport = true gateioConfig.API.AuthenticatedWebsocketSupport = true @@ -45,7 +45,7 @@ func TestSetup(t *testing.T) { err = g.Setup(gateioConfig) if err != nil { - t.Fatal("Test Failed - GateIO setup error", err) + t.Fatal("GateIO setup error", err) } } @@ -53,7 +53,7 @@ func TestGetSymbols(t *testing.T) { t.Parallel() _, err := g.GetSymbols() if err != nil { - t.Errorf("Test failed - Gateio TestGetSymbols: %s", err) + t.Errorf("Gateio TestGetSymbols: %s", err) } } @@ -61,7 +61,7 @@ func TestGetMarketInfo(t *testing.T) { t.Parallel() _, err := g.GetMarketInfo() if err != nil { - t.Errorf("Test failed - Gateio GetMarketInfo: %s", err) + t.Errorf("Gateio GetMarketInfo: %s", err) } } @@ -79,7 +79,7 @@ func TestSpotNewOrder(t *testing.T) { Type: exchange.SellOrderSide.ToLower().ToString(), }) if err != nil { - t.Errorf("Test failed - Gateio SpotNewOrder: %s", err) + t.Errorf("Gateio SpotNewOrder: %s", err) } } @@ -92,7 +92,7 @@ func TestCancelExistingOrder(t *testing.T) { _, err := g.CancelExistingOrder(917591554, "btc_usdt") if err != nil { - t.Errorf("Test failed - Gateio CancelExistingOrder: %s", err) + t.Errorf("Gateio CancelExistingOrder: %s", err) } } @@ -105,7 +105,7 @@ func TestGetBalances(t *testing.T) { _, err := g.GetBalances() if err != nil { - t.Errorf("Test failed - Gateio GetBalances: %s", err) + t.Errorf("Gateio GetBalances: %s", err) } } @@ -113,7 +113,7 @@ func TestGetLatestSpotPrice(t *testing.T) { t.Parallel() _, err := g.GetLatestSpotPrice("btc_usdt") if err != nil { - t.Errorf("Test failed - Gateio GetLatestSpotPrice: %s", err) + t.Errorf("Gateio GetLatestSpotPrice: %s", err) } } @@ -121,7 +121,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := g.GetTicker("btc_usdt") if err != nil { - t.Errorf("Test failed - Gateio GetTicker: %s", err) + t.Errorf("Gateio GetTicker: %s", err) } } @@ -129,7 +129,7 @@ func TestGetTickers(t *testing.T) { t.Parallel() _, err := g.GetTickers() if err != nil { - t.Errorf("Test failed - Gateio GetTicker: %s", err) + t.Errorf("Gateio GetTicker: %s", err) } } @@ -137,7 +137,7 @@ func TestGetOrderbook(t *testing.T) { t.Parallel() _, err := g.GetOrderbook("btc_usdt") if err != nil { - t.Errorf("Test failed - Gateio GetTicker: %s", err) + t.Errorf("Gateio GetTicker: %s", err) } } @@ -151,7 +151,7 @@ func TestGetSpotKline(t *testing.T) { }) if err != nil { - t.Errorf("Test failed - Gateio GetSpotKline: %s", err) + t.Errorf("Gateio GetSpotKline: %s", err) } } @@ -192,7 +192,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := g.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) } // CryptocurrencyTradeFee High quantity @@ -200,7 +200,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := g.GetFee(feeBuilder); resp != float64(2000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2000), resp) t.Error(err) } @@ -208,7 +208,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := g.GetFee(feeBuilder); resp != float64(0.002) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) t.Error(err) } @@ -216,7 +216,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -224,7 +224,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := g.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) t.Error(err) } @@ -233,7 +233,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -241,7 +241,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -249,7 +249,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -258,7 +258,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -405,12 +405,12 @@ func TestGetAccountInfo(t *testing.T) { if apiSecret == "" || apiKey == "" { _, err := g.GetAccountInfo() if err == nil { - t.Error("Test Failed - GetAccountInfo() error") + t.Error("GetAccountInfo() Expected error") } } else { _, err := g.GetAccountInfo() if err != nil { - t.Error("Test Failed - GetAccountInfo() error", err) + t.Error("GetAccountInfo() error", err) } } } @@ -418,7 +418,7 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := g.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index d3d602a0..c4436beb 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -69,9 +70,34 @@ func (g *Gateio) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + OrderbookFetching: true, + TradeFetching: true, + KlineFetching: true, + Subscribe: true, + Unsubscribe: true, + AuthenticatedEndpoints: true, + MessageCorrelation: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.NoFiatWithdrawals, @@ -92,14 +118,6 @@ func (g *Gateio) SetDefaults() { g.API.Endpoints.URLSecondary = g.API.Endpoints.URLSecondaryDefault g.API.Endpoints.WebsocketURL = gateioWebsocketEndpoint g.Websocket = wshandler.New() - g.Websocket.Functionality = wshandler.WebsocketTickerSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketKlineSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported | - wshandler.WebsocketMessageCorrelationSupported g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout g.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -129,6 +147,7 @@ func (g *Gateio) Setup(exch *config.ExchangeConfig) error { Connector: g.WsConnect, Subscriber: g.Subscribe, UnSubscriber: g.Unsubscribe, + Features: &g.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/gemini/gemini_live_test.go b/exchanges/gemini/gemini_live_test.go index 52721fff..cf5daf3e 100644 --- a/exchanges/gemini/gemini_live_test.go +++ b/exchanges/gemini/gemini_live_test.go @@ -19,11 +19,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Gemini load config error", err) + log.Fatal("Gemini load config error", err) } geminiConfig, err := cfg.GetExchangeConfig("Gemini") if err != nil { - log.Fatal("Test Failed - Gemini Setup() init error", err) + log.Fatal("Gemini Setup() init error", err) } geminiConfig.API.AuthenticatedSupport = true geminiConfig.API.Credentials.Key = apiKey @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { g.SetDefaults() err = g.Setup(geminiConfig) if err != nil { - log.Fatal("Test Failed - Gemini setup error", err) + log.Fatal("Gemini setup error", err) } g.API.Endpoints.URL = geminiSandboxAPIURL log.Printf(sharedtestvalues.LiveTesting, g.GetName(), g.API.Endpoints.URL) diff --git a/exchanges/gemini/gemini_mock_test.go b/exchanges/gemini/gemini_mock_test.go index 4fc36d59..6b56984a 100644 --- a/exchanges/gemini/gemini_mock_test.go +++ b/exchanges/gemini/gemini_mock_test.go @@ -22,11 +22,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Gemini load config error", err) + log.Fatal("Gemini load config error", err) } geminiConfig, err := cfg.GetExchangeConfig("Gemini") if err != nil { - log.Fatal("Test Failed - Mock server error", err) + log.Fatal("Mock server error", err) } g.SkipAuthCheck = true geminiConfig.API.AuthenticatedSupport = true @@ -35,12 +35,12 @@ func TestMain(m *testing.M) { g.SetDefaults() err = g.Setup(geminiConfig) if err != nil { - log.Fatal("Test Failed - Gemini setup error", err) + log.Fatal("Gemini setup error", err) } serverDetails, newClient, err := mock.NewVCRServer(mockFile) if err != nil { - log.Fatalf("Test Failed - Mock server error %s", err) + log.Fatalf("Mock server error %s", err) } g.HTTPClient = newClient diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index 32c4b703..032ec73f 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -30,7 +30,7 @@ func TestGetSymbols(t *testing.T) { t.Parallel() _, err := g.GetSymbols() if err != nil { - t.Error("Test Failed - GetSymbols() error", err) + t.Error("GetSymbols() error", err) } } @@ -38,11 +38,11 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := g.GetTicker("BTCUSD") if err != nil { - t.Error("Test Failed - GetTicker() error", err) + t.Error("GetTicker() error", err) } _, err = g.GetTicker("bla") if err == nil { - t.Error("Test Failed - GetTicker() error", err) + t.Error("GetTicker() Expected error") } } @@ -50,7 +50,7 @@ func TestGetOrderbook(t *testing.T) { t.Parallel() _, err := g.GetOrderbook(testCurrency, url.Values{}) if err != nil { - t.Error("Test Failed - GetOrderbook() error", err) + t.Error("GetOrderbook() error", err) } } @@ -58,7 +58,7 @@ func TestGetTrades(t *testing.T) { t.Parallel() _, err := g.GetTrades(testCurrency, url.Values{}) if err != nil { - t.Error("Test Failed - GetTrades() error", err) + t.Error("GetTrades() error", err) } } @@ -66,9 +66,9 @@ func TestGetNotionalVolume(t *testing.T) { t.Parallel() _, err := g.GetNotionalVolume() if err != nil && mockTests { - t.Error("Test Failed - GetNotionalVolume() error", err) + t.Error("GetNotionalVolume() error", err) } else if err == nil && !mockTests { - t.Error("Test Failed - GetNotionalVolume() error cannot be nil") + t.Error("GetNotionalVolume() error cannot be nil") } } @@ -76,7 +76,7 @@ func TestGetAuction(t *testing.T) { t.Parallel() _, err := g.GetAuction(testCurrency) if err != nil { - t.Error("Test Failed - GetAuction() error", err) + t.Error("GetAuction() error", err) } } @@ -84,7 +84,7 @@ func TestGetAuctionHistory(t *testing.T) { t.Parallel() _, err := g.GetAuctionHistory(testCurrency, url.Values{}) if err != nil { - t.Error("Test Failed - GetAuctionHistory() error", err) + t.Error("GetAuctionHistory() error", err) } } @@ -92,9 +92,9 @@ func TestNewOrder(t *testing.T) { t.Parallel() _, err := g.NewOrder(testCurrency, 1, 9000, "buy", "exchange limit") if err != nil && mockTests { - t.Error("Test Failed - NewOrder() error", err) + t.Error("NewOrder() error", err) } else if err == nil && !mockTests { - t.Error("Test Failed - NewOrder() error cannot be nil") + t.Error("NewOrder() error cannot be nil") } } @@ -102,9 +102,9 @@ func TestCancelExistingOrder(t *testing.T) { t.Parallel() _, err := g.CancelExistingOrder(265555413) if err != nil && mockTests { - t.Error("Test Failed - CancelExistingOrder() error", err) + t.Error("CancelExistingOrder() error", err) } else if err == nil && !mockTests { - t.Error("Test Failed - CancelExistingOrder() error cannot be nil") + t.Error("CancelExistingOrder() error cannot be nil") } } @@ -112,9 +112,9 @@ func TestCancelExistingOrders(t *testing.T) { t.Parallel() _, err := g.CancelExistingOrders(false) if err != nil && mockTests { - t.Error("Test Failed - CancelExistingOrders() error", err) + t.Error("CancelExistingOrders() error", err) } else if err == nil && !mockTests { - t.Error("Test Failed - CancelExistingOrders() error cannot be nil") + t.Error("CancelExistingOrders() error cannot be nil") } } @@ -122,9 +122,9 @@ func TestGetOrderStatus(t *testing.T) { t.Parallel() _, err := g.GetOrderStatus(265563260) if err != nil && mockTests { - t.Error("Test Failed - GetOrderStatus() error", err) + t.Error("GetOrderStatus() error", err) } else if err == nil && !mockTests { - t.Error("Test Failed - GetOrderStatus() error cannot be nil") + t.Error("GetOrderStatus() error cannot be nil") } } @@ -132,9 +132,9 @@ func TestGetOrders(t *testing.T) { t.Parallel() _, err := g.GetOrders() if err != nil && mockTests { - t.Error("Test Failed - GetOrders() error", err) + t.Error("GetOrders() error", err) } else if err == nil && !mockTests { - t.Error("Test Failed - GetOrders() error cannot be nil") + t.Error("GetOrders() error cannot be nil") } } @@ -142,9 +142,9 @@ func TestGetTradeHistory(t *testing.T) { t.Parallel() _, err := g.GetTradeHistory(testCurrency, 0) if err != nil && mockTests { - t.Error("Test Failed - GetTradeHistory() error", err) + t.Error("GetTradeHistory() error", err) } else if err == nil && !mockTests { - t.Error("Test Failed - GetTradeHistory() error cannot be nil") + t.Error("GetTradeHistory() error cannot be nil") } } @@ -152,9 +152,9 @@ func TestGetTradeVolume(t *testing.T) { t.Parallel() _, err := g.GetTradeVolume() if err != nil && mockTests { - t.Error("Test Failed - GetTradeVolume() error", err) + t.Error("GetTradeVolume() error", err) } else if err == nil && !mockTests { - t.Error("Test Failed - GetTradeVolume() error cannot be nil") + t.Error("GetTradeVolume() error cannot be nil") } } @@ -162,9 +162,9 @@ func TestGetBalances(t *testing.T) { t.Parallel() _, err := g.GetBalances() if err != nil && mockTests { - t.Error("Test Failed - GetBalances() error", err) + t.Error("GetBalances() error", err) } else if err == nil && !mockTests { - t.Error("Test Failed - GetBalances() error cannot be nil") + t.Error("GetBalances() error cannot be nil") } } @@ -172,7 +172,7 @@ func TestGetCryptoDepositAddress(t *testing.T) { t.Parallel() _, err := g.GetCryptoDepositAddress("LOL123", "btc") if err == nil { - t.Error("Test Failed - GetCryptoDepositAddress() error", err) + t.Error("GetCryptoDepositAddress() Expected error") } } @@ -180,7 +180,7 @@ func TestWithdrawCrypto(t *testing.T) { t.Parallel() _, err := g.WithdrawCrypto("LOL123", "btc", 1) if err == nil { - t.Error("Test Failed - WithdrawCrypto() error", err) + t.Error("WithdrawCrypto() Expected error") } } @@ -188,9 +188,9 @@ func TestPostHeartbeat(t *testing.T) { t.Parallel() _, err := g.PostHeartbeat() if err != nil && mockTests { - t.Error("Test Failed - PostHeartbeat() error", err) + t.Error("PostHeartbeat() error", err) } else if err == nil && !mockTests { - t.Error("Test Failed - PostHeartbeat() error cannot be nil") + t.Error("PostHeartbeat() error cannot be nil") } } @@ -234,7 +234,7 @@ func TestGetFee(t *testing.T) { if areTestAPIKeysSet() || mockTests { // CryptocurrencyTradeFee Basic if resp, err := g.GetFee(feeBuilder); resp != float64(0.0035) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0035), resp) t.Error(err) @@ -245,7 +245,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := g.GetFee(feeBuilder); resp != float64(3500) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(3500), resp) t.Error(err) @@ -255,7 +255,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := g.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) t.Error(err) @@ -265,7 +265,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -275,7 +275,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -286,7 +286,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -296,7 +296,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -306,7 +306,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -317,7 +317,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := g.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) @@ -470,7 +470,7 @@ func TestModifyOrder(t *testing.T) { t.Parallel() _, err := g.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -535,7 +535,7 @@ func TestGetDepositAddress(t *testing.T) { t.Parallel() _, err := g.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress error cannot be nil") + t.Error("GetDepositAddress error cannot be nil") } } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index dd2338a9..93a2cbc9 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -15,6 +15,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -69,9 +70,28 @@ func (g *Gemini) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: false, + RESTCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + FiatWithdrawalFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + OrderbookFetching: true, + TradeFetching: true, + AuthenticatedEndpoints: true, + MessageSequenceNumbers: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.AutoWithdrawCryptoWithSetup | @@ -91,10 +111,6 @@ func (g *Gemini) SetDefaults() { g.API.Endpoints.URL = g.API.Endpoints.URLDefault g.API.Endpoints.WebsocketURL = geminiWebsocketEndpoint g.Websocket = wshandler.New() - g.Websocket.Functionality = wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported | - wshandler.WebsocketSequenceNumberSupported g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout g.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -126,6 +142,7 @@ func (g *Gemini) Setup(exch *config.ExchangeConfig) error { ExchangeName: exch.Name, RunningURL: exch.API.Endpoints.WebsocketURL, Connector: g.WsConnect, + Features: &g.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index ba39081a..4326501e 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -33,11 +33,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - HitBTC load config error", err) + t.Fatal("HitBTC load config error", err) } hitbtcConfig, err := cfg.GetExchangeConfig("HitBTC") if err != nil { - t.Error("Test Failed - HitBTC Setup() init error") + t.Error("HitBTC Setup() init error") } hitbtcConfig.API.AuthenticatedSupport = true hitbtcConfig.API.AuthenticatedWebsocketSupport = true @@ -46,7 +46,7 @@ func TestSetup(t *testing.T) { err = h.Setup(hitbtcConfig) if err != nil { - t.Fatal("Test Failed - HitBTC setup error", err) + t.Fatal("HitBTC setup error", err) } } @@ -143,7 +143,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := h.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) } // CryptocurrencyTradeFee High quantity @@ -151,7 +151,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := h.GetFee(feeBuilder); resp != float64(2000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2000), resp) t.Error(err) } @@ -159,7 +159,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := h.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) t.Error(err) } @@ -167,7 +167,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -175,7 +175,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := h.GetFee(feeBuilder); resp != float64(0.042800) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.042800), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.042800), resp) t.Error(err) } @@ -184,7 +184,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err == nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -195,7 +195,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.BTC feeBuilder.Pair.Quote = currency.LTC if resp, err := h.GetFee(feeBuilder); resp != float64(0.0006) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0006), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0006), resp) t.Error(err) } @@ -203,7 +203,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -212,7 +212,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -355,7 +355,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := h.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -418,12 +418,12 @@ func TestGetDepositAddress(t *testing.T) { if areTestAPIKeysSet() { _, err := h.GetDepositAddress(currency.BTC, "") if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } } else { _, err := h.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } } diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index f1fcaa90..2c36f2bc 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -69,9 +70,36 @@ func (h *HitBTC) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + ModifyOrder: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + CryptoDepositFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + OrderbookFetching: true, + Subscribe: true, + Unsubscribe: true, + AuthenticatedEndpoints: true, + SubmitOrder: true, + CancelOrder: true, + MessageSequenceNumbers: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.NoFiatWithdrawals, @@ -90,14 +118,6 @@ func (h *HitBTC) SetDefaults() { h.API.Endpoints.URL = h.API.Endpoints.URLDefault h.API.Endpoints.WebsocketURL = hitbtcWebsocketAddress h.Websocket = wshandler.New() - h.Websocket.Functionality = wshandler.WebsocketTickerSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported | - wshandler.WebsocketSubmitOrderSupported | - wshandler.WebsocketCancelOrderSupported | - wshandler.WebsocketMessageCorrelationSupported h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -127,6 +147,7 @@ func (h *HitBTC) Setup(exch *config.ExchangeConfig) error { Connector: h.WsConnect, Subscriber: h.Subscribe, UnSubscriber: h.Unsubscribe, + Features: &h.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 644471e4..18aae6b2 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -40,11 +40,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Huobi load config error", err) + log.Fatal("Huobi load config error", err) } hConfig, err := cfg.GetExchangeConfig("Huobi") if err != nil { - t.Error("Test Failed - Huobi Setup() init error") + t.Error("Huobi Setup() init error") } hConfig.API.AuthenticatedSupport = true hConfig.API.AuthenticatedWebsocketSupport = true @@ -53,7 +53,7 @@ func TestSetup(t *testing.T) { err = h.Setup(hConfig) if err != nil { - t.Fatal("Test Failed - Huobi setup error", err) + t.Fatal("Huobi setup error", err) } } @@ -105,7 +105,7 @@ func TestGetSpotKline(t *testing.T) { Size: 0, }) if err != nil { - t.Errorf("Test failed - Huobi TestGetSpotKline: %s", err) + t.Errorf("Huobi TestGetSpotKline: %s", err) } } @@ -113,7 +113,7 @@ func TestGetMarketDetailMerged(t *testing.T) { t.Parallel() _, err := h.GetMarketDetailMerged(testSymbol) if err != nil { - t.Errorf("Test failed - Huobi TestGetMarketDetailMerged: %s", err) + t.Errorf("Huobi TestGetMarketDetailMerged: %s", err) } } @@ -125,7 +125,7 @@ func TestGetDepth(t *testing.T) { }) if err != nil { - t.Errorf("Test failed - Huobi TestGetDepth: %s", err) + t.Errorf("Huobi TestGetDepth: %s", err) } } @@ -133,7 +133,7 @@ func TestGetTrades(t *testing.T) { t.Parallel() _, err := h.GetTrades(testSymbol) if err != nil { - t.Errorf("Test failed - Huobi TestGetTrades: %s", err) + t.Errorf("Huobi TestGetTrades: %s", err) } } @@ -141,7 +141,7 @@ func TestGetLatestSpotPrice(t *testing.T) { t.Parallel() _, err := h.GetLatestSpotPrice(testSymbol) if err != nil { - t.Errorf("Test failed - Huobi GetLatestSpotPrice: %s", err) + t.Errorf("Huobi GetLatestSpotPrice: %s", err) } } @@ -149,7 +149,7 @@ func TestGetTradeHistory(t *testing.T) { t.Parallel() _, err := h.GetTradeHistory(testSymbol, "50") if err != nil { - t.Errorf("Test failed - Huobi TestGetTradeHistory: %s", err) + t.Errorf("Huobi TestGetTradeHistory: %s", err) } } @@ -157,7 +157,7 @@ func TestGetMarketDetail(t *testing.T) { t.Parallel() _, err := h.GetMarketDetail(testSymbol) if err != nil { - t.Errorf("Test failed - Huobi TestGetTradeHistory: %s", err) + t.Errorf("Huobi TestGetTradeHistory: %s", err) } } @@ -165,7 +165,7 @@ func TestGetSymbols(t *testing.T) { t.Parallel() _, err := h.GetSymbols() if err != nil { - t.Errorf("Test failed - Huobi TestGetSymbols: %s", err) + t.Errorf("Huobi TestGetSymbols: %s", err) } } @@ -173,7 +173,7 @@ func TestGetCurrencies(t *testing.T) { t.Parallel() _, err := h.GetCurrencies() if err != nil { - t.Errorf("Test failed - Huobi TestGetCurrencies: %s", err) + t.Errorf("Huobi TestGetCurrencies: %s", err) } } @@ -188,7 +188,7 @@ func TestGetTimestamp(t *testing.T) { t.Parallel() _, err := h.GetTimestamp() if err != nil { - t.Errorf("Test failed - Huobi TestGetTimestamp: %s", err) + t.Errorf("Huobi TestGetTimestamp: %s", err) } } @@ -201,7 +201,7 @@ func TestGetAccounts(t *testing.T) { _, err := h.GetAccounts() if err != nil { - t.Errorf("Test failed - Huobi GetAccounts: %s", err) + t.Errorf("Huobi GetAccounts: %s", err) } } @@ -214,13 +214,13 @@ func TestGetAccountBalance(t *testing.T) { result, err := h.GetAccounts() if err != nil { - t.Errorf("Test failed - Huobi GetAccounts: %s", err) + t.Errorf("Huobi GetAccounts: %s", err) } userID := strconv.FormatInt(result[0].ID, 10) _, err = h.GetAccountBalance(userID) if err != nil { - t.Errorf("Test failed - Huobi GetAccountBalance: %s", err) + t.Errorf("Huobi GetAccountBalance: %s", err) } } @@ -233,7 +233,7 @@ func TestGetAggregatedBalance(t *testing.T) { _, err := h.GetAggregatedBalance() if err != nil { - t.Errorf("Test failed - Huobi GetAggregatedBalance: %s", err) + t.Errorf("Huobi GetAggregatedBalance: %s", err) } } @@ -254,7 +254,7 @@ func TestSpotNewOrder(t *testing.T) { _, err := h.SpotNewOrder(arg) if err != nil { - t.Errorf("Test failed - Huobi SpotNewOrder: %s", err) + t.Errorf("Huobi SpotNewOrder: %s", err) } } @@ -263,7 +263,7 @@ func TestCancelExistingOrder(t *testing.T) { _, err := h.CancelExistingOrder(1337) if err == nil { - t.Error("Test failed - Huobi TestCancelExistingOrder: Invalid orderID returned true") + t.Error("Huobi TestCancelExistingOrder Expected error") } } @@ -272,7 +272,7 @@ func TestGetOrder(t *testing.T) { _, err := h.GetOrder(1337) if err == nil { - t.Error("Test failed - Huobi TestCancelOrder: Invalid orderID returned true") + t.Error("Huobi TestCancelOrder Expected error") } } @@ -285,7 +285,7 @@ func TestGetMarginLoanOrders(t *testing.T) { _, err := h.GetMarginLoanOrders(testSymbol, "", "", "", "", "", "", "") if err != nil { - t.Errorf("Test failed - Huobi TestGetMarginLoanOrders: %s", err) + t.Errorf("Huobi TestGetMarginLoanOrders: %s", err) } } @@ -298,7 +298,7 @@ func TestGetMarginAccountBalance(t *testing.T) { _, err := h.GetMarginAccountBalance(testSymbol) if err != nil { - t.Errorf("Test failed - Huobi TestGetMarginAccountBalance: %s", err) + t.Errorf("Huobi TestGetMarginAccountBalance: %s", err) } } @@ -307,7 +307,7 @@ func TestCancelWithdraw(t *testing.T) { _, err := h.CancelWithdraw(1337) if err == nil { - t.Error("Test failed - Huobi TestCancelWithdraw: Invalid withdraw-ID was valid") + t.Error("Huobi TestCancelWithdraw Expected error") } } @@ -317,23 +317,23 @@ func TestPEMLoadAndSign(t *testing.T) { pemKey := strings.NewReader(h.API.Credentials.PEMKey) pemBytes, err := ioutil.ReadAll(pemKey) if err != nil { - t.Fatalf("Test Failed. TestPEMLoadAndSign Unable to ioutil.ReadAll PEM key: %s", err) + t.Fatalf("TestPEMLoadAndSign Unable to ioutil.ReadAll PEM key: %s", err) } block, _ := pem.Decode(pemBytes) if block == nil { - t.Fatalf("Test Failed. TestPEMLoadAndSign Block is nil") + t.Fatalf("TestPEMLoadAndSign Block is nil") } x509Encoded := block.Bytes privKey, err := x509.ParseECPrivateKey(x509Encoded) if err != nil { - t.Fatalf("Test Failed. TestPEMLoadAndSign Unable to ParseECPrivKey: %s", err) + t.Fatalf("TestPEMLoadAndSign Unable to ParseECPrivKey: %s", err) } _, _, err = ecdsa.Sign(rand.Reader, privKey, crypto.GetSHA256([]byte("test"))) if err != nil { - t.Fatalf("Test Failed. TestPEMLoadAndSign Unable to sign: %s", err) + t.Fatalf("TestPEMLoadAndSign Unable to sign: %s", err) } } @@ -371,7 +371,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := h.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) } // CryptocurrencyTradeFee High quantity @@ -379,7 +379,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := h.GetFee(feeBuilder); resp != float64(2000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2000), resp) t.Error(err) } @@ -387,7 +387,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := h.GetFee(feeBuilder); resp != float64(0.002) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) t.Error(err) } @@ -395,14 +395,14 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } // CryptocurrencyWithdrawalFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -411,7 +411,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -419,7 +419,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -427,7 +427,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -436,7 +436,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := h.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -586,12 +586,12 @@ func TestGetAccountInfo(t *testing.T) { if apiKey == "" || apiSecret == "" { _, err := h.GetAccountInfo() if err == nil { - t.Error("Test Failed - GetAccountInfo() error") + t.Error("GetAccountInfo() Expected error") } } else { _, err := h.GetAccountInfo() if err != nil { - t.Error("Test Failed - GetAccountInfo() error", err) + t.Error("GetAccountInfo() error", err) } } } @@ -599,7 +599,7 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := h.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -661,7 +661,7 @@ func TestWithdrawInternationalBank(t *testing.T) { func TestGetDepositAddress(t *testing.T) { _, err := h.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 8aa6ccce..ec423131 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -70,9 +71,31 @@ func (h *HUOBI) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + CryptoWithdrawal: true, + TradeFee: true, + }, + WebsocketCapabilities: protocol.Features{ + KlineFetching: true, + OrderbookFetching: true, + TradeFetching: true, + Subscribe: true, + Unsubscribe: true, + AuthenticatedEndpoints: true, + AccountInfo: true, + MessageCorrelation: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | exchange.NoFiatWithdrawals, @@ -91,14 +114,6 @@ func (h *HUOBI) SetDefaults() { h.API.Endpoints.URL = h.API.Endpoints.URLDefault h.API.Endpoints.WebsocketURL = wsMarketURL h.Websocket = wshandler.New() - h.Websocket.Functionality = wshandler.WebsocketKlineSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported | - wshandler.WebsocketAccountDataSupported | - wshandler.WebsocketMessageCorrelationSupported h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -131,6 +146,7 @@ func (h *HUOBI) Setup(exch *config.ExchangeConfig) error { Connector: h.WsConnect, Subscriber: h.Subscribe, UnSubscriber: h.Unsubscribe, + Features: &h.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index b7d3dc95..5c2c91e9 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -28,11 +28,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - Itbit load config error", err) + t.Fatal("Itbit load config error", err) } itbitConfig, err := cfg.GetExchangeConfig("ITBIT") if err != nil { - t.Error("Test Failed - Itbit Setup() init error") + t.Error("Itbit Setup() init error") } itbitConfig.API.AuthenticatedSupport = true itbitConfig.API.Credentials.Key = apiKey @@ -41,7 +41,7 @@ func TestSetup(t *testing.T) { err = i.Setup(itbitConfig) if err != nil { - t.Fatal("Test Failed - Itbit setup error", err) + t.Fatal("Itbit setup error", err) } } @@ -49,7 +49,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := i.GetTicker("XBTUSD") if err != nil { - t.Error("Test Failed - GetTicker() error", err) + t.Error("GetTicker() error", err) } } @@ -57,7 +57,7 @@ func TestGetOrderbook(t *testing.T) { t.Parallel() _, err := i.GetOrderbook("XBTSGD") if err != nil { - t.Error("Test Failed - GetOrderbook() error", err) + t.Error("GetOrderbook() error", err) } } @@ -65,49 +65,49 @@ func TestGetTradeHistory(t *testing.T) { t.Parallel() _, err := i.GetTradeHistory("XBTUSD", "0") if err != nil { - t.Error("Test Failed - GetTradeHistory() error", err) + t.Error("GetTradeHistory() error", err) } } func TestGetWallets(t *testing.T) { _, err := i.GetWallets(url.Values{}) if err == nil { - t.Error("Test Failed - GetWallets() error", err) + t.Error("GetWallets() Expected error") } } func TestCreateWallet(t *testing.T) { _, err := i.CreateWallet("test") if err == nil { - t.Error("Test Failed - CreateWallet() error", err) + t.Error("CreateWallet() Expected error") } } func TestGetWallet(t *testing.T) { _, err := i.GetWallet("1337") if err == nil { - t.Error("Test Failed - GetWallet() error", err) + t.Error("GetWallet() Expected error") } } func TestGetWalletBalance(t *testing.T) { _, err := i.GetWalletBalance("1337", "XRT") if err == nil { - t.Error("Test Failed - GetWalletBalance() error", err) + t.Error("GetWalletBalance() Expected error") } } func TestGetWalletTrades(t *testing.T) { _, err := i.GetWalletTrades("1337", url.Values{}) if err == nil { - t.Error("Test Failed - GetWalletTrades() error", err) + t.Error("GetWalletTrades() Expected error") } } func TestGetFundingHistory(t *testing.T) { _, err := i.GetFundingHistoryForWallet("1337", url.Values{}) if err == nil { - t.Error("Test Failed - GetFundingHistory() error", err) + t.Error("GetFundingHistory() Expected error") } } @@ -116,14 +116,14 @@ func TestPlaceOrder(t *testing.T) { exchange.LimitOrderType.ToLower().ToString(), "USD", 1, 0.2, "banjo", "sauce") if err == nil { - t.Error("Test Failed - PlaceOrder() error", err) + t.Error("PlaceOrder() Expected error") } } func TestGetOrder(t *testing.T) { _, err := i.GetOrder("1337", url.Values{}) if err == nil { - t.Error("Test Failed - GetOrder() error", err) + t.Error("GetOrder() Expected error") } } @@ -131,21 +131,21 @@ func TestCancelExistingOrder(t *testing.T) { t.Skip() err := i.CancelExistingOrder("1337", "1337order") if err == nil { - t.Error("Test Failed - CancelOrder() error", err) + t.Error("CancelOrder() Expected error") } } func TestGetCryptoDepositAddress(t *testing.T) { _, err := i.GetCryptoDepositAddress("1337", "AUD") if err == nil { - t.Error("Test Failed - GetCryptoDepositAddress() error", err) + t.Error("GetCryptoDepositAddress() Expected error") } } func TestWalletTransfer(t *testing.T) { _, err := i.WalletTransfer("1337", "mywallet", "anotherwallet", 200, "USD") if err == nil { - t.Error("Test Failed - WalletTransfer() error", err) + t.Error("WalletTransfer() Expected error") } } @@ -183,7 +183,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := i.GetFee(feeBuilder); resp != float64(0.0035) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0035), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0035), resp) } // CryptocurrencyTradeFee High quantity @@ -191,7 +191,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := i.GetFee(feeBuilder); resp != float64(3500) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(3500), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(3500), resp) t.Error(err) } @@ -199,7 +199,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := i.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -207,14 +207,14 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := i.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } // CryptocurrencyWithdrawalFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := i.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -223,7 +223,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := i.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -231,7 +231,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := i.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -239,7 +239,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := i.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -248,7 +248,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := i.GetFee(feeBuilder); resp != float64(40) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(40), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(40), resp) t.Error(err) } } @@ -390,7 +390,7 @@ func TestGetAccountInfo(t *testing.T) { if apiKey != "" || apiSecret != "" || clientID != "" { _, err := i.GetAccountInfo() if err == nil { - t.Error("Test Failed - GetAccountInfo() error") + t.Error("GetAccountInfo() Expected error") } } } @@ -398,7 +398,7 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := i.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -457,6 +457,6 @@ func TestWithdrawInternationalBank(t *testing.T) { func TestGetDepositAddress(t *testing.T) { _, err := i.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 092baa9e..b7c3d88b 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -68,9 +69,21 @@ func (i *ItBit) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: false, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: false, - TickerBatching: false, + RESTCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrder: true, + SubmitOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoDeposit: true, + TradeFee: true, + FiatWithdrawalFee: true, }, WithdrawPermissions: exchange.WithdrawCryptoViaWebsiteOnly | exchange.WithdrawFiatViaWebsiteOnly, diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index be1b4954..37b71e5b 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -34,11 +34,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Kraken load config error", err) + log.Fatal("Kraken load config error", err) } krakenConfig, err := cfg.GetExchangeConfig("Kraken") if err != nil { - t.Error("Test Failed - kraken Setup() init error", err) + t.Error("kraken Setup() init error", err) } krakenConfig.API.AuthenticatedSupport = true krakenConfig.API.Credentials.Key = apiKey @@ -49,7 +49,7 @@ func TestSetup(t *testing.T) { err = k.Setup(krakenConfig) if err != nil { - t.Fatal("Test Failed - Kraken setup error", err) + t.Fatal("Kraken setup error", err) } } @@ -58,7 +58,7 @@ func TestGetServerTime(t *testing.T) { t.Parallel() _, err := k.GetServerTime() if err != nil { - t.Error("Test Failed - GetServerTime() error", err) + t.Error("GetServerTime() error", err) } } @@ -67,7 +67,7 @@ func TestGetAssets(t *testing.T) { t.Parallel() _, err := k.GetAssets() if err != nil { - t.Error("Test Failed - GetAssets() error", err) + t.Error("GetAssets() error", err) } } @@ -76,7 +76,7 @@ func TestGetAssetPairs(t *testing.T) { t.Parallel() _, err := k.GetAssetPairs() if err != nil { - t.Error("Test Failed - GetAssetPairs() error", err) + t.Error("GetAssetPairs() error", err) } } @@ -85,7 +85,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := k.GetTicker("BCHEUR") if err != nil { - t.Error("Test Failed - GetTicker() error", err) + t.Error("GetTicker() error", err) } } @@ -94,7 +94,7 @@ func TestGetTickers(t *testing.T) { t.Parallel() _, err := k.GetTickers("LTCUSD,ETCUSD") if err != nil { - t.Error("Test failed - GetTickers() error", err) + t.Error("GetTickers() error", err) } } @@ -103,7 +103,7 @@ func TestGetOHLC(t *testing.T) { t.Parallel() _, err := k.GetOHLC("BCHEUR") if err != nil { - t.Error("Test Failed - GetOHLC() error", err) + t.Error("GetOHLC() error", err) } } @@ -112,7 +112,7 @@ func TestGetDepth(t *testing.T) { t.Parallel() _, err := k.GetDepth("BCHEUR") if err != nil { - t.Error("Test Failed - GetDepth() error", err) + t.Error("GetDepth() error", err) } } @@ -121,7 +121,7 @@ func TestGetTrades(t *testing.T) { t.Parallel() _, err := k.GetTrades("BCHEUR") if err != nil { - t.Error("Test Failed - GetTrades() error", err) + t.Error("GetTrades() error", err) } } @@ -130,7 +130,7 @@ func TestGetSpread(t *testing.T) { t.Parallel() _, err := k.GetSpread("BCHEUR") if err != nil { - t.Error("Test Failed - GetSpread() error", err) + t.Error("GetSpread() error", err) } } @@ -139,7 +139,7 @@ func TestGetBalance(t *testing.T) { t.Parallel() _, err := k.GetBalance() if err == nil { - t.Error("Test Failed - GetBalance() error", err) + t.Error("GetBalance() Expected error") } } @@ -149,7 +149,7 @@ func TestGetTradeBalance(t *testing.T) { args := TradeBalanceOptions{Asset: "ZEUR"} _, err := k.GetTradeBalance(args) if err == nil { - t.Error("Test Failed - GetTradeBalance() error", err) + t.Error("GetTradeBalance() Expected error") } } @@ -159,7 +159,7 @@ func TestGetOpenOrders(t *testing.T) { args := OrderInfoOptions{Trades: true} _, err := k.GetOpenOrders(args) if err == nil { - t.Error("Test Failed - GetOpenOrders() error", err) + t.Error("GetOpenOrders() Expected error") } } @@ -169,7 +169,7 @@ func TestGetClosedOrders(t *testing.T) { args := GetClosedOrdersOptions{Trades: true, Start: "OE4KV4-4FVQ5-V7XGPU"} _, err := k.GetClosedOrders(args) if err == nil { - t.Error("Test Failed - GetClosedOrders() error", err) + t.Error("GetClosedOrders() Expected error") } } @@ -179,7 +179,7 @@ func TestQueryOrdersInfo(t *testing.T) { args := OrderInfoOptions{Trades: true} _, err := k.QueryOrdersInfo(args, "OR6ZFV-AA6TT-CKFFIW", "OAMUAJ-HLVKG-D3QJ5F") if err == nil { - t.Error("Test Failed - QueryOrdersInfo() error", err) + t.Error("QueryOrdersInfo() Expected error") } } @@ -189,7 +189,7 @@ func TestGetTradesHistory(t *testing.T) { args := GetTradesHistoryOptions{Trades: true, Start: "TMZEDR-VBJN2-NGY6DX", End: "TVRXG2-R62VE-RWP3UW"} _, err := k.GetTradesHistory(args) if err == nil { - t.Error("Test Failed - GetTradesHistory() error", err) + t.Error("GetTradesHistory() Expected error") } } @@ -198,7 +198,7 @@ func TestQueryTrades(t *testing.T) { t.Parallel() _, err := k.QueryTrades(true, "TMZEDR-VBJN2-NGY6DX", "TFLWIB-KTT7L-4TWR3L", "TDVRAH-2H6OS-SLSXRX") if err == nil { - t.Error("Test Failed - QueryTrades() error", err) + t.Error("QueryTrades() Expected error") } } @@ -207,7 +207,7 @@ func TestOpenPositions(t *testing.T) { t.Parallel() _, err := k.OpenPositions(false) if err == nil { - t.Error("Test Failed - OpenPositions() error", err) + t.Error("OpenPositions() Expected error") } } @@ -217,7 +217,7 @@ func TestGetLedgers(t *testing.T) { args := GetLedgersOptions{Start: "LRUHXI-IWECY-K4JYGO", End: "L5NIY7-JZQJD-3J4M2V", Ofs: 15} _, err := k.GetLedgers(args) if err == nil { - t.Error("Test Failed - GetLedgers() error", err) + t.Error("GetLedgers() Expected error") } } @@ -226,7 +226,7 @@ func TestQueryLedgers(t *testing.T) { t.Parallel() _, err := k.QueryLedgers("LVTSFS-NHZVM-EXNZ5M") if err == nil { - t.Error("Test Failed - QueryLedgers() error", err) + t.Error("QueryLedgers() Expected error") } } @@ -235,7 +235,7 @@ func TestGetTradeVolume(t *testing.T) { t.Parallel() _, err := k.GetTradeVolume(true, "OAVY7T-MV5VK-KHDF5X") if err == nil { - t.Error("Test Failed - GetTradeVolume() error", err) + t.Error("GetTradeVolume() Expected error") } } @@ -247,7 +247,7 @@ func TestAddOrder(t *testing.T) { exchange.SellOrderSide.ToLower().ToString(), exchange.LimitOrderType.ToLower().ToString(), 0.00000001, 0, 0, 0, &args) if err == nil { - t.Error("Test Failed - AddOrder() error", err) + t.Error("AddOrder() Expected error") } } @@ -256,7 +256,7 @@ func TestCancelExistingOrder(t *testing.T) { t.Parallel() _, err := k.CancelExistingOrder("OAVY7T-MV5VK-KHDF5X") if err == nil { - t.Error("Test Failed - CancelExistingOrder() error", err) + t.Error("CancelExistingOrder() Expected error") } } @@ -297,7 +297,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := k.GetFee(feeBuilder); resp != float64(0.0026) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0026), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0026), resp) } // CryptocurrencyTradeFee High quantity @@ -305,7 +305,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := k.GetFee(feeBuilder); resp != float64(2600) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2600), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2600), resp) t.Error(err) } @@ -313,7 +313,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := k.GetFee(feeBuilder); resp != float64(0.0016) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0016), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0016), resp) t.Error(err) } @@ -321,7 +321,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := k.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -329,7 +329,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := k.GetFee(feeBuilder); resp != float64(5) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(5), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(5), resp) t.Error(err) } } @@ -339,7 +339,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.CyptocurrencyDepositFee feeBuilder.Pair.Base = currency.XXBT if resp, err := k.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(5), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(5), resp) t.Error(err) } @@ -347,7 +347,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := k.GetFee(feeBuilder); resp != float64(0.0005) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) t.Error(err) } @@ -356,7 +356,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := k.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -365,7 +365,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := k.GetFee(feeBuilder); resp != float64(5) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(5), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(5), resp) t.Error(err) } } @@ -514,12 +514,12 @@ func TestGetAccountInfo(t *testing.T) { if apiKey != "" || apiSecret != "" || clientID != "" { _, err := k.GetAccountInfo() if err != nil { - t.Error("Test Failed - GetAccountInfo() error", err) + t.Error("GetAccountInfo() error", err) } } else { _, err := k.GetAccountInfo() if err == nil { - t.Error("Test Failed - GetAccountInfo() error") + t.Error("GetAccountInfo() Expected error") } } } @@ -528,7 +528,7 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := k.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -619,12 +619,12 @@ func TestGetDepositAddress(t *testing.T) { if areTestAPIKeysSet() { _, err := k.GetDepositAddress(currency.BTC, "") if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } } else { _, err := k.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error can not be nil") + t.Error("GetDepositAddress() error can not be nil") } } } @@ -637,12 +637,12 @@ func TestWithdrawStatus(t *testing.T) { if areTestAPIKeysSet() { _, err := k.WithdrawStatus(currency.BTC, "") if err != nil { - t.Error("Test Failed - WithdrawStatus() error", err) + t.Error("WithdrawStatus() error", err) } } else { _, err := k.WithdrawStatus(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error can not be nil", err) + t.Error("GetDepositAddress() error can not be nil") } } } @@ -653,9 +653,9 @@ func TestWithdrawCancel(t *testing.T) { TestSetup(t) _, err := k.WithdrawCancel(currency.BTC, "") if areTestAPIKeysSet() && err == nil { - t.Error("Test Failed - WithdrawCancel() error cannot be nil") + t.Error("WithdrawCancel() error cannot be nil") } else if !areTestAPIKeysSet() && err == nil { - t.Errorf("Test Failed - WithdrawCancel() error - expecting an error when no keys are set but received nil") + t.Errorf("WithdrawCancel() error - expecting an error when no keys are set but received nil") } } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 3763a672..a2c40be5 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -13,6 +13,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -72,9 +73,37 @@ func (k *Kraken) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: false, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrder: true, + SubmitOrder: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + FiatDeposit: true, + FiatWithdraw: true, + TradeFee: true, + FiatDepositFee: true, + FiatWithdrawalFee: true, + CryptoDepositFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + KlineFetching: true, + OrderbookFetching: true, + Subscribe: true, + Unsubscribe: true, + MessageCorrelation: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | exchange.WithdrawCryptoWith2FA | @@ -95,13 +124,6 @@ func (k *Kraken) SetDefaults() { k.API.Endpoints.URL = k.API.Endpoints.URLDefault k.Websocket = wshandler.New() k.API.Endpoints.WebsocketURL = krakenWSURL - k.Websocket.Functionality = wshandler.WebsocketTickerSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketKlineSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketMessageCorrelationSupported k.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit k.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout k.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -131,6 +153,7 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) error { Connector: k.WsConnect, Subscriber: k.Subscribe, UnSubscriber: k.Unsubscribe, + Features: &k.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index 8c18c824..bf1fa5df 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -35,11 +35,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - LakeBTC load config error", err) + log.Fatal("LakeBTC load config error", err) } lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC") if err != nil { - t.Error("Test Failed - LakeBTC Setup() init error") + t.Error("LakeBTC Setup() init error") } lakebtcConfig.API.AuthenticatedSupport = true lakebtcConfig.API.Credentials.Key = apiKey @@ -47,7 +47,7 @@ func TestSetup(t *testing.T) { lakebtcConfig.Features.Enabled.Websocket = true err = l.Setup(lakebtcConfig) if err != nil { - t.Fatal("Test Failed - LakeBTC setup error", err) + t.Fatal("LakeBTC setup error", err) } l.API.Endpoints.WebsocketURL = lakeBTCWSURL setupRan = true @@ -58,7 +58,7 @@ func TestFetchTradablePairs(t *testing.T) { t.Parallel() _, err := l.FetchTradablePairs(asset.Spot) if err != nil { - t.Fatalf("Test failed. GetTradablePairs err: %s", err) + t.Fatalf("GetTradablePairs err: %s", err) } } @@ -66,7 +66,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := l.GetTicker() if err != nil { - t.Error("Test Failed - GetTicker() error", err) + t.Error("GetTicker() error", err) } } @@ -74,7 +74,7 @@ func TestGetOrderBook(t *testing.T) { t.Parallel() _, err := l.GetOrderBook("BTCUSD") if err != nil { - t.Error("Test Failed - GetOrderBook() error", err) + t.Error("GetOrderBook() error", err) } } @@ -82,7 +82,7 @@ func TestGetTradeHistory(t *testing.T) { t.Parallel() _, err := l.GetTradeHistory("BTCUSD") if err != nil { - t.Error("Test Failed - GetTradeHistory() error", err) + t.Error("GetTradeHistory() error", err) } } @@ -93,7 +93,7 @@ func TestTrade(t *testing.T) { } _, err := l.Trade(false, 0, 0, "USD") if err == nil { - t.Error("Test Failed - Trade() error", err) + t.Error("Trade() Expected error") } } @@ -104,7 +104,7 @@ func TestGetOpenOrders(t *testing.T) { } _, err := l.GetOpenOrders() if err == nil { - t.Error("Test Failed - GetOpenOrders() error", err) + t.Error("GetOpenOrders() Expected error") } } @@ -115,7 +115,7 @@ func TestGetOrders(t *testing.T) { } _, err := l.GetOrders([]int64{1, 2}) if err == nil { - t.Error("Test Failed - GetOrders() error", err) + t.Error("GetOrders() Expected error") } } @@ -126,7 +126,7 @@ func TestCancelOrder(t *testing.T) { } err := l.CancelExistingOrder(1337) if err == nil { - t.Error("Test Failed - CancelExistingOrder() error", err) + t.Error("CancelExistingOrder() Expected error") } } @@ -137,7 +137,7 @@ func TestGetTrades(t *testing.T) { } _, err := l.GetTrades(1337) if err == nil { - t.Error("Test Failed - GetTrades() error", err) + t.Error("GetTrades() Expected error") } } @@ -148,7 +148,7 @@ func TestGetExternalAccounts(t *testing.T) { } _, err := l.GetExternalAccounts() if err == nil { - t.Error("Test Failed - GetExternalAccounts() error", err) + t.Error("GetExternalAccounts() Expected error") } } @@ -187,7 +187,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := l.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) } // CryptocurrencyTradeFee High quantity @@ -195,7 +195,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := l.GetFee(feeBuilder); resp != float64(2000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2000), resp) t.Error(err) } @@ -203,7 +203,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := l.GetFee(feeBuilder); resp != float64(0.0015) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) t.Error(err) } @@ -211,14 +211,14 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } // CryptocurrencyWithdrawalFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -227,7 +227,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -235,7 +235,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := l.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) t.Error(err) } @@ -243,7 +243,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -252,7 +252,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -393,7 +393,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := l.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -456,12 +456,12 @@ func TestGetDepositAddress(t *testing.T) { if areTestAPIKeysSet() { _, err := l.GetDepositAddress(currency.BTC, "") if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } } else { _, err := l.GetDepositAddress(currency.DASH, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error cannot be nil") + t.Error("GetDepositAddress() error cannot be nil") } } } diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index c7c9be68..91503dc2 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -69,9 +70,27 @@ func (l *LakeBTC) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + UserTradeHistory: true, + CryptoWithdrawal: true, + TradeFee: true, + CryptoDepositFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TradeFetching: true, + OrderbookFetching: true, + Subscribe: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.WithdrawFiatViaWebsiteOnly, @@ -90,9 +109,6 @@ func (l *LakeBTC) SetDefaults() { l.API.Endpoints.URL = l.API.Endpoints.URLDefault l.Websocket = wshandler.New() l.API.Endpoints.WebsocketURL = lakeBTCWSURL - l.Websocket.Functionality = wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketSubscribeSupported l.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit l.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout l.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -121,6 +137,7 @@ func (l *LakeBTC) Setup(exch *config.ExchangeConfig) error { RunningURL: exch.API.Endpoints.WebsocketURL, Connector: l.WsConnect, Subscriber: l.Subscribe, + Features: &l.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go index 25c75f2c..3c909081 100644 --- a/exchanges/lbank/lbank_test.go +++ b/exchanges/lbank/lbank_test.go @@ -34,18 +34,18 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatalf("Test Failed - Lbank Setup() init error:, %v", err) + t.Fatalf("Lbank Setup() init error:, %v", err) } lbankConfig, err := cfg.GetExchangeConfig("Lbank") if err != nil { - t.Fatalf("Test Failed - Lbank Setup() init error: %v", err) + t.Fatalf("Lbank Setup() init error: %v", err) } lbankConfig.API.AuthenticatedSupport = true lbankConfig.API.Credentials.Secret = testAPISecret lbankConfig.API.Credentials.Key = testAPIKey err = l.Setup(lbankConfig) if err != nil { - t.Fatal("Test Failed - LBank setup error", err) + t.Fatal("LBank setup error", err) } setupRan = true } @@ -58,7 +58,7 @@ func TestGetTicker(t *testing.T) { TestSetup(t) _, err := l.GetTicker("btc_usdt") if err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } } @@ -66,7 +66,7 @@ func TestGetTickers(t *testing.T) { TestSetup(t) tickers, err := l.GetTickers() if err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } if len(tickers) <= 1 { t.Errorf("Expected multiple tickers, received %v", len(tickers)) @@ -77,7 +77,7 @@ func TestGetCurrencyPairs(t *testing.T) { TestSetup(t) _, err := l.GetCurrencyPairs() if err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } } @@ -97,11 +97,11 @@ func TestGetTrades(t *testing.T) { TestSetup(t) _, err := l.GetTrades("btc_usdt", "600", fmt.Sprintf("%v", time.Now().Unix())) if err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } a, err := l.GetTrades("btc_usdt", "600", "0") if len(a) != 600 && err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } } @@ -109,7 +109,7 @@ func TestGetKlines(t *testing.T) { TestSetup(t) _, err := l.GetKlines("btc_usdt", "600", "minute1", fmt.Sprintf("%v", time.Now().Unix())) if err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } } @@ -145,15 +145,15 @@ func TestCreateOrder(t *testing.T) { cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_") _, err := l.CreateOrder(cp.Lower().String(), "what", 1231, 12314) if err == nil { - t.Error("Test Failed - CreateOrder error cannot be nil") + t.Error("CreateOrder error cannot be nil") } _, err = l.CreateOrder(cp.Lower().String(), "buy", 0, 0) if err == nil { - t.Error("Test Failed - CreateOrder error cannot be nil") + t.Error("CreateOrder error cannot be nil") } _, err = l.CreateOrder(cp.Lower().String(), "sell", 1231, 0) if err == nil { - t.Error("Test Failed - CreateOrder error cannot be nil") + t.Error("CreateOrder error cannot be nil") } _, err = l.CreateOrder(cp.Lower().String(), "buy", 58, 681) if err != nil { @@ -193,7 +193,7 @@ func TestQueryOrderHistory(t *testing.T) { cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_") _, err := l.QueryOrderHistory(cp.Lower().String(), "1", "100") if err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } } @@ -289,7 +289,7 @@ func TestLoadPrivKey(t *testing.T) { l.API.Credentials.Secret = "errortest" err = l.loadPrivKey() if err == nil { - t.Errorf("expected error due to pemblock nil, got err: %v", err) + t.Errorf("Expected error due to pemblock nil") } } @@ -302,7 +302,7 @@ func TestSign(t *testing.T) { l.loadPrivKey() _, err := l.sign("hello123") if err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } } @@ -343,7 +343,7 @@ func TestCancelOrder(t *testing.T) { a.OrderID = "24f7ce27-af1d-4dca-a8c1-ef1cbeec1b23" err := l.CancelOrder(&a) if err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } } @@ -354,7 +354,7 @@ func TestGetOrderInfo(t *testing.T) { } _, err := l.GetOrderInfo("9ead39f5-701a-400b-b635-d7349eb0f6b") if err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } } @@ -365,7 +365,7 @@ func TestGetAllOpenOrderID(t *testing.T) { } _, err := l.getAllOpenOrderID() if err != nil { - t.Errorf("test failed: %v", err) + t.Errorf("%v", err) } } @@ -378,7 +378,7 @@ func TestGetFeeByType(t *testing.T) { input.Pair = cp a, err := l.GetFeeByType(&input) if err != nil { - t.Errorf("test failed. couldnt get fee: %v", err) + t.Errorf("couldnt get fee: %v", err) } if a != 0.0005 { t.Errorf("testGetFeeByType failed. Expected: 0.0005, Received: %v", a) diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index 2090dcc4..5f4e90e8 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -13,6 +13,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -67,9 +68,23 @@ func (l *Lbank) SetDefaults() { l.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrder: true, + SubmitOrder: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoWithdrawal: true, + TradeFee: true, + CryptoWithdrawalFee: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.NoFiatWithdrawals, diff --git a/exchanges/localbitcoins/localbitcoins_live_test.go b/exchanges/localbitcoins/localbitcoins_live_test.go index 1fa86101..332da00d 100644 --- a/exchanges/localbitcoins/localbitcoins_live_test.go +++ b/exchanges/localbitcoins/localbitcoins_live_test.go @@ -19,11 +19,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - LocalBitcoins load config error", err) + log.Fatal("LocalBitcoins load config error", err) } localbitcoinsConfig, err := cfg.GetExchangeConfig("LocalBitcoins") if err != nil { - log.Fatal("Test Failed - LocalBitcoins Setup() init error", err) + log.Fatal("LocalBitcoins Setup() init error", err) } localbitcoinsConfig.API.AuthenticatedSupport = true localbitcoinsConfig.API.Credentials.Key = apiKey @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { l.SetDefaults() err = l.Setup(localbitcoinsConfig) if err != nil { - log.Fatal("Test Failed - Localbitcoins setup error", err) + log.Fatal("Localbitcoins setup error", err) } log.Printf(sharedtestvalues.LiveTesting, l.GetName(), l.API.Endpoints.URL) os.Exit(m.Run()) diff --git a/exchanges/localbitcoins/localbitcoins_mock_test.go b/exchanges/localbitcoins/localbitcoins_mock_test.go index a09aa423..40992afe 100644 --- a/exchanges/localbitcoins/localbitcoins_mock_test.go +++ b/exchanges/localbitcoins/localbitcoins_mock_test.go @@ -22,11 +22,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Localbitcoins load config error", err) + log.Fatal("Localbitcoins load config error", err) } localbitcoinsConfig, err := cfg.GetExchangeConfig("LocalBitcoins") if err != nil { - log.Fatal("Test Failed - Localbitcoins Setup() init error", err) + log.Fatal("Localbitcoins Setup() init error", err) } l.SkipAuthCheck = true localbitcoinsConfig.API.AuthenticatedSupport = true @@ -35,12 +35,12 @@ func TestMain(m *testing.M) { l.SetDefaults() err = l.Setup(localbitcoinsConfig) if err != nil { - log.Fatal("Test Failed - Localbitcoins setup error", err) + log.Fatal("Localbitcoins setup error", err) } serverDetails, newClient, err := mock.NewVCRServer(mockfile) if err != nil { - log.Fatalf("Test Failed - Mock server error %s", err) + log.Fatalf("Mock server error %s", err) } l.HTTPClient = newClient diff --git a/exchanges/localbitcoins/localbitcoins_test.go b/exchanges/localbitcoins/localbitcoins_test.go index d99d9a0e..647de2c7 100644 --- a/exchanges/localbitcoins/localbitcoins_test.go +++ b/exchanges/localbitcoins/localbitcoins_test.go @@ -23,7 +23,7 @@ func TestGetTicker(t *testing.T) { _, err := l.GetTicker() if err != nil { - t.Errorf("Test failed - GetTicker() returned: %s", err) + t.Errorf("GetTicker() returned: %s", err) } } @@ -32,7 +32,7 @@ func TestGetTradableCurrencies(t *testing.T) { _, err := l.GetTradableCurrencies() if err != nil { - t.Errorf("Test failed - GetTradableCurrencies() returned: %s", err) + t.Errorf("GetTradableCurrencies() returned: %s", err) } } @@ -112,7 +112,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) } // CryptocurrencyTradeFee High quantity @@ -120,7 +120,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -128,7 +128,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -136,14 +136,14 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } // CryptocurrencyWithdrawalFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -152,7 +152,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -160,7 +160,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -168,7 +168,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -177,7 +177,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := l.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -327,7 +327,7 @@ func TestModifyOrder(t *testing.T) { _, err := l.ModifyOrder(&exchange.ModifyOrder{}) if err != common.ErrFunctionNotSupported { - t.Error("Test failed - ModifyOrder() error", err) + t.Error("ModifyOrder() error", err) } } @@ -388,10 +388,10 @@ func TestGetDepositAddress(t *testing.T) { _, err := l.GetDepositAddress(currency.BTC, "") switch { case areTestAPIKeysSet() && err != nil && !mockTests: - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) case !areTestAPIKeysSet() && err == nil && !mockTests: - t.Error("Test Failed - GetDepositAddress() expecting an error when no APIKeys are set") + t.Error("GetDepositAddress() expecting an error when no APIKeys are set") case mockTests && err != nil: - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() error", err) } } diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 67671a14..020e3be0 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -15,6 +15,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -70,9 +71,19 @@ func (l *LocalBitcoins) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: false, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + CancelOrder: true, + SubmitOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.WithdrawFiatViaWebsiteOnly, diff --git a/exchanges/mock/README.md b/exchanges/mock/README.md index 8734035c..d8eea5c3 100644 --- a/exchanges/mock/README.md +++ b/exchanges/mock/README.md @@ -56,7 +56,7 @@ func TestMain(m *testing.M) { cfg.LoadConfig("../../testdata/configtest.json") your_current_exchange_nameConfig, err := cfg.GetExchangeConfig("your_current_exchange_name") if err != nil { - log.Fatal("Test Failed - your_current_exchange_name Setup() init error", err) + log.Fatal("your_current_exchange_name Setup() init error", err) } your_current_exchange_nameConfig.AuthenticatedAPISupport = true your_current_exchange_nameConfig.APIKey = apiKey @@ -96,7 +96,7 @@ func TestMain(m *testing.M) { cfg.LoadConfig("../../testdata/configtest.json") your_current_exchange_nameConfig, err := cfg.GetExchangeConfig("your_current_exchange_name") if err != nil { - log.Fatal("Test Failed - your_current_exchange_name Setup() init error", err) + log.Fatal("your_current_exchange_name Setup() init error", err) } your_current_exchange_nameConfig.AuthenticatedAPISupport = true your_current_exchange_nameConfig.APIKey = apiKey @@ -106,7 +106,7 @@ func TestMain(m *testing.M) { serverDetails, newClient, err := mock.NewVCRServer(mockfile) if err != nil { - log.Fatalf("Test Failed - Mock server error %s", err) + log.Fatalf("Mock server error %s", err) } g.HTTPClient = newClient diff --git a/exchanges/mock/common_test.go b/exchanges/mock/common_test.go index d06269dc..ae7ede80 100644 --- a/exchanges/mock/common_test.go +++ b/exchanges/mock/common_test.go @@ -19,42 +19,42 @@ func TestMatchURLVals(t *testing.T) { var expected = false received := MatchURLVals(testVal, emptyVal) if received != expected { - t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + t.Errorf("MatchURLVals error expected %v received %v", expected, received) } received = MatchURLVals(emptyVal, testVal) if received != expected { - t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + t.Errorf("MatchURLVals error expected %v received %v", expected, received) } received = MatchURLVals(testVal, testVal2) if received != expected { - t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + t.Errorf("MatchURLVals error expected %v received %v", expected, received) } received = MatchURLVals(testVal2, testVal) if received != expected { - t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + t.Errorf("MatchURLVals error expected %v received %v", expected, received) } received = MatchURLVals(testVal, testVal3) if received != expected { - t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + t.Errorf("MatchURLVals error expected %v received %v", expected, received) } received = MatchURLVals(nonceVal1, testVal2) if received != expected { - t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + t.Errorf("MatchURLVals error expected %v received %v", expected, received) } @@ -62,21 +62,21 @@ func TestMatchURLVals(t *testing.T) { expected = true received = MatchURLVals(emptyVal, emptyVal) if received != expected { - t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + t.Errorf("MatchURLVals error expected %v received %v", expected, received) } received = MatchURLVals(testVal, testVal) if received != expected { - t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + t.Errorf("MatchURLVals error expected %v received %v", expected, received) } received = MatchURLVals(nonceVal1, nonceVal2) if received != expected { - t.Errorf("Test Failed - MatchURLVals error expected %v received %v", + t.Errorf("MatchURLVals error expected %v received %v", expected, received) } @@ -105,12 +105,12 @@ func TestDeriveURLValsFromJSON(t *testing.T) { payload, err := json.Marshal(test1) if err != nil { - t.Error("Test Failed - marshal error", err) + t.Error("marshal error", err) } _, err = DeriveURLValsFromJSONMap(payload) if err != nil { - t.Error("Test Failed - DeriveURLValsFromJSON error", err) + t.Error("DeriveURLValsFromJSON error", err) } test2 := map[string]string{ @@ -125,16 +125,16 @@ func TestDeriveURLValsFromJSON(t *testing.T) { payload, err = json.Marshal(test2) if err != nil { - t.Error("Test Failed - marshal error", err) + t.Error("marshal error", err) } vals, err := DeriveURLValsFromJSONMap(payload) if err != nil { - t.Error("Test Failed - DeriveURLValsFromJSON error", err) + t.Error("DeriveURLValsFromJSON error", err) } if vals["val"][0] != "1" { - t.Error("Test Failed - DeriveURLValsFromJSON unexpected value", + t.Error("DeriveURLValsFromJSON unexpected value", vals["val"][0]) } } diff --git a/exchanges/mock/recording_test.go b/exchanges/mock/recording_test.go index 978c0c2b..3198e32f 100644 --- a/exchanges/mock/recording_test.go +++ b/exchanges/mock/recording_test.go @@ -19,7 +19,7 @@ func TestGetFilteredHeader(t *testing.T) { } if fMap.Get("Key") != "" { - t.Error("Test Failed - risky vals where not replaced correctly") + t.Error("risky vals where not replaced correctly") } } @@ -29,11 +29,11 @@ func TestGetFilteredURLVals(t *testing.T) { shadyVals.Set("real_name", superSecretData) cleanVals, err := GetFilteredURLVals(shadyVals) if err != nil { - t.Error("Test Failed - GetFilteredURLVals error", err) + t.Error("GetFilteredURLVals error", err) } if strings.Contains(cleanVals, superSecretData) { - t.Error("Test Failed - Super secret data found") + t.Error("Super secret data found") } } @@ -46,12 +46,12 @@ func TestCheckResponsePayload(t *testing.T) { payload, err := json.Marshal(testbody) if err != nil { - t.Fatal("Test Failed - json marshal error", err) + t.Fatal("json marshal error", err) } data, err := CheckResponsePayload(payload) if err != nil { - t.Error("Test Failed - CheckBody error", err) + t.Error("CheckBody error", err) } expected := `{ @@ -126,23 +126,23 @@ func TestCheckJSON(t *testing.T) { exclusionList, err := GetExcludedItems() if err != nil { - t.Error("Test Failed - GetExcludedItems error", err) + t.Error("GetExcludedItems error", err) } vals, err := CheckJSON(testVal, &exclusionList) if err != nil { - t.Error("Test Failed - Check JSON error", err) + t.Error("Check JSON error", err) } payload, err := json.Marshal(vals) if err != nil { - t.Fatal("Test Failed - json marshal error", err) + t.Fatal("json marshal error", err) } newStruct := TestStructLevel0{} err = json.Unmarshal(payload, &newStruct) if err != nil { - t.Fatal("Test Failed - Umarshal error", err) + t.Fatal("Umarshal error", err) } if newStruct.StructVal.BadVal != "" { @@ -173,14 +173,14 @@ func TestCheckJSON(t *testing.T) { func TestGetExcludedItems(t *testing.T) { exclusionList, err := GetExcludedItems() if err != nil { - t.Error("Test Failed - GetExcludedItems error", err) + t.Error("GetExcludedItems error", err) } if len(exclusionList.Headers) == 0 { - t.Error("Test Failed - Header exclusion list not popoulated") + t.Error("Header exclusion list not popoulated") } if len(exclusionList.Variables) == 0 { - t.Error("Test Failed - Variable exclusion list not popoulated") + t.Error("Variable exclusion list not popoulated") } } diff --git a/exchanges/mock/server_test.go b/exchanges/mock/server_test.go index 370d1a31..68042057 100644 --- a/exchanges/mock/server_test.go +++ b/exchanges/mock/server_test.go @@ -24,7 +24,7 @@ const testFile = "test.json" func TestNewVCRServer(t *testing.T) { _, _, err := NewVCRServer("") if err == nil { - t.Error("Test Failed - NewVCRServer error cannot be nil") + t.Error("NewVCRServer error cannot be nil") } // Set up mock data @@ -36,7 +36,7 @@ func TestNewVCRServer(t *testing.T) { Amount: 1, Currency: "bitcoin"}) if err != nil { - t.Fatal("Test Failed - marshal error", err) + t.Fatal("marshal error", err) } testValue := HTTPResponse{Data: rp, QueryString: queryString, BodyParams: queryString} @@ -44,17 +44,17 @@ func TestNewVCRServer(t *testing.T) { payload, err := json.Marshal(test1) if err != nil { - t.Fatal("Test Failed - marshal error", err) + t.Fatal("marshal error", err) } err = ioutil.WriteFile(testFile, payload, os.ModePerm) if err != nil { - t.Fatal("Test Failed - marshal error", err) + t.Fatal("marshal error", err) } deets, client, err := NewVCRServer(testFile) if err != nil { - t.Error("Test Failed - NewVCRServer error", err) + t.Error("NewVCRServer error", err) } common.HTTPClient = client // Set common package global HTTP Client @@ -64,7 +64,7 @@ func TestNewVCRServer(t *testing.T) { nil, bytes.NewBufferString("")) if err == nil { - t.Error("Test Failed - Sending http request expected an error") + t.Error("Sending http request expected an error") } // Expected good outcome @@ -73,11 +73,11 @@ func TestNewVCRServer(t *testing.T) { nil, bytes.NewBufferString("")) if err != nil { - t.Error("Test Failed - Sending http request error", err) + t.Error("Sending http request error", err) } if !strings.Contains(r, "404 page not found") { - t.Error("Test Failed - Was not expecting any value returned:", r) + t.Error("Was not expecting any value returned:", r) } r, err = common.SendHTTPRequest(http.MethodGet, @@ -85,33 +85,33 @@ func TestNewVCRServer(t *testing.T) { nil, bytes.NewBufferString("")) if err != nil { - t.Error("Test Failed - Sending http request error", err) + t.Error("Sending http request error", err) } var res responsePayload err = json.Unmarshal([]byte(r), &res) if err != nil { - t.Error("Test Failed - unmarshal error", err) + t.Error("unmarshal error", err) } if res.Price != 8000 { - t.Error("Test Failed - response error expected 8000 but received:", + t.Error("response error expected 8000 but received:", res.Price) } if res.Amount != 1 { - t.Error("Test Failed - response error expected 1 but received:", + t.Error("response error expected 1 but received:", res.Amount) } if res.Currency != "bitcoin" { - t.Error("Test Failed - response error expected \"bitcoin\" but received:", + t.Error("response error expected \"bitcoin\" but received:", res.Currency) } // clean up test.json file err = os.Remove(testFile) if err != nil { - t.Fatal("Test Failed - Remove error", err) + t.Fatal("Remove error", err) } } diff --git a/exchanges/nonce/nonce_test.go b/exchanges/nonce/nonce_test.go index a4923bed..3ddc290b 100644 --- a/exchanges/nonce/nonce_test.go +++ b/exchanges/nonce/nonce_test.go @@ -12,7 +12,7 @@ func TestInc(t *testing.T) { expected := Value(2) result := nonce.Get() if result != expected { - t.Errorf("Test failed. Expected %d got %d", expected, result) + t.Errorf("Expected %d got %d", expected, result) } } @@ -22,7 +22,7 @@ func TestGet(t *testing.T) { expected := Value(112321313) result := nonce.Get() if expected != result { - t.Errorf("Test failed. Expected %d got %d", expected, result) + t.Errorf("Expected %d got %d", expected, result) } } @@ -32,7 +32,7 @@ func TestGetInc(t *testing.T) { expected := Value(2) result := nonce.GetInc() if expected != result { - t.Errorf("Test failed. Expected %d got %d", expected, result) + t.Errorf("Expected %d got %d", expected, result) } } @@ -42,7 +42,7 @@ func TestSet(t *testing.T) { expected := Value(1) result := nonce.Get() if expected != result { - t.Errorf("Test failed. Expected %d got %d", expected, result) + t.Errorf("Expected %d got %d", expected, result) } } @@ -52,12 +52,12 @@ func TestString(t *testing.T) { expected := "12312313131" result := nonce.String() if expected != result { - t.Errorf("Test failed. Expected %s got %s", expected, result) + t.Errorf("Expected %s got %s", expected, result) } v := nonce.Get() if expected != v.String() { - t.Errorf("Test failed. Expected %s got %s", expected, result) + t.Errorf("Expected %s got %s", expected, result) } } @@ -75,6 +75,6 @@ func TestNonceConcurrency(t *testing.T) { result := nonce.Get() expected := Value(12312 + 1000) if expected != result { - t.Errorf("Test failed. Expected %d got %d", expected, result) + t.Errorf("Expected %d got %d", expected, result) } } diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 8c35ed07..25f7bf1e 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -38,7 +38,7 @@ func TestSetDefaults(t *testing.T) { o.SetDefaults() } if o.GetName() != OKGroupExchange { - t.Errorf("Test Failed - %v - SetDefaults() error", OKGroupExchange) + t.Errorf("%v - SetDefaults() error", OKGroupExchange) } TestSetup(t) } @@ -64,11 +64,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - Okcoin load config error", err) + t.Fatal("Okcoin load config error", err) } okcoinConfig, err := cfg.GetExchangeConfig(OKGroupExchange) if err != nil { - t.Fatalf("Test Failed - %v Setup() init error", OKGroupExchange) + t.Fatalf("%v Setup() init error", OKGroupExchange) } if okcoinConfig.Features.Enabled.Websocket { websocketEnabled = true @@ -82,7 +82,7 @@ func TestSetup(t *testing.T) { okcoinConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL err = o.Setup(okcoinConfig) if err != nil { - t.Fatal("Test Failed - OKCoin setup error", err) + t.Fatal("OKCoin setup error", err) } testSetupRan = true o.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() @@ -990,42 +990,42 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := o.GetFee(feeBuilder); resp != float64(0.0015) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) } // CryptocurrencyTradeFee High quantity feeBuilder = setFeeBuilder() feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := o.GetFee(feeBuilder); resp != float64(1500) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(1500), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(1500), resp) t.Error(err) } // CryptocurrencyTradeFee IsMaker feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := o.GetFee(feeBuilder); resp != float64(0.0005) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) t.Error(err) } // CryptocurrencyTradeFee Negative purchase price feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } // CyptocurrencyDepositFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } // InternationalBankDepositFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } // InternationalBankWithdrawalFee Basic @@ -1033,7 +1033,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index ef65153f..ce5a2a92 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -73,9 +74,37 @@ func (o *OKCoin) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrder: true, + CancelOrders: true, + SubmitOrder: true, + SubmitOrders: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + KlineFetching: true, + OrderbookFetching: true, + Subscribe: true, + Unsubscribe: true, + AuthenticatedEndpoints: true, + MessageCorrelation: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.NoFiatWithdrawals, @@ -96,14 +125,6 @@ func (o *OKCoin) SetDefaults() { o.API.Endpoints.WebsocketURL = okCoinWebsocketURL o.APIVersion = okCoinAPIVersion o.Websocket = wshandler.New() - o.Websocket.Functionality = wshandler.WebsocketTickerSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketKlineSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported | - wshandler.WebsocketMessageCorrelationSupported o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout o.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index 506ec7ab..b565cec8 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -39,7 +39,7 @@ func TestSetDefaults(t *testing.T) { o.SetDefaults() } if o.GetName() != OKGroupExchange { - t.Errorf("Test Failed - %v - SetDefaults() error", OKGroupExchange) + t.Errorf("%v - SetDefaults() error", OKGroupExchange) } TestSetup(t) } @@ -65,12 +65,12 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - Okex load config error", err) + t.Fatal("Okex load config error", err) } okexConfig, err := cfg.GetExchangeConfig(OKGroupExchange) if err != nil { - t.Fatalf("Test Failed - %v Setup() init error", OKGroupExchange) + t.Fatalf("%v Setup() init error", OKGroupExchange) } if okexConfig.Features.Enabled.Websocket { websocketEnabled = true @@ -83,7 +83,7 @@ func TestSetup(t *testing.T) { okexConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL err = o.Setup(okexConfig) if err != nil { - t.Fatal("Test Failed - Okex setup error", err) + t.Fatal("Okex setup error", err) } testSetupRan = true o.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() @@ -1748,7 +1748,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := o.GetFee(feeBuilder); resp != float64(0.0015) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) } // CryptocurrencyTradeFee High quantity @@ -1756,7 +1756,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := o.GetFee(feeBuilder); resp != float64(1500) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(1500), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(1500), resp) t.Error(err) } @@ -1764,7 +1764,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := o.GetFee(feeBuilder); resp != float64(0.0005) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0005), resp) t.Error(err) } @@ -1772,7 +1772,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -1780,7 +1780,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -1788,7 +1788,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -1797,7 +1797,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := o.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go index 9d01460a..3e1b96f2 100644 --- a/exchanges/okex/okex_wrapper.go +++ b/exchanges/okex/okex_wrapper.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -90,9 +91,37 @@ func (o *OKEX) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrder: true, + CancelOrders: true, + SubmitOrder: true, + SubmitOrders: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + KlineFetching: true, + OrderbookFetching: true, + Subscribe: true, + Unsubscribe: true, + AuthenticatedEndpoints: true, + MessageCorrelation: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.NoFiatWithdrawals, @@ -113,14 +142,6 @@ func (o *OKEX) SetDefaults() { o.API.Endpoints.WebsocketURL = OkExWebsocketURL o.Websocket = wshandler.New() o.APIVersion = okExAPIVersion - o.Websocket.Functionality = wshandler.WebsocketTickerSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketKlineSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported | - wshandler.WebsocketMessageCorrelationSupported o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout o.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index a0ae6828..ee74e1a0 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -42,6 +42,7 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) error { Connector: o.WsConnect, Subscriber: o.Subscribe, UnSubscriber: o.Unsubscribe, + Features: &o.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 03db6595..9389ad08 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -56,7 +56,7 @@ func TestSubscribeOrderbook(t *testing.T) { err = b.Process() if err != nil { - t.Error("test failed - process error", err) + t.Error("process error", err) } _, err = SubscribeOrderbook("SubscribeOBTest", p, asset.Spot) @@ -67,7 +67,7 @@ func TestSubscribeOrderbook(t *testing.T) { // process redundant update err = b.Process() if err != nil { - t.Error("test failed - process error", err) + t.Error("process error", err) } } @@ -125,7 +125,7 @@ func TestSubscribeToExchangeOrderbooks(t *testing.T) { err = b.Process() if err != nil { - t.Error("test failed:", err) + t.Error("", err) } _, err = SubscribeToExchangeOrderbooks("SubscribeToExchangeOrderbooks") @@ -167,7 +167,7 @@ func TestCalculateTotalBids(t *testing.T) { a, b := base.TotalBidsAmount() if a != 10 && b != 1000 { - t.Fatal("Test failed. TestCalculateTotalBids expected a = 10 and b = 1000") + t.Fatal("TestCalculateTotalBids expected a = 10 and b = 1000") } } @@ -181,7 +181,7 @@ func TestCalculateTotaAsks(t *testing.T) { a, b := base.TotalAsksAmount() if a != 10 && b != 1000 { - t.Fatal("Test failed. TestCalculateTotalAsks expected a = 10 and b = 1000") + t.Fatal("TestCalculateTotalAsks expected a = 10 and b = 1000") } } @@ -202,17 +202,17 @@ func TestUpdate(t *testing.T) { base.Update(bids, asks) if !base.LastUpdated.After(timeNow) { - t.Fatal("test failed. TestUpdate expected LastUpdated to be greater then original time") + t.Fatal("TestUpdate expected LastUpdated to be greater then original time") } a, b := base.TotalAsksAmount() if a != 100 && b != 20200 { - t.Fatal("Test failed. TestUpdate expected a = 100 and b = 20100") + t.Fatal("TestUpdate expected a = 100 and b = 20100") } a, b = base.TotalBidsAmount() if a != 100 && b != 20100 { - t.Fatal("Test failed. TestUpdate expected a = 100 and b = 20100") + t.Fatal("TestUpdate expected a = 100 and b = 20100") } } @@ -233,28 +233,28 @@ func TestGetOrderbook(t *testing.T) { result, err := Get("Exchange", c, asset.Spot) if err != nil { - t.Fatalf("Test failed. TestGetOrderbook failed to get orderbook. Error %s", + t.Fatalf("TestGetOrderbook failed to get orderbook. Error %s", err) } if !result.Pair.Equal(c) { - t.Fatal("Test failed. TestGetOrderbook failed. Mismatched pairs") + t.Fatal("TestGetOrderbook failed. Mismatched pairs") } _, err = Get("nonexistent", c, asset.Spot) if err == nil { - t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook") + t.Fatal("TestGetOrderbook retrieved non-existent orderbook") } c.Base = currency.NewCode("blah") _, err = Get("Exchange", c, asset.Spot) if err == nil { - t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook using invalid first currency") + t.Fatal("TestGetOrderbook retrieved non-existent orderbook using invalid first currency") } newCurrency := currency.NewPairFromStrings("BTC", "AUD") _, err = Get("Exchange", newCurrency, asset.Spot) if err == nil { - t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook using invalid second currency") + t.Fatal("TestGetOrderbook retrieved non-existent orderbook using invalid second currency") } base.Pair = newCurrency @@ -286,21 +286,21 @@ func TestCreateNewOrderbook(t *testing.T) { result, err := Get("testCreateNewOrderbook", c, asset.Spot) if err != nil { - t.Fatal("Test failed. TestCreateNewOrderbook failed to create new orderbook", err) + t.Fatal("TestCreateNewOrderbook failed to create new orderbook", err) } if !result.Pair.Equal(c) { - t.Fatal("Test failed. TestCreateNewOrderbook result pair is incorrect") + t.Fatal("TestCreateNewOrderbook result pair is incorrect") } a, b := result.TotalAsksAmount() if a != 10 && b != 1000 { - t.Fatal("Test failed. TestCreateNewOrderbook CalculateTotalAsks value is incorrect") + t.Fatal("TestCreateNewOrderbook CalculateTotalAsks value is incorrect") } a, b = result.TotalBidsAmount() if a != 10 && b != 2000 { - t.Fatal("Test failed. TestCreateNewOrderbook CalculateTotalBids value is incorrect") + t.Fatal("TestCreateNewOrderbook CalculateTotalBids value is incorrect") } } @@ -334,10 +334,10 @@ func TestProcessOrderbook(t *testing.T) { } result, err := Get("ProcessOrderbook", c, asset.Spot) if err != nil { - t.Fatal("Test failed. TestProcessOrderbook failed to create new orderbook") + t.Fatal("TestProcessOrderbook failed to create new orderbook") } if !result.Pair.Equal(c) { - t.Fatal("Test failed. TestProcessOrderbook result pair is incorrect") + t.Fatal("TestProcessOrderbook result pair is incorrect") } // now test for processing a pair with a different quote currency @@ -345,14 +345,14 @@ func TestProcessOrderbook(t *testing.T) { base.Pair = c err = base.Process() if err != nil { - t.Error("Test Failed - Process() error", err) + t.Error("Process() error", err) } result, err = Get("ProcessOrderbook", c, asset.Spot) if err != nil { - t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") + t.Fatal("TestProcessOrderbook failed to retrieve new orderbook") } if !result.Pair.Equal(c) { - t.Fatal("Test failed. TestProcessOrderbook result pair is incorrect") + t.Fatal("TestProcessOrderbook result pair is incorrect") } // now test for processing a pair which has a different base currency @@ -360,31 +360,31 @@ func TestProcessOrderbook(t *testing.T) { base.Pair = c err = base.Process() if err != nil { - t.Error("Test Failed - Process() error", err) + t.Error("Process() error", err) } result, err = Get("ProcessOrderbook", c, asset.Spot) if err != nil { - t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") + t.Fatal("TestProcessOrderbook failed to retrieve new orderbook") } if !result.Pair.Equal(c) { - t.Fatal("Test failed. TestProcessOrderbook result pair is incorrect") + t.Fatal("TestProcessOrderbook result pair is incorrect") } base.Asks = []Item{{Price: 200, Amount: 200}} base.AssetType = "monthly" err = base.Process() if err != nil { - t.Error("Test Failed - Process() error", err) + t.Error("Process() error", err) } result, err = Get("ProcessOrderbook", c, "monthly") if err != nil { - t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") + t.Fatal("TestProcessOrderbook failed to retrieve new orderbook") } a, b := result.TotalAsksAmount() if a != 200 && b != 40000 { - t.Fatal("Test failed. TestProcessOrderbook CalculateTotalsAsks incorrect values") + t.Fatal("TestProcessOrderbook CalculateTotalsAsks incorrect values") } base.Bids = []Item{{Price: 420, Amount: 200}} @@ -392,16 +392,16 @@ func TestProcessOrderbook(t *testing.T) { base.AssetType = "quarterly" err = base.Process() if err != nil { - t.Error("Test Failed - Process() error", err) + t.Error("Process() error", err) } result, err = Get("Blah", c, "quarterly") if err != nil { - t.Fatal("Test failed. TestProcessOrderbook failed to create new orderbook") + t.Fatal("TestProcessOrderbook failed to create new orderbook") } if a != 200 && b != 84000 { - t.Fatal("Test failed. TestProcessOrderbook CalculateTotalsBids incorrect values") + t.Fatal("TestProcessOrderbook CalculateTotalsBids incorrect values") } type quick struct { @@ -456,7 +456,7 @@ func TestProcessOrderbook(t *testing.T) { } if catastrophicFailure { - t.Fatal("Test Failed - Process() error", err) + t.Fatal("Process() error", err) } wg.Wait() @@ -472,18 +472,18 @@ func TestProcessOrderbook(t *testing.T) { } if result.Asks[0] != test.Asks[0] { - t.Error("Test failed. TestProcessOrderbook failed bad values") + t.Error("TestProcessOrderbook failed bad values") } if result.Bids[0] != test.Bids[0] { - t.Error("Test failed. TestProcessOrderbook failed bad values") + t.Error("TestProcessOrderbook failed bad values") } wg.Done() }(test) if fatalErr { - t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") + t.Fatal("TestProcessOrderbook failed to retrieve new orderbook") } } @@ -493,13 +493,13 @@ func TestProcessOrderbook(t *testing.T) { func TestSetNewData(t *testing.T) { err := service.SetNewData(nil) if err == nil { - t.Error("error cannot be nil ") + t.Error("error cannot be nil") } } func TestGetAssociations(t *testing.T) { _, err := service.GetAssociations(nil) if err == nil { - t.Error("error cannot be nil ") + t.Error("error cannot be nil") } } diff --git a/exchanges/orders/orders_test.go b/exchanges/orders/orders_test.go index 4237165f..0a7eef4e 100644 --- a/exchanges/orders/orders_test.go +++ b/exchanges/orders/orders_test.go @@ -7,31 +7,31 @@ import ( func TestNewOrder(t *testing.T) { ID := NewOrder("ANX", 2000, 20.00) if ID != 0 { - t.Error("Test Failed - Orders_test.go NewOrder() - Error") + t.Error("Orders_test.go NewOrder() - Error") } ID = NewOrder("BATMAN", 400, 25.00) if ID != 1 { - t.Error("Test Failed - Orders_test.go NewOrder() - Error") + t.Error("Orders_test.go NewOrder() - Error") } } func TestDeleteOrder(t *testing.T) { if value := DeleteOrder(0); !value { - t.Error("Test Failed - Orders_test.go DeleteOrder() - Error") + t.Error("Orders_test.go DeleteOrder() - Error") } if value := DeleteOrder(100); value { - t.Error("Test Failed - Orders_test.go DeleteOrder() - Error") + t.Error("Orders_test.go DeleteOrder() - Error") } } func TestGetOrdersByExchange(t *testing.T) { if value := GetOrdersByExchange("ANX"); len(value) != 0 { - t.Error("Test Failed - Orders_test.go GetOrdersByExchange() - Error") + t.Error("Orders_test.go GetOrdersByExchange() - Error") } } func TestGetOrderByOrderID(t *testing.T) { if value := GetOrderByOrderID(69); value != nil { - t.Error("Test Failed - Orders_test.go GetOrdersByExchange() - Error") + t.Error("Orders_test.go GetOrdersByExchange() - Error") } } diff --git a/exchanges/poloniex/poloniex_live_test.go b/exchanges/poloniex/poloniex_live_test.go index 7eb685c9..26d83788 100644 --- a/exchanges/poloniex/poloniex_live_test.go +++ b/exchanges/poloniex/poloniex_live_test.go @@ -19,11 +19,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Poloniex load config error", err) + log.Fatal("Poloniex load config error", err) } poloniexConfig, err := cfg.GetExchangeConfig("Poloniex") if err != nil { - log.Fatal("Test Failed - Poloniex Setup() init error", err) + log.Fatal("Poloniex Setup() init error", err) } poloniexConfig.API.AuthenticatedSupport = true poloniexConfig.API.Credentials.Key = apiKey @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { p.SetDefaults() err = p.Setup(poloniexConfig) if err != nil { - log.Fatal("Test Failed - Poloniex setup error", err) + log.Fatal("Poloniex setup error", err) } log.Printf(sharedtestvalues.LiveTesting, p.GetName(), p.API.Endpoints.URL) os.Exit(m.Run()) diff --git a/exchanges/poloniex/poloniex_mock_test.go b/exchanges/poloniex/poloniex_mock_test.go index 96748300..2e7205dc 100644 --- a/exchanges/poloniex/poloniex_mock_test.go +++ b/exchanges/poloniex/poloniex_mock_test.go @@ -22,11 +22,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Test Failed - Poloniex load config error", err) + log.Fatal("Poloniex load config error", err) } poloniexConfig, err := cfg.GetExchangeConfig("Poloniex") if err != nil { - log.Fatal("Test Failed - Poloniex Setup() init error", err) + log.Fatal("Poloniex Setup() init error", err) } p.SkipAuthCheck = true poloniexConfig.API.AuthenticatedSupport = true @@ -35,12 +35,12 @@ func TestMain(m *testing.M) { p.SetDefaults() err = p.Setup(poloniexConfig) if err != nil { - log.Fatal("Test Failed - Poloniex setup error", err) + log.Fatal("Poloniex setup error", err) } serverDetails, newClient, err := mock.NewVCRServer(mockfile) if err != nil { - log.Fatalf("Test Failed - Mock server error %s", err) + log.Fatalf("Mock server error %s", err) } p.HTTPClient = newClient diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index bed54367..5107ada9 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -30,7 +30,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := p.GetTicker() if err != nil { - t.Error("Test Failed - Poloniex GetTicker() error", err) + t.Error("Poloniex GetTicker() error", err) } } @@ -124,7 +124,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := p.GetFee(feeBuilder); resp != float64(0.0025) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0025), resp) } @@ -133,7 +133,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := p.GetFee(feeBuilder); resp != float64(2500) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2500), resp) t.Error(err) } @@ -142,7 +142,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := p.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -151,7 +151,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := p.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) t.Error(err) } @@ -161,7 +161,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := p.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -170,7 +170,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := p.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -179,7 +179,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := p.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -189,7 +189,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := p.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -219,11 +219,11 @@ func TestGetActiveOrders(t *testing.T) { _, err := p.GetActiveOrders(&getOrdersRequest) switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - GetActiveOrders() error", err) + t.Error("GetActiveOrders() error", err) case !areTestAPIKeysSet() && !mockTests && err == nil: - t.Error("Test Failed - Expecting an error when no keys are set") + t.Error("Expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock GetActiveOrders() err", err) + t.Error("Mock GetActiveOrders() err", err) } } @@ -273,7 +273,7 @@ func TestSubmitOrder(t *testing.T) { case !areTestAPIKeysSet() && !mockTests && err == nil: t.Error("Expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock SubmitOrder() err", err) + t.Error("Mock SubmitOrder() err", err) } } @@ -297,7 +297,7 @@ func TestCancelExchangeOrder(t *testing.T) { case areTestAPIKeysSet() && err != nil: t.Errorf("Could not cancel orders: %v", err) case mockTests && err != nil: - t.Error("Test Failed - Mock CancelExchangeOrder() err", err) + t.Error("Mock CancelExchangeOrder() err", err) } } @@ -323,7 +323,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { case areTestAPIKeysSet() && err != nil: t.Errorf("Could not cancel orders: %v", err) case mockTests && err != nil: - t.Error("Test Failed - Mock CancelAllExchangeOrders() err", err) + t.Error("Mock CancelAllExchangeOrders() err", err) } if len(resp.OrderStatus) > 0 { t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) @@ -339,11 +339,11 @@ func TestModifyOrder(t *testing.T) { _, err := p.ModifyOrder(&exchange.ModifyOrder{OrderID: "1337", Price: 1337}) switch { case areTestAPIKeysSet() && err != nil && mockTests: - t.Error("Test Failed - ModifyOrder() error", err) + t.Error("ModifyOrder() error", err) case !areTestAPIKeysSet() && !mockTests && err == nil: - t.Error("Test Failed - ModifyOrder() error cannot be nil") + t.Error("ModifyOrder() error cannot be nil") case mockTests && err != nil: - t.Error("Test Failed - Mock ModifyOrder() err", err) + t.Error("Mock ModifyOrder() err", err) } } @@ -369,7 +369,7 @@ func TestWithdraw(t *testing.T) { case !areTestAPIKeysSet() && !mockTests && err == nil: t.Error("Expecting an error when no keys are set") case mockTests && err != nil: - t.Error("Test Failed - Mock Withdraw() err", err) + t.Error("Mock Withdraw() err", err) } } @@ -406,11 +406,11 @@ func TestGetDepositAddress(t *testing.T) { _, err := p.GetDepositAddress(currency.DASH, "") switch { case areTestAPIKeysSet() && err != nil: - t.Error("Test Failed - GetDepositAddress()", err) + t.Error("GetDepositAddress()", err) case !areTestAPIKeysSet() && !mockTests && err == nil: - t.Error("Test Failed - GetDepositAddress() cannot be nil") + t.Error("GetDepositAddress() cannot be nil") case mockTests && err != nil: - t.Error("Test Failed - Mock GetDepositAddress() err", err) + t.Error("Mock GetDepositAddress() err", err) } } diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 2febf687..6477eda0 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -13,6 +13,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -69,9 +70,34 @@ func (p *Poloniex) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrder: true, + CancelOrders: true, + SubmitOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + Subscribe: true, + Unsubscribe: true, + AuthenticatedEndpoints: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.NoFiatWithdrawals, @@ -90,12 +116,6 @@ func (p *Poloniex) SetDefaults() { p.API.Endpoints.URL = p.API.Endpoints.URLDefault p.API.Endpoints.WebsocketURL = poloniexWebsocketAddress p.Websocket = wshandler.New() - p.Websocket.Functionality = wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketTickerSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketUnsubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported p.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit p.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout p.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -125,6 +145,7 @@ func (p *Poloniex) Setup(exch *config.ExchangeConfig) error { Connector: p.WsConnect, Subscriber: p.Subscribe, UnSubscriber: p.Unsubscribe, + Features: &p.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/exchanges/protocol/features.go b/exchanges/protocol/features.go new file mode 100644 index 00000000..09e24396 --- /dev/null +++ b/exchanges/protocol/features.go @@ -0,0 +1,40 @@ +package protocol + +// Features holds all variables for the exchanges supported features +// for a protocol (e.g REST or Websocket) +type Features struct { + TickerBatching bool `json:"tickerBatching,omitempty"` + AutoPairUpdates bool `json:"autoPairUpdates,omitempty"` + AccountBalance bool `json:"accountBalance,omitempty"` + CryptoDeposit bool `json:"cryptoDeposit,omitempty"` + CryptoWithdrawal bool `json:"cryptoWithdrawal,omitempty"` + FiatWithdraw bool `json:"fiatWithdraw,omitempty"` + GetOrder bool `json:"getOrder,omitempty"` + GetOrders bool `json:"getOrders,omitempty"` + CancelOrders bool `json:"cancelOrders,omitempty"` + CancelOrder bool `json:"cancelOrder,omitempty"` + SubmitOrder bool `json:"submitOrder,omitempty"` + SubmitOrders bool `json:"submitOrders,omitempty"` + ModifyOrder bool `json:"modifyOrder,omitempty"` + DepositHistory bool `json:"depositHistory,omitempty"` + WithdrawalHistory bool `json:"withdrawalHistory,omitempty"` + TradeHistory bool `json:"tradeHistory,omitempty"` + UserTradeHistory bool `json:"userTradeHistory,omitempty"` + TradeFee bool `json:"tradeFee,omitempty"` + FiatDepositFee bool `json:"fiatDepositFee,omitempty"` + FiatWithdrawalFee bool `json:"fiatWithdrawalFee,omitempty"` + CryptoDepositFee bool `json:"cryptoDepositFee,omitempty"` + CryptoWithdrawalFee bool `json:"cryptoWithdrawalFee,omitempty"` + TickerFetching bool `json:"tickerFetching,omitempty"` + KlineFetching bool `json:"klineFetching,omitempty"` + TradeFetching bool `json:"tradeFetching,omitempty"` + OrderbookFetching bool `json:"orderbookFetching,omitempty"` + AccountInfo bool `json:"accountInfo,omitempty"` + FiatDeposit bool `json:"fiatDeposit,omitempty"` + DeadMansSwitch bool `json:"deadMansSwitch,omitempty"` + Subscribe bool `json:"subscribe,omitempty"` + Unsubscribe bool `json:"unsubscribe,omitempty"` + AuthenticatedEndpoints bool `json:"authenticatedEndpoints,omitempty"` + MessageCorrelation bool `json:"messageCorrelation,omitempty"` + MessageSequenceNumbers bool `json:"messageSequenceNumbers,omitempty"` +} diff --git a/exchanges/request/request_test.go b/exchanges/request/request_test.go index b93eb826..9acf9a58 100644 --- a/exchanges/request/request_test.go +++ b/exchanges/request/request_test.go @@ -202,32 +202,29 @@ func TestDoRequest(t *testing.T) { var test = new(Requester) err := test.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false, false) if err == nil { - t.Fatal("not iniitalised") + t.Fatal("Expected error") } r := New("", NewRateLimit(time.Second*10, 5), NewRateLimit(time.Second*20, 100), new(http.Client)) - if err == nil { - t.Fatal("unexpected values") - } r.Name = "bitfinex" err = r.SendPayload("BLAH", "https://www.google.com", nil, nil, nil, false, false, true, false, false) if err == nil { - t.Fatal("unexpected values") + t.Fatal("Expected error") } err = r.SendPayload(http.MethodGet, "", nil, nil, nil, false, false, true, false, false) if err == nil { - t.Fatal("unexpected values") + t.Fatal("Expected error") } err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false, false) if err != nil { - t.Fatal("unexpected values") + t.Fatal("unexpected values", err) } if !r.RequiresRateLimiter() { - t.Fatal("unexpcted values") + t.Fatal("unexpected values") } r.SetRateLimit(false, time.Second, 0) @@ -235,7 +232,7 @@ func TestDoRequest(t *testing.T) { err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false, false) if err != nil { - t.Fatal("unexpected values") + t.Fatal("unexpected values", err) } if r.RequiresRateLimiter() { @@ -247,7 +244,7 @@ func TestDoRequest(t *testing.T) { r.Cycle = time.Now().Add(time.Millisecond * -201) if r.IsValidCycle(false) { - t.Fatal("unexepcted values") + t.Fatal("unexpected values") } err = r.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false, false) @@ -288,18 +285,18 @@ func TestDoRequest(t *testing.T) { err = r.SetTimeoutRetryAttempts(1) if err != nil { - t.Fatal("test failed - setting timeout retry attempts") + t.Fatal("setting timeout retry attempts") } err = r.SetTimeoutRetryAttempts(-1) if err == nil { - t.Fatal("test failed - setting timeout retry attempts with negative value") + t.Fatal("setting timeout retry attempts with negative value") } r.HTTPClient.Timeout = 1 * time.Second err = r.SendPayload(http.MethodPost, "https://httpstat.us/200?sleep=20000", nil, nil, nil, false, false, true, false, false) if err == nil { - t.Fatal(err) + t.Fatal("Expected error") } proxy, err := url.Parse("") @@ -309,7 +306,7 @@ func TestDoRequest(t *testing.T) { err = r.SetProxy(proxy) if err == nil { - t.Error("failed to set proxy") + t.Error("Expected error") } proxy, err = url.Parse("https://192.0.0.1") diff --git a/exchanges/stats/stats_test.go b/exchanges/stats/stats_test.go index e5aa2cea..0e22edd6 100644 --- a/exchanges/stats/stats_test.go +++ b/exchanges/stats/stats_test.go @@ -20,7 +20,7 @@ func TestLenByPrice(t *testing.T) { } if ByPrice.Len(Items) < 1 { - t.Error("Test Failed - stats LenByPrice() length not correct.") + t.Error("stats LenByPrice() length not correct.") } } @@ -45,10 +45,10 @@ func TestLessByPrice(t *testing.T) { } if !ByPrice.Less(Items, 1, 0) { - t.Error("Test Failed - stats LessByPrice() incorrect return.") + t.Error("stats LessByPrice() incorrect return.") } if ByPrice.Less(Items, 0, 1) { - t.Error("Test Failed - stats LessByPrice() incorrect return.") + t.Error("stats LessByPrice() incorrect return.") } } @@ -74,22 +74,22 @@ func TestSwapByPrice(t *testing.T) { ByPrice.Swap(Items, 0, 1) if Items[0].Exchange != "bitfinex" || Items[1].Exchange != "bitstamp" { - t.Error("Test Failed - stats SwapByPrice did not swap values.") + t.Error("stats SwapByPrice did not swap values.") } } func TestLenByVolume(t *testing.T) { if ByVolume.Len(Items) != 2 { - t.Error("Test Failed - stats lenByVolume did not swap values.") + t.Error("stats lenByVolume did not swap values.") } } func TestLessByVolume(t *testing.T) { if !ByVolume.Less(Items, 1, 0) { - t.Error("Test Failed - stats LessByVolume() incorrect return.") + t.Error("stats LessByVolume() incorrect return.") } if ByVolume.Less(Items, 0, 1) { - t.Error("Test Failed - stats LessByVolume() incorrect return.") + t.Error("stats LessByVolume() incorrect return.") } } @@ -97,7 +97,7 @@ func TestSwapByVolume(t *testing.T) { ByPrice.Swap(Items, 0, 1) if Items[1].Exchange != "bitfinex" || Items[0].Exchange != "bitstamp" { - t.Error("Test Failed - stats SwapByVolume did not swap values.") + t.Error("stats SwapByVolume did not swap values.") } } @@ -107,27 +107,27 @@ func TestAdd(t *testing.T) { Add("ANX", p, asset.Spot, 1200, 42) if len(Items) < 1 { - t.Error("Test Failed - stats Add did not add exchange info.") + t.Error("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.") + t.Error("stats Add did not add exchange info.") } p.Base = currency.XBT Add("ANX", p, asset.Spot, 1201, 43) if Items[1].Pair.String() != "XBTUSD" { - t.Fatal("Test failed. stats Add did not add exchange info.") + t.Fatal("stats Add did not add exchange info.") } p = currency.NewPairFromStrings("ETH", "USDT") Add("ANX", p, asset.Spot, 300, 1000) if Items[2].Pair.String() != "ETHUSD" { - t.Fatal("Test failed. stats Add did not add exchange info.") + t.Fatal("stats Add did not add exchange info.") } } @@ -135,23 +135,23 @@ func TestAppend(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") Append("sillyexchange", p, asset.Spot, 1234, 45) if len(Items) < 2 { - t.Error("Test Failed - stats Append did not add exchange values.") + t.Error("stats Append did not add exchange values.") } Append("sillyexchange", p, asset.Spot, 1234, 45) if len(Items) == 3 { - t.Error("Test Failed - stats Append added exchange values") + t.Error("stats Append added exchange values") } } func TestAlreadyExists(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") if !AlreadyExists("ANX", p, asset.Spot, 1200, 42) { - t.Error("Test Failed - stats AlreadyExists exchange does not exist.") + t.Error("stats AlreadyExists exchange does not exist.") } p.Base = currency.NewCode("dii") if AlreadyExists("bla", p, asset.Spot, 1234, 123) { - t.Error("Test Failed - stats AlreadyExists found incorrect exchange.") + t.Error("stats AlreadyExists found incorrect exchange.") } } @@ -159,12 +159,12 @@ func TestSortExchangesByVolume(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") topVolume := SortExchangesByVolume(p, asset.Spot, true) if topVolume[0].Exchange != "sillyexchange" { - t.Error("Test Failed - stats SortExchangesByVolume incorrectly sorted values.") + t.Error("stats SortExchangesByVolume incorrectly sorted values.") } topVolume = SortExchangesByVolume(p, asset.Spot, false) if topVolume[0].Exchange != "ANX" { - t.Error("Test Failed - stats SortExchangesByVolume incorrectly sorted values.") + t.Error("stats SortExchangesByVolume incorrectly sorted values.") } } @@ -172,11 +172,11 @@ func TestSortExchangesByPrice(t *testing.T) { p := currency.NewPairFromStrings("BTC", "USD") topPrice := SortExchangesByPrice(p, asset.Spot, true) if topPrice[0].Exchange != "sillyexchange" { - t.Error("Test Failed - stats SortExchangesByPrice incorrectly sorted values.") + t.Error("stats SortExchangesByPrice incorrectly sorted values.") } topPrice = SortExchangesByPrice(p, asset.Spot, false) if topPrice[0].Exchange != "ANX" { - t.Error("Test Failed - stats SortExchangesByPrice incorrectly sorted values.") + t.Error("stats SortExchangesByPrice incorrectly sorted values.") } } diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index 91e6c9ab..9cc686d9 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -107,64 +107,64 @@ func TestGetTicker(t *testing.T) { err := ProcessTicker("bitfinex", &priceStruct, asset.Spot) if err != nil { - t.Fatal("Test failed. ProcessTicker error", err) + t.Fatal("ProcessTicker error", err) } tickerPrice, err := GetTicker("bitfinex", newPair, asset.Spot) if err != nil { - t.Errorf("Test Failed - Ticker GetTicker init error: %s", err) + t.Errorf("Ticker GetTicker init error: %s", err) } if !tickerPrice.Pair.Equal(newPair) { - t.Error("Test Failed - ticker tickerPrice.CurrencyPair value is incorrect") + t.Error("ticker tickerPrice.CurrencyPair value is incorrect") } _, err = GetTicker("blah", newPair, asset.Spot) if err == nil { - t.Fatal("Test Failed. TestGetTicker returned nil error on invalid exchange") + t.Fatal("TestGetTicker returned nil error on invalid exchange") } newPair.Base = currency.ETH _, err = GetTicker("bitfinex", newPair, asset.Spot) if err == nil { - t.Fatal("Test Failed. TestGetTicker returned ticker for invalid first currency") + t.Fatal("TestGetTicker returned ticker for invalid first currency") } btcltcPair := currency.NewPairFromStrings("BTC", "LTC") _, err = GetTicker("bitfinex", btcltcPair, asset.Spot) if err == nil { - t.Fatal("Test Failed. TestGetTicker returned ticker for invalid second currency") + t.Fatal("TestGetTicker returned ticker for invalid second currency") } priceStruct.PriceATH = 9001 priceStruct.Pair.Base = currency.ETH err = ProcessTicker("bitfinex", &priceStruct, "futures_3m") if err != nil { - t.Fatal("Test failed. ProcessTicker error", err) + t.Fatal("ProcessTicker error", err) } tickerPrice, err = GetTicker("bitfinex", newPair, "futures_3m") if err != nil { - t.Errorf("Test Failed - Ticker GetTicker init error: %s", err) + t.Errorf("Ticker GetTicker init error: %s", err) } if tickerPrice.PriceATH != 9001 { - t.Error("Test Failed - ticker tickerPrice.PriceATH value is incorrect") + t.Error("ticker tickerPrice.PriceATH value is incorrect") } _, err = GetTicker("bitfinex", newPair, "meowCats") if err == nil { - t.Error("Test Failed - Ticker GetTicker error cannot be nil") + t.Error("Ticker GetTicker error cannot be nil") } err = ProcessTicker("bitfinex", &priceStruct, "meowCats") if err != nil { - t.Fatal("Test failed. ProcessTicker error", err) + t.Fatal("ProcessTicker error", err) } // process update again err = ProcessTicker("bitfinex", &priceStruct, "meowCats") if err != nil { - t.Fatal("Test failed. ProcessTicker error", err) + t.Fatal("ProcessTicker error", err) } } @@ -196,20 +196,20 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers priceStruct.Pair = newPair err = ProcessTicker(exchName, &priceStruct, "") if err == nil { - t.Fatal("Test failed. ProcessTicker error cannot be nil") + t.Fatal("ProcessTicker error cannot be nil") } // now process a valid ticker err = ProcessTicker(exchName, &priceStruct, asset.Spot) if err != nil { - t.Fatal("Test failed. ProcessTicker error", err) + t.Fatal("ProcessTicker error", err) } result, err := GetTicker(exchName, newPair, asset.Spot) if err != nil { - t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker") + t.Fatal("TestProcessTicker failed to create and return a new ticker") } if !result.Pair.Equal(newPair) { - t.Fatal("Test failed. TestProcessTicker pair mismatch") + t.Fatal("TestProcessTicker pair mismatch") } // now test for processing a pair with a different quote currency @@ -217,15 +217,15 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers priceStruct.Pair = newPair err = ProcessTicker(exchName, &priceStruct, asset.Spot) if err != nil { - t.Fatal("Test failed. ProcessTicker error", err) + t.Fatal("ProcessTicker error", err) } result, err = GetTicker(exchName, newPair, asset.Spot) if err != nil { - t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker") + t.Fatal("TestProcessTicker failed to create and return a new ticker") } result, err = GetTicker(exchName, newPair, asset.Spot) if err != nil { - t.Fatal("Test failed. TestProcessTicker failed to return an existing ticker") + t.Fatal("TestProcessTicker failed to return an existing ticker") } // now test for processing a pair which has a different base currency @@ -233,15 +233,15 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers priceStruct.Pair = newPair err = ProcessTicker(exchName, &priceStruct, asset.Spot) if err != nil { - t.Fatal("Test failed. ProcessTicker error", err) + t.Fatal("ProcessTicker error", err) } result, err = GetTicker(exchName, newPair, asset.Spot) if err != nil { - t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker") + t.Fatal("TestProcessTicker failed to create and return a new ticker") } result, err = GetTicker(exchName, newPair, asset.Spot) if err != nil { - t.Fatal("Test failed. TestProcessTicker failed to return an existing ticker") + t.Fatal("TestProcessTicker failed to return an existing ticker") } type quick struct { @@ -289,7 +289,7 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers } if catastrophicFailure { - t.Fatal("Test failed. ProcessTicker error") + t.Fatal("ProcessTicker error") } wg.Wait() @@ -305,14 +305,14 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers } if result.Last != test.TP.Last { - t.Error("Test failed. TestProcessTicker failed bad values") + t.Error("TestProcessTicker failed bad values") } wg.Done() }(test) if fatalErr { - t.Fatal("Test failed. TestProcessTicker failed to retrieve new ticker") + t.Fatal("TestProcessTicker failed to retrieve new ticker") } } wg.Wait() @@ -343,7 +343,7 @@ func TestSetItemID(t *testing.T) { func TestGetAssociation(t *testing.T) { _, err := service.GetAssociations(nil) if err == nil { - t.Error("error cannot be nil ") + t.Error("error cannot be nil") } p := currency.NewPair(currency.CYC, currency.CYG) @@ -352,7 +352,7 @@ func TestGetAssociation(t *testing.T) { _, err = service.GetAssociations(&Price{Pair: p, ExchangeName: "GetAssociation"}) if err == nil { - t.Error("error cannot be nil ") + t.Error("error cannot be nil") } service.mux = cpyMux diff --git a/exchanges/websocket/wshandler/wshandler.go b/exchanges/websocket/wshandler/wshandler.go index caa29869..596a631b 100644 --- a/exchanges/websocket/wshandler/wshandler.go +++ b/exchanges/websocket/wshandler/wshandler.go @@ -45,6 +45,7 @@ func (w *Websocket) Setup(setupData *WebsocketSetup) error { w.SetExchangeName(setupData.ExchangeName) w.SetCanUseAuthenticatedEndpoints(setupData.AuthenticatedWebsocketAPISupport) w.trafficTimeout = setupData.WebsocketTimeout + w.features = setupData.Features err := w.Initialise() if err != nil { return err @@ -91,7 +92,7 @@ func (w *Websocket) Connect() error { if !w.IsConnectionMonitorRunning() { go w.connectionMonitor() } - if w.SupportsFunctionality(WebsocketSubscribeSupported) || w.SupportsFunctionality(WebsocketUnsubscribeSupported) { + if w.features.Subscribe || w.features.Unsubscribe { w.Wg.Add(1) go w.manageSubscriptions() } @@ -402,87 +403,6 @@ func (w *Websocket) GetName() string { return w.exchangeName } -// GetFunctionality returns a functionality bitmask for the websocket -// connection -func (w *Websocket) GetFunctionality() uint32 { - return w.Functionality -} - -// SupportsFunctionality returns if the functionality is supported as a boolean -func (w *Websocket) SupportsFunctionality(f uint32) bool { - return w.GetFunctionality()&f == f -} - -// FormatFunctionality will return each of the websocket connection compatible -// stream methods as a string -func (w *Websocket) FormatFunctionality() string { - var functionality []string - for i := 0; i < 32; i++ { - var check uint32 = 1 << uint32(i) - if w.GetFunctionality()&check != 0 { - switch check { - case WebsocketTickerSupported: - functionality = append(functionality, WebsocketTickerSupportedText) - - case WebsocketOrderbookSupported: - functionality = append(functionality, WebsocketOrderbookSupportedText) - - case WebsocketKlineSupported: - functionality = append(functionality, WebsocketKlineSupportedText) - - case WebsocketTradeDataSupported: - functionality = append(functionality, WebsocketTradeDataSupportedText) - - case WebsocketAccountSupported: - functionality = append(functionality, WebsocketAccountSupportedText) - - case WebsocketAllowsRequests: - functionality = append(functionality, WebsocketAllowsRequestsText) - - case WebsocketSubscribeSupported: - functionality = append(functionality, WebsocketSubscribeSupportedText) - - case WebsocketUnsubscribeSupported: - functionality = append(functionality, WebsocketUnsubscribeSupportedText) - - case WebsocketAuthenticatedEndpointsSupported: - functionality = append(functionality, WebsocketAuthenticatedEndpointsSupportedText) - - case WebsocketAccountDataSupported: - functionality = append(functionality, WebsocketAccountDataSupportedText) - - case WebsocketSubmitOrderSupported: - functionality = append(functionality, WebsocketSubmitOrderSupportedText) - - case WebsocketCancelOrderSupported: - functionality = append(functionality, WebsocketCancelOrderSupportedText) - - case WebsocketWithdrawSupported: - functionality = append(functionality, WebsocketWithdrawSupportedText) - - case WebsocketMessageCorrelationSupported: - functionality = append(functionality, WebsocketMessageCorrelationSupportedText) - - case WebsocketSequenceNumberSupported: - functionality = append(functionality, WebsocketSequenceNumberSupportedText) - - case WebsocketDeadMansSwitchSupported: - functionality = append(functionality, WebsocketDeadMansSwitchSupportedText) - - default: - functionality = append(functionality, - fmt.Sprintf("%s[1<<%v]", UnknownWebsocketFunctionality, i)) - } - } - } - - if len(functionality) > 0 { - return strings.Join(functionality, " & ") - } - - return NoWebsocketSupportText -} - // SetChannelSubscriber sets the function to use the base subscribe func func (w *Websocket) SetChannelSubscriber(subscriber func(channelToSubscribe WebsocketChannelSubscription) error) { w.channelSubscriber = subscriber @@ -495,7 +415,7 @@ func (w *Websocket) SetChannelUnsubscriber(unsubscriber func(channelToUnsubscrib // ManageSubscriptions ensures the subscriptions specified continue to be subscribed to func (w *Websocket) manageSubscriptions() { - if !w.SupportsFunctionality(WebsocketSubscribeSupported) && !w.SupportsFunctionality(WebsocketUnsubscribeSupported) { + if !w.features.Subscribe && !w.features.Unsubscribe { w.DataHandler <- fmt.Errorf("%v does not support channel subscriptions, exiting ManageSubscriptions()", w.exchangeName) return } @@ -528,13 +448,13 @@ func (w *Websocket) manageSubscriptions() { log.Debugf(log.WebsocketMgr, "%v checking subscriptions", w.exchangeName) } // Subscribe to channels Pending a subscription - if w.SupportsFunctionality(WebsocketSubscribeSupported) { + if w.features.Subscribe { err := w.appendSubscribedChannels() if err != nil { w.DataHandler <- err } } - if w.SupportsFunctionality(WebsocketUnsubscribeSupported) { + if w.features.Unsubscribe { err := w.unsubscribeToChannels() if err != nil { w.DataHandler <- err diff --git a/exchanges/websocket/wshandler/wshandler_test.go b/exchanges/websocket/wshandler/wshandler_test.go index dc293cf1..0c7cb7ec 100644 --- a/exchanges/websocket/wshandler/wshandler_test.go +++ b/exchanges/websocket/wshandler/wshandler_test.go @@ -16,6 +16,7 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" ) func TestTrafficMonitorTimeout(t *testing.T) { @@ -85,6 +86,7 @@ func TestConnectionMessageErrors(t *testing.T) { ws.DataHandler = make(chan interface{}) ws.ShutdownC = make(chan struct{}) ws.connector = func() error { return nil } + ws.features = &protocol.Features{} go ws.connectionMonitor() timer := time.NewTimer(900 * time.Millisecond) ws.ReadMessageErrors <- errors.New("errorText") @@ -126,7 +128,7 @@ func TestWebsocket(t *testing.T) { ws = *New() err = ws.SetProxyAddress("testProxy") if err != nil { - t.Error("test failed - SetProxyAddress", err) + t.Error("SetProxyAddress", err) } err = ws.Setup( @@ -140,102 +142,60 @@ func TestWebsocket(t *testing.T) { Connector: func() error { return nil }, Subscriber: func(test WebsocketChannelSubscription) error { return nil }, UnSubscriber: func(test WebsocketChannelSubscription) error { return nil }, + Features: &protocol.Features{}, }) if err != nil { t.Error(err) } if ws.GetName() != "exchangeName" { - t.Error("test failed - WebsocketSetup") + t.Error("WebsocketSetup") } if !ws.IsEnabled() { - t.Error("test failed - WebsocketSetup") + t.Error("WebsocketSetup") } if ws.GetProxyAddress() != "testProxy" { - t.Error("test failed - WebsocketSetup") + t.Error("WebsocketSetup") } if ws.GetDefaultURL() != "testDefaultURL" { - t.Error("test failed - WebsocketSetup") + t.Error("WebsocketSetup") } if ws.GetWebsocketURL() != "testRunningURL" { - t.Error("test failed - WebsocketSetup") + t.Error("WebsocketSetup") } if ws.trafficTimeout != time.Duration(2) { - t.Error("test failed - WebsocketSetup") + t.Error("WebsocketSetup") } // -- Not connected shutdown err = ws.Shutdown() if err == nil { - t.Fatal("test failed - should not be connected to able to shut down") + t.Fatal("should not be connected to able to shut down") } ws.Wg.Wait() // -- Normal connect err = ws.Connect() if err != nil { - t.Fatal("test failed - WebsocketSetup", err) + t.Fatal("WebsocketSetup", err) } ws.SetWebsocketURL("ws://demos.kaazing.com/echo") // -- Already connected connect err = ws.Connect() if err == nil { - t.Fatal("test failed - should not connect, already connected") + t.Fatal("should not connect, already connected") } // -- Normal shutdown err = ws.Shutdown() if err != nil { - t.Fatal("test failed - WebsocketSetup", err) + t.Fatal("WebsocketSetup", err) } ws.Wg.Wait() } -func TestFunctionality(t *testing.T) { - ws := New() - if ws.FormatFunctionality() != NoWebsocketSupportText { - t.Fatalf("Test Failed - FormatFunctionality error expected %s but received %s", - NoWebsocketSupportText, ws.FormatFunctionality()) - } - - ws.Functionality = 1 << 31 - - if ws.FormatFunctionality() != UnknownWebsocketFunctionality+"[1<<31]" { - t.Fatal("Test Failed - GetFunctionality error incorrect error returned") - } - - ws.Functionality = WebsocketOrderbookSupported - - if ws.GetFunctionality() != WebsocketOrderbookSupported { - t.Fatal("Test Failed - GetFunctionality error incorrect bitmask returned") - } - - if !ws.SupportsFunctionality(WebsocketOrderbookSupported) { - t.Fatal("Test Failed - SupportsFunctionality error should be true") - } - - ws.Functionality = WebsocketTickerSupported | WebsocketOrderbookSupported | WebsocketKlineSupported | - WebsocketTradeDataSupported | WebsocketAccountSupported | WebsocketAllowsRequests | - WebsocketSubscribeSupported | WebsocketUnsubscribeSupported | WebsocketAuthenticatedEndpointsSupported | - WebsocketAccountDataSupported | WebsocketSubmitOrderSupported | WebsocketCancelOrderSupported | - WebsocketWithdrawSupported | WebsocketMessageCorrelationSupported | WebsocketSequenceNumberSupported | - WebsocketDeadMansSwitchSupported - formatted := ws.FormatFunctionality() - - if !strings.Contains(formatted, WebsocketTickerSupportedText) || !strings.Contains(formatted, WebsocketOrderbookSupportedText) || - !strings.Contains(formatted, WebsocketKlineSupportedText) || !strings.Contains(formatted, WebsocketTradeDataSupportedText) || - !strings.Contains(formatted, WebsocketAccountSupportedText) || !strings.Contains(formatted, WebsocketAllowsRequestsText) || - !strings.Contains(formatted, WebsocketSubscribeSupportedText) || !strings.Contains(formatted, WebsocketUnsubscribeSupportedText) || - !strings.Contains(formatted, WebsocketAuthenticatedEndpointsSupportedText) || !strings.Contains(formatted, WebsocketAccountDataSupportedText) || - !strings.Contains(formatted, WebsocketSubmitOrderSupportedText) || !strings.Contains(formatted, WebsocketCancelOrderSupportedText) || - !strings.Contains(formatted, WebsocketWithdrawSupportedText) || !strings.Contains(formatted, WebsocketMessageCorrelationSupportedText) || - !strings.Contains(formatted, WebsocketSequenceNumberSupportedText) || !strings.Contains(formatted, WebsocketDeadMansSwitchSupportedText) { - t.Error("Failed to format and include supported websocket features") - } -} - // placeholderSubscriber basic function to test subscriptions func placeholderSubscriber(channelToSubscribe WebsocketChannelSubscription) error { return nil @@ -349,8 +309,8 @@ func TestUnsubscriptionWithExistingEntry(t *testing.T) { // TestManageSubscriptionsStartStop logic test func TestManageSubscriptionsStartStop(t *testing.T) { w := Websocket{ - ShutdownC: make(chan struct{}), - Functionality: WebsocketSubscribeSupported | WebsocketUnsubscribeSupported, + ShutdownC: make(chan struct{}), + features: &protocol.Features{Subscribe: true, Unsubscribe: true}, } w.Wg.Add(1) go w.manageSubscriptions() @@ -361,8 +321,8 @@ func TestManageSubscriptionsStartStop(t *testing.T) { // TestManageSubscriptions logic test func TestManageSubscriptions(t *testing.T) { w := Websocket{ - ShutdownC: make(chan struct{}), - Functionality: WebsocketSubscribeSupported | WebsocketUnsubscribeSupported, + ShutdownC: make(chan struct{}), + features: &protocol.Features{Subscribe: true, Unsubscribe: true}, subscribedChannels: []WebsocketChannelSubscription{ { Channel: "hello", diff --git a/exchanges/websocket/wshandler/wshandler_types.go b/exchanges/websocket/wshandler/wshandler_types.go index 6469efe4..c4e9b341 100644 --- a/exchanges/websocket/wshandler/wshandler_types.go +++ b/exchanges/websocket/wshandler/wshandler_types.go @@ -7,47 +7,12 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" ) // Websocket functionality list and state consts const ( - NoWebsocketSupport uint32 = 0 - WebsocketTickerSupported uint32 = 1 << (iota - 1) - WebsocketOrderbookSupported - WebsocketKlineSupported - WebsocketTradeDataSupported - WebsocketAccountSupported - WebsocketAllowsRequests - WebsocketSubscribeSupported - WebsocketUnsubscribeSupported - WebsocketAuthenticatedEndpointsSupported - WebsocketAccountDataSupported - WebsocketSubmitOrderSupported - WebsocketCancelOrderSupported - WebsocketWithdrawSupported - WebsocketMessageCorrelationSupported - WebsocketSequenceNumberSupported - WebsocketDeadMansSwitchSupported - - WebsocketTickerSupportedText = "TICKER STREAMING SUPPORTED" - WebsocketOrderbookSupportedText = "ORDERBOOK STREAMING SUPPORTED" - WebsocketKlineSupportedText = "KLINE STREAMING SUPPORTED" - WebsocketTradeDataSupportedText = "TRADE STREAMING SUPPORTED" - WebsocketAccountSupportedText = "ACCOUNT STREAMING SUPPORTED" - WebsocketAllowsRequestsText = "WEBSOCKET REQUESTS SUPPORTED" - NoWebsocketSupportText = "WEBSOCKET NOT SUPPORTED" - UnknownWebsocketFunctionality = "UNKNOWN FUNCTIONALITY BITMASK" - WebsocketSubscribeSupportedText = "WEBSOCKET SUBSCRIBE SUPPORTED" - WebsocketUnsubscribeSupportedText = "WEBSOCKET UNSUBSCRIBE SUPPORTED" - WebsocketAuthenticatedEndpointsSupportedText = "WEBSOCKET AUTHENTICATED ENDPOINTS SUPPORTED" - WebsocketAccountDataSupportedText = "WEBSOCKET ACCOUNT DATA SUPPORTED" - WebsocketSubmitOrderSupportedText = "WEBSOCKET SUBMIT ORDER SUPPORTED" - WebsocketCancelOrderSupportedText = "WEBSOCKET CANCEL ORDER SUPPORTED" - WebsocketWithdrawSupportedText = "WEBSOCKET WITHDRAW SUPPORTED" - WebsocketMessageCorrelationSupportedText = "WEBSOCKET MESSAGE CORRELATION SUPPORTED" - WebsocketSequenceNumberSupportedText = "WEBSOCKET SEQUENCE NUMBER SUPPORTED" - WebsocketDeadMansSwitchSupportedText = "WEBSOCKET DEAD MANS SWITCH SUPPORTED" // WebsocketNotEnabled alerts of a disabled websocket WebsocketNotEnabled = "exchange_websocket_not_enabled" manageSubscriptionsDelay = 5 * time.Second @@ -58,8 +23,6 @@ const ( // Websocket defines a return type for websocket connections via the interface // wrapper for routine processing in routines.go type Websocket struct { - // Functionality defines websocket stream capabilities - Functionality uint32 canUseAuthenticatedEndpoints bool enabled bool init bool @@ -93,6 +56,7 @@ type Websocket struct { TrafficAlert chan struct{} // ReadMessageErrors will received all errors from ws.ReadMessage() and verify if its a disconnection ReadMessageErrors chan error + features *protocol.Features } type WebsocketSetup struct { @@ -106,6 +70,7 @@ type WebsocketSetup struct { Connector func() error Subscriber func(channelToSubscribe WebsocketChannelSubscription) error UnSubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error + Features *protocol.Features } // WebsocketChannelSubscription container for websocket subscriptions diff --git a/exchanges/websocket/wsorderbook/wsorderbook_test.go b/exchanges/websocket/wsorderbook/wsorderbook_test.go index a60fd067..78e27cf7 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_test.go +++ b/exchanges/websocket/wsorderbook/wsorderbook_test.go @@ -242,6 +242,39 @@ func BenchmarkNoBufferPerformance(b *testing.B) { } } +func TestUpdates(t *testing.T) { + obl, curr, _, _, err := createSnapshot() + if err != nil { + t.Error(err) + } + + obl.updateAsksByPrice(obl.ob[curr][asset.Spot], &WebsocketOrderbookUpdate{ + Bids: itemArray[5], + Asks: itemArray[5], + CurrencyPair: curr, + UpdateTime: time.Now(), + AssetType: asset.Spot, + }) + if err != nil { + t.Error(err) + } + + obl.updateAsksByPrice(obl.ob[curr][asset.Spot], &WebsocketOrderbookUpdate{ + Bids: itemArray[0], + Asks: itemArray[0], + CurrencyPair: curr, + UpdateTime: time.Now(), + AssetType: asset.Spot, + }) + if err != nil { + t.Error(err) + } + + if len(obl.ob[curr][asset.Spot].Asks) != 3 { + t.Error("Did not update") + } +} + // TestHittingTheBuffer logic test func TestHittingTheBuffer(t *testing.T) { obl, curr, _, _, err := createSnapshot() diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index 20193af2..a0d92fa2 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -29,11 +29,11 @@ func TestSetup(t *testing.T) { yobitConfig := config.GetConfig() err := yobitConfig.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - Yobit load config error", err) + t.Fatal("Yobit load config error", err) } conf, err := yobitConfig.GetExchangeConfig("Yobit") if err != nil { - t.Fatal("Test Failed - Yobit init error", err) + t.Fatal("Yobit init error", err) } conf.API.Credentials.Key = apiKey conf.API.Credentials.Secret = apiSecret @@ -41,7 +41,7 @@ func TestSetup(t *testing.T) { err = y.Setup(conf) if err != nil { - t.Fatal("Test Failed - Yobit setup error", err) + t.Fatal("Yobit setup error", err) } } @@ -49,7 +49,7 @@ func TestFetchTradablePairs(t *testing.T) { t.Parallel() _, err := y.FetchTradablePairs(asset.Spot) if err != nil { - t.Errorf("Test failed. FetchTradablePairs err: %s", err) + t.Errorf("FetchTradablePairs err: %s", err) } } @@ -57,7 +57,7 @@ func TestGetInfo(t *testing.T) { t.Parallel() _, err := y.GetInfo() if err != nil { - t.Error("Test Failed - GetInfo() error") + t.Error("GetInfo() error") } } @@ -65,7 +65,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := y.GetTicker("btc_usd") if err != nil { - t.Error("Test Failed - GetTicker() error", err) + t.Error("GetTicker() error", err) } } @@ -73,7 +73,7 @@ func TestGetDepth(t *testing.T) { t.Parallel() _, err := y.GetDepth("btc_usd") if err != nil { - t.Error("Test Failed - GetDepth() error", err) + t.Error("GetDepth() error", err) } } @@ -81,7 +81,7 @@ func TestGetTrades(t *testing.T) { t.Parallel() _, err := y.GetTrades("btc_usd") if err != nil { - t.Error("Test Failed - GetTrades() error", err) + t.Error("GetTrades() error", err) } } @@ -89,7 +89,7 @@ func TestGetAccountInfo(t *testing.T) { t.Parallel() _, err := y.GetAccountInfo() if err == nil { - t.Error("Test Failed - GetAccountInfo() error", err) + t.Error("GetAccountInfo() Expected error") } } @@ -97,7 +97,7 @@ func TestGetOpenOrders(t *testing.T) { t.Parallel() _, err := y.GetOpenOrders("") if err == nil { - t.Error("Test Failed - GetOpenOrders() error", err) + t.Error("GetOpenOrders() Expected error") } } @@ -105,7 +105,7 @@ func TestGetOrderInfo(t *testing.T) { t.Parallel() _, err := y.GetOrderInfo("6196974") if err == nil { - t.Error("Test Failed - GetOrderInfo() error", err) + t.Error("GetOrderInfo() Expected error") } } @@ -113,7 +113,7 @@ func TestCancelOrder(t *testing.T) { t.Parallel() _, err := y.CancelExistingOrder(1337) if err == nil { - t.Error("Test Failed - CancelOrder() error", err) + t.Error("CancelOrder() Expected error") } } @@ -121,7 +121,7 @@ func TestTrade(t *testing.T) { t.Parallel() _, err := y.Trade("", exchange.BuyOrderSide.ToLower().ToString(), 0, 0) if err == nil { - t.Error("Test Failed - Trade() error", err) + t.Error("Trade() Expected error") } } @@ -129,7 +129,7 @@ func TestWithdrawCoinsToAddress(t *testing.T) { t.Parallel() _, err := y.WithdrawCoinsToAddress("", 0, "") if err == nil { - t.Error("Test Failed - WithdrawCoinsToAddress() error", err) + t.Error("WithdrawCoinsToAddress() Expected error") } } @@ -137,7 +137,7 @@ func TestCreateYobicode(t *testing.T) { t.Parallel() _, err := y.CreateCoupon("bla", 0) if err == nil { - t.Error("Test Failed - CreateYobicode() error", err) + t.Error("CreateYobicode() Expected error") } } @@ -145,7 +145,7 @@ func TestRedeemYobicode(t *testing.T) { t.Parallel() _, err := y.RedeemCoupon("bla2") if err == nil { - t.Error("Test Failed - RedeemYobicode() error", err) + t.Error("RedeemYobicode() Expected error") } } @@ -185,7 +185,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := y.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) } // CryptocurrencyTradeFee High quantity @@ -193,7 +193,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := y.GetFee(feeBuilder); resp != float64(2000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2000), resp) t.Error(err) } @@ -201,7 +201,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := y.GetFee(feeBuilder); resp != float64(0.002) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) t.Error(err) } @@ -209,14 +209,14 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := y.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } // CryptocurrencyWithdrawalFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := y.GetFee(feeBuilder); resp != float64(0.002) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) t.Error(err) } @@ -225,7 +225,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := y.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -233,7 +233,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := y.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -241,7 +241,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := y.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -250,7 +250,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := y.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -260,7 +260,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FiatCurrency = currency.USD feeBuilder.BankTransactionType = exchange.Qiwi if resp, err := y.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -270,7 +270,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FiatCurrency = currency.USD feeBuilder.BankTransactionType = exchange.WireTransfer if resp, err := y.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -280,7 +280,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FiatCurrency = currency.USD feeBuilder.BankTransactionType = exchange.Payeer if resp, err := y.GetFee(feeBuilder); resp != float64(0.03) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.03), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.03), resp) t.Error(err) } @@ -290,7 +290,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FiatCurrency = currency.RUR feeBuilder.BankTransactionType = exchange.Capitalist if resp, err := y.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -300,7 +300,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FiatCurrency = currency.USD feeBuilder.BankTransactionType = exchange.AdvCash if resp, err := y.GetFee(feeBuilder); resp != float64(0.04) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.04), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.04), resp) t.Error(err) } @@ -310,7 +310,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FiatCurrency = currency.RUR feeBuilder.BankTransactionType = exchange.PerfectMoney if resp, err := y.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -458,7 +458,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := y.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -521,12 +521,12 @@ func TestGetDepositAddress(t *testing.T) { if apiKey != "" || apiSecret != "" { _, err := y.GetDepositAddress(currency.BTC, "") if err != nil { - t.Error("Test Failed - GetDepositAddress() error", err) + t.Error("GetDepositAddress() Expected error") } } else { _, err := y.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error") + t.Error("GetDepositAddress() error") } } } diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index b2f960a1..c4edd5c0 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -15,6 +15,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -72,9 +73,23 @@ func (y *Yobit) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: false, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrder: true, + UserTradeHistory: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + FiatDepositFee: true, + FiatWithdrawalFee: true, + CryptoWithdrawalFee: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.WithdrawFiatViaWebsiteOnly, diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index 1f0004cb..bbd77cc3 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -31,11 +31,11 @@ func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Test Failed - ZB load config error", err) + t.Fatal("ZB load config error", err) } zbConfig, err := cfg.GetExchangeConfig("ZB") if err != nil { - t.Fatal("Test Failed - ZB Setup() init error", err) + t.Fatal("ZB Setup() init error", err) } zbConfig.API.AuthenticatedSupport = true zbConfig.API.AuthenticatedWebsocketSupport = true @@ -44,7 +44,7 @@ func TestSetup(t *testing.T) { err = z.Setup(zbConfig) if err != nil { - t.Fatal("Test Failed - ZB setup error", err) + t.Fatal("ZB setup error", err) } } @@ -90,7 +90,7 @@ func TestSpotNewOrder(t *testing.T) { } orderid, err := z.SpotNewOrder(arg) if err != nil { - t.Errorf("Test failed - ZB SpotNewOrder: %s", err) + t.Errorf("ZB SpotNewOrder: %s", err) } else { t.Log(orderid) } @@ -105,7 +105,7 @@ func TestCancelExistingOrder(t *testing.T) { err := z.CancelExistingOrder(20180629145864850, "btc_usdt") if err != nil { - t.Errorf("Test failed - ZB CancelExistingOrder: %s", err) + t.Errorf("ZB CancelExistingOrder: %s", err) } } @@ -113,7 +113,7 @@ func TestGetLatestSpotPrice(t *testing.T) { t.Parallel() _, err := z.GetLatestSpotPrice("btc_usdt") if err != nil { - t.Errorf("Test failed - ZB GetLatestSpotPrice: %s", err) + t.Errorf("ZB GetLatestSpotPrice: %s", err) } } @@ -121,7 +121,7 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := z.GetTicker("btc_usdt") if err != nil { - t.Errorf("Test failed - ZB GetTicker: %s", err) + t.Errorf("ZB GetTicker: %s", err) } } @@ -129,7 +129,7 @@ func TestGetTickers(t *testing.T) { t.Parallel() _, err := z.GetTickers() if err != nil { - t.Errorf("Test failed - ZB GetTicker: %s", err) + t.Errorf("ZB GetTicker: %s", err) } } @@ -137,7 +137,7 @@ func TestGetOrderbook(t *testing.T) { t.Parallel() _, err := z.GetOrderbook("btc_usdt") if err != nil { - t.Errorf("Test failed - ZB GetTicker: %s", err) + t.Errorf("ZB GetTicker: %s", err) } } @@ -145,7 +145,7 @@ func TestGetMarkets(t *testing.T) { t.Parallel() _, err := z.GetMarkets() if err != nil { - t.Errorf("Test failed - ZB GetMarkets: %s", err) + t.Errorf("ZB GetMarkets: %s", err) } } @@ -159,7 +159,7 @@ func TestGetSpotKline(t *testing.T) { } _, err := z.GetSpotKline(arg) if err != nil { - t.Errorf("Test failed - ZB GetSpotKline: %s", err) + t.Errorf("ZB GetSpotKline: %s", err) } } @@ -199,7 +199,7 @@ func TestGetFee(t *testing.T) { // CryptocurrencyTradeFee Basic if resp, err := z.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0015), resp) } // CryptocurrencyTradeFee High quantity @@ -207,7 +207,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Amount = 1000 feeBuilder.PurchasePrice = 1000 if resp, err := z.GetFee(feeBuilder); resp != float64(2000) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(2000), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(2000), resp) t.Error(err) } @@ -215,7 +215,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.IsMaker = true if resp, err := z.GetFee(feeBuilder); resp != float64(0.002) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) t.Error(err) } @@ -223,14 +223,14 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.PurchasePrice = -1000 if resp, err := z.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } // CryptocurrencyWithdrawalFee Basic feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := z.GetFee(feeBuilder); resp != float64(0.005) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0.005), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.005), resp) t.Error(err) } @@ -239,7 +239,7 @@ func TestGetFee(t *testing.T) { feeBuilder.Pair.Base = currency.NewCode("hello") feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee if resp, err := z.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -247,7 +247,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.CyptocurrencyDepositFee if resp, err := z.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -255,7 +255,7 @@ func TestGetFee(t *testing.T) { feeBuilder = setFeeBuilder() feeBuilder.FeeType = exchange.InternationalBankDepositFee if resp, err := z.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } @@ -264,7 +264,7 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee feeBuilder.FiatCurrency = currency.USD if resp, err := z.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp) + t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) t.Error(err) } } @@ -413,12 +413,12 @@ func TestGetAccountInfo(t *testing.T) { if z.ValidateAPICredentials() { _, err := z.GetAccountInfo() if err != nil { - t.Error("Test Failed - GetAccountInfo() error", err) + t.Error("GetAccountInfo() error", err) } } else { _, err := z.GetAccountInfo() if err == nil { - t.Error("Test Failed - GetAccountInfo() error") + t.Error("GetAccountInfo() Expected error") } } } @@ -426,7 +426,7 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { _, err := z.ModifyOrder(&exchange.ModifyOrder{}) if err == nil { - t.Error("Test failed - ModifyOrder() error") + t.Error("ModifyOrder() Expected error") } } @@ -490,13 +490,13 @@ func TestGetDepositAddress(t *testing.T) { if apiKey != "" || apiSecret != "" { _, err := z.GetDepositAddress(currency.BTC, "") if err != nil { - t.Error("Test Failed - GetDepositAddress() error PLEASE MAKE SURE YOU CREATE DEPOSIT ADDRESSES VIA ZB.COM", + t.Error("GetDepositAddress() error PLEASE MAKE SURE YOU CREATE DEPOSIT ADDRESSES VIA ZB.COM", err) } } else { _, err := z.GetDepositAddress(currency.BTC, "") if err == nil { - t.Error("Test Failed - GetDepositAddress() error") + t.Error("GetDepositAddress() Expected error") } } } diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 37a2a637..1b56dc54 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -13,6 +13,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -69,9 +70,32 @@ func (z *ZB) SetDefaults() { Supports: exchange.FeaturesSupported{ REST: true, Websocket: true, - RESTCapabilities: exchange.ProtocolFeatures{ - AutoPairUpdates: true, - TickerBatching: true, + RESTCapabilities: protocol.Features{ + TickerBatching: true, + TickerFetching: true, + KlineFetching: true, + OrderbookFetching: true, + AutoPairUpdates: true, + AccountInfo: true, + GetOrder: true, + GetOrders: true, + CancelOrder: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + TradeFee: true, + CryptoDepositFee: true, + CryptoWithdrawalFee: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + TradeFetching: true, + OrderbookFetching: true, + Subscribe: true, + AuthenticatedEndpoints: true, + AccountInfo: true, + CancelOrder: true, + SubmitOrder: true, + MessageCorrelation: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.NoFiatWithdrawals, @@ -92,15 +116,6 @@ func (z *ZB) SetDefaults() { z.API.Endpoints.URLSecondary = z.API.Endpoints.URLSecondaryDefault z.API.Endpoints.WebsocketURL = zbWebsocketAPI z.Websocket = wshandler.New() - z.Websocket.Functionality = wshandler.WebsocketTickerSupported | - wshandler.WebsocketOrderbookSupported | - wshandler.WebsocketTradeDataSupported | - wshandler.WebsocketSubscribeSupported | - wshandler.WebsocketAuthenticatedEndpointsSupported | - wshandler.WebsocketAccountDataSupported | - wshandler.WebsocketCancelOrderSupported | - wshandler.WebsocketSubmitOrderSupported | - wshandler.WebsocketMessageCorrelationSupported z.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit z.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout } @@ -128,6 +143,7 @@ func (z *ZB) Setup(exch *config.ExchangeConfig) error { RunningURL: exch.API.Endpoints.WebsocketURL, Connector: z.WsConnect, Subscriber: z.Subscribe, + Features: &z.Features.Supports.WebsocketCapabilities, }) if err != nil { return err diff --git a/logger/logger_test.go b/logger/logger_test.go index 7af289e0..29fb2ce2 100644 --- a/logger/logger_test.go +++ b/logger/logger_test.go @@ -108,7 +108,7 @@ func TestLevel(t *testing.T) { _, err = Level("totallyinvalidlogger") if err == nil { - t.Error("expected error on invalid logger") + t.Error("Expected error on invalid logger") } } @@ -141,7 +141,7 @@ func TestValidSubLogger(t *testing.T) { t.Skip("validSubLogger() should return found, pointer if valid logger found") } if logPtr == nil { - t.Error("validSubLogger() should return a pointer and not nil ") + t.Error("validSubLogger() should return a pointer and not nil") } } diff --git a/ntpclient/ntpclient_test.go b/ntpclient/ntpclient_test.go index 0668409e..6fe5ee5e 100644 --- a/ntpclient/ntpclient_test.go +++ b/ntpclient/ntpclient_test.go @@ -14,7 +14,7 @@ func TestNTPClient(t *testing.T) { invalidpool := []string{"pool.thisisinvalid.org"} _, err = NTPClient(invalidpool) if err == nil { - t.Errorf("failed to get time %v", err) + t.Errorf("Expected error") } firstInvalid := []string{"pool.thisisinvalid.org", "pool.ntp.org:123", "0.pool.ntp.org:123"} diff --git a/portfolio/portfolio_test.go b/portfolio/portfolio_test.go index 03b75ddd..6334b4e2 100644 --- a/portfolio/portfolio_test.go +++ b/portfolio/portfolio_test.go @@ -14,16 +14,16 @@ func TestGetEthereumBalance(t *testing.T) { response, err := GetEthereumBalance(address) if err != nil { - t.Errorf("Test Failed - Portfolio GetEthereumBalance() Error: %s", err) + t.Errorf("Portfolio GetEthereumBalance() Error: %s", err) } if response.Address != "0xb794f5ea0ba39494ce839613fffba74279579268" { - t.Error("Test Failed - Portfolio GetEthereumBalance() address invalid") + t.Error("Portfolio GetEthereumBalance() address invalid") } response, err = GetEthereumBalance(nonsenseAddress) if response.Error.Message != "" || err == nil { - t.Errorf("Test Failed - Portfolio GetEthereumBalance() Error: %s", + t.Errorf("Portfolio GetEthereumBalance() Error: %s", response.Error.Message) } } @@ -32,7 +32,7 @@ func TestGetCryptoIDBalance(t *testing.T) { ltcAddress := "LX2LMYXtuv5tiYEMztSSoEZcafFPYJFRK1" _, err := GetCryptoIDAddress(ltcAddress, currency.LTC) if err != nil { - t.Fatalf("Test failed. TestGetCryptoIDBalance error: %s", err) + t.Fatalf("TestGetCryptoIDBalance error: %s", err) } } @@ -50,7 +50,7 @@ func TestGetAddressBalance(t *testing.T) { ltc) if addBalance != balance { - t.Error("Test Failed - Portfolio GetAddressBalance() Error: Incorrect value") + t.Error("Portfolio GetAddressBalance() Error: Incorrect value") } addBalance, found := portfolio.GetAddressBalance("WigWham", @@ -58,10 +58,10 @@ func TestGetAddressBalance(t *testing.T) { ltc) if addBalance != 0 { - t.Error("Test Failed - Portfolio GetAddressBalance() Error: Incorrect value") + t.Error("Portfolio GetAddressBalance() Error: Incorrect value") } if found { - t.Error("Test Failed - Portfolio GetAddressBalance() Error: Incorrect value") + t.Error("Portfolio GetAddressBalance() Error: Incorrect value") } } @@ -73,10 +73,10 @@ func TestExchangeExists(t *testing.T) { 0.02) if !newBase.ExchangeExists("someaddress") { - t.Error("Test Failed - portfolio_test.go - AddressExists error") + t.Error("portfolio_test.go - AddressExists error") } if newBase.ExchangeExists("bla") { - t.Error("Test Failed - portfolio_test.go - AddressExists error") + t.Error("portfolio_test.go - AddressExists error") } } @@ -88,10 +88,10 @@ func TestAddressExists(t *testing.T) { 0.02) if !newbase.AddressExists("someaddress") { - t.Error("Test Failed - portfolio_test.go - AddressExists error") + t.Error("portfolio_test.go - AddressExists error") } if newbase.AddressExists("bla") { - t.Error("Test Failed - portfolio_test.go - AddressExists error") + t.Error("portfolio_test.go - AddressExists error") } } @@ -103,10 +103,10 @@ func TestExchangeAddressExists(t *testing.T) { 0.02) if !newbase.ExchangeAddressExists("someaddress", currency.LTC) { - t.Error("Test Failed - portfolio_test.go - ExchangeAddressExists error") + t.Error("portfolio_test.go - ExchangeAddressExists error") } if newbase.ExchangeAddressExists("TEST", currency.LTC) { - t.Error("Test Failed - portfolio_test.go - ExchangeAddressExists error") + t.Error("portfolio_test.go - ExchangeAddressExists error") } } @@ -117,7 +117,7 @@ func TestAddExchangeAddress(t *testing.T) { newbase.AddExchangeAddress("ANX", currency.BTC, 200) if !newbase.ExchangeAddressExists("ANX", currency.BTC) { - t.Error("Test Failed - TestExchangeAddressExists address doesn't exist") + t.Error("TestExchangeAddressExists address doesn't exist") } } @@ -133,7 +133,7 @@ func TestUpdateAddressBalance(t *testing.T) { value := newbase.GetPortfolioSummary() if value.Totals[0].Coin != currency.LTC && value.Totals[0].Balance != 0.03 { - t.Error("Test Failed - portfolio_test.go - UpdateUpdateAddressBalance error") + t.Error("portfolio_test.go - UpdateUpdateAddressBalance error") } } @@ -157,14 +157,14 @@ func TestRemoveAddress(t *testing.T) { 420) if !newbase.AddressExists("someaddr") { - t.Error("Test failed - portfolio_test.go - TestRemoveAddress") + t.Error("portfolio_test.go - TestRemoveAddress") } newbase.RemoveAddress("someaddr", currency.LTC.String(), currency.NewCode("LTCWALLETTEST")) if newbase.AddressExists("someaddr") { - t.Error("Test failed - portfolio_test.go - TestRemoveAddress") + t.Error("portfolio_test.go - TestRemoveAddress") } } @@ -176,12 +176,12 @@ func TestRemoveExchangeAddress(t *testing.T) { newbase.AddExchangeAddress(exchangeName, coinType, 420) if !newbase.ExchangeAddressExists(exchangeName, coinType) { - t.Error("Test failed - portfolio_test.go - TestRemoveAddress") + t.Error("portfolio_test.go - TestRemoveAddress") } newbase.RemoveExchangeAddress(exchangeName, coinType) if newbase.ExchangeAddressExists(exchangeName, coinType) { - t.Error("Test failed - portfolio_test.go - TestRemoveAddress") + t.Error("portfolio_test.go - TestRemoveAddress") } } @@ -194,7 +194,7 @@ func TestUpdateExchangeAddressBalance(t *testing.T) { value := portfolio.GetPortfolioSummary() if value.Totals[0].Coin != currency.LTC && value.Totals[0].Balance != 0.04 { - t.Error("Test Failed - portfolio_test.go - UpdateExchangeAddressBalance error") + t.Error("portfolio_test.go - UpdateExchangeAddressBalance error") } } @@ -232,7 +232,7 @@ func TestAddAddress(t *testing.T) { portfolio := GetPortfolio() portfolio.Seed(newbase) if !portfolio.AddressExists("Gibson") { - t.Error("Test Failed - portfolio_test.go - AddAddress error") + t.Error("portfolio_test.go - AddAddress error") } // Test updating balance to <= 0, expected result is to remove the address. @@ -243,7 +243,7 @@ func TestAddAddress(t *testing.T) { -1) if newbase.AddressExists("Gibson") { - t.Error("Test Failed - portfolio_test.go - AddAddress error") + t.Error("portfolio_test.go - AddAddress error") } } @@ -261,24 +261,24 @@ func TestUpdatePortfolio(t *testing.T) { []string{"LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL"}, currency.LTC, ) if !value { - t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + t.Error("portfolio_test.go - UpdatePortfolio error") } value = portfolio.UpdatePortfolio([]string{"Testy"}, currency.LTC) if value { - t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + t.Error("portfolio_test.go - UpdatePortfolio error") } value = portfolio.UpdatePortfolio( []string{"LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL", "LVa8wZ983PvWtdwXZ8viK6SocMENLCXkEy"}, currency.LTC, ) if !value { - t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + t.Error("portfolio_test.go - UpdatePortfolio error") } value = portfolio.UpdatePortfolio( []string{"LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL", "Testy"}, currency.LTC, ) if value { - t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + t.Error("portfolio_test.go - UpdatePortfolio error") } time.Sleep(time.Second * 5) @@ -287,20 +287,20 @@ func TestUpdatePortfolio(t *testing.T) { "0xe853c56864a2ebe4576a807d26fdc4a0ada51919"}, currency.ETH, ) if !value { - t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + t.Error("portfolio_test.go - UpdatePortfolio error") } value = portfolio.UpdatePortfolio( []string{"0xb794f5ea0ba39494ce839613fffba74279579268", "TESTY"}, currency.ETH, ) if value { - t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + t.Error("portfolio_test.go - UpdatePortfolio error") } value = portfolio.UpdatePortfolio( []string{PortfolioAddressExchange, PortfolioAddressPersonal}, currency.LTC) if !value { - t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + t.Error("portfolio_test.go - UpdatePortfolio error") } } @@ -314,21 +314,21 @@ func TestGetPortfolioByExchange(t *testing.T) { value := portfolio.GetPortfolioByExchange("ANX") result, ok := value[currency.LTC] if !ok { - t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange error") + t.Error("portfolio_test.go - GetPortfolioByExchange error") } if result != 0.07 { - t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange result != 0.10") + t.Error("portfolio_test.go - GetPortfolioByExchange result != 0.10") } value = portfolio.GetPortfolioByExchange("Bitfinex") result, ok = value[currency.LTC] if !ok { - t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange error") + t.Error("portfolio_test.go - GetPortfolioByExchange error") } if result != 0.05 { - t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange result != 0.05") + t.Error("portfolio_test.go - GetPortfolioByExchange result != 0.05") } } @@ -343,11 +343,11 @@ func TestGetExchangePortfolio(t *testing.T) { result, ok := value[currency.LTC] if !ok { - t.Error("Test Failed - portfolio_test.go - GetExchangePortfolio error") + t.Error("portfolio_test.go - GetExchangePortfolio error") } if result != 0.08 { - t.Error("Test Failed - portfolio_test.go - GetExchangePortfolio result != 0.08") + t.Error("portfolio_test.go - GetExchangePortfolio result != 0.08") } } @@ -361,11 +361,11 @@ func TestGetPersonalPortfolio(t *testing.T) { value := portfolio.GetPersonalPortfolio() result, ok := value[currency.N2O] if !ok { - t.Error("Test Failed - portfolio_test.go - GetPersonalPortfolio error") + t.Error("portfolio_test.go - GetPersonalPortfolio error") } if result != 0.05 { - t.Error("Test Failed - portfolio_test.go - GetPersonalPortfolio result != 0.05") + t.Error("portfolio_test.go - GetPersonalPortfolio result != 0.05") } } @@ -399,19 +399,19 @@ func TestGetPortfolioSummary(t *testing.T) { } if getTotalsVal(currency.LTC).Coin != currency.LTC { - t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error") + t.Error("portfolio_test.go - TestGetPortfolioSummary error") } if getTotalsVal(currency.ETH).Coin == currency.LTC { - t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error") + t.Error("portfolio_test.go - TestGetPortfolioSummary error") } if getTotalsVal(currency.LTC).Balance != 23 { - t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error") + t.Error("portfolio_test.go - TestGetPortfolioSummary error") } if getTotalsVal(currency.BTC).Balance != 200 { - t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error") + t.Error("portfolio_test.go - TestGetPortfolioSummary error") } } @@ -423,7 +423,7 @@ func TestGetPortfolioGroupedCoin(t *testing.T) { portfolio.Seed(newbase) value := portfolio.GetPortfolioGroupedCoin() if value[currency.LTC][0] != "someaddress" && len(value[currency.LTC][0]) != 1 { - t.Error("Test Failed - portfolio_test.go - GetPortfolioGroupedCoin error") + t.Error("portfolio_test.go - GetPortfolioGroupedCoin error") } } @@ -434,7 +434,7 @@ func TestSeed(t *testing.T) { portfolio.Seed(newbase) if !portfolio.AddressExists("someaddress") { - t.Error("Test Failed - portfolio_test.go - Seed error") + t.Error("portfolio_test.go - Seed error") } } @@ -454,7 +454,7 @@ func TestStartPortfolioWatcher(t *testing.T) { portfolio.Seed(newBase) if !portfolio.AddressExists("LX2LMYXtuv5tiYEMztSSoEZcafFPYJFRK1") { - t.Error("Test Failed - portfolio_test.go - TestStartPortfolioWatcher") + t.Error("portfolio_test.go - TestStartPortfolioWatcher") } go StartPortfolioWatcher() @@ -463,6 +463,6 @@ func TestStartPortfolioWatcher(t *testing.T) { func TestGetPortfolio(t *testing.T) { ptrBASE := GetPortfolio() if reflect.TypeOf(ptrBASE).String() != "*portfolio.Base" { - t.Error("Test Failed - portfolio_test.go - GetoPortfolio error") + t.Error("portfolio_test.go - GetoPortfolio error") } } From 596be31b6a327dc2979de9a9b885cfa70b438121 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 23 Oct 2019 11:30:40 +1100 Subject: [PATCH 55/71] dispatcher: Use int32 for atomic operations to prevent crash on ARM 32bit systems (#370) See https://github.com/golang/go/issues/599 --- dispatch/dispatch.go | 18 +++++++++--------- dispatch/dispatch_types.go | 4 ++-- engine/engine_types.go | 2 +- main.go | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/dispatch/dispatch.go b/dispatch/dispatch.go index 8fb8b096..b7e5f0f4 100644 --- a/dispatch/dispatch.go +++ b/dispatch/dispatch.go @@ -25,7 +25,7 @@ func init() { } // Start starts the dispatch system by spawning workers and allocating memory -func Start(workers int64) error { +func Start(workers int) error { if dispatcher == nil { return errors.New(errNotInitialised) } @@ -78,7 +78,7 @@ func SpawnWorker() error { // start compares atomic running value, sets defaults, overides with // configuration, then spawns workers -func (d *Dispatcher) start(workers int64) error { +func (d *Dispatcher) start(workers int) error { if atomic.LoadUint32(&d.running) == 1 { return errors.New(errAlreadyStarted) } @@ -89,14 +89,14 @@ func (d *Dispatcher) start(workers int64) error { workers = DefaultMaxWorkers } - d.maxWorkers = workers + d.maxWorkers = int32(workers) d.shutdown = make(chan *sync.WaitGroup) - if atomic.LoadInt64(&d.count) != 0 { + if atomic.LoadInt32(&d.count) != 0 { return errors.New("dispatcher leaked workers found") } - for i := int64(0); i < d.maxWorkers; i++ { + for i := int32(0); i < d.maxWorkers; i++ { err := d.spawnWorker() if err != nil { return err @@ -162,7 +162,7 @@ func (d *Dispatcher) dropWorker() { // spawnWorker allocates a new worker for job processing func (d *Dispatcher) spawnWorker() error { - if atomic.LoadInt64(&d.count) >= d.maxWorkers { + if atomic.LoadInt32(&d.count) >= d.maxWorkers { return errors.New("dispatcher cannot spawn more workers; ceiling reached") } var spawnWg sync.WaitGroup @@ -174,7 +174,7 @@ func (d *Dispatcher) spawnWorker() error { // Relayer routine relays communications across the defined routes func (d *Dispatcher) relayer(i *sync.WaitGroup) { - atomic.AddInt64(&d.count, 1) + atomic.AddInt32(&d.count, 1) d.wg.Add(1) timeout := time.NewTimer(0) i.Done() @@ -219,7 +219,7 @@ func (d *Dispatcher) relayer(i *sync.WaitGroup) { default: } } - atomic.AddInt64(&d.count, -1) + atomic.AddInt32(&d.count, -1) if v != nil { v.Done() } @@ -255,7 +255,7 @@ func (d *Dispatcher) publish(id uuid.UUID, data interface{}) error { default: return fmt.Errorf("dispatcher buffer at max capacity [%d] current worker count [%d], spawn more workers via --dispatchworkers=x", len(d.jobs), - atomic.LoadInt64(&d.count)) + atomic.LoadInt32(&d.count)) } return nil diff --git a/dispatch/dispatch_types.go b/dispatch/dispatch_types.go index b52bb42f..05ce744f 100644 --- a/dispatch/dispatch_types.go +++ b/dispatch/dispatch_types.go @@ -46,11 +46,11 @@ type Dispatcher struct { outbound sync.Pool // MaxWorkers defines max worker ceiling - maxWorkers int64 + maxWorkers int32 // Atomic values ----------------------- // Worker counter - count int64 + count int32 // Dispatch status running uint32 diff --git a/engine/engine_types.go b/engine/engine_types.go index 0372df9a..11d3a3ec 100644 --- a/engine/engine_types.go +++ b/engine/engine_types.go @@ -64,5 +64,5 @@ type Settings struct { // Dispatch system settings EnableDispatcher bool - DispatchMaxWorkerAmount int64 + DispatchMaxWorkerAmount int } diff --git a/main.go b/main.go index 99b1fd1d..4b9b41fa 100644 --- a/main.go +++ b/main.go @@ -48,7 +48,7 @@ func main() { flag.DurationVar(&settings.EventManagerDelay, "eventmanagerdelay", time.Duration(0), "sets the event managers sleep delay between event checking") flag.BoolVar(&settings.EnableNTPClient, "ntpclient", true, "enables the NTP client to check system clock drift") flag.BoolVar(&settings.EnableDispatcher, "dispatch", true, "enables the dispatch system") - flag.Int64Var(&settings.DispatchMaxWorkerAmount, "dispatchworkers", dispatch.DefaultMaxWorkers, "sets the dispatch package max worker generation limit") + flag.IntVar(&settings.DispatchMaxWorkerAmount, "dispatchworkers", dispatch.DefaultMaxWorkers, "sets the dispatch package max worker generation limit") // Forex provider settings flag.BoolVar(&settings.EnableCurrencyConverter, "currencyconverter", false, "overrides config and sets up foreign exchange Currency Converter") From 242b02c382b1742b6c052a6de0b479484bb88d03 Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 29 Oct 2019 14:00:45 +1100 Subject: [PATCH 56/71] (Engine) Bugfix: Unlocking an unlocked mutex PANIC + Increase dispatcher job capacity via commandline (#371) * Removes lock unlock timer and instead sets unlocks between getting a nonce and sending a payload. Increases dispatch channel buffer to deal with len(enabledCurrencies) > ~100 * Adds additional comments to help explain the situation * Fixes bug that could unlock mutex too early * Fixes LIES where Gemini gets a nonce and then proceeds to declare it doesn't get a nonce causing an unrecoverable lock * Fun new concept! The creation of a tested timed mutex. Unlocking an unlocked mutex cannot occur and response can be checked to verify whether the mutex was unlocked from timeout or command. * Adds new cmd parameter "dispatchjobbuffer" * Expands comments and renames benchmark. Makes `Timer` property private * Happy little linters * Renames jobBuffer and all related instances to jobs limit * Tiny error message update * Grammatical fix and setting dispatch.Start to use defaults --- common/timedmutex/timed_mutex.go | 77 ++++++++++++++++ common/timedmutex/timed_mutex_test.go | 86 ++++++++++++++++++ common/timedmutex/timed_mutex_types.go | 15 ++++ dispatch/dispatch.go | 19 ++-- dispatch/dispatch_test.go | 19 ++-- dispatch/dispatch_types.go | 5 +- engine/engine.go | 4 +- engine/engine_types.go | 1 + engine/helpers.go | 2 +- exchanges/gemini/gemini.go | 2 +- exchanges/orderbook/orderbook_test.go | 2 +- exchanges/request/request.go | 119 +++---------------------- exchanges/request/request_test.go | 11 +-- exchanges/request/request_types.go | 75 ++++++++++++++++ exchanges/ticker/ticker_test.go | 2 +- main.go | 1 + 16 files changed, 301 insertions(+), 139 deletions(-) create mode 100644 common/timedmutex/timed_mutex.go create mode 100644 common/timedmutex/timed_mutex_test.go create mode 100644 common/timedmutex/timed_mutex_types.go create mode 100644 exchanges/request/request_types.go diff --git a/common/timedmutex/timed_mutex.go b/common/timedmutex/timed_mutex.go new file mode 100644 index 00000000..6f70d762 --- /dev/null +++ b/common/timedmutex/timed_mutex.go @@ -0,0 +1,77 @@ +package timedmutex + +import ( + "sync" + "time" +) + +// NewTimedMutex creates a new timed mutex with a +// specified duration +func NewTimedMutex(length time.Duration) *TimedMutex { + return &TimedMutex{ + duration: length, + } +} + +// LockForDuration will start a timer, lock the mutex, +// then allow the caller to continue +// After the duration, the mutex will be unlocked +func (t *TimedMutex) LockForDuration() { + var wg sync.WaitGroup + wg.Add(1) + go t.lockAndSetTimer(&wg) + wg.Wait() +} + +func (t *TimedMutex) lockAndSetTimer(wg *sync.WaitGroup) { + t.mtx.Lock() + t.setTimer() + wg.Done() +} + +// UnlockIfLocked will unlock the mutex if its currently locked +// Will return true if successfully unlocked +func (t *TimedMutex) UnlockIfLocked() bool { + if t.isTimerNil() { + return false + } + + if !t.stopTimer() { + return false + } + t.mtx.Unlock() + return true +} + +// stopTimer will return true if timer has been stopped by this command +// If the timer has expired, clear the channel +func (t *TimedMutex) stopTimer() bool { + t.timerLock.Lock() + defer t.timerLock.Unlock() + if !t.timer.Stop() { + select { + case <-t.timer.C: + default: + } + return false + } + return true +} + +// isTimerNil safely read locks to detect nil +func (t *TimedMutex) isTimerNil() bool { + t.timerLock.RLock() + defer t.timerLock.RUnlock() + return t.timer == nil +} + +// setTimer safely locks and sets a timer +// which will automatically execute a mutex unlock +// once timer expires +func (t *TimedMutex) setTimer() { + t.timerLock.Lock() + t.timer = time.AfterFunc(t.duration, func() { + t.mtx.Unlock() + }) + t.timerLock.Unlock() +} diff --git a/common/timedmutex/timed_mutex_test.go b/common/timedmutex/timed_mutex_test.go new file mode 100644 index 00000000..bf9c3941 --- /dev/null +++ b/common/timedmutex/timed_mutex_test.go @@ -0,0 +1,86 @@ +package timedmutex + +import ( + "testing" + "time" +) + +func BenchmarkTimedMutexTime(b *testing.B) { + tm := NewTimedMutex(20 * time.Millisecond) + for i := 0; i < b.N; i++ { + tm.LockForDuration() + } +} + +func TestConsistencyOfPanicFreeUnlock(t *testing.T) { + t.Parallel() + duration := 20 * time.Millisecond + tm := NewTimedMutex(duration) + for i := 1; i <= 50; i++ { + testUnlockTime := time.Duration(i) * time.Millisecond + tm.LockForDuration() + time.Sleep(testUnlockTime) + tm.UnlockIfLocked() + } +} + +func TestUnlockAfterTimeout(t *testing.T) { + t.Parallel() + tm := NewTimedMutex(time.Second) + tm.LockForDuration() + time.Sleep(2 * time.Second) + wasUnlocked := tm.UnlockIfLocked() + if wasUnlocked { + t.Error("Mutex should have been unlocked by timeout, not command") + } +} + +func TestUnlockBeforeTimeout(t *testing.T) { + t.Parallel() + tm := NewTimedMutex(2 * time.Second) + tm.LockForDuration() + time.Sleep(time.Second) + wasUnlocked := tm.UnlockIfLocked() + if !wasUnlocked { + t.Error("Mutex should have been unlocked by command, not timeout") + } +} + +// TestUnlockAtSameTimeAsTimeout this test ensures +// that even if the timeout and the command occur at +// the same time, no panics occur. The result of the +// 'who' unlocking this doesn't matter, so long as +// the unlock occurs without this test panicking +func TestUnlockAtSameTimeAsTimeout(t *testing.T) { + t.Parallel() + duration := time.Second + tm := NewTimedMutex(duration) + tm.LockForDuration() + time.Sleep(duration) + tm.UnlockIfLocked() +} + +func TestMultipleUnlocks(t *testing.T) { + t.Parallel() + tm := NewTimedMutex(10 * time.Second) + tm.LockForDuration() + wasUnlocked := tm.UnlockIfLocked() + if !wasUnlocked { + t.Error("Mutex should have been unlocked by command, not timeout") + } + wasUnlocked = tm.UnlockIfLocked() + if wasUnlocked { + t.Error("Mutex should have been already unlocked by command") + } + wasUnlocked = tm.UnlockIfLocked() + if wasUnlocked { + t.Error("Mutex should have been already unlocked by command") + } +} + +func TestJustWaitItOut(t *testing.T) { + t.Parallel() + tm := NewTimedMutex(1 * time.Second) + tm.LockForDuration() + time.Sleep(2 * time.Second) +} diff --git a/common/timedmutex/timed_mutex_types.go b/common/timedmutex/timed_mutex_types.go new file mode 100644 index 00000000..841ebcdc --- /dev/null +++ b/common/timedmutex/timed_mutex_types.go @@ -0,0 +1,15 @@ +package timedmutex + +import ( + "sync" + "time" +) + +// TimedMutex is a blocking mutex which will unlock +// after a specified time +type TimedMutex struct { + mtx sync.Mutex + timerLock sync.RWMutex + timer *time.Timer + duration time.Duration +} diff --git a/dispatch/dispatch.go b/dispatch/dispatch.go index b7e5f0f4..63ee5cfe 100644 --- a/dispatch/dispatch.go +++ b/dispatch/dispatch.go @@ -14,7 +14,6 @@ import ( func init() { dispatcher = &Dispatcher{ routes: make(map[uuid.UUID][]chan interface{}), - jobs: make(chan *job, DefaultJobBuffer), outbound: sync.Pool{ New: func() interface{} { // Create unbuffered channel for data pass @@ -25,14 +24,14 @@ func init() { } // Start starts the dispatch system by spawning workers and allocating memory -func Start(workers int) error { +func Start(workers, jobsLimit int) error { if dispatcher == nil { return errors.New(errNotInitialised) } mtx.Lock() defer mtx.Unlock() - return dispatcher.start(workers) + return dispatcher.start(workers, jobsLimit) } // Stop attempts to stop the dispatch service, this will close all pipe channels @@ -78,17 +77,22 @@ func SpawnWorker() error { // start compares atomic running value, sets defaults, overides with // configuration, then spawns workers -func (d *Dispatcher) start(workers int) error { +func (d *Dispatcher) start(workers, channelCapacity int) error { if atomic.LoadUint32(&d.running) == 1 { return errors.New(errAlreadyStarted) } if workers < 1 { log.Warn(log.DispatchMgr, - "Dispatcher: workers cannot be zero using default values") + "Dispatcher: workers cannot be zero, using default values") workers = DefaultMaxWorkers } - + if channelCapacity < 1 { + log.Warn(log.DispatchMgr, + "Dispatcher: jobs limit cannot be zero, using default values") + channelCapacity = DefaultJobsLimit + } + d.jobs = make(chan *job, channelCapacity) d.maxWorkers = int32(workers) d.shutdown = make(chan *sync.WaitGroup) @@ -253,7 +257,8 @@ func (d *Dispatcher) publish(id uuid.UUID, data interface{}) error { select { case d.jobs <- newJob: default: - return fmt.Errorf("dispatcher buffer at max capacity [%d] current worker count [%d], spawn more workers via --dispatchworkers=x", + return fmt.Errorf("dispatcher jobs at limit [%d] current worker count [%d]. Spawn more workers via --dispatchworkers=x"+ + ", or increase the jobs limit via --dispatchjobslimit=x", len(d.jobs), atomic.LoadInt32(&d.count)) } diff --git a/dispatch/dispatch_test.go b/dispatch/dispatch_test.go index cdbf1aaa..ee39af81 100644 --- a/dispatch/dispatch_test.go +++ b/dispatch/dispatch_test.go @@ -12,7 +12,7 @@ import ( var mux *Mux func TestMain(m *testing.M) { - err := Start(DefaultMaxWorkers) + err := Start(DefaultMaxWorkers, 0) if err != nil { fmt.Println(err) os.Exit(1) @@ -34,11 +34,10 @@ func TestDispatcher(t *testing.T) { t.Error("error cannot be nil") } - err = Start(10) + err = Start(10, 0) if err == nil { t.Error("error cannot be nil") } - if IsRunning() { t.Error("should be false") } @@ -59,7 +58,7 @@ func TestDispatcher(t *testing.T) { t.Error("should be true") } - err = Start(10) + err = Start(10, 0) if err == nil { t.Error("error cannot be nil") } @@ -99,11 +98,13 @@ func TestDispatcher(t *testing.T) { t.Error("error cannot be nil") } - err = Start(0) + err = Start(0, 20) if err != nil { t.Error(err) } - + if cap(dispatcher.jobs) != 20 { + t.Errorf("Expected jobs limit to be %v, is %v", 20, cap(dispatcher.jobs)) + } payload := "something" err = dispatcher.publish(uuid.UUID{}, &payload) @@ -141,11 +142,13 @@ func TestDispatcher(t *testing.T) { t.Error("error cannot be nil") } - err = dispatcher.start(10) + err = dispatcher.start(10, -1) if err != nil { t.Error(err) } - + if cap(dispatcher.jobs) != DefaultJobsLimit { + t.Errorf("Expected jobs limit to be %v, is %v", DefaultJobsLimit, cap(dispatcher.jobs)) + } someID, err := uuid.NewV4() if err != nil { t.Error(err) diff --git a/dispatch/dispatch_types.go b/dispatch/dispatch_types.go index 05ce744f..1b9d7d99 100644 --- a/dispatch/dispatch_types.go +++ b/dispatch/dispatch_types.go @@ -8,8 +8,8 @@ import ( ) const ( - // DefaultJobBuffer defines a maxiumum amount of jobs allowed in channel - DefaultJobBuffer = 100 + // DefaultJobsLimit defines a maxiumum amount of jobs allowed in channel + DefaultJobsLimit = 100 // DefaultMaxWorkers is the package default worker ceiling amount DefaultMaxWorkers = 10 @@ -47,7 +47,6 @@ type Dispatcher struct { // MaxWorkers defines max worker ceiling maxWorkers int32 - // Atomic values ----------------------- // Worker counter count int32 diff --git a/engine/engine.go b/engine/engine.go index c97153db..f69514d2 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -207,6 +207,7 @@ func ValidateSettings(b *Engine, s *Settings) { b.Settings.GlobalHTTPProxy = s.GlobalHTTPProxy b.Settings.DispatchMaxWorkerAmount = s.DispatchMaxWorkerAmount + b.Settings.DispatchJobsLimit = s.DispatchJobsLimit } // PrintSettings returns the engine settings @@ -237,6 +238,7 @@ func PrintSettings(s *Settings) { log.Debugf(log.Global, "\t Enable Database manager: %v", s.EnableDatabaseManager) log.Debugf(log.Global, "\t Enable dispatcher: %v", s.EnableDispatcher) log.Debugf(log.Global, "\t Dispatch package max worker amount: %d", s.DispatchMaxWorkerAmount) + log.Debugf(log.Global, "\t Dispatch package jobs limit: %d", s.DispatchJobsLimit) log.Debugf(log.Global, "- FOREX SETTINGS:") log.Debugf(log.Global, "\t Enable currency conveter: %v", s.EnableCurrencyConverter) log.Debugf(log.Global, "\t Enable currency layer: %v", s.EnableCurrencyLayer) @@ -274,7 +276,7 @@ func (e *Engine) Start() error { } if e.Settings.EnableDispatcher { - if err := dispatch.Start(e.Settings.DispatchMaxWorkerAmount); err != nil { + if err := dispatch.Start(e.Settings.DispatchMaxWorkerAmount, e.Settings.DispatchJobsLimit); err != nil { log.Errorf(log.DispatchMgr, "Dispatcher unable to start: %v", err) } } diff --git a/engine/engine_types.go b/engine/engine_types.go index 11d3a3ec..da1ba5ad 100644 --- a/engine/engine_types.go +++ b/engine/engine_types.go @@ -65,4 +65,5 @@ type Settings struct { // Dispatch system settings EnableDispatcher bool DispatchMaxWorkerAmount int + DispatchJobsLimit int } diff --git a/engine/helpers.go b/engine/helpers.go index 4ef42aa5..d628b2a4 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -116,7 +116,7 @@ func SetSubsystem(subsys string, enable bool) error { Bot.ExchangeCurrencyPairManager.Stop() case "dispatch": if enable { - return dispatch.Start(Bot.Settings.DispatchMaxWorkerAmount) + return dispatch.Start(Bot.Settings.DispatchMaxWorkerAmount, Bot.Settings.DispatchJobsLimit) } return dispatch.Stop() } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 0e11a441..794677e4 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -397,7 +397,7 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st nil, result, true, - false, + true, g.Verbose, g.HTTPDebugging, g.HTTPRecording) diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 9389ad08..9c750478 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -15,7 +15,7 @@ import ( ) func TestMain(m *testing.M) { - err := dispatch.Start(1) + err := dispatch.Start(1, dispatch.DefaultJobsLimit) if err != nil { log.Fatal(err) } diff --git a/exchanges/request/request.go b/exchanges/request/request.go index 51cc651b..66d0cae9 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -11,80 +11,15 @@ import ( "net/http/httputil" "net/url" "strings" - "sync" "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/timedmutex" "github.com/thrasher-corp/gocryptotrader/exchanges/mock" "github.com/thrasher-corp/gocryptotrader/exchanges/nonce" log "github.com/thrasher-corp/gocryptotrader/logger" ) -var supportedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead, - http.MethodPut, http.MethodDelete, http.MethodOptions, http.MethodConnect} - -// Const vars for rate limiter -const ( - DefaultMaxRequestJobs = 50 - DefaultTimeoutRetryAttempts = 3 - - proxyTLSTimeout = 15 * time.Second -) - -// Vars for rate limiter -var ( - MaxRequestJobs = DefaultMaxRequestJobs - TimeoutRetryAttempts = DefaultTimeoutRetryAttempts - DisableRateLimiter bool -) - -// Requester struct for the request client -type Requester struct { - HTTPClient *http.Client - UnauthLimit *RateLimit - AuthLimit *RateLimit - Name string - UserAgent string - Cycle time.Time - timeoutRetryAttempts int - m sync.Mutex - Jobs chan Job - disengage chan struct{} - WorkerStarted bool - Nonce nonce.Nonce - fifoLock sync.Mutex - DisableRateLimiter bool -} - -// RateLimit struct -type RateLimit struct { - Duration time.Duration - Rate int - Requests int - Mutex sync.Mutex -} - -// JobResult holds a request job result -type JobResult struct { - Error error - Result interface{} -} - -// Job holds a request job -type Job struct { - Request *http.Request - Method string - Path string - Headers map[string]string - Body io.Reader - Result interface{} - JobResult chan *JobResult - AuthRequest bool - Verbose bool - HTTPDebugging bool - Record bool -} - // NewRateLimit creates a new RateLimit func NewRateLimit(d time.Duration, rate int) *RateLimit { return &RateLimit{Duration: d, Rate: rate} @@ -237,8 +172,8 @@ func New(name string, authLimit, unauthLimit *RateLimit, httpRequester *http.Cli AuthLimit: authLimit, Name: name, Jobs: make(chan Job, MaxRequestJobs), - disengage: make(chan struct{}, 1), timeoutRetryAttempts: TimeoutRetryAttempts, + timedLock: timedmutex.NewTimedMutex(DefaultMutexLockTimeout), } } @@ -443,27 +378,27 @@ func (r *Requester) worker() { // SendPayload handles sending HTTP/HTTPS requests func (r *Requester) SendPayload(method, path string, headers map[string]string, body io.Reader, result interface{}, authRequest, nonceEnabled, verbose, httpDebugging, record bool) error { if !nonceEnabled { - r.lock() + r.timedLock.LockForDuration() } if r == nil || r.Name == "" { - r.unlock() + r.timedLock.UnlockIfLocked() return errors.New("not initiliased, SetDefaults() called before making request?") } if !IsValidMethod(method) { - r.unlock() + r.timedLock.UnlockIfLocked() return fmt.Errorf("incorrect method supplied %s: supported %s", method, supportedMethods) } if path == "" { - r.unlock() + r.timedLock.UnlockIfLocked() return errors.New("invalid path") } req, err := r.checkRequest(method, path, body, headers) if err != nil { - r.unlock() + r.timedLock.UnlockIfLocked() return err } @@ -478,12 +413,12 @@ func (r *Requester) SendPayload(method, path string, headers map[string]string, } if !r.RequiresRateLimiter() { - r.unlock() + r.timedLock.UnlockIfLocked() return r.DoRequest(req, path, body, result, authRequest, verbose, httpDebugging, record) } if len(r.Jobs) == MaxRequestJobs { - r.unlock() + r.timedLock.UnlockIfLocked() return errors.New("max request jobs reached") } @@ -515,7 +450,7 @@ func (r *Requester) SendPayload(method, path string, headers map[string]string, log.Debugf(log.ExchangeSys, "%s request. Attaching new job.", r.Name) } r.Jobs <- newJob - r.unlock() + r.timedLock.UnlockIfLocked() if verbose { log.Debugf(log.ExchangeSys, "%s request. Waiting for job to complete.", r.Name) @@ -532,7 +467,7 @@ func (r *Requester) SendPayload(method, path string, headers map[string]string, // GetNonce returns a nonce for requests. This locks and enforces concurrent // nonce FIFO on the buffered job channel func (r *Requester) GetNonce(isNano bool) nonce.Value { - r.lock() + r.timedLock.LockForDuration() if r.Nonce.Get() == 0 { if isNano { r.Nonce.Set(time.Now().UnixNano()) @@ -548,7 +483,7 @@ func (r *Requester) GetNonce(isNano bool) nonce.Value { // GetNonceMilli returns a nonce for requests. This locks and enforces concurrent // nonce FIFO on the buffered job channel this is for millisecond func (r *Requester) GetNonceMilli() nonce.Value { - r.lock() + r.timedLock.LockForDuration() if r.Nonce.Get() == 0 { r.Nonce.Set(time.Now().UnixNano() / int64(time.Millisecond)) return r.Nonce.Get() @@ -569,33 +504,3 @@ func (r *Requester) SetProxy(p *url.URL) error { } return nil } - -// lock locks and sets up an issue timer, if something errors out of scope it -// automatically unlocks -func (r *Requester) lock() { - if r.disengage == nil { - r.disengage = make(chan struct{}, 1) - } - var wg sync.WaitGroup - r.fifoLock.Lock() - wg.Add(1) - go func() { - timer := time.NewTimer(50 * time.Millisecond) - wg.Done() - select { - case <-timer.C: - log.Errorf(log.ExchangeSys, "Unlocking due to possible error for %s", r.Name) - r.fifoLock.Unlock() - - case <-r.disengage: - return - } - }() - wg.Wait() -} - -// unlock unlocks mtx and shuts down a timer -func (r *Requester) unlock() { - r.disengage <- struct{}{} - r.fifoLock.Unlock() -} diff --git a/exchanges/request/request_test.go b/exchanges/request/request_test.go index 9acf9a58..37b94077 100644 --- a/exchanges/request/request_test.go +++ b/exchanges/request/request_test.go @@ -199,16 +199,9 @@ func TestCheckRequest(t *testing.T) { } func TestDoRequest(t *testing.T) { - var test = new(Requester) - err := test.SendPayload(http.MethodGet, "https://www.google.com", nil, nil, nil, false, false, true, false, false) - if err == nil { - t.Fatal("Expected error") - } - r := New("", NewRateLimit(time.Second*10, 5), NewRateLimit(time.Second*20, 100), new(http.Client)) - r.Name = "bitfinex" - err = r.SendPayload("BLAH", "https://www.google.com", nil, nil, nil, false, false, true, false, false) + err := r.SendPayload("BLAH", "https://www.google.com", nil, nil, nil, false, false, true, false, false) if err == nil { t.Fatal("Expected error") } @@ -321,7 +314,7 @@ func TestDoRequest(t *testing.T) { } func BenchmarkRequestLockMech(b *testing.B) { - var r = new(Requester) + r := New("", NewRateLimit(time.Second*10, 5), NewRateLimit(time.Second*20, 100), new(http.Client)) var meep interface{} for n := 0; n < b.N; n++ { r.SendPayload(http.MethodGet, "127.0.0.1", nil, nil, &meep, false, false, false, false, false) diff --git a/exchanges/request/request_types.go b/exchanges/request/request_types.go new file mode 100644 index 00000000..f35fe3cb --- /dev/null +++ b/exchanges/request/request_types.go @@ -0,0 +1,75 @@ +package request + +import ( + "io" + "net/http" + "sync" + "time" + + "github.com/thrasher-corp/gocryptotrader/common/timedmutex" + "github.com/thrasher-corp/gocryptotrader/exchanges/nonce" +) + +var supportedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead, + http.MethodPut, http.MethodDelete, http.MethodOptions, http.MethodConnect} + +// Const vars for rate limiter +const ( + DefaultMaxRequestJobs = 50 + DefaultTimeoutRetryAttempts = 3 + DefaultMutexLockTimeout = 50 * time.Millisecond + proxyTLSTimeout = 15 * time.Second +) + +// Vars for rate limiter +var ( + MaxRequestJobs = DefaultMaxRequestJobs + TimeoutRetryAttempts = DefaultTimeoutRetryAttempts + DisableRateLimiter bool +) + +// Requester struct for the request client +type Requester struct { + HTTPClient *http.Client + UnauthLimit *RateLimit + AuthLimit *RateLimit + Name string + UserAgent string + Cycle time.Time + timeoutRetryAttempts int + m sync.Mutex + Jobs chan Job + WorkerStarted bool + Nonce nonce.Nonce + DisableRateLimiter bool + timedLock *timedmutex.TimedMutex +} + +// RateLimit struct +type RateLimit struct { + Duration time.Duration + Rate int + Requests int + Mutex sync.Mutex +} + +// JobResult holds a request job result +type JobResult struct { + Error error + Result interface{} +} + +// Job holds a request job +type Job struct { + Request *http.Request + Method string + Path string + Headers map[string]string + Body io.Reader + Result interface{} + JobResult chan *JobResult + AuthRequest bool + Verbose bool + HTTPDebugging bool + Record bool +} diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index 9cc686d9..328e13f1 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -15,7 +15,7 @@ import ( ) func TestMain(m *testing.M) { - err := dispatch.Start(1) + err := dispatch.Start(1, dispatch.DefaultJobsLimit) if err != nil { log.Fatal(err) } diff --git a/main.go b/main.go index 4b9b41fa..10816b3e 100644 --- a/main.go +++ b/main.go @@ -49,6 +49,7 @@ func main() { flag.BoolVar(&settings.EnableNTPClient, "ntpclient", true, "enables the NTP client to check system clock drift") flag.BoolVar(&settings.EnableDispatcher, "dispatch", true, "enables the dispatch system") flag.IntVar(&settings.DispatchMaxWorkerAmount, "dispatchworkers", dispatch.DefaultMaxWorkers, "sets the dispatch package max worker generation limit") + flag.IntVar(&settings.DispatchJobsLimit, "dispatchjobslimit", dispatch.DefaultJobsLimit, "sets the dispatch package max jobs limit") // Forex provider settings flag.BoolVar(&settings.EnableCurrencyConverter, "currencyconverter", false, "overrides config and sets up foreign exchange Currency Converter") From 63a9e9fcb1fd6ccc874971d6a104f710c654aeb3 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Wed, 30 Oct 2019 14:06:48 +1100 Subject: [PATCH 57/71] Engine QA - Order update (#372) Engine QA - Order update --- cmd/exchange_template/wrapper_file.tmpl | 20 +- cmd/exchange_wrapper_coverage/main.go | 17 +- cmd/exchange_wrapper_issues/main.go | 84 ++-- engine/database.go | 16 +- engine/orders.go | 60 +-- engine/orders_types.go | 6 +- engine/rpcserver.go | 28 +- exchanges/alphapoint/alphapoint.go | 3 +- exchanges/alphapoint/alphapoint_test.go | 27 +- exchanges/alphapoint/alphapoint_types.go | 20 +- exchanges/alphapoint/alphapoint_wrapper.go | 101 +++-- exchanges/anx/anx_test.go | 25 +- exchanges/anx/anx_wrapper.go | 76 ++-- exchanges/binance/binance_test.go | 21 +- exchanges/binance/binance_wrapper.go | 148 ++++--- exchanges/bitfinex/bitfinex.go | 13 +- exchanges/bitfinex/bitfinex_test.go | 39 +- exchanges/bitfinex/bitfinex_wrapper.go | 123 +++--- exchanges/bitflyer/bitflyer_test.go | 21 +- exchanges/bitflyer/bitflyer_wrapper.go | 39 +- exchanges/bithumb/bithumb_test.go | 30 +- exchanges/bithumb/bithumb_wrapper.go | 116 ++--- exchanges/bitmex/bitmex_test.go | 27 +- exchanges/bitmex/bitmex_types.go | 18 +- exchanges/bitmex/bitmex_websocket.go | 3 +- exchanges/bitmex/bitmex_wrapper.go | 83 ++-- exchanges/bitstamp/bitstamp.go | 5 +- exchanges/bitstamp/bitstamp_test.go | 25 +- exchanges/bitstamp/bitstamp_wrapper.go | 162 ++++--- exchanges/bittrex/bittrex_test.go | 25 +- exchanges/bittrex/bittrex_wrapper.go | 115 ++--- exchanges/btcmarkets/btcmarkets_test.go | 27 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 136 +++--- exchanges/btse/btse_test.go | 21 +- exchanges/btse/btse_websocket.go | 6 +- exchanges/btse/btse_wrapper.go | 91 ++-- exchanges/coinbasepro/coinbasepro.go | 5 +- exchanges/coinbasepro/coinbasepro_test.go | 25 +- .../coinbasepro/coinbasepro_websocket.go | 3 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 103 ++--- exchanges/coinut/coinut.go | 5 +- exchanges/coinut/coinut_test.go | 31 +- exchanges/coinut/coinut_types.go | 4 +- exchanges/coinut/coinut_websocket.go | 3 +- exchanges/coinut/coinut_wrapper.go | 104 ++--- exchanges/exchange_test.go | 313 -------------- exchanges/exmo/exmo_test.go | 25 +- exchanges/exmo/exmo_wrapper.go | 111 ++--- exchanges/gateio/gateio_test.go | 27 +- exchanges/gateio/gateio_wrapper.go | 106 ++--- exchanges/gemini/gemini_test.go | 25 +- exchanges/gemini/gemini_websocket.go | 3 +- exchanges/gemini/gemini_wrapper.go | 96 ++--- exchanges/hitbtc/hitbtc_test.go | 30 +- exchanges/hitbtc/hitbtc_wrapper.go | 94 ++-- exchanges/huobi/huobi_test.go | 25 +- exchanges/huobi/huobi_wrapper.go | 177 ++++---- exchanges/interfaces.go | 15 +- exchanges/itbit/itbit_test.go | 29 +- exchanges/itbit/itbit_wrapper.go | 130 +++--- exchanges/kraken/kraken.go | 7 +- exchanges/kraken/kraken_test.go | 27 +- exchanges/kraken/kraken_wrapper.go | 118 +++--- exchanges/lakebtc/lakebtc_test.go | 25 +- exchanges/lakebtc/lakebtc_wrapper.go | 88 ++-- exchanges/lbank/lbank_test.go | 13 +- exchanges/lbank/lbank_wrapper.go | 71 ++-- exchanges/localbitcoins/localbitcoins_test.go | 25 +- .../localbitcoins/localbitcoins_wrapper.go | 87 ++-- exchanges/okcoin/okcoin_test.go | 57 +-- exchanges/okex/okex_test.go | 69 +-- exchanges/okgroup/okgroup_wrapper.go | 160 +++---- exchanges/{orders => order}/README.md | 0 exchanges/order/order_test.go | 400 ++++++++++++++++++ exchanges/order/order_types.go | 190 +++++++++ exchanges/order/orders.go | 302 +++++++++++++ exchanges/{orders => order}/orders_test.go | 2 +- exchanges/order_test.go | 81 ---- exchanges/order_types.go | 387 ----------------- exchanges/orders/orders.go | 69 --- exchanges/poloniex/poloniex.go | 5 +- exchanges/poloniex/poloniex_test.go | 25 +- exchanges/poloniex/poloniex_wrapper.go | 121 +++--- exchanges/yobit/yobit.go | 10 +- exchanges/yobit/yobit_test.go | 37 +- exchanges/yobit/yobit_wrapper.go | 146 ++++--- exchanges/zb/zb_test.go | 27 +- exchanges/zb/zb_types.go | 8 +- exchanges/zb/zb_wrapper.go | 130 +++--- 89 files changed, 3077 insertions(+), 2876 deletions(-) rename exchanges/{orders => order}/README.md (100%) create mode 100644 exchanges/order/order_test.go create mode 100644 exchanges/order/order_types.go create mode 100644 exchanges/order/orders.go rename exchanges/{orders => order}/orders_test.go (98%) delete mode 100644 exchanges/order_test.go delete mode 100644 exchanges/order_types.go delete mode 100644 exchanges/orders/orders.go diff --git a/cmd/exchange_template/wrapper_file.tmpl b/cmd/exchange_template/wrapper_file.tmpl index 1e5b50c8..0346f6a3 100644 --- a/cmd/exchange_template/wrapper_file.tmpl +++ b/cmd/exchange_template/wrapper_file.tmpl @@ -116,29 +116,29 @@ func ({{.Variable}} *{{.CapitalName}}) GetExchangeHistory(p currency.Pair, asset } // SubmitOrder submits a new order -func ({{.Variable}} *{{.CapitalName}}) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) { - return exchange.SubmitOrderResponse{}, common.ErrNotYetImplemented +func ({{.Variable}} *{{.CapitalName}}) SubmitOrder(p currency.Pair, side order.Side, orderType order.Type, amount, price float64, clientID string) (order.SubmitResponse, error) { + return order.SubmitResponse{}, common.ErrNotYetImplemented } // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func ({{.Variable}} *{{.CapitalName}}) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func ({{.Variable}} *{{.CapitalName}}) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrNotYetImplemented } // CancelOrder cancels an order by its corresponding ID number -func ({{.Variable}} *{{.CapitalName}}) CancelOrder(order *exchange.OrderCancellation) error { +func ({{.Variable}} *{{.CapitalName}}) CancelOrder(order *order.Cancel) error { return common.ErrNotYetImplemented } // CancelAllOrders cancels all orders associated with a currency pair -func ({{.Variable}} *{{.CapitalName}}) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - return exchange.CancelAllOrdersResponse{}, common.ErrNotYetImplemented +func ({{.Variable}} *{{.CapitalName}}) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { + return order.CancelAllResponse{}, common.ErrNotYetImplemented } // GetOrderInfo returns information on a current open order -func ({{.Variable}} *{{.CapitalName}}) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - return exchange.OrderDetail{}, common.ErrNotYetImplemented +func ({{.Variable}} *{{.CapitalName}}) GetOrderInfo(orderID string) (order.Detail, error) { + return order.Detail{}, common.ErrNotYetImplemented } // GetDepositAddress returns a deposit address for a specified currency @@ -170,13 +170,13 @@ func ({{.Variable}} *{{.CapitalName}}) GetWebsocket() (*exchange.Websocket, erro } // GetActiveOrders retrieves any orders that are active/open -func ({{.Variable}} *{{.CapitalName}}) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func ({{.Variable}} *{{.CapitalName}}) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { return nil, common.ErrNotYetImplemented } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func ({{.Variable}} *{{.CapitalName}}) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func ({{.Variable}} *{{.CapitalName}}) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { return nil, common.ErrNotYetImplemented } diff --git a/cmd/exchange_wrapper_coverage/main.go b/cmd/exchange_wrapper_coverage/main.go index 68a61b16..d37808b5 100644 --- a/cmd/exchange_wrapper_coverage/main.go +++ b/cmd/exchange_wrapper_coverage/main.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/engine" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) const ( @@ -116,10 +117,10 @@ func testWrappers(e exchange.IBotExchange) []string { funcs = append(funcs, "GetFundingHistory") } - s := &exchange.OrderSubmission{ + s := &order.Submit{ Pair: p, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Amount: 1000000, Price: 10000000000, ClientID: "meow", @@ -129,17 +130,17 @@ func testWrappers(e exchange.IBotExchange) []string { funcs = append(funcs, "SubmitOrder") } - _, err = e.ModifyOrder(&exchange.ModifyOrder{}) + _, err = e.ModifyOrder(&order.Modify{}) if err == common.ErrNotYetImplemented { funcs = append(funcs, "ModifyOrder") } - err = e.CancelOrder(&exchange.OrderCancellation{}) + err = e.CancelOrder(&order.Cancel{}) if err == common.ErrNotYetImplemented { funcs = append(funcs, "CancelOrder") } - _, err = e.CancelAllOrders(&exchange.OrderCancellation{}) + _, err = e.CancelAllOrders(&order.Cancel{}) if err == common.ErrNotYetImplemented { funcs = append(funcs, "CancelAllOrders") } @@ -149,12 +150,12 @@ func testWrappers(e exchange.IBotExchange) []string { funcs = append(funcs, "GetOrderInfo") } - _, err = e.GetOrderHistory(&exchange.GetOrdersRequest{}) + _, err = e.GetOrderHistory(&order.GetOrdersRequest{}) if err == common.ErrNotYetImplemented { funcs = append(funcs, "GetOrderHistory") } - _, err = e.GetActiveOrders(&exchange.GetOrdersRequest{}) + _, err = e.GetActiveOrders(&order.GetOrdersRequest{}) if err == common.ErrNotYetImplemented { funcs = append(funcs, "GetActiveOrders") } diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go index ee8af437..b6bf0c4a 100644 --- a/cmd/exchange_wrapper_issues/main.go +++ b/cmd/exchange_wrapper_issues/main.go @@ -19,6 +19,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/engine" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" ) @@ -225,43 +226,44 @@ func setExchangeAPIKeys(name string, keys map[string]*config.APICredentialsConfi return base.ValidateAPICredentials() } -func parseOrderSide(orderSide string) exchange.OrderSide { +func parseOrderSide(orderSide string) order.Side { switch orderSide { - case exchange.AnyOrderSide.ToString(): - return exchange.AnyOrderSide - case exchange.BuyOrderSide.ToString(): - return exchange.BuyOrderSide - case exchange.SellOrderSide.ToString(): - return exchange.SellOrderSide - case exchange.BidOrderSide.ToString(): - return exchange.BidOrderSide - case exchange.AskOrderSide.ToString(): - return exchange.AskOrderSide + case order.AnySide.String(): + return order.AnySide + case order.Buy.String(): + return order.Buy + case order.Sell.String(): + return order.Sell + case order.Bid.String(): + return order.Bid + case order.Ask.String(): + return order.Ask default: log.Printf("Orderside '%v' not recognised, defaulting to BUY", orderSide) - return exchange.BuyOrderSide + return order.Buy } } -func parseOrderType(orderType string) exchange.OrderType { +func parseOrderType(orderType string) order.Type { switch orderType { - case exchange.AnyOrderType.ToString(): - return exchange.AnyOrderType - case exchange.LimitOrderType.ToString(): - return exchange.LimitOrderType - case exchange.MarketOrderType.ToString(): - return exchange.MarketOrderType - case exchange.ImmediateOrCancelOrderType.ToString(): - return exchange.ImmediateOrCancelOrderType - case exchange.StopOrderType.ToString(): - return exchange.StopOrderType - case exchange.TrailingStopOrderType.ToString(): - return exchange.TrailingStopOrderType - case exchange.UnknownOrderType.ToString(): - return exchange.UnknownOrderType + case order.AnyType.String(): + return order.AnyType + case order.Limit.String(): + return order.Limit + case order.Market.String(): + return order.Market + case order.ImmediateOrCancel.String(): + return order.ImmediateOrCancel + case order.Stop.String(): + return order.Stop + case order.TrailingStop.String(): + return order.TrailingStop + case order.Unknown.String(): + return order.Unknown default: - log.Printf("OrderType '%v' not recognised, defaulting to LIMIT", orderTypeOverride) - return exchange.LimitOrderType + log.Printf("OrderType '%v' not recognised, defaulting to LIMIT", + orderTypeOverride) + return order.Limit } } @@ -457,7 +459,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) Response: jsonifyInterface([]interface{}{r10}), }) - s := &exchange.OrderSubmission{ + s := &order.Submit{ Pair: p, OrderSide: testOrderSide, OrderType: testOrderType, @@ -465,7 +467,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) Price: config.OrderSubmission.Price, ClientID: config.OrderSubmission.OrderID, } - var r11 exchange.SubmitOrderResponse + var r11 order.SubmitResponse r11, err = e.SubmitOrder(s) msg = "" if err != nil { @@ -479,10 +481,10 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) Response: jsonifyInterface([]interface{}{r11}), }) - modifyRequest := exchange.ModifyOrder{ + modifyRequest := order.Modify{ OrderID: config.OrderSubmission.OrderID, - OrderType: testOrderType, - OrderSide: testOrderSide, + Type: testOrderType, + Side: testOrderSide, CurrencyPair: p, Price: config.OrderSubmission.Price, Amount: config.OrderSubmission.Amount, @@ -501,7 +503,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) Response: r12, }) // r13 - cancelRequest := exchange.OrderCancellation{ + cancelRequest := order.Cancel{ Side: testOrderSide, CurrencyPair: p, OrderID: config.OrderSubmission.OrderID, @@ -519,7 +521,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) Response: jsonifyInterface([]interface{}{nil}), }) - var r14 exchange.CancelAllOrdersResponse + var r14 order.CancelAllResponse r14, err = e.CancelAllOrders(&cancelRequest) msg = "" if err != nil { @@ -533,7 +535,7 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) Response: jsonifyInterface([]interface{}{r14}), }) - var r15 exchange.OrderDetail + var r15 order.Detail r15, err = e.GetOrderInfo(config.OrderSubmission.OrderID) msg = "" if err != nil { @@ -547,12 +549,12 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) Response: jsonifyInterface([]interface{}{r15}), }) - historyRequest := exchange.GetOrdersRequest{ + historyRequest := order.GetOrdersRequest{ OrderType: testOrderType, OrderSide: testOrderSide, Currencies: []currency.Pair{p}, } - var r16 []exchange.OrderDetail + var r16 []order.Detail r16, err = e.GetOrderHistory(&historyRequest) msg = "" if err != nil { @@ -566,12 +568,12 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) Response: jsonifyInterface([]interface{}{r16}), }) - orderRequest := exchange.GetOrdersRequest{ + orderRequest := order.GetOrdersRequest{ OrderType: testOrderType, OrderSide: testOrderSide, Currencies: []currency.Pair{p}, } - var r17 []exchange.OrderDetail + var r17 []order.Detail r17, err = e.GetActiveOrders(&orderRequest) msg = "" if err != nil { diff --git a/engine/database.go b/engine/database.go index 72f9e7ce..c6811f0b 100644 --- a/engine/database.go +++ b/engine/database.go @@ -37,12 +37,18 @@ func (a *databaseManager) Start() (err error) { if Bot.Config.Database.Enabled { if Bot.Config.Database.Driver == database.DBPostgreSQL { - log.Debugf(log.DatabaseMgr, "Attempting to establish database connection to host %s/%s utilising %s driver\n", - Bot.Config.Database.Host, Bot.Config.Database.Database, Bot.Config.Database.Driver) + log.Debugf(log.DatabaseMgr, + "Attempting to establish database connection to host %s/%s utilising %s driver\n", + Bot.Config.Database.Host, + Bot.Config.Database.Database, + Bot.Config.Database.Driver) dbConn, err = dbpsql.Connect() - } else if Bot.Config.Database.Driver == database.DBSQLite || Bot.Config.Database.Driver == database.DBSQLite3 { - log.Debugf(log.DatabaseMgr, "Attempting to establish database connection to %s utilising %s driver\n", - Bot.Config.Database.Database, Bot.Config.Database.Driver) + } else if Bot.Config.Database.Driver == database.DBSQLite || + Bot.Config.Database.Driver == database.DBSQLite3 { + log.Debugf(log.DatabaseMgr, + "Attempting to establish database connection to %s utilising %s driver\n", + Bot.Config.Database.Database, + Bot.Config.Database.Driver) dbConn, err = dbsqlite3.Connect() } if err != nil { diff --git a/engine/orders.go b/engine/orders.go index 2f7118c1..e23671ff 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -8,7 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/communications/base" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -18,13 +18,13 @@ var ( ErrOrdersAlreadyExists = errors.New("order already exists") ) -func (o *orderStore) Get() map[string][]exchange.OrderDetail { +func (o *orderStore) Get() map[string][]order.Detail { o.m.Lock() defer o.m.Unlock() return o.Orders } -func (o *orderStore) exists(order *exchange.OrderDetail) bool { +func (o *orderStore) exists(order *order.Detail) bool { r, ok := o.Orders[order.Exchange] if !ok { return false @@ -39,7 +39,7 @@ func (o *orderStore) exists(order *exchange.OrderDetail) bool { return false } -func (o *orderStore) Add(order *exchange.OrderDetail) error { +func (o *orderStore) Add(order *order.Detail) error { o.m.Lock() defer o.m.Unlock() @@ -65,7 +65,7 @@ func (o *orderManager) Start() error { log.Debugln(log.OrderBook, "Order manager starting...") o.shutdown = make(chan struct{}) - o.orderStore.Orders = make(map[string][]exchange.OrderDetail) + o.orderStore.Orders = make(map[string][]order.Detail) go o.run() return nil } @@ -100,7 +100,7 @@ func (o *orderManager) gracefulShutdown() { for y := range v { log.Debugf(log.OrderMgr, "order manager: Cancelling order ID %v [%v]", v[y].ID, v[y]) - err := o.Cancel(k, &exchange.OrderCancellation{ + err := o.Cancel(k, &order.Cancel{ OrderID: v[y].ID, }) if err != nil { @@ -149,16 +149,16 @@ func (o *orderManager) run() { func (o *orderManager) CancelAllOrders() {} -func (o *orderManager) Cancel(exchName string, order *exchange.OrderCancellation) error { +func (o *orderManager) Cancel(exchName string, cancel *order.Cancel) error { if exchName == "" { return errors.New("order exchange name is empty") } - if order == nil { + if cancel == nil { return errors.New("order cancel param is nil") } - if order.OrderID == "" { + if cancel.OrderID == "" { return errors.New("order id is empty") } @@ -167,32 +167,28 @@ func (o *orderManager) Cancel(exchName string, order *exchange.OrderCancellation return errors.New("unable to get exchange by name") } - if order.AssetType.String() != "" && !exch.GetAssetTypes().Contains(order.AssetType) { + if cancel.AssetType.String() != "" && !exch.GetAssetTypes().Contains(cancel.AssetType) { return errors.New("order asset type not supported by exchange") } - return exch.CancelOrder(order) + return exch.CancelOrder(cancel) } -func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) (*orderSubmitResponse, error) { +func (o *orderManager) Submit(exchName string, newOrder *order.Submit) (*orderSubmitResponse, error) { if exchName == "" { return nil, errors.New("order exchange name must be specified") } - if order == nil { - return nil, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { + if err := newOrder.Validate(); err != nil { return nil, err } if o.cfg.EnforceLimitConfig { - if !o.cfg.AllowMarketOrders && order.OrderType == exchange.MarketOrderType { + if !o.cfg.AllowMarketOrders && newOrder.OrderType == order.Market { return nil, errors.New("order market type is not allowed") } - if o.cfg.LimitAmount > 0 && order.Amount > o.cfg.LimitAmount { + if o.cfg.LimitAmount > 0 && newOrder.Amount > o.cfg.LimitAmount { return nil, errors.New("order limit exceeds allowed limit") } @@ -201,7 +197,7 @@ func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) return nil, errors.New("order exchange not found in allowed list") } - if len(o.cfg.AllowedPairs) > 0 && !o.cfg.AllowedPairs.Contains(order.Pair, true) { + if len(o.cfg.AllowedPairs) > 0 && !o.cfg.AllowedPairs.Contains(newOrder.Pair, true) { return nil, errors.New("order pair not found in allowed list") } } @@ -213,10 +209,12 @@ func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) id, err := common.GetV4UUID() if err != nil { - log.Warnf(log.OrderMgr, "Order manager: Unable to generate UUID. Err: %s\n", err) + log.Warnf(log.OrderMgr, + "Order manager: Unable to generate UUID. Err: %s\n", + err) } - result, err := exch.SubmitOrder(order) + result, err := exch.SubmitOrder(newOrder) if err != nil { return nil, err } @@ -226,7 +224,15 @@ func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) } msg := fmt.Sprintf("Order manager: Exchange %s submitted order ID=%v [Ours: %v] pair=%v price=%v amount=%v side=%v type=%v.", - exchName, result.OrderID, id.String(), order.Pair, order.Price, order.Amount, order.OrderSide, order.OrderType) + exchName, + result.OrderID, + id.String(), + newOrder.Pair, + newOrder.Price, + newOrder.Amount, + newOrder.OrderSide, + newOrder.OrderType) + log.Debugln(log.OrderMgr, msg) Bot.CommsManager.PushEvent(base.Event{ Type: "order", @@ -234,7 +240,7 @@ func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) }) return &orderSubmitResponse{ - SubmitOrderResponse: exchange.SubmitOrderResponse{ + SubmitResponse: order.SubmitResponse{ OrderID: result.OrderID, }, OurOrderID: id.String(), @@ -246,9 +252,9 @@ func (o *orderManager) processOrders() { for x := range authExchanges { log.Debugf(log.OrderMgr, "Order manager: Procesing orders for exchange %v.\n", authExchanges[x]) exch := GetExchangeByName(authExchanges[x]) - req := exchange.GetOrdersRequest{ - OrderSide: exchange.AnyOrderSide, - OrderType: exchange.AnyOrderType, + req := order.GetOrdersRequest{ + OrderSide: order.AnySide, + OrderType: order.AnyType, } result, err := exch.GetActiveOrders(&req) if err != nil { diff --git a/engine/orders_types.go b/engine/orders_types.go index 35c11de9..c0479299 100644 --- a/engine/orders_types.go +++ b/engine/orders_types.go @@ -4,7 +4,7 @@ import ( "sync" "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) type orderManagerConfig struct { @@ -19,7 +19,7 @@ type orderManagerConfig struct { type orderStore struct { m sync.Mutex - Orders map[string][]exchange.OrderDetail + Orders map[string][]order.Detail } type orderManager struct { @@ -31,6 +31,6 @@ type orderManager struct { } type orderSubmitResponse struct { - exchange.SubmitOrderResponse + order.SubmitResponse OurOrderID string } diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 1936aae6..6d86c014 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -18,8 +18,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/database/models/postgres" "github.com/thrasher-corp/gocryptotrader/database/models/sqlite3" "github.com/thrasher-corp/gocryptotrader/database/repository/audit" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/gctrpc" @@ -621,7 +621,7 @@ func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) ( return nil, errors.New("exchange is not loaded/doesn't exist") } - resp, err := exch.GetActiveOrders(&exchange.GetOrdersRequest{}) + resp, err := exch.GetActiveOrders(&order.GetOrdersRequest{}) if err != nil { return nil, err } @@ -634,10 +634,10 @@ func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) ( BaseCurrency: resp[x].CurrencyPair.Base.String(), QuoteCurrency: resp[x].CurrencyPair.Quote.String(), AssetType: asset.Spot.String(), - OrderType: resp[x].OrderType.ToString(), - OrderSide: resp[x].OrderSide.ToString(), + OrderType: resp[x].OrderType.String(), + OrderSide: resp[x].OrderSide.String(), CreationTime: resp[x].OrderDate.Unix(), - Status: resp[x].Status, + Status: resp[x].Status.String(), Price: resp[x].Price, Amount: resp[x].Amount, }) @@ -660,10 +660,10 @@ func (s *RPCServer) SubmitOrder(ctx context.Context, r *gctrpc.SubmitOrderReques } p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) - submission := &exchange.OrderSubmission{ + submission := &order.Submit{ Pair: p, - OrderSide: exchange.OrderSide(r.Side), - OrderType: exchange.OrderType(r.OrderType), + OrderSide: order.Side(r.Side), + OrderType: order.Type(r.OrderType), Amount: r.Amount, Price: r.Price, ClientID: r.ClientId, @@ -690,8 +690,8 @@ func (s *RPCServer) SimulateOrder(ctx context.Context, r *gctrpc.SimulateOrderRe } var buy = true - if !strings.EqualFold(r.Side, exchange.BuyOrderSide.ToString()) && - !strings.EqualFold(r.Side, exchange.BidOrderSide.ToString()) { + if !strings.EqualFold(r.Side, order.Buy.String()) && + !strings.EqualFold(r.Side, order.Bid.String()) { buy = false } @@ -727,8 +727,8 @@ func (s *RPCServer) WhaleBomb(ctx context.Context, r *gctrpc.WhaleBombRequest) ( } var buy = true - if !strings.EqualFold(r.Side, exchange.BuyOrderSide.ToString()) && - !strings.EqualFold(r.Side, exchange.BidOrderSide.ToString()) { + if !strings.EqualFold(r.Side, order.Buy.String()) && + !strings.EqualFold(r.Side, order.Bid.String()) { buy = false } @@ -757,10 +757,10 @@ func (s *RPCServer) CancelOrder(ctx context.Context, r *gctrpc.CancelOrderReques return nil, errors.New("exchange is not loaded/doesn't exist") } - err := exch.CancelOrder(&exchange.OrderCancellation{ + err := exch.CancelOrder(&order.Cancel{ AccountID: r.AccountId, OrderID: r.OrderId, - Side: exchange.OrderSide(r.Side), + Side: order.Side(r.Side), WalletAddress: r.WalletAddress, }) diff --git a/exchanges/alphapoint/alphapoint.go b/exchanges/alphapoint/alphapoint.go index 6d2859b6..f1ea56db 100644 --- a/exchanges/alphapoint/alphapoint.go +++ b/exchanges/alphapoint/alphapoint.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) const ( @@ -345,7 +346,7 @@ func (a *Alphapoint) WithdrawCoins(symbol, product, address string, amount float } func (a *Alphapoint) convertOrderTypeToOrderTypeNumber(orderType string) (orderTypeNumber int64) { - if orderType == exchange.MarketOrderType.ToString() { + if orderType == order.Market.String() { orderTypeNumber = 1 } diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index f76b1630..eed2d253 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -6,6 +6,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) const ( @@ -412,7 +413,7 @@ func TestCreateOrder(t *testing.T) { return } - _, err := a.CreateOrder("", "", exchange.LimitOrderType.ToString(), 0.01, 0) + _, err := a.CreateOrder("", "", order.Limit.String(), 0.01, 0) if err == nil { t.Error("GetUserInfo() Expected error") } @@ -494,8 +495,8 @@ func TestGetActiveOrders(t *testing.T) { a := &Alphapoint{} a.SetDefaults() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := a.GetActiveOrders(&getOrdersRequest) @@ -510,8 +511,8 @@ func TestGetOrderHistory(t *testing.T) { a := &Alphapoint{} a.SetDefaults() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := a.GetOrderHistory(&getOrdersRequest) @@ -537,14 +538,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -573,7 +574,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -599,7 +600,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -615,8 +616,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Withdraw failed to be placed: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -624,7 +625,7 @@ func TestModifyOrder(t *testing.T) { a := &Alphapoint{} a.SetDefaults() - _, err := a.ModifyOrder(&exchange.ModifyOrder{}) + _, err := a.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/alphapoint/alphapoint_types.go b/exchanges/alphapoint/alphapoint_types.go index 795f0426..081896ca 100644 --- a/exchanges/alphapoint/alphapoint_types.go +++ b/exchanges/alphapoint/alphapoint_types.go @@ -1,6 +1,8 @@ package alphapoint -import exchange "github.com/thrasher-corp/gocryptotrader/exchanges" +import ( + "github.com/thrasher-corp/gocryptotrader/exchanges/order" +) // Response contains general responses from the exchange type Response struct { @@ -198,15 +200,15 @@ type WebsocketTicker struct { } // orderSideMap holds order type info based on Alphapoint data -var orderSideMap = map[int64]exchange.OrderSide{ - 1: exchange.BuyOrderSide, - 2: exchange.SellOrderSide, +var orderSideMap = map[int64]order.Side{ + 1: order.Buy, + 2: order.Sell, } // orderTypeMap holds order type info based on Alphapoint data -var orderTypeMap = map[int]exchange.OrderType{ - 1: exchange.MarketOrderType, - 2: exchange.LimitOrderType, - 3: exchange.StopOrderType, - 6: exchange.TrailingStopOrderType, +var orderTypeMap = map[int]order.Type{ + 1: order.Market, + 2: order.Limit, + 3: order.Stop, + 6: order.TrailingStop, } diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index e92a2ad8..db352257 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -2,7 +2,6 @@ package alphapoint import ( "errors" - "fmt" "strconv" "time" @@ -11,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -200,23 +200,20 @@ func (a *Alphapoint) GetExchangeHistory(p currency.Pair, assetType asset.Item) ( // SubmitOrder submits a new order and returns a true value when // successfully submitted -func (a *Alphapoint) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (a *Alphapoint) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - response, err := a.CreateOrder(order.Pair.String(), - order.OrderSide.ToString(), - order.OrderSide.ToString(), - order.Amount, order.Price) + response, err := a.CreateOrder(s.Pair.String(), + s.OrderSide.String(), + s.OrderSide.String(), + s.Amount, + s.Price) if response > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response) + submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } if err == nil { @@ -228,25 +225,23 @@ func (a *Alphapoint) SubmitOrder(order *exchange.OrderSubmission) (exchange.Subm // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (a *Alphapoint) ModifyOrder(_ *exchange.ModifyOrder) (string, error) { +func (a *Alphapoint) ModifyOrder(_ *order.Modify) (string, error) { return "", common.ErrNotYetImplemented } // CancelOrder cancels an order by its corresponding ID number -func (a *Alphapoint) CancelOrder(order *exchange.OrderCancellation) error { +func (a *Alphapoint) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) if err != nil { return err } - _, err = a.CancelExistingOrder(orderIDInt, order.AccountID) - return err } // CancelAllOrders cancels all orders for a given account -func (a *Alphapoint) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - return exchange.CancelAllOrdersResponse{}, +func (a *Alphapoint) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { + return order.CancelAllResponse{}, a.CancelAllExistingOrders(orderCancellation.AccountID) } @@ -311,84 +306,84 @@ func (a *Alphapoint) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, err // GetActiveOrders retrieves any orders that are active/open // This function is not concurrency safe due to orderSide/orderType maps -func (a *Alphapoint) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (a *Alphapoint) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := a.GetOrders() if err != nil { return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for x := range resp { - for _, order := range resp[x].OpenOrders { - if order.State != 1 { + for y := range resp[x].OpenOrders { + if resp[x].OpenOrders[y].State != 1 { continue } - orderDetail := exchange.OrderDetail{ - Amount: order.QtyTotal, + orderDetail := order.Detail{ + Amount: resp[x].OpenOrders[y].QtyTotal, Exchange: a.Name, - AccountID: fmt.Sprintf("%v", order.AccountID), - ID: fmt.Sprintf("%v", order.ServerOrderID), - Price: order.Price, - RemainingAmount: order.QtyRemaining, + AccountID: strconv.FormatInt(int64(resp[x].OpenOrders[y].AccountID), 10), + ID: strconv.FormatInt(int64(resp[x].OpenOrders[y].ServerOrderID), 10), + Price: resp[x].OpenOrders[y].Price, + RemainingAmount: resp[x].OpenOrders[y].QtyRemaining, } - orderDetail.OrderSide = orderSideMap[order.Side] - orderDetail.OrderDate = time.Unix(order.ReceiveTime, 0) - orderDetail.OrderType = orderTypeMap[order.OrderType] + orderDetail.OrderSide = orderSideMap[resp[x].OpenOrders[y].Side] + orderDetail.OrderDate = time.Unix(resp[x].OpenOrders[y].ReceiveTime, 0) + orderDetail.OrderType = orderTypeMap[resp[x].OpenOrders[y].OrderType] if orderDetail.OrderType == "" { - orderDetail.OrderType = exchange.UnknownOrderType + orderDetail.OrderType = order.Unknown } orders = append(orders, orderDetail) } } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status // This function is not concurrency safe due to orderSide/orderType maps -func (a *Alphapoint) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (a *Alphapoint) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := a.GetOrders() if err != nil { return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for x := range resp { - for _, order := range resp[x].OpenOrders { - if order.State == 1 { + for y := range resp[x].OpenOrders { + if resp[x].OpenOrders[y].State == 1 { continue } - orderDetail := exchange.OrderDetail{ - Amount: order.QtyTotal, - AccountID: fmt.Sprintf("%v", order.AccountID), + orderDetail := order.Detail{ + Amount: resp[x].OpenOrders[y].QtyTotal, + AccountID: strconv.FormatInt(int64(resp[x].OpenOrders[y].AccountID), 10), Exchange: a.Name, - ID: fmt.Sprintf("%v", order.ServerOrderID), - Price: order.Price, - RemainingAmount: order.QtyRemaining, + ID: strconv.FormatInt(int64(resp[x].OpenOrders[y].ServerOrderID), 10), + Price: resp[x].OpenOrders[y].Price, + RemainingAmount: resp[x].OpenOrders[y].QtyRemaining, } - orderDetail.OrderSide = orderSideMap[order.Side] - orderDetail.OrderDate = time.Unix(order.ReceiveTime, 0) - orderDetail.OrderType = orderTypeMap[order.OrderType] + orderDetail.OrderSide = orderSideMap[resp[x].OpenOrders[y].Side] + orderDetail.OrderDate = time.Unix(resp[x].OpenOrders[y].ReceiveTime, 0) + orderDetail.OrderType = orderTypeMap[resp[x].OpenOrders[y].OrderType] if orderDetail.OrderType == "" { - orderDetail.OrderType = exchange.UnknownOrderType + orderDetail.OrderType = order.Unknown } orders = append(orders, orderDetail) } } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) return orders, nil } diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index 16aa053c..f2951332 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Please supply your own keys here for due diligence testing @@ -180,8 +181,8 @@ func TestFormatWithdrawPermissions(t *testing.T) { func TestGetActiveOrders(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := a.GetActiveOrders(&getOrdersRequest) @@ -197,8 +198,8 @@ func TestGetActiveOrders(t *testing.T) { func TestGetOrderHistory(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := a.GetOrderHistory(&getOrdersRequest) @@ -225,14 +226,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.MarketOrderType, + OrderSide: order.Buy, + OrderType: order.Market, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -254,7 +255,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -280,7 +281,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -297,8 +298,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("QA pass needs to be completed and mock needs to be updated error cannot be nil") } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -317,7 +318,7 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { t.Parallel() - _, err := a.ModifyOrder(&exchange.ModifyOrder{}) + _, err := a.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 9735e61a..d15c0dea 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -319,33 +320,29 @@ func (a *ANX) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]excha } // SubmitOrder submits a new order -func (a *ANX) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (a *ANX) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } var isBuying bool var limitPriceInSettlementCurrency float64 - if order.OrderSide == exchange.BuyOrderSide { + if s.OrderSide == order.Buy { isBuying = true } - if order.OrderType == exchange.LimitOrderType { - limitPriceInSettlementCurrency = order.Price + if s.OrderType == order.Limit { + limitPriceInSettlementCurrency = s.Price } - response, err := a.NewOrder(order.OrderType.ToString(), + response, err := a.NewOrder(s.OrderType.String(), isBuying, - order.Pair.Base.String(), - order.Amount, - order.Pair.Quote.String(), - order.Amount, + s.Pair.Base.String(), + s.Amount, + s.Pair.Quote.String(), + s.Amount, limitPriceInSettlementCurrency, false, "", @@ -364,21 +361,21 @@ func (a *ANX) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrder // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (a *ANX) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (a *ANX) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (a *ANX) CancelOrder(order *exchange.OrderCancellation) error { +func (a *ANX) CancelOrder(order *order.Cancel) error { orderIDs := []string{order.OrderID} _, err := a.CancelOrderByIDs(orderIDs) return err } // CancelAllOrders cancels all orders associated with a currency pair -func (a *ANX) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (a *ANX) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } placedOrders, err := a.GetOrderList(true) if err != nil { @@ -397,7 +394,7 @@ func (a *ANX) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAll for _, order := range resp.OrderCancellationResponses { if order.Error != CancelRequestSubmitted { - cancelAllOrdersResponse.OrderStatus[order.UUID] = order.Error + cancelAllOrdersResponse.Status[order.UUID] = order.Error } } @@ -405,8 +402,8 @@ func (a *ANX) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAll } // GetOrderInfo returns information on a current open order -func (a *ANX) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (a *ANX) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -450,18 +447,18 @@ func (a *ANX) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { } // GetActiveOrders retrieves any orders that are active/open -func (a *ANX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (a *ANX) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := a.GetOrderList(true) if err != nil { return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp { orderDate := time.Unix(resp[i].Timestamp, 0) - orderType := exchange.OrderType(strings.ToUpper(resp[i].OrderType)) + orderType := order.Type(strings.ToUpper(resp[i].OrderType)) - orderDetail := exchange.OrderDetail{ + orderDetail := order.Detail{ Amount: resp[i].TradedCurrencyAmount, CurrencyPair: currency.NewPairWithDelimiter(resp[i].TradedCurrency, resp[i].SettlementCurrency, @@ -471,40 +468,40 @@ func (a *ANX) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]ex ID: resp[i].OrderID, OrderType: orderType, Price: resp[i].SettlementCurrencyAmount, - Status: resp[i].OrderStatus, + Status: order.Status(resp[i].OrderStatus), } orders = append(orders, orderDetail) } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, + order.FilterOrdersByType(&orders, getOrdersRequest.OrderType) + order.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (a *ANX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (a *ANX) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := a.GetOrderList(false) if err != nil { return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp { orderDate := time.Unix(resp[i].Timestamp, 0) - orderType := exchange.OrderType(strings.ToUpper(resp[i].OrderType)) + orderType := order.Type(strings.ToUpper(resp[i].OrderType)) - orderDetail := exchange.OrderDetail{ + orderDetail := order.Detail{ Amount: resp[i].TradedCurrencyAmount, OrderDate: orderDate, Exchange: a.Name, ID: resp[i].OrderID, OrderType: orderType, Price: resp[i].SettlementCurrencyAmount, - Status: resp[i].OrderStatus, + Status: order.Status(resp[i].OrderStatus), CurrencyPair: currency.NewPairWithDelimiter(resp[i].TradedCurrency, resp[i].SettlementCurrency, a.GetPairFormat(asset.Spot, false).Delimiter), @@ -513,10 +510,9 @@ func (a *ANX) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]ex orders = append(orders, orderDetail) } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index f0426906..6249edd9 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Please supply your own keys here for due diligence testing @@ -292,8 +293,8 @@ func TestFormatWithdrawPermissions(t *testing.T) { func TestGetActiveOrders(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetActiveOrders(&getOrdersRequest) if err == nil { @@ -318,8 +319,8 @@ func TestGetActiveOrders(t *testing.T) { func TestGetOrderHistory(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetOrderHistory(&getOrdersRequest) @@ -352,14 +353,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", Base: currency.LTC, Quote: currency.BTC, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.MarketOrderType, + OrderSide: order.Buy, + OrderType: order.Market, Price: 1, Amount: 1000000000, ClientID: "meowOrder", @@ -383,7 +384,7 @@ func TestCancelExchangeOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -408,7 +409,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -443,7 +444,7 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { t.Parallel() - _, err := b.ModifyOrder(&exchange.ModifyOrder{}) + _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() error cannot be nil") } diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 47d6ecd5..e30234ac 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -182,7 +183,10 @@ func (b *Binance) Start(wg *sync.WaitGroup) { func (b *Binance) Run() { if b.Verbose { log.Debugf(log.ExchangeSys, - "%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.Websocket.GetWebsocketURL()) + "%s Websocket: %s. (url: %s).\n", + b.GetName(), + common.IsEnabled(b.Websocket.IsEnabled()), + b.Websocket.GetWebsocketURL()) b.PrintEnabledPairs() } @@ -196,7 +200,9 @@ func (b *Binance) Run() { err := b.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", b.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + b.Name, err) } } @@ -206,7 +212,10 @@ func (b *Binance) Run() { err := b.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + b.Name, + err) } } @@ -303,14 +312,20 @@ func (b *Binance) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderb return orderBook, err } - for _, bids := range orderbookNew.Bids { + for x := range orderbookNew.Bids { orderBook.Bids = append(orderBook.Bids, - orderbook.Item{Amount: bids.Quantity, Price: bids.Price}) + orderbook.Item{ + Amount: orderbookNew.Bids[x].Quantity, + Price: orderbookNew.Bids[x].Price, + }) } - for _, asks := range orderbookNew.Asks { + for x := range orderbookNew.Asks { orderBook.Asks = append(orderBook.Asks, - orderbook.Item{Amount: asks.Quantity, Price: asks.Price}) + orderbook.Item{ + Amount: orderbookNew.Asks[x].Quantity, + Price: orderbookNew.Asks[x].Price, + }) } orderBook.Pair = p @@ -335,19 +350,19 @@ func (b *Binance) GetAccountInfo() (exchange.AccountInfo, error) { } var currencyBalance []exchange.AccountCurrencyInfo - for _, balance := range raw.Balances { - freeCurrency, err := strconv.ParseFloat(balance.Free, 64) + for i := range raw.Balances { + freeCurrency, err := strconv.ParseFloat(raw.Balances[i].Free, 64) if err != nil { return info, err } - lockedCurrency, err := strconv.ParseFloat(balance.Locked, 64) + lockedCurrency, err := strconv.ParseFloat(raw.Balances[i].Locked, 64) if err != nil { return info, err } currencyBalance = append(currencyBalance, exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(balance.Asset), + CurrencyName: currency.NewCode(raw.Balances[i].Asset), TotalValue: freeCurrency + lockedCurrency, Hold: freeCurrency, }) @@ -374,28 +389,24 @@ func (b *Binance) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]e } // SubmitOrder submits a new order -func (b *Binance) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (b *Binance) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } var sideType string - if order.OrderSide == exchange.BuyOrderSide { - sideType = exchange.BuyOrderSide.ToString() + if s.OrderSide == order.Buy { + sideType = order.Buy.String() } else { - sideType = exchange.SellOrderSide.ToString() + sideType = order.Sell.String() } var requestParamsOrderType RequestParamsOrderType - switch order.OrderType { - case exchange.MarketOrderType: + switch s.OrderType { + case order.Market: requestParamsOrderType = BinanceRequestParamsOrderMarket - case exchange.LimitOrderType: + case order.Limit: requestParamsOrderType = BinanceRequestParamsOrderLimit default: submitOrderResponse.IsOrderPlaced = false @@ -403,10 +414,10 @@ func (b *Binance) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitO } var orderRequest = NewOrderRequest{ - Symbol: order.Pair.Base.String() + order.Pair.Quote.String(), + Symbol: s.Pair.Base.String() + s.Pair.Quote.String(), Side: sideType, - Price: order.Price, - Quantity: order.Amount, + Price: s.Price, + Quantity: s.Amount, TradeType: requestParamsOrderType, TimeInForce: BinanceRequestParamsTimeGTC, } @@ -414,7 +425,7 @@ func (b *Binance) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitO response, err := b.NewOrder(&orderRequest) if response.OrderID > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response.OrderID) + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) } if err == nil { @@ -426,27 +437,28 @@ func (b *Binance) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitO // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (b *Binance) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (b *Binance) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (b *Binance) CancelOrder(order *exchange.OrderCancellation) error { +func (b *Binance) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) if err != nil { return err } _, err = b.CancelExistingOrder(b.FormatExchangeCurrency(order.CurrencyPair, - order.AssetType).String(), orderIDInt, order.AccountID) - + order.AssetType).String(), + orderIDInt, + order.AccountID) return err } // CancelAllOrders cancels all orders associated with a currency pair -func (b *Binance) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (b *Binance) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } openOrders, err := b.OpenOrders("") if err != nil { @@ -454,9 +466,11 @@ func (b *Binance) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cance } for i := range openOrders { - _, err = b.CancelExistingOrder(openOrders[i].Symbol, openOrders[i].OrderID, "") + _, err = b.CancelExistingOrder(openOrders[i].Symbol, + openOrders[i].OrderID, + "") if err != nil { - cancelAllOrdersResponse.OrderStatus[strconv.FormatInt(openOrders[i].OrderID, 10)] = err.Error() + cancelAllOrdersResponse.Status[strconv.FormatInt(openOrders[i].OrderID, 10)] = err.Error() } } @@ -464,8 +478,8 @@ func (b *Binance) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cance } // GetOrderInfo returns information on a current open order -func (b *Binance) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (b *Binance) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -511,85 +525,87 @@ func (b *Binance) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) } // GetActiveOrders retrieves any orders that are active/open -func (b *Binance) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - if len(getOrdersRequest.Currencies) == 0 { +func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { + if len(req.Currencies) == 0 { return nil, errors.New("at least one currency is required to fetch order history") } - var orders []exchange.OrderDetail - for _, c := range getOrdersRequest.Currencies { - resp, err := b.OpenOrders(b.FormatExchangeCurrency(c, + var orders []order.Detail + for x := range req.Currencies { + resp, err := b.OpenOrders(b.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String()) if err != nil { return nil, err } for i := range resp { - orderSide := exchange.OrderSide(strings.ToUpper(resp[i].Side)) - orderType := exchange.OrderType(strings.ToUpper(resp[i].Type)) + orderSide := order.Side(strings.ToUpper(resp[i].Side)) + orderType := order.Type(strings.ToUpper(resp[i].Type)) orderDate := time.Unix(0, int64(resp[i].Time)*int64(time.Millisecond)) - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ Amount: resp[i].OrigQty, OrderDate: orderDate, Exchange: b.Name, - ID: fmt.Sprintf("%v", resp[i].OrderID), + ID: strconv.FormatInt(resp[i].OrderID, 10), OrderSide: orderSide, OrderType: orderType, Price: resp[i].Price, - Status: resp[i].Status, + Status: order.Status(resp[i].Status), CurrencyPair: currency.NewPairFromString(resp[i].Symbol), }) } } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (b *Binance) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - if len(getOrdersRequest.Currencies) == 0 { +func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + if len(req.Currencies) == 0 { return nil, errors.New("at least one currency is required to fetch order history") } - var orders []exchange.OrderDetail - for _, c := range getOrdersRequest.Currencies { - resp, err := b.AllOrders(b.FormatExchangeCurrency(c, - asset.Spot).String(), "", "1000") + var orders []order.Detail + for x := range req.Currencies { + resp, err := b.AllOrders(b.FormatExchangeCurrency(req.Currencies[x], + asset.Spot).String(), + "", + "1000") if err != nil { return nil, err } for i := range resp { - orderSide := exchange.OrderSide(strings.ToUpper(resp[i].Side)) - orderType := exchange.OrderType(strings.ToUpper(resp[i].Type)) + orderSide := order.Side(strings.ToUpper(resp[i].Side)) + orderType := order.Type(strings.ToUpper(resp[i].Type)) orderDate := time.Unix(0, int64(resp[i].Time)*int64(time.Millisecond)) // New orders are covered in GetOpenOrders if resp[i].Status == "NEW" { continue } - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ Amount: resp[i].OrigQty, OrderDate: orderDate, Exchange: b.Name, - ID: fmt.Sprintf("%v", resp[i].OrderID), + ID: strconv.FormatInt(resp[i].OrderID, 10), OrderSide: orderSide, OrderType: orderType, Price: resp[i].Price, CurrencyPair: currency.NewPairFromString(resp[i].Symbol), - Status: resp[i].Status, + Status: order.Status(resp[i].Status), }) } } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) return orders, nil } diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index a02bc5a6..80113385 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -362,10 +363,10 @@ func (b *Bitfinex) GetTradesV2(currencyPair string, timestampStart, timestampEnd tempHistory.Amount = data[2].(float64) tempHistory.Price = data[3].(float64) tempHistory.Exchange = b.Name - tempHistory.Type = exchange.BuyOrderSide.ToString() + tempHistory.Type = order.Buy.String() if tempHistory.Amount < 0 { - tempHistory.Type = exchange.SellOrderSide.ToString() + tempHistory.Type = order.Sell.String() tempHistory.Amount *= -1 } @@ -582,9 +583,9 @@ func (b *Bitfinex) NewOrder(currencyPair string, amount, price float64, buy bool req["is_hidden"] = hidden if buy { - req["side"] = exchange.BuyOrderSide.ToLower().ToString() + req["side"] = order.Buy.Lower() } else { - req["side"] = exchange.SellOrderSide.ToLower().ToString() + req["side"] = order.Sell.Lower() } return response, @@ -657,9 +658,9 @@ func (b *Bitfinex) ReplaceOrder(orderID int64, symbol string, amount, price floa req["is_hidden"] = hidden if buy { - req["side"] = exchange.BuyOrderSide.ToLower().ToString() + req["side"] = order.Buy.Lower() } else { - req["side"] = exchange.SellOrderSide.ToLower().ToString() + req["side"] = order.Sell.Lower() } return response, diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 2119f97f..a8e2aa1f 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -365,8 +366,12 @@ func TestNewOrder(t *testing.T) { } t.Parallel() - _, err := b.NewOrder("BTCUSD", 1, 2, true, - exchange.LimitOrderType.ToLower().ToString(), false) + _, err := b.NewOrder("BTCUSD", + 1, + 2, + true, + order.Limit.Lower(), + false) if err == nil { t.Error("NewOrder() Expected error") } @@ -384,8 +389,8 @@ func TestNewOrderMulti(t *testing.T) { Amount: 1, Price: 1, Exchange: "bitfinex", - Side: exchange.BuyOrderSide.ToLower().ToString(), - Type: exchange.LimitOrderType.ToLower().ToString(), + Side: order.Buy.Lower(), + Type: order.Limit.Lower(), }, } @@ -438,7 +443,7 @@ func TestReplaceOrder(t *testing.T) { t.Parallel() _, err := b.ReplaceOrder(1337, "BTCUSD", - 1, 1, true, exchange.LimitOrderType.ToLower().ToString(), false) + 1, 1, true, order.Limit.Lower(), false) if err == nil { t.Error("ReplaceOrder() Expected error") } @@ -748,8 +753,8 @@ func TestGetActiveOrders(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetActiveOrders(&getOrdersRequest) @@ -764,8 +769,8 @@ func TestGetOrderHistory(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetOrderHistory(&getOrdersRequest) @@ -789,14 +794,14 @@ func TestSubmitOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -819,7 +824,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -845,7 +850,7 @@ func TestCancelAllExchangeOrdera(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -861,13 +866,13 @@ func TestCancelAllExchangeOrdera(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { - _, err := b.ModifyOrder(&exchange.ModifyOrder{}) + _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index cb79bd66..5b247971 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -189,7 +190,9 @@ func (b *Bitfinex) Start(wg *sync.WaitGroup) { func (b *Bitfinex) Run() { if b.Verbose { log.Debugf(log.ExchangeSys, - "%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled())) + "%s Websocket: %s.", + b.GetName(), + common.IsEnabled(b.Websocket.IsEnabled())) b.PrintEnabledPairs() } @@ -326,14 +329,14 @@ func (b *Bitfinex) GetAccountInfo() (exchange.AccountInfo, error) { {ID: "trading"}, } - for _, bal := range accountBalance { + for x := range accountBalance { for i := range Accounts { - if Accounts[i].ID == bal.Type { + if Accounts[i].ID == accountBalance[x].Type { Accounts[i].Currencies = append(Accounts[i].Currencies, exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(bal.Currency), - TotalValue: bal.Amount, - Hold: bal.Amount - bal.Available, + CurrencyName: currency.NewCode(accountBalance[x].Currency), + TotalValue: accountBalance[x].Amount, + Hold: accountBalance[x].Amount - accountBalance[x].Available, }) } } @@ -356,30 +359,26 @@ func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([] } // SubmitOrder submits a new order -func (b *Bitfinex) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (b *Bitfinex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } var isBuying bool - if order.OrderSide == exchange.BuyOrderSide { + if s.OrderSide == order.Buy { isBuying = true } - b.appendOptionalDelimiter(&order.Pair) - response, err := b.NewOrder(order.Pair.String(), - order.Amount, - order.Price, + b.appendOptionalDelimiter(&s.Pair) + response, err := b.NewOrder(s.Pair.String(), + s.Amount, + s.Price, isBuying, - order.OrderType.ToString(), + s.OrderType.String(), false) if response.OrderID > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response.OrderID) + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) } if err == nil { @@ -391,31 +390,29 @@ func (b *Bitfinex) SubmitOrder(order *exchange.OrderSubmission) (exchange.Submit // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (b *Bitfinex) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (b *Bitfinex) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (b *Bitfinex) CancelOrder(order *exchange.OrderCancellation) error { +func (b *Bitfinex) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) - if err != nil { return err } - _, err = b.CancelExistingOrder(orderIDInt) return err } // CancelAllOrders cancels all orders associated with a currency pair -func (b *Bitfinex) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { +func (b *Bitfinex) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { _, err := b.CancelAllExistingOrders() - return exchange.CancelAllOrdersResponse{}, err + return order.CancelAllResponse{}, err } // GetOrderInfo returns information on a current open order -func (b *Bitfinex) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (b *Bitfinex) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -454,7 +451,7 @@ func (b *Bitfinex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoW return "", errors.New("no withdrawID returned. Check order status") } - return fmt.Sprintf("%v", resp[0].WithdrawalID), err + return strconv.FormatInt(resp[0].WithdrawalID, 10), err } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is submitted @@ -511,26 +508,28 @@ func (b *Bitfinex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error } // GetActiveOrders retrieves any orders that are active/open -func (b *Bitfinex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var orders []exchange.OrderDetail +func (b *Bitfinex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { + var orders []order.Detail resp, err := b.GetOpenOrders() if err != nil { return nil, err } for i := range resp { - orderSide := exchange.OrderSide(strings.ToUpper(resp[i].Side)) + orderSide := order.Side(strings.ToUpper(resp[i].Side)) timestamp, err := strconv.ParseInt(resp[i].Timestamp, 10, 64) if err != nil { - log.Warnf(log.ExchangeSys, "Unable to convert timestamp '%v', leaving blank", resp[i].Timestamp) + log.Warnf(log.ExchangeSys, + "Unable to convert timestamp '%s', leaving blank", + resp[i].Timestamp) } orderDate := time.Unix(timestamp, 0) - orderDetail := exchange.OrderDetail{ + orderDetail := order.Detail{ Amount: resp[i].OriginalAmount, OrderDate: orderDate, Exchange: b.Name, - ID: fmt.Sprintf("%v", resp[i].OrderID), + ID: strconv.FormatInt(resp[i].OrderID, 10), OrderSide: orderSide, Price: resp[i].Price, RemainingAmount: resp[i].RemainingAmount, @@ -540,56 +539,56 @@ func (b *Bitfinex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) switch { case resp[i].IsLive: - orderDetail.Status = string(exchange.ActiveOrderStatus) + orderDetail.Status = order.Active case resp[i].IsCancelled: - orderDetail.Status = string(exchange.CancelledOrderStatus) + orderDetail.Status = order.Cancelled case resp[i].IsHidden: - orderDetail.Status = string(exchange.HiddenOrderStatus) + orderDetail.Status = order.Hidden default: - orderDetail.Status = string(exchange.UnknownOrderStatus) + orderDetail.Status = order.UnknownStatus } // API docs discrepency. Example contains prefixed "exchange " // Return type suggests “market” / “limit” / “stop” / “trailing-stop” orderType := strings.Replace(resp[i].Type, "exchange ", "", 1) if orderType == "trailing-stop" { - orderDetail.OrderType = exchange.TrailingStopOrderType + orderDetail.OrderType = order.TrailingStop } else { - orderDetail.OrderType = exchange.OrderType(strings.ToUpper(orderType)) + orderDetail.OrderType = order.Type(strings.ToUpper(orderType)) } orders = append(orders, orderDetail) } - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (b *Bitfinex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var orders []exchange.OrderDetail +func (b *Bitfinex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + var orders []order.Detail resp, err := b.GetInactiveOrders() if err != nil { return nil, err } for i := range resp { - orderSide := exchange.OrderSide(strings.ToUpper(resp[i].Side)) + orderSide := order.Side(strings.ToUpper(resp[i].Side)) timestamp, err := strconv.ParseInt(resp[i].Timestamp, 10, 64) if err != nil { log.Warnf(log.ExchangeSys, "Unable to convert timestamp '%v', leaving blank", resp[i].Timestamp) } orderDate := time.Unix(timestamp, 0) - orderDetail := exchange.OrderDetail{ + orderDetail := order.Detail{ Amount: resp[i].OriginalAmount, OrderDate: orderDate, Exchange: b.Name, - ID: fmt.Sprintf("%v", resp[i].OrderID), + ID: strconv.FormatInt(resp[i].OrderID, 10), OrderSide: orderSide, Price: resp[i].Price, RemainingAmount: resp[i].RemainingAmount, @@ -599,34 +598,34 @@ func (b *Bitfinex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) switch { case resp[i].IsLive: - orderDetail.Status = string(exchange.ActiveOrderStatus) + orderDetail.Status = order.Active case resp[i].IsCancelled: - orderDetail.Status = string(exchange.CancelledOrderStatus) + orderDetail.Status = order.Cancelled case resp[i].IsHidden: - orderDetail.Status = string(exchange.HiddenOrderStatus) + orderDetail.Status = order.Hidden default: - orderDetail.Status = string(exchange.UnknownOrderStatus) + orderDetail.Status = order.UnknownStatus } // API docs discrepency. Example contains prefixed "exchange " // Return type suggests “market” / “limit” / “stop” / “trailing-stop” orderType := strings.Replace(resp[i].Type, "exchange ", "", 1) if orderType == "trailing-stop" { - orderDetail.OrderType = exchange.TrailingStopOrderType + orderDetail.OrderType = order.TrailingStop } else { - orderDetail.OrderType = exchange.OrderType(strings.ToUpper(orderType)) + orderDetail.OrderType = order.Type(strings.ToUpper(orderType)) } orders = append(orders, orderDetail) } - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - for i := range getOrdersRequest.Currencies { - b.appendOptionalDelimiter(&getOrdersRequest.Currencies[i]) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + for i := range req.Currencies { + b.appendOptionalDelimiter(&req.Currencies[i]) } - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index 83690b9c..3364b944 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -8,6 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -269,8 +270,8 @@ func TestGetActiveOrders(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetActiveOrders(&getOrdersRequest) @@ -285,8 +286,8 @@ func TestGetOrderHistory(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetOrderHistory(&getOrdersRequest) @@ -309,13 +310,13 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.LTC, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -335,7 +336,7 @@ func TestCancelExchangeOrder(t *testing.T) { } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -358,7 +359,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -395,7 +396,7 @@ func TestWithdraw(t *testing.T) { } func TestModifyOrder(t *testing.T) { - _, err := b.ModifyOrder(&exchange.ModifyOrder{}) + _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index 145fab0f..992226dd 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -139,11 +140,14 @@ func (b *Bitflyer) FetchTradablePairs(assetType asset.Item) ([]string, error) { } var products []string - for _, info := range pairs { - if info.Alias != "" && assetType == asset.Futures { - products = append(products, info.Alias) - } else if info.Alias == "" && assetType == asset.Spot && strings.Contains(info.ProductCode, b.GetPairFormat(assetType, false).Delimiter) { - products = append(products, info.ProductCode) + for i := range pairs { + if pairs[i].Alias != "" && assetType == asset.Futures { + products = append(products, pairs[i].Alias) + } else if pairs[i].Alias == "" && + assetType == asset.Spot && + strings.Contains(pairs[i].ProductCode, + b.GetPairFormat(assetType, false).Delimiter) { + products = append(products, pairs[i].ProductCode) } } return products, nil @@ -159,7 +163,10 @@ func (b *Bitflyer) UpdateTradablePairs(forceUpdate bool) error { return err } - err = b.UpdatePairs(currency.NewPairsFromStrings(pairs), a, false, forceUpdate) + err = b.UpdatePairs(currency.NewPairsFromStrings(pairs), + a, + false, + forceUpdate) if err != nil { return err } @@ -267,31 +274,31 @@ func (b *Bitflyer) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([] } // SubmitOrder submits a new order -func (b *Bitflyer) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - return exchange.SubmitOrderResponse{}, common.ErrNotYetImplemented +func (b *Bitflyer) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + return order.SubmitResponse{}, common.ErrNotYetImplemented } // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (b *Bitflyer) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (b *Bitflyer) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (b *Bitflyer) CancelOrder(order *exchange.OrderCancellation) error { +func (b *Bitflyer) CancelOrder(order *order.Cancel) error { return common.ErrNotYetImplemented } // CancelAllOrders cancels all orders associated with a currency pair -func (b *Bitflyer) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { +func (b *Bitflyer) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { // TODO, implement BitFlyer API b.CancelAllExistingOrders() - return exchange.CancelAllOrdersResponse{}, common.ErrNotYetImplemented + return order.CancelAllResponse{}, common.ErrNotYetImplemented } // GetOrderInfo returns information on a current open order -func (b *Bitflyer) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (b *Bitflyer) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -324,13 +331,13 @@ func (b *Bitflyer) GetWebsocket() (*wshandler.Websocket, error) { } // GetActiveOrders retrieves any orders that are active/open -func (b *Bitflyer) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (b *Bitflyer) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { return nil, common.ErrNotYetImplemented } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (b *Bitflyer) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (b *Bitflyer) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { return nil, common.ErrNotYetImplemented } diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index 7ec45d63..05f5ad56 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Please supply your own keys here for due diligence testing @@ -308,9 +309,9 @@ func TestGetActiveOrders(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, - OrderSide: exchange.SellOrderSide, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, + OrderSide: order.Sell, } _, err := b.GetActiveOrders(&getOrdersRequest) @@ -325,8 +326,8 @@ func TestGetOrderHistory(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetOrderHistory(&getOrdersRequest) @@ -351,13 +352,13 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.LTC, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -380,7 +381,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -406,7 +407,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -422,8 +423,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel order: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -444,10 +445,11 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { curr := currency.NewPairFromString("BTCUSD") - _, err := b.ModifyOrder(&exchange.ModifyOrder{OrderID: "1337", + _, err := b.ModifyOrder(&order.Modify{ + OrderID: "1337", Price: 100, Amount: 1000, - OrderSide: exchange.SellOrderSide, + Side: order.Sell, CurrencyPair: curr}) if err == nil { t.Error("ModifyOrder() Expected error") diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 7a577543..d8f3041d 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -5,7 +5,6 @@ import ( "fmt" "math" "strconv" - "strings" "sync" "time" @@ -14,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -180,15 +180,16 @@ func (b *Bithumb) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr pairs := b.GetEnabledPairs(assetType) for i := range pairs { curr := pairs[i].Base.String() - if _, ok := tickers[curr]; !ok { + t, ok := tickers[curr] + if !ok { continue } tp := ticker.Price{ - High: tickers[curr].MaxPrice, - Low: tickers[curr].MinPrice, - Volume: tickers[curr].UnitsTraded24Hr, - Open: tickers[curr].OpeningPrice, - Close: tickers[curr].ClosingPrice, + High: t.MaxPrice, + Low: t.MinPrice, + Volume: t.UnitsTraded24Hr, + Open: t.OpeningPrice, + Close: t.ClosingPrice, Pair: pairs[i], } err = ticker.ProcessTicker(b.Name, &tp, assetType) @@ -227,12 +228,20 @@ func (b *Bithumb) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderb return orderBook, err } - for _, bids := range orderbookNew.Data.Bids { - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: bids.Quantity, Price: bids.Price}) + for i := range orderbookNew.Data.Bids { + orderBook.Bids = append(orderBook.Bids, + orderbook.Item{ + Amount: orderbookNew.Data.Bids[i].Quantity, + Price: orderbookNew.Data.Bids[i].Price, + }) } - for _, asks := range orderbookNew.Data.Asks { - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: asks.Quantity, Price: asks.Price}) + for i := range orderbookNew.Data.Asks { + orderBook.Asks = append(orderBook.Asks, + orderbook.Item{ + Amount: orderbookNew.Data.Asks[i].Quantity, + Price: orderbookNew.Data.Asks[i].Price, + }) } orderBook.Pair = p @@ -293,30 +302,26 @@ func (b *Bithumb) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]e // SubmitOrder submits a new order // TODO: Fill this out to support limit orders -func (b *Bithumb) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (b *Bithumb) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } var orderID string var err error - if order.OrderSide == exchange.BuyOrderSide { + if s.OrderSide == order.Buy { var result MarketBuy - result, err = b.MarketBuyOrder(order.Pair.Base.String(), order.Amount) + result, err = b.MarketBuyOrder(s.Pair.Base.String(), s.Amount) orderID = result.OrderID - } else if order.OrderSide == exchange.SellOrderSide { + } else if s.OrderSide == order.Sell { var result MarketSell - result, err = b.MarketSellOrder(order.Pair.Base.String(), order.Amount) + result, err = b.MarketSellOrder(s.Pair.Base.String(), s.Amount) orderID = result.OrderID } if orderID != "" { - submitOrderResponse.OrderID = fmt.Sprintf("%v", orderID) + submitOrderResponse.OrderID = orderID } if err == nil { submitOrderResponse.IsOrderPlaced = true @@ -326,10 +331,10 @@ func (b *Bithumb) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitO // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (b *Bithumb) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (b *Bithumb) ModifyOrder(action *order.Modify) (string, error) { order, err := b.ModifyTrade(action.OrderID, action.CurrencyPair.Base.String(), - strings.ToLower(action.OrderSide.ToString()), + action.Side.Lower(), action.Amount, int64(action.Price)) @@ -341,23 +346,23 @@ func (b *Bithumb) ModifyOrder(action *exchange.ModifyOrder) (string, error) { } // CancelOrder cancels an order by its corresponding ID number -func (b *Bithumb) CancelOrder(order *exchange.OrderCancellation) error { - _, err := b.CancelTrade(order.Side.ToString(), +func (b *Bithumb) CancelOrder(order *order.Cancel) error { + _, err := b.CancelTrade(order.Side.String(), order.OrderID, order.CurrencyPair.Base.String()) return err } // CancelAllOrders cancels all orders associated with a currency pair -func (b *Bithumb) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (b *Bithumb) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } var allOrders []OrderData for _, currency := range b.GetEnabledPairs(asset.Spot) { orders, err := b.GetOrders("", - orderCancellation.Side.ToString(), + orderCancellation.Side.String(), "100", "", currency.Base.String()) @@ -368,11 +373,11 @@ func (b *Bithumb) CancelAllOrders(orderCancellation *exchange.OrderCancellation) } for i := range allOrders { - _, err := b.CancelTrade(orderCancellation.Side.ToString(), + _, err := b.CancelTrade(orderCancellation.Side.String(), allOrders[i].OrderID, orderCancellation.CurrencyPair.Base.String()) if err != nil { - cancelAllOrdersResponse.OrderStatus[allOrders[i].OrderID] = err.Error() + cancelAllOrdersResponse.Status[allOrders[i].OrderID] = err.Error() } } @@ -380,8 +385,8 @@ func (b *Bithumb) CancelAllOrders(orderCancellation *exchange.OrderCancellation) } // GetOrderInfo returns information on a current open order -func (b *Bithumb) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (b *Bithumb) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -398,7 +403,10 @@ func (b *Bithumb) GetDepositAddress(cryptocurrency currency.Code, _ string) (str // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted func (b *Bithumb) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { - _, err := b.WithdrawCrypto(withdrawRequest.Address, withdrawRequest.AddressTag, withdrawRequest.Currency.String(), withdrawRequest.Amount) + _, err := b.WithdrawCrypto(withdrawRequest.Address, + withdrawRequest.AddressTag, + withdrawRequest.Currency.String(), + withdrawRequest.Amount) return "", err } @@ -445,8 +453,8 @@ func (b *Bithumb) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) } // GetActiveOrders retrieves any orders that are active/open -func (b *Bithumb) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var orders []exchange.OrderDetail +func (b *Bithumb) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { + var orders []order.Detail resp, err := b.GetOrders("", "", "1000", "", "") if err != nil { return nil, err @@ -458,39 +466,38 @@ func (b *Bithumb) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( } orderDate := time.Unix(resp.Data[i].OrderDate, 0) - orderDetail := exchange.OrderDetail{ + orderDetail := order.Detail{ Amount: resp.Data[i].Units, Exchange: b.Name, ID: resp.Data[i].OrderID, OrderDate: orderDate, Price: resp.Data[i].Price, RemainingAmount: resp.Data[i].UnitsRemaining, - Status: string(exchange.ActiveOrderStatus), + Status: order.Active, CurrencyPair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency, resp.Data[i].PaymentCurrency, b.GetPairFormat(asset.Spot, false).Delimiter), } if resp.Data[i].Type == "bid" { - orderDetail.OrderSide = exchange.BuyOrderSide + orderDetail.OrderSide = order.Buy } else if resp.Data[i].Type == "ask" { - orderDetail.OrderSide = exchange.SellOrderSide + orderDetail.OrderSide = order.Sell } orders = append(orders, orderDetail) } - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (b *Bithumb) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var orders []exchange.OrderDetail +func (b *Bithumb) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + var orders []order.Detail resp, err := b.GetOrders("", "", "1000", "", "") if err != nil { return nil, err @@ -502,7 +509,7 @@ func (b *Bithumb) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( } orderDate := time.Unix(resp.Data[i].OrderDate, 0) - orderDetail := exchange.OrderDetail{ + orderDetail := order.Detail{ Amount: resp.Data[i].Units, Exchange: b.Name, ID: resp.Data[i].OrderID, @@ -515,18 +522,17 @@ func (b *Bithumb) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( } if resp.Data[i].Type == "bid" { - orderDetail.OrderSide = exchange.BuyOrderSide + orderDetail.OrderSide = order.Buy } else if resp.Data[i].Type == "ask" { - orderDetail.OrderSide = exchange.SellOrderSide + orderDetail.OrderSide = order.Sell } orders = append(orders, orderDetail) } - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index 36a93e78..c1dd5b64 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -246,7 +247,7 @@ func TestCancelOrders(t *testing.T) { func TestCancelAllOrders(t *testing.T) { _, err := b.CancelAllExistingOrders(OrderCancelAllParams{}) if err == nil { - t.Error("CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error)", err) + t.Error("CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error)", err) } } @@ -483,8 +484,8 @@ func TestGetActiveOrders(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetActiveOrders(&getOrdersRequest) @@ -499,8 +500,8 @@ func TestGetOrderHistory(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)}, } @@ -527,13 +528,13 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.XBT, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -556,7 +557,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "123456789012345678901234567890123456", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -582,7 +583,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "123456789012345678901234567890123456", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -598,8 +599,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -618,7 +619,7 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { - _, err := b.ModifyOrder(&exchange.ModifyOrder{OrderID: "1337"}) + _, err := b.ModifyOrder(&order.Modify{OrderID: "1337"}) if err == nil { t.Error("ModifyOrder() error") } diff --git a/exchanges/bitmex/bitmex_types.go b/exchanges/bitmex/bitmex_types.go index 399ee0a9..9445045b 100644 --- a/exchanges/bitmex/bitmex_types.go +++ b/exchanges/bitmex/bitmex_types.go @@ -4,7 +4,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // RequestError allows for a general error capture from requests @@ -675,15 +675,15 @@ type WalletInfo struct { } // orderTypeMap holds order type info based on Bitmex data -var orderTypeMap = map[int64]exchange.OrderType{ - 1: exchange.MarketOrderType, - 2: exchange.LimitOrderType, - 3: exchange.StopOrderType, - 7: exchange.TrailingStopOrderType, +var orderTypeMap = map[int64]order.Type{ + 1: order.Market, + 2: order.Limit, + 3: order.Stop, + 7: order.TrailingStop, } // orderSideMap holds order type info based on Bitmex data -var orderSideMap = map[int64]exchange.OrderSide{ - 1: exchange.BuyOrderSide, - 2: exchange.SellOrderSide, +var orderSideMap = map[int64]order.Side{ + 1: order.Buy, + 2: order.Sell, } diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 0c82fffc..bb77a528 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" @@ -340,7 +341,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai var newOrderBook orderbook.Base var bids, asks []orderbook.Item for i := range data { - if strings.EqualFold(data[i].Side, exchange.SellOrderSide.ToString()) { + if strings.EqualFold(data[i].Side, order.Sell.String()) { asks = append(asks, orderbook.Item{ Price: data[i].Price, Amount: float64(data[i].Size), diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index d91adaa2..d7968829 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -345,12 +346,12 @@ func (b *Bitmex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo } for _, ob := range orderbookNew { - if strings.EqualFold(ob.Side, exchange.SellOrderSide.ToString()) { + if strings.EqualFold(ob.Side, order.Sell.String()) { orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: float64(ob.Size), Price: ob.Price}) continue } - if strings.EqualFold(ob.Side, exchange.BuyOrderSide.ToString()) { + if strings.EqualFold(ob.Side, order.Buy.String()) { orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: float64(ob.Size), Price: ob.Price}) continue @@ -408,30 +409,26 @@ func (b *Bitmex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex } // SubmitOrder submits a new order -func (b *Bitmex) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (b *Bitmex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - if math.Mod(order.Amount, 1) != 0 { + if math.Mod(s.Amount, 1) != 0 { return submitOrderResponse, errors.New("order contract amount can not have decimals") } var orderNewParams = OrderNewParams{ - OrdType: order.OrderSide.ToString(), - Symbol: order.Pair.String(), - OrderQty: order.Amount, - Side: order.OrderSide.ToString(), + OrdType: s.OrderSide.String(), + Symbol: s.Pair.String(), + OrderQty: s.Amount, + Side: s.OrderSide.String(), } - if order.OrderType == exchange.LimitOrderType { - orderNewParams.Price = order.Price + if s.OrderType == order.Limit { + orderNewParams.Price = s.Price } response, err := b.CreateOrder(&orderNewParams) @@ -448,7 +445,7 @@ func (b *Bitmex) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOr // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (b *Bitmex) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (b *Bitmex) ModifyOrder(action *order.Modify) (string, error) { var params OrderAmendParams if math.Mod(action.Amount, 1) != 0 { @@ -468,19 +465,18 @@ func (b *Bitmex) ModifyOrder(action *exchange.ModifyOrder) (string, error) { } // CancelOrder cancels an order by its corresponding ID number -func (b *Bitmex) CancelOrder(order *exchange.OrderCancellation) error { +func (b *Bitmex) CancelOrder(order *order.Cancel) error { var params = OrderCancelParams{ OrderID: order.OrderID, } _, err := b.CancelOrders(¶ms) - return err } // CancelAllOrders cancels all orders associated with a currency pair -func (b *Bitmex) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (b *Bitmex) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } var emptyParams OrderCancelAllParams orders, err := b.CancelAllExistingOrders(emptyParams) @@ -490,7 +486,7 @@ func (b *Bitmex) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel for i := range orders { if orders[i].OrdRejReason != "" { - cancelAllOrdersResponse.OrderStatus[orders[i].OrderID] = orders[i].OrdRejReason + cancelAllOrdersResponse.Status[orders[i].OrderID] = orders[i].OrdRejReason } } @@ -498,8 +494,8 @@ func (b *Bitmex) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel } // GetOrderInfo returns information on a current open order -func (b *Bitmex) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (b *Bitmex) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -557,8 +553,8 @@ func (b *Bitmex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) // GetActiveOrders retrieves any orders that are active/open // This function is not concurrency safe due to orderSide/orderType maps -func (b *Bitmex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var orders []exchange.OrderDetail +func (b *Bitmex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { + var orders []order.Detail params := OrdersRequest{} params.Filter = "{\"open\":true}" @@ -571,17 +567,17 @@ func (b *Bitmex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ orderSide := orderSideMap[resp[i].Side] orderType := orderTypeMap[resp[i].OrdType] if orderType == "" { - orderType = exchange.UnknownOrderType + orderType = order.Unknown } - orderDetail := exchange.OrderDetail{ + orderDetail := order.Detail{ Price: resp[i].Price, Amount: float64(resp[i].OrderQty), Exchange: b.Name, ID: resp[i].OrderID, OrderSide: orderSide, OrderType: orderType, - Status: resp[i].OrdStatus, + Status: order.Status(resp[i].OrdStatus), CurrencyPair: currency.NewPairWithDelimiter(resp[i].Symbol, resp[i].SettlCurrency, b.GetPairFormat(asset.PerpetualContract, false).Delimiter), @@ -590,19 +586,18 @@ func (b *Bitmex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ orders = append(orders, orderDetail) } - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status // This function is not concurrency safe due to orderSide/orderType maps -func (b *Bitmex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var orders []exchange.OrderDetail +func (b *Bitmex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + var orders []order.Detail params := OrdersRequest{} resp, err := b.GetOrders(¶ms) if err != nil { @@ -613,17 +608,17 @@ func (b *Bitmex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ orderSide := orderSideMap[resp[i].Side] orderType := orderTypeMap[resp[i].OrdType] if orderType == "" { - orderType = exchange.UnknownOrderType + orderType = order.Unknown } - orderDetail := exchange.OrderDetail{ + orderDetail := order.Detail{ Price: resp[i].Price, Amount: float64(resp[i].OrderQty), Exchange: b.Name, ID: resp[i].OrderID, OrderSide: orderSide, OrderType: orderType, - Status: resp[i].OrdStatus, + Status: order.Status(resp[i].OrdStatus), CurrencyPair: currency.NewPairWithDelimiter(resp[i].Symbol, resp[i].SettlCurrency, b.GetPairFormat(asset.PerpetualContract, false).Delimiter), @@ -632,10 +627,10 @@ func (b *Bitmex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ orders = append(orders, orderDetail) } - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index cc0a72dd..64ecc018 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -15,6 +15,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -381,10 +382,10 @@ func (b *Bitstamp) PlaceOrder(currencyPair string, price, amount float64, buy, m req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("price", strconv.FormatFloat(price, 'f', -1, 64)) response := Order{} - orderType := exchange.BuyOrderSide.ToLower().ToString() + orderType := order.Buy.Lower() if !buy { - orderType = exchange.SellOrderSide.ToLower().ToString() + orderType = order.Sell.Lower() } var path string diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 1ddbfaaa..8720d655 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -6,6 +6,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Please add your private keys and customerID for better tests @@ -328,8 +329,8 @@ func TestFormatWithdrawPermissions(t *testing.T) { func TestGetActiveOrders(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetActiveOrders(&getOrdersRequest) @@ -346,8 +347,8 @@ func TestGetActiveOrders(t *testing.T) { func TestGetOrderHistory(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetOrderHistory(&getOrdersRequest) @@ -371,13 +372,13 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -402,7 +403,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -429,7 +430,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -446,15 +447,15 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { t.Parallel() - _, err := b.ModifyOrder(&exchange.ModifyOrder{}) + _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 48fe98a4..b04dc4d0 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -2,7 +2,6 @@ package bitstamp import ( "errors" - "fmt" "strconv" "strings" "sync" @@ -13,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -181,7 +181,10 @@ func (b *Bitstamp) Start(wg *sync.WaitGroup) { // Run implements the Bitstamp wrapper func (b *Bitstamp) Run() { if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled())) + log.Debugf(log.ExchangeSys, + "%s Websocket: %s.", + b.GetName(), + common.IsEnabled(b.Websocket.IsEnabled())) b.PrintEnabledPairs() } @@ -191,7 +194,10 @@ func (b *Bitstamp) Run() { err := b.UpdateTradablePairs(false) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + b.Name, + err) } } @@ -364,23 +370,22 @@ func (b *Bitstamp) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([] } // SubmitOrder submits a new order -func (b *Bitstamp) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (b *Bitstamp) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - buy := order.OrderSide == exchange.BuyOrderSide - market := order.OrderType == exchange.MarketOrderType - response, err := b.PlaceOrder(order.Pair.String(), order.Price, order.Amount, - buy, market) + buy := s.OrderSide == order.Buy + market := s.OrderType == order.Market + response, err := b.PlaceOrder(s.Pair.String(), + s.Price, + s.Amount, + buy, + market) if response.ID > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response.ID) + submitOrderResponse.OrderID = strconv.FormatInt(response.ID, 10) } if err == nil { @@ -392,38 +397,36 @@ func (b *Bitstamp) SubmitOrder(order *exchange.OrderSubmission) (exchange.Submit // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (b *Bitstamp) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (b *Bitstamp) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (b *Bitstamp) CancelOrder(order *exchange.OrderCancellation) error { +func (b *Bitstamp) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) - if err != nil { return err } _, err = b.CancelExistingOrder(orderIDInt) - return err } // CancelAllOrders cancels all orders associated with a currency pair -func (b *Bitstamp) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { +func (b *Bitstamp) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { success, err := b.CancelAllExistingOrders() if err != nil { - return exchange.CancelAllOrdersResponse{}, err + return order.CancelAllResponse{}, err } if !success { err = errors.New("cancel all orders failed. Bitstamp provides no further information. Check order status to verify") } - return exchange.CancelAllOrdersResponse{}, err + return order.CancelAllResponse{}, err } // GetOrderInfo returns information on a current open order -func (b *Bitstamp) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (b *Bitstamp) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -435,7 +438,11 @@ func (b *Bitstamp) GetDepositAddress(cryptocurrency currency.Code, _ string) (st // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted func (b *Bitstamp) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { - resp, err := b.CryptoWithdrawal(withdrawRequest.Amount, withdrawRequest.Address, withdrawRequest.Currency.String(), withdrawRequest.AddressTag, true) + resp, err := b.CryptoWithdrawal(withdrawRequest.Amount, + withdrawRequest.Address, + withdrawRequest.Currency.String(), + withdrawRequest.AddressTag, + true) if err != nil { return "", err } @@ -453,10 +460,17 @@ func (b *Bitstamp) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoW // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted func (b *Bitstamp) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { - resp, err := b.OpenBankWithdrawal(withdrawRequest.Amount, withdrawRequest.Currency.String(), - withdrawRequest.BankAccountName, withdrawRequest.IBAN, withdrawRequest.SwiftCode, withdrawRequest.BankAddress, - withdrawRequest.BankPostalCode, withdrawRequest.BankCity, withdrawRequest.BankCountry, - withdrawRequest.Description, sepaWithdrawal) + resp, err := b.OpenBankWithdrawal(withdrawRequest.Amount, + withdrawRequest.Currency.String(), + withdrawRequest.BankAccountName, + withdrawRequest.IBAN, + withdrawRequest.SwiftCode, + withdrawRequest.BankAddress, + withdrawRequest.BankPostalCode, + withdrawRequest.BankCity, + withdrawRequest.BankCountry, + withdrawRequest.Description, + sepaWithdrawal) if err != nil { return "", err } @@ -474,12 +488,23 @@ func (b *Bitstamp) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawReque // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a // withdrawal is submitted func (b *Bitstamp) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { - resp, err := b.OpenInternationalBankWithdrawal(withdrawRequest.Amount, withdrawRequest.Currency.String(), - withdrawRequest.BankAccountName, withdrawRequest.IBAN, withdrawRequest.SwiftCode, withdrawRequest.BankAddress, - withdrawRequest.BankPostalCode, withdrawRequest.BankCity, withdrawRequest.BankCountry, - withdrawRequest.IntermediaryBankName, withdrawRequest.IntermediaryBankAddress, withdrawRequest.IntermediaryBankPostalCode, - withdrawRequest.IntermediaryBankCity, withdrawRequest.IntermediaryBankCountry, withdrawRequest.WireCurrency, - withdrawRequest.Description, internationalWithdrawal) + resp, err := b.OpenInternationalBankWithdrawal(withdrawRequest.Amount, + withdrawRequest.Currency.String(), + withdrawRequest.BankAccountName, + withdrawRequest.IBAN, + withdrawRequest.SwiftCode, + withdrawRequest.BankAddress, + withdrawRequest.BankPostalCode, + withdrawRequest.BankCity, + withdrawRequest.BankCountry, + withdrawRequest.IntermediaryBankName, + withdrawRequest.IntermediaryBankAddress, + withdrawRequest.IntermediaryBankPostalCode, + withdrawRequest.IntermediaryBankCity, + withdrawRequest.IntermediaryBankCountry, + withdrawRequest.WireCurrency, + withdrawRequest.Description, + internationalWithdrawal) if err != nil { return "", err } @@ -500,13 +525,13 @@ func (b *Bitstamp) GetWebsocket() (*wshandler.Websocket, error) { } // GetActiveOrders retrieves any orders that are active/open -func (b *Bitstamp) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var orders []exchange.OrderDetail +func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { + var orders []order.Detail var currPair string - if len(getOrdersRequest.Currencies) != 1 { + if len(req.Currencies) != 1 { currPair = "all" } else { - currPair = getOrdersRequest.Currencies[0].String() + currPair = req.Currencies[0].String() } resp, err := b.GetOpenOrders(currPair) @@ -514,59 +539,63 @@ func (b *Bitstamp) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) return nil, err } - for _, order := range resp { - orderDate := time.Unix(order.Date, 0) - orders = append(orders, exchange.OrderDetail{ - Amount: order.Amount, - ID: fmt.Sprintf("%v", order.ID), - Price: order.Price, + for i := range resp { + orderDate := time.Unix(resp[i].Date, 0) + orders = append(orders, order.Detail{ + Amount: resp[i].Amount, + ID: strconv.FormatInt(resp[i].ID, 10), + Price: resp[i].Price, OrderDate: orderDate, - CurrencyPair: currency.NewPairFromStrings(order.Currency[0:3], - order.Currency[len(order.Currency)-3:]), + CurrencyPair: currency.NewPairFromStrings(resp[i].Currency[0:3], + resp[i].Currency[len(resp[i].Currency)-3:]), Exchange: b.Name, }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (b *Bitstamp) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var currPair string - if len(getOrdersRequest.Currencies) == 1 { - currPair = getOrdersRequest.Currencies[0].String() + if len(req.Currencies) == 1 { + currPair = req.Currencies[0].String() } resp, err := b.GetUserTransactions(currPair) if err != nil { return nil, err } - var orders []exchange.OrderDetail - for _, order := range resp { - if order.Type != 2 { + var orders []order.Detail + for i := range resp { + if resp[i].Type != 2 { continue } var quoteCurrency, baseCurrency currency.Code switch { - case order.BTC > 0: + case resp[i].BTC > 0: baseCurrency = currency.BTC - case order.XRP > 0: + case resp[i].XRP > 0: baseCurrency = currency.XRP default: - log.Warnf(log.ExchangeSys, "no base currency found for OrderID '%v'", order.OrderID) + log.Warnf(log.ExchangeSys, + "no base currency found for OrderID '%d'", + resp[i].OrderID) } switch { - case order.USD > 0: + case resp[i].USD > 0: quoteCurrency = currency.USD - case order.EUR > 0: + case resp[i].EUR > 0: quoteCurrency = currency.EUR default: - log.Warnf(log.ExchangeSys, "no quote currency found for orderID '%v'", order.OrderID) + log.Warnf(log.ExchangeSys, + "no quote currency found for orderID '%d'", + resp[i].OrderID) } var currPair currency.Pair @@ -576,22 +605,21 @@ func (b *Bitstamp) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) b.GetPairFormat(asset.Spot, false).Delimiter) } - orderDate, err := time.Parse("2006-01-02 15:04:05", order.Date) + orderDate, err := time.Parse("2006-01-02 15:04:05", resp[i].Date) if err != nil { return nil, err } - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.OrderID), + orders = append(orders, order.Detail{ + ID: strconv.FormatInt(resp[i].OrderID, 10), OrderDate: orderDate, Exchange: b.Name, CurrencyPair: currPair, }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 6b82573a..4f4179a1 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Please supply you own test keys here to run better tests. @@ -333,8 +334,8 @@ func TestGetActiveOrders(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)}, } @@ -353,8 +354,8 @@ func TestGetOrderHistory(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetOrderHistory(&getOrdersRequest) @@ -379,14 +380,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "-", Base: currency.BTC, Quote: currency.LTC, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -409,7 +410,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -435,7 +436,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -451,13 +452,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { - _, err := b.ModifyOrder(&exchange.ModifyOrder{}) + _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 3cb3d48b..45b1a5ea 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -2,7 +2,6 @@ package bittrex import ( "errors" - "fmt" "strings" "sync" "time" @@ -12,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -139,7 +139,10 @@ func (b *Bittrex) Run() { err := b.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), asset.Spot, true, true) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", b.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + b.Name, + err) } } @@ -149,7 +152,10 @@ func (b *Bittrex) Run() { err := b.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + b.Name, + err) } } @@ -313,30 +319,29 @@ func (b *Bittrex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]e } // SubmitOrder submits a new order -func (b *Bittrex) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (b *Bittrex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - buy := order.OrderSide == exchange.BuyOrderSide - var response UUID - var err error + buy := s.OrderSide == order.Buy - if order.OrderType != exchange.LimitOrderType { - return submitOrderResponse, errors.New("limit order not supported on exchange") + if s.OrderType != order.Limit { + return submitOrderResponse, + errors.New("limit order not supported on exchange") } + var response UUID + var err error if buy { - response, err = b.PlaceBuyLimit(order.Pair.String(), order.Amount, - order.Price) + response, err = b.PlaceBuyLimit(s.Pair.String(), + s.Amount, + s.Price) } else { - response, err = b.PlaceSellLimit(order.Pair.String(), order.Amount, - order.Price) + response, err = b.PlaceSellLimit(s.Pair.String(), + s.Amount, + s.Price) } if response.Result.ID != "" { @@ -352,21 +357,21 @@ func (b *Bittrex) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitO // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (b *Bittrex) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (b *Bittrex) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (b *Bittrex) CancelOrder(order *exchange.OrderCancellation) error { +func (b *Bittrex) CancelOrder(order *order.Cancel) error { _, err := b.CancelExistingOrder(order.OrderID) return err } // CancelAllOrders cancels all orders associated with a currency pair -func (b *Bittrex) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (b *Bittrex) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } openOrders, err := b.GetOpenOrders("") if err != nil { @@ -376,7 +381,7 @@ func (b *Bittrex) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cance for i := range openOrders.Result { _, err := b.CancelExistingOrder(openOrders.Result[i].OrderUUID) if err != nil { - cancelAllOrdersResponse.OrderStatus[openOrders.Result[i].OrderUUID] = err.Error() + cancelAllOrdersResponse.Status[openOrders.Result[i].OrderUUID] = err.Error() } } @@ -384,8 +389,8 @@ func (b *Bittrex) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cance } // GetOrderInfo returns information on a current open order -func (b *Bittrex) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (b *Bittrex) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -403,7 +408,7 @@ func (b *Bittrex) GetDepositAddress(cryptocurrency currency.Code, _ string) (str // submitted func (b *Bittrex) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { uuid, err := b.Withdraw(withdrawRequest.Currency.String(), withdrawRequest.AddressTag, withdrawRequest.Address, withdrawRequest.Amount) - return fmt.Sprintf("%v", uuid), err + return uuid.Result.ID, err } // WithdrawFiatFunds returns a withdrawal ID when a @@ -434,10 +439,10 @@ func (b *Bittrex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) } // GetActiveOrders retrieves any orders that are active/open -func (b *Bittrex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (b *Bittrex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { var currPair string - if len(getOrdersRequest.Currencies) == 1 { - currPair = getOrdersRequest.Currencies[0].String() + if len(req.Currencies) == 1 { + currPair = req.Currencies[0].String() } resp, err := b.GetOpenOrders(currPair) @@ -445,19 +450,23 @@ func (b *Bittrex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp.Result { orderDate, err := time.Parse(time.RFC3339, resp.Result[i].Opened) if err != nil { - log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", - b.Name, "GetActiveOrders", resp.Result[i].OrderUUID, resp.Result[i].Opened) + log.Warnf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + b.Name, + "GetActiveOrders", + resp.Result[i].OrderUUID, + resp.Result[i].Opened) } pair := currency.NewPairDelimiter(resp.Result[i].Exchange, b.GetPairFormat(asset.Spot, false).Delimiter) - orderType := exchange.OrderType(strings.ToUpper(resp.Result[i].Type)) + orderType := order.Type(strings.ToUpper(resp.Result[i].Type)) - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ Amount: resp.Result[i].Quantity, RemainingAmount: resp.Result[i].QuantityRemaining, Price: resp.Result[i].Price, @@ -469,19 +478,18 @@ func (b *Bittrex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( }) } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (b *Bittrex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (b *Bittrex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var currPair string - if len(getOrdersRequest.Currencies) == 1 { - currPair = getOrdersRequest.Currencies[0].String() + if len(req.Currencies) == 1 { + currPair = req.Currencies[0].String() } resp, err := b.GetOrderHistoryForCurrency(currPair) @@ -489,19 +497,23 @@ func (b *Bittrex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp.Result { orderDate, err := time.Parse(time.RFC3339, resp.Result[i].TimeStamp) if err != nil { - log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", - b.Name, "GetActiveOrders", resp.Result[i].OrderUUID, resp.Result[i].Opened) + log.Warnf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + b.Name, + "GetActiveOrders", + resp.Result[i].OrderUUID, + resp.Result[i].Opened) } pair := currency.NewPairDelimiter(resp.Result[i].Exchange, b.GetPairFormat(asset.Spot, false).Delimiter) - orderType := exchange.OrderType(strings.ToUpper(resp.Result[i].Type)) + orderType := order.Type(strings.ToUpper(resp.Result[i].Type)) - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ Amount: resp.Result[i].Quantity, RemainingAmount: resp.Result[i].QuantityRemaining, Price: resp.Result[i].Price, @@ -514,10 +526,9 @@ func (b *Bittrex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( }) } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index 24384ef1..4fca1c10 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -8,6 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) var b BTCMarkets @@ -85,7 +86,7 @@ func TestGetTrades(t *testing.T) { func TestNewOrder(t *testing.T) { t.Parallel() _, err := b.NewOrder("AUD", "BTC", 0, 0, "Bid", - exchange.LimitOrderType.ToLower().ToString(), "testTest") + order.Limit.Lower(), "testTest") if err == nil { t.Error("NewOrder() Expected error") } @@ -294,8 +295,8 @@ func TestGetActiveOrders(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetActiveOrders(&getOrdersRequest) @@ -310,8 +311,8 @@ func TestGetOrderHistory(t *testing.T) { b.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)}, } @@ -338,14 +339,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "-", Base: currency.BTC, Quote: currency.LTC, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -368,7 +369,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -394,7 +395,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -410,13 +411,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { - _, err := b.ModifyOrder(&exchange.ModifyOrder{}) + _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 98164cb7..83bdc268 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -340,33 +341,29 @@ func (b *BTCMarkets) GetExchangeHistory(p currency.Pair, assetType asset.Item) ( } // SubmitOrder submits a new order -func (b *BTCMarkets) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (b *BTCMarkets) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - if strings.EqualFold(order.OrderSide.ToString(), exchange.SellOrderSide.ToString()) { - order.OrderSide = exchange.AskOrderSide + if strings.EqualFold(s.OrderSide.String(), order.Sell.String()) { + s.OrderSide = order.Ask } - if strings.EqualFold(order.OrderSide.ToString(), exchange.BuyOrderSide.ToString()) { - order.OrderSide = exchange.BuyOrderSide + if strings.EqualFold(s.OrderSide.String(), order.Buy.String()) { + s.OrderSide = order.Bid } - response, err := b.NewOrder(order.Pair.Base.Upper().String(), - order.Pair.Quote.Upper().String(), - order.Price, - order.Amount, - order.OrderSide.ToString(), - order.OrderType.ToString(), - order.ClientID) + response, err := b.NewOrder(s.Pair.Base.Upper().String(), + s.Pair.Quote.Upper().String(), + s.Price, + s.Amount, + s.OrderSide.String(), + s.OrderType.String(), + s.ClientID) if response > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response) + submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } if err == nil { @@ -378,12 +375,12 @@ func (b *BTCMarkets) SubmitOrder(order *exchange.OrderSubmission) (exchange.Subm // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (b *BTCMarkets) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (b *BTCMarkets) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (b *BTCMarkets) CancelOrder(order *exchange.OrderCancellation) error { +func (b *BTCMarkets) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) if err != nil { return err @@ -394,9 +391,9 @@ func (b *BTCMarkets) CancelOrder(order *exchange.OrderCancellation) error { } // CancelAllOrders cancels all orders associated with a currency pair -func (b *BTCMarkets) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (b *BTCMarkets) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } openOrders, err := b.GetOpenOrders() if err != nil { @@ -416,7 +413,7 @@ func (b *BTCMarkets) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Ca for i := range orders { if !orders[i].Success { - cancelAllOrdersResponse.OrderStatus[strconv.FormatInt(orders[i].ID, 10)] = orders[i].ErrorMessage + cancelAllOrdersResponse.Status[strconv.FormatInt(orders[i].ID, 10)] = orders[i].ErrorMessage } } } @@ -424,8 +421,8 @@ func (b *BTCMarkets) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Ca } // GetOrderInfo returns information on a current open order -func (b *BTCMarkets) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var OrderDetail exchange.OrderDetail +func (b *BTCMarkets) GetOrderInfo(orderID string) (order.Detail, error) { + var OrderDetail order.Detail o, err := strconv.ParseInt(orderID, 10, 64) if err != nil { @@ -446,14 +443,14 @@ func (b *BTCMarkets) GetOrderInfo(orderID string) (exchange.OrderDetail, error) } for i := range orders { - var side exchange.OrderSide - if strings.EqualFold(orders[i].OrderSide, exchange.AskOrderSide.ToString()) { - side = exchange.SellOrderSide - } else if strings.EqualFold(orders[i].OrderSide, exchange.BidOrderSide.ToString()) { - side = exchange.BuyOrderSide + var side order.Side + if strings.EqualFold(orders[i].OrderSide, order.Ask.String()) { + side = order.Sell + } else if strings.EqualFold(orders[i].OrderSide, order.Bid.String()) { + side = order.Buy } orderDate := time.Unix(int64(orders[i].CreationTime), 0) - orderType := exchange.OrderType(strings.ToUpper(orders[i].OrderType)) + orderType := order.Type(strings.ToUpper(orders[i].OrderType)) OrderDetail.Amount = orders[i].Volume OrderDetail.OrderDate = orderDate @@ -463,7 +460,7 @@ func (b *BTCMarkets) GetOrderInfo(orderID string) (exchange.OrderDetail, error) OrderDetail.OrderSide = side OrderDetail.OrderType = orderType OrderDetail.Price = orders[i].Price - OrderDetail.Status = orders[i].Status + OrderDetail.Status = order.Status(orders[i].Status) OrderDetail.CurrencyPair = currency.NewPairWithDelimiter(orders[i].Instrument, orders[i].Currency, b.GetPairFormat(asset.Spot, false).Delimiter) @@ -488,7 +485,11 @@ func (b *BTCMarkets) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawReq if withdrawRequest.Currency != currency.AUD { return "", errors.New("only AUD is supported for withdrawals") } - return b.WithdrawAUD(withdrawRequest.BankAccountName, fmt.Sprintf("%v", withdrawRequest.BankAccountNumber), withdrawRequest.BankName, fmt.Sprintf("%v", withdrawRequest.BankCode), withdrawRequest.Amount) + return b.WithdrawAUD(withdrawRequest.BankAccountName, + strconv.FormatFloat(withdrawRequest.BankAccountNumber, 'f', -1, 64), + withdrawRequest.BankName, + strconv.FormatFloat(withdrawRequest.BankCode, 'f', -1, 64), + withdrawRequest.Amount) } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a @@ -512,24 +513,24 @@ func (b *BTCMarkets) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, err } // GetActiveOrders retrieves any orders that are active/open -func (b *BTCMarkets) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (b *BTCMarkets) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := b.GetOpenOrders() if err != nil { return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp { - var side exchange.OrderSide - if strings.EqualFold(resp[i].OrderSide, exchange.AskOrderSide.ToString()) { - side = exchange.SellOrderSide - } else if strings.EqualFold(resp[i].OrderSide, exchange.BidOrderSide.ToString()) { - side = exchange.BuyOrderSide + var side order.Side + if strings.EqualFold(resp[i].OrderSide, order.Ask.String()) { + side = order.Sell + } else if strings.EqualFold(resp[i].OrderSide, order.Bid.String()) { + side = order.Buy } orderDate := time.Unix(int64(resp[i].CreationTime), 0) - orderType := exchange.OrderType(strings.ToUpper(resp[i].OrderType)) + orderType := order.Type(strings.ToUpper(resp[i].OrderType)) - openOrder := exchange.OrderDetail{ + openOrder := order.Detail{ ID: strconv.FormatInt(resp[i].ID, 10), Amount: resp[i].Volume, Exchange: b.Name, @@ -538,7 +539,7 @@ func (b *BTCMarkets) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest OrderSide: side, OrderType: orderType, Price: resp[i].Price, - Status: resp[i].Status, + Status: order.Status(resp[i].Status), CurrencyPair: currency.NewPairWithDelimiter(resp[i].Instrument, resp[i].Currency, b.GetPairFormat(asset.Spot, false).Delimiter), @@ -546,7 +547,7 @@ func (b *BTCMarkets) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest for j := range resp[i].Trades { tradeDate := time.Unix(int64(resp[i].Trades[j].CreationTime), 0) - openOrder.Trades = append(openOrder.Trades, exchange.TradeHistory{ + openOrder.Trades = append(openOrder.Trades, order.TradeHistory{ Amount: resp[i].Trades[j].Volume, Exchange: b.Name, Price: resp[i].Trades[j].Price, @@ -560,24 +561,23 @@ func (b *BTCMarkets) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest orders = append(orders, openOrder) } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - if len(getOrdersRequest.Currencies) == 0 { +func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + if len(req.Currencies) == 0 { return nil, errors.New("requires at least one currency pair to retrieve history") } var respOrders []Order - for _, currency := range getOrdersRequest.Currencies { - resp, err := b.GetOrders(currency.Base.String(), - currency.Quote.String(), + for i := range req.Currencies { + resp, err := b.GetOrders(req.Currencies[i].Base.String(), + req.Currencies[i].Quote.String(), 200, 0, true) @@ -587,18 +587,18 @@ func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest respOrders = append(respOrders, resp...) } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range respOrders { - var side exchange.OrderSide - if strings.EqualFold(respOrders[i].OrderSide, exchange.AskOrderSide.ToString()) { - side = exchange.SellOrderSide - } else if strings.EqualFold(respOrders[i].OrderSide, exchange.BidOrderSide.ToString()) { - side = exchange.BuyOrderSide + var side order.Side + if strings.EqualFold(respOrders[i].OrderSide, order.Ask.String()) { + side = order.Sell + } else if strings.EqualFold(respOrders[i].OrderSide, order.Bid.String()) { + side = order.Buy } orderDate := time.Unix(int64(respOrders[i].CreationTime), 0) - orderType := exchange.OrderType(strings.ToUpper(respOrders[i].OrderType)) + orderType := order.Type(strings.ToUpper(respOrders[i].OrderType)) - openOrder := exchange.OrderDetail{ + openOrder := order.Detail{ ID: strconv.FormatInt(respOrders[i].ID, 10), Amount: respOrders[i].Volume, Exchange: b.Name, @@ -607,7 +607,7 @@ func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest OrderSide: side, OrderType: orderType, Price: respOrders[i].Price, - Status: respOrders[i].Status, + Status: order.Status(respOrders[i].Status), CurrencyPair: currency.NewPairWithDelimiter(respOrders[i].Instrument, respOrders[i].Currency, b.GetPairFormat(asset.Spot, false).Delimiter), @@ -615,7 +615,7 @@ func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest for j := range respOrders[i].Trades { tradeDate := time.Unix(int64(respOrders[i].Trades[j].CreationTime), 0) - openOrder.Trades = append(openOrder.Trades, exchange.TradeHistory{ + openOrder.Trades = append(openOrder.Trades, order.TradeHistory{ Amount: respOrders[i].Trades[j].Volume, Exchange: b.Name, Price: respOrders[i].Trades[j].Price, @@ -628,9 +628,9 @@ func (b *BTCMarkets) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest orders = append(orders, openOrder) } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } diff --git a/exchanges/btse/btse_test.go b/exchanges/btse/btse_test.go index 7f39f439..2282da5b 100644 --- a/exchanges/btse/btse_test.go +++ b/exchanges/btse/btse_test.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Please supply your own keys here to do better tests @@ -150,8 +151,8 @@ func TestGetActiveOrders(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("API keys not set, skipping test") } - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetActiveOrders(&getOrdersRequest) @@ -165,8 +166,8 @@ func TestGetOrderHistory(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("API keys not set, skipping test") } - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := b.GetOrderHistory(&getOrdersRequest) if err != nil { @@ -279,13 +280,13 @@ func TestSubmitOrder(t *testing.T) { if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.SellOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 100000, Amount: 0.1, ClientID: "meowOrder", @@ -307,7 +308,7 @@ func TestCancelExchangeOrder(t *testing.T) { currency.USD.String(), "-") - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "b334ecef-2b42-4998-b8a4-b6b14f6d2671", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -328,7 +329,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currency.USD.String(), "-") - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -339,7 +340,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { if err != nil { t.Errorf("Could not cancel orders: %v", err) } - for k, v := range resp.OrderStatus { + for k, v := range resp.Status { if strings.Contains(v, "Failed") { t.Errorf("order id: %s failed to cancel: %v", k, v) } diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index ad8b7384..d362c0bd 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -11,8 +11,8 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" @@ -82,9 +82,9 @@ func (b *BTSE) WsHandleData() { continue } for x := range tradeHistory.Data { - side := exchange.BuyOrderSide.ToString() + side := order.Buy.String() if tradeHistory.Data[x].Gain == -1 { - side = exchange.SellOrderSide.ToString() + side = order.Sell.String() } b.Websocket.DataHandler <- wshandler.TradeData{ Timestamp: time.Unix(tradeHistory.Data[x].TransactionTime, 0), diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index eac852a5..b7137a54 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -233,7 +234,6 @@ func (b *BTSE) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price assetType).String()) if err != nil { return tickerPrice, err - } tickerPrice.Pair = p @@ -337,23 +337,19 @@ func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exch } // SubmitOrder submits a new order -func (b *BTSE) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var resp exchange.SubmitOrderResponse - if order == nil { - return resp, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (b *BTSE) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var resp order.SubmitResponse + if err := s.Validate(); err != nil { return resp, err } - r, err := b.CreateOrder(order.Amount, - order.Price, - order.OrderSide.ToString(), - order.OrderType.ToString(), - b.FormatExchangeCurrency(order.Pair, asset.Spot).String(), + r, err := b.CreateOrder(s.Amount, + s.Price, + s.OrderSide.String(), + s.OrderType.String(), + b.FormatExchangeCurrency(s.Pair, asset.Spot).String(), goodTillCancel, - order.ClientID) + s.ClientID) if err != nil { return resp, err } @@ -368,12 +364,12 @@ func (b *BTSE) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrde // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (b *BTSE) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (b *BTSE) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (b *BTSE) CancelOrder(order *exchange.OrderCancellation) error { +func (b *BTSE) CancelOrder(order *order.Cancel) error { r, err := b.CancelExistingOrder(order.OrderID, b.FormatExchangeCurrency(order.CurrencyPair, asset.Spot).String()) @@ -394,14 +390,14 @@ func (b *BTSE) CancelOrder(order *exchange.OrderCancellation) error { // CancelAllOrders cancels all orders associated with a currency pair // If product ID is sent, all orders of that specified market will be cancelled // If not specified, all orders of all markets will be cancelled -func (b *BTSE) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - var resp exchange.CancelAllOrdersResponse +func (b *BTSE) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { + var resp order.CancelAllResponse markets, err := b.GetMarkets() if err != nil { return resp, err } - resp.OrderStatus = make(map[string]string) + resp.Status = make(map[string]string) for x := range markets { strPair := b.FormatExchangeCurrency(orderCancellation.CurrencyPair, orderCancellation.AssetType).String() @@ -421,7 +417,7 @@ func (b *BTSE) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (e if err != nil { success = "Order Cancellation Failed" } - resp.OrderStatus[orders[y].Order.ID] = success + resp.Status[orders[y].Order.ID] = success } } } @@ -429,13 +425,13 @@ func (b *BTSE) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (e } // GetOrderInfo returns information on a current open order -func (b *BTSE) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { +func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) { o, err := b.GetOrders("") if err != nil { - return exchange.OrderDetail{}, err + return order.Detail{}, err } - var od exchange.OrderDetail + var od order.Detail if len(o) == 0 { return od, errors.New("no orders found") } @@ -445,9 +441,9 @@ func (b *BTSE) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { continue } - var side = exchange.BuyOrderSide - if strings.EqualFold(o[i].Side, exchange.AskOrderSide.ToString()) { - side = exchange.SellOrderSide + var side = order.Buy + if strings.EqualFold(o[i].Side, order.Ask.String()) { + side = order.Sell } od.CurrencyPair = currency.NewPairDelimiter(o[i].Symbol, @@ -457,24 +453,26 @@ func (b *BTSE) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { od.ID = o[i].ID od.OrderDate = parseOrderTime(o[i].CreatedAt) od.OrderSide = side - od.OrderType = exchange.OrderType(strings.ToUpper(o[i].Type)) + od.OrderType = order.Type(strings.ToUpper(o[i].Type)) od.Price = o[i].Price - od.Status = o[i].Status + od.Status = order.Status(o[i].Status) fills, err := b.GetFills(orderID, "", "", "", "", "") if err != nil { - return od, fmt.Errorf("unable to get order fills for orderID %s", orderID) + return od, + fmt.Errorf("unable to get order fills for orderID %s", + orderID) } for i := range fills { createdAt, _ := time.Parse(time.RFC3339, fills[i].CreatedAt) - od.Trades = append(od.Trades, exchange.TradeHistory{ + od.Trades = append(od.Trades, order.TradeHistory{ Timestamp: createdAt, TID: fills[i].ID, Price: fills[i].Price, Amount: fills[i].Amount, Exchange: b.Name, - Type: fills[i].Side, + Side: order.Side(fills[i].Side), Fee: fills[i].Fee, }) } @@ -511,20 +509,20 @@ func (b *BTSE) GetWebsocket() (*wshandler.Websocket, error) { } // GetActiveOrders retrieves any orders that are active/open -func (b *BTSE) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := b.GetOrders("") if err != nil { return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp { - var side = exchange.BuyOrderSide - if strings.EqualFold(resp[i].Side, exchange.AskOrderSide.ToString()) { - side = exchange.SellOrderSide + var side = order.Buy + if strings.EqualFold(resp[i].Side, order.Ask.String()) { + side = order.Sell } - openOrder := exchange.OrderDetail{ + openOrder := order.Detail{ CurrencyPair: currency.NewPairDelimiter(resp[i].Symbol, b.GetPairFormat(asset.Spot, false).Delimiter), Exchange: b.Name, @@ -532,43 +530,44 @@ func (b *BTSE) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]e ID: resp[i].ID, OrderDate: parseOrderTime(resp[i].CreatedAt), OrderSide: side, - OrderType: exchange.OrderType(strings.ToUpper(resp[i].Type)), + OrderType: order.Type(strings.ToUpper(resp[i].Type)), Price: resp[i].Price, - Status: resp[i].Status, + Status: order.Status(resp[i].Status), } fills, err := b.GetFills(resp[i].ID, "", "", "", "", "") if err != nil { log.Errorf(log.ExchangeSys, - "%s: Unable to get order fills for orderID %s", b.Name, + "%s: Unable to get order fills for orderID %s", + b.Name, resp[i].ID) continue } for i := range fills { createdAt, _ := time.Parse(time.RFC3339, fills[i].CreatedAt) - openOrder.Trades = append(openOrder.Trades, exchange.TradeHistory{ + openOrder.Trades = append(openOrder.Trades, order.TradeHistory{ Timestamp: createdAt, TID: fills[i].ID, Price: fills[i].Price, Amount: fills[i].Amount, Exchange: b.Name, - Type: fills[i].Side, + Side: order.Side(fills[i].Side), Fee: fills[i].Fee, }) } orders = append(orders, openOrder) } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (b *BTSE) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (b *BTSE) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { return nil, common.ErrFunctionNotSupported } diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 65a212a5..da81870c 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -288,7 +289,7 @@ func (c *CoinbasePro) GetHolds(accountID string) ([]AccountHolds, error) { func (c *CoinbasePro) PlaceLimitOrder(clientRef string, price, amount float64, side, timeInforce, cancelAfter, productID, stp string, postOnly bool) (string, error) { resp := GeneralizedOrderResponse{} req := make(map[string]interface{}) - req["type"] = exchange.LimitOrderType.ToLower().ToString() + req["type"] = order.Limit.Lower() req["price"] = strconv.FormatFloat(price, 'f', -1, 64) req["size"] = strconv.FormatFloat(amount, 'f', -1, 64) req["side"] = side @@ -339,7 +340,7 @@ func (c *CoinbasePro) PlaceMarketOrder(clientRef string, size, funds float64, si req := make(map[string]interface{}) req["side"] = side req["product_id"] = productID - req["type"] = exchange.MarketOrderType.ToLower().ToString() + req["type"] = order.Market.Lower() if size != 0 { req["size"] = strconv.FormatFloat(size, 'f', -1, 64) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 017f7d33..11537da8 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -382,8 +383,8 @@ func TestGetActiveOrders(t *testing.T) { c.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.LTC)}, } @@ -400,8 +401,8 @@ func TestGetOrderHistory(t *testing.T) { c.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.LTC)}, } @@ -428,14 +429,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "-", Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -459,7 +460,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -486,7 +487,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -502,13 +503,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { - _, err := c.ModifyOrder(&exchange.ModifyOrder{}) + _, err := c.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index f0a9764e..9d151d10 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" @@ -240,7 +241,7 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { price, _ := strconv.ParseFloat(update.Changes[i][1].(string), 64) volume, _ := strconv.ParseFloat(update.Changes[i][2].(string), 64) - if update.Changes[i][0].(string) == exchange.BuyOrderSide.ToLower().ToString() { + if update.Changes[i][0].(string) == order.Buy.Lower() { bids = append(bids, orderbook.Item{Price: price, Amount: volume}) } else { asks = append(asks, orderbook.Item{Price: price, Amount: volume}) diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index f81c78ee..a039ccd7 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -186,7 +187,8 @@ func (c *CoinbasePro) Start(wg *sync.WaitGroup) { // Run implements the coinbasepro wrapper func (c *CoinbasePro) Run() { if c.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", + log.Debugf(log.ExchangeSys, + "%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket.IsEnabled()), coinbaseproWebsocketURL) @@ -369,34 +371,30 @@ func (c *CoinbasePro) GetExchangeHistory(p currency.Pair, assetType asset.Item) } // SubmitOrder submits a new order -func (c *CoinbasePro) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (c *CoinbasePro) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } var response string var err error - switch order.OrderType { - case exchange.MarketOrderType: + switch s.OrderType { + case order.Market: response, err = c.PlaceMarketOrder("", - order.Amount, - order.Amount, - order.OrderSide.ToString(), - c.FormatExchangeCurrency(order.Pair, asset.Spot).String(), + s.Amount, + s.Amount, + s.OrderSide.String(), + c.FormatExchangeCurrency(s.Pair, asset.Spot).String(), "") - case exchange.LimitOrderType: + case order.Limit: response, err = c.PlaceLimitOrder("", - order.Price, - order.Amount, - order.OrderSide.ToString(), + s.Price, + s.Amount, + s.OrderSide.String(), "", "", - c.FormatExchangeCurrency(order.Pair, asset.Spot).String(), + c.FormatExchangeCurrency(s.Pair, asset.Spot).String(), "", false) default: @@ -416,25 +414,25 @@ func (c *CoinbasePro) SubmitOrder(order *exchange.OrderSubmission) (exchange.Sub // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (c *CoinbasePro) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (c *CoinbasePro) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (c *CoinbasePro) CancelOrder(order *exchange.OrderCancellation) error { +func (c *CoinbasePro) CancelOrder(order *order.Cancel) error { return c.CancelExistingOrder(order.OrderID) } // CancelAllOrders cancels all orders associated with a currency pair -func (c *CoinbasePro) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { +func (c *CoinbasePro) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { // CancellAllExisting orders returns a list of successful cancellations, we're only interested in failures _, err := c.CancelAllExistingOrders("") - return exchange.CancelAllOrdersResponse{}, err + return order.CancelAllResponse{}, err } // GetOrderInfo returns information on a current open order -func (c *CoinbasePro) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (c *CoinbasePro) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -498,30 +496,34 @@ func (c *CoinbasePro) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, er } // GetActiveOrders retrieves any orders that are active/open -func (c *CoinbasePro) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { var respOrders []GeneralizedOrderResponse - for i := range getOrdersRequest.Currencies { + for i := range req.Currencies { resp, err := c.GetOrders([]string{"open", "pending", "active"}, - c.FormatExchangeCurrency(getOrdersRequest.Currencies[i], asset.Spot).String()) + c.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String()) if err != nil { return nil, err } respOrders = append(respOrders, resp...) } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range respOrders { currency := currency.NewPairDelimiter(respOrders[i].ProductID, c.GetPairFormat(asset.Spot, false).Delimiter) - orderSide := exchange.OrderSide(strings.ToUpper(respOrders[i].Side)) - orderType := exchange.OrderType(strings.ToUpper(respOrders[i].Type)) + orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) + orderType := order.Type(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) if err != nil { - log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", - c.Name, "GetActiveOrders", respOrders[i].ID, respOrders[i].CreatedAt) + log.Warnf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + c.Name, + "GetActiveOrders", + respOrders[i].ID, + respOrders[i].CreatedAt) } - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ ID: respOrders[i].ID, Amount: respOrders[i].Size, ExecutedAmount: respOrders[i].FilledSize, @@ -533,17 +535,17 @@ func (c *CoinbasePro) GetActiveOrders(getOrdersRequest *exchange.GetOrdersReques }) } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var respOrders []GeneralizedOrderResponse - for _, currency := range getOrdersRequest.Currencies { + for _, currency := range req.Currencies { resp, err := c.GetOrders([]string{"done", "settled"}, c.FormatExchangeCurrency(currency, asset.Spot).String()) if err != nil { @@ -552,19 +554,23 @@ func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersReques respOrders = append(respOrders, resp...) } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range respOrders { currency := currency.NewPairDelimiter(respOrders[i].ProductID, c.GetPairFormat(asset.Spot, false).Delimiter) - orderSide := exchange.OrderSide(strings.ToUpper(respOrders[i].Side)) - orderType := exchange.OrderType(strings.ToUpper(respOrders[i].Type)) + orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) + orderType := order.Type(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) if err != nil { - log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", - c.Name, "GetActiveOrders", respOrders[i].ID, respOrders[i].CreatedAt) + log.Warnf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + c.Name, + "GetActiveOrders", + respOrders[i].ID, + respOrders[i].CreatedAt) } - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ ID: respOrders[i].ID, Amount: respOrders[i].Size, ExecutedAmount: respOrders[i].FilledSize, @@ -576,10 +582,9 @@ func (c *CoinbasePro) GetOrderHistory(getOrdersRequest *exchange.GetOrdersReques }) } - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index faf70829..034c3e22 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -120,9 +121,9 @@ func (c *COINUT) NewOrder(instrumentID int64, quantity, price float64, buy bool, params["price"] = fmt.Sprintf("%v", price) } params["qty"] = fmt.Sprintf("%v", quantity) - params["side"] = exchange.BuyOrderSide.ToString() + params["side"] = order.Buy.String() if !buy { - params["side"] = exchange.SellOrderSide.ToString() + params["side"] = order.Sell.String() } params["client_ord_id"] = orderID diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 1fb1bcee..891944b4 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -262,8 +263,8 @@ func TestGetActiveOrders(t *testing.T) { c.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := c.GetActiveOrders(&getOrdersRequest) @@ -276,8 +277,8 @@ func TestGetOrderHistory(t *testing.T) { c.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.LTC)}, } @@ -302,13 +303,13 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -332,7 +333,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -359,7 +360,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -375,8 +376,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -395,7 +396,7 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { - _, err := c.ModifyOrder(&exchange.ModifyOrder{}) + _, err := c.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } @@ -480,7 +481,7 @@ func TestWsAuthSubmitOrder(t *testing.T) { Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 1, Price: 1, - Side: exchange.BuyOrderSide, + Side: order.Buy, } _, err := c.wsSubmitOrder(&order) if err != nil { @@ -499,14 +500,14 @@ func TestWsAuthSubmitOrders(t *testing.T) { Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 1, Price: 1, - Side: exchange.BuyOrderSide, + Side: order.Buy, } order2 := WsSubmitOrderParameters{ Amount: 3, Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 2, Price: 2, - Side: exchange.BuyOrderSide, + Side: order.Buy, } _, err := c.wsSubmitOrders([]WsSubmitOrderParameters{order1, order2}) if err != nil { diff --git a/exchanges/coinut/coinut_types.go b/exchanges/coinut/coinut_types.go index 1822bfed..2540e6a1 100644 --- a/exchanges/coinut/coinut_types.go +++ b/exchanges/coinut/coinut_types.go @@ -5,7 +5,7 @@ import ( "sync" "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // GenericResponse is the generic response you will get from coinut @@ -477,7 +477,7 @@ type WsSubmitOrderRequest struct { // WsSubmitOrderParameters ws request parameters type WsSubmitOrderParameters struct { Currency currency.Pair - Side exchange.OrderSide + Side order.Side Amount, Price float64 OrderID int64 } diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index ecb067c9..ff30766a 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" @@ -305,7 +306,7 @@ func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error { UpdateID: update.TransID, AssetType: asset.Spot, } - if strings.EqualFold(update.Side, exchange.BuyOrderSide.ToLower().ToString()) { + if strings.EqualFold(update.Side, order.Buy.Lower()) { bufferUpdate.Bids = []orderbook.Item{{Price: update.Price, Amount: update.Volume}} } else { bufferUpdate.Asks = []orderbook.Item{{Price: update.Price, Amount: update.Volume}} diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 687833c7..14c94edc 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -430,19 +431,15 @@ func (c *COINUT) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex } // SubmitOrder submits a new order -func (c *COINUT) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (c *COINUT) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } var APIresponse interface{} - isBuyOrder := order.OrderSide == exchange.BuyOrderSide - clientIDInt, err := strconv.ParseUint(order.ClientID, 0, 32) + isBuyOrder := s.OrderSide == order.Buy + clientIDInt, err := strconv.ParseUint(s.ClientID, 0, 32) if err != nil { return submitOrderResponse, err } @@ -456,32 +453,40 @@ func (c *COINUT) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOr } } - currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(order.Pair, + currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(s.Pair, asset.Spot).String()) if currencyID == 0 { return submitOrderResponse, errLookupInstrumentID } - switch order.OrderType { - case exchange.LimitOrderType: - APIresponse, err = c.NewOrder(currencyID, order.Amount, order.Price, - isBuyOrder, clientIDUint) - case exchange.MarketOrderType: - APIresponse, err = c.NewOrder(currencyID, order.Amount, 0, isBuyOrder, + switch s.OrderType { + case order.Limit: + APIresponse, err = c.NewOrder(currencyID, + s.Amount, + s.Price, + isBuyOrder, + clientIDUint) + case order.Market: + APIresponse, err = c.NewOrder(currencyID, + s.Amount, + 0, + isBuyOrder, clientIDUint) } switch apiResp := APIresponse.(type) { case OrdersBase: orderResult := apiResp - submitOrderResponse.OrderID = fmt.Sprintf("%v", orderResult.OrderID) + submitOrderResponse.OrderID = strconv.FormatInt(orderResult.OrderID, 10) case OrderFilledResponse: orderResult := apiResp - submitOrderResponse.OrderID = fmt.Sprintf("%v", orderResult.Order.OrderID) + submitOrderResponse.OrderID = strconv.FormatInt(orderResult.Order.OrderID, 10) case OrderRejectResponse: orderResult := apiResp - submitOrderResponse.OrderID = fmt.Sprintf("%v", orderResult.OrderID) - err = fmt.Errorf("orderID: %v was rejected: %v", orderResult.OrderID, orderResult.Reasons) + submitOrderResponse.OrderID = strconv.FormatInt(orderResult.OrderID, 10) + err = fmt.Errorf("orderID: %d was rejected: %v", + orderResult.OrderID, + orderResult.Reasons) } if err == nil { @@ -493,12 +498,12 @@ func (c *COINUT) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOr // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (c *COINUT) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (c *COINUT) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (c *COINUT) CancelOrder(order *exchange.OrderCancellation) error { +func (c *COINUT) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) if err != nil { return err @@ -523,13 +528,14 @@ func (c *COINUT) CancelOrder(order *exchange.OrderCancellation) error { } // CancelAllOrders cancels all orders associated with a currency pair -func (c *COINUT) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { +func (c *COINUT) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { // TODO, this is a terrible implementation. Requires DB to improve // Coinut provides no way of retrieving orders without a currency - // So we need to retrieve all currencies, then retrieve orders for each currency - // Then cancel. Advisable to never use this until DB due to performance - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), + // So we need to retrieve all currencies, then retrieve orders for each + // currency then cancel. Advisable to never use this until DB due to + // performance. + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } if !c.instrumentMap.IsLoaded() { @@ -566,7 +572,7 @@ func (c *COINUT) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel for _, order := range resp.Results { if order.Status != "OK" { - cancelAllOrdersResponse.OrderStatus[strconv.FormatInt(order.OrderID, 10)] = order.Status + cancelAllOrdersResponse.Status[strconv.FormatInt(order.OrderID, 10)] = order.Status } } } @@ -575,8 +581,8 @@ func (c *COINUT) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel } // GetOrderInfo returns information on a current open order -func (c *COINUT) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - return exchange.OrderDetail{}, common.ErrNotYetImplemented +func (c *COINUT) GetOrderInfo(orderID string) (order.Detail, error) { + return order.Detail{}, common.ErrNotYetImplemented } // GetDepositAddress returns a deposit address for a specified currency @@ -617,7 +623,7 @@ func (c *COINUT) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) } // GetActiveOrders retrieves any orders that are active/open -func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { if !c.instrumentMap.IsLoaded() { err := c.SeedInstruments() if err != nil { @@ -626,9 +632,9 @@ func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ } var instrumentsToUse []int64 - if len(getOrdersRequest.Currencies) > 0 { - for x := range getOrdersRequest.Currencies { - currency := c.FormatExchangeCurrency(getOrdersRequest.Currencies[x], + if len(req.Currencies) > 0 { + for x := range req.Currencies { + currency := c.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() instrumentsToUse = append(instrumentsToUse, c.instrumentMap.LookupID(currency)) @@ -641,7 +647,7 @@ func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ return nil, errors.New("no instrument IDs to use") } - var orders []exchange.OrderDetail + var orders []order.Detail for x := range instrumentsToUse { openOrders, err := c.GetOpenOrders(instrumentsToUse[x]) if err != nil { @@ -652,9 +658,9 @@ func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ p := currency.NewPairFromFormattedPairs(curr, c.GetEnabledPairs(asset.Spot), c.GetPairFormat(asset.Spot, true)) - orderSide := exchange.OrderSide(strings.ToUpper(openOrders.Orders[y].Side)) + orderSide := order.Side(strings.ToUpper(openOrders.Orders[y].Side)) orderDate := time.Unix(openOrders.Orders[y].Timestamp, 0) - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ ID: strconv.FormatInt(openOrders.Orders[y].OrderID, 10), Amount: openOrders.Orders[y].Quantity, Price: openOrders.Orders[y].Price, @@ -666,14 +672,14 @@ func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ } } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (c *COINUT) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { if !c.instrumentMap.IsLoaded() { err := c.SeedInstruments() if err != nil { @@ -682,9 +688,9 @@ func (c *COINUT) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ } var instrumentsToUse []int64 - if len(getOrdersRequest.Currencies) > 0 { - for x := range getOrdersRequest.Currencies { - currency := c.FormatExchangeCurrency(getOrdersRequest.Currencies[x], + if len(req.Currencies) > 0 { + for x := range req.Currencies { + currency := c.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() instrumentsToUse = append(instrumentsToUse, c.instrumentMap.LookupID(currency)) @@ -697,7 +703,7 @@ func (c *COINUT) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ return nil, errors.New("no instrument IDs to use") } - var allOrders []exchange.OrderDetail + var allOrders []order.Detail for x := range instrumentsToUse { orders, err := c.GetTradeHistory(instrumentsToUse[x], -1, -1) if err != nil { @@ -708,10 +714,9 @@ func (c *COINUT) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ p := currency.NewPairFromFormattedPairs(curr, c.GetEnabledPairs(asset.Spot), c.GetPairFormat(asset.Spot, true)) - orderSide := exchange.OrderSide( - strings.ToUpper(orders.Trades[y].Order.Side)) + orderSide := order.Side(strings.ToUpper(orders.Trades[y].Order.Side)) orderDate := time.Unix(orders.Trades[y].Order.Timestamp, 0) - allOrders = append(allOrders, exchange.OrderDetail{ + allOrders = append(allOrders, order.Detail{ ID: strconv.FormatInt(orders.Trades[y].Order.OrderID, 10), Amount: orders.Trades[y].Order.Quantity, Price: orders.Trades[y].Order.Price, @@ -724,9 +729,8 @@ func (c *COINUT) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ } - exchange.FilterOrdersByTickRange(&allOrders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&allOrders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&allOrders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&allOrders, req.OrderSide) return allOrders, nil } diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 3d5beb28..f9a9ad55 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -1314,319 +1314,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { } } -func TestOrderSides(t *testing.T) { - t.Parallel() - - var os = BuyOrderSide - if os.ToString() != "BUY" { - t.Errorf("unexpected string %s", os.ToString()) - } - - if os.ToLower() != "buy" { - t.Errorf("unexpected string %s", os.ToString()) - } -} - -func TestOrderTypes(t *testing.T) { - t.Parallel() - - var ot OrderType = "Mo'Money" - - if ot.ToString() != "Mo'Money" { - t.Errorf("unexpected string %s", ot.ToString()) - } - - if ot.ToLower() != "mo'money" { - t.Errorf("unexpected string %s", ot.ToString()) - } -} - -func TestFilterOrdersByType(t *testing.T) { - t.Parallel() - - var orders = []OrderDetail{ - { - OrderType: ImmediateOrCancelOrderType, - }, - { - OrderType: LimitOrderType, - }, - } - - FilterOrdersByType(&orders, AnyOrderType) - if len(orders) != 2 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) - } - - FilterOrdersByType(&orders, LimitOrderType) - if len(orders) != 1 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } - - FilterOrdersByType(&orders, StopOrderType) - if len(orders) != 0 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) - } -} - -func TestFilterOrdersBySide(t *testing.T) { - t.Parallel() - - var orders = []OrderDetail{ - { - OrderSide: BuyOrderSide, - }, - { - OrderSide: SellOrderSide, - }, - {}, - } - - FilterOrdersBySide(&orders, AnyOrderSide) - if len(orders) != 3 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) - } - - FilterOrdersBySide(&orders, BuyOrderSide) - if len(orders) != 1 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } - - FilterOrdersBySide(&orders, SellOrderSide) - if len(orders) != 0 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) - } -} - -func TestFilterOrdersByTickRange(t *testing.T) { - t.Parallel() - - var orders = []OrderDetail{ - { - OrderDate: time.Unix(100, 0), - }, - { - OrderDate: time.Unix(110, 0), - }, - { - OrderDate: time.Unix(111, 0), - }, - } - - FilterOrdersByTickRange(&orders, time.Unix(0, 0), time.Unix(0, 0)) - if len(orders) != 3 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) - } - - FilterOrdersByTickRange(&orders, time.Unix(100, 0), time.Unix(111, 0)) - if len(orders) != 3 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) - } - - FilterOrdersByTickRange(&orders, time.Unix(101, 0), time.Unix(111, 0)) - if len(orders) != 2 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) - } - - FilterOrdersByTickRange(&orders, time.Unix(200, 0), time.Unix(300, 0)) - if len(orders) != 0 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) - } -} - -func TestFilterOrdersByCurrencies(t *testing.T) { - t.Parallel() - - var orders = []OrderDetail{ - { - CurrencyPair: currency.NewPair(currency.BTC, currency.USD), - }, - { - CurrencyPair: currency.NewPair(currency.LTC, currency.EUR), - }, - { - CurrencyPair: currency.NewPair(currency.DOGE, currency.RUB), - }, - } - - currencies := []currency.Pair{currency.NewPair(currency.BTC, currency.USD), - currency.NewPair(currency.LTC, currency.EUR), - currency.NewPair(currency.DOGE, currency.RUB)} - FilterOrdersByCurrencies(&orders, currencies) - if len(orders) != 3 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) - } - - currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD), - currency.NewPair(currency.LTC, currency.EUR)} - FilterOrdersByCurrencies(&orders, currencies) - if len(orders) != 2 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) - } - - currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD)} - FilterOrdersByCurrencies(&orders, currencies) - if len(orders) != 1 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } - - currencies = []currency.Pair{} - FilterOrdersByCurrencies(&orders, currencies) - if len(orders) != 1 { - t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) - } -} - -func TestSortOrdersByPrice(t *testing.T) { - t.Parallel() - - orders := []OrderDetail{ - { - Price: 100, - }, { - Price: 0, - }, { - Price: 50, - }, - } - - SortOrdersByPrice(&orders, false) - if orders[0].Price != 0 { - t.Errorf("Expected: '%v', received: '%v'", 0, orders[0].Price) - } - - SortOrdersByPrice(&orders, true) - if orders[0].Price != 100 { - t.Errorf("Expected: '%v', received: '%v'", 100, orders[0].Price) - } -} - -func TestSortOrdersByDate(t *testing.T) { - t.Parallel() - - orders := []OrderDetail{ - { - OrderDate: time.Unix(0, 0), - }, { - OrderDate: time.Unix(1, 0), - }, { - OrderDate: time.Unix(2, 0), - }, - } - - SortOrdersByDate(&orders, false) - if orders[0].OrderDate.Unix() != time.Unix(0, 0).Unix() { - t.Errorf("Expected: '%v', received: '%v'", - time.Unix(0, 0).Unix(), - orders[0].OrderDate.Unix()) - } - - SortOrdersByDate(&orders, true) - if orders[0].OrderDate.Unix() != time.Unix(2, 0).Unix() { - t.Errorf("Expected: '%v', received: '%v'", - time.Unix(2, 0).Unix(), - orders[0].OrderDate.Unix()) - } -} - -func TestSortOrdersByCurrency(t *testing.T) { - t.Parallel() - - orders := []OrderDetail{ - { - CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), - currency.USD.String(), - "-"), - }, { - CurrencyPair: currency.NewPairWithDelimiter(currency.DOGE.String(), - currency.USD.String(), - "-"), - }, { - CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), - currency.RUB.String(), - "-"), - }, { - CurrencyPair: currency.NewPairWithDelimiter(currency.LTC.String(), - currency.EUR.String(), - "-"), - }, { - CurrencyPair: currency.NewPairWithDelimiter(currency.LTC.String(), - currency.AUD.String(), - "-"), - }, - } - - SortOrdersByCurrency(&orders, false) - if orders[0].CurrencyPair.String() != currency.BTC.String()+"-"+currency.RUB.String() { - t.Errorf("Expected: '%v', received: '%v'", - currency.BTC.String()+"-"+currency.RUB.String(), - orders[0].CurrencyPair.String()) - } - - SortOrdersByCurrency(&orders, true) - if orders[0].CurrencyPair.String() != currency.LTC.String()+"-"+currency.EUR.String() { - t.Errorf("Expected: '%v', received: '%v'", - currency.LTC.String()+"-"+currency.EUR.String(), - orders[0].CurrencyPair.String()) - } -} - -func TestSortOrdersByOrderSide(t *testing.T) { - t.Parallel() - - orders := []OrderDetail{ - { - OrderSide: BuyOrderSide, - }, { - OrderSide: SellOrderSide, - }, { - OrderSide: SellOrderSide, - }, { - OrderSide: BuyOrderSide, - }, - } - - SortOrdersBySide(&orders, false) - if !strings.EqualFold(orders[0].OrderSide.ToString(), BuyOrderSide.ToString()) { - t.Errorf("Expected: '%v', received: '%v'", - BuyOrderSide, - orders[0].OrderSide) - } - - SortOrdersBySide(&orders, true) - if !strings.EqualFold(orders[0].OrderSide.ToString(), SellOrderSide.ToString()) { - t.Errorf("Expected: '%v', received: '%v'", - SellOrderSide, - orders[0].OrderSide) - } -} - -func TestSortOrdersByOrderType(t *testing.T) { - t.Parallel() - - orders := []OrderDetail{ - { - OrderType: MarketOrderType, - }, { - OrderType: LimitOrderType, - }, { - OrderType: ImmediateOrCancelOrderType, - }, { - OrderType: TrailingStopOrderType, - }, - } - - SortOrdersByType(&orders, false) - if !strings.EqualFold(orders[0].OrderType.ToString(), ImmediateOrCancelOrderType.ToString()) { - t.Errorf("Expected: '%v', received: '%v'", ImmediateOrCancelOrderType, orders[0].OrderType) - } - - SortOrdersByType(&orders, true) - if !strings.EqualFold(orders[0].OrderType.ToString(), TrailingStopOrderType.ToString()) { - t.Errorf("Expected: '%v', received: '%v'", TrailingStopOrderType, orders[0].OrderType) - } -} - func TestIsAssetTypeSupported(t *testing.T) { t.Parallel() diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index d78d0822..9c7f25c5 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -8,6 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) const ( @@ -269,8 +270,8 @@ func TestGetActiveOrders(t *testing.T) { e.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := e.GetActiveOrders(&getOrdersRequest) @@ -285,8 +286,8 @@ func TestGetOrderHistory(t *testing.T) { e.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } currPair := currency.NewPair(currency.BTC, currency.USD) currPair.Delimiter = "_" @@ -313,14 +314,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -343,7 +344,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -369,7 +370,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -385,13 +386,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { - _, err := e.ModifyOrder(&exchange.ModifyOrder{}) + _, err := e.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index 6aa4fcb6..2486a3c7 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -227,7 +228,8 @@ func (e *EXMO) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook. // UpdateOrderbook updates and returns the orderbook for a currency pair func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - pairsCollated, err := e.FormatExchangeCurrencies(e.GetEnabledPairs(assetType), assetType) + pairsCollated, err := e.FormatExchangeCurrencies(e.GetEnabledPairs(assetType), + assetType) if err != nil { return orderBook, err } @@ -249,7 +251,8 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook z := data.Ask[y] price, _ := strconv.ParseFloat(z[0], 64) amount, _ := strconv.ParseFloat(z[1], 64) - obItems = append(obItems, orderbook.Item{Price: price, Amount: amount}) + obItems = append(obItems, + orderbook.Item{Price: price, Amount: amount}) } orderBook.Asks = obItems @@ -258,7 +261,8 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook z := data.Bid[y] price, _ := strconv.ParseFloat(z[0], 64) amount, _ := strconv.ParseFloat(z[1], 64) - obItems = append(obItems, orderbook.Item{Price: price, Amount: amount}) + obItems = append(obItems, + orderbook.Item{Price: price, Amount: amount}) } orderBook.Bids = obItems @@ -319,31 +323,30 @@ func (e *EXMO) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exch } // SubmitOrder submits a new order -func (e *EXMO) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (e *EXMO) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } var oT string - switch order.OrderType { - case exchange.LimitOrderType: + switch s.OrderType { + case order.Limit: return submitOrderResponse, errors.New("unsupported order type") - case exchange.MarketOrderType: - oT = "market_buy" - if order.OrderSide == exchange.SellOrderSide { + case order.Market: + if s.OrderSide == order.Sell { oT = "market_sell" + } else { + oT = "market_buy" } } - response, err := e.CreateOrder(order.Pair.String(), oT, order.Price, - order.Amount) + response, err := e.CreateOrder(s.Pair.String(), + oT, + s.Price, + s.Amount) if response > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response) + submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } if err == nil { @@ -355,12 +358,12 @@ func (e *EXMO) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrde // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (e *EXMO) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (e *EXMO) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (e *EXMO) CancelOrder(order *exchange.OrderCancellation) error { +func (e *EXMO) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) if err != nil { return err @@ -370,9 +373,9 @@ func (e *EXMO) CancelOrder(order *exchange.OrderCancellation) error { } // CancelAllOrders cancels all orders associated with a currency pair -func (e *EXMO) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (e *EXMO) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } openOrders, err := e.GetOpenOrders() @@ -383,7 +386,7 @@ func (e *EXMO) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAl for _, order := range openOrders { err = e.CancelExistingOrder(order.OrderID) if err != nil { - cancelAllOrdersResponse.OrderStatus[strconv.FormatInt(order.OrderID, 10)] = err.Error() + cancelAllOrdersResponse.Status[strconv.FormatInt(order.OrderID, 10)] = err.Error() } } @@ -391,8 +394,8 @@ func (e *EXMO) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAl } // GetOrderInfo returns information on a current open order -func (e *EXMO) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (e *EXMO) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -419,7 +422,7 @@ func (e *EXMO) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithd withdrawRequest.AddressTag, withdrawRequest.Amount) - return fmt.Sprintf("%v", resp), err + return strconv.FormatInt(resp, 10), err } // WithdrawFiatFunds returns a withdrawal ID when a @@ -449,43 +452,42 @@ func (e *EXMO) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { } // GetActiveOrders retrieves any orders that are active/open -func (e *EXMO) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (e *EXMO) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := e.GetOpenOrders() if err != nil { return nil, err } - var orders []exchange.OrderDetail - for _, order := range resp { - symbol := currency.NewPairDelimiter(order.Pair, "_") - orderDate := time.Unix(order.Created, 0) - orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.OrderID), - Amount: order.Quantity, + var orders []order.Detail + for i := range resp { + symbol := currency.NewPairDelimiter(resp[i].Pair, "_") + orderDate := time.Unix(resp[i].Created, 0) + orderSide := order.Side(strings.ToUpper(resp[i].Type)) + orders = append(orders, order.Detail{ + ID: strconv.FormatInt(resp[i].OrderID, 10), + Amount: resp[i].Quantity, OrderDate: orderDate, - Price: order.Price, + Price: resp[i].Price, OrderSide: orderSide, Exchange: e.Name, CurrencyPair: symbol, }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (e *EXMO) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - if len(getOrdersRequest.Currencies) == 0 { +func (e *EXMO) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + if len(req.Currencies) == 0 { return nil, errors.New("currency must be supplied") } var allTrades []UserTrades - for _, currency := range getOrdersRequest.Currencies { + for _, currency := range req.Currencies { resp, err := e.GetUserTrades(e.FormatExchangeCurrency(currency, asset.Spot).String(), "", "10000") if err != nil { return nil, err @@ -495,25 +497,24 @@ func (e *EXMO) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]e } } - var orders []exchange.OrderDetail - for _, order := range allTrades { - symbol := currency.NewPairDelimiter(order.Pair, "_") - orderDate := time.Unix(order.Date, 0) - orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.TradeID), - Amount: order.Quantity, + var orders []order.Detail + for i := range allTrades { + symbol := currency.NewPairDelimiter(allTrades[i].Pair, "_") + orderDate := time.Unix(allTrades[i].Date, 0) + orderSide := order.Side(strings.ToUpper(allTrades[i].Type)) + orders = append(orders, order.Detail{ + ID: strconv.FormatInt(allTrades[i].TradeID, 10), + Amount: allTrades[i].Quantity, OrderDate: orderDate, - Price: order.Price, + Price: allTrades[i].Price, OrderSide: orderSide, Exchange: e.Name, CurrencyPair: symbol, }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 6ba9a5ab..7437e6da 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -76,7 +77,7 @@ func TestSpotNewOrder(t *testing.T) { Symbol: "btc_usdt", Amount: 1.1, Price: 10.1, - Type: exchange.SellOrderSide.ToLower().ToString(), + Type: order.Sell.Lower(), }) if err != nil { t.Errorf("Gateio SpotNewOrder: %s", err) @@ -278,8 +279,8 @@ func TestGetActiveOrders(t *testing.T) { g.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := g.GetActiveOrders(&getOrdersRequest) @@ -294,8 +295,8 @@ func TestGetOrderHistory(t *testing.T) { g.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } currPair := currency.NewPair(currency.LTC, currency.BTC) @@ -324,14 +325,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip() } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", Base: currency.LTC, Quote: currency.BTC, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -354,7 +355,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -380,7 +381,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -396,8 +397,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -416,7 +417,7 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { - _, err := g.ModifyOrder(&exchange.ModifyOrder{}) + _, err := g.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index c4436beb..e8308b97 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -376,33 +377,29 @@ func (g *Gateio) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex // SubmitOrder submits a new order // TODO: support multiple order types (IOC) -func (g *Gateio) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (g *Gateio) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } var orderTypeFormat string - if order.OrderSide == exchange.BuyOrderSide { - orderTypeFormat = exchange.BuyOrderSide.ToLower().ToString() + if s.OrderSide == order.Buy { + orderTypeFormat = order.Buy.Lower() } else { - orderTypeFormat = exchange.SellOrderSide.ToLower().ToString() + orderTypeFormat = order.Sell.Lower() } var spotNewOrderRequestParams = SpotNewOrderRequestParams{ - Amount: order.Amount, - Price: order.Price, - Symbol: order.Pair.String(), + Amount: s.Amount, + Price: s.Price, + Symbol: s.Pair.String(), Type: orderTypeFormat, } response, err := g.SpotNewOrder(spotNewOrderRequestParams) if response.OrderNumber > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response.OrderNumber) + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) } if err == nil { @@ -414,27 +411,25 @@ func (g *Gateio) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOr // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (g *Gateio) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (g *Gateio) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (g *Gateio) CancelOrder(order *exchange.OrderCancellation) error { +func (g *Gateio) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) - if err != nil { return err } - _, err = g.CancelExistingOrder(orderIDInt, g.FormatExchangeCurrency(order.CurrencyPair, - order.AssetType).String()) - + _, err = g.CancelExistingOrder(orderIDInt, + g.FormatExchangeCurrency(order.CurrencyPair, order.AssetType).String()) return err } // CancelAllOrders cancels all orders associated with a currency pair -func (g *Gateio) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (g *Gateio) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } openOrders, err := g.GetOpenOrders("") if err != nil { @@ -449,7 +444,7 @@ func (g *Gateio) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel for unique := range uniqueSymbols { err = g.CancelAllExistingOrders(-1, unique) if err != nil { - cancelAllOrdersResponse.OrderStatus[unique] = err.Error() + cancelAllOrdersResponse.Status[unique] = err.Error() } } @@ -457,8 +452,8 @@ func (g *Gateio) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel } // GetOrderInfo returns information on a current open order -func (g *Gateio) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (g *Gateio) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail orders, err := g.GetOpenOrders("") if err != nil { @@ -474,14 +469,14 @@ func (g *Gateio) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { orderDetail.ExecutedAmount = orders.Orders[x].FilledAmount orderDetail.Amount = orders.Orders[x].InitialAmount orderDetail.OrderDate = time.Unix(orders.Orders[x].Timestamp, 0) - orderDetail.Status = orders.Orders[x].Status + orderDetail.Status = order.Status(orders.Orders[x].Status) orderDetail.Price = orders.Orders[x].Rate orderDetail.CurrencyPair = currency.NewPairDelimiter(orders.Orders[x].CurrencyPair, g.GetPairFormat(asset.Spot, false).Delimiter) - if strings.EqualFold(orders.Orders[x].Type, exchange.AskOrderSide.ToString()) { - orderDetail.OrderSide = exchange.AskOrderSide - } else if strings.EqualFold(orders.Orders[x].Type, exchange.BidOrderSide.ToString()) { - orderDetail.OrderSide = exchange.BuyOrderSide + if strings.EqualFold(orders.Orders[x].Type, order.Ask.String()) { + orderDetail.OrderSide = order.Ask + } else if strings.EqualFold(orders.Orders[x].Type, order.Bid.String()) { + orderDetail.OrderSide = order.Buy } return orderDetail, nil } @@ -495,21 +490,11 @@ func (g *Gateio) GetDepositAddress(cryptocurrency currency.Code, _ string) (stri return "", err } - // Waits for new generated address if not created yet, its variable per - // currency if addr == gateioGenerateAddress { - time.Sleep(10 * time.Second) - addr, err = g.GetCryptoDepositAddress(cryptocurrency.String()) - if err != nil { - return "", err - } - if addr == gateioGenerateAddress { - return "", errors.New("new deposit address is being generated, please retry again shortly") - } - return addr, nil + return "", + errors.New("new deposit address is being generated, please retry again shortly") } - - return addr, err + return addr, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is @@ -545,10 +530,10 @@ func (g *Gateio) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) } // GetActiveOrders retrieves any orders that are active/open -func (g *Gateio) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { var currPair string - if len(getOrdersRequest.Currencies) == 1 { - currPair = getOrdersRequest.Currencies[0].String() + if len(req.Currencies) == 1 { + currPair = req.Currencies[0].String() } resp, err := g.GetOpenOrders(currPair) @@ -556,7 +541,7 @@ func (g *Gateio) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp.Orders { if resp.Orders[i].Status != "open" { continue @@ -564,10 +549,10 @@ func (g *Gateio) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ symbol := currency.NewPairDelimiter(resp.Orders[i].CurrencyPair, g.GetPairFormat(asset.Spot, false).Delimiter) - side := exchange.OrderSide(strings.ToUpper(resp.Orders[i].Type)) + side := order.Side(strings.ToUpper(resp.Orders[i].Type)) orderDate := time.Unix(resp.Orders[i].Timestamp, 0) - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ ID: resp.Orders[i].OrderNumber, Amount: resp.Orders[i].Amount, Price: resp.Orders[i].Rate, @@ -576,20 +561,20 @@ func (g *Gateio) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ OrderSide: side, Exchange: g.Name, CurrencyPair: symbol, - Status: resp.Orders[i].Status, + Status: order.Status(resp.Orders[i].Status), }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (g *Gateio) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (g *Gateio) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var trades []TradesResponse - for _, currency := range getOrdersRequest.Currencies { + for _, currency := range req.Currencies { resp, err := g.GetTradeHistory(currency.String()) if err != nil { return nil, err @@ -597,13 +582,13 @@ func (g *Gateio) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ trades = append(trades, resp.Trades...) } - var orders []exchange.OrderDetail + var orders []order.Detail for _, trade := range trades { symbol := currency.NewPairDelimiter(trade.Pair, g.GetPairFormat(asset.Spot, false).Delimiter) - side := exchange.OrderSide(strings.ToUpper(trade.Type)) + side := order.Side(strings.ToUpper(trade.Type)) orderDate := time.Unix(trade.TimeUnix, 0) - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ ID: strconv.FormatInt(trade.OrderID, 10), Amount: trade.Amount, Price: trade.Rate, @@ -614,9 +599,8 @@ func (g *Gateio) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index 032ec73f..9681a402 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -343,8 +344,8 @@ func TestFormatWithdrawPermissions(t *testing.T) { func TestGetActiveOrders(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{ currency.NewPair(currency.LTC, currency.BTC), }, @@ -363,8 +364,8 @@ func TestGetActiveOrders(t *testing.T) { func TestGetOrderHistory(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)}, } @@ -391,14 +392,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", Base: currency.LTC, Quote: currency.BTC, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 10, Amount: 1, ClientID: "1234234", @@ -421,7 +422,7 @@ func TestCancelExchangeOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "266029865", } @@ -444,7 +445,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -461,14 +462,14 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { t.Parallel() - _, err := g.ModifyOrder(&exchange.ModifyOrder{}) + _, err := g.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 42a2392e..089f9fff 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -16,6 +16,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" @@ -310,7 +311,7 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa Amount: result.Events[i].Remaining, Price: result.Events[i].Price, } - if strings.EqualFold(result.Events[i].Side, exchange.AskOrderSide.ToString()) { + if strings.EqualFold(result.Events[i].Side, order.Ask.String()) { asks = append(asks, item) } else { bids = append(bids, item) diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 93a2cbc9..f0763251 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -2,7 +2,6 @@ package gemini import ( "errors" - "fmt" "net/url" "strconv" "strings" @@ -14,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -317,28 +317,25 @@ func (g *Gemini) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex } // SubmitOrder submits a new order -func (g *Gemini) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (g *Gemini) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - if order.OrderType != exchange.LimitOrderType { - return submitOrderResponse, errors.New("only limit orders are enabled through this exchange") + if s.OrderType != order.Limit { + return submitOrderResponse, + errors.New("only limit orders are enabled through this exchange") } response, err := g.NewOrder( - g.FormatExchangeCurrency(order.Pair, asset.Spot).String(), - order.Amount, - order.Price, - order.OrderSide.ToString(), + g.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + s.Amount, + s.Price, + s.OrderSide.String(), "exchange limit") if response > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response) + submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } if err == nil { submitOrderResponse.IsOrderPlaced = true @@ -348,12 +345,12 @@ func (g *Gemini) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOr // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (g *Gemini) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (g *Gemini) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (g *Gemini) CancelOrder(order *exchange.OrderCancellation) error { +func (g *Gemini) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) if err != nil { return err @@ -364,9 +361,9 @@ func (g *Gemini) CancelOrder(order *exchange.OrderCancellation) error { } // CancelAllOrders cancels all orders associated with a currency pair -func (g *Gemini) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (g *Gemini) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } resp, err := g.CancelExistingOrders(false) if err != nil { @@ -374,15 +371,15 @@ func (g *Gemini) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel } for _, order := range resp.Details.CancelRejects { - cancelAllOrdersResponse.OrderStatus[order] = "Could not cancel order" + cancelAllOrdersResponse.Status[order] = "Could not cancel order" } return cancelAllOrdersResponse, nil } // GetOrderInfo returns information on a current open order -func (g *Gemini) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (g *Gemini) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -436,30 +433,30 @@ func (g *Gemini) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) } // GetActiveOrders retrieves any orders that are active/open -func (g *Gemini) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (g *Gemini) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := g.GetOrders() if err != nil { return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp { symbol := currency.NewPairDelimiter(resp[i].Symbol, g.GetPairFormat(asset.Spot, false).Delimiter) - var orderType exchange.OrderType + var orderType order.Type if resp[i].Type == "exchange limit" { - orderType = exchange.LimitOrderType + orderType = order.Limit } else if resp[i].Type == "market buy" || resp[i].Type == "market sell" { - orderType = exchange.MarketOrderType + orderType = order.Market } - side := exchange.OrderSide(strings.ToUpper(resp[i].Type)) + side := order.Side(strings.ToUpper(resp[i].Type)) orderDate := time.Unix(resp[i].Timestamp, 0) - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ Amount: resp[i].OriginalAmount, RemainingAmount: resp[i].RemainingAmount, - ID: fmt.Sprintf("%v", resp[i].OrderID), + ID: strconv.FormatInt(resp[i].OrderID, 10), ExecutedAmount: resp[i].ExecutedAmount, Exchange: g.Name, OrderType: orderType, @@ -470,44 +467,44 @@ func (g *Gemini) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByType(&orders, req.OrderType) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (g *Gemini) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - if len(getOrdersRequest.Currencies) == 0 { +func (g *Gemini) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + if len(req.Currencies) == 0 { return nil, errors.New("currency must be supplied") } var trades []TradeHistory - for _, currency := range getOrdersRequest.Currencies { - resp, err := g.GetTradeHistory(g.FormatExchangeCurrency(currency, - asset.Spot).String(), getOrdersRequest.StartTicks.Unix()) + for j := range req.Currencies { + resp, err := g.GetTradeHistory(g.FormatExchangeCurrency(req.Currencies[j], + asset.Spot).String(), + req.StartTicks.Unix()) if err != nil { return nil, err } for i := range resp { - resp[i].BaseCurrency = currency.Base.String() - resp[i].QuoteCurrency = currency.Quote.String() + resp[i].BaseCurrency = req.Currencies[j].Base.String() + resp[i].QuoteCurrency = req.Currencies[j].Quote.String() trades = append(trades, resp[i]) } } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range trades { - side := exchange.OrderSide(strings.ToUpper(trades[i].Type)) + side := order.Side(strings.ToUpper(trades[i].Type)) orderDate := time.Unix(trades[i].Timestamp, 0) - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ Amount: trades[i].Amount, - ID: fmt.Sprintf("%v", trades[i].OrderID), + ID: strconv.FormatInt(trades[i].OrderID, 10), Exchange: g.Name, OrderDate: orderDate, OrderSide: side, @@ -519,9 +516,8 @@ func (g *Gemini) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index 4326501e..976de532 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -232,8 +233,8 @@ func TestGetActiveOrders(t *testing.T) { h.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.ETH, currency.BTC)}, } @@ -249,8 +250,8 @@ func TestGetOrderHistory(t *testing.T) { h.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.ETH, currency.BTC)}, } @@ -276,13 +277,13 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.DGD, Quote: currency.BTC, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -305,7 +306,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -331,7 +332,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -347,13 +348,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { - _, err := h.ModifyOrder(&exchange.ModifyOrder{}) + _, err := h.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } @@ -480,7 +481,10 @@ func TestWsPlaceOrder(t *testing.T) { if !canManipulateRealOrders { t.Skip("canManipulateRealOrders false, skipping test") } - _, err := h.wsPlaceOrder(currency.NewPair(currency.LTC, currency.BTC), exchange.BuyOrderSide.ToString(), 1, 1) + _, err := h.wsPlaceOrder(currency.NewPair(currency.LTC, currency.BTC), + order.Buy.String(), + 1, + 1) if err != nil { t.Fatal(err) } diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 2c36f2bc..b4e829ac 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -340,17 +341,18 @@ func (h *HitBTC) GetAccountInfo() (exchange.AccountInfo, error) { } var currencies []exchange.AccountCurrencyInfo - for _, item := range accountBalance { + for i := range accountBalance { var exchangeCurrency exchange.AccountCurrencyInfo - exchangeCurrency.CurrencyName = currency.NewCode(item.Currency) - exchangeCurrency.TotalValue = item.Available - exchangeCurrency.Hold = item.Reserved + exchangeCurrency.CurrencyName = currency.NewCode(accountBalance[i].Currency) + exchangeCurrency.TotalValue = accountBalance[i].Available + exchangeCurrency.Hold = accountBalance[i].Reserved currencies = append(currencies, exchangeCurrency) } - response.Accounts = append(response.Accounts, exchange.Account{ - Currencies: currencies, - }) + response.Accounts = append(response.Accounts, + exchange.Account{ + Currencies: currencies, + }) return response, nil } @@ -368,23 +370,19 @@ func (h *HitBTC) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex } // SubmitOrder submits a new order -func (h *HitBTC) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (h *HitBTC) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - response, err := h.PlaceOrder(order.Pair.String(), - order.Price, - order.Amount, - strings.ToLower(order.OrderType.ToString()), - strings.ToLower(order.OrderSide.ToString())) + response, err := h.PlaceOrder(s.Pair.String(), + s.Price, + s.Amount, + strings.ToLower(s.OrderType.String()), + strings.ToLower(s.OrderSide.String())) if response.OrderNumber > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response.OrderNumber) + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) } if err == nil { submitOrderResponse.IsOrderPlaced = true @@ -394,27 +392,25 @@ func (h *HitBTC) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOr // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (h *HitBTC) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (h *HitBTC) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (h *HitBTC) CancelOrder(order *exchange.OrderCancellation) error { +func (h *HitBTC) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) - if err != nil { return err } _, err = h.CancelExistingOrder(orderIDInt) - return err } // CancelAllOrders cancels all orders associated with a currency pair -func (h *HitBTC) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (h *HitBTC) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } resp, err := h.CancelAllExistingOrders() @@ -424,7 +420,7 @@ func (h *HitBTC) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel for i := range resp { if resp[i].Status != "canceled" { - cancelAllOrdersResponse.OrderStatus[strconv.FormatInt(resp[i].ID, 10)] = + cancelAllOrdersResponse.Status[strconv.FormatInt(resp[i].ID, 10)] = fmt.Sprintf("Could not cancel order %v. Status: %v", resp[i].ID, resp[i].Status) @@ -435,8 +431,8 @@ func (h *HitBTC) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel } // GetOrderInfo returns information on a current open order -func (h *HitBTC) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (h *HitBTC) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -454,7 +450,6 @@ func (h *HitBTC) GetDepositAddress(currency currency.Code, _ string) (string, er // submitted func (h *HitBTC) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { _, err := h.Withdraw(withdrawRequest.Currency.String(), withdrawRequest.Address, withdrawRequest.Amount) - return "", err } @@ -485,26 +480,26 @@ func (h *HitBTC) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) } // GetActiveOrders retrieves any orders that are active/open -func (h *HitBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - if len(getOrdersRequest.Currencies) == 0 { +func (h *HitBTC) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { + if len(req.Currencies) == 0 { return nil, errors.New("currency must be supplied") } var allOrders []OrderHistoryResponse - for _, currency := range getOrdersRequest.Currencies { - resp, err := h.GetOpenOrders(currency.String()) + for i := range req.Currencies { + resp, err := h.GetOpenOrders(req.Currencies[i].String()) if err != nil { return nil, err } allOrders = append(allOrders, resp...) } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, h.GetPairFormat(asset.Spot, false).Delimiter) - side := exchange.OrderSide(strings.ToUpper(allOrders[i].Side)) - orders = append(orders, exchange.OrderDetail{ + side := order.Side(strings.ToUpper(allOrders[i].Side)) + orders = append(orders, order.Detail{ ID: allOrders[i].ID, Amount: allOrders[i].Quantity, Exchange: h.Name, @@ -515,34 +510,33 @@ func (h *HitBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (h *HitBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - if len(getOrdersRequest.Currencies) == 0 { +func (h *HitBTC) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + if len(req.Currencies) == 0 { return nil, errors.New("currency must be supplied") } var allOrders []OrderHistoryResponse - for _, currency := range getOrdersRequest.Currencies { - resp, err := h.GetOrders(currency.String()) + for i := range req.Currencies { + resp, err := h.GetOrders(req.Currencies[i].String()) if err != nil { return nil, err } allOrders = append(allOrders, resp...) } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Symbol, h.GetPairFormat(asset.Spot, false).Delimiter) - side := exchange.OrderSide(strings.ToUpper(allOrders[i].Side)) - orders = append(orders, exchange.OrderDetail{ + side := order.Side(strings.ToUpper(allOrders[i].Side)) + orders = append(orders, order.Detail{ ID: allOrders[i].ID, Amount: allOrders[i].Quantity, Exchange: h.Name, @@ -553,8 +547,8 @@ func (h *HitBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 18aae6b2..91079f85 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -17,6 +17,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -456,8 +457,8 @@ func TestGetActiveOrders(t *testing.T) { h.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)}, } @@ -473,8 +474,8 @@ func TestGetOrderHistory(t *testing.T) { h.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)}, } @@ -509,13 +510,13 @@ func TestSubmitOrder(t *testing.T) { t.Fatalf("Failed to get accounts. Err: %s", err) } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.USDT, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: strconv.FormatInt(accounts[0].ID, 10), @@ -536,7 +537,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -561,7 +562,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = exchange.OrderCancellation{ + var orderCancellation = order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -577,8 +578,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -597,7 +598,7 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { - _, err := h.ModifyOrder(&exchange.ModifyOrder{}) + _, err := h.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index ec423131..b14483a0 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -193,7 +194,11 @@ func (h *HUOBI) Start(wg *sync.WaitGroup) { // Run implements the HUOBI wrapper func (h *HUOBI) Run() { if h.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket.IsEnabled()), wsMarketURL) + log.Debugf(log.ExchangeSys, + "%s Websocket: %s (url: %s).\n", + h.GetName(), + common.IsEnabled(h.Websocket.IsEnabled()), + wsMarketURL) h.PrintEnabledPairs() } @@ -207,7 +212,10 @@ func (h *HUOBI) Run() { cfg := config.GetConfig() exchCfg, err := cfg.GetExchangeConfig(h.Name) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to get exchange config. %s\n", h.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to get exchange config. %s\n", + h.Name, + err) return } exchCfg.BaseCurrencies = currency.Currencies{currency.USD} @@ -221,11 +229,14 @@ func (h *HUOBI) Run() { Delimiter: "-", }, } - log.Warn(log.ExchangeSys, "Available and enabled pairs for Huobi reset due to config upgrade, please enable the ones you would like again") + log.Warn(log.ExchangeSys, + "Available and enabled pairs for Huobi reset due to config upgrade, please enable the ones you would like again") err := h.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf(log.ExchangeSys, "%s Failed to update enabled currencies.\n", h.GetName()) + log.Errorf(log.ExchangeSys, + "%s Failed to update enabled currencies.\n", + h.GetName()) } } @@ -235,7 +246,10 @@ func (h *HUOBI) Run() { err := h.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", h.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + h.Name, + err) } } @@ -251,8 +265,10 @@ func (h *HUOBI) FetchTradablePairs(asset asset.Item) ([]string, error) { if symbols[x].State != "online" { continue } - pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, - h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) + pairs = append(pairs, fmt.Sprintf("%v%v%v", + symbols[x].BaseCurrency, + h.GetPairFormat(asset, false).Delimiter, + symbols[x].QuoteCurrency)) } return pairs, nil @@ -266,7 +282,10 @@ func (h *HUOBI) UpdateTradablePairs(forceUpdate bool) error { return err } - return h.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + return h.UpdatePairs(currency.NewPairsFromStrings(pairs), + asset.Spot, + false, + forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair @@ -377,30 +396,28 @@ func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { return info, err } - for _, account := range accounts { + for i := range accounts { var acc exchange.Account - - acc.ID = strconv.FormatInt(account.ID, 10) - + acc.ID = strconv.FormatInt(accounts[i].ID, 10) balances, err := h.GetAccountBalance(acc.ID) if err != nil { return info, err } var currencyDetails []exchange.AccountCurrencyInfo - for _, balance := range balances { + for j := range balances { var frozen bool - if balance.Type == "frozen" { + if balances[j].Type == "frozen" { frozen = true } var updated bool for i := range currencyDetails { - if currencyDetails[i].CurrencyName == currency.NewCode(balance.Currency) { + if currencyDetails[i].CurrencyName == currency.NewCode(balances[j].Currency) { if frozen { - currencyDetails[i].Hold = balance.Balance + currencyDetails[i].Hold = balances[j].Balance } else { - currencyDetails[i].TotalValue = balance.Balance + currencyDetails[i].TotalValue = balances[j].Balance } updated = true } @@ -413,14 +430,14 @@ func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { if frozen { currencyDetails = append(currencyDetails, exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(balance.Currency), - Hold: balance.Balance, + CurrencyName: currency.NewCode(balances[j].Currency), + Hold: balances[j].Balance, }) } else { currencyDetails = append(currencyDetails, exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(balance.Currency), - TotalValue: balance.Balance, + CurrencyName: currency.NewCode(balances[j].Currency), + TotalValue: balances[j].Balance, }) } } @@ -445,46 +462,42 @@ func (h *HUOBI) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exc } // SubmitOrder submits a new order -func (h *HUOBI) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (h *HUOBI) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - accountID, err := strconv.ParseInt(order.ClientID, 10, 64) + accountID, err := strconv.ParseInt(s.ClientID, 10, 64) if err != nil { return submitOrderResponse, err } var formattedType SpotNewOrderRequestParamsType var params = SpotNewOrderRequestParams{ - Amount: order.Amount, + Amount: s.Amount, Source: "api", - Symbol: strings.ToLower(order.Pair.String()), + Symbol: s.Pair.Lower().String(), AccountID: int(accountID), } switch { - case order.OrderSide == exchange.BuyOrderSide && order.OrderType == exchange.MarketOrderType: + case s.OrderSide == order.Buy && s.OrderType == order.Market: formattedType = SpotNewOrderRequestTypeBuyMarket - case order.OrderSide == exchange.SellOrderSide && order.OrderType == exchange.MarketOrderType: + case s.OrderSide == order.Sell && s.OrderType == order.Market: formattedType = SpotNewOrderRequestTypeSellMarket - case order.OrderSide == exchange.BuyOrderSide && order.OrderType == exchange.LimitOrderType: + case s.OrderSide == order.Buy && s.OrderType == order.Limit: formattedType = SpotNewOrderRequestTypeBuyLimit - params.Price = order.Price - case order.OrderSide == exchange.SellOrderSide && order.OrderType == exchange.LimitOrderType: + params.Price = s.Price + case s.OrderSide == order.Sell && s.OrderType == order.Limit: formattedType = SpotNewOrderRequestTypeSellLimit - params.Price = order.Price + params.Price = s.Price } params.Type = formattedType response, err := h.SpotNewOrder(params) if response > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response) + submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } if err == nil { submitOrderResponse.IsOrderPlaced = true @@ -494,26 +507,24 @@ func (h *HUOBI) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrd // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (h *HUOBI) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (h *HUOBI) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (h *HUOBI) CancelOrder(order *exchange.OrderCancellation) error { +func (h *HUOBI) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) - if err != nil { return err } _, err = h.CancelExistingOrder(orderIDInt) - return err } // CancelAllOrders cancels all orders associated with a currency pair -func (h *HUOBI) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - var cancelAllOrdersResponse exchange.CancelAllOrdersResponse +func (h *HUOBI) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { + var cancelAllOrdersResponse order.CancelAllResponse for _, currency := range h.GetEnabledPairs(asset.Spot) { resp, err := h.CancelOpenOrdersBatch(orderCancellation.AccountID, h.FormatExchangeCurrency(currency, asset.Spot).String()) @@ -522,7 +533,9 @@ func (h *HUOBI) CancelAllOrders(orderCancellation *exchange.OrderCancellation) ( } if resp.Data.FailedCount > 0 { - return cancelAllOrdersResponse, fmt.Errorf("%v orders failed to cancel", resp.Data.FailedCount) + return cancelAllOrdersResponse, + fmt.Errorf("%v orders failed to cancel", + resp.Data.FailedCount) } if resp.Status == "error" { @@ -534,8 +547,8 @@ func (h *HUOBI) CancelAllOrders(orderCancellation *exchange.OrderCancellation) ( } // GetOrderInfo returns information on a current open order -func (h *HUOBI) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (h *HUOBI) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -548,7 +561,7 @@ func (h *HUOBI) GetDepositAddress(cryptocurrency currency.Code, accountID string // submitted func (h *HUOBI) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { resp, err := h.Withdraw(withdrawRequest.Currency, withdrawRequest.Address, withdrawRequest.AddressTag, withdrawRequest.Amount, withdrawRequest.FeeAmount) - return fmt.Sprintf("%v", resp), err + return strconv.FormatInt(resp, 10), err } // WithdrawFiatFunds returns a withdrawal ID when a @@ -578,37 +591,39 @@ func (h *HUOBI) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { } // GetActiveOrders retrieves any orders that are active/open -func (h *HUOBI) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - if len(getOrdersRequest.Currencies) == 0 { +func (h *HUOBI) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { + if len(req.Currencies) == 0 { return nil, errors.New("currency must be supplied") } side := "" - if getOrdersRequest.OrderSide == exchange.AnyOrderSide || getOrdersRequest.OrderSide == "" { + if req.OrderSide == order.AnySide || req.OrderSide == "" { side = "" - } else if getOrdersRequest.OrderSide == exchange.SellOrderSide { - side = strings.ToLower(string(getOrdersRequest.OrderSide)) + } else if req.OrderSide == order.Sell { + side = strings.ToLower(string(req.OrderSide)) } - var orders []exchange.OrderDetail + var orders []order.Detail - for _, c := range getOrdersRequest.Currencies { + for i := range req.Currencies { resp, err := h.GetOpenOrders(h.API.Credentials.ClientID, - c.Lower().String(), side, 500) + req.Currencies[i].Lower().String(), + side, + 500) if err != nil { return nil, err } for i := range resp { - orderDetail := exchange.OrderDetail{ - ID: fmt.Sprintf("%v", resp[i].ID), + orderDetail := order.Detail{ + ID: strconv.FormatInt(int64(resp[i].ID), 10), Price: resp[i].Price, Amount: resp[i].Amount, - CurrencyPair: c, + CurrencyPair: req.Currencies[i], Exchange: h.Name, ExecutedAmount: resp[i].FilledAmount, OrderDate: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)), - Status: resp[i].State, + Status: order.Status(resp[i].State), AccountID: strconv.FormatFloat(resp[i].AccountID, 'f', -1, 64), Fee: resp[i].FilledFees, } @@ -619,22 +634,21 @@ func (h *HUOBI) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] } } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (h *HUOBI) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - if len(getOrdersRequest.Currencies) == 0 { +func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + if len(req.Currencies) == 0 { return nil, errors.New("currency must be supplied") } states := "partial-canceled,filled,canceled" - var orders []exchange.OrderDetail - for _, c := range getOrdersRequest.Currencies { - resp, err := h.GetOrders(c.Lower().String(), + var orders []order.Detail + for i := range req.Currencies { + resp, err := h.GetOrders(req.Currencies[i].Lower().String(), "", "", "", @@ -647,15 +661,15 @@ func (h *HUOBI) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] } for i := range resp { - orderDetail := exchange.OrderDetail{ - ID: fmt.Sprintf("%v", resp[i].ID), + orderDetail := order.Detail{ + ID: strconv.FormatInt(int64(resp[i].ID), 10), Price: resp[i].Price, Amount: resp[i].Amount, - CurrencyPair: c, + CurrencyPair: req.Currencies[i], Exchange: h.Name, ExecutedAmount: resp[i].FilledAmount, OrderDate: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)), - Status: resp[i].State, + Status: order.Status(resp[i].State), AccountID: strconv.FormatFloat(resp[i].AccountID, 'f', -1, 64), Fee: resp[i].FilledFees, } @@ -666,25 +680,24 @@ func (h *HUOBI) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] } } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) return orders, nil } -func setOrderSideAndType(requestType string, orderDetail *exchange.OrderDetail) { +func setOrderSideAndType(requestType string, orderDetail *order.Detail) { switch SpotNewOrderRequestParamsType(requestType) { case SpotNewOrderRequestTypeBuyMarket: - orderDetail.OrderSide = exchange.BuyOrderSide - orderDetail.OrderType = exchange.MarketOrderType + orderDetail.OrderSide = order.Buy + orderDetail.OrderType = order.Market case SpotNewOrderRequestTypeSellMarket: - orderDetail.OrderSide = exchange.SellOrderSide - orderDetail.OrderType = exchange.MarketOrderType + orderDetail.OrderSide = order.Sell + orderDetail.OrderType = order.Market case SpotNewOrderRequestTypeBuyLimit: - orderDetail.OrderSide = exchange.BuyOrderSide - orderDetail.OrderType = exchange.LimitOrderType + orderDetail.OrderSide = order.Buy + orderDetail.OrderType = order.Limit case SpotNewOrderRequestTypeSellLimit: - orderDetail.OrderSide = exchange.SellOrderSide - orderDetail.OrderType = exchange.LimitOrderType + orderDetail.OrderSide = order.Sell + orderDetail.OrderType = order.Limit } } diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go index d0a3c5ca..d90ce726 100644 --- a/exchanges/interfaces.go +++ b/exchanges/interfaces.go @@ -6,6 +6,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -41,14 +42,14 @@ type IBotExchange interface { FormatWithdrawPermissions() string SupportsWithdrawPermissions(permissions uint32) bool GetFundingHistory() ([]FundHistory, error) - SubmitOrder(order *OrderSubmission) (SubmitOrderResponse, error) - ModifyOrder(action *ModifyOrder) (string, error) - CancelOrder(order *OrderCancellation) error - CancelAllOrders(orders *OrderCancellation) (CancelAllOrdersResponse, error) - GetOrderInfo(orderID string) (OrderDetail, error) + SubmitOrder(s *order.Submit) (order.SubmitResponse, error) + ModifyOrder(action *order.Modify) (string, error) + CancelOrder(order *order.Cancel) error + CancelAllOrders(orders *order.Cancel) (order.CancelAllResponse, error) + GetOrderInfo(orderID string) (order.Detail, error) GetDepositAddress(cryptocurrency currency.Code, accountID string) (string, error) - GetOrderHistory(getOrdersRequest *GetOrdersRequest) ([]OrderDetail, error) - GetActiveOrders(getOrdersRequest *GetOrdersRequest) ([]OrderDetail, error) + GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) + GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) WithdrawCryptocurrencyFunds(withdrawRequest *CryptoWithdrawRequest) (string, error) WithdrawFiatFunds(withdrawRequest *FiatWithdrawRequest) (string, error) WithdrawFiatFundsToInternationalBank(withdrawRequest *FiatWithdrawRequest) (string, error) diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index 5c2c91e9..42f29425 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -8,6 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) var i ItBit @@ -112,8 +113,8 @@ func TestGetFundingHistory(t *testing.T) { } func TestPlaceOrder(t *testing.T) { - _, err := i.PlaceOrder("1337", exchange.BuyOrderSide.ToLower().ToString(), - exchange.LimitOrderType.ToLower().ToString(), "USD", 1, 0.2, "banjo", + _, err := i.PlaceOrder("1337", order.Buy.Lower(), + order.Limit.Lower(), "USD", 1, 0.2, "banjo", "sauce") if err == nil { t.Error("PlaceOrder() Expected error") @@ -268,8 +269,8 @@ func TestGetActiveOrders(t *testing.T) { i.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := i.GetActiveOrders(&getOrdersRequest) @@ -284,8 +285,8 @@ func TestGetOrderHistory(t *testing.T) { i.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := i.GetOrderHistory(&getOrdersRequest) @@ -309,13 +310,13 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -338,7 +339,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -365,7 +366,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -381,8 +382,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -396,7 +397,7 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { - _, err := i.ModifyOrder(&exchange.ModifyOrder{}) + _, err := i.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index b7c3d88b..72c5f2be 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -202,7 +203,11 @@ func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo if err != nil { return orderBook, err } - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: amount, Price: price}) + orderBook.Bids = append(orderBook.Bids, + orderbook.Item{ + Amount: amount, + Price: price, + }) } for x := range orderbookNew.Asks { @@ -216,7 +221,11 @@ func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo if err != nil { return orderBook, err } - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: amount, Price: price}) + orderBook.Asks = append(orderBook.Asks, + orderbook.Item{ + Amount: amount, + Price: price, + }) } orderBook.Pair = p @@ -248,8 +257,8 @@ func (i *ItBit) GetAccountInfo() (exchange.AccountInfo, error) { var amounts = make(map[string]*balance) - for _, wallet := range wallets { - for _, cb := range wallet.Balances { + for x := range wallets { + for _, cb := range wallets[x].Balances { if _, ok := amounts[cb.Currency]; !ok { amounts[cb.Currency] = &balance{} } @@ -260,12 +269,11 @@ func (i *ItBit) GetAccountInfo() (exchange.AccountInfo, error) { } var fullBalance []exchange.AccountCurrencyInfo - - for key, data := range amounts { + for key := range amounts { fullBalance = append(fullBalance, exchange.AccountCurrencyInfo{ CurrencyName: currency.NewCode(key), - TotalValue: data.TotalValue, - Hold: data.Hold, + TotalValue: amounts[key].TotalValue, + Hold: amounts[key].Hold, }) } @@ -289,13 +297,9 @@ func (i *ItBit) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exc } // SubmitOrder submits a new order -func (i *ItBit) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (i *ItBit) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } @@ -306,11 +310,11 @@ func (i *ItBit) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrd } // Determine what wallet ID to use if there is any actual available currency to make the trade! - for _, i := range wallets { - for j := range i.Balances { - if i.Balances[j].Currency == order.Pair.Base.String() && - i.Balances[j].AvailableBalance >= order.Amount { - wallet = i.ID + for i := range wallets { + for j := range wallets[i].Balances { + if wallets[i].Balances[j].Currency == s.Pair.Base.String() && + wallets[i].Balances[j].AvailableBalance >= s.Amount { + wallet = wallets[i].ID } } } @@ -318,17 +322,17 @@ func (i *ItBit) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrd if wallet == "" { return submitOrderResponse, fmt.Errorf("no wallet found with currency: %s with amount >= %v", - order.Pair.Base, - order.Amount) + s.Pair.Base, + s.Amount) } response, err := i.PlaceOrder(wallet, - order.OrderSide.ToString(), - order.OrderType.ToString(), - order.Pair.Base.String(), - order.Amount, - order.Price, - order.Pair.String(), + s.OrderSide.String(), + s.OrderType.String(), + s.Pair.Base.String(), + s.Amount, + s.Price, + s.Pair.String(), "") if response.ID != "" { submitOrderResponse.OrderID = response.ID @@ -342,19 +346,19 @@ func (i *ItBit) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrd // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (i *ItBit) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (i *ItBit) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (i *ItBit) CancelOrder(order *exchange.OrderCancellation) error { +func (i *ItBit) CancelOrder(order *order.Cancel) error { return i.CancelExistingOrder(order.WalletAddress, order.OrderID) } // CancelAllOrders cancels all orders associated with a currency pair -func (i *ItBit) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (i *ItBit) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } openOrders, err := i.GetOrders(orderCancellation.WalletAddress, "", "open", 0, 0) if err != nil { @@ -364,7 +368,7 @@ func (i *ItBit) CancelAllOrders(orderCancellation *exchange.OrderCancellation) ( for j := range openOrders { err = i.CancelExistingOrder(orderCancellation.WalletAddress, openOrders[j].ID) if err != nil { - cancelAllOrdersResponse.OrderStatus[openOrders[j].ID] = err.Error() + cancelAllOrdersResponse.Status[openOrders[j].ID] = err.Error() } } @@ -372,8 +376,8 @@ func (i *ItBit) CancelAllOrders(orderCancellation *exchange.OrderCancellation) ( } // GetOrderInfo returns information on a current open order -func (i *ItBit) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (i *ItBit) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -418,33 +422,37 @@ func (i *ItBit) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { } // GetActiveOrders retrieves any orders that are active/open -func (i *ItBit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (i *ItBit) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { wallets, err := i.GetWallets(url.Values{}) if err != nil { return nil, err } var allOrders []Order - for _, wallet := range wallets { - resp, err := i.GetOrders(wallet.ID, "", "open", 0, 0) + for x := range wallets { + resp, err := i.GetOrders(wallets[x].ID, "", "open", 0, 0) if err != nil { return nil, err } allOrders = append(allOrders, resp...) } - var orders []exchange.OrderDetail + var orders []order.Detail for j := range allOrders { symbol := currency.NewPairDelimiter(allOrders[j].Instrument, i.GetPairFormat(asset.Spot, false).Delimiter) - side := exchange.OrderSide(strings.ToUpper(allOrders[j].Side)) + side := order.Side(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { - log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", - i.Name, "GetActiveOrders", allOrders[j].ID, allOrders[j].CreatedTime) + log.Warnf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + i.Name, + "GetActiveOrders", + allOrders[j].ID, + allOrders[j].CreatedTime) } - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ ID: allOrders[j].ID, OrderSide: side, Amount: allOrders[j].Amount, @@ -456,31 +464,30 @@ func (i *ItBit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (i *ItBit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (i *ItBit) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { wallets, err := i.GetWallets(url.Values{}) if err != nil { return nil, err } var allOrders []Order - for _, wallet := range wallets { - resp, err := i.GetOrders(wallet.ID, "", "", 0, 0) + for x := range wallets { + resp, err := i.GetOrders(wallets[x].ID, "", "", 0, 0) if err != nil { return nil, err } allOrders = append(allOrders, resp...) } - var orders []exchange.OrderDetail + var orders []order.Detail for j := range allOrders { if allOrders[j].Type == "open" { continue @@ -488,14 +495,18 @@ func (i *ItBit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] symbol := currency.NewPairDelimiter(allOrders[j].Instrument, i.GetPairFormat(asset.Spot, false).Delimiter) - side := exchange.OrderSide(strings.ToUpper(allOrders[j].Side)) + side := order.Side(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { - log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", - i.Name, "GetActiveOrders", allOrders[j].ID, allOrders[j].CreatedTime) + log.Warnf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + i.Name, + "GetActiveOrders", + allOrders[j].ID, + allOrders[j].CreatedTime) } - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ ID: allOrders[j].ID, OrderSide: side, Amount: allOrders[j].Amount, @@ -507,10 +518,9 @@ func (i *ItBit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index b3dd7633..7acc1be1 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -154,7 +155,7 @@ func (k *Kraken) GetTicker(symbol string) (Ticker, error) { // GetTickers supports fetching multiple tickers from Kraken // pairList must be in the format pairs separated by commas // ("LTCUSD,ETCUSD") -func (k *Kraken) GetTickers(pairList string) (Tickers, error) { +func (k *Kraken) GetTickers(pairList string) (map[string]Ticker, error) { values := url.Values{} values.Set("pair", pairList) @@ -175,7 +176,7 @@ func (k *Kraken) GetTickers(pairList string) (Tickers, error) { return nil, fmt.Errorf("%s error: %s", k.Name, resp.Error) } - tickers := make(Tickers) + tickers := make(map[string]Ticker) for i := range resp.Data { tick := Ticker{} @@ -763,7 +764,7 @@ func (k *Kraken) AddOrder(symbol, side, orderType string, volume, price, price2, "volume": {strconv.FormatFloat(volume, 'f', -1, 64)}, } - if orderType == exchange.LimitOrderType.ToLower().ToString() || price > 0 { + if orderType == order.Limit.Lower() || price > 0 { params.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) } diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 37b71e5b..41b22ed7 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -244,7 +245,7 @@ func TestAddOrder(t *testing.T) { t.Parallel() args := AddOrderOptions{OrderFlags: "fcib"} _, err := k.AddOrder("XXBTZUSD", - exchange.SellOrderSide.ToLower().ToString(), exchange.LimitOrderType.ToLower().ToString(), + order.Sell.Lower(), order.Limit.Lower(), 0.00000001, 0, 0, 0, &args) if err == nil { t.Error("AddOrder() Expected error") @@ -387,8 +388,8 @@ func TestGetActiveOrders(t *testing.T) { k.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := k.GetActiveOrders(&getOrdersRequest) @@ -404,8 +405,8 @@ func TestGetOrderHistory(t *testing.T) { k.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := k.GetOrderHistory(&getOrdersRequest) @@ -431,13 +432,13 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.XBT, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -461,7 +462,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -488,7 +489,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -504,8 +505,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -526,7 +527,7 @@ func TestGetAccountInfo(t *testing.T) { // TestModifyOrder wrapper test func TestModifyOrder(t *testing.T) { - _, err := k.ModifyOrder(&exchange.ModifyOrder{}) + _, err := k.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index a2c40be5..f28691d2 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -3,6 +3,7 @@ package kraken import ( "errors" "fmt" + "strconv" "strings" "sync" "time" @@ -12,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -203,7 +205,10 @@ func (k *Kraken) Run() { err := k.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", k.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + k.Name, + err) } } @@ -213,7 +218,10 @@ func (k *Kraken) Run() { err := k.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", k.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + k.Name, + err) } } @@ -238,7 +246,10 @@ func (k *Kraken) FetchTradablePairs(asset asset.Item) ([]string, error) { if v.Quote[0] == 'Z' || v.Quote[0] == 'X' { v.Quote = v.Quote[1:] } - products = append(products, fmt.Sprintf("%v%v%v", v.Base, k.GetPairFormat(asset, false).Delimiter, v.Quote)) + products = append(products, fmt.Sprintf("%v%v%v", + v.Base, + k.GetPairFormat(asset, false).Delimiter, + v.Quote)) } return products, nil } @@ -268,12 +279,11 @@ func (k *Kraken) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri } for i := range pairs { - for curr, v := range tickers { + for c, t := range tickers { pairFmt := k.FormatExchangeCurrency(pairs[i], assetType).String() - if !strings.EqualFold(pairFmt, curr) { - var altCurrency string - var ok bool - if altCurrency, ok = assetPairMap[curr]; !ok { + if !strings.EqualFold(pairFmt, c) { + altCurrency, ok := assetPairMap[c] + if !ok { continue } if !strings.EqualFold(pairFmt, altCurrency) { @@ -282,13 +292,13 @@ func (k *Kraken) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri } tickerPrice = ticker.Price{ - Last: v.Last, - High: v.High, - Low: v.Low, - Bid: v.Bid, - Ask: v.Ask, - Volume: v.Volume, - Open: v.Open, + Last: t.Last, + High: t.High, + Low: t.Low, + Bid: t.Bid, + Ask: t.Ask, + Volume: t.Volume, + Open: t.Open, Pair: pairs[i], } err = ticker.ProcessTicker(k.Name, &tickerPrice, assetType) @@ -359,10 +369,10 @@ func (k *Kraken) GetAccountInfo() (exchange.AccountInfo, error) { } var balances []exchange.AccountCurrencyInfo - for key, data := range bal { + for key := range bal { balances = append(balances, exchange.AccountCurrencyInfo{ CurrencyName: currency.NewCode(key), - TotalValue: data, + TotalValue: bal[key], }) } @@ -386,25 +396,20 @@ func (k *Kraken) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex } // SubmitOrder submits a new order -func (k *Kraken) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (k *Kraken) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - var args = AddOrderOptions{} - response, err := k.AddOrder(order.Pair.String(), - order.OrderSide.ToString(), - order.OrderType.ToString(), - order.Amount, - order.Price, + response, err := k.AddOrder(s.Pair.String(), + s.OrderSide.String(), + s.OrderType.String(), + s.Amount, + s.Price, 0, 0, - &args) + &AddOrderOptions{}) if len(response.TransactionIds) > 0 { submitOrderResponse.OrderID = strings.Join(response.TransactionIds, ", ") } @@ -416,21 +421,21 @@ func (k *Kraken) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOr // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (k *Kraken) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (k *Kraken) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (k *Kraken) CancelOrder(order *exchange.OrderCancellation) error { +func (k *Kraken) CancelOrder(order *order.Cancel) error { _, err := k.CancelExistingOrder(order.OrderID) return err } // CancelAllOrders cancels all orders associated with a currency pair -func (k *Kraken) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (k *Kraken) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } var emptyOrderOptions OrderInfoOptions openOrders, err := k.GetOpenOrders(emptyOrderOptions) @@ -441,7 +446,7 @@ func (k *Kraken) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel for orderID := range openOrders.Open { _, err = k.CancelExistingOrder(orderID) if err != nil { - cancelAllOrdersResponse.OrderStatus[orderID] = err.Error() + cancelAllOrdersResponse.Status[orderID] = err.Error() } } @@ -449,8 +454,8 @@ func (k *Kraken) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel } // GetOrderInfo returns information on a current open order -func (k *Kraken) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (k *Kraken) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -506,20 +511,20 @@ func (k *Kraken) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) } // GetActiveOrders retrieves any orders that are active/open -func (k *Kraken) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (k *Kraken) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := k.GetOpenOrders(OrderInfoOptions{}) if err != nil { return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp.Open { symbol := currency.NewPairFromString(resp.Open[i].Description.Pair) orderDate := time.Unix(int64(resp.Open[i].StartTime), 0) - side := exchange.OrderSide(strings.ToUpper(resp.Open[i].Description.Type)) - orderType := exchange.OrderType(strings.ToUpper(resp.Open[i].Description.OrderType)) + side := order.Side(strings.ToUpper(resp.Open[i].Description.Type)) + orderType := order.Type(strings.ToUpper(resp.Open[i].Description.OrderType)) - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ ID: i, Amount: resp.Open[i].Volume, RemainingAmount: (resp.Open[i].Volume - resp.Open[i].VolumeExecuted), @@ -533,22 +538,21 @@ func (k *Kraken) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([ }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (k *Kraken) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (k *Kraken) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { req := GetClosedOrdersOptions{} if getOrdersRequest.StartTicks.Unix() > 0 { - req.Start = fmt.Sprintf("%v", getOrdersRequest.StartTicks.Unix()) + req.Start = strconv.FormatInt(getOrdersRequest.StartTicks.Unix(), 10) } if getOrdersRequest.EndTicks.Unix() > 0 { - req.End = fmt.Sprintf("%v", getOrdersRequest.EndTicks.Unix()) + req.End = strconv.FormatInt(getOrdersRequest.EndTicks.Unix(), 10) } resp, err := k.GetClosedOrders(req) @@ -556,14 +560,14 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp.Closed { symbol := currency.NewPairFromString(resp.Closed[i].Description.Pair) orderDate := time.Unix(int64(resp.Closed[i].StartTime), 0) - side := exchange.OrderSide(strings.ToUpper(resp.Closed[i].Description.Type)) - orderType := exchange.OrderType(strings.ToUpper(resp.Closed[i].Description.OrderType)) + side := order.Side(strings.ToUpper(resp.Closed[i].Description.Type)) + orderType := order.Type(strings.ToUpper(resp.Closed[i].Description.OrderType)) - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ ID: i, Amount: resp.Closed[i].Volume, RemainingAmount: (resp.Closed[i].Volume - resp.Closed[i].VolumeExecuted), @@ -577,8 +581,8 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([ }) } - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) return orders, nil } diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index bf1fa5df..48b8e33c 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -272,8 +273,8 @@ func TestGetActiveOrders(t *testing.T) { l.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := l.GetActiveOrders(&getOrdersRequest) @@ -288,8 +289,8 @@ func TestGetOrderHistory(t *testing.T) { l.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := l.GetOrderHistory(&getOrdersRequest) @@ -314,13 +315,13 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.EUR, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -343,7 +344,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -369,7 +370,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -385,13 +386,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { - _, err := l.ModifyOrder(&exchange.ModifyOrder{}) + _, err := l.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 91503dc2..17649b40 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -324,21 +325,17 @@ func (l *LakeBTC) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]e } // SubmitOrder submits a new order -func (l *LakeBTC) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (l *LakeBTC) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - isBuyOrder := order.OrderSide == exchange.BuyOrderSide - response, err := l.Trade(isBuyOrder, order.Amount, order.Price, - order.Pair.Lower().String()) + isBuyOrder := s.OrderSide == order.Buy + response, err := l.Trade(isBuyOrder, s.Amount, s.Price, + s.Pair.Lower().String()) if response.ID > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response.ID) + submitOrderResponse.OrderID = strconv.FormatInt(response.ID, 10) } if err == nil { submitOrderResponse.IsOrderPlaced = true @@ -348,12 +345,12 @@ func (l *LakeBTC) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitO // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (l *LakeBTC) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (l *LakeBTC) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (l *LakeBTC) CancelOrder(order *exchange.OrderCancellation) error { +func (l *LakeBTC) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) if err != nil { @@ -364,8 +361,8 @@ func (l *LakeBTC) CancelOrder(order *exchange.OrderCancellation) error { } // CancelAllOrders cancels all orders associated with a currency pair -func (l *LakeBTC) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - var cancelAllOrdersResponse exchange.CancelAllOrdersResponse +func (l *LakeBTC) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + var cancelAllOrdersResponse order.CancelAllResponse openOrders, err := l.GetOpenOrders() if err != nil { return cancelAllOrdersResponse, err @@ -381,8 +378,8 @@ func (l *LakeBTC) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cance } // GetOrderInfo returns information on a current open order -func (l *LakeBTC) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (l *LakeBTC) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -413,7 +410,7 @@ func (l *LakeBTC) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWi return "", err } - return fmt.Sprintf("%v", resp.ID), nil + return strconv.FormatInt(resp.ID, 10), nil } // WithdrawFiatFunds returns a withdrawal ID when a @@ -443,23 +440,23 @@ func (l *LakeBTC) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) } // GetActiveOrders retrieves any orders that are active/open -func (l *LakeBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (l *LakeBTC) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := l.GetOpenOrders() if err != nil { return nil, err } - var orders []exchange.OrderDetail - for _, order := range resp { - symbol := currency.NewPairDelimiter(order.Symbol, + var orders []order.Detail + for i := range resp { + symbol := currency.NewPairDelimiter(resp[i].Symbol, l.GetPairFormat(asset.Spot, false).Delimiter) - orderDate := time.Unix(order.At, 0) - side := exchange.OrderSide(strings.ToUpper(order.Type)) + orderDate := time.Unix(resp[i].At, 0) + side := order.Side(strings.ToUpper(resp[i].Type)) - orders = append(orders, exchange.OrderDetail{ - Amount: order.Amount, - ID: fmt.Sprintf("%v", order.ID), - Price: order.Price, + orders = append(orders, order.Detail{ + Amount: resp[i].Amount, + ID: strconv.FormatInt(resp[i].ID, 10), + Price: resp[i].Price, OrderSide: side, OrderDate: orderDate, CurrencyPair: symbol, @@ -467,36 +464,36 @@ func (l *LakeBTC) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (l *LakeBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (l *LakeBTC) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := l.GetOrders([]int64{}) if err != nil { return nil, err } - var orders []exchange.OrderDetail - for _, order := range resp { - if order.State == "active" { + var orders []order.Detail + for i := range resp { + if resp[i].State == "active" { continue } - symbol := currency.NewPairDelimiter(order.Symbol, + symbol := currency.NewPairDelimiter(resp[i].Symbol, l.GetPairFormat(asset.Spot, false).Delimiter) - orderDate := time.Unix(order.At, 0) - side := exchange.OrderSide(strings.ToUpper(order.Type)) + orderDate := time.Unix(resp[i].At, 0) + side := order.Side(strings.ToUpper(resp[i].Type)) - orders = append(orders, exchange.OrderDetail{ - Amount: order.Amount, - ID: fmt.Sprintf("%v", order.ID), - Price: order.Price, + orders = append(orders, order.Detail{ + Amount: resp[i].Amount, + ID: strconv.FormatInt(resp[i].ID, 10), + Price: resp[i].Price, OrderSide: side, OrderDate: orderDate, CurrencyPair: symbol, @@ -504,10 +501,9 @@ func (l *LakeBTC) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ( }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) + order.FilterOrdersByCurrencies(&orders, req.Currencies) return orders, nil } diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go index 3c909081..f8580356 100644 --- a/exchanges/lbank/lbank_test.go +++ b/exchanges/lbank/lbank_test.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Please supply your own keys here for due diligence testing @@ -312,14 +313,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.USDT, Delimiter: "_", }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -338,7 +339,7 @@ func TestCancelOrder(t *testing.T) { t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") } cp := currency.NewPairWithDelimiter(currency.ETH.String(), currency.BTC.String(), "_") - var a exchange.OrderCancellation + var a order.Cancel a.CurrencyPair = cp a.OrderID = "24f7ce27-af1d-4dca-a8c1-ef1cbeec1b23" err := l.CancelOrder(&a) @@ -401,8 +402,8 @@ func TestGetOrderHistory(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("API keys required but not set, skipping test") } - var input exchange.GetOrdersRequest - input.OrderSide = exchange.BuyOrderSide + var input order.GetOrdersRequest + input.OrderSide = order.Buy _, err := l.GetOrderHistory(&input) if err != nil { t.Error(err) diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index 5f4e90e8..78b80097 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -293,27 +294,22 @@ func (l *Lbank) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exc } // SubmitOrder submits a new order -func (l *Lbank) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var resp exchange.SubmitOrderResponse - if order == nil { - return resp, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (l *Lbank) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var resp order.SubmitResponse + if err := s.Validate(); err != nil { return resp, err } - if order.OrderSide != exchange.BuyOrderSide && - order.OrderSide != exchange.SellOrderSide { + if s.OrderSide != order.Buy && s.OrderSide != order.Sell { return resp, fmt.Errorf("%s order side is not supported by the exchange", - order.OrderSide) + s.OrderSide) } tempResp, err := l.CreateOrder( - l.FormatExchangeCurrency(order.Pair, asset.Spot).String(), - order.OrderSide.ToString(), - order.Amount, - order.Price) + l.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + s.OrderSide.String(), + s.Amount, + s.Price) if err != nil { return resp, err } @@ -324,20 +320,20 @@ func (l *Lbank) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrd // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (l *Lbank) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (l *Lbank) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (l *Lbank) CancelOrder(order *exchange.OrderCancellation) error { +func (l *Lbank) CancelOrder(order *order.Cancel) error { _, err := l.RemoveOrder(l.FormatExchangeCurrency(order.CurrencyPair, order.AssetType).String(), order.OrderID) return err } // CancelAllOrders cancels all orders associated with a currency pair -func (l *Lbank) CancelAllOrders(orders *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - var resp exchange.CancelAllOrdersResponse +func (l *Lbank) CancelAllOrders(orders *order.Cancel) (order.CancelAllResponse, error) { + var resp order.CancelAllResponse orderIDs, err := l.getAllOpenOrderID() if err != nil { return resp, nil @@ -362,11 +358,11 @@ func (l *Lbank) CancelAllOrders(orders *exchange.OrderCancellation) (exchange.Ca } tempStringSuccess := strings.Split(CancelResponse.Success, ",") for k := range tempStringSuccess { - resp.OrderStatus[tempStringSuccess[k]] = "Cancelled" + resp.Status[tempStringSuccess[k]] = "Cancelled" } tempStringError := strings.Split(CancelResponse.Err, ",") for l := range tempStringError { - resp.OrderStatus[tempStringError[l]] = "Failed" + resp.Status[tempStringError[l]] = "Failed" } tempSlice = tempSlice[:0] y++ @@ -380,11 +376,11 @@ func (l *Lbank) CancelAllOrders(orders *exchange.OrderCancellation) (exchange.Ca } tempStringSuccess := strings.Split(CancelResponse.Success, ",") for k := range tempStringSuccess { - resp.OrderStatus[tempStringSuccess[k]] = "Cancelled" + resp.Status[tempStringSuccess[k]] = "Cancelled" } tempStringError := strings.Split(CancelResponse.Err, ",") for l := range tempStringError { - resp.OrderStatus[tempStringError[l]] = "Failed" + resp.Status[tempStringError[l]] = "Failed" } tempSlice = tempSlice[:0] } @@ -393,8 +389,8 @@ func (l *Lbank) CancelAllOrders(orders *exchange.OrderCancellation) (exchange.Ca } // GetOrderInfo returns information on a current open order -func (l *Lbank) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var resp exchange.OrderDetail +func (l *Lbank) GetOrderInfo(orderID string) (order.Detail, error) { + var resp order.Detail orderIDs, err := l.getAllOpenOrderID() if err != nil { return resp, err @@ -412,9 +408,9 @@ func (l *Lbank) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { resp.Exchange = l.GetName() resp.CurrencyPair = currency.NewPairFromString(key) if strings.EqualFold(tempResp.Orders[0].Type, "buy") { - resp.OrderSide = exchange.BuyOrderSide + resp.OrderSide = order.Buy } else { - resp.OrderSide = exchange.SellOrderSide + resp.OrderSide = order.Sell } z := tempResp.Orders[0].Status switch { @@ -477,9 +473,9 @@ func (l *Lbank) GetWebsocket() (*wshandler.Websocket, error) { } // GetActiveOrders retrieves any orders that are active/open -func (l *Lbank) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var finalResp []exchange.OrderDetail - var resp exchange.OrderDetail +func (l *Lbank) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { + var finalResp []order.Detail + var resp order.Detail tempData, err := l.getAllOpenOrderID() if err != nil { return finalResp, err @@ -494,9 +490,9 @@ func (l *Lbank) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] resp.Exchange = l.GetName() resp.CurrencyPair = currency.NewPairFromString(key) if strings.EqualFold(tempResp.Orders[0].Type, "buy") { - resp.OrderSide = exchange.BuyOrderSide + resp.OrderSide = order.Buy } else { - resp.OrderSide = exchange.SellOrderSide + resp.OrderSide = order.Sell } z := tempResp.Orders[0].Status switch { @@ -533,7 +529,8 @@ func (l *Lbank) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] finalResp = append(finalResp, resp) continue } - if strings.EqualFold(getOrdersRequest.OrderSide.ToString(), tempResp.Orders[0].Type) { + if strings.EqualFold(getOrdersRequest.OrderSide.String(), + tempResp.Orders[0].Type) { finalResp = append(finalResp, resp) } } @@ -544,9 +541,9 @@ func (l *Lbank) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] // GetOrderHistory retrieves account order information * // Can Limit response to specific order status -func (l *Lbank) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var finalResp []exchange.OrderDetail - var resp exchange.OrderDetail +func (l *Lbank) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { + var finalResp []order.Detail + var resp order.Detail var tempCurr currency.Pairs if len(getOrdersRequest.Currencies) == 0 { tempCurr = l.GetEnabledPairs(asset.Spot) @@ -569,9 +566,9 @@ func (l *Lbank) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] resp.Exchange = l.GetName() resp.CurrencyPair = currency.NewPairFromString(tempResp.Orders[x].Symbol) if strings.EqualFold(tempResp.Orders[x].Type, "buy") { - resp.OrderSide = exchange.BuyOrderSide + resp.OrderSide = order.Buy } else { - resp.OrderSide = exchange.SellOrderSide + resp.OrderSide = order.Sell } z := tempResp.Orders[x].Status switch { diff --git a/exchanges/localbitcoins/localbitcoins_test.go b/exchanges/localbitcoins/localbitcoins_test.go index 647de2c7..8962afe1 100644 --- a/exchanges/localbitcoins/localbitcoins_test.go +++ b/exchanges/localbitcoins/localbitcoins_test.go @@ -6,6 +6,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Please supply your own APIKEYS here for due diligence testing @@ -200,8 +201,8 @@ func TestFormatWithdrawPermissions(t *testing.T) { func TestGetActiveOrders(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := l.GetActiveOrders(&getOrdersRequest) @@ -218,8 +219,8 @@ func TestGetActiveOrders(t *testing.T) { func TestGetOrderHistory(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := l.GetOrderHistory(&getOrdersRequest) @@ -246,13 +247,13 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.EUR, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -275,7 +276,7 @@ func TestCancelExchangeOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -300,7 +301,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -317,15 +318,15 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { t.Parallel() - _, err := l.ModifyOrder(&exchange.ModifyOrder{}) + _, err := l.ModifyOrder(&order.Modify{}) if err != common.ErrFunctionNotSupported { t.Error("ModifyOrder() error", err) } diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 020e3be0..1c5d74ec 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -271,13 +272,9 @@ func (l *LocalBitcoins) GetExchangeHistory(p currency.Pair, assetType asset.Item } // SubmitOrder submits a new order -func (l *LocalBitcoins) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (l *LocalBitcoins) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } @@ -290,17 +287,17 @@ func (l *LocalBitcoins) SubmitOrder(order *exchange.OrderSubmission) (exchange.S City: "City", Location: "Location", CountryCode: "US", - Currency: order.Pair.Quote.String(), + Currency: s.Pair.Quote.String(), AccountInfo: "-", BankName: "Bank", - MSG: order.OrderSide.ToString(), + MSG: s.OrderSide.String(), SMSVerficationRequired: true, TrackMaxAmount: true, RequireTrustedByAdvertiser: true, RequireIdentification: true, OnlineProvider: "", TradeType: "", - MinAmount: int(math.Round(order.Amount)), + MinAmount: int(math.Round(s.Amount)), } // Does not return any orderID, so create the add, then get the order @@ -330,8 +327,8 @@ func (l *LocalBitcoins) SubmitOrder(order *exchange.OrderSubmission) (exchange.S ads.AdList[i].Data.RequireTrustedByAdvertiser == params.RequireTrustedByAdvertiser && ads.AdList[i].Data.OnlineProvider == params.OnlineProvider && ads.AdList[i].Data.TradeType == params.TradeType && - ads.AdList[i].Data.MinAmount == fmt.Sprintf("%v", params.MinAmount) { - adID = fmt.Sprintf("%v", ads.AdList[i].Data.AdID) + ads.AdList[i].Data.MinAmount == strconv.FormatInt(int64(params.MinAmount), 10) { + adID = strconv.FormatInt(ads.AdList[i].Data.AdID, 10) } } @@ -346,19 +343,19 @@ func (l *LocalBitcoins) SubmitOrder(order *exchange.OrderSubmission) (exchange.S // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (l *LocalBitcoins) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (l *LocalBitcoins) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (l *LocalBitcoins) CancelOrder(order *exchange.OrderCancellation) error { +func (l *LocalBitcoins) CancelOrder(order *order.Cancel) error { return l.DeleteAd(order.OrderID) } // CancelAllOrders cancels all orders associated with a currency pair -func (l *LocalBitcoins) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (l *LocalBitcoins) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } ads, err := l.Getads() if err != nil { @@ -369,7 +366,7 @@ func (l *LocalBitcoins) CancelAllOrders(_ *exchange.OrderCancellation) (exchange adIDString := strconv.FormatInt(ads.AdList[i].Data.AdID, 10) err = l.DeleteAd(adIDString) if err != nil { - cancelAllOrdersResponse.OrderStatus[adIDString] = err.Error() + cancelAllOrdersResponse.Status[adIDString] = err.Error() } } @@ -377,8 +374,8 @@ func (l *LocalBitcoins) CancelAllOrders(_ *exchange.OrderCancellation) (exchange } // GetOrderInfo returns information on a current open order -func (l *LocalBitcoins) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (l *LocalBitcoins) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -428,13 +425,13 @@ func (l *LocalBitcoins) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, } // GetActiveOrders retrieves any orders that are active/open -func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := l.GetDashboardInfo() if err != nil { return nil, err } - var orders []exchange.OrderDetail + var orders []order.Detail for i := range resp { orderDate, err := time.Parse(time.RFC3339, resp[i].Data.CreatedAt) if err != nil { @@ -445,17 +442,17 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequ resp[i].Data.CreatedAt) } - var side exchange.OrderSide + var side order.Side if resp[i].Data.IsBuying { - side = exchange.BuyOrderSide + side = order.Buy } else if resp[i].Data.IsSelling { - side = exchange.SellOrderSide + side = order.Sell } - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ Amount: resp[i].Data.AmountBTC, Price: resp[i].Data.Amount, - ID: fmt.Sprintf("%v", resp[i].Data.Advertisement.ID), + ID: strconv.FormatInt(int64(resp[i].Data.Advertisement.ID), 10), OrderDate: orderDate, Fee: resp[i].Data.FeeBTC, OrderSide: side, @@ -466,16 +463,16 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequ }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, + order.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { var allTrades []DashBoardInfo resp, err := l.GetDashboardCancelledTrades() if err != nil { @@ -495,43 +492,47 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequ } allTrades = append(allTrades, resp...) - var orders []exchange.OrderDetail + var orders []order.Detail for i := range allTrades { orderDate, err := time.Parse(time.RFC3339, allTrades[i].Data.CreatedAt) if err != nil { - log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Warnf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", l.Name, "GetActiveOrders", allTrades[i].Data.Advertisement.ID, allTrades[i].Data.CreatedAt) } - var side exchange.OrderSide + var side order.Side if allTrades[i].Data.IsBuying { - side = exchange.BuyOrderSide + side = order.Buy } else if allTrades[i].Data.IsSelling { - side = exchange.SellOrderSide + side = order.Sell } status := "" switch { - case allTrades[i].Data.ReleasedAt != "" && allTrades[i].Data.ReleasedAt != null: + case allTrades[i].Data.ReleasedAt != "" && + allTrades[i].Data.ReleasedAt != null: status = "Released" - case allTrades[i].Data.CanceledAt != "" && allTrades[i].Data.CanceledAt != null: + case allTrades[i].Data.CanceledAt != "" && + allTrades[i].Data.CanceledAt != null: status = "Cancelled" - case allTrades[i].Data.ClosedAt != "" && allTrades[i].Data.ClosedAt != null: + case allTrades[i].Data.ClosedAt != "" && + allTrades[i].Data.ClosedAt != null: status = "Closed" } - orders = append(orders, exchange.OrderDetail{ + orders = append(orders, order.Detail{ Amount: allTrades[i].Data.AmountBTC, Price: allTrades[i].Data.Amount, - ID: fmt.Sprintf("%v", allTrades[i].Data.Advertisement.ID), + ID: strconv.FormatInt(int64(allTrades[i].Data.Advertisement.ID), 10), OrderDate: orderDate, Fee: allTrades[i].Data.FeeBTC, OrderSide: side, - Status: status, + Status: order.Status(status), CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), allTrades[i].Data.Currency, l.GetPairFormat(asset.Spot, false).Delimiter), @@ -539,9 +540,9 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequ }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, + order.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) return orders, nil } diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 25f7bf1e..bdd9b24d 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -14,6 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/okgroup" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -286,8 +287,8 @@ func TestPlaceSpotOrderLimit(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Price: "100", Size: "100", @@ -302,8 +303,8 @@ func TestPlaceSpotOrderMarket(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.MarketOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Market.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "-100", Notional: "100", @@ -318,8 +319,8 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { TestSetRealOrderDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -340,8 +341,8 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { TestSetDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -366,8 +367,8 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { TestSetDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -613,8 +614,8 @@ func TestPlaceMarginOrderLimit(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "2", Price: "100", Size: "100", @@ -629,8 +630,8 @@ func TestPlaceMarginOrderMarket(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.MarketOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Market.Lower(), + Side: order.Buy.Lower(), MarginTrading: "2", Size: "-100", Notional: "100", @@ -645,8 +646,8 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { TestSetRealOrderDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -667,8 +668,8 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { TestSetDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -693,8 +694,8 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { TestSetDefaults(t) order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -1053,13 +1054,13 @@ func TestFormatWithdrawPermissions(t *testing.T) { // TestSubmitOrder Wrapper test func TestSubmitOrder(t *testing.T) { TestSetRealOrderDefaults(t) - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -1076,7 +1077,7 @@ func TestSubmitOrder(t *testing.T) { func TestCancelExchangeOrder(t *testing.T) { TestSetRealOrderDefaults(t) currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = exchange.OrderCancellation{ + var orderCancellation = order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -1092,7 +1093,7 @@ func TestCancelExchangeOrder(t *testing.T) { func TestCancelAllExchangeOrders(t *testing.T) { TestSetRealOrderDefaults(t) currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = exchange.OrderCancellation{ + var orderCancellation = order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -1101,8 +1102,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { resp, err := o.CancelAllOrders(&orderCancellation) testStandardErrorHandling(t, err) - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -1115,7 +1116,7 @@ func TestGetAccountInfo(t *testing.T) { // TestModifyOrder Wrapper test func TestModifyOrder(t *testing.T) { TestSetRealOrderDefaults(t) - _, err := o.ModifyOrder(&exchange.ModifyOrder{}) + _, err := o.ModifyOrder(&order.Modify{}) if err != common.ErrFunctionNotSupported { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) } diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index b565cec8..11752506 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -15,6 +15,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/okgroup" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -307,8 +308,8 @@ func TestPlaceSpotOrderLimit(t *testing.T) { t.Parallel() request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Price: "100", Size: "100", @@ -324,8 +325,8 @@ func TestPlaceSpotOrderMarket(t *testing.T) { t.Parallel() request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.MarketOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Market.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "-100", Notional: "100", @@ -341,8 +342,8 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -364,8 +365,8 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -391,8 +392,8 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -661,8 +662,8 @@ func TestPlaceMarginOrderLimit(t *testing.T) { t.Parallel() request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "2", Price: "100", Size: "100", @@ -678,8 +679,8 @@ func TestPlaceMarginOrderMarket(t *testing.T) { t.Parallel() request := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.MarketOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Market.Lower(), + Side: order.Buy.Lower(), MarginTrading: "2", Size: "-100", Notional: "100", @@ -695,8 +696,8 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -718,8 +719,8 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -745,8 +746,8 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { t.Parallel() order := okgroup.PlaceSpotOrderRequest{ InstrumentID: spotCurrency, - Type: exchange.LimitOrderType.ToLower().ToString(), - Side: exchange.BuyOrderSide.ToLower().ToString(), + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), MarginTrading: "1", Size: "100", Notional: "100", @@ -1819,13 +1820,13 @@ func TestFormatWithdrawPermissions(t *testing.T) { func TestSubmitOrder(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Base: currency.BTC, Quote: currency.USDT, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -1843,7 +1844,7 @@ func TestCancelExchangeOrder(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = exchange.OrderCancellation{ + var orderCancellation = order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -1860,7 +1861,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = exchange.OrderCancellation{ + var orderCancellation = order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -1870,8 +1871,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { resp, err := o.CancelAllOrders(&orderCancellation) testStandardErrorHandling(t, err) - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%d orders failed to cancel", len(resp.Status)) } } @@ -1885,9 +1886,11 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - _, err := o.ModifyOrder(&exchange.ModifyOrder{}) + _, err := o.ModifyOrder(&order.Modify{}) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } @@ -1916,7 +1919,9 @@ func TestWithdrawFiat(t *testing.T) { var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := o.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } @@ -1927,6 +1932,8 @@ func TestWithdrawInternationalBank(t *testing.T) { var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := o.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index ee74e1a0..52673cc7 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" @@ -89,11 +90,15 @@ func (o *OKGroup) UpdateOrderbook(p currency.Pair, assetType asset.Item) (resp o for x := range orderbookNew.Bids { amount, convErr := strconv.ParseFloat(orderbookNew.Bids[x][1], 64) if convErr != nil { - log.Errorf(log.ExchangeSys, "Could not convert %v to float64", orderbookNew.Bids[x][1]) + log.Errorf(log.ExchangeSys, + "Could not convert %v to float64", + orderbookNew.Bids[x][1]) } price, convErr := strconv.ParseFloat(orderbookNew.Bids[x][0], 64) if convErr != nil { - log.Errorf(log.ExchangeSys, "Could not convert %v to float64", orderbookNew.Bids[x][0]) + log.Errorf(log.ExchangeSys, + "Could not convert %v to float64", + orderbookNew.Bids[x][0]) } resp.Bids = append(resp.Bids, orderbook.Item{ Amount: amount, @@ -104,11 +109,15 @@ func (o *OKGroup) UpdateOrderbook(p currency.Pair, assetType asset.Item) (resp o for x := range orderbookNew.Asks { amount, convErr := strconv.ParseFloat(orderbookNew.Asks[x][1], 64) if convErr != nil { - log.Errorf(log.ExchangeSys, "Could not convert %v to float64", orderbookNew.Asks[x][1]) + log.Errorf(log.ExchangeSys, + "Could not convert %v to float64", + orderbookNew.Asks[x][1]) } price, convErr := strconv.ParseFloat(orderbookNew.Asks[x][0], 64) if convErr != nil { - log.Errorf(log.ExchangeSys, "Could not convert %v to float64", orderbookNew.Asks[x][0]) + log.Errorf(log.ExchangeSys, + "Could not convert %v to float64", + orderbookNew.Asks[x][0]) } resp.Asks = append(resp.Asks, orderbook.Item{ Amount: amount, @@ -134,20 +143,25 @@ func (o *OKGroup) GetAccountInfo() (resp exchange.AccountInfo, err error) { currencies, err := o.GetSpotTradingAccounts() currencyAccount := exchange.Account{} - for _, curr := range currencies { - hold, err := strconv.ParseFloat(curr.Hold, 64) + for i := range currencies { + hold, err := strconv.ParseFloat(currencies[i].Hold, 64) if err != nil { - log.Errorf(log.ExchangeSys, "Could not convert %v to float64", curr.Hold) + log.Errorf(log.ExchangeSys, + "Could not convert %v to float64", + currencies[i].Hold) } - totalValue, err := strconv.ParseFloat(curr.Balance, 64) + totalValue, err := strconv.ParseFloat(currencies[i].Balance, 64) if err != nil { - log.Errorf(log.ExchangeSys, "Could not convert %v to float64", curr.Balance) + log.Errorf(log.ExchangeSys, + "Could not convert %v to float64", + currencies[i].Balance) } - currencyAccount.Currencies = append(currencyAccount.Currencies, exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(curr.Currency), - Hold: hold, - TotalValue: totalValue, - }) + currencyAccount.Currencies = append(currencyAccount.Currencies, + exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(currencies[i].Currency), + Hold: hold, + TotalValue: totalValue, + }) } resp.Accounts = append(resp.Accounts, currencyAccount) @@ -161,9 +175,9 @@ func (o *OKGroup) GetFundingHistory() (resp []exchange.FundHistory, err error) { if err != nil { return } - for _, deposit := range accountDepositHistory { + for x := range accountDepositHistory { orderStatus := "" - switch deposit.Status { + switch accountDepositHistory[x].Status { case 0: orderStatus = "waiting" case 1: @@ -173,12 +187,12 @@ func (o *OKGroup) GetFundingHistory() (resp []exchange.FundHistory, err error) { } resp = append(resp, exchange.FundHistory{ - Amount: deposit.Amount, - Currency: deposit.Currency, + Amount: accountDepositHistory[x].Amount, + Currency: accountDepositHistory[x].Currency, ExchangeName: o.Name, Status: orderStatus, - Timestamp: deposit.Timestamp, - TransferID: deposit.TransactionID, + Timestamp: accountDepositHistory[x].Timestamp, + TransferID: accountDepositHistory[x].TransactionID, TransferType: "deposit", }) } @@ -203,25 +217,21 @@ func (o *OKGroup) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]e } // SubmitOrder submits a new order -func (o *OKGroup) SubmitOrder(order *exchange.OrderSubmission) (resp exchange.SubmitOrderResponse, err error) { - if order == nil { - return resp, exchange.ErrOrderSubmissionIsNil - } - - err = order.Validate() +func (o *OKGroup) SubmitOrder(s *order.Submit) (resp order.SubmitResponse, err error) { + err = s.Validate() if err != nil { return resp, err } request := PlaceSpotOrderRequest{ - ClientOID: order.ClientID, - InstrumentID: o.FormatExchangeCurrency(order.Pair, asset.Spot).String(), - Side: strings.ToLower(order.OrderSide.ToString()), - Type: strings.ToLower(order.OrderType.ToString()), - Size: strconv.FormatFloat(order.Amount, 'f', -1, 64), + ClientOID: s.ClientID, + InstrumentID: o.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + Side: strings.ToLower(s.OrderSide.String()), + Type: strings.ToLower(s.OrderType.String()), + Size: strconv.FormatFloat(s.Amount, 'f', -1, 64), } - if order.OrderType == exchange.LimitOrderType { - request.Price = strconv.FormatFloat(order.Price, 'f', -1, 64) + if s.OrderType == order.Limit { + request.Price = strconv.FormatFloat(s.Price, 'f', -1, 64) } orderResponse, err := o.PlaceSpotOrder(&request) @@ -236,12 +246,12 @@ func (o *OKGroup) SubmitOrder(order *exchange.OrderSubmission) (resp exchange.Su // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (o *OKGroup) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (o *OKGroup) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (o *OKGroup) CancelOrder(orderCancellation *exchange.OrderCancellation) (err error) { +func (o *OKGroup) CancelOrder(orderCancellation *order.Cancel) (err error) { orderID, err := strconv.ParseInt(orderCancellation.OrderID, 10, 64) if err != nil { return @@ -252,21 +262,22 @@ func (o *OKGroup) CancelOrder(orderCancellation *exchange.OrderCancellation) (er OrderID: orderID, }) if !orderCancellationResponse.Result { - err = fmt.Errorf("order %v failed to be cancelled", orderCancellationResponse.OrderID) + err = fmt.Errorf("order %d failed to be cancelled", + orderCancellationResponse.OrderID) } return } // CancelAllOrders cancels all orders associated with a currency pair -func (o *OKGroup) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (resp exchange.CancelAllOrdersResponse, err error) { +func (o *OKGroup) CancelAllOrders(orderCancellation *order.Cancel) (resp order.CancelAllResponse, err error) { orderIDs := strings.Split(orderCancellation.OrderID, ",") - resp.OrderStatus = make(map[string]string) + resp.Status = make(map[string]string) var orderIDNumbers []int64 - for _, i := range orderIDs { - orderIDNumber, strConvErr := strconv.ParseInt(i, 10, 64) + for i := range orderIDs { + orderIDNumber, strConvErr := strconv.ParseInt(orderIDs[i], 10, 64) if strConvErr != nil { - resp.OrderStatus[i] = strConvErr.Error() + resp.Status[orderIDs[i]] = strConvErr.Error() continue } orderIDNumbers = append(orderIDNumbers, orderIDNumber) @@ -281,9 +292,9 @@ func (o *OKGroup) CancelAllOrders(orderCancellation *exchange.OrderCancellation) return } - for _, orderMap := range cancelOrdersResponse { - for _, cancelledOrder := range orderMap { - resp.OrderStatus[fmt.Sprintf("%v", cancelledOrder.OrderID)] = fmt.Sprintf("%v", cancelledOrder.Result) + for x := range cancelOrdersResponse { + for y := range cancelOrdersResponse[x] { + resp.Status[strconv.FormatInt(cancelOrdersResponse[x][y].OrderID, 10)] = strconv.FormatBool(cancelOrdersResponse[x][y].Result) } } @@ -291,29 +302,29 @@ func (o *OKGroup) CancelAllOrders(orderCancellation *exchange.OrderCancellation) } // GetOrderInfo returns information on a current open order -func (o *OKGroup) GetOrderInfo(orderID string) (resp exchange.OrderDetail, err error) { - order, err := o.GetSpotOrder(GetSpotOrderRequest{OrderID: orderID}) +func (o *OKGroup) GetOrderInfo(orderID string) (resp order.Detail, err error) { + mOrder, err := o.GetSpotOrder(GetSpotOrderRequest{OrderID: orderID}) if err != nil { return } - resp = exchange.OrderDetail{ - Amount: order.Size, - CurrencyPair: currency.NewPairDelimiter(order.InstrumentID, + resp = order.Detail{ + Amount: mOrder.Size, + CurrencyPair: currency.NewPairDelimiter(mOrder.InstrumentID, o.GetPairFormat(asset.Spot, false).Delimiter), Exchange: o.Name, - OrderDate: order.Timestamp, - ExecutedAmount: order.FilledSize, - Status: order.Status, - OrderSide: exchange.OrderSide(order.Side), + OrderDate: mOrder.Timestamp, + ExecutedAmount: mOrder.FilledSize, + Status: order.Status(mOrder.Status), + OrderSide: order.Side(mOrder.Side), } return } // GetDepositAddress returns a deposit address for a specified currency -func (o *OKGroup) GetDepositAddress(p currency.Code, accountID string) (_ string, err error) { +func (o *OKGroup) GetDepositAddress(p currency.Code, accountID string) (string, error) { wallet, err := o.GetAccountDepositAddressForCurrency(p.Lower().String()) if err != nil || len(wallet) == 0 { - return + return "", err } return wallet[0].Address, nil } @@ -333,10 +344,13 @@ func (o *OKGroup) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWi return "", err } if !withdrawal.Result { - return fmt.Sprintf("%v", withdrawal.WithdrawalID), fmt.Errorf("could not withdraw currency %v to %v, no error specified", withdrawRequest.Currency.String(), withdrawRequest.Address) + return strconv.FormatInt(withdrawal.WithdrawalID, 10), + fmt.Errorf("could not withdraw currency %s to %s, no error specified", + withdrawRequest.Currency, + withdrawRequest.Address) } - return fmt.Sprintf("%v", withdrawal.WithdrawalID), nil + return strconv.FormatInt(withdrawal.WithdrawalID, 10), nil } // WithdrawFiatFunds returns a withdrawal ID when a @@ -352,27 +366,27 @@ func (o *OKGroup) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange } // GetActiveOrders retrieves any orders that are active/open -func (o *OKGroup) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) (resp []exchange.OrderDetail, err error) { - for _, currency := range getOrdersRequest.Currencies { +func (o *OKGroup) GetActiveOrders(req *order.GetOrdersRequest) (resp []order.Detail, err error) { + for x := range req.Currencies { spotOpenOrders, err := o.GetSpotOpenOrders(GetSpotOpenOrdersRequest{ - InstrumentID: o.FormatExchangeCurrency(currency, + InstrumentID: o.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String(), }) if err != nil { return resp, err } for i := range spotOpenOrders { - resp = append(resp, exchange.OrderDetail{ + resp = append(resp, order.Detail{ ID: spotOpenOrders[i].OrderID, Price: spotOpenOrders[i].Price, Amount: spotOpenOrders[i].Size, - CurrencyPair: currency, + CurrencyPair: req.Currencies[x], Exchange: o.Name, - OrderSide: exchange.OrderSide(spotOpenOrders[i].Side), - OrderType: exchange.OrderType(spotOpenOrders[i].Type), + OrderSide: order.Side(spotOpenOrders[i].Side), + OrderType: order.Type(spotOpenOrders[i].Type), ExecutedAmount: spotOpenOrders[i].FilledSize, OrderDate: spotOpenOrders[i].Timestamp, - Status: spotOpenOrders[i].Status, + Status: order.Status(spotOpenOrders[i].Status), }) } } @@ -382,28 +396,28 @@ func (o *OKGroup) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ( // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (o *OKGroup) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) (resp []exchange.OrderDetail, err error) { - for _, currency := range getOrdersRequest.Currencies { +func (o *OKGroup) GetOrderHistory(req *order.GetOrdersRequest) (resp []order.Detail, err error) { + for x := range req.Currencies { spotOpenOrders, err := o.GetSpotOrders(GetSpotOrdersRequest{ Status: strings.Join([]string{"filled", "cancelled", "failure"}, "|"), - InstrumentID: o.FormatExchangeCurrency(currency, + InstrumentID: o.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String(), }) if err != nil { return resp, err } for i := range spotOpenOrders { - resp = append(resp, exchange.OrderDetail{ + resp = append(resp, order.Detail{ ID: spotOpenOrders[i].OrderID, Price: spotOpenOrders[i].Price, Amount: spotOpenOrders[i].Size, - CurrencyPair: currency, + CurrencyPair: req.Currencies[x], Exchange: o.Name, - OrderSide: exchange.OrderSide(spotOpenOrders[i].Side), - OrderType: exchange.OrderType(spotOpenOrders[i].Type), + OrderSide: order.Side(spotOpenOrders[i].Side), + OrderType: order.Type(spotOpenOrders[i].Type), ExecutedAmount: spotOpenOrders[i].FilledSize, OrderDate: spotOpenOrders[i].Timestamp, - Status: spotOpenOrders[i].Status, + Status: order.Status(spotOpenOrders[i].Status), }) } } diff --git a/exchanges/orders/README.md b/exchanges/order/README.md similarity index 100% rename from exchanges/orders/README.md rename to exchanges/order/README.md diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go new file mode 100644 index 00000000..2d7b7cd3 --- /dev/null +++ b/exchanges/order/order_test.go @@ -0,0 +1,400 @@ +package order + +import ( + "strings" + "testing" + "time" + + "github.com/thrasher-corp/gocryptotrader/currency" +) + +func TestValidate(t *testing.T) { + testPair := currency.NewPair(currency.BTC, currency.LTC) + tester := []struct { + Pair currency.Pair + Side + Type + Amount float64 + Price float64 + ExpectedErr error + }{ + { + ExpectedErr: ErrPairIsEmpty, + }, // empty pair + { + Pair: testPair, + ExpectedErr: ErrSideIsInvalid, + }, // valid pair but invalid order side + { + Pair: testPair, + Side: Buy, + ExpectedErr: ErrTypeIsInvalid, + }, // valid pair and order side but invalid order type + { + Pair: testPair, + Side: Sell, + ExpectedErr: ErrTypeIsInvalid, + }, // valid pair and order side but invalid order type + { + Pair: testPair, + Side: Bid, + ExpectedErr: ErrTypeIsInvalid, + }, // valid pair and order side but invalid order type + { + Pair: testPair, + Side: Ask, + ExpectedErr: ErrTypeIsInvalid, + }, // valid pair and order side but invalid order type + { + Pair: testPair, + Side: Ask, + Type: Market, + ExpectedErr: ErrAmountIsInvalid, + }, // valid pair, order side, type but invalid amount + { + Pair: testPair, + Side: Ask, + Type: Limit, + Amount: 1, + ExpectedErr: ErrPriceMustBeSetIfLimitOrder, + }, // valid pair, order side, type, amount but invalid price + { + Pair: testPair, + Side: Ask, + Type: Limit, + Amount: 1, + Price: 1000, + ExpectedErr: nil, + }, // valid order! + } + + for x := range tester { + s := Submit{ + Pair: tester[x].Pair, + OrderSide: tester[x].Side, + OrderType: tester[x].Type, + Amount: tester[x].Amount, + Price: tester[x].Price, + } + if err := s.Validate(); err != tester[x].ExpectedErr { + t.Errorf("Unexpected result. Got: %s, want: %s", err, tester[x].ExpectedErr) + } + } +} + +func TestOrderSides(t *testing.T) { + t.Parallel() + + var os = Buy + if os.String() != "BUY" { + t.Errorf("unexpected string %s", os.String()) + } + + if os.Lower() != "buy" { + t.Errorf("unexpected string %s", os.String()) + } +} + +func TestOrderTypes(t *testing.T) { + t.Parallel() + + var ot Type = "Mo'Money" + + if ot.String() != "Mo'Money" { + t.Errorf("unexpected string %s", ot.String()) + } + + if ot.Lower() != "mo'money" { + t.Errorf("unexpected string %s", ot.Lower()) + } +} + +func TestFilterOrdersByType(t *testing.T) { + t.Parallel() + + var orders = []Detail{ + { + OrderType: ImmediateOrCancel, + }, + { + OrderType: Limit, + }, + } + + FilterOrdersByType(&orders, AnyType) + if len(orders) != 2 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) + } + + FilterOrdersByType(&orders, Limit) + if len(orders) != 1 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + } + + FilterOrdersByType(&orders, Stop) + if len(orders) != 0 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) + } +} + +func TestFilterOrdersBySide(t *testing.T) { + t.Parallel() + + var orders = []Detail{ + { + OrderSide: Buy, + }, + { + OrderSide: Sell, + }, + {}, + } + + FilterOrdersBySide(&orders, AnySide) + if len(orders) != 3 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + } + + FilterOrdersBySide(&orders, Buy) + if len(orders) != 1 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + } + + FilterOrdersBySide(&orders, Sell) + if len(orders) != 0 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) + } +} + +func TestFilterOrdersByTickRange(t *testing.T) { + t.Parallel() + + var orders = []Detail{ + { + OrderDate: time.Unix(100, 0), + }, + { + OrderDate: time.Unix(110, 0), + }, + { + OrderDate: time.Unix(111, 0), + }, + } + + FilterOrdersByTickRange(&orders, time.Unix(0, 0), time.Unix(0, 0)) + if len(orders) != 3 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + } + + FilterOrdersByTickRange(&orders, time.Unix(100, 0), time.Unix(111, 0)) + if len(orders) != 3 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + } + + FilterOrdersByTickRange(&orders, time.Unix(101, 0), time.Unix(111, 0)) + if len(orders) != 2 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) + } + + FilterOrdersByTickRange(&orders, time.Unix(200, 0), time.Unix(300, 0)) + if len(orders) != 0 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders)) + } +} + +func TestFilterOrdersByCurrencies(t *testing.T) { + t.Parallel() + + var orders = []Detail{ + { + CurrencyPair: currency.NewPair(currency.BTC, currency.USD), + }, + { + CurrencyPair: currency.NewPair(currency.LTC, currency.EUR), + }, + { + CurrencyPair: currency.NewPair(currency.DOGE, currency.RUB), + }, + } + + currencies := []currency.Pair{currency.NewPair(currency.BTC, currency.USD), + currency.NewPair(currency.LTC, currency.EUR), + currency.NewPair(currency.DOGE, currency.RUB)} + FilterOrdersByCurrencies(&orders, currencies) + if len(orders) != 3 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders)) + } + + currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD), + currency.NewPair(currency.LTC, currency.EUR)} + FilterOrdersByCurrencies(&orders, currencies) + if len(orders) != 2 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders)) + } + + currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD)} + FilterOrdersByCurrencies(&orders, currencies) + if len(orders) != 1 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + } + + currencies = []currency.Pair{} + FilterOrdersByCurrencies(&orders, currencies) + if len(orders) != 1 { + t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) + } +} + +func TestSortOrdersByPrice(t *testing.T) { + t.Parallel() + + orders := []Detail{ + { + Price: 100, + }, { + Price: 0, + }, { + Price: 50, + }, + } + + SortOrdersByPrice(&orders, false) + if orders[0].Price != 0 { + t.Errorf("Expected: '%v', received: '%v'", 0, orders[0].Price) + } + + SortOrdersByPrice(&orders, true) + if orders[0].Price != 100 { + t.Errorf("Expected: '%v', received: '%v'", 100, orders[0].Price) + } +} + +func TestSortOrdersByDate(t *testing.T) { + t.Parallel() + + orders := []Detail{ + { + OrderDate: time.Unix(0, 0), + }, { + OrderDate: time.Unix(1, 0), + }, { + OrderDate: time.Unix(2, 0), + }, + } + + SortOrdersByDate(&orders, false) + if orders[0].OrderDate.Unix() != time.Unix(0, 0).Unix() { + t.Errorf("Expected: '%v', received: '%v'", + time.Unix(0, 0).Unix(), + orders[0].OrderDate.Unix()) + } + + SortOrdersByDate(&orders, true) + if orders[0].OrderDate.Unix() != time.Unix(2, 0).Unix() { + t.Errorf("Expected: '%v', received: '%v'", + time.Unix(2, 0).Unix(), + orders[0].OrderDate.Unix()) + } +} + +func TestSortOrdersByCurrency(t *testing.T) { + t.Parallel() + + orders := []Detail{ + { + CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), + currency.USD.String(), + "-"), + }, { + CurrencyPair: currency.NewPairWithDelimiter(currency.DOGE.String(), + currency.USD.String(), + "-"), + }, { + CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(), + currency.RUB.String(), + "-"), + }, { + CurrencyPair: currency.NewPairWithDelimiter(currency.LTC.String(), + currency.EUR.String(), + "-"), + }, { + CurrencyPair: currency.NewPairWithDelimiter(currency.LTC.String(), + currency.AUD.String(), + "-"), + }, + } + + SortOrdersByCurrency(&orders, false) + if orders[0].CurrencyPair.String() != currency.BTC.String()+"-"+currency.RUB.String() { + t.Errorf("Expected: '%v', received: '%v'", + currency.BTC.String()+"-"+currency.RUB.String(), + orders[0].CurrencyPair.String()) + } + + SortOrdersByCurrency(&orders, true) + if orders[0].CurrencyPair.String() != currency.LTC.String()+"-"+currency.EUR.String() { + t.Errorf("Expected: '%v', received: '%v'", + currency.LTC.String()+"-"+currency.EUR.String(), + orders[0].CurrencyPair.String()) + } +} + +func TestSortOrdersByOrderSide(t *testing.T) { + t.Parallel() + + orders := []Detail{ + { + OrderSide: Buy, + }, { + OrderSide: Sell, + }, { + OrderSide: Sell, + }, { + OrderSide: Buy, + }, + } + + SortOrdersBySide(&orders, false) + if !strings.EqualFold(orders[0].OrderSide.String(), Buy.String()) { + t.Errorf("Expected: '%v', received: '%v'", + Buy, + orders[0].OrderSide) + } + + SortOrdersBySide(&orders, true) + if !strings.EqualFold(orders[0].OrderSide.String(), Sell.String()) { + t.Errorf("Expected: '%v', received: '%v'", + Sell, + orders[0].OrderSide) + } +} + +func TestSortOrdersByOrderType(t *testing.T) { + t.Parallel() + + orders := []Detail{ + { + OrderType: Market, + }, { + OrderType: Limit, + }, { + OrderType: ImmediateOrCancel, + }, { + OrderType: TrailingStop, + }, + } + + SortOrdersByType(&orders, false) + if !strings.EqualFold(orders[0].OrderType.String(), ImmediateOrCancel.String()) { + t.Errorf("Expected: '%v', received: '%v'", + ImmediateOrCancel, + orders[0].OrderType) + } + + SortOrdersByType(&orders, true) + if !strings.EqualFold(orders[0].OrderType.String(), TrailingStop.String()) { + t.Errorf("Expected: '%v', received: '%v'", + TrailingStop, + orders[0].OrderType) + } +} diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go new file mode 100644 index 00000000..43eeb5c4 --- /dev/null +++ b/exchanges/order/order_types.go @@ -0,0 +1,190 @@ +package order + +import ( + "errors" + "time" + + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" +) + +const ( + limitOrder = iota + marketOrder +) + +// Orders variable holds an array of pointers to order structs +var Orders []*Order + +// Order struct holds order values +type Order struct { + OrderID int + Exchange string + Type int + Amount float64 + Price float64 +} + +// vars related to orders +var ( + ErrSubmissionIsNil = errors.New("order submission is nil") + ErrPairIsEmpty = errors.New("order pair is empty") + ErrSideIsInvalid = errors.New("order side is invalid") + ErrTypeIsInvalid = errors.New("order type is invalid") + ErrAmountIsInvalid = errors.New("order amount is invalid") + ErrPriceMustBeSetIfLimitOrder = errors.New("order price must be set if limit order type is desired") +) + +// Submit contains the order submission data +type Submit struct { + Pair currency.Pair + OrderType Type + OrderSide Side + Price float64 + Amount float64 + ClientID string +} + +// SubmitResponse is what is returned after submitting an order to an exchange +type SubmitResponse struct { + IsOrderPlaced bool + OrderID string +} + +// Modify is an order modifyer +type Modify struct { + OrderID string + Type + Side + Price float64 + Amount float64 + LimitPriceUpper float64 + LimitPriceLower float64 + CurrencyPair currency.Pair + ImmediateOrCancel bool + HiddenOrder bool + FillOrKill bool + PostOnly bool +} + +// ModifyResponse is an order modifying return type +type ModifyResponse struct { + OrderID string +} + +// CancelAllResponse returns the status from attempting to cancel all orders on +// an exchagne +type CancelAllResponse struct { + Status map[string]string +} + +// Type enforces a standard for order types across the code base +type Type string + +// Defined package order types +const ( + AnyType Type = "ANY" + Limit Type = "LIMIT" + Market Type = "MARKET" + ImmediateOrCancel Type = "IMMEDIATE_OR_CANCEL" + Stop Type = "STOP" + TrailingStop Type = "TRAILINGSTOP" + Unknown Type = "UNKNOWN" +) + +// Side enforces a standard for order sides across the code base +type Side string + +// Order side types +const ( + AnySide Side = "ANY" + Buy Side = "BUY" + Sell Side = "SELL" + Bid Side = "BID" + Ask Side = "ASK" +) + +// Detail holds order detail data +type Detail struct { + Exchange string + AccountID string + ID string + CurrencyPair currency.Pair + OrderSide Side + OrderType Type + OrderDate time.Time + Status + Price float64 + Amount float64 + ExecutedAmount float64 + RemainingAmount float64 + Fee float64 + Trades []TradeHistory +} + +// TradeHistory holds exchange history data +type TradeHistory struct { + Timestamp time.Time + TID int64 + Price float64 + Amount float64 + Exchange string + Type + Side + Fee float64 + Description string +} + +// Cancel type required when requesting to cancel an order +type Cancel struct { + AccountID string + OrderID string + CurrencyPair currency.Pair + AssetType asset.Item + WalletAddress string + Side +} + +// GetOrdersRequest used for GetOrderHistory and GetOpenOrders wrapper functions +type GetOrdersRequest struct { + OrderType Type + OrderSide Side + StartTicks time.Time + EndTicks time.Time + // Currencies Empty array = all currencies. Some endpoints only support + // singular currency enquiries + Currencies []currency.Pair +} + +// Status defines order status types +type Status string + +// All order status types +const ( + AnyStatus Status = "ANY" + New Status = "NEW" + Active Status = "ACTIVE" + PartiallyFilled Status = "PARTIALLY_FILLED" + Filled Status = "FILLED" + Cancelled Status = "CANCELED" + PendingCancel Status = "PENDING_CANCEL" + Rejected Status = "REJECTED" + Expired Status = "EXPIRED" + Hidden Status = "HIDDEN" + UnknownStatus Status = "UNKNOWN" +) + +// ByPrice used for sorting orders by price +type ByPrice []Detail + +// ByOrderType used for sorting orders by order type +type ByOrderType []Detail + +// ByCurrency used for sorting orders by order currency +type ByCurrency []Detail + +// ByDate used for sorting orders by order date +type ByDate []Detail + +// ByOrderSide used for sorting orders by order side (buy sell) +type ByOrderSide []Detail diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go new file mode 100644 index 00000000..85452186 --- /dev/null +++ b/exchanges/order/orders.go @@ -0,0 +1,302 @@ +package order + +import ( + "sort" + "strings" + "time" + + "github.com/thrasher-corp/gocryptotrader/currency" +) + +// NewOrder creates a new order and returns a an orderID +func NewOrder(exchangeName string, amount, price float64) int { + order := &Order{} + if len(Orders) == 0 { + order.OrderID = 0 + } else { + order.OrderID = len(Orders) + } + + order.Exchange = exchangeName + order.Amount = amount + order.Price = price + Orders = append(Orders, order) + return order.OrderID +} + +// DeleteOrder deletes orders by ID and returns state +func DeleteOrder(orderID int) bool { + for i := range Orders { + if Orders[i].OrderID == orderID { + Orders = append(Orders[:i], Orders[i+1:]...) + return true + } + } + return false +} + +// GetOrdersByExchange returns order pointer grouped by exchange +func GetOrdersByExchange(exchange string) []*Order { + var orders []*Order + for i := range Orders { + if Orders[i].Exchange == exchange { + orders = append(orders, Orders[i]) + } + } + if len(orders) > 0 { + return orders + } + return nil +} + +// GetOrderByOrderID returns order pointer by ID +func GetOrderByOrderID(orderID int) *Order { + for i := range Orders { + if Orders[i].OrderID == orderID { + return Orders[i] + } + } + return nil +} + +// Validate checks the supplied data and returns whether or not it's valid +func (s *Submit) Validate() error { + if s == nil { + return ErrSubmissionIsNil + } + + if s.Pair.IsEmpty() { + return ErrPairIsEmpty + } + + if s.OrderSide != Buy && + s.OrderSide != Sell && + s.OrderSide != Bid && + s.OrderSide != Ask { + return ErrSideIsInvalid + } + + if s.OrderType != Market && s.OrderType != Limit { + return ErrTypeIsInvalid + } + + if s.Amount <= 0 { + return ErrAmountIsInvalid + } + + if s.OrderType == Limit && s.Price <= 0 { + return ErrPriceMustBeSetIfLimitOrder + } + + return nil +} + +// String implements the stringer interface +func (t Type) String() string { + return string(t) +} + +// Lower returns the type lower case string +func (t Type) Lower() string { + return strings.ToLower(string(t)) +} + +// String implements the stringer interface +func (s Side) String() string { + return string(s) +} + +// Lower returns the side lower case string +func (s Side) Lower() string { + return strings.ToLower(string(s)) +} + +// String implements the stringer interface +func (s Status) String() string { + return string(s) +} + +// FilterOrdersBySide removes any order details that don't match the +// order status provided +func FilterOrdersBySide(orders *[]Detail, side Side) { + if side == "" || side == AnySide { + return + } + + var filteredOrders []Detail + for i := range *orders { + if strings.EqualFold(string((*orders)[i].OrderSide), string(side)) { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByType removes any order details that don't match the order type +// provided +func FilterOrdersByType(orders *[]Detail, orderType Type) { + if orderType == "" || orderType == AnyType { + return + } + + var filteredOrders []Detail + for i := range *orders { + if strings.EqualFold(string((*orders)[i].OrderType), string(orderType)) { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByTickRange removes any OrderDetails outside of the tick range +func FilterOrdersByTickRange(orders *[]Detail, startTicks, endTicks time.Time) { + if startTicks.IsZero() || + endTicks.IsZero() || + startTicks.Unix() == 0 || + endTicks.Unix() == 0 || + endTicks.Before(startTicks) { + return + } + + var filteredOrders []Detail + for i := range *orders { + if (*orders)[i].OrderDate.Unix() >= startTicks.Unix() && + (*orders)[i].OrderDate.Unix() <= endTicks.Unix() { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +// FilterOrdersByCurrencies removes any order details that do not match the +// provided currency list. It is forgiving in that the provided currencies can +// match quote or base currencies +func FilterOrdersByCurrencies(orders *[]Detail, currencies []currency.Pair) { + if len(currencies) == 0 { + return + } + + var filteredOrders []Detail + for i := range *orders { + matchFound := false + for _, c := range currencies { + if !matchFound && (*orders)[i].CurrencyPair.EqualIncludeReciprocal(c) { + matchFound = true + } + } + + if matchFound { + filteredOrders = append(filteredOrders, (*orders)[i]) + } + } + + *orders = filteredOrders +} + +func (b ByPrice) Len() int { + return len(b) +} + +func (b ByPrice) Less(i, j int) bool { + return b[i].Price < b[j].Price +} + +func (b ByPrice) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByPrice the caller function to sort orders +func SortOrdersByPrice(orders *[]Detail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByPrice(*orders))) + } else { + sort.Sort(ByPrice(*orders)) + } +} + +func (b ByOrderType) Len() int { + return len(b) +} + +func (b ByOrderType) Less(i, j int) bool { + return b[i].OrderType.String() < b[j].OrderType.String() +} + +func (b ByOrderType) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByType the caller function to sort orders +func SortOrdersByType(orders *[]Detail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByOrderType(*orders))) + } else { + sort.Sort(ByOrderType(*orders)) + } +} + +func (b ByCurrency) Len() int { + return len(b) +} + +func (b ByCurrency) Less(i, j int) bool { + return b[i].CurrencyPair.String() < b[j].CurrencyPair.String() +} + +func (b ByCurrency) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByCurrency the caller function to sort orders +func SortOrdersByCurrency(orders *[]Detail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByCurrency(*orders))) + } else { + sort.Sort(ByCurrency(*orders)) + } +} + +func (b ByDate) Len() int { + return len(b) +} + +func (b ByDate) Less(i, j int) bool { + return b[i].OrderDate.Unix() < b[j].OrderDate.Unix() +} + +func (b ByDate) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersByDate the caller function to sort orders +func SortOrdersByDate(orders *[]Detail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByDate(*orders))) + } else { + sort.Sort(ByDate(*orders)) + } +} + +func (b ByOrderSide) Len() int { + return len(b) +} + +func (b ByOrderSide) Less(i, j int) bool { + return b[i].OrderSide.String() < b[j].OrderSide.String() +} + +func (b ByOrderSide) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// SortOrdersBySide the caller function to sort orders +func SortOrdersBySide(orders *[]Detail, reverse bool) { + if reverse { + sort.Sort(sort.Reverse(ByOrderSide(*orders))) + } else { + sort.Sort(ByOrderSide(*orders)) + } +} diff --git a/exchanges/orders/orders_test.go b/exchanges/order/orders_test.go similarity index 98% rename from exchanges/orders/orders_test.go rename to exchanges/order/orders_test.go index 0a7eef4e..13c92bb8 100644 --- a/exchanges/orders/orders_test.go +++ b/exchanges/order/orders_test.go @@ -1,4 +1,4 @@ -package orders +package order import ( "testing" diff --git a/exchanges/order_test.go b/exchanges/order_test.go deleted file mode 100644 index 1274bc19..00000000 --- a/exchanges/order_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package exchange - -import ( - "testing" - - "github.com/thrasher-corp/gocryptotrader/currency" -) - -func TestValidate(t *testing.T) { - testPair := currency.NewPair(currency.BTC, currency.LTC) - tester := []struct { - Pair currency.Pair - Side OrderSide - Type OrderType - Amount float64 - Price float64 - ExpectedErr error - }{ - { - ExpectedErr: ErrOrderPairIsEmpty, - }, // empty pair - { - Pair: testPair, - ExpectedErr: ErrOrderSideIsInvalid, - }, // valid pair but invalid order side - { - Pair: testPair, - Side: BuyOrderSide, - ExpectedErr: ErrOrderTypeIsInvalid, - }, // valid pair and order side but invalid order type - { - Pair: testPair, - Side: SellOrderSide, - ExpectedErr: ErrOrderTypeIsInvalid, - }, // valid pair and order side but invalid order type - { - Pair: testPair, - Side: BidOrderSide, - ExpectedErr: ErrOrderTypeIsInvalid, - }, // valid pair and order side but invalid order type - { - Pair: testPair, - Side: AskOrderSide, - ExpectedErr: ErrOrderTypeIsInvalid, - }, // valid pair and order side but invalid order type - { - Pair: testPair, - Side: AskOrderSide, - Type: MarketOrderType, - ExpectedErr: ErrOrderAmountIsInvalid, - }, // valid pair, order side, type but invalid amount - { - Pair: testPair, - Side: AskOrderSide, - Type: LimitOrderType, - Amount: 1, - ExpectedErr: ErrOrderPriceMustBeSetIfLimitOrder, - }, // valid pair, order side, type, amount but invalid price - { - Pair: testPair, - Side: AskOrderSide, - Type: LimitOrderType, - Amount: 1, - Price: 1000, - ExpectedErr: nil, - }, // valid order! - } - - for x := range tester { - s := OrderSubmission{ - Pair: tester[x].Pair, - OrderSide: tester[x].Side, - OrderType: tester[x].Type, - Amount: tester[x].Amount, - Price: tester[x].Price, - } - if err := s.Validate(); err != tester[x].ExpectedErr { - t.Errorf("Unexpected result. Got: %s, want: %s", err, tester[x].ExpectedErr) - } - } -} diff --git a/exchanges/order_types.go b/exchanges/order_types.go deleted file mode 100644 index da86da47..00000000 --- a/exchanges/order_types.go +++ /dev/null @@ -1,387 +0,0 @@ -package exchange - -import ( - "errors" - "fmt" - "sort" - "strings" - "time" - - "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" -) - -// vars related to orders -var ( - ErrOrderSubmissionIsNil = errors.New("order submission is nil") - ErrOrderPairIsEmpty = errors.New("order pair is empty") - ErrOrderSideIsInvalid = errors.New("order side is invalid") - ErrOrderTypeIsInvalid = errors.New("order type is invalid") - ErrOrderAmountIsInvalid = errors.New("order amount is invalid") - ErrOrderPriceMustBeSetIfLimitOrder = errors.New("order price must be set if limit order type is desired") -) - -// OrderSubmission contains the order submission data -type OrderSubmission struct { - Pair currency.Pair - OrderSide OrderSide - OrderType OrderType - Price float64 - Amount float64 - ClientID string -} - -// Validate checks the supplied data and returns whether or not its valid -func (o *OrderSubmission) Validate() error { - if o.Pair.IsEmpty() { - return ErrOrderPairIsEmpty - } - - o.OrderSide = OrderSide(strings.ToUpper(o.OrderSide.ToString())) - if o.OrderSide != BuyOrderSide && o.OrderSide != SellOrderSide && - o.OrderSide != BidOrderSide && o.OrderSide != AskOrderSide { - return ErrOrderSideIsInvalid - } - - o.OrderType = OrderType(strings.ToUpper(o.OrderType.ToString())) - if o.OrderType != MarketOrderType && o.OrderType != LimitOrderType { - return ErrOrderTypeIsInvalid - } - - if o.Amount <= 0 { - return ErrOrderAmountIsInvalid - } - - if o.OrderType == LimitOrderType && o.Price <= 0 { - return ErrOrderPriceMustBeSetIfLimitOrder - } - - return nil -} - -// SubmitOrderResponse is what is returned after submitting an order to an exchange -type SubmitOrderResponse struct { - IsOrderPlaced bool - OrderID string -} - -// ModifyOrder is a an order modifyer -type ModifyOrder struct { - OrderID string - OrderType - OrderSide - Price float64 - Amount float64 - LimitPriceUpper float64 - LimitPriceLower float64 - CurrencyPair currency.Pair - ImmediateOrCancel bool - HiddenOrder bool - FillOrKill bool - PostOnly bool -} - -// ModifyOrderResponse is an order modifying return type -type ModifyOrderResponse struct { - OrderID string -} - -// CancelAllOrdersResponse returns the status from attempting to cancel all orders on an exchagne -type CancelAllOrdersResponse struct { - OrderStatus map[string]string -} - -// OrderType enforces a standard for Ordertypes across the code base -type OrderType string - -// OrderType ...types -const ( - AnyOrderType OrderType = "ANY" - LimitOrderType OrderType = "LIMIT" - MarketOrderType OrderType = "MARKET" - ImmediateOrCancelOrderType OrderType = "IMMEDIATE_OR_CANCEL" - StopOrderType OrderType = "STOP" - TrailingStopOrderType OrderType = "TRAILINGSTOP" - UnknownOrderType OrderType = "UNKNOWN" -) - -// ToLower changes the ordertype to lower case -func (o OrderType) ToLower() OrderType { - return OrderType(strings.ToLower(string(o))) -} - -// ToString changes the ordertype to the exchange standard and returns a string -func (o OrderType) ToString() string { - return fmt.Sprintf("%v", o) -} - -// OrderSide enforces a standard for OrderSides across the code base -type OrderSide string - -// OrderSide types -const ( - AnyOrderSide OrderSide = "ANY" - BuyOrderSide OrderSide = "BUY" - SellOrderSide OrderSide = "SELL" - BidOrderSide OrderSide = "BID" - AskOrderSide OrderSide = "ASK" -) - -// ToLower changes the ordertype to lower case -func (o OrderSide) ToLower() OrderSide { - return OrderSide(strings.ToLower(string(o))) -} - -// ToString changes the ordertype to the exchange standard and returns a string -func (o OrderSide) ToString() string { - return fmt.Sprintf("%v", o) -} - -// OrderDetail holds order detail data -type OrderDetail struct { - Exchange string - AccountID string - ID string - CurrencyPair currency.Pair - OrderSide OrderSide - OrderType OrderType - OrderDate time.Time - Status string - Price float64 - Amount float64 - ExecutedAmount float64 - RemainingAmount float64 - Fee float64 - Trades []TradeHistory -} - -// OrderCancellation type required when requesting to cancel an order -type OrderCancellation struct { - AccountID string - OrderID string - CurrencyPair currency.Pair - AssetType asset.Item - WalletAddress string - Side OrderSide -} - -// GetOrdersRequest used for GetOrderHistory and GetOpenOrders wrapper functions -type GetOrdersRequest struct { - OrderType OrderType - OrderSide OrderSide - StartTicks time.Time - EndTicks time.Time - // Currencies Empty array = all currencies. Some endpoints only support singular currency enquiries - Currencies []currency.Pair -} - -// OrderStatus defines order status types -type OrderStatus string - -// All OrderStatus types -const ( - AnyOrderStatus OrderStatus = "ANY" - NewOrderStatus OrderStatus = "NEW" - ActiveOrderStatus OrderStatus = "ACTIVE" - PartiallyFilledOrderStatus OrderStatus = "PARTIALLY_FILLED" - FilledOrderStatus OrderStatus = "FILLED" - CancelledOrderStatus OrderStatus = "CANCELED" - PendingCancelOrderStatus OrderStatus = "PENDING_CANCEL" - RejectedOrderStatus OrderStatus = "REJECTED" - ExpiredOrderStatus OrderStatus = "EXPIRED" - HiddenOrderStatus OrderStatus = "HIDDEN" - UnknownOrderStatus OrderStatus = "UNKNOWN" -) - -// FilterOrdersBySide removes any OrderDetails that don't match the orderStatus provided -func FilterOrdersBySide(orders *[]OrderDetail, orderSide OrderSide) { - if orderSide == "" || orderSide == AnyOrderSide { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - if strings.EqualFold(string((*orders)[i].OrderSide), string(orderSide)) { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// FilterOrdersByType removes any OrderDetails that don't match the orderType provided -func FilterOrdersByType(orders *[]OrderDetail, orderType OrderType) { - if orderType == "" || orderType == AnyOrderType { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - if strings.EqualFold(string((*orders)[i].OrderType), string(orderType)) { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// FilterOrdersByTickRange removes any OrderDetails outside of the tick range -func FilterOrdersByTickRange(orders *[]OrderDetail, startTicks, endTicks time.Time) { - if startTicks.IsZero() || endTicks.IsZero() || - startTicks.Unix() == 0 || endTicks.Unix() == 0 || endTicks.Before(startTicks) { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - if (*orders)[i].OrderDate.Unix() >= startTicks.Unix() && (*orders)[i].OrderDate.Unix() <= endTicks.Unix() { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// FilterOrdersByCurrencies removes any OrderDetails that do not match the provided currency list -// It is forgiving in that the provided currencies can match quote or base currencies -func FilterOrdersByCurrencies(orders *[]OrderDetail, currencies []currency.Pair) { - if len(currencies) == 0 { - return - } - - var filteredOrders []OrderDetail - for i := range *orders { - matchFound := false - for _, c := range currencies { - if !matchFound && (*orders)[i].CurrencyPair.EqualIncludeReciprocal(c) { - matchFound = true - } - } - - if matchFound { - filteredOrders = append(filteredOrders, (*orders)[i]) - } - } - - *orders = filteredOrders -} - -// ByPrice used for sorting orders by price -type ByPrice []OrderDetail - -func (b ByPrice) Len() int { - return len(b) -} - -func (b ByPrice) Less(i, j int) bool { - return b[i].Price < b[j].Price -} - -func (b ByPrice) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByPrice the caller function to sort orders -func SortOrdersByPrice(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByPrice(*orders))) - } else { - sort.Sort(ByPrice(*orders)) - } -} - -// ByOrderType used for sorting orders by order type -type ByOrderType []OrderDetail - -func (b ByOrderType) Len() int { - return len(b) -} - -func (b ByOrderType) Less(i, j int) bool { - return b[i].OrderType.ToString() < b[j].OrderType.ToString() -} - -func (b ByOrderType) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByType the caller function to sort orders -func SortOrdersByType(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByOrderType(*orders))) - } else { - sort.Sort(ByOrderType(*orders)) - } -} - -// ByCurrency used for sorting orders by order currency -type ByCurrency []OrderDetail - -func (b ByCurrency) Len() int { - return len(b) -} - -func (b ByCurrency) Less(i, j int) bool { - return b[i].CurrencyPair.String() < b[j].CurrencyPair.String() -} - -func (b ByCurrency) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByCurrency the caller function to sort orders -func SortOrdersByCurrency(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByCurrency(*orders))) - } else { - sort.Sort(ByCurrency(*orders)) - } -} - -// ByDate used for sorting orders by order date -type ByDate []OrderDetail - -func (b ByDate) Len() int { - return len(b) -} - -func (b ByDate) Less(i, j int) bool { - return b[i].OrderDate.Unix() < b[j].OrderDate.Unix() -} - -func (b ByDate) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersByDate the caller function to sort orders -func SortOrdersByDate(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByDate(*orders))) - } else { - sort.Sort(ByDate(*orders)) - } -} - -// ByOrderSide used for sorting orders by order side (buy sell) -type ByOrderSide []OrderDetail - -func (b ByOrderSide) Len() int { - return len(b) -} - -func (b ByOrderSide) Less(i, j int) bool { - return b[i].OrderSide.ToString() < b[j].OrderSide.ToString() -} - -func (b ByOrderSide) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// SortOrdersBySide the caller function to sort orders -func SortOrdersBySide(orders *[]OrderDetail, reverse bool) { - if reverse { - sort.Sort(sort.Reverse(ByOrderSide(*orders))) - } else { - sort.Sort(ByOrderSide(*orders)) - } -} diff --git a/exchanges/orders/orders.go b/exchanges/orders/orders.go deleted file mode 100644 index 8308b4b0..00000000 --- a/exchanges/orders/orders.go +++ /dev/null @@ -1,69 +0,0 @@ -package orders - -const ( - limitOrder = iota - marketOrder -) - -// Orders variable holds an array of pointers to order structs -var Orders []*Order - -// Order struct holds order values -type Order struct { - OrderID int - Exchange string - Type int - Amount float64 - Price float64 -} - -// NewOrder creates a new order and returns a an orderID -func NewOrder(exchangeName string, amount, price float64) int { - order := &Order{} - if len(Orders) == 0 { - order.OrderID = 0 - } else { - order.OrderID = len(Orders) - } - - order.Exchange = exchangeName - order.Amount = amount - order.Price = price - Orders = append(Orders, order) - return order.OrderID -} - -// DeleteOrder deletes orders by ID and returns state -func DeleteOrder(orderID int) bool { - for i := range Orders { - if Orders[i].OrderID == orderID { - Orders = append(Orders[:i], Orders[i+1:]...) - return true - } - } - return false -} - -// GetOrdersByExchange returns order pointer grouped by exchange -func GetOrdersByExchange(exchange string) []*Order { - var orders []*Order - for i := range Orders { - if Orders[i].Exchange == exchange { - orders = append(orders, Orders[i]) - } - } - if len(orders) > 0 { - return orders - } - return nil -} - -// GetOrderByOrderID returns order pointer by ID -func GetOrderByOrderID(orderID int) *Order { - for i := range Orders { - if Orders[i].OrderID == orderID { - return Orders[i] - } - } - return nil -} diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 72e7639b..927f4ff2 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -410,9 +411,9 @@ func (p *Poloniex) PlaceOrder(currency string, rate, amount float64, immediate, var orderType string if buy { - orderType = exchange.BuyOrderSide.ToLower().ToString() + orderType = order.Buy.Lower() } else { - orderType = exchange.SellOrderSide.ToLower().ToString() + orderType = order.Sell.Lower() } values.Set("currencyPair", currency) diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index 5107ada9..b63036f1 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -212,8 +213,8 @@ func TestFormatWithdrawPermissions(t *testing.T) { func TestGetActiveOrders(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := p.GetActiveOrders(&getOrdersRequest) @@ -229,8 +230,8 @@ func TestGetActiveOrders(t *testing.T) { func TestGetOrderHistory(t *testing.T) { t.Parallel() - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, } _, err := p.GetOrderHistory(&getOrdersRequest) @@ -253,14 +254,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", Base: currency.BTC, Quote: currency.LTC, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.MarketOrderType, + OrderSide: order.Buy, + OrderType: order.Market, Price: 10, Amount: 10000000, ClientID: "hi", @@ -283,7 +284,7 @@ func TestCancelExchangeOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -309,7 +310,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -325,8 +326,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { case mockTests && err != nil: t.Error("Mock CancelAllExchangeOrders() err", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -336,7 +337,7 @@ func TestModifyOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - _, err := p.ModifyOrder(&exchange.ModifyOrder{OrderID: "1337", Price: 1337}) + _, err := p.ModifyOrder(&order.Modify{OrderID: "1337", Price: 1337}) switch { case areTestAPIKeysSet() && err != nil && mockTests: t.Error("ModifyOrder() error", err) diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 6477eda0..09144a30 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -359,26 +360,22 @@ func (p *Poloniex) GetExchangeHistory(currencyPair currency.Pair, assetType asse } // SubmitOrder submits a new order -func (p *Poloniex) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (p *Poloniex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - fillOrKill := order.OrderType == exchange.MarketOrderType - isBuyOrder := order.OrderSide == exchange.BuyOrderSide - response, err := p.PlaceOrder(order.Pair.String(), - order.Price, - order.Amount, + fillOrKill := s.OrderType == order.Market + isBuyOrder := s.OrderSide == order.Buy + response, err := p.PlaceOrder(s.Pair.String(), + s.Price, + s.Amount, false, fillOrKill, isBuyOrder) if response.OrderNumber > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response.OrderNumber) + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) } if err == nil { submitOrderResponse.IsOrderPlaced = true @@ -388,7 +385,7 @@ func (p *Poloniex) SubmitOrder(order *exchange.OrderSubmission) (exchange.Submit // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (p *Poloniex) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (p *Poloniex) ModifyOrder(action *order.Modify) (string, error) { oID, err := strconv.ParseInt(action.OrderID, 10, 64) if err != nil { return "", err @@ -407,7 +404,7 @@ func (p *Poloniex) ModifyOrder(action *exchange.ModifyOrder) (string, error) { } // CancelOrder cancels an order by its corresponding ID number -func (p *Poloniex) CancelOrder(order *exchange.OrderCancellation) error { +func (p *Poloniex) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) if err != nil { return err @@ -417,20 +414,21 @@ func (p *Poloniex) CancelOrder(order *exchange.OrderCancellation) error { } // CancelAllOrders cancels all orders associated with a currency pair -func (p *Poloniex) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (p *Poloniex) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } openOrders, err := p.GetOpenOrdersForAllCurrencies() if err != nil { return cancelAllOrdersResponse, err } - for _, openOrderPerCurrency := range openOrders.Data { - for _, openOrder := range openOrderPerCurrency { - err = p.CancelExistingOrder(openOrder.OrderNumber) + for key := range openOrders.Data { + for i := range openOrders.Data[key] { + err = p.CancelExistingOrder(openOrders.Data[key][i].OrderNumber) if err != nil { - cancelAllOrdersResponse.OrderStatus[strconv.FormatInt(openOrder.OrderNumber, 10)] = err.Error() + id := strconv.FormatInt(openOrders.Data[key][i].OrderNumber, 10) + cancelAllOrdersResponse.Status[id] = err.Error() } } } @@ -439,8 +437,8 @@ func (p *Poloniex) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Canc } // GetOrderInfo returns information on a current open order -func (p *Poloniex) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (p *Poloniex) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -494,81 +492,90 @@ func (p *Poloniex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error } // GetActiveOrders retrieves any orders that are active/open -func (p *Poloniex) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (p *Poloniex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := p.GetOpenOrdersForAllCurrencies() if err != nil { return nil, err } - var orders []exchange.OrderDetail - for currencyPair, openOrders := range resp.Data { - symbol := currency.NewPairDelimiter(currencyPair, + var orders []order.Detail + for key := range resp.Data { + symbol := currency.NewPairDelimiter(key, p.GetPairFormat(asset.Spot, false).Delimiter) - for _, order := range openOrders { - orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) - orderDate, err := time.Parse(poloniexDateLayout, order.Date) + for i := range resp.Data[key] { + orderSide := order.Side(strings.ToUpper(resp.Data[key][i].Type)) + orderDate, err := time.Parse(poloniexDateLayout, resp.Data[key][i].Date) if err != nil { - log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", - p.Name, "GetActiveOrders", order.OrderNumber, order.Date) + log.Warnf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + p.Name, + "GetActiveOrders", + resp.Data[key][i].OrderNumber, + resp.Data[key][i].Date) } - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.OrderNumber), + orders = append(orders, order.Detail{ + ID: strconv.FormatInt(resp.Data[key][i].OrderNumber, 10), OrderSide: orderSide, - Amount: order.Amount, + Amount: resp.Data[key][i].Amount, OrderDate: orderDate, - Price: order.Rate, + Price: resp.Data[key][i].Rate, CurrencyPair: symbol, Exchange: p.Name, }) } } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersByCurrencies(&orders, req.Currencies) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (p *Poloniex) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - resp, err := p.GetAuthenticatedTradeHistory(getOrdersRequest.StartTicks.Unix(), - getOrdersRequest.EndTicks.Unix(), +func (p *Poloniex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + resp, err := p.GetAuthenticatedTradeHistory(req.StartTicks.Unix(), + req.EndTicks.Unix(), 10000) if err != nil { return nil, err } - var orders []exchange.OrderDetail - for currencyPair, historicOrders := range resp.Data { - symbol := currency.NewPairDelimiter(currencyPair, + var orders []order.Detail + for key := range resp.Data { + symbol := currency.NewPairDelimiter(key, p.GetPairFormat(asset.Spot, false).Delimiter) - for _, order := range historicOrders { - orderSide := exchange.OrderSide(strings.ToUpper(order.Type)) - orderDate, err := time.Parse(poloniexDateLayout, order.Date) + for i := range resp.Data[key] { + orderSide := order.Side(strings.ToUpper(resp.Data[key][i].Type)) + orderDate, err := time.Parse(poloniexDateLayout, + resp.Data[key][i].Date) if err != nil { - log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", - p.Name, "GetActiveOrders", order.OrderNumber, order.Date) + log.Warnf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + p.Name, + "GetActiveOrders", + resp.Data[key][i].OrderNumber, + resp.Data[key][i].Date) } - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.GlobalTradeID), + orders = append(orders, order.Detail{ + ID: strconv.FormatInt(resp.Data[key][i].GlobalTradeID, 10), OrderSide: orderSide, - Amount: order.Amount, + Amount: resp.Data[key][i].Amount, OrderDate: orderDate, - Price: order.Rate, + Price: resp.Data[key][i].Rate, CurrencyPair: symbol, Exchange: p.Name, }) } } - exchange.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByCurrencies(&orders, req.Currencies) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } diff --git a/exchanges/yobit/yobit.go b/exchanges/yobit/yobit.go index 392736f9..da82d928 100644 --- a/exchanges/yobit/yobit.go +++ b/exchanges/yobit/yobit.go @@ -105,7 +105,7 @@ func (y *Yobit) GetAccountInformation() (AccountInfo, error) { func (y *Yobit) Trade(pair, orderType string, amount, price float64) (int64, error) { req := url.Values{} req.Add("pair", pair) - req.Add("type", orderType) + req.Add("type", strings.ToLower(orderType)) req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("rate", strconv.FormatFloat(price, 'f', -1, 64)) @@ -142,7 +142,7 @@ func (y *Yobit) GetOrderInformation(orderID int64) (map[string]OrderInfo, error) } // CancelExistingOrder cancels an order for a specific order ID -func (y *Yobit) CancelExistingOrder(orderID int64) (bool, error) { +func (y *Yobit) CancelExistingOrder(orderID int64) error { req := url.Values{} req.Add("order_id", strconv.FormatInt(orderID, 10)) @@ -150,12 +150,12 @@ func (y *Yobit) CancelExistingOrder(orderID int64) (bool, error) { err := y.SendAuthenticatedHTTPRequest(privateCancelOrder, req, &result) if err != nil { - return false, err + return err } if result.Error != "" { - return false, errors.New(result.Error) + return errors.New(result.Error) } - return true, nil + return nil } // GetTradeHistory returns the trade history diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index a0d92fa2..3307cb71 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) var y Yobit @@ -111,7 +112,7 @@ func TestGetOrderInfo(t *testing.T) { func TestCancelOrder(t *testing.T) { t.Parallel() - _, err := y.CancelExistingOrder(1337) + err := y.CancelExistingOrder(1337) if err == nil { t.Error("CancelOrder() Expected error") } @@ -119,7 +120,7 @@ func TestCancelOrder(t *testing.T) { func TestTrade(t *testing.T) { t.Parallel() - _, err := y.Trade("", exchange.BuyOrderSide.ToLower().ToString(), 0, 0) + _, err := y.Trade("", order.Buy.String(), 0, 0) if err == nil { t.Error("Trade() Expected error") } @@ -330,8 +331,8 @@ func TestGetActiveOrders(t *testing.T) { y.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)}, } @@ -348,8 +349,8 @@ func TestGetOrderHistory(t *testing.T) { y.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)}, StartTicks: time.Unix(0, 0), @@ -378,14 +379,14 @@ func TestSubmitOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", Base: currency.BTC, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -408,7 +409,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -434,7 +435,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -450,13 +451,13 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } func TestModifyOrder(t *testing.T) { - _, err := y.ModifyOrder(&exchange.ModifyOrder{}) + _, err := y.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } @@ -498,7 +499,9 @@ func TestWithdrawFiat(t *testing.T) { var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := y.WithdrawFiatFunds(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } @@ -513,7 +516,9 @@ func TestWithdrawInternationalBank(t *testing.T) { var withdrawFiatRequest = exchange.FiatWithdrawRequest{} _, err := y.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) + t.Errorf("Expected '%v', received: '%v'", + common.ErrFunctionNotSupported, + err) } } diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index c4edd5c0..b2d36ae0 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -2,7 +2,6 @@ package yobit import ( "errors" - "fmt" "math" "strconv" "strings" @@ -14,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -141,7 +141,10 @@ func (y *Yobit) Run() { err := y.UpdateTradablePairs(false) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", y.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + y.Name, + err) } } @@ -233,14 +236,20 @@ func (y *Yobit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo 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 i := range orderbookNew.Bids { + orderBook.Bids = append(orderBook.Bids, + orderbook.Item{ + Price: orderbookNew.Bids[i][0], + Amount: orderbookNew.Bids[i][1], + }) } - for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Price: data[0], Amount: data[1]}) + for i := range orderbookNew.Asks { + orderBook.Asks = append(orderBook.Asks, + orderbook.Item{ + Price: orderbookNew.Asks[i][0], + Amount: orderbookNew.Asks[i][1], + }) } orderBook.Pair = p @@ -301,24 +310,22 @@ func (y *Yobit) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exc // SubmitOrder submits a new order // Yobit only supports limit orders -func (y *Yobit) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (y *Yobit) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } - if order.OrderType != exchange.LimitOrderType { + if s.OrderType != order.Limit { return submitOrderResponse, errors.New("only limit orders are allowed") } - response, err := y.Trade(order.Pair.String(), order.OrderSide.ToString(), - order.Amount, order.Price) + response, err := y.Trade(s.Pair.String(), + s.OrderSide.String(), + s.Amount, + s.Price) if response > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response) + submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } if err == nil { submitOrderResponse.IsOrderPlaced = true @@ -328,31 +335,31 @@ func (y *Yobit) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrd // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (y *Yobit) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (y *Yobit) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (y *Yobit) CancelOrder(order *exchange.OrderCancellation) error { +func (y *Yobit) CancelOrder(order *order.Cancel) error { orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) if err != nil { return err } - _, err = y.CancelExistingOrder(orderIDInt) - return err + return y.CancelExistingOrder(orderIDInt) } // CancelAllOrders cancels all orders associated with a currency pair -func (y *Yobit) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (y *Yobit) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } - var allActiveOrders []map[string]ActiveOrders - for _, pair := range y.GetEnabledPairs(asset.Spot) { - activeOrdersForPair, err := y.GetOpenOrders(y.FormatExchangeCurrency(pair, - asset.Spot).String()) + var allActiveOrders []map[string]ActiveOrders + enabledPairs := y.GetEnabledPairs(asset.Spot) + for i := range enabledPairs { + fCurr := y.FormatExchangeCurrency(enabledPairs[i], asset.Spot).String() + activeOrdersForPair, err := y.GetOpenOrders(fCurr) if err != nil { return cancelAllOrdersResponse, err } @@ -360,17 +367,17 @@ func (y *Yobit) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelA allActiveOrders = append(allActiveOrders, activeOrdersForPair) } - for _, activeOrders := range allActiveOrders { - for key := range activeOrders { + for i := range allActiveOrders { + for key := range allActiveOrders[i] { orderIDInt, err := strconv.ParseInt(key, 10, 64) if err != nil { - cancelAllOrdersResponse.OrderStatus[key] = err.Error() + cancelAllOrdersResponse.Status[key] = err.Error() continue } - _, err = y.CancelExistingOrder(orderIDInt) + err = y.CancelExistingOrder(orderIDInt) if err != nil { - cancelAllOrdersResponse.OrderStatus[key] = err.Error() + cancelAllOrdersResponse.Status[key] = err.Error() } } } @@ -379,8 +386,8 @@ func (y *Yobit) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelA } // GetOrderInfo returns information on a current open order -func (y *Yobit) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (y *Yobit) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -434,24 +441,24 @@ func (y *Yobit) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { } // GetActiveOrders retrieves any orders that are active/open -func (y *Yobit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - var orders []exchange.OrderDetail - for _, c := range getOrdersRequest.Currencies { - resp, err := y.GetOpenOrders(y.FormatExchangeCurrency(c, - asset.Spot).String()) +func (y *Yobit) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { + var orders []order.Detail + for x := range req.Currencies { + fCurr := y.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() + resp, err := y.GetOpenOrders(fCurr) if err != nil { return nil, err } - for ID, order := range resp { - symbol := currency.NewPairDelimiter(order.Pair, + for id := range resp { + symbol := currency.NewPairDelimiter(resp[id].Pair, y.GetPairFormat(asset.Spot, false).Delimiter) - orderDate := time.Unix(int64(order.TimestampCreated), 0) - side := exchange.OrderSide(strings.ToUpper(order.Type)) - orders = append(orders, exchange.OrderDetail{ - ID: ID, - Amount: order.Amount, - Price: order.Rate, + orderDate := time.Unix(int64(resp[id].TimestampCreated), 0) + side := order.Side(strings.ToUpper(resp[id].Type)) + orders = append(orders, order.Detail{ + ID: id, + Amount: resp[id].Amount, + Price: resp[id].Rate, OrderSide: side, OrderDate: orderDate, CurrencyPair: symbol, @@ -460,43 +467,42 @@ func (y *Yobit) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([] } } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status -func (y *Yobit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (y *Yobit) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var allOrders []TradeHistory - for _, currency := range getOrdersRequest.Currencies { + for x := range req.Currencies { resp, err := y.GetTradeHistory(0, 10000, math.MaxInt64, - getOrdersRequest.StartTicks.Unix(), - getOrdersRequest.EndTicks.Unix(), + req.StartTicks.Unix(), + req.EndTicks.Unix(), "DESC", - y.FormatExchangeCurrency(currency, asset.Spot).String()) + y.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String()) if err != nil { return nil, err } - for _, order := range resp { - allOrders = append(allOrders, order) + for key := range resp { + allOrders = append(allOrders, resp[key]) } } - var orders []exchange.OrderDetail - for _, order := range allOrders { - symbol := currency.NewPairDelimiter(order.Pair, + var orders []order.Detail + for i := range allOrders { + symbol := currency.NewPairDelimiter(allOrders[i].Pair, y.GetPairFormat(asset.Spot, false).Delimiter) - orderDate := time.Unix(int64(order.Timestamp), 0) - side := exchange.OrderSide(strings.ToUpper(order.Type)) - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.OrderID), - Amount: order.Amount, - Price: order.Rate, + orderDate := time.Unix(int64(allOrders[i].Timestamp), 0) + side := order.Side(strings.ToUpper(allOrders[i].Type)) + orders = append(orders, order.Detail{ + ID: strconv.FormatFloat(allOrders[i].OrderID, 'f', -1, 64), + Amount: allOrders[i].Amount, + Price: allOrders[i].Rate, OrderSide: side, OrderDate: orderDate, CurrencyPair: symbol, @@ -504,7 +510,7 @@ func (y *Yobit) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([] }) } - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index bbd77cc3..4c22f6a7 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -284,8 +285,8 @@ func TestGetActiveOrders(t *testing.T) { z.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)}, } @@ -302,9 +303,9 @@ func TestGetOrderHistory(t *testing.T) { z.SetDefaults() TestSetup(t) - var getOrdersRequest = exchange.GetOrdersRequest{ - OrderType: exchange.AnyOrderType, - OrderSide: exchange.BuyOrderSide, + var getOrdersRequest = order.GetOrdersRequest{ + OrderType: order.AnyType, + OrderSide: order.Buy, Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)}, } @@ -332,14 +333,14 @@ func TestSubmitOrder(t *testing.T) { canManipulateRealOrders)) } - var orderSubmission = &exchange.OrderSubmission{ + var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", Base: currency.QTUM, Quote: currency.USD, }, - OrderSide: exchange.BuyOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Buy, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", @@ -362,7 +363,7 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -388,7 +389,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &exchange.OrderCancellation{ + var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", AccountID: "1", @@ -404,8 +405,8 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Errorf("Could not cancel orders: %v", err) } - if len(resp.OrderStatus) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.OrderStatus)) + if len(resp.Status) > 0 { + t.Errorf("%v orders failed to cancel", len(resp.Status)) } } @@ -424,7 +425,7 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { - _, err := z.ModifyOrder(&exchange.ModifyOrder{}) + _, err := z.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") } diff --git a/exchanges/zb/zb_types.go b/exchanges/zb/zb_types.go index c1613da6..6953e841 100644 --- a/exchanges/zb/zb_types.go +++ b/exchanges/zb/zb_types.go @@ -4,7 +4,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // OrderbookResponse holds the orderbook data for a symbol @@ -241,7 +241,7 @@ var WithdrawalFees = map[currency.Code]float64{ } // orderSideMap holds order type info based on Alphapoint data -var orderSideMap = map[int64]exchange.OrderSide{ - 0: exchange.BuyOrderSide, - 1: exchange.SellOrderSide, +var orderSideMap = map[int64]order.Side{ + 0: order.Buy, + 1: order.Sell, } diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 1b56dc54..59cede51 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -344,27 +345,23 @@ func (z *ZB) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchan } // SubmitOrder submits a new order -func (z *ZB) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderResponse, error) { - var submitOrderResponse exchange.SubmitOrderResponse - if order == nil { - return submitOrderResponse, exchange.ErrOrderSubmissionIsNil - } - - if err := order.Validate(); err != nil { +func (z *ZB) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { return submitOrderResponse, err } var oT SpotNewOrderRequestParamsType - if order.OrderSide == exchange.BuyOrderSide { + if s.OrderSide == order.Buy { oT = SpotNewOrderRequestParamsTypeBuy } else { oT = SpotNewOrderRequestParamsTypeSell } var params = SpotNewOrderRequestParams{ - Amount: order.Amount, - Price: order.Price, - Symbol: order.Pair.Lower().String(), + Amount: s.Amount, + Price: s.Price, + Symbol: s.Pair.Lower().String(), Type: oT, } response, err := z.SpotNewOrder(params) @@ -379,32 +376,34 @@ func (z *ZB) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrderR // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (z *ZB) ModifyOrder(action *exchange.ModifyOrder) (string, error) { +func (z *ZB) ModifyOrder(action *order.Modify) (string, error) { return "", common.ErrFunctionNotSupported } // CancelOrder cancels an order by its corresponding ID number -func (z *ZB) CancelOrder(order *exchange.OrderCancellation) error { - orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) - +func (z *ZB) CancelOrder(o *order.Cancel) error { + orderIDInt, err := strconv.ParseInt(o.OrderID, 10, 64) if err != nil { return err } - - return z.CancelExistingOrder(orderIDInt, z.FormatExchangeCurrency(order.CurrencyPair, - order.AssetType).String()) + curr := z.FormatExchangeCurrency(o.CurrencyPair, o.AssetType).String() + return z.CancelExistingOrder(orderIDInt, curr) } // CancelAllOrders cancels all orders associated with a currency pair -func (z *ZB) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) { - cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{ - OrderStatus: make(map[string]string), +func (z *ZB) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { + cancelAllOrdersResponse := order.CancelAllResponse{ + Status: make(map[string]string), } var allOpenOrders []Order - for _, currency := range z.GetEnabledPairs(asset.Spot) { + enabledPairs := z.GetEnabledPairs(asset.Spot) + for x := range enabledPairs { // Limiting to 10 pages - for i := 0; i < 10; i++ { - openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(z.FormatExchangeCurrency(currency, asset.Spot).String(), 1, 10) + for pageNumber := int64(0); pageNumber < 11; pageNumber++ { + fCurr := z.FormatExchangeCurrency(enabledPairs[x], asset.Spot).String() + openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(fCurr, + pageNumber, + 10) if err != nil { return cancelAllOrdersResponse, err } @@ -417,10 +416,12 @@ func (z *ZB) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllO } } - for _, openOrder := range allOpenOrders { - err := z.CancelExistingOrder(openOrder.ID, openOrder.Currency) + for i := range allOpenOrders { + err := z.CancelExistingOrder(allOpenOrders[i].ID, + allOpenOrders[i].Currency) if err != nil { - cancelAllOrdersResponse.OrderStatus[strconv.FormatInt(openOrder.ID, 10)] = err.Error() + ID := strconv.FormatInt(allOpenOrders[i].ID, 10) + cancelAllOrdersResponse.Status[ID] = err.Error() } } @@ -428,8 +429,8 @@ func (z *ZB) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.CancelAllO } // GetOrderInfo returns information on a current open order -func (z *ZB) GetOrderInfo(orderID string) (exchange.OrderDetail, error) { - var orderDetail exchange.OrderDetail +func (z *ZB) GetOrderInfo(orderID string) (order.Detail, error) { + var orderDetail order.Detail return orderDetail, common.ErrNotYetImplemented } @@ -477,13 +478,15 @@ func (z *ZB) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { // GetActiveOrders retrieves any orders that are active/open // This function is not concurrency safe due to orderSide/orderType maps -func (z *ZB) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { +func (z *ZB) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { var allOrders []Order - for _, currency := range getOrdersRequest.Currencies { - var pageNumber int64 + for x := range req.Currencies { // Limiting to 10 pages - for i := 0; i < 10; i++ { - resp, err := z.GetUnfinishedOrdersIgnoreTradeType(z.FormatExchangeCurrency(currency, asset.Spot).String(), pageNumber, 10) + for pageNumber := int64(0); pageNumber < 11; pageNumber++ { + fCurr := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() + resp, err := z.GetUnfinishedOrdersIgnoreTradeType(fCurr, + pageNumber, + 10) if err != nil { return nil, err } @@ -492,53 +495,51 @@ func (z *ZB) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exc } allOrders = append(allOrders, resp...) - pageNumber++ } } - var orders []exchange.OrderDetail - for _, order := range allOrders { - symbol := currency.NewPairDelimiter(order.Currency, + var orders []order.Detail + for i := range allOrders { + symbol := currency.NewPairDelimiter(allOrders[i].Currency, z.GetPairFormat(asset.Spot, false).Delimiter) - orderDate := time.Unix(int64(order.TradeDate), 0) - orderSide := orderSideMap[order.Type] - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.ID), - Amount: order.TotalAmount, + orderDate := time.Unix(int64(allOrders[i].TradeDate), 0) + orderSide := orderSideMap[allOrders[i].Type] + orders = append(orders, order.Detail{ + ID: fmt.Sprintf("%d", allOrders[i].ID), + Amount: allOrders[i].TotalAmount, Exchange: z.Name, OrderDate: orderDate, - Price: order.Price, + Price: allOrders[i].Price, OrderSide: orderSide, CurrencyPair: symbol, }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, - getOrdersRequest.EndTicks) - exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status // This function is not concurrency safe due to orderSide/orderType maps -func (z *ZB) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) { - if getOrdersRequest.OrderSide == exchange.AnyOrderSide || getOrdersRequest.OrderSide == "" { +func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + if req.OrderSide == order.AnySide || req.OrderSide == "" { return nil, errors.New("specific order side is required") } var allOrders []Order var side int64 - if getOrdersRequest.OrderSide == exchange.BuyOrderSide { + if req.OrderSide == order.Buy { side = 1 } - for _, currency := range getOrdersRequest.Currencies { - var pageNumber int64 + for x := range req.Currencies { // Limiting to 10 pages - for i := 0; i < 10; i++ { - resp, err := z.GetOrders(z.FormatExchangeCurrency(currency, asset.Spot).String(), pageNumber, side) + for pageNumber := int64(0); pageNumber < 11; pageNumber++ { + fCurr := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() + resp, err := z.GetOrders(fCurr, pageNumber, side) if err != nil { return nil, err } @@ -548,28 +549,27 @@ func (z *ZB) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exc } allOrders = append(allOrders, resp...) - pageNumber++ } } - var orders []exchange.OrderDetail - for _, order := range allOrders { - symbol := currency.NewPairDelimiter(order.Currency, + var orders []order.Detail + for i := range allOrders { + symbol := currency.NewPairDelimiter(allOrders[i].Currency, z.GetPairFormat(asset.Spot, false).Delimiter) - orderDate := time.Unix(int64(order.TradeDate), 0) - orderSide := orderSideMap[order.Type] - orders = append(orders, exchange.OrderDetail{ - ID: fmt.Sprintf("%v", order.ID), - Amount: order.TotalAmount, + orderDate := time.Unix(int64(allOrders[i].TradeDate), 0) + orderSide := orderSideMap[allOrders[i].Type] + orders = append(orders, order.Detail{ + ID: fmt.Sprintf("%d", allOrders[i].ID), + Amount: allOrders[i].TotalAmount, Exchange: z.Name, OrderDate: orderDate, - Price: order.Price, + Price: allOrders[i].Price, OrderSide: orderSide, CurrencyPair: symbol, }) } - exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks) + order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) return orders, nil } From 84734dbeb7cdbb9d35b6ffe0d852471424c6656f Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 31 Oct 2019 11:59:42 +1100 Subject: [PATCH 58/71] Add in types for order fields (#373) --- exchanges/order/order_types.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 43eeb5c4..02eb6d8e 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -106,14 +106,14 @@ const ( // Detail holds order detail data type Detail struct { - Exchange string - AccountID string - ID string - CurrencyPair currency.Pair - OrderSide Side - OrderType Type - OrderDate time.Time - Status + Exchange string + AccountID string + ID string + CurrencyPair currency.Pair + OrderSide Side + OrderType Type + OrderDate time.Time + Status Status Price float64 Amount float64 ExecutedAmount float64 @@ -124,13 +124,13 @@ type Detail struct { // TradeHistory holds exchange history data type TradeHistory struct { - Timestamp time.Time - TID int64 - Price float64 - Amount float64 - Exchange string - Type - Side + Timestamp time.Time + TID int64 + Price float64 + Amount float64 + Exchange string + Type Type + Side Side Fee float64 Description string } @@ -142,7 +142,7 @@ type Cancel struct { CurrencyPair currency.Pair AssetType asset.Item WalletAddress string - Side + Side Side } // GetOrdersRequest used for GetOrderHistory and GetOpenOrders wrapper functions From 22ff33cd54796e2b897a9191098c51c8341b82d8 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Mon, 4 Nov 2019 15:34:30 +1100 Subject: [PATCH 59/71] Engine QA (#367) * Improved error message when no config is set on startup * Change inccorect error wording * bump Bitfinex websocket orderbook return length to max * temporary fix of incorrect orderbook updates, limit to bid and ask len of 100, will be extended later if needed * Fixed issue in binance websocket that appended 0 volume bid/ask items * Fix panic when unmarshalling an empty pair from config * Add get pair asset method for exchange base Fix Bitmex orderbook stream Unbuffer Bitmex orderbook stream * force syncer to update ticker instead of fetch, which allows a stream * Fix websocket last price for coinbasepro * fix websocket ticker for coinut * Fix websocket orderbook stream Huobi * increase orderbook depth REST for Huobi * Fix websocket support and ensure data integrity * Fix time parsing issue after error checks * check error, only process enabled currency pairs, signal websocket data processing * expanded websocket functionality for okgroup * Add logic to not process zero length slice for orderbooks * fix websocket ticker only updating enabled and individual book updates * ZB fixes to order submission/retrieval/cancellation w/ general fixes * Quiet unnecessary warning * updated config entry values for REST and websocket (initial hack until I come up with a better solution for asset types) * Ch GetName function to field access modifyer & rm useless code * Add in error I missed * Nits addressed * some more fixes * Turned kraken default websocket to true and some small changes * fixes linter issues * Ensured okgroup books and sent update through to datahandler. Zb update as well. * Add test case to get asset type from pair * Add test for pairs unmarshal * Add testing and addressed nits * FIX linter issue * Addressed Gees nits * Thanks glorious spotter * more nitorinos * Addres even more nits * Add stringerino 4000 * Fix for panic cause by sort slice out of range, also nits addressed * fix linter issues * Changed from function to field access * Changed from function to field access * fix for orderbook update panic, removes quick fix - caused by sync item fetching through same protocol * Add new test and update random generator * pass in invalid string to future ob fetching, due to futures contract expire and a http 400 error is returned --- config/config.go | 3 +- currency/pairs.go | 5 + currency/pairs_test.go | 19 +- engine/syncer.go | 2 +- exchanges/binance/binance_types.go | 14 +- exchanges/binance/binance_websocket.go | 58 +- exchanges/binance/binance_wrapper.go | 10 +- exchanges/bitfinex/bitfinex_websocket.go | 9 +- exchanges/bitmex/bitmex_websocket.go | 47 +- exchanges/bitmex/bitmex_wrapper.go | 2 +- exchanges/bitstamp/bitstamp_websocket.go | 82 ++- exchanges/bitstamp/bitstamp_wrapper.go | 7 - exchanges/bittrex/bittrex_wrapper.go | 2 +- .../coinbasepro/coinbasepro_websocket.go | 13 +- exchanges/coinut/coinut_types.go | 25 +- exchanges/coinut/coinut_websocket.go | 17 +- exchanges/coinut/coinut_wrapper.go | 6 +- exchanges/exchange.go | 10 + exchanges/exchange_test.go | 26 + exchanges/gateio/gateio_websocket.go | 10 +- exchanges/gemini/gemini_websocket.go | 10 +- exchanges/hitbtc/hitbtc_websocket.go | 10 +- exchanges/huobi/huobi_types.go | 8 +- exchanges/huobi/huobi_websocket.go | 37 +- exchanges/huobi/huobi_wrapper.go | 2 +- exchanges/kraken/kraken_websocket.go | 332 +++++++---- exchanges/kraken/kraken_wrapper.go | 2 +- exchanges/lakebtc/lakebtc.go | 33 +- exchanges/lakebtc/lakebtc_websocket.go | 41 +- exchanges/lakebtc/lakebtc_wrapper.go | 27 +- exchanges/okcoin/okcoin_test.go | 12 - exchanges/okcoin/okcoin_wrapper.go | 19 +- exchanges/okex/okex.go | 73 +-- exchanges/okex/okex_test.go | 38 -- exchanges/okex/okex_wrapper.go | 253 ++++++--- exchanges/okgroup/okgroup.go | 35 +- exchanges/okgroup/okgroup_test.go | 78 +++ exchanges/okgroup/okgroup_types.go | 61 +- exchanges/okgroup/okgroup_websocket.go | 536 ++++++++++++------ exchanges/okgroup/okgroup_wrapper.go | 80 ++- exchanges/orderbook/orderbook.go | 12 +- exchanges/orderbook/orderbook_types.go | 4 + exchanges/poloniex/poloniex_test.go | 2 +- exchanges/poloniex/poloniex_websocket.go | 171 ++++-- exchanges/poloniex/poloniex_wrapper.go | 6 +- exchanges/request/request.go | 9 +- .../websocket/wsorderbook/wsorderbook.go | 163 +++--- .../websocket/wsorderbook/wsorderbook_test.go | 342 ++++++----- .../wsorderbook/wsorderbook_types.go | 14 +- exchanges/zb/zb_test.go | 13 +- exchanges/zb/zb_websocket.go | 14 +- exchanges/zb/zb_websocket_types.go | 18 +- exchanges/zb/zb_wrapper.go | 65 ++- 53 files changed, 1813 insertions(+), 1074 deletions(-) create mode 100644 exchanges/okgroup/okgroup_test.go diff --git a/config/config.go b/config/config.go index 52373774..d720beb4 100644 --- a/config/config.go +++ b/config/config.go @@ -1433,7 +1433,8 @@ func GetFilePath(file string) (string, error) { return newDirs[0], nil } - return "", errors.New("config default file path error") + return "", fmt.Errorf("config.json file not found in %s, please follow README.md in root dir for config generation", + newDir) } // ReadConfig verifies and checks for encryption and verifies the unencrypted diff --git a/currency/pairs.go b/currency/pairs.go index a6855e87..f383f99d 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -73,6 +73,11 @@ func (p *Pairs) UnmarshalJSON(d []byte) error { return err } + // If no pairs enabled in config just continue + if pairs == "" { + return nil + } + var allThePairs Pairs for _, data := range strings.Split(pairs, ",") { allThePairs = append(allThePairs, NewPairFromString(data)) diff --git a/currency/pairs_test.go b/currency/pairs_test.go index bd52814d..d58d57d1 100644 --- a/currency/pairs_test.go +++ b/currency/pairs_test.go @@ -68,13 +68,28 @@ func TestPairsFormat(t *testing.T) { func TestPairsUnmarshalJSON(t *testing.T) { var unmarshalHere Pairs - configPairs := "btc_usd,btc_aud,btc_ltc" - + configPairs := "" encoded, err := common.JSONEncode(configPairs) if err != nil { t.Fatal("Pairs UnmarshalJSON() error", err) } + err = common.JSONDecode([]byte{1, 3, 3, 7}, &unmarshalHere) + if err == nil { + t.Fatal("error cannot be nil") + } + + err = common.JSONDecode(encoded, &unmarshalHere) + if err != nil { + t.Fatal("Pairs UnmarshalJSON() error", err) + } + + configPairs = "btc_usd,btc_aud,btc_ltc" + encoded, err = common.JSONEncode(configPairs) + if err != nil { + t.Fatal("Pairs UnmarshalJSON() error", err) + } + err = common.JSONDecode(encoded, &unmarshalHere) if err != nil { t.Fatal("Pairs UnmarshalJSON() error", err) diff --git a/engine/syncer.go b/engine/syncer.go index 598661f5..8a944cb7 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -400,7 +400,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { result, err = Bot.Exchanges[x].FetchTicker(c.Pair, c.AssetType) } } else { - result, err = Bot.Exchanges[x].FetchTicker(c.Pair, c.AssetType) + result, err = Bot.Exchanges[x].UpdateTicker(c.Pair, c.AssetType) } printTickerSummary(&result, c.Pair, c.AssetType, exchangeName, err) if err == nil { diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index 30718199..e3bd8125 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -92,13 +92,13 @@ type DepthUpdateParams []struct { // WebsocketDepthStream is the difference for the update depth stream type WebsocketDepthStream struct { - Event string `json:"e"` - Timestamp int64 `json:"E"` - Pair string `json:"s"` - FirstUpdateID int64 `json:"U"` - LastUpdateID int64 `json:"u"` - UpdateBids []interface{} `json:"b"` - UpdateAsks []interface{} `json:"a"` + Event string `json:"e"` + Timestamp int64 `json:"E"` + Pair string `json:"s"` + FirstUpdateID int64 `json:"U"` + LastUpdateID int64 `json:"u"` + UpdateBids [][]interface{} `json:"b"` + UpdateAsks [][]interface{} `json:"a"` } // RecentTradeRequestParams represents Klines request data. diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index 72715f16..9b2ab70b 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -236,12 +236,16 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error { } for i := range orderbookNew.Bids { - newOrderBook.Bids = append(newOrderBook.Bids, - orderbook.Item{Amount: orderbookNew.Bids[i].Quantity, Price: orderbookNew.Bids[i].Price}) + newOrderBook.Bids = append(newOrderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[i].Quantity, + Price: orderbookNew.Bids[i].Price, + }) } for i := range orderbookNew.Asks { - newOrderBook.Asks = append(newOrderBook.Asks, - orderbook.Item{Amount: orderbookNew.Asks[i].Quantity, Price: orderbookNew.Asks[i].Price}) + newOrderBook.Asks = append(newOrderBook.Asks, orderbook.Item{ + Amount: orderbookNew.Asks[i].Quantity, + Price: orderbookNew.Asks[i].Price, + }) } newOrderBook.LastUpdated = time.Unix(orderbookNew.LastUpdateID, 0) @@ -256,38 +260,38 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error { func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error { var updateBid, updateAsk []orderbook.Item for i := range wsdp.UpdateBids { - var priceToBeUpdated orderbook.Item - for i, bids := range wsdp.UpdateBids[i].([]interface{}) { - switch i { - case 0: - priceToBeUpdated.Price, _ = strconv.ParseFloat(bids.(string), 64) - case 1: - priceToBeUpdated.Amount, _ = strconv.ParseFloat(bids.(string), 64) - } + p, err := strconv.ParseFloat(wsdp.UpdateBids[i][0].(string), 64) + if err != nil { + return err } - updateBid = append(updateBid, priceToBeUpdated) + a, err := strconv.ParseFloat(wsdp.UpdateBids[i][1].(string), 64) + if err != nil { + return err + } + + updateBid = append(updateBid, orderbook.Item{Price: p, Amount: a}) } for i := range wsdp.UpdateAsks { - var priceToBeUpdated orderbook.Item - for i, asks := range wsdp.UpdateAsks[i].([]interface{}) { - switch i { - case 0: - priceToBeUpdated.Price, _ = strconv.ParseFloat(asks.(string), 64) - case 1: - priceToBeUpdated.Amount, _ = strconv.ParseFloat(asks.(string), 64) - } + p, err := strconv.ParseFloat(wsdp.UpdateAsks[i][0].(string), 64) + if err != nil { + return err } - updateAsk = append(updateAsk, priceToBeUpdated) + a, err := strconv.ParseFloat(wsdp.UpdateAsks[i][1].(string), 64) + if err != nil { + return err + } + + updateAsk = append(updateAsk, orderbook.Item{Price: p, Amount: a}) } currencyPair := currency.NewPairFromFormattedPairs(wsdp.Pair, b.GetEnabledPairs(asset.Spot), b.GetPairFormat(asset.Spot, true)) return b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ - Bids: updateBid, - Asks: updateAsk, - CurrencyPair: currencyPair, - UpdateID: wsdp.LastUpdateID, - AssetType: asset.Spot, + Bids: updateBid, + Asks: updateAsk, + Pair: currencyPair, + UpdateID: wsdp.LastUpdateID, + Asset: asset.Spot, }) } diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index e30234ac..65c1d515 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -162,7 +162,7 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) error { b.Websocket.Orderbook.Setup( exch.WebsocketOrderbookBufferLimit, - true, + false, true, true, false, @@ -202,7 +202,8 @@ func (b *Binance) Run() { if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", - b.Name, err) + b.Name, + err) } } @@ -247,7 +248,10 @@ func (b *Binance) UpdateTradablePairs(forceUpdate bool) error { return err } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + return b.UpdatePairs(currency.NewPairsFromStrings(pairs), + asset.Spot, + false, + forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index b0381e36..80b3541d 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -498,10 +498,10 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books // orderbook sides func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book []WebsocketBook) error { orderbookUpdate := wsorderbook.WebsocketOrderbookUpdate{ - Asks: []orderbook.Item{}, - Bids: []orderbook.Item{}, - AssetType: assetType, - CurrencyPair: p, + Asks: []orderbook.Item{}, + Bids: []orderbook.Item{}, + Asset: assetType, + Pair: p, } for i := 0; i < len(book); i++ { @@ -548,6 +548,7 @@ func (b *Bitfinex) GenerateDefaultSubscriptions() { params := make(map[string]interface{}) if channels[i] == "book" { params["prec"] = "P0" + params["len"] = "100" } subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ Channel: channels[i], diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index bb77a528..603cbe46 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -206,8 +206,17 @@ func (b *Bitmex) wsHandleIncomingData() { } p := currency.NewPairFromString(orderbooks.Data[0].Symbol) - // TODO: update this to support multiple asset types - err = b.processOrderbook(orderbooks.Data, orderbooks.Action, p, "CONTRACT") + var a asset.Item + a, err = b.GetPairAssetType(p) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + + err = b.processOrderbook(orderbooks.Data, + orderbooks.Action, + p, + a) if err != nil { b.Websocket.DataHandler <- err continue @@ -345,12 +354,14 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai asks = append(asks, orderbook.Item{ Price: data[i].Price, Amount: float64(data[i].Size), + ID: data[i].ID, }) continue } bids = append(bids, orderbook.Item{ Price: data[i].Price, Amount: float64(data[i].Size), + ID: data[i].ID, }) } @@ -379,24 +390,23 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai for i := range data { if strings.EqualFold(data[i].Side, "Sell") { asks = append(asks, orderbook.Item{ - Price: data[i].Price, Amount: float64(data[i].Size), + ID: data[i].ID, }) continue } bids = append(bids, orderbook.Item{ - Price: data[i].Price, Amount: float64(data[i].Size), + ID: data[i].ID, }) } err := b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: currencyPair, - UpdateTime: time.Now(), - AssetType: assetType, - Action: action, + Bids: bids, + Asks: asks, + Pair: currencyPair, + Asset: assetType, + Action: action, }) if err != nil { return err @@ -413,7 +423,16 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (b *Bitmex) GenerateDefaultSubscriptions() { - contracts := b.GetEnabledPairs(asset.PerpetualContract) + assets := b.GetAssetTypes() + var allPairs currency.Pairs + + for x := range assets { + contracts := b.GetEnabledPairs(assets[x]) + for y := range contracts { + allPairs = allPairs.Add(contracts[y]) + } + } + channels := []string{bitmexWSOrderbookL2, bitmexWSTrade} subscriptions := []wshandler.WebsocketChannelSubscription{ { @@ -422,10 +441,10 @@ func (b *Bitmex) GenerateDefaultSubscriptions() { } for i := range channels { - for j := range contracts { + for j := range allPairs { subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: fmt.Sprintf("%v:%v", channels[i], contracts[j].String()), - Currency: contracts[j], + Channel: fmt.Sprintf("%v:%v", channels[i], allPairs[j].String()), + Currency: allPairs[j], }) } } diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index d7968829..3f44db1a 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -188,7 +188,7 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) error { b.Websocket.Orderbook.Setup( exch.WebsocketOrderbookBufferLimit, - true, + false, false, false, true, diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 67d6c619..9cccbb62 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/common" @@ -13,7 +14,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -32,7 +32,7 @@ func (b *Bitstamp) WsConnect() error { return err } if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.GetName()) + log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name) } err = b.seedOrderBook() @@ -76,7 +76,7 @@ func (b *Bitstamp) WsHandleData() { switch wsResponse.Event { case "bts:request_reconnect": if b.Verbose { - log.Debugf(log.ExchangeSys, "%v - Websocket reconnection request received", b.GetName()) + log.Debugf(log.ExchangeSys, "%v - Websocket reconnection request received", b.Name) } go b.Websocket.Shutdown() // Connection monitor will reconnect @@ -89,7 +89,7 @@ func (b *Bitstamp) WsHandleData() { } currencyPair := strings.Split(wsResponse.Channel, "_") - p := currency.NewPairFromString(strings.ToUpper(currencyPair[3])) + p := currency.NewPairFromString(strings.ToUpper(currencyPair[2])) err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, asset.Spot) if err != nil { @@ -113,7 +113,7 @@ func (b *Bitstamp) WsHandleData() { Price: wsTradeTemp.Data.Price, Amount: wsTradeTemp.Data.Amount, CurrencyPair: p, - Exchange: b.GetName(), + Exchange: b.Name, AssetType: asset.Spot, } } @@ -122,7 +122,7 @@ func (b *Bitstamp) WsHandleData() { } func (b *Bitstamp) generateDefaultSubscriptions() { - var channels = []string{"live_trades_", "diff_order_book_"} + var channels = []string{"live_trades_", "order_book_"} enabledCurrencies := b.GetEnabledPairs(asset.Spot) var subscriptions []wshandler.WebsocketChannelSubscription for i := range channels { @@ -163,47 +163,45 @@ func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair, } var asks, bids []orderbook.Item - if len(update.Asks) > 0 { - for i := range update.Asks { - target, err := strconv.ParseFloat(update.Asks[i][0], 64) - if err != nil { - b.Websocket.DataHandler <- err - continue - } - - amount, err := strconv.ParseFloat(update.Asks[i][1], 64) - if err != nil { - b.Websocket.DataHandler <- err - continue - } - - asks = append(asks, orderbook.Item{Price: target, Amount: amount}) + for i := range update.Asks { + target, err := strconv.ParseFloat(update.Asks[i][0], 64) + if err != nil { + b.Websocket.DataHandler <- err + continue } + + amount, err := strconv.ParseFloat(update.Asks[i][1], 64) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + + asks = append(asks, orderbook.Item{Price: target, Amount: amount}) } - if len(update.Bids) > 0 { - for i := range update.Bids { - target, err := strconv.ParseFloat(update.Bids[i][0], 64) - if err != nil { - b.Websocket.DataHandler <- err - continue - } - - amount, err := strconv.ParseFloat(update.Bids[i][1], 64) - if err != nil { - b.Websocket.DataHandler <- err - continue - } - - bids = append(bids, orderbook.Item{Price: target, Amount: amount}) + for i := range update.Bids { + target, err := strconv.ParseFloat(update.Bids[i][0], 64) + if err != nil { + b.Websocket.DataHandler <- err + continue } + + amount, err := strconv.ParseFloat(update.Bids[i][1], 64) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + + bids = append(bids, orderbook.Item{Price: target, Amount: amount}) } - err := b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ + + err := b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{ Bids: bids, Asks: asks, - CurrencyPair: p, - UpdateID: update.Timestamp, + Pair: p, + LastUpdated: time.Unix(update.Timestamp, 0), AssetType: asset.Spot, + ExchangeName: b.Name, }) if err != nil { return err @@ -212,7 +210,7 @@ func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair, b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: p, Asset: assetType, - Exchange: b.GetName(), + Exchange: b.Name, } return nil @@ -247,7 +245,7 @@ func (b *Bitstamp) seedOrderBook() error { newOrderBook.Bids = bids newOrderBook.Pair = p[x] newOrderBook.AssetType = asset.Spot - newOrderBook.ExchangeName = b.GetName() + newOrderBook.ExchangeName = b.Name err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { @@ -257,7 +255,7 @@ func (b *Bitstamp) seedOrderBook() error { b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: p[x], Asset: asset.Spot, - Exchange: b.GetName(), + Exchange: b.Name, } } return nil diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index b04dc4d0..50a0fd69 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -159,13 +159,6 @@ func (b *Bitstamp) Setup(exch *config.ExchangeConfig) error { ResponseMaxLimit: exch.WebsocketResponseMaxLimit, } - b.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - true, - true, - true, - false, - exch.Name) return nil } diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 45b1a5ea..07cc5f13 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -329,7 +329,7 @@ func (b *Bittrex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { if s.OrderType != order.Limit { return submitOrderResponse, - errors.New("limit order not supported on exchange") + errors.New("limit orders only supported on exchange") } var response UUID diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 9d151d10..a94f9cda 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -97,11 +97,10 @@ func (c *CoinbasePro) WsHandleData() { Open: ticker.Open24H, High: ticker.High24H, Low: ticker.Low24H, - Close: ticker.Price, + Last: ticker.Price, Volume: ticker.Volume24H, Bid: ticker.BestBid, Ask: ticker.BestAsk, - Last: ticker.LastSize, } case "snapshot": @@ -258,11 +257,11 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { return err } err = c.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: p, - UpdateTime: timestamp, - AssetType: asset.Spot, + Bids: bids, + Asks: asks, + Pair: p, + UpdateTime: timestamp, + Asset: asset.Spot, }) if err != nil { return err diff --git a/exchanges/coinut/coinut_types.go b/exchanges/coinut/coinut_types.go index 2540e6a1..b42d3425 100644 --- a/exchanges/coinut/coinut_types.go +++ b/exchanges/coinut/coinut_types.go @@ -31,15 +31,22 @@ type Instruments struct { // Ticker holds ticker information type Ticker struct { - HighestBuy float64 `json:"highest_buy,string"` - InstrumentID int `json:"inst_id"` - Last float64 `json:"last,string"` - LowestSell float64 `json:"lowest_sell,string"` - OpenInterest float64 `json:"open_interest,string"` - Timestamp int64 `json:"timestamp"` - TransID int64 `json:"trans_id"` - Volume float64 `json:"volume,string"` - Volume24 float64 `json:"volume24,string"` + High24 float64 `json:"high24,string"` + HighestBuy float64 `json:"highest_buy,string"` + InstrumentID int `json:"inst_id"` + Last float64 `json:"last,string"` + Low24 float64 `json:"low24,string"` + LowestSell float64 `json:"lowest_sell,string"` + PrevTransID int64 `json:"prev_trans_id"` + PriceChange24 float64 `json:"price_change_24,string"` + Reply string `json:"reply"` + OpenInterest float64 `json:"open_interest,string"` + Timestamp int64 `json:"timestamp"` + TransID int64 `json:"trans_id"` + Volume float64 `json:"volume,string"` + Volume24 float64 `json:"volume24,string"` + Volume24Quote float64 `json:"volume24_quote,string"` + VolumeQuote float64 `json:"volume_quote,string"` } // OrderbookBase is a sub-type holding price and quantity diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index ff30766a..977e41c2 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -136,13 +136,16 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } + currencyPair := wsInstrumentMap.LookupInstrument(ticker.InstID) c.Websocket.DataHandler <- wshandler.TickerData{ Exchange: c.Name, - Volume: ticker.Volume, - QuoteVolume: ticker.VolumeQuote, - High: ticker.HighestBuy, - Low: ticker.LowestSell, + Volume: ticker.Volume24, + QuoteVolume: ticker.Volume24Quote, + Bid: ticker.HighestBuy, + Ask: ticker.LowestSell, + High: ticker.High24, + Low: ticker.Low24, Last: ticker.Last, Timestamp: time.Unix(0, ticker.Timestamp), AssetType: asset.Spot, @@ -302,9 +305,9 @@ func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error { c.GetPairFormat(asset.Spot, true), ) bufferUpdate := &wsorderbook.WebsocketOrderbookUpdate{ - CurrencyPair: p, - UpdateID: update.TransID, - AssetType: asset.Spot, + Pair: p, + UpdateID: update.TransID, + Asset: asset.Spot, } if strings.EqualFold(update.Side, order.Buy.Lower()) { bufferUpdate.Bids = []orderbook.Item{{Price: update.Price, Amount: update.Volume}} diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 14c94edc..501000d2 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -342,8 +342,10 @@ func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri } tickerPrice = ticker.Price{ Last: tick.Last, - High: tick.HighestBuy, - Low: tick.LowestSell, + High: tick.High24, + Low: tick.Low24, + Bid: tick.HighestBuy, + Ask: tick.LowestSell, Volume: tick.Volume24, Pair: p, LastUpdated: time.Unix(0, tick.Timestamp), diff --git a/exchanges/exchange.go b/exchanges/exchange.go index a5376011..37eabdc7 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -230,6 +230,16 @@ func (e *Base) GetAssetTypes() asset.Items { return e.CurrencyPairs.AssetTypes } +// GetPairAssetType returns the associated asset type for the currency pair +func (e *Base) GetPairAssetType(c currency.Pair) (asset.Item, error) { + for i := range e.GetAssetTypes() { + if e.GetEnabledPairs(e.GetAssetTypes()[i]).Contains(c, true) { + return e.GetAssetTypes()[i], nil + } + } + return "", errors.New("asset type not associated with currency pair") +} + // GetClientBankAccounts returns banking details associated with // a client for withdrawal purposes func (e *Base) GetClientBankAccounts(exchangeName, withdrawalCurrency string) (config.BankAccount, error) { diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index f9a9ad55..ae1c53ea 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -1357,3 +1357,29 @@ func TestGetBase(t *testing.T) { t.Error("name should be rawr") } } + +func TestGetAssetType(t *testing.T) { + var b Base + p := currency.NewPair(currency.BTC, currency.USD) + _, err := b.GetPairAssetType(p) + if err == nil { + t.Fatal("error cannot be nil") + } + b.CurrencyPairs.AssetTypes = asset.Items{asset.Spot} + b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) + b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + Enabled: currency.Pairs{ + currency.NewPair(currency.BTC, currency.USD), + }, + ConfigFormat: ¤cy.PairFormat{Delimiter: "-"}, + } + + a, err := b.GetPairAssetType(p) + if err != nil { + t.Fatal(err) + } + + if a != asset.Spot { + t.Error("should be spot but is", a) + } +} diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index fe8d5e83..49d4221e 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -246,11 +246,11 @@ func (g *Gateio) WsHandleData() { } else { err = g.Websocket.Orderbook.Update( &wsorderbook.WebsocketOrderbookUpdate{ - Asks: asks, - Bids: bids, - CurrencyPair: currency.NewPairFromString(c), - UpdateTime: time.Now(), - AssetType: asset.Spot, + Asks: asks, + Bids: bids, + Pair: currency.NewPairFromString(c), + UpdateTime: time.Now(), + Asset: asset.Spot, }) if err != nil { g.Websocket.DataHandler <- err diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 089f9fff..681d839c 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -319,11 +319,11 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa } } err := g.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ - Asks: asks, - Bids: bids, - CurrencyPair: pair, - UpdateTime: time.Unix(0, result.TimestampMS), - AssetType: asset.Spot, + Asks: asks, + Bids: bids, + Pair: pair, + UpdateTime: time.Unix(0, result.TimestampMS), + Asset: asset.Spot, }) if err != nil { g.Websocket.DataHandler <- fmt.Errorf("%v %v", g.Name, err) diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index f8cfe0c3..5726ca8a 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -284,11 +284,11 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error { p := currency.NewPairFromFormattedPairs(update.Params.Symbol, h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)) err := h.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ - Asks: asks, - Bids: bids, - CurrencyPair: p, - UpdateID: update.Params.Sequence, - AssetType: asset.Spot, + Asks: asks, + Bids: bids, + Pair: p, + UpdateID: update.Params.Sequence, + Asset: asset.Spot, }) if err != nil { return err diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index 7dd12b22..b7ea87db 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -320,10 +320,10 @@ type WsDepth struct { Channel string `json:"ch"` Timestamp int64 `json:"ts"` Tick struct { - Bids []interface{} `json:"bids"` - Asks []interface{} `json:"asks"` - Timestamp int64 `json:"ts"` - Version int64 `json:"version"` + Bids [][]interface{} `json:"bids"` + Asks [][]interface{} `json:"asks"` + Timestamp int64 `json:"ts"` + Version int64 `json:"version"` } `json:"tick"` } diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index fb5897ec..e6675735 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -233,8 +233,14 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { h.Websocket.DataHandler <- err return } + data := strings.Split(depth.Channel, ".") - h.WsProcessOrderbook(&depth, data[1]) + err = h.WsProcessOrderbook(&depth, data[1]) + if err != nil { + h.Websocket.DataHandler <- err + return + } + case strings.Contains(init.Channel, "kline"): var kline WsKline err := common.JSONDecode(resp.Raw, &kline) @@ -297,32 +303,41 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { // WsProcessOrderbook processes new orderbook data func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { p := currency.NewPairFromFormattedPairs(symbol, - h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)) + h.GetEnabledPairs(asset.Spot), + h.GetPairFormat(asset.Spot, true)) + var bids, asks []orderbook.Item - for i := 0; i < len(update.Tick.Bids); i++ { - bidLevel := update.Tick.Bids[i].([]interface{}) - bids = append(bids, orderbook.Item{Price: bidLevel[0].(float64), - Amount: bidLevel[0].(float64)}) + for i := range update.Tick.Bids { + bids = append(bids, orderbook.Item{ + Price: update.Tick.Bids[i][0].(float64), + Amount: update.Tick.Bids[i][1].(float64), + }) } - for i := 0; i < len(update.Tick.Asks); i++ { - askLevel := update.Tick.Asks[i].([]interface{}) - asks = append(asks, orderbook.Item{Price: askLevel[0].(float64), - Amount: askLevel[0].(float64)}) + + for i := range update.Tick.Asks { + asks = append(asks, orderbook.Item{ + Price: update.Tick.Asks[i][0].(float64), + Amount: update.Tick.Asks[i][1].(float64), + }) } + var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids newOrderBook.Pair = p + newOrderBook.AssetType = asset.Spot + newOrderBook.ExchangeName = h.Name + err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return err } + h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: p, Exchange: h.GetName(), Asset: asset.Spot, } - return nil } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index b14483a0..8c68e079 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -343,7 +343,7 @@ func (h *HUOBI) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo var orderBook orderbook.Base orderbookNew, err := h.GetDepth(OrderBookDataRequestParams{ Symbol: h.FormatExchangeCurrency(p, assetType).String(), - Type: OrderBookDataRequestParamsTypeStep1, + Type: OrderBookDataRequestParamsTypeStep0, }) if err != nil { return orderBook, err diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index 025d5aea..f4c2ceb9 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -143,31 +143,31 @@ func (k *Kraken) WsHandleDataResponse(response WebsocketDataResponse) { log.Debugf(log.ExchangeSys, "%v Websocket ticker data received", k.Name) } - k.wsProcessTickers(&channelData, response[1]) + k.wsProcessTickers(&channelData, response[1].(map[string]interface{})) case krakenWsOHLC: if k.Verbose { log.Debugf(log.ExchangeSys, "%v Websocket OHLC data received", k.Name) } - k.wsProcessCandles(&channelData, response[1]) + k.wsProcessCandles(&channelData, response[1].([]interface{})) case krakenWsOrderbook: if k.Verbose { log.Debugf(log.ExchangeSys, "%v Websocket Orderbook data received", k.Name) } - k.wsProcessOrderBook(&channelData, response[1]) + k.wsProcessOrderBook(&channelData, response[1].(map[string]interface{})) case krakenWsSpread: if k.Verbose { log.Debugf(log.ExchangeSys, "%v Websocket Spread data received", k.Name) } - k.wsProcessSpread(&channelData, response[1]) + k.wsProcessSpread(&channelData, response[1].([]interface{})) case krakenWsTrade: if k.Verbose { log.Debugf(log.ExchangeSys, "%v Websocket Trade data received", k.Name) } - k.wsProcessTrades(&channelData, response[1]) + k.wsProcessTrades(&channelData, response[1].([]interface{})) default: log.Errorf(log.ExchangeSys, "%v Unidentified websocket data received: %v", k.Name, @@ -238,22 +238,48 @@ func getSubscriptionChannelData(id int64) WebsocketChannelData { } // wsProcessTickers converts ticker data and sends it to the datahandler -func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data interface{}) { - tickerData := data.(map[string]interface{}) - askData := tickerData["a"].([]interface{}) - bidData := tickerData["b"].([]interface{}) - closeData := tickerData["c"].([]interface{}) - openData := tickerData["o"].([]interface{}) - lowData := tickerData["l"].([]interface{}) - highData := tickerData["h"].([]interface{}) - volumeData := tickerData["v"].([]interface{}) - closePrice, _ := strconv.ParseFloat(closeData[0].(string), 64) - openPrice, _ := strconv.ParseFloat(openData[0].(string), 64) - highPrice, _ := strconv.ParseFloat(highData[0].(string), 64) - lowPrice, _ := strconv.ParseFloat(lowData[0].(string), 64) - quantity, _ := strconv.ParseFloat(volumeData[0].(string), 64) - ask, _ := strconv.ParseFloat(askData[0].(string), 64) - bid, _ := strconv.ParseFloat(bidData[0].(string), 64) +func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data map[string]interface{}) { + closePrice, err := strconv.ParseFloat(data["c"].([]interface{})[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + openPrice, err := strconv.ParseFloat(data["o"].([]interface{})[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + highPrice, err := strconv.ParseFloat(data["h"].([]interface{})[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + lowPrice, err := strconv.ParseFloat(data["l"].([]interface{})[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + quantity, err := strconv.ParseFloat(data["v"].([]interface{})[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + ask, err := strconv.ParseFloat(data["a"].([]interface{})[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + bid, err := strconv.ParseFloat(data["b"].([]interface{})[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } k.Websocket.DataHandler <- wshandler.TickerData{ Exchange: k.Name, @@ -271,13 +297,17 @@ func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data interf } // wsProcessTickers converts ticker data and sends it to the datahandler -func (k *Kraken) wsProcessSpread(channelData *WebsocketChannelData, data interface{}) { - spreadData := data.([]interface{}) - bestBid := spreadData[0].(string) - bestAsk := spreadData[1].(string) - timeData, _ := strconv.ParseFloat(spreadData[2].(string), 64) - bidVolume := spreadData[3].(string) - askVolume := spreadData[4].(string) +func (k *Kraken) wsProcessSpread(channelData *WebsocketChannelData, data []interface{}) { + bestBid := data[0].(string) + bestAsk := data[1].(string) + timeData, err := strconv.ParseFloat(data[2].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + bidVolume := data[3].(string) + askVolume := data[4].(string) sec, dec := math.Modf(timeData) spreadTimestamp := time.Unix(int64(sec), int64(dec*(1e9))) if k.Verbose { @@ -294,14 +324,28 @@ func (k *Kraken) wsProcessSpread(channelData *WebsocketChannelData, data interfa } // wsProcessTrades converts trade data and sends it to the datahandler -func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data interface{}) { - tradeData := data.([]interface{}) - for i := range tradeData { - trade := tradeData[i].([]interface{}) - timeData, _ := strconv.ParseInt(trade[2].(string), 10, 64) - timeUnix := time.Unix(timeData, 0) - price, _ := strconv.ParseFloat(trade[0].(string), 64) - amount, _ := strconv.ParseFloat(trade[1].(string), 64) +func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []interface{}) { + for i := range data { + trade := data[i].([]interface{}) + timeData, err := strconv.ParseFloat(trade[2].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + sec, dec := math.Modf(timeData) + timeUnix := time.Unix(int64(sec), int64(dec*(1e9))) + + price, err := strconv.ParseFloat(trade[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + amount, err := strconv.ParseFloat(trade[1].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } k.Websocket.DataHandler <- wshandler.TradeData{ AssetType: asset.Spot, @@ -318,17 +362,17 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data interfa // wsProcessOrderBook determines if the orderbook data is partial or update // Then sends to appropriate fun -func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data interface{}) { - obData := data.(map[string]interface{}) - if _, ok := obData["as"]; ok { - k.wsProcessOrderBookPartial(channelData, obData) +func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data map[string]interface{}) { + if fullAsk, ok := data["as"].([]interface{}); ok { + fullBids := data["as"].([]interface{}) + k.wsProcessOrderBookPartial(channelData, fullAsk, fullBids) } else { - _, asksExist := obData["a"] - _, bidsExist := obData["b"] + askData, asksExist := data["a"].([]interface{}) + bidData, bidsExist := data["b"].([]interface{}) if asksExist || bidsExist { k.wsRequestMtx.Lock() defer k.wsRequestMtx.Unlock() - err := k.wsProcessOrderBookUpdate(channelData, obData) + err := k.wsProcessOrderBookUpdate(channelData, askData, bidData) if err != nil { subscriptionToRemove := wshandler.WebsocketChannelSubscription{ Channel: krakenWsOrderbook, @@ -341,40 +385,64 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data inte } // wsProcessOrderBookPartial creates a new orderbook entry for a given currency pair -func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, obData map[string]interface{}) { +func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, askData, bidData []interface{}) { base := orderbook.Base{ Pair: channelData.Pair, AssetType: asset.Spot, } - // Kraken ob data is timestamped per price, GCT orderbook data is timestamped per entry - // Using the highest last update time, we can attempt to respect both within a reasonable degree + // Kraken ob data is timestamped per price, GCT orderbook data is + // timestamped per entry using the highest last update time, we can attempt + // to respect both within a reasonable degree var highestLastUpdate time.Time - askData := obData["as"].([]interface{}) for i := range askData { asks := askData[i].([]interface{}) - price, _ := strconv.ParseFloat(asks[0].(string), 64) - amount, _ := strconv.ParseFloat(asks[1].(string), 64) + price, err := strconv.ParseFloat(asks[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + amount, err := strconv.ParseFloat(asks[1].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } base.Asks = append(base.Asks, orderbook.Item{ Amount: amount, Price: price, }) - timeData, _ := strconv.ParseFloat(asks[2].(string), 64) + timeData, err := strconv.ParseFloat(asks[2].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } sec, dec := math.Modf(timeData) askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9))) if highestLastUpdate.Before(askUpdatedTime) { highestLastUpdate = askUpdatedTime } } - bidData := obData["bs"].([]interface{}) + for i := range bidData { bids := bidData[i].([]interface{}) - price, _ := strconv.ParseFloat(bids[0].(string), 64) - amount, _ := strconv.ParseFloat(bids[1].(string), 64) + price, err := strconv.ParseFloat(bids[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + amount, err := strconv.ParseFloat(bids[1].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } base.Bids = append(base.Bids, orderbook.Item{ Amount: amount, Price: price, }) - timeData, _ := strconv.ParseFloat(bids[2].(string), 64) + timeData, err := strconv.ParseFloat(bids[2].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } sec, dec := math.Modf(timeData) bidUpdateTime := time.Unix(int64(sec), int64(dec*(1e9))) if highestLastUpdate.Before(bidUpdateTime) { @@ -382,6 +450,7 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob } } base.LastUpdated = highestLastUpdate + base.ExchangeName = k.Name err := k.Websocket.Orderbook.LoadSnapshot(&base) if err != nil { k.Websocket.DataHandler <- err @@ -395,48 +464,68 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob } // wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair -func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, obData map[string]interface{}) error { +func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, askData, bidData []interface{}) error { update := wsorderbook.WebsocketOrderbookUpdate{ - AssetType: asset.Spot, - CurrencyPair: channelData.Pair, + Asset: asset.Spot, + Pair: channelData.Pair, } + var highestLastUpdate time.Time // Ask data is not always sent - if _, ok := obData["a"]; ok { - askData := obData["a"].([]interface{}) - for i := range askData { - asks := askData[i].([]interface{}) - price, _ := strconv.ParseFloat(asks[0].(string), 64) - amount, _ := strconv.ParseFloat(asks[1].(string), 64) - update.Asks = append(update.Asks, orderbook.Item{ - Amount: amount, - Price: price, - }) - timeData, _ := strconv.ParseFloat(asks[2].(string), 64) - sec, dec := math.Modf(timeData) - askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9))) - if highestLastUpdate.Before(askUpdatedTime) { - highestLastUpdate = askUpdatedTime - } + for i := range askData { + asks := askData[i].([]interface{}) + price, err := strconv.ParseFloat(asks[0].(string), 64) + if err != nil { + return err + } + + amount, err := strconv.ParseFloat(asks[1].(string), 64) + if err != nil { + return err + } + + update.Asks = append(update.Asks, orderbook.Item{ + Amount: amount, + Price: price, + }) + timeData, err := strconv.ParseFloat(asks[2].(string), 64) + if err != nil { + return err + } + + sec, dec := math.Modf(timeData) + askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9))) + if highestLastUpdate.Before(askUpdatedTime) { + highestLastUpdate = askUpdatedTime } } + // Bid data is not always sent - if _, ok := obData["b"]; ok { - bidData := obData["b"].([]interface{}) - for i := range bidData { - bids := bidData[i].([]interface{}) - price, _ := strconv.ParseFloat(bids[0].(string), 64) - amount, _ := strconv.ParseFloat(bids[1].(string), 64) - update.Bids = append(update.Bids, orderbook.Item{ - Amount: amount, - Price: price, - }) - timeData, _ := strconv.ParseFloat(bids[2].(string), 64) - sec, dec := math.Modf(timeData) - bidUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9))) - if highestLastUpdate.Before(bidUpdatedTime) { - highestLastUpdate = bidUpdatedTime - } + for i := range bidData { + bids := bidData[i].([]interface{}) + price, err := strconv.ParseFloat(bids[0].(string), 64) + if err != nil { + return err + } + + amount, err := strconv.ParseFloat(bids[1].(string), 64) + if err != nil { + return err + } + + update.Bids = append(update.Bids, orderbook.Item{ + Amount: amount, + Price: price, + }) + timeData, err := strconv.ParseFloat(bids[2].(string), 64) + if err != nil { + return err + } + + sec, dec := math.Modf(timeData) + bidUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9))) + if highestLastUpdate.Before(bidUpdatedTime) { + highestLastUpdate = bidUpdatedTime } } update.UpdateTime = highestLastUpdate @@ -454,17 +543,52 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, obD } // wsProcessCandles converts candle data and sends it to the data handler -func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data interface{}) { - candleData := data.([]interface{}) - startTimeData, _ := strconv.ParseInt(candleData[0].(string), 10, 64) - startTimeUnix := time.Unix(startTimeData, 0) - endTimeData, _ := strconv.ParseInt(candleData[1].(string), 10, 64) - endTimeUnix := time.Unix(endTimeData, 0) - openPrice, _ := strconv.ParseFloat(candleData[2].(string), 64) - highPrice, _ := strconv.ParseFloat(candleData[3].(string), 64) - lowPrice, _ := strconv.ParseFloat(candleData[4].(string), 64) - closePrice, _ := strconv.ParseFloat(candleData[5].(string), 64) - volume, _ := strconv.ParseFloat(candleData[7].(string), 64) +func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []interface{}) { + startTime, err := strconv.ParseFloat(data[0].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + sec, dec := math.Modf(startTime) + startTimeUnix := time.Unix(int64(sec), int64(dec*(1e9))) + + endTime, err := strconv.ParseFloat(data[1].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + sec, dec = math.Modf(endTime) + endTimeUnix := time.Unix(int64(sec), int64(dec*(1e9))) + + openPrice, err := strconv.ParseFloat(data[2].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + highPrice, err := strconv.ParseFloat(data[3].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + lowPrice, err := strconv.ParseFloat(data[4].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + closePrice, err := strconv.ParseFloat(data[5].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } + + volume, err := strconv.ParseFloat(data[7].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + return + } k.Websocket.DataHandler <- wshandler.KlineData{ AssetType: asset.Spot, @@ -501,11 +625,17 @@ func (k *Kraken) GenerateDefaultSubscriptions() { // Subscribe sends a websocket message to receive data from the channel func (k *Kraken) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { + var depth int64 + if channelToSubscribe.Channel == "book" { + depth = 1000 + } + resp := WebsocketSubscriptionEventRequest{ Event: krakenWsSubscribe, Pairs: []string{channelToSubscribe.Currency.String()}, Subscription: WebsocketSubscriptionData{ - Name: channelToSubscribe.Channel, + Name: channelToSubscribe.Channel, + Depth: depth, }, RequestID: k.WebsocketConn.GenerateMessageID(false), } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index f28691d2..22f6cf75 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -74,7 +74,7 @@ func (k *Kraken) SetDefaults() { k.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, - Websocket: false, + Websocket: true, RESTCapabilities: protocol.Features{ TickerBatching: true, TickerFetching: true, diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 12d09c24..498e88df 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -45,7 +45,8 @@ func (l *LakeBTC) GetTicker() (map[string]Ticker, error) { response := make(map[string]TickerResponse) path := fmt.Sprintf("%s/%s", l.API.Endpoints.URL, lakeBTCTicker) - if err := l.SendHTTPRequest(path, &response); err != nil { + err := l.SendHTTPRequest(path, &response) + if err != nil { return nil, err } @@ -55,22 +56,40 @@ func (l *LakeBTC) GetTicker() (map[string]Ticker, error) { var tick Ticker key := strings.ToUpper(k) if v.Ask != nil { - tick.Ask, _ = strconv.ParseFloat(v.Ask.(string), 64) + tick.Ask, err = strconv.ParseFloat(v.Ask.(string), 64) + if err != nil { + return nil, err + } } if v.Bid != nil { - tick.Bid, _ = strconv.ParseFloat(v.Bid.(string), 64) + tick.Bid, err = strconv.ParseFloat(v.Bid.(string), 64) + if err != nil { + return nil, err + } } if v.High != nil { - tick.High, _ = strconv.ParseFloat(v.High.(string), 64) + tick.High, err = strconv.ParseFloat(v.High.(string), 64) + if err != nil { + return nil, err + } } if v.Last != nil { - tick.Last, _ = strconv.ParseFloat(v.Last.(string), 64) + tick.Last, err = strconv.ParseFloat(v.Last.(string), 64) + if err != nil { + return nil, err + } } if v.Low != nil { - tick.Low, _ = strconv.ParseFloat(v.Low.(string), 64) + tick.Low, err = strconv.ParseFloat(v.Low.(string), 64) + if err != nil { + return nil, err + } } if v.Volume != nil { - tick.Volume, _ = strconv.ParseFloat(v.Volume.(string), 64) + tick.Volume, err = strconv.ParseFloat(v.Volume.(string), 64) + if err != nil { + return nil, err + } } result[key] = tick } diff --git a/exchanges/lakebtc/lakebtc_websocket.go b/exchanges/lakebtc/lakebtc_websocket.go index 3b1abae5..1e3003ba 100644 --- a/exchanges/lakebtc/lakebtc_websocket.go +++ b/exchanges/lakebtc/lakebtc_websocket.go @@ -74,9 +74,14 @@ func (l *LakeBTC) listenToEndpoints() error { func (l *LakeBTC) GenerateDefaultSubscriptions() { var subscriptions []wshandler.WebsocketChannelSubscription enabledCurrencies := l.GetEnabledPairs(asset.Spot) + for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "" - channel := fmt.Sprintf("%v%v%v", marketSubstring, enabledCurrencies[j].Lower(), globalSubstring) + channel := fmt.Sprintf("%v%v%v", + marketSubstring, + enabledCurrencies[j].Lower(), + globalSubstring) + subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ Channel: channel, Currency: enabledCurrencies[j], @@ -164,8 +169,11 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { if err != nil { return err } + + p := l.getCurrencyFromChannel(channel) + book := orderbook.Base{ - Pair: l.getCurrencyFromChannel(channel), + Pair: p, LastUpdated: time.Now(), AssetType: asset.Spot, ExchangeName: l.Name, @@ -188,6 +196,7 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { Price: price, }) } + for i := 0; i < len(update.Bids); i++ { var amount, price float64 amount, err = strconv.ParseFloat(update.Bids[i][1], 64) @@ -205,7 +214,19 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { Price: price, }) } - return l.Websocket.Orderbook.LoadSnapshot(&book) + + err = l.Websocket.Orderbook.LoadSnapshot(&book) + if err != nil { + return err + } + + l.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ + Pair: p, + Asset: asset.Spot, + Exchange: l.Name, + } + + return nil } func (l *LakeBTC) getCurrencyFromChannel(channel string) currency.Pair { @@ -221,7 +242,14 @@ func (l *LakeBTC) processTicker(ticker string) error { l.Websocket.DataHandler <- err return err } + + enabled := l.GetEnabledPairs(asset.Spot) for k, v := range tUpdate { + returnCurrency := currency.NewPairFromString(k) + if !enabled.Contains(returnCurrency, true) { + continue + } + tickerData := v.(map[string]interface{}) processTickerItem := func(tick map[string]interface{}, item string) float64 { if tick[item] == nil { @@ -230,7 +258,10 @@ func (l *LakeBTC) processTicker(ticker string) error { p, err := strconv.ParseFloat(tick[item].(string), 64) if err != nil { - l.Websocket.DataHandler <- fmt.Errorf("%s error parsing ticker data '%s' %v", l.Name, item, tickerData) + l.Websocket.DataHandler <- fmt.Errorf("%s error parsing ticker data '%s' %v", + l.Name, + item, + tickerData) return 0 } @@ -246,7 +277,7 @@ func (l *LakeBTC) processTicker(ticker string) error { Ask: processTickerItem(tickerData, tickerSellString), Volume: processTickerItem(tickerData, tickerVolumeString), AssetType: asset.Spot, - Pair: currency.NewPairFromString(k), + Pair: returnCurrency, } } return nil diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 17649b40..9912f15b 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -214,20 +214,21 @@ func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr pairs := l.GetEnabledPairs(assetType) for i := range pairs { - currency := l.FormatExchangeCurrency(pairs[i], assetType).String() - if _, ok := ticks[currency]; !ok { + c, ok := ticks[l.FormatExchangeCurrency(pairs[i], assetType).String()] + if !ok { continue } + var tickerPrice ticker.Price tickerPrice.Pair = pairs[i] - tickerPrice.Ask = ticks[currency].Ask - tickerPrice.Bid = ticks[currency].Bid - tickerPrice.Volume = ticks[currency].Volume - tickerPrice.High = ticks[currency].High - tickerPrice.Low = ticks[currency].Low - tickerPrice.Last = ticks[currency].Last + tickerPrice.Ask = c.Ask + tickerPrice.Bid = c.Bid + tickerPrice.Volume = c.Volume + tickerPrice.High = c.High + tickerPrice.Low = c.Low + tickerPrice.Last = c.Last - err = ticker.ProcessTicker(l.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(l.Name, &tickerPrice, assetType) if err != nil { log.Error(log.Ticker, err) } @@ -237,7 +238,7 @@ func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr // FetchTicker returns the ticker for a currency pair func (l *LakeBTC) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(l.Name, p, assetType) if err != nil { return l.UpdateTicker(p, assetType) } @@ -246,7 +247,7 @@ func (l *LakeBTC) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pri // FetchOrderbook returns orderbook base on the currency pair func (l *LakeBTC) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(l.GetName(), p, assetType) + ob, err := orderbook.Get(l.Name, p, assetType) if err != nil { return l.UpdateOrderbook(p, assetType) } @@ -270,7 +271,7 @@ func (l *LakeBTC) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderb } orderBook.Pair = p - orderBook.ExchangeName = l.GetName() + orderBook.ExchangeName = l.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -285,7 +286,7 @@ func (l *LakeBTC) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderb // LakeBTC exchange func (l *LakeBTC) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = l.GetName() + response.Exchange = l.Name accountInfo, err := l.GetAccountInformation() if err != nil { return response, err diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index bdd9b24d..1d058432 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -488,18 +488,6 @@ func TestGetSpotTokenPairDetails(t *testing.T) { } } -// TestGetSpotOrderBook API endpoint test -func TestGetSpotOrderBook(t *testing.T) { - TestSetDefaults(t) - request := okgroup.GetSpotOrderBookRequest{ - InstrumentID: spotCurrency, - } - _, err := o.GetSpotOrderBook(request) - if err != nil { - t.Error(err) - } -} - // TestGetSpotAllTokenPairsInformation API endpoint test func TestGetSpotAllTokenPairsInformation(t *testing.T) { TestSetDefaults(t) diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index ce5a2a92..f5a80933 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -142,7 +142,11 @@ func (o *OKCoin) Start(wg *sync.WaitGroup) { // Run implements the OKEX wrapper func (o *OKCoin) Run() { if o.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL) + log.Debugf(log.ExchangeSys, + "%s Websocket: %s. (url: %s).\n", + o.Name, + common.IsEnabled(o.Websocket.IsEnabled()), + o.WebsocketURL) } forceUpdate := false @@ -161,7 +165,9 @@ func (o *OKCoin) Run() { err := o.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", o.GetName()) + log.Errorf(log.ExchangeSys, + "%s failed to update currencies.\n", + o.Name) return } } @@ -172,7 +178,10 @@ func (o *OKCoin) Run() { err := o.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", o.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + o.Name, + err) } } @@ -237,12 +246,12 @@ func (o *OKCoin) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri } } } - return ticker.GetTicker(o.GetName(), p, assetType) + return ticker.GetTicker(o.Name, p, assetType) } // FetchTicker returns the ticker for a currency pair func (o *OKCoin) FetchTicker(p currency.Pair, assetType asset.Item) (tickerData ticker.Price, err error) { - tickerData, err = ticker.GetTicker(o.GetName(), p, assetType) + tickerData, err = ticker.GetTicker(o.Name, p, assetType) if err != nil { return o.UpdateTicker(p, assetType) } diff --git a/exchanges/okex/okex.go b/exchanges/okex/okex.go index c452f490..51a4d2bc 100644 --- a/exchanges/okex/okex.go +++ b/exchanges/okex/okex.go @@ -3,8 +3,6 @@ package okex import ( "fmt" "net/http" - "strconv" - "time" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/exchanges/okgroup" @@ -18,7 +16,7 @@ const ( okExAPIVersion = "/v3/" okExExchangeName = "OKEX" // OkExWebsocketURL WebsocketURL - OkExWebsocketURL = "wss://real.okex.com:10442/ws/v3" + OkExWebsocketURL = "wss://real.okex.com:8443/ws/v3" // API subsections okGroupFuturesSubsection = "futures" okGroupSwapSubsection = "swap" @@ -141,69 +139,6 @@ func (o *OKEX) GetFuturesContractInformation() (resp []okgroup.GetFuturesContrac return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, okgroup.OKGroupInstruments, nil, &resp, false) } -// GetFuturesOrderBook List all contracts. This request does not support pagination. The full list will be returned for a request. -func (o *OKEX) GetFuturesOrderBook(request okgroup.GetFuturesOrderBookRequest) (resp okgroup.GetFuturesOrderBookResponse, err error) { - requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupGetSpotOrderBook, okgroup.FormatParameters(request)) - - type tempOB struct { - Bids [][]string `json:"bids"` - Asks [][]string `json:"asks"` - Timestamp time.Time `json:"timestamp"` - } - - var tmpOB tempOB - err = o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &tmpOB, false) - if err != nil { - return resp, err - } - - processOB := func(ob [][]string) ([]okgroup.FuturesOrderbookItem, error) { - var processedOB []okgroup.FuturesOrderbookItem - for x := range ob { - price, convErr := strconv.ParseFloat(ob[x][0], 64) - if err != nil { - return nil, convErr - } - - size, convErr := strconv.ParseInt(ob[x][1], 10, 64) - if err != nil { - return nil, convErr - } - - liqOrders, convErr := strconv.ParseInt(ob[x][2], 10, 64) - if err != nil { - return nil, convErr - } - - numOrders, convErr := strconv.ParseInt(ob[x][3], 10, 64) - if err != nil { - return nil, convErr - } - - processedOB = append(processedOB, okgroup.FuturesOrderbookItem{ - Price: price, - Size: size, - ForceLiquidatedOrders: liqOrders, - NumberOrders: numOrders, - }) - } - return processedOB, nil - } - - resp.Bids, err = processOB(tmpOB.Bids) - if err != nil { - return - } - - resp.Asks, err = processOB(tmpOB.Asks) - if err != nil { - return - } - - resp.Timestamp = tmpOB.Timestamp - return resp, nil -} - // GetAllFuturesTokenInfo Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all contracts. func (o *OKEX) GetAllFuturesTokenInfo() (resp []okgroup.GetFuturesTokenInfoResponse, _ error) { requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupInstruments, okgroup.OKGroupTicker) @@ -373,12 +308,6 @@ func (o *OKEX) GetSwapContractInformation() (resp []okgroup.GetSwapContractInfor return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, okgroup.OKGroupInstruments, nil, &resp, false) } -// GetSwapOrderBook Get the charts of the trading pairs. -func (o *OKEX) GetSwapOrderBook(request okgroup.GetSwapOrderBookRequest) (resp okgroup.GetSwapOrderBookResponse, _ error) { - requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okGroupDepth, okgroup.FormatParameters(request)) - return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, false) -} - // GetAllSwapTokensInformation Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all contracts. func (o *OKEX) GetAllSwapTokensInformation() (resp []okgroup.GetAllSwapTokensInformationResponse, _ error) { requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupInstruments, okgroup.OKGroupTicker) diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index 11752506..485837e2 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -522,19 +522,6 @@ func TestGetSpotTokenPairDetails(t *testing.T) { } } -// TestGetSpotOrderBook API endpoint test -func TestGetSpotOrderBook(t *testing.T) { - TestSetDefaults(t) - t.Parallel() - request := okgroup.GetSpotOrderBookRequest{ - InstrumentID: spotCurrency, - } - _, err := o.GetSpotOrderBook(request) - if err != nil { - t.Error(err) - } -} - // TestGetSpotAllTokenPairsInformation API endpoint test func TestGetSpotAllTokenPairsInformation(t *testing.T) { TestSetDefaults(t) @@ -1035,18 +1022,6 @@ func TestGetFuturesContractInformation(t *testing.T) { } } -// TestGetFuturesContractInformation API endpoint test -func TestGetFuturesOrderBook(t *testing.T) { - TestSetDefaults(t) - _, err := o.GetFuturesOrderBook(okgroup.GetFuturesOrderBookRequest{ - InstrumentID: getFutureInstrumentID(), - Size: 10, - }) - if err != nil { - t.Error(err) - } -} - // TestGetAllFuturesTokenInfo API endpoint test func TestGetAllFuturesTokenInfo(t *testing.T) { TestSetDefaults(t) @@ -1315,19 +1290,6 @@ func TestGetSwapContractInformation(t *testing.T) { } } -// TestGetSwapOrderBook API endpoint test -func TestGetSwapOrderBook(t *testing.T) { - TestSetDefaults(t) - t.Parallel() - _, err := o.GetSwapOrderBook(okgroup.GetSwapOrderBookRequest{ - InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), - Size: 200, - }) - if err != nil { - t.Error(err) - } -} - // TestGetAllSwapTokensInformation API endpoint test func TestGetAllSwapTokensInformation(t *testing.T) { TestSetDefaults(t) diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go index 3e1b96f2..8fb0c00b 100644 --- a/exchanges/okex/okex_wrapper.go +++ b/exchanges/okex/okex_wrapper.go @@ -1,7 +1,9 @@ package okex import ( + "errors" "fmt" + "strings" "sync" "time" @@ -17,6 +19,11 @@ import ( log "github.com/thrasher-corp/gocryptotrader/logger" ) +const ( + delimiterDash = "-" + delimiterUnderscore = "_" +) + // GetDefaultConfig returns a default exchange config func (o *OKEX) GetDefaultConfig() (*config.ExchangeConfig, error) { o.SetDefaults() @@ -64,28 +71,38 @@ func (o *OKEX) SetDefaults() { fmt1 := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: "-", + Delimiter: delimiterDash, }, ConfigFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: "_", + Delimiter: delimiterUnderscore, }, } o.CurrencyPairs.Store(asset.PerpetualSwap, fmt1) o.CurrencyPairs.Store(asset.Futures, fmt1) - fmt2 := currency.PairStore{ + index := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: "-", + Delimiter: delimiterDash, }, ConfigFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: "-", }, } - o.CurrencyPairs.Store(asset.Spot, fmt2) - o.CurrencyPairs.Store(asset.Index, fmt2) + + spot := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: delimiterDash, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: delimiterDash, + }, + } + o.CurrencyPairs.Store(asset.Spot, spot) + o.CurrencyPairs.Store(asset.Index, index) o.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ @@ -159,19 +176,25 @@ func (o *OKEX) Start(wg *sync.WaitGroup) { // Run implements the OKEX wrapper func (o *OKEX) Run() { if o.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.API.Endpoints.WebsocketURL) + log.Debugf(log.ExchangeSys, + "%s Websocket: %s. (url: %s).\n", + o.Name, + common.IsEnabled(o.Websocket.IsEnabled()), + o.API.Endpoints.WebsocketURL) } - if o.Config.CurrencyPairs.Pairs[asset.Spot].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Spot].RequestFormat == nil || - o.Config.CurrencyPairs.Pairs[asset.Index].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Index].RequestFormat == nil { + if o.Config.CurrencyPairs.Pairs[asset.Spot].ConfigFormat == nil || + o.Config.CurrencyPairs.Pairs[asset.Spot].RequestFormat == nil || + o.Config.CurrencyPairs.Pairs[asset.Index].ConfigFormat == nil || + o.Config.CurrencyPairs.Pairs[asset.Index].RequestFormat == nil { currFmt := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: "-", + Delimiter: delimiterDash, }, ConfigFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: "-", + Delimiter: delimiterDash, }, } o.CurrencyPairs.Store(asset.Spot, currFmt) @@ -180,16 +203,18 @@ func (o *OKEX) Run() { o.Config.CurrencyPairs.Store(asset.Index, currFmt) } - if o.Config.CurrencyPairs.Pairs[asset.Futures].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Futures].RequestFormat == nil || - o.Config.CurrencyPairs.Pairs[asset.PerpetualSwap].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.PerpetualSwap].RequestFormat == nil { + if o.Config.CurrencyPairs.Pairs[asset.Futures].ConfigFormat == nil || + o.Config.CurrencyPairs.Pairs[asset.Futures].RequestFormat == nil || + o.Config.CurrencyPairs.Pairs[asset.PerpetualSwap].ConfigFormat == nil || + o.Config.CurrencyPairs.Pairs[asset.PerpetualSwap].RequestFormat == nil { currFmt := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: "-", + Delimiter: delimiterDash, }, ConfigFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: "_", + Delimiter: delimiterUnderscore, }, } o.CurrencyPairs.Store(asset.Futures, currFmt) @@ -198,14 +223,18 @@ func (o *OKEX) Run() { o.Config.CurrencyPairs.Store(asset.PerpetualSwap, currFmt) } - if !common.StringDataContains(o.Config.CurrencyPairs.Pairs[asset.Spot].Enabled.Strings(), o.CurrencyPairs.Pairs[asset.Spot].RequestFormat.Delimiter) { + if !common.StringDataContains(o.Config.CurrencyPairs.Pairs[asset.Spot].Enabled.Strings(), + o.CurrencyPairs.Pairs[asset.Spot].RequestFormat.Delimiter) { enabledPairs := currency.NewPairsFromStrings([]string{"EOS-USDT"}) log.Warnf(log.ExchangeSys, - "Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.", o.Name) + "Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.", + o.Name) err := o.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", o.GetName()) + log.Errorf(log.ExchangeSys, + "%s failed to update currencies.\n", + o.Name) return } } @@ -216,7 +245,10 @@ func (o *OKEX) Run() { err := o.UpdateTradablePairs(false) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", o.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + o.Name, + err) } } @@ -231,7 +263,10 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { } for x := range prods { - pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].BaseCurrency, o.GetPairFormat(i, false).Delimiter, prods[x].QuoteCurrency)) + pairs = append(pairs, + currency.NewPairWithDelimiter(prods[x].BaseCurrency, + prods[x].QuoteCurrency, + o.GetPairFormat(i, false).Delimiter).String()) } return pairs, nil case asset.Futures: @@ -240,9 +275,10 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { return nil, err } - var pairs []string for x := range prods { - pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].UnderlyingIndex+prods[x].QuoteCurrency, o.GetPairFormat(i, false).Delimiter, prods[x].Delivery)) + p := strings.Split(prods[x].InstrumentID, delimiterDash) + pairs = append(pairs, + p[0]+delimiterDash+p[1]+o.GetPairFormat(i, false).Delimiter+p[2]) } return pairs, nil @@ -252,13 +288,18 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { return nil, err } - var pairs []string for x := range prods { - pairs = append(pairs, fmt.Sprintf("%v%v%v%vSWAP", prods[x].UnderlyingIndex, o.GetPairFormat(i, false).Delimiter, prods[x].QuoteCurrency, o.GetPairFormat(i, false).Delimiter)) + pairs = append(pairs, + prods[x].UnderlyingIndex+ + delimiterDash+ + prods[x].QuoteCurrency+ + o.GetPairFormat(i, false).Delimiter+ + "SWAP") } return pairs, nil case asset.Index: - return []string{fmt.Sprintf("BTC%vUSD", o.GetPairFormat(i, false).Delimiter)}, nil + // This is updated in futures index + return nil, errors.New("index updated in futures") } return nil, fmt.Errorf("%s invalid asset type", o.Name) @@ -268,13 +309,33 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { // them in the exchanges config func (o *OKEX) UpdateTradablePairs(forceUpdate bool) error { for x := range o.CurrencyPairs.AssetTypes { - a := o.CurrencyPairs.AssetTypes[x] - pairs, err := o.FetchTradablePairs(a) + if o.CurrencyPairs.AssetTypes[x] == asset.Index { + // Update from futures + continue + } + + pairs, err := o.FetchTradablePairs(o.CurrencyPairs.AssetTypes[x]) if err != nil { return err } - err = o.UpdatePairs(currency.NewPairsFromStrings(pairs), a, false, forceUpdate) + if o.CurrencyPairs.AssetTypes[x] == asset.Futures { + var indexPairs []string + for i := range pairs { + indexPairs = append(indexPairs, + strings.Split(pairs[i], delimiterUnderscore)[0]) + } + err = o.UpdatePairs(currency.NewPairsFromStrings(indexPairs), + asset.Index, + false, + forceUpdate) + if err != nil { + return err + } + } + + err = o.UpdatePairs(currency.NewPairsFromStrings(pairs), + o.CurrencyPairs.AssetTypes[x], false, forceUpdate) if err != nil { return err } @@ -291,92 +352,98 @@ func (o *OKEX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price if err != nil { return tickerData, err } - pairs := o.GetEnabledPairs(assetType) - for i := range pairs { - for j := range resp { - if !pairs[i].Equal(resp[j].InstrumentID) { - continue - } - tickerData = ticker.Price{ - Last: resp[j].Last, - High: resp[j].High24h, - Low: resp[j].Low24h, - Bid: resp[j].BestBid, - Ask: resp[j].BestAsk, - Volume: resp[j].BaseVolume24h, - QuoteVolume: resp[j].QuoteVolume24h, - Open: resp[j].Open24h, - Pair: pairs[i], - LastUpdated: resp[j].Timestamp, - } - err = ticker.ProcessTicker(o.Name, &tickerData, assetType) - if err != nil { - log.Error(log.Ticker, err) - } + for j := range resp { + if !o.GetEnabledPairs(assetType).Contains(resp[j].InstrumentID, true) { + continue + } + tickerData = ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24h, + Low: resp[j].Low24h, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].BaseVolume24h, + QuoteVolume: resp[j].QuoteVolume24h, + Open: resp[j].Open24h, + Pair: resp[j].InstrumentID, + LastUpdated: resp[j].Timestamp, + } + err = ticker.ProcessTicker(o.Name, &tickerData, assetType) + if err != nil { + log.Error(log.Ticker, err) } } + case asset.PerpetualSwap: resp, err := o.GetAllSwapTokensInformation() if err != nil { return tickerData, err } - pairs := o.GetEnabledPairs(assetType) - for i := range pairs { - for j := range resp { - if !pairs[i].Equal(resp[j].InstrumentID) { - continue - } - tickerData = ticker.Price{ - Last: resp[j].Last, - High: resp[j].High24H, - Low: resp[j].Low24H, - Bid: resp[j].BestBid, - Ask: resp[j].BestAsk, - Volume: resp[j].Volume24H, - Pair: resp[j].InstrumentID, - LastUpdated: resp[j].Timestamp, - } - err = ticker.ProcessTicker(o.Name, &tickerData, assetType) - if err != nil { - log.Error(log.Ticker, err) - } + + for j := range resp { + p := strings.Split(resp[j].InstrumentID, delimiterDash) + nC := currency.NewPairWithDelimiter(p[0]+delimiterDash+p[1], + p[2], + delimiterUnderscore) + if !o.GetEnabledPairs(assetType).Contains(nC, true) { + continue + } + tickerData = ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24H, + Low: resp[j].Low24H, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].Volume24H, + Pair: nC, + LastUpdated: resp[j].Timestamp, + } + err = ticker.ProcessTicker(o.Name, &tickerData, assetType) + if err != nil { + log.Error(log.Ticker, err) } } + case asset.Futures: resp, err := o.GetAllFuturesTokenInfo() if err != nil { return tickerData, err } - pairs := o.GetEnabledPairs(assetType) - for i := range pairs { - for j := range resp { - if !pairs[i].Equal(resp[j].InstrumentID) { - continue - } - tickerData = ticker.Price{ - Last: resp[j].Last, - High: resp[j].High24h, - Low: resp[j].Low24h, - Bid: resp[j].BestBid, - Ask: resp[j].BestAsk, - Volume: resp[j].Volume24h, - Pair: resp[j].InstrumentID, - LastUpdated: resp[j].Timestamp, - } - err = ticker.ProcessTicker(o.Name, &tickerData, assetType) - if err != nil { - log.Error(log.Ticker, err) - } + + for j := range resp { + p := strings.Split(resp[j].InstrumentID, delimiterDash) + nC := currency.NewPairWithDelimiter(p[0]+delimiterDash+p[1], + p[2], + delimiterUnderscore) + if !o.GetEnabledPairs(assetType).Contains(nC, true) { + continue + } + tickerData = ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24h, + Low: resp[j].Low24h, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].Volume24h, + Pair: nC, + LastUpdated: resp[j].Timestamp, + } + err = ticker.ProcessTicker(o.Name, &tickerData, assetType) + if err != nil { + log.Error(log.Ticker, err) } } } - return ticker.GetTicker(o.GetName(), p, assetType) + return ticker.GetTicker(o.Name, p, assetType) } // FetchTicker returns the ticker for a currency pair func (o *OKEX) FetchTicker(p currency.Pair, assetType asset.Item) (tickerData ticker.Price, err error) { - tickerData, err = ticker.GetTicker(o.GetName(), p, assetType) + if assetType == asset.Index { + return tickerData, errors.New("ticker fetching not supported for index") + } + tickerData, err = ticker.GetTicker(o.Name, p, assetType) if err != nil { return o.UpdateTicker(p, assetType) } diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go index 478139bd..491cecec 100644 --- a/exchanges/okgroup/okgroup.go +++ b/exchanges/okgroup/okgroup.go @@ -16,6 +16,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -307,11 +308,35 @@ func (o *OKGroup) GetSpotTokenPairDetails() (resp []GetSpotTokenPairDetailsRespo return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, OKGroupInstruments, nil, &resp, false) } -// GetSpotOrderBook Getting the order book of a trading pair. Pagination is not supported here. -// The whole book will be returned for one request. Websocket is recommended here. -func (o *OKGroup) GetSpotOrderBook(request GetSpotOrderBookRequest) (resp GetSpotOrderBookResponse, _ error) { - requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupGetSpotOrderBook, FormatParameters(request)) - return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, false) +// GetOrderBook Getting the order book of a trading pair. Pagination is not +// supported here. The whole book will be returned for one request. Websocket is +// recommended here. +func (o *OKGroup) GetOrderBook(request GetOrderBookRequest, a asset.Item) (resp GetOrderBookResponse, _ error) { + var requestType, endpoint string + switch a { + case asset.Spot: + endpoint = OKGroupGetSpotOrderBook + requestType = okGroupTokenSubsection + case asset.Futures: + endpoint = OKGroupGetSpotOrderBook + requestType = "futures" + case asset.PerpetualSwap: + endpoint = "depth" + requestType = "swap" + default: + return resp, errors.New("unhandled asset type") + } + requestURL := fmt.Sprintf("%v/%v/%v/%v", + OKGroupInstruments, + request.InstrumentID, + endpoint, + FormatParameters(request)) + return resp, o.SendHTTPRequest(http.MethodGet, + requestType, + requestURL, + nil, + &resp, + false) } // GetSpotAllTokenPairsInformation Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all trading pairs. diff --git a/exchanges/okgroup/okgroup_test.go b/exchanges/okgroup/okgroup_test.go new file mode 100644 index 00000000..edcbf618 --- /dev/null +++ b/exchanges/okgroup/okgroup_test.go @@ -0,0 +1,78 @@ +package okgroup + +import ( + "log" + "os" + "testing" + "time" + + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/config" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" +) + +const ( + apiKey = "" + apiSecret = "" + + testAPIURL = "https://www.okex.com/api/" + testAPIVersion = "/v3/" +) + +var o OKGroup + +func TestMain(m *testing.M) { + cfg := config.GetConfig() + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("okgroup load config error", err) + } + okgroup, err := cfg.GetExchangeConfig("Okex") + if err != nil { + log.Fatal("okgroup Setup() init error", err) + } + + okgroup.API.AuthenticatedSupport = true + okgroup.API.Credentials.Key = apiKey + okgroup.API.Credentials.Secret = apiSecret + o.API.Endpoints.URL = testAPIURL + o.APIVersion = testAPIVersion + + o.Requester = request.New("okgroup_test_things", + request.NewRateLimit(time.Second, 10), + request.NewRateLimit(time.Second, 10), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), + ) + o.Websocket = wshandler.New() + + err = o.Setup(okgroup) + if err != nil { + log.Fatal("okgroup setup error", err) + } + os.Exit(m.Run()) +} + +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := o.GetOrderBook(GetOrderBookRequest{InstrumentID: "BTC-USDT"}, + asset.Spot) + if err != nil { + t.Error(err) + } + + // futures expire and break test, will need to mock this in the future + _, err = o.GetOrderBook(GetOrderBookRequest{InstrumentID: "Payload"}, + asset.Futures) + if err == nil { + t.Error("error cannot be nil") + } + + _, err = o.GetOrderBook(GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, + asset.PerpetualSwap) + if err != nil { + t.Error(err) + } +} diff --git a/exchanges/okgroup/okgroup_types.go b/exchanges/okgroup/okgroup_types.go index 4eb8493e..266e36eb 100644 --- a/exchanges/okgroup/okgroup_types.go +++ b/exchanges/okgroup/okgroup_types.go @@ -274,15 +274,15 @@ type GetSpotTokenPairDetailsResponse struct { TickSize string `json:"tick_size"` } -// GetSpotOrderBookRequest request data for GetSpotOrderBook -type GetSpotOrderBookRequest struct { +// GetOrderBookRequest request data for GetOrderBook +type GetOrderBookRequest struct { Size int64 `url:"size,string,omitempty"` // [optional] number of results per request. Maximum 200 Depth float64 `url:"depth,string,omitempty"` // [optional] the aggregation of the book. e.g . 0.1,0.001 InstrumentID string `url:"-"` // [required] trading pairs } -// GetSpotOrderBookResponse response data for GetSpotOrderBook -type GetSpotOrderBookResponse struct { +// GetOrderBookResponse response data +type GetOrderBookResponse struct { Timestamp time.Time `json:"timestamp"` Asks [][]string `json:"asks"` // [[0]: "Price", [1]: "Size", [2]: "Num_orders"], ... Bids [][]string `json:"bids"` // [[0]: "Price", [1]: "Size", [2]: "Num_orders"], ... @@ -678,37 +678,16 @@ type GetFuturesContractInformationResponse struct { UnderlyingIndex string `json:"underlying_index"` } -// GetFuturesOrderBookRequest request data for GetFuturesOrderBook -type GetFuturesOrderBookRequest struct { - InstrumentID string `url:"-"` // [required] Contract ID, e.g. "BTC-USD-180213" - Size int64 `url:"size,omitempty"` // [optional] The size of the price range (max: 200) -} - -// FuturesOrderbookItem stores an individual futures orderbook item -type FuturesOrderbookItem struct { - Price float64 - Size int64 - ForceLiquidatedOrders int64 // Number of force liquidated orders - NumberOrders int64 // Number of orders on the price -} - -// GetFuturesOrderBookResponse response data for GetFuturesOrderBook -type GetFuturesOrderBookResponse struct { - Asks []FuturesOrderbookItem - Bids []FuturesOrderbookItem - Timestamp time.Time -} - // GetFuturesTokenInfoResponse response data for GetFuturesOrderBook type GetFuturesTokenInfoResponse struct { - BestAsk float64 `json:"best_ask,string"` - BestBid float64 `json:"best_bid,string"` - High24h float64 `json:"high_24h,string"` - InstrumentID currency.Pair `json:"instrument_id"` - Last float64 `json:"last,string"` - Low24h float64 `json:"low_24h,string"` - Timestamp time.Time `json:"timestamp"` - Volume24h float64 `json:"volume_24h,string"` + BestAsk float64 `json:"best_ask,string"` + BestBid float64 `json:"best_bid,string"` + High24h float64 `json:"high_24h,string"` + InstrumentID string `json:"instrument_id"` + Last float64 `json:"last,string"` + Low24h float64 `json:"low_24h,string"` + Timestamp time.Time `json:"timestamp"` + Volume24h float64 `json:"volume_24h,string"` } // GetFuturesFilledOrderRequest request data for GetFuturesFilledOrder @@ -1059,14 +1038,14 @@ type GetSwapOrderBookResponse struct { // GetAllSwapTokensInformationResponse response data for GetAllSwapTokensInformation type GetAllSwapTokensInformationResponse struct { - InstrumentID currency.Pair `json:"instrument_id"` - Last float64 `json:"last,string"` - High24H float64 `json:"high_24h,string"` - Low24H float64 `json:"low_24h,string"` - BestBid float64 `json:"best_bid,string"` - BestAsk float64 `json:"best_ask,string"` - Volume24H float64 `json:"volume_24h,string"` - Timestamp time.Time `json:"timestamp"` + InstrumentID string `json:"instrument_id"` + Last float64 `json:"last,string"` + High24H float64 `json:"high_24h,string"` + Low24H float64 `json:"low_24h,string"` + BestBid float64 `json:"best_bid,string"` + BestAsk float64 `json:"best_ask,string"` + Volume24H float64 `json:"volume_24h,string"` + Timestamp time.Time `json:"timestamp"` } // GetSwapFilledOrdersDataRequest request data for GetSwapFilledOrdersData diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index fe10fee8..b1c794ba 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -140,11 +140,36 @@ const ( okGroupWsFuturesOrder = okGroupWsFuturesSubsection + okGroupWsOrder okGroupWsRateLimit = 30 + + allowableIterations = 25 + delimiterColon = ":" + delimiterDash = "-" + delimiterUnderscore = "_" ) -// orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time +// orderbookMutex Ensures if two entries arrive at once, only one can be +// processed at a time var orderbookMutex sync.Mutex -var defaultSubscribedChannels = []string{okGroupWsSpotDepth, okGroupWsSpotCandle300s, okGroupWsSpotTicker, okGroupWsSpotTrade} + +var defaultSpotSubscribedChannels = []string{okGroupWsSpotDepth, + okGroupWsSpotCandle300s, + okGroupWsSpotTicker, + okGroupWsSpotTrade} + +var defaultFuturesSubscribedChannels = []string{okGroupWsFuturesDepth, + okGroupWsFuturesCandle300s, + okGroupWsFuturesTicker, + okGroupWsFuturesTrade} + +var defaultIndexSubscribedChannels = []string{okGroupWsIndexCandle300s, + okGroupWsIndexTicker} + +var defaultSwapSubscribedChannels = []string{okGroupWsSwapDepth, + okGroupWsSwapCandle300s, + okGroupWsSwapTicker, + okGroupWsSwapTrade, + okGroupWsSwapFundingRate, + okGroupWsSwapMarkPrice} // WsConnect initiates a websocket connection func (o *OKGroup) WsConnect() error { @@ -161,13 +186,15 @@ func (o *OKGroup) WsConnect() error { o.Websocket.GetWebsocketURL()) } wg := sync.WaitGroup{} - wg.Add(2) + wg.Add(1) go o.WsHandleData(&wg) - go o.wsPingHandler(&wg) if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { err = o.WsLogin() if err != nil { - log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", o.Name, err) + log.Errorf(log.ExchangeSys, + "%v - authentication failed: %v\n", + o.Name, + err) } } @@ -177,36 +204,6 @@ func (o *OKGroup) WsConnect() error { return nil } -// wsPingHandler sends a message "ping" every 27 to maintain the connection to the websocket -func (o *OKGroup) wsPingHandler(wg *sync.WaitGroup) { - o.Websocket.Wg.Add(1) - defer o.Websocket.Wg.Done() - - ticker := time.NewTicker(time.Second * 27) - defer ticker.Stop() - - wg.Done() - - for { - select { - case <-o.Websocket.ShutdownC: - return - - case <-ticker.C: - if !o.Websocket.IsConnected() { - continue - } - err := o.WebsocketConn.Connection.WriteMessage(websocket.TextMessage, []byte("ping")) - if o.Verbose { - log.Debugf(log.ExchangeSys, "%v sending ping", o.GetName()) - } - if err != nil { - o.Websocket.DataHandler <- err - } - } - } -} - // WsHandleData handles the read data from the websocket connection func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) { o.Websocket.Wg.Add(1) @@ -240,7 +237,11 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) { err = common.JSONDecode(resp.Raw, &errorResponse) if err == nil && errorResponse.ErrorCode > 0 { if o.Verbose { - log.Debugf(log.ExchangeSys, "WS Error Event: %v Message: %v", errorResponse.Event, errorResponse.Message) + log.Debugf(log.ExchangeSys, + "WS Error Event: %v Message: %v for %s", + errorResponse.Event, + errorResponse.Message, + o.Name) } o.WsHandleErrorResponse(errorResponse) continue @@ -252,10 +253,12 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) { o.Websocket.SetCanUseAuthenticatedEndpoints(eventResponse.Success) } if o.Verbose { - log.Debugf(log.ExchangeSys, "WS Event: %v on Channel: %v", eventResponse.Event, eventResponse.Channel) + log.Debugf(log.ExchangeSys, + "WS Event: %v on Channel: %v for %s", + eventResponse.Event, + eventResponse.Channel, + o.Name) } - o.Websocket.DataHandler <- eventResponse - continue } } } @@ -273,7 +276,10 @@ func (o *OKGroup) WsLogin() error { base64 := crypto.Base64Encode(hmac) request := WebsocketEventRequest{ Operation: "login", - Arguments: []string{o.API.Credentials.Key, o.API.Credentials.ClientID, fmt.Sprintf("%v", unixTime), base64}, + Arguments: []string{o.API.Credentials.Key, + o.API.Credentials.ClientID, + fmt.Sprintf("%v", unixTime), + base64}, } err := o.WebsocketConn.SendMessage(request) if err != nil { @@ -286,7 +292,9 @@ func (o *OKGroup) WsLogin() error { // WsHandleErrorResponse sends an error message to ws handler func (o *OKGroup) WsHandleErrorResponse(event WebsocketErrorResponse) { errorMessage := fmt.Sprintf("%v error - %v message: %s ", - o.GetName(), event.ErrorCode, event.Message) + o.Name, + event.ErrorCode, + event.Message) if o.Verbose { log.Error(log.ExchangeSys, errorMessage) } @@ -314,28 +322,54 @@ func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string { // eg "spot/ticker:BTCUSD" results in "SPOT" func (o *OKGroup) GetAssetTypeFromTableName(table string) asset.Item { assetIndex := strings.Index(table, "/") - return asset.Item(table[:assetIndex]) + switch table[:assetIndex] { + case asset.Futures.String(): + return asset.Futures + case asset.Spot.String(): + return asset.Spot + case "swap": + return asset.PerpetualSwap + case asset.Index.String(): + return asset.Index + default: + log.Warnf(log.ExchangeSys, "%s unhandled asset type %s", + o.Name, + table[:assetIndex]) + return asset.Item(table[:assetIndex]) + } } // WsHandleDataResponse classifies the WS response and sends to appropriate handler func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) { switch o.GetWsChannelWithoutOrderType(response.Table) { - - case okGroupWsCandle60s, okGroupWsCandle180s, okGroupWsCandle300s, okGroupWsCandle900s, - okGroupWsCandle1800s, okGroupWsCandle3600s, okGroupWsCandle7200s, okGroupWsCandle14400s, - okGroupWsCandle21600s, okGroupWsCandle43200s, okGroupWsCandle86400s, okGroupWsCandle604900s: + case okGroupWsCandle60s, okGroupWsCandle180s, okGroupWsCandle300s, + okGroupWsCandle900s, okGroupWsCandle1800s, okGroupWsCandle3600s, + okGroupWsCandle7200s, okGroupWsCandle14400s, okGroupWsCandle21600s, + okGroupWsCandle43200s, okGroupWsCandle86400s, okGroupWsCandle604900s: o.wsProcessCandles(response) case okGroupWsDepth, okGroupWsDepth5: // Locking, orderbooks cannot be processed out of order orderbookMutex.Lock() err := o.WsProcessOrderBook(response) if err != nil { - pair := currency.NewPairDelimiter(response.Data[0].InstrumentID, "-") - channelToResubscribe := wshandler.WebsocketChannelSubscription{ - Channel: response.Table, - Currency: pair, + for i := range response.Data { + a := o.GetAssetTypeFromTableName(response.Table) + var c currency.Pair + switch a { + case asset.Futures, asset.PerpetualSwap: + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterDash) + default: + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash) + } + + channelToResubscribe := wshandler.WebsocketChannelSubscription{ + Channel: response.Table, + Currency: c, + } + o.Websocket.ResubscribeToChannel(channelToResubscribe) } - o.Websocket.ResubscribeToChannel(channelToResubscribe) } orderbookMutex.Unlock() case okGroupWsTicker: @@ -343,26 +377,37 @@ func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) { case okGroupWsTrade: o.wsProcessTrades(response) default: - logDataResponse(response) + logDataResponse(response, o.Name) } } // logDataResponse will log the details of any websocket data event // where there is no websocket datahandler for it -func logDataResponse(response *WebsocketDataResponse) { +func logDataResponse(response *WebsocketDataResponse, exchangeName string) { for i := range response.Data { - log.Errorf(log.ExchangeSys, "Unhandled channel: '%v'. Instrument '%v' Timestamp '%v', Data '%v", + log.Warnf(log.ExchangeSys, + "%s Unhandled channel: '%v'. Instrument '%v' Timestamp '%v'", + exchangeName, response.Table, response.Data[i].InstrumentID, - response.Data[i].Timestamp, - response.Data[i]) + response.Data[i].Timestamp) } } // wsProcessTickers converts ticker data and sends it to the datahandler func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) { for i := range response.Data { - instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-") + a := o.GetAssetTypeFromTableName(response.Table) + var c currency.Pair + switch a { + case asset.Futures, asset.PerpetualSwap: + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore) + default: + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash) + } + o.Websocket.DataHandler <- wshandler.TickerData{ Exchange: o.Name, Open: response.Data[i].Open24h, @@ -376,7 +421,7 @@ func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) { Last: response.Data[i].Last, Timestamp: response.Data[i].Timestamp, AssetType: o.GetAssetTypeFromTableName(response.Table), - Pair: instrument, + Pair: c, } } } @@ -384,11 +429,21 @@ func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) { // wsProcessTrades converts trade data and sends it to the datahandler func (o *OKGroup) wsProcessTrades(response *WebsocketDataResponse) { for i := range response.Data { - instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-") + a := o.GetAssetTypeFromTableName(response.Table) + var c currency.Pair + switch a { + case asset.Futures, asset.PerpetualSwap: + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore) + default: + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash) + } + o.Websocket.DataHandler <- wshandler.TradeData{ Amount: response.Data[i].Size, AssetType: o.GetAssetTypeFromTableName(response.Table), - CurrencyPair: instrument, + CurrencyPair: c, EventTime: time.Now().Unix(), Exchange: o.GetName(), Price: response.Data[i].WebsocketTradeResponse.Price, @@ -401,10 +456,24 @@ func (o *OKGroup) wsProcessTrades(response *WebsocketDataResponse) { // wsProcessCandles converts candle data and sends it to the data handler func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) { for i := range response.Data { - instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-") - timeData, err := time.Parse(time.RFC3339Nano, response.Data[i].WebsocketCandleResponse.Candle[0]) + a := o.GetAssetTypeFromTableName(response.Table) + var c currency.Pair + switch a { + case asset.Futures, asset.PerpetualSwap: + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore) + default: + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash) + } + + timeData, err := time.Parse(time.RFC3339Nano, + response.Data[i].WebsocketCandleResponse.Candle[0]) if err != nil { - log.Warnf(log.ExchangeSys, "%v Time data could not be parsed: %v", o.GetName(), response.Data[i].Candle[0]) + log.Warnf(log.ExchangeSys, + "%v Time data could not be parsed: %v", + o.Name, + response.Data[i].Candle[0]) } candleIndex := strings.LastIndex(response.Table, okGroupWsCandle) @@ -416,16 +485,36 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) { klineData := wshandler.KlineData{ AssetType: o.GetAssetTypeFromTableName(response.Table), - Pair: instrument, + Pair: c, Exchange: o.GetName(), Timestamp: timeData, Interval: candleInterval, } - klineData.OpenPrice, _ = strconv.ParseFloat(response.Data[i].Candle[1], 64) - klineData.HighPrice, _ = strconv.ParseFloat(response.Data[i].Candle[2], 64) - klineData.LowPrice, _ = strconv.ParseFloat(response.Data[i].Candle[3], 64) - klineData.ClosePrice, _ = strconv.ParseFloat(response.Data[i].Candle[4], 64) - klineData.Volume, _ = strconv.ParseFloat(response.Data[i].Candle[5], 64) + klineData.OpenPrice, err = strconv.ParseFloat(response.Data[i].Candle[1], 64) + if err != nil { + o.Websocket.DataHandler <- err + continue + } + klineData.HighPrice, err = strconv.ParseFloat(response.Data[i].Candle[2], 64) + if err != nil { + o.Websocket.DataHandler <- err + continue + } + klineData.LowPrice, err = strconv.ParseFloat(response.Data[i].Candle[3], 64) + if err != nil { + o.Websocket.DataHandler <- err + continue + } + klineData.ClosePrice, err = strconv.ParseFloat(response.Data[i].Candle[4], 64) + if err != nil { + o.Websocket.DataHandler <- err + continue + } + klineData.Volume, err = strconv.ParseFloat(response.Data[i].Candle[5], 64) + if err != nil { + o.Websocket.DataHandler <- err + continue + } o.Websocket.DataHandler <- klineData } @@ -434,57 +523,96 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) { // WsProcessOrderBook Validates the checksum and updates internal orderbook values func (o *OKGroup) WsProcessOrderBook(response *WebsocketDataResponse) (err error) { for i := range response.Data { - instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-") + a := o.GetAssetTypeFromTableName(response.Table) + var c currency.Pair + switch a { + case asset.Futures, asset.PerpetualSwap: + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore) + default: + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash) + } + if response.Action == okGroupWsOrderbookPartial { - err = o.WsProcessPartialOrderBook(&response.Data[i], instrument, response.Table) + err = o.WsProcessPartialOrderBook(&response.Data[i], c, a) + if err != nil { + return + } } else if response.Action == okGroupWsOrderbookUpdate { - err = o.WsProcessUpdateOrderbook(&response.Data[i], instrument, response.Table) + if len(response.Data[i].Asks) == 0 && len(response.Data[i].Bids) == 0 { + continue + } + err = o.WsProcessUpdateOrderbook(&response.Data[i], c, a) + if err != nil { + return + } } } return } // AppendWsOrderbookItems adds websocket orderbook data bid/asks into an orderbook item array -func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) (orderbookItems []orderbook.Item) { +func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) ([]orderbook.Item, error) { + var items []orderbook.Item for j := range entries { - amount, _ := strconv.ParseFloat(entries[j][1].(string), 64) - price, _ := strconv.ParseFloat(entries[j][0].(string), 64) - orderbookItems = append(orderbookItems, orderbook.Item{ - Amount: amount, - Price: price, - }) + amount, err := strconv.ParseFloat(entries[j][1].(string), 64) + if err != nil { + return nil, err + } + price, err := strconv.ParseFloat(entries[j][0].(string), 64) + if err != nil { + return nil, err + } + items = append(items, orderbook.Item{Amount: amount, Price: price}) } - return + return items, nil } // WsProcessPartialOrderBook takes websocket orderbook data and creates an orderbook // Calculates checksum to ensure it is valid -func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, tableName string) error { +func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, a asset.Item) error { signedChecksum := o.CalculatePartialOrderbookChecksum(wsEventData) if signedChecksum != wsEventData.Checksum { - return fmt.Errorf("channel: %v. Orderbook partial for %v checksum invalid", tableName, instrument) + return fmt.Errorf("%s channel: %s. Orderbook partial for %v checksum invalid", + o.Name, + a, + instrument) } if o.Verbose { - log.Debug(log.ExchangeSys, "Passed checksum!") + log.Debugf(log.ExchangeSys, + "%s passed checksum for instrument %s", + o.Name, + instrument) } - asks := o.AppendWsOrderbookItems(wsEventData.Asks) - bids := o.AppendWsOrderbookItems(wsEventData.Bids) + + asks, err := o.AppendWsOrderbookItems(wsEventData.Asks) + if err != nil { + return err + } + + bids, err := o.AppendWsOrderbookItems(wsEventData.Bids) + if err != nil { + return err + } + newOrderBook := orderbook.Base{ Asks: asks, Bids: bids, - AssetType: o.GetAssetTypeFromTableName(tableName), + AssetType: a, LastUpdated: wsEventData.Timestamp, Pair: instrument, ExchangeName: o.GetName(), } - err := o.Websocket.Orderbook.LoadSnapshot(&newOrderBook) + err = o.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return err } + o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Exchange: o.GetName(), - Asset: o.GetAssetTypeFromTableName(tableName), + Asset: a, Pair: instrument, } return nil @@ -492,123 +620,209 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i // WsProcessUpdateOrderbook updates an existing orderbook using websocket data // After merging WS data, it will sort, validate and finally update the existing orderbook -func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, tableName string) error { +func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, a asset.Item) error { update := wsorderbook.WebsocketOrderbookUpdate{ - AssetType: asset.Spot, - CurrencyPair: instrument, - UpdateTime: wsEventData.Timestamp, + Asset: a, + Pair: instrument, + UpdateTime: wsEventData.Timestamp, } - update.Asks = o.AppendWsOrderbookItems(wsEventData.Asks) - update.Bids = o.AppendWsOrderbookItems(wsEventData.Bids) - err := o.Websocket.Orderbook.Update(&update) - if err != nil { - log.Error(log.ExchangeSys, err) - } - updatedOb := o.Websocket.Orderbook.GetOrderbook(instrument, asset.Spot) - checksum := o.CalculateUpdateOrderbookChecksum(updatedOb) - if checksum == wsEventData.Checksum { - if o.Verbose { - log.Debug(log.ExchangeSys, "Orderbook valid") - } - o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: o.GetName(), - Asset: o.GetAssetTypeFromTableName(tableName), - Pair: instrument, - } - } else { - if o.Verbose { - log.Warnln(log.ExchangeSys, "Orderbook invalid") - } - return fmt.Errorf("channel: %v. Orderbook update for %v checksum invalid. Received %v Calculated %v", tableName, instrument, wsEventData.Checksum, checksum) + var err error + update.Asks, err = o.AppendWsOrderbookItems(wsEventData.Asks) + if err != nil { + return err } + update.Bids, err = o.AppendWsOrderbookItems(wsEventData.Bids) + if err != nil { + return err + } + + err = o.Websocket.Orderbook.Update(&update) + if err != nil { + return err + } + + updatedOb := o.Websocket.Orderbook.GetOrderbook(instrument, a) + checksum := o.CalculateUpdateOrderbookChecksum(updatedOb) + + if checksum != wsEventData.Checksum { + // re-sub + log.Warnf(log.ExchangeSys, "%s checksum failure for item %s", + o.Name, + wsEventData.InstrumentID) + return errors.New("checksum failed") + } + + o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ + Exchange: o.GetName(), + Asset: a, + Pair: instrument, + } + return nil } -// CalculatePartialOrderbookChecksum alternates over the first 25 bid and ask entries from websocket data -// The checksum is made up of the price and the quantity with a semicolon (:) deliminating them -// This will also work when there are less than 25 entries (for whatever reason) +// CalculatePartialOrderbookChecksum alternates over the first 25 bid and ask +// entries from websocket data. The checksum is made up of the price and the +// quantity with a semicolon (:) deliminating them. This will also work when +// there are less than 25 entries (for whatever reason) // eg Bid:Ask:Bid:Ask:Ask:Ask func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketDataWrapper) int32 { var checksum string - iterations := 25 - for i := 0; i < iterations; i++ { - bidsMessage := "" - askMessage := "" + for i := 0; i < allowableIterations; i++ { if len(orderbookData.Bids)-1 >= i { - bidsMessage = fmt.Sprintf("%v:%v:", orderbookData.Bids[i][0], orderbookData.Bids[i][1]) + checksum += orderbookData.Bids[i][0].(string) + + delimiterColon + + orderbookData.Bids[i][1].(string) + + delimiterColon } if len(orderbookData.Asks)-1 >= i { - askMessage = fmt.Sprintf("%v:%v:", orderbookData.Asks[i][0], orderbookData.Asks[i][1]) - - } - if checksum == "" { - checksum = fmt.Sprintf("%v%v", bidsMessage, askMessage) - } else { - checksum = fmt.Sprintf("%v%v%v", checksum, bidsMessage, askMessage) + checksum += orderbookData.Asks[i][0].(string) + + delimiterColon + + orderbookData.Asks[i][1].(string) + + delimiterColon } } - checksum = strings.TrimSuffix(checksum, ":") + checksum = strings.TrimSuffix(checksum, delimiterColon) return int32(crc32.ChecksumIEEE([]byte(checksum))) } -// CalculateUpdateOrderbookChecksum alternates over the first 25 bid and ask entries of a merged orderbook -// The checksum is made up of the price and the quantity with a semicolon (:) deliminating them -// This will also work when there are less than 25 entries (for whatever reason) +// CalculateUpdateOrderbookChecksum alternates over the first 25 bid and ask +// entries of a merged orderbook. The checksum is made up of the price and the +// quantity with a semicolon (:) deliminating them. This will also work when +// there are less than 25 entries (for whatever reason) // eg Bid:Ask:Bid:Ask:Ask:Ask func (o *OKGroup) CalculateUpdateOrderbookChecksum(orderbookData *orderbook.Base) int32 { var checksum string - iterations := 25 - for i := 0; i < iterations; i++ { - bidsMessage := "" - askMessage := "" + for i := 0; i < allowableIterations; i++ { if len(orderbookData.Bids)-1 >= i { price := strconv.FormatFloat(orderbookData.Bids[i].Price, 'f', -1, 64) amount := strconv.FormatFloat(orderbookData.Bids[i].Amount, 'f', -1, 64) - bidsMessage = fmt.Sprintf("%v:%v:", price, amount) + checksum += price + delimiterColon + amount + delimiterColon } if len(orderbookData.Asks)-1 >= i { price := strconv.FormatFloat(orderbookData.Asks[i].Price, 'f', -1, 64) amount := strconv.FormatFloat(orderbookData.Asks[i].Amount, 'f', -1, 64) - askMessage = fmt.Sprintf("%v:%v:", price, amount) - } - if checksum == "" { - checksum = fmt.Sprintf("%v%v", bidsMessage, askMessage) - } else { - checksum = fmt.Sprintf("%v%v%v", checksum, bidsMessage, askMessage) + checksum += price + delimiterColon + amount + delimiterColon } } - checksum = strings.TrimSuffix(checksum, ":") + checksum = strings.TrimSuffix(checksum, delimiterColon) return int32(crc32.ChecksumIEEE([]byte(checksum))) } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() +// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be +// handled by ManageSubscriptions() func (o *OKGroup) GenerateDefaultSubscriptions() { - enabledCurrencies := o.GetEnabledPairs(asset.Spot) var subscriptions []wshandler.WebsocketChannelSubscription - if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - defaultSubscribedChannels = append(defaultSubscribedChannels, okGroupWsSpotMarginAccount, okGroupWsSpotAccount, okGroupWsSpotOrder) - } - for i := range defaultSubscribedChannels { - for j := range enabledCurrencies { - enabledCurrencies[j].Delimiter = "-" - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: defaultSubscribedChannels[i], - Currency: enabledCurrencies[j], - }) + assets := o.GetAssetTypes() + for x := range assets { + enabledCurrencies := o.GetEnabledPairs(assets[x]) + if len(enabledCurrencies) == 0 { + continue + } + + switch assets[x] { + case asset.Spot: + for i := range enabledCurrencies { + for y := range defaultSpotSubscribedChannels { + subscriptions = append(subscriptions, + wshandler.WebsocketChannelSubscription{ + Channel: defaultSpotSubscribedChannels[y], + Currency: o.FormatExchangeCurrency(enabledCurrencies[i], + asset.Spot), + }) + } + } + + if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + subscriptions = append(subscriptions, + wshandler.WebsocketChannelSubscription{ + Channel: okGroupWsSpotMarginAccount, + }, + wshandler.WebsocketChannelSubscription{ + Channel: okGroupWsSpotAccount, + }, + wshandler.WebsocketChannelSubscription{ + Channel: okGroupWsSpotOrder, + }) + } + case asset.Futures: + for i := range enabledCurrencies { + for y := range defaultFuturesSubscribedChannels { + subscriptions = append(subscriptions, + wshandler.WebsocketChannelSubscription{ + Channel: defaultFuturesSubscribedChannels[y], + Currency: o.FormatExchangeCurrency(enabledCurrencies[i], + asset.Futures), + }) + } + } + + if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + subscriptions = append(subscriptions, + wshandler.WebsocketChannelSubscription{ + Channel: okGroupWsFuturesAccount, + }, + wshandler.WebsocketChannelSubscription{ + Channel: okGroupWsFuturesPosition, + }, + wshandler.WebsocketChannelSubscription{ + Channel: okGroupWsFuturesOrder, + }) + } + case asset.PerpetualSwap: + for i := range enabledCurrencies { + for y := range defaultSwapSubscribedChannels { + subscriptions = append(subscriptions, + wshandler.WebsocketChannelSubscription{ + Channel: defaultSwapSubscribedChannels[y], + Currency: o.FormatExchangeCurrency(enabledCurrencies[i], + asset.PerpetualSwap), + }) + } + } + + if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + subscriptions = append(subscriptions, + wshandler.WebsocketChannelSubscription{ + Channel: okGroupWsSwapAccount, + }, + wshandler.WebsocketChannelSubscription{ + Channel: okGroupWsSwapPosition, + }, + wshandler.WebsocketChannelSubscription{ + Channel: okGroupWsSwapOrder, + }) + } + case asset.Index: + for i := range enabledCurrencies { + for y := range defaultIndexSubscribedChannels { + subscriptions = append(subscriptions, + wshandler.WebsocketChannelSubscription{ + Channel: defaultIndexSubscribedChannels[y], + Currency: o.FormatExchangeCurrency(enabledCurrencies[i], asset.Index), + }) + } + } + default: + o.Websocket.DataHandler <- errors.New("unhandled asset type") } } + o.Websocket.SubscribeToChannels(subscriptions) } // Subscribe sends a websocket message to receive data from the channel func (o *OKGroup) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { + c := channelToSubscribe.Currency.String() request := WebsocketEventRequest{ Operation: "subscribe", - Arguments: []string{fmt.Sprintf("%v:%v", channelToSubscribe.Channel, channelToSubscribe.Currency.String())}, + Arguments: []string{channelToSubscribe.Channel + delimiterColon + c}, } if strings.EqualFold(channelToSubscribe.Channel, okGroupWsSpotAccount) { - request.Arguments = []string{fmt.Sprintf("%v:%v", channelToSubscribe.Channel, channelToSubscribe.Currency.Base.String())} + request.Arguments = []string{channelToSubscribe.Channel + + delimiterColon + + channelToSubscribe.Currency.Base.String()} } return o.WebsocketConn.SendMessage(request) @@ -618,7 +832,9 @@ func (o *OKGroup) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscri func (o *OKGroup) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { request := WebsocketEventRequest{ Operation: "unsubscribe", - Arguments: []string{fmt.Sprintf("%v:%v", channelToSubscribe.Channel, channelToSubscribe.Currency.String())}, + Arguments: []string{channelToSubscribe.Channel + + delimiterColon + + channelToSubscribe.Currency.String()}, } return o.WebsocketConn.SendMessage(request) } diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index 52673cc7..5f7a85f8 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -1,6 +1,7 @@ package okgroup import ( + "errors" "fmt" "strconv" "strings" @@ -79,62 +80,93 @@ func (o *OKGroup) FetchOrderbook(p currency.Pair, assetType asset.Item) (resp or } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (o *OKGroup) UpdateOrderbook(p currency.Pair, assetType asset.Item) (resp orderbook.Base, err error) { - orderbookNew, err := o.GetSpotOrderBook(GetSpotOrderBookRequest{ - InstrumentID: o.FormatExchangeCurrency(p, assetType).String(), - }) +func (o *OKGroup) UpdateOrderbook(p currency.Pair, a asset.Item) (orderbook.Base, error) { + var resp orderbook.Base + if a == asset.Index { + return resp, errors.New("no orderbooks for index") + } + + orderbookNew, err := o.GetOrderBook(GetOrderBookRequest{ + InstrumentID: o.FormatExchangeCurrency(p, a).String(), + }, a) if err != nil { - return + return resp, err } for x := range orderbookNew.Bids { amount, convErr := strconv.ParseFloat(orderbookNew.Bids[x][1], 64) if convErr != nil { - log.Errorf(log.ExchangeSys, - "Could not convert %v to float64", - orderbookNew.Bids[x][1]) + return resp, err } price, convErr := strconv.ParseFloat(orderbookNew.Bids[x][0], 64) if convErr != nil { - log.Errorf(log.ExchangeSys, - "Could not convert %v to float64", - orderbookNew.Bids[x][0]) + return resp, err } + + var liquidationOrders, orderCount int64 + // Contract specific variables + if len(orderbookNew.Bids[x]) == 4 { + liquidationOrders, convErr = strconv.ParseInt(orderbookNew.Bids[x][2], 10, 64) + if convErr != nil { + return resp, err + } + + orderCount, convErr = strconv.ParseInt(orderbookNew.Bids[x][3], 10, 64) + if convErr != nil { + return resp, err + } + } + resp.Bids = append(resp.Bids, orderbook.Item{ - Amount: amount, - Price: price, + Amount: amount, + Price: price, + LiquidationOrders: liquidationOrders, + OrderCount: orderCount, }) } for x := range orderbookNew.Asks { amount, convErr := strconv.ParseFloat(orderbookNew.Asks[x][1], 64) if convErr != nil { - log.Errorf(log.ExchangeSys, - "Could not convert %v to float64", - orderbookNew.Asks[x][1]) + return resp, err } price, convErr := strconv.ParseFloat(orderbookNew.Asks[x][0], 64) if convErr != nil { - log.Errorf(log.ExchangeSys, - "Could not convert %v to float64", - orderbookNew.Asks[x][0]) + return resp, err } + + var liquidationOrders, orderCount int64 + // Contract specific variables + if len(orderbookNew.Asks[x]) == 4 { + liquidationOrders, convErr = strconv.ParseInt(orderbookNew.Asks[x][2], 10, 64) + if convErr != nil { + return resp, err + } + + orderCount, convErr = strconv.ParseInt(orderbookNew.Asks[x][3], 10, 64) + if convErr != nil { + return resp, err + } + } + resp.Asks = append(resp.Asks, orderbook.Item{ - Amount: amount, - Price: price, + Amount: amount, + Price: price, + LiquidationOrders: liquidationOrders, + OrderCount: orderCount, }) } resp.Pair = p - resp.AssetType = assetType + resp.AssetType = a resp.ExchangeName = o.Name err = resp.Process() if err != nil { - return + return resp, err } - return orderbook.Get(o.Name, p, assetType) + return orderbook.Get(o.Name, p, a) } // GetAccountInfo retrieves balances for all enabled currencies diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index 6396148f..4d9392f6 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -117,7 +117,17 @@ func (s *Service) SetNewData(b *Base) error { return err } - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] = &Book{b: b, + // Below instigates orderbook item separation so we can ensure, in the event + // of a simultaneous update via websocket/rest/fix, we don't affect package + // scoped orderbook data which could result in a potential panic + cpyBook := *b + cpyBook.Bids = make([]Item, len(b.Bids)) + copy(cpyBook.Bids, b.Bids) + cpyBook.Asks = make([]Item, len(b.Asks)) + copy(cpyBook.Asks, b.Asks) + + s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] = &Book{ + b: &cpyBook, Main: singleID, Assoc: ids} return nil diff --git a/exchanges/orderbook/orderbook_types.go b/exchanges/orderbook/orderbook_types.go index 56260dbe..3686adcc 100644 --- a/exchanges/orderbook/orderbook_types.go +++ b/exchanges/orderbook/orderbook_types.go @@ -50,6 +50,10 @@ type Item struct { Amount float64 Price float64 ID int64 + + // Contract variables + LiquidationOrders int64 + OrderCount int64 } // Base holds the fields for the orderbook base diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index b63036f1..3ce7d352 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -256,7 +256,7 @@ func TestSubmitOrder(t *testing.T) { var orderSubmission = &order.Submit{ Pair: currency.Pair{ - Delimiter: "_", + Delimiter: delimiterUnderscore, Base: currency.BTC, Quote: currency.LTC, }, diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index 79008dda..4a81a363 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -26,6 +26,7 @@ const ( wsTickerDataID = 1002 ws24HourExchangeVolumeID = 1003 wsHeartbeat = 1010 + delimiterUnderscore = "_" ) var ( @@ -107,10 +108,15 @@ func (p *Poloniex) WsHandleData() { if len(data) == 2 && chanID != wsHeartbeat { if checkSubscriptionSuccess(data) { if p.Verbose { - log.Debugf(log.ExchangeSys, "poloniex websocket subscribed to channel successfully. %d", chanID) + log.Debugf(log.ExchangeSys, + "%s websocket subscribed to channel successfully. %d", + p.Name, + chanID) } } else { - p.Websocket.DataHandler <- fmt.Errorf("poloniex websocket subscription to channel failed. %d", chanID) + p.Websocket.DataHandler <- fmt.Errorf("%s websocket subscription to channel failed. %d", + p.Name, + chanID) } continue } @@ -135,17 +141,20 @@ func (p *Poloniex) WsHandleData() { dataL3map := dataL3[1].(map[string]interface{}) currencyPair, ok := dataL3map["currencyPair"].(string) if !ok { - p.Websocket.DataHandler <- errors.New("poloniex.go error - could not find currency pair in map") + p.Websocket.DataHandler <- fmt.Errorf("%s websocket could not find currency pair in map", + p.Name) continue } orderbookData, ok := dataL3map["orderBook"].([]interface{}) if !ok { - p.Websocket.DataHandler <- errors.New("poloniex.go error - could not find orderbook data in map") + p.Websocket.DataHandler <- fmt.Errorf("%s websocket could not find orderbook data in map", + p.Name) continue } - err := p.WsProcessOrderbookSnapshot(orderbookData, currencyPair) + err = p.WsProcessOrderbookSnapshot(orderbookData, + currencyPair) if err != nil { p.Websocket.DataHandler <- err continue @@ -158,7 +167,9 @@ func (p *Poloniex) WsHandleData() { } case "o": currencyPair := currencyIDMap[chanID] - err := p.WsProcessOrderbookUpdate(int64(data[1].(float64)), dataL3, currencyPair) + err = p.WsProcessOrderbookUpdate(int64(data[1].(float64)), + dataL3, + currencyPair) if err != nil { p.Websocket.DataHandler <- err continue @@ -180,8 +191,16 @@ func (p *Poloniex) WsHandleData() { side = "sell" } trade.Side = side - trade.Volume, _ = strconv.ParseFloat(dataL3[3].(string), 64) - trade.Price, _ = strconv.ParseFloat(dataL3[4].(string), 64) + trade.Volume, err = strconv.ParseFloat(dataL3[3].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + continue + } + trade.Price, err = strconv.ParseFloat(dataL3[4].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + continue + } trade.Timestamp = int64(dataL3[5].(float64)) p.Websocket.DataHandler <- wshandler.TradeData{ @@ -203,16 +222,60 @@ func (p *Poloniex) WsHandleData() { func (p *Poloniex) wsHandleTickerData(data []interface{}) { tickerData := data[2].([]interface{}) var t WsTicker - currencyPair := currencyIDMap[int(tickerData[0].(float64))] - t.LastPrice, _ = strconv.ParseFloat(tickerData[1].(string), 64) - t.LowestAsk, _ = strconv.ParseFloat(tickerData[2].(string), 64) - t.HighestBid, _ = strconv.ParseFloat(tickerData[3].(string), 64) - t.PercentageChange, _ = strconv.ParseFloat(tickerData[4].(string), 64) - t.BaseCurrencyVolume24H, _ = strconv.ParseFloat(tickerData[5].(string), 64) - t.QuoteCurrencyVolume24H, _ = strconv.ParseFloat(tickerData[6].(string), 64) + currencyPair := currency.NewPairDelimiter(currencyIDMap[int(tickerData[0].(float64))], delimiterUnderscore) + if !p.GetEnabledPairs(asset.Spot).Contains(currencyPair, true) { + return + } + + var err error + t.LastPrice, err = strconv.ParseFloat(tickerData[1].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + t.LowestAsk, err = strconv.ParseFloat(tickerData[2].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + t.HighestBid, err = strconv.ParseFloat(tickerData[3].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + t.PercentageChange, err = strconv.ParseFloat(tickerData[4].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + t.BaseCurrencyVolume24H, err = strconv.ParseFloat(tickerData[5].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + t.QuoteCurrencyVolume24H, err = strconv.ParseFloat(tickerData[6].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + t.IsFrozen = tickerData[7].(float64) == 1 - t.HighestTradeIn24H, _ = strconv.ParseFloat(tickerData[8].(string), 64) - t.LowestTradePrice24H, _ = strconv.ParseFloat(tickerData[9].(string), 64) + t.HighestTradeIn24H, err = strconv.ParseFloat(tickerData[8].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + t.LowestTradePrice24H, err = strconv.ParseFloat(tickerData[9].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } p.Websocket.DataHandler <- wshandler.TickerData{ Exchange: p.Name, @@ -225,7 +288,7 @@ func (p *Poloniex) wsHandleTickerData(data []interface{}) { Last: t.LastPrice, Timestamp: time.Now(), AssetType: asset.Spot, - Pair: currency.NewPairDelimiter(currencyPair, "_"), + Pair: currencyPair, } } @@ -234,7 +297,12 @@ func (p *Poloniex) wsHandleAccountData(accountData [][]interface{}) { for i := range accountData { switch accountData[i][0].(string) { case "b": - amount, _ := strconv.ParseFloat(accountData[i][3].(string), 64) + amount, err := strconv.ParseFloat(accountData[i][3].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + response := WsAccountBalanceUpdateResponse{ currencyID: accountData[i][1].(float64), wallet: accountData[i][2].(string), @@ -242,9 +310,23 @@ func (p *Poloniex) wsHandleAccountData(accountData [][]interface{}) { } p.Websocket.DataHandler <- response case "n": - timeParse, _ := time.Parse("2006-01-02 15:04:05", accountData[i][6].(string)) - rate, _ := strconv.ParseFloat(accountData[i][4].(string), 64) - amount, _ := strconv.ParseFloat(accountData[i][5].(string), 64) + timeParse, err := time.Parse("2006-01-02 15:04:05", accountData[i][6].(string)) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + rate, err := strconv.ParseFloat(accountData[i][4].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + amount, err := strconv.ParseFloat(accountData[i][5].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } response := WsNewLimitOrderResponse{ currencyID: accountData[i][1].(float64), @@ -262,11 +344,35 @@ func (p *Poloniex) wsHandleAccountData(accountData [][]interface{}) { } p.Websocket.DataHandler <- response case "t": - timeParse, _ := time.Parse("2006-01-02 15:04:05", accountData[i][8].(string)) - rate, _ := strconv.ParseFloat(accountData[i][2].(string), 64) - amount, _ := strconv.ParseFloat(accountData[i][3].(string), 64) - feeMultiplier, _ := strconv.ParseFloat(accountData[i][4].(string), 64) - totalFee, _ := strconv.ParseFloat(accountData[i][7].(string), 64) + timeParse, err := time.Parse("2006-01-02 15:04:05", accountData[i][8].(string)) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + rate, err := strconv.ParseFloat(accountData[i][2].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + amount, err := strconv.ParseFloat(accountData[i][3].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + feeMultiplier, err := strconv.ParseFloat(accountData[i][4].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } + + totalFee, err := strconv.ParseFloat(accountData[i][7].(string), 64) + if err != nil { + p.Websocket.DataHandler <- err + return + } response := WsTradeNotificationResponse{ TradeID: accountData[i][1].(float64), @@ -336,7 +442,6 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e // WsProcessOrderbookUpdate processes new orderbook updates func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []interface{}, symbol string) error { - sideCheck := target[1].(float64) cP := currency.NewPairFromString(symbol) price, err := strconv.ParseFloat(target[2].(string), 64) if err != nil { @@ -347,11 +452,11 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []inter return err } update := &wsorderbook.WebsocketOrderbookUpdate{ - CurrencyPair: cP, - AssetType: asset.Spot, - UpdateID: sequenceNumber, + Pair: cP, + Asset: asset.Spot, + UpdateID: sequenceNumber, } - if sideCheck == 0 { + if target[1].(float64) == 1 { update.Bids = []orderbook.Item{{Price: price, Amount: volume}} } else { update.Asks = []orderbook.Item{{Price: price, Amount: volume}} @@ -374,7 +479,7 @@ func (p *Poloniex) GenerateDefaultSubscriptions() { enabledCurrencies := p.GetEnabledPairs(asset.Spot) for j := range enabledCurrencies { - enabledCurrencies[j].Delimiter = "_" + enabledCurrencies[j].Delimiter = delimiterUnderscore subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ Channel: "orderbook", Currency: enabledCurrencies[j], diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 09144a30..f5844854 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -58,11 +58,11 @@ func (p *Poloniex) SetDefaults() { }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ - Delimiter: "_", + Delimiter: delimiterUnderscore, Uppercase: true, }, ConfigFormat: ¤cy.PairFormat{ - Delimiter: "_", + Delimiter: delimiterUnderscore, Uppercase: true, }, } @@ -163,7 +163,7 @@ func (p *Poloniex) Setup(exch *config.ExchangeConfig) error { p.Websocket.Orderbook.Setup( exch.WebsocketOrderbookBufferLimit, - true, + false, true, true, false, diff --git a/exchanges/request/request.go b/exchanges/request/request.go index 66d0cae9..10141a36 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -277,8 +277,13 @@ func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, re reader = resp.Body default: - log.Warnf(log.ExchangeSys, "%s request response content type differs from JSON; received %v [path: %s]\n", - r.Name, resp.Header.Get("Content-Type"), path) + if verbose { + log.Warnf(log.ExchangeSys, + "%s request response content type differs from JSON; received %v [path: %s]\n", + r.Name, + resp.Header.Get("Content-Type"), + path) + } reader = resp.Body } } diff --git a/exchanges/websocket/wsorderbook/wsorderbook.go b/exchanges/websocket/wsorderbook/wsorderbook.go index e24a93ea..26c4de55 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook.go +++ b/exchanges/websocket/wsorderbook/wsorderbook.go @@ -25,27 +25,28 @@ func (w *WebsocketOrderbookLocal) Setup(obBufferLimit int, bufferEnabled, sortBu // Volume == 0; deletion at price target // Price target not found; append of price target // Price target found; amend volume of price target -func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpdate) error { - if (orderbookUpdate.Bids == nil && orderbookUpdate.Asks == nil) || - (len(orderbookUpdate.Bids) == 0 && len(orderbookUpdate.Asks) == 0) { - return fmt.Errorf("%v cannot have bids and ask targets both nil", w.exchangeName) +func (w *WebsocketOrderbookLocal) Update(u *WebsocketOrderbookUpdate) error { + if (u.Bids == nil && u.Asks == nil) || (len(u.Bids) == 0 && len(u.Asks) == 0) { + return fmt.Errorf("%v cannot have bids and ask targets both nil", + w.exchangeName) } w.m.Lock() defer w.m.Unlock() - obLookup, ok := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] + obLookup, ok := w.ob[u.Pair][u.Asset] if !ok { return fmt.Errorf("ob.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s", w.exchangeName, - orderbookUpdate.CurrencyPair.String(), - orderbookUpdate.AssetType) + u.Pair, + u.Asset) } + if w.bufferEnabled { - overBufferLimit := w.processBufferUpdate(obLookup, orderbookUpdate) + overBufferLimit := w.processBufferUpdate(obLookup, u) if !overBufferLimit { return nil } } else { - w.processObUpdate(obLookup, orderbookUpdate) + w.processObUpdate(obLookup, u) } err := obLookup.Process() if err != nil { @@ -53,23 +54,23 @@ func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpda } if w.bufferEnabled { // Reset the buffer - w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = nil + w.buffer[u.Pair][u.Asset] = nil } return nil } -func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) bool { +func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, u *WebsocketOrderbookUpdate) bool { if w.buffer == nil { w.buffer = make(map[currency.Pair]map[asset.Item][]*WebsocketOrderbookUpdate) } - if w.buffer[orderbookUpdate.CurrencyPair] == nil { - w.buffer[orderbookUpdate.CurrencyPair] = make(map[asset.Item][]*WebsocketOrderbookUpdate) + if w.buffer[u.Pair] == nil { + w.buffer[u.Pair] = make(map[asset.Item][]*WebsocketOrderbookUpdate) } - bufferLookup := w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] + bufferLookup := w.buffer[u.Pair][u.Asset] if len(bufferLookup) <= w.obBufferLimit { - bufferLookup = append(bufferLookup, orderbookUpdate) + bufferLookup = append(bufferLookup, u) if len(bufferLookup) < w.obBufferLimit { - w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = bufferLookup + w.buffer[u.Pair][u.Asset] = bufferLookup return false } } @@ -88,61 +89,59 @@ func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, orderbo for i := 0; i < len(bufferLookup); i++ { w.processObUpdate(o, bufferLookup[i]) } - w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = bufferLookup + w.buffer[u.Pair][u.Asset] = bufferLookup return true } -func (w *WebsocketOrderbookLocal) processObUpdate(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) { +func (w *WebsocketOrderbookLocal) processObUpdate(o *orderbook.Base, u *WebsocketOrderbookUpdate) { if w.updateEntriesByID { - w.updateByIDAndAction(o, orderbookUpdate) + w.updateByIDAndAction(o, u) } else { - w.updateAsksByPrice(o, orderbookUpdate) - w.updateBidsByPrice(o, orderbookUpdate) + w.updateAsksByPrice(o, u) + w.updateBidsByPrice(o, u) } } -func (w *WebsocketOrderbookLocal) updateAsksByPrice(o *orderbook.Base, base *WebsocketOrderbookUpdate) { - for j := 0; j < len(base.Asks); j++ { - found := false - for k := 0; k < len(o.Asks); k++ { - if o.Asks[k].Price == base.Asks[j].Price { - found = true - if base.Asks[j].Amount == 0 { - o.Asks = append(o.Asks[:k], - o.Asks[k+1:]...) - break +func (w *WebsocketOrderbookLocal) updateAsksByPrice(o *orderbook.Base, u *WebsocketOrderbookUpdate) { +updates: + for j := range u.Asks { + for k := range o.Asks { + if o.Asks[k].Price == u.Asks[j].Price { + if u.Asks[j].Amount <= 0 { + o.Asks = append(o.Asks[:k], o.Asks[k+1:]...) + continue updates } - o.Asks[k].Amount = base.Asks[j].Amount - break + o.Asks[k].Amount = u.Asks[j].Amount + continue updates } } - if !found { - o.Asks = append(o.Asks, base.Asks[j]) + if u.Asks[j].Amount == 0 { + continue } + o.Asks = append(o.Asks, u.Asks[j]) } sort.Slice(o.Asks, func(i, j int) bool { return o.Asks[i].Price < o.Asks[j].Price }) } -func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, base *WebsocketOrderbookUpdate) { - for j := 0; j < len(base.Bids); j++ { - found := false - for k := 0; k < len(o.Bids); k++ { - if o.Bids[k].Price == base.Bids[j].Price { - found = true - if base.Bids[j].Amount == 0 { - o.Bids = append(o.Bids[:k], - o.Bids[k+1:]...) - break +func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, u *WebsocketOrderbookUpdate) { +updates: + for j := range u.Bids { + for k := range o.Bids { + if o.Bids[k].Price == u.Bids[j].Price { + if u.Bids[j].Amount <= 0 { + o.Bids = append(o.Bids[:k], o.Bids[k+1:]...) + continue updates } - o.Bids[k].Amount = base.Bids[j].Amount - break + o.Bids[k].Amount = u.Bids[j].Amount + continue updates } } - if !found { - o.Bids = append(o.Bids, base.Bids[j]) + if u.Bids[j].Amount == 0 { + continue } + o.Bids = append(o.Bids, u.Bids[j]) } sort.Slice(o.Bids, func(i, j int) bool { return o.Bids[i].Price > o.Bids[j].Price @@ -151,49 +150,52 @@ func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, base *Web // updateByIDAndAction will receive an action to execute against the orderbook // it will then match by IDs instead of price to perform the action -func (w *WebsocketOrderbookLocal) updateByIDAndAction(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) { - switch orderbookUpdate.Action { +func (w *WebsocketOrderbookLocal) updateByIDAndAction(o *orderbook.Base, u *WebsocketOrderbookUpdate) { + switch u.Action { case "update": - for _, target := range orderbookUpdate.Bids { - for i := range o.Bids { - if o.Bids[i].ID == target.ID { - o.Bids[i].Amount = target.Amount + for x := range u.Bids { + for y := range o.Bids { + if o.Bids[y].ID == u.Bids[x].ID { + o.Bids[y].Amount = u.Bids[x].Amount break } } } - for _, target := range orderbookUpdate.Asks { - for i := range o.Asks { - if o.Asks[i].ID == target.ID { - o.Asks[i].Amount = target.Amount + for x := range u.Asks { + for y := range o.Asks { + if o.Asks[y].ID == u.Asks[x].ID { + o.Asks[y].Amount = u.Asks[x].Amount break } } } case "delete": - for _, target := range orderbookUpdate.Bids { - for i := 0; i < len(o.Bids); i++ { - if o.Bids[i].ID == target.ID { - o.Bids = append(o.Bids[:i], - o.Bids[i+1:]...) - i-- + for x := range u.Bids { + for y := 0; y < len(o.Bids); y++ { + if o.Bids[y].ID == u.Bids[x].ID { + o.Bids = append(o.Bids[:y], o.Bids[y+1:]...) break } } } - for _, target := range orderbookUpdate.Asks { - for i := 0; i < len(o.Asks); i++ { - if o.Asks[i].ID == target.ID { - o.Asks = append(o.Asks[:i], - o.Asks[i+1:]...) - i-- + for x := range u.Asks { + for y := 0; y < len(o.Asks); y++ { + if o.Asks[y].ID == u.Asks[x].ID { + o.Asks = append(o.Asks[:y], o.Asks[y+1:]...) break } } } case "insert": - o.Bids = append(o.Bids, orderbookUpdate.Bids...) - o.Asks = append(o.Asks, orderbookUpdate.Asks...) + o.Bids = append(o.Bids, u.Bids...) + sort.Slice(o.Bids, func(i, j int) bool { + return o.Bids[i].Price > o.Bids[j].Price + }) + + o.Asks = append(o.Asks, u.Asks...) + sort.Slice(o.Asks, func(i, j int) bool { + return o.Asks[i].Price < o.Asks[j].Price + }) } } @@ -225,22 +227,17 @@ func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base) err if w.ob[newOrderbook.Pair] == nil { w.ob[newOrderbook.Pair] = make(map[asset.Item]*orderbook.Base) } - fullObLookup := w.ob[newOrderbook.Pair][newOrderbook.AssetType] - if fullObLookup != nil && - (len(fullObLookup.Asks) > 0 || - len(fullObLookup.Bids) > 0) { - fullObLookup = newOrderbook - return newOrderbook.Process() - } + w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook return newOrderbook.Process() } -// GetOrderbook use sparingly. Modifying anything here will ruin hash calculation and cause problems -func (w *WebsocketOrderbookLocal) GetOrderbook(p currency.Pair, assetType asset.Item) *orderbook.Base { +// GetOrderbook use sparingly. Modifying anything here will ruin hash +// calculation and cause problems +func (w *WebsocketOrderbookLocal) GetOrderbook(p currency.Pair, a asset.Item) *orderbook.Base { w.m.Lock() defer w.m.Unlock() - return w.ob[p][assetType] + return w.ob[p][a] } // FlushCache flushes w.ob data to be garbage collected and refreshed when a diff --git a/exchanges/websocket/wsorderbook/wsorderbook_test.go b/exchanges/websocket/wsorderbook/wsorderbook_test.go index 78e27cf7..b67461fe 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_test.go +++ b/exchanges/websocket/wsorderbook/wsorderbook_test.go @@ -20,14 +20,15 @@ var itemArray = [][]orderbook.Item{ {{Price: 5000, Amount: 1, ID: 5}}, } +var cp = currency.NewPairFromString("BTCUSD") + const ( exchangeName = "exchangeTest" ) -func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, bids []orderbook.Item, err error) { +func createSnapshot() (obl *WebsocketOrderbookLocal, asks, bids []orderbook.Item, err error) { var snapShot1 orderbook.Base snapShot1.ExchangeName = exchangeName - curr = currency.NewPairFromString("BTCUSD") asks = []orderbook.Item{ {Price: 4000, Amount: 1, ID: 6}, } @@ -37,7 +38,7 @@ func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, b snapShot1.Asks = asks snapShot1.Bids = bids snapShot1.AssetType = asset.Spot - snapShot1.Pair = curr + snapShot1.Pair = cp obl = &WebsocketOrderbookLocal{exchangeName: exchangeName} err = obl.LoadSnapshot(&snapShot1) return @@ -52,7 +53,7 @@ func bidAskGenerator() []orderbook.Item { price = 1 } response = append(response, orderbook.Item{ - Amount: float64(rand.Intn(1)), + Amount: float64(rand.Intn(10)), Price: price, ID: int64(i), }) @@ -61,7 +62,7 @@ func bidAskGenerator() []orderbook.Item { } func BenchmarkUpdateBidsByPrice(b *testing.B) { - ob, curr, _, _, err := createSnapshot() + ob, _, _, err := createSnapshot() if err != nil { b.Error(err) } @@ -69,18 +70,18 @@ func BenchmarkUpdateBidsByPrice(b *testing.B) { for i := 0; i < b.N; i++ { bidAsks := bidAskGenerator() update := &WebsocketOrderbookUpdate{ - Bids: bidAsks, - Asks: bidAsks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + Bids: bidAsks, + Asks: bidAsks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, } - ob.updateBidsByPrice(ob.ob[curr][asset.Spot], update) + ob.updateBidsByPrice(ob.ob[cp][asset.Spot], update) } } func BenchmarkUpdateAsksByPrice(b *testing.B) { - ob, curr, _, _, err := createSnapshot() + ob, _, _, err := createSnapshot() if err != nil { b.Error(err) } @@ -88,25 +89,24 @@ func BenchmarkUpdateAsksByPrice(b *testing.B) { for i := 0; i < b.N; i++ { bidAsks := bidAskGenerator() update := &WebsocketOrderbookUpdate{ - Bids: bidAsks, - Asks: bidAsks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + Bids: bidAsks, + Asks: bidAsks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, } - ob.updateAsksByPrice(ob.ob[curr][asset.Spot], update) + ob.updateAsksByPrice(ob.ob[cp][asset.Spot], update) } } // BenchmarkBufferPerformance demonstrates buffer more performant than multi // process calls func BenchmarkBufferPerformance(b *testing.B) { - obl, curr, asks, bids, err := createSnapshot() + obl, asks, bids, err := createSnapshot() if err != nil { b.Fatal(err) } obl.bufferEnabled = true - cp := currency.NewPairFromString("BTCUSD") // This is to ensure we do not send in zero orderbook info to our main book // in orderbook.go, orderbooks should not be zero even after an update. dummyItem := orderbook.Item{ @@ -116,11 +116,11 @@ func BenchmarkBufferPerformance(b *testing.B) { } obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) update := &WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + Bids: bids, + Asks: asks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -136,13 +136,12 @@ func BenchmarkBufferPerformance(b *testing.B) { // BenchmarkBufferSortingPerformance benchmark func BenchmarkBufferSortingPerformance(b *testing.B) { - obl, curr, asks, bids, err := createSnapshot() + obl, asks, bids, err := createSnapshot() if err != nil { b.Fatal(err) } obl.bufferEnabled = true obl.sortBuffer = true - cp := currency.NewPairFromString("BTCUSD") // This is to ensure we do not send in zero orderbook info to our main book // in orderbook.go, orderbooks should not be zero even after an update. dummyItem := orderbook.Item{ @@ -152,11 +151,11 @@ func BenchmarkBufferSortingPerformance(b *testing.B) { } obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) update := &WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + Bids: bids, + Asks: asks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -172,14 +171,13 @@ func BenchmarkBufferSortingPerformance(b *testing.B) { // BenchmarkBufferSortingPerformance benchmark func BenchmarkBufferSortingByIDPerformance(b *testing.B) { - obl, curr, asks, bids, err := createSnapshot() + obl, asks, bids, err := createSnapshot() if err != nil { b.Fatal(err) } obl.bufferEnabled = true obl.sortBuffer = true obl.sortBufferByUpdateIDs = true - cp := currency.NewPairFromString("BTCUSD") // This is to ensure we do not send in zero orderbook info to our main book // in orderbook.go, orderbooks should not be zero even after an update. dummyItem := orderbook.Item{ @@ -189,11 +187,11 @@ func BenchmarkBufferSortingByIDPerformance(b *testing.B) { } obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) update := &WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + Bids: bids, + Asks: asks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -210,11 +208,10 @@ func BenchmarkBufferSortingByIDPerformance(b *testing.B) { // BenchmarkNoBufferPerformance demonstrates orderbook process less performant // than buffer func BenchmarkNoBufferPerformance(b *testing.B) { - obl, curr, asks, bids, err := createSnapshot() + obl, asks, bids, err := createSnapshot() if err != nil { b.Fatal(err) } - cp := currency.NewPairFromString("BTCUSD") // This is to ensure we do not send in zero orderbook info to our main book // in orderbook.go, orderbooks should not be zero even after an update. dummyItem := orderbook.Item{ @@ -224,11 +221,11 @@ func BenchmarkNoBufferPerformance(b *testing.B) { } obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) update := &WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + Bids: bids, + Asks: asks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -243,41 +240,41 @@ func BenchmarkNoBufferPerformance(b *testing.B) { } func TestUpdates(t *testing.T) { - obl, curr, _, _, err := createSnapshot() + obl, _, _, err := createSnapshot() if err != nil { t.Error(err) } - obl.updateAsksByPrice(obl.ob[curr][asset.Spot], &WebsocketOrderbookUpdate{ - Bids: itemArray[5], - Asks: itemArray[5], - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{ + Bids: itemArray[5], + Asks: itemArray[5], + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, }) if err != nil { t.Error(err) } - obl.updateAsksByPrice(obl.ob[curr][asset.Spot], &WebsocketOrderbookUpdate{ - Bids: itemArray[0], - Asks: itemArray[0], - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{ + Bids: itemArray[0], + Asks: itemArray[0], + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, }) if err != nil { t.Error(err) } - if len(obl.ob[curr][asset.Spot].Asks) != 3 { + if len(obl.ob[cp][asset.Spot].Asks) != 3 { t.Error("Did not update") } } // TestHittingTheBuffer logic test func TestHittingTheBuffer(t *testing.T) { - obl, curr, _, _, err := createSnapshot() + obl, _, _, err := createSnapshot() if err != nil { t.Fatal(err) } @@ -287,30 +284,30 @@ func TestHittingTheBuffer(t *testing.T) { asks := itemArray[i] bids := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + Bids: bids, + Asks: asks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, }) if err != nil { t.Fatal(err) } } - if len(obl.ob[curr][asset.Spot].Asks) != 3 { - t.Log(obl.ob[curr][asset.Spot]) + if len(obl.ob[cp][asset.Spot].Asks) != 3 { + t.Log(obl.ob[cp][asset.Spot]) t.Errorf("expected 3 entries, received: %v", - len(obl.ob[curr][asset.Spot].Asks)) + len(obl.ob[cp][asset.Spot].Asks)) } - if len(obl.ob[curr][asset.Spot].Bids) != 3 { + if len(obl.ob[cp][asset.Spot].Bids) != 3 { t.Errorf("expected 3 entries, received: %v", - len(obl.ob[curr][asset.Spot].Bids)) + len(obl.ob[cp][asset.Spot].Bids)) } } // TestInsertWithIDs logic test func TestInsertWithIDs(t *testing.T) { - obl, curr, _, _, err := createSnapshot() + obl, _, _, err := createSnapshot() if err != nil { t.Fatal(err) } @@ -321,30 +318,30 @@ func TestInsertWithIDs(t *testing.T) { asks := itemArray[i] bids := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, - Action: "insert", + Bids: bids, + Asks: asks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, + Action: "insert", }) if err != nil { t.Fatal(err) } } - if len(obl.ob[curr][asset.Spot].Asks) != 6 { + if len(obl.ob[cp][asset.Spot].Asks) != 6 { t.Errorf("expected 6 entries, received: %v", - len(obl.ob[curr][asset.Spot].Asks)) + len(obl.ob[cp][asset.Spot].Asks)) } - if len(obl.ob[curr][asset.Spot].Bids) != 6 { + if len(obl.ob[cp][asset.Spot].Bids) != 6 { t.Errorf("expected 6 entries, received: %v", - len(obl.ob[curr][asset.Spot].Bids)) + len(obl.ob[cp][asset.Spot].Bids)) } } // TestSortIDs logic test func TestSortIDs(t *testing.T) { - obl, curr, _, _, err := createSnapshot() + obl, _, _, err := createSnapshot() if err != nil { t.Fatal(err) } @@ -356,34 +353,33 @@ func TestSortIDs(t *testing.T) { asks := itemArray[i] bids := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: curr, - UpdateID: int64(i), - AssetType: asset.Spot, + Bids: bids, + Asks: asks, + Pair: cp, + UpdateID: int64(i), + Asset: asset.Spot, }) if err != nil { t.Fatal(err) } } - if len(obl.ob[curr][asset.Spot].Asks) != 3 { + if len(obl.ob[cp][asset.Spot].Asks) != 3 { t.Errorf("expected 3 entries, received: %v", - len(obl.ob[curr][asset.Spot].Asks)) + len(obl.ob[cp][asset.Spot].Asks)) } - if len(obl.ob[curr][asset.Spot].Bids) != 3 { + if len(obl.ob[cp][asset.Spot].Bids) != 3 { t.Errorf("expected 3 entries, received: %v", - len(obl.ob[curr][asset.Spot].Bids)) + len(obl.ob[cp][asset.Spot].Bids)) } } // TestDeleteWithIDs logic test func TestDeleteWithIDs(t *testing.T) { - obl, curr, _, _, err := createSnapshot() + obl, _, _, err := createSnapshot() if err != nil { t.Fatal(err) } - cp := currency.NewPairFromString("BTCUSD") // This is to ensure we do not send in zero orderbook info to our main book // in orderbook.go, orderbooks should not be zero even after an update. dummyItem := orderbook.Item{ @@ -392,35 +388,41 @@ func TestDeleteWithIDs(t *testing.T) { ID: 1337, } obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) + obl.ob[cp][asset.Spot].Asks = append(obl.ob[cp][asset.Spot].Asks, + itemArray[2][0]) + obl.ob[cp][asset.Spot].Asks = append(obl.ob[cp][asset.Spot].Asks, + itemArray[1][0]) + obl.updateEntriesByID = true for i := 0; i < len(itemArray); i++ { asks := itemArray[i] bids := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, - Action: "delete", + Bids: bids, + Asks: asks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, + Action: "delete", }) if err != nil { t.Fatal(err) } } - if len(obl.ob[curr][asset.Spot].Asks) != 0 { + + if len(obl.ob[cp][asset.Spot].Asks) != 0 { t.Errorf("expected 0 entries, received: %v", - len(obl.ob[curr][asset.Spot].Asks)) + len(obl.ob[cp][asset.Spot].Asks)) } - if len(obl.ob[curr][asset.Spot].Bids) != 1 { + if len(obl.ob[cp][asset.Spot].Bids) != 1 { t.Errorf("expected 1 entries, received: %v", - len(obl.ob[curr][asset.Spot].Bids)) + len(obl.ob[cp][asset.Spot].Bids)) } } // TestUpdateWithIDs logic test func TestUpdateWithIDs(t *testing.T) { - obl, curr, _, _, err := createSnapshot() + obl, _, _, err := createSnapshot() if err != nil { t.Fatal(err) } @@ -429,35 +431,38 @@ func TestUpdateWithIDs(t *testing.T) { asks := itemArray[i] bids := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, - Action: "update", + Bids: bids, + Asks: asks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, + Action: "update", }) if err != nil { t.Fatal(err) } } - if len(obl.ob[curr][asset.Spot].Asks) != 1 { - t.Log(obl.ob[curr][asset.Spot]) - t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks)) + if len(obl.ob[cp][asset.Spot].Asks) != 1 { + t.Log(obl.ob[cp][asset.Spot]) + t.Errorf("expected 1 entries, received: %v", + len(obl.ob[cp][asset.Spot].Asks)) } - if len(obl.ob[curr][asset.Spot].Bids) != 1 { - t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids)) + if len(obl.ob[cp][asset.Spot].Bids) != 1 { + t.Errorf("expected 1 entries, received: %v", + len(obl.ob[cp][asset.Spot].Bids)) } } // TestOutOfOrderIDs logic test func TestOutOfOrderIDs(t *testing.T) { - obl, curr, _, _, err := createSnapshot() + obl, _, _, err := createSnapshot() if err != nil { t.Fatal(err) } outOFOrderIDs := []int64{2, 1, 5, 3, 4, 6, 7} if itemArray[0][0].Price != 1000 { - t.Errorf("expected sorted price to be 3000, received: %v", itemArray[1][0].Price) + t.Errorf("expected sorted price to be 3000, received: %v", + itemArray[1][0].Price) } obl.bufferEnabled = true obl.sortBuffer = true @@ -465,18 +470,19 @@ func TestOutOfOrderIDs(t *testing.T) { for i := 0; i < len(itemArray); i++ { asks := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ - Asks: asks, - CurrencyPair: curr, - UpdateID: outOFOrderIDs[i], - AssetType: asset.Spot, + Asks: asks, + Pair: cp, + UpdateID: outOFOrderIDs[i], + Asset: asset.Spot, }) if err != nil { t.Fatal(err) } } // Index 1 since index 0 is price 7000 - if obl.ob[curr][asset.Spot].Asks[1].Price != 2000 { - t.Errorf("expected sorted price to be 3000, received: %v", obl.ob[curr][asset.Spot].Asks[1].Price) + if obl.ob[cp][asset.Spot].Asks[1].Price != 2000 { + t.Errorf("expected sorted price to be 3000, received: %v", + obl.ob[cp][asset.Spot].Asks[1].Price) } } @@ -484,7 +490,6 @@ func TestOutOfOrderIDs(t *testing.T) { func TestRunUpdateWithoutSnapshot(t *testing.T) { var obl WebsocketOrderbookLocal var snapShot1 orderbook.Base - curr := currency.NewPairFromString("BTCUSD") asks := []orderbook.Item{ {Price: 4000, Amount: 1, ID: 8}, } @@ -495,14 +500,14 @@ func TestRunUpdateWithoutSnapshot(t *testing.T) { snapShot1.Asks = asks snapShot1.Bids = bids snapShot1.AssetType = asset.Spot - snapShot1.Pair = curr + snapShot1.Pair = cp obl.exchangeName = exchangeName err := obl.Update(&WebsocketOrderbookUpdate{ - Bids: bids, - Asks: asks, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + Bids: bids, + Asks: asks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, }) if err == nil { t.Fatal("expected an error running update with no snapshot loaded") @@ -516,18 +521,17 @@ func TestRunUpdateWithoutSnapshot(t *testing.T) { func TestRunUpdateWithoutAnyUpdates(t *testing.T) { var obl WebsocketOrderbookLocal var snapShot1 orderbook.Base - curr := currency.NewPairFromString("BTCUSD") snapShot1.Asks = []orderbook.Item{} snapShot1.Bids = []orderbook.Item{} snapShot1.AssetType = asset.Spot - snapShot1.Pair = curr + snapShot1.Pair = cp obl.exchangeName = exchangeName err := obl.Update(&WebsocketOrderbookUpdate{ - Bids: snapShot1.Asks, - Asks: snapShot1.Bids, - CurrencyPair: curr, - UpdateTime: time.Now(), - AssetType: asset.Spot, + Bids: snapShot1.Asks, + Asks: snapShot1.Bids, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, }) if err == nil { t.Fatal("expected an error running update with no snapshot loaded") @@ -542,11 +546,10 @@ func TestRunUpdateWithoutAnyUpdates(t *testing.T) { func TestRunSnapshotWithNoData(t *testing.T) { var obl WebsocketOrderbookLocal var snapShot1 orderbook.Base - curr := currency.NewPairFromString("BTCUSD") snapShot1.Asks = []orderbook.Item{} snapShot1.Bids = []orderbook.Item{} snapShot1.AssetType = asset.Spot - snapShot1.Pair = curr + snapShot1.Pair = cp snapShot1.ExchangeName = "test" obl.exchangeName = "test" err := obl.LoadSnapshot(&snapShot1) @@ -563,7 +566,6 @@ func TestLoadSnapshot(t *testing.T) { var obl WebsocketOrderbookLocal var snapShot1 orderbook.Base snapShot1.ExchangeName = "SnapshotWithOverride" - curr := currency.NewPairFromString("BTCUSD") asks := []orderbook.Item{ {Price: 4000, Amount: 1, ID: 8}, } @@ -573,7 +575,7 @@ func TestLoadSnapshot(t *testing.T) { snapShot1.Asks = asks snapShot1.Bids = bids snapShot1.AssetType = asset.Spot - snapShot1.Pair = curr + snapShot1.Pair = cp err := obl.LoadSnapshot(&snapShot1) if err != nil { t.Error(err) @@ -582,15 +584,15 @@ func TestLoadSnapshot(t *testing.T) { // TestFlushCache logic test func TestFlushCache(t *testing.T) { - obl, curr, _, _, err := createSnapshot() + obl, _, _, err := createSnapshot() if err != nil { t.Fatal(err) } - if obl.ob[curr][asset.Spot] == nil { + if obl.ob[cp][asset.Spot] == nil { t.Error("expected ob to have ask entries") } obl.FlushCache() - if obl.ob[curr][asset.Spot] != nil { + if obl.ob[cp][asset.Spot] != nil { t.Error("expected ob be flushed") } @@ -632,7 +634,7 @@ func TestInsertingSnapShots(t *testing.T) { snapShot1.Asks = asks snapShot1.Bids = bids snapShot1.AssetType = asset.Spot - snapShot1.Pair = currency.NewPairFromString("BTCUSD") + snapShot1.Pair = cp err := obl.LoadSnapshot(&snapShot1) if err != nil { t.Fatal(err) @@ -714,23 +716,29 @@ func TestInsertingSnapShots(t *testing.T) { t.Fatal(err) } if obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0] != snapShot1.Asks[0] { - t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot1.Asks[0], obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0]) + t.Errorf("loaded data mismatch. Expected %v, received %v", + snapShot1.Asks[0], + obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0]) } if obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0] != snapShot2.Asks[0] { - t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot2.Asks[0], obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0]) + t.Errorf("loaded data mismatch. Expected %v, received %v", + snapShot2.Asks[0], + obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0]) } if obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0] != snapShot3.Asks[0] { - t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot3.Asks[0], obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0]) + t.Errorf("loaded data mismatch. Expected %v, received %v", + snapShot3.Asks[0], + obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0]) } } func TestGetOrderbook(t *testing.T) { - obl, curr, _, _, err := createSnapshot() + obl, _, _, err := createSnapshot() if err != nil { t.Fatal(err) } - ob := obl.GetOrderbook(curr, asset.Spot) - if obl.ob[curr][asset.Spot] != ob { + ob := obl.GetOrderbook(cp, asset.Spot) + if obl.ob[cp][asset.Spot] != ob { t.Error("Failed to get orderbook") } } @@ -738,7 +746,35 @@ func TestGetOrderbook(t *testing.T) { func TestSetup(t *testing.T) { w := WebsocketOrderbookLocal{} w.Setup(1, true, true, true, true, "hi") - if w.obBufferLimit != 1 || !w.bufferEnabled || !w.sortBuffer || !w.sortBufferByUpdateIDs || !w.updateEntriesByID || w.exchangeName != "hi" { + if w.obBufferLimit != 1 || + !w.bufferEnabled || + !w.sortBuffer || + !w.sortBufferByUpdateIDs || + !w.updateEntriesByID || + w.exchangeName != "hi" { t.Errorf("Setup incorrectly loaded %s", w.exchangeName) } } + +func TestEnsureMultipleUpdatesViaPrice(t *testing.T) { + obl, _, _, err := createSnapshot() + if err != nil { + t.Error(err) + } + + asks := bidAskGenerator() + obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{ + Bids: asks, + Asks: asks, + Pair: cp, + UpdateTime: time.Now(), + Asset: asset.Spot, + }) + if err != nil { + t.Error(err) + } + + if len(obl.ob[cp][asset.Spot].Asks) <= 3 { + t.Errorf("Insufficient updates") + } +} diff --git a/exchanges/websocket/wsorderbook/wsorderbook_types.go b/exchanges/websocket/wsorderbook/wsorderbook_types.go index 6766c797..2ae77a29 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_types.go +++ b/exchanges/websocket/wsorderbook/wsorderbook_types.go @@ -25,11 +25,11 @@ type WebsocketOrderbookLocal struct { // WebsocketOrderbookUpdate stores orderbook updates and dictates what features to use when processing type WebsocketOrderbookUpdate struct { - UpdateID int64 // Used when no time is provided - UpdateTime time.Time - AssetType asset.Item - Action string // Used in conjunction with UpdateEntriesByID - Bids []orderbook.Item - Asks []orderbook.Item - CurrencyPair currency.Pair + UpdateID int64 // Used when no time is provided + UpdateTime time.Time + Asset asset.Item + Action string // Used in conjunction with UpdateEntriesByID + Bids []orderbook.Item + Asks []orderbook.Item + Pair currency.Pair } diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index 4c22f6a7..d097d406 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -287,8 +287,8 @@ func TestGetActiveOrders(t *testing.T) { var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, - Currencies: []currency.Pair{currency.NewPair(currency.LTC, - currency.BTC)}, + Currencies: []currency.Pair{currency.NewPair(currency.XRP, + currency.USDT)}, } _, err := z.GetActiveOrders(&getOrdersRequest) @@ -327,6 +327,7 @@ func areTestAPIKeysSet() bool { func TestSubmitOrder(t *testing.T) { z.SetDefaults() TestSetup(t) + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip(fmt.Sprintf("ApiKey: %s. Can place orders: %v", z.API.Credentials.Key, @@ -336,8 +337,8 @@ func TestSubmitOrder(t *testing.T) { var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", - Base: currency.QTUM, - Quote: currency.USD, + Base: currency.XRP, + Quote: currency.USDT, }, OrderSide: order.Buy, OrderType: order.Limit, @@ -361,7 +362,7 @@ func TestCancelExchangeOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - currencyPair := currency.NewPair(currency.LTC, currency.BTC) + currencyPair := currency.NewPair(currency.XRP, currency.USDT) var orderCancellation = &order.Cancel{ OrderID: "1", @@ -387,7 +388,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - currencyPair := currency.NewPair(currency.LTC, currency.BTC) + currencyPair := currency.NewPair(currency.XRP, currency.USDT) var orderCancellation = &order.Cancel{ OrderID: "1", diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 0ac6d2ec..f85cfd9c 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -104,7 +104,7 @@ func (z *ZB) WsHandleData() { Last: ticker.Data.Last, Bid: ticker.Data.Buy, Ask: ticker.Data.Sell, - Timestamp: time.Unix(0, ticker.Date), + Timestamp: time.Unix(0, ticker.Date*int64(time.Millisecond)), AssetType: asset.Spot, Pair: currency.NewPairFromString(cPair[0]), } @@ -119,19 +119,17 @@ func (z *ZB) WsHandleData() { var asks []orderbook.Item for i := range depth.Asks { - ask := depth.Asks[i].([]interface{}) asks = append(asks, orderbook.Item{ - Amount: ask[1].(float64), - Price: ask[0].(float64), + Amount: depth.Asks[i][1].(float64), + Price: depth.Asks[i][0].(float64), }) } var bids []orderbook.Item for i := range depth.Bids { - bid := depth.Bids[i].([]interface{}) bids = append(bids, orderbook.Item{ - Amount: bid[1].(float64), - Price: bid[0].(float64), + Amount: depth.Bids[i][1].(float64), + Price: depth.Bids[i][0].(float64), }) } @@ -172,7 +170,7 @@ func (z *ZB) WsHandleData() { channelInfo := strings.Split(result.Channel, "_") cPair := currency.NewPairFromString(channelInfo[0]) z.Websocket.DataHandler <- wshandler.TradeData{ - Timestamp: time.Unix(0, t.Date), + Timestamp: time.Unix(0, t.Date*int64(time.Millisecond)), CurrencyPair: cPair, AssetType: asset.Spot, Exchange: z.GetName(), diff --git a/exchanges/zb/zb_websocket_types.go b/exchanges/zb/zb_websocket_types.go index 4faef0a0..983c92aa 100644 --- a/exchanges/zb/zb_websocket_types.go +++ b/exchanges/zb/zb_websocket_types.go @@ -43,20 +43,20 @@ type WsTicker struct { // WsDepth defines websocket orderbook data type WsDepth struct { - Timestamp int64 `json:"timestamp"` - Asks []interface{} `json:"asks"` - Bids []interface{} `json:"bids"` + Timestamp int64 `json:"timestamp"` + Asks [][]interface{} `json:"asks"` + Bids [][]interface{} `json:"bids"` } // WsTrades defines websocket trade data type WsTrades struct { Data []struct { - Amount float64 `json:"amount,string"` - Price float64 `json:"price,string"` - TID int64 `json:"tid"` - Date int64 `json:"date"` - Type string `json:"type"` - TradeType string `json:"trade_type"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + TID interface{} `json:"tid"` + Date int64 `json:"date"` + Type string `json:"type"` + TradeType string `json:"trade_type"` } `json:"data"` } diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 59cede51..4ca4efd7 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -2,8 +2,8 @@ package zb import ( "errors" - "fmt" "strconv" + "strings" "sync" "time" @@ -221,15 +221,16 @@ func (z *ZB) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, return tickerPrice, err } - for _, x := range z.GetEnabledPairs(assetType) { + enabledPairs := z.GetEnabledPairs(assetType) + for x := range enabledPairs { // We can't use either pair format here, so format it to lower- // case and without any delimiter - curr := x.Format("", false).String() + curr := enabledPairs[x].Format("", false).String() if _, ok := result[curr]; !ok { continue } var tp ticker.Price - tp.Pair = x + tp.Pair = enabledPairs[x] tp.High = result[curr].High tp.Last = result[curr].Last tp.Ask = result[curr].Sell @@ -276,12 +277,14 @@ func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.B for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{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.Item{Amount: data[1], Price: data[0]}) + orderBook.Asks = append(orderBook.Asks, + orderbook.Item{Amount: data[1], Price: data[0]}) } orderBook.Pair = p @@ -366,7 +369,7 @@ func (z *ZB) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } response, err := z.SpotNewOrder(params) if response > 0 { - submitOrderResponse.OrderID = fmt.Sprintf("%v", response) + submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } if err == nil { submitOrderResponse.IsOrderPlaced = true @@ -398,13 +401,13 @@ func (z *ZB) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { var allOpenOrders []Order enabledPairs := z.GetEnabledPairs(asset.Spot) for x := range enabledPairs { - // Limiting to 10 pages - for pageNumber := int64(0); pageNumber < 11; pageNumber++ { - fCurr := z.FormatExchangeCurrency(enabledPairs[x], asset.Spot).String() - openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(fCurr, - pageNumber, - 10) + fPair := z.FormatExchangeCurrency(enabledPairs[x], asset.Spot).String() + for y := int64(1); ; y++ { + openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(fPair, y, 10) if err != nil { + if strings.Contains(err.Error(), "3001") { + break + } return cancelAllOrdersResponse, err } @@ -413,6 +416,10 @@ func (z *ZB) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { } allOpenOrders = append(allOpenOrders, openOrders...) + + if len(openOrders) != 10 { + break + } } } @@ -481,20 +488,25 @@ func (z *ZB) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { func (z *ZB) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { var allOrders []Order for x := range req.Currencies { - // Limiting to 10 pages - for pageNumber := int64(0); pageNumber < 11; pageNumber++ { - fCurr := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() - resp, err := z.GetUnfinishedOrdersIgnoreTradeType(fCurr, - pageNumber, - 10) + for i := int64(1); ; i++ { + fPair := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() + resp, err := z.GetUnfinishedOrdersIgnoreTradeType(fPair, i, 10) if err != nil { + if strings.Contains(err.Error(), "3001") { + break + } return nil, err } + if len(resp) == 0 { break } allOrders = append(allOrders, resp...) + + if len(resp) != 10 { + break + } } } @@ -505,7 +517,7 @@ func (z *ZB) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error orderDate := time.Unix(int64(allOrders[i].TradeDate), 0) orderSide := orderSideMap[allOrders[i].Type] orders = append(orders, order.Detail{ - ID: fmt.Sprintf("%d", allOrders[i].ID), + ID: strconv.FormatInt(allOrders[i].ID, 10), Amount: allOrders[i].TotalAmount, Exchange: z.Name, OrderDate: orderDate, @@ -536,10 +548,9 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error } for x := range req.Currencies { - // Limiting to 10 pages - for pageNumber := int64(0); pageNumber < 11; pageNumber++ { - fCurr := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() - resp, err := z.GetOrders(fCurr, pageNumber, side) + for y := int64(1); ; y++ { + fPair := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() + resp, err := z.GetOrders(fPair, y, side) if err != nil { return nil, err } @@ -549,6 +560,10 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error } allOrders = append(allOrders, resp...) + + if len(resp) != 10 { + break + } } } @@ -559,7 +574,7 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error orderDate := time.Unix(int64(allOrders[i].TradeDate), 0) orderSide := orderSideMap[allOrders[i].Type] orders = append(orders, order.Detail{ - ID: fmt.Sprintf("%d", allOrders[i].ID), + ID: strconv.FormatInt(allOrders[i].ID, 10), Amount: allOrders[i].TotalAmount, Exchange: z.Name, OrderDate: orderDate, From 52e2686b9ee059898ab4fef521c900f7df7c45b6 Mon Sep 17 00:00:00 2001 From: lozdog245 <37864968+lozdog245@users.noreply.github.com> Date: Wed, 20 Nov 2019 09:50:14 +1100 Subject: [PATCH 60/71] Change exchanges usage of GetName to Name (#378) * Change exchanges usage of GetName to Name * Changed GetName to Name --- cmd/exchange_template/wrapper_file.tmpl | 12 ++++++------ exchanges/alphapoint/alphapoint_wrapper.go | 10 +++++----- exchanges/anx/anx_live_test.go | 2 +- exchanges/anx/anx_mock_test.go | 2 +- exchanges/anx/anx_wrapper.go | 14 +++++++------- exchanges/binance/binance_live_test.go | 2 +- exchanges/binance/binance_mock_test.go | 2 +- exchanges/binance/binance_websocket.go | 8 ++++---- exchanges/binance/binance_wrapper.go | 10 +++++----- exchanges/bitfinex/bitfinex_websocket.go | 12 ++++++------ exchanges/bitfinex/bitfinex_wrapper.go | 10 +++++----- exchanges/bitflyer/bitflyer_wrapper.go | 8 ++++---- exchanges/bithumb/bithumb_wrapper.go | 8 ++++---- exchanges/bitmex/bitmex_websocket.go | 8 ++++---- exchanges/bitmex/bitmex_wrapper.go | 10 +++++----- exchanges/bitstamp/bitstamp_live_test.go | 2 +- exchanges/bitstamp/bitstamp_mock_test.go | 2 +- exchanges/bitstamp/bitstamp_wrapper.go | 12 ++++++------ exchanges/bittrex/bittrex_test.go | 2 +- exchanges/bittrex/bittrex_wrapper.go | 10 +++++----- exchanges/btcmarkets/btcmarkets.go | 4 ++-- exchanges/btcmarkets/btcmarkets_websocket.go | 10 +++++----- exchanges/btcmarkets/btcmarkets_wrapper.go | 12 ++++++------ exchanges/btse/btse_wrapper.go | 6 +++--- exchanges/coinbasepro/coinbasepro_websocket.go | 6 +++--- exchanges/coinbasepro/coinbasepro_wrapper.go | 12 ++++++------ exchanges/coinut/coinut_websocket.go | 8 ++++---- exchanges/coinut/coinut_wrapper.go | 12 ++++++------ exchanges/exmo/exmo_wrapper.go | 8 ++++---- exchanges/gateio/gateio_websocket.go | 8 ++++---- exchanges/gateio/gateio_wrapper.go | 10 +++++----- exchanges/gemini/gemini_live_test.go | 2 +- exchanges/gemini/gemini_mock_test.go | 2 +- exchanges/gemini/gemini_websocket.go | 6 +++--- exchanges/gemini/gemini_wrapper.go | 10 +++++----- exchanges/hitbtc/hitbtc_websocket.go | 6 +++--- exchanges/hitbtc/hitbtc_wrapper.go | 14 +++++++------- exchanges/huobi/huobi_websocket.go | 6 +++--- exchanges/huobi/huobi_wrapper.go | 14 +++++++------- exchanges/itbit/itbit_wrapper.go | 10 +++++----- exchanges/kraken/kraken_wrapper.go | 10 +++++----- exchanges/lakebtc/lakebtc_websocket.go | 8 ++++---- exchanges/localbitcoins/localbitcoins_live_test.go | 2 +- exchanges/localbitcoins/localbitcoins_mock_test.go | 2 +- exchanges/localbitcoins/localbitcoins_wrapper.go | 12 ++++++------ exchanges/mock/README.md | 4 ++-- exchanges/okcoin/okcoin_test.go | 2 +- exchanges/okex/okex_test.go | 2 +- exchanges/okgroup/okgroup_websocket.go | 10 +++++----- exchanges/okgroup/okgroup_wrapper.go | 2 +- exchanges/poloniex/poloniex_live_test.go | 2 +- exchanges/poloniex/poloniex_mock_test.go | 2 +- exchanges/poloniex/poloniex_websocket.go | 6 +++--- exchanges/poloniex/poloniex_wrapper.go | 12 ++++++------ exchanges/yobit/yobit_wrapper.go | 8 ++++---- exchanges/zb/zb_websocket.go | 6 +++--- exchanges/zb/zb_wrapper.go | 8 ++++---- 57 files changed, 205 insertions(+), 205 deletions(-) diff --git a/cmd/exchange_template/wrapper_file.tmpl b/cmd/exchange_template/wrapper_file.tmpl index 0346f6a3..fd86723b 100644 --- a/cmd/exchange_template/wrapper_file.tmpl +++ b/cmd/exchange_template/wrapper_file.tmpl @@ -24,9 +24,9 @@ func ({{.Variable}} *{{.CapitalName}}) Start(wg *sync.WaitGroup) { // Run implements the {{.CapitalName}} wrapper func ({{.Variable}} *{{.CapitalName}}) Run() { if {{.Variable}}.Verbose { -{{if .WS}} log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", {{.Variable}}.GetName(), common.IsEnabled({{.Variable}}.Websocket.IsEnabled()), {{.Variable}}.Websocket.GetWebsocketURL()) {{end}} - log.Debugf(log.ExchangeSys, "%s polling delay: %ds.\n", {{.Variable}}.GetName(), {{.Variable}}.RESTPollingDelay) - log.Debugf(log.ExchangeSys, "%s %d currencies enabled: %s.\n", {{.Variable}}.GetName(), len({{.Variable}}.EnabledPairs), {{.Variable}}.EnabledPairs) +{{if .WS}} log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", {{.Variable}}.Name, common.IsEnabled({{.Variable}}.Websocket.IsEnabled()), {{.Variable}}.Websocket.GetWebsocketURL()) {{end}} + log.Debugf(log.ExchangeSys, "%s polling delay: %ds.\n", {{.Variable}}.Name, {{.Variable}}.RESTPollingDelay) + log.Debugf(log.ExchangeSys, "%s %d currencies enabled: %s.\n", {{.Variable}}.Name, len({{.Variable}}.EnabledPairs), {{.Variable}}.EnabledPairs) } } @@ -60,7 +60,7 @@ func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(p currency.Pair, assetType a // FetchTicker returns the ticker for a currency pair func ({{.Variable}} *{{.CapitalName}}) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker({{.Variable}}.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker({{.Variable}}.Name, p, assetType) if err != nil { return {{.Variable}}.UpdateTicker(p, assetType) } @@ -69,7 +69,7 @@ func ({{.Variable}} *{{.CapitalName}}) FetchTicker(p currency.Pair, assetType as // FetchOrderbook returns orderbook base on the currency pair func ({{.Variable}} *{{.CapitalName}}) FetchOrderbook(currency currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get({{.Variable}}.GetName(), currency, assetType) + ob, err := orderbook.Get({{.Variable}}.Name, currency, assetType) if err != nil { return {{.Variable}}.UpdateOrderbook(currency, assetType) } @@ -93,7 +93,7 @@ func ({{.Variable}} *{{.CapitalName}}) UpdateOrderbook(p currency.Pair, assetTyp // orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: asks.Quantity, Price: asks.Price}) //} - //orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) + //orderbook.ProcessOrderbook(b.Name, p, orderBook, assetType) //return orderbook.Get({{.Variable}}.Name, p, assetType) return orderBook, nil // NOTE DO NOT USE AS RETURN } diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index db352257..a28e4368 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -88,7 +88,7 @@ func (a *Alphapoint) UpdateTradablePairs(forceUpdate bool) error { // Alphapoint exchange func (a *Alphapoint) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = a.GetName() + response.Exchange = a.Name account, err := a.GetAccountInformation() if err != nil { return response, err @@ -127,7 +127,7 @@ func (a *Alphapoint) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker tickerPrice.Volume = tick.Volume tickerPrice.Last = tick.Last - err = ticker.ProcessTicker(a.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(a.Name, &tickerPrice, assetType) if err != nil { return tickerPrice, err } @@ -137,7 +137,7 @@ func (a *Alphapoint) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker // FetchTicker returns the ticker for a currency pair func (a *Alphapoint) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tick, err := ticker.GetTicker(a.GetName(), p, assetType) + tick, err := ticker.GetTicker(a.Name, p, assetType) if err != nil { return a.UpdateTicker(p, assetType) } @@ -165,7 +165,7 @@ func (a *Alphapoint) UpdateOrderbook(p currency.Pair, assetType asset.Item) (ord } orderBook.Pair = p - orderBook.ExchangeName = a.GetName() + orderBook.ExchangeName = a.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -178,7 +178,7 @@ func (a *Alphapoint) UpdateOrderbook(p currency.Pair, assetType asset.Item) (ord // FetchOrderbook returns the orderbook for a currency pair func (a *Alphapoint) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(a.GetName(), p, assetType) + ob, err := orderbook.Get(a.Name, p, assetType) if err != nil { return a.UpdateOrderbook(p, assetType) } diff --git a/exchanges/anx/anx_live_test.go b/exchanges/anx/anx_live_test.go index f1d0d3cf..2618cad8 100644 --- a/exchanges/anx/anx_live_test.go +++ b/exchanges/anx/anx_live_test.go @@ -33,6 +33,6 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal("ANX setup error", err) } - log.Printf(sharedtestvalues.LiveTesting, a.GetName(), a.API.Endpoints.URL) + log.Printf(sharedtestvalues.LiveTesting, a.Name, a.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/anx/anx_mock_test.go b/exchanges/anx/anx_mock_test.go index f4b426b8..8871ced0 100644 --- a/exchanges/anx/anx_mock_test.go +++ b/exchanges/anx/anx_mock_test.go @@ -46,6 +46,6 @@ func TestMain(m *testing.M) { a.HTTPClient = newClient a.API.Endpoints.URL = serverDetails + "/" - log.Printf(sharedtestvalues.MockTesting, a.GetName(), a.API.Endpoints.URL) + log.Printf(sharedtestvalues.MockTesting, a.Name, a.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index d15c0dea..6b4b7e57 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -155,7 +155,7 @@ func (a *ANX) Run() { forceUpdate = true err := a.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", a.GetName()) + log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", a.Name) return } } @@ -166,7 +166,7 @@ func (a *ANX) Run() { err := a.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", a.GetName(), err) + log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", a.Name, err) } } @@ -221,7 +221,7 @@ func (a *ANX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, LastUpdated: time.Unix(0, tick.Data.UpdateTime), } - err = ticker.ProcessTicker(a.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(a.Name, &tickerPrice, assetType) if err != nil { return tickerPrice, err } @@ -231,7 +231,7 @@ func (a *ANX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, // FetchTicker returns the ticker for a currency pair func (a *ANX) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(a.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(a.Name, p, assetType) if err != nil { return a.UpdateTicker(p, assetType) } @@ -240,7 +240,7 @@ func (a *ANX) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, // FetchOrderbook returns the orderbook for a currency pair func (a *ANX) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(a.GetName(), p, assetType) + ob, err := orderbook.Get(a.Name, p, assetType) if err != nil { return a.UpdateOrderbook(p, assetType) } @@ -270,7 +270,7 @@ func (a *ANX) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook. } orderBook.Pair = p - orderBook.ExchangeName = a.GetName() + orderBook.ExchangeName = a.Name orderBook.AssetType = assetType err = orderBook.Process() if err != nil { @@ -299,7 +299,7 @@ func (a *ANX) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.Exchange = a.GetName() + info.Exchange = a.Name info.Accounts = append(info.Accounts, exchange.Account{ Currencies: balance, }) diff --git a/exchanges/binance/binance_live_test.go b/exchanges/binance/binance_live_test.go index f0dce783..d9745102 100644 --- a/exchanges/binance/binance_live_test.go +++ b/exchanges/binance/binance_live_test.go @@ -33,6 +33,6 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal("Binance setup error", err) } - log.Printf(sharedtestvalues.LiveTesting, b.GetName(), b.API.Endpoints.URL) + log.Printf(sharedtestvalues.LiveTesting, b.Name, b.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/binance/binance_mock_test.go b/exchanges/binance/binance_mock_test.go index dccf08fd..28160f03 100644 --- a/exchanges/binance/binance_mock_test.go +++ b/exchanges/binance/binance_mock_test.go @@ -46,6 +46,6 @@ func TestMain(m *testing.M) { b.HTTPClient = newClient b.API.Endpoints.URL = serverDetails - log.Printf(sharedtestvalues.MockTesting, b.GetName(), b.API.Endpoints.URL) + log.Printf(sharedtestvalues.MockTesting, b.Name, b.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index 9b2ab70b..f555d48d 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -133,7 +133,7 @@ func (b *Binance) WsHandleData() { Timestamp: time.Unix(0, trade.TimeStamp), Price: price, Amount: amount, - Exchange: b.GetName(), + Exchange: b.Name, AssetType: asset.Spot, Side: trade.EventType, } @@ -181,7 +181,7 @@ func (b *Binance) WsHandleData() { wsKline.Pair = currency.NewPairFromFormattedPairs(kline.Symbol, b.GetEnabledPairs(asset.Spot), b.GetPairFormat(asset.Spot, true)) wsKline.AssetType = asset.Spot - wsKline.Exchange = b.GetName() + wsKline.Exchange = b.Name wsKline.StartTime = time.Unix(0, kline.Kline.StartTime) wsKline.CloseTime = time.Unix(0, kline.Kline.CloseTime) wsKline.Interval = kline.Kline.Interval @@ -215,7 +215,7 @@ func (b *Binance) WsHandleData() { b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: currencyPair, Asset: asset.Spot, - Exchange: b.GetName(), + Exchange: b.Name, } continue } @@ -251,7 +251,7 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error { newOrderBook.LastUpdated = time.Unix(orderbookNew.LastUpdateID, 0) newOrderBook.Pair = p newOrderBook.AssetType = asset.Spot - newOrderBook.ExchangeName = b.GetName() + newOrderBook.ExchangeName = b.Name return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 65c1d515..829915fc 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -184,7 +184,7 @@ func (b *Binance) Run() { if b.Verbose { log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", - b.GetName(), + b.Name, common.IsEnabled(b.Websocket.IsEnabled()), b.Websocket.GetWebsocketURL()) b.PrintEnabledPairs() @@ -291,7 +291,7 @@ func (b *Binance) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr // FetchTicker returns the ticker for a currency pair func (b *Binance) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(b.Name, p, assetType) if err != nil { return b.UpdateTicker(p, assetType) } @@ -300,7 +300,7 @@ func (b *Binance) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pri // FetchOrderbook returns orderbook base on the currency pair func (b *Binance) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), p, assetType) + ob, err := orderbook.Get(b.Name, p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) } @@ -333,7 +333,7 @@ func (b *Binance) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderb } orderBook.Pair = p - orderBook.ExchangeName = b.GetName() + orderBook.ExchangeName = b.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -372,7 +372,7 @@ func (b *Binance) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.Exchange = b.GetName() + info.Exchange = b.Name info.Accounts = append(info.Accounts, exchange.Account{ Currencies: currencyBalance, }) diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 478ea6ad..3be37672 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -106,7 +106,7 @@ func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) { if b.Verbose { log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", - b.GetName(), + b.Name, channel, pair, chanID) @@ -151,7 +151,7 @@ func (b *Bitfinex) WsConnect() error { b.GenerateDefaultSubscriptions() if hs.Event == "info" { if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.GetName()) + log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name) } } @@ -445,7 +445,7 @@ func (b *Bitfinex) WsDataHandler() { Timestamp: time.Unix(trades[0].Timestamp, 0), Price: trades[0].Price, Amount: newAmount, - Exchange: b.GetName(), + Exchange: b.Name, AssetType: asset.Spot, Side: side, } @@ -479,7 +479,7 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books newOrderBook.AssetType = assetType newOrderBook.Bids = bid newOrderBook.Pair = p - newOrderBook.ExchangeName = b.GetName() + newOrderBook.ExchangeName = b.Name err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { @@ -487,7 +487,7 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books } b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p, Asset: assetType, - Exchange: b.GetName()} + Exchange: b.Name} return nil } @@ -529,7 +529,7 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p, Asset: assetType, - Exchange: b.GetName()} + Exchange: b.Name} return nil } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 5b247971..c5aa9cc2 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -191,7 +191,7 @@ func (b *Bitfinex) Run() { if b.Verbose { log.Debugf(log.ExchangeSys, "%s Websocket: %s.", - b.GetName(), + b.Name, common.IsEnabled(b.Websocket.IsEnabled())) b.PrintEnabledPairs() } @@ -260,7 +260,7 @@ func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.P // FetchTicker returns the ticker for a currency pair func (b *Bitfinex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { b.appendOptionalDelimiter(&p) - tick, err := ticker.GetTicker(b.GetName(), p, asset.Spot) + tick, err := ticker.GetTicker(b.Name, p, asset.Spot) if err != nil { return b.UpdateTicker(p, assetType) } @@ -270,7 +270,7 @@ func (b *Bitfinex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pr // FetchOrderbook returns the orderbook for a currency pair func (b *Bitfinex) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { b.appendOptionalDelimiter(&p) - ob, err := orderbook.Get(b.GetName(), p, assetType) + ob, err := orderbook.Get(b.Name, p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) } @@ -302,7 +302,7 @@ func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (order } orderBook.Pair = p - orderBook.ExchangeName = b.GetName() + orderBook.ExchangeName = b.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -317,7 +317,7 @@ func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (order // Bitfinex exchange func (b *Bitfinex) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = b.GetName() + response.Exchange = b.Name accountBalance, err := b.GetAccountBalance() if err != nil { return response, err diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index 992226dd..7bbce079 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -190,7 +190,7 @@ func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.P tickerPrice.Bid = tickerNew.BestBid tickerPrice.Last = tickerNew.Last tickerPrice.Volume = tickerNew.Volume - err = ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(b.Name, &tickerPrice, assetType) if err != nil { return tickerPrice, err } @@ -200,7 +200,7 @@ func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.P // FetchTicker returns the ticker for a currency pair func (b *Bitflyer) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tick, err := ticker.GetTicker(b.GetName(), p, assetType) + tick, err := ticker.GetTicker(b.Name, p, assetType) if err != nil { return b.UpdateTicker(p, assetType) } @@ -218,7 +218,7 @@ func (b *Bitflyer) CheckFXString(p currency.Pair) currency.Pair { // FetchOrderbook returns the orderbook for a currency pair func (b *Bitflyer) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), p, assetType) + ob, err := orderbook.Get(b.Name, p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) } @@ -245,7 +245,7 @@ func (b *Bitflyer) UpdateOrderbook(p currency.Pair, assetType asset.Item) (order } orderBook.Pair = p - orderBook.ExchangeName = b.GetName() + orderBook.ExchangeName = b.Name orderBook.AssetType = assetType err = orderBook.Process() diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index d8f3041d..2e9b12c1 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -202,7 +202,7 @@ func (b *Bithumb) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr // FetchTicker returns the ticker for a currency pair func (b *Bithumb) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(b.Name, p, assetType) if err != nil { return b.UpdateTicker(p, assetType) } @@ -211,7 +211,7 @@ func (b *Bithumb) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pri // FetchOrderbook returns orderbook base on the currency pair func (b *Bithumb) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), p, assetType) + ob, err := orderbook.Get(b.Name, p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) } @@ -245,7 +245,7 @@ func (b *Bithumb) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderb } orderBook.Pair = p - orderBook.ExchangeName = b.GetName() + orderBook.ExchangeName = b.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -284,7 +284,7 @@ func (b *Bithumb) GetAccountInfo() (exchange.AccountInfo, error) { Currencies: exchangeBalances, }) - info.Exchange = b.GetName() + info.Exchange = b.Name return info, nil } diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 11718f72..5b33a6e1 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -246,7 +246,7 @@ func (b *Bitmex) wsHandleIncomingData() { Price: trades.Data[i].Price, Amount: float64(trades.Data[i].Size), CurrencyPair: currency.NewPairFromString(trades.Data[i].Symbol), - Exchange: b.GetName(), + Exchange: b.Name, AssetType: "CONTRACT", Side: trades.Data[i].Side, } @@ -372,7 +372,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai newOrderBook.Bids = bids newOrderBook.AssetType = assetType newOrderBook.Pair = currencyPair - newOrderBook.ExchangeName = b.GetName() + newOrderBook.ExchangeName = b.Name err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { @@ -382,7 +382,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: currencyPair, Asset: assetType, - Exchange: b.GetName(), + Exchange: b.Name, } default: var asks, bids []orderbook.Item @@ -414,7 +414,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: currencyPair, Asset: assetType, - Exchange: b.GetName(), + Exchange: b.Name, } } return nil diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 3f44db1a..306a8a9a 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -208,7 +208,7 @@ func (b *Bitmex) Start(wg *sync.WaitGroup) { // Run implements the Bitmex wrapper func (b *Bitmex) Run() { if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.API.Endpoints.WebsocketURL) + log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", b.Name, common.IsEnabled(b.Websocket.IsEnabled()), b.API.Endpoints.WebsocketURL) b.PrintEnabledPairs() } @@ -318,7 +318,7 @@ func (b *Bitmex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri // FetchTicker returns the ticker for a currency pair func (b *Bitmex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(b.Name, p, assetType) if err != nil { return b.UpdateTicker(p, assetType) } @@ -327,7 +327,7 @@ func (b *Bitmex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pric // FetchOrderbook returns orderbook base on the currency pair func (b *Bitmex) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), p, assetType) + ob, err := orderbook.Get(b.Name, p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) } @@ -359,7 +359,7 @@ func (b *Bitmex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo } orderBook.Pair = p - orderBook.ExchangeName = b.GetName() + orderBook.ExchangeName = b.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -389,7 +389,7 @@ func (b *Bitmex) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.Exchange = b.GetName() + info.Exchange = b.Name info.Accounts = append(info.Accounts, exchange.Account{ Currencies: balances, }) diff --git a/exchanges/bitstamp/bitstamp_live_test.go b/exchanges/bitstamp/bitstamp_live_test.go index 7fe346d1..66d0ef87 100644 --- a/exchanges/bitstamp/bitstamp_live_test.go +++ b/exchanges/bitstamp/bitstamp_live_test.go @@ -34,6 +34,6 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal("Bitstamp setup error", err) } - log.Printf(sharedtestvalues.LiveTesting, b.GetName(), b.API.Endpoints.URL) + log.Printf(sharedtestvalues.LiveTesting, b.Name, b.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/bitstamp/bitstamp_mock_test.go b/exchanges/bitstamp/bitstamp_mock_test.go index d8b46e59..ffb3943f 100644 --- a/exchanges/bitstamp/bitstamp_mock_test.go +++ b/exchanges/bitstamp/bitstamp_mock_test.go @@ -47,6 +47,6 @@ func TestMain(m *testing.M) { b.HTTPClient = newClient b.API.Endpoints.URL = serverDetails + "/api" - log.Printf(sharedtestvalues.MockTesting, b.GetName(), b.API.Endpoints.URL) + log.Printf(sharedtestvalues.MockTesting, b.Name, b.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index b269da78..bd647384 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -176,7 +176,7 @@ func (b *Bitstamp) Run() { if b.Verbose { log.Debugf(log.ExchangeSys, "%s Websocket: %s.", - b.GetName(), + b.Name, common.IsEnabled(b.Websocket.IsEnabled())) b.PrintEnabledPairs() } @@ -245,7 +245,7 @@ func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.P LastUpdated: time.Unix(tick.Timestamp, 0), } - err = ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(b.Name, &tickerPrice, assetType) if err != nil { return tickerPrice, err } @@ -255,7 +255,7 @@ func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.P // FetchTicker returns the ticker for a currency pair func (b *Bitstamp) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tick, err := ticker.GetTicker(b.GetName(), p, assetType) + tick, err := ticker.GetTicker(b.Name, p, assetType) if err != nil { return b.UpdateTicker(p, assetType) } @@ -273,7 +273,7 @@ func (b *Bitstamp) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error // FetchOrderbook returns the orderbook for a currency pair func (b *Bitstamp) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), p, assetType) + ob, err := orderbook.Get(b.Name, p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) } @@ -299,7 +299,7 @@ func (b *Bitstamp) UpdateOrderbook(p currency.Pair, assetType asset.Item) (order } orderBook.Pair = p - orderBook.ExchangeName = b.GetName() + orderBook.ExchangeName = b.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -314,7 +314,7 @@ func (b *Bitstamp) UpdateOrderbook(p currency.Pair, assetType asset.Item) (order // Bitstamp exchange func (b *Bitstamp) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = b.GetName() + response.Exchange = b.Name accountBalance, err := b.GetBalance() if err != nil { return response, err diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 4f4179a1..32196f3f 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -21,7 +21,7 @@ var b Bittrex func TestSetDefaults(t *testing.T) { b.SetDefaults() - if b.GetName() != "Bittrex" { + if b.Name != "Bittrex" { t.Error("Bittrex - SetDefaults() error") } } diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index d1b1d19b..15cef5ec 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -192,7 +192,7 @@ func (b *Bittrex) UpdateTradablePairs(forceUpdate bool) error { // Bittrex exchange func (b *Bittrex) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = b.GetName() + response.Exchange = b.Name accountBalance, err := b.GetAccountBalances() if err != nil { return response, err @@ -240,7 +240,7 @@ func (b *Bittrex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr Pair: pairs[i], LastUpdated: tickerTime, } - err = ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(b.Name, &tickerPrice, assetType) if err != nil { log.Error(log.Ticker, err) } @@ -252,7 +252,7 @@ func (b *Bittrex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr // FetchTicker returns the ticker for a currency pair func (b *Bittrex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tick, err := ticker.GetTicker(b.GetName(), p, assetType) + tick, err := ticker.GetTicker(b.Name, p, assetType) if err != nil { return b.UpdateTicker(p, assetType) } @@ -261,7 +261,7 @@ func (b *Bittrex) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pri // FetchOrderbook returns the orderbook for a currency pair func (b *Bittrex) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), p, assetType) + ob, err := orderbook.Get(b.Name, p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) } @@ -295,7 +295,7 @@ func (b *Bittrex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderb } orderBook.Pair = p - orderBook.ExchangeName = b.GetName() + orderBook.ExchangeName = b.Name orderBook.AssetType = assetType err = orderBook.Process() diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index ff6ac360..88679ad6 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -138,7 +138,7 @@ func (b *BTCMarkets) NewOrder(instrument, currency string, price, amount float64 } if !resp.Success { - return 0, fmt.Errorf("%s Unable to place order. Error message: %s", b.GetName(), resp.ErrorMessage) + return 0, fmt.Errorf("%s Unable to place order. Error message: %s", b.Name, resp.ErrorMessage) } return int64(resp.ID), nil } @@ -159,7 +159,7 @@ func (b *BTCMarkets) CancelExistingOrder(orderID []int64) ([]ResponseDetails, er } if !resp.Success { - return resp.Responses, fmt.Errorf("%s Unable to cancel order. Error message: %s", b.GetName(), resp.ErrorMessage) + return resp.Responses, fmt.Errorf("%s Unable to cancel order. Error message: %s", b.Name, resp.ErrorMessage) } return resp.Responses, nil diff --git a/exchanges/btcmarkets/btcmarkets_websocket.go b/exchanges/btcmarkets/btcmarkets_websocket.go index 840a25c3..285eef43 100644 --- a/exchanges/btcmarkets/btcmarkets_websocket.go +++ b/exchanges/btcmarkets/btcmarkets_websocket.go @@ -30,7 +30,7 @@ func (b *BTCMarkets) WsConnect() error { return err } if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.GetName()) + log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name) } b.generateDefaultSubscriptions() @@ -66,7 +66,7 @@ func (b *BTCMarkets) WsHandleData() { switch wsResponse.MessageType { case "heartbeat": if b.Verbose { - log.Debugf(log.ExchangeSys, "%v - Websocket heartbeat received %s", b.GetName(), resp.Raw) + log.Debugf(log.ExchangeSys, "%v - Websocket heartbeat received %s", b.Name, resp.Raw) } case "orderbook": var ob WsOrderbook @@ -127,7 +127,7 @@ func (b *BTCMarkets) WsHandleData() { b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: p, Asset: asset.Spot, - Exchange: b.GetName(), + Exchange: b.Name, } case "trade": var trade WsTrade @@ -141,7 +141,7 @@ func (b *BTCMarkets) WsHandleData() { Timestamp: trade.Timestamp, CurrencyPair: p, AssetType: asset.Spot, - Exchange: b.GetName(), + Exchange: b.Name, Price: trade.Price, Amount: trade.Volume, } @@ -155,7 +155,7 @@ func (b *BTCMarkets) WsHandleData() { p := currency.NewPairFromString(tick.Currency) b.Websocket.DataHandler <- wshandler.TickerData{ - Exchange: b.GetName(), + Exchange: b.Name, Volume: tick.Volume, High: tick.High24, Low: tick.Low24h, diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 83bdc268..21025ae3 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -243,7 +243,7 @@ func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker LastUpdated: time.Unix(tick.Timestamp, 0), } - err = ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(b.Name, &tickerPrice, assetType) if err != nil { return tickerPrice, err } @@ -253,7 +253,7 @@ func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker // FetchTicker returns the ticker for a currency pair func (b *BTCMarkets) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(b.Name, p, assetType) if err != nil { return b.UpdateTicker(p, assetType) } @@ -262,7 +262,7 @@ func (b *BTCMarkets) FetchTicker(p currency.Pair, assetType asset.Item) (ticker. // FetchOrderbook returns orderbook base on the currency pair func (b *BTCMarkets) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), p, assetType) + ob, err := orderbook.Get(b.Name, p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) } @@ -289,7 +289,7 @@ func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType asset.Item) (ord } orderBook.Pair = p - orderBook.ExchangeName = b.GetName() + orderBook.ExchangeName = b.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -304,7 +304,7 @@ func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType asset.Item) (ord // BTCMarkets exchange func (b *BTCMarkets) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = b.GetName() + response.Exchange = b.Name accountBalance, err := b.GetAccountBalance() if err != nil { @@ -454,7 +454,7 @@ func (b *BTCMarkets) GetOrderInfo(orderID string) (order.Detail, error) { OrderDetail.Amount = orders[i].Volume OrderDetail.OrderDate = orderDate - OrderDetail.Exchange = b.GetName() + OrderDetail.Exchange = b.Name OrderDetail.ID = strconv.FormatInt(orders[i].ID, 10) OrderDetail.RemainingAmount = orders[i].OpenVolume OrderDetail.OrderSide = side diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 3753792e..d7e9fca3 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -244,7 +244,7 @@ func (b *BTSE) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price tickerPrice.High = s.High tickerPrice.LastUpdated = s.Time - err = ticker.ProcessTicker(b.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(b.Name, &tickerPrice, assetType) if err != nil { return tickerPrice, err } @@ -253,7 +253,7 @@ func (b *BTSE) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price // FetchTicker returns the ticker for a currency pair func (b *BTSE) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(b.Name, p, assetType) if err != nil { return b.UpdateTicker(p, assetType) } @@ -262,7 +262,7 @@ func (b *BTSE) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, // FetchOrderbook returns orderbook base on the currency pair func (b *BTSE) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(b.GetName(), p, assetType) + ob, err := orderbook.Get(b.Name, p, assetType) if err != nil { return b.UpdateOrderbook(p, assetType) } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index a94f9cda..7cd93295 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -216,7 +216,7 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro pair := currency.NewPairFromString(snapshot.ProductID) base.AssetType = asset.Spot base.Pair = pair - base.ExchangeName = c.GetName() + base.ExchangeName = c.Name err := c.Websocket.Orderbook.LoadSnapshot(&base) if err != nil { @@ -226,7 +226,7 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: pair, Asset: asset.Spot, - Exchange: c.GetName(), + Exchange: c.Name, } return nil @@ -270,7 +270,7 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: p, Asset: asset.Spot, - Exchange: c.GetName(), + Exchange: c.Name, } return nil diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index a039ccd7..dd687b04 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -189,7 +189,7 @@ func (c *CoinbasePro) Run() { if c.Verbose { log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", - c.GetName(), + c.Name, common.IsEnabled(c.Websocket.IsEnabled()), coinbaseproWebsocketURL) c.PrintEnabledPairs() @@ -255,7 +255,7 @@ func (c *CoinbasePro) UpdateTradablePairs(forceUpdate bool) error { // coinbasepro exchange func (c *CoinbasePro) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = c.GetName() + response.Exchange = c.Name accountBalance, err := c.GetAccounts() if err != nil { return response, err @@ -302,7 +302,7 @@ func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType asset.Item) (ticke LastUpdated: tick.Time, } - err = ticker.ProcessTicker(c.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(c.Name, &tickerPrice, assetType) if err != nil { return tickerPrice, err } @@ -312,7 +312,7 @@ func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType asset.Item) (ticke // FetchTicker returns the ticker for a currency pair func (c *CoinbasePro) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(c.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(c.Name, p, assetType) if err != nil { return c.UpdateTicker(p, assetType) } @@ -321,7 +321,7 @@ func (c *CoinbasePro) FetchTicker(p currency.Pair, assetType asset.Item) (ticker // FetchOrderbook returns orderbook base on the currency pair func (c *CoinbasePro) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(c.GetName(), p, assetType) + ob, err := orderbook.Get(c.Name, p, assetType) if err != nil { return c.UpdateOrderbook(p, assetType) } @@ -348,7 +348,7 @@ func (c *CoinbasePro) UpdateOrderbook(p currency.Pair, assetType asset.Item) (or } orderBook.Pair = p - orderBook.ExchangeName = c.GetName() + orderBook.ExchangeName = c.Name orderBook.AssetType = assetType err = orderBook.Process() diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index 44b170d5..9b0935dc 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -166,7 +166,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { } currencyPair := wsInstrumentMap.LookupInstrument(orderbooksnapshot.InstID) c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: c.GetName(), + Exchange: c.Name, Asset: asset.Spot, Pair: currency.NewPairFromFormattedPairs(currencyPair, c.GetEnabledPairs(asset.Spot), @@ -186,7 +186,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { } currencyPair := wsInstrumentMap.LookupInstrument(orderbookUpdate.InstID) c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: c.GetName(), + Exchange: c.Name, Asset: asset.Spot, Pair: currency.NewPairFromFormattedPairs(currencyPair, c.GetEnabledPairs(asset.Spot), @@ -214,7 +214,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.GetEnabledPairs(asset.Spot), c.GetPairFormat(asset.Spot, true)), AssetType: asset.Spot, - Exchange: c.GetName(), + Exchange: c.Name, Price: tradeUpdate.Price, Side: tradeUpdate.Side, } @@ -290,7 +290,7 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { c.GetPairFormat(asset.Spot, true), ) newOrderBook.AssetType = asset.Spot - newOrderBook.ExchangeName = c.GetName() + newOrderBook.ExchangeName = c.Name return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index c20b5a65..75779a48 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -182,7 +182,7 @@ func (c *COINUT) Start(wg *sync.WaitGroup) { // Run implements the COINUT wrapper func (c *COINUT) Run() { if c.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket.IsEnabled()), coinutWebsocketURL) + log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", c.Name, common.IsEnabled(c.Websocket.IsEnabled()), coinutWebsocketURL) c.PrintEnabledPairs() } @@ -311,7 +311,7 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { TotalValue: bal.ZEC, }, } - info.Exchange = c.GetName() + info.Exchange = c.Name info.Accounts = append(info.Accounts, exchange.Account{ Currencies: balances, }) @@ -350,7 +350,7 @@ func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri Pair: p, LastUpdated: time.Unix(0, tick.Timestamp), } - err = ticker.ProcessTicker(c.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(c.Name, &tickerPrice, assetType) if err != nil { return tickerPrice, err } @@ -360,7 +360,7 @@ func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri // FetchTicker returns the ticker for a currency pair func (c *COINUT) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(c.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(c.Name, p, assetType) if err != nil { return c.UpdateTicker(p, assetType) } @@ -369,7 +369,7 @@ func (c *COINUT) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pric // FetchOrderbook returns orderbook base on the currency pair func (c *COINUT) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(c.GetName(), p, assetType) + ob, err := orderbook.Get(c.Name, p, assetType) if err != nil { return c.UpdateOrderbook(p, assetType) } @@ -407,7 +407,7 @@ func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo } orderBook.Pair = p - orderBook.ExchangeName = c.GetName() + orderBook.ExchangeName = c.Name orderBook.AssetType = assetType err = orderBook.Process() diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index 2486a3c7..c2b79d2b 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -209,7 +209,7 @@ func (e *EXMO) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price // FetchTicker returns the ticker for a currency pair func (e *EXMO) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tick, err := ticker.GetTicker(e.GetName(), p, assetType) + tick, err := ticker.GetTicker(e.Name, p, assetType) if err != nil { return e.UpdateTicker(p, assetType) } @@ -218,7 +218,7 @@ func (e *EXMO) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, // FetchOrderbook returns the orderbook for a currency pair func (e *EXMO) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(e.GetName(), p, assetType) + ob, err := orderbook.Get(e.Name, p, assetType) if err != nil { return e.UpdateOrderbook(p, assetType) } @@ -267,7 +267,7 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook orderBook.Bids = obItems orderBook.Pair = x - orderBook.ExchangeName = e.GetName() + orderBook.ExchangeName = e.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -282,7 +282,7 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook // Exmo exchange func (e *EXMO) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = e.GetName() + response.Exchange = e.Name result, err := e.GetUserInfo() if err != nil { return response, err diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 49d4221e..75347608 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -168,7 +168,7 @@ func (g *Gateio) WsHandleData() { Timestamp: time.Now(), CurrencyPair: currency.NewPairFromString(c), AssetType: asset.Spot, - Exchange: g.GetName(), + Exchange: g.Name, Price: trades[i].Price, Amount: trades[i].Amount, Side: trades[i].Type, @@ -237,7 +237,7 @@ func (g *Gateio) WsHandleData() { newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = currency.NewPairFromString(c) - newOrderBook.ExchangeName = g.GetName() + newOrderBook.ExchangeName = g.Name err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { @@ -260,7 +260,7 @@ func (g *Gateio) WsHandleData() { g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: currency.NewPairFromString(c), Asset: asset.Spot, - Exchange: g.GetName(), + Exchange: g.Name, } case strings.Contains(result.Method, "kline"): @@ -281,7 +281,7 @@ func (g *Gateio) WsHandleData() { Timestamp: time.Now(), Pair: currency.NewPairFromString(data[7].(string)), AssetType: asset.Spot, - Exchange: g.GetName(), + Exchange: g.Name, OpenPrice: open, ClosePrice: closePrice, HighPrice: high, diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index e8308b97..f3d894fd 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -250,7 +250,7 @@ func (g *Gateio) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri // FetchTicker returns the ticker for a currency pair func (g *Gateio) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(g.Name, p, assetType) if err != nil { return g.UpdateTicker(p, assetType) } @@ -259,7 +259,7 @@ func (g *Gateio) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pric // FetchOrderbook returns orderbook base on the currency pair func (g *Gateio) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(g.GetName(), p, assetType) + ob, err := orderbook.Get(g.Name, p, assetType) if err != nil { return g.UpdateOrderbook(p, assetType) } @@ -287,7 +287,7 @@ func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo } orderBook.Pair = p - orderBook.ExchangeName = g.GetName() + orderBook.ExchangeName = g.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -358,7 +358,7 @@ func (g *Gateio) GetAccountInfo() (exchange.AccountInfo, error) { Currencies: balances, }) - info.Exchange = g.GetName() + info.Exchange = g.Name return info, nil } @@ -463,7 +463,7 @@ func (g *Gateio) GetOrderInfo(orderID string) (order.Detail, error) { if orders.Orders[x].OrderNumber != orderID { continue } - orderDetail.Exchange = g.GetName() + orderDetail.Exchange = g.Name orderDetail.ID = orders.Orders[x].OrderNumber orderDetail.RemainingAmount = orders.Orders[x].InitialAmount - orders.Orders[x].FilledAmount orderDetail.ExecutedAmount = orders.Orders[x].FilledAmount diff --git a/exchanges/gemini/gemini_live_test.go b/exchanges/gemini/gemini_live_test.go index cf5daf3e..522fda2e 100644 --- a/exchanges/gemini/gemini_live_test.go +++ b/exchanges/gemini/gemini_live_test.go @@ -34,6 +34,6 @@ func TestMain(m *testing.M) { log.Fatal("Gemini setup error", err) } g.API.Endpoints.URL = geminiSandboxAPIURL - log.Printf(sharedtestvalues.LiveTesting, g.GetName(), g.API.Endpoints.URL) + log.Printf(sharedtestvalues.LiveTesting, g.Name, g.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/gemini/gemini_mock_test.go b/exchanges/gemini/gemini_mock_test.go index 6b56984a..c7982e5c 100644 --- a/exchanges/gemini/gemini_mock_test.go +++ b/exchanges/gemini/gemini_mock_test.go @@ -46,6 +46,6 @@ func TestMain(m *testing.M) { g.HTTPClient = newClient g.API.Endpoints.URL = serverDetails - log.Printf(sharedtestvalues.MockTesting, g.GetName(), g.API.Endpoints.URL) + log.Printf(sharedtestvalues.MockTesting, g.Name, g.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 681d839c..c4926252 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -283,7 +283,7 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = pair - newOrderBook.ExchangeName = g.GetName() + newOrderBook.ExchangeName = g.Name err := g.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { g.Websocket.DataHandler <- err @@ -291,7 +291,7 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa } g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair, Asset: asset.Spot, - Exchange: g.GetName()} + Exchange: g.Name} } else { var asks, bids []orderbook.Item for i := 0; i < len(result.Events); i++ { @@ -330,6 +330,6 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa } g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair, Asset: asset.Spot, - Exchange: g.GetName()} + Exchange: g.Name} } } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index f0763251..1596393d 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -212,7 +212,7 @@ func (g *Gemini) UpdateTradablePairs(forceUpdate bool) error { // Gemini exchange func (g *Gemini) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = g.GetName() + response.Exchange = g.Name accountBalance, err := g.GetBalances() if err != nil { return response, err @@ -250,7 +250,7 @@ func (g *Gemini) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri Close: tick.Close, Pair: p, } - err = ticker.ProcessTicker(g.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(g.Name, &tickerPrice, assetType) if err != nil { return tickerPrice, err } @@ -260,7 +260,7 @@ func (g *Gemini) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri // FetchTicker returns the ticker for a currency pair func (g *Gemini) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(g.Name, p, assetType) if err != nil { return g.UpdateTicker(p, assetType) } @@ -269,7 +269,7 @@ func (g *Gemini) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pric // FetchOrderbook returns orderbook base on the currency pair func (g *Gemini) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(g.GetName(), p, assetType) + ob, err := orderbook.Get(g.Name, p, assetType) if err != nil { return g.UpdateOrderbook(p, assetType) } @@ -293,7 +293,7 @@ func (g *Gemini) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo } orderBook.Pair = p - orderBook.ExchangeName = g.GetName() + orderBook.ExchangeName = g.Name orderBook.AssetType = assetType err = orderBook.Process() diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index 5726ca8a..99d7c296 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -250,7 +250,7 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = p - newOrderBook.ExchangeName = h.GetName() + newOrderBook.ExchangeName = h.Name err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { @@ -258,7 +258,7 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { } h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: h.GetName(), + Exchange: h.Name, Asset: asset.Spot, Pair: p, } @@ -295,7 +295,7 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error { } h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: h.GetName(), + Exchange: h.Name, Asset: asset.Spot, Pair: p, } diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index b4e829ac..98fe7b7a 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -186,7 +186,7 @@ func (h *HitBTC) Start(wg *sync.WaitGroup) { // Run implements the HitBTC wrapper func (h *HitBTC) Run() { if h.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket.IsEnabled()), hitbtcWebsocketAddress) + log.Debugf(log.ExchangeSys, "%s Websocket: %s (url: %s).\n", h.Name, common.IsEnabled(h.Websocket.IsEnabled()), hitbtcWebsocketAddress) h.PrintEnabledPairs() } @@ -199,7 +199,7 @@ func (h *HitBTC) Run() { err := h.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), asset.Spot, true, true) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update enabled currencies.\n", h.GetName()) + log.Errorf(log.ExchangeSys, "%s failed to update enabled currencies.\n", h.Name) } } @@ -273,7 +273,7 @@ func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType asset.Item) Pair: pairs[i], LastUpdated: tick[j].Timestamp, } - err = ticker.ProcessTicker(h.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(h.Name, &tickerPrice, assetType) if err != nil { log.Error(log.Ticker, err) } @@ -284,7 +284,7 @@ func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType asset.Item) // FetchTicker returns the ticker for a currency pair func (h *HitBTC) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(h.Name, p, assetType) if err != nil { return h.UpdateTicker(p, assetType) } @@ -293,7 +293,7 @@ func (h *HitBTC) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pric // FetchOrderbook returns orderbook base on the currency pair func (h *HitBTC) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(h.GetName(), p, assetType) + ob, err := orderbook.Get(h.Name, p, assetType) if err != nil { return h.UpdateOrderbook(p, assetType) } @@ -319,7 +319,7 @@ func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType asset.Ite } orderBook.Pair = currencyPair - orderBook.ExchangeName = h.GetName() + orderBook.ExchangeName = h.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -334,7 +334,7 @@ func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType asset.Ite // HitBTC exchange func (h *HitBTC) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = h.GetName() + response.Exchange = h.Name accountBalance, err := h.GetBalances() if err != nil { return response, err diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index e6675735..0b1d8cec 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -251,7 +251,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { data := strings.Split(kline.Channel, ".") h.Websocket.DataHandler <- wshandler.KlineData{ Timestamp: time.Unix(0, kline.Timestamp), - Exchange: h.GetName(), + Exchange: h.Name, AssetType: asset.Spot, Pair: currency.NewPairFromFormattedPairs(data[1], h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), @@ -270,7 +270,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { } data := strings.Split(trade.Channel, ".") h.Websocket.DataHandler <- wshandler.TradeData{ - Exchange: h.GetName(), + Exchange: h.Name, AssetType: asset.Spot, CurrencyPair: currency.NewPairFromFormattedPairs(data[1], h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), @@ -335,7 +335,7 @@ func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: p, - Exchange: h.GetName(), + Exchange: h.Name, Asset: asset.Spot, } return nil diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 8c68e079..bc918852 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -196,7 +196,7 @@ func (h *HUOBI) Run() { if h.Verbose { log.Debugf(log.ExchangeSys, "%s Websocket: %s (url: %s).\n", - h.GetName(), + h.Name, common.IsEnabled(h.Websocket.IsEnabled()), wsMarketURL) h.PrintEnabledPairs() @@ -236,7 +236,7 @@ func (h *HUOBI) Run() { if err != nil { log.Errorf(log.ExchangeSys, "%s Failed to update enabled currencies.\n", - h.GetName()) + h.Name) } } @@ -310,7 +310,7 @@ func (h *HUOBI) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric Close: tickers.Data[j].Close, Pair: pairs[i], } - err = ticker.ProcessTicker(h.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(h.Name, &tickerPrice, assetType) if err != nil { log.Error(log.Ticker, err) } @@ -322,7 +322,7 @@ func (h *HUOBI) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric // FetchTicker returns the ticker for a currency pair func (h *HUOBI) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(h.Name, p, assetType) if err != nil { return h.UpdateTicker(p, assetType) } @@ -331,7 +331,7 @@ func (h *HUOBI) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price // FetchOrderbook returns orderbook base on the currency pair func (h *HUOBI) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(h.GetName(), p, assetType) + ob, err := orderbook.Get(h.Name, p, assetType) if err != nil { return h.UpdateOrderbook(p, assetType) } @@ -360,7 +360,7 @@ func (h *HUOBI) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo } orderBook.Pair = p - orderBook.ExchangeName = h.GetName() + orderBook.ExchangeName = h.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -389,7 +389,7 @@ func (h *HUOBI) GetAccountID() ([]Account, error) { // HUOBI exchange - to-do func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - info.Exchange = h.GetName() + info.Exchange = h.Name accounts, err := h.GetAccountID() if err != nil { diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 72c5f2be..a10a6a09 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -158,7 +158,7 @@ func (i *ItBit) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric Pair: p, LastUpdated: tick.ServertimeUTC, } - err = ticker.ProcessTicker(i.GetName(), &tickerPrice, assetType) + err = ticker.ProcessTicker(i.Name, &tickerPrice, assetType) if err != nil { return tickerPrice, err } @@ -168,7 +168,7 @@ func (i *ItBit) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric // FetchTicker returns the ticker for a currency pair func (i *ItBit) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(i.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(i.Name, p, assetType) if err != nil { return i.UpdateTicker(p, assetType) } @@ -177,7 +177,7 @@ func (i *ItBit) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price // FetchOrderbook returns orderbook base on the currency pair func (i *ItBit) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(i.GetName(), p, assetType) + ob, err := orderbook.Get(i.Name, p, assetType) if err != nil { return i.UpdateOrderbook(p, assetType) } @@ -229,7 +229,7 @@ func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo } orderBook.Pair = p - orderBook.ExchangeName = i.GetName() + orderBook.ExchangeName = i.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -243,7 +243,7 @@ func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo // GetAccountInfo retrieves balances for all enabled currencies func (i *ItBit) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - info.Exchange = i.GetName() + info.Exchange = i.Name wallets, err := i.GetWallets(url.Values{}) if err != nil { diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 22f6cf75..b4282ea1 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -307,12 +307,12 @@ func (k *Kraken) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri } } } - return ticker.GetTicker(k.GetName(), p, assetType) + return ticker.GetTicker(k.Name, p, assetType) } // FetchTicker returns the ticker for a currency pair func (k *Kraken) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(k.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(k.Name, p, assetType) if err != nil { return k.UpdateTicker(p, assetType) } @@ -321,7 +321,7 @@ func (k *Kraken) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pric // FetchOrderbook returns orderbook base on the currency pair func (k *Kraken) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(k.GetName(), p, assetType) + ob, err := orderbook.Get(k.Name, p, assetType) if err != nil { return k.UpdateOrderbook(p, assetType) } @@ -346,7 +346,7 @@ func (k *Kraken) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo } orderBook.Pair = p - orderBook.ExchangeName = k.GetName() + orderBook.ExchangeName = k.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -361,7 +361,7 @@ func (k *Kraken) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo // Kraken exchange - to-do func (k *Kraken) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - info.Exchange = k.GetName() + info.Exchange = k.Name bal, err := k.GetBalance() if err != nil { diff --git a/exchanges/lakebtc/lakebtc_websocket.go b/exchanges/lakebtc/lakebtc_websocket.go index 1e3003ba..af662d9c 100644 --- a/exchanges/lakebtc/lakebtc_websocket.go +++ b/exchanges/lakebtc/lakebtc_websocket.go @@ -57,15 +57,15 @@ func (l *LakeBTC) listenToEndpoints() error { var err error l.WebsocketConn.Ticker, err = l.WebsocketConn.Client.Bind("tickers") if err != nil { - return fmt.Errorf("%s Websocket Bind error: %s", l.GetName(), err) + return fmt.Errorf("%s Websocket Bind error: %s", l.Name, err) } l.WebsocketConn.Orderbook, err = l.WebsocketConn.Client.Bind("update") if err != nil { - return fmt.Errorf("%s Websocket Bind error: %s", l.GetName(), err) + return fmt.Errorf("%s Websocket Bind error: %s", l.Name, err) } l.WebsocketConn.Trade, err = l.WebsocketConn.Client.Bind("trades") if err != nil { - return fmt.Errorf("%s Websocket Bind error: %s", l.GetName(), err) + return fmt.Errorf("%s Websocket Bind error: %s", l.Name, err) } return nil } @@ -152,7 +152,7 @@ func (l *LakeBTC) processTrades(data, channel string) error { Timestamp: time.Unix(tradeData.Trades[i].Date, 0), CurrencyPair: curr, AssetType: asset.Spot, - Exchange: l.GetName(), + Exchange: l.Name, EventType: asset.Spot.String(), EventTime: tradeData.Trades[i].Date, Price: tradeData.Trades[i].Price, diff --git a/exchanges/localbitcoins/localbitcoins_live_test.go b/exchanges/localbitcoins/localbitcoins_live_test.go index 332da00d..be9fe318 100644 --- a/exchanges/localbitcoins/localbitcoins_live_test.go +++ b/exchanges/localbitcoins/localbitcoins_live_test.go @@ -33,6 +33,6 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal("Localbitcoins setup error", err) } - log.Printf(sharedtestvalues.LiveTesting, l.GetName(), l.API.Endpoints.URL) + log.Printf(sharedtestvalues.LiveTesting, l.Name, l.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/localbitcoins/localbitcoins_mock_test.go b/exchanges/localbitcoins/localbitcoins_mock_test.go index 40992afe..83c0b23d 100644 --- a/exchanges/localbitcoins/localbitcoins_mock_test.go +++ b/exchanges/localbitcoins/localbitcoins_mock_test.go @@ -46,6 +46,6 @@ func TestMain(m *testing.M) { l.HTTPClient = newClient l.API.Endpoints.URL = serverDetails - log.Printf(sharedtestvalues.MockTesting, l.GetName(), l.API.Endpoints.URL) + log.Printf(sharedtestvalues.MockTesting, l.Name, l.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 36ff2ed1..e03ac508 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -182,18 +182,18 @@ func (l *LocalBitcoins) UpdateTicker(p currency.Pair, assetType asset.Item) (tic tp.Last = tick[curr].Avg24h tp.Volume = tick[curr].VolumeBTC - err = ticker.ProcessTicker(l.GetName(), &tp, assetType) + err = ticker.ProcessTicker(l.Name, &tp, assetType) if err != nil { log.Error(log.Ticker, err) } } - return ticker.GetTicker(l.GetName(), p, assetType) + return ticker.GetTicker(l.Name, p, assetType) } // FetchTicker returns the ticker for a currency pair func (l *LocalBitcoins) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(l.Name, p, assetType) if err != nil { return l.UpdateTicker(p, assetType) } @@ -202,7 +202,7 @@ func (l *LocalBitcoins) FetchTicker(p currency.Pair, assetType asset.Item) (tick // FetchOrderbook returns orderbook base on the currency pair func (l *LocalBitcoins) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(l.GetName(), p, assetType) + ob, err := orderbook.Get(l.Name, p, assetType) if err != nil { return l.UpdateOrderbook(p, assetType) } @@ -228,7 +228,7 @@ func (l *LocalBitcoins) UpdateOrderbook(p currency.Pair, assetType asset.Item) ( } orderBook.Pair = p - orderBook.ExchangeName = l.GetName() + orderBook.ExchangeName = l.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -243,7 +243,7 @@ func (l *LocalBitcoins) UpdateOrderbook(p currency.Pair, assetType asset.Item) ( // LocalBitcoins exchange func (l *LocalBitcoins) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = l.GetName() + response.Exchange = l.Name accountBalance, err := l.GetWalletBalance() if err != nil { return response, err diff --git a/exchanges/mock/README.md b/exchanges/mock/README.md index d8eea5c3..9f9e7422 100644 --- a/exchanges/mock/README.md +++ b/exchanges/mock/README.md @@ -63,7 +63,7 @@ func TestMain(m *testing.M) { your_current_exchange_nameConfig.APISecret = apiSecret l.SetDefaults() l.Setup(&your_current_exchange_nameConfig) - log.Printf(sharedtestvalues.LiveTesting, l.GetName(), l.APIUrl) + log.Printf(sharedtestvalues.LiveTesting, l.Name, l.APIUrl) os.Exit(m.Run()) } ``` @@ -112,7 +112,7 @@ func TestMain(m *testing.M) { g.HTTPClient = newClient g.APIUrl = serverDetails - log.Printf(sharedtestvalues.MockTesting, l.GetName(), l.APIUrl) + log.Printf(sharedtestvalues.MockTesting, l.Name, l.APIUrl) os.Exit(m.Run()) } diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 79659e55..19849b0b 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -38,7 +38,7 @@ func TestSetDefaults(t *testing.T) { if o.Name != OKGroupExchange { o.SetDefaults() } - if o.GetName() != OKGroupExchange { + if o.Name != OKGroupExchange { t.Errorf("%v - SetDefaults() error", OKGroupExchange) } TestSetup(t) diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index a4f75553..8ae4cb93 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -39,7 +39,7 @@ func TestSetDefaults(t *testing.T) { if o.Name != OKGroupExchange { o.SetDefaults() } - if o.GetName() != OKGroupExchange { + if o.Name != OKGroupExchange { t.Errorf("%v - SetDefaults() error", OKGroupExchange) } TestSetup(t) diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index b1c794ba..8aa944ad 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -445,7 +445,7 @@ func (o *OKGroup) wsProcessTrades(response *WebsocketDataResponse) { AssetType: o.GetAssetTypeFromTableName(response.Table), CurrencyPair: c, EventTime: time.Now().Unix(), - Exchange: o.GetName(), + Exchange: o.Name, Price: response.Data[i].WebsocketTradeResponse.Price, Side: response.Data[i].Side, Timestamp: response.Data[i].Timestamp, @@ -486,7 +486,7 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) { klineData := wshandler.KlineData{ AssetType: o.GetAssetTypeFromTableName(response.Table), Pair: c, - Exchange: o.GetName(), + Exchange: o.Name, Timestamp: timeData, Interval: candleInterval, } @@ -602,7 +602,7 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i AssetType: a, LastUpdated: wsEventData.Timestamp, Pair: instrument, - ExchangeName: o.GetName(), + ExchangeName: o.Name, } err = o.Websocket.Orderbook.LoadSnapshot(&newOrderBook) @@ -611,7 +611,7 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i } o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: o.GetName(), + Exchange: o.Name, Asset: a, Pair: instrument, } @@ -654,7 +654,7 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, in } o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: o.GetName(), + Exchange: o.Name, Asset: a, Pair: instrument, } diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index 5f7a85f8..d9f1a032 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -72,7 +72,7 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) error { // FetchOrderbook returns orderbook base on the currency pair func (o *OKGroup) FetchOrderbook(p currency.Pair, assetType asset.Item) (resp orderbook.Base, err error) { - ob, err := orderbook.Get(o.GetName(), p, assetType) + ob, err := orderbook.Get(o.Name, p, assetType) if err != nil { return o.UpdateOrderbook(p, assetType) } diff --git a/exchanges/poloniex/poloniex_live_test.go b/exchanges/poloniex/poloniex_live_test.go index 26d83788..f2ad34f2 100644 --- a/exchanges/poloniex/poloniex_live_test.go +++ b/exchanges/poloniex/poloniex_live_test.go @@ -33,6 +33,6 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal("Poloniex setup error", err) } - log.Printf(sharedtestvalues.LiveTesting, p.GetName(), p.API.Endpoints.URL) + log.Printf(sharedtestvalues.LiveTesting, p.Name, p.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/poloniex/poloniex_mock_test.go b/exchanges/poloniex/poloniex_mock_test.go index 2e7205dc..546ee7d3 100644 --- a/exchanges/poloniex/poloniex_mock_test.go +++ b/exchanges/poloniex/poloniex_mock_test.go @@ -46,6 +46,6 @@ func TestMain(m *testing.M) { p.HTTPClient = newClient p.API.Endpoints.URL = serverDetails - log.Printf(sharedtestvalues.MockTesting, p.GetName(), p.API.Endpoints.URL) + log.Printf(sharedtestvalues.MockTesting, p.Name, p.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index 4a81a363..62e2186c 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -161,7 +161,7 @@ func (p *Poloniex) WsHandleData() { } p.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: p.GetName(), + Exchange: p.Name, Asset: asset.Spot, Pair: currency.NewPairFromString(currencyPair), } @@ -176,7 +176,7 @@ func (p *Poloniex) WsHandleData() { } p.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: p.GetName(), + Exchange: p.Name, Asset: asset.Spot, Pair: currency.NewPairFromString(currencyPair), } @@ -435,7 +435,7 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = currency.NewPairFromString(symbol) - newOrderBook.ExchangeName = p.GetName() + newOrderBook.ExchangeName = p.Name return p.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index f5844854..9bccee98 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -183,7 +183,7 @@ func (p *Poloniex) Start(wg *sync.WaitGroup) { // Run implements the Poloniex wrapper func (p *Poloniex) Run() { if p.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s (url: %s).\n", p.GetName(), common.IsEnabled(p.Websocket.IsEnabled()), poloniexWebsocketAddress) + log.Debugf(log.ExchangeSys, "%s Websocket: %s (url: %s).\n", p.Name, common.IsEnabled(p.Websocket.IsEnabled()), poloniexWebsocketAddress) p.PrintEnabledPairs() } @@ -253,7 +253,7 @@ func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType asset.Item tp.Volume = tick[curr].BaseVolume tp.QuoteVolume = tick[curr].QuoteVolume - err = ticker.ProcessTicker(p.GetName(), &tp, assetType) + err = ticker.ProcessTicker(p.Name, &tp, assetType) if err != nil { log.Error(log.Ticker, err) } @@ -263,7 +263,7 @@ func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType asset.Item // FetchTicker returns the ticker for a currency pair func (p *Poloniex) FetchTicker(currencyPair currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair, assetType) + tickerNew, err := ticker.GetTicker(p.Name, currencyPair, assetType) if err != nil { return p.UpdateTicker(currencyPair, assetType) } @@ -272,7 +272,7 @@ func (p *Poloniex) FetchTicker(currencyPair currency.Pair, assetType asset.Item) // FetchOrderbook returns orderbook base on the currency pair func (p *Poloniex) FetchOrderbook(currencyPair currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(p.GetName(), currencyPair, assetType) + ob, err := orderbook.Get(p.Name, currencyPair, assetType) if err != nil { return p.UpdateOrderbook(currencyPair, assetType) } @@ -311,7 +311,7 @@ func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.I orderBook.Pair = x orderBook.Asks = obItems - orderBook.ExchangeName = p.GetName() + orderBook.ExchangeName = p.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -326,7 +326,7 @@ func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.I // Poloniex exchange func (p *Poloniex) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = p.GetName() + response.Exchange = p.Name accountBalance, err := p.GetBalances() if err != nil { return response, err diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index b2d36ae0..bd7ed666 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -212,7 +212,7 @@ func (y *Yobit) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric // FetchTicker returns the ticker for a currency pair func (y *Yobit) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tick, err := ticker.GetTicker(y.GetName(), p, assetType) + tick, err := ticker.GetTicker(y.Name, p, assetType) if err != nil { return y.UpdateTicker(p, assetType) } @@ -221,7 +221,7 @@ func (y *Yobit) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price // FetchOrderbook returns the orderbook for a currency pair func (y *Yobit) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(y.GetName(), p, assetType) + ob, err := orderbook.Get(y.Name, p, assetType) if err != nil { return y.UpdateOrderbook(p, assetType) } @@ -253,7 +253,7 @@ func (y *Yobit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo } orderBook.Pair = p - orderBook.ExchangeName = y.GetName() + orderBook.ExchangeName = y.Name orderBook.AssetType = assetType err = orderBook.Process() @@ -268,7 +268,7 @@ func (y *Yobit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo // Yobit exchange func (y *Yobit) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.Exchange = y.GetName() + response.Exchange = y.Name accountBalance, err := y.GetAccountInformation() if err != nil { return response, err diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index f85cfd9c..0ac7d39d 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -140,7 +140,7 @@ func (z *ZB) WsHandleData() { newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = cPair - newOrderBook.ExchangeName = z.GetName() + newOrderBook.ExchangeName = z.Name err = z.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { @@ -151,7 +151,7 @@ func (z *ZB) WsHandleData() { z.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Pair: cPair, Asset: asset.Spot, - Exchange: z.GetName(), + Exchange: z.Name, } case strings.Contains(result.Channel, "trades"): @@ -173,7 +173,7 @@ func (z *ZB) WsHandleData() { Timestamp: time.Unix(0, t.Date*int64(time.Millisecond)), CurrencyPair: cPair, AssetType: asset.Spot, - Exchange: z.GetName(), + Exchange: z.Name, EventTime: t.Date, Price: t.Price, Amount: t.Amount, diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 4ca4efd7..60108f15 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -249,7 +249,7 @@ func (z *ZB) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, // FetchTicker returns the ticker for a currency pair func (z *ZB) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - tickerNew, err := ticker.GetTicker(z.GetName(), p, assetType) + tickerNew, err := ticker.GetTicker(z.Name, p, assetType) if err != nil { return z.UpdateTicker(p, assetType) } @@ -258,7 +258,7 @@ func (z *ZB) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, e // FetchOrderbook returns orderbook base on the currency pair func (z *ZB) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { - ob, err := orderbook.Get(z.GetName(), p, assetType) + ob, err := orderbook.Get(z.Name, p, assetType) if err != nil { return z.UpdateOrderbook(p, assetType) } @@ -289,7 +289,7 @@ func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.B orderBook.Pair = p orderBook.AssetType = assetType - orderBook.ExchangeName = z.GetName() + orderBook.ExchangeName = z.Name err = orderBook.Process() if err != nil { @@ -327,7 +327,7 @@ func (z *ZB) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.Exchange = z.GetName() + info.Exchange = z.Name info.Accounts = append(info.Accounts, exchange.Account{ Currencies: balances, }) From 63191ce3ecdf8853ba8d6c976abd2648e6b59596 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 22 Nov 2019 16:07:30 +1100 Subject: [PATCH 61/71] Engine QA (#381) * 1) Update Dockerfile/docker-compose.yml 2) Remove inline strings for buy/sell/test pairs 3) Remove dangerous order submission values 4) Fix consistency with audit_events (all other spec files use CamelCase) 5) Update web websocket endpoint 6) Fix main param set (and induce dryrun mode on specific command line params) * Engine QA Link up exchange syncer to cmd params, disarm market selling bombs and fix OKEX endpoints * Fix linter issue after merge * Engine QA changes Template updates Wrapper code cleanup Disarmed order bombs Documentation updates * Daily engine QA Bitstamp improvements Spelling mistakes Add Coinbene exchange to support list Protect API authenticated calls for Coinbene/LBank * Engine QA changes Fix exchange_wrapper_coverage tool Add SupportsAsset to exchange interface Fix inline string usage and add BCH withdrawal support * Engine QA Fix Bitstamp types Inform user of errors when parsing time accross the codebase Change time parsing warnings to errors (as they are) Update markdown docs [with linter fixes] * Engine QA changes 1) Add test for dryrunParamInteraction 2) Disarm OKCoin/OKEX bombs if someone accidently sets canManipulateRealOrders to true and runs all package tests 3) Actually check exchange setup errors for BTSE and Coinbene, plus address this in the wrapper template 4) Hardcode missing/non-retrievable contributors and bump the contributors 5) Convert numbers/strings to meaningful types in Bitstamp and OKEX 6) If WS is supported for the exchange wrapper template, preset authWebsocketSupport var * Fix the shadow people * Link the SyncContinuously paramerino * Also show SyncContinuously in engine.PrintSettings * Address nitterinos and use correct filepath for logs * Bitstamp: Extract ALL THE APM * Fix additional nitterinos * Fix time parsing error for Bittrex --- .github/CONTRIBUTING.md | 59 +- .github/CONTRIBUTING_TEMPLATE.md | 6 +- .github/ISSUE_TEMPLATE.md | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 22 +- CONTRIBUTORS | 3 +- Dockerfile | 14 +- README.md | 7 +- cmd/documentation/documentation.go | 38 + .../exchanges_templates/coinbene.tmpl | 6 +- .../exchanges_templates/lbank.tmpl | 10 +- cmd/exchange_template/exchange_template.go | 24 +- cmd/exchange_template/main_file.tmpl | 90 +- cmd/exchange_template/test_file.tmpl | 44 +- cmd/exchange_template/wrapper_file.tmpl | 291 +++- cmd/exchange_wrapper_coverage/main.go | 8 + .../wrapperconfig.json | 11 +- communications/slack/slack_test.go | 68 +- communications/slack/slack_types.go | 404 +---- config/config_test.go | 9 +- docker-compose.yml | 6 +- docs/EXCHANGE_API.md | 8 +- engine/engine.go | 48 +- engine/engine_types.go | 21 +- engine/events.go | 1 - engine/exchange.go | 27 + engine/exchange_test.go | 83 +- engine/routines.go | 11 +- engine/syncer.go | 28 +- engine/syncer_types.go | 6 +- exchanges/alphapoint/alphapoint_wrapper.go | 17 +- exchanges/anx/anx_test.go | 2 +- exchanges/anx/anx_wrapper.go | 3 +- exchanges/binance/binance.go | 57 +- exchanges/binance/binance_test.go | 2 +- exchanges/binance/binance_types.go | 26 +- exchanges/binance/binance_wrapper.go | 3 +- exchanges/bitfinex/bitfinex_websocket.go | 7 +- exchanges/bitfinex/bitfinex_wrapper.go | 3 +- exchanges/bithumb/bithumb_test.go | 19 +- exchanges/bithumb/bithumb_wrapper.go | 3 +- exchanges/bitmex/bitmex_websocket.go | 12 +- exchanges/bitstamp/bitstamp.go | 176 +- exchanges/bitstamp/bitstamp_test.go | 47 +- exchanges/bitstamp/bitstamp_types.go | 90 +- exchanges/bitstamp/bitstamp_websocket.go | 22 +- exchanges/bitstamp/bitstamp_wrapper.go | 87 +- exchanges/bittrex/bittrex.go | 6 + exchanges/bittrex/bittrex_test.go | 18 + exchanges/bittrex/bittrex_wrapper.go | 17 +- exchanges/btcmarkets/btcmarkets_types.go | 1 + exchanges/btcmarkets/btcmarkets_wrapper.go | 15 +- exchanges/btse/btse.go | 8 +- exchanges/btse/btse_test.go | 34 +- exchanges/btse/btse_websocket.go | 15 +- exchanges/btse/btse_wrapper.go | 30 +- exchanges/coinbasepro/coinbasepro_test.go | 19 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 4 +- exchanges/coinbene/README.md | 10 +- exchanges/coinbene/coinbene.go | 5 + exchanges/coinbene/coinbene_test.go | 6 +- exchanges/coinut/coinut_wrapper.go | 4 +- exchanges/exchange.go | 6 +- exchanges/exchange_test.go | 10 +- exchanges/exmo/exmo_wrapper.go | 3 +- exchanges/gateio/gateio_wrapper.go | 15 +- exchanges/gemini/gemini_test.go | 6 +- exchanges/gemini/gemini_wrapper.go | 3 +- exchanges/hitbtc/hitbtc_websocket.go | 27 +- exchanges/hitbtc/hitbtc_wrapper.go | 19 +- exchanges/huobi/huobi_wrapper.go | 18 +- exchanges/interfaces.go | 1 + exchanges/itbit/itbit_wrapper.go | 17 +- exchanges/kraken/kraken_wrapper.go | 3 +- exchanges/lakebtc/lakebtc_websocket.go | 7 +- exchanges/lakebtc/lakebtc_wrapper.go | 3 +- exchanges/lbank/README.md | 10 +- exchanges/lbank/lbank.go | 8 +- exchanges/lbank/lbank_wrapper.go | 6 +- .../localbitcoins/localbitcoins_wrapper.go | 19 +- exchanges/okcoin/okcoin_test.go | 143 +- exchanges/okex/okex_test.go | 162 +- exchanges/okgroup/README.md | 10 +- exchanges/okgroup/okgroup.go | 31 +- exchanges/okgroup/okgroup_types.go | 60 +- exchanges/okgroup/okgroup_websocket.go | 18 +- exchanges/okgroup/okgroup_wrapper.go | 8 +- exchanges/poloniex/poloniex.go | 20 +- exchanges/poloniex/poloniex_websocket.go | 7 +- exchanges/poloniex/poloniex_wrapper.go | 22 +- exchanges/support.go | 1 + exchanges/yobit/yobit_wrapper.go | 3 +- exchanges/zb/zb_wrapper.go | 22 +- gctrpc/rpc.pb.go | 755 ++++---- gctrpc/rpc.pb.gw.go | 1531 +++++++++++++++++ gctrpc/rpc.proto | 4 +- gctrpc/rpc.swagger.json | 36 +- main.go | 14 +- testdata/http_mock/anx/anx.json | 2 +- testdata/http_mock/binance/binance.json | 2 +- testdata/http_mock/bitstamp/bitstamp.json | 2 +- testdata/http_mock/gemini/gemini.json | 2 +- .../websocket-response-handler.service.ts | 2 +- 102 files changed, 3447 insertions(+), 1714 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index cd01660e..6c539d02 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -17,6 +17,7 @@ In order to maintain a consistent style across the codebase, the following codin - In line with gofmt, for loops and if statements don't require parenthesis. Block style example: + ```go func SendHTTPRequest(method, path string, headers map[string]string, body io.Reader) (string, error) { result := strings.ToUpper(method) @@ -38,51 +39,51 @@ func SendHTTPRequest(method, path string, headers map[string]string, body io.Rea ``` ## Effective Go Guidelines + [CodeLingo](https://codelingo.io) automatically checks every pull request against the following guidelines from [Effective Go](https://golang.org/doc/effective_go.html). - ### Comment First Word as Subject + Doc comments work best as complete sentences, which allow a wide variety of automated presentations. The first sentence should be a one-sentence summary that starts with the name being declared. - ### Good Package Name -It's helpful if everyone using the package can use the same name -to refer to its contents, which implies that the package name should -be good: short, concise, evocative. By convention, packages are -given lower case, single-word names; there should be no need for -underscores or mixedCaps. Err on the side of brevity, since everyone -using your package will be typing that name. And don't worry about -collisions a priori. The package name is only the default name for -imports; it need not be unique across all source code, and in the -rare case of a collision the importing package can choose a different -name to use locally. In any case, confusion is rare because the file + +It's helpful if everyone using the package can use the same name +to refer to its contents, which implies that the package name should +be good: short, concise, evocative. By convention, packages are +given lower case, single-word names; there should be no need for +underscores or mixedCaps. Err on the side of brevity, since everyone +using your package will be typing that name. And don't worry about +collisions a priori. The package name is only the default name for +imports; it need not be unique across all source code, and in the +rare case of a collision the importing package can choose a different +name to use locally. In any case, confusion is rare because the file name in the import determines just which package is being used. - ### Package Comment -Every package should have a package comment, a block comment preceding the package clause. -For multi-file packages, the package comment only needs to be present in one file, and any one will do. -The package comment should introduce the package and provide information relevant to the package as a + +Every package should have a package comment, a block comment preceding the package clause. +For multi-file packages, the package comment only needs to be present in one file, and any one will do. +The package comment should introduce the package and provide information relevant to the package as a whole. It will appear first on the godoc page and should set up the detailed documentation that follows. - ### Single Method Interface Name -By convention, one-method interfaces are named by the method name plus an -er suffix + +By convention, one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc. -There are a number of such names and it's productive to honor them and the function names they capture. -Read, Write, Close, Flush, String and so on have canonical signatures and meanings. To avoid confusion, -don't give your method one of those names unless it has the same signature and meaning. Conversely, -if your type implements a method with the same meaning as a method on a well-known type, give it the +There are a number of such names and it's productive to honor them and the function names they capture. +Read, Write, Close, Flush, String and so on have canonical signatures and meanings. To avoid confusion, +don't give your method one of those names unless it has the same signature and meaning. Conversely, +if your type implements a method with the same meaning as a method on a well-known type, give it the same name and signature; call your string-converter method String not ToString. - ### Avoid Annotations in Comments -Comments do not need extra formatting such as banners of stars. The generated output -may not even be presented in a fixed-width font, so don't depend on spacing for alignment—godoc, -like gofmt, takes care of that. The comments are uninterpreted plain text, so HTML and other -annotations such as _this_ will reproduce verbatim and should not be used. One adjustment godoc -does do is to display indented text in a fixed-width font, suitable for program snippets. -The package comment for the fmt package uses this to good effect. +Comments do not need extra formatting such as banners of stars. The generated output +may not even be presented in a fixed-width font, so don't depend on spacing for alignment—godoc, +like gofmt, takes care of that. The comments are uninterpreted plain text, so HTML and other +annotations such as _this_ will reproduce verbatim and should not be used. One adjustment godoc +does do is to display indented text in a fixed-width font, suitable for program snippets. +The package comment for the fmt package uses this to good effect. diff --git a/.github/CONTRIBUTING_TEMPLATE.md b/.github/CONTRIBUTING_TEMPLATE.md index f4aecf7c..cff2cb3f 100644 --- a/.github/CONTRIBUTING_TEMPLATE.md +++ b/.github/CONTRIBUTING_TEMPLATE.md @@ -17,6 +17,7 @@ In order to maintain a consistent style across the codebase, the following codin - In line with gofmt, for loops and if statements don't require parenthesis. Block style example: + ```go func SendHTTPRequest(method, path string, headers map[string]string, body io.Reader) (string, error) { result := strings.ToUpper(method) @@ -38,9 +39,12 @@ func SendHTTPRequest(method, path string, headers map[string]string, body io.Rea ``` ## Effective Go Guidelines + [CodeLingo](https://codelingo.io) automatically checks every pull request against the following guidelines from [Effective Go](https://golang.org/doc/effective_go.html). {{range .}} + ### {{.title}} + {{.body}} -{{end}} \ No newline at end of file +{{end}} diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index cbf11442..75073df6 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -30,4 +30,4 @@ Please provide detailed steps for reproducing the issue. ### Failure Logs -By default, GoCryptoTrader stores its `debug.log` file in `%APPDATA%\GoCryptoTrader` on Windows and `~/.gocryptotrader` on Linux/Unix/macOS. Raw text or a link to a pastebin type site is preferred. \ No newline at end of file +By default and if file logging is enabled, GoCryptoTrader stores its `log.txt` file in `%APPDATA%\GoCryptoTrader\logs` on Windows and `~/.gocryptotrader/logs` on Linux/Unix/macOS. Raw text or a link to a pastebin type site is preferred. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d3ddb8a0..edabdd52 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ -# Description +# PR Description -Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. +Please include a summary of the change, feature or issue which this pull request addresses. Please also include relevant motivation and context. List any dependencies that are required for this change. Fixes # (issue) @@ -13,22 +13,22 @@ Please delete options that are not relevant and add an `x` in `[]` as item is co - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update -# How Has This Been Tested? +## How has this been tested -Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration and +also consider improving test coverage whilst working on a certain feature or package. -## Please also consider improving test coverage whilst working on a certain package +- [ ] go test ./... -race +- [ ] golangci-lint run +- [ ] Test X -- [ ] Test A -- [ ] Test B - -# Checklist: +## Checklist - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation and regenerated documentation via the documentation tool +- [ ] I have made corresponding changes to the documentation and regenerated documentation via the documentation tool - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally and on Travis with my changes -- [ ] Any dependent changes have been merged and published in downstream modules \ No newline at end of file +- [ ] Any dependent changes have been merged and published in downstream modules diff --git a/CONTRIBUTORS b/CONTRIBUTORS index c8958923..3453fc18 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -8,8 +8,8 @@ ermalguni | https://github.com/ermalguni vadimzhukck | https://github.com/vadimzhukck 140am | https://github.com/140am marcofranssen | https://github.com/marcofranssen -cranktakular | https://github.com/cranktakular MadCozBadd | https://github.com/MadCozBadd +cranktakular | https://github.com/cranktakular leilaes | https://github.com/leilaes crackcomm | https://github.com/crackcomm andreygrehov | https://github.com/andreygrehov @@ -30,6 +30,7 @@ frankzougc | https://github.com/frankzougc starit | https://github.com/starit Jimexist | https://github.com/Jimexist lookfirst | https://github.com/lookfirst +idoall | https://github.com/idoall mattkanwisher | https://github.com/mattkanwisher mKurrels | https://github.com/mKurrels m1kola | https://github.com/m1kola diff --git a/Dockerfile b/Dockerfile index 67fa7f9b..2524f16b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,18 @@ -FROM golang:1.12 as build +FROM golang:1.13 as build WORKDIR /go/src/github.com/thrasher-corp/gocryptotrader COPY . . RUN GO111MODULE=on go mod vendor RUN mv -vn config_example.json config.json \ - && GOARCH=386 GOOS=linux CGO_ENABLED=0 go build . \ - && mv gocryptotrader /go/bin/gocryptotrader + && GOARCH=386 GOOS=linux go build . \ + && GOARCH=386 GOOS=linux go build ./cmd/gctcli \ + && mv gocryptotrader /go/bin/gocryptotrader \ + && mv gctcli /go/bin/gctcli FROM alpine:latest -RUN apk update && apk add --no-cache ca-certificates +VOLUME /root/.gocryptotrader +RUN apk update && apk add --no-cache ca-certificates bash COPY --from=build /go/bin/gocryptotrader /app/ +COPY --from=build /go/bin/gctcli /app/ COPY --from=build /go/src/github.com/thrasher-corp/gocryptotrader/config.json /app/ EXPOSE 9050-9053 -CMD ["/app/gocryptotrader"] +ENTRYPOINT [ "/app/gocryptotrader" ] diff --git a/README.md b/README.md index 3f99dcda..5aee76a4 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ Binaries will be published once the codebase reaches a stable condition. |User|Contribution Amount| |--|--| -| [thrasher-](https://github.com/thrasher-) | 548 | +| [thrasher-](https://github.com/thrasher-) | 551 | | [shazbert](https://github.com/shazbert) | 176 | | [gloriousCode](https://github.com/gloriousCode) | 155 | | [xtda](https://github.com/xtda) | 18 | @@ -146,8 +146,8 @@ Binaries will be published once the codebase reaches a stable condition. | [vadimzhukck](https://github.com/vadimzhukck) | 10 | | [140am](https://github.com/140am) | 8 | | [marcofranssen](https://github.com/marcofranssen) | 8 | +| [MadCozBadd](https://github.com/MadCozBadd) | 6 | | [cranktakular](https://github.com/cranktakular) | 5 | -| [MadCozBadd](https://github.com/MadCozBadd) | 3 | | [leilaes](https://github.com/leilaes) | 3 | | [crackcomm](https://github.com/crackcomm) | 3 | | [andreygrehov](https://github.com/andreygrehov) | 2 | @@ -168,8 +168,9 @@ Binaries will be published once the codebase reaches a stable condition. | [starit](https://github.com/starit) | 1 | | [Jimexist](https://github.com/Jimexist) | 1 | | [lookfirst](https://github.com/lookfirst) | 1 | +| [idoall](https://github.com/idoall) | 1 | | [mattkanwisher](https://github.com/mattkanwisher) | 1 | | [mKurrels](https://github.com/mKurrels) | 1 | | [m1kola](https://github.com/m1kola) | 1 | | [cavapoo2](https://github.com/cavapoo2) | 1 | -| [zeldrinn](https://github.com/zeldrinn )| 1 | +| [zeldrinn](https://github.com/zeldrinn) | 1 | diff --git a/cmd/documentation/documentation.go b/cmd/documentation/documentation.go index 0fcba9fb..5267e306 100644 --- a/cmd/documentation/documentation.go +++ b/cmd/documentation/documentation.go @@ -116,6 +116,44 @@ func main() { err) } + // idoall's contributors were forked and merged, so his contributions + // aren't automatically retrievable + contributors = append(contributors, Contributor{ + Login: "idoall", + URL: "https://github.com/idoall", + Contributions: 1, + }) + + // Github API missing contributors + missingAPIContributors := []Contributor{ + { + Login: "mattkanwisher", + URL: "https://github.com/mattkanwisher", + Contributions: 1, + }, + { + Login: "mKurrels", + URL: "https://github.com/mKurrels", + Contributions: 1, + }, + { + Login: "m1kola", + URL: "https://github.com/m1kola", + Contributions: 1, + }, + { + Login: "cavapoo2", + URL: "https://github.com/cavapoo2", + Contributions: 1, + }, + { + Login: "zeldrinn", + URL: "https://github.com/zeldrinn", + Contributions: 1, + }, + } + contributors = append(contributors, missingAPIContributors...) + if *verbose { fmt.Println("Contributor List Fetched") for i := range contributors { diff --git a/cmd/documentation/exchanges_templates/coinbene.tmpl b/cmd/documentation/exchanges_templates/coinbene.tmpl index 4efb55ba..7dc3b4d4 100644 --- a/cmd/documentation/exchanges_templates/coinbene.tmpl +++ b/cmd/documentation/exchanges_templates/coinbene.tmpl @@ -30,9 +30,9 @@ main.go ```go var c exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Coinbene" { - c = bot.exchanges[i] +for i := range Bot.Exchanges { + if Bot.Exchanges[i].GetName() == "Coinbene" { + c = Bot.Exchanges[i] } } diff --git a/cmd/documentation/exchanges_templates/lbank.tmpl b/cmd/documentation/exchanges_templates/lbank.tmpl index 45fb9441..890fb91f 100644 --- a/cmd/documentation/exchanges_templates/lbank.tmpl +++ b/cmd/documentation/exchanges_templates/lbank.tmpl @@ -29,22 +29,22 @@ main.go ```go var l exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Lbank" { - l = bot.exchanges[i] +for i := range Bot.Exchanges { + if Bot.Exchanges[i].GetName() == "Lbank" { + l = Bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := l.GetTickerPrice() +tick, err := l.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := l.GetOrderbookEx() +ob, err := l.FetchOrderbook() if err != nil { // Handle error } diff --git a/cmd/exchange_template/exchange_template.go b/cmd/exchange_template/exchange_template.go index d01224eb..ef976e09 100644 --- a/cmd/exchange_template/exchange_template.go +++ b/cmd/exchange_template/exchange_template.go @@ -24,7 +24,6 @@ const ( packageReadme = "README.md" exchangePackageLocation = "../../exchanges" - exchangeLocation = "../../exchange.go" exchangeConfigPath = "../../testdata/configtest.json" ) @@ -35,7 +34,6 @@ var ( exchangeWrapper string exchangeMain string exchangeReadme string - exchangeJSON string ) type exchange struct { @@ -119,17 +117,22 @@ func main() { newExchConfig.Enabled = true newExchConfig.API.Credentials.Key = "Key" newExchConfig.API.Credentials.Secret = "Secret" - newExchConfig.CurrencyPairs = ¤cy.PairsManager{ AssetTypes: asset.Items{ asset.Spot, }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, } configTestFile.Exchanges = append(configTestFile.Exchanges, newExchConfig) - // TODO sorting function so exchanges are in alphabetical order - low priority - err = configTestFile.SaveConfig(exchangeJSON, false) + err = configTestFile.SaveConfig(exchangeConfigPath, false) if err != nil { log.Fatal("GoCryptoTrader: Exchange templating configuration error - cannot save") } @@ -217,12 +220,13 @@ func main() { } fmt.Println("GoCryptoTrader: Exchange templating tool service complete") - fmt.Println("When wrapper is finished add exchange to exchange.go") - fmt.Println("Test exchange.go") - fmt.Println("Update the config_test.go file") - fmt.Println("Test config.go") + fmt.Println("When the exchange code implementation has been completed (REST/Websocket/wrappers and tests), please add the exchange to engine/exchange.go") + fmt.Println("Add the exchange config settings to config_example.json (it will automatically be added to testdata/configtest.json)") + fmt.Println("Increment the available exchanges counter in config/config_test.go") + fmt.Println("Add the exchange name to exchanges/support.go") + fmt.Println("Ensure go test ./... -race passes") fmt.Println("Open a pull request") - fmt.Println("If help is needed please post a message on Slack.") + fmt.Println("If help is needed, please post a message in Slack.") } func newFile(path string) { diff --git a/cmd/exchange_template/main_file.tmpl b/cmd/exchange_template/main_file.tmpl index 151080b5..ef86003a 100644 --- a/cmd/exchange_template/main_file.tmpl +++ b/cmd/exchange_template/main_file.tmpl @@ -2,15 +2,7 @@ package {{.Name}} import ( - "time" - - "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" - "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - log "github.com/thrasher-corp/gocryptotrader/logger" ) // {{.CapitalName}} is the overarching type across this package @@ -20,91 +12,13 @@ type {{.CapitalName}} struct { const ( {{.Name}}APIURL = "" - {{.Name}}APIVersion = "" + {{.Name}}APIVersion = "" // Public endpoints // Authenticated endpoints - ) -// SetDefaults sets the basic defaults for {{.CapitalName}} -func ({{.Variable}} *{{.CapitalName}}) SetDefaults() { - {{.Variable}}.Name = "{{.CapitalName}}" - {{.Variable}}.Enabled = false - {{.Variable}}.Verbose = false - {{.Variable}}.RequestCurrencyPairFormat.Delimiter = "" - {{.Variable}}.RequestCurrencyPairFormat.Uppercase = true - {{.Variable}}.ConfigCurrencyPairFormat.Delimiter = "" - {{.Variable}}.ConfigCurrencyPairFormat.Uppercase = true - {{.Variable}}.AssetTypes = asset.Items{asset.Spot} - {{.Variable}}.SupportsAutoPairUpdating = false - {{.Variable}}.SupportsRESTTickerBatching = false - {{.Variable}}.Requester = request.New({{.Variable}}.Name, - request.NewRateLimit(time.Second, 0), - request.NewRateLimit(time.Second, 0), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) - {{.Variable}}.API.Endpoints.URLDefault = {{.Name}}APIURL - {{.Variable}}.API.Endpoints.URL = {{.Variable}}.API.Endpoints.URLDefault - {{.Variable}}.Websocket = monitor.New() - {{.Variable}}.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit - {{.Variable}}.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout -} +// Start implementing public and private exchange API funcs below -// Setup takes in the supplied exchange configuration details and sets params -func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) error { - if !exch.Enabled { - {{.Variable}}.SetEnabled(false) - } else { - {{.Variable}}.Enabled = true - {{.Variable}}.API.AuthenticatedSupport = exch.API.AuthenticatedSupport - {{.Variable}}.API.AuthenticatedWebsocketSupport = exch.API.AuthenticatedWebsocketSupport - {{.Variable}}.SetAPIKeys(exch.API.Credentials.Key, exch.API.Credentials.Secret, "", false) - {{.Variable}}.SetHTTPClientTimeout(exch.HTTPTimeout) - {{.Variable}}.SetHTTPClientUserAgent(exch.HTTPUserAgent) - {{.Variable}}.Verbose = exch.Verbose - {{.Variable}}.Websocket.SetWsStatusAndConnection(exch.Features.Enabled.Websocket) - {{.Variable}}.BaseCurrencies = strings.Split(exch.BaseCurrencies, ",") - {{.Variable}}.AvailablePairs = strings.Split(exch.AvailablePairs, ",") - {{.Variable}}.EnabledPairs = strings.Split(exch.EnabledPairs, ",") - err := {{.Variable}}.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = {{.Variable}}.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - err = {{.Variable}}.SetFeatureDefaults() - if err != nil { - log.Fatal(err) - } - err = {{.Variable}}.SetAPIURL(exch) - if err != nil { - log.Fatal(err) - } - err = {{.Variable}}.SetClientProxyAddress(exch.ProxyAddress) - if err != nil { - log.Fatal(err) - } - - // If the exchange supports websocket, update the below block - // err = {{.Variable}}.Websocket.Setup({{.Variable}}.WsConnect, - // exch.Name, - // exch.Features.Enabled.Websocket, - // {{.Name}}Websocket, - // exch.Features.Enabled.WebsocketURL) - // if err != nil { - // log.Fatal(err) - // } - // {{.Variable}}.WebsocketConn = &wshandler.WebsocketConnection{ - // ExchangeName: {{.Variable}}.Name, - // URL: {{.Variable}}.Websocket.GetWebsocketURL(), - // ProxyURL: {{.Variable}}.Websocket.GetProxyAddress(), - // Verbose: {{.Variable}}.Verbose, - // ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, - // ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - // } - } -} {{end}} diff --git a/cmd/exchange_template/test_file.tmpl b/cmd/exchange_template/test_file.tmpl index 87ee2bdc..989238e8 100644 --- a/cmd/exchange_template/test_file.tmpl +++ b/cmd/exchange_template/test_file.tmpl @@ -2,35 +2,51 @@ package {{.Name}} import ( + "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/config" ) -// Please supply your own keys here for due diligence testing +// Please supply your own keys here to do authenticated endpoint testing const ( - testAPIKey = "" - testAPISecret = "" + apiKey = "" + apiSecret = "" + canManipulateRealOrders = false ) var {{.Variable}} {{.CapitalName}} -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { {{.Variable}}.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.json") - {{.Name}}Config, err := cfg.GetExchangeConfig("{{.CapitalName}}") + err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Error("{{.CapitalName}} Setup() init error") + log.Fatal(err) } - {{.Name}}Config.API.AuthenticatedSupport = true - {{.Name}}Config.API.Credentials.Key = testAPIKey - {{.Name}}Config.API.Credentials.Secret = testAPISecret + exchCfg, err := cfg.GetExchangeConfig("{{.CapitalName}}") + if err != nil { + log.Fatal(err) + } - {{.Variable}}.Setup({{.Name}}Config) + exchCfg.API.AuthenticatedSupport = true + {{ if .WS }} exchCfg.API.AuthenticatedWebsocketSupport = true {{ end }} + exchCfg.API.Credentials.Key = apiKey + exchCfg.API.Credentials.Secret = apiSecret + + err = {{.Variable}}.Setup(exchCfg) + if err != nil { + log.Fatal(err) + } + + os.Exit(m.Run()) } + +func areTestAPIKeysSet() bool { + return {{.Variable}}.ValidateAPICredentials() +} + +// Implement tests for API endpoints below {{end}} diff --git a/cmd/exchange_template/wrapper_file.tmpl b/cmd/exchange_template/wrapper_file.tmpl index fd86723b..0ed4873f 100644 --- a/cmd/exchange_template/wrapper_file.tmpl +++ b/cmd/exchange_template/wrapper_file.tmpl @@ -3,15 +3,150 @@ package {{.Name}} import ( "sync" + "time" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/currency/pair" - "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/currency" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" + "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" ) +// GetDefaultConfig returns a default exchange config +func ({{.Variable}} *{{.CapitalName}}) GetDefaultConfig() (*config.ExchangeConfig, error) { + {{.Variable}}.SetDefaults() + exchCfg := new(config.ExchangeConfig) + exchCfg.Name = {{.Variable}}.Name + exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout + exchCfg.BaseCurrencies = {{.Variable}}.BaseCurrencies + + err := {{.Variable}}.SetupDefaults(exchCfg) + if err != nil { + return nil, err + } + + if {{.Variable}}.Features.Supports.RESTCapabilities.AutoPairUpdates { + err = {{.Variable}}.UpdateTradablePairs(true) + if err != nil { + return nil, err + } + } + return exchCfg, nil +} + +// SetDefaults sets the basic defaults for {{.CapitalName}} +func ({{.Variable}} *{{.CapitalName}}) SetDefaults() { + {{.Variable}}.Name = "{{.CapitalName}}" + {{.Variable}}.Enabled = true + {{.Variable}}.Verbose = true + {{.Variable}}.API.CredentialsValidator.RequiresKey = true + {{.Variable}}.API.CredentialsValidator.RequiresSecret = true + {{.Variable}}.CurrencyPairs = currency.PairsManager{ + AssetTypes: asset.Items{ + asset.Spot, + }, + UseGlobalFormat: true, + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "-", + }, + } + // Fill out the capabilities/features that the exchange supports + {{.Variable}}.Features = exchange.Features{ + Supports: exchange.FeaturesSupported{ + {{ if .REST }} REST: true, {{ end }} + {{ if .WS }} Websocket: true, {{ end }} + RESTCapabilities: protocol.Features{ + TickerFetching: true, + OrderbookFetching: true, + }, + WebsocketCapabilities: protocol.Features{ + TickerFetching: true, + OrderbookFetching: true, + }, + WithdrawPermissions: exchange.AutoWithdrawCrypto | + exchange.AutoWithdrawFiat, + }, + Enabled: exchange.FeaturesEnabled{ + AutoPairUpdates: true, + }, + } + {{.Variable}}.Requester = request.New({{.Variable}}.Name, + request.NewRateLimit(time.Second, 0), + request.NewRateLimit(time.Second, 0), + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + {{.Variable}}.API.Endpoints.URLDefault = {{.Name}}APIURL + {{.Variable}}.API.Endpoints.URL = {{.Variable}}.API.Endpoints.URLDefault + {{.Variable}}.Websocket = wshandler.New() + {{.Variable}}.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit + {{.Variable}}.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout + {{.Variable}}.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit +} + +// Setup takes in the supplied exchange configuration details and sets params +func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + {{.Variable}}.SetEnabled(false) + return nil + } + + err := {{.Variable}}.SetupDefaults(exch) + if err != nil { + return err + } + + // If websocket is supported, please fill out the following + /* + err = {{.Variable}}.Websocket.Setup( + &wshandler.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: {{.Name}}WSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: {{.Variable}}.WsConnect, + Subscriber: {{.Variable}}.Subscribe, + UnSubscriber: {{.Variable}}.Unsubscribe, + Features: &{{.Variable}}.Features.Supports.WebsocketCapabilities, + }) + if err != nil { + return err + } + + {{.Variable}}.WebsocketConn = &wshandler.WebsocketConnection{ + ExchangeName: {{.Variable}}.Name, + URL: {{.Variable}}.Websocket.GetWebsocketURL(), + ProxyURL: {{.Variable}}.Websocket.GetProxyAddress(), + Verbose: {{.Variable}}.Verbose, + ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, + ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + } + + // NOTE: PLEASE ENSURE YOU SET THE ORDERBOOK BUFFER SETTINGS CORRECTLY + {{.Variable}}.Websocket.Orderbook.Setup( + exch.WebsocketOrderbookBufferLimit, + true, + true, + false, + false, + exch.Name) + */ + return nil +} + // Start starts the {{.CapitalName}} go routine func ({{.Variable}} *{{.CapitalName}}) Start(wg *sync.WaitGroup) { wg.Add(1) @@ -24,38 +159,68 @@ func ({{.Variable}} *{{.CapitalName}}) Start(wg *sync.WaitGroup) { // Run implements the {{.CapitalName}} wrapper func ({{.Variable}} *{{.CapitalName}}) Run() { if {{.Variable}}.Verbose { -{{if .WS}} log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", {{.Variable}}.Name, common.IsEnabled({{.Variable}}.Websocket.IsEnabled()), {{.Variable}}.Websocket.GetWebsocketURL()) {{end}} - log.Debugf(log.ExchangeSys, "%s polling delay: %ds.\n", {{.Variable}}.Name, {{.Variable}}.RESTPollingDelay) - log.Debugf(log.ExchangeSys, "%s %d currencies enabled: %s.\n", {{.Variable}}.Name, len({{.Variable}}.EnabledPairs), {{.Variable}}.EnabledPairs) + {{ if .WS }} log.Debugf(log.ExchangeSys, + "%s Websocket: %s.", + {{.Variable}}.Name, + common.IsEnabled({{.Variable}}.Websocket.IsEnabled())) {{ end }} + {{.Variable}}.PrintEnabledPairs() + } + + if !{{.Variable}}.GetEnabledFeatures().AutoPairUpdates { + return + } + + err := {{.Variable}}.UpdateTradablePairs(false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + {{.Variable}}.Name, + err) } } +// FetchTradablePairs returns a list of the exchanges tradable pairs +func ({{.Variable}} *{{.CapitalName}}) FetchTradablePairs(asset asset.Item) ([]string, error) { + // Implement fetching the exchange available pairs if supported + return nil, nil +} + +// UpdateTradablePairs updates the exchanges available pairs and stores +// them in the exchanges config +func ({{.Variable}} *{{.CapitalName}}) UpdateTradablePairs(forceUpdate bool) error { + pairs, err := {{.Variable}}.FetchTradablePairs(asset.Spot) + if err != nil { + return err + } + return {{.Variable}}.UpdatePairs(currency.NewPairsFromStrings(pairs), + asset.Spot, false, forceUpdate) +} + + // UpdateTicker updates and returns the ticker for a currency pair func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { + // NOTE: EXAMPLE FOR GETTING TICKER PRICE + /* var tickerPrice ticker.Price - // NOTE EXAMPLE FOR GETTING TICKER PRICE - //tick, err := {{.Variable}}.GetTickers() - //if err != nil { - // return tickerPrice, err - //} - - //for _, x := range {{.Variable}}.GetEnabledPairs(assetType) { - //curr := exchange.FormatExchangeCurrency({{.Variable}}.Name, x) - //for y := range tick { - // if tick[y].Symbol == curr.String() { - // tickerPrice.Pair = x - // tickerPrice.Ask = tick[y].AskPrice - // tickerPrice.Bid = tick[y].BidPrice - // tickerPrice.High = tick[y].HighPrice - // tickerPrice.Last = tick[y].LastPrice - // tickerPrice.Low = tick[y].LowPrice - // tickerPrice.Volume = tick[y].Volume - // ticker.ProcessTicker({{.Variable}}.Name, x, &tickerPrice, assetType) - // } - // } - //} - //return ticker.GetTicker({{.Variable}}.Name, p, assetType) - return tickerPrice, nil // NOTE DO NOT USE AS RETURN + tick, err := {{.Variable}}.GetTicker(p.String()) + if err != nil { + return tickerPrice, err + } + tickerPrice = ticker.Price{ + High: tick.High, + Low: tick.Low, + Bid: tick.Bid, + Ask: tick.Ask, + Open: tick.Open, + Close: tick.Close, + Pair: p, + } + err = ticker.ProcessTicker({{.Variable}}.Name, &tickerPrice, assetType) + if err != nil { + return tickerPrice, err + } + */ + return ticker.GetTicker({{.Variable}}.Name, p, assetType) } // FetchTicker returns the ticker for a currency pair @@ -79,23 +244,39 @@ func ({{.Variable}} *{{.CapitalName}}) FetchOrderbook(currency currency.Pair, as // UpdateOrderbook updates and returns the orderbook for a currency pair func ({{.Variable}} *{{.CapitalName}}) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - //NOTE UPDATE ORDERBOOK EXAMPLE - //orderbookNew, err := {{.Variable}}.GetOrderBook(exchange.FormatExchangeCurrency({{.Variable}}.Name, p).String(), 1000) - //if err != nil { - // return orderBook, err - //} + // NOTE: UPDATE ORDERBOOK EXAMPLE + /* + orderbookNew, err := {{.Variable}}.GetOrderBook(exchange.FormatExchangeCurrency({{.Variable}}.Name, p).String(), 1000) + if err != nil { + return orderBook, err + } - //for _, bids := range orderbookNew.Bids { - // orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: bids.Quantity, Price: bids.Price}) - //} + for x := range orderbookNew.Bids { + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[x].Quantity, + Price: orderbookNew.Bids[x].Price, + }) + } - //for _, asks := range orderbookNew.Asks { - // orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: asks.Quantity, Price: asks.Price}) - //} + for x := range orderbookNew.Asks { + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: orderBook.Asks[x].Quantity, + Price: orderBook.Asks[x].Price, + }) + } + */ - //orderbook.ProcessOrderbook(b.Name, p, orderBook, assetType) - //return orderbook.Get({{.Variable}}.Name, p, assetType) - return orderBook, nil // NOTE DO NOT USE AS RETURN + + orderBook.Pair = p + orderBook.ExchangeName = {{.Variable}}.Name + orderBook.AssetType = assetType + + err := orderBook.Process() + if err != nil { + return orderBook, err + } + + return orderbook.Get({{.Variable}}.Name, p, assetType) } // GetAccountInfo retrieves balances for all enabled currencies for the @@ -116,8 +297,12 @@ func ({{.Variable}} *{{.CapitalName}}) GetExchangeHistory(p currency.Pair, asset } // SubmitOrder submits a new order -func ({{.Variable}} *{{.CapitalName}}) SubmitOrder(p currency.Pair, side order.Side, orderType order.Type, amount, price float64, clientID string) (order.SubmitResponse, error) { - return order.SubmitResponse{}, common.ErrNotYetImplemented +func ({{.Variable}} *{{.CapitalName}}) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + var submitOrderResponse order.SubmitResponse + if err := s.Validate(); err != nil { + return submitOrderResponse, err + } + return submitOrderResponse, common.ErrNotYetImplemented } // ModifyOrder will allow of changing orderbook placement and limit to @@ -142,30 +327,30 @@ func ({{.Variable}} *{{.CapitalName}}) GetOrderInfo(orderID string) (order.Detai } // GetDepositAddress returns a deposit address for a specified currency -func ({{.Variable}} *{{.CapitalName}}) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { +func ({{.Variable}} *{{.CapitalName}}) GetDepositAddress(cryptocurrency currency.Code, accountID string) (string, error) { return "", common.ErrNotYetImplemented } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted -func ({{.Variable}} *{{.CapitalName}}) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func ({{.Variable}} *{{.CapitalName}}) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { return "", common.ErrNotYetImplemented } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is // submitted -func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrNotYetImplemented } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is // submitted -func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) { +func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { return "", common.ErrNotYetImplemented } // GetWebsocket returns a pointer to the exchange websocket -func ({{.Variable}} *{{.CapitalName}}) GetWebsocket() (*exchange.Websocket, error) { +func ({{.Variable}} *{{.CapitalName}}) GetWebsocket() (*wshandler.Websocket, error) { return nil, common.ErrNotYetImplemented } @@ -187,20 +372,20 @@ func ({{.Variable}} *{{.CapitalName}}) GetFeeByType(feeBuilder *exchange.FeeBuil // SubscribeToWebsocketChannels appends to ChannelsToSubscribe // which lets websocket.manageSubscriptions handle subscribing -func ({{.Variable}} *{{.CapitalName}}) SubscribeToWebsocketChannels(channels []monitor.WebsocketChannelSubscription) error { +func ({{.Variable}} *{{.CapitalName}}) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { {{.Variable}}.Websocket.SubscribeToChannels(channels) return nil } // UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe // which lets websocket.manageSubscriptions handle unsubscribing -func ({{.Variable}} *{{.CapitalName}}) UnsubscribeToWebsocketChannels(channels []monitor.WebsocketChannelSubscription) error { - {{.Variable}}.Websocket.UnubscribeToChannels(channels) +func ({{.Variable}} *{{.CapitalName}}) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { + {{.Variable}}.Websocket.RemoveSubscribedChannels(channels) return nil } // GetSubscriptions returns a copied list of subscriptions -func ({{.Variable}} *{{.CapitalName}}) GetSubscriptions() ([]monitor.WebsocketChannelSubscription, error) { +func ({{.Variable}} *{{.CapitalName}}) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { return nil, common.ErrNotYetImplemented } diff --git a/cmd/exchange_wrapper_coverage/main.go b/cmd/exchange_wrapper_coverage/main.go index d37808b5..40dba073 100644 --- a/cmd/exchange_wrapper_coverage/main.go +++ b/cmd/exchange_wrapper_coverage/main.go @@ -2,7 +2,9 @@ package main import ( "log" + "math/rand" "sync" + "time" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" @@ -70,6 +72,12 @@ func main() { func testWrappers(e exchange.IBotExchange) []string { p := currency.NewPair(currency.BTC, currency.USD) assetType := asset.Spot + if !e.SupportsAsset(assetType) { + assets := e.GetAssetTypes() + rand.Seed(time.Now().Unix()) + assetType = assets[rand.Intn(len(assets))] + } + var funcs []string _, err := e.FetchTicker(p, assetType) diff --git a/cmd/exchange_wrapper_issues/wrapperconfig.json b/cmd/exchange_wrapper_issues/wrapperconfig.json index ad94b188..3d54a318 100644 --- a/cmd/exchange_wrapper_issues/wrapperconfig.json +++ b/cmd/exchange_wrapper_issues/wrapperconfig.json @@ -91,6 +91,11 @@ "clientID": "ClientID", "otpSecret": "-" }, + "coinbene": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, "coinut": { "key": "Key", "clientID": "ClientID", @@ -136,7 +141,11 @@ "secret": "Secret", "otpSecret": "-" }, - "lbank": {}, + "lbank": { + "key": "Key", + "secret": "Secret", + "otpSecret": "-" + }, "localbitcoins": { "key": "Key", "secret": "Secret", diff --git a/communications/slack/slack_test.go b/communications/slack/slack_test.go index 85e1d6e6..4996d2b1 100644 --- a/communications/slack/slack_test.go +++ b/communications/slack/slack_test.go @@ -15,28 +15,9 @@ const ( var s Slack type group struct { - ID string `json:"id"` - Name string `json:"name"` - IsGroup bool `json:"is_group"` - Created int64 `json:"created"` - Creator string `json:"creator"` - IsArchived bool `json:"is_archived"` - NameNormalised string `json:"name_normalised"` - IsMPIM bool `json:"is_mpim"` - HasPins bool `json:"has_pins"` - IsOpen bool `json:"is_open"` - LastRead string `json:"last_read"` - Members []string `json:"members"` - Topic struct { - Value string `json:"value"` - Creator string `json:"creator"` - LastSet int64 `json:"last_set"` - } `json:"topic"` - Purpose struct { - Value string `json:"value"` - Creator string `json:"creator"` - LastSet int64 `json:"last_set"` - } `json:"purpose"` + ID string `json:"id"` + Name string `json:"name"` + Members []string `json:"members"` } func TestSetup(t *testing.T) { @@ -76,16 +57,7 @@ func TestBuildURL(t *testing.T) { func TestGetChannelsString(t *testing.T) { s.Details.Channels = append(s.Details.Channels, struct { - Created int `json:"created"` - Creator string `json:"creator"` - HasPins bool `json:"has_pins"` ID string `json:"id"` - IsArchived bool `json:"is_archived"` - IsChannel bool `json:"is_channel"` - IsGeneral bool `json:"is_general"` - IsMember bool `json:"is_member"` - IsOrgShared bool `json:"is_org_shared"` - IsShared bool `json:"is_shared"` Name string `json:"name"` NameNormalized string `json:"name_normalized"` PreviousNames []string `json:"previous_names"` @@ -112,28 +84,9 @@ func TestGetUsernameByID(t *testing.T) { } s.Details.Users = append(s.Details.Users, struct { - Deleted bool `json:"deleted"` - ID string `json:"id"` - IsBot bool `json:"is_bot"` - Name string `json:"name"` - Presence string `json:"presence"` - Profile struct { - AvatarHash string `json:"avatar_hash"` - Email string `json:"email"` - Fields interface{} `json:"fields"` - FirstName string `json:"first_name"` - Image192 string `json:"image_192"` - Image24 string `json:"image_24"` - Image32 string `json:"image_32"` - Image48 string `json:"image_48"` - Image512 string `json:"image_512"` - Image72 string `json:"image_72"` - LastName string `json:"last_name"` - RealName string `json:"real_name"` - RealNameNormalized string `json:"real_name_normalized"` - } `json:"profile"` - TeamID string `json:"team_id"` - Updated int `json:"updated"` + ID string `json:"id"` + Name string `json:"name"` + TeamID string `json:"team_id"` }{ ID: "1337", Name: "cranktakular", @@ -186,16 +139,7 @@ func TestGetChannelIDByName(t *testing.T) { } s.Details.Channels = append(s.Details.Channels, struct { - Created int `json:"created"` - Creator string `json:"creator"` - HasPins bool `json:"has_pins"` ID string `json:"id"` - IsArchived bool `json:"is_archived"` - IsChannel bool `json:"is_channel"` - IsGeneral bool `json:"is_general"` - IsMember bool `json:"is_member"` - IsOrgShared bool `json:"is_org_shared"` - IsShared bool `json:"is_shared"` Name string `json:"name"` NameNormalized string `json:"name_normalized"` PreviousNames []string `json:"previous_names"` diff --git a/communications/slack/slack_types.go b/communications/slack/slack_types.go index 07358656..e738aab2 100644 --- a/communications/slack/slack_types.go +++ b/communications/slack/slack_types.go @@ -36,406 +36,32 @@ type PresenceChange struct { // Response is a generalised response type type Response struct { - Bots []struct { - AppID string `json:"app_id"` - Deleted bool `json:"deleted"` - Icons struct { - Image36 string `json:"image_36"` - Image48 string `json:"image_48"` - Image72 string `json:"image_72"` - } `json:"icons"` - ID string `json:"id"` - Name string `json:"name"` - Updated int `json:"updated"` - } `json:"bots"` - CacheTs int `json:"cache_ts"` - CacheTsVersion string `json:"cache_ts_version"` - CacheVersion string `json:"cache_version"` - CanManageSharedChannels bool `json:"can_manage_shared_channels"` - Channels []struct { - Created int `json:"created"` - Creator string `json:"creator"` - HasPins bool `json:"has_pins"` + Channels []struct { ID string `json:"id"` - IsArchived bool `json:"is_archived"` - IsChannel bool `json:"is_channel"` - IsGeneral bool `json:"is_general"` - IsMember bool `json:"is_member"` - IsOrgShared bool `json:"is_org_shared"` - IsShared bool `json:"is_shared"` Name string `json:"name"` NameNormalized string `json:"name_normalized"` PreviousNames []string `json:"previous_names"` } `json:"channels"` - Dnd struct { - DndEnabled bool `json:"dnd_enabled"` - NextDndEndTs int `json:"next_dnd_end_ts"` - NextDndStartTs int `json:"next_dnd_start_ts"` - SnoozeEnabled bool `json:"snooze_enabled"` - } `json:"dnd"` Groups []struct { - ID string `json:"id"` - Name string `json:"name"` - IsGroup bool `json:"is_group"` - Created int64 `json:"created"` - Creator string `json:"creator"` - IsArchived bool `json:"is_archived"` - NameNormalised string `json:"name_normalised"` - IsMPIM bool `json:"is_mpim"` - HasPins bool `json:"has_pins"` - IsOpen bool `json:"is_open"` - LastRead string `json:"last_read"` - Members []string `json:"members"` - Topic struct { - Value string `json:"value"` - Creator string `json:"creator"` - LastSet int64 `json:"last_set"` - } `json:"topic"` - Purpose struct { - Value string `json:"value"` - Creator string `json:"creator"` - LastSet int64 `json:"last_set"` - } `json:"purpose"` + ID string `json:"id"` + Name string `json:"name"` + Members []string `json:"members"` } `json:"groups"` - Ims []struct { - Created int `json:"created"` - HasPins bool `json:"has_pins"` - ID string `json:"id"` - IsIm bool `json:"is_im"` - IsOpen bool `json:"is_open"` - IsOrgShared bool `json:"is_org_shared"` - LastRead string `json:"last_read"` - User string `json:"user"` - } `json:"ims"` - LatestEventTs string `json:"latest_event_ts"` - Ok bool `json:"ok"` - Error string `json:"error"` - ReadOnlyChannels []interface{} `json:"read_only_channels"` - Self struct { - Created int `json:"created"` - ID string `json:"id"` - ManualPresence string `json:"manual_presence"` - Name string `json:"name"` - Prefs struct { - A11yAnimations bool `json:"a11y_animations"` - A11yFontSize string `json:"a11y_font_size"` - AllChannelsLoud bool `json:"all_channels_loud"` - AllNotificationsPrefs string `json:"all_notifications_prefs"` - AllUnreadsSortOrder string `json:"all_unreads_sort_order"` - AllowCallsToSetCurrentStatus bool `json:"allow_calls_to_set_current_status"` - AnalyticsUpsellCoachmarkSeen bool `json:"analytics_upsell_coachmark_seen"` - ArrowHistory bool `json:"arrow_history"` - AtChannelSuppressedChannels string `json:"at_channel_suppressed_channels"` - BoxEnabled bool `json:"box_enabled"` - ChannelSort string `json:"channel_sort"` - ClientLogsPri string `json:"client_logs_pri"` - ColorNamesInList bool `json:"color_names_in_list"` - ConfirmClearAllUnreads bool `json:"confirm_clear_all_unreads"` - ConfirmShCallStart bool `json:"confirm_sh_call_start"` - ConfirmUserMarkedAway bool `json:"confirm_user_marked_away"` - ConvertEmoticons bool `json:"convert_emoticons"` - DisplayDisplayNames bool `json:"display_display_names"` - DisplayRealNamesOverride int `json:"display_real_names_override"` - DndEnabled bool `json:"dnd_enabled"` - DndEndHour string `json:"dnd_end_hour"` - DndStartHour string `json:"dnd_start_hour"` - DropboxEnabled bool `json:"dropbox_enabled"` - EmailAlerts string `json:"email_alerts"` - EmailAlertsSleepUntil int `json:"email_alerts_sleep_until"` - EmailMisc bool `json:"email_misc"` - EmailWeekly bool `json:"email_weekly"` - EmojiAutocompleteBig bool `json:"emoji_autocomplete_big"` - EmojiMode string `json:"emoji_mode"` - EmojiUse string `json:"emoji_use"` - EnableReactEmojiPicker bool `json:"enable_react_emoji_picker"` - EnableUnreadView bool `json:"enable_unread_view"` - EnhancedDebugging bool `json:"enhanced_debugging"` - EnterIsSpecialInTbt bool `json:"enter_is_special_in_tbt"` - EnterpriseMdmCustomMsg string `json:"enterprise_mdm_custom_msg"` - EnterpriseMigrationSeen bool `json:"enterprise_migration_seen"` - ExpandInlineImgs bool `json:"expand_inline_imgs"` - ExpandInternalInlineImgs bool `json:"expand_internal_inline_imgs"` - ExpandNonMediaAttachments bool `json:"expand_non_media_attachments"` - ExpandSnippets bool `json:"expand_snippets"` - FKeySearch bool `json:"f_key_search"` - FlannelServerPool string `json:"flannel_server_pool"` - FrecencyEntJumper string `json:"frecency_ent_jumper"` - FrecencyJumper string `json:"frecency_jumper"` - FullTextExtracts bool `json:"full_text_extracts"` - FullerTimestamps bool `json:"fuller_timestamps"` - GdriveAuthed bool `json:"gdrive_authed"` - GdriveEnabled bool `json:"gdrive_enabled"` - GraphicEmoticons bool `json:"graphic_emoticons"` - GrowlsEnabled bool `json:"growls_enabled"` - GrowthMsgLimitApproachingCtaCount int `json:"growth_msg_limit_approaching_cta_count"` - GrowthMsgLimitApproachingCtaTs int `json:"growth_msg_limit_approaching_cta_ts"` - GrowthMsgLimitLongReachedCtaCount int `json:"growth_msg_limit_long_reached_cta_count"` - GrowthMsgLimitLongReachedCtaLastTs int `json:"growth_msg_limit_long_reached_cta_last_ts"` - GrowthMsgLimitReachedCtaCount int `json:"growth_msg_limit_reached_cta_count"` - GrowthMsgLimitReachedCtaLastTs int `json:"growth_msg_limit_reached_cta_last_ts"` - HasCreatedChannel bool `json:"has_created_channel"` - HasInvited bool `json:"has_invited"` - HasSearched bool `json:"has_searched"` - HasUploaded bool `json:"has_uploaded"` - HideHexSwatch bool `json:"hide_hex_swatch"` - HideUserGroupInfoPane bool `json:"hide_user_group_info_pane"` - HighlightWords string `json:"highlight_words"` - IntroToAppsMessageSeen bool `json:"intro_to_apps_message_seen"` - Jumbomoji bool `json:"jumbomoji"` - KKeyOmnibox bool `json:"k_key_omnibox"` - KKeyOmniboxAutoHideCount int `json:"k_key_omnibox_auto_hide_count"` - LastSeenAtChannelWarning int `json:"last_seen_at_channel_warning"` - LastSnippetType string `json:"last_snippet_type"` - LastTosAcknowledged interface{} `json:"last_tos_acknowledged"` - LoadLato2 bool `json:"load_lato_2"` - Locale string `json:"locale"` - LoudChannels string `json:"loud_channels"` - LoudChannelsSet string `json:"loud_channels_set"` - LsDisabled bool `json:"ls_disabled"` - MacSsbBounce string `json:"mac_ssb_bounce"` - MacSsbBullet bool `json:"mac_ssb_bullet"` - MarkMsgsReadImmediately bool `json:"mark_msgs_read_immediately"` - MeasureCSSUsage bool `json:"measure_css_usage"` - MentionsExcludeAtChannels bool `json:"mentions_exclude_at_channels"` - MentionsExcludeAtUserGroups bool `json:"mentions_exclude_at_user_groups"` - MessagesTheme string `json:"messages_theme"` - MsgPreview bool `json:"msg_preview"` - MsgPreviewPersistent bool `json:"msg_preview_persistent"` - MuteSounds bool `json:"mute_sounds"` - MutedChannels string `json:"muted_channels"` - NeverChannels string `json:"never_channels"` - NewMsgSnd string `json:"new_msg_snd"` - NewxpSeenLastMessage int `json:"newxp_seen_last_message"` - NoCreatedOverlays bool `json:"no_created_overlays"` - NoInvitesWidgetInSidebar bool `json:"no_invites_widget_in_sidebar"` - NoJoinedOverlays bool `json:"no_joined_overlays"` - NoMacelectronBanner bool `json:"no_macelectron_banner"` - NoMacssb1Banner bool `json:"no_macssb1_banner"` - NoMacssb2Banner bool `json:"no_macssb2_banner"` - NoOmniboxInChannels bool `json:"no_omnibox_in_channels"` - NoTextInNotifications bool `json:"no_text_in_notifications"` - NoWinssb1Banner bool `json:"no_winssb1_banner"` - ObeyInlineImgLimit bool `json:"obey_inline_img_limit"` - OnboardingCancelled bool `json:"onboarding_cancelled"` - OnboardingSlackbotConversationStep int `json:"onboarding_slackbot_conversation_step"` - OverloadedMessageEnabled bool `json:"overloaded_message_enabled"` - PagekeysHandled bool `json:"pagekeys_handled"` - PostsFormattingGuide bool `json:"posts_formatting_guide"` - PreferredSkinTone string `json:"preferred_skin_tone"` - PrevNextBtn bool `json:"prev_next_btn"` - PrivacyPolicySeen bool `json:"privacy_policy_seen"` - PromptedForEmailDisabling bool `json:"prompted_for_email_disabling"` - PushAtChannelSuppressedChannels string `json:"push_at_channel_suppressed_channels"` - PushDmAlert bool `json:"push_dm_alert"` - PushEverything bool `json:"push_everything"` - PushIdleWait int `json:"push_idle_wait"` - PushLoudChannels string `json:"push_loud_channels"` - PushLoudChannelsSet string `json:"push_loud_channels_set"` - PushMentionAlert bool `json:"push_mention_alert"` - PushMentionChannels string `json:"push_mention_channels"` - PushShowPreview bool `json:"push_show_preview"` - PushSound string `json:"push_sound"` - QuestsEnabled bool `json:"quests_enabled"` - RequireAt bool `json:"require_at"` - SearchExcludeBots bool `json:"search_exclude_bots"` - SearchExcludeChannels string `json:"search_exclude_channels"` - SearchOnlyCurrentTeam bool `json:"search_only_current_team"` - SearchOnlyMyChannels bool `json:"search_only_my_channels"` - SearchSort string `json:"search_sort"` - SeenAppSpaceCoachmark bool `json:"seen_app_space_coachmark"` - SeenAppSpaceTutorial bool `json:"seen_app_space_tutorial"` - SeenCallsSsMainCoachmark bool `json:"seen_calls_ss_main_coachmark"` - SeenCallsSsWindowCoachmark bool `json:"seen_calls_ss_window_coachmark"` - SeenCallsVideoBetaCoachmark bool `json:"seen_calls_video_beta_coachmark"` - SeenCallsVideoGaCoachmark bool `json:"seen_calls_video_ga_coachmark"` - SeenCustomStatusBadge bool `json:"seen_custom_status_badge"` - SeenCustomStatusCallout bool `json:"seen_custom_status_callout"` - SeenDomainInviteReminder bool `json:"seen_domain_invite_reminder"` - SeenGdriveCoachmark bool `json:"seen_gdrive_coachmark"` - SeenGuestAdminSlackbotAnnouncement bool `json:"seen_guest_admin_slackbot_announcement"` - SeenHighlightsArrowsCoachmark bool `json:"seen_highlights_arrows_coachmark"` - SeenHighlightsCoachmark bool `json:"seen_highlights_coachmark"` - SeenHighlightsWarmWelcome bool `json:"seen_highlights_warm_welcome"` - SeenIntlChannelNamesCoachmark bool `json:"seen_intl_channel_names_coachmark"` - SeenMemberInviteReminder bool `json:"seen_member_invite_reminder"` - SeenOnboardingChannels bool `json:"seen_onboarding_channels"` - SeenOnboardingDirectMessages bool `json:"seen_onboarding_direct_messages"` - SeenOnboardingInvites bool `json:"seen_onboarding_invites"` - SeenOnboardingPrivateGroups bool `json:"seen_onboarding_private_groups"` - SeenOnboardingRecentMentions bool `json:"seen_onboarding_recent_mentions"` - SeenOnboardingSearch bool `json:"seen_onboarding_search"` - SeenOnboardingSlackbotConversation bool `json:"seen_onboarding_slackbot_conversation"` - SeenOnboardingStarredItems bool `json:"seen_onboarding_starred_items"` - SeenOnboardingStart bool `json:"seen_onboarding_start"` - SeenRepliesCoachmark bool `json:"seen_replies_coachmark"` - SeenSingleEmojiMsg bool `json:"seen_single_emoji_msg"` - SeenSsbPrompt bool `json:"seen_ssb_prompt"` - SeenThreadsNotificationBanner bool `json:"seen_threads_notification_banner"` - SeenUnreadViewCoachmark bool `json:"seen_unread_view_coachmark"` - SeenWelcome2 bool `json:"seen_welcome_2"` - SeparatePrivateChannels bool `json:"separate_private_channels"` - SeparateSharedChannels bool `json:"separate_shared_channels"` - ShowAllSkinTones bool `json:"show_all_skin_tones"` - ShowJumperScores bool `json:"show_jumper_scores"` - ShowMemoryInstrument bool `json:"show_memory_instrument"` - ShowTyping bool `json:"show_typing"` - SidebarBehavior string `json:"sidebar_behavior"` - SidebarTheme string `json:"sidebar_theme"` - SidebarThemeCustomValues string `json:"sidebar_theme_custom_values"` - SnippetEditorWrapLongLines bool `json:"snippet_editor_wrap_long_lines"` - SpacesNewXpBannerDismissed bool `json:"spaces_new_xp_banner_dismissed"` - SsEmojis bool `json:"ss_emojis"` - SsbSpaceWindow string `json:"ssb_space_window"` - StartScrollAtOldest bool `json:"start_scroll_at_oldest"` - TabUIReturnSelects bool `json:"tab_ui_return_selects"` - ThreadsEverything bool `json:"threads_everything"` - Time24 bool `json:"time24"` - TwoFactorAuthEnabled bool `json:"two_factor_auth_enabled"` - TwoFactorBackupType interface{} `json:"two_factor_backup_type"` - TwoFactorType interface{} `json:"two_factor_type"` - Tz interface{} `json:"tz"` - UseReactSidebar bool `json:"use_react_sidebar"` - UserColors string `json:"user_colors"` - WebappSpellcheck bool `json:"webapp_spellcheck"` - WelcomeMessageHidden bool `json:"welcome_message_hidden"` - WhatsNewRead int `json:"whats_new_read"` - WinssbRunFromTray bool `json:"winssb_run_from_tray"` - WinssbWindowFlashBehavior string `json:"winssb_window_flash_behavior"` - } `json:"prefs"` + Ok bool `json:"ok"` + Error string `json:"error"` + Self struct { + ID string `json:"id"` + Name string `json:"name"` } `json:"self"` - Subteams struct { - All []interface{} `json:"all"` - Self []interface{} `json:"self"` - } `json:"subteams"` Team struct { - ApproachingMsgLimit bool `json:"approaching_msg_limit"` - AvatarBaseURL string `json:"avatar_base_url"` - Domain string `json:"domain"` - EmailDomain string `json:"email_domain"` - Icon struct { - Image102 string `json:"image_102"` - Image132 string `json:"image_132"` - Image230 string `json:"image_230"` - Image34 string `json:"image_34"` - Image44 string `json:"image_44"` - Image68 string `json:"image_68"` - Image88 string `json:"image_88"` - ImageOriginal string `json:"image_original"` - } `json:"icon"` - ID string `json:"id"` - MessagesCount int `json:"messages_count"` - MsgEditWindowMins int `json:"msg_edit_window_mins"` - Name string `json:"name"` - OverIntegrationsLimit bool `json:"over_integrations_limit"` - OverStorageLimit bool `json:"over_storage_limit"` - Plan string `json:"plan"` - Prefs struct { - AllowCalls bool `json:"allow_calls"` - AllowMessageDeletion bool `json:"allow_message_deletion"` - AllowRetentionOverride bool `json:"allow_retention_override"` - AllowSharedChannelPermsOverride bool `json:"allow_shared_channel_perms_override"` - AuthMode string `json:"auth_mode"` - CallingAppName string `json:"calling_app_name"` - ChannelHandyRxns interface{} `json:"channel_handy_rxns"` - ComplianceExportStart int `json:"compliance_export_start"` - CustomStatusDefaultEmoji string `json:"custom_status_default_emoji"` - CustomStatusPresets [][]string `json:"custom_status_presets"` - DefaultChannels []string `json:"default_channels"` - DefaultRxns []string `json:"default_rxns"` - DisableFileDeleting bool `json:"disable_file_deleting"` - DisableFileEditing bool `json:"disable_file_editing"` - DisableFileUploads string `json:"disable_file_uploads"` - DisallowPublicFileUrls bool `json:"disallow_public_file_urls"` - Discoverable string `json:"discoverable"` - DisplayEmailAddresses bool `json:"display_email_addresses"` - DisplayRealNames bool `json:"display_real_names"` - DmRetentionDuration int `json:"dm_retention_duration"` - DmRetentionType int `json:"dm_retention_type"` - DndEnabled bool `json:"dnd_enabled"` - DndEndHour string `json:"dnd_end_hour"` - DndStartHour string `json:"dnd_start_hour"` - EnterpriseDefaultChannels []interface{} `json:"enterprise_default_channels"` - EnterpriseMandatoryChannels []interface{} `json:"enterprise_mandatory_channels"` - EnterpriseMdmDateEnabled int `json:"enterprise_mdm_date_enabled"` - EnterpriseMdmLevel int `json:"enterprise_mdm_level"` - EnterpriseTeamCreationRequest struct { - IsEnabled bool `json:"is_enabled"` - } `json:"enterprise_team_creation_request"` - FileRetentionDuration int `json:"file_retention_duration"` - FileRetentionType int `json:"file_retention_type"` - GdriveEnabledTeam bool `json:"gdrive_enabled_team"` - GroupRetentionDuration int `json:"group_retention_duration"` - GroupRetentionType int `json:"group_retention_type"` - HideReferers bool `json:"hide_referers"` - InvitesLimit bool `json:"invites_limit"` - InvitesOnlyAdmins bool `json:"invites_only_admins"` - LimitReachedTs int `json:"limit_reached_ts"` - Locale string `json:"locale"` - LoudChannelMentionsLimit int `json:"loud_channel_mentions_limit"` - MsgEditWindowMins int `json:"msg_edit_window_mins"` - RequireAtForMention bool `json:"require_at_for_mention"` - RetentionDuration int `json:"retention_duration"` - RetentionType int `json:"retention_type"` - ShowJoinLeave bool `json:"show_join_leave"` - TeamHandyRxns struct { - List []struct { - Name string `json:"name"` - Title string `json:"title"` - } `json:"list"` - Restrict bool `json:"restrict"` - } `json:"team_handy_rxns"` - UsesCustomizedCustomStatusPresets bool `json:"uses_customized_custom_status_presets"` - WarnBeforeAtChannel string `json:"warn_before_at_channel"` - WhoCanArchiveChannels string `json:"who_can_archive_channels"` - WhoCanAtChannel string `json:"who_can_at_channel"` - WhoCanAtEveryone string `json:"who_can_at_everyone"` - WhoCanChangeTeamProfile string `json:"who_can_change_team_profile"` - WhoCanCreateChannels string `json:"who_can_create_channels"` - WhoCanCreateDeleteUserGroups string `json:"who_can_create_delete_user_groups"` - WhoCanCreateGroups string `json:"who_can_create_groups"` - WhoCanCreateSharedChannels string `json:"who_can_create_shared_channels"` - WhoCanEditUserGroups string `json:"who_can_edit_user_groups"` - WhoCanKickChannels string `json:"who_can_kick_channels"` - WhoCanKickGroups string `json:"who_can_kick_groups"` - WhoCanManageGuests struct { - Type []string `json:"type"` - } `json:"who_can_manage_guests"` - WhoCanManageIntegrations struct { - Type []string `json:"type"` - } `json:"who_can_manage_integrations"` - WhoCanManageSharedChannels struct { - Type []string `json:"type"` - } `json:"who_can_manage_shared_channels"` - WhoCanPostGeneral string `json:"who_can_post_general"` - WhoCanPostInSharedChannels struct { - Type []string `json:"type"` - } `json:"who_can_post_in_shared_channels"` - WhoHasTeamVisibility string `json:"who_has_team_visibility"` - } `json:"prefs"` + Domain string `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` } `json:"team"` URL string `json:"url"` Users []struct { - Deleted bool `json:"deleted"` - ID string `json:"id"` - IsBot bool `json:"is_bot"` - Name string `json:"name"` - Presence string `json:"presence"` - Profile struct { - AvatarHash string `json:"avatar_hash"` - Email string `json:"email"` - Fields interface{} `json:"fields"` - FirstName string `json:"first_name"` - Image192 string `json:"image_192"` - Image24 string `json:"image_24"` - Image32 string `json:"image_32"` - Image48 string `json:"image_48"` - Image512 string `json:"image_512"` - Image72 string `json:"image_72"` - LastName string `json:"last_name"` - RealName string `json:"real_name"` - RealNameNormalized string `json:"real_name_normalized"` - } `json:"profile"` - TeamID string `json:"team_id"` - Updated int `json:"updated"` + ID string `json:"id"` + Name string `json:"name"` + TeamID string `json:"team_id"` } `json:"users"` } diff --git a/config/config_test.go b/config/config_test.go index 7ae37ea8..c71f40c5 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -18,6 +18,7 @@ const ( // added or removed defaultEnabledExchanges = 28 testFakeExchangeName = "Stampbit" + testPair = "BTC-USD" ) func TestGetCurrencyConfig(t *testing.T) { @@ -668,7 +669,7 @@ func TestCheckPairConfigFormats(t *testing.T) { Index: "USD", }, Available: currency.Pairs{ - currency.NewPairDelimiter("BTC-USD", "-"), + currency.NewPairDelimiter(testPair, "-"), }, Enabled: currency.Pairs{ currency.NewPairDelimiter("BTC~USD", "~"), @@ -1327,7 +1328,7 @@ func TestCheckExchangeConfigValues(t *testing.T) { setupPairs := func(emptyAssets bool) { cfg.Exchanges[0].CurrencyPairs = nil p := currency.Pairs{ - currency.NewPairDelimiter("BTC-USD", "-"), + currency.NewPairDelimiter(testPair, "-"), } cfg.Exchanges[0].PairsLastUpdated = int64ptr(1234567) @@ -1381,12 +1382,12 @@ func TestCheckExchangeConfigValues(t *testing.T) { } pairs := cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, true) - if len(pairs) == 0 || pairs.Join() != "BTC-USD" { + if len(pairs) == 0 || pairs.Join() != testPair { t.Error("pairs not set properly") } pairs = cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, false) - if len(pairs) == 0 || pairs.Join() != "BTC-USD" { + if len(pairs) == 0 || pairs.Join() != testPair { t.Error("pairs not set properly") } diff --git a/docker-compose.yml b/docker-compose.yml index 3bbf0297..87c18d76 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,10 +7,12 @@ services: depends_on: - daemon ports: - - "9052:80" + - "9054:80" daemon: build: . ports: - - "9051:9051" - "9050:9050" + - "9051:9051" + - "9052:9052" + - "9053:9053" diff --git a/docs/EXCHANGE_API.md b/docs/EXCHANGE_API.md index 1448fc5c..2af91388 100644 --- a/docs/EXCHANGE_API.md +++ b/docs/EXCHANGE_API.md @@ -57,14 +57,14 @@ supplied meet the requirements to make an authenticated request. b.API.Credentials.Secret = "your_secret" b.API.Credentials.ClientID = "your_clientid" - order := &exchange.OrderSubmission{ + o := &order.Submit{ Pair: currency.NewPair(currency.BTC, currency.USD), - OrderSide: exchange.SellOrderSide, - OrderType: exchange.LimitOrderType, + OrderSide: order.Sell, + OrderType: order.Limit, Price: 1000000, Amount: 0.1, } - resp, err := b.SubmitOrder(order) + resp, err := b.SubmitOrder(o) if err != nil { // Handle error } diff --git a/engine/engine.go b/engine/engine.go index f69514d2..0b16523c 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -4,7 +4,9 @@ import ( "errors" "flag" "fmt" + "path/filepath" "runtime" + "strings" "sync" "time" @@ -42,13 +44,10 @@ type Engine struct { // Vars for engine var ( Bot *Engine -) -func init() { - if Bot == nil { - return - } -} + // Stores the set flags + flagSet = make(map[string]bool) +) // New starts a new engine func New() (*Engine, error) { @@ -94,6 +93,7 @@ func NewFromSettings(settings *Settings) (*Engine, error) { b.Settings.ConfigFile = filePath b.Settings.DataDir = settings.DataDir + b.Settings.CheckParamInteraction = settings.CheckParamInteraction err = utils.AdjustGoMaxProcs(settings.GoMaxProcs) if err != nil { @@ -106,6 +106,8 @@ func NewFromSettings(settings *Settings) (*Engine, error) { // ValidateSettings validates and sets all bot settings func ValidateSettings(b *Engine, s *Settings) { + flag.Visit(func(f *flag.Flag) { flagSet[f.Name] = true }) + b.Settings.Verbose = s.Verbose b.Settings.EnableDryRun = s.EnableDryRun b.Settings.EnableAllExchanges = s.EnableAllExchanges @@ -115,26 +117,25 @@ func ValidateSettings(b *Engine, s *Settings) { b.Settings.EnableDatabaseManager = s.EnableDatabaseManager b.Settings.EnableDispatcher = s.EnableDispatcher - // TO-DO: FIXME - if flag.Lookup("grpc") != nil { + if flagSet["grpc"] { b.Settings.EnableGRPC = s.EnableGRPC } else { b.Settings.EnableGRPC = b.Config.RemoteControl.GRPC.Enabled } - if flag.Lookup("grpcproxy") != nil { + if flagSet["grpcproxy"] { b.Settings.EnableGRPCProxy = s.EnableGRPCProxy } else { b.Settings.EnableGRPCProxy = b.Config.RemoteControl.GRPC.GRPCProxyEnabled } - if flag.Lookup("websocketrpc") != nil { + if flagSet["websocketrpc"] { b.Settings.EnableWebsocketRPC = s.EnableWebsocketRPC } else { b.Settings.EnableWebsocketRPC = b.Config.RemoteControl.WebsocketRPC.Enabled } - if flag.Lookup("deprecatedrpc") != nil { + if flagSet["deprecatedrpc"] { b.Settings.EnableDeprecatedRPC = s.EnableDeprecatedRPC } else { b.Settings.EnableDeprecatedRPC = b.Config.RemoteControl.DeprecatedRPC.Enabled @@ -155,9 +156,13 @@ func ValidateSettings(b *Engine, s *Settings) { b.Settings.EnableNTPClient = s.EnableNTPClient b.Settings.EnableOrderManager = s.EnableOrderManager b.Settings.EnableExchangeSyncManager = s.EnableExchangeSyncManager - b.Settings.EnableDepositAddressManager = s.EnableDepositAddressManager b.Settings.EnableTickerSyncing = s.EnableTickerSyncing b.Settings.EnableOrderbookSyncing = s.EnableOrderbookSyncing + b.Settings.EnableTradeSyncing = s.EnableTradeSyncing + b.Settings.SyncWorkers = s.SyncWorkers + b.Settings.SyncTimeout = s.SyncTimeout + b.Settings.SyncContinuously = s.SyncContinuously + b.Settings.EnableDepositAddressManager = s.EnableDepositAddressManager b.Settings.EnableExchangeAutoPairUpdates = s.EnableExchangeAutoPairUpdates b.Settings.EnableExchangeWebsocketSupport = s.EnableExchangeWebsocketSupport b.Settings.EnableExchangeRESTSupport = s.EnableExchangeRESTSupport @@ -231,14 +236,19 @@ func PrintSettings(s *Settings) { log.Debugf(log.Global, "\t Enable order manager: %v", s.EnableOrderManager) log.Debugf(log.Global, "\t Enable exchange sync manager: %v", s.EnableExchangeSyncManager) log.Debugf(log.Global, "\t Enable deposit address manager: %v\n", s.EnableDepositAddressManager) - log.Debugf(log.Global, "\t Enable ticker syncing: %v", s.EnableTickerSyncing) - log.Debugf(log.Global, "\t Enable orderbook syncing: %v", s.EnableOrderbookSyncing) log.Debugf(log.Global, "\t Enable websocket routine: %v\n", s.EnableWebsocketRoutine) log.Debugf(log.Global, "\t Enable NTP client: %v", s.EnableNTPClient) log.Debugf(log.Global, "\t Enable Database manager: %v", s.EnableDatabaseManager) log.Debugf(log.Global, "\t Enable dispatcher: %v", s.EnableDispatcher) log.Debugf(log.Global, "\t Dispatch package max worker amount: %d", s.DispatchMaxWorkerAmount) log.Debugf(log.Global, "\t Dispatch package jobs limit: %d", s.DispatchJobsLimit) + log.Debugf(log.Global, "- EXCHANGE SYNCER SETTINGS:\n") + log.Debugf(log.Global, "\t Exchange sync continuously: %v\n", s.SyncContinuously) + log.Debugf(log.Global, "\t Exchange sync workers: %v\n", s.SyncWorkers) + log.Debugf(log.Global, "\t Enable ticker syncing: %v\n", s.EnableTickerSyncing) + log.Debugf(log.Global, "\t Enable orderbook syncing: %v\n", s.EnableOrderbookSyncing) + log.Debugf(log.Global, "\t Enable trade syncing: %v\n", s.EnableTradeSyncing) + log.Debugf(log.Global, "\t Exchange sync timeout: %v\n", s.SyncTimeout) log.Debugf(log.Global, "- FOREX SETTINGS:") log.Debugf(log.Global, "\t Enable currency conveter: %v", s.EnableCurrencyConverter) log.Debugf(log.Global, "\t Enable currency layer: %v", s.EnableCurrencyLayer) @@ -297,6 +307,10 @@ func (e *Engine) Start() error { e.Uptime = time.Now() log.Debugf(log.Global, "Bot '%s' started.\n", e.Config.Name) log.Debugf(log.Global, "Using data dir: %s\n", e.Settings.DataDir) + if *e.Config.Logging.Enabled && strings.Contains(e.Config.Logging.Output, "file") { + log.Debugf(log.Global, "Using log file: %s\n", + filepath.Join(log.LogPath, e.Config.Logging.LoggerFileConfig.FileName)) + } log.Debugf(log.Global, "Using %d out of %d logical processors for runtime performance\n", runtime.GOMAXPROCS(-1), runtime.NumCPU()) @@ -387,8 +401,10 @@ func (e *Engine) Start() error { exchangeSyncCfg := CurrencyPairSyncerConfig{ SyncTicker: e.Settings.EnableTickerSyncing, SyncOrderbook: e.Settings.EnableOrderbookSyncing, - SyncContinuously: true, - NumWorkers: 15, + SyncTrades: e.Settings.EnableTradeSyncing, + SyncContinuously: e.Settings.SyncContinuously, + NumWorkers: e.Settings.SyncWorkers, + Verbose: e.Settings.Verbose, } e.ExchangeCurrencyPairManager, err = NewCurrencyPairSyncer(exchangeSyncCfg) diff --git a/engine/engine_types.go b/engine/engine_types.go index da1ba5ad..9f0bbc95 100644 --- a/engine/engine_types.go +++ b/engine/engine_types.go @@ -4,11 +4,12 @@ import "time" // Settings stores engine params type Settings struct { - ConfigFile string - DataDir string - MigrationDir string - LogFile string - GoMaxProcs int + ConfigFile string + DataDir string + MigrationDir string + LogFile string + GoMaxProcs int + CheckParamInteraction bool // Core Settings EnableDryRun bool @@ -23,8 +24,6 @@ type Settings struct { EnableCommsRelayer bool EnableExchangeSyncManager bool EnableDepositAddressManager bool - EnableTickerSyncing bool - EnableOrderbookSyncing bool EnableEventManager bool EnableOrderManager bool EnableConnectivityMonitor bool @@ -34,6 +33,14 @@ type Settings struct { EventManagerDelay time.Duration Verbose bool + // Exchange syncer settings + EnableTickerSyncing bool + EnableOrderbookSyncing bool + EnableTradeSyncing bool + SyncWorkers int + SyncContinuously bool + SyncTimeout time.Duration + // Forex settings EnableCurrencyConverter bool EnableCurrencyLayer bool diff --git a/engine/events.go b/engine/events.go index 15c8de54..bf87a4c3 100644 --- a/engine/events.go +++ b/engine/events.go @@ -310,7 +310,6 @@ func EventManger() { // IsValidExchange validates the exchange func IsValidExchange(exchangeName string) bool { - exchangeName = strings.ToLower(exchangeName) cfg := config.GetConfig() for x := range cfg.Exchanges { if strings.EqualFold(cfg.Exchanges[x].Name, exchangeName) && cfg.Exchanges[x].Enabled { diff --git a/engine/exchange.go b/engine/exchange.go index ce0b8f36..1cc5141a 100644 --- a/engine/exchange.go +++ b/engine/exchange.go @@ -46,6 +46,20 @@ var ( ErrExchangeFailedToLoad = errors.New("exchange failed to load") ) +func dryrunParamInteraction(param string) { + if !Bot.Settings.CheckParamInteraction { + return + } + + if !Bot.Settings.EnableDryRun && !flagSet["dryrun"] { + log.Warnf(log.Global, + "Command line argument '-%s' induces dry run mode."+ + " Set -dryrun=false if you wish to override this.", + param) + Bot.Settings.EnableDryRun = true + } +} + // CheckExchangeExists returns true whether or not an exchange has already // been loaded func CheckExchangeExists(exchName string) bool { @@ -204,6 +218,7 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { if Bot.Settings.EnableAllPairs { if exchCfg.CurrencyPairs != nil { + dryrunParamInteraction("enableallpairs") assets := exchCfg.CurrencyPairs.GetAssetTypes() for x := range assets { pairs := exchCfg.CurrencyPairs.GetPairs(assets[x], false) @@ -213,10 +228,12 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { } if Bot.Settings.EnableExchangeVerbose { + dryrunParamInteraction("exchangeverbose") exchCfg.Verbose = true } if Bot.Settings.EnableExchangeWebsocketSupport { + dryrunParamInteraction("exchangewebsocketsupport") if exchCfg.Features != nil { if exchCfg.Features.Supports.Websocket { exchCfg.Features.Enabled.Websocket = true @@ -225,6 +242,7 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { } if Bot.Settings.EnableExchangeAutoPairUpdates { + dryrunParamInteraction("exchangeautopairupdates") if exchCfg.Features != nil { if exchCfg.Features.Supports.RESTCapabilities.AutoPairUpdates { exchCfg.Features.Enabled.AutoPairUpdates = true @@ -233,6 +251,7 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { } if Bot.Settings.DisableExchangeAutoPairUpdates { + dryrunParamInteraction("exchangedisableautopairupdates") if exchCfg.Features != nil { if exchCfg.Features.Supports.RESTCapabilities.AutoPairUpdates { exchCfg.Features.Enabled.AutoPairUpdates = false @@ -241,21 +260,29 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { } if Bot.Settings.ExchangeHTTPUserAgent != "" { + dryrunParamInteraction("exchangehttpuseragent") exchCfg.HTTPUserAgent = Bot.Settings.ExchangeHTTPUserAgent } if Bot.Settings.ExchangeHTTPProxy != "" { + dryrunParamInteraction("exchangehttpproxy") exchCfg.ProxyAddress = Bot.Settings.ExchangeHTTPProxy } if Bot.Settings.ExchangeHTTPTimeout != exchange.DefaultHTTPTimeout { + dryrunParamInteraction("exchangehttptimeout") exchCfg.HTTPTimeout = Bot.Settings.ExchangeHTTPTimeout } if Bot.Settings.EnableExchangeHTTPDebugging { + dryrunParamInteraction("exchangehttpdebugging") exchCfg.HTTPDebugging = Bot.Settings.EnableExchangeHTTPDebugging } + if Bot.Settings.EnableAllExchanges { + dryrunParamInteraction("enableallexchanges") + } + exchCfg.Enabled = true err = exch.Setup(exchCfg) if err != nil { diff --git a/engine/exchange_test.go b/engine/exchange_test.go index b7713b82..18f08032 100644 --- a/engine/exchange_test.go +++ b/engine/exchange_test.go @@ -21,21 +21,21 @@ func SetupTest(t *testing.T) { testSetup = true } - if CheckExchangeExists("Bitfinex") { + if CheckExchangeExists(testExchange) { return } - err := LoadExchange("Bitfinex", false, nil) + err := LoadExchange(testExchange, false, nil) if err != nil { t.Errorf("SetupTest: Failed to load exchange: %s", err) } } func CleanupTest(t *testing.T) { - if !CheckExchangeExists("Bitfinex") { + if !CheckExchangeExists(testExchange) { return } - err := UnloadExchange("Bitfinex") + err := UnloadExchange(testExchange) if err != nil { t.Fatalf("CleanupTest: Failed to unload exchange: %s", err) @@ -45,7 +45,7 @@ func CleanupTest(t *testing.T) { func TestCheckExchangeExists(t *testing.T) { SetupTest(t) - if !CheckExchangeExists("Bitfinex") { + if !CheckExchangeExists(testExchange) { t.Errorf("TestGetExchangeExists: Unable to find exchange") } @@ -59,7 +59,7 @@ func TestCheckExchangeExists(t *testing.T) { func TestGetExchangeByName(t *testing.T) { SetupTest(t) - exch := GetExchangeByName("Bitfinex") + exch := GetExchangeByName(testExchange) if exch == nil { t.Errorf("TestGetExchangeByName: Failed to get exchange") } @@ -69,12 +69,12 @@ func TestGetExchangeByName(t *testing.T) { } exch.SetEnabled(false) - bfx := GetExchangeByName("Bitfinex") + bfx := GetExchangeByName(testExchange) if bfx.IsEnabled() { t.Errorf("TestGetExchangeByName: Unexpected result") } - if exch.GetName() != "Bitfinex" { + if exch.GetName() != testExchange { t.Errorf("TestGetExchangeByName: Unexpected result") } @@ -95,7 +95,7 @@ func TestReloadExchange(t *testing.T) { err) } - err = ReloadExchange("Bitfinex") + err = ReloadExchange(testExchange) if err != nil { t.Errorf("TestReloadExchange: Incorrect result: %s", err) @@ -119,7 +119,7 @@ func TestUnloadExchange(t *testing.T) { err) } - err = UnloadExchange("Bitfinex") + err = UnloadExchange(testExchange) if err != nil { t.Errorf("TestUnloadExchange: Failed to get exchange. %s", err) @@ -133,3 +133,66 @@ func TestUnloadExchange(t *testing.T) { CleanupTest(t) } + +func TestDryRunParamInteraction(t *testing.T) { + SetupTest(t) + + // Load bot as per normal, dry run and verbose for Bitfinex should be + // disabled + exchCfg, err := Bot.Config.GetExchangeConfig(testExchange) + if err != nil { + t.Error(err) + } + + if Bot.Settings.EnableDryRun || + exchCfg.Verbose { + t.Error("dryrun and verbose should have been disabled") + } + + // Simulate overiding default settings and ensure that enabling exchange + // verbose mode will be set on Bitfinex + if err = UnloadExchange(testExchange); err != nil { + t.Error(err) + } + + Bot.Settings.CheckParamInteraction = true + Bot.Settings.EnableExchangeVerbose = true + if err = LoadExchange(testExchange, false, nil); err != nil { + t.Error(err) + } + + exchCfg, err = Bot.Config.GetExchangeConfig(testExchange) + if err != nil { + t.Error(err) + } + + if !Bot.Settings.EnableDryRun || + !exchCfg.Verbose { + t.Error("dryrun and verbose should have been enabled") + } + + if err = UnloadExchange(testExchange); err != nil { + t.Error(err) + } + + // Now set dryrun mode to false (via flagset and the previously enabled + // setting), enable exchange verbose mode and verify that verbose mode + // will be set on Bitfinex + Bot.Settings.EnableDryRun = false + Bot.Settings.CheckParamInteraction = true + Bot.Settings.EnableExchangeVerbose = true + flagSet["dryrun"] = true + if err = LoadExchange(testExchange, false, nil); err != nil { + t.Error(err) + } + + exchCfg, err = Bot.Config.GetExchangeConfig(testExchange) + if err != nil { + t.Error(err) + } + + if Bot.Settings.EnableDryRun || + !exchCfg.Verbose { + t.Error("dryrun should be false and verbose should be true") + } +} diff --git a/engine/routines.go b/engine/routines.go index 16320491..87ded1e9 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -335,11 +335,18 @@ func WebsocketDataHandler(ws *wshandler.Websocket) { // TO-DO: printOrderbookSummary //nolint:gocritic if Bot.Settings.Verbose { - log.Infof(log.WebsocketMgr, "Websocket %s %s orderbook updated\n", ws.GetName(), result.Pair.String()) + log.Infof(log.WebsocketMgr, + "Websocket %s %s orderbook updated\n", + ws.GetName(), + FormatCurrency(result.Pair), + ) } default: if Bot.Settings.Verbose { - log.Warnf(log.WebsocketMgr, "Websocket Unknown type: %s\n", d) + log.Warnf(log.WebsocketMgr, + "Websocket %s Unknown type: %v\n", + ws.GetName(), + d) } } } diff --git a/engine/syncer.go b/engine/syncer.go index 47c88284..6807dd9c 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -17,8 +17,8 @@ const ( SyncItemOrderbook SyncItemTrade - defaultSyncerWorkers = 30 - defaultSyncerTimeout = time.Second * 15 + DefaultSyncerWorkers = 15 + DefaultSyncerTimeout = time.Second * 15 ) var ( @@ -33,7 +33,11 @@ func NewCurrencyPairSyncer(c CurrencyPairSyncerConfig) (*ExchangeCurrencyPairSyn } if c.NumWorkers <= 0 { - c.NumWorkers = defaultSyncerWorkers + c.NumWorkers = DefaultSyncerWorkers + } + + if c.SyncTimeout <= time.Duration(0) { + c.SyncTimeout = DefaultSyncerTimeout } s := ExchangeCurrencyPairSyncer{ @@ -42,6 +46,7 @@ func NewCurrencyPairSyncer(c CurrencyPairSyncerConfig) (*ExchangeCurrencyPairSyn SyncOrderbook: c.SyncOrderbook, SyncTrades: c.SyncTrades, SyncContinuously: c.SyncContinuously, + SyncTimeout: c.SyncTimeout, NumWorkers: c.NumWorkers, }, } @@ -50,9 +55,9 @@ func NewCurrencyPairSyncer(c CurrencyPairSyncerConfig) (*ExchangeCurrencyPairSyn log.Debugf(log.SyncMgr, "Exchange currency pair syncer config: continuous: %v ticker: %v"+ - " orderbook: %v trades: %v workers: %v verbose: %v\n", + " orderbook: %v trades: %v workers: %v verbose: %v timeout: %v\n", s.Cfg.SyncContinuously, s.Cfg.SyncTicker, s.Cfg.SyncOrderbook, - s.Cfg.SyncTrades, s.Cfg.NumWorkers, s.Cfg.Verbose) + s.Cfg.SyncTrades, s.Cfg.NumWorkers, s.Cfg.Verbose, s.Cfg.SyncTimeout) return &s, nil } @@ -354,9 +359,9 @@ func (e *ExchangeCurrencyPairSyncer) worker() { } if e.Cfg.SyncTicker { if !e.isProcessing(exchangeName, c.Pair, c.AssetType, SyncItemTicker) { - if c.Ticker.LastUpdated.IsZero() || time.Since(c.Ticker.LastUpdated) > defaultSyncerTimeout { + if c.Ticker.LastUpdated.IsZero() || time.Since(c.Ticker.LastUpdated) > e.Cfg.SyncTimeout { if c.Ticker.IsUsingWebsocket { - if time.Since(c.Created) < defaultSyncerTimeout { + if time.Since(c.Created) < e.Cfg.SyncTimeout { continue } @@ -385,7 +390,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { } e.mux.Unlock() - if batchLastDone.IsZero() || time.Since(batchLastDone) > defaultSyncerTimeout { + if batchLastDone.IsZero() || time.Since(batchLastDone) > e.Cfg.SyncTimeout { e.mux.Lock() if e.Cfg.Verbose { log.Debugf(log.SyncMgr, "%s Init'ing REST ticker batching\n", exchangeName) @@ -419,9 +424,9 @@ func (e *ExchangeCurrencyPairSyncer) worker() { if e.Cfg.SyncOrderbook { if !e.isProcessing(exchangeName, c.Pair, c.AssetType, SyncItemOrderbook) { - if c.Orderbook.LastUpdated.IsZero() || time.Since(c.Orderbook.LastUpdated) > defaultSyncerTimeout { + if c.Orderbook.LastUpdated.IsZero() || time.Since(c.Orderbook.LastUpdated) > e.Cfg.SyncTimeout { if c.Orderbook.IsUsingWebsocket { - if time.Since(c.Created) < defaultSyncerTimeout { + if time.Since(c.Created) < e.Cfg.SyncTimeout { continue } if supportsREST { @@ -452,7 +457,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { } if e.Cfg.SyncTrades { if !e.isProcessing(exchangeName, c.Pair, c.AssetType, SyncItemTrade) { - if c.Trade.LastUpdated.IsZero() || time.Since(c.Trade.LastUpdated) > defaultSyncerTimeout { + if c.Trade.LastUpdated.IsZero() || time.Since(c.Trade.LastUpdated) > e.Cfg.SyncTimeout { e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTrade, true) e.update(c.Exchange, c.Pair, c.AssetType, SyncItemTrade, nil) } @@ -570,7 +575,6 @@ func (e *ExchangeCurrencyPairSyncer) Start() { if !e.Cfg.SyncContinuously { log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer stopping.") e.Stop() - Bot.Stop() return } } diff --git a/engine/syncer_types.go b/engine/syncer_types.go index 54551a9e..4701b503 100644 --- a/engine/syncer_types.go +++ b/engine/syncer_types.go @@ -14,9 +14,9 @@ type CurrencyPairSyncerConfig struct { SyncOrderbook bool SyncTrades bool SyncContinuously bool - - NumWorkers int - Verbose bool + SyncTimeout time.Duration + NumWorkers int + Verbose bool } // ExchangeSyncerConfig stores the exchange syncer config diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index a28e4368..2eb89200 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -153,15 +153,17 @@ func (a *Alphapoint) UpdateOrderbook(p currency.Pair, assetType asset.Item) (ord } for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, - orderbook.Item{Amount: data.Quantity, Price: data.Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[x].Quantity, + Price: orderbookNew.Bids[x].Price, + }) } for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, - orderbook.Item{Amount: data.Quantity, Price: data.Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: orderbookNew.Asks[x].Quantity, + Price: orderbookNew.Asks[x].Price, + }) } orderBook.Pair = p @@ -188,9 +190,8 @@ func (a *Alphapoint) FetchOrderbook(p currency.Pair, assetType asset.Item) (orde // GetFundingHistory returns funding history, deposits and // withdrawals func (a *Alphapoint) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory // https://alphapoint.github.io/slate/#generatetreasuryactivityreport - return fundHistory, common.ErrNotYetImplemented + return nil, common.ErrNotYetImplemented } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index f2951332..e6b29f10 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -233,7 +233,7 @@ func TestSubmitOrder(t *testing.T) { Quote: currency.USD, }, OrderSide: order.Buy, - OrderType: order.Market, + OrderType: order.Limit, Price: 1, Amount: 1, ClientID: "meowOrder", diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 6b4b7e57..96b657ff 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -310,8 +310,7 @@ func (a *ANX) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (a *ANX) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index dc841f73..2dbbdf94 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -87,8 +87,7 @@ func (b *Binance) GetExchangeInfo() (ExchangeInfo, error) { // symbol: string of currency pair // limit: returned limit amount func (b *Binance) GetOrderBook(obd OrderBookDataRequestParams) (OrderBook, error) { - orderbook, resp := OrderBook{}, OrderBookData{} - + var orderbook OrderBook if err := b.CheckLimit(obd.Limit); err != nil { return orderbook, err } @@ -97,42 +96,44 @@ func (b *Binance) GetOrderBook(obd OrderBookDataRequestParams) (OrderBook, error params.Set("symbol", strings.ToUpper(obd.Symbol)) params.Set("limit", fmt.Sprintf("%d", obd.Limit)) - path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, orderBookDepth, params.Encode()) - + var resp OrderBookData + path := common.EncodeURLValues(b.API.Endpoints.URL+orderBookDepth, params) if err := b.SendHTTPRequest(path, &resp); err != nil { return orderbook, err } - for _, asks := range resp.Asks { - var ASK struct { - Price float64 - Quantity float64 + for x := range resp.Bids { + price, err := strconv.ParseFloat(resp.Bids[x][0], 64) + if err != nil { + return orderbook, err } - for i, ask := range asks.([]interface{}) { - switch i { - case 0: - ASK.Price, _ = strconv.ParseFloat(ask.(string), 64) - case 1: - ASK.Quantity, _ = strconv.ParseFloat(ask.(string), 64) - orderbook.Asks = append(orderbook.Asks, ASK) - } + + amount, err := strconv.ParseFloat(resp.Bids[x][1], 64) + if err != nil { + return orderbook, err } + + orderbook.Bids = append(orderbook.Bids, OrderbookItem{ + Price: price, + Quantity: amount, + }) } - for _, bids := range resp.Bids { - var BID struct { - Price float64 - Quantity float64 + for x := range resp.Asks { + price, err := strconv.ParseFloat(resp.Asks[x][0], 64) + if err != nil { + return orderbook, err } - for i, bid := range bids.([]interface{}) { - switch i { - case 0: - BID.Price, _ = strconv.ParseFloat(bid.(string), 64) - case 1: - BID.Quantity, _ = strconv.ParseFloat(bid.(string), 64) - orderbook.Bids = append(orderbook.Bids, BID) - } + + amount, err := strconv.ParseFloat(resp.Asks[x][1], 64) + if err != nil { + return orderbook, err } + + orderbook.Asks = append(orderbook.Asks, OrderbookItem{ + Price: price, + Quantity: amount, + }) } orderbook.LastUpdateID = resp.LastUpdateID diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 779894db..0f142019 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -359,7 +359,7 @@ func TestSubmitOrder(t *testing.T) { Quote: currency.BTC, }, OrderSide: order.Buy, - OrderType: order.Market, + OrderType: order.Limit, Price: 1, Amount: 1000000000, ClientID: "meowOrder", diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index e3bd8125..48a90381 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -59,13 +59,19 @@ type OrderBookDataRequestParams struct { Limit int `json:"limit"` // Default 100; max 1000. Valid limits:[5, 10, 20, 50, 100, 500, 1000] } +// OrderbookItem stores an individual orderbook item +type OrderbookItem struct { + Price float64 + Quantity float64 +} + // OrderBookData is resp data from orderbook endpoint type OrderBookData struct { - Code int `json:"code"` - Msg string `json:"msg"` - LastUpdateID int64 `json:"lastUpdateId"` - Bids []interface{} `json:"bids"` - Asks []interface{} `json:"asks"` + Code int `json:"code"` + Msg string `json:"msg"` + LastUpdateID int64 `json:"lastUpdateId"` + Bids [][]string `json:"bids"` + Asks [][]string `json:"asks"` } // OrderBook actual structured data that can be used for orderbook @@ -73,14 +79,8 @@ type OrderBook struct { LastUpdateID int64 Code int Msg string - Bids []struct { - Price float64 - Quantity float64 - } - Asks []struct { - Price float64 - Quantity float64 - } + Bids []OrderbookItem + Asks []OrderbookItem } // DepthUpdateParams is used as an embedded type for WebsocketDepthStream diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 829915fc..7d66a87b 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -383,8 +383,7 @@ func (b *Binance) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Binance) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 3be37672..be6ff69e 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" @@ -433,10 +434,10 @@ func (b *Bitfinex) WsDataHandler() { } if len(trades) > 0 { - side := "BUY" + side := order.Buy newAmount := trades[0].Amount if newAmount < 0 { - side = "SELL" + side = order.Sell newAmount *= -1 } @@ -447,7 +448,7 @@ func (b *Bitfinex) WsDataHandler() { Amount: newAmount, Exchange: b.Name, AssetType: asset.Spot, - Side: side, + Side: side.String(), } } } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index c5aa9cc2..f5337b75 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -349,8 +349,7 @@ func (b *Bitfinex) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Bitfinex) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index 05f5ad56..a4127426 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -15,6 +15,7 @@ const ( apiKey = "" apiSecret = "" canManipulateRealOrders = false + testCurrency = "btc" ) var b Bithumb @@ -54,7 +55,7 @@ func TestGetTradablePairs(t *testing.T) { func TestGetTicker(t *testing.T) { t.Parallel() - _, err := b.GetTicker("btc") + _, err := b.GetTicker(testCurrency) if err != nil { t.Error("Bithumb GetTicker() error", err) } @@ -70,7 +71,7 @@ func TestGetAllTickers(t *testing.T) { func TestGetOrderBook(t *testing.T) { t.Parallel() - _, err := b.GetOrderBook("btc") + _, err := b.GetOrderBook(testCurrency) if err != nil { t.Error("Bithumb GetOrderBook() error", err) } @@ -78,7 +79,7 @@ func TestGetOrderBook(t *testing.T) { func TestGetTransactionHistory(t *testing.T) { t.Parallel() - _, err := b.GetTransactionHistory("btc") + _, err := b.GetTransactionHistory(testCurrency) if err != nil { t.Error("Bithumb GetTransactionHistory() error", err) } @@ -90,7 +91,7 @@ func TestGetAccountBalance(t *testing.T) { t.Skip() } - _, err := b.GetAccountBalance("BTC") + _, err := b.GetAccountBalance(testCurrency) if err == nil { t.Error("Bithumb GetAccountBalance() Expected error") } @@ -118,7 +119,7 @@ func TestGetLastTransaction(t *testing.T) { func TestGetOrders(t *testing.T) { t.Parallel() - _, err := b.GetOrders("1337", "bid", "100", "", "BTC") + _, err := b.GetOrders("1337", order.Bid.Lower(), "100", "", testCurrency) if err == nil { t.Error("Bithumb GetOrders() Expected error") } @@ -134,7 +135,7 @@ func TestGetUserTransactions(t *testing.T) { func TestPlaceTrade(t *testing.T) { t.Parallel() - _, err := b.PlaceTrade("btc", "bid", 0, 0) + _, err := b.PlaceTrade(testCurrency, order.Bid.Lower(), 0, 0) if err == nil { t.Error("Bithumb PlaceTrade() Expected error") } @@ -142,7 +143,7 @@ func TestPlaceTrade(t *testing.T) { func TestGetOrderDetails(t *testing.T) { t.Parallel() - _, err := b.GetOrderDetails("1337", "bid", "btc") + _, err := b.GetOrderDetails("1337", order.Bid.Lower(), testCurrency) if err == nil { t.Error("Bithumb GetOrderDetails() Expected error") } @@ -185,7 +186,7 @@ func TestRequestKRWWithdraw(t *testing.T) { func TestMarketBuyOrder(t *testing.T) { t.Parallel() - _, err := b.MarketBuyOrder("btc", 0) + _, err := b.MarketBuyOrder(testCurrency, 0) if err == nil { t.Error("Bithumb MarketBuyOrder() Expected error") } @@ -193,7 +194,7 @@ func TestMarketBuyOrder(t *testing.T) { func TestMarketSellOrder(t *testing.T) { t.Parallel() - _, err := b.MarketSellOrder("btc", 0) + _, err := b.MarketSellOrder(testCurrency, 0) if err == nil { t.Error("Bithumb MarketSellOrder() Expected error") } diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 2e9b12c1..c4bf45a8 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -291,8 +291,7 @@ func (b *Bithumb) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Bithumb) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 5b33a6e1..a3131648 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -347,29 +347,21 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai switch action { case bitmexActionInitialData: var newOrderBook orderbook.Base - var bids, asks []orderbook.Item for i := range data { if strings.EqualFold(data[i].Side, order.Sell.String()) { - asks = append(asks, orderbook.Item{ + newOrderBook.Asks = append(newOrderBook.Asks, orderbook.Item{ Price: data[i].Price, Amount: float64(data[i].Size), ID: data[i].ID, }) continue } - bids = append(bids, orderbook.Item{ + newOrderBook.Bids = append(newOrderBook.Bids, orderbook.Item{ Price: data[i].Price, Amount: float64(data[i].Size), ID: data[i].ID, }) } - - if len(bids) == 0 || len(asks) == 0 { - return errors.New("bitmex_websocket.go error - snapshot not initialised correctly") - } - - newOrderBook.Asks = asks - newOrderBook.Bids = bids newOrderBook.AssetType = assetType newOrderBook.Pair = currencyPair newOrderBook.ExchangeName = b.Name diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 64ecc018..202944d6 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -7,9 +7,9 @@ import ( "fmt" "net/http" "net/url" - "reflect" "strconv" "strings" + "time" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" @@ -40,6 +40,7 @@ const ( bitstampAPIBitcoinWithdrawal = "bitcoin_withdrawal" bitstampAPILTCWithdrawal = "ltc_withdrawal" bitstampAPIETHWithdrawal = "eth_withdrawal" + bitstampAPIBCHWithdrawal = "bch_withdrawal" bitstampAPIBitcoinDeposit = "bitcoin_deposit_address" bitstampAPILitecoinDeposit = "ltc_address" bitstampAPIEthereumDeposit = "eth_address" @@ -54,6 +55,7 @@ const ( bitstampAuthRate = 8000 bitstampUnauthRate = 8000 + bitstampTimeLayout = "2006-1-2 15:04:05" ) // Bitstamp is the overarching type across the bitstamp package @@ -121,22 +123,17 @@ func getInternationalBankDepositFee(amount float64) float64 { } // CalculateTradingFee returns fee on a currency pair -func (b *Bitstamp) CalculateTradingFee(base, quote currency.Code, purchasePrice, amount float64, balances *Balances) float64 { +func (b *Bitstamp) CalculateTradingFee(base, quote currency.Code, purchasePrice, amount float64, balances Balances) float64 { var fee float64 - - switch base.String() + quote.String() { - case currency.BTC.String() + currency.USD.String(): - fee = balances.BTCUSDFee - case currency.BTC.String() + currency.EUR.String(): - fee = balances.BTCEURFee - case currency.XRP.String() + currency.EUR.String(): - fee = balances.XRPEURFee - case currency.XRP.String() + currency.USD.String(): - fee = balances.XRPUSDFee - case currency.EUR.String() + currency.USD.String(): - fee = balances.EURUSDFee - default: - fee = 0 + if v, ok := balances[base.String()]; ok { + switch quote { + case currency.BTC: + fee = v.BTCFee + case currency.USD: + fee = v.USDFee + case currency.EUR: + fee = v.EURFee + } } return fee * purchasePrice * amount } @@ -259,25 +256,62 @@ func (b *Bitstamp) GetEURUSDConversionRate() (EURUSDConversionRate, error) { } // GetBalance returns full balance of currency held on the exchange -func (b *Bitstamp) GetBalance() (*Balances, error) { - var balance Balances - return &balance, - b.SendAuthenticatedHTTPRequest(bitstampAPIBalance, true, nil, &balance) +func (b *Bitstamp) GetBalance() (Balances, error) { + var balance map[string]string + err := b.SendAuthenticatedHTTPRequest(bitstampAPIBalance, true, nil, &balance) + if err != nil { + return nil, err + } + + balances := make(map[string]Balance) + for k := range balance { + curr := k[0:3] + _, ok := balances[strings.ToUpper(curr)] + if !ok { + avail, _ := strconv.ParseFloat(balance[curr+"_available"], 64) + bal, _ := strconv.ParseFloat(balance[curr+"_balance"], 64) + reserved, _ := strconv.ParseFloat(balance[curr+"_reserved"], 64) + withdrawalFee, _ := strconv.ParseFloat(balance[curr+"_withdrawal_fee"], 64) + currBalance := Balance{ + Available: avail, + Balance: bal, + Reserved: reserved, + WithdrawalFee: withdrawalFee, + } + switch strings.ToUpper(curr) { + case currency.USD.String(): + eurFee, _ := strconv.ParseFloat(balance[curr+"eur_fee"], 64) + currBalance.EURFee = eurFee + case currency.EUR.String(): + usdFee, _ := strconv.ParseFloat(balance[curr+"usd_fee"], 64) + currBalance.USDFee = usdFee + default: + btcFee, _ := strconv.ParseFloat(balance[curr+"btc_fee"], 64) + currBalance.BTCFee = btcFee + eurFee, _ := strconv.ParseFloat(balance[curr+"eur_fee"], 64) + currBalance.EURFee = eurFee + usdFee, _ := strconv.ParseFloat(balance[curr+"usd_fee"], 64) + currBalance.USDFee = usdFee + } + balances[strings.ToUpper(curr)] = currBalance + } + } + return balances, nil } // GetUserTransactions returns an array of transactions func (b *Bitstamp) GetUserTransactions(currencyPair string) ([]UserTransactions, error) { type Response struct { - Date string `json:"datetime"` - TransID int64 `json:"id"` - Type int `json:"type,string"` - USD interface{} `json:"usd"` - EUR float64 `json:"eur"` - XRP float64 `json:"xrp"` - BTC interface{} `json:"btc"` - BTCUSD interface{} `json:"btc_usd"` - Fee float64 `json:"fee,string"` - OrderID int64 `json:"order_id"` + Date string `json:"datetime"` + TransactionID int64 `json:"id"` + Type int `json:"type,string"` + USD interface{} `json:"usd"` + EUR interface{} `json:"eur"` + XRP interface{} `json:"xrp"` + BTC interface{} `json:"btc"` + BTCUSD interface{} `json:"btc_usd"` + Fee float64 `json:"fee,string"` + OrderID int64 `json:"order_id"` } var response []Response @@ -297,41 +331,31 @@ func (b *Bitstamp) GetUserTransactions(currencyPair string) ([]UserTransactions, } } + processNumber := func(i interface{}) float64 { + switch t := i.(type) { + case float64: + return t + case string: + amt, _ := strconv.ParseFloat(t, 64) + return amt + default: + return 0 + } + } + var transactions []UserTransactions - - for _, y := range response { + for x := range response { tx := UserTransactions{} - tx.Date = y.Date - tx.TransID = y.TransID - tx.Type = y.Type - - /* Hack due to inconsistent JSON values... */ - varType := reflect.TypeOf(y.USD).String() - if varType == bitstampAPIReturnType { - tx.USD, _ = strconv.ParseFloat(y.USD.(string), 64) - } else { - tx.USD = y.USD.(float64) - } - - tx.EUR = y.EUR - tx.XRP = y.XRP - - varType = reflect.TypeOf(y.BTC).String() - if varType == bitstampAPIReturnType { - tx.BTC, _ = strconv.ParseFloat(y.BTC.(string), 64) - } else { - tx.BTC = y.BTC.(float64) - } - - varType = reflect.TypeOf(y.BTCUSD).String() - if varType == bitstampAPIReturnType { - tx.BTCUSD, _ = strconv.ParseFloat(y.BTCUSD.(string), 64) - } else { - tx.BTCUSD = y.BTCUSD.(float64) - } - - tx.Fee = y.Fee - tx.OrderID = y.OrderID + tx.Date = response[x].Date + tx.TransactionID = response[x].TransactionID + tx.Type = response[x].Type + tx.EUR = processNumber(response[x].EUR) + tx.XRP = processNumber(response[x].XRP) + tx.USD = processNumber(response[x].USD) + tx.BTC = processNumber(response[x].BTC) + tx.BTCUSD = processNumber(response[x].BTCUSD) + tx.Fee = response[x].Fee + tx.OrderID = response[x].OrderID transactions = append(transactions, tx) } @@ -359,13 +383,17 @@ func (b *Bitstamp) GetOrderStatus(orderID int64) (OrderStatus, error) { } // CancelExistingOrder cancels order by ID -func (b *Bitstamp) CancelExistingOrder(orderID int64) (bool, error) { - result := false +func (b *Bitstamp) CancelExistingOrder(orderID int64) (CancelOrder, error) { var req = url.Values{} req.Add("id", strconv.FormatInt(orderID, 10)) - return result, - b.SendAuthenticatedHTTPRequest(bitstampAPICancelOrder, true, req, &result) + var result CancelOrder + err := b.SendAuthenticatedHTTPRequest(bitstampAPICancelOrder, true, req, &result) + if err != nil { + return result, err + } + + return result, nil } // CancelAllExistingOrders cancels all open orders on the exchange @@ -433,22 +461,24 @@ func (b *Bitstamp) CryptoWithdrawal(amount float64, address, symbol, destTag str var endpoint string switch strings.ToLower(symbol) { - case "btc": + case currency.BTC.Lower().String(): if instant { req.Add("instant", "1") } else { req.Add("instant", "0") } endpoint = bitstampAPIBitcoinWithdrawal - case "ltc": + case currency.LTC.Lower().String(): endpoint = bitstampAPILTCWithdrawal - case "eth": + case currency.ETH.Lower().String(): endpoint = bitstampAPIETHWithdrawal - case "xrp": + case currency.XRP.Lower().String(): if destTag != "" { req.Add("destination_tag", destTag) } endpoint = bitstampAPIXrpWithdrawal + case currency.BCH.Lower().String(): + endpoint = bitstampAPIBCHWithdrawal default: return resp, errors.New("incorrect symbol") } @@ -669,3 +699,7 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url return common.JSONDecode(interim, result) } + +func parseTime(dateTime string) (time.Time, error) { + return time.Parse(bitstampTimeLayout, dateTime) +} diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 8720d655..c541dd8a 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -143,9 +143,11 @@ func TestGetFee(t *testing.T) { func TestCalculateTradingFee(t *testing.T) { t.Parallel() - var newBalance = new(Balances) - newBalance.BTCUSDFee = 1 - newBalance.BTCEURFee = 0 + newBalance := make(Balances) + newBalance["BTC"] = Balance{ + USDFee: 1, + EURFee: 0, + } if resp := b.CalculateTradingFee(currency.BTC, currency.USD, 0, 0, newBalance); resp != 0 { t.Error("GetFee() error") @@ -401,15 +403,9 @@ func TestCancelExchangeOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - - var orderCancellation = &order.Cancel{ - OrderID: "1", - WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - AccountID: "1", - CurrencyPair: currencyPair, + orderCancellation := &order.Cancel{ + OrderID: "1234", } - err := b.CancelOrder(orderCancellation) switch { case !areTestAPIKeysSet() && err == nil && !mockTests: @@ -428,16 +424,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - - var orderCancellation = &order.Cancel{ - OrderID: "1", - WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - AccountID: "1", - CurrencyPair: currencyPair, - } - - resp, err := b.CancelAllOrders(orderCancellation) + resp, err := b.CancelAllOrders(&order.Cancel{}) switch { case !areTestAPIKeysSet() && err == nil && !mockTests: t.Error("Expecting an error when no keys are set") @@ -583,3 +570,21 @@ func TestGetDepositAddress(t *testing.T) { t.Error("GetDepositAddress error", err) } } + +func TestParseTime(t *testing.T) { + t.Parallel() + + tm, err := parseTime("2019-10-18 01:55:14") + if err != nil { + t.Error(err) + } + + if tm.Year() != 2019 || + tm.Month() != 10 || + tm.Day() != 18 || + tm.Hour() != 1 || + tm.Minute() != 55 || + tm.Second() != 14 { + t.Error("invalid time values") + } +} diff --git a/exchanges/bitstamp/bitstamp_types.go b/exchanges/bitstamp/bitstamp_types.go index e8d8a676..8483211b 100644 --- a/exchanges/bitstamp/bitstamp_types.go +++ b/exchanges/bitstamp/bitstamp_types.go @@ -1,5 +1,19 @@ package bitstamp +// Transaction types +const ( + Deposit = iota + Withdrawal + MarketTrade + SubAccountTransfer = 14 +) + +// Order side type +const ( + BuyOrder = iota + SellOrder +) + // Ticker holds ticker information type Ticker struct { Last float64 `json:"last,string"` @@ -52,55 +66,51 @@ type EURUSDConversionRate struct { Sell float64 `json:"sell,string"` } -// Balances holds full balance information with the supplied APIKEYS -type Balances struct { - USDBalance float64 `json:"usd_balance,string"` - BTCBalance float64 `json:"btc_balance,string"` - EURBalance float64 `json:"eur_balance,string"` - XRPBalance float64 `json:"xrp_balance,string"` - USDReserved float64 `json:"usd_reserved,string"` - BTCReserved float64 `json:"btc_reserved,string"` - EURReserved float64 `json:"eur_reserved,string"` - XRPReserved float64 `json:"xrp_reserved,string"` - USDAvailable float64 `json:"usd_available,string"` - BTCAvailable float64 `json:"btc_available,string"` - EURAvailable float64 `json:"eur_available,string"` - XRPAvailable float64 `json:"xrp_available,string"` - BTCUSDFee float64 `json:"btcusd_fee,string"` - BTCEURFee float64 `json:"btceur_fee,string"` - EURUSDFee float64 `json:"eurusd_fee,string"` - XRPUSDFee float64 `json:"xrpusd_fee,string"` - XRPEURFee float64 `json:"xrpeur_fee,string"` - XRPBTCFee float64 `json:"xrpbtc_fee,string"` - Fee float64 `json:"fee,string"` +// Balance stores the balance info +type Balance struct { + Available float64 + Balance float64 + Reserved float64 + WithdrawalFee float64 + BTCFee float64 // for cryptocurrency pairs + USDFee float64 + EURFee float64 } +// Balances holds full balance information with the supplied APIKEYS +type Balances map[string]Balance + // UserTransactions holds user transaction information type UserTransactions struct { - Date string `json:"datetime"` - TransID int64 `json:"id"` - Type int `json:"type,string"` - USD float64 `json:"usd"` - EUR float64 `json:"eur"` - BTC float64 `json:"btc"` - XRP float64 `json:"xrp"` - BTCUSD float64 `json:"btc_usd"` - Fee float64 `json:"fee,string"` - OrderID int64 `json:"order_id"` + Date string `json:"datetime"` + TransactionID int64 `json:"id"` + Type int `json:"type,string"` + USD float64 `json:"usd"` + EUR float64 `json:"eur"` + BTC float64 `json:"btc"` + XRP float64 `json:"xrp"` + BTCUSD float64 `json:"btc_usd"` + Fee float64 `json:"fee,string"` + OrderID int64 `json:"order_id"` } // Order holds current open order data type Order struct { - ID int64 `json:"id"` - Date int64 `json:"datetime"` - Type int `json:"type"` - Price float64 `json:"price"` - Amount float64 `json:"amount"` + ID int64 `json:"id,string"` + DateTime string `json:"datetime"` + Type int `json:"type,string"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` Currency string `json:"currency_pair"` } // OrderStatus holds order status information type OrderStatus struct { + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + Type int `json:"type"` + ID int64 `json:"id,string"` + DateTime string `json:"datetime"` Status string Transactions []struct { TradeID int64 `json:"tid"` @@ -111,6 +121,14 @@ type OrderStatus struct { } } +// CancelOrder holds the order cancellation info +type CancelOrder struct { + Price float64 `json:"price"` + Amount float64 `json:"amount"` + Type int `json:"type"` + ID int64 `json:"id"` +} + // WithdrawalRequests holds request information on withdrawals type WithdrawalRequests struct { OrderID int64 `json:"id"` diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 9cccbb62..3cee9712 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -225,24 +225,18 @@ func (b *Bitstamp) seedOrderBook() error { } var newOrderBook orderbook.Base - var asks, bids []orderbook.Item - for i := range orderbookSeed.Asks { - var item orderbook.Item - item.Amount = orderbookSeed.Asks[i].Amount - item.Price = orderbookSeed.Asks[i].Price - asks = append(asks, item) + newOrderBook.Asks = append(newOrderBook.Asks, orderbook.Item{ + Price: orderbookSeed.Asks[i].Price, + Amount: orderbookSeed.Asks[i].Amount, + }) } - for i := range orderbookSeed.Bids { - var item orderbook.Item - item.Amount = orderbookSeed.Bids[i].Amount - item.Price = orderbookSeed.Bids[i].Price - bids = append(bids, item) + newOrderBook.Bids = append(newOrderBook.Bids, orderbook.Item{ + Price: orderbookSeed.Bids[i].Price, + Amount: orderbookSeed.Bids[i].Amount, + }) } - - newOrderBook.Asks = asks - newOrderBook.Bids = bids newOrderBook.Pair = p[x] newOrderBook.AssetType = asset.Spot newOrderBook.ExchangeName = b.Name diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index bd647384..facc2950 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -289,13 +289,17 @@ func (b *Bitstamp) UpdateOrderbook(p currency.Pair, assetType asset.Item) (order } for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Amount, Price: data.Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[x].Amount, + Price: orderbookNew.Bids[x].Price, + }) } for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: orderbookNew.Asks[x].Amount, + Price: orderbookNew.Asks[x].Price, + }) } orderBook.Pair = p @@ -320,27 +324,13 @@ func (b *Bitstamp) GetAccountInfo() (exchange.AccountInfo, error) { return response, err } - var currencies = []exchange.AccountCurrencyInfo{ - { - CurrencyName: currency.BTC, - TotalValue: accountBalance.BTCAvailable, - Hold: accountBalance.BTCReserved, - }, - { - CurrencyName: currency.XRP, - TotalValue: accountBalance.XRPAvailable, - Hold: accountBalance.XRPReserved, - }, - { - CurrencyName: currency.USD, - TotalValue: accountBalance.USDAvailable, - Hold: accountBalance.USDReserved, - }, - { - CurrencyName: currency.EUR, - TotalValue: accountBalance.EURAvailable, - Hold: accountBalance.EURReserved, - }, + var currencies []exchange.AccountCurrencyInfo + for k, v := range accountBalance { + currencies = append(currencies, exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(k), + TotalValue: v.Available, + Hold: v.Reserved, + }) } response.Accounts = append(response.Accounts, exchange.Account{ Currencies: currencies, @@ -352,8 +342,7 @@ func (b *Bitstamp) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Bitstamp) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. @@ -518,7 +507,6 @@ func (b *Bitstamp) GetWebsocket() (*wshandler.Websocket, error) { // GetActiveOrders retrieves any orders that are active/open func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { - var orders []order.Detail var currPair string if len(req.Currencies) != 1 { currPair = "all" @@ -531,16 +519,28 @@ func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, return nil, err } + var orders []order.Detail for i := range resp { - orderDate := time.Unix(resp[i].Date, 0) + orderSide := order.Buy + if resp[i].Type == SellOrder { + orderSide = order.Sell + } + + tm, err := parseTime(resp[i].DateTime) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s GetActiveOrders unable to parse time: %s\n", b.Name, err) + } + orders = append(orders, order.Detail{ - Amount: resp[i].Amount, - ID: strconv.FormatInt(resp[i].ID, 10), - Price: resp[i].Price, - OrderDate: orderDate, - CurrencyPair: currency.NewPairFromStrings(resp[i].Currency[0:3], - resp[i].Currency[len(resp[i].Currency)-3:]), - Exchange: b.Name, + Amount: resp[i].Amount, + ID: strconv.FormatInt(resp[i].ID, 10), + Price: resp[i].Price, + OrderType: order.Limit, + OrderSide: orderSide, + OrderDate: tm, + CurrencyPair: currency.NewPairFromString(resp[i].Currency), + Exchange: b.Name, }) } @@ -563,7 +563,7 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, var orders []order.Detail for i := range resp { - if resp[i].Type != 2 { + if resp[i].Type != MarketTrade { continue } var quoteCurrency, baseCurrency currency.Code @@ -575,7 +575,8 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, baseCurrency = currency.XRP default: log.Warnf(log.ExchangeSys, - "no base currency found for OrderID '%d'", + "%s No base currency found for OrderID '%d'\n", + b.Name, resp[i].OrderID) } @@ -586,7 +587,8 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, quoteCurrency = currency.EUR default: log.Warnf(log.ExchangeSys, - "no quote currency found for orderID '%d'", + "%s No quote currency found for orderID '%d'\n", + b.Name, resp[i].OrderID) } @@ -597,14 +599,15 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, b.GetPairFormat(asset.Spot, false).Delimiter) } - orderDate, err := time.Parse("2006-01-02 15:04:05", resp[i].Date) + tm, err := parseTime(resp[i].Date) if err != nil { - return nil, err + log.Errorf(log.ExchangeSys, + "%s GetOrderHistory unable to parse time: %s\n", b.Name, err) } orders = append(orders, order.Detail{ ID: strconv.FormatInt(resp[i].OrderID, 10), - OrderDate: orderDate, + OrderDate: tm, Exchange: b.Name, CurrencyPair: currPair, }) diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index 1120bc68..4d9489d5 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -7,6 +7,7 @@ import ( "net/url" "strconv" "strings" + "time" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" @@ -53,6 +54,7 @@ const ( bittrexAuthRate = 0 bittrexUnauthRate = 0 + bittrexTimeLayout = "2006-01-02T15:04:05" ) // Bittrex is the overaching type across the bittrex methods @@ -508,3 +510,7 @@ func (b *Bittrex) GetWithdrawalFee(c currency.Code) (float64, error) { func calculateTradingFee(price, amount float64) float64 { return 0.0025 * price * amount } + +func parseTime(t string) (time.Time, error) { + return time.Parse(bittrexTimeLayout, t) +} diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 32196f3f..976fe165 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -534,3 +534,21 @@ func TestGetDepositAddress(t *testing.T) { } } } + +func TestParseTime(t *testing.T) { + t.Parallel() + + tm, err := parseTime("2019-11-21T02:08:34.87") + if err != nil { + t.Fatal(err) + } + + if tm.Year() != 2019 || + tm.Month() != 11 || + tm.Day() != 21 || + tm.Hour() != 2 || + tm.Minute() != 8 || + tm.Second() != 34 { + t.Error("invalid time values") + } +} diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 15cef5ec..da8a543b 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -227,7 +227,11 @@ func (b *Bittrex) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr if !strings.EqualFold(ticks.Result[j].MarketName, pairs[i].String()) { continue } - tickerTime, _ := time.Parse(time.RFC3339, ticks.Result[j].TimeStamp) + tickerTime, err := parseTime(ticks.Result[j].TimeStamp) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s UpdateTicker unable to parse time: %s\n", b.Name, err) + } tickerPrice = ticker.Price{ Last: ticks.Result[j].Last, High: ticks.Result[j].High, @@ -309,8 +313,7 @@ func (b *Bittrex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderb // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Bittrex) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. @@ -451,9 +454,9 @@ func (b *Bittrex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, var orders []order.Detail for i := range resp.Result { - orderDate, err := time.Parse(time.RFC3339, resp.Result[i].Opened) + orderDate, err := parseTime(resp.Result[i].Opened) if err != nil { - log.Warnf(log.ExchangeSys, + log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", b.Name, "GetActiveOrders", @@ -498,9 +501,9 @@ func (b *Bittrex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, var orders []order.Detail for i := range resp.Result { - orderDate, err := time.Parse(time.RFC3339, resp.Result[i].TimeStamp) + orderDate, err := parseTime(resp.Result[i].TimeStamp) if err != nil { - log.Warnf(log.ExchangeSys, + log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", b.Name, "GetActiveOrders", diff --git a/exchanges/btcmarkets/btcmarkets_types.go b/exchanges/btcmarkets/btcmarkets_types.go index 33e61d78..7cccb8b2 100644 --- a/exchanges/btcmarkets/btcmarkets_types.go +++ b/exchanges/btcmarkets/btcmarkets_types.go @@ -193,6 +193,7 @@ type WsOrderbook struct { MessageType string `json:"messageType"` } +// WsError message received for orderbook errors type WsError struct { MessageType string `json:"messageType"` Code int64 `json:"code"` diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 21025ae3..b89f199b 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -279,13 +279,17 @@ func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType asset.Item) (ord } for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[x][1], + Price: orderbookNew.Bids[x][0], + }) } for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: orderbookNew.Asks[x][1], + Price: orderbookNew.Asks[x][0], + }) } orderBook.Pair = p @@ -331,8 +335,7 @@ func (b *BTCMarkets) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (b *BTCMarkets) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/btse/btse.go b/exchanges/btse/btse.go index 4642ec37..bf2e2ac6 100644 --- a/exchanges/btse/btse.go +++ b/exchanges/btse/btse.go @@ -42,6 +42,7 @@ const ( btsePendingOrders = "pending" btseDeleteOrder = "deleteOrder" btseFills = "fills" + btseTimeLayout = "2006-01-02 15:04:04" ) // GetMarketsSummary stores market summary data @@ -238,7 +239,7 @@ func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[str b.Name, method, path, string(payload)) } return b.SendPayload(method, - btseAPIURL+path, + b.API.Endpoints.URL+path, headers, body, &result, @@ -320,7 +321,6 @@ func calculateTradingFee(isMaker bool) float64 { return fee } -func parseOrderTime(timeStr string) time.Time { - t, _ := time.Parse("2006-01-02 15:04:04", timeStr) - return t +func parseOrderTime(timeStr string) (time.Time, error) { + return time.Parse(btseTimeLayout, timeStr) } diff --git a/exchanges/btse/btse_test.go b/exchanges/btse/btse_test.go index 916bce54..d97130b0 100644 --- a/exchanges/btse/btse_test.go +++ b/exchanges/btse/btse_test.go @@ -17,6 +17,7 @@ const ( apiKey = "" apiSecret = "" canManipulateRealOrders = false + testPair = "BTC-USD" ) var b BTSE @@ -37,7 +38,11 @@ func TestMain(m *testing.M) { btseConfig.API.Credentials.Key = apiKey btseConfig.API.Credentials.Secret = apiSecret - b.Setup(btseConfig) + err = b.Setup(btseConfig) + if err != nil { + log.Fatal(err) + } + os.Exit(m.Run()) } @@ -63,7 +68,7 @@ func TestGetMarkets(t *testing.T) { func TestFetchOrderBook(t *testing.T) { t.Parallel() - _, err := b.FetchOrderBook("BTC-USD") + _, err := b.FetchOrderBook(testPair) if err != nil { t.Error(err) } @@ -71,7 +76,7 @@ func TestFetchOrderBook(t *testing.T) { func TestGetTrades(t *testing.T) { t.Parallel() - _, err := b.GetTrades("BTC-USD") + _, err := b.GetTrades(testPair) if err != nil { t.Error(err) } @@ -79,7 +84,7 @@ func TestGetTrades(t *testing.T) { func TestGetTicker(t *testing.T) { t.Parallel() - _, err := b.GetTicker("BTC-USD") + _, err := b.GetTicker(testPair) if err != nil { t.Error(err) } @@ -87,7 +92,7 @@ func TestGetTicker(t *testing.T) { func TestGetMarketStatistics(t *testing.T) { t.Parallel() - _, err := b.GetMarketStatistics("BTC-USD") + _, err := b.GetMarketStatistics(testPair) if err != nil { t.Error(err) } @@ -117,7 +122,7 @@ func TestGetFills(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("API keys not set, skipping test") } - _, err := b.GetFills("", "BTC-USD", "", "", "", "") + _, err := b.GetFills("", testPair, "", "", "", "") if err != nil { t.Error(err) } @@ -128,7 +133,13 @@ func TestCreateOrder(t *testing.T) { if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") } - _, err := b.CreateOrder(0.1, 10000, "sell", "limit", "BTC-USD", "", "") + _, err := b.CreateOrder(0.1, + 10000, + order.Sell.String(), + order.Limit.String(), + testPair, + "", + "") if err != nil { t.Error(err) } @@ -266,9 +277,12 @@ func TestGetFee(t *testing.T) { func TestParseOrderTime(t *testing.T) { expected := int64(1534794360) - actual := parseOrderTime("2018-08-20 19:20:46").Unix() - if expected != actual { - t.Errorf("TestParseOrderTime expected: %d, got %d", expected, actual) + actual, err := parseOrderTime("2018-08-20 19:20:46") + if err != nil { + t.Fatal(err) + } + if expected != actual.Unix() { + t.Errorf("TestParseOrderTime expected: %d, got %d", expected, actual.Unix()) } } diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index d362c0bd..e12d3eeb 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -103,8 +103,8 @@ func (b *BTSE) WsHandleData() { b.Websocket.DataHandler <- err continue } + var newOB orderbook.Base var price, amount float64 - var asks, bids []orderbook.Item for i := range t.Data.SellQuote { p := strings.Replace(t.Data.SellQuote[i].Price, ",", "", -1) price, err = strconv.ParseFloat(p, 64) @@ -118,7 +118,10 @@ func (b *BTSE) WsHandleData() { b.Websocket.DataHandler <- err continue } - asks = append(asks, orderbook.Item{Price: price, Amount: amount}) + newOB.Asks = append(newOB.Asks, orderbook.Item{ + Price: price, + Amount: amount, + }) } for j := range t.Data.BuyQuote { p := strings.Replace(t.Data.BuyQuote[j].Price, ",", "", -1) @@ -133,11 +136,11 @@ func (b *BTSE) WsHandleData() { b.Websocket.DataHandler <- err continue } - bids = append(bids, orderbook.Item{Price: price, Amount: amount}) + newOB.Bids = append(newOB.Bids, orderbook.Item{ + Price: price, + Amount: amount, + }) } - var newOB orderbook.Base - newOB.Asks = asks - newOB.Bids = bids newOB.AssetType = asset.Spot newOB.Pair = currency.NewPairFromString(t.Topic[strings.Index(t.Topic, ":")+1 : strings.Index(t.Topic, "_")]) newOB.ExchangeName = b.Name diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index d7e9fca3..5a5ca079 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -450,7 +450,11 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) { od.Exchange = b.Name od.Amount = o[i].Amount od.ID = o[i].ID - od.OrderDate = parseOrderTime(o[i].CreatedAt) + od.OrderDate, err = parseOrderTime(o[i].CreatedAt) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s GetOrderInfo unable to parse time: %s\n", b.Name, err) + } od.OrderSide = side od.OrderType = order.Type(strings.ToUpper(o[i].Type)) od.Price = o[i].Price @@ -464,7 +468,11 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) { } for i := range fills { - createdAt, _ := time.Parse(time.RFC3339, fills[i].CreatedAt) + createdAt, err := parseOrderTime(fills[i].CreatedAt) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s GetOrderInfo unable to parse time: %s\n", b.Name, err) + } od.Trades = append(od.Trades, order.TradeHistory{ Timestamp: createdAt, TID: fills[i].ID, @@ -521,13 +529,21 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err side = order.Sell } + tm, err := parseOrderTime(resp[i].CreatedAt) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s GetActiveOrders unable to parse time: %s\n", + b.Name, + err) + } + openOrder := order.Detail{ CurrencyPair: currency.NewPairDelimiter(resp[i].Symbol, b.GetPairFormat(asset.Spot, false).Delimiter), Exchange: b.Name, Amount: resp[i].Amount, ID: resp[i].ID, - OrderDate: parseOrderTime(resp[i].CreatedAt), + OrderDate: tm, OrderSide: side, OrderType: order.Type(strings.ToUpper(resp[i].Type)), Price: resp[i].Price, @@ -544,7 +560,13 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err } for i := range fills { - createdAt, _ := time.Parse(time.RFC3339, fills[i].CreatedAt) + createdAt, err := parseOrderTime(fills[i].CreatedAt) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s GetActiveOrders unable to parse time: %s\n", + b.Name, + err) + } openOrder.Trades = append(openOrder.Trades, order.TradeHistory{ Timestamp: createdAt, TID: fills[i].ID, diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 315b0433..95d84f4a 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -22,6 +22,7 @@ const ( apiSecret = "" clientID = "" // passphrase you made at API CREATION canManipulateRealOrders = false + testPair = "BTC-USD" ) func TestSetDefaults(t *testing.T) { @@ -58,28 +59,28 @@ func TestGetProducts(t *testing.T) { } func TestGetTicker(t *testing.T) { - _, err := c.GetTicker("BTC-USD") + _, err := c.GetTicker(testPair) if err != nil { t.Error("GetTicker() error", err) } } func TestGetTrades(t *testing.T) { - _, err := c.GetTrades("BTC-USD") + _, err := c.GetTrades(testPair) if err != nil { t.Error("GetTrades() error", err) } } func TestGetHistoricRates(t *testing.T) { - _, err := c.GetHistoricRates("BTC-USD", 0, 0, 0) + _, err := c.GetHistoricRates(testPair, 0, 0, 0) if err != nil { t.Error("GetHistoricRates() error", err) } } func TestGetStats(t *testing.T) { - _, err := c.GetStats("BTC-USD") + _, err := c.GetStats(testPair) if err != nil { t.Error("GetStats() error", err) } @@ -128,21 +129,23 @@ func TestAuthRequests(t *testing.T) { if err == nil { t.Error("Expecting error") } - orderResponse, err := c.PlaceLimitOrder("", 0.001, 0.001, "buy", "", "", "BTC-USD", "", false) + orderResponse, err := c.PlaceLimitOrder("", 0.001, 0.001, + order.Buy.Lower(), "", "", testPair, "", false) if orderResponse != "" { t.Error("Expecting no data returned") } if err == nil { t.Error("Expecting error") } - marketOrderResponse, err := c.PlaceMarketOrder("", 1, 0, "buy", "BTC-USD", "") + marketOrderResponse, err := c.PlaceMarketOrder("", 1, 0, + order.Buy.Lower(), testPair, "") if marketOrderResponse != "" { t.Error("Expecting no data returned") } if err == nil { t.Error("Expecting error") } - fillsResponse, err := c.GetFills("1337", "BTC-USD") + fillsResponse, err := c.GetFills("1337", testPair) if len(fillsResponse) > 0 { t.Error("Expecting no data returned") } @@ -616,7 +619,7 @@ func TestWsAuth(t *testing.T) { go c.WsHandleData() err = c.Subscribe(wshandler.WebsocketChannelSubscription{ Channel: "user", - Currency: currency.NewPairFromString("BTC-USD"), + Currency: currency.NewPairFromString(testPair), }) if err != nil { t.Error(err) diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index dd687b04..a66ebf41 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -515,7 +515,7 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta orderType := order.Type(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) if err != nil { - log.Warnf(log.ExchangeSys, + log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", c.Name, "GetActiveOrders", @@ -562,7 +562,7 @@ func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Deta orderType := order.Type(strings.ToUpper(respOrders[i].Type)) orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) if err != nil { - log.Warnf(log.ExchangeSys, + log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", c.Name, "GetActiveOrders", diff --git a/exchanges/coinbene/README.md b/exchanges/coinbene/README.md index 44f6ef31..e5a7cd7e 100644 --- a/exchanges/coinbene/README.md +++ b/exchanges/coinbene/README.md @@ -48,22 +48,22 @@ main.go ```go var c exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Coinbene" { - c = bot.exchanges[i] +for i := range Bot.Exchanges { + if Bot.Exchanges[i].GetName() == "Coinbene" { + c = Bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := c.GetTickerPrice() +tick, err := c.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := c.GetOrderbookEx() +ob, err := c.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/coinbene/coinbene.go b/exchanges/coinbene/coinbene.go index 75c88419..4bb0653e 100644 --- a/exchanges/coinbene/coinbene.go +++ b/exchanges/coinbene/coinbene.go @@ -247,9 +247,14 @@ func (c *Coinbene) SendHTTPRequest(path string, result interface{}) error { // SendAuthHTTPRequest sends an authenticated HTTP request func (c *Coinbene) SendAuthHTTPRequest(method, path, epPath string, params url.Values, result interface{}) error { + if !c.AllowAuthenticatedRequest() { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, c.Name) + } + if params == nil { params = url.Values{} } + timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.999Z") var finalBody io.Reader var preSign string diff --git a/exchanges/coinbene/coinbene_test.go b/exchanges/coinbene/coinbene_test.go index f24255a0..b870ce71 100644 --- a/exchanges/coinbene/coinbene_test.go +++ b/exchanges/coinbene/coinbene_test.go @@ -34,7 +34,11 @@ func TestMain(m *testing.M) { coinbeneConfig.API.AuthenticatedSupport = true coinbeneConfig.API.Credentials.Secret = testAPISecret coinbeneConfig.API.Credentials.Key = testAPIKey - c.Setup(coinbeneConfig) + + err = c.Setup(coinbeneConfig) + if err != nil { + log.Fatal(err) + } os.Exit(m.Run()) } diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 75779a48..c5c3e03e 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -421,9 +421,7 @@ func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo // GetFundingHistory returns funding history, deposits and // withdrawals func (c *COINUT) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 42cac143..4932817e 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -755,10 +755,10 @@ func (e *Base) FormatWithdrawPermissions() string { return NoAPIWithdrawalMethodsText } -// IsAssetTypeSupported whether or not the supplied asset is supported +// SupportsAsset whether or not the supplied asset is supported // by the exchange -func (e *Base) IsAssetTypeSupported(asset asset.Item) bool { - return e.CurrencyPairs.AssetTypes.Contains(asset) +func (e *Base) SupportsAsset(a asset.Item) bool { + return e.CurrencyPairs.AssetTypes.Contains(a) } // PrintEnabledPairs prints the exchanges enabled asset pairs diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 5aab5f4a..b070e27b 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -550,7 +550,7 @@ func TestGetEnabledPairs(t *testing.T) { } b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{"BTC-USD"}), true) + currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}), true) format := currency.PairFormat{ Delimiter: "-", Index: "", @@ -1321,18 +1321,16 @@ func TestFormatWithdrawPermissions(t *testing.T) { } } -func TestIsAssetTypeSupported(t *testing.T) { +func TestSupportsAsset(t *testing.T) { t.Parallel() - var b Base b.CurrencyPairs.AssetTypes = asset.Items{ asset.Spot, } - - if !b.IsAssetTypeSupported(asset.Spot) { + if !b.SupportsAsset(asset.Spot) { t.Error("spot should be supported") } - if b.IsAssetTypeSupported(asset.Index) { + if b.SupportsAsset(asset.Index) { t.Error("index shouldn't be supported") } } diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index c2b79d2b..e228fc7a 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -313,8 +313,7 @@ func (e *EXMO) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (e *EXMO) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index f3d894fd..91970c94 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -277,13 +277,17 @@ func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo } for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Amount, Price: data.Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[x].Amount, + Price: orderbookNew.Bids[x].Price, + }) } for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: orderbookNew.Asks[x].Amount, + Price: orderbookNew.Asks[x].Price, + }) } orderBook.Pair = p @@ -366,8 +370,7 @@ func (g *Gateio) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (g *Gateio) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index 9681a402..dec36bab 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -91,7 +91,11 @@ func TestGetAuctionHistory(t *testing.T) { func TestNewOrder(t *testing.T) { t.Parallel() - _, err := g.NewOrder(testCurrency, 1, 9000, "buy", "exchange limit") + _, err := g.NewOrder(testCurrency, + 1, + 9000000, + order.Sell.Lower(), + "exchange limit") if err != nil && mockTests { t.Error("NewOrder() error", err) } else if err == nil && !mockTests { diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 1596393d..16e66d45 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -307,8 +307,7 @@ func (g *Gemini) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo // GetFundingHistory returns funding history, deposits and // withdrawals func (g *Gemini) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index 99d7c296..ea0cf2fd 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -232,22 +232,23 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { return errors.New("hitbtc.go error - no orderbooks to process") } - var bids []orderbook.Item + var newOrderBook orderbook.Base for i := range ob.Params.Bid { - bids = append(bids, orderbook.Item{Amount: ob.Params.Bid[i].Size, Price: ob.Params.Bid[i].Price}) + newOrderBook.Bids = append(newOrderBook.Bids, orderbook.Item{ + Amount: ob.Params.Bid[i].Size, + Price: ob.Params.Bid[i].Price, + }) } - var asks []orderbook.Item for i := range ob.Params.Ask { - asks = append(asks, orderbook.Item{Amount: ob.Params.Ask[i].Size, Price: ob.Params.Ask[i].Price}) + newOrderBook.Asks = append(newOrderBook.Asks, orderbook.Item{ + Amount: ob.Params.Ask[i].Size, + Price: ob.Params.Ask[i].Price, + }) } p := currency.NewPairFromFormattedPairs(ob.Params.Symbol, h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)) - - var newOrderBook orderbook.Base - newOrderBook.Asks = asks - newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot newOrderBook.Pair = p newOrderBook.ExchangeName = h.Name @@ -274,11 +275,17 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error { var bids, asks []orderbook.Item for i := range update.Params.Bid { - bids = append(bids, orderbook.Item{Price: update.Params.Bid[i].Price, Amount: update.Params.Bid[i].Size}) + bids = append(bids, orderbook.Item{ + Price: update.Params.Bid[i].Price, + Amount: update.Params.Bid[i].Size, + }) } for i := range update.Params.Ask { - asks = append(asks, orderbook.Item{Price: update.Params.Ask[i].Price, Amount: update.Params.Ask[i].Size}) + asks = append(asks, orderbook.Item{ + Price: update.Params.Ask[i].Price, + Amount: update.Params.Ask[i].Size, + }) } p := currency.NewPairFromFormattedPairs(update.Params.Symbol, diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 98fe7b7a..bd823364 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -309,13 +309,17 @@ func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType asset.Ite } for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Amount, Price: data.Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[x].Amount, + Price: orderbookNew.Bids[x].Price, + }) } for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: orderbookNew.Asks[x].Amount, + Price: orderbookNew.Asks[x].Price, + }) } orderBook.Pair = currencyPair @@ -360,8 +364,7 @@ func (h *HitBTC) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (h *HitBTC) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. @@ -379,8 +382,8 @@ func (h *HitBTC) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { response, err := h.PlaceOrder(s.Pair.String(), s.Price, s.Amount, - strings.ToLower(s.OrderType.String()), - strings.ToLower(s.OrderSide.String())) + s.OrderType.Lower(), + s.OrderSide.Lower()) if response.OrderNumber > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index bc918852..e83c5e8b 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "strconv" - "strings" "sync" "time" @@ -350,13 +349,17 @@ func (h *HUOBI) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo } for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[x][1], + Price: orderbookNew.Bids[x][0], + }) } for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: orderbookNew.Asks[x][1], + Price: orderbookNew.Asks[x][0], + }) } orderBook.Pair = p @@ -452,8 +455,7 @@ func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (h *HUOBI) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. @@ -600,7 +602,7 @@ func (h *HUOBI) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er if req.OrderSide == order.AnySide || req.OrderSide == "" { side = "" } else if req.OrderSide == order.Sell { - side = strings.ToLower(string(req.OrderSide)) + side = req.OrderSide.Lower() } var orders []order.Detail diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go index d90ce726..3f5723e3 100644 --- a/exchanges/interfaces.go +++ b/exchanges/interfaces.go @@ -66,4 +66,5 @@ type IBotExchange interface { GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) GetDefaultConfig() (*config.ExchangeConfig, error) GetBase() *Base + SupportsAsset(assetType asset.Item) bool } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index a10a6a09..1168ebc4 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -193,13 +193,12 @@ func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo } for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] var price, amount float64 - price, err = strconv.ParseFloat(data[0], 64) + price, err = strconv.ParseFloat(orderbookNew.Bids[x][0], 64) if err != nil { return orderBook, err } - amount, err = strconv.ParseFloat(data[1], 64) + amount, err = strconv.ParseFloat(orderbookNew.Bids[x][1], 64) if err != nil { return orderBook, err } @@ -211,13 +210,12 @@ func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo } for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] var price, amount float64 - price, err = strconv.ParseFloat(data[0], 64) + price, err = strconv.ParseFloat(orderbookNew.Asks[x][0], 64) if err != nil { return orderBook, err } - amount, err = strconv.ParseFloat(data[1], 64) + amount, err = strconv.ParseFloat(orderbookNew.Asks[x][1], 64) if err != nil { return orderBook, err } @@ -287,8 +285,7 @@ func (i *ItBit) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (i *ItBit) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. @@ -444,7 +441,7 @@ func (i *ItBit) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er side := order.Side(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { - log.Warnf(log.ExchangeSys, + log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", i.Name, "GetActiveOrders", @@ -498,7 +495,7 @@ func (i *ItBit) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er side := order.Side(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { - log.Warnf(log.ExchangeSys, + log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", i.Name, "GetActiveOrders", diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index b4282ea1..2831e760 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -386,8 +386,7 @@ func (k *Kraken) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (k *Kraken) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/lakebtc/lakebtc_websocket.go b/exchanges/lakebtc/lakebtc_websocket.go index af662d9c..81879957 100644 --- a/exchanges/lakebtc/lakebtc_websocket.go +++ b/exchanges/lakebtc/lakebtc_websocket.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" @@ -21,11 +22,9 @@ const ( marketGlobalEndpoint = "market-global" marketSubstring = "market-" globalSubstring = "-global" - tickerBuyString = "buy" tickerHighString = "high" tickerLastString = "last" tickerLowString = "low" - tickerSellString = "sell" tickerVolumeString = "volume" wssSchem = "wss" ) @@ -270,11 +269,11 @@ func (l *LakeBTC) processTicker(ticker string) error { l.Websocket.DataHandler <- wshandler.TickerData{ Exchange: l.Name, - Bid: processTickerItem(tickerData, tickerBuyString), + Bid: processTickerItem(tickerData, order.Buy.Lower()), High: processTickerItem(tickerData, tickerHighString), Last: processTickerItem(tickerData, tickerLastString), Low: processTickerItem(tickerData, tickerLowString), - Ask: processTickerItem(tickerData, tickerSellString), + Ask: processTickerItem(tickerData, order.Sell.Lower()), Volume: processTickerItem(tickerData, tickerVolumeString), AssetType: asset.Spot, Pair: returnCurrency, diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index a46f184f..3be7eb82 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -316,8 +316,7 @@ func (l *LakeBTC) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (l *LakeBTC) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/lbank/README.md b/exchanges/lbank/README.md index 8e025ed5..78e67672 100644 --- a/exchanges/lbank/README.md +++ b/exchanges/lbank/README.md @@ -47,22 +47,22 @@ main.go ```go var l exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "Lbank" { - l = bot.exchanges[i] +for i := range Bot.Exchanges { + if Bot.Exchanges[i].GetName() == "Lbank" { + l = Bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := l.GetTickerPrice() +tick, err := l.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := l.GetOrderbookEx() +ob, err := l.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/lbank/lbank.go b/exchanges/lbank/lbank.go index 143c51a1..f7f7322b 100644 --- a/exchanges/lbank/lbank.go +++ b/exchanges/lbank/lbank.go @@ -17,6 +17,7 @@ import ( gctcrypto "github.com/thrasher-corp/gocryptotrader/common/crypto" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -195,7 +196,8 @@ func (l *Lbank) GetUserInfo() (InfoFinalResponse, error) { // CreateOrder creates an order func (l *Lbank) CreateOrder(pair, side string, amount, price float64) (CreateOrderResponse, error) { var resp CreateOrderResponse - if !strings.EqualFold(side, "buy") && !strings.EqualFold(side, "sell") { + if !strings.EqualFold(side, order.Buy.String()) && + !strings.EqualFold(side, order.Sell.String()) { return resp, errors.New("side type invalid can only be 'buy' or 'sell'") } if amount <= 0 { @@ -546,6 +548,10 @@ func (l *Lbank) sign(data string) (string, error) { // SendAuthHTTPRequest sends an authenticated request func (l *Lbank) SendAuthHTTPRequest(method, endpoint string, vals url.Values, result interface{}) error { + if !l.AllowAuthenticatedRequest() { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name) + } + if vals == nil { vals = url.Values{} } diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index 24e9e649..e9fbd8b3 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -407,7 +407,7 @@ func (l *Lbank) GetOrderInfo(orderID string) (order.Detail, error) { } resp.Exchange = l.Name resp.CurrencyPair = currency.NewPairFromString(key) - if strings.EqualFold(tempResp.Orders[0].Type, "buy") { + if strings.EqualFold(tempResp.Orders[0].Type, order.Buy.String()) { resp.OrderSide = order.Buy } else { resp.OrderSide = order.Sell @@ -491,7 +491,7 @@ func (l *Lbank) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]ord } resp.Exchange = l.Name resp.CurrencyPair = currency.NewPairFromString(key) - if strings.EqualFold(tempResp.Orders[0].Type, "buy") { + if strings.EqualFold(tempResp.Orders[0].Type, order.Buy.String()) { resp.OrderSide = order.Buy } else { resp.OrderSide = order.Sell @@ -567,7 +567,7 @@ func (l *Lbank) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]ord for x := 0; x < len(tempResp.Orders); x++ { resp.Exchange = l.Name resp.CurrencyPair = currency.NewPairFromString(tempResp.Orders[x].Symbol) - if strings.EqualFold(tempResp.Orders[x].Type, "buy") { + if strings.EqualFold(tempResp.Orders[x].Type, order.Buy.String()) { resp.OrderSide = order.Buy } else { resp.OrderSide = order.Sell diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index e03ac508..1e1bf0a2 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -218,13 +218,17 @@ func (l *LocalBitcoins) UpdateOrderbook(p currency.Pair, assetType asset.Item) ( } for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Amount / data.Price, Price: data.Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[x].Amount / orderbookNew.Bids[x].Price, + Price: orderbookNew.Bids[x].Price, + }) } for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount / data.Price, Price: data.Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: orderbookNew.Asks[x].Amount / orderbookNew.Asks[x].Price, + Price: orderbookNew.Asks[x].Price, + }) } orderBook.Pair = p @@ -261,8 +265,7 @@ func (l *LocalBitcoins) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (l *LocalBitcoins) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. @@ -434,7 +437,7 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest for i := range resp { orderDate, err := time.Parse(time.RFC3339, resp[i].Data.CreatedAt) if err != nil { - log.Warnf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", + log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", l.Name, "GetActiveOrders", resp[i].Data.Advertisement.ID, @@ -495,7 +498,7 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest for i := range allTrades { orderDate, err := time.Parse(time.RFC3339, allTrades[i].Data.CreatedAt) if err != nil { - log.Warnf(log.ExchangeSys, + log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", l.Name, "GetActiveOrders", diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 19849b0b..5c57708c 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -146,7 +146,7 @@ func TestGetAccountWalletInformationForCurrency(t *testing.T) { func TestTransferAccountFunds(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.TransferAccountFundsRequest{ - Amount: 10, + Amount: -10, Currency: currency.BTC.String(), From: 6, To: 1, @@ -159,7 +159,7 @@ func TestTransferAccountFunds(t *testing.T) { func TestAccountWithdrawRequest(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.AccountWithdrawRequest{ - Amount: 10, + Amount: -10, Currency: currency.BTC.String(), TradePwd: "1234", Destination: 4, @@ -285,13 +285,12 @@ func TestGetSpotBillDetailsForCurrencyBadLimit(t *testing.T) { // TestPlaceSpotOrderLimit API endpoint test func TestPlaceSpotOrderLimit(t *testing.T) { TestSetRealOrderDefaults(t) - request := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Limit.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "1", - Price: "100", - Size: "100", + request := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), + Price: "-100", + Size: "100", } _, err := o.PlaceSpotOrder(&request) @@ -301,13 +300,12 @@ func TestPlaceSpotOrderLimit(t *testing.T) { // TestPlaceSpotOrderMarket API endpoint test func TestPlaceSpotOrderMarket(t *testing.T) { TestSetRealOrderDefaults(t) - request := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Market.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "1", - Size: "-100", - Notional: "100", + request := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Market.Lower(), + Side: order.Buy.Lower(), + Size: "-100", + Notional: "100", } _, err := o.PlaceSpotOrder(&request) @@ -317,16 +315,15 @@ func TestPlaceSpotOrderMarket(t *testing.T) { // TestPlaceMultipleSpotOrders API endpoint test func TestPlaceMultipleSpotOrders(t *testing.T) { TestSetRealOrderDefaults(t) - order := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Limit.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "1", - Size: "100", - Notional: "100", + order := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), + Size: "-100", + Price: "1", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, } @@ -339,16 +336,15 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { // TestPlaceMultipleSpotOrdersOverCurrencyLimits API logic test func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { TestSetDefaults(t) - order := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Limit.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "1", - Size: "100", - Notional: "100", + order := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), + Size: "-100", + Price: "1", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, order, order, @@ -365,27 +361,29 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { // TestPlaceMultipleSpotOrdersOverPairLimits API logic test func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { TestSetDefaults(t) - order := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Limit.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "1", - Size: "100", - Notional: "100", + order := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), + Size: "-100", + Price: "1", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, } - order.InstrumentID = currency.NewPairWithDelimiter(currency.LTC.String(), currency.USD.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.DOGE.String(), currency.USD.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.XMR.String(), currency.USD.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.BCH.String(), currency.USD.String(), "-").Lower().String() - request = append(request, order) + pairs := currency.Pairs{ + currency.NewPair(currency.LTC, currency.USDT), + currency.NewPair(currency.ETH, currency.USDT), + currency.NewPair(currency.BCH, currency.USDT), + currency.NewPair(currency.XMR, currency.USDT), + } + + for x := range pairs { + order.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, order) + } _, errs := o.PlaceMultipleSpotOrders(request) if errs[0].Error() != "up to 4 trading pairs" { @@ -574,7 +572,7 @@ func TestGetMarginAccountSettingsForCurrency(t *testing.T) { func TestOpenMarginLoan(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.OpenMarginLoanRequest{ - Amount: 100, + Amount: -100, InstrumentID: spotCurrency, QuoteCurrency: currency.USD.String(), } @@ -587,7 +585,7 @@ func TestOpenMarginLoan(t *testing.T) { func TestRepayMarginLoan(t *testing.T) { TestSetRealOrderDefaults(t) request := okgroup.RepayMarginLoanRequest{ - Amount: 100, + Amount: -100, InstrumentID: spotCurrency, QuoteCurrency: currency.USD.String(), BorrowID: 1, @@ -600,12 +598,12 @@ func TestRepayMarginLoan(t *testing.T) { // TestPlaceMarginOrderLimit API endpoint test func TestPlaceMarginOrderLimit(t *testing.T) { TestSetRealOrderDefaults(t) - request := okgroup.PlaceSpotOrderRequest{ + request := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), MarginTrading: "2", - Price: "100", + Price: "-100", Size: "100", } @@ -616,7 +614,7 @@ func TestPlaceMarginOrderLimit(t *testing.T) { // TestPlaceMarginOrderMarket API endpoint test func TestPlaceMarginOrderMarket(t *testing.T) { TestSetRealOrderDefaults(t) - request := okgroup.PlaceSpotOrderRequest{ + request := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Market.Lower(), Side: order.Buy.Lower(), @@ -632,16 +630,16 @@ func TestPlaceMarginOrderMarket(t *testing.T) { // TestPlaceMultipleMarginOrders API endpoint test func TestPlaceMultipleMarginOrders(t *testing.T) { TestSetRealOrderDefaults(t) - order := okgroup.PlaceSpotOrderRequest{ + order := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), MarginTrading: "1", - Size: "100", + Size: "-100", Notional: "100", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, } @@ -654,16 +652,16 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { // TestPlaceMultipleMarginOrdersOverCurrencyLimits API logic test func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { TestSetDefaults(t) - order := okgroup.PlaceSpotOrderRequest{ + order := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), MarginTrading: "1", - Size: "100", + Size: "-100", Notional: "100", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, order, order, @@ -680,27 +678,30 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { // TestPlaceMultipleMarginOrdersOverPairLimits API logic test func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { TestSetDefaults(t) - order := okgroup.PlaceSpotOrderRequest{ + order := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), MarginTrading: "1", - Size: "100", + Size: "-100", Notional: "100", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, } - order.InstrumentID = currency.NewPairWithDelimiter(currency.LTC.String(), currency.USD.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.DOGE.String(), currency.USD.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.XMR.String(), currency.USD.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.BCH.String(), currency.USD.String(), "-").Lower().String() - request = append(request, order) + pairs := currency.Pairs{ + currency.NewPair(currency.LTC, currency.USDT), + currency.NewPair(currency.ETH, currency.USDT), + currency.NewPair(currency.BCH, currency.USDT), + currency.NewPair(currency.XMR, currency.USDT), + } + + for x := range pairs { + order.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, order) + } _, errs := o.PlaceMultipleMarginOrders(request) if errs[0].Error() != "up to 4 trading pairs" { @@ -1049,7 +1050,7 @@ func TestSubmitOrder(t *testing.T) { }, OrderSide: order.Buy, OrderType: order.Limit, - Price: 1, + Price: -1, Amount: 1, ClientID: "meowOrder", } diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index 8ae4cb93..f033e4c5 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -3,6 +3,7 @@ package okex import ( "fmt" "net/http" + "strconv" "strings" "sync" "testing" @@ -154,7 +155,7 @@ func TestTransferAccountFunds(t *testing.T) { Amount: 10, Currency: currency.BTC.String(), From: 6, - To: 1, + To: -1, } _, err := o.TransferAccountFunds(request) @@ -166,7 +167,7 @@ func TestAccountWithdrawRequest(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() request := okgroup.AccountWithdrawRequest{ - Amount: 10, + Amount: -1, Currency: currency.BTC.String(), TradePwd: "1234", Destination: 4, @@ -306,13 +307,12 @@ func TestGetSpotBillDetailsForCurrencyBadLimit(t *testing.T) { func TestPlaceSpotOrderLimit(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - request := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Limit.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "1", - Price: "100", - Size: "100", + request := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), + Price: "-1", + Size: "0.001", } _, err := o.PlaceSpotOrder(&request) @@ -323,13 +323,12 @@ func TestPlaceSpotOrderLimit(t *testing.T) { func TestPlaceSpotOrderMarket(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - request := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Market.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "1", - Size: "-100", - Notional: "100", + request := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Market.Lower(), + Side: order.Buy.Lower(), + Size: "-100", + Notional: "100", } _, err := o.PlaceSpotOrder(&request) @@ -340,16 +339,15 @@ func TestPlaceSpotOrderMarket(t *testing.T) { func TestPlaceMultipleSpotOrders(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - order := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Limit.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "1", - Size: "100", - Notional: "100", + order := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), + Size: "-100", + Price: "1", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, } @@ -363,16 +361,15 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Limit.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "1", - Size: "100", - Notional: "100", + order := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), + Size: "-100", + Price: "1", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, order, order, @@ -390,27 +387,29 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Limit.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "1", - Size: "100", - Notional: "100", + order := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), + Size: "-1", + Price: "1", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, } - order.InstrumentID = currency.NewPairWithDelimiter(currency.LTC.String(), currency.USDT.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.DOGE.String(), currency.USDT.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.XMR.String(), currency.USDT.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.BCH.String(), currency.USDT.String(), "-").Lower().String() - request = append(request, order) + pairs := currency.Pairs{ + currency.NewPair(currency.LTC, currency.USDT), + currency.NewPair(currency.ETH, currency.USDT), + currency.NewPair(currency.BCH, currency.USDT), + currency.NewPair(currency.XMR, currency.USDT), + } + + for x := range pairs { + order.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, order) + } _, errs := o.PlaceMultipleSpotOrders(request) if errs[0].Error() != "up to 4 trading pairs" { @@ -619,7 +618,7 @@ func TestOpenMarginLoan(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() request := okgroup.OpenMarginLoanRequest{ - Amount: 100, + Amount: -100, InstrumentID: spotCurrency, QuoteCurrency: currency.USDT.String(), } @@ -633,7 +632,7 @@ func TestRepayMarginLoan(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() request := okgroup.RepayMarginLoanRequest{ - Amount: 100, + Amount: -100, InstrumentID: spotCurrency, QuoteCurrency: currency.USDT.String(), BorrowID: 1, @@ -647,13 +646,13 @@ func TestRepayMarginLoan(t *testing.T) { func TestPlaceMarginOrderLimit(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - request := okgroup.PlaceSpotOrderRequest{ - InstrumentID: spotCurrency, - Type: order.Limit.Lower(), - Side: order.Buy.Lower(), - MarginTrading: "2", - Price: "100", - Size: "100", + request := okgroup.PlaceOrderRequest{ + InstrumentID: spotCurrency, + Type: order.Limit.Lower(), + Side: order.Buy.Lower(), + OrderType: strconv.Itoa(okgroup.NormalOrder), + Price: "-100", + Size: "100", } _, err := o.PlaceMarginOrder(&request) @@ -664,7 +663,7 @@ func TestPlaceMarginOrderLimit(t *testing.T) { func TestPlaceMarginOrderMarket(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - request := okgroup.PlaceSpotOrderRequest{ + request := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Market.Lower(), Side: order.Buy.Lower(), @@ -681,16 +680,16 @@ func TestPlaceMarginOrderMarket(t *testing.T) { func TestPlaceMultipleMarginOrders(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - order := okgroup.PlaceSpotOrderRequest{ + order := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), MarginTrading: "1", - Size: "100", + Size: "-100", Notional: "100", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, } @@ -704,16 +703,16 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceSpotOrderRequest{ + order := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), MarginTrading: "1", - Size: "100", + Size: "-100", Notional: "100", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, order, order, @@ -731,27 +730,30 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceSpotOrderRequest{ + order := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), MarginTrading: "1", - Size: "100", + Size: "-100", Notional: "100", } - request := []okgroup.PlaceSpotOrderRequest{ + request := []okgroup.PlaceOrderRequest{ order, } - order.InstrumentID = currency.NewPairWithDelimiter(currency.LTC.String(), currency.USDT.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.DOGE.String(), currency.USDT.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.XMR.String(), currency.USDT.String(), "-").Lower().String() - request = append(request, order) - order.InstrumentID = currency.NewPairWithDelimiter(currency.BCH.String(), currency.USDT.String(), "-").Lower().String() - request = append(request, order) + pairs := currency.Pairs{ + currency.NewPair(currency.LTC, currency.USDT), + currency.NewPair(currency.ETH, currency.USDT), + currency.NewPair(currency.BCH, currency.USDT), + currency.NewPair(currency.XMR, currency.USDT), + } + + for x := range pairs { + order.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, order) + } _, errs := o.PlaceMultipleMarginOrders(request) if errs[0].Error() != "up to 4 trading pairs" { @@ -935,7 +937,7 @@ func TestPlaceFuturesOrder(t *testing.T) { Leverage: 10, Type: 1, Size: 2, - Price: 432.11, + Price: -432.11, ClientOid: "12233456", }) testStandardErrorHandling(t, err) @@ -951,7 +953,7 @@ func TestPlaceFuturesOrderBatch(t *testing.T) { { ClientOid: "1", MatchPrice: "0", - Price: "100", + Price: "-100", Size: "100", Type: "1", }, @@ -1212,13 +1214,13 @@ func TestPlaceMultipleSwapOrders(t *testing.T) { ClientOID: "hello", MatchPrice: "0", Price: "10", - Size: "1", + Size: "-1", Type: "1", }, { ClientOID: "hello2", MatchPrice: "0", Price: "10", - Size: "1", + Size: "-1", Type: "1", }}, }) @@ -1463,7 +1465,7 @@ func TestPlaceETTOrder(t *testing.T) { QuoteCurrency: spotCurrency, Type: 0, Size: "100", - Amount: 1, + Amount: -1, ETT: "OK06", } diff --git a/exchanges/okgroup/README.md b/exchanges/okgroup/README.md index 32b623af..d9f96ea3 100644 --- a/exchanges/okgroup/README.md +++ b/exchanges/okgroup/README.md @@ -47,22 +47,22 @@ main.go ```go var o exchange.IBotExchange -for i := range bot.exchanges { - if bot.exchanges[i].GetName() == "OKex" { - y = bot.exchanges[i] +for i := range Bot.Exchanges { + if Bot.Exchanges[i].GetName() == "OKex" { + y = Bot.Exchanges[i] } } // Public calls - wrapper functions // Fetches current ticker information -tick, err := o.GetTickerPrice() +tick, err := o.FetchTicker() if err != nil { // Handle error } // Fetches current orderbook information -ob, err := o.GetOrderbookEx() +ob, err := o.FetchOrderbook() if err != nil { // Handle error } diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go index 4f1a8942..1b24871e 100644 --- a/exchanges/okgroup/okgroup.go +++ b/exchanges/okgroup/okgroup.go @@ -103,7 +103,7 @@ type OKGroup struct { // GetAccountCurrencies returns a list of tradable spot instruments and their properties func (o *OKGroup) GetAccountCurrencies() (resp []GetAccountCurrenciesResponse, _ error) { - return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, okGroupGetAccountCurrencies, nil, &resp, false) + return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, okGroupGetAccountCurrencies, nil, &resp, true) } // GetAccountWalletInformation returns a list of wallets and their properties @@ -173,7 +173,7 @@ func (o *OKGroup) GetAccountDepositHistory(currency string) (resp []GetAccountDe if currency != "" { requestURL = fmt.Sprintf("%v/%v", OKGroupGetAccountDepositHistory, currency) } else { - requestURL = okGroupGetWithdrawalHistory + requestURL = OKGroupGetAccountDepositHistory } return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true) } @@ -198,17 +198,23 @@ func (o *OKGroup) GetSpotBillDetailsForCurrency(request GetSpotBillDetailsForCur // PlaceSpotOrder token trading only supports limit and market orders (more order types will become available in the future). // You can place an order only if you have enough funds. // Once your order is placed, the amount will be put on hold. -func (o *OKGroup) PlaceSpotOrder(request *PlaceSpotOrderRequest) (resp PlaceSpotOrderResponse, _ error) { +func (o *OKGroup) PlaceSpotOrder(request *PlaceOrderRequest) (resp PlaceOrderResponse, _ error) { + if request.OrderType == "" { + request.OrderType = strconv.Itoa(NormalOrder) + } return resp, o.SendHTTPRequest(http.MethodPost, okGroupTokenSubsection, OKGroupOrders, request, &resp, true) } // PlaceMultipleSpotOrders supports placing multiple orders for specific trading pairs // up to 4 trading pairs, maximum 4 orders for each pair -func (o *OKGroup) PlaceMultipleSpotOrders(request []PlaceSpotOrderRequest) (map[string][]PlaceSpotOrderResponse, []error) { +func (o *OKGroup) PlaceMultipleSpotOrders(request []PlaceOrderRequest) (map[string][]PlaceOrderResponse, []error) { currencyPairOrders := make(map[string]int) - resp := make(map[string][]PlaceSpotOrderResponse) + resp := make(map[string][]PlaceOrderResponse) for i := range request { + if request[i].OrderType == "" { + request[i].OrderType = strconv.Itoa(NormalOrder) + } currencyPairOrders[request[i].InstrumentID]++ } @@ -422,14 +428,14 @@ func (o *OKGroup) RepayMarginLoan(request RepayMarginLoanRequest) (resp RepayMar // PlaceMarginOrder OKEx API only supports limit and market orders (more orders will become available in the future). // You can place an order only if you have enough funds. Once your order is placed, the amount will be put on hold. -func (o *OKGroup) PlaceMarginOrder(request *PlaceSpotOrderRequest) (resp PlaceSpotOrderResponse, _ error) { +func (o *OKGroup) PlaceMarginOrder(request *PlaceOrderRequest) (resp PlaceOrderResponse, _ error) { return resp, o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, OKGroupOrders, request, &resp, true) } // PlaceMultipleMarginOrders Place multiple orders for specific trading pairs (up to 4 trading pairs, maximum 4 orders each) -func (o *OKGroup) PlaceMultipleMarginOrders(request []PlaceSpotOrderRequest) (map[string][]PlaceSpotOrderResponse, []error) { +func (o *OKGroup) PlaceMultipleMarginOrders(request []PlaceOrderRequest) (map[string][]PlaceOrderResponse, []error) { currencyPairOrders := make(map[string]int) - resp := make(map[string][]PlaceSpotOrderResponse) + resp := make(map[string][]PlaceOrderResponse) for i := range request { currencyPairOrders[request[i].InstrumentID]++ } @@ -556,10 +562,7 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d o.Name) } - utcTime := time.Now().UTC() - iso := utcTime.String() - isoBytes := []byte(iso) - iso = string(isoBytes[:10]) + "T" + string(isoBytes[11:23]) + "Z" + utcTime := time.Now().UTC().Format(time.RFC3339) payload := []byte("") if data != nil { @@ -584,11 +587,11 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d signPath := fmt.Sprintf("/%v%v%v%v", OKGroupAPIPath, requestType, o.APIVersion, requestPath) hmac := crypto.GetHMAC(crypto.HashSHA256, - []byte(iso+httpMethod+signPath+string(payload)), + []byte(utcTime+httpMethod+signPath+string(payload)), []byte(o.API.Credentials.Secret)) headers["OK-ACCESS-KEY"] = o.API.Credentials.Key headers["OK-ACCESS-SIGN"] = crypto.Base64Encode(hmac) - headers["OK-ACCESS-TIMESTAMP"] = iso + headers["OK-ACCESS-TIMESTAMP"] = utcTime headers["OK-ACCESS-PASSPHRASE"] = o.API.Credentials.ClientID } diff --git a/exchanges/okgroup/okgroup_types.go b/exchanges/okgroup/okgroup_types.go index 076e656f..6a64b4a1 100644 --- a/exchanges/okgroup/okgroup_types.go +++ b/exchanges/okgroup/okgroup_types.go @@ -6,13 +6,21 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" ) +// Order types +const ( + NormalOrder = iota + PostOnlyOrder + FillOrKillOrder + ImmediateOrCancelOrder +) + // GetAccountCurrenciesResponse response data for GetAccountCurrencies type GetAccountCurrenciesResponse struct { - CanDeposit int64 `json:"can_deposit"` - CanWithdraw int64 `json:"can_withdraw"` - Currency string `json:"currency"` - MinWithdrawal float64 `json:"min_withdrawal"` Name string `json:"name"` + Currency string `json:"currency"` + CanDeposit int `json:"can_deposit,string"` + CanWithdraw int `json:"can_withdraw,string"` + MinWithdrawal float64 `json:"min_withdrawal,string"` } // WalletInformationResponse response data for WalletInformation @@ -64,22 +72,22 @@ type AccountWithdrawResponse struct { // GetAccountWithdrawalFeeResponse response data for GetAccountWithdrawalFee type GetAccountWithdrawalFeeResponse struct { Currency string `json:"currency"` - MinFee float64 `json:"min_fee"` - MaxFee float64 `json:"max_fee"` + MinFee float64 `json:"min_fee,string"` + MaxFee float64 `json:"max_fee,string"` } // WithdrawalHistoryResponse response data for WithdrawalHistoryResponse type WithdrawalHistoryResponse struct { - Amount float64 `json:"amount"` - Currency string `json:"currency"` - Fee string `json:"fee"` - From string `json:"from"` - Status int64 `json:"status"` - Timestamp time.Time `json:"timestamp"` - To string `json:"to"` - Txid string `json:"txid"` - PaymentID string `json:"payment_id"` - Tag string `json:"tag"` + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + Fee string `json:"fee"` + From string `json:"from"` + Status int64 `json:"status,string"` + Timestamp time.Time `json:"timestamp"` + To string `json:"to"` + TransactionID string `json:"txid"` + PaymentID string `json:"payment_id"` + Tag string `json:"tag"` } // GetAccountBillDetailsRequest request data for GetAccountBillDetailsRequest @@ -112,11 +120,12 @@ type GetDepositAddressResponse struct { // GetAccountDepositHistoryResponse response data for GetAccountDepositHistory type GetAccountDepositHistoryResponse struct { - Amount float64 `json:"amount"` + Amount float64 `json:"amount,string"` Currency string `json:"currency"` - Status int64 `json:"status"` - Timestamp time.Time `json:"timestamp"` + From string `json:"from"` To string `json:"to"` + Timestamp time.Time `json:"timestamp"` + Status int64 `json:"status,string"` TransactionID string `json:"txid"` } @@ -156,20 +165,21 @@ type SpotBillDetails struct { InstrumentID string `json:"instrument_id"` } -// PlaceSpotOrderRequest request data for PlaceSpotOrder -type PlaceSpotOrderRequest struct { +// PlaceOrderRequest request data for placing an order +type PlaceOrderRequest struct { ClientOID string `json:"client_oid,omitempty"` // the order ID customized by yourself Type string `json:"type"` // limit / market(default: limit) Side string `json:"side"` // buy or sell InstrumentID string `json:"instrument_id"` // trading pair - MarginTrading string `json:"margin_trading"` // order type (The request value is 1) + MarginTrading string `json:"margin_trading"` // margin trading + OrderType string `json:"order_type"` // order type (0: Normal order (Unfilled and 0 imply normal limit order) 1: Post only 2: Fill or Kill 3: Immediate Or Cancel Size string `json:"size"` Notional string `json:"notional,omitempty"` // Price string `json:"price,omitempty"` // price (Limit order only) } -// PlaceSpotOrderResponse response data for PlaceSpotOrder -type PlaceSpotOrderResponse struct { +// PlaceOrderResponse response data for PlaceSpotOrder +type PlaceOrderResponse struct { ClientOid string `json:"client_oid"` OrderID string `json:"order_id"` Result bool `json:"result"` @@ -1497,7 +1507,7 @@ type WebsocketSpotOrderResponse struct { Notional float64 `json:"notional,string"` Size float64 `json:"size,string"` Status string `json:"status"` - MarginTrading int64 `json:"margin_trading"` + MarginTrading int64 `json:"margin_trading,omitempty"` Type string `json:"type"` // Price A member, but part already exists as part of WebsocketDataResponse // InstrumentID A member, but part already exists as part of WebsocketDataResponse diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index 8aa944ad..19ee8b79 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -267,19 +267,21 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) { // WsLogin sends a login request to websocket to enable access to authenticated endpoints func (o *OKGroup) WsLogin() error { o.Websocket.SetCanUseAuthenticatedEndpoints(true) - utcTime := time.Now().UTC() - unixTime := utcTime.Unix() + unixTime := time.Now().UTC().Unix() signPath := "/users/self/verify" hmac := crypto.GetHMAC(crypto.HashSHA256, - []byte(fmt.Sprintf("%v", unixTime)+http.MethodGet+signPath), - []byte(o.API.Credentials.Secret)) + []byte(strconv.FormatInt(unixTime, 10)+http.MethodGet+signPath), + []byte(o.API.Credentials.Secret), + ) base64 := crypto.Base64Encode(hmac) request := WebsocketEventRequest{ Operation: "login", - Arguments: []string{o.API.Credentials.Key, + Arguments: []string{ + o.API.Credentials.Key, o.API.Credentials.ClientID, - fmt.Sprintf("%v", unixTime), - base64}, + strconv.FormatInt(unixTime, 10), + base64, + }, } err := o.WebsocketConn.SendMessage(request) if err != nil { @@ -470,7 +472,7 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) { timeData, err := time.Parse(time.RFC3339Nano, response.Data[i].WebsocketCandleResponse.Candle[0]) if err != nil { - log.Warnf(log.ExchangeSys, + log.Errorf(log.ExchangeSys, "%v Time data could not be parsed: %v", o.Name, response.Data[i].Candle[0]) diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index d9f1a032..91658ba0 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -236,7 +236,7 @@ func (o *OKGroup) GetFundingHistory() (resp []exchange.FundHistory, err error) { ExchangeName: o.Name, Status: OrderStatus[accountWithdrawlHistory[i].Status], Timestamp: accountWithdrawlHistory[i].Timestamp, - TransferID: accountWithdrawlHistory[i].Txid, + TransferID: accountWithdrawlHistory[i].TransactionID, TransferType: "withdrawal", }) } @@ -255,11 +255,11 @@ func (o *OKGroup) SubmitOrder(s *order.Submit) (resp order.SubmitResponse, err e return resp, err } - request := PlaceSpotOrderRequest{ + request := PlaceOrderRequest{ ClientOID: s.ClientID, InstrumentID: o.FormatExchangeCurrency(s.Pair, asset.Spot).String(), - Side: strings.ToLower(s.OrderSide.String()), - Type: strings.ToLower(s.OrderType.String()), + Side: s.OrderSide.Lower(), + Type: s.OrderType.Lower(), Size: strconv.FormatFloat(s.Amount, 'f', -1, 64), } if s.OrderType == order.Limit { diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 927f4ff2..eb28f579 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -129,25 +129,27 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (OrderbookAll, e return oba, err } for currency, orderbook := range resp.Data { - ob := Orderbook{} + var ob Orderbook for x := range orderbook.Asks { - data := orderbook.Asks[x] - price, err := strconv.ParseFloat(data[0].(string), 64) + price, err := strconv.ParseFloat(orderbook.Asks[x][0].(string), 64) if err != nil { return oba, err } - amount := data[1].(float64) - ob.Asks = append(ob.Asks, OrderbookItem{Price: price, Amount: amount}) + ob.Asks = append(ob.Asks, OrderbookItem{ + Price: price, + Amount: orderbook.Asks[x][1].(float64), + }) } for x := range orderbook.Bids { - data := orderbook.Bids[x] - price, err := strconv.ParseFloat(data[0].(string), 64) + price, err := strconv.ParseFloat(orderbook.Bids[x][0].(string), 64) if err != nil { return oba, err } - amount := data[1].(float64) - ob.Bids = append(ob.Bids, OrderbookItem{Price: price, Amount: amount}) + ob.Asks = append(ob.Asks, OrderbookItem{ + Price: price, + Amount: orderbook.Bids[x][1].(float64), + }) } oba.Data[currency] = Orderbook{Bids: ob.Bids, Asks: ob.Asks} } diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index 62e2186c..648a64c8 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" @@ -186,11 +187,11 @@ func (p *Poloniex) WsHandleData() { trade.Symbol = currencyIDMap[chanID] trade.TradeID, _ = strconv.ParseInt(dataL3[1].(string), 10, 64) // 1 for buy 0 for sell - side := "buy" + side := order.Buy if dataL3[2].(float64) != 1 { - side = "sell" + side = order.Sell } - trade.Side = side + trade.Side = side.Lower() trade.Volume, err = strconv.ParseFloat(dataL3[3].(string), 64) if err != nil { p.Websocket.DataHandler <- err diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 9bccee98..02ed3818 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -296,21 +296,18 @@ func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.I var obItems []orderbook.Item for y := range data.Bids { - obData := data.Bids[y] - obItems = append(obItems, - orderbook.Item{Amount: obData.Amount, Price: obData.Price}) + obItems = append(obItems, orderbook.Item{ + Amount: data.Bids[y].Amount, Price: data.Bids[y].Price}) } - orderBook.Bids = obItems + obItems = []orderbook.Item{} for y := range data.Asks { - obData := data.Asks[y] - obItems = append(obItems, - orderbook.Item{Amount: obData.Amount, Price: obData.Price}) + obItems = append(obItems, orderbook.Item{ + Amount: data.Asks[y].Amount, Price: data.Asks[y].Price}) } - - orderBook.Pair = x orderBook.Asks = obItems + orderBook.Pair = x orderBook.ExchangeName = p.Name orderBook.AssetType = assetType @@ -350,8 +347,7 @@ func (p *Poloniex) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (p *Poloniex) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. @@ -507,7 +503,7 @@ func (p *Poloniex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, orderSide := order.Side(strings.ToUpper(resp.Data[key][i].Type)) orderDate, err := time.Parse(poloniexDateLayout, resp.Data[key][i].Date) if err != nil { - log.Warnf(log.ExchangeSys, + log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", p.Name, "GetActiveOrders", @@ -554,7 +550,7 @@ func (p *Poloniex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, orderDate, err := time.Parse(poloniexDateLayout, resp.Data[key][i].Date) if err != nil { - log.Warnf(log.ExchangeSys, + log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", p.Name, "GetActiveOrders", diff --git a/exchanges/support.go b/exchanges/support.go index d9e0e0a1..37e0ceed 100644 --- a/exchanges/support.go +++ b/exchanges/support.go @@ -25,6 +25,7 @@ var Exchanges = []string{ "btc markets", "btse", "coinbasepro", + "coinbene", "coinut", "exmo", "gateio", diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index bd7ed666..400383a4 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -299,8 +299,7 @@ func (y *Yobit) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (y *Yobit) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 60108f15..2f9608e9 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -268,23 +268,24 @@ func (z *ZB) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Ba // UpdateOrderbook updates and returns the orderbook for a currency pair func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - currency := z.FormatExchangeCurrency(p, assetType).String() - - orderbookNew, err := z.GetOrderbook(currency) + orderbookNew, err := z.GetOrderbook(z.FormatExchangeCurrency(p, + assetType).String()) if err != nil { return orderBook, err } for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, - orderbook.Item{Amount: data[1], Price: data[0]}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[x][1], + Price: orderbookNew.Bids[x][0], + }) } for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, - orderbook.Item{Amount: data[1], Price: data[0]}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: orderbookNew.Asks[x][1], + Price: orderbookNew.Asks[x][0], + }) } orderBook.Pair = p @@ -338,8 +339,7 @@ func (z *ZB) GetAccountInfo() (exchange.AccountInfo, error) { // GetFundingHistory returns funding history, deposits and // withdrawals func (z *ZB) GetFundingHistory() ([]exchange.FundHistory, error) { - var fundHistory []exchange.FundHistory - return fundHistory, common.ErrFunctionNotSupported + return nil, common.ErrFunctionNotSupported } // GetExchangeHistory returns historic trade data since exchange opening. diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index 00cbb677..a5796e2b 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -9,6 +9,8 @@ import ( proto "github.com/golang/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" math "math" ) @@ -5188,316 +5190,316 @@ func init() { proto.RegisterType((*GetExchangeTickerStreamRequest)(nil), "gctrpc.GetExchangeTickerStreamRequest") proto.RegisterType((*GetAuditEventRequest)(nil), "gctrpc.GetAuditEventRequest") proto.RegisterType((*GetAuditEventResponse)(nil), "gctrpc.GetAuditEventResponse") - proto.RegisterType((*AuditEvent)(nil), "gctrpc.audit_event") + proto.RegisterType((*AuditEvent)(nil), "gctrpc.AuditEvent") } func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 4844 bytes of a gzipped FileDescriptorProto + // 4838 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3c, 0x4d, 0x8f, 0x24, 0x47, - 0x56, 0xca, 0xea, 0x9e, 0xee, 0xae, 0x57, 0xd5, 0x5f, 0xd1, 0x5f, 0x35, 0xd5, 0xdd, 0xf3, 0x91, - 0x5e, 0x8f, 0x67, 0xfc, 0xd1, 0x63, 0x8f, 0x07, 0xd6, 0xac, 0xcd, 0x2e, 0xed, 0x1e, 0xbb, 0xd7, - 0xd8, 0xeb, 0x69, 0xb2, 0x67, 0xc7, 0x92, 0x17, 0xb9, 0xc8, 0xae, 0x8c, 0xea, 0x4e, 0xa6, 0x2a, - 0x33, 0x9d, 0x19, 0xd5, 0x3d, 0x65, 0x40, 0xac, 0x2c, 0x81, 0x38, 0x20, 0x38, 0xac, 0x90, 0x40, - 0xe2, 0xc4, 0x11, 0x89, 0x0b, 0xe2, 0xc4, 0x61, 0xc5, 0x15, 0x71, 0xe4, 0xc2, 0x0f, 0x40, 0xdc, - 0x00, 0x69, 0x25, 0x2e, 0x9c, 0x50, 0xbc, 0xf8, 0xc8, 0x88, 0xcc, 0xac, 0xea, 0xea, 0xdd, 0x59, - 0x73, 0xb1, 0x2b, 0x5f, 0xbc, 0x78, 0xef, 0xc5, 0x8b, 0x17, 0x2f, 0xde, 0x7b, 0xf1, 0x7a, 0xa0, - 0x9e, 0x26, 0xdd, 0xbd, 0x24, 0x8d, 0x59, 0x4c, 0xe6, 0x4e, 0xbb, 0x2c, 0x4d, 0xba, 0xed, 0x9d, - 0xd3, 0x38, 0x3e, 0xed, 0xd3, 0xfb, 0x7e, 0x12, 0xde, 0xf7, 0xa3, 0x28, 0x66, 0x3e, 0x0b, 0xe3, - 0x28, 0x13, 0x58, 0xee, 0x0a, 0x2c, 0x1d, 0x52, 0xf6, 0x51, 0xd4, 0x8b, 0x3d, 0xfa, 0xe5, 0x90, - 0x66, 0xcc, 0xfd, 0x87, 0x59, 0x58, 0xd6, 0xa0, 0x2c, 0x89, 0xa3, 0x8c, 0x92, 0x4d, 0x98, 0x1b, - 0x26, 0x2c, 0x1c, 0xd0, 0x96, 0x73, 0xcb, 0xb9, 0x5b, 0xf7, 0xe4, 0x17, 0xb9, 0x0f, 0x6b, 0xfe, - 0xb9, 0x1f, 0xf6, 0xfd, 0x93, 0x3e, 0xed, 0xd0, 0xe7, 0xdd, 0x33, 0x3f, 0x3a, 0xa5, 0x59, 0xab, - 0x76, 0xcb, 0xb9, 0x3b, 0xe3, 0x11, 0x3d, 0xf4, 0x81, 0x1a, 0x21, 0xaf, 0xc1, 0x2a, 0x8d, 0x38, - 0x28, 0x30, 0xd0, 0x67, 0x10, 0x7d, 0x45, 0x0e, 0xe4, 0xc8, 0x0f, 0x61, 0x33, 0xa0, 0x3d, 0x7f, - 0xd8, 0x67, 0x9d, 0x5e, 0x9c, 0xd2, 0xe7, 0x9d, 0x24, 0x8d, 0xcf, 0xc3, 0x80, 0xa6, 0xad, 0x59, - 0x94, 0x62, 0x5d, 0x8e, 0x7e, 0xc8, 0x07, 0x8f, 0xe4, 0x18, 0x79, 0x00, 0x1b, 0x7a, 0x56, 0xe8, - 0xb3, 0x4e, 0x77, 0x98, 0xa6, 0x34, 0xea, 0x8e, 0x5a, 0xd7, 0x70, 0xd2, 0x9a, 0x9a, 0x14, 0xfa, - 0xec, 0x40, 0x0e, 0x91, 0xcf, 0x60, 0x25, 0x1b, 0x9e, 0x64, 0xa3, 0x8c, 0xd1, 0x41, 0x27, 0x63, - 0x3e, 0x1b, 0x66, 0xad, 0xb9, 0x5b, 0x33, 0x77, 0x1b, 0x0f, 0x5e, 0xdf, 0x13, 0x6a, 0xdc, 0x2b, - 0xa8, 0x64, 0xef, 0x58, 0xe1, 0x1f, 0x23, 0xfa, 0x07, 0x11, 0x4b, 0x47, 0xde, 0x72, 0x66, 0x43, - 0xc9, 0xa7, 0xb0, 0x98, 0x26, 0xdd, 0x0e, 0x8d, 0x82, 0x24, 0x0e, 0x23, 0x96, 0xb5, 0xe6, 0x91, - 0xea, 0xbd, 0x71, 0x54, 0xbd, 0xa4, 0xfb, 0x81, 0xc2, 0x15, 0x24, 0x9b, 0xa9, 0x01, 0x6a, 0xbf, - 0x0f, 0xeb, 0x55, 0x8c, 0xc9, 0x0a, 0xcc, 0x3c, 0xa3, 0x23, 0xb9, 0x3b, 0xfc, 0x27, 0x59, 0x87, - 0x6b, 0xe7, 0x7e, 0x7f, 0x48, 0x71, 0x33, 0x16, 0x3c, 0xf1, 0xf1, 0x9d, 0xda, 0x3b, 0x4e, 0xfb, - 0x09, 0xac, 0x96, 0xd8, 0x54, 0x10, 0xb8, 0x67, 0x12, 0x68, 0x3c, 0x58, 0x53, 0x22, 0x7b, 0x47, - 0x07, 0x6a, 0xae, 0x41, 0xd5, 0xbd, 0x0d, 0x37, 0x0f, 0x29, 0x3b, 0x88, 0x07, 0x83, 0x61, 0x14, - 0x76, 0xd1, 0xc6, 0x3c, 0xda, 0xf7, 0x47, 0x34, 0xcd, 0x94, 0x65, 0x7d, 0x0a, 0xeb, 0x55, 0xe3, - 0xa4, 0x05, 0xf3, 0x72, 0xef, 0x91, 0xff, 0x82, 0xa7, 0x3e, 0xc9, 0x0e, 0xd4, 0xbb, 0x71, 0x14, - 0xd1, 0x2e, 0xa3, 0x81, 0x5c, 0x48, 0x0e, 0x70, 0xff, 0xb8, 0x06, 0xb7, 0xc6, 0xf3, 0x94, 0xa6, - 0xfb, 0x15, 0x6c, 0x76, 0x4d, 0x84, 0x4e, 0x2a, 0x31, 0x5a, 0x0e, 0x6e, 0xc5, 0x81, 0xb1, 0x15, - 0x13, 0x29, 0xed, 0x55, 0x8e, 0x8a, 0x4d, 0xda, 0xe8, 0x56, 0x8d, 0xb5, 0x7b, 0xd0, 0x1e, 0x3f, - 0xa9, 0x42, 0xe5, 0x0f, 0x6c, 0x95, 0xef, 0x28, 0xd1, 0xaa, 0x88, 0x98, 0xba, 0xff, 0x36, 0x6c, - 0x1d, 0xd2, 0x88, 0xa6, 0x61, 0x57, 0x1b, 0x87, 0xd4, 0x39, 0xd7, 0xa0, 0xb6, 0x49, 0xc9, 0x2a, - 0x07, 0xb8, 0x6d, 0x68, 0x95, 0x27, 0x8a, 0xe5, 0xba, 0x9b, 0xb0, 0x7e, 0x48, 0x99, 0x86, 0xeb, - 0x5d, 0xfc, 0xa9, 0x03, 0x1b, 0x38, 0x90, 0x9d, 0x64, 0x23, 0x31, 0x20, 0x55, 0xfd, 0x3b, 0xb0, - 0xaa, 0x49, 0x67, 0xea, 0x18, 0x09, 0x2d, 0xbf, 0x6d, 0x68, 0xb9, 0x3c, 0x33, 0x3f, 0x4c, 0x99, - 0x79, 0x9a, 0xf2, 0x33, 0x29, 0xc1, 0xed, 0x03, 0xd8, 0xa8, 0x44, 0xbd, 0x8a, 0xfd, 0xbb, 0x2d, - 0xd8, 0x3c, 0xa4, 0xcc, 0x30, 0x63, 0xc3, 0x40, 0x1b, 0x06, 0x98, 0xdb, 0x65, 0xc6, 0xfc, 0x94, - 0xe5, 0x76, 0x29, 0x3f, 0xc9, 0xcb, 0xb0, 0xd4, 0x0f, 0x33, 0x46, 0xa3, 0x8e, 0x1f, 0x04, 0x29, - 0xcd, 0x84, 0xcb, 0xab, 0x7b, 0x8b, 0x02, 0xba, 0x2f, 0x80, 0xee, 0x3f, 0x3a, 0x7c, 0x63, 0x0a, - 0xac, 0xa4, 0xb2, 0x3e, 0x81, 0x7a, 0xee, 0x15, 0x84, 0x92, 0xf6, 0x0c, 0x25, 0x55, 0xcd, 0xd9, - 0x2b, 0xb8, 0x86, 0x9c, 0x40, 0xfb, 0xb7, 0x60, 0xe9, 0x45, 0x1f, 0xe8, 0x77, 0xa0, 0x2d, 0x6d, - 0x43, 0x79, 0xe4, 0x4f, 0xfd, 0x01, 0x55, 0x76, 0xd5, 0x86, 0x05, 0xe5, 0xc0, 0x25, 0x0f, 0xfd, - 0xed, 0xee, 0xc2, 0x76, 0xe5, 0x4c, 0x69, 0x58, 0xf7, 0x61, 0xed, 0x90, 0x32, 0xed, 0xe6, 0x15, - 0xc5, 0xb1, 0x5e, 0xc0, 0x7d, 0x88, 0x96, 0x68, 0x4c, 0x90, 0x2a, 0xdc, 0x81, 0x7a, 0x7e, 0x89, - 0x48, 0xdb, 0xd6, 0x00, 0xf7, 0x01, 0x9a, 0xa9, 0x9a, 0xf5, 0xf8, 0xc9, 0x91, 0x47, 0xc5, 0xb4, - 0xeb, 0xb0, 0x10, 0xb3, 0xa4, 0xd3, 0x8d, 0x03, 0x25, 0xfa, 0x7c, 0xcc, 0x92, 0x83, 0x38, 0xa0, - 0xd2, 0x34, 0x8c, 0x39, 0xda, 0x34, 0xfe, 0x46, 0x6c, 0xa5, 0x3d, 0x24, 0xe5, 0xf8, 0x4d, 0xa8, - 0x2b, 0x82, 0x6a, 0x2b, 0xdf, 0x30, 0xb6, 0xb2, 0x6a, 0xce, 0xde, 0x63, 0xc1, 0x51, 0xee, 0xe4, - 0x82, 0x14, 0x20, 0x6b, 0xbf, 0x0b, 0x8b, 0xd6, 0xd0, 0x65, 0x96, 0x5d, 0x37, 0xb7, 0xec, 0x21, - 0x6c, 0x3e, 0x0a, 0x33, 0xf3, 0xc6, 0x9d, 0x66, 0xbb, 0xbe, 0x80, 0xa5, 0x23, 0x3f, 0x4c, 0xb3, - 0xe3, 0x61, 0x92, 0xc4, 0x68, 0xde, 0xaf, 0xc0, 0x72, 0x7e, 0xad, 0x27, 0x7c, 0x4c, 0x4e, 0x5a, - 0xd2, 0x60, 0x9c, 0x41, 0x5e, 0x82, 0x45, 0x75, 0x9d, 0x0b, 0x34, 0x21, 0x52, 0x53, 0x02, 0x11, - 0xc9, 0xfd, 0x7a, 0xd6, 0x52, 0x9d, 0x15, 0x58, 0x10, 0x98, 0x8d, 0x7c, 0x1d, 0x56, 0xe0, 0x6f, - 0xd3, 0x10, 0x6a, 0xf6, 0x75, 0xd0, 0x82, 0xf9, 0x73, 0x9a, 0x9e, 0xc4, 0x19, 0xc5, 0x98, 0x61, - 0xc1, 0x53, 0x9f, 0x5c, 0x90, 0x61, 0x16, 0x46, 0xa7, 0x9d, 0xcc, 0x8f, 0x82, 0x93, 0xf8, 0x39, - 0x46, 0x08, 0x0b, 0x5e, 0x13, 0x81, 0xc7, 0x02, 0x46, 0x6e, 0x43, 0xf3, 0x8c, 0xb1, 0xa4, 0xc3, - 0x43, 0x97, 0x78, 0xc8, 0x64, 0x40, 0xd0, 0xe0, 0xb0, 0x27, 0x02, 0xc4, 0x0f, 0x36, 0xa2, 0x0c, - 0x33, 0x9a, 0xfa, 0xa7, 0x34, 0x62, 0xad, 0x39, 0x71, 0xb0, 0x39, 0xf4, 0x87, 0x0a, 0x48, 0x76, - 0x01, 0x10, 0x2d, 0x49, 0xe3, 0xe7, 0xa3, 0xd6, 0xbc, 0x30, 0x3d, 0x0e, 0x39, 0xe2, 0x00, 0xae, - 0xbf, 0x13, 0x3f, 0xa3, 0x2a, 0xf4, 0x08, 0x69, 0xd6, 0x5a, 0x10, 0xfa, 0xe3, 0xe0, 0x03, 0x0d, - 0x25, 0x1d, 0x1e, 0x77, 0x48, 0xad, 0x77, 0xfc, 0x2c, 0xa3, 0x2c, 0x6b, 0xd5, 0xd1, 0x80, 0x1e, - 0x56, 0x18, 0x50, 0x21, 0xfe, 0x90, 0xf3, 0xf6, 0x71, 0x9a, 0x8e, 0x3f, 0x2c, 0x28, 0x8f, 0xb7, - 0xfc, 0x21, 0x3b, 0xa3, 0x11, 0xe3, 0xb7, 0x07, 0x67, 0x92, 0x84, 0x2d, 0x40, 0xdd, 0xac, 0x58, - 0x03, 0xfb, 0x49, 0xd8, 0xfe, 0x9c, 0x07, 0x17, 0x65, 0xaa, 0x15, 0x26, 0xf8, 0xba, 0xed, 0x4a, - 0x36, 0x95, 0xb0, 0xb6, 0x1d, 0x99, 0xa6, 0x79, 0x01, 0x2b, 0x87, 0x94, 0x3d, 0x09, 0xbb, 0xcf, - 0x68, 0x3a, 0x85, 0x51, 0x92, 0xbb, 0x30, 0xcb, 0x2d, 0x4a, 0x32, 0x58, 0xd7, 0x37, 0xa1, 0x8c, - 0xd8, 0x38, 0x23, 0x0f, 0x31, 0xf8, 0x5e, 0xa0, 0xe6, 0x3a, 0x6c, 0x94, 0x08, 0xbb, 0xa8, 0x7b, - 0x75, 0x84, 0x3c, 0x19, 0x25, 0xd4, 0x7d, 0x0a, 0x4d, 0x73, 0x12, 0x77, 0x1a, 0x01, 0xed, 0x87, - 0x83, 0x90, 0xd1, 0x54, 0x39, 0x0d, 0x0d, 0xe0, 0xf6, 0xc8, 0xb7, 0x48, 0xda, 0x31, 0xfe, 0xe6, - 0xe7, 0xed, 0xcb, 0x61, 0xcc, 0x14, 0x6d, 0xf1, 0xe1, 0xfe, 0x45, 0x0d, 0x96, 0xd4, 0x72, 0xa4, - 0x31, 0x2b, 0x99, 0x9d, 0x4b, 0x65, 0xbe, 0x0d, 0xcd, 0xbe, 0x9f, 0xb1, 0xce, 0x30, 0x09, 0x7c, - 0x15, 0xda, 0xcc, 0x78, 0x0d, 0x0e, 0xfb, 0xa1, 0x00, 0x71, 0x8b, 0x56, 0x91, 0x2b, 0x9e, 0x2d, - 0xc9, 0xbd, 0xd9, 0x35, 0x17, 0x43, 0x60, 0x96, 0xcf, 0x41, 0x6b, 0x77, 0x3c, 0xfc, 0xcd, 0x61, - 0x67, 0xe1, 0xe9, 0x19, 0x5a, 0xb7, 0xe3, 0xe1, 0x6f, 0xbe, 0x83, 0xfd, 0xf8, 0x02, 0x6d, 0xd9, - 0xf1, 0xf8, 0x4f, 0x0e, 0x39, 0x09, 0x03, 0x34, 0x5d, 0xc7, 0xe3, 0x3f, 0x39, 0xc4, 0xcf, 0x9e, - 0xa1, 0xa1, 0x3a, 0x1e, 0xff, 0xc9, 0xa3, 0xfe, 0xf3, 0xb8, 0x3f, 0x1c, 0xd0, 0x56, 0x1d, 0x81, - 0xf2, 0x8b, 0x6c, 0x43, 0x3d, 0x49, 0xc3, 0x2e, 0xed, 0xf8, 0xec, 0x0c, 0x8d, 0xc9, 0xf1, 0x16, - 0x10, 0xb0, 0xcf, 0xce, 0xdc, 0x35, 0x58, 0xd5, 0x1b, 0xad, 0xbd, 0xe7, 0x67, 0x30, 0x2f, 0x21, - 0x13, 0x37, 0xfd, 0x4d, 0x98, 0x67, 0x02, 0xad, 0x55, 0xc3, 0x53, 0xa0, 0x0d, 0xcb, 0xd6, 0xb4, - 0xa7, 0xd0, 0xdc, 0xef, 0x01, 0x31, 0xb9, 0xc9, 0x8d, 0xb8, 0x97, 0xd3, 0x11, 0xee, 0x78, 0xd9, - 0xa6, 0x93, 0xe5, 0x04, 0xbe, 0xc2, 0xcb, 0xe8, 0x71, 0x1a, 0x70, 0x47, 0x12, 0x3f, 0xfb, 0x46, - 0x4d, 0xf3, 0x07, 0xb0, 0xa8, 0x19, 0x7f, 0xc4, 0xe8, 0x80, 0x2b, 0xdc, 0x1f, 0xc4, 0xc3, 0x88, + 0x56, 0xca, 0xea, 0xcf, 0x7a, 0x5d, 0xfd, 0x15, 0xfd, 0x55, 0x53, 0xdd, 0x3d, 0x3d, 0x93, 0x5e, + 0x8f, 0x67, 0x66, 0xbd, 0x3d, 0xf6, 0x78, 0x60, 0xcd, 0xda, 0xec, 0xd2, 0x6e, 0xdb, 0xbd, 0xc6, + 0x5e, 0x4f, 0x93, 0x3d, 0x3b, 0x96, 0xbc, 0xc8, 0x45, 0x76, 0x65, 0x54, 0x77, 0x32, 0x55, 0x99, + 0xe9, 0xcc, 0xa8, 0xee, 0x29, 0x03, 0x62, 0x65, 0x09, 0xc4, 0x01, 0xc1, 0x61, 0x85, 0x04, 0x12, + 0x27, 0x8e, 0x48, 0x5c, 0x10, 0x27, 0x0e, 0x2b, 0xae, 0x88, 0x23, 0x17, 0x7e, 0x00, 0xe2, 0x06, + 0x48, 0x2b, 0x71, 0xe1, 0x84, 0xe2, 0xc5, 0x47, 0x46, 0x64, 0x66, 0x55, 0x57, 0xef, 0xce, 0x0e, + 0x17, 0xbb, 0xf2, 0xc5, 0x8b, 0xf7, 0x5e, 0xbc, 0x78, 0xf1, 0xe2, 0xbd, 0x17, 0xaf, 0x07, 0xea, + 0x69, 0xd2, 0xd9, 0x4f, 0xd2, 0x98, 0xc5, 0x64, 0xf6, 0xac, 0xc3, 0xd2, 0xa4, 0xd3, 0xda, 0x39, + 0x8b, 0xe3, 0xb3, 0x1e, 0x7d, 0xe0, 0x27, 0xe1, 0x03, 0x3f, 0x8a, 0x62, 0xe6, 0xb3, 0x30, 0x8e, + 0x32, 0x81, 0xe5, 0xae, 0xc0, 0xd2, 0x11, 0x65, 0x1f, 0x45, 0xdd, 0xd8, 0xa3, 0x5f, 0x0e, 0x68, + 0xc6, 0xdc, 0x7f, 0x98, 0x86, 0x65, 0x0d, 0xca, 0x92, 0x38, 0xca, 0x28, 0xd9, 0x84, 0xd9, 0x41, + 0xc2, 0xc2, 0x3e, 0x6d, 0x3a, 0xb7, 0x9c, 0xbb, 0x75, 0x4f, 0x7e, 0x91, 0x07, 0xb0, 0xe6, 0x5f, + 0xf8, 0x61, 0xcf, 0x3f, 0xed, 0xd1, 0x36, 0x7d, 0xde, 0x39, 0xf7, 0xa3, 0x33, 0x9a, 0x35, 0x6b, + 0xb7, 0x9c, 0xbb, 0x53, 0x1e, 0xd1, 0x43, 0x1f, 0xa8, 0x11, 0xf2, 0x4d, 0x58, 0xa5, 0x11, 0x07, + 0x05, 0x06, 0xfa, 0x14, 0xa2, 0xaf, 0xc8, 0x81, 0x1c, 0xf9, 0x11, 0x6c, 0x06, 0xb4, 0xeb, 0x0f, + 0x7a, 0xac, 0xdd, 0x8d, 0x53, 0xfa, 0xbc, 0x9d, 0xa4, 0xf1, 0x45, 0x18, 0xd0, 0xb4, 0x39, 0x8d, + 0x52, 0xac, 0xcb, 0xd1, 0x0f, 0xf9, 0xe0, 0xb1, 0x1c, 0x23, 0x0f, 0x61, 0x43, 0xcf, 0x0a, 0x7d, + 0xd6, 0xee, 0x0c, 0xd2, 0x94, 0x46, 0x9d, 0x61, 0x73, 0x06, 0x27, 0xad, 0xa9, 0x49, 0xa1, 0xcf, + 0x0e, 0xe5, 0x10, 0xf9, 0x0c, 0x56, 0xb2, 0xc1, 0x69, 0x36, 0xcc, 0x18, 0xed, 0xb7, 0x33, 0xe6, + 0xb3, 0x41, 0xd6, 0x9c, 0xbd, 0x35, 0x75, 0x77, 0xe1, 0xe1, 0xeb, 0xfb, 0x42, 0x8d, 0xfb, 0x05, + 0x95, 0xec, 0x9f, 0x28, 0xfc, 0x13, 0x44, 0xff, 0x20, 0x62, 0xe9, 0xd0, 0x5b, 0xce, 0x6c, 0x28, + 0xf9, 0x14, 0x16, 0xd3, 0xa4, 0xd3, 0xa6, 0x51, 0x90, 0xc4, 0x61, 0xc4, 0xb2, 0xe6, 0x1c, 0x52, + 0xbd, 0x37, 0x8a, 0xaa, 0x97, 0x74, 0x3e, 0x50, 0xb8, 0x82, 0x64, 0x23, 0x35, 0x40, 0xad, 0xf7, + 0x60, 0xbd, 0x8a, 0x31, 0x59, 0x81, 0xa9, 0x67, 0x74, 0x28, 0x77, 0x87, 0xff, 0x24, 0xeb, 0x30, + 0x73, 0xe1, 0xf7, 0x06, 0x14, 0x37, 0x63, 0xde, 0x13, 0x1f, 0xdf, 0xa9, 0xbd, 0xed, 0xb4, 0x9e, + 0xc0, 0x6a, 0x89, 0x4d, 0x05, 0x81, 0x7b, 0x26, 0x81, 0x85, 0x87, 0x6b, 0x4a, 0x64, 0xef, 0xf8, + 0x50, 0xcd, 0x35, 0xa8, 0xba, 0xb7, 0x61, 0xef, 0x88, 0xb2, 0xc3, 0xb8, 0xdf, 0x1f, 0x44, 0x61, + 0x07, 0x6d, 0xcc, 0xa3, 0x3d, 0x7f, 0x48, 0xd3, 0x4c, 0x59, 0xd6, 0xa7, 0xb0, 0x5e, 0x35, 0x4e, + 0x9a, 0x30, 0x27, 0xf7, 0x1e, 0xf9, 0xcf, 0x7b, 0xea, 0x93, 0xec, 0x40, 0xbd, 0x13, 0x47, 0x11, + 0xed, 0x30, 0x1a, 0xc8, 0x85, 0xe4, 0x00, 0xf7, 0x8f, 0x6b, 0x70, 0x6b, 0x34, 0x4f, 0x69, 0xba, + 0x5f, 0xc1, 0x66, 0xc7, 0x44, 0x68, 0xa7, 0x12, 0xa3, 0xe9, 0xe0, 0x56, 0x1c, 0x1a, 0x5b, 0x31, + 0x96, 0xd2, 0x7e, 0xe5, 0xa8, 0xd8, 0xa4, 0x8d, 0x4e, 0xd5, 0x58, 0xab, 0x0b, 0xad, 0xd1, 0x93, + 0x2a, 0x54, 0xfe, 0xd0, 0x56, 0xf9, 0x8e, 0x12, 0xad, 0x8a, 0x88, 0xa9, 0xfb, 0x6f, 0xc3, 0xd6, + 0x11, 0x8d, 0x68, 0x1a, 0x76, 0xb4, 0x71, 0x48, 0x9d, 0x73, 0x0d, 0x6a, 0x9b, 0x94, 0xac, 0x72, + 0x80, 0xdb, 0x82, 0x66, 0x79, 0xa2, 0x58, 0xae, 0xbb, 0x09, 0xeb, 0x47, 0x94, 0x69, 0xb8, 0xde, + 0xc5, 0x9f, 0x3a, 0xb0, 0x81, 0x03, 0xd9, 0x69, 0x36, 0x14, 0x03, 0x52, 0xd5, 0xbf, 0x03, 0xab, + 0x9a, 0x74, 0xa6, 0x8e, 0x91, 0xd0, 0xf2, 0x5b, 0x86, 0x96, 0xcb, 0x33, 0xf3, 0xc3, 0x94, 0x99, + 0xa7, 0x29, 0x3f, 0x93, 0x12, 0xdc, 0x3a, 0x84, 0x8d, 0x4a, 0xd4, 0xeb, 0xd8, 0xbf, 0xdb, 0x84, + 0xcd, 0x23, 0xca, 0x0c, 0x33, 0x36, 0x0c, 0x74, 0xc1, 0x00, 0x73, 0xbb, 0xcc, 0x98, 0x9f, 0xb2, + 0xdc, 0x2e, 0xe5, 0x27, 0x79, 0x15, 0x96, 0x7a, 0x61, 0xc6, 0x68, 0xd4, 0xf6, 0x83, 0x20, 0xa5, + 0x99, 0x70, 0x79, 0x75, 0x6f, 0x51, 0x40, 0x0f, 0x04, 0xd0, 0xfd, 0x47, 0x87, 0x6f, 0x4c, 0x81, + 0x95, 0x54, 0xd6, 0x27, 0x50, 0xcf, 0xbd, 0x82, 0x50, 0xd2, 0xbe, 0xa1, 0xa4, 0xaa, 0x39, 0xfb, + 0x05, 0xd7, 0x90, 0x13, 0x68, 0xfd, 0x16, 0x2c, 0xbd, 0xe8, 0x03, 0xfd, 0x36, 0xb4, 0xa4, 0x6d, + 0x28, 0x8f, 0xfc, 0xa9, 0xdf, 0xa7, 0xca, 0xae, 0x5a, 0x30, 0xaf, 0x1c, 0xb8, 0xe4, 0xa1, 0xbf, + 0xdd, 0x5d, 0xd8, 0xae, 0x9c, 0x29, 0x0d, 0xeb, 0x01, 0xac, 0x1d, 0x51, 0xa6, 0xdd, 0xbc, 0xa2, + 0x38, 0xd2, 0x0b, 0xb8, 0x8f, 0xd0, 0x12, 0x8d, 0x09, 0x52, 0x85, 0x3b, 0x50, 0xcf, 0x2f, 0x11, + 0x69, 0xdb, 0x1a, 0xe0, 0x3e, 0x44, 0x33, 0x55, 0xb3, 0x1e, 0x3f, 0x39, 0xf6, 0xa8, 0x98, 0x76, + 0x03, 0xe6, 0x63, 0x96, 0xb4, 0x3b, 0x71, 0xa0, 0x44, 0x9f, 0x8b, 0x59, 0x72, 0x18, 0x07, 0x54, + 0x9a, 0x86, 0x31, 0x47, 0x9b, 0xc6, 0xdf, 0x88, 0xad, 0xb4, 0x87, 0xa4, 0x1c, 0xbf, 0x09, 0x75, + 0x45, 0x50, 0x6d, 0xe5, 0xb7, 0x8c, 0xad, 0xac, 0x9a, 0xb3, 0xff, 0x58, 0x70, 0x94, 0x3b, 0x39, + 0x2f, 0x05, 0xc8, 0x5a, 0xef, 0xc0, 0xa2, 0x35, 0x74, 0x95, 0x65, 0xd7, 0xcd, 0x2d, 0x7b, 0x04, + 0x9b, 0xef, 0x87, 0x99, 0x79, 0xe3, 0x4e, 0xb2, 0x5d, 0x5f, 0xc0, 0xd2, 0xb1, 0x1f, 0xa6, 0xd9, + 0xc9, 0x20, 0x49, 0x62, 0x34, 0xef, 0xd7, 0x60, 0x39, 0xbf, 0xd6, 0x13, 0x3e, 0x26, 0x27, 0x2d, + 0x69, 0x30, 0xce, 0x20, 0xaf, 0xc0, 0xa2, 0xba, 0xce, 0x05, 0x9a, 0x10, 0xa9, 0x21, 0x81, 0x88, + 0xe4, 0x7e, 0x3d, 0x6d, 0xa9, 0xce, 0x0a, 0x2c, 0x08, 0x4c, 0x47, 0xbe, 0x0e, 0x2b, 0xf0, 0xb7, + 0x69, 0x08, 0x35, 0xfb, 0x3a, 0x68, 0xc2, 0xdc, 0x05, 0x4d, 0x4f, 0xe3, 0x8c, 0x62, 0xcc, 0x30, + 0xef, 0xa9, 0x4f, 0x2e, 0xc8, 0x20, 0x0b, 0xa3, 0xb3, 0x76, 0xe6, 0x47, 0xc1, 0x69, 0xfc, 0x1c, + 0x23, 0x84, 0x79, 0xaf, 0x81, 0xc0, 0x13, 0x01, 0x23, 0xb7, 0xa1, 0x71, 0xce, 0x58, 0xd2, 0xe6, + 0xa1, 0x4b, 0x3c, 0x60, 0x32, 0x20, 0x58, 0xe0, 0xb0, 0x27, 0x02, 0xc4, 0x0f, 0x36, 0xa2, 0x0c, + 0x32, 0x9a, 0xfa, 0x67, 0x34, 0x62, 0xcd, 0x59, 0x71, 0xb0, 0x39, 0xf4, 0x87, 0x0a, 0x48, 0x76, + 0x01, 0x10, 0x2d, 0x49, 0xe3, 0xe7, 0xc3, 0xe6, 0x9c, 0x30, 0x3d, 0x0e, 0x39, 0xe6, 0x00, 0xae, + 0xbf, 0x53, 0x3f, 0xa3, 0x2a, 0xf4, 0x08, 0x69, 0xd6, 0x9c, 0x17, 0xfa, 0xe3, 0xe0, 0x43, 0x0d, + 0x25, 0x6d, 0x1e, 0x77, 0x48, 0xad, 0xb7, 0xfd, 0x2c, 0xa3, 0x2c, 0x6b, 0xd6, 0xd1, 0x80, 0x1e, + 0x55, 0x18, 0x50, 0x21, 0xfe, 0x90, 0xf3, 0x0e, 0x70, 0x9a, 0x8e, 0x3f, 0x2c, 0x28, 0x8f, 0xb7, + 0xfc, 0x01, 0x3b, 0xa7, 0x11, 0xe3, 0xb7, 0x07, 0x67, 0x92, 0x84, 0x4d, 0x40, 0xdd, 0xac, 0x58, + 0x03, 0x07, 0x49, 0xd8, 0xfa, 0x9c, 0x07, 0x17, 0x65, 0xaa, 0x15, 0x26, 0xf8, 0xba, 0xed, 0x4a, + 0x36, 0x95, 0xb0, 0xb6, 0x1d, 0x99, 0xa6, 0x79, 0x09, 0x2b, 0x47, 0x94, 0x3d, 0x09, 0x3b, 0xcf, + 0x68, 0x3a, 0x81, 0x51, 0x92, 0xbb, 0x30, 0xcd, 0x2d, 0x4a, 0x32, 0x58, 0xd7, 0x37, 0xa1, 0x8c, + 0xd8, 0x38, 0x23, 0x0f, 0x31, 0xf8, 0x5e, 0xa0, 0xe6, 0xda, 0x6c, 0x98, 0x08, 0xbb, 0xa8, 0x7b, + 0x75, 0x84, 0x3c, 0x19, 0x26, 0xd4, 0x7d, 0x0a, 0x0d, 0x73, 0x12, 0x77, 0x1a, 0x01, 0xed, 0x85, + 0xfd, 0x90, 0xd1, 0x54, 0x39, 0x0d, 0x0d, 0xe0, 0xf6, 0xc8, 0xb7, 0x48, 0xda, 0x31, 0xfe, 0xe6, + 0xe7, 0xed, 0xcb, 0x41, 0xcc, 0x14, 0x6d, 0xf1, 0xe1, 0xfe, 0x45, 0x0d, 0x96, 0xd4, 0x72, 0xa4, + 0x31, 0x2b, 0x99, 0x9d, 0x2b, 0x65, 0xbe, 0x0d, 0x8d, 0x9e, 0x9f, 0xb1, 0xf6, 0x20, 0x09, 0x7c, + 0x15, 0xda, 0x4c, 0x79, 0x0b, 0x1c, 0xf6, 0x43, 0x01, 0xe2, 0x16, 0xad, 0x22, 0x57, 0x3c, 0x5b, + 0x92, 0x7b, 0xa3, 0x63, 0x2e, 0x86, 0xc0, 0x34, 0x9f, 0x83, 0xd6, 0xee, 0x78, 0xf8, 0x9b, 0xc3, + 0xce, 0xc3, 0xb3, 0x73, 0xb4, 0x6e, 0xc7, 0xc3, 0xdf, 0x7c, 0x07, 0x7b, 0xf1, 0x25, 0xda, 0xb2, + 0xe3, 0xf1, 0x9f, 0x1c, 0x72, 0x1a, 0x06, 0x68, 0xba, 0x8e, 0xc7, 0x7f, 0x72, 0x88, 0x9f, 0x3d, + 0x43, 0x43, 0x75, 0x3c, 0xfe, 0x93, 0x47, 0xfd, 0x17, 0x71, 0x6f, 0xd0, 0xa7, 0xcd, 0x3a, 0x02, + 0xe5, 0x17, 0xd9, 0x86, 0x7a, 0x92, 0x86, 0x1d, 0xda, 0xf6, 0xd9, 0x39, 0x1a, 0x93, 0xe3, 0xcd, + 0x23, 0xe0, 0x80, 0x9d, 0xbb, 0x6b, 0xb0, 0xaa, 0x37, 0x5a, 0x7b, 0xcf, 0xcf, 0x60, 0x4e, 0x42, + 0xc6, 0x6e, 0xfa, 0x1b, 0x30, 0xc7, 0x04, 0x5a, 0xb3, 0x86, 0xa7, 0x40, 0x1b, 0x96, 0xad, 0x69, + 0x4f, 0xa1, 0xb9, 0xdf, 0x03, 0x62, 0x72, 0x93, 0x1b, 0x71, 0x2f, 0xa7, 0x23, 0xdc, 0xf1, 0xb2, + 0x4d, 0x27, 0xcb, 0x09, 0x7c, 0x85, 0x97, 0xd1, 0xe3, 0x34, 0xe0, 0x8e, 0x24, 0x7e, 0xf6, 0x52, + 0x4d, 0xf3, 0x07, 0xb0, 0xa8, 0x19, 0x7f, 0xc4, 0x68, 0x9f, 0x2b, 0xdc, 0xef, 0xc7, 0x83, 0x88, 0x21, 0x4f, 0xc7, 0x93, 0x5f, 0xdc, 0x02, 0x51, 0xbf, 0xc8, 0xd2, 0xf1, 0xc4, 0x07, 0x59, 0x82, - 0x5a, 0x18, 0xc8, 0xe4, 0xa9, 0x16, 0x06, 0xee, 0xff, 0x3a, 0xb0, 0x6a, 0x2c, 0xe4, 0xca, 0x46, - 0x59, 0xb2, 0xb8, 0x5a, 0x85, 0xc5, 0xdd, 0x83, 0xd9, 0x93, 0x30, 0xe0, 0x39, 0x1b, 0xd7, 0xeb, - 0x86, 0x22, 0x67, 0xad, 0xc3, 0x43, 0x14, 0x8e, 0xea, 0x67, 0xcf, 0xb2, 0xd6, 0xec, 0x44, 0x54, - 0x8e, 0x52, 0x3a, 0x0f, 0xd7, 0xca, 0xe7, 0xc1, 0xd6, 0xe5, 0x5c, 0x51, 0x97, 0x22, 0x5a, 0xd5, - 0xb4, 0xb5, 0xe5, 0x75, 0x01, 0x72, 0xe0, 0xc4, 0x6d, 0xfd, 0x35, 0x80, 0x58, 0x63, 0x4a, 0xfb, - 0xbb, 0x5e, 0x12, 0x5a, 0x9b, 0xa0, 0x81, 0xec, 0x7e, 0x8c, 0xa1, 0x86, 0xc9, 0x5c, 0x2a, 0xff, - 0x81, 0x45, 0x53, 0xd8, 0x22, 0x29, 0xd1, 0xcc, 0x2c, 0x62, 0x6f, 0x23, 0xb1, 0xfd, 0x6e, 0x97, - 0x6f, 0xbd, 0x91, 0x98, 0x4f, 0xbc, 0xc3, 0x9f, 0xc2, 0xbc, 0x9c, 0x21, 0xcd, 0x42, 0x20, 0xd4, - 0xc2, 0x80, 0xbc, 0x0b, 0x60, 0xdc, 0x43, 0x62, 0x5d, 0xdb, 0x4a, 0x06, 0x39, 0x49, 0x59, 0x03, - 0xb2, 0x33, 0xd0, 0xdd, 0x1e, 0xac, 0x55, 0xa0, 0x70, 0x51, 0x74, 0x5a, 0x2d, 0x45, 0x51, 0xdf, - 0xe4, 0x26, 0x34, 0x58, 0xcc, 0xfc, 0x7e, 0x27, 0xbf, 0x21, 0x1c, 0x0f, 0x10, 0xf4, 0x94, 0x43, - 0xd0, 0x41, 0xc5, 0x7d, 0x61, 0xb9, 0xdc, 0x41, 0xc5, 0xfd, 0xc0, 0xf5, 0x31, 0xf0, 0xb2, 0x16, - 0x2d, 0x55, 0x38, 0x69, 0xcb, 0x5e, 0x83, 0x05, 0x5f, 0x4c, 0x51, 0x0b, 0x5b, 0x2e, 0x2c, 0xcc, - 0xd3, 0x08, 0x2e, 0xc1, 0x1b, 0xe8, 0x20, 0x8e, 0x7a, 0xe1, 0xa9, 0xb2, 0x8e, 0x57, 0xd0, 0x59, - 0x29, 0x58, 0x1e, 0x93, 0x04, 0x3e, 0xf3, 0x91, 0x5b, 0xd3, 0xc3, 0xdf, 0xee, 0x1f, 0x39, 0xb0, - 0x72, 0x14, 0xa7, 0xac, 0x17, 0xf7, 0xc3, 0x58, 0x86, 0xf7, 0x3c, 0x1c, 0x51, 0xe1, 0xbf, 0x8c, - 0x23, 0xe5, 0x27, 0xf7, 0x90, 0xdd, 0x38, 0x8c, 0x84, 0xad, 0xd6, 0xa4, 0x82, 0xe2, 0x30, 0xe2, - 0xa6, 0x4a, 0x6e, 0x41, 0x23, 0xa0, 0x59, 0x37, 0x0d, 0x13, 0x9e, 0xce, 0x49, 0xb7, 0x60, 0x82, - 0x38, 0xe1, 0x13, 0xbf, 0xef, 0x47, 0x5d, 0x2a, 0x3d, 0xbb, 0xfa, 0x74, 0x37, 0xd0, 0x5d, 0x69, - 0x49, 0x8c, 0xcc, 0xda, 0x06, 0xcb, 0xa5, 0xfc, 0x2a, 0xd4, 0x13, 0x05, 0x94, 0xe6, 0xd7, 0xd2, - 0x77, 0x75, 0x61, 0x39, 0x5e, 0x8e, 0xea, 0xee, 0xf0, 0xd8, 0x3f, 0xa7, 0x77, 0x3c, 0x1c, 0x0c, - 0xfc, 0x74, 0xa4, 0xb8, 0x45, 0x30, 0x7b, 0x10, 0x87, 0x11, 0x57, 0x14, 0x5f, 0x94, 0x0a, 0xde, - 0xf8, 0x6f, 0x53, 0xf4, 0x9a, 0x25, 0xba, 0xa9, 0xad, 0x19, 0x5b, 0x5b, 0x37, 0x00, 0x12, 0x9a, - 0x76, 0x69, 0xc4, 0xfc, 0x53, 0xb5, 0x62, 0x03, 0xe2, 0x9e, 0x01, 0x79, 0xdc, 0xeb, 0xf5, 0xc3, - 0x88, 0x72, 0xb6, 0x52, 0x98, 0x09, 0xda, 0x1f, 0x2f, 0x83, 0xcd, 0x69, 0xa6, 0xc4, 0xe9, 0x07, - 0xb0, 0xfa, 0x38, 0xaa, 0x60, 0xa4, 0xc8, 0x39, 0x93, 0xc8, 0xd5, 0x4a, 0xe4, 0xbe, 0x0f, 0x4d, - 0x43, 0xf0, 0x8c, 0xbc, 0x03, 0x75, 0x29, 0xa3, 0x4e, 0x14, 0xda, 0xda, 0x1b, 0x94, 0x56, 0xe8, - 0xe5, 0xc8, 0xee, 0x5f, 0x3a, 0xd0, 0xc8, 0x25, 0xcb, 0xc8, 0x43, 0xb8, 0xc6, 0xd5, 0xad, 0xa8, - 0xdc, 0xd0, 0x54, 0x72, 0x9c, 0x3d, 0xfc, 0xaf, 0x88, 0x0b, 0x05, 0x72, 0xfb, 0x18, 0x20, 0x07, - 0x56, 0x84, 0x75, 0xf7, 0xed, 0xb0, 0xee, 0x7a, 0x99, 0xaa, 0x12, 0xcd, 0x88, 0xec, 0xfe, 0x65, - 0x96, 0xa7, 0x7b, 0x15, 0xc6, 0x22, 0x6d, 0xf0, 0x0d, 0x68, 0x88, 0xb3, 0xc0, 0x3d, 0x80, 0x12, - 0xb8, 0x99, 0x97, 0x36, 0xc2, 0xc8, 0x03, 0x3c, 0x1b, 0x38, 0x4e, 0xde, 0x82, 0x45, 0x14, 0xb6, - 0x13, 0x0b, 0x85, 0xc8, 0x83, 0x6d, 0x4f, 0x68, 0x22, 0x8a, 0x54, 0x19, 0x49, 0x60, 0xc3, 0x9a, - 0xd2, 0xc9, 0x84, 0x08, 0xf2, 0x92, 0x7a, 0xcf, 0x08, 0xa5, 0xc7, 0x49, 0x29, 0x94, 0x25, 0x09, - 0xca, 0x31, 0xa1, 0xba, 0xb5, 0x6e, 0x79, 0x84, 0xdc, 0x87, 0xa6, 0xe4, 0x88, 0x9a, 0x91, 0x57, - 0x9c, 0x2d, 0x63, 0x43, 0x4c, 0x44, 0x04, 0x32, 0x80, 0x75, 0x73, 0x82, 0x96, 0xf0, 0x1a, 0x4e, - 0x7c, 0x77, 0x7a, 0x09, 0xa3, 0x92, 0x80, 0xa4, 0x5b, 0x1a, 0x68, 0xff, 0x36, 0xb4, 0xc6, 0x2d, - 0xa8, 0x62, 0xdb, 0x5f, 0xb5, 0xb7, 0x7d, 0xbd, 0xc2, 0x24, 0x33, 0xb3, 0x80, 0xf8, 0x39, 0x6c, - 0x8d, 0x11, 0xe6, 0x0a, 0x55, 0x07, 0xc3, 0x52, 0x4d, 0x6b, 0xfa, 0x73, 0x07, 0xda, 0xfb, 0x41, - 0x50, 0x72, 0x4e, 0x79, 0x91, 0xe0, 0x9b, 0x76, 0xb9, 0xbb, 0xb0, 0x5d, 0x29, 0x90, 0xac, 0x66, - 0x3c, 0x87, 0x5d, 0x8f, 0x0e, 0xe2, 0x73, 0xfa, 0x4d, 0x8b, 0xec, 0xde, 0x82, 0x1b, 0xe3, 0x38, + 0x5a, 0x18, 0xc8, 0xe4, 0xa9, 0x16, 0x06, 0xee, 0xff, 0x3a, 0xb0, 0x6a, 0x2c, 0xe4, 0xda, 0x46, + 0x59, 0xb2, 0xb8, 0x5a, 0x85, 0xc5, 0xdd, 0x83, 0xe9, 0xd3, 0x30, 0xe0, 0x39, 0x1b, 0xd7, 0xeb, + 0x86, 0x22, 0x67, 0xad, 0xc3, 0x43, 0x14, 0x8e, 0xea, 0x67, 0xcf, 0xb2, 0xe6, 0xf4, 0x58, 0x54, + 0x8e, 0x52, 0x3a, 0x0f, 0x33, 0xe5, 0xf3, 0x60, 0xeb, 0x72, 0xb6, 0xa8, 0x4b, 0x11, 0xad, 0x6a, + 0xda, 0xda, 0xf2, 0x3a, 0x00, 0x39, 0x70, 0xec, 0xb6, 0xfe, 0x1a, 0x40, 0xac, 0x31, 0xa5, 0xfd, + 0xdd, 0x28, 0x09, 0xad, 0x4d, 0xd0, 0x40, 0x76, 0x3f, 0xc6, 0x50, 0xc3, 0x64, 0x2e, 0x95, 0xff, + 0xd0, 0xa2, 0x29, 0x6c, 0x91, 0x94, 0x68, 0x66, 0x16, 0xb1, 0xb7, 0x90, 0xd8, 0x41, 0xa7, 0xc3, + 0xb7, 0xde, 0x48, 0xcc, 0xc7, 0xde, 0xe1, 0x4f, 0x61, 0x4e, 0xce, 0x90, 0x66, 0x21, 0x10, 0x6a, + 0x61, 0x40, 0xde, 0x01, 0x30, 0xee, 0x21, 0xb1, 0xae, 0x6d, 0x25, 0x83, 0x9c, 0xa4, 0xac, 0x01, + 0xd9, 0x19, 0xe8, 0x6e, 0x17, 0xd6, 0x2a, 0x50, 0xb8, 0x28, 0x3a, 0xad, 0x96, 0xa2, 0xa8, 0x6f, + 0xb2, 0x07, 0x0b, 0x2c, 0x66, 0x7e, 0xaf, 0x9d, 0xdf, 0x10, 0x8e, 0x07, 0x08, 0x7a, 0xca, 0x21, + 0xe8, 0xa0, 0xe2, 0x9e, 0xb0, 0x5c, 0xee, 0xa0, 0xe2, 0x5e, 0xe0, 0xfa, 0x18, 0x78, 0x59, 0x8b, + 0x96, 0x2a, 0x1c, 0xb7, 0x65, 0xdf, 0x84, 0x79, 0x5f, 0x4c, 0x51, 0x0b, 0x5b, 0x2e, 0x2c, 0xcc, + 0xd3, 0x08, 0x2e, 0xc1, 0x1b, 0xe8, 0x30, 0x8e, 0xba, 0xe1, 0x99, 0xb2, 0x8e, 0xd7, 0xd0, 0x59, + 0x29, 0x58, 0x1e, 0x93, 0x04, 0x3e, 0xf3, 0x91, 0x5b, 0xc3, 0xc3, 0xdf, 0xee, 0x1f, 0x39, 0xb0, + 0x72, 0x1c, 0xa7, 0xac, 0x1b, 0xf7, 0xc2, 0x58, 0x86, 0xf7, 0x3c, 0x1c, 0x51, 0xe1, 0xbf, 0x8c, + 0x23, 0xe5, 0x27, 0xf7, 0x90, 0x9d, 0x38, 0x8c, 0x84, 0xad, 0xd6, 0xa4, 0x82, 0xe2, 0x30, 0xe2, + 0xa6, 0x4a, 0x6e, 0xc1, 0x42, 0x40, 0xb3, 0x4e, 0x1a, 0x26, 0x3c, 0x9d, 0x93, 0x6e, 0xc1, 0x04, + 0x71, 0xc2, 0xa7, 0x7e, 0xcf, 0x8f, 0x3a, 0x54, 0x7a, 0x76, 0xf5, 0xe9, 0x6e, 0xa0, 0xbb, 0xd2, + 0x92, 0x18, 0x99, 0xb5, 0x0d, 0x96, 0x4b, 0xf9, 0x55, 0xa8, 0x27, 0x0a, 0x28, 0xcd, 0xaf, 0xa9, + 0xef, 0xea, 0xc2, 0x72, 0xbc, 0x1c, 0xd5, 0xdd, 0xe1, 0xb1, 0x7f, 0x4e, 0xef, 0x64, 0xd0, 0xef, + 0xfb, 0xe9, 0x50, 0x71, 0x8b, 0x60, 0xfa, 0x30, 0x0e, 0x23, 0xae, 0x28, 0xbe, 0x28, 0x15, 0xbc, + 0xf1, 0xdf, 0xa6, 0xe8, 0x35, 0x4b, 0x74, 0x53, 0x5b, 0x53, 0xb6, 0xb6, 0x6e, 0x02, 0x24, 0x34, + 0xed, 0xd0, 0x88, 0xf9, 0x67, 0x6a, 0xc5, 0x06, 0xc4, 0x3d, 0x07, 0xf2, 0xb8, 0xdb, 0xed, 0x85, + 0x11, 0xe5, 0x6c, 0xa5, 0x30, 0x63, 0xb4, 0x3f, 0x5a, 0x06, 0x9b, 0xd3, 0x54, 0x89, 0xd3, 0x0f, + 0x60, 0xf5, 0x71, 0x54, 0xc1, 0x48, 0x91, 0x73, 0xc6, 0x91, 0xab, 0x95, 0xc8, 0x7d, 0x1f, 0x1a, + 0x86, 0xe0, 0x19, 0x79, 0x1b, 0xea, 0x52, 0x46, 0x9d, 0x28, 0xb4, 0xb4, 0x37, 0x28, 0xad, 0xd0, + 0xcb, 0x91, 0xdd, 0xbf, 0x74, 0x60, 0x21, 0x97, 0x2c, 0x23, 0x8f, 0x60, 0x86, 0xab, 0x5b, 0x51, + 0xb9, 0xa9, 0xa9, 0xe4, 0x38, 0xfb, 0xf8, 0x5f, 0x11, 0x17, 0x0a, 0xe4, 0xd6, 0x09, 0x40, 0x0e, + 0xac, 0x08, 0xeb, 0x1e, 0xd8, 0x61, 0xdd, 0x8d, 0x32, 0x55, 0x25, 0x9a, 0x11, 0xd9, 0xfd, 0xcb, + 0x34, 0x4f, 0xf7, 0x2a, 0x8c, 0x45, 0xda, 0xe0, 0xb7, 0x60, 0x41, 0x9c, 0x05, 0xee, 0x01, 0x94, + 0xc0, 0x8d, 0xbc, 0xb4, 0x11, 0x46, 0x1e, 0xe0, 0xd9, 0xc0, 0x71, 0xf2, 0x26, 0x2c, 0xa2, 0xb0, + 0xed, 0x58, 0x28, 0x44, 0x1e, 0x6c, 0x7b, 0x42, 0x03, 0x51, 0xa4, 0xca, 0x48, 0x02, 0x1b, 0xd6, + 0x94, 0x76, 0x26, 0x44, 0x90, 0x97, 0xd4, 0xbb, 0x46, 0x28, 0x3d, 0x4a, 0x4a, 0xa1, 0x2c, 0x49, + 0x50, 0x8e, 0x09, 0xd5, 0xad, 0x75, 0xca, 0x23, 0xe4, 0x01, 0x34, 0x24, 0x47, 0xd4, 0x8c, 0xbc, + 0xe2, 0x6c, 0x19, 0x17, 0xc4, 0x44, 0x44, 0x20, 0x7d, 0x58, 0x37, 0x27, 0x68, 0x09, 0x67, 0x70, + 0xe2, 0x3b, 0x93, 0x4b, 0x18, 0x95, 0x04, 0x24, 0x9d, 0xd2, 0x40, 0xeb, 0xb7, 0xa1, 0x39, 0x6a, + 0x41, 0x15, 0xdb, 0x7e, 0xdf, 0xde, 0xf6, 0xf5, 0x0a, 0x93, 0xcc, 0xcc, 0x02, 0xe2, 0xe7, 0xb0, + 0x35, 0x42, 0x98, 0x6b, 0x54, 0x1d, 0x0c, 0x4b, 0x35, 0xad, 0xe9, 0xcf, 0x1d, 0x68, 0x1d, 0x04, + 0x41, 0xc9, 0x39, 0xe5, 0x45, 0x82, 0x97, 0xed, 0x72, 0x77, 0x61, 0xbb, 0x52, 0x20, 0x59, 0xcd, + 0x78, 0x0e, 0xbb, 0x1e, 0xed, 0xc7, 0x17, 0xf4, 0x65, 0x8b, 0xec, 0xde, 0x82, 0x9b, 0xa3, 0x38, 0x4b, 0xd9, 0xb0, 0xbc, 0x67, 0x97, 0xc7, 0x75, 0x60, 0xf4, 0x9f, 0x0e, 0x2c, 0xda, 0x85, 0xf3, - 0x17, 0x95, 0x8b, 0xbf, 0x0e, 0x24, 0xa5, 0x19, 0xeb, 0x24, 0x71, 0xbf, 0xcf, 0x53, 0xf2, 0x80, - 0xf6, 0xfd, 0x91, 0x2c, 0xd9, 0xaf, 0xf0, 0x91, 0x23, 0x31, 0xf0, 0x88, 0xc3, 0xc9, 0x16, 0xcc, - 0xfb, 0x49, 0xd8, 0xe1, 0x56, 0x23, 0xf2, 0xf1, 0x39, 0x3f, 0x09, 0x3f, 0xa6, 0x23, 0xe2, 0xc2, - 0xa2, 0x1c, 0xe8, 0xf4, 0xe9, 0x39, 0xed, 0x63, 0xcc, 0x37, 0xe3, 0x35, 0xc4, 0xf0, 0x27, 0x1c, - 0x44, 0xee, 0xc1, 0x4a, 0x92, 0x86, 0xdc, 0xfc, 0xf2, 0xb7, 0x81, 0x79, 0x94, 0x66, 0x59, 0xc2, - 0xd5, 0xea, 0xdc, 0x1f, 0xc1, 0xf5, 0x0a, 0x5d, 0x48, 0x1f, 0xf5, 0x5d, 0x58, 0xb6, 0x5f, 0x18, - 0x94, 0x9f, 0xd2, 0x51, 0xab, 0x35, 0xd1, 0x5b, 0xea, 0x59, 0x74, 0x64, 0xf4, 0x89, 0x38, 0x9e, - 0xcf, 0x74, 0x4d, 0xcb, 0xfd, 0x12, 0xd6, 0x73, 0xe0, 0x41, 0x1c, 0x9d, 0xd3, 0x34, 0xe3, 0xd6, - 0x46, 0x60, 0xb6, 0x97, 0xc6, 0xaa, 0x20, 0x8b, 0xbf, 0x79, 0xdc, 0xc6, 0x62, 0x69, 0x06, 0x35, - 0x16, 0x73, 0x9c, 0xd4, 0x67, 0xea, 0x96, 0xc2, 0xdf, 0x3c, 0x4e, 0x0e, 0x91, 0x08, 0xed, 0xe0, - 0x98, 0x30, 0xd5, 0x86, 0x84, 0x71, 0x2e, 0xee, 0x53, 0x0c, 0x1f, 0x4d, 0x51, 0xe4, 0x1a, 0x7f, - 0x1d, 0x1a, 0x62, 0x8d, 0x7c, 0xa6, 0x5a, 0xdf, 0x8e, 0xb5, 0xbe, 0x82, 0x98, 0x1e, 0xf4, 0x34, - 0xd4, 0xfd, 0xef, 0x1a, 0x34, 0x31, 0x62, 0x7d, 0x44, 0x99, 0x1f, 0xf6, 0x27, 0xc7, 0xd2, 0x22, - 0x06, 0xad, 0xe9, 0x18, 0xf4, 0x25, 0x58, 0x34, 0x0b, 0x22, 0x23, 0x95, 0xcc, 0x1a, 0xe5, 0x90, - 0x11, 0x79, 0x19, 0x96, 0x30, 0xb5, 0xce, 0xb1, 0x84, 0xcd, 0x2c, 0x22, 0x54, 0xa3, 0xd9, 0x89, - 0xc0, 0xb5, 0x42, 0x22, 0xc0, 0x87, 0x31, 0x98, 0xee, 0x64, 0x61, 0xa0, 0xf3, 0x04, 0x84, 0x1c, - 0x87, 0x81, 0x31, 0x8c, 0xb3, 0xe7, 0x8d, 0x61, 0x9c, 0xcd, 0x73, 0xa0, 0x94, 0x8a, 0x87, 0x02, - 0x7c, 0xef, 0x5a, 0x40, 0xa3, 0x6b, 0x2a, 0xe0, 0x93, 0x70, 0x80, 0xaf, 0x61, 0xb2, 0xb8, 0x5d, - 0x17, 0x16, 0x2b, 0xbe, 0xf2, 0x34, 0x0d, 0xcc, 0x34, 0x2d, 0x4f, 0xea, 0x1a, 0x56, 0x52, 0x77, - 0x13, 0x1a, 0x71, 0x42, 0xa3, 0x8e, 0x4c, 0xb1, 0x9b, 0x22, 0x7a, 0xe0, 0xa0, 0xa7, 0x08, 0x91, - 0x25, 0x13, 0xd4, 0x79, 0x36, 0x4d, 0x5e, 0x6a, 0x2b, 0xa6, 0x56, 0x54, 0x8c, 0x4a, 0x04, 0x67, - 0x2e, 0x4b, 0x04, 0xdd, 0x7d, 0x8c, 0x8a, 0x15, 0x63, 0x69, 0x3e, 0xaf, 0xc3, 0x1c, 0xaa, 0x49, - 0x59, 0xce, 0xba, 0x95, 0xc6, 0x48, 0xa3, 0xf0, 0x24, 0x8e, 0xfb, 0x7d, 0x7c, 0x43, 0xc4, 0xa1, - 0x69, 0x44, 0xbf, 0x0e, 0x0b, 0x62, 0x57, 0xb4, 0xd5, 0xcc, 0xe3, 0xf7, 0x47, 0x81, 0xfb, 0x6f, - 0x0e, 0x90, 0xe3, 0xe1, 0xc9, 0x20, 0x9c, 0x9e, 0xda, 0xf4, 0x09, 0x3a, 0x81, 0x59, 0x34, 0x13, - 0x61, 0x8e, 0xf8, 0xbb, 0x60, 0x21, 0xb3, 0x45, 0x0b, 0xc9, 0xb7, 0xf3, 0x5a, 0x75, 0x8e, 0x3e, - 0x67, 0x6e, 0x3e, 0x77, 0xf1, 0xfd, 0x90, 0x46, 0xac, 0x23, 0x8b, 0x2d, 0xdc, 0xc5, 0x23, 0xe0, - 0xa3, 0xc0, 0x3d, 0x86, 0x35, 0x6b, 0x65, 0x52, 0xd3, 0xb7, 0xa1, 0x29, 0x04, 0x48, 0xfa, 0x7e, - 0x57, 0x57, 0xc3, 0x1b, 0x08, 0x3b, 0x42, 0xd0, 0x24, 0x7d, 0xfd, 0x89, 0x03, 0xeb, 0xc7, 0xe1, - 0x60, 0xd8, 0xf7, 0x19, 0xfd, 0x25, 0x68, 0x2c, 0x5f, 0xfe, 0x8c, 0xb5, 0x7c, 0xa5, 0xc9, 0xd9, - 0x5c, 0x93, 0xee, 0xcf, 0x1c, 0xd8, 0x28, 0x88, 0xa2, 0x63, 0x42, 0xdb, 0x98, 0xc6, 0x14, 0x07, - 0x24, 0x92, 0xc1, 0xb4, 0x66, 0x31, 0x7d, 0x09, 0x16, 0x07, 0x61, 0x14, 0x0e, 0x86, 0x83, 0x8e, - 0xd0, 0xbd, 0x90, 0xa9, 0x29, 0x81, 0x47, 0xb8, 0x05, 0x1c, 0xc9, 0x7f, 0x6e, 0x20, 0xcd, 0x4a, - 0x24, 0x01, 0x14, 0x48, 0x6f, 0xc2, 0x7a, 0x1e, 0xb7, 0x77, 0x4e, 0xfd, 0x30, 0xea, 0xf4, 0xe3, - 0x2c, 0x93, 0x7b, 0x4c, 0xf2, 0xb1, 0x43, 0x3f, 0x8c, 0x3e, 0x89, 0xb3, 0xcc, 0x70, 0x02, 0x73, - 0xa6, 0x13, 0xe0, 0x01, 0xcc, 0xca, 0x67, 0x67, 0x7e, 0x9f, 0xbe, 0x1f, 0x0f, 0x4e, 0x5e, 0xac, - 0xee, 0x6f, 0x43, 0x53, 0xd4, 0xdd, 0x98, 0x9f, 0x9e, 0x52, 0xb5, 0x03, 0x0d, 0x84, 0x3d, 0x41, - 0x50, 0xe5, 0x36, 0xfc, 0x97, 0x03, 0xe4, 0x80, 0x87, 0x32, 0xfd, 0xa9, 0xed, 0x81, 0xbb, 0x12, - 0x91, 0x37, 0xe7, 0x16, 0x56, 0x97, 0x90, 0x8f, 0x6c, 0xf3, 0x9b, 0xb1, 0xcc, 0x4f, 0xaf, 0x66, - 0xf6, 0x8a, 0xc5, 0xb1, 0x92, 0x1f, 0x7f, 0x19, 0x96, 0x2e, 0xfc, 0x7e, 0x9f, 0x32, 0xfd, 0xc4, - 0x26, 0x2b, 0xf1, 0x02, 0xaa, 0x72, 0x70, 0xb5, 0xe0, 0x79, 0x63, 0xc1, 0x1b, 0xb0, 0x66, 0xad, - 0x57, 0x46, 0x43, 0x0f, 0x61, 0x53, 0x80, 0xf7, 0xfb, 0xfd, 0xa9, 0xbd, 0xaa, 0xfb, 0xd7, 0x35, - 0xd8, 0x2a, 0x4d, 0xd3, 0x61, 0x83, 0x6d, 0xc6, 0x77, 0xf4, 0x72, 0xab, 0x27, 0xec, 0xc9, 0x4f, - 0x39, 0xab, 0xfd, 0x4f, 0x0e, 0xcc, 0x09, 0xd0, 0xc4, 0xdd, 0xf8, 0x5c, 0x39, 0x04, 0x69, 0x70, - 0x22, 0x23, 0xfa, 0xf6, 0x74, 0xcc, 0xc4, 0xff, 0xcc, 0x67, 0x55, 0xe1, 0x49, 0xe4, 0x8b, 0xea, - 0x77, 0x61, 0xa5, 0x88, 0x70, 0xa5, 0x27, 0x27, 0x51, 0x55, 0xf9, 0xe0, 0x9c, 0x1a, 0xcf, 0xa8, - 0x3f, 0x75, 0x60, 0xf9, 0x20, 0x8e, 0x82, 0x90, 0xdf, 0x98, 0x47, 0x7e, 0xea, 0x0f, 0x32, 0xf9, - 0x92, 0x2f, 0x40, 0xaa, 0xec, 0xae, 0x01, 0x63, 0x0a, 0x9c, 0xbb, 0x00, 0xdd, 0x33, 0xda, 0x7d, - 0xd6, 0x91, 0x15, 0x47, 0xf1, 0xfc, 0xcf, 0x21, 0xef, 0x87, 0x41, 0x46, 0xde, 0x80, 0xb5, 0x7c, - 0xb8, 0xe3, 0x47, 0x41, 0x47, 0x96, 0x1b, 0xf1, 0x75, 0x43, 0xe3, 0xed, 0x47, 0xc1, 0x7e, 0xf6, - 0x2c, 0xe3, 0xb1, 0xa2, 0xae, 0xb2, 0x75, 0x2c, 0x17, 0xbe, 0xac, 0xe1, 0xfb, 0x08, 0x76, 0xff, - 0xc7, 0xc1, 0x1b, 0x50, 0xad, 0x4a, 0xee, 0x76, 0x5e, 0x58, 0xc3, 0x7a, 0xab, 0xb5, 0x65, 0xb5, - 0xc2, 0x96, 0x11, 0x98, 0x0d, 0x19, 0x1d, 0xa8, 0x8b, 0x85, 0xff, 0x26, 0xef, 0xc3, 0x8a, 0x5e, - 0x71, 0x27, 0x41, 0xb5, 0xc8, 0x63, 0xb2, 0x95, 0x27, 0x8e, 0x96, 0xd6, 0xbc, 0xe5, 0x6e, 0x41, - 0x8d, 0xea, 0x78, 0x5d, 0x9b, 0xca, 0x51, 0x77, 0x51, 0xdb, 0xd2, 0x3f, 0x89, 0x2f, 0x21, 0x35, - 0xed, 0x0e, 0x19, 0x0d, 0x64, 0xa8, 0xac, 0xbf, 0xdd, 0xff, 0x70, 0x60, 0x79, 0x3f, 0x08, 0x70, - 0xdd, 0xd3, 0xb8, 0x09, 0xb5, 0xca, 0xda, 0x25, 0xab, 0x9c, 0xf9, 0x39, 0x57, 0xf9, 0x0b, 0x3b, - 0x91, 0x31, 0x4a, 0x70, 0x5d, 0x58, 0xc9, 0xd7, 0x59, 0xbd, 0xbd, 0xee, 0xb7, 0x80, 0x88, 0xf4, - 0xca, 0x52, 0x47, 0x11, 0x6b, 0x03, 0xd6, 0x2c, 0x2c, 0xe9, 0x6b, 0x3e, 0x84, 0xbb, 0x87, 0x94, - 0x1d, 0xa4, 0xa3, 0x84, 0xc5, 0x2a, 0x9c, 0x7d, 0x44, 0x93, 0x38, 0x0b, 0x95, 0xe7, 0xa2, 0x53, - 0x79, 0x9f, 0x7f, 0x76, 0xe0, 0xde, 0x14, 0x84, 0xe4, 0x12, 0xbe, 0x28, 0xd7, 0x97, 0x7e, 0xc3, - 0x6c, 0x6f, 0x99, 0x8a, 0xca, 0x9e, 0x86, 0xc8, 0x2e, 0x03, 0x4d, 0xb2, 0xfd, 0x1e, 0x2c, 0xd9, - 0x83, 0x57, 0x72, 0x15, 0x7d, 0xb8, 0x73, 0x89, 0x10, 0xd3, 0xd8, 0xdc, 0x1d, 0x58, 0xea, 0x5a, - 0x24, 0x24, 0xa3, 0x02, 0xd4, 0x3d, 0x80, 0x57, 0x2e, 0xe5, 0x26, 0xd5, 0x36, 0x36, 0x43, 0x77, - 0xff, 0x6e, 0x16, 0xb6, 0x3e, 0x0b, 0xd9, 0x59, 0x90, 0xfa, 0x17, 0xca, 0xfa, 0xa6, 0x11, 0xb2, - 0x90, 0xbc, 0xd7, 0xca, 0xf5, 0x86, 0x57, 0x61, 0x35, 0x8e, 0x28, 0xe6, 0x18, 0x9d, 0xc4, 0xcf, - 0xb2, 0x8b, 0x38, 0x55, 0x77, 0xe9, 0x72, 0x1c, 0x51, 0x9e, 0x67, 0x1c, 0x49, 0x70, 0xe1, 0x36, - 0x9e, 0x2d, 0xde, 0xc6, 0x2b, 0x30, 0x93, 0x84, 0x91, 0x7c, 0x33, 0xe1, 0x3f, 0xf9, 0xdd, 0xc9, - 0x52, 0x3f, 0x30, 0x28, 0xcb, 0xbb, 0x13, 0xa1, 0x9a, 0xae, 0x59, 0xc5, 0x9f, 0x2f, 0x54, 0xf1, - 0x0d, 0x9d, 0x2c, 0xd8, 0x55, 0x8b, 0x9b, 0xd0, 0x90, 0x3f, 0x3b, 0xcc, 0x3f, 0x95, 0x29, 0x10, - 0x48, 0xd0, 0x13, 0xff, 0xd4, 0x88, 0xd6, 0xc0, 0x8a, 0xd6, 0x76, 0x01, 0x7a, 0x94, 0x76, 0xac, - 0x64, 0xa8, 0xde, 0xa3, 0x54, 0x38, 0x5d, 0x1e, 0x2a, 0x9f, 0xf8, 0xd1, 0xb3, 0x0e, 0xd6, 0x20, - 0x9a, 0x42, 0x1c, 0x0e, 0xf8, 0xd4, 0x1f, 0x60, 0x4c, 0x8c, 0x83, 0x4a, 0xa6, 0x45, 0xa1, 0x51, - 0x0e, 0xdb, 0xcf, 0xab, 0x29, 0x88, 0xd2, 0x0d, 0xd9, 0xa8, 0xb5, 0x94, 0xcf, 0x3f, 0x08, 0xd9, - 0x48, 0xcf, 0x47, 0x9d, 0xa5, 0xa3, 0xd6, 0x72, 0x3e, 0xff, 0x40, 0x80, 0xb8, 0x78, 0xd9, 0x45, - 0xd8, 0xa3, 0xa2, 0x31, 0x64, 0x45, 0xb6, 0x4a, 0x71, 0xc8, 0x41, 0x1c, 0x60, 0x18, 0x79, 0x11, - 0xa6, 0x46, 0x72, 0xba, 0x2a, 0x52, 0x58, 0x0e, 0x54, 0xa6, 0xe1, 0xbe, 0x0a, 0x2b, 0xca, 0x5c, - 0xcc, 0xde, 0xc9, 0x94, 0x66, 0xc3, 0x3e, 0x53, 0xbd, 0x93, 0xe2, 0xcb, 0x7d, 0x0b, 0xbb, 0x22, - 0x3e, 0x89, 0x4f, 0x4f, 0xf3, 0xf4, 0x49, 0x9a, 0xd6, 0x26, 0xcc, 0xf5, 0x11, 0xae, 0xa6, 0x88, - 0x2f, 0x37, 0xc2, 0x7a, 0x4e, 0x61, 0x4a, 0xfe, 0x6a, 0x11, 0x46, 0xbd, 0x58, 0x66, 0x0b, 0xf8, - 0x9b, 0x9f, 0xc5, 0x80, 0x9e, 0x0c, 0x4f, 0x55, 0x0f, 0x14, 0x7e, 0x70, 0xcc, 0x0b, 0x3f, 0x8d, - 0xe4, 0x85, 0x8a, 0xbf, 0x39, 0x26, 0x4d, 0xd3, 0x38, 0x95, 0xb7, 0xa7, 0xf8, 0x70, 0x0f, 0x61, - 0xeb, 0xf8, 0x6a, 0x22, 0x72, 0x42, 0xa2, 0x5a, 0x23, 0x8f, 0x3f, 0x7e, 0xb8, 0x1f, 0x5b, 0x1d, - 0x20, 0xd8, 0x25, 0x30, 0xcd, 0x31, 0x5a, 0x87, 0x6b, 0xe8, 0xcb, 0x15, 0x31, 0xfc, 0xe0, 0x19, - 0x61, 0xab, 0x4c, 0x4d, 0xf7, 0xa0, 0x95, 0x3b, 0x2a, 0x84, 0x27, 0xfc, 0x95, 0x8a, 0x8e, 0x0a, - 0x6b, 0xee, 0x74, 0x2d, 0x15, 0xbf, 0xd4, 0x2e, 0x89, 0xaf, 0x60, 0xcd, 0x14, 0xed, 0x1b, 0xcd, - 0xfa, 0x7f, 0xec, 0x60, 0x85, 0x4c, 0x67, 0x60, 0xc7, 0x2c, 0xa5, 0xfe, 0xe0, 0x1b, 0x7d, 0x10, - 0xff, 0x1e, 0xdc, 0x36, 0xfb, 0xa5, 0xae, 0x2c, 0x89, 0xfb, 0x07, 0xf8, 0x8c, 0x28, 0x1e, 0xf9, - 0xff, 0x1f, 0xe4, 0x7f, 0x0f, 0x6e, 0x18, 0xf2, 0x5f, 0x51, 0x0c, 0xf7, 0xaf, 0x1c, 0xac, 0x22, - 0xee, 0x0f, 0x83, 0x90, 0x59, 0x31, 0x07, 0xf7, 0x4c, 0xcc, 0x4f, 0x59, 0x27, 0xf0, 0x19, 0xd5, - 0x4d, 0x9c, 0x1c, 0xf2, 0xc8, 0x67, 0x58, 0x3c, 0xa1, 0x51, 0x20, 0x06, 0x65, 0x31, 0x80, 0x46, - 0x81, 0x1a, 0x12, 0x99, 0xc3, 0xc9, 0xc8, 0x4a, 0xd4, 0xde, 0xc7, 0x7b, 0x1a, 0x9b, 0x5e, 0xf0, - 0xc4, 0x5f, 0xf3, 0xc4, 0x07, 0x3f, 0xd6, 0x71, 0xaf, 0xc7, 0x8f, 0xdc, 0x35, 0x04, 0xcb, 0x2f, - 0xf7, 0x91, 0x78, 0x94, 0x36, 0x44, 0x93, 0xe7, 0xed, 0x35, 0x98, 0xa3, 0x18, 0x26, 0xcb, 0x53, - 0xa6, 0xeb, 0xfb, 0x3e, 0xc7, 0xed, 0xe0, 0x98, 0x27, 0x51, 0xdc, 0x11, 0x34, 0x0c, 0x30, 0x77, - 0x44, 0xa8, 0x47, 0x59, 0x05, 0xe5, 0xbf, 0xc9, 0x0d, 0x80, 0x30, 0xa0, 0x11, 0x0b, 0x7b, 0x21, - 0x55, 0x1d, 0x08, 0x06, 0x84, 0xdf, 0x4b, 0x03, 0x9a, 0x65, 0xea, 0xf9, 0xae, 0xee, 0xa9, 0x4f, - 0x9e, 0x61, 0xf0, 0xdb, 0x34, 0x63, 0xfe, 0x20, 0x51, 0x97, 0xa4, 0x06, 0x3c, 0xf8, 0xd9, 0xab, - 0xb0, 0x74, 0x18, 0x8b, 0x70, 0xe0, 0x09, 0xbf, 0x05, 0x53, 0xf2, 0x18, 0xe6, 0x65, 0xfb, 0x35, - 0xd9, 0x2c, 0xf5, 0x63, 0xa3, 0xe6, 0xdb, 0x5b, 0x63, 0xfa, 0xb4, 0xdd, 0xb5, 0xaf, 0xff, 0xf5, - 0xdf, 0x7f, 0x52, 0x5b, 0x24, 0x8d, 0xfb, 0xe7, 0x6f, 0xdd, 0x3f, 0xa5, 0x0c, 0xdd, 0xed, 0x29, - 0x2c, 0x5a, 0x1d, 0xb3, 0x64, 0xc7, 0xea, 0x7a, 0x2d, 0x34, 0xd2, 0xb6, 0x77, 0x27, 0xf6, 0xc4, - 0xba, 0xd7, 0x91, 0xc5, 0x1a, 0x59, 0x95, 0x2c, 0xf2, 0x66, 0x58, 0xf2, 0x25, 0x2c, 0x7f, 0x80, - 0x65, 0x78, 0x4d, 0x94, 0xdc, 0xcc, 0x89, 0x55, 0x36, 0x02, 0xb7, 0x6f, 0x8d, 0x47, 0x90, 0x0c, - 0xb7, 0x91, 0xe1, 0x06, 0x59, 0xe3, 0x0c, 0x45, 0x99, 0x5f, 0xf3, 0x24, 0x19, 0xac, 0xc8, 0xd6, - 0xc2, 0x17, 0xca, 0x73, 0x07, 0x79, 0x6e, 0x92, 0x75, 0xce, 0x33, 0x10, 0x0c, 0x72, 0xa6, 0x31, - 0x56, 0x11, 0xcd, 0x56, 0x58, 0x72, 0x63, 0x6c, 0x8f, 0xac, 0x60, 0x79, 0xf3, 0x92, 0x1e, 0x5a, - 0x7b, 0x95, 0xa7, 0x94, 0xe3, 0xea, 0x36, 0x5a, 0xf2, 0x13, 0x71, 0xb5, 0x54, 0x36, 0x6d, 0x93, - 0x57, 0x2e, 0xef, 0x14, 0x17, 0x32, 0xdc, 0x9d, 0xb6, 0xa5, 0xdc, 0xfd, 0x16, 0x0a, 0x73, 0x83, - 0xec, 0x48, 0x61, 0xac, 0x36, 0x72, 0xd5, 0xa8, 0x4e, 0xba, 0xd0, 0x34, 0xfb, 0x5f, 0xc9, 0x76, - 0xc5, 0x4d, 0xa6, 0x99, 0xef, 0x54, 0x0f, 0x4a, 0x86, 0x2d, 0x64, 0x48, 0xc8, 0x8a, 0x64, 0xa8, - 0xdb, 0x65, 0xc9, 0x57, 0xb0, 0x5c, 0xe8, 0x1d, 0x25, 0x6e, 0x61, 0xfb, 0x2a, 0xfa, 0x80, 0xdb, - 0x2f, 0x4d, 0xc4, 0x91, 0x5c, 0x6f, 0x20, 0xd7, 0x96, 0xbb, 0x66, 0xec, 0xb2, 0xe2, 0xfc, 0x1d, - 0xe7, 0x55, 0x92, 0xe1, 0x3e, 0x9b, 0x6d, 0x8e, 0x53, 0xf1, 0xbe, 0x79, 0x49, 0x8f, 0x64, 0x69, - 0xaf, 0x15, 0x4f, 0x3c, 0xad, 0x19, 0xb6, 0x8e, 0x19, 0xcd, 0xb9, 0x18, 0xe6, 0x4d, 0xc3, 0x77, - 0xb7, 0xba, 0xb9, 0x57, 0xf6, 0x17, 0xbb, 0x6d, 0xe4, 0xba, 0x4e, 0x48, 0x81, 0x6b, 0xcc, 0x12, - 0x92, 0x59, 0xbd, 0xcf, 0x92, 0xa9, 0x6d, 0xd5, 0x15, 0xdd, 0xc7, 0x95, 0x2b, 0x35, 0xdb, 0x89, - 0xc7, 0xae, 0x34, 0x66, 0x49, 0x46, 0x9e, 0xc3, 0x92, 0x70, 0x17, 0x2f, 0x7e, 0x67, 0x77, 0x91, - 0xef, 0x96, 0x4b, 0x72, 0x9f, 0x61, 0x6e, 0xec, 0x67, 0x50, 0xd7, 0xf7, 0x31, 0x69, 0x19, 0x8b, - 0xb0, 0x1a, 0x41, 0xdb, 0x63, 0xda, 0xfc, 0x94, 0xb5, 0xba, 0x8b, 0x72, 0x55, 0xa2, 0x69, 0x8f, - 0x13, 0xfe, 0x11, 0x40, 0xde, 0xf7, 0x47, 0xae, 0x97, 0x28, 0x6b, 0xcd, 0xb5, 0xab, 0x86, 0xd4, - 0x5f, 0x38, 0x20, 0xf9, 0x15, 0xb2, 0x64, 0x91, 0x57, 0xe7, 0x4d, 0x87, 0x1f, 0xd6, 0x79, 0x2b, - 0x76, 0x0a, 0xb6, 0xc7, 0xb7, 0x88, 0xa9, 0x4d, 0x71, 0xd5, 0x61, 0xd3, 0x65, 0x26, 0xbe, 0x02, - 0x71, 0x59, 0x18, 0xbd, 0x69, 0x3b, 0x55, 0x5c, 0x2a, 0x2f, 0x8b, 0x72, 0xa3, 0x59, 0xe9, 0xb2, - 0xc8, 0xfb, 0xc9, 0xc8, 0x33, 0xfc, 0x0b, 0x2f, 0xa3, 0xb5, 0x8a, 0x98, 0xb4, 0xca, 0x7d, 0x66, - 0xed, 0x1b, 0xe3, 0x86, 0xb3, 0x6a, 0xfb, 0x96, 0x99, 0x28, 0x1e, 0x2a, 0xb1, 0xe1, 0xa2, 0xa1, - 0xca, 0xda, 0x70, 0xab, 0xef, 0xaa, 0x7d, 0xbd, 0x62, 0x44, 0x52, 0xdf, 0x40, 0xea, 0xcb, 0x64, - 0x51, 0xbb, 0x44, 0xa4, 0x25, 0xf6, 0x44, 0xbf, 0x74, 0x5b, 0x7b, 0x52, 0x6c, 0x87, 0xb2, 0x7c, - 0x60, 0xa9, 0x29, 0xaa, 0xe4, 0x03, 0x75, 0xdb, 0x13, 0xf9, 0x43, 0xbb, 0xbb, 0x4a, 0x75, 0x7b, - 0xb8, 0x13, 0xdb, 0x33, 0x4a, 0xa7, 0x65, 0x6c, 0x0b, 0x87, 0x7b, 0x13, 0x39, 0x5f, 0x27, 0x5b, - 0x45, 0xce, 0xb2, 0x1d, 0x84, 0x7c, 0xed, 0xc0, 0x5a, 0x45, 0xb3, 0x41, 0x2e, 0xc1, 0xf8, 0xd6, - 0x88, 0x5c, 0x82, 0x49, 0xdd, 0x0a, 0x2e, 0x4a, 0xb0, 0xe3, 0xa2, 0x04, 0x7e, 0x10, 0x68, 0x09, - 0x64, 0x62, 0xcd, 0x2d, 0xf3, 0xcf, 0x1c, 0xd8, 0xac, 0x6e, 0x2c, 0x20, 0x2f, 0xeb, 0xbf, 0x19, - 0x99, 0xd4, 0xf2, 0xd0, 0xbe, 0x73, 0x19, 0x9a, 0x94, 0xe6, 0x65, 0x94, 0xe6, 0xa6, 0xdb, 0xe6, - 0xd2, 0xa4, 0x88, 0x5b, 0x25, 0xd0, 0x05, 0x56, 0x63, 0xed, 0xa7, 0x7b, 0x62, 0xc4, 0x16, 0xd5, - 0x1d, 0x0e, 0xed, 0xdb, 0x13, 0x30, 0x6c, 0xf7, 0x45, 0x36, 0xe4, 0x86, 0xe0, 0x7b, 0xb7, 0xee, - 0x01, 0x90, 0x67, 0x34, 0x7f, 0x1a, 0xb7, 0xce, 0x68, 0xe9, 0xb5, 0xdf, 0x3a, 0xa3, 0xe5, 0x07, - 0xf8, 0xd2, 0x19, 0x45, 0x66, 0xf8, 0x18, 0x4f, 0x3e, 0xc7, 0x63, 0x23, 0x9f, 0x02, 0x5a, 0xc5, - 0xa3, 0x9e, 0x55, 0x1d, 0x1b, 0xbb, 0xd8, 0x5f, 0x72, 0x95, 0xe2, 0x85, 0x81, 0x6b, 0xcf, 0x83, - 0x05, 0x85, 0x4e, 0xb6, 0x8a, 0x04, 0x14, 0xe5, 0xca, 0xd7, 0x5c, 0x77, 0x0b, 0x89, 0xae, 0xba, - 0x4d, 0x93, 0x28, 0xa7, 0x79, 0x02, 0x0d, 0xe3, 0xe5, 0x92, 0x68, 0x27, 0x5b, 0x7e, 0xa8, 0x6d, - 0x6f, 0x57, 0x8e, 0xd9, 0xae, 0xc4, 0x5d, 0xe6, 0x0c, 0x32, 0x44, 0xd0, 0x3c, 0x7e, 0x17, 0x16, - 0xad, 0xc7, 0xc3, 0x5c, 0xf9, 0x55, 0xcf, 0x9b, 0xb9, 0xf2, 0x2b, 0x5f, 0x1c, 0x55, 0xa0, 0xe9, - 0xa2, 0xf2, 0x33, 0x89, 0xa2, 0x79, 0x7d, 0x01, 0x75, 0xfd, 0x66, 0x97, 0xeb, 0xbf, 0xf8, 0x8c, - 0x77, 0x19, 0x0f, 0x6b, 0x0f, 0x2e, 0xf8, 0xe4, 0x93, 0x78, 0x70, 0x22, 0xf5, 0x65, 0xbc, 0x48, - 0xe5, 0xfa, 0x2a, 0x3f, 0xcb, 0xe5, 0xfa, 0xaa, 0x7a, 0xc2, 0xb2, 0xf4, 0xd5, 0x45, 0x04, 0xbd, - 0x86, 0x14, 0x96, 0x0b, 0x2f, 0x41, 0x79, 0x58, 0x51, 0xfd, 0xee, 0x95, 0x87, 0x15, 0x63, 0x9e, - 0x90, 0xec, 0xc0, 0x4d, 0xf0, 0xf3, 0xfb, 0xfd, 0xdc, 0xb6, 0x84, 0xbb, 0x17, 0xef, 0x24, 0x96, - 0xdd, 0x5a, 0x0f, 0x42, 0x96, 0xdd, 0xda, 0x8f, 0x2a, 0x25, 0x77, 0x2f, 0x32, 0x45, 0xf2, 0x14, - 0x16, 0x54, 0x81, 0x3e, 0x37, 0xda, 0xc2, 0xd3, 0x44, 0xbb, 0x55, 0x1e, 0x90, 0x54, 0x2d, 0xc3, - 0xf5, 0x83, 0x00, 0xa9, 0xca, 0x8d, 0x30, 0xca, 0xf5, 0xf9, 0x46, 0x94, 0x2b, 0xfd, 0xf9, 0x46, - 0x54, 0xd5, 0xf7, 0xad, 0x8d, 0x10, 0x9e, 0x4b, 0xf3, 0xf8, 0x7b, 0x07, 0xcb, 0x18, 0x93, 0xab, - 0xed, 0xe4, 0xcd, 0x2b, 0x14, 0xe6, 0x85, 0x40, 0x6f, 0x5d, 0xb9, 0x94, 0xef, 0xde, 0x45, 0x31, - 0x5d, 0x77, 0x57, 0x5d, 0xa6, 0x38, 0x2d, 0x10, 0xe8, 0xba, 0xae, 0xcf, 0x85, 0xfe, 0x5b, 0x47, - 0xfc, 0xfd, 0xee, 0x04, 0xba, 0x64, 0x6f, 0x4a, 0x01, 0x94, 0xc0, 0xf7, 0xa7, 0xc6, 0x97, 0xe2, - 0xde, 0x41, 0x71, 0x6f, 0xb9, 0xdb, 0x13, 0xc4, 0xe5, 0xc2, 0xfe, 0x3e, 0x6c, 0xeb, 0xaa, 0xbc, - 0x45, 0xf7, 0xc3, 0x61, 0x14, 0x64, 0x79, 0x5e, 0x3a, 0xa6, 0x74, 0x9f, 0x1b, 0x4e, 0xb1, 0x58, - 0x6b, 0xdf, 0x8f, 0x17, 0x72, 0x54, 0x88, 0xd1, 0xe3, 0xb4, 0x39, 0xf7, 0x04, 0x56, 0xd5, 0xbc, - 0x0f, 0x43, 0x9f, 0xfd, 0xc2, 0x3c, 0x6f, 0x21, 0xcf, 0xb6, 0xbb, 0x61, 0xf2, 0xec, 0x85, 0x3e, - 0xd3, 0x1c, 0x33, 0x7c, 0x64, 0xb5, 0xea, 0xb0, 0x66, 0xf2, 0x5d, 0x59, 0xa1, 0x35, 0x93, 0xef, - 0xea, 0x92, 0xb1, 0x9d, 0x7c, 0x9f, 0x52, 0x26, 0x4a, 0xb8, 0x81, 0x64, 0x70, 0x0e, 0x2b, 0xc7, - 0x63, 0x99, 0x1e, 0xff, 0xdc, 0x4c, 0x65, 0x0c, 0xe4, 0x22, 0xd3, 0xac, 0xc0, 0x94, 0x2f, 0xf6, - 0x5c, 0xbc, 0x28, 0x9b, 0x15, 0x5a, 0x72, 0x73, 0x7c, 0xed, 0xb6, 0xcc, 0xb7, 0xb2, 0xb8, 0x6b, - 0xf3, 0x35, 0x32, 0x24, 0xfc, 0xbb, 0x45, 0xce, 0x77, 0x04, 0xc4, 0xce, 0x92, 0xf0, 0xef, 0x5d, - 0xb4, 0x17, 0xa8, 0xa8, 0xcb, 0x4e, 0x97, 0x22, 0xdd, 0x46, 0xc6, 0xdb, 0xee, 0x66, 0x39, 0x45, - 0xe2, 0xbc, 0x39, 0xeb, 0xdf, 0x83, 0xb5, 0x42, 0xee, 0xfd, 0x82, 0x78, 0x5b, 0xe6, 0x5c, 0x48, - 0xbc, 0x15, 0x73, 0x86, 0x79, 0x70, 0xa1, 0xd8, 0x4a, 0x6e, 0x57, 0xe5, 0x1b, 0x56, 0x2d, 0x73, - 0x52, 0xe6, 0x23, 0xef, 0x0d, 0xb2, 0x59, 0x4a, 0x47, 0x90, 0xc2, 0x9b, 0x0e, 0xf9, 0x53, 0x07, - 0xff, 0xc4, 0x60, 0x4c, 0xad, 0x97, 0xdc, 0xab, 0x4a, 0x78, 0xaf, 0x2c, 0x86, 0xf4, 0x27, 0xe4, - 0x46, 0x31, 0x2b, 0x2e, 0x89, 0x73, 0x86, 0x15, 0x08, 0xb3, 0x62, 0x6b, 0xe5, 0xe4, 0x15, 0xa5, - 0xdc, 0xb1, 0x49, 0x6b, 0x31, 0x15, 0x97, 0x59, 0xa5, 0xe2, 0xf4, 0x63, 0xfb, 0x0f, 0x89, 0x2d, - 0x96, 0x77, 0x2a, 0x56, 0x7d, 0x15, 0xd6, 0x2f, 0x21, 0xeb, 0x5d, 0xb2, 0x5d, 0x58, 0x6f, 0x41, - 0x04, 0x11, 0xd6, 0xe6, 0xc5, 0x5c, 0x2b, 0xac, 0x2d, 0x95, 0x9f, 0xad, 0xb0, 0xb6, 0x5c, 0x01, - 0x2e, 0x85, 0xb5, 0x58, 0xe1, 0xc5, 0xcb, 0xf0, 0x64, 0x0e, 0xff, 0x8d, 0x91, 0xb7, 0xff, 0x2f, - 0x00, 0x00, 0xff, 0xff, 0xb9, 0x45, 0x93, 0xa0, 0x96, 0x44, 0x00, 0x00, + 0x17, 0x95, 0x8b, 0xbf, 0x0e, 0x24, 0xa5, 0x19, 0x6b, 0x27, 0x71, 0xaf, 0xc7, 0x53, 0xf2, 0x80, + 0xf6, 0xfc, 0xa1, 0x2c, 0xd9, 0xaf, 0xf0, 0x91, 0x63, 0x31, 0xf0, 0x3e, 0x87, 0x93, 0x2d, 0x98, + 0xf3, 0x93, 0xb0, 0xcd, 0xad, 0x46, 0xe4, 0xe3, 0xb3, 0x7e, 0x12, 0x7e, 0x4c, 0x87, 0xc4, 0x85, + 0x45, 0x39, 0xd0, 0xee, 0xd1, 0x0b, 0xda, 0xc3, 0x98, 0x6f, 0xca, 0x5b, 0x10, 0xc3, 0x9f, 0x70, + 0x10, 0xb9, 0x07, 0x2b, 0x49, 0x1a, 0x72, 0xf3, 0xcb, 0xdf, 0x06, 0xe6, 0x50, 0x9a, 0x65, 0x09, + 0x57, 0xab, 0x73, 0x7f, 0x04, 0x37, 0x2a, 0x74, 0x21, 0x7d, 0xd4, 0x77, 0x61, 0xd9, 0x7e, 0x61, + 0x50, 0x7e, 0x4a, 0x47, 0xad, 0xd6, 0x44, 0x6f, 0xa9, 0x6b, 0xd1, 0x91, 0xd1, 0x27, 0xe2, 0x78, + 0x3e, 0xd3, 0x35, 0x2d, 0xf7, 0x4b, 0x58, 0xcf, 0x81, 0x87, 0x71, 0x74, 0x41, 0xd3, 0x8c, 0x5b, + 0x1b, 0x81, 0xe9, 0x6e, 0x1a, 0xab, 0x82, 0x2c, 0xfe, 0xe6, 0x71, 0x1b, 0x8b, 0xa5, 0x19, 0xd4, + 0x58, 0xcc, 0x71, 0x52, 0x9f, 0xa9, 0x5b, 0x0a, 0x7f, 0xf3, 0x38, 0x39, 0x44, 0x22, 0xb4, 0x8d, + 0x63, 0xc2, 0x54, 0x17, 0x24, 0x8c, 0x73, 0x71, 0x9f, 0x62, 0xf8, 0x68, 0x8a, 0x22, 0xd7, 0xf8, + 0xeb, 0xb0, 0x20, 0xd6, 0xc8, 0x67, 0xaa, 0xf5, 0xed, 0x58, 0xeb, 0x2b, 0x88, 0xe9, 0x41, 0x57, + 0x43, 0xdd, 0xff, 0xae, 0x41, 0x03, 0x23, 0xd6, 0xf7, 0x29, 0xf3, 0xc3, 0xde, 0xf8, 0x58, 0x5a, + 0xc4, 0xa0, 0x35, 0x1d, 0x83, 0xbe, 0x02, 0x8b, 0x66, 0x41, 0x64, 0xa8, 0x92, 0x59, 0xa3, 0x1c, + 0x32, 0x24, 0xaf, 0xc2, 0x12, 0xa6, 0xd6, 0x39, 0x96, 0xb0, 0x99, 0x45, 0x84, 0x6a, 0x34, 0x3b, + 0x11, 0x98, 0x29, 0x24, 0x02, 0x7c, 0x18, 0x83, 0xe9, 0x76, 0x16, 0x06, 0x3a, 0x4f, 0x40, 0xc8, + 0x49, 0x18, 0x18, 0xc3, 0x38, 0x7b, 0xce, 0x18, 0xc6, 0xd9, 0x3c, 0x07, 0x4a, 0xa9, 0x78, 0x28, + 0xc0, 0xf7, 0xae, 0x79, 0x34, 0xba, 0x86, 0x02, 0x3e, 0x09, 0xfb, 0xf8, 0x1a, 0x26, 0x8b, 0xdb, + 0x75, 0x61, 0xb1, 0xe2, 0x2b, 0x4f, 0xd3, 0xc0, 0x4c, 0xd3, 0xf2, 0xa4, 0x6e, 0xc1, 0x4a, 0xea, + 0xf6, 0x60, 0x21, 0x4e, 0x68, 0xd4, 0x96, 0x29, 0x76, 0x43, 0x44, 0x0f, 0x1c, 0xf4, 0x14, 0x21, + 0xb2, 0x64, 0x82, 0x3a, 0xcf, 0x26, 0xc9, 0x4b, 0x6d, 0xc5, 0xd4, 0x8a, 0x8a, 0x51, 0x89, 0xe0, + 0xd4, 0x55, 0x89, 0xa0, 0x7b, 0x80, 0x51, 0xb1, 0x62, 0x2c, 0xcd, 0xe7, 0x75, 0x98, 0x45, 0x35, + 0x29, 0xcb, 0x59, 0xb7, 0xd2, 0x18, 0x69, 0x14, 0x9e, 0xc4, 0x71, 0xbf, 0x8f, 0x6f, 0x88, 0x38, + 0x34, 0x89, 0xe8, 0x37, 0x60, 0x5e, 0xec, 0x8a, 0xb6, 0x9a, 0x39, 0xfc, 0xfe, 0x28, 0x70, 0xff, + 0xcd, 0x01, 0x72, 0x32, 0x38, 0xed, 0x87, 0x93, 0x53, 0x9b, 0x3c, 0x41, 0x27, 0x30, 0x8d, 0x66, + 0x22, 0xcc, 0x11, 0x7f, 0x17, 0x2c, 0x64, 0xba, 0x68, 0x21, 0xf9, 0x76, 0xce, 0x54, 0xe7, 0xe8, + 0xb3, 0xe6, 0xe6, 0x73, 0x17, 0xdf, 0x0b, 0x69, 0xc4, 0xda, 0xb2, 0xd8, 0xc2, 0x5d, 0x3c, 0x02, + 0x3e, 0x0a, 0xdc, 0x13, 0x58, 0xb3, 0x56, 0x26, 0x35, 0x7d, 0x1b, 0x1a, 0x42, 0x80, 0xa4, 0xe7, + 0x77, 0x74, 0x35, 0x7c, 0x01, 0x61, 0xc7, 0x08, 0x1a, 0xa7, 0xaf, 0x3f, 0x71, 0x60, 0xfd, 0x24, + 0xec, 0x0f, 0x7a, 0x3e, 0xa3, 0xbf, 0x04, 0x8d, 0xe5, 0xcb, 0x9f, 0xb2, 0x96, 0xaf, 0x34, 0x39, + 0x9d, 0x6b, 0xd2, 0xfd, 0x99, 0x03, 0x1b, 0x05, 0x51, 0x74, 0x4c, 0x68, 0x1b, 0xd3, 0x88, 0xe2, + 0x80, 0x44, 0x32, 0x98, 0xd6, 0x2c, 0xa6, 0xaf, 0xc0, 0x62, 0x3f, 0x8c, 0xc2, 0xfe, 0xa0, 0xdf, + 0x16, 0xba, 0x17, 0x32, 0x35, 0x24, 0xf0, 0x18, 0xb7, 0x80, 0x23, 0xf9, 0xcf, 0x0d, 0xa4, 0x69, + 0x89, 0x24, 0x80, 0x02, 0xe9, 0x0d, 0x58, 0xcf, 0xe3, 0xf6, 0xf6, 0x99, 0x1f, 0x46, 0xed, 0x5e, + 0x9c, 0x65, 0x72, 0x8f, 0x49, 0x3e, 0x76, 0xe4, 0x87, 0xd1, 0x27, 0x71, 0x96, 0x19, 0x4e, 0x60, + 0xd6, 0x74, 0x02, 0x3c, 0x80, 0x59, 0xf9, 0xec, 0xdc, 0xef, 0xd1, 0xf7, 0xe2, 0xfe, 0xe9, 0x8b, + 0xd5, 0xfd, 0x6d, 0x68, 0x88, 0xba, 0x1b, 0xf3, 0xd3, 0x33, 0xaa, 0x76, 0x60, 0x01, 0x61, 0x4f, + 0x10, 0x54, 0xb9, 0x0d, 0xff, 0xe5, 0x00, 0x39, 0xe4, 0xa1, 0x4c, 0x6f, 0x62, 0x7b, 0xe0, 0xae, + 0x44, 0xe4, 0xcd, 0xb9, 0x85, 0xd5, 0x25, 0xe4, 0x23, 0xdb, 0xfc, 0xa6, 0x2c, 0xf3, 0xd3, 0xab, + 0x99, 0xbe, 0x66, 0x71, 0xac, 0xe4, 0xc7, 0x5f, 0x85, 0xa5, 0x4b, 0xbf, 0xd7, 0xa3, 0x4c, 0x3f, + 0xb1, 0xc9, 0x4a, 0xbc, 0x80, 0xaa, 0x1c, 0x5c, 0x2d, 0x78, 0xce, 0x58, 0xf0, 0x06, 0xac, 0x59, + 0xeb, 0x95, 0xd1, 0xd0, 0x23, 0xd8, 0x14, 0xe0, 0x83, 0x5e, 0x6f, 0x62, 0xaf, 0xea, 0xfe, 0x75, + 0x0d, 0xb6, 0x4a, 0xd3, 0x74, 0xd8, 0x60, 0x9b, 0xf1, 0x1d, 0xbd, 0xdc, 0xea, 0x09, 0xfb, 0xf2, + 0x53, 0xce, 0x6a, 0xfd, 0x93, 0x03, 0xb3, 0x02, 0x34, 0x76, 0x37, 0x3e, 0x57, 0x0e, 0x41, 0x1a, + 0x9c, 0xc8, 0x88, 0xbe, 0x3d, 0x19, 0x33, 0xf1, 0x3f, 0xf3, 0x59, 0x55, 0x78, 0x12, 0xf9, 0xa2, + 0xfa, 0x5d, 0x58, 0x29, 0x22, 0x5c, 0xeb, 0xc9, 0x49, 0x54, 0x55, 0x3e, 0xb8, 0xa0, 0xc6, 0x33, + 0xea, 0x4f, 0x1d, 0x58, 0x3e, 0x8c, 0xa3, 0x20, 0xe4, 0x37, 0xe6, 0xb1, 0x9f, 0xfa, 0xfd, 0x4c, + 0xbe, 0xe4, 0x0b, 0x90, 0x2a, 0xbb, 0x6b, 0xc0, 0x88, 0x02, 0xe7, 0x2e, 0x40, 0xe7, 0x9c, 0x76, + 0x9e, 0xb5, 0x65, 0xc5, 0x51, 0x3c, 0xff, 0x73, 0xc8, 0x7b, 0x61, 0x90, 0x91, 0x6f, 0xc1, 0x5a, + 0x3e, 0xdc, 0xf6, 0xa3, 0xa0, 0x2d, 0xcb, 0x8d, 0xf8, 0xba, 0xa1, 0xf1, 0x0e, 0xa2, 0xe0, 0x20, + 0x7b, 0x96, 0xf1, 0x58, 0x51, 0x57, 0xd9, 0xda, 0x96, 0x0b, 0x5f, 0xd6, 0xf0, 0x03, 0x04, 0xbb, + 0xff, 0xe3, 0xe0, 0x0d, 0xa8, 0x56, 0x25, 0x77, 0x3b, 0x2f, 0xac, 0x61, 0xbd, 0xd5, 0xda, 0xb2, + 0x5a, 0x61, 0xcb, 0x08, 0x4c, 0x87, 0x8c, 0xf6, 0xd5, 0xc5, 0xc2, 0x7f, 0x93, 0xf7, 0x60, 0x45, + 0xaf, 0xb8, 0x9d, 0xa0, 0x5a, 0xe4, 0x31, 0xd9, 0xca, 0x13, 0x47, 0x4b, 0x6b, 0xde, 0x72, 0xa7, + 0xa0, 0x46, 0x75, 0xbc, 0x66, 0x26, 0x72, 0xd4, 0x1d, 0xd4, 0xb6, 0xf4, 0x4f, 0xe2, 0x4b, 0x48, + 0x4d, 0x3b, 0x03, 0x46, 0x03, 0x19, 0x2a, 0xeb, 0x6f, 0xf7, 0x3f, 0x1c, 0x58, 0x3e, 0x08, 0x02, + 0x5c, 0xf7, 0x24, 0x6e, 0x42, 0xad, 0xb2, 0x76, 0xc5, 0x2a, 0xa7, 0x7e, 0xce, 0x55, 0xfe, 0xc2, + 0x4e, 0x64, 0x84, 0x12, 0x5c, 0x17, 0x56, 0xf2, 0x75, 0x56, 0x6f, 0xaf, 0xfb, 0x0d, 0x20, 0x22, + 0xbd, 0xb2, 0xd4, 0x51, 0xc4, 0xda, 0x80, 0x35, 0x0b, 0x4b, 0xfa, 0x9a, 0x0f, 0xe1, 0xee, 0x11, + 0x65, 0x87, 0xe9, 0x30, 0x61, 0xb1, 0x0a, 0x67, 0xdf, 0xa7, 0x49, 0x9c, 0x85, 0xca, 0x73, 0xd1, + 0x89, 0xbc, 0xcf, 0x3f, 0x3b, 0x70, 0x6f, 0x02, 0x42, 0x72, 0x09, 0x5f, 0x94, 0xeb, 0x4b, 0xbf, + 0x61, 0xb6, 0xb7, 0x4c, 0x44, 0x65, 0x5f, 0x43, 0x64, 0x97, 0x81, 0x26, 0xd9, 0x7a, 0x17, 0x96, + 0xec, 0xc1, 0x6b, 0xb9, 0x8a, 0x1e, 0xdc, 0xb9, 0x42, 0x88, 0x49, 0x6c, 0xee, 0x0e, 0x2c, 0x75, + 0x2c, 0x12, 0x92, 0x51, 0x01, 0xea, 0x1e, 0xc2, 0x6b, 0x57, 0x72, 0x93, 0x6a, 0x1b, 0x99, 0xa1, + 0xbb, 0x7f, 0x37, 0x0d, 0x5b, 0x9f, 0x85, 0xec, 0x3c, 0x48, 0xfd, 0x4b, 0x65, 0x7d, 0x93, 0x08, + 0x59, 0x48, 0xde, 0x6b, 0xe5, 0x7a, 0xc3, 0x7d, 0x58, 0x8d, 0x23, 0x8a, 0x39, 0x46, 0x3b, 0xf1, + 0xb3, 0xec, 0x32, 0x4e, 0xd5, 0x5d, 0xba, 0x1c, 0x47, 0x94, 0xe7, 0x19, 0xc7, 0x12, 0x5c, 0xb8, + 0x8d, 0xa7, 0x8b, 0xb7, 0xf1, 0x0a, 0x4c, 0x25, 0x61, 0x24, 0xdf, 0x4c, 0xf8, 0x4f, 0x7e, 0x77, + 0xb2, 0xd4, 0x0f, 0x0c, 0xca, 0xf2, 0xee, 0x44, 0xa8, 0xa6, 0x6b, 0x56, 0xf1, 0xe7, 0x0a, 0x55, + 0x7c, 0x43, 0x27, 0xf3, 0x76, 0xd5, 0x62, 0x0f, 0x16, 0xe4, 0xcf, 0x36, 0xf3, 0xcf, 0x64, 0x0a, + 0x04, 0x12, 0xf4, 0xc4, 0x3f, 0x33, 0xa2, 0x35, 0xb0, 0xa2, 0xb5, 0x5d, 0x80, 0x2e, 0xa5, 0x6d, + 0x2b, 0x19, 0xaa, 0x77, 0x29, 0x15, 0x4e, 0x97, 0x87, 0xca, 0xa7, 0x7e, 0xf4, 0xac, 0x8d, 0x35, + 0x88, 0x86, 0x10, 0x87, 0x03, 0x3e, 0xf5, 0xfb, 0x18, 0x13, 0xe3, 0xa0, 0x92, 0x69, 0x51, 0x68, + 0x94, 0xc3, 0x0e, 0xf2, 0x6a, 0x0a, 0xa2, 0x74, 0x42, 0x36, 0x6c, 0x2e, 0xe5, 0xf3, 0x0f, 0x43, + 0x36, 0xd4, 0xf3, 0x51, 0x67, 0xe9, 0xb0, 0xb9, 0x9c, 0xcf, 0x3f, 0x14, 0x20, 0x2e, 0x5e, 0x76, + 0x19, 0x76, 0xa9, 0x68, 0x0c, 0x59, 0x91, 0xad, 0x52, 0x1c, 0x72, 0x18, 0x07, 0x18, 0x46, 0x5e, + 0x86, 0xa9, 0x91, 0x9c, 0xae, 0x8a, 0x14, 0x96, 0x03, 0x95, 0x69, 0xb8, 0xf7, 0x61, 0x45, 0x99, + 0x8b, 0xd9, 0x3b, 0x99, 0xd2, 0x6c, 0xd0, 0x63, 0xaa, 0x77, 0x52, 0x7c, 0xb9, 0x6f, 0x62, 0x57, + 0xc4, 0x27, 0xf1, 0xd9, 0x59, 0x9e, 0x3e, 0x49, 0xd3, 0xda, 0x84, 0xd9, 0x1e, 0xc2, 0xd5, 0x14, + 0xf1, 0xe5, 0x46, 0x58, 0xcf, 0x29, 0x4c, 0xc9, 0x5f, 0x2d, 0xc2, 0xa8, 0x1b, 0xcb, 0x6c, 0x01, + 0x7f, 0xf3, 0xb3, 0x18, 0xd0, 0xd3, 0xc1, 0x99, 0xea, 0x81, 0xc2, 0x0f, 0x8e, 0x79, 0xe9, 0xa7, + 0x91, 0xbc, 0x50, 0xf1, 0x37, 0xc7, 0xa4, 0x69, 0x1a, 0xa7, 0xf2, 0xf6, 0x14, 0x1f, 0xee, 0x11, + 0x6c, 0x9d, 0x5c, 0x4f, 0x44, 0x4e, 0x48, 0x54, 0x6b, 0xe4, 0xf1, 0xc7, 0x0f, 0xf7, 0x63, 0xab, + 0x03, 0x04, 0xbb, 0x04, 0x26, 0x39, 0x46, 0xeb, 0x30, 0x83, 0xbe, 0x5c, 0x11, 0xc3, 0x0f, 0x9e, + 0x11, 0x36, 0xcb, 0xd4, 0x74, 0x0f, 0x5a, 0xb9, 0xa3, 0x42, 0x78, 0xc2, 0x5f, 0xa9, 0xe8, 0xa8, + 0xb0, 0xe6, 0x4e, 0xd6, 0x52, 0xf1, 0x4b, 0xed, 0x92, 0xf8, 0x0a, 0xd6, 0x4c, 0xd1, 0x5e, 0x6a, + 0xd6, 0xff, 0x63, 0x07, 0x2b, 0x64, 0x3a, 0x03, 0x3b, 0x61, 0x29, 0xf5, 0xfb, 0x2f, 0xf5, 0x41, + 0xfc, 0x7b, 0x70, 0xdb, 0xec, 0x97, 0xba, 0xb6, 0x24, 0xee, 0x1f, 0xe0, 0x33, 0xa2, 0x78, 0xe4, + 0xff, 0x7f, 0x90, 0xff, 0x5d, 0xb8, 0x69, 0xc8, 0x7f, 0x4d, 0x31, 0xdc, 0xbf, 0x72, 0xb0, 0x8a, + 0x78, 0x30, 0x08, 0x42, 0x66, 0xc5, 0x1c, 0xdc, 0x33, 0x31, 0x3f, 0x65, 0xed, 0xc0, 0x67, 0x54, + 0x37, 0x71, 0x72, 0xc8, 0xfb, 0x3e, 0xc3, 0xe2, 0x09, 0x8d, 0x02, 0x31, 0x28, 0x8b, 0x01, 0x34, + 0x0a, 0xd4, 0x90, 0xc8, 0x1c, 0x4e, 0x87, 0x56, 0xa2, 0xf6, 0x1e, 0xde, 0xd3, 0xd8, 0xf4, 0x82, + 0x27, 0x7e, 0xc6, 0x13, 0x1f, 0xfc, 0x58, 0xc7, 0xdd, 0x2e, 0x3f, 0x72, 0x33, 0x08, 0x96, 0x5f, + 0xee, 0xa1, 0x78, 0x94, 0x36, 0x44, 0x93, 0xe7, 0xed, 0x3e, 0xcc, 0x52, 0x0c, 0x93, 0x8b, 0xaf, + 0xdb, 0x06, 0xae, 0xc4, 0x70, 0x9f, 0x03, 0xe4, 0x50, 0xee, 0x86, 0x50, 0x8b, 0xb2, 0x06, 0xca, + 0x7f, 0x93, 0x9b, 0x00, 0x61, 0x40, 0x23, 0x16, 0x76, 0x43, 0xaa, 0xfa, 0x0f, 0x0c, 0x08, 0xbf, + 0x95, 0xfa, 0x34, 0xcb, 0xd4, 0xe3, 0x5d, 0xdd, 0x53, 0x9f, 0x3c, 0xbf, 0xe0, 0x77, 0x69, 0xc6, + 0xfc, 0x7e, 0xa2, 0xae, 0x48, 0x0d, 0x78, 0xf8, 0xb3, 0xfb, 0xb0, 0x74, 0x14, 0x8b, 0x60, 0xe0, + 0x09, 0xbf, 0x03, 0x53, 0xf2, 0x18, 0xe6, 0x64, 0xf3, 0x35, 0xd9, 0x2c, 0x75, 0x63, 0xa3, 0xde, + 0x5b, 0x5b, 0x23, 0xba, 0xb4, 0xdd, 0xb5, 0xaf, 0xff, 0xf5, 0xdf, 0x7f, 0x52, 0x5b, 0x24, 0x0b, + 0x0f, 0x2e, 0xde, 0x7c, 0x70, 0x46, 0x19, 0x3a, 0xdb, 0x33, 0x58, 0xb4, 0xfa, 0x65, 0xc9, 0x8e, + 0xd5, 0xf3, 0x5a, 0x68, 0xa3, 0x6d, 0xed, 0x8e, 0xed, 0x88, 0x75, 0x6f, 0x20, 0x8b, 0x35, 0xb2, + 0x2a, 0x59, 0xe4, 0xad, 0xb0, 0xe4, 0x4b, 0x58, 0xfe, 0x00, 0x8b, 0xf0, 0x9a, 0x28, 0xd9, 0xcb, + 0x89, 0x55, 0xb6, 0x01, 0xb7, 0x6e, 0x8d, 0x46, 0x90, 0x0c, 0xb7, 0x91, 0xe1, 0x06, 0x59, 0xe3, + 0x0c, 0x45, 0x91, 0x5f, 0xf3, 0x24, 0x19, 0xac, 0xc8, 0xc6, 0xc2, 0x17, 0xca, 0x73, 0x07, 0x79, + 0x6e, 0x92, 0x75, 0xce, 0x33, 0x10, 0x0c, 0x72, 0xa6, 0x31, 0xd6, 0x10, 0xcd, 0x46, 0x58, 0x72, + 0x73, 0x64, 0x87, 0xac, 0x60, 0xb9, 0x77, 0x45, 0x07, 0xad, 0xbd, 0xca, 0x33, 0xca, 0x71, 0x75, + 0x13, 0x2d, 0xf9, 0x89, 0xb8, 0x58, 0x2a, 0x5b, 0xb6, 0xc9, 0x6b, 0x57, 0xf7, 0x89, 0x0b, 0x19, + 0xee, 0x4e, 0xda, 0x50, 0xee, 0x7e, 0x03, 0x85, 0xb9, 0x49, 0x76, 0xa4, 0x30, 0x56, 0x13, 0xb9, + 0x6a, 0x53, 0x27, 0x1d, 0x68, 0x98, 0xdd, 0xaf, 0x64, 0xbb, 0xe2, 0x1e, 0xd3, 0xcc, 0x77, 0xaa, + 0x07, 0x25, 0xc3, 0x26, 0x32, 0x24, 0x64, 0x45, 0x32, 0xd4, 0xcd, 0xb2, 0xe4, 0x2b, 0x58, 0x2e, + 0x74, 0x8e, 0x12, 0xb7, 0xb0, 0x7d, 0x15, 0x5d, 0xc0, 0xad, 0x57, 0xc6, 0xe2, 0x48, 0xae, 0x37, + 0x91, 0x6b, 0xd3, 0x5d, 0x33, 0x76, 0x59, 0x71, 0xfe, 0x8e, 0x73, 0x9f, 0x64, 0xb8, 0xcf, 0x66, + 0x93, 0xe3, 0x44, 0xbc, 0xf7, 0xae, 0xe8, 0x90, 0x2c, 0xed, 0xb5, 0xe2, 0x89, 0xa7, 0x35, 0xc3, + 0xc6, 0x31, 0xa3, 0x35, 0x17, 0x83, 0xbc, 0x49, 0xf8, 0xee, 0x56, 0xb7, 0xf6, 0xca, 0xee, 0x62, + 0xb7, 0x85, 0x5c, 0xd7, 0x09, 0x29, 0x70, 0x8d, 0x59, 0x42, 0x32, 0xab, 0xf3, 0x59, 0x32, 0xb5, + 0xad, 0xba, 0xa2, 0xf7, 0xb8, 0x72, 0xa5, 0x66, 0x33, 0xf1, 0xc8, 0x95, 0xc6, 0x2c, 0xc9, 0xc8, + 0x73, 0x58, 0x12, 0xee, 0xe2, 0xc5, 0xef, 0xec, 0x2e, 0xf2, 0xdd, 0x72, 0x49, 0xee, 0x33, 0xcc, + 0x8d, 0xfd, 0x0c, 0xea, 0xfa, 0x36, 0x26, 0x4d, 0x63, 0x11, 0x56, 0x1b, 0x68, 0x6b, 0x44, 0x93, + 0x9f, 0xb2, 0x56, 0x77, 0x51, 0xae, 0x4a, 0xb4, 0xec, 0x71, 0xc2, 0x3f, 0x02, 0xc8, 0xbb, 0xfe, + 0xc8, 0x8d, 0x12, 0x65, 0xad, 0xb9, 0x56, 0xd5, 0x90, 0xfa, 0xfb, 0x06, 0x24, 0xbf, 0x42, 0x96, + 0x2c, 0xf2, 0xea, 0xbc, 0xe9, 0xe0, 0xc3, 0x3a, 0x6f, 0xc5, 0x3e, 0xc1, 0xd6, 0xe8, 0x06, 0x31, + 0xb5, 0x29, 0xae, 0x3a, 0x6c, 0xba, 0xc8, 0xc4, 0x57, 0x20, 0x2e, 0x0b, 0xa3, 0x33, 0x6d, 0xa7, + 0x8a, 0x4b, 0xe5, 0x65, 0x51, 0x6e, 0x33, 0x2b, 0x5d, 0x16, 0x79, 0x37, 0x19, 0x79, 0x86, 0x7f, + 0xdf, 0x65, 0x34, 0x56, 0x11, 0x93, 0x56, 0xb9, 0xcb, 0xac, 0x75, 0x73, 0xd4, 0x70, 0x56, 0x6d, + 0xdf, 0x32, 0x0f, 0xc5, 0x43, 0x25, 0x36, 0x5c, 0xb4, 0x53, 0x59, 0x1b, 0x6e, 0x75, 0x5d, 0xb5, + 0x6e, 0x54, 0x8c, 0x48, 0xea, 0x1b, 0x48, 0x7d, 0x99, 0x2c, 0x6a, 0x97, 0x88, 0xb4, 0xc4, 0x9e, + 0xe8, 0x77, 0x6e, 0x6b, 0x4f, 0x8a, 0xcd, 0x50, 0x96, 0x0f, 0x2c, 0xb5, 0x44, 0x95, 0x7c, 0xa0, + 0x6e, 0x7a, 0x22, 0x7f, 0x68, 0xf7, 0x56, 0xa9, 0x5e, 0x0f, 0x77, 0x6c, 0x73, 0x46, 0xe9, 0xb4, + 0x8c, 0x6c, 0xe0, 0x70, 0xf7, 0x90, 0xf3, 0x0d, 0xb2, 0x55, 0xe4, 0x2c, 0x9b, 0x41, 0xc8, 0xd7, + 0x0e, 0xac, 0x55, 0xb4, 0x1a, 0xe4, 0x12, 0x8c, 0x6e, 0x8c, 0xc8, 0x25, 0x18, 0xd7, 0xab, 0xe0, + 0xa2, 0x04, 0x3b, 0x2e, 0x4a, 0xe0, 0x07, 0x81, 0x96, 0x40, 0xa6, 0xd5, 0xdc, 0x32, 0xff, 0xcc, + 0x81, 0xcd, 0xea, 0xb6, 0x02, 0xf2, 0xaa, 0xfe, 0x8b, 0x91, 0x71, 0x0d, 0x0f, 0xad, 0x3b, 0x57, + 0xa1, 0x49, 0x69, 0x5e, 0x45, 0x69, 0xf6, 0xdc, 0x16, 0x97, 0x26, 0x45, 0xdc, 0x2a, 0x81, 0x2e, + 0xb1, 0x16, 0x6b, 0x3f, 0xdc, 0x13, 0x23, 0xb6, 0xa8, 0xee, 0x6f, 0x68, 0xdd, 0x1e, 0x83, 0x61, + 0xbb, 0x2f, 0xb2, 0x21, 0x37, 0x04, 0x5f, 0xbb, 0x75, 0x07, 0x80, 0x3c, 0xa3, 0xf9, 0xc3, 0xb8, + 0x75, 0x46, 0x4b, 0x6f, 0xfd, 0xd6, 0x19, 0x2d, 0x3f, 0xbf, 0x97, 0xce, 0x28, 0x32, 0xc3, 0xa7, + 0x78, 0xf2, 0x39, 0x1e, 0x1b, 0xf9, 0x10, 0xd0, 0x2c, 0x1e, 0xf5, 0xac, 0xea, 0xd8, 0xd8, 0xa5, + 0xfe, 0x92, 0xab, 0x14, 0xef, 0x0b, 0x5c, 0x7b, 0x1e, 0xcc, 0x2b, 0x74, 0xb2, 0x55, 0x24, 0xa0, + 0x28, 0x57, 0xbe, 0xe5, 0xba, 0x5b, 0x48, 0x74, 0xd5, 0x6d, 0x98, 0x44, 0x39, 0xcd, 0x53, 0x58, + 0x30, 0xde, 0x2d, 0x89, 0x76, 0xb2, 0xe5, 0x67, 0xda, 0xd6, 0x76, 0xe5, 0x98, 0xed, 0x4a, 0xdc, + 0x65, 0xce, 0x20, 0x43, 0x04, 0xcd, 0xe3, 0x77, 0x61, 0xd1, 0x7a, 0x3a, 0xcc, 0x95, 0x5f, 0xf5, + 0xb8, 0x99, 0x2b, 0xbf, 0xf2, 0xbd, 0x51, 0x05, 0x9a, 0x2e, 0x2a, 0x3f, 0x93, 0x28, 0x9a, 0xd7, + 0x17, 0x50, 0xd7, 0x2f, 0x76, 0xb9, 0xfe, 0x8b, 0x8f, 0x78, 0x57, 0xf1, 0xb0, 0xf6, 0xe0, 0x92, + 0x4f, 0x3e, 0x8d, 0xfb, 0xa7, 0x52, 0x5f, 0xc6, 0x7b, 0x54, 0xae, 0xaf, 0xf2, 0xa3, 0x5c, 0xae, + 0xaf, 0xaa, 0x07, 0x2c, 0x4b, 0x5f, 0x1d, 0x44, 0xd0, 0x6b, 0x48, 0x61, 0xb9, 0xf0, 0x0e, 0x94, + 0x87, 0x15, 0xd5, 0xaf, 0x5e, 0x79, 0x58, 0x31, 0xe2, 0x01, 0xc9, 0x0e, 0xdc, 0x04, 0x3f, 0xbf, + 0xd7, 0xcb, 0x6d, 0x4b, 0xb8, 0x7b, 0xf1, 0x4a, 0x62, 0xd9, 0xad, 0xf5, 0x1c, 0x64, 0xd9, 0xad, + 0xfd, 0xa4, 0x52, 0x72, 0xf7, 0x22, 0x51, 0x24, 0x4f, 0x61, 0x5e, 0x95, 0xe7, 0x73, 0xa3, 0x2d, + 0x3c, 0x4c, 0xb4, 0x9a, 0xe5, 0x01, 0x49, 0xd5, 0x32, 0x5c, 0x3f, 0x08, 0x90, 0xaa, 0xdc, 0x08, + 0xa3, 0x58, 0x9f, 0x6f, 0x44, 0xb9, 0xce, 0x9f, 0x6f, 0x44, 0x55, 0x75, 0xdf, 0xda, 0x08, 0xe1, + 0xb9, 0x34, 0x8f, 0xbf, 0x77, 0xb0, 0x88, 0x31, 0xbe, 0xd6, 0x4e, 0xde, 0xb8, 0x46, 0x59, 0x5e, + 0x08, 0xf4, 0xe6, 0xb5, 0x0b, 0xf9, 0xee, 0x5d, 0x14, 0xd3, 0x75, 0x77, 0xd5, 0x65, 0x8a, 0xd3, + 0x02, 0x81, 0xae, 0xab, 0xfa, 0x5c, 0xe8, 0xbf, 0x75, 0xc4, 0x5f, 0xef, 0x8e, 0xa1, 0x4b, 0xf6, + 0x27, 0x14, 0x40, 0x09, 0xfc, 0x60, 0x62, 0x7c, 0x29, 0xee, 0x1d, 0x14, 0xf7, 0x96, 0xbb, 0x3d, + 0x46, 0x5c, 0x2e, 0xec, 0xef, 0xc3, 0xb6, 0xae, 0xc9, 0x5b, 0x74, 0x3f, 0x1c, 0x44, 0x41, 0x96, + 0xe7, 0xa5, 0x23, 0x0a, 0xf7, 0xb9, 0xe1, 0x14, 0x4b, 0xb5, 0xf6, 0xfd, 0x78, 0x29, 0x47, 0x85, + 0x18, 0x5d, 0x4e, 0x9b, 0x73, 0x4f, 0x60, 0x55, 0xcd, 0xfb, 0x30, 0xf4, 0xd9, 0x2f, 0xcc, 0xf3, + 0x16, 0xf2, 0x6c, 0xb9, 0x1b, 0x26, 0xcf, 0x6e, 0xe8, 0x33, 0xcd, 0x31, 0xc3, 0x27, 0x56, 0xab, + 0x0a, 0x6b, 0x26, 0xdf, 0x95, 0xf5, 0x59, 0x33, 0xf9, 0xae, 0x2e, 0x18, 0xdb, 0xc9, 0xf7, 0x19, + 0x65, 0xa2, 0x80, 0x1b, 0x48, 0x06, 0x17, 0xb0, 0x72, 0x32, 0x92, 0xe9, 0xc9, 0xcf, 0xcd, 0x54, + 0xc6, 0x40, 0x2e, 0x32, 0xcd, 0x0a, 0x4c, 0xf9, 0x62, 0x2f, 0xc4, 0x7b, 0xb2, 0x59, 0x9f, 0x25, + 0x7b, 0xa3, 0x2b, 0xb7, 0x65, 0xbe, 0x95, 0xa5, 0x5d, 0x9b, 0xaf, 0x91, 0x21, 0xe1, 0x5f, 0x2d, + 0x72, 0xbe, 0x43, 0x20, 0x76, 0x96, 0x84, 0x7f, 0xed, 0xa2, 0xbd, 0x40, 0x45, 0x55, 0x76, 0xb2, + 0x14, 0xe9, 0x36, 0x32, 0xde, 0x76, 0x37, 0xcb, 0x29, 0x12, 0xe7, 0xcd, 0x59, 0xff, 0x1e, 0xac, + 0x15, 0x72, 0xef, 0x17, 0xc4, 0xdb, 0x32, 0xe7, 0x42, 0xe2, 0xad, 0x98, 0x33, 0xcc, 0x83, 0x0b, + 0xa5, 0x56, 0x72, 0xbb, 0x2a, 0xdf, 0xb0, 0x2a, 0x99, 0xe3, 0x32, 0x1f, 0x79, 0x6f, 0x90, 0xcd, + 0x52, 0x3a, 0x82, 0x14, 0xde, 0x70, 0xc8, 0x9f, 0x3a, 0xf8, 0x07, 0x06, 0x23, 0x2a, 0xbd, 0xe4, + 0x5e, 0x55, 0xc2, 0x7b, 0x6d, 0x31, 0xa4, 0x3f, 0x21, 0x37, 0x8b, 0x59, 0x71, 0x49, 0x9c, 0x73, + 0xac, 0x40, 0x98, 0xf5, 0x5a, 0x2b, 0x27, 0xaf, 0x28, 0xe4, 0x8e, 0x4c, 0x5a, 0x8b, 0xa9, 0xb8, + 0xcc, 0x2a, 0x15, 0xa7, 0x1f, 0xdb, 0x7f, 0x46, 0x6c, 0xb1, 0xbc, 0x53, 0xb1, 0xea, 0xeb, 0xb0, + 0x7e, 0x05, 0x59, 0xef, 0x92, 0xed, 0xc2, 0x7a, 0x0b, 0x22, 0x88, 0xb0, 0xd6, 0x28, 0xc4, 0x9a, + 0x61, 0x6d, 0xa9, 0xf8, 0x6c, 0x85, 0xb5, 0xe5, 0xfa, 0x6f, 0x29, 0xac, 0xf5, 0x39, 0x0a, 0x5e, + 0x86, 0xa7, 0xb3, 0xf8, 0x2f, 0x8c, 0xbc, 0xf5, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x02, 0x27, + 0x05, 0x6d, 0x94, 0x44, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -6146,6 +6148,155 @@ type GoCryptoTraderServer interface { GetAuditEvent(context.Context, *GetAuditEventRequest) (*GetAuditEventResponse, error) } +// UnimplementedGoCryptoTraderServer can be embedded to have forward compatible implementations. +type UnimplementedGoCryptoTraderServer struct { +} + +func (*UnimplementedGoCryptoTraderServer) GetInfo(ctx context.Context, req *GetInfoRequest) (*GetInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetSubsystems(ctx context.Context, req *GetSubsystemsRequest) (*GetSusbsytemsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSubsystems not implemented") +} +func (*UnimplementedGoCryptoTraderServer) EnableSubsystem(ctx context.Context, req *GenericSubsystemRequest) (*GenericSubsystemResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EnableSubsystem not implemented") +} +func (*UnimplementedGoCryptoTraderServer) DisableSubsystem(ctx context.Context, req *GenericSubsystemRequest) (*GenericSubsystemResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DisableSubsystem not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetRPCEndpoints(ctx context.Context, req *GetRPCEndpointsRequest) (*GetRPCEndpointsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRPCEndpoints not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetCommunicationRelayers(ctx context.Context, req *GetCommunicationRelayersRequest) (*GetCommunicationRelayersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCommunicationRelayers not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchanges(ctx context.Context, req *GetExchangesRequest) (*GetExchangesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchanges not implemented") +} +func (*UnimplementedGoCryptoTraderServer) DisableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DisableExchange not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeInfo(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangeInfo not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCode(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeOTPReponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangeOTPCode not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCodes(ctx context.Context, req *GetExchangeOTPsRequest) (*GetExchangeOTPsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangeOTPCodes not implemented") +} +func (*UnimplementedGoCryptoTraderServer) EnableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EnableExchange not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetTicker(ctx context.Context, req *GetTickerRequest) (*TickerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTicker not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetTickers(ctx context.Context, req *GetTickersRequest) (*GetTickersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTickers not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrderbook(ctx context.Context, req *GetOrderbookRequest) (*OrderbookResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrderbook not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrderbooks(ctx context.Context, req *GetOrderbooksRequest) (*GetOrderbooksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrderbooks not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetAccountInfo(ctx context.Context, req *GetAccountInfoRequest) (*GetAccountInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAccountInfo not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetConfig(ctx context.Context, req *GetConfigRequest) (*GetConfigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetPortfolio(ctx context.Context, req *GetPortfolioRequest) (*GetPortfolioResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPortfolio not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetPortfolioSummary(ctx context.Context, req *GetPortfolioSummaryRequest) (*GetPortfolioSummaryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPortfolioSummary not implemented") +} +func (*UnimplementedGoCryptoTraderServer) AddPortfolioAddress(ctx context.Context, req *AddPortfolioAddressRequest) (*AddPortfolioAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddPortfolioAddress not implemented") +} +func (*UnimplementedGoCryptoTraderServer) RemovePortfolioAddress(ctx context.Context, req *RemovePortfolioAddressRequest) (*RemovePortfolioAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemovePortfolioAddress not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetForexProviders(ctx context.Context, req *GetForexProvidersRequest) (*GetForexProvidersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetForexProviders not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetForexRates(ctx context.Context, req *GetForexRatesRequest) (*GetForexRatesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetForexRates not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrders(ctx context.Context, req *GetOrdersRequest) (*GetOrdersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrders not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrder(ctx context.Context, req *GetOrderRequest) (*OrderDetails, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) SubmitOrder(ctx context.Context, req *SubmitOrderRequest) (*SubmitOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) SimulateOrder(ctx context.Context, req *SimulateOrderRequest) (*SimulateOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SimulateOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WhaleBomb(ctx context.Context, req *WhaleBombRequest) (*SimulateOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WhaleBomb not implemented") +} +func (*UnimplementedGoCryptoTraderServer) CancelOrder(ctx context.Context, req *CancelOrderRequest) (*CancelOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CancelOrder not implemented") +} +func (*UnimplementedGoCryptoTraderServer) CancelAllOrders(ctx context.Context, req *CancelAllOrdersRequest) (*CancelAllOrdersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CancelAllOrders not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetEvents(ctx context.Context, req *GetEventsRequest) (*GetEventsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented") +} +func (*UnimplementedGoCryptoTraderServer) AddEvent(ctx context.Context, req *AddEventRequest) (*AddEventResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddEvent not implemented") +} +func (*UnimplementedGoCryptoTraderServer) RemoveEvent(ctx context.Context, req *RemoveEventRequest) (*RemoveEventResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveEvent not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddresses(ctx context.Context, req *GetCryptocurrencyDepositAddressesRequest) (*GetCryptocurrencyDepositAddressesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddresses not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddress(ctx context.Context, req *GetCryptocurrencyDepositAddressRequest) (*GetCryptocurrencyDepositAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddress not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WithdrawCryptocurrencyFunds(ctx context.Context, req *WithdrawCurrencyRequest) (*WithdrawResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WithdrawCryptocurrencyFunds not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WithdrawFiatFunds(ctx context.Context, req *WithdrawCurrencyRequest) (*WithdrawResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WithdrawFiatFunds not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetLoggerDetails(ctx context.Context, req *GetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetLoggerDetails not implemented") +} +func (*UnimplementedGoCryptoTraderServer) SetLoggerDetails(ctx context.Context, req *SetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetLoggerDetails not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangePairs(ctx context.Context, req *GetExchangePairsRequest) (*GetExchangePairsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangePairs not implemented") +} +func (*UnimplementedGoCryptoTraderServer) EnableExchangePair(ctx context.Context, req *ExchangePairRequest) (*GenericExchangeNameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EnableExchangePair not implemented") +} +func (*UnimplementedGoCryptoTraderServer) DisableExchangePair(ctx context.Context, req *ExchangePairRequest) (*GenericExchangeNameResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DisableExchangePair not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetOrderbookStream(req *GetOrderbookStreamRequest, srv GoCryptoTrader_GetOrderbookStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetOrderbookStream not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeOrderbookStream(req *GetExchangeOrderbookStreamRequest, srv GoCryptoTrader_GetExchangeOrderbookStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetExchangeOrderbookStream not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetTickerStream(req *GetTickerStreamRequest, srv GoCryptoTrader_GetTickerStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetTickerStream not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeTickerStream(req *GetExchangeTickerStreamRequest, srv GoCryptoTrader_GetExchangeTickerStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetExchangeTickerStream not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetAuditEvent(ctx context.Context, req *GetAuditEventRequest) (*GetAuditEventResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAuditEvent not implemented") +} + func RegisterGoCryptoTraderServer(s *grpc.Server, srv GoCryptoTraderServer) { s.RegisterService(&_GoCryptoTrader_serviceDesc, srv) } diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 1aacb63d..6788ff42 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -13,6 +13,7 @@ import ( "io" "net/http" + "github.com/golang/protobuf/descriptor" "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/grpc-ecosystem/grpc-gateway/utilities" @@ -22,11 +23,13 @@ import ( "google.golang.org/grpc/status" ) +// Suppress "imported and not used" errors var _ codes.Code var _ io.Reader var _ status.Status var _ = runtime.String var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage func request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetInfoRequest @@ -37,6 +40,15 @@ func request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Mar } +func local_request_GoCryptoTrader_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetInfoRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetInfo(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetSubsystems_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetSubsystemsRequest var metadata runtime.ServerMetadata @@ -46,6 +58,15 @@ func request_GoCryptoTrader_GetSubsystems_0(ctx context.Context, marshaler runti } +func local_request_GoCryptoTrader_GetSubsystems_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetSubsystemsRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetSubsystems(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_EnableSubsystem_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -66,6 +87,19 @@ func request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshaler run } +func local_request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericSubsystemRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_EnableSubsystem_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.EnableSubsystem(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_DisableSubsystem_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -86,6 +120,19 @@ func request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marshaler ru } +func local_request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericSubsystemRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_DisableSubsystem_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DisableSubsystem(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetRPCEndpointsRequest var metadata runtime.ServerMetadata @@ -95,6 +142,15 @@ func request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler run } +func local_request_GoCryptoTrader_GetRPCEndpoints_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetRPCEndpointsRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetRPCEndpoints(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetCommunicationRelayers_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetCommunicationRelayersRequest var metadata runtime.ServerMetadata @@ -104,6 +160,15 @@ func request_GoCryptoTrader_GetCommunicationRelayers_0(ctx context.Context, mars } +func local_request_GoCryptoTrader_GetCommunicationRelayers_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCommunicationRelayersRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetCommunicationRelayers(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetExchanges_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -124,6 +189,19 @@ func request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtim } +func local_request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangesRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetExchanges(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_DisableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata @@ -141,6 +219,23 @@ func request_GoCryptoTrader_DisableExchange_0(ctx context.Context, marshaler run } +func local_request_GoCryptoTrader_DisableExchange_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DisableExchange(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetExchangeInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -161,6 +256,19 @@ func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler run } +func local_request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetExchangeInfo(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetExchangeOTPCode_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -181,6 +289,19 @@ func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetExchangeOTPCode(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetExchangeOTPsRequest var metadata runtime.ServerMetadata @@ -190,6 +311,15 @@ func request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangeOTPsRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetExchangeOTPCodes(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata @@ -207,6 +337,23 @@ func request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runt } +func local_request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GenericExchangeNameRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.EnableExchange(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetTicker_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetTickerRequest var metadata runtime.ServerMetadata @@ -224,6 +371,23 @@ func request_GoCryptoTrader_GetTicker_0(ctx context.Context, marshaler runtime.M } +func local_request_GoCryptoTrader_GetTicker_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetTickerRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetTicker(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetTickers_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetTickersRequest var metadata runtime.ServerMetadata @@ -233,6 +397,15 @@ func request_GoCryptoTrader_GetTickers_0(ctx context.Context, marshaler runtime. } +func local_request_GoCryptoTrader_GetTickers_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetTickersRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetTickers(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetOrderbook_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrderbookRequest var metadata runtime.ServerMetadata @@ -250,6 +423,23 @@ func request_GoCryptoTrader_GetOrderbook_0(ctx context.Context, marshaler runtim } +func local_request_GoCryptoTrader_GetOrderbook_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrderbookRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetOrderbook(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetOrderbooks_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrderbooksRequest var metadata runtime.ServerMetadata @@ -259,6 +449,15 @@ func request_GoCryptoTrader_GetOrderbooks_0(ctx context.Context, marshaler runti } +func local_request_GoCryptoTrader_GetOrderbooks_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrderbooksRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetOrderbooks(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetAccountInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -279,6 +478,19 @@ func request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runt } +func local_request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetAccountInfoRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetAccountInfo(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetConfigRequest var metadata runtime.ServerMetadata @@ -288,6 +500,15 @@ func request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.M } +func local_request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetConfigRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetConfig(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetPortfolio_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetPortfolioRequest var metadata runtime.ServerMetadata @@ -297,6 +518,15 @@ func request_GoCryptoTrader_GetPortfolio_0(ctx context.Context, marshaler runtim } +func local_request_GoCryptoTrader_GetPortfolio_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetPortfolioRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetPortfolio(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetPortfolioSummary_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetPortfolioSummaryRequest var metadata runtime.ServerMetadata @@ -306,6 +536,15 @@ func request_GoCryptoTrader_GetPortfolioSummary_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_GetPortfolioSummary_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetPortfolioSummaryRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetPortfolioSummary(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_AddPortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq AddPortfolioAddressRequest var metadata runtime.ServerMetadata @@ -323,6 +562,23 @@ func request_GoCryptoTrader_AddPortfolioAddress_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_AddPortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddPortfolioAddressRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.AddPortfolioAddress(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_RemovePortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq RemovePortfolioAddressRequest var metadata runtime.ServerMetadata @@ -340,6 +596,23 @@ func request_GoCryptoTrader_RemovePortfolioAddress_0(ctx context.Context, marsha } +func local_request_GoCryptoTrader_RemovePortfolioAddress_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RemovePortfolioAddressRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.RemovePortfolioAddress(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetForexProviders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetForexProvidersRequest var metadata runtime.ServerMetadata @@ -349,6 +622,15 @@ func request_GoCryptoTrader_GetForexProviders_0(ctx context.Context, marshaler r } +func local_request_GoCryptoTrader_GetForexProviders_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetForexProvidersRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetForexProviders(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetForexRates_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetForexRatesRequest var metadata runtime.ServerMetadata @@ -358,6 +640,15 @@ func request_GoCryptoTrader_GetForexRates_0(ctx context.Context, marshaler runti } +func local_request_GoCryptoTrader_GetForexRates_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetForexRatesRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetForexRates(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetOrders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrdersRequest var metadata runtime.ServerMetadata @@ -375,6 +666,23 @@ func request_GoCryptoTrader_GetOrders_0(ctx context.Context, marshaler runtime.M } +func local_request_GoCryptoTrader_GetOrders_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrdersRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetOrders(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetOrderRequest var metadata runtime.ServerMetadata @@ -392,6 +700,23 @@ func request_GoCryptoTrader_GetOrder_0(ctx context.Context, marshaler runtime.Ma } +func local_request_GoCryptoTrader_GetOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetOrder(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq SubmitOrderRequest var metadata runtime.ServerMetadata @@ -409,6 +734,23 @@ func request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime } +func local_request_GoCryptoTrader_SubmitOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SubmitOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SubmitOrder(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_SimulateOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq SimulateOrderRequest var metadata runtime.ServerMetadata @@ -426,6 +768,23 @@ func request_GoCryptoTrader_SimulateOrder_0(ctx context.Context, marshaler runti } +func local_request_GoCryptoTrader_SimulateOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SimulateOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SimulateOrder(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_WhaleBomb_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq WhaleBombRequest var metadata runtime.ServerMetadata @@ -443,6 +802,23 @@ func request_GoCryptoTrader_WhaleBomb_0(ctx context.Context, marshaler runtime.M } +func local_request_GoCryptoTrader_WhaleBomb_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WhaleBombRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WhaleBomb(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq CancelOrderRequest var metadata runtime.ServerMetadata @@ -460,6 +836,23 @@ func request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime } +func local_request_GoCryptoTrader_CancelOrder_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CancelOrderRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.CancelOrder(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_CancelAllOrders_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq CancelAllOrdersRequest var metadata runtime.ServerMetadata @@ -477,6 +870,23 @@ func request_GoCryptoTrader_CancelAllOrders_0(ctx context.Context, marshaler run } +func local_request_GoCryptoTrader_CancelAllOrders_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CancelAllOrdersRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.CancelAllOrders(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetEvents_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetEventsRequest var metadata runtime.ServerMetadata @@ -486,6 +896,15 @@ func request_GoCryptoTrader_GetEvents_0(ctx context.Context, marshaler runtime.M } +func local_request_GoCryptoTrader_GetEvents_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetEventsRequest + var metadata runtime.ServerMetadata + + msg, err := server.GetEvents(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_AddEvent_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq AddEventRequest var metadata runtime.ServerMetadata @@ -503,6 +922,23 @@ func request_GoCryptoTrader_AddEvent_0(ctx context.Context, marshaler runtime.Ma } +func local_request_GoCryptoTrader_AddEvent_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddEventRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.AddEvent(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_RemoveEvent_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq RemoveEventRequest var metadata runtime.ServerMetadata @@ -520,6 +956,23 @@ func request_GoCryptoTrader_RemoveEvent_0(ctx context.Context, marshaler runtime } +func local_request_GoCryptoTrader_RemoveEvent_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RemoveEventRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.RemoveEvent(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetCryptocurrencyDepositAddressesRequest var metadata runtime.ServerMetadata @@ -537,6 +990,23 @@ func request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx context.Cont } +func local_request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCryptocurrencyDepositAddressesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetCryptocurrencyDepositAddresses(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetCryptocurrencyDepositAddressRequest var metadata runtime.ServerMetadata @@ -554,6 +1024,23 @@ func request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx context.Contex } +func local_request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCryptocurrencyDepositAddressRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetCryptocurrencyDepositAddress(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq WithdrawCurrencyRequest var metadata runtime.ServerMetadata @@ -571,6 +1058,23 @@ func request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx context.Context, m } +func local_request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WithdrawCurrencyRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WithdrawCryptocurrencyFunds(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq WithdrawCurrencyRequest var metadata runtime.ServerMetadata @@ -588,6 +1092,23 @@ func request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler r } +func local_request_GoCryptoTrader_WithdrawFiatFunds_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WithdrawCurrencyRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WithdrawFiatFunds(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetLoggerDetails_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -608,6 +1129,19 @@ func request_GoCryptoTrader_GetLoggerDetails_0(ctx context.Context, marshaler ru } +func local_request_GoCryptoTrader_GetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetLoggerDetailsRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetLoggerDetails_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetLoggerDetails(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq SetLoggerDetailsRequest var metadata runtime.ServerMetadata @@ -625,6 +1159,23 @@ func request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler ru } +func local_request_GoCryptoTrader_SetLoggerDetails_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetLoggerDetailsRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SetLoggerDetails(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetExchangePairsRequest var metadata runtime.ServerMetadata @@ -642,6 +1193,23 @@ func request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marshaler ru } +func local_request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangePairsRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetExchangePairs(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ExchangePairRequest var metadata runtime.ServerMetadata @@ -659,6 +1227,23 @@ func request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExchangePairRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.EnableExchangePair(ctx, &protoReq) + return msg, metadata, err + +} + func request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ExchangePairRequest var metadata runtime.ServerMetadata @@ -676,6 +1261,23 @@ func request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler } +func local_request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExchangePairRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DisableExchangePair(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_GoCryptoTrader_GetOrderbookStream_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -808,6 +1410,935 @@ func request_GoCryptoTrader_GetAuditEvent_0(ctx context.Context, marshaler runti } +func local_request_GoCryptoTrader_GetAuditEvent_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetAuditEventRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAuditEvent_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetAuditEvent(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterGoCryptoTraderHandlerServer registers the http handlers for service GoCryptoTrader to "mux". +// UnaryRPC :call GoCryptoTraderServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +func RegisterGoCryptoTraderHandlerServer(ctx context.Context, mux *runtime.ServeMux, server GoCryptoTraderServer) error { + + mux.Handle("GET", pattern_GoCryptoTrader_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetInfo_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetSubsystems_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetSubsystems_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetSubsystems_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_EnableSubsystem_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_EnableSubsystem_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_EnableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_DisableSubsystem_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_DisableSubsystem_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_DisableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetRPCEndpoints_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetRPCEndpoints_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetRPCEndpoints_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetCommunicationRelayers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetCommunicationRelayers_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetCommunicationRelayers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchanges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchanges_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchanges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_DisableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_DisableExchange_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_DisableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchangeInfo_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOTPCode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchangeOTPCode_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeOTPCode_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOTPCodes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchangeOTPCodes_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeOTPCodes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_EnableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_EnableExchange_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_EnableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetTicker_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetTicker_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetTicker_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetTickers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetTickers_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetTickers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetOrderbook_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetOrderbook_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrderbook_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetOrderbooks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetOrderbooks_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrderbooks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetAccountInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetAccountInfo_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetAccountInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetConfig_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetConfig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetPortfolio_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetPortfolio_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetPortfolio_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetPortfolioSummary_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetPortfolioSummary_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetPortfolioSummary_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_AddPortfolioAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_AddPortfolioAddress_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_AddPortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_RemovePortfolioAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_RemovePortfolioAddress_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_RemovePortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetForexProviders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetForexProviders_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetForexProviders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetForexRates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetForexRates_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetForexRates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetOrders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetOrders_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetOrder_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_SubmitOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_SubmitOrder_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SubmitOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_SimulateOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_SimulateOrder_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SimulateOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_WhaleBomb_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WhaleBomb_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WhaleBomb_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_CancelOrder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_CancelOrder_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_CancelOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_CancelAllOrders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_CancelAllOrders_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_CancelAllOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetEvents_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetEvents_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_AddEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_AddEvent_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_AddEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_RemoveEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_RemoveEvent_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_RemoveEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetCryptocurrencyDepositAddresses_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetCryptocurrencyDepositAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetCryptocurrencyDepositAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_WithdrawCryptocurrencyFunds_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WithdrawCryptocurrencyFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_WithdrawFiatFunds_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WithdrawFiatFunds_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WithdrawFiatFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetLoggerDetails_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetLoggerDetails_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_SetLoggerDetails_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_SetLoggerDetails_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_GetExchangePairs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchangePairs_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangePairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_EnableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_EnableExchangePair_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_EnableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GoCryptoTrader_DisableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_DisableExchangePair_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_DisableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetOrderbookStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOrderbookStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetTickerStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeTickerStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetAuditEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetAuditEvent_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetAuditEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + // RegisterGoCryptoTraderHandlerFromEndpoint is same as RegisterGoCryptoTraderHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterGoCryptoTraderHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 7e42319f..079d2747 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -518,10 +518,10 @@ message GetAuditEventRequest { } message GetAuditEventResponse { - repeated audit_event events = 1; + repeated AuditEvent events = 1; } -message audit_event { +message AuditEvent { string type = 1 ; string identifier = 2; string message = 3; diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index c6687dea..6f754eb1 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -1283,6 +1283,23 @@ "gctrpcAddPortfolioAddressResponse": { "type": "object" }, + "gctrpcAuditEvent": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "message": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, "gctrpcCancelAllOrdersRequest": { "type": "object", "properties": { @@ -1498,7 +1515,7 @@ "events": { "type": "array", "items": { - "$ref": "#/definitions/gctrpcaudit_event" + "$ref": "#/definitions/gctrpcAuditEvent" } } } @@ -2381,23 +2398,6 @@ } } }, - "gctrpcaudit_event": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "identifier": { - "type": "string" - }, - "message": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, "protobufAny": { "type": "object", "properties": { diff --git a/main.go b/main.go index 10816b3e..38dab747 100644 --- a/main.go +++ b/main.go @@ -36,8 +36,6 @@ func main() { flag.BoolVar(&settings.EnableCommsRelayer, "enablecommsrelayer", true, "enables available communications relayer") flag.BoolVar(&settings.Verbose, "verbose", false, "increases logging verbosity for GoCryptoTrader") flag.BoolVar(&settings.EnableExchangeSyncManager, "syncmanager", true, "enables to exchange sync manager") - flag.BoolVar(&settings.EnableTickerSyncing, "tickersync", true, "enables ticker syncing for all enabled exchanges") - flag.BoolVar(&settings.EnableOrderbookSyncing, "orderbooksync", true, "enables orderbook syncing for all enabled exchanges") flag.BoolVar(&settings.EnableWebsocketRoutine, "websocketroutine", true, "enables the websocket routine for all loaded exchanges") flag.BoolVar(&settings.EnableCoinmarketcapAnalysis, "coinmarketcap", false, "overrides config and runs currency analysis") flag.BoolVar(&settings.EnableEventManager, "eventmanager", true, "enables the event manager") @@ -51,6 +49,15 @@ func main() { flag.IntVar(&settings.DispatchMaxWorkerAmount, "dispatchworkers", dispatch.DefaultMaxWorkers, "sets the dispatch package max worker generation limit") flag.IntVar(&settings.DispatchJobsLimit, "dispatchjobslimit", dispatch.DefaultJobsLimit, "sets the dispatch package max jobs limit") + // Exchange syncer settings + flag.BoolVar(&settings.EnableTickerSyncing, "tickersync", true, "enables ticker syncing for all enabled exchanges") + flag.BoolVar(&settings.EnableOrderbookSyncing, "orderbooksync", true, "enables orderbook syncing for all enabled exchanges") + flag.BoolVar(&settings.EnableTradeSyncing, "tradesync", false, "enables trade syncing for all enabled exchanges") + flag.IntVar(&settings.SyncWorkers, "syncworkers", engine.DefaultSyncerWorkers, "the amount of workers (goroutines) to use for syncing exchange data") + flag.BoolVar(&settings.SyncContinuously, "synccontinuously", true, "whether to sync exchange data continuously (ticker, orderbook and trade history info") + flag.DurationVar(&settings.SyncTimeout, "synctimeout", engine.DefaultSyncerTimeout, + "the amount of time before the syncer will switch from one protocol to the other (e.g. from REST to websocket)") + // Forex provider settings flag.BoolVar(&settings.EnableCurrencyConverter, "currencyconverter", false, "overrides config and sets up foreign exchange Currency Converter") flag.BoolVar(&settings.EnableCurrencyLayer, "currencylayer", false, "overrides config and sets up foreign exchange Currency Layer") @@ -65,7 +72,7 @@ func main() { flag.BoolVar(&settings.EnableExchangeVerbose, "exchangeverbose", false, "increases exchange logging verbosity") flag.BoolVar(&settings.ExchangePurgeCredentials, "exchangepurgecredentials", false, "purges the stored exchange API credentials") flag.BoolVar(&settings.EnableExchangeHTTPRateLimiter, "ratelimiter", true, "enables the rate limiter for HTTP requests") - flag.IntVar(&settings.MaxHTTPRequestJobsLimit, "maxhttprequestjobslimit", request.DefaultMaxRequestJobs, "sets the max amount of jobs the HTTP request package stores") + flag.IntVar(&settings.MaxHTTPRequestJobsLimit, "requestjobslimit", request.DefaultMaxRequestJobs, "sets the max amount of jobs the HTTP request package stores") flag.IntVar(&settings.RequestTimeoutRetryAttempts, "exchangehttptimeoutretryattempts", request.DefaultTimeoutRetryAttempts, "sets the amount of retry attempts after a HTTP request times out") flag.DurationVar(&settings.ExchangeHTTPTimeout, "exchangehttptimeout", time.Duration(0), "sets the exchangs HTTP timeout value for HTTP requests") flag.StringVar(&settings.ExchangeHTTPUserAgent, "exchangehttpuseragent", "", "sets the exchanges HTTP user agent") @@ -88,6 +95,7 @@ func main() { fmt.Println(core.Version(false)) var err error + settings.CheckParamInteraction = true engine.Bot, err = engine.NewFromSettings(&settings) if engine.Bot == nil || err != nil { log.Errorf(log.Global, "Unable to initialise bot engine. Error: %s\n", err) diff --git a/testdata/http_mock/anx/anx.json b/testdata/http_mock/anx/anx.json index fcf8ca46..7875089f 100644 --- a/testdata/http_mock/anx/anx.json +++ b/testdata/http_mock/anx/anx.json @@ -2339,7 +2339,7 @@ "timestamp": "1565238020536" }, "queryString": "", - "bodyParams": "{\"nonce\":\"1565238020075\",\"order\":{\"orderType\":\"MARKET\",\"buyTradedCurrency\":true,\"tradedCurrency\":\"BTC\",\"settlementCurrency\":\"USD\",\"tradedCurrencyAmount\":\"1\",\"settlementCurrencyAmount\":\"0\",\"limitPriceInSettlementCurrency\":\"0\",\"replaceExistingOrderUuid\":\"\",\"replaceOnlyIfActive\":false}}", + "bodyParams": "{\"nonce\":\"1565238020075\",\"order\":{\"orderType\":\"LIMIT\",\"buyTradedCurrency\":true,\"tradedCurrency\":\"BTC\",\"settlementCurrency\":\"USD\",\"tradedCurrencyAmount\":\"1\",\"settlementCurrencyAmount\":\"0\",\"limitPriceInSettlementCurrency\":\"1\",\"replaceExistingOrderUuid\":\"\",\"replaceOnlyIfActive\":false}}", "headers": { "Content-Type": [ "application/json" diff --git a/testdata/http_mock/binance/binance.json b/testdata/http_mock/binance/binance.json index 0094c820..3bedf419 100644 --- a/testdata/http_mock/binance/binance.json +++ b/testdata/http_mock/binance/binance.json @@ -45378,7 +45378,7 @@ "POST": [ { "data": {}, - "queryString": "quantity=1000000000\u0026recvWindow=5000\u0026side=BUY\u0026signature=d122bfd46f4dcf8dda98974aed14221916aabc050a6aa30194d124e81ffe7f44\u0026symbol=LTCBTC\u0026timeInForce=GTC\u0026timestamp=1560236466000\u0026type=MARKET", + "queryString": "price=1\u0026quantity=1000000000\u0026recvWindow=5000\u0026side=BUY\u0026symbol=LTCBTC\u0026timeInForce=GTC\u0026timestamp=1572330506000\u0026type=LIMIT\u0026signature=00954d00c69761017b3440e6e26d9bf6b394bea354c658ca09254b8c28995c73", "bodyParams": "", "headers": { "Key": [ diff --git a/testdata/http_mock/bitstamp/bitstamp.json b/testdata/http_mock/bitstamp/bitstamp.json index 900a690e..8032e1f8 100644 --- a/testdata/http_mock/bitstamp/bitstamp.json +++ b/testdata/http_mock/bitstamp/bitstamp.json @@ -177,7 +177,7 @@ "error": "Order not found" }, "queryString": "", - "bodyParams": "id=1\u0026key=\u0026nonce=1560467884949723197\u0026signature=79D88FC2BC3AECBFDD27F70EF5DDBB485349F45129C58EE725BAEE0DF7818452", + "bodyParams": "id=1234\u0026key=\u0026nonce=1560467884949723197\u0026signature=79D88FC2BC3AECBFDD27F70EF5DDBB485349F45129C58EE725BAEE0DF7818452", "headers": { "Content-Type": [ "application/x-www-form-urlencoded" diff --git a/testdata/http_mock/gemini/gemini.json b/testdata/http_mock/gemini/gemini.json index b40bab4a..e0e75a8c 100644 --- a/testdata/http_mock/gemini/gemini.json +++ b/testdata/http_mock/gemini/gemini.json @@ -1903,7 +1903,7 @@ "result": "error" }, "queryString": "", - "bodyParams": "{\"amount\":\"1\",\"nonce\":\"1565757283294274961\",\"price\":\"9000\",\"request\":\"/v1/order/new\",\"side\":\"buy\",\"symbol\":\"btcusd\",\"type\":\"exchange limit\"}", + "bodyParams": "{\"amount\":\"1\",\"nonce\":\"1565757283294274961\",\"price\":\"9000000\",\"request\":\"/v1/order/new\",\"side\":\"sell\",\"symbol\":\"btcusd\",\"type\":\"exchange limit\"}", "headers": { "Cache-Control": [ "no-cache" diff --git a/web/src/app/services/websocket-response-handler/websocket-response-handler.service.ts b/web/src/app/services/websocket-response-handler/websocket-response-handler.service.ts index 4618cf65..ec5603d8 100644 --- a/web/src/app/services/websocket-response-handler/websocket-response-handler.service.ts +++ b/web/src/app/services/websocket-response-handler/websocket-response-handler.service.ts @@ -5,7 +5,7 @@ import { Observable, Subject } from 'rxjs/Rx'; import { WebsocketService } from './../../services/websocket/websocket.service'; import { WebSocketMessage } from './../../shared/classes/websocket'; -const WEBSOCKET_URL = 'ws://localhost:9050/ws'; +const WEBSOCKET_URL = 'ws://localhost:9051/ws'; @NgModule({ }) From e20d204b197d9c7d7d82b88a097fa7d693796002 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 28 Nov 2019 11:56:05 +1100 Subject: [PATCH 62/71] Fix Docker os.Rename invalid cross-device link issue (#386) * Adds new file.Move func to address a bug with Golang/Docker volumes when using os.Rename Also uses TempDir for tests instead of live directories and increases test coverage for file.Write * Goimport the imports * Make usage of file package name consistent so it no longer clashes with vars * Remove outputFile if io.Copy fails --- cmd/config/config.go | 16 ++--- cmd/exchange_wrapper_issues/main.go | 5 +- cmd/gen_cert/main.go | 6 +- cmd/gen_sqlboiler_config/main.go | 2 +- cmd/huobi_auth/main.go | 6 +- common/common.go | 11 +-- common/common_test.go | 12 ++-- common/file/file.go | 47 ++++++++++++ common/file/file_test.go | 106 ++++++++++++++++++++++++++++ config/config.go | 25 +++---- currency/storage.go | 3 +- engine/helpers.go | 5 +- exchanges/mock/recording.go | 4 +- exchanges/mock/server.go | 3 +- logger/logger_rotate.go | 4 +- 15 files changed, 205 insertions(+), 50 deletions(-) create mode 100644 common/file/file.go create mode 100644 common/file/file_test.go diff --git a/cmd/config/config.go b/cmd/config/config.go index a85761fb..3274b9ed 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "log" - "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/config" ) @@ -43,19 +43,19 @@ func main() { key = string(result) } - file, err := ioutil.ReadFile(inFile) + fileData, err := ioutil.ReadFile(inFile) if err != nil { log.Fatalf("Unable to read input file %s. Error: %s.", inFile, err) } - if config.ConfirmECS(file) && encrypt { + if config.ConfirmECS(fileData) && encrypt { log.Println("File is already encrypted. Decrypting..") encrypt = false } - if !config.ConfirmECS(file) && !encrypt { + if !config.ConfirmECS(fileData) && !encrypt { var result interface{} - errf := config.ConfirmConfigJSON(file, result) + errf := config.ConfirmConfigJSON(fileData, result) if errf != nil { log.Fatal("File isn't in JSON format") } @@ -65,18 +65,18 @@ func main() { var data []byte if encrypt { - data, err = config.EncryptConfigFile(file, []byte(key)) + data, err = config.EncryptConfigFile(fileData, []byte(key)) if err != nil { log.Fatalf("Unable to encrypt config data. Error: %s.", err) } } else { - data, err = config.DecryptConfigFile(file, []byte(key)) + data, err = config.DecryptConfigFile(fileData, []byte(key)) if err != nil { log.Fatalf("Unable to decrypt config data. Error: %s.", err) } } - err = common.WriteFile(outFile, data) + err = file.Write(outFile, data) if err != nil { log.Fatalf("Unable to write output file %s. Error: %s", outFile, err) } diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go index a016b544..377c3658 100644 --- a/cmd/exchange_wrapper_issues/main.go +++ b/cmd/exchange_wrapper_issues/main.go @@ -14,6 +14,7 @@ import ( "text/template" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/engine" @@ -754,7 +755,7 @@ func saveConfig(config *Config) { } log.Printf("Outputting to: %v", filepath.Join(dir, "wrapperconfig.json")) - err = common.WriteFile(filepath.Join(dir, "wrapperconfig.json"), jsonOutput) + err = file.Write(filepath.Join(dir, "wrapperconfig.json"), jsonOutput) if err != nil { log.Printf("Encountered error writing to disk: %v", err) return @@ -775,7 +776,7 @@ func outputToJSON(exchangeResponses []ExchangeResponses) { } log.Printf("Outputting to: %v", filepath.Join(dir, fmt.Sprintf("%v.json", outputFileName))) - err = common.WriteFile(filepath.Join(dir, fmt.Sprintf("%v.json", outputFileName)), jsonOutput) + err = file.Write(filepath.Join(dir, fmt.Sprintf("%v.json", outputFileName)), jsonOutput) if err != nil { log.Printf("Encountered error writing to disk: %v", err) return diff --git a/cmd/gen_cert/main.go b/cmd/gen_cert/main.go index 97aa51c1..d8e96a6e 100644 --- a/cmd/gen_cert/main.go +++ b/cmd/gen_cert/main.go @@ -14,7 +14,7 @@ import ( "os" "time" - "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/file" ) func main() { @@ -83,13 +83,13 @@ func main() { log.Fatalf("key pem data is nil") } - err = common.WriteFile("key.pem", keyData) + err = file.Write("key.pem", keyData) if err != nil { log.Fatalf("failed to write key.pem file %s", err) } log.Printf("wrote key.pem file") - err = common.WriteFile("cert.pem", certData) + err = file.Write("cert.pem", certData) if err != nil { log.Fatalf("failed to write cert.pem file %s", err) } diff --git a/cmd/gen_sqlboiler_config/main.go b/cmd/gen_sqlboiler_config/main.go index c5198315..9d739a2c 100644 --- a/cmd/gen_sqlboiler_config/main.go +++ b/cmd/gen_sqlboiler_config/main.go @@ -68,7 +68,7 @@ func main() { } path := filepath.Join(outputFolder, "sqlboiler.json") - err = ioutil.WriteFile(path, jsonOutput, 0644) + err = ioutil.WriteFile(path, jsonOutput, 0770) if err != nil { fmt.Printf("Write failed: %v", err) os.Exit(1) diff --git a/cmd/huobi_auth/main.go b/cmd/huobi_auth/main.go index e7636e1d..2b2a9572 100644 --- a/cmd/huobi_auth/main.go +++ b/cmd/huobi_auth/main.go @@ -13,7 +13,7 @@ import ( "io/ioutil" "log" - "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/file" ) func encodePEM(privKey *ecdsa.PrivateKey, pubKey bool) ([]byte, error) { @@ -54,8 +54,8 @@ func decodePEM(pemPrivKey []byte) (*ecdsa.PrivateKey, error) { return x509.ParseECPrivateKey(x509Enc) } -func writeFile(file string, data []byte) error { - return common.WriteFile(file, data) +func writeFile(fileName string, data []byte) error { + return file.Write(fileName, data) } func main() { diff --git a/common/common.go b/common/common.go index 71a6aa75..dcabebf8 100644 --- a/common/common.go +++ b/common/common.go @@ -279,7 +279,7 @@ func ExtractPort(host string) int { func OutputCSV(filePath string, data [][]string) error { _, err := ioutil.ReadFile(filePath) if err != nil { - errTwo := WriteFile(filePath, nil) + errTwo := ioutil.WriteFile(filePath, nil, 0770) if errTwo != nil { return errTwo } @@ -295,11 +295,6 @@ func OutputCSV(filePath string, data [][]string) error { return writer.WriteAll(data) } -// WriteFile writes selected data to a file and returns an error -func WriteFile(file string, data []byte) error { - return ioutil.WriteFile(file, data, 0644) -} - // GetURIPath returns the path of a URL given a URI func GetURIPath(uri string) string { urip, err := url.Parse(uri) @@ -353,8 +348,8 @@ func CreateDir(dir string) error { return os.MkdirAll(dir, 0770) } -// ChangePerm lists all the directories and files in an array -func ChangePerm(directory string) error { +// ChangePermission lists all the directories and files in an array +func ChangePermission(directory string) error { return filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { if err != nil { return err diff --git a/common/common_test.go b/common/common_test.go index d8bf9f84..10a7a858 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -504,11 +504,11 @@ func TestCreateDir(t *testing.T) { } } -func TestChangePerm(t *testing.T) { - testDir := filepath.Join(GetDefaultDataDir(runtime.GOOS), "TestFileASDFGHJ") +func TestChangePermission(t *testing.T) { + testDir := filepath.Join(os.TempDir(), "TestFileASDFGHJ") switch runtime.GOOS { case "windows": - err := ChangePerm("*") + err := ChangePermission("*") if err == nil { t.Fatal("expected an error on non-existent path") } @@ -516,7 +516,7 @@ func TestChangePerm(t *testing.T) { if err != nil { t.Fatalf("Mkdir failed. Err: %v", err) } - err = ChangePerm(GetDefaultDataDir(runtime.GOOS)) + err = ChangePermission(testDir) if err != nil { t.Fatalf("ChangePerm was unsuccessful. Err: %v", err) } @@ -529,7 +529,7 @@ func TestChangePerm(t *testing.T) { t.Fatalf("os.Remove failed. Err: %v", err) } default: - err := ChangePerm("") + err := ChangePermission("") if err == nil { t.Fatal("expected an error on non-existent path") } @@ -537,7 +537,7 @@ func TestChangePerm(t *testing.T) { if err != nil { t.Fatalf("Mkdir failed. Err: %v", err) } - err = ChangePerm(GetDefaultDataDir(runtime.GOOS)) + err = ChangePermission(testDir) if err != nil { t.Fatalf("ChangePerm was unsuccessful. Err: %v", err) } diff --git a/common/file/file.go b/common/file/file.go new file mode 100644 index 00000000..ec3ed644 --- /dev/null +++ b/common/file/file.go @@ -0,0 +1,47 @@ +package file + +import ( + "fmt" + "io" + "io/ioutil" + "os" +) + +// Write writes selected data to a file or returns an error if it fails. This +// func also ensures that all files are set to this permission (only rw access +// for the running user and the group the user is a member of) +func Write(file string, data []byte) error { + return ioutil.WriteFile(file, data, 0770) +} + +// Move moves a file from a source path to a destination path +// This must be used across the codebase for compatibility with Docker volumes +// and Golang (fixes Invalid cross-device link when using os.Rename) +func Move(sourcePath, destPath string) error { + inputFile, err := os.Open(sourcePath) + if err != nil { + return err + } + + outputFile, err := os.Create(destPath) + if err != nil { + inputFile.Close() + return err + } + + _, err = io.Copy(outputFile, inputFile) + inputFile.Close() + outputFile.Close() + if err != nil { + if errRem := os.Remove(destPath); errRem != nil { + return fmt.Errorf( + "unable to os.Remove error: %s after io.Copy error: %s", + errRem, + err, + ) + } + return err + } + + return os.Remove(sourcePath) +} diff --git a/common/file/file_test.go b/common/file/file_test.go new file mode 100644 index 00000000..f0db7d1f --- /dev/null +++ b/common/file/file_test.go @@ -0,0 +1,106 @@ +package file + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "testing" +) + +func TestWrite(t *testing.T) { + tester := func(in string) error { + err := Write(in, []byte("GoCryptoTrader")) + if err != nil { + return err + } + return os.Remove(in) + } + + type testTable struct { + InFile string + ErrExpected bool + Cleanup bool + } + + var tests []testTable + testFile := filepath.Join(os.TempDir(), "gcttest.txt") + switch runtime.GOOS { + case "windows": + tests = []testTable{ + {InFile: "*", ErrExpected: true}, + {InFile: testFile, ErrExpected: false}, + } + default: + tests = []testTable{ + {InFile: "", ErrExpected: true}, + {InFile: testFile, ErrExpected: false}, + } + } + + for x := range tests { + err := tester(tests[x].InFile) + if err != nil && !tests[x].ErrExpected { + t.Errorf("Test %d failed, unexpected err %s\n", x, err) + } + } +} + +func TestMove(t *testing.T) { + tester := func(in, out string, write bool) error { + if write { + if err := ioutil.WriteFile(in, []byte("GoCryptoTrader"), 0770); err != nil { + return err + } + } + + if err := Move(in, out); err != nil { + return err + } + + contents, err := ioutil.ReadFile(out) + if err != nil { + return err + } + + if !strings.Contains(string(contents), "GoCryptoTrader") { + return fmt.Errorf("unable to find previously written data") + } + + return os.Remove(out) + } + + type testTable struct { + InFile string + OutFile string + Write bool + ErrExpected bool + } + + var tests []testTable + switch runtime.GOOS { + case "windows": + tests = []testTable{ + {InFile: "*", OutFile: "gct.txt", Write: true, ErrExpected: true}, + {InFile: "*", OutFile: "gct.txt", Write: false, ErrExpected: true}, + {InFile: "in.txt", OutFile: "*", Write: true, ErrExpected: true}, + {InFile: "in.txt", OutFile: "gct.txt", Write: true, ErrExpected: false}, + } + default: + tests = []testTable{ + {InFile: "", OutFile: "gct.txt", Write: true, ErrExpected: true}, + {InFile: "", OutFile: "gct.txt", Write: false, ErrExpected: true}, + {InFile: "in.txt", OutFile: "", Write: true, ErrExpected: true}, + {InFile: "in.txt", OutFile: "gct.txt", Write: true, ErrExpected: false}, + } + } + + for x := range tests { + err := tester(tests[x].InFile, tests[x].OutFile, tests[x].Write) + if err != nil && !tests[x].ErrExpected { + t.Errorf("Test %d failed, unexpected err %s\n", x, err) + } + } +} diff --git a/config/config.go b/config/config.go index 5ad56549..88db3703 100644 --- a/config/config.go +++ b/config/config.go @@ -17,6 +17,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/connchecker" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider" @@ -1331,9 +1332,9 @@ func (c *Config) CheckConnectionMonitorConfig() { // GetFilePath returns the desired config file or the default config file name // based on if the application is being run under test or normal mode. -func GetFilePath(file string) (string, error) { - if file != "" { - return file, nil +func GetFilePath(configfile string) (string, error) { + if configfile != "" { + return configfile, nil } if flag.Lookup("test.v") != nil && !testBypass { @@ -1375,7 +1376,7 @@ func GetFilePath(file string) (string, error) { return newDirs[x], nil } if filepath.Ext(oldDirs[x]) == ".json" { - err = os.Rename(oldDirs[x], newDirs[0]) + err = file.Move(oldDirs[x], newDirs[0]) if err != nil { return "", err } @@ -1384,7 +1385,7 @@ func GetFilePath(file string) (string, error) { oldDirs[x], newDirs[0]) } else { - err = os.Rename(oldDirs[x], newDirs[1]) + err = file.Move(oldDirs[x], newDirs[1]) if err != nil { return "", err } @@ -1412,7 +1413,7 @@ func GetFilePath(file string) (string, error) { return newDirs[x], nil } - err = os.Rename(newDirs[x], newDirs[1]) + err = file.Move(newDirs[x], newDirs[1]) if err != nil { return "", err } @@ -1423,7 +1424,7 @@ func GetFilePath(file string) (string, error) { return newDirs[x], nil } - err = os.Rename(newDirs[x], newDirs[0]) + err = file.Move(newDirs[x], newDirs[0]) if err != nil { return "", err } @@ -1443,13 +1444,13 @@ func (c *Config) ReadConfig(configPath string, dryrun bool) error { return err } - file, err := ioutil.ReadFile(defaultPath) + fileData, err := ioutil.ReadFile(defaultPath) if err != nil { return err } - if !ConfirmECS(file) { - err = ConfirmConfigJSON(file, &c) + if !ConfirmECS(fileData) { + err = ConfirmConfigJSON(fileData, &c) if err != nil { return err } @@ -1481,7 +1482,7 @@ func (c *Config) ReadConfig(configPath string, dryrun bool) error { } var f []byte - f = append(f, file...) + f = append(f, fileData...) data, err := DecryptConfigFile(f, key) if err != nil { log.Errorf(log.ConfigMgr, "DecryptConfigFile err: %s", err) @@ -1535,7 +1536,7 @@ func (c *Config) SaveConfig(configPath string, dryrun bool) error { return err } } - return common.WriteFile(defaultPath, payload) + return file.Write(defaultPath, payload) } // CheckRemoteControlConfig checks to see if the old c.Webserver field is used diff --git a/currency/storage.go b/currency/storage.go index dd1e6089..e4148647 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -9,6 +9,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/currency/coinmarketcap" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" @@ -318,7 +319,7 @@ func (s *Storage) WriteCurrencyDataToFile(path string, mainUpdate bool) error { return err } - return common.WriteFile(path, encoded) + return file.Write(path, encoded) } // LoadFileCurrencyData loads currencies into the currency codes diff --git a/engine/helpers.go b/engine/helpers.go index 3df4c11a..0db45287 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -18,6 +18,7 @@ import ( "github.com/pquerna/otp/totp" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/dispatch" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -867,12 +868,12 @@ func genCert(targetDir string) error { return fmt.Errorf("key pem data is nil") } - err = common.WriteFile(filepath.Join(targetDir, "key.pem"), keyData) + err = file.Write(filepath.Join(targetDir, "key.pem"), keyData) if err != nil { return fmt.Errorf("failed to write key.pem file %s", err) } - err = common.WriteFile(filepath.Join(targetDir, "cert.pem"), certData) + err = file.Write(filepath.Join(targetDir, "cert.pem"), certData) if err != nil { return fmt.Errorf("failed to write cert.pem file %s", err) } diff --git a/exchanges/mock/recording.go b/exchanges/mock/recording.go index 36dc9826..e561e1f0 100644 --- a/exchanges/mock/recording.go +++ b/exchanges/mock/recording.go @@ -13,8 +13,8 @@ import ( "strings" "sync" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" + "github.com/thrasher-corp/gocryptotrader/common/file" ) // HTTPResponse defines expected response from the end point including request @@ -212,7 +212,7 @@ func HTTPRecord(res *http.Response, service string, respContents []byte) error { return err } - return common.WriteFile(fileout, payload) + return file.Write(fileout, payload) } // GetFilteredHeader filters excluded http headers for insertion into a mock diff --git a/exchanges/mock/server.go b/exchanges/mock/server.go index c04dbd3a..3965ffec 100644 --- a/exchanges/mock/server.go +++ b/exchanges/mock/server.go @@ -15,6 +15,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" + "github.com/thrasher-corp/gocryptotrader/common/file" ) // DefaultDirectory defines the main mock directory @@ -56,7 +57,7 @@ func NewVCRServer(path string) (string, *http.Client, error) { return "", nil, jErr } - err = common.WriteFile(path, data) + err = file.Write(path, data) if err != nil { return "", nil, err } diff --git a/logger/logger_rotate.go b/logger/logger_rotate.go index 985c4c70..bf42abc1 100644 --- a/logger/logger_rotate.go +++ b/logger/logger_rotate.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "time" + + "github.com/thrasher-corp/gocryptotrader/common/file" ) // Write implementation to satisfy io.Writer handles length check and rotation @@ -78,7 +80,7 @@ func (r *Rotate) openNew() error { timestamp := time.Now().Format("2006-01-02T15-04-05") newName := filepath.Join(LogPath, timestamp+"-"+r.FileName) - err = os.Rename(name, newName) + err = file.Move(name, newName) if err != nil { return fmt.Errorf("can't rename log file: %s", err) } From d6368823105b630c7b368abd21594e2c4c45c937 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 29 Nov 2019 10:38:44 +1100 Subject: [PATCH 63/71] Use sync.Mutex instead of sync/atomic for 32 bit systems (#388) Change CLRF -> LF --- contrib/bash_autocomplete | 44 +++++++++++++++++----------------- contrib/zsh_autocomplete | 26 ++++++++++---------- exchanges/huobi/huobi_types.go | 4 ++-- exchanges/nonce/nonce.go | 15 ++++++++---- 4 files changed, 48 insertions(+), 41 deletions(-) diff --git a/contrib/bash_autocomplete b/contrib/bash_autocomplete index bf09a110..6d6c7d9a 100644 --- a/contrib/bash_autocomplete +++ b/contrib/bash_autocomplete @@ -1,23 +1,23 @@ -#! /bin/bash -# bash programmable completion for gctcli -# copy to /etc/bash_completion.d/gctcli and source it or restart your shell - -: ${PROG:=$(basename ${BASH_SOURCE})} - -_gctcli() { - if [[ "${COMP_WORDS[0]}" != "source" ]]; then - local cur opts base - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - if [[ "$cur" == "-"* ]]; then - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) - else - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) - fi - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 - fi -} - -complete -o bashdefault -o default -o nospace -F _gctcli $PROG +#! /bin/bash +# bash programmable completion for gctcli +# copy to /etc/bash_completion.d/gctcli and source it or restart your shell + +: ${PROG:=$(basename ${BASH_SOURCE})} + +_gctcli() { + if [[ "${COMP_WORDS[0]}" != "source" ]]; then + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + if [[ "$cur" == "-"* ]]; then + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) + else + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + fi + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi +} + +complete -o bashdefault -o default -o nospace -F _gctcli $PROG unset PROG \ No newline at end of file diff --git a/contrib/zsh_autocomplete b/contrib/zsh_autocomplete index 6aaa9487..ed4951b2 100644 --- a/contrib/zsh_autocomplete +++ b/contrib/zsh_autocomplete @@ -1,14 +1,14 @@ -# zsh programmable completion for gctcli -# source zsh_autocomplete - -_gctcli() { - - local -a opts - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") - - _describe 'values' opts - - return -} - +# zsh programmable completion for gctcli +# source zsh_autocomplete + +_gctcli() { + + local -a opts + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") + + _describe 'values' opts + + return +} + compdef _gctcli gctcli \ No newline at end of file diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index b7ea87db..c4d516bc 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -35,7 +35,7 @@ type CancelOpenOrdersBatch struct { // DetailMerged stores the ticker detail merged data type DetailMerged struct { Detail - Version int `json:"version"` + Version int64 `json:"version"` Ask []float64 `json:"ask"` Bid []float64 `json:"bid"` } @@ -108,7 +108,7 @@ type Detail struct { Close float64 `json:"close"` High float64 `json:"high"` Timestamp int64 `json:"timestamp"` - ID int `json:"id"` + ID int64 `json:"id"` Count int `json:"count"` Low float64 `json:"low"` Volume float64 `json:"vol"` diff --git a/exchanges/nonce/nonce.go b/exchanges/nonce/nonce.go index 2a863a16..fd358b9f 100644 --- a/exchanges/nonce/nonce.go +++ b/exchanges/nonce/nonce.go @@ -2,22 +2,27 @@ package nonce import ( "strconv" - "sync/atomic" + "sync" ) // Nonce struct holds the nonce value type Nonce struct { n int64 + m sync.Mutex } // Inc increments the nonce value func (n *Nonce) Inc() { - atomic.AddInt64(&n.n, 1) + n.m.Lock() + n.n++ + n.m.Unlock() } // Get retrives the nonce value func (n *Nonce) Get() Value { - return Value(atomic.LoadInt64(&n.n)) + n.m.Lock() + defer n.m.Unlock() + return Value(n.n) } // GetInc increments and returns the value of the nonce @@ -28,7 +33,9 @@ func (n *Nonce) GetInc() Value { // Set sets the nonce value func (n *Nonce) Set(val int64) { - atomic.StoreInt64(&n.n, val) + n.m.Lock() + n.n = val + n.m.Unlock() } // String returns a string version of the nonce From 24bddcc0902a9c3814546c4f909dad3b219fed82 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Fri, 29 Nov 2019 11:14:19 +1100 Subject: [PATCH 64/71] Add directories to exclusion list && change default repo string (#387) * Add directories to exclusion list && change default repo string * Add in .idea folder to directory exclusion list * cleaned * Added support for calling the tool outside of its file * fix formatting * changed strings --- cmd/documentation/README.md | 1 - .../fx_currencyconverterapi.tmpl | 8 +- .../currency_templates/fx_currencylayer.tmpl | 6 +- .../fx_exchangeratesapi.tmpl | 40 ++++ .../currency_templates/fx_fixer.tmpl | 8 +- .../fx_openexchangerates.tmpl | 6 +- cmd/documentation/documentation.go | 172 ++++++++++++------ .../exchanges_templates/btcmarkets.tmpl | 1 + .../exchanges_templates/btse.tmpl | 99 ++++++++++ .../root_templates/CONTRIBUTORS.tmpl | 2 +- .../root_templates/root_readme.tmpl | 2 +- .../sub_templates/contributors.tmpl | 4 +- .../sub_templates/donations.tmpl | 2 +- common/README.md | 1 - communications/base/README.md | 1 - communications/slack/README.md | 1 - communications/smsglobal/README.md | 1 - communications/telegram/README.md | 1 - config/README.md | 1 - currency/README.md | 1 - currency/forexprovider/README.md | 1 - currency/forexprovider/base/README.md | 1 - .../currencyconverterapi/README.md | 9 +- .../forexprovider/currencylayer/README.md | 7 +- .../exchangeratesapi.io/README.md | 74 ++++++++ currency/forexprovider/fixer.io/README.md | 9 +- .../forexprovider/openexchangerates/README.md | 7 +- exchanges/README.md | 1 - exchanges/alphapoint/README.md | 1 - exchanges/anx/README.md | 1 - exchanges/binance/README.md | 1 - exchanges/bitfinex/README.md | 1 - exchanges/bitflyer/README.md | 1 - exchanges/bithumb/README.md | 1 - exchanges/bitmex/README.md | 1 - exchanges/bitstamp/README.md | 1 - exchanges/bittrex/README.md | 1 - exchanges/btcmarkets/README.md | 2 +- exchanges/btse/README.md | 135 ++++++++++++-- exchanges/coinbasepro/README.md | 1 - exchanges/coinbene/README.md | 5 +- exchanges/coinut/README.md | 1 - exchanges/exmo/README.md | 1 - exchanges/gateio/README.md | 1 - exchanges/gemini/README.md | 1 - exchanges/hitbtc/README.md | 1 - exchanges/huobi/README.md | 1 - exchanges/itbit/README.md | 1 - exchanges/kraken/README.md | 1 - exchanges/lakebtc/README.md | 1 - exchanges/lbank/README.md | 1 - exchanges/localbitcoins/README.md | 1 - exchanges/nonce/README.md | 1 - exchanges/okcoin/README.md | 1 - exchanges/okex/README.md | 1 - exchanges/orderbook/README.md | 1 - exchanges/poloniex/README.md | 1 - exchanges/request/README.md | 1 - exchanges/stats/README.md | 1 - exchanges/ticker/README.md | 1 - exchanges/yobit/README.md | 1 - exchanges/zb/README.md | 1 - portfolio/README.md | 1 - testdata/README.md | 3 +- web/README.md | 1 - 65 files changed, 482 insertions(+), 163 deletions(-) create mode 100644 cmd/documentation/currency_templates/fx_exchangeratesapi.tmpl create mode 100644 cmd/documentation/exchanges_templates/btse.tmpl create mode 100644 currency/forexprovider/exchangeratesapi.io/README.md diff --git a/cmd/documentation/README.md b/cmd/documentation/README.md index e32e6a55..e6fb031f 100644 --- a/cmd/documentation/README.md +++ b/cmd/documentation/README.md @@ -95,4 +95,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/cmd/documentation/currency_templates/fx_currencyconverterapi.tmpl b/cmd/documentation/currency_templates/fx_currencyconverterapi.tmpl index bad1a573..d6da5a20 100644 --- a/cmd/documentation/currency_templates/fx_currencyconverterapi.tmpl +++ b/cmd/documentation/currency_templates/fx_currencyconverterapi.tmpl @@ -1,4 +1,4 @@ -{{define "currency forexprovider currencyconverter" -}} +{{define "currency forexprovider currencyconverterapi" -}} {{template "header" .}} ## Current Features for {{.Name}} @@ -11,15 +11,15 @@ + Individual package example below: ```go import ( -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencyconverter" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencyconverter" ) c := currencyconverter.CurrencyConverter{} // Define configuration newSettings := base.Settings{ - Name: "CurrencyConverter", + Name: "CurrencyConverter", Enabled: true, Verbose: false, RESTPollingDelay: time.Duration, diff --git a/cmd/documentation/currency_templates/fx_currencylayer.tmpl b/cmd/documentation/currency_templates/fx_currencylayer.tmpl index e96a1513..b2654fd9 100644 --- a/cmd/documentation/currency_templates/fx_currencylayer.tmpl +++ b/cmd/documentation/currency_templates/fx_currencylayer.tmpl @@ -11,15 +11,15 @@ + Individual package example below: ```go import ( -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencylayer" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencylayer" ) c := currencylayer.CurrencyLayer{} // Define configuration newSettings := base.Settings{ - Name: "CurrencyLayer", + Name: "CurrencyLayer", Enabled: true, Verbose: false, RESTPollingDelay: time.Duration, diff --git a/cmd/documentation/currency_templates/fx_exchangeratesapi.tmpl b/cmd/documentation/currency_templates/fx_exchangeratesapi.tmpl new file mode 100644 index 00000000..49830305 --- /dev/null +++ b/cmd/documentation/currency_templates/fx_exchangeratesapi.tmpl @@ -0,0 +1,40 @@ +{{define "currency forexprovider exchangeratesapi.io" -}} +{{template "header" .}} +## Current Features for {{.Name}} + ++ Fetches up to date curency data from [Exchange rates API]("http://exchangeratesapi.io") + +### How to enable + ++ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-currency-via-config-example) + ++ Individual package example below: +```go +import ( + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/exchangerates" +) + +c := exchangerates.ExchangeRates{} + +// Define configuration +newSettings := base.Settings{ + Name: "ExchangeRates", + Enabled: true, + Verbose: false, + RESTPollingDelay: time.Duration, + APIKey: "key", + APIKeyLvl: "keylvl", + PrimaryProvider: true, +} + +c.Setup(newSettings) + +mapstringfloat, err := c.GetRates("USD", "EUR,CHY") +// Handle error +``` + +### Please click GoDocs chevron above to view current GoDoc information for this package +{{template "contributions"}} +{{template "donations"}} +{{- end}} \ No newline at end of file diff --git a/cmd/documentation/currency_templates/fx_fixer.tmpl b/cmd/documentation/currency_templates/fx_fixer.tmpl index 346e9b31..60a1e4dd 100644 --- a/cmd/documentation/currency_templates/fx_fixer.tmpl +++ b/cmd/documentation/currency_templates/fx_fixer.tmpl @@ -1,4 +1,4 @@ -{{define "currency forexprovider fixer" -}} +{{define "currency forexprovider fixer.io" -}} {{template "header" .}} ## Current Features for {{.Name}} @@ -11,15 +11,15 @@ + Individual package example below: ```go import ( -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/fixer.io" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/fixer.io" ) c := fixer.Fixer{} // Define configuration newSettings := base.Settings{ - Name: "Fixer", + Name: "Fixer", Enabled: true, Verbose: false, RESTPollingDelay: time.Duration, diff --git a/cmd/documentation/currency_templates/fx_openexchangerates.tmpl b/cmd/documentation/currency_templates/fx_openexchangerates.tmpl index 72dbd7c3..372e1936 100644 --- a/cmd/documentation/currency_templates/fx_openexchangerates.tmpl +++ b/cmd/documentation/currency_templates/fx_openexchangerates.tmpl @@ -11,15 +11,15 @@ + Individual package example below: ```go import ( -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/openexchangerates" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/openexchangerates" ) c := openexchangerates.OXR{} // Define configuration newSettings := base.Settings{ - Name: "openexchangerates", + Name: "openexchangerates", Enabled: true, Verbose: false, RESTPollingDelay: time.Duration, diff --git a/cmd/documentation/documentation.go b/cmd/documentation/documentation.go index 5267e306..1b7fe352 100644 --- a/cmd/documentation/documentation.go +++ b/cmd/documentation/documentation.go @@ -19,7 +19,7 @@ import ( const ( // DefaultRepo is the main example repository - DefaultRepo = "https://api.github.com/repos/[REPO ADDRESS HERE]" + DefaultRepo = "https://api.github.com/repos/thrasher-corp/gocryptotrader" // GithubAPIEndpoint allows the program to query your repository // contributor list @@ -32,6 +32,39 @@ const ( ContributorFile = "CONTRIBUTORS" ) +var ( + // DefaultExcludedDirectories defines the basic directory exclusion list for GCT + DefaultExcludedDirectories = []string{".github", + ".git", + "node_modules", + ".vscode", + ".idea", + "cmd_templates", + "common_templates", + "communications_templates", + "config_templates", + "currency_templates", + "events_templates", + "exchanges_templates", + "portfolio_templates", + "root_templates", + "sub_templates", + "testdata_templates", + "tools_templates", + "web_templates", + } + + // global flag for verbosity + verbose bool + // current tool directory to specify working templates + toolDir string + // exposes root directory if outside of document tool directory + repoDir string + // is a broken down version of the documentation tool dir for cross platform + // checking + ref = []string{"gocryptotrader", "cmd", "documentation"} +) + // Contributor defines an account associated with this code base by doing // contributions type Contributor struct { @@ -43,12 +76,11 @@ type Contributor struct { // Config defines the running config to deploy documentation across a github // repository including exclusion lists for files and directories type Config struct { - GithubRepo string `json:"githubRepo"` - Exclusions Exclusions `json:"exclusionList"` - RootReadme bool `json:"rootReadmeActive"` - LicenseFile bool `json:"licenseFileActive"` - ContributorFile bool `json:"contributorFileActive"` - ReferencePathToRepo string `json:"referencePathToRepo"` + GithubRepo string `json:"githubRepo"` + Exclusions Exclusions `json:"exclusionList"` + RootReadme bool `json:"rootReadmeActive"` + LicenseFile bool `json:"licenseFileActive"` + ContributorFile bool `json:"contributorFileActive"` } // Exclusions defines the exclusion list so documents are not generated @@ -62,7 +94,6 @@ type DocumentationDetails struct { Directories []string Tmpl *template.Template Contributors []Contributor - Verbose bool Config *Config } @@ -77,15 +108,33 @@ type Attributes struct { } func main() { - verbose := flag.Bool("v", false, "Verbose output") - + flag.BoolVar(&verbose, "v", false, "Verbose output") + flag.StringVar(&toolDir, "tooldir", "", "Pass in the documentation tool directory if outside tool folder") flag.Parse() + wd, err := os.Getwd() + if err != nil { + fmt.Println("Documentation tool error cannot get working dir:", err) + os.Exit(1) + } + + if strings.Contains(wd, filepath.Join(ref...)) { + rootdir := filepath.Dir(filepath.Dir(wd)) + repoDir = rootdir + toolDir = wd + } else { + if toolDir == "" { + fmt.Println("Please set documentation tool directory via the tooldir flag if working outside of tool directory") + os.Exit(1) + } + repoDir = wd + } + fmt.Println(core.Banner) fmt.Println("This will update and regenerate documentation for the different packages in your repo.") fmt.Println() - if *verbose { + if verbose { fmt.Println("Fetching configuration...") } @@ -95,11 +144,11 @@ func main() { err) } - if *verbose { + if verbose { fmt.Println("Fetching project directory tree...") } - dirList, err := GetProjectDirectoryTree(&config, *verbose) + dirList, err := GetProjectDirectoryTree(&config) if err != nil { log.Fatalf("Documentation Generation Tool - GetProjectDirectoryTree error %s", err) @@ -107,7 +156,7 @@ func main() { var contributors []Contributor if config.ContributorFile { - if *verbose { + if verbose { fmt.Println("Fetching repository contributor list...") } contributors, err = GetContributorList(config.GithubRepo) @@ -116,16 +165,15 @@ func main() { err) } - // idoall's contributors were forked and merged, so his contributions - // aren't automatically retrievable - contributors = append(contributors, Contributor{ - Login: "idoall", - URL: "https://github.com/idoall", - Contributions: 1, - }) - // Github API missing contributors - missingAPIContributors := []Contributor{ + contributors = append(contributors, []Contributor{ + // idoall's contributors were forked and merged, so his contributions + // aren't automatically retrievable + { + Login: "idoall", + URL: "https://github.com/idoall", + Contributions: 1, + }, { Login: "mattkanwisher", URL: "https://github.com/mattkanwisher", @@ -151,10 +199,9 @@ func main() { URL: "https://github.com/zeldrinn", Contributions: 1, }, - } - contributors = append(contributors, missingAPIContributors...) + }...) - if *verbose { + if verbose { fmt.Println("Contributor List Fetched") for i := range contributors { fmt.Println(contributors[i].Login) @@ -164,7 +211,7 @@ func main() { fmt.Println("Contributor list file disabled skipping fetching details") } - if *verbose { + if verbose { fmt.Println("Fetching template files...") } @@ -174,7 +221,7 @@ func main() { err) } - if *verbose { + if verbose { fmt.Println("All core systems fetched, updating documentation...") } @@ -182,7 +229,6 @@ func main() { dirList, tmpl, contributors, - *verbose, &config}) if err != nil { log.Fatalf("Documentation Generation Tool - UpdateDocumentation error %s", @@ -195,11 +241,12 @@ func main() { // GetConfiguration retrieves the documentation configuration func GetConfiguration() (Config, error) { var c Config - file, err := os.OpenFile("config.json", os.O_RDWR, os.ModePerm) + configFilePath := filepath.Join([]string{toolDir, "config.json"}...) + file, err := os.OpenFile(configFilePath, os.O_RDWR, os.ModePerm) if err != nil { - fmt.Println("Creating configuration file, please add github repository path and preferences") + fmt.Println("Creating configuration file, please check to add a different github repository path and change preferences") - file, err = os.Create("config.json") + file, err = os.Create(configFilePath) if err != nil { return c, err } @@ -209,8 +256,7 @@ func GetConfiguration() (Config, error) { c.ContributorFile = true c.LicenseFile = true c.RootReadme = true - c.ReferencePathToRepo = "../../" - c.Exclusions.Directories = []string{".github"} + c.Exclusions.Directories = DefaultExcludedDirectories data, mErr := json.MarshalIndent(c, "", " ") if mErr != nil { @@ -235,21 +281,17 @@ func GetConfiguration() (Config, error) { return c, err } - if c.GithubRepo == "" || c.GithubRepo == DefaultRepo { + if c.GithubRepo == "" { return c, errors.New("repository not set in config.json file, please change") } - if c.ReferencePathToRepo == "" { - return c, errors.New("reference path not set in the config.json file, please set") - } - return c, nil } // IsExcluded returns if the file path is included in the exclusion list func IsExcluded(path string, exclusion []string) bool { - for _, data := range exclusion { - if strings.Contains(path, data) { + for i := range exclusion { + if path == exclusion[i] { return true } } @@ -258,18 +300,18 @@ func IsExcluded(path string, exclusion []string) bool { // GetProjectDirectoryTree uses filepath walk functions to get each individual // directory name and path to match templates with -func GetProjectDirectoryTree(c *Config, verbose bool) ([]string, error) { +func GetProjectDirectoryTree(c *Config) ([]string, error) { var directoryData []string if c.RootReadme { // Projects root README.md - directoryData = append(directoryData, c.ReferencePathToRepo) + directoryData = append(directoryData, repoDir) } if c.LicenseFile { // Standard license file - directoryData = append(directoryData, c.ReferencePathToRepo+LicenseFile) + directoryData = append(directoryData, filepath.Join(repoDir, LicenseFile)) } if c.ContributorFile { // Standard contributor file - directoryData = append(directoryData, c.ReferencePathToRepo+ContributorFile) + directoryData = append(directoryData, filepath.Join(repoDir, ContributorFile)) } walkfn := func(path string, info os.FileInfo, err error) error { @@ -293,7 +335,7 @@ func GetProjectDirectoryTree(c *Config, verbose bool) ([]string, error) { return nil } - return directoryData, filepath.Walk(c.ReferencePathToRepo, walkfn) + return directoryData, filepath.Walk(repoDir, walkfn) } // GetTemplateFiles parses and returns all template files in the documentation @@ -313,6 +355,9 @@ func GetTemplateFiles() (*template.Template, error) { var parseError error tmpl, parseError = tmpl.ParseGlob(filepath.Join(path, "*.tmpl")) if parseError != nil { + if strings.Contains(parseError.Error(), "pattern matches no files") { + return nil + } return parseError } return filepath.SkipDir @@ -320,7 +365,7 @@ func GetTemplateFiles() (*template.Template, error) { return nil } - return tmpl, filepath.Walk(".", walkfn) + return tmpl, filepath.Walk(toolDir, walkfn) } // GetContributorList fetches a list of contributors from the github api @@ -371,18 +416,23 @@ func GetGoDocURL(name string) string { // UpdateDocumentation generates or updates readme/documentation files across // the codebase func UpdateDocumentation(details DocumentationDetails) error { - for _, path := range details.Directories { - data := strings.Split(path, "/") + for i := range details.Directories { + cutset := details.Directories[i][len(repoDir):] + if cutset != "" && cutset[0] == os.PathSeparator { + cutset = cutset[1:] + } + + data := strings.Split(cutset, string(os.PathSeparator)) + var temp []string - for _, d := range data { - if d == ".." { + for x := range data { + if data[x] == ".." { continue } - if d == "" { + if data[x] == "" { break } - - temp = append(temp, d) + temp = append(temp, data[x]) } var name string @@ -393,7 +443,7 @@ func UpdateDocumentation(details DocumentationDetails) error { } if IsExcluded(name, details.Config.Exclusions.Files) { - if details.Verbose { + if verbose { fmt.Println("Excluding file:", name) } continue @@ -401,20 +451,21 @@ func UpdateDocumentation(details DocumentationDetails) error { if details.Tmpl.Lookup(name) == nil { fmt.Printf("Template not found for path %s create new template with {{define \"%s\" -}} TEMPLATE HERE {{end}}\n", - path, + details.Directories[i], name) continue } var mainPath string if name == LicenseFile || name == ContributorFile { - mainPath = path + mainPath = details.Directories[i] } else { - mainPath = filepath.Join(path, "README.md") + mainPath = filepath.Join(details.Directories[i], "README.md") } err := os.Remove(mainPath) - if err != nil && !strings.Contains(err.Error(), "no such file or directory") { + if err != nil && !(strings.Contains(err.Error(), "no such file or directory") || + strings.Contains(err.Error(), "The system cannot find the file specified.")) { return err } @@ -422,14 +473,15 @@ func UpdateDocumentation(details DocumentationDetails) error { if err != nil { return err } - defer file.Close() attr := GetDocumentationAttributes(name, details.Contributors) err = details.Tmpl.ExecuteTemplate(file, name, attr) if err != nil { + file.Close() return err } + file.Close() } return nil } diff --git a/cmd/documentation/exchanges_templates/btcmarkets.tmpl b/cmd/documentation/exchanges_templates/btcmarkets.tmpl index 83350473..4d81557e 100644 --- a/cmd/documentation/exchanges_templates/btcmarkets.tmpl +++ b/cmd/documentation/exchanges_templates/btcmarkets.tmpl @@ -5,6 +5,7 @@ ### Current Features + REST Support ++ Websocket Support ### How to enable diff --git a/cmd/documentation/exchanges_templates/btse.tmpl b/cmd/documentation/exchanges_templates/btse.tmpl new file mode 100644 index 00000000..9df4638f --- /dev/null +++ b/cmd/documentation/exchanges_templates/btse.tmpl @@ -0,0 +1,99 @@ +{{define "exchanges btse" -}} +{{template "header" .}} +## BTCMarkets Exchange + +### Current Features + ++ REST Support ++ Websocket Support + +### How to enable + ++ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example) + ++ Individual package example below: + +```go + // Exchanges will be abstracted out in further updates and examples will be + // supplied then +``` + +### How to do REST public/private calls + ++ If enabled via "configuration".json file the exchange will be added to the +IBotExchange array in the ```go var bot Bot``` and you will only be able to use +the wrapper interface functions for accessing exchange data. View routines.go +for an example of integration usage with GoCryptoTrader. Rudimentary example +below: + +main.go +```go +var b exchange.IBotExchange + +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "BTSE" { + b = bot.Exchanges[i] + } +} + +// Public calls - wrapper functions + +// Fetches current ticker information +tick, err := b.FetchTicker() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := b.FetchOrderbook() +if err != nil { + // Handle error +} + +// Private calls - wrapper functions - make sure your APIKEY and APISECRET are +// set and AuthenticatedAPISupport is set to true + +// Fetches current account information +accountInfo, err := b.GetAccountInfo() +if err != nil { + // Handle error +} +``` + ++ If enabled via individually importing package, rudimentary example below: + +```go +// Public calls + +// Fetches current ticker information +ticker, err := b.GetTicker() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := b.GetOrderBook() +if err != nil { + // Handle error +} + +// Private calls - make sure your APIKEY and APISECRET are set and +// AuthenticatedAPISupport is set to true + +// GetUserInfo returns account info +accountInfo, err := b.GetUserInfo(...) +if err != nil { + // Handle error +} + +// Submits an order and the exchange and returns its tradeID +tradeID, err := b.Trade(...) +if err != nil { + // Handle error +} +``` + +### Please click GoDocs chevron above to view current GoDoc information for this package +{{template "contributions"}} +{{template "donations"}} +{{end}} diff --git a/cmd/documentation/root_templates/CONTRIBUTORS.tmpl b/cmd/documentation/root_templates/CONTRIBUTORS.tmpl index fa2ba817..0a25998d 100644 --- a/cmd/documentation/root_templates/CONTRIBUTORS.tmpl +++ b/cmd/documentation/root_templates/CONTRIBUTORS.tmpl @@ -3,4 +3,4 @@ Thanks to the following contributors: {{range $contributor := .Contributors -}} {{$contributor.Login}} | {{$contributor.URL}} {{end}} -{{end}} +{{- end}} diff --git a/cmd/documentation/root_templates/root_readme.tmpl b/cmd/documentation/root_templates/root_readme.tmpl index 3d7ca831..ec6f5159 100644 --- a/cmd/documentation/root_templates/root_readme.tmpl +++ b/cmd/documentation/root_templates/root_readme.tmpl @@ -133,4 +133,4 @@ If this framework helped you in any way, or you would like to support the develo Binaries will be published once the codebase reaches a stable condition. {{template "contributors" .}} -{{end}} +{{- end}} diff --git a/cmd/documentation/sub_templates/contributors.tmpl b/cmd/documentation/sub_templates/contributors.tmpl index 1051ea3b..35c8c455 100644 --- a/cmd/documentation/sub_templates/contributors.tmpl +++ b/cmd/documentation/sub_templates/contributors.tmpl @@ -5,7 +5,7 @@ |User|Contribution Amount| |--|--| -{{- range $contributor := .Contributors -}} +{{range $contributor := .Contributors -}} | [{{$contributor.Login}}]({{$contributor.URL}}) | {{$contributor.Contributions}} | {{end}} -{{end}} +{{- end}} diff --git a/cmd/documentation/sub_templates/donations.tmpl b/cmd/documentation/sub_templates/donations.tmpl index a864ed70..3fd5682f 100644 --- a/cmd/documentation/sub_templates/donations.tmpl +++ b/cmd/documentation/sub_templates/donations.tmpl @@ -6,4 +6,4 @@ If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** -{{end}} +{{- end}} diff --git a/common/README.md b/common/README.md index bb47fcca..d7c634d0 100644 --- a/common/README.md +++ b/common/README.md @@ -55,4 +55,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/communications/base/README.md b/communications/base/README.md index 1802f1df..dd3655be 100644 --- a/communications/base/README.md +++ b/communications/base/README.md @@ -44,4 +44,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/communications/slack/README.md b/communications/slack/README.md index 39b1675d..b68f14d1 100644 --- a/communications/slack/README.md +++ b/communications/slack/README.md @@ -92,4 +92,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/communications/smsglobal/README.md b/communications/smsglobal/README.md index 04cf4cb4..938ef324 100644 --- a/communications/smsglobal/README.md +++ b/communications/smsglobal/README.md @@ -77,4 +77,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/communications/telegram/README.md b/communications/telegram/README.md index 3163302b..eff3ee88 100644 --- a/communications/telegram/README.md +++ b/communications/telegram/README.md @@ -92,4 +92,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/config/README.md b/config/README.md index 70772f5a..73ee7e7d 100644 --- a/config/README.md +++ b/config/README.md @@ -249,4 +249,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/currency/README.md b/currency/README.md index 90d66ea3..a7a75f08 100644 --- a/currency/README.md +++ b/currency/README.md @@ -46,4 +46,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/currency/forexprovider/README.md b/currency/forexprovider/README.md index 84874e23..aaad1106 100644 --- a/currency/forexprovider/README.md +++ b/currency/forexprovider/README.md @@ -45,4 +45,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/currency/forexprovider/base/README.md b/currency/forexprovider/base/README.md index 5dfe1e1d..30b5e707 100644 --- a/currency/forexprovider/base/README.md +++ b/currency/forexprovider/base/README.md @@ -43,4 +43,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/currency/forexprovider/currencyconverterapi/README.md b/currency/forexprovider/currencyconverterapi/README.md index 8df13b14..cca5b1d3 100644 --- a/currency/forexprovider/currencyconverterapi/README.md +++ b/currency/forexprovider/currencyconverterapi/README.md @@ -5,7 +5,7 @@ [![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader) [![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencyconverter) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencyconverterapi) [![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) @@ -29,15 +29,15 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader + Individual package example below: ```go import ( -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencyconverter" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencyconverter" ) c := currencyconverter.CurrencyConverter{} // Define configuration newSettings := base.Settings{ - Name: "CurrencyConverter", + Name: "CurrencyConverter", Enabled: true, Verbose: false, RESTPollingDelay: time.Duration, @@ -72,4 +72,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/currency/forexprovider/currencylayer/README.md b/currency/forexprovider/currencylayer/README.md index b18bfb9b..37d76211 100644 --- a/currency/forexprovider/currencylayer/README.md +++ b/currency/forexprovider/currencylayer/README.md @@ -29,15 +29,15 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader + Individual package example below: ```go import ( -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencylayer" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencylayer" ) c := currencylayer.CurrencyLayer{} // Define configuration newSettings := base.Settings{ - Name: "CurrencyLayer", + Name: "CurrencyLayer", Enabled: true, Verbose: false, RESTPollingDelay: time.Duration, @@ -72,4 +72,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/currency/forexprovider/exchangeratesapi.io/README.md b/currency/forexprovider/exchangeratesapi.io/README.md new file mode 100644 index 00000000..793f5466 --- /dev/null +++ b/currency/forexprovider/exchangeratesapi.io/README.md @@ -0,0 +1,74 @@ +# GoCryptoTrader package Forexprovider + + + + +[![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/currency/forexprovider/exchangeratesapi.io) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + + +This forexprovider package is part of the GoCryptoTrader codebase. + +## This is still in active development + +You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) + +## Current Features for forexprovider + ++ Fetches up to date curency data from [Exchange rates API]("http://exchangeratesapi.io") + +### How to enable + ++ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-currency-via-config-example) + ++ Individual package example below: +```go +import ( + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/exchangerates" +) + +c := exchangerates.ExchangeRates{} + +// Define configuration +newSettings := base.Settings{ + Name: "ExchangeRates", + Enabled: true, + Verbose: false, + RESTPollingDelay: time.Duration, + APIKey: "key", + APIKeyLvl: "keylvl", + PrimaryProvider: true, +} + +c.Setup(newSettings) + +mapstringfloat, err := c.GetRates("USD", "EUR,CHY") +// Handle error +``` + +### Please click GoDocs chevron above to view current GoDoc information for this package + +## Contribution + +Please feel free to submit any pull requests or suggest any desired features to be added. + +When submitting a PR, please abide by our coding guidelines: + ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. + +## Donations + + + +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: + +***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** \ No newline at end of file diff --git a/currency/forexprovider/fixer.io/README.md b/currency/forexprovider/fixer.io/README.md index 1ec733db..135caff4 100644 --- a/currency/forexprovider/fixer.io/README.md +++ b/currency/forexprovider/fixer.io/README.md @@ -5,7 +5,7 @@ [![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader) [![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/currency/forexprovider/fixer) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/currency/forexprovider/fixer.io) [![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) @@ -29,15 +29,15 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader + Individual package example below: ```go import ( -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/fixer.io" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/fixer.io" ) c := fixer.Fixer{} // Define configuration newSettings := base.Settings{ - Name: "Fixer", + Name: "Fixer", Enabled: true, Verbose: false, RESTPollingDelay: time.Duration, @@ -72,4 +72,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/currency/forexprovider/openexchangerates/README.md b/currency/forexprovider/openexchangerates/README.md index a9724ed1..83f7fac3 100644 --- a/currency/forexprovider/openexchangerates/README.md +++ b/currency/forexprovider/openexchangerates/README.md @@ -29,15 +29,15 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader + Individual package example below: ```go import ( -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" -"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/openexchangerates" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/openexchangerates" ) c := openexchangerates.OXR{} // Define configuration newSettings := base.Settings{ - Name: "openexchangerates", + Name: "openexchangerates", Enabled: true, Verbose: false, RESTPollingDelay: time.Duration, @@ -72,4 +72,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/README.md b/exchanges/README.md index bbaf3ca1..9333cc6e 100644 --- a/exchanges/README.md +++ b/exchanges/README.md @@ -45,4 +45,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/alphapoint/README.md b/exchanges/alphapoint/README.md index dd75369b..99c6a8f7 100644 --- a/exchanges/alphapoint/README.md +++ b/exchanges/alphapoint/README.md @@ -46,4 +46,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/anx/README.md b/exchanges/anx/README.md index 43477a3c..1826110a 100644 --- a/exchanges/anx/README.md +++ b/exchanges/anx/README.md @@ -141,4 +141,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/binance/README.md b/exchanges/binance/README.md index d3cff5ab..11625cbd 100644 --- a/exchanges/binance/README.md +++ b/exchanges/binance/README.md @@ -138,4 +138,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/bitfinex/README.md b/exchanges/bitfinex/README.md index c7e03d5f..616cb503 100644 --- a/exchanges/bitfinex/README.md +++ b/exchanges/bitfinex/README.md @@ -138,4 +138,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/bitflyer/README.md b/exchanges/bitflyer/README.md index c666afd8..34a95fec 100644 --- a/exchanges/bitflyer/README.md +++ b/exchanges/bitflyer/README.md @@ -137,4 +137,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/bithumb/README.md b/exchanges/bithumb/README.md index 04fdc017..052e307e 100644 --- a/exchanges/bithumb/README.md +++ b/exchanges/bithumb/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/bitmex/README.md b/exchanges/bitmex/README.md index 0a78e42d..d77a9fed 100644 --- a/exchanges/bitmex/README.md +++ b/exchanges/bitmex/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/bitstamp/README.md b/exchanges/bitstamp/README.md index 9f2b4f2a..0acefd1f 100644 --- a/exchanges/bitstamp/README.md +++ b/exchanges/bitstamp/README.md @@ -138,4 +138,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/bittrex/README.md b/exchanges/bittrex/README.md index 6b1a0c63..039bc2e9 100644 --- a/exchanges/bittrex/README.md +++ b/exchanges/bittrex/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/btcmarkets/README.md b/exchanges/btcmarkets/README.md index 762de864..06a67665 100644 --- a/exchanges/btcmarkets/README.md +++ b/exchanges/btcmarkets/README.md @@ -23,6 +23,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader ### Current Features + REST Support ++ Websocket Support ### How to enable @@ -130,4 +131,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/btse/README.md b/exchanges/btse/README.md index 5c5688e8..cdb5fd7e 100644 --- a/exchanges/btse/README.md +++ b/exchanges/btse/README.md @@ -1,30 +1,133 @@ - -# GoCryptoTrader Btse Exchange Wrapper +# GoCryptoTrader package Btse -An exchange interface wrapper for the GoCryptoTrader application. + +[![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/btse) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + + +This btse package is part of the GoCryptoTrader codebase. ## This is still in active development - You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). +You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). -## Current Btse Exchange Features +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) -+ REST Support -+ Websocket Support +## BTCMarkets Exchange -+ Can be used as a package +### Current Features -## Notes ++ REST Support ++ Websocket Support -+ Please add notes here with any production issues -+ Please provide link to exchange website and API documentation +### How to enable -## Contributors ++ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example) -+ Please add your information ++ Individual package example below: -|User|Github|Contribution| -|--|--|--| -|AliasGoesHere|https://github.com/AliasGoesHere |WHAT-YOU-DID| +```go + // Exchanges will be abstracted out in further updates and examples will be + // supplied then +``` + +### How to do REST public/private calls + ++ If enabled via "configuration".json file the exchange will be added to the +IBotExchange array in the ```go var bot Bot``` and you will only be able to use +the wrapper interface functions for accessing exchange data. View routines.go +for an example of integration usage with GoCryptoTrader. Rudimentary example +below: + +main.go +```go +var b exchange.IBotExchange + +for i := range bot.Exchanges { + if bot.Exchanges[i].GetName() == "BTSE" { + b = bot.Exchanges[i] + } +} + +// Public calls - wrapper functions + +// Fetches current ticker information +tick, err := b.FetchTicker() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := b.FetchOrderbook() +if err != nil { + // Handle error +} + +// Private calls - wrapper functions - make sure your APIKEY and APISECRET are +// set and AuthenticatedAPISupport is set to true + +// Fetches current account information +accountInfo, err := b.GetAccountInfo() +if err != nil { + // Handle error +} +``` + ++ If enabled via individually importing package, rudimentary example below: + +```go +// Public calls + +// Fetches current ticker information +ticker, err := b.GetTicker() +if err != nil { + // Handle error +} + +// Fetches current orderbook information +ob, err := b.GetOrderBook() +if err != nil { + // Handle error +} + +// Private calls - make sure your APIKEY and APISECRET are set and +// AuthenticatedAPISupport is set to true + +// GetUserInfo returns account info +accountInfo, err := b.GetUserInfo(...) +if err != nil { + // Handle error +} + +// Submits an order and the exchange and returns its tradeID +tradeID, err := b.Trade(...) +if err != nil { + // Handle error +} +``` + +### Please click GoDocs chevron above to view current GoDoc information for this package + +## Contribution + +Please feel free to submit any pull requests or suggest any desired features to be added. + +When submitting a PR, please abide by our coding guidelines: + ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. + +## Donations + + + +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: + +***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** diff --git a/exchanges/coinbasepro/README.md b/exchanges/coinbasepro/README.md index d3df8fe8..b734dd6b 100644 --- a/exchanges/coinbasepro/README.md +++ b/exchanges/coinbasepro/README.md @@ -138,4 +138,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/coinbene/README.md b/exchanges/coinbene/README.md index e5a7cd7e..c4fe862f 100644 --- a/exchanges/coinbene/README.md +++ b/exchanges/coinbene/README.md @@ -90,7 +90,7 @@ if err != nil { } // Fetches current orderbook information -ob, err := c.GetOrderBook() +ob, err := c.GetOrderbook() if err != nil { // Handle error } @@ -105,7 +105,7 @@ if err != nil { } // Submits an order and the exchange and returns its tradeID -tradeID, err := c.Trade(...) +resp, err := c.SubmitOrder(...) if err != nil { // Handle error } @@ -138,4 +138,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/coinut/README.md b/exchanges/coinut/README.md index 1faf793f..dc2cc3ce 100644 --- a/exchanges/coinut/README.md +++ b/exchanges/coinut/README.md @@ -138,4 +138,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/exmo/README.md b/exchanges/exmo/README.md index 70fc25fa..64e212d8 100644 --- a/exchanges/exmo/README.md +++ b/exchanges/exmo/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/gateio/README.md b/exchanges/gateio/README.md index 98c5535e..f4e9763c 100644 --- a/exchanges/gateio/README.md +++ b/exchanges/gateio/README.md @@ -137,4 +137,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/gemini/README.md b/exchanges/gemini/README.md index 63bf7c14..7e528485 100644 --- a/exchanges/gemini/README.md +++ b/exchanges/gemini/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/hitbtc/README.md b/exchanges/hitbtc/README.md index 20bfa3a4..7acbaef6 100644 --- a/exchanges/hitbtc/README.md +++ b/exchanges/hitbtc/README.md @@ -138,4 +138,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/huobi/README.md b/exchanges/huobi/README.md index 3fad2d0f..6ef6ed77 100644 --- a/exchanges/huobi/README.md +++ b/exchanges/huobi/README.md @@ -137,4 +137,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/itbit/README.md b/exchanges/itbit/README.md index cb7a6d14..1c87aa4b 100644 --- a/exchanges/itbit/README.md +++ b/exchanges/itbit/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/kraken/README.md b/exchanges/kraken/README.md index 06609685..7992a499 100644 --- a/exchanges/kraken/README.md +++ b/exchanges/kraken/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/lakebtc/README.md b/exchanges/lakebtc/README.md index 0a0d1231..0a043476 100644 --- a/exchanges/lakebtc/README.md +++ b/exchanges/lakebtc/README.md @@ -131,4 +131,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/lbank/README.md b/exchanges/lbank/README.md index 78e67672..df025828 100644 --- a/exchanges/lbank/README.md +++ b/exchanges/lbank/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/localbitcoins/README.md b/exchanges/localbitcoins/README.md index 715e8c39..22123daa 100644 --- a/exchanges/localbitcoins/README.md +++ b/exchanges/localbitcoins/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/nonce/README.md b/exchanges/nonce/README.md index db4073fa..a30717dd 100644 --- a/exchanges/nonce/README.md +++ b/exchanges/nonce/README.md @@ -42,4 +42,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/okcoin/README.md b/exchanges/okcoin/README.md index 751cedee..578b12de 100644 --- a/exchanges/okcoin/README.md +++ b/exchanges/okcoin/README.md @@ -138,4 +138,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/okex/README.md b/exchanges/okex/README.md index 2b45d8de..5a6715db 100644 --- a/exchanges/okex/README.md +++ b/exchanges/okex/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/orderbook/README.md b/exchanges/orderbook/README.md index bd5c6ed4..e9befce8 100644 --- a/exchanges/orderbook/README.md +++ b/exchanges/orderbook/README.md @@ -73,4 +73,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/poloniex/README.md b/exchanges/poloniex/README.md index e9817e43..743045d7 100644 --- a/exchanges/poloniex/README.md +++ b/exchanges/poloniex/README.md @@ -138,4 +138,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/request/README.md b/exchanges/request/README.md index 37787934..fe20f726 100644 --- a/exchanges/request/README.md +++ b/exchanges/request/README.md @@ -43,4 +43,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/stats/README.md b/exchanges/stats/README.md index 890b0930..f90c39e1 100644 --- a/exchanges/stats/README.md +++ b/exchanges/stats/README.md @@ -46,4 +46,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/ticker/README.md b/exchanges/ticker/README.md index 4dc3d5a3..f67a4c91 100644 --- a/exchanges/ticker/README.md +++ b/exchanges/ticker/README.md @@ -74,4 +74,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/yobit/README.md b/exchanges/yobit/README.md index efada20d..fc89aca6 100644 --- a/exchanges/yobit/README.md +++ b/exchanges/yobit/README.md @@ -130,4 +130,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/exchanges/zb/README.md b/exchanges/zb/README.md index 6fb94c2e..4d94cb06 100644 --- a/exchanges/zb/README.md +++ b/exchanges/zb/README.md @@ -137,4 +137,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/portfolio/README.md b/portfolio/README.md index e9f77b9d..cf5b3579 100644 --- a/portfolio/README.md +++ b/portfolio/README.md @@ -42,4 +42,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - diff --git a/testdata/README.md b/testdata/README.md index 2f0aea4b..f3b7a360 100644 --- a/testdata/README.md +++ b/testdata/README.md @@ -41,5 +41,4 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: -***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - +***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** \ No newline at end of file diff --git a/web/README.md b/web/README.md index 351ea81f..a4887b58 100644 --- a/web/README.md +++ b/web/README.md @@ -98,4 +98,3 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** - From c27b8657e2c3bf092db5f17aa509204be85933f0 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 3 Dec 2019 08:44:57 +1100 Subject: [PATCH 65/71] Add 32-bit linux build target for Travis (#389) * Add 32bit build matrix test * Adjust travis/Makefile * Set dist back to xenial * export CGO_ENABLED=1 * Add gcc-multilib * Sudo installerino * make check -> make test --- .travis.yml | 32 ++++++++++++++++++++++++++++++-- Makefile | 5 +++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 209562ce..f41e6b4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,9 +13,10 @@ matrix: script: - npm run lint - npm run build + - language: go dist: xenial - name: 'GoCryptoTrader [back-end] [linux]' + name: 'GoCryptoTrader [back-end] [linux] [64-bit]' go: - 1.13.x env: @@ -30,12 +31,39 @@ matrix: services: - postgresql before_script: - - psql -c 'create database gct_dev_ci;' -U postgres + - psql -c 'create database gct_dev_ci;' -U postgres script: - make check after_success: - bash <(curl -s https://codecov.io/bash) + - language: go + dist: xenial + name: 'GoCryptoTrader [back-end] [linux] [32-bit]' + go: + - 1.13.x + env: + - GO111MODULE=on + - NO_RACE_TEST=1 + - PSQL_USER=postgres + - PSQL_HOST=localhost + - PSQL_DBNAME=gct_dev_ci + install: true + cache: + directories: + - $GOPATH/pkg/mod + services: + - postgresql + before_script: + - psql -c 'create database gct_dev_ci;' -U postgres + script: + - export GOARCH=386 + - export CGO_ENABLED=1 + - sudo apt-get install gcc-multilib + - make test + after_success: + - bash <(curl -s https://codecov.io/bash) + - language: go os: osx name: 'GoCryptoTrader [back-end] [darwin]' diff --git a/Makefile b/Makefile index 1a2cb53b..7094b991 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ GCTLISTENPORT=9050 GCTPROFILERLISTENPORT=8085 CRON = $(TRAVIS_EVENT_TYPE) DRIVER ?= psql +RACE_FLAG := $(if $(NO_RACE_TEST),,-race) get: GO111MODULE=on go get $(GCTPKG) @@ -19,9 +20,9 @@ check: linter test test: ifeq ($(CRON), cron) - go test -race -tags=mock_test_off -coverprofile=coverage.txt -covermode=atomic ./... + go test $(RACE_FLAG) -tags=mock_test_off -coverprofile=coverage.txt -covermode=atomic ./... else - go test -race -coverprofile=coverage.txt -covermode=atomic ./... + go test $(RACE_FLAG) -coverprofile=coverage.txt -covermode=atomic ./... endif build: From 0c5d75b22cd4aa9e3108dbbd2fc3e170b4c2b649 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 3 Dec 2019 10:06:08 +1100 Subject: [PATCH 66/71] (Engine) Variety of engine updates (#390) * drop common uuid v4 func and imported package as needed * removed common functions regarding json marshal and unmarshal and used the json package directly. WRT unmarshal it was calling reflect and converted to string which is also checked in the JSON package so it was doing a double up, this will be a tiny gain as it was directly used in the requester package for all our outbound requests. * add in string * explicitly throw away return error value * atleast return the error that websocket initialise returns * return error when not connected * fix comment * Adds comments * move package declarations * drop append whenever we call supported * remove unused import * Change incorrect spelling * fix tests * fix go import issue --- cmd/exchange_wrapper_issues/main.go | 3 +- cmd/websocket_client/main.go | 5 +- common/common.go | 22 +------- common/common_test.go | 56 ------------------- communications/communications_test.go | 2 +- communications/slack/slack.go | 10 ++-- communications/slack/slack_test.go | 10 ++-- communications/telegram/telegram.go | 12 ++-- config/config_encryption.go | 3 +- currency/code.go | 19 ++++--- currency/code_test.go | 27 +++++---- currency/code_types.go | 6 +- currency/currencies.go | 7 +-- currency/currencies_test.go | 11 ++-- currency/pair.go | 7 +-- currency/pair_test.go | 11 ++-- currency/pairs.go | 6 +- currency/pairs_test.go | 17 +++--- currency/storage.go | 5 +- dispatch/mux.go | 3 +- engine/connection.go | 4 ++ engine/orders.go | 3 +- engine/websocket.go | 18 +++--- exchanges/alphapoint/alphapoint.go | 6 +- exchanges/alphapoint/alphapoint_test.go | 13 +++-- exchanges/alphapoint/alphapoint_websocket.go | 6 +- exchanges/anx/anx.go | 4 +- exchanges/asset/asset.go | 26 ++++----- exchanges/binance/binance.go | 4 +- exchanges/binance/binance_websocket.go | 12 ++-- exchanges/bitfinex/bitfinex.go | 3 +- exchanges/bitfinex/bitfinex_websocket.go | 6 +- exchanges/bithumb/bithumb.go | 7 +-- exchanges/bitmex/bitmex.go | 7 +-- exchanges/bitmex/bitmex_websocket.go | 34 +++++------ exchanges/bitstamp/bitstamp.go | 4 +- exchanges/bitstamp/bitstamp_websocket.go | 8 +-- exchanges/btcmarkets/btcmarkets.go | 3 +- exchanges/btcmarkets/btcmarkets_websocket.go | 12 ++-- exchanges/btse/btse.go | 4 +- exchanges/btse/btse_websocket.go | 8 +-- exchanges/coinbasepro/coinbasepro.go | 3 +- .../coinbasepro/coinbasepro_websocket.go | 20 +++---- exchanges/coinbene/coinbene_websocket.go | 18 +++--- exchanges/coinut/coinut.go | 7 +-- exchanges/coinut/coinut_websocket.go | 48 ++++++++-------- exchanges/exchange.go | 4 +- exchanges/gateio/gateio.go | 5 +- exchanges/gateio/gateio_websocket.go | 30 +++++----- exchanges/gemini/gemini.go | 3 +- exchanges/gemini/gemini_websocket.go | 24 ++++---- exchanges/hitbtc/hitbtc_websocket.go | 44 +++++++-------- exchanges/huobi/huobi_websocket.go | 33 ++++++----- exchanges/itbit/itbit.go | 8 +-- exchanges/kraken/kraken_websocket.go | 6 +- exchanges/lakebtc/lakebtc.go | 4 +- exchanges/lakebtc/lakebtc_websocket.go | 8 +-- exchanges/okcoin/okcoin_test.go | 11 ++-- exchanges/okex/okex_test.go | 11 ++-- exchanges/okgroup/okgroup.go | 7 +-- exchanges/okgroup/okgroup_websocket.go | 8 +-- exchanges/poloniex/poloniex_test.go | 3 +- exchanges/poloniex/poloniex_websocket.go | 4 +- exchanges/request/request.go | 3 +- exchanges/websocket/wshandler/wshandler.go | 4 +- .../websocket/wshandler/wshandler_test.go | 4 +- exchanges/zb/zb.go | 5 +- exchanges/zb/zb_test.go | 13 +++-- exchanges/zb/zb_websocket.go | 36 ++++++------ logger/logger_types.go | 47 ++++++++-------- 70 files changed, 393 insertions(+), 462 deletions(-) diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go index 377c3658..e4d2b6e4 100644 --- a/cmd/exchange_wrapper_issues/main.go +++ b/cmd/exchange_wrapper_issues/main.go @@ -13,7 +13,6 @@ import ( "sync" "text/template" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" @@ -737,7 +736,7 @@ func loadConfig() (Config, error) { return config, err } - err = common.JSONDecode(keys, &config) + err = json.Unmarshal(keys, &config) return config, err } diff --git a/cmd/websocket_client/main.go b/cmd/websocket_client/main.go index c71b8876..858dbc86 100644 --- a/cmd/websocket_client/main.go +++ b/cmd/websocket_client/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "errors" "fmt" "log" @@ -113,13 +114,13 @@ func main() { } log.Printf("Fetched config.") - dataJSON, err := common.JSONEncode(&wsResp.Data) + dataJSON, err := json.Marshal(&wsResp.Data) if err != nil { log.Fatal(err) } var resultCfg config.Config - err = common.JSONDecode(dataJSON, &resultCfg) + err = json.Unmarshal(dataJSON, &resultCfg) if err != nil { log.Fatal(err) } diff --git a/common/common.go b/common/common.go index dcabebf8..83229675 100644 --- a/common/common.go +++ b/common/common.go @@ -12,13 +12,11 @@ import ( "os" "os/user" "path/filepath" - "reflect" "regexp" "strconv" "strings" "time" - "github.com/gofrs/uuid" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -43,11 +41,6 @@ const ( WeiPerEther = 1000000000000000000 ) -// GetV4UUID returns a RFC 4122 UUID based on random numbers -func GetV4UUID() (uuid.UUID, error) { - return uuid.NewV4() -} - func initialiseHTTPClient() { // If the HTTPClient isn't set, start a new client with a default timeout of 15 seconds if HTTPClient == nil { @@ -227,7 +220,7 @@ func SendHTTPGetRequest(urlPath string, jsonDecode, isVerbose bool, result inter defer res.Body.Close() if jsonDecode { - err := JSONDecode(contents, result) + err := json.Unmarshal(contents, result) if err != nil { return err } @@ -236,19 +229,6 @@ func SendHTTPGetRequest(urlPath string, jsonDecode, isVerbose bool, result inter return nil } -// JSONEncode encodes structure data into JSON -func JSONEncode(v interface{}) ([]byte, error) { - return json.Marshal(v) -} - -// JSONDecode decodes JSON data into a structure -func JSONDecode(data []byte, to interface{}) error { - if !strings.Contains(reflect.ValueOf(to).Type().String(), "*") { - return errors.New("json decode error - memory address not supplied") - } - return json.Unmarshal(data, to) -} - // EncodeURLValues concatenates url values onto a url string and returns a // string func EncodeURLValues(urlPath string, values url.Values) string { diff --git a/common/common_test.go b/common/common_test.go index 10a7a858..7f892fbf 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -263,62 +263,6 @@ func TestSendHTTPGetRequest(t *testing.T) { } } -func TestJSONEncode(t *testing.T) { - t.Parallel() - type test struct { - Status int `json:"status"` - Data []struct { - Address string `json:"address"` - Balance float64 `json:"balance"` - Nonce interface{} `json:"nonce"` - Code string `json:"code"` - Name interface{} `json:"name"` - Storage interface{} `json:"storage"` - FirstSeen interface{} `json:"firstSeen"` - } `json:"data"` - } - expectOutputString := `{"status":0,"data":null}` - v := test{} - - bitey, err := JSONEncode(v) - if err != nil { - t.Errorf("common JSONEncode error: %s", err) - } - if string(bitey) != expectOutputString { - t.Error("common JSONEncode error") - } - _, err = JSONEncode("WigWham") - if err != nil { - t.Errorf("common JSONEncode error: %s", err) - } -} - -func TestJSONDecode(t *testing.T) { - t.Parallel() - var data []byte - result := "Not a memory address" - err := JSONDecode(data, result) - if err == nil { - t.Error("Common JSONDecode, unmarshalled when address not supplied") - } - - type test struct { - Status int `json:"status"` - Data []struct { - Address string `json:"address"` - Balance float64 `json:"balance"` - } `json:"data"` - } - - var v test - data = []byte(`{"status":1,"data":null}`) - err = JSONDecode(data, &v) - if err != nil || v.Status != 1 { - t.Errorf("Common JSONDecode. Data: %v \nError: %s", - v, err) - } -} - func TestEncodeURLValues(t *testing.T) { t.Parallel() urlstring := "https://www.test.com" diff --git a/communications/communications_test.go b/communications/communications_test.go index cbc90dc5..82717bc6 100644 --- a/communications/communications_test.go +++ b/communications/communications_test.go @@ -10,7 +10,7 @@ func TestNewComm(t *testing.T) { var cfg config.CommunicationsConfig _, err := NewComm(&cfg) if err == nil { - t.Error("NewComm should failed on no enabled communication mediums") + t.Error("NewComm should have failed on no enabled communication mediums") } cfg.TelegramConfig.Enabled = true diff --git a/communications/slack/slack.go b/communications/slack/slack.go index 900925a3..eb7b0b81 100644 --- a/communications/slack/slack.go +++ b/communications/slack/slack.go @@ -82,7 +82,7 @@ func (s *Slack) PushEvent(event base.Event) error { return s.WebsocketSend("message", fmt.Sprintf("event: %s %s", event.Type, event.Message)) } - return nil + return errors.New("slack not connected") } // BuildURL returns an appended token string with the SlackURL @@ -212,7 +212,7 @@ func (s *Slack) WebsocketReader() { } var data WebsocketResponse - err = common.JSONDecode(resp, &data) + err = json.Unmarshal(resp, &data) if err != nil { log.Errorln(log.CommunicationMgr, err) continue @@ -253,7 +253,7 @@ func (s *Slack) WebsocketReader() { func (s *Slack) handlePresenceChange(resp []byte) error { var pres PresenceChange - err := common.JSONDecode(resp, &pres) + err := json.Unmarshal(resp, &pres) if err != nil { return err } @@ -270,7 +270,7 @@ func (s *Slack) handleMessageResponse(resp []byte, data WebsocketResponse) error return errors.New("reply to is != 0") } var msg Message - err := common.JSONDecode(resp, &msg) + err := json.Unmarshal(resp, &msg) if err != nil { return err } @@ -318,7 +318,7 @@ func (s *Slack) handleReconnectResponse(resp []byte) error { URL string `json:"url"` } var recURL reconnectResponse - err := common.JSONDecode(resp, &recURL) + err := json.Unmarshal(resp, &recURL) if err != nil { return err } diff --git a/communications/slack/slack_test.go b/communications/slack/slack_test.go index 4996d2b1..5b1563c0 100644 --- a/communications/slack/slack_test.go +++ b/communications/slack/slack_test.go @@ -1,9 +1,9 @@ package slack import ( + "encoding/json" "testing" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/communications/base" "github.com/thrasher-corp/gocryptotrader/config" ) @@ -198,7 +198,7 @@ func TestHandlePresenceChange(t *testing.T) { t.Error("slack handlePresenceChange(), unmarshalled malformed json") } - data, _ := common.JSONEncode(pres) + data, _ := json.Marshal(pres) err = s.handlePresenceChange(data) if err != nil { t.Errorf("slack handlePresenceChange() Error: %s", err) @@ -225,7 +225,7 @@ func TestHandleMessageResponse(t *testing.T) { var msg Message msg.User = "1337" msg.Text = "Hello World!" - resp, _ := common.JSONEncode(msg) + resp, _ := json.Marshal(msg) err = s.handleMessageResponse(resp, data) if err != nil { @@ -233,7 +233,7 @@ func TestHandleMessageResponse(t *testing.T) { } msg.Text = "!notacommand" - resp, _ = common.JSONEncode(msg) + resp, _ = json.Marshal(msg) err = s.handleMessageResponse(resp, data) if err == nil { @@ -270,7 +270,7 @@ func TestHandleReconnectResponse(t *testing.T) { } testURL.URL = "https://www.thrasher.io" - data, _ := common.JSONEncode(testURL) + data, _ := json.Marshal(testURL) err = s.handleReconnectResponse(data) if err != nil || s.ReconnectURL != "https://www.thrasher.io" { diff --git a/communications/telegram/telegram.go b/communications/telegram/telegram.go index 05c9bb12..30a41b33 100644 --- a/communications/telegram/telegram.go +++ b/communications/telegram/telegram.go @@ -5,6 +5,7 @@ package telegram import ( "bytes" + "encoding/json" "errors" "fmt" "net/http" @@ -219,7 +220,7 @@ func (t *Telegram) SendMessage(text string, chatID int64) error { text, } - json, err := common.JSONEncode(&messageToSend) + json, err := json.Marshal(&messageToSend) if err != nil { return err } @@ -241,14 +242,17 @@ func (t *Telegram) SendMessage(text string, chatID int64) error { } // SendHTTPRequest sends an authenticated HTTP request -func (t *Telegram) SendHTTPRequest(path string, json []byte, result interface{}) error { +func (t *Telegram) SendHTTPRequest(path string, data []byte, result interface{}) error { headers := make(map[string]string) headers["content-type"] = "application/json" - resp, err := common.SendHTTPRequest(http.MethodPost, path, headers, bytes.NewBuffer(json)) + resp, err := common.SendHTTPRequest(http.MethodPost, + path, + headers, + bytes.NewBuffer(data)) if err != nil { return err } - return common.JSONDecode([]byte(resp), result) + return json.Unmarshal([]byte(resp), result) } diff --git a/config/config_encryption.go b/config/config_encryption.go index 6ec7d3d4..51f9464d 100644 --- a/config/config_encryption.go +++ b/config/config_encryption.go @@ -5,6 +5,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "encoding/json" "errors" "fmt" "io" @@ -168,7 +169,7 @@ func DecryptConfigFile(configData, key []byte) ([]byte, error) { // ConfirmConfigJSON confirms JSON in file func ConfirmConfigJSON(file []byte, result interface{}) error { - return common.JSONDecode(file, &result) + return json.Unmarshal(file, &result) } // ConfirmSalt checks whether the encrypted data contains a salt diff --git a/currency/code.go b/currency/code.go index e1991530..eadae293 100644 --- a/currency/code.go +++ b/currency/code.go @@ -1,6 +1,7 @@ package currency import ( + "encoding/json" "errors" "fmt" "strings" @@ -11,7 +12,7 @@ import ( func (r Role) String() string { switch r { case Unset: - return UnsetRollString + return UnsetRoleString case Fiat: return FiatCurrencyString case Cryptocurrency: @@ -25,21 +26,21 @@ func (r Role) String() string { } } -// MarshalJSON conforms Roll to the marshaller interface +// MarshalJSON conforms Role to the marshaller interface func (r Role) MarshalJSON() ([]byte, error) { - return common.JSONEncode(r.String()) + return json.Marshal(r.String()) } -// UnmarshalJSON conforms Roll to the unmarshaller interface +// UnmarshalJSON conforms Role to the unmarshaller interface func (r *Role) UnmarshalJSON(d []byte) error { var incoming string - err := common.JSONDecode(d, &incoming) + err := json.Unmarshal(d, &incoming) if err != nil { return err } switch incoming { - case UnsetRollString: + case UnsetRoleString: *r = Unset case FiatCurrencyString: *r = Fiat @@ -400,7 +401,7 @@ func (c Code) Upper() Code { // UnmarshalJSON comforms type to the umarshaler interface func (c *Code) UnmarshalJSON(d []byte) error { var newcode string - err := common.JSONDecode(d, &newcode) + err := json.Unmarshal(d, &newcode) if err != nil { return err } @@ -411,9 +412,9 @@ func (c *Code) UnmarshalJSON(d []byte) error { // MarshalJSON conforms type to the marshaler interface func (c Code) MarshalJSON() ([]byte, error) { if c.Item == nil { - return common.JSONEncode("") + return json.Marshal("") } - return common.JSONEncode(c.String()) + return json.Marshal(c.String()) } // IsEmpty returns true if the code is empty diff --git a/currency/code_test.go b/currency/code_test.go index e27dc4c6..1568a7e4 100644 --- a/currency/code_test.go +++ b/currency/code_test.go @@ -1,15 +1,14 @@ package currency import ( + "encoding/json" "testing" - - "github.com/thrasher-corp/gocryptotrader/common" ) func TestRoleString(t *testing.T) { - if Unset.String() != UnsetRollString { + if Unset.String() != UnsetRoleString { t.Errorf("Role String() error expected %s but received %s", - UnsetRollString, + UnsetRoleString, Unset) } @@ -47,7 +46,7 @@ func TestRoleString(t *testing.T) { } func TestRoleMarshalJSON(t *testing.T) { - d, err := common.JSONEncode(Fiat) + d, err := json.Marshal(Fiat) if err != nil { t.Error("Role MarshalJSON() error", err) } @@ -79,23 +78,23 @@ func TestRoleUnmarshalJSON(t *testing.T) { RoleFive: Contract, } - e, err := common.JSONEncode(1337) + e, err := json.Marshal(1337) if err != nil { t.Fatal("Role UnmarshalJSON() error", err) } var incoming AllTheRoles - err = common.JSONDecode(e, &incoming) + err = json.Unmarshal(e, &incoming) if err == nil { t.Fatal("Role UnmarshalJSON() Expected error") } - e, err = common.JSONEncode(outgoing) + e, err = json.Marshal(outgoing) if err != nil { t.Fatal("Role UnmarshalJSON() error", err) } - err = common.JSONDecode(e, &incoming) + err = json.Unmarshal(e, &incoming) if err != nil { t.Fatal("Role UnmarshalJSON() error", err) } @@ -365,17 +364,17 @@ func TestCodeUpper(t *testing.T) { func TestCodeUnmarshalJSON(t *testing.T) { var unmarshalHere Code expected := "BRO" - encoded, err := common.JSONEncode(expected) + encoded, err := json.Marshal(expected) if err != nil { t.Fatal("Currency Code UnmarshalJSON error", err) } - err = common.JSONDecode(encoded, &unmarshalHere) + err = json.Unmarshal(encoded, &unmarshalHere) if err != nil { t.Fatal("Currency Code UnmarshalJSON error", err) } - err = common.JSONDecode(encoded, &unmarshalHere) + err = json.Unmarshal(encoded, &unmarshalHere) if err != nil { t.Fatal("Currency Code UnmarshalJSON error", err) } @@ -396,7 +395,7 @@ func TestCodeMarshalJSON(t *testing.T) { expectedJSON := `{"sweetCodes":"BRO"}` - encoded, err := common.JSONEncode(quickstruct) + encoded, err := json.Marshal(quickstruct) if err != nil { t.Fatal("Currency Code UnmarshalJSON error", err) } @@ -413,7 +412,7 @@ func TestCodeMarshalJSON(t *testing.T) { Codey: Code{}, // nil code } - encoded, err = common.JSONEncode(quickstruct) + encoded, err = json.Marshal(quickstruct) if err != nil { t.Fatal("Currency Code UnmarshalJSON error", err) } diff --git a/currency/code_types.go b/currency/code_types.go index cff8e6e3..d387a076 100644 --- a/currency/code_types.go +++ b/currency/code_types.go @@ -5,7 +5,7 @@ import ( "time" ) -// Bitmasks const for currency rolls +// Bitmasks const for currency roles const ( Unset Role = 0 Fiat Role = 1 << (iota - 1) @@ -13,14 +13,14 @@ const ( Token Contract - UnsetRollString = "roleUnset" + UnsetRoleString = "roleUnset" FiatCurrencyString = "fiatCurrency" CryptocurrencyString = "cryptocurrency" TokenString = "token" ContractString = "contract" ) -// Role defines a bitmask for the full currency rolls either; fiat, +// Role defines a bitmask for the full currency roles either; fiat, // cryptocurrency, token, or contract type Role uint8 diff --git a/currency/currencies.go b/currency/currencies.go index 7c3b5338..2647fbc1 100644 --- a/currency/currencies.go +++ b/currency/currencies.go @@ -1,9 +1,8 @@ package currency import ( + "encoding/json" "strings" - - "github.com/thrasher-corp/gocryptotrader/common" ) // NewCurrenciesFromStringArray returns a Currencies object from strings @@ -48,7 +47,7 @@ func (c Currencies) Join() string { // UnmarshalJSON comforms type to the umarshaler interface func (c *Currencies) UnmarshalJSON(d []byte) error { var configCurrencies string - err := common.JSONDecode(d, &configCurrencies) + err := json.Unmarshal(d, &configCurrencies) if err != nil { return err } @@ -64,7 +63,7 @@ func (c *Currencies) UnmarshalJSON(d []byte) error { // MarshalJSON conforms type to the marshaler interface func (c Currencies) MarshalJSON() ([]byte, error) { - return common.JSONEncode(c.Join()) + return json.Marshal(c.Join()) } // Match returns if the full list equals the supplied list diff --git a/currency/currencies_test.go b/currency/currencies_test.go index 6805ebf1..d97df832 100644 --- a/currency/currencies_test.go +++ b/currency/currencies_test.go @@ -1,25 +1,24 @@ package currency import ( + "encoding/json" "testing" - - "github.com/thrasher-corp/gocryptotrader/common" ) func TestCurrenciesUnmarshalJSON(t *testing.T) { var unmarshalHere Currencies expected := "btc,usd,ltc,bro,things" - encoded, err := common.JSONEncode(expected) + encoded, err := json.Marshal(expected) if err != nil { t.Fatal("Currencies UnmarshalJSON() error", err) } - err = common.JSONDecode(encoded, &unmarshalHere) + err = json.Unmarshal(encoded, &unmarshalHere) if err != nil { t.Fatal("Currencies UnmarshalJSON() error", err) } - err = common.JSONDecode(encoded, &unmarshalHere) + err = json.Unmarshal(encoded, &unmarshalHere) if err != nil { t.Fatal("Currencies UnmarshalJSON() error", err) } @@ -37,7 +36,7 @@ func TestCurrenciesMarshalJSON(t *testing.T) { C: NewCurrenciesFromStringArray([]string{"btc", "usd", "ltc", "bro", "things"}), } - encoded, err := common.JSONEncode(quickStruct) + encoded, err := json.Marshal(quickStruct) if err != nil { t.Fatal("Currencies MarshalJSON() error", err) } diff --git a/currency/pair.go b/currency/pair.go index e3c6fcdd..cd699ebb 100644 --- a/currency/pair.go +++ b/currency/pair.go @@ -1,10 +1,9 @@ package currency import ( + "encoding/json" "fmt" "strings" - - "github.com/thrasher-corp/gocryptotrader/common" ) // NewPairDelimiter splits the desired currency string at delimeter, the returns @@ -113,7 +112,7 @@ func (p Pair) Upper() Pair { // UnmarshalJSON comforms type to the umarshaler interface func (p *Pair) UnmarshalJSON(d []byte) error { var pair string - err := common.JSONDecode(d, &pair) + err := json.Unmarshal(d, &pair) if err != nil { return err } @@ -124,7 +123,7 @@ func (p *Pair) UnmarshalJSON(d []byte) error { // MarshalJSON conforms type to the marshaler interface func (p Pair) MarshalJSON() ([]byte, error) { - return common.JSONEncode(p.String()) + return json.Marshal(p.String()) } // Format changes the currency based on user preferences overriding the default diff --git a/currency/pair_test.go b/currency/pair_test.go index 87b7c5ff..837d52e2 100644 --- a/currency/pair_test.go +++ b/currency/pair_test.go @@ -1,9 +1,8 @@ package currency import ( + "encoding/json" "testing" - - "github.com/thrasher-corp/gocryptotrader/common" ) const ( @@ -37,17 +36,17 @@ func TestPairUnmarshalJSON(t *testing.T) { var unmarshalHere Pair configPair := NewPairDelimiter("btc_usd", "_") - encoded, err := common.JSONEncode(configPair) + encoded, err := json.Marshal(configPair) if err != nil { t.Fatal("Pair UnmarshalJSON() error", err) } - err = common.JSONDecode(encoded, &unmarshalHere) + err = json.Unmarshal(encoded, &unmarshalHere) if err != nil { t.Fatal("Pair UnmarshalJSON() error", err) } - err = common.JSONDecode(encoded, &unmarshalHere) + err = json.Unmarshal(encoded, &unmarshalHere) if err != nil { t.Fatal("Pair UnmarshalJSON() error", err) } @@ -65,7 +64,7 @@ func TestPairMarshalJSON(t *testing.T) { Pair{Base: BTC, Quote: USD, Delimiter: "-"}, } - encoded, err := common.JSONEncode(quickstruct) + encoded, err := json.Marshal(quickstruct) if err != nil { t.Fatal("Pair MarshalJSON() error", err) } diff --git a/currency/pairs.go b/currency/pairs.go index f383f99d..66dd4617 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -1,10 +1,10 @@ package currency import ( + "encoding/json" "math/rand" "strings" - "github.com/thrasher-corp/gocryptotrader/common" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -68,7 +68,7 @@ func (p Pairs) Format(delimiter, index string, uppercase bool) Pairs { // UnmarshalJSON comforms type to the umarshaler interface func (p *Pairs) UnmarshalJSON(d []byte) error { var pairs string - err := common.JSONDecode(d, &pairs) + err := json.Unmarshal(d, &pairs) if err != nil { return err } @@ -89,7 +89,7 @@ func (p *Pairs) UnmarshalJSON(d []byte) error { // MarshalJSON conforms type to the marshaler interface func (p Pairs) MarshalJSON() ([]byte, error) { - return common.JSONEncode(p.Join()) + return json.Marshal(p.Join()) } // Upper returns an upper formatted pair list diff --git a/currency/pairs_test.go b/currency/pairs_test.go index d58d57d1..1af69a8f 100644 --- a/currency/pairs_test.go +++ b/currency/pairs_test.go @@ -1,9 +1,8 @@ package currency import ( + "encoding/json" "testing" - - "github.com/thrasher-corp/gocryptotrader/common" ) func TestPairsUpper(t *testing.T) { @@ -69,33 +68,33 @@ func TestPairsFormat(t *testing.T) { func TestPairsUnmarshalJSON(t *testing.T) { var unmarshalHere Pairs configPairs := "" - encoded, err := common.JSONEncode(configPairs) + encoded, err := json.Marshal(configPairs) if err != nil { t.Fatal("Pairs UnmarshalJSON() error", err) } - err = common.JSONDecode([]byte{1, 3, 3, 7}, &unmarshalHere) + err = json.Unmarshal([]byte{1, 3, 3, 7}, &unmarshalHere) if err == nil { t.Fatal("error cannot be nil") } - err = common.JSONDecode(encoded, &unmarshalHere) + err = json.Unmarshal(encoded, &unmarshalHere) if err != nil { t.Fatal("Pairs UnmarshalJSON() error", err) } configPairs = "btc_usd,btc_aud,btc_ltc" - encoded, err = common.JSONEncode(configPairs) + encoded, err = json.Marshal(configPairs) if err != nil { t.Fatal("Pairs UnmarshalJSON() error", err) } - err = common.JSONDecode(encoded, &unmarshalHere) + err = json.Unmarshal(encoded, &unmarshalHere) if err != nil { t.Fatal("Pairs UnmarshalJSON() error", err) } - err = common.JSONDecode(encoded, &unmarshalHere) + err = json.Unmarshal(encoded, &unmarshalHere) if err != nil { t.Fatal("Pairs UnmarshalJSON() error", err) } @@ -113,7 +112,7 @@ func TestPairsMarshalJSON(t *testing.T) { Pairs: NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}), } - encoded, err := common.JSONEncode(quickstruct) + encoded, err := json.Marshal(quickstruct) if err != nil { t.Fatal("Pairs MarshalJSON() error", err) } diff --git a/currency/storage.go b/currency/storage.go index e4148647..b0452f0e 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -8,7 +8,6 @@ import ( "path/filepath" "time" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/currency/coinmarketcap" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider" @@ -264,7 +263,7 @@ func (s *Storage) SeedCurrencyAnalysisData() error { } var fromFile File - err = common.JSONDecode(b, &fromFile) + err = json.Unmarshal(b, &fromFile) if err != nil { return err } @@ -364,7 +363,7 @@ func (s *Storage) LoadFileCurrencyData(f *File) error { return nil } -// UpdateCurrencies updates currency roll and information using coin market cap +// UpdateCurrencies updates currency role and information using coin market cap func (s *Storage) UpdateCurrencies() error { m, err := s.currencyAnalysis.GetCryptocurrencyIDMap() if err != nil { diff --git a/dispatch/mux.go b/dispatch/mux.go index 1c741e67..38037403 100644 --- a/dispatch/mux.go +++ b/dispatch/mux.go @@ -62,12 +62,11 @@ func (m *Mux) Publish(ids []uuid.UUID, data interface{}) error { return nil } -// GetID gets a lovely new ID +// GetID a new unique ID to track routing information in the dispatch system func (m *Mux) GetID() (uuid.UUID, error) { if m == nil { return uuid.UUID{}, errors.New("mux is nil") } - return m.d.getNewID() } diff --git a/engine/connection.go b/engine/connection.go index beb97324..01f46db6 100644 --- a/engine/connection.go +++ b/engine/connection.go @@ -15,10 +15,12 @@ type connectionManager struct { conn *connchecker.Checker } +// Started returns if the connection manager has started func (c *connectionManager) Started() bool { return atomic.LoadInt32(&c.started) == 1 } +// Start starts an instance of the connection manager func (c *connectionManager) Start() error { if atomic.AddInt32(&c.started, 1) != 1 { return errors.New("connection manager already started") @@ -38,6 +40,7 @@ func (c *connectionManager) Start() error { return nil } +// Stop stops the connection manager func (c *connectionManager) Stop() error { if atomic.LoadInt32(&c.started) == 0 { return errors.New("connection manager not started") @@ -55,6 +58,7 @@ func (c *connectionManager) Stop() error { return nil } +// IsOnline returns if the connection manager is online func (c *connectionManager) IsOnline() bool { if c.conn == nil { log.Warnln(log.ConnectionMgr, "Connection manager: IsOnline called but conn is nil") diff --git a/engine/orders.go b/engine/orders.go index e23671ff..303df534 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -6,6 +6,7 @@ import ( "sync/atomic" "time" + "github.com/gofrs/uuid" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/communications/base" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -207,7 +208,7 @@ func (o *orderManager) Submit(exchName string, newOrder *order.Submit) (*orderSu return nil, errors.New("unable to get exchange by name") } - id, err := common.GetV4UUID() + id, err := uuid.NewV4() if err != nil { log.Warnf(log.OrderMgr, "Order manager: Unable to generate UUID. Err: %s\n", diff --git a/engine/websocket.go b/engine/websocket.go index 21613fd4..2f3c1431 100644 --- a/engine/websocket.go +++ b/engine/websocket.go @@ -1,12 +1,12 @@ package engine import ( + "encoding/json" "errors" "net/http" "strings" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" @@ -79,7 +79,7 @@ func (h *WebsocketHub) run() { // SendWebsocketMessage sends a websocket event to the client func (c *WebsocketClient) SendWebsocketMessage(evt interface{}) error { - data, err := common.JSONEncode(evt) + data, err := json.Marshal(evt) if err != nil { log.Errorf(log.WebsocketMgr, "websocket: failed to send message: %s\n", err) return err @@ -106,7 +106,7 @@ func (c *WebsocketClient) read() { if msgType == websocket.TextMessage { var evt WebsocketEvent - err := common.JSONDecode(message, &evt) + err := json.Unmarshal(message, &evt) if err != nil { log.Errorf(log.WebsocketMgr, "websocket: failed to decode JSON sent from client %s\n", err) continue @@ -117,7 +117,7 @@ func (c *WebsocketClient) read() { continue } - dataJSON, err := common.JSONEncode(evt.Data) + dataJSON, err := json.Marshal(evt.Data) if err != nil { log.Errorln(log.WebsocketMgr, "websocket: client sent data we couldn't JSON decode") break @@ -197,7 +197,7 @@ func BroadcastWebsocketMessage(evt WebsocketEvent) error { return errors.New("websocket service not started") } - data, err := common.JSONEncode(evt) + data, err := json.Marshal(evt) if err != nil { return err } @@ -257,7 +257,7 @@ func wsAuth(client *WebsocketClient, data interface{}) error { } var auth WebsocketAuth - err := common.JSONDecode(data.([]byte), &auth) + err := json.Unmarshal(data.([]byte), &auth) if err != nil { wsResp.Error = err.Error() client.SendWebsocketMessage(wsResp) @@ -303,7 +303,7 @@ func wsSaveConfig(client *WebsocketClient, data interface{}) error { Event: "SaveConfig", } var cfg config.Config - err := common.JSONDecode(data.([]byte), &cfg) + err := json.Unmarshal(data.([]byte), &cfg) if err != nil { wsResp.Error = err.Error() client.SendWebsocketMessage(wsResp) @@ -344,7 +344,7 @@ func wsGetTicker(client *WebsocketClient, data interface{}) error { Event: "GetTicker", } var tickerReq WebsocketOrderbookTickerRequest - err := common.JSONDecode(data.([]byte), &tickerReq) + err := json.Unmarshal(data.([]byte), &tickerReq) if err != nil { wsResp.Error = err.Error() client.SendWebsocketMessage(wsResp) @@ -376,7 +376,7 @@ func wsGetOrderbook(client *WebsocketClient, data interface{}) error { Event: "GetOrderbook", } var orderbookReq WebsocketOrderbookTickerRequest - err := common.JSONDecode(data.([]byte), &orderbookReq) + err := json.Unmarshal(data.([]byte), &orderbookReq) if err != nil { wsResp.Error = err.Error() client.SendWebsocketMessage(wsResp) diff --git a/exchanges/alphapoint/alphapoint.go b/exchanges/alphapoint/alphapoint.go index f1ea56db..33400dd2 100644 --- a/exchanges/alphapoint/alphapoint.go +++ b/exchanges/alphapoint/alphapoint.go @@ -2,6 +2,7 @@ package alphapoint import ( "bytes" + "encoding/json" "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "strings" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -513,7 +513,7 @@ func (a *Alphapoint) SendHTTPRequest(method, path string, data map[string]interf headers["Content-Type"] = "application/json" path = fmt.Sprintf("%s/ajax/v%s/%s", a.API.Endpoints.URL, alphapointAPIVersion, path) - PayloadJSON, err := common.JSONEncode(data) + PayloadJSON, err := json.Marshal(data) if err != nil { return errors.New("unable to JSON request") } @@ -548,7 +548,7 @@ func (a *Alphapoint) SendAuthenticatedHTTPRequest(method, path string, data map[ data["apiSig"] = strings.ToUpper(crypto.HexEncodeToString(hmac)) path = fmt.Sprintf("%s/ajax/v%s/%s", a.API.Endpoints.URL, alphapointAPIVersion, path) - PayloadJSON, err := common.JSONEncode(data) + PayloadJSON, err := json.Marshal(data) if err != nil { return errors.New("unable to JSON request") } diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index eed2d253..964cd0d8 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -1,6 +1,7 @@ package alphapoint import ( + "encoding/json" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -63,7 +64,7 @@ func TestGetTicker(t *testing.T) { string(`{"high":253.101,"last":249.76,"bid":248.8901,"volume":5.813354,"low":231.21,"ask":248.9012,"Total24HrQtyTraded":52.654968,"Total24HrProduct2Traded":569.05762,"Total24HrNumTrades":4,"sellOrderCount":7,"buyOrderCount":11,"numOfCreateOrders":0,"isAccepted":true}`), ) - err = common.JSONDecode(mockResp, &ticker) + err = json.Unmarshal(mockResp, &ticker) if err != nil { t.Fatal("Alphapoint GetTicker unmarshalling error: ", err) } @@ -100,7 +101,7 @@ func TestGetTrades(t *testing.T) { string(`{"isAccepted":true,"dateTimeUtc":635507981548085938,"ins":"BTCUSD","startIndex":0,"count":10,"trades":[{"tid":0,"px":231.8379,"qty":4.913,"unixtime":1399951989,"utcticks":635355487898355234,"incomingOrderSide":0,"incomingServerOrderId":2598,"bookServerOrderId":2588},{"tid":1,"px":7895.1487,"qty":0.25,"unixtime":1403143708,"utcticks":635387405087297421,"incomingOrderSide":0,"incomingServerOrderId":284241,"bookServerOrderId":284235},{"tid":2,"px":7935.058,"qty":0.25,"unixtime":1403195348,"utcticks":635387921488684140,"incomingOrderSide":0,"incomingServerOrderId":575845,"bookServerOrderId":574078},{"tid":3,"px":7935.0448,"qty":0.25,"unixtime":1403195378,"utcticks":635387921780090390,"incomingOrderSide":0,"incomingServerOrderId":576028,"bookServerOrderId":575946},{"tid":4,"px":7933.9566,"qty":0.1168,"unixtime":1403195510,"utcticks":635387923108371640,"incomingOrderSide":0,"incomingServerOrderId":576974,"bookServerOrderId":576947},{"tid":5,"px":7961.0856,"qty":0.25,"unixtime":1403202307,"utcticks":635387991073850156,"incomingOrderSide":0,"incomingServerOrderId":600547,"bookServerOrderId":600338},{"tid":6,"px":7961.1388,"qty":0.011,"unixtime":1403202307,"utcticks":635387991073850156,"incomingOrderSide":0,"incomingServerOrderId":600547,"bookServerOrderId":600418},{"tid":7,"px":7961.2451,"qty":0.02,"unixtime":1403202307,"utcticks":635387991073850156,"incomingOrderSide":0,"incomingServerOrderId":600547,"bookServerOrderId":600428},{"tid":8,"px":7947.1437,"qty":0.09,"unixtime":1403202749,"utcticks":635387995498225156,"incomingOrderSide":0,"incomingServerOrderId":602183,"bookServerOrderId":601745},{"tid":9,"px":7818.5073,"qty":0.25,"unixtime":1403219720,"utcticks":635388165206506406,"incomingOrderSide":0,"incomingServerOrderId":661909,"bookServerOrderId":661620}]}`), ) - err = common.JSONDecode(mockResp, &trades) + err = json.Unmarshal(mockResp, &trades) if err != nil { t.Fatal("GetTrades unmarshalling error: ", err) } @@ -140,7 +141,7 @@ func TestGetTradesByDate(t *testing.T) { string(`{"isAccepted":true,"dateTimeUtc":635504540880633671,"ins":"BTCUSD","startDate":1414799400,"endDate":1414800000,"trades":[{"tid":11505,"px":334.669,"qty":0.1211,"unixtime":1414799403,"utcticks":635503962032459843,"incomingOrderSide":1,"incomingServerOrderId":5185651,"bookServerOrderId":5162440},{"tid":11506,"px":334.669,"qty":0.1211,"unixtime":1414799405,"utcticks":635503962058446171,"incomingOrderSide":1,"incomingServerOrderId":5186245,"bookServerOrderId":5162440},{"tid":11507,"px":336.498,"qty":0.011,"unixtime":1414799407,"utcticks":635503962072967656,"incomingOrderSide":0,"incomingServerOrderId":5186530,"bookServerOrderId":5178944},{"tid":11508,"px":335.948,"qty":0.011,"unixtime":1414799410,"utcticks":635503962108055546,"incomingOrderSide":0,"incomingServerOrderId":5187260,"bookServerOrderId":5186531}]}`), ) - err = common.JSONDecode(mockResp, &trades) + err = json.Unmarshal(mockResp, &trades) if err != nil { t.Fatal("GetTradesByDate unmarshalling error: ", err) } @@ -188,7 +189,7 @@ func TestGetOrderbook(t *testing.T) { string(`{"bids":[{"qty":725,"px":66},{"qty":1289,"px":65},{"qty":1266,"px":64}],"asks":[{"qty":1,"px":67},{"qty":1,"px":69},{"qty":2,"px":70}],"isAccepted":true}`), ) - err = common.JSONDecode(mockResp, &orderBook) + err = json.Unmarshal(mockResp, &orderBook) if err != nil { t.Fatal("TestGetOrderbook unmarshalling error: ", err) } @@ -228,7 +229,7 @@ func TestGetProductPairs(t *testing.T) { string(`{"productPairs":[{"name":"LTCUSD","productPairCode":100,"product1Label":"LTC","product1DecimalPlaces":8,"product2Label":"USD","product2DecimalPlaces":6}, {"name":"BTCUSD","productPairCode":99,"product1Label":"BTC","product1DecimalPlaces":8,"product2Label":"USD","product2DecimalPlaces":6}],"isAccepted":true}`), ) - err = common.JSONDecode(mockResp, &products) + err = json.Unmarshal(mockResp, &products) if err != nil { t.Fatal("TestGetProductPairs unmarshalling error: ", err) } @@ -268,7 +269,7 @@ func TestGetProducts(t *testing.T) { string(`{"products": [{"name": "USD","isDigital": false,"productCode": 0,"decimalPlaces": 4,"fullName": "US Dollar"},{"name": "BTC","isDigital": true,"productCode": 1,"decimalPlaces": 6,"fullName": "Bitcoin"}],"isAccepted": true}`), ) - err = common.JSONDecode(mockResp, &products) + err = json.Unmarshal(mockResp, &products) if err != nil { t.Fatal("TestGetProducts unmarshalling error: ", err) } diff --git a/exchanges/alphapoint/alphapoint_websocket.go b/exchanges/alphapoint/alphapoint_websocket.go index 05f389b2..ebb1329d 100644 --- a/exchanges/alphapoint/alphapoint_websocket.go +++ b/exchanges/alphapoint/alphapoint_websocket.go @@ -1,10 +1,10 @@ package alphapoint import ( + "encoding/json" "net/http" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -49,7 +49,7 @@ func (a *Alphapoint) WebsocketClient() { } msgType := MsgType{} - err := common.JSONDecode(resp, &msgType) + err := json.Unmarshal(resp, &msgType) if err != nil { log.Error(log.ExchangeSys, err) continue @@ -57,7 +57,7 @@ func (a *Alphapoint) WebsocketClient() { if msgType.MessageType == "Ticker" { ticker := WebsocketTicker{} - err = common.JSONDecode(resp, &ticker) + err = json.Unmarshal(resp, &ticker) if err != nil { log.Error(log.ExchangeSys, err) continue diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 666aaddf..09bf290a 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -2,13 +2,13 @@ package anx import ( "bytes" + "encoding/json" "errors" "fmt" "net/http" "strconv" "time" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -357,7 +357,7 @@ func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interf req[key] = value } - PayloadJSON, err := common.JSONEncode(req) + PayloadJSON, err := json.Marshal(req) if err != nil { return errors.New("unable to JSON request") } diff --git a/exchanges/asset/asset.go b/exchanges/asset/asset.go index 77137482..43947812 100644 --- a/exchanges/asset/asset.go +++ b/exchanges/asset/asset.go @@ -23,21 +23,21 @@ const ( DownsideProfitContract = Item("downsideprofitcontract") ) +var supported = Items{ + Spot, + Margin, + Index, + Binary, + PerpetualContract, + PerpetualSwap, + Futures, + UpsideProfitContract, + DownsideProfitContract, +} + // Supported returns a list of supported asset types func Supported() Items { - var a Items - a = append(a, - Spot, - Margin, - Index, - Binary, - PerpetualContract, - PerpetualSwap, - Futures, - UpsideProfitContract, - DownsideProfitContract, - ) - return a + return supported } // returns an Item to string diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 2dbbdf94..17e609f1 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -526,13 +526,13 @@ func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, re return err } - if err := common.JSONDecode(interim, &errCap); err == nil { + if err := json.Unmarshal(interim, &errCap); err == nil { if !errCap.Success && errCap.Message != "" { return errors.New(errCap.Message) } } - return common.JSONDecode(interim, result) + return json.Unmarshal(interim, result) } // CheckLimit checks value against a variable list diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index f555d48d..ab437935 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -1,6 +1,7 @@ package binance import ( + "encoding/json" "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -92,7 +92,7 @@ func (b *Binance) WsHandleData() { } b.Websocket.TrafficAlert <- struct{}{} var multiStreamData MultiStreamData - err = common.JSONDecode(read.Raw, &multiStreamData) + err = json.Unmarshal(read.Raw, &multiStreamData) if err != nil { b.Websocket.DataHandler <- fmt.Errorf("%v - Could not load multi stream data: %s", b.Name, @@ -103,7 +103,7 @@ func (b *Binance) WsHandleData() { switch streamType[1] { case "trade": trade := TradeStream{} - err := common.JSONDecode(multiStreamData.Data, &trade) + err := json.Unmarshal(multiStreamData.Data, &trade) if err != nil { b.Websocket.DataHandler <- fmt.Errorf("%v - Could not unmarshal trade data: %s", b.Name, @@ -140,7 +140,7 @@ func (b *Binance) WsHandleData() { continue case "ticker": t := TickerStream{} - err := common.JSONDecode(multiStreamData.Data, &t) + err := json.Unmarshal(multiStreamData.Data, &t) if err != nil { b.Websocket.DataHandler <- fmt.Errorf("%v - Could not convert to a TickerStream structure %s", b.Name, @@ -168,7 +168,7 @@ func (b *Binance) WsHandleData() { continue case "kline": kline := KlineStream{} - err := common.JSONDecode(multiStreamData.Data, &kline) + err := json.Unmarshal(multiStreamData.Data, &kline) if err != nil { b.Websocket.DataHandler <- fmt.Errorf("%v - Could not convert to a KlineStream structure %s", b.Name, @@ -194,7 +194,7 @@ func (b *Binance) WsHandleData() { continue case "depth": depth := WebsocketDepthStream{} - err := common.JSONDecode(multiStreamData.Data, &depth) + err := json.Unmarshal(multiStreamData.Data, &depth) if err != nil { b.Websocket.DataHandler <- fmt.Errorf("%v - Could not convert to depthStream structure %s", b.Name, diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 80113385..5e185410 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -1,6 +1,7 @@ package bitfinex import ( + "encoding/json" "errors" "fmt" "net/http" @@ -953,7 +954,7 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequest(method, path string, params map[ req[key] = value } - PayloadJSON, err := common.JSONEncode(req) + PayloadJSON, err := json.Marshal(req) if err != nil { return errors.New("sendAuthenticatedAPIRequest: unable to JSON request") } diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index be6ff69e..3fb9a7f9 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -1,6 +1,7 @@ package bitfinex import ( + "encoding/json" "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -139,7 +139,7 @@ func (b *Bitfinex) WsConnect() error { } b.Websocket.TrafficAlert <- struct{}{} var hs WebsocketHandshake - err = common.JSONDecode(resp.Raw, &hs) + err = json.Unmarshal(resp.Raw, &hs) if err != nil { return err } @@ -184,7 +184,7 @@ func (b *Bitfinex) WsDataHandler() { if stream.Type == websocket.TextMessage { var result interface{} - err = common.JSONDecode(stream.Raw, &result) + err = json.Unmarshal(stream.Raw, &result) if err != nil { b.Websocket.DataHandler <- err return diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go index 77589394..3cdd892a 100644 --- a/exchanges/bithumb/bithumb.go +++ b/exchanges/bithumb/bithumb.go @@ -11,7 +11,6 @@ import ( "strconv" "strings" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -110,7 +109,7 @@ func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) { continue } var newTicker Ticker - err := common.JSONDecode(v, &newTicker) + err := json.Unmarshal(v, &newTicker) if err != nil { return nil, err } @@ -518,7 +517,7 @@ func (b *Bithumb) SendAuthenticatedHTTPRequest(path string, params url.Values, r return err } - err = common.JSONDecode(intermediary, &errCapture) + err = json.Unmarshal(intermediary, &errCapture) if err == nil { if errCapture.Status != "" && errCapture.Status != noError { return fmt.Errorf("sendAuthenticatedAPIRequest error code: %s message:%s", @@ -527,7 +526,7 @@ func (b *Bithumb) SendAuthenticatedHTTPRequest(path string, params url.Values, r } } - return common.JSONDecode(intermediary, result) + return json.Unmarshal(intermediary, result) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index 33f17238..a073a923 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -9,7 +9,6 @@ import ( "strings" "time" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -829,7 +828,7 @@ func (b *Bitmex) SendAuthenticatedHTTPRequest(verb, path string, params Paramete if err != nil { return err } - data, err := common.JSONEncode(params) + data, err := json.Marshal(params) if err != nil { return err } @@ -870,14 +869,14 @@ func (b *Bitmex) CaptureError(resp, reType interface{}) error { return err } - err = common.JSONDecode(marshalled, &Error) + err = json.Unmarshal(marshalled, &Error) if err == nil { return fmt.Errorf("bitmex error %s: %s", Error.Error.Name, Error.Error.Message) } - return common.JSONDecode(marshalled, reType) + return json.Unmarshal(marshalled, reType) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index a3131648..b0338ad9 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -1,6 +1,7 @@ package bitmex import ( + "encoding/json" "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -85,7 +85,7 @@ func (b *Bitmex) WsConnect() error { } b.Websocket.TrafficAlert <- struct{}{} var welcomeResp WebsocketWelcome - err = common.JSONDecode(p.Raw, &welcomeResp) + err = json.Unmarshal(p.Raw, &welcomeResp) if err != nil { return err } @@ -143,7 +143,7 @@ func (b *Bitmex) wsHandleIncomingData() { } quickCapture := make(map[string]interface{}) - err = common.JSONDecode(resp.Raw, &quickCapture) + err = json.Unmarshal(resp.Raw, &quickCapture) if err != nil { b.Websocket.DataHandler <- err continue @@ -151,7 +151,7 @@ func (b *Bitmex) wsHandleIncomingData() { var respError WebsocketErrorResponse if _, ok := quickCapture["status"]; ok { - err = common.JSONDecode(resp.Raw, &respError) + err = json.Unmarshal(resp.Raw, &respError) if err != nil { b.Websocket.DataHandler <- err continue @@ -162,7 +162,7 @@ func (b *Bitmex) wsHandleIncomingData() { if _, ok := quickCapture["success"]; ok { var decodedResp WebsocketSubscribeResp - err := common.JSONDecode(resp.Raw, &decodedResp) + err := json.Unmarshal(resp.Raw, &decodedResp) if err != nil { b.Websocket.DataHandler <- err continue @@ -189,7 +189,7 @@ func (b *Bitmex) wsHandleIncomingData() { b.Name, decodedResp.Subscribe) } else if _, ok := quickCapture["table"]; ok { var decodedResp WebsocketMainResponse - err := common.JSONDecode(resp.Raw, &decodedResp) + err := json.Unmarshal(resp.Raw, &decodedResp) if err != nil { b.Websocket.DataHandler <- err continue @@ -198,7 +198,7 @@ func (b *Bitmex) wsHandleIncomingData() { switch decodedResp.Table { case bitmexWSOrderbookL2: var orderbooks OrderBookData - err = common.JSONDecode(resp.Raw, &orderbooks) + err = json.Unmarshal(resp.Raw, &orderbooks) if err != nil { b.Websocket.DataHandler <- err continue @@ -223,7 +223,7 @@ func (b *Bitmex) wsHandleIncomingData() { case bitmexWSTrade: var trades TradeData - err = common.JSONDecode(resp.Raw, &trades) + err = json.Unmarshal(resp.Raw, &trades) if err != nil { b.Websocket.DataHandler <- err continue @@ -254,7 +254,7 @@ func (b *Bitmex) wsHandleIncomingData() { case bitmexWSAnnouncement: var announcement AnnouncementData - err = common.JSONDecode(resp.Raw, &announcement) + err = json.Unmarshal(resp.Raw, &announcement) if err != nil { b.Websocket.DataHandler <- err continue @@ -267,7 +267,7 @@ func (b *Bitmex) wsHandleIncomingData() { b.Websocket.DataHandler <- announcement.Data case bitmexWSAffiliate: var response WsAffiliateResponse - err = common.JSONDecode(resp.Raw, &response) + err = json.Unmarshal(resp.Raw, &response) if err != nil { b.Websocket.DataHandler <- err continue @@ -275,7 +275,7 @@ func (b *Bitmex) wsHandleIncomingData() { b.Websocket.DataHandler <- response case bitmexWSExecution: var response WsExecutionResponse - err = common.JSONDecode(resp.Raw, &response) + err = json.Unmarshal(resp.Raw, &response) if err != nil { b.Websocket.DataHandler <- err continue @@ -283,7 +283,7 @@ func (b *Bitmex) wsHandleIncomingData() { b.Websocket.DataHandler <- response case bitmexWSOrder: var response WsOrderResponse - err = common.JSONDecode(resp.Raw, &response) + err = json.Unmarshal(resp.Raw, &response) if err != nil { b.Websocket.DataHandler <- err continue @@ -291,7 +291,7 @@ func (b *Bitmex) wsHandleIncomingData() { b.Websocket.DataHandler <- response case bitmexWSMargin: var response WsMarginResponse - err = common.JSONDecode(resp.Raw, &response) + err = json.Unmarshal(resp.Raw, &response) if err != nil { b.Websocket.DataHandler <- err continue @@ -299,7 +299,7 @@ func (b *Bitmex) wsHandleIncomingData() { b.Websocket.DataHandler <- response case bitmexWSPosition: var response WsPositionResponse - err = common.JSONDecode(resp.Raw, &response) + err = json.Unmarshal(resp.Raw, &response) if err != nil { b.Websocket.DataHandler <- err continue @@ -307,7 +307,7 @@ func (b *Bitmex) wsHandleIncomingData() { b.Websocket.DataHandler <- response case bitmexWSPrivateNotifications: var response WsPrivateNotificationsResponse - err = common.JSONDecode(resp.Raw, &response) + err = json.Unmarshal(resp.Raw, &response) if err != nil { b.Websocket.DataHandler <- err continue @@ -315,7 +315,7 @@ func (b *Bitmex) wsHandleIncomingData() { b.Websocket.DataHandler <- response case bitmexWSTransact: var response WsTransactResponse - err = common.JSONDecode(resp.Raw, &response) + err = json.Unmarshal(resp.Raw, &response) if err != nil { b.Websocket.DataHandler <- err continue @@ -323,7 +323,7 @@ func (b *Bitmex) wsHandleIncomingData() { b.Websocket.DataHandler <- response case bitmexWSWallet: var response WsWalletResponse - err = common.JSONDecode(resp.Raw, &response) + err = json.Unmarshal(resp.Raw, &response) if err != nil { b.Websocket.DataHandler <- err continue diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 202944d6..61b7b5fc 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -676,7 +676,7 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url return err } - if err := common.JSONDecode(interim, &errCap); err == nil { + if err := json.Unmarshal(interim, &errCap); err == nil { if errCap.Error != "" { return errors.New(errCap.Error) } @@ -697,7 +697,7 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url } } - return common.JSONDecode(interim, result) + return json.Unmarshal(interim, result) } func parseTime(dateTime string) (time.Time, error) { diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 3cee9712..8bc00249 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -1,6 +1,7 @@ package bitstamp import ( + "encoding/json" "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -67,7 +67,7 @@ func (b *Bitstamp) WsHandleData() { } b.Websocket.TrafficAlert <- struct{}{} wsResponse := websocketResponse{} - err = common.JSONDecode(resp.Raw, &wsResponse) + err = json.Unmarshal(resp.Raw, &wsResponse) if err != nil { b.Websocket.DataHandler <- err continue @@ -82,7 +82,7 @@ func (b *Bitstamp) WsHandleData() { case "data": wsOrderBookTemp := websocketOrderBookResponse{} - err := common.JSONDecode(resp.Raw, &wsOrderBookTemp) + err := json.Unmarshal(resp.Raw, &wsOrderBookTemp) if err != nil { b.Websocket.DataHandler <- err continue @@ -100,7 +100,7 @@ func (b *Bitstamp) WsHandleData() { case "trade": wsTradeTemp := websocketTradeResponse{} - err := common.JSONDecode(resp.Raw, &wsTradeTemp) + err := json.Unmarshal(resp.Raw, &wsTradeTemp) if err != nil { b.Websocket.DataHandler <- err continue diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 88679ad6..11882328 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -2,6 +2,7 @@ package btcmarkets import ( "bytes" + "encoding/json" "errors" "fmt" "net/http" @@ -391,7 +392,7 @@ func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data, result payload := []byte("") if data != nil { - payload, err = common.JSONEncode(data) + payload, err = json.Marshal(data) if err != nil { return err } diff --git a/exchanges/btcmarkets/btcmarkets_websocket.go b/exchanges/btcmarkets/btcmarkets_websocket.go index 285eef43..7b5d3917 100644 --- a/exchanges/btcmarkets/btcmarkets_websocket.go +++ b/exchanges/btcmarkets/btcmarkets_websocket.go @@ -1,13 +1,13 @@ package btcmarkets import ( + "encoding/json" "errors" "fmt" "net/http" "strconv" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -58,7 +58,7 @@ func (b *BTCMarkets) WsHandleData() { } b.Websocket.TrafficAlert <- struct{}{} var wsResponse WsMessageType - err = common.JSONDecode(resp.Raw, &wsResponse) + err = json.Unmarshal(resp.Raw, &wsResponse) if err != nil { b.Websocket.DataHandler <- err continue @@ -70,7 +70,7 @@ func (b *BTCMarkets) WsHandleData() { } case "orderbook": var ob WsOrderbook - err := common.JSONDecode(resp.Raw, &ob) + err := json.Unmarshal(resp.Raw, &ob) if err != nil { b.Websocket.DataHandler <- err continue @@ -131,7 +131,7 @@ func (b *BTCMarkets) WsHandleData() { } case "trade": var trade WsTrade - err := common.JSONDecode(resp.Raw, &trade) + err := json.Unmarshal(resp.Raw, &trade) if err != nil { b.Websocket.DataHandler <- err continue @@ -147,7 +147,7 @@ func (b *BTCMarkets) WsHandleData() { } case "tick": var tick WsTick - err := common.JSONDecode(resp.Raw, &tick) + err := json.Unmarshal(resp.Raw, &tick) if err != nil { b.Websocket.DataHandler <- err continue @@ -168,7 +168,7 @@ func (b *BTCMarkets) WsHandleData() { } case "error": var wsErr WsError - err := common.JSONDecode(resp.Raw, &wsErr) + err := json.Unmarshal(resp.Raw, &wsErr) if err != nil { b.Websocket.DataHandler <- err continue diff --git a/exchanges/btse/btse.go b/exchanges/btse/btse.go index bf2e2ac6..99faa3b7 100644 --- a/exchanges/btse/btse.go +++ b/exchanges/btse/btse.go @@ -2,6 +2,7 @@ package btse import ( "bytes" + "encoding/json" "errors" "fmt" "io" @@ -9,7 +10,6 @@ import ( "strconv" "time" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -215,7 +215,7 @@ func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[str var payload []byte if len(req) != 0 { var err error - payload, err = common.JSONEncode(req) + payload, err = json.Marshal(req) if err != nil { return err } diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index e12d3eeb..b5bc3997 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -1,6 +1,7 @@ package btse import ( + "encoding/json" "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -63,7 +63,7 @@ func (b *BTSE) WsHandleData() { type Result map[string]interface{} result := Result{} - err = common.JSONDecode(resp.Raw, &result) + err = json.Unmarshal(resp.Raw, &result) if err != nil { b.Websocket.DataHandler <- err continue @@ -76,7 +76,7 @@ func (b *BTSE) WsHandleData() { "sell side", b.Name) var tradeHistory wsTradeHistory - err = common.JSONDecode(resp.Raw, &tradeHistory) + err = json.Unmarshal(resp.Raw, &tradeHistory) if err != nil { b.Websocket.DataHandler <- err continue @@ -98,7 +98,7 @@ func (b *BTSE) WsHandleData() { } case strings.Contains(result["topic"].(string), "orderBookApi"): var t wsOrderBook - err = common.JSONDecode(resp.Raw, &t) + err = json.Unmarshal(resp.Raw, &t) if err != nil { b.Websocket.DataHandler <- err continue diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index da81870c..8ebcab1a 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -2,6 +2,7 @@ package coinbasepro import ( "bytes" + "encoding/json" "errors" "fmt" "net/http" @@ -733,7 +734,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(method, path string, params m payload := []byte("") if params != nil { - payload, err = common.JSONEncode(params) + payload, err = json.Marshal(params) if err != nil { return errors.New("sendAuthenticatedHTTPRequest: Unable to JSON request") } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 7cd93295..96c80b44 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -1,6 +1,7 @@ package coinbasepro import ( + "encoding/json" "errors" "fmt" "net/http" @@ -8,7 +9,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -67,7 +67,7 @@ func (c *CoinbasePro) WsHandleData() { } msgType := MsgType{} - err = common.JSONDecode(resp.Raw, &msgType) + err = json.Unmarshal(resp.Raw, &msgType) if err != nil { c.Websocket.DataHandler <- err continue @@ -83,7 +83,7 @@ func (c *CoinbasePro) WsHandleData() { case "ticker": ticker := WebsocketTicker{} - err := common.JSONDecode(resp.Raw, &ticker) + err := json.Unmarshal(resp.Raw, &ticker) if err != nil { c.Websocket.DataHandler <- err continue @@ -105,7 +105,7 @@ func (c *CoinbasePro) WsHandleData() { case "snapshot": snapshot := WebsocketOrderbookSnapshot{} - err := common.JSONDecode(resp.Raw, &snapshot) + err := json.Unmarshal(resp.Raw, &snapshot) if err != nil { c.Websocket.DataHandler <- err continue @@ -119,7 +119,7 @@ func (c *CoinbasePro) WsHandleData() { case "l2update": update := WebsocketL2Update{} - err := common.JSONDecode(resp.Raw, &update) + err := json.Unmarshal(resp.Raw, &update) if err != nil { c.Websocket.DataHandler <- err continue @@ -133,7 +133,7 @@ func (c *CoinbasePro) WsHandleData() { case "received": // We currently use l2update to calculate orderbook changes received := WebsocketReceived{} - err := common.JSONDecode(resp.Raw, &received) + err := json.Unmarshal(resp.Raw, &received) if err != nil { c.Websocket.DataHandler <- err continue @@ -142,7 +142,7 @@ func (c *CoinbasePro) WsHandleData() { case "open": // We currently use l2update to calculate orderbook changes open := WebsocketOpen{} - err := common.JSONDecode(resp.Raw, &open) + err := json.Unmarshal(resp.Raw, &open) if err != nil { c.Websocket.DataHandler <- err continue @@ -151,7 +151,7 @@ func (c *CoinbasePro) WsHandleData() { case "done": // We currently use l2update to calculate orderbook changes done := WebsocketDone{} - err := common.JSONDecode(resp.Raw, &done) + err := json.Unmarshal(resp.Raw, &done) if err != nil { c.Websocket.DataHandler <- err continue @@ -160,7 +160,7 @@ func (c *CoinbasePro) WsHandleData() { case "change": // We currently use l2update to calculate orderbook changes change := WebsocketChange{} - err := common.JSONDecode(resp.Raw, &change) + err := json.Unmarshal(resp.Raw, &change) if err != nil { c.Websocket.DataHandler <- err continue @@ -169,7 +169,7 @@ func (c *CoinbasePro) WsHandleData() { case "activate": // We currently use l2update to calculate orderbook changes activate := WebsocketActivate{} - err := common.JSONDecode(resp.Raw, &activate) + err := json.Unmarshal(resp.Raw, &activate) if err != nil { c.Websocket.DataHandler <- err continue diff --git a/exchanges/coinbene/coinbene_websocket.go b/exchanges/coinbene/coinbene_websocket.go index 791cabae..c1245a7c 100644 --- a/exchanges/coinbene/coinbene_websocket.go +++ b/exchanges/coinbene/coinbene_websocket.go @@ -1,6 +1,7 @@ package coinbene import ( + "encoding/json" "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -102,7 +102,7 @@ func (c *Coinbene) WsDataHandler() { continue } var result map[string]interface{} - err = common.JSONDecode(stream.Raw, &result) + err = json.Unmarshal(stream.Raw, &result) if err != nil { c.Websocket.DataHandler <- err } @@ -127,7 +127,7 @@ func (c *Coinbene) WsDataHandler() { switch { case strings.Contains(result[topic].(string), "ticker"): var ticker WsTicker - err = common.JSONDecode(stream.Raw, &ticker) + err = json.Unmarshal(stream.Raw, &ticker) if err != nil { c.Websocket.DataHandler <- err continue @@ -147,7 +147,7 @@ func (c *Coinbene) WsDataHandler() { } case strings.Contains(result[topic].(string), "tradeList"): var tradeList WsTradeList - err = common.JSONDecode(stream.Raw, &tradeList) + err = json.Unmarshal(stream.Raw, &tradeList) if err != nil { c.Websocket.DataHandler <- err continue @@ -183,7 +183,7 @@ func (c *Coinbene) WsDataHandler() { } case strings.Contains(result[topic].(string), "orderBook"): var orderBook WsOrderbook - err = common.JSONDecode(stream.Raw, &orderBook) + err = json.Unmarshal(stream.Raw, &orderBook) if err != nil { c.Websocket.DataHandler <- err continue @@ -264,7 +264,7 @@ func (c *Coinbene) WsDataHandler() { var kline WsKline var tempFloat float64 var tempKline []float64 - err = common.JSONDecode(stream.Raw, &kline) + err = json.Unmarshal(stream.Raw, &kline) if err != nil { c.Websocket.DataHandler <- err continue @@ -293,7 +293,7 @@ func (c *Coinbene) WsDataHandler() { } case strings.Contains(result[topic].(string), "user.account"): var userinfo WsUserInfo - err = common.JSONDecode(stream.Raw, &userinfo) + err = json.Unmarshal(stream.Raw, &userinfo) if err != nil { c.Websocket.DataHandler <- err continue @@ -301,7 +301,7 @@ func (c *Coinbene) WsDataHandler() { c.Websocket.DataHandler <- userinfo case strings.Contains(result[topic].(string), "user.position"): var position WsPosition - err = common.JSONDecode(stream.Raw, &position) + err = json.Unmarshal(stream.Raw, &position) if err != nil { c.Websocket.DataHandler <- err continue @@ -309,7 +309,7 @@ func (c *Coinbene) WsDataHandler() { c.Websocket.DataHandler <- position case strings.Contains(result[topic].(string), "user.order"): var orders WsUserOrders - err = common.JSONDecode(stream.Raw, &orders) + err = json.Unmarshal(stream.Raw, &orders) if err != nil { c.Websocket.DataHandler <- err continue diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 034c3e22..4ba2ec97 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -8,7 +8,6 @@ import ( "net/http" "strings" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -284,7 +283,7 @@ func (c *COINUT) SendHTTPRequest(apiRequest string, params map[string]interface{ params["nonce"] = n params["request"] = apiRequest - payload, err := common.JSONEncode(params) + payload, err := json.Marshal(params) if err != nil { return errors.New("sendHTTPRequest: Unable to JSON request") } @@ -317,7 +316,7 @@ func (c *COINUT) SendHTTPRequest(apiRequest string, params map[string]interface{ } var genResp GenericResponse - err = common.JSONDecode(rawMsg, &genResp) + err = json.Unmarshal(rawMsg, &genResp) if err != nil { return err } @@ -327,7 +326,7 @@ func (c *COINUT) SendHTTPRequest(apiRequest string, params map[string]interface{ genResp.Status[0]) } - return common.JSONDecode(rawMsg, result) + return json.Unmarshal(rawMsg, result) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index 9b0935dc..6d242239 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -1,6 +1,7 @@ package coinut import ( + "encoding/json" "errors" "fmt" "net/http" @@ -8,7 +9,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -85,7 +85,7 @@ func (c *COINUT) WsHandleData() { if strings.HasPrefix(string(resp.Raw), "[") { var incoming []wsResponse - err = common.JSONDecode(resp.Raw, &incoming) + err = json.Unmarshal(resp.Raw, &incoming) if err != nil { c.Websocket.DataHandler <- err continue @@ -96,7 +96,7 @@ func (c *COINUT) WsHandleData() { break } var individualJSON []byte - individualJSON, err = common.JSONEncode(incoming[i]) + individualJSON, err = json.Marshal(incoming[i]) if err != nil { c.Websocket.DataHandler <- err continue @@ -105,7 +105,7 @@ func (c *COINUT) WsHandleData() { } } else { var incoming wsResponse - err = common.JSONDecode(resp.Raw, &incoming) + err = json.Unmarshal(resp.Raw, &incoming) if err != nil { c.Websocket.DataHandler <- err continue @@ -119,7 +119,7 @@ func (c *COINUT) WsHandleData() { func (c *COINUT) wsProcessResponse(resp []byte) { var incoming wsResponse - err := common.JSONDecode(resp, &incoming) + err := json.Unmarshal(resp, &incoming) if err != nil { c.Websocket.DataHandler <- err return @@ -129,7 +129,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { channels["hb"] <- resp case "inst_tick": var ticker WsTicker - err := common.JSONDecode(resp, &ticker) + err := json.Unmarshal(resp, &ticker) if err != nil { c.Websocket.DataHandler <- err return @@ -154,7 +154,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { case "inst_order_book": var orderbooksnapshot WsOrderbookSnapshot - err := common.JSONDecode(resp, &orderbooksnapshot) + err := json.Unmarshal(resp, &orderbooksnapshot) if err != nil { c.Websocket.DataHandler <- err return @@ -174,7 +174,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { } case "inst_order_book_update": var orderbookUpdate WsOrderbookUpdate - err := common.JSONDecode(resp, &orderbookUpdate) + err := json.Unmarshal(resp, &orderbookUpdate) if err != nil { c.Websocket.DataHandler <- err return @@ -194,7 +194,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { } case "inst_trade": var tradeSnap WsTradeSnapshot - err := common.JSONDecode(resp, &tradeSnap) + err := json.Unmarshal(resp, &tradeSnap) if err != nil { c.Websocket.DataHandler <- err return @@ -202,7 +202,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { case "inst_trade_update": var tradeUpdate WsTradeUpdate - err := common.JSONDecode(resp, &tradeUpdate) + err := json.Unmarshal(resp, &tradeUpdate) if err != nil { c.Websocket.DataHandler <- err return @@ -250,7 +250,7 @@ func (c *COINUT) WsSetInstrumentList() error { return err } var list WsInstrumentList - err = common.JSONDecode(resp, &list) + err = json.Unmarshal(resp, &list) if err != nil { return err } @@ -357,7 +357,7 @@ func (c *COINUT) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscr return err } var response map[string]interface{} - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return err } @@ -394,7 +394,7 @@ func (c *COINUT) wsAuthenticate() error { return err } var response map[string]interface{} - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return err } @@ -419,7 +419,7 @@ func (c *COINUT) wsGetAccountBalance() (*WsGetAccountBalanceResponse, error) { return nil, err } var response WsGetAccountBalanceResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } @@ -466,14 +466,14 @@ func (c *COINUT) wsSubmitOrder(order *WsSubmitOrderParameters) (*WsStandardOrder func (c *COINUT) wsStandardiseOrderResponse(resp []byte) (WsStandardOrderResponse, error) { var response WsStandardOrderResponse var incoming wsResponse - err := common.JSONDecode(resp, &incoming) + err := json.Unmarshal(resp, &incoming) if err != nil { return response, err } switch incoming.Reply { case "order_accepted": var orderAccepted WsOrderAcceptedResponse - err := common.JSONDecode(resp, &orderAccepted) + err := json.Unmarshal(resp, &orderAccepted) if err != nil { return response, err } @@ -492,7 +492,7 @@ func (c *COINUT) wsStandardiseOrderResponse(resp []byte) (WsStandardOrderRespons } case "order_filled": var orderFilled WsOrderFilledResponse - err := common.JSONDecode(resp, &orderFilled) + err := json.Unmarshal(resp, &orderFilled) if err != nil { return response, err } @@ -511,7 +511,7 @@ func (c *COINUT) wsStandardiseOrderResponse(resp []byte) (WsStandardOrderRespons } case "order_rejected": var orderRejected WsOrderRejectedResponse - err := common.JSONDecode(resp, &orderRejected) + err := json.Unmarshal(resp, &orderRejected) if err != nil { return response, err } @@ -561,14 +561,14 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO return nil, errors } var incoming []interface{} - err = common.JSONDecode(resp, &incoming) + err = json.Unmarshal(resp, &incoming) if err != nil { errors = append(errors, err) return nil, errors } for i := range incoming { var individualJSON []byte - individualJSON, err = common.JSONEncode(incoming[i]) + individualJSON, err = json.Marshal(incoming[i]) if err != nil { errors = append(errors, err) continue @@ -612,7 +612,7 @@ func (c *COINUT) wsGetOpenOrders(p currency.Pair) error { return err } var response map[string]interface{} - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return err } @@ -640,7 +640,7 @@ func (c *COINUT) wsCancelOrder(cancellation WsCancelOrderParameters) error { return err } var response map[string]interface{} - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return err } @@ -675,7 +675,7 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*WsCan return nil, []error{err} } var response WsCancelOrdersResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, []error{err} } @@ -711,7 +711,7 @@ func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) error { return err } var response map[string]interface{} - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return err } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 4932817e..0f76eb90 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -82,7 +82,7 @@ func (e *Base) SetClientProxyAddress(addr string) error { // No needs to check err here as the only err condition is an empty // string which is already checked above - e.Requester.SetProxy(proxy) + _ = e.Requester.SetProxy(proxy) if e.Websocket != nil { err = e.Websocket.SetProxyAddress(addr) @@ -460,7 +460,7 @@ func (e *Base) SetupDefaults(exch *config.ExchangeConfig) error { } if e.Features.Supports.Websocket { - e.Websocket.Initialise() + return e.Websocket.Initialise() } return nil } diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index fef8d55b..d9551eb9 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -8,7 +8,6 @@ import ( "strconv" "strings" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" @@ -428,7 +427,7 @@ func (g *Gateio) SendAuthenticatedHTTPRequest(method, endpoint, param string, re Message string `json:"message"` }{} - if err := common.JSONDecode(intermidiary, &errCap); err == nil { + if err := json.Unmarshal(intermidiary, &errCap); err == nil { if !errCap.Result { return fmt.Errorf("%s auth request error, code: %d message: %s", g.Name, @@ -437,7 +436,7 @@ func (g *Gateio) SendAuthenticatedHTTPRequest(method, endpoint, param string, re } } - return common.JSONDecode(intermidiary, result) + return json.Unmarshal(intermidiary, result) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 75347608..04545179 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -1,6 +1,7 @@ package gateio import ( + "encoding/json" "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -64,7 +64,7 @@ func (g *Gateio) wsServerSignIn() (*WebsocketAuthenticationResponse, error) { return nil, err } var response WebsocketAuthenticationResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { g.Websocket.SetCanUseAuthenticatedEndpoints(false) return nil, err @@ -97,7 +97,7 @@ func (g *Gateio) WsHandleData() { } g.Websocket.TrafficAlert <- struct{}{} var result WebsocketResponse - err = common.JSONDecode(resp.Raw, &result) + err = json.Unmarshal(resp.Raw, &result) if err != nil { g.Websocket.DataHandler <- err continue @@ -123,13 +123,13 @@ func (g *Gateio) WsHandleData() { case strings.Contains(result.Method, "ticker"): var ticker WebsocketTicker var c string - err = common.JSONDecode(result.Params[1], &ticker) + err = json.Unmarshal(result.Params[1], &ticker) if err != nil { g.Websocket.DataHandler <- err continue } - err = common.JSONDecode(result.Params[0], &c) + err = json.Unmarshal(result.Params[0], &c) if err != nil { g.Websocket.DataHandler <- err continue @@ -151,13 +151,13 @@ func (g *Gateio) WsHandleData() { case strings.Contains(result.Method, "trades"): var trades []WebsocketTrade var c string - err = common.JSONDecode(result.Params[1], &trades) + err = json.Unmarshal(result.Params[1], &trades) if err != nil { g.Websocket.DataHandler <- err continue } - err = common.JSONDecode(result.Params[0], &c) + err = json.Unmarshal(result.Params[0], &c) if err != nil { g.Websocket.DataHandler <- err continue @@ -179,19 +179,19 @@ func (g *Gateio) WsHandleData() { var IsSnapshot bool var c string var data = make(map[string][][]string) - err = common.JSONDecode(result.Params[0], &IsSnapshot) + err = json.Unmarshal(result.Params[0], &IsSnapshot) if err != nil { g.Websocket.DataHandler <- err continue } - err = common.JSONDecode(result.Params[2], &c) + err = json.Unmarshal(result.Params[2], &c) if err != nil { g.Websocket.DataHandler <- err continue } - err = common.JSONDecode(result.Params[1], &data) + err = json.Unmarshal(result.Params[1], &data) if err != nil { g.Websocket.DataHandler <- err continue @@ -265,7 +265,7 @@ func (g *Gateio) WsHandleData() { case strings.Contains(result.Method, "kline"): var data []interface{} - err = common.JSONDecode(result.Params[0], &data) + err = json.Unmarshal(result.Params[0], &data) if err != nil { g.Websocket.DataHandler <- err continue @@ -356,7 +356,7 @@ func (g *Gateio) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscrip return err } var response WebsocketAuthenticationResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return err } @@ -380,7 +380,7 @@ func (g *Gateio) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscr return err } var response WebsocketAuthenticationResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return err } @@ -404,7 +404,7 @@ func (g *Gateio) wsGetBalance(currencies []string) (*WsGetBalanceResponse, error return nil, err } var balance WsGetBalanceResponse - err = common.JSONDecode(resp, &balance) + err = json.Unmarshal(resp, &balance) if err != nil { return &balance, err } @@ -430,7 +430,7 @@ func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) (*WebSocketOrd return nil, err } var orderQuery WebSocketOrderQueryResult - err = common.JSONDecode(resp, &orderQuery) + err = json.Unmarshal(resp, &orderQuery) if err != nil { return &orderQuery, err } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 794677e4..cc5d96a6 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -1,6 +1,7 @@ package gemini import ( + "encoding/json" "errors" "fmt" "net/http" @@ -371,7 +372,7 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st req[key] = value } - PayloadJSON, err := common.JSONEncode(req) + PayloadJSON, err := json.Marshal(req) if err != nil { return errors.New("sendAuthenticatedHTTPRequest: Unable to JSON request") } diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index c4926252..9815ab36 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -3,6 +3,7 @@ package gemini import ( + "encoding/json" "errors" "fmt" "net/http" @@ -11,7 +12,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -99,7 +99,7 @@ func (g *Gemini) WsSecureSubscribe(dialer *websocket.Dialer, url string) error { Request: fmt.Sprintf("/v1/%v", url), Nonce: time.Now().UnixNano(), } - PayloadJSON, err := common.JSONEncode(payload) + PayloadJSON, err := json.Marshal(payload) if err != nil { return fmt.Errorf("%v sendAuthenticatedHTTPRequest: Unable to JSON request", g.Name) } @@ -166,7 +166,7 @@ func (g *Gemini) WsHandleData() { continue } var result map[string]interface{} - err := common.JSONDecode(resp.Raw, &result) + err := json.Unmarshal(resp.Raw, &result) if err != nil { g.Websocket.DataHandler <- fmt.Errorf("%v Error: %v, Raw: %v", g.Name, err, string(resp.Raw)) continue @@ -174,7 +174,7 @@ func (g *Gemini) WsHandleData() { switch result["type"] { case "subscription_ack": var result WsSubscriptionAcknowledgementResponse - err := common.JSONDecode(resp.Raw, &result) + err := json.Unmarshal(resp.Raw, &result) if err != nil { g.Websocket.DataHandler <- err continue @@ -182,7 +182,7 @@ func (g *Gemini) WsHandleData() { g.Websocket.DataHandler <- result case "initial": var result WsSubscriptionAcknowledgementResponse - err := common.JSONDecode(resp.Raw, &result) + err := json.Unmarshal(resp.Raw, &result) if err != nil { g.Websocket.DataHandler <- err continue @@ -190,7 +190,7 @@ func (g *Gemini) WsHandleData() { g.Websocket.DataHandler <- result case "accepted": var result WsActiveOrdersResponse - err := common.JSONDecode(resp.Raw, &result) + err := json.Unmarshal(resp.Raw, &result) if err != nil { g.Websocket.DataHandler <- err continue @@ -198,7 +198,7 @@ func (g *Gemini) WsHandleData() { g.Websocket.DataHandler <- result case "booked": var result WsOrderBookedResponse - err := common.JSONDecode(resp.Raw, &result) + err := json.Unmarshal(resp.Raw, &result) if err != nil { g.Websocket.DataHandler <- err continue @@ -206,7 +206,7 @@ func (g *Gemini) WsHandleData() { g.Websocket.DataHandler <- result case "fill": var result WsOrderFilledResponse - err := common.JSONDecode(resp.Raw, &result) + err := json.Unmarshal(resp.Raw, &result) if err != nil { g.Websocket.DataHandler <- err continue @@ -214,7 +214,7 @@ func (g *Gemini) WsHandleData() { g.Websocket.DataHandler <- result case "cancelled": var result WsOrderCancelledResponse - err := common.JSONDecode(resp.Raw, &result) + err := json.Unmarshal(resp.Raw, &result) if err != nil { g.Websocket.DataHandler <- err continue @@ -222,7 +222,7 @@ func (g *Gemini) WsHandleData() { g.Websocket.DataHandler <- result case "closed": var result WsOrderClosedResponse - err := common.JSONDecode(resp.Raw, &result) + err := json.Unmarshal(resp.Raw, &result) if err != nil { g.Websocket.DataHandler <- err continue @@ -230,7 +230,7 @@ func (g *Gemini) WsHandleData() { g.Websocket.DataHandler <- result case "heartbeat": var result WsHeartbeatResponse - err := common.JSONDecode(resp.Raw, &result) + err := json.Unmarshal(resp.Raw, &result) if err != nil { g.Websocket.DataHandler <- err continue @@ -243,7 +243,7 @@ func (g *Gemini) WsHandleData() { continue } var marketUpdate WsMarketUpdateResponse - err := common.JSONDecode(resp.Raw, &marketUpdate) + err := json.Unmarshal(resp.Raw, &marketUpdate) if err != nil { g.Websocket.DataHandler <- err continue diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index ea0cf2fd..e77522ff 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -1,6 +1,7 @@ package hitbtc import ( + "encoding/json" "errors" "fmt" "net/http" @@ -8,7 +9,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -71,7 +71,7 @@ func (h *HitBTC) WsHandleData() { h.Websocket.TrafficAlert <- struct{}{} var init capture - err = common.JSONDecode(resp.Raw, &init) + err = json.Unmarshal(resp.Raw, &init) if err != nil { h.Websocket.DataHandler <- err continue @@ -105,7 +105,7 @@ func (h *HitBTC) handleSubscriptionUpdates(resp wshandler.WebsocketResponse, ini switch init.Method { case "ticker": var ticker WsTicker - err := common.JSONDecode(resp.Raw, &ticker) + err := json.Unmarshal(resp.Raw, &ticker) if err != nil { h.Websocket.DataHandler <- err return @@ -132,7 +132,7 @@ func (h *HitBTC) handleSubscriptionUpdates(resp wshandler.WebsocketResponse, ini } case "snapshotOrderbook": var obSnapshot WsOrderbook - err := common.JSONDecode(resp.Raw, &obSnapshot) + err := json.Unmarshal(resp.Raw, &obSnapshot) if err != nil { h.Websocket.DataHandler <- err } @@ -142,33 +142,33 @@ func (h *HitBTC) handleSubscriptionUpdates(resp wshandler.WebsocketResponse, ini } case "updateOrderbook": var obUpdate WsOrderbook - err := common.JSONDecode(resp.Raw, &obUpdate) + err := json.Unmarshal(resp.Raw, &obUpdate) if err != nil { h.Websocket.DataHandler <- err } h.WsProcessOrderbookUpdate(obUpdate) case "snapshotTrades": var tradeSnapshot WsTrade - err := common.JSONDecode(resp.Raw, &tradeSnapshot) + err := json.Unmarshal(resp.Raw, &tradeSnapshot) if err != nil { h.Websocket.DataHandler <- err } case "updateTrades": var tradeUpdates WsTrade - err := common.JSONDecode(resp.Raw, &tradeUpdates) + err := json.Unmarshal(resp.Raw, &tradeUpdates) if err != nil { h.Websocket.DataHandler <- err } case "activeOrders": var activeOrders WsActiveOrdersResponse - err := common.JSONDecode(resp.Raw, &activeOrders) + err := json.Unmarshal(resp.Raw, &activeOrders) if err != nil { h.Websocket.DataHandler <- err } h.Websocket.DataHandler <- activeOrders case "report": var reportData WsReportResponse - err := common.JSONDecode(resp.Raw, &reportData) + err := json.Unmarshal(resp.Raw, &reportData) if err != nil { h.Websocket.DataHandler <- err } @@ -182,21 +182,21 @@ func (h *HitBTC) handleCommandResponses(resp wshandler.WebsocketResponse, init c switch resultType["reportType"].(string) { case "new": var response WsSubmitOrderSuccessResponse - err := common.JSONDecode(resp.Raw, &response) + err := json.Unmarshal(resp.Raw, &response) if err != nil { h.Websocket.DataHandler <- err } h.Websocket.DataHandler <- response case "canceled": var response WsCancelOrderResponse - err := common.JSONDecode(resp.Raw, &response) + err := json.Unmarshal(resp.Raw, &response) if err != nil { h.Websocket.DataHandler <- err } h.Websocket.DataHandler <- response case "replaced": var response WsReplaceOrderResponse - err := common.JSONDecode(resp.Raw, &response) + err := json.Unmarshal(resp.Raw, &response) if err != nil { h.Websocket.DataHandler <- err } @@ -210,14 +210,14 @@ func (h *HitBTC) handleCommandResponses(resp wshandler.WebsocketResponse, init c data := resultType[0].(map[string]interface{}) if _, ok := data["clientOrderId"]; ok { var response WsActiveOrdersResponse - err := common.JSONDecode(resp.Raw, &response) + err := json.Unmarshal(resp.Raw, &response) if err != nil { h.Websocket.DataHandler <- err } h.Websocket.DataHandler <- response } else if _, ok := data["available"]; ok { var response WsGetTradingBalanceResponse - err := common.JSONDecode(resp.Raw, &response) + err := json.Unmarshal(resp.Raw, &response) if err != nil { h.Websocket.DataHandler <- err } @@ -437,7 +437,7 @@ func (h *HitBTC) wsPlaceOrder(pair currency.Pair, side string, price, quantity f return nil, fmt.Errorf("%v %v", h.Name, err) } var response WsSubmitOrderSuccessResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -464,7 +464,7 @@ func (h *HitBTC) wsCancelOrder(clientOrderID string) (*WsCancelOrderResponse, er return nil, fmt.Errorf("%v %v", h.Name, err) } var response WsCancelOrderResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -494,7 +494,7 @@ func (h *HitBTC) wsReplaceOrder(clientOrderID string, quantity, price float64) ( return nil, fmt.Errorf("%v %v", h.Name, err) } var response WsReplaceOrderResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -519,7 +519,7 @@ func (h *HitBTC) wsGetActiveOrders() (*WsActiveOrdersResponse, error) { return nil, fmt.Errorf("%v %v", h.Name, err) } var response WsActiveOrdersResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -544,7 +544,7 @@ func (h *HitBTC) wsGetTradingBalance() (*WsGetTradingBalanceResponse, error) { return nil, fmt.Errorf("%v %v", h.Name, err) } var response WsGetTradingBalanceResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -568,7 +568,7 @@ func (h *HitBTC) wsGetCurrencies(currencyItem currency.Code) (*WsGetCurrenciesRe return nil, fmt.Errorf("%v %v", h.Name, err) } var response WsGetCurrenciesResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -592,7 +592,7 @@ func (h *HitBTC) wsGetSymbols(currencyItem currency.Pair) (*WsGetSymbolsResponse return nil, fmt.Errorf("%v %v", h.Name, err) } var response WsGetSymbolsResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -619,7 +619,7 @@ func (h *HitBTC) wsGetTrades(currencyItem currency.Pair, limit int64, sort, by s return nil, fmt.Errorf("%v %v", h.Name, err) } var response WsGetTradesResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index 0b1d8cec..fa7022f4 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -1,6 +1,7 @@ package huobi import ( + "encoding/json" "errors" "fmt" "net/http" @@ -8,13 +9,11 @@ import ( "strings" "time" - "github.com/thrasher-corp/gocryptotrader/common/crypto" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" log "github.com/thrasher-corp/gocryptotrader/logger" @@ -139,7 +138,7 @@ func (h *HUOBI) WsHandleData() { func (h *HUOBI) wsHandleAuthenticatedData(resp WsMessage) { var init WsAuthenticatedDataResponse - err := common.JSONDecode(resp.Raw, &init) + err := json.Unmarshal(resp.Raw, &init) if err != nil { h.Websocket.DataHandler <- err return @@ -169,14 +168,14 @@ func (h *HUOBI) wsHandleAuthenticatedData(resp WsMessage) { case strings.EqualFold(init.Op, authOp): h.Websocket.SetCanUseAuthenticatedEndpoints(true) var response WsAuthenticatedDataResponse - err := common.JSONDecode(resp.Raw, &response) + err := json.Unmarshal(resp.Raw, &response) if err != nil { h.Websocket.DataHandler <- err } h.Websocket.DataHandler <- response case strings.EqualFold(init.Topic, "accounts"): var response WsAuthenticatedAccountsResponse - err := common.JSONDecode(resp.Raw, &response) + err := json.Unmarshal(resp.Raw, &response) if err != nil { h.Websocket.DataHandler <- err } @@ -184,14 +183,14 @@ func (h *HUOBI) wsHandleAuthenticatedData(resp WsMessage) { case strings.Contains(init.Topic, "orders") && strings.Contains(init.Topic, "update"): var response WsAuthenticatedOrdersUpdateResponse - err := common.JSONDecode(resp.Raw, &response) + err := json.Unmarshal(resp.Raw, &response) if err != nil { h.Websocket.DataHandler <- err } h.Websocket.DataHandler <- response case strings.Contains(init.Topic, "orders"): var response WsAuthenticatedOrdersResponse - err := common.JSONDecode(resp.Raw, &response) + err := json.Unmarshal(resp.Raw, &response) if err != nil { h.Websocket.DataHandler <- err } @@ -201,7 +200,7 @@ func (h *HUOBI) wsHandleAuthenticatedData(resp WsMessage) { func (h *HUOBI) wsHandleMarketData(resp WsMessage) { var init WsResponse - err := common.JSONDecode(resp.Raw, &init) + err := json.Unmarshal(resp.Raw, &init) if err != nil { h.Websocket.DataHandler <- err return @@ -228,7 +227,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { switch { case strings.Contains(init.Channel, "depth"): var depth WsDepth - err := common.JSONDecode(resp.Raw, &depth) + err := json.Unmarshal(resp.Raw, &depth) if err != nil { h.Websocket.DataHandler <- err return @@ -243,7 +242,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { case strings.Contains(init.Channel, "kline"): var kline WsKline - err := common.JSONDecode(resp.Raw, &kline) + err := json.Unmarshal(resp.Raw, &kline) if err != nil { h.Websocket.DataHandler <- err return @@ -263,7 +262,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { } case strings.Contains(init.Channel, "trade.detail"): var trade WsTrade - err := common.JSONDecode(resp.Raw, &trade) + err := json.Unmarshal(resp.Raw, &trade) if err != nil { h.Websocket.DataHandler <- err return @@ -278,7 +277,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) { } case strings.Contains(init.Channel, "detail"): var ticker WsTick - err := common.JSONDecode(resp.Raw, &ticker) + err := json.Unmarshal(resp.Raw, &ticker) if err != nil { h.Websocket.DataHandler <- err return @@ -457,7 +456,7 @@ func (h *HUOBI) wsGetAccountsList(pair currency.Pair) (*WsAuthenticatedAccountsL return nil, err } var response WsAuthenticatedAccountsListResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) return &response, err } @@ -485,7 +484,7 @@ func (h *HUOBI) wsGetOrdersList(accountID int64, pair currency.Pair) (*WsAuthent return nil, err } var response WsAuthenticatedOrdersResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) return &response, err } @@ -511,6 +510,6 @@ func (h *HUOBI) wsGetOrderDetails(orderID string) (*WsAuthenticatedOrderDetailRe return nil, err } var response WsAuthenticatedOrderDetailResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) return &response, err } diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index 9a4a6958..555853f5 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -305,7 +305,7 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method, path string, params map[str var err error if params != nil { - PayloadJSON, err = common.JSONEncode(req) + PayloadJSON, err = json.Marshal(req) if err != nil { return err } @@ -317,7 +317,7 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method, path string, params map[str n := i.Requester.GetNonce(true).String() timestamp := strconv.FormatInt(time.Now().UnixNano()/1000000, 10) - message, err := common.JSONEncode([]string{method, urlPath, string(PayloadJSON), n, timestamp}) + message, err := json.Marshal([]string{method, urlPath, string(PayloadJSON), n, timestamp}) if err != nil { return err } @@ -354,7 +354,7 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method, path string, params map[str return err } - err = common.JSONDecode(intermediary, &errCheck) + err = json.Unmarshal(intermediary, &errCheck) if err == nil { if errCheck.Code != 0 || errCheck.Description != "" { return fmt.Errorf("itbit.go SendAuthRequest error code: %d description: %s", @@ -363,7 +363,7 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method, path string, params map[str } } - return common.JSONDecode(intermediary, result) + return json.Unmarshal(intermediary, result) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index f4c2ceb9..1be268b1 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -1,6 +1,7 @@ package kraken import ( + "encoding/json" "errors" "fmt" "math" @@ -9,7 +10,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -116,14 +116,14 @@ func (k *Kraken) WsHandleData() { k.Websocket.TrafficAlert <- struct{}{} // event response handling var eventResponse WebsocketEventResponse - err = common.JSONDecode(resp.Raw, &eventResponse) + err = json.Unmarshal(resp.Raw, &eventResponse) if err == nil && eventResponse.Event != "" { k.WsHandleEventResponse(&eventResponse, resp.Raw) continue } // Data response handling var dataResponse WebsocketDataResponse - err = common.JSONDecode(resp.Raw, &dataResponse) + err = json.Unmarshal(resp.Raw, &dataResponse) if err == nil && dataResponse[0].(float64) >= 0 { k.WsHandleDataResponse(dataResponse) continue diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 498e88df..1aef5c5b 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -1,13 +1,13 @@ package lakebtc import ( + "encoding/json" "errors" "fmt" "net/http" "strconv" "strings" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -302,7 +302,7 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int postData["id"] = 1 postData["params"] = strings.Split(params, ",") - data, err := common.JSONEncode(postData) + data, err := json.Marshal(postData) if err != nil { return err } diff --git a/exchanges/lakebtc/lakebtc_websocket.go b/exchanges/lakebtc/lakebtc_websocket.go index 81879957..0639ddc3 100644 --- a/exchanges/lakebtc/lakebtc_websocket.go +++ b/exchanges/lakebtc/lakebtc_websocket.go @@ -1,13 +1,13 @@ package lakebtc import ( + "encoding/json" "errors" "fmt" "strconv" "strings" "time" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -141,7 +141,7 @@ func (l *LakeBTC) wsHandleIncomingData() { func (l *LakeBTC) processTrades(data, channel string) error { var tradeData WsTrades - err := common.JSONDecode([]byte(data), &tradeData) + err := json.Unmarshal([]byte(data), &tradeData) if err != nil { return err } @@ -164,7 +164,7 @@ func (l *LakeBTC) processTrades(data, channel string) error { func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { var update WsOrderbookUpdate - err := common.JSONDecode([]byte(obUpdate), &update) + err := json.Unmarshal([]byte(obUpdate), &update) if err != nil { return err } @@ -236,7 +236,7 @@ func (l *LakeBTC) getCurrencyFromChannel(channel string) currency.Pair { func (l *LakeBTC) processTicker(ticker string) error { var tUpdate map[string]interface{} - err := common.JSONDecode([]byte(ticker), &tUpdate) + err := json.Unmarshal([]byte(ticker), &tUpdate) if err != nil { l.Websocket.DataHandler <- err return err diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 5c57708c..8b7fa490 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -1,6 +1,7 @@ package okcoin import ( + "encoding/json" "net/http" "strings" "sync" @@ -878,7 +879,7 @@ func TestOrderBookUpdateChecksumCalculator(t *testing.T) { original := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"BTC-USDT","asks":[["3864.6786","0.145",1],["3864.7682","0.005",1],["3864.9851","0.57",1],["3864.9852","0.30137754",1],["3864.9986","2.81818419",1],["3864.9995","0.002",1],["3865","0.0597",1],["3865.0309","0.4",1],["3865.1995","0.004",1],["3865.3995","0.004",1],["3865.5995","0.004",1],["3865.7995","0.004",1],["3865.9995","0.004",1],["3866.0961","0.25865886",1],["3866.1995","0.004",1],["3866.3995","0.004",1],["3866.4004","0.3243",2],["3866.5995","0.004",1],["3866.7633","0.44247086",1],["3866.7995","0.004",1],["3866.9197","0.511",1],["3867.256","0.51716256",1],["3867.3951","0.02588112",1],["3867.4014","0.025",1],["3867.4566","0.02499999",1],["3867.4675","4.01155057",5],["3867.5515","1.1",1],["3867.6113","0.009",1],["3867.7349","0.026",1],["3867.7781","0.03738652",1],["3867.9163","0.0521",1],["3868.0381","0.34354941",1],["3868.0436","0.051",1],["3868.0657","0.90552172",3],["3868.1819","0.03863346",1],["3868.2013","0.194",1],["3868.346","0.051",1],["3868.3863","0.01155",1],["3868.7716","0.009",1],["3868.947","0.025",1],["3868.98","0.001",1],["3869.0764","1.03487931",1],["3869.2773","0.07724578",1],["3869.4039","0.025",1],["3869.4068","1.03",1],["3869.7068","2.06976398",1],["3870","0.5",1],["3870.0465","0.01",1],["3870.7042","0.02099651",1],["3870.9451","2.07047375",1],["3871.5254","1.2",1],["3871.5596","0.001",1],["3871.6605","0.01035032",1],["3871.7179","2.07047375",1],["3871.8816","0.51751625",1],["3872.1","0.75",1],["3872.2464","0.0646",1],["3872.3747","0.283",1],["3872.4039","0.2",1],["3872.7655","0.23179307",1],["3872.8005","2.06976398",1],["3873.1509","2",1],["3873.3215","0.26",1],["3874.1392","0.001",1],["3874.1487","3.88224364",4],["3874.1685","1.8",1],["3874.5571","0.08974762",1],["3874.734","2.06976398",1],["3874.99","0.3",1],["3875","1.001",2],["3875.0041","1.03505051",1],["3875.45","0.3",1],["3875.4766","0.15",1],["3875.7057","0.51751625",1],["3876","0.001",1],["3876.68","0.3",1],["3876.7188","0.001",1],["3877","0.75",1],["3877.31","0.035",1],["3877.38","0.3",1],["3877.7","0.3",1],["3877.88","0.3",1],["3878.0364","0.34770122",1],["3878.4525","0.48579748",1],["3878.4955","0.02812511",1],["3878.8855","0.00258579",1],["3878.9605","0.895",1],["3879","0.001",1],["3879.2984","0.002",2],["3879.432","0.001",1],["3879.6313","6",1],["3879.9999","0.002",2],["3880","1.25132834",5],["3880.2526","0.04075162",1],["3880.7145","0.0647",1],["3881.2469","1.883",1],["3881.878","0.002",2],["3884.4576","0.002",2],["3885","0.002",2],["3885.2233","0.28304103",1],["3885.7416","18",1],["3886","0.001",1],["3886.1554","5.4",1],["3887","0.001",1],["3887.0372","0.002",2],["3887.2559","0.05214011",1],["3887.9238","0.0019",1],["3888","0.15810538",4],["3889","0.001",1],["3889.5175","0.50510653",1],["3889.6168","0.002",2],["3889.9999","0.001",1],["3890","2.34968109",4],["3890.5222","0.00257806",1],["3891.2659","5",1],["3891.9999","0.00893897",1],["3892.1964","0.002",2],["3892.4358","0.0176",1],["3893.1388","1.4279",1],["3894","0.0026321",1],["3894.776","0.001",1],["3895","1.501",2],["3895.379","0.25881288",1],["3897","0.05",1],["3897.3556","0.001",1],["3897.8432","0.73708079",1],["3898","3.31353018",7],["3898.4462","4.757",1],["3898.6","0.47159638",1],["3898.8769","0.0129",1],["3899","6",2],["3899.6516","0.025",1],["3899.9352","0.001",1],["3899.9999","0.013",2],["3900","22.37447743",24],["3900.9999","0.07763916",1],["3901","0.10192487",1],["3902.1937","0.00257034",1],["3902.3991","1.5532141",1],["3902.5148","0.001",1],["3904","1.49331984",1],["3904.9999","0.95905447",1],["3905","0.501",2],["3905.0944","0.001",1],["3905.61","0.099",1],["3905.6801","0.54343686",1],["3906.2901","0.0258",1],["3907.674","0.001",1],["3907.85","1.35778084",1],["3908","0.03846153",1],["3908.23","1.95189531",1],["3908.906","0.03148978",1],["3909","0.001",1],["3909.9999","0.01398721",2],["3910","0.016",2],["3910.2536","0.001",1],["3912.5406","0.88270517",1],["3912.8332","0.001",1],["3913","1.2640608",1],["3913.87","1.69114184",1],["3913.9003","0.00256266",1],["3914","1.21766411",1],["3915","0.001",1],["3915.4128","0.001",1],["3915.7425","6.848",1],["3916","0.0050949",1],["3917.36","1.28658296",1],["3917.9924","0.001",1],["3919","0.001",1],["3919.9999","0.001",1],["3920","1.21171832",3],["3920.0002","0.20217038",1],["3920.572","0.001",1],["3921","0.128",1],["3923.0756","0.00148064",1],["3923.1516","0.001",1],["3923.86","1.38831714",1],["3925","0.01867801",2],["3925.642","0.00255499",1],["3925.7312","0.001",1],["3926","0.04290757",1],["3927","0.023",1],["3927.3175","0.01212865",1],["3927.65","1.51375612",1],["3928","0.5",1],["3928.3108","0.001",1],["3929","0.001",1],["3929.9999","0.01519338",2],["3930","0.0174985",3],["3930.21","1.49335799",1],["3930.8904","0.001",1],["3932.2999","0.01953",1],["3932.8962","7.96",1],["3933.0387","11.808",1],["3933.47","0.001",1],["3934","1.40839932",1],["3935","0.001",1],["3936.8","0.62879518",1],["3937.23","1.56977841",1],["3937.4189","0.00254735",1]],"bids":[["3864.5217","0.00540709",1],["3864.5216","0.14068758",2],["3864.2275","0.01033576",1],["3864.0989","0.00825047",1],["3864.0273","0.38",1],["3864.0272","0.4",1],["3863.9957","0.01083539",1],["3863.9184","0.01653723",1],["3863.8282","0.25588165",1],["3863.8153","0.154",1],["3863.7791","1.14122492",1],["3863.6866","0.01733662",1],["3863.6093","0.02645958",1],["3863.3775","0.02773862",1],["3863.0297","0.513",1],["3863.0286","1.1028564",2],["3862.8489","0.01",1],["3862.5972","0.01890179",1],["3862.3431","0.01152944",1],["3862.313","0.009",1],["3862.2445","0.90551002",3],["3862.0734","0.014",1],["3862.0539","0.64976067",1],["3861.8586","0.025",1],["3861.7888","0.025",1],["3861.7673","0.008",1],["3861.5785","0.01",1],["3861.3895","0.005",1],["3861.3338","0.25875855",1],["3861.161","0.01",1],["3861.1111","0.03863352",1],["3861.0732","0.51703882",1],["3860.9116","0.17754895",1],["3860.75","0.19",1],["3860.6554","0.015",1],["3860.6172","0.005",1],["3860.6088","0.008",1],["3860.4724","0.12940042",1],["3860.4424","0.25880084",1],["3860.42","0.01",1],["3860.3725","0.51760102",1],["3859.8449","0.005",1],["3859.8285","0.03738652",1],["3859.7638","0.07726703",1],["3859.4502","0.008",1],["3859.3772","0.05173471",1],["3859.3409","0.194",1],["3859","5",1],["3858.827","0.0521",1],["3858.8208","0.001",1],["3858.679","0.26",1],["3858.4814","0.07477305",1],["3858.1669","1.03503422",1],["3857.6005","0.006",1],["3857.4005","0.004",1],["3857.2005","0.004",1],["3857.1871","1.218",1],["3857.0005","0.004",1],["3856.8135","0.0646",1],["3856.8005","0.004",1],["3856.2412","0.001",1],["3856.2349","1.03503422",1],["3856.0197","0.01037339",1],["3855.8781","0.23178117",1],["3855.8005","0.004",1],["3855.7165","0.00259355",1],["3855.4858","0.25875855",1],["3854.4584","0.01",1],["3853.6616","0.001",1],["3853.1373","0.92",1],["3852.5072","0.48599702",1],["3851.3926","0.13008333",1],["3851.082","0.001",1],["3850.9317","2",1],["3850.6359","0.34770165",1],["3850.2058","0.51751624",1],["3850.0823","0.15",1],["3850.0042","0.5175171",1],["3850","0.001",1],["3849.6325","1.8",1],["3849.41","0.3",1],["3848.9686","1.85",1],["3848.7426","0.18511466",1],["3848.52","0.3",1],["3848.5024","0.001",1],["3848.42","0.3",1],["3848.1618","2.204",1],["3847.77","0.3",1],["3847.48","0.3",1],["3847.3581","2.05",1],["3846.8259","0.0646",1],["3846.59","0.3",1],["3846.49","0.3",1],["3845.9228","0.001",1],["3844.184","0.00260133",1],["3844.0092","6.3",1],["3843.3432","0.001",1],["3841","0.06300963",1],["3840.7636","0.001",1],["3840","0.201",3],["3839.7681","18",1],["3839.5328","0.05214011",1],["3838.184","0.001",1],["3837.2344","0.27589557",1],["3836.6479","5.2",1],["3836","2.37196773",3],["3835.6044","0.001",1],["3833.6053","0.25873556",1],["3833.0248","0.001",1],["3833","0.8726502",1],["3832.6859","0.00260913",1],["3832","0.007",1],["3831.637","6",1],["3831.0602","0.001",1],["3830.4452","0.001",1],["3830","0.20375718",4],["3829.7125","0.07833486",1],["3829.6283","0.3519681",1],["3829","0.0039261",1],["3827.8656","0.001",1],["3826.0001","0.53251232",1],["3826","0.0509",1],["3825.7834","0.00698562",1],["3825.286","0.001",1],["3823.0001","0.03010127",1],["3822.8014","0.00261588",1],["3822.7064","0.001",1],["3822.2","1",1],["3822.1121","0.35994101",1],["3821.2222","0.00261696",1],["3821","0.001",1],["3820.1268","0.001",1],["3820","1.12992803",4],["3819","0.01331195",2],["3817.5472","0.001",1],["3816","1.13807184",2],["3815.8343","0.32463428",1],["3815.7834","0.00525295",1],["3815","28.99386799",4],["3814.9676","0.001",1],["3813","0.91303023",4],["3812.388","0.002",2],["3811.2257","0.07",1],["3810","0.32573997",2],["3809.8084","0.001",1],["3809.7928","0.00262481",1],["3807.2288","0.001",1],["3806.8421","0.07003461",1],["3806","0.19",1],["3805.8041","0.05678805",1],["3805","1.01",2],["3804.6492","0.001",1],["3804.3551","0.1",1],["3803","0.005",1],["3802.22","2.05042631",1],["3802.0696","0.001",1],["3802","1.63290092",1],["3801.2257","0.07",1],["3801","57.4",3],["3800.9853","0.02492278",1],["3800.8421","0.06503533",1],["3800.7844","0.02812628",1],["3800.0001","0.00409473",1],["3800","17.91401074",15],["3799.49","0.001",1],["3799","0.1",1],["3796.9104","0.001",1],["3796","9.00128053",2],["3795.5441","0.0028",1],["3794.3308","0.001",1],["3791","55",1],["3790.7777","0.07",1],["3790","12.03238184",7],["3789","1",1],["3788","0.21110454",2],["3787.2959","9",1],["3786.592","0.001",1],["3786","9.01916822",2],["3785","12.87914268",5],["3784.0124","0.001",1],["3781.4328","0.002",2],["3781","56.3",2],["3780.7777","0.07",1],["3780","23.41537654",10],["3778.8532","0.002",2],["3776","9",1],["3774","0.003",1],["3772.2481","0.06901672",1],["3771","55.1",2],["3770.7777","0.07",1],["3770","7.30268416",5],["3769","0.25",1],["3768","1.3725",3],["3766.66","0.02",1],["3766","7.64837924",2],["3765.58","1.22775492",1],["3762.58","1.22873383",1],["3761","51.68262164",1],["3760.8031","0.0399",1],["3760.7777","0.07",1]],"timestamp":"2019-03-06T23:19:17.705Z","checksum":-1785549915}]}` update := `{"table":"spot/depth","action":"update","data":[{"instrument_id":"BTC-USDT","asks":[["3864.6786","0",0],["3864.9852","0",0],["3865.9994","0.48402971",1],["3866.4004","0.001",1],["3866.7995","0.3273",2],["3867.4566","0",0],["3867.7031","0.025",1],["3868.0436","0",0],["3868.346","0",0],["3868.3695","0.051",1],["3870.9243","0.642",1],["3874.9942","0.51751796",1],["3875.7057","0",0],["3939","0.001",1]],"bids":[["3864.55","0.0565449",1],["3863.8282","0",0],["3863.8153","0",0],["3863.7898","0.01320077",1],["3863.4807","0.02112123",1],["3863.3002","0.04233533",1],["3863.1717","0.03379397",1],["3863.0685","0.04438179",1],["3863.0286","0.7362564",1],["3862.9912","0.06773651",1],["3862.8626","0.05407035",1],["3862.7595","0.07101087",1],["3862.313","0.3756",2],["3862.1848","0.012",1],["3862.0734","0",0],["3861.8391","0.025",1],["3861.7888","0",0],["3856.6716","0.38893641",1],["3768","0",0],["3766.66","0",0],["3766","0",0],["3765.58","0",0],["3762.58","0",0],["3761","0",0],["3760.8031","0",0],["3760.7777","0",0]],"timestamp":"2019-03-06T23:19:18.239Z","checksum":-1587788848}]}` var dataResponse okgroup.WebsocketDataResponse - err := common.JSONDecode([]byte(original), &dataResponse) + err := json.Unmarshal([]byte(original), &dataResponse) if err != nil { t.Error(err) } @@ -888,7 +889,7 @@ func TestOrderBookUpdateChecksumCalculator(t *testing.T) { return } var updateResponse okgroup.WebsocketDataResponse - err = common.JSONDecode([]byte(update), &updateResponse) + err = json.Unmarshal([]byte(update), &updateResponse) if err != nil { t.Error(err) } @@ -908,7 +909,7 @@ func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { original := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"WAVES-BTC","asks":[["0.000714","1.15414979",1],["0.000715","3.3",2],["0.000717","426.71348",2],["0.000719","140.84507042",1],["0.00072","590.77",1],["0.000721","991.77",1],["0.000724","0.3532032",1],["0.000725","58.82698567",1],["0.000726","1033.15469748",2],["0.000729","0.35320321",1],["0.00073","352.77",1],["0.000735","0.38469748",1],["0.000736","625.77",1],["0.00075191","152.44796961",1],["0.00075192","114.3359772",1],["0.00075193","85.7519829",1],["0.00075194","64.31398718",1],["0.00075195","48.23549038",1],["0.00075196","36.17661779",1],["0.00075199","61.04804253",1],["0.0007591","70.71318474",1],["0.0007621","53.03488855",1],["0.00076211","39.77616642",1],["0.00076212","29.83212481",1],["0.0007635","22.37409361",1],["0.00076351","29.36599786",2],["0.00076352","9.43907074",1],["0.00076353","7.07930306",1],["0.00076354","14.15860612",1],["0.00076355","3.53965153",1],["0.00076369","3.53965153",1],["0.0008","34.36841101",1],["0.00082858","1.69936503",1],["0.00083232","2.8",1],["0.00084","15.69220129",1],["0.00085","4.42785042",1],["0.00088","0.1",1],["0.000891","0.1",1],["0.0009","12.41486491",2],["0.00093","5",1],["0.0012","12.31486492",1],["0.00531314","6.91803114",1],["0.00799999","0.02",1],["0.0084","0.05989",1],["0.00931314","5.18852336",1],["0.0799999","0.02",1],["0.499","6.00423396",1],["0.5","0.4995",1],["0.799999","0.02",1],["4.99","2",1],["5","3.98583144",1],["7.99999999","0.02",1],["79.99999999","0.02",1],["799.99999999","0.02986704",1]],"bids":[["0.000709","222.91679881",3],["0.000703","0.47161952",1],["0.000701","140.73015789",2],["0.0007","0.3",1],["0.000699","401",1],["0.000698","232.61801667",2],["0.000689","0.71396896",1],["0.000688","0.69910125",1],["0.000613","227.54771052",1],["0.0005","0.01",1],["0.00026789","3.69905341",1],["0.000238","2.4",1],["0.00022","0.53",1],["0.0000055","374.09871696",1],["0.00000056","222",1],["0.00000055","736.84761363",1],["0.0000002","999",1],["0.00000009","1222.22222417",1],["0.00000008","20868.64520447",1],["0.00000002","110000",1],["0.00000001","10000",1]],"timestamp":"2019-03-12T22:22:42.274Z","checksum":1319037905}]}` update := `{"table":"spot/depth","action":"update","data":[{"instrument_id":"WAVES-BTC","asks":[["0.000715","100.48199596",3],["0.000716","62.21679881",1]],"bids":[["0.000713","38.95772168",1]],"timestamp":"2019-03-12T22:22:42.938Z","checksum":-131160897}]}` var dataResponse okgroup.WebsocketDataResponse - err := common.JSONDecode([]byte(original), &dataResponse) + err := json.Unmarshal([]byte(original), &dataResponse) if err != nil { t.Error(err) } @@ -918,7 +919,7 @@ func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { return } var updateResponse okgroup.WebsocketDataResponse - err = common.JSONDecode([]byte(update), &updateResponse) + err = json.Unmarshal([]byte(update), &updateResponse) if err != nil { t.Error(err) } @@ -933,7 +934,7 @@ func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { func TestOrderBookPartialChecksumCalculator(t *testing.T) { orderbookPartialJSON := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"EOS-USDT","asks":[["3.5196","0.1077",1],["3.5198","21.71",1],["3.5199","51.1805",1],["3.5208","75.09",1],["3.521","196.3333",1],["3.5213","0.1",1],["3.5218","39.276",2],["3.5219","395.6334",1],["3.522","27.956",1],["3.5222","404.9595",1],["3.5225","300",1],["3.5227","143.5442",2],["3.523","42.4746",1],["3.5231","852.64",2],["3.5235","34.9602",1],["3.5237","442.0918",2],["3.5238","352.8404",2],["3.5239","341.6759",2],["3.524","84.9493",1],["3.5241","148.4882",1],["3.5242","261.64",1],["3.5243","142.045",1],["3.5246","10",1],["3.5247","284.0788",1],["3.5248","720",1],["3.5249","89.2518",2],["3.5251","1201.8965",2],["3.5254","426.2938",1],["3.5255","213.0863",1],["3.5257","568.1576",1],["3.5258","0.3",1],["3.5259","34.4602",1],["3.526","0.1",1],["3.5263","850.771",1],["3.5265","5.9",1],["3.5268","10.5064",2],["3.5272","1136.8965",1],["3.5274","255.1481",1],["3.5276","29.5374",1],["3.5278","50",1],["3.5282","284.1797",1],["3.5283","1136.8965",1],["3.5284","0.4275",1],["3.5285","100",1],["3.5292","90.9",1],["3.5298","0.2",1],["3.5303","568.1576",1],["3.5305","279.9999",1],["3.532","0.409",1],["3.5321","568.1576",1],["3.5326","6016.8756",1],["3.5328","4.9849",1],["3.533","92.88",2],["3.5343","1200.2383",2],["3.5344","100",1],["3.535","359.7047",1],["3.5354","100",1],["3.5355","100",1],["3.5356","10",1],["3.5358","200",2],["3.5362","435.139",1],["3.5365","2152",1],["3.5366","284.1756",1],["3.5367","568.4644",1],["3.5369","33.9878",1],["3.537","337.1191",2],["3.5373","0.4045",1],["3.5383","1136.7188",1],["3.5386","12.1614",1],["3.5387","90.89",1],["3.54","4.54",1],["3.5423","90.8",1],["3.5436","0.1",1],["3.5454","853.4156",1],["3.5468","142.0656",1],["3.5491","0.0008",1],["3.55","14478.8206",6],["3.5537","21521",1],["3.5555","11.53",1],["3.5573","50.6001",1],["3.5599","4591.4221",1],["3.56","1227.0002",4],["3.5603","2670",1],["3.5608","58.6638",1],["3.5613","0.1",1],["3.5621","45.9473",1],["3.57","2141.7274",3],["3.5712","2956.9816",1],["3.5717","27.9978",1],["3.5718","0.9285",1],["3.5739","299.73",1],["3.5761","864",1],["3.579","22.5225",1],["3.5791","38.26",2],["3.58","7618.4634",5],["3.5801","457.2184",1],["3.582","24.5",1],["3.5822","1572.6425",1],["3.5845","14.1438",1],["3.585","527.169",1],["3.5865","20",1],["3.5867","4490",1],["3.5876","39.0493",1],["3.5879","392.9083",1],["3.5888","436.42",2],["3.5896","50",1],["3.59","2608.9128",8],["3.5913","19.5246",1],["3.5938","7082",1],["3.597","0.1",1],["3.5979","399",1],["3.5995","315.1509",1],["3.5999","2566.2648",1],["3.6","18511.2292",35],["3.603","22.3379",2],["3.605","499.5",1],["3.6055","100",1],["3.6058","499.5",1],["3.608","1021.1485",1],["3.61","11755.4596",13],["3.611","42.8571",1],["3.6131","6690",1],["3.6157","19.5247",1],["3.618","2500",1],["3.6197","525.7146",1],["3.6198","0.4455",1],["3.62","6440.6295",8],["3.6219","0.4175",1],["3.6237","168",1],["3.6265","0.1001",1],["3.628","64.9345",1],["3.63","4435.4985",6],["3.6308","1.7815",1],["3.6331","0.1",1],["3.6338","355.527",2],["3.6358","50",1],["3.6363","2074.7096",1],["3.6376","4000",1],["3.6396","11090",1],["3.6399","0.4055",1],["3.64","4161.9805",4],["3.6437","117.6524",1],["3.648","190",1],["3.6488","200",1],["3.65","11740.5045",25],["3.6512","0.1",1],["3.6521","728",1],["3.6555","100",1],["3.6598","36.6914",1],["3.66","4331.2148",6],["3.6638","200",1],["3.6673","100",1],["3.6679","38",1],["3.6688","2",1],["3.6695","0.1",1],["3.67","7984.698",6],["3.672","300",1],["3.6777","257.8247",1],["3.6789","393.4217",2],["3.68","9202.3222",11],["3.6818","500",1],["3.6823","299.7",1],["3.6839","422.3748",1],["3.685","100",1],["3.6878","0.1",1],["3.6888","72.0958",2],["3.6889","2876",1],["3.689","28",1],["3.6891","28",1],["3.6892","28",1],["3.6895","28",1],["3.6898","28",1],["3.69","643.96",7],["3.6908","118",2],["3.691","28",1],["3.6916","28",1],["3.6918","28",1],["3.6926","28",1],["3.6928","28",1],["3.6932","28",1],["3.6933","200",1],["3.6935","28",1],["3.6936","28",1],["3.6938","28",1],["3.694","28",1],["3.698","1498.5",1],["3.6988","2014.2004",2],["3.7","21904.2689",22],["3.7029","71.95",1],["3.704","3690.1362",1],["3.7055","100",1],["3.7063","0.1",1],["3.71","4421.3468",4],["3.719","17.3491",1],["3.72","1304.5995",3],["3.7211","10",1],["3.7248","0.1",1],["3.725","1900",1],["3.73","31.1785",2],["3.7375","38",1]],"bids":[["3.5182","151.5343",6],["3.5181","0.3691",1],["3.518","271.3967",2],["3.5179","257.8352",1],["3.5178","12.3811",1],["3.5173","34.1921",2],["3.5171","1013.8256",2],["3.517","272.1119",2],["3.5168","395.3376",1],["3.5166","317.1756",2],["3.5165","348.302",3],["3.5164","142.0414",1],["3.5163","96.8933",2],["3.516","600.1034",3],["3.5159","27.481",1],["3.5158","27.33",1],["3.5157","583.1898",2],["3.5156","24.6819",2],["3.5154","25",1],["3.5153","0.429",1],["3.5152","453.9204",3],["3.5151","2131.592",4],["3.515","335",3],["3.5149","37.1586",1],["3.5147","41.6759",1],["3.5146","54.569",1],["3.5145","70.3515",1],["3.5143","68.206",3],["3.5142","359.4538",2],["3.5139","45.4123",2],["3.5137","71.673",2],["3.5136","25",1],["3.5135","300",1],["3.5134","442.57",2],["3.5132","83.3518",1],["3.513","1245.2529",3],["3.5127","20",1],["3.512","284.1353",1],["3.5119","1136.8319",1],["3.5113","56.9351",1],["3.5111","588.1898",2],["3.5109","255.0946",1],["3.5105","48.65",1],["3.5103","50.2",1],["3.5098","720",1],["3.5096","148.95",1],["3.5094","570.5758",2],["3.509","2.386",1],["3.5089","0.4065",1],["3.5087","282.3859",2],["3.5086","145.036",2],["3.5084","2.386",1],["3.5082","90.98",1],["3.5081","2.386",1],["3.5079","2.386",1],["3.5078","857.6229",2],["3.5075","2.386",1],["3.5074","284.1877",1],["3.5073","100",1],["3.5071","100",1],["3.507","768.4159",3],["3.5069","313.0863",2],["3.5068","426.2938",1],["3.5066","568.3594",1],["3.5063","1136.6865",1],["3.5059","0.3",1],["3.5054","9.9999",1],["3.5053","0.2",1],["3.5051","392.428",1],["3.505","13.79",1],["3.5048","99.5497",2],["3.5047","78.5331",2],["3.5046","2153",1],["3.5041","5983.999",1],["3.5037","668.5682",1],["3.5036","160.5948",1],["3.5024","534.8075",1],["3.5014","28.5604",1],["3.5011","91",1],["3.5","1058.8771",2],["3.4997","50.2",1],["3.4985","3430.0414",1],["3.4949","232.0591",1],["3.4942","21521",1],["3.493","2",1],["3.4928","2",1],["3.4925","0.44",1],["3.4917","142.0656",1],["3.49","2051.8826",4],["3.488","280.7459",1],["3.4852","643.4038",1],["3.4851","86.0807",1],["3.485","213.2436",1],["3.484","0.1",1],["3.4811","144.3399",1],["3.4808","89",1],["3.4803","12.1999",1],["3.4801","2390",1],["3.48","930.8453",9],["3.4791","310",1],["3.4768","206",1],["3.4767","0.9415",1],["3.4754","1.4387",1],["3.4728","20",1],["3.4701","1219.2873",1],["3.47","1904.3139",7],["3.468","0.4035",1],["3.4667","0.1",1],["3.4666","3020.0101",1],["3.465","10",1],["3.464","0.4485",1],["3.462","2119.6556",1],["3.46","1305.6113",8],["3.4589","8.0228",1],["3.457","100",1],["3.456","70.3859",2],["3.4538","20",1],["3.4536","4323.9486",2],["3.4531","827.0427",1],["3.4528","0.439",1],["3.4522","8.0381",1],["3.4513","441.1873",1],["3.4512","50.707",1],["3.451","87.0902",1],["3.4509","200",1],["3.4506","100",1],["3.4505","86.4045",2],["3.45","12409.4595",28],["3.4494","0.5365",2],["3.449","10761",1],["3.4482","8.0476",1],["3.4469","0.449",1],["3.445","2000",1],["3.4427","14",1],["3.4421","100",1],["3.4416","8.0631",1],["3.4404","1",1],["3.44","4580.733",11],["3.4388","1868.2085",1],["3.438","937.7246",2],["3.4367","1500",1],["3.4366","62",1],["3.436","29.8743",1],["3.4356","25.4801",1],["3.4349","4.3086",1],["3.4343","43.2402",1],["3.433","2.0688",1],["3.4322","2.7335",2],["3.432","93.3233",1],["3.4302","328.8301",2],["3.43","4440.8158",11],["3.4288","754.574",2],["3.4283","125.7043",2],["3.428","744.3154",2],["3.4273","5460",1],["3.4258","50",1],["3.4255","109.005",1],["3.4248","100",1],["3.4241","129.2048",2],["3.4233","5.3598",1],["3.4228","4498.866",1],["3.4222","3.5435",1],["3.4217","404.3252",2],["3.4211","1000",1],["3.4208","31",1],["3.42","1834.024",9],["3.4175","300",1],["3.4162","400",1],["3.4152","0.1",1],["3.4151","4.3336",1],["3.415","1.5974",1],["3.414","1146",1],["3.4134","306.4246",1],["3.4129","7.5556",1],["3.4111","198.5188",1],["3.4109","500",1],["3.4106","4305",1],["3.41","2150.7635",13],["3.4085","4.342",1],["3.4054","5.6985",1],["3.4019","5.438",1],["3.4015","1010.846",1],["3.4009","8610",1],["3.4005","1.9122",1],["3.4004","1",1],["3.4","27081.1806",67],["3.3955","3.2682",1],["3.3953","5.4486",1],["3.3937","1591.3805",1],["3.39","3221.4155",8],["3.3899","3.2736",1],["3.3888","1500",2],["3.3887","5.4592",1],["3.385","117.0969",2],["3.3821","5.4699",1],["3.382","100.0529",1],["3.3818","172.0164",1],["3.3815","165.6288",1],["3.381","887.3115",1],["3.3808","100",1]],"timestamp":"2019-03-04T00:15:04.155Z","checksum":-2036653089}]}` var dataResponse okgroup.WebsocketDataResponse - err := common.JSONDecode([]byte(orderbookPartialJSON), &dataResponse) + err := json.Unmarshal([]byte(orderbookPartialJSON), &dataResponse) if err != nil { t.Error(err) } diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index f033e4c5..fa9d6e2a 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -1,6 +1,7 @@ package okex import ( + "encoding/json" "fmt" "net/http" "strconv" @@ -1614,7 +1615,7 @@ func TestOrderBookUpdateChecksumCalculator(t *testing.T) { original := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"BTC-USDT","asks":[["3864.6786","0.145",1],["3864.7682","0.005",1],["3864.9851","0.57",1],["3864.9852","0.30137754",1],["3864.9986","2.81818419",1],["3864.9995","0.002",1],["3865","0.0597",1],["3865.0309","0.4",1],["3865.1995","0.004",1],["3865.3995","0.004",1],["3865.5995","0.004",1],["3865.7995","0.004",1],["3865.9995","0.004",1],["3866.0961","0.25865886",1],["3866.1995","0.004",1],["3866.3995","0.004",1],["3866.4004","0.3243",2],["3866.5995","0.004",1],["3866.7633","0.44247086",1],["3866.7995","0.004",1],["3866.9197","0.511",1],["3867.256","0.51716256",1],["3867.3951","0.02588112",1],["3867.4014","0.025",1],["3867.4566","0.02499999",1],["3867.4675","4.01155057",5],["3867.5515","1.1",1],["3867.6113","0.009",1],["3867.7349","0.026",1],["3867.7781","0.03738652",1],["3867.9163","0.0521",1],["3868.0381","0.34354941",1],["3868.0436","0.051",1],["3868.0657","0.90552172",3],["3868.1819","0.03863346",1],["3868.2013","0.194",1],["3868.346","0.051",1],["3868.3863","0.01155",1],["3868.7716","0.009",1],["3868.947","0.025",1],["3868.98","0.001",1],["3869.0764","1.03487931",1],["3869.2773","0.07724578",1],["3869.4039","0.025",1],["3869.4068","1.03",1],["3869.7068","2.06976398",1],["3870","0.5",1],["3870.0465","0.01",1],["3870.7042","0.02099651",1],["3870.9451","2.07047375",1],["3871.5254","1.2",1],["3871.5596","0.001",1],["3871.6605","0.01035032",1],["3871.7179","2.07047375",1],["3871.8816","0.51751625",1],["3872.1","0.75",1],["3872.2464","0.0646",1],["3872.3747","0.283",1],["3872.4039","0.2",1],["3872.7655","0.23179307",1],["3872.8005","2.06976398",1],["3873.1509","2",1],["3873.3215","0.26",1],["3874.1392","0.001",1],["3874.1487","3.88224364",4],["3874.1685","1.8",1],["3874.5571","0.08974762",1],["3874.734","2.06976398",1],["3874.99","0.3",1],["3875","1.001",2],["3875.0041","1.03505051",1],["3875.45","0.3",1],["3875.4766","0.15",1],["3875.7057","0.51751625",1],["3876","0.001",1],["3876.68","0.3",1],["3876.7188","0.001",1],["3877","0.75",1],["3877.31","0.035",1],["3877.38","0.3",1],["3877.7","0.3",1],["3877.88","0.3",1],["3878.0364","0.34770122",1],["3878.4525","0.48579748",1],["3878.4955","0.02812511",1],["3878.8855","0.00258579",1],["3878.9605","0.895",1],["3879","0.001",1],["3879.2984","0.002",2],["3879.432","0.001",1],["3879.6313","6",1],["3879.9999","0.002",2],["3880","1.25132834",5],["3880.2526","0.04075162",1],["3880.7145","0.0647",1],["3881.2469","1.883",1],["3881.878","0.002",2],["3884.4576","0.002",2],["3885","0.002",2],["3885.2233","0.28304103",1],["3885.7416","18",1],["3886","0.001",1],["3886.1554","5.4",1],["3887","0.001",1],["3887.0372","0.002",2],["3887.2559","0.05214011",1],["3887.9238","0.0019",1],["3888","0.15810538",4],["3889","0.001",1],["3889.5175","0.50510653",1],["3889.6168","0.002",2],["3889.9999","0.001",1],["3890","2.34968109",4],["3890.5222","0.00257806",1],["3891.2659","5",1],["3891.9999","0.00893897",1],["3892.1964","0.002",2],["3892.4358","0.0176",1],["3893.1388","1.4279",1],["3894","0.0026321",1],["3894.776","0.001",1],["3895","1.501",2],["3895.379","0.25881288",1],["3897","0.05",1],["3897.3556","0.001",1],["3897.8432","0.73708079",1],["3898","3.31353018",7],["3898.4462","4.757",1],["3898.6","0.47159638",1],["3898.8769","0.0129",1],["3899","6",2],["3899.6516","0.025",1],["3899.9352","0.001",1],["3899.9999","0.013",2],["3900","22.37447743",24],["3900.9999","0.07763916",1],["3901","0.10192487",1],["3902.1937","0.00257034",1],["3902.3991","1.5532141",1],["3902.5148","0.001",1],["3904","1.49331984",1],["3904.9999","0.95905447",1],["3905","0.501",2],["3905.0944","0.001",1],["3905.61","0.099",1],["3905.6801","0.54343686",1],["3906.2901","0.0258",1],["3907.674","0.001",1],["3907.85","1.35778084",1],["3908","0.03846153",1],["3908.23","1.95189531",1],["3908.906","0.03148978",1],["3909","0.001",1],["3909.9999","0.01398721",2],["3910","0.016",2],["3910.2536","0.001",1],["3912.5406","0.88270517",1],["3912.8332","0.001",1],["3913","1.2640608",1],["3913.87","1.69114184",1],["3913.9003","0.00256266",1],["3914","1.21766411",1],["3915","0.001",1],["3915.4128","0.001",1],["3915.7425","6.848",1],["3916","0.0050949",1],["3917.36","1.28658296",1],["3917.9924","0.001",1],["3919","0.001",1],["3919.9999","0.001",1],["3920","1.21171832",3],["3920.0002","0.20217038",1],["3920.572","0.001",1],["3921","0.128",1],["3923.0756","0.00148064",1],["3923.1516","0.001",1],["3923.86","1.38831714",1],["3925","0.01867801",2],["3925.642","0.00255499",1],["3925.7312","0.001",1],["3926","0.04290757",1],["3927","0.023",1],["3927.3175","0.01212865",1],["3927.65","1.51375612",1],["3928","0.5",1],["3928.3108","0.001",1],["3929","0.001",1],["3929.9999","0.01519338",2],["3930","0.0174985",3],["3930.21","1.49335799",1],["3930.8904","0.001",1],["3932.2999","0.01953",1],["3932.8962","7.96",1],["3933.0387","11.808",1],["3933.47","0.001",1],["3934","1.40839932",1],["3935","0.001",1],["3936.8","0.62879518",1],["3937.23","1.56977841",1],["3937.4189","0.00254735",1]],"bids":[["3864.5217","0.00540709",1],["3864.5216","0.14068758",2],["3864.2275","0.01033576",1],["3864.0989","0.00825047",1],["3864.0273","0.38",1],["3864.0272","0.4",1],["3863.9957","0.01083539",1],["3863.9184","0.01653723",1],["3863.8282","0.25588165",1],["3863.8153","0.154",1],["3863.7791","1.14122492",1],["3863.6866","0.01733662",1],["3863.6093","0.02645958",1],["3863.3775","0.02773862",1],["3863.0297","0.513",1],["3863.0286","1.1028564",2],["3862.8489","0.01",1],["3862.5972","0.01890179",1],["3862.3431","0.01152944",1],["3862.313","0.009",1],["3862.2445","0.90551002",3],["3862.0734","0.014",1],["3862.0539","0.64976067",1],["3861.8586","0.025",1],["3861.7888","0.025",1],["3861.7673","0.008",1],["3861.5785","0.01",1],["3861.3895","0.005",1],["3861.3338","0.25875855",1],["3861.161","0.01",1],["3861.1111","0.03863352",1],["3861.0732","0.51703882",1],["3860.9116","0.17754895",1],["3860.75","0.19",1],["3860.6554","0.015",1],["3860.6172","0.005",1],["3860.6088","0.008",1],["3860.4724","0.12940042",1],["3860.4424","0.25880084",1],["3860.42","0.01",1],["3860.3725","0.51760102",1],["3859.8449","0.005",1],["3859.8285","0.03738652",1],["3859.7638","0.07726703",1],["3859.4502","0.008",1],["3859.3772","0.05173471",1],["3859.3409","0.194",1],["3859","5",1],["3858.827","0.0521",1],["3858.8208","0.001",1],["3858.679","0.26",1],["3858.4814","0.07477305",1],["3858.1669","1.03503422",1],["3857.6005","0.006",1],["3857.4005","0.004",1],["3857.2005","0.004",1],["3857.1871","1.218",1],["3857.0005","0.004",1],["3856.8135","0.0646",1],["3856.8005","0.004",1],["3856.2412","0.001",1],["3856.2349","1.03503422",1],["3856.0197","0.01037339",1],["3855.8781","0.23178117",1],["3855.8005","0.004",1],["3855.7165","0.00259355",1],["3855.4858","0.25875855",1],["3854.4584","0.01",1],["3853.6616","0.001",1],["3853.1373","0.92",1],["3852.5072","0.48599702",1],["3851.3926","0.13008333",1],["3851.082","0.001",1],["3850.9317","2",1],["3850.6359","0.34770165",1],["3850.2058","0.51751624",1],["3850.0823","0.15",1],["3850.0042","0.5175171",1],["3850","0.001",1],["3849.6325","1.8",1],["3849.41","0.3",1],["3848.9686","1.85",1],["3848.7426","0.18511466",1],["3848.52","0.3",1],["3848.5024","0.001",1],["3848.42","0.3",1],["3848.1618","2.204",1],["3847.77","0.3",1],["3847.48","0.3",1],["3847.3581","2.05",1],["3846.8259","0.0646",1],["3846.59","0.3",1],["3846.49","0.3",1],["3845.9228","0.001",1],["3844.184","0.00260133",1],["3844.0092","6.3",1],["3843.3432","0.001",1],["3841","0.06300963",1],["3840.7636","0.001",1],["3840","0.201",3],["3839.7681","18",1],["3839.5328","0.05214011",1],["3838.184","0.001",1],["3837.2344","0.27589557",1],["3836.6479","5.2",1],["3836","2.37196773",3],["3835.6044","0.001",1],["3833.6053","0.25873556",1],["3833.0248","0.001",1],["3833","0.8726502",1],["3832.6859","0.00260913",1],["3832","0.007",1],["3831.637","6",1],["3831.0602","0.001",1],["3830.4452","0.001",1],["3830","0.20375718",4],["3829.7125","0.07833486",1],["3829.6283","0.3519681",1],["3829","0.0039261",1],["3827.8656","0.001",1],["3826.0001","0.53251232",1],["3826","0.0509",1],["3825.7834","0.00698562",1],["3825.286","0.001",1],["3823.0001","0.03010127",1],["3822.8014","0.00261588",1],["3822.7064","0.001",1],["3822.2","1",1],["3822.1121","0.35994101",1],["3821.2222","0.00261696",1],["3821","0.001",1],["3820.1268","0.001",1],["3820","1.12992803",4],["3819","0.01331195",2],["3817.5472","0.001",1],["3816","1.13807184",2],["3815.8343","0.32463428",1],["3815.7834","0.00525295",1],["3815","28.99386799",4],["3814.9676","0.001",1],["3813","0.91303023",4],["3812.388","0.002",2],["3811.2257","0.07",1],["3810","0.32573997",2],["3809.8084","0.001",1],["3809.7928","0.00262481",1],["3807.2288","0.001",1],["3806.8421","0.07003461",1],["3806","0.19",1],["3805.8041","0.05678805",1],["3805","1.01",2],["3804.6492","0.001",1],["3804.3551","0.1",1],["3803","0.005",1],["3802.22","2.05042631",1],["3802.0696","0.001",1],["3802","1.63290092",1],["3801.2257","0.07",1],["3801","57.4",3],["3800.9853","0.02492278",1],["3800.8421","0.06503533",1],["3800.7844","0.02812628",1],["3800.0001","0.00409473",1],["3800","17.91401074",15],["3799.49","0.001",1],["3799","0.1",1],["3796.9104","0.001",1],["3796","9.00128053",2],["3795.5441","0.0028",1],["3794.3308","0.001",1],["3791","55",1],["3790.7777","0.07",1],["3790","12.03238184",7],["3789","1",1],["3788","0.21110454",2],["3787.2959","9",1],["3786.592","0.001",1],["3786","9.01916822",2],["3785","12.87914268",5],["3784.0124","0.001",1],["3781.4328","0.002",2],["3781","56.3",2],["3780.7777","0.07",1],["3780","23.41537654",10],["3778.8532","0.002",2],["3776","9",1],["3774","0.003",1],["3772.2481","0.06901672",1],["3771","55.1",2],["3770.7777","0.07",1],["3770","7.30268416",5],["3769","0.25",1],["3768","1.3725",3],["3766.66","0.02",1],["3766","7.64837924",2],["3765.58","1.22775492",1],["3762.58","1.22873383",1],["3761","51.68262164",1],["3760.8031","0.0399",1],["3760.7777","0.07",1]],"timestamp":"2019-03-06T23:19:17.705Z","checksum":-1785549915}]}` update := `{"table":"spot/depth","action":"update","data":[{"instrument_id":"BTC-USDT","asks":[["3864.6786","0",0],["3864.9852","0",0],["3865.9994","0.48402971",1],["3866.4004","0.001",1],["3866.7995","0.3273",2],["3867.4566","0",0],["3867.7031","0.025",1],["3868.0436","0",0],["3868.346","0",0],["3868.3695","0.051",1],["3870.9243","0.642",1],["3874.9942","0.51751796",1],["3875.7057","0",0],["3939","0.001",1]],"bids":[["3864.55","0.0565449",1],["3863.8282","0",0],["3863.8153","0",0],["3863.7898","0.01320077",1],["3863.4807","0.02112123",1],["3863.3002","0.04233533",1],["3863.1717","0.03379397",1],["3863.0685","0.04438179",1],["3863.0286","0.7362564",1],["3862.9912","0.06773651",1],["3862.8626","0.05407035",1],["3862.7595","0.07101087",1],["3862.313","0.3756",2],["3862.1848","0.012",1],["3862.0734","0",0],["3861.8391","0.025",1],["3861.7888","0",0],["3856.6716","0.38893641",1],["3768","0",0],["3766.66","0",0],["3766","0",0],["3765.58","0",0],["3762.58","0",0],["3761","0",0],["3760.8031","0",0],["3760.7777","0",0]],"timestamp":"2019-03-06T23:19:18.239Z","checksum":-1587788848}]}` var dataResponse okgroup.WebsocketDataResponse - err := common.JSONDecode([]byte(original), &dataResponse) + err := json.Unmarshal([]byte(original), &dataResponse) if err != nil { t.Error(err) } @@ -1624,7 +1625,7 @@ func TestOrderBookUpdateChecksumCalculator(t *testing.T) { return } var updateResponse okgroup.WebsocketDataResponse - err = common.JSONDecode([]byte(update), &updateResponse) + err = json.Unmarshal([]byte(update), &updateResponse) if err != nil { t.Error(err) } @@ -1641,7 +1642,7 @@ func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { original := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"WAVES-BTC","asks":[["0.000714","1.15414979",1],["0.000715","3.3",2],["0.000717","426.71348",2],["0.000719","140.84507042",1],["0.00072","590.77",1],["0.000721","991.77",1],["0.000724","0.3532032",1],["0.000725","58.82698567",1],["0.000726","1033.15469748",2],["0.000729","0.35320321",1],["0.00073","352.77",1],["0.000735","0.38469748",1],["0.000736","625.77",1],["0.00075191","152.44796961",1],["0.00075192","114.3359772",1],["0.00075193","85.7519829",1],["0.00075194","64.31398718",1],["0.00075195","48.23549038",1],["0.00075196","36.17661779",1],["0.00075199","61.04804253",1],["0.0007591","70.71318474",1],["0.0007621","53.03488855",1],["0.00076211","39.77616642",1],["0.00076212","29.83212481",1],["0.0007635","22.37409361",1],["0.00076351","29.36599786",2],["0.00076352","9.43907074",1],["0.00076353","7.07930306",1],["0.00076354","14.15860612",1],["0.00076355","3.53965153",1],["0.00076369","3.53965153",1],["0.0008","34.36841101",1],["0.00082858","1.69936503",1],["0.00083232","2.8",1],["0.00084","15.69220129",1],["0.00085","4.42785042",1],["0.00088","0.1",1],["0.000891","0.1",1],["0.0009","12.41486491",2],["0.00093","5",1],["0.0012","12.31486492",1],["0.00531314","6.91803114",1],["0.00799999","0.02",1],["0.0084","0.05989",1],["0.00931314","5.18852336",1],["0.0799999","0.02",1],["0.499","6.00423396",1],["0.5","0.4995",1],["0.799999","0.02",1],["4.99","2",1],["5","3.98583144",1],["7.99999999","0.02",1],["79.99999999","0.02",1],["799.99999999","0.02986704",1]],"bids":[["0.000709","222.91679881",3],["0.000703","0.47161952",1],["0.000701","140.73015789",2],["0.0007","0.3",1],["0.000699","401",1],["0.000698","232.61801667",2],["0.000689","0.71396896",1],["0.000688","0.69910125",1],["0.000613","227.54771052",1],["0.0005","0.01",1],["0.00026789","3.69905341",1],["0.000238","2.4",1],["0.00022","0.53",1],["0.0000055","374.09871696",1],["0.00000056","222",1],["0.00000055","736.84761363",1],["0.0000002","999",1],["0.00000009","1222.22222417",1],["0.00000008","20868.64520447",1],["0.00000002","110000",1],["0.00000001","10000",1]],"timestamp":"2019-03-12T22:22:42.274Z","checksum":1319037905}]}` update := `{"table":"spot/depth","action":"update","data":[{"instrument_id":"WAVES-BTC","asks":[["0.000715","100.48199596",3],["0.000716","62.21679881",1]],"bids":[["0.000713","38.95772168",1]],"timestamp":"2019-03-12T22:22:42.938Z","checksum":-131160897}]}` var dataResponse okgroup.WebsocketDataResponse - err := common.JSONDecode([]byte(original), &dataResponse) + err := json.Unmarshal([]byte(original), &dataResponse) if err != nil { t.Error(err) } @@ -1651,7 +1652,7 @@ func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { return } var updateResponse okgroup.WebsocketDataResponse - err = common.JSONDecode([]byte(update), &updateResponse) + err = json.Unmarshal([]byte(update), &updateResponse) if err != nil { t.Error(err) } @@ -1666,7 +1667,7 @@ func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { func TestOrderBookPartialChecksumCalculator(t *testing.T) { orderbookPartialJSON := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"EOS-USDT","asks":[["3.5196","0.1077",1],["3.5198","21.71",1],["3.5199","51.1805",1],["3.5208","75.09",1],["3.521","196.3333",1],["3.5213","0.1",1],["3.5218","39.276",2],["3.5219","395.6334",1],["3.522","27.956",1],["3.5222","404.9595",1],["3.5225","300",1],["3.5227","143.5442",2],["3.523","42.4746",1],["3.5231","852.64",2],["3.5235","34.9602",1],["3.5237","442.0918",2],["3.5238","352.8404",2],["3.5239","341.6759",2],["3.524","84.9493",1],["3.5241","148.4882",1],["3.5242","261.64",1],["3.5243","142.045",1],["3.5246","10",1],["3.5247","284.0788",1],["3.5248","720",1],["3.5249","89.2518",2],["3.5251","1201.8965",2],["3.5254","426.2938",1],["3.5255","213.0863",1],["3.5257","568.1576",1],["3.5258","0.3",1],["3.5259","34.4602",1],["3.526","0.1",1],["3.5263","850.771",1],["3.5265","5.9",1],["3.5268","10.5064",2],["3.5272","1136.8965",1],["3.5274","255.1481",1],["3.5276","29.5374",1],["3.5278","50",1],["3.5282","284.1797",1],["3.5283","1136.8965",1],["3.5284","0.4275",1],["3.5285","100",1],["3.5292","90.9",1],["3.5298","0.2",1],["3.5303","568.1576",1],["3.5305","279.9999",1],["3.532","0.409",1],["3.5321","568.1576",1],["3.5326","6016.8756",1],["3.5328","4.9849",1],["3.533","92.88",2],["3.5343","1200.2383",2],["3.5344","100",1],["3.535","359.7047",1],["3.5354","100",1],["3.5355","100",1],["3.5356","10",1],["3.5358","200",2],["3.5362","435.139",1],["3.5365","2152",1],["3.5366","284.1756",1],["3.5367","568.4644",1],["3.5369","33.9878",1],["3.537","337.1191",2],["3.5373","0.4045",1],["3.5383","1136.7188",1],["3.5386","12.1614",1],["3.5387","90.89",1],["3.54","4.54",1],["3.5423","90.8",1],["3.5436","0.1",1],["3.5454","853.4156",1],["3.5468","142.0656",1],["3.5491","0.0008",1],["3.55","14478.8206",6],["3.5537","21521",1],["3.5555","11.53",1],["3.5573","50.6001",1],["3.5599","4591.4221",1],["3.56","1227.0002",4],["3.5603","2670",1],["3.5608","58.6638",1],["3.5613","0.1",1],["3.5621","45.9473",1],["3.57","2141.7274",3],["3.5712","2956.9816",1],["3.5717","27.9978",1],["3.5718","0.9285",1],["3.5739","299.73",1],["3.5761","864",1],["3.579","22.5225",1],["3.5791","38.26",2],["3.58","7618.4634",5],["3.5801","457.2184",1],["3.582","24.5",1],["3.5822","1572.6425",1],["3.5845","14.1438",1],["3.585","527.169",1],["3.5865","20",1],["3.5867","4490",1],["3.5876","39.0493",1],["3.5879","392.9083",1],["3.5888","436.42",2],["3.5896","50",1],["3.59","2608.9128",8],["3.5913","19.5246",1],["3.5938","7082",1],["3.597","0.1",1],["3.5979","399",1],["3.5995","315.1509",1],["3.5999","2566.2648",1],["3.6","18511.2292",35],["3.603","22.3379",2],["3.605","499.5",1],["3.6055","100",1],["3.6058","499.5",1],["3.608","1021.1485",1],["3.61","11755.4596",13],["3.611","42.8571",1],["3.6131","6690",1],["3.6157","19.5247",1],["3.618","2500",1],["3.6197","525.7146",1],["3.6198","0.4455",1],["3.62","6440.6295",8],["3.6219","0.4175",1],["3.6237","168",1],["3.6265","0.1001",1],["3.628","64.9345",1],["3.63","4435.4985",6],["3.6308","1.7815",1],["3.6331","0.1",1],["3.6338","355.527",2],["3.6358","50",1],["3.6363","2074.7096",1],["3.6376","4000",1],["3.6396","11090",1],["3.6399","0.4055",1],["3.64","4161.9805",4],["3.6437","117.6524",1],["3.648","190",1],["3.6488","200",1],["3.65","11740.5045",25],["3.6512","0.1",1],["3.6521","728",1],["3.6555","100",1],["3.6598","36.6914",1],["3.66","4331.2148",6],["3.6638","200",1],["3.6673","100",1],["3.6679","38",1],["3.6688","2",1],["3.6695","0.1",1],["3.67","7984.698",6],["3.672","300",1],["3.6777","257.8247",1],["3.6789","393.4217",2],["3.68","9202.3222",11],["3.6818","500",1],["3.6823","299.7",1],["3.6839","422.3748",1],["3.685","100",1],["3.6878","0.1",1],["3.6888","72.0958",2],["3.6889","2876",1],["3.689","28",1],["3.6891","28",1],["3.6892","28",1],["3.6895","28",1],["3.6898","28",1],["3.69","643.96",7],["3.6908","118",2],["3.691","28",1],["3.6916","28",1],["3.6918","28",1],["3.6926","28",1],["3.6928","28",1],["3.6932","28",1],["3.6933","200",1],["3.6935","28",1],["3.6936","28",1],["3.6938","28",1],["3.694","28",1],["3.698","1498.5",1],["3.6988","2014.2004",2],["3.7","21904.2689",22],["3.7029","71.95",1],["3.704","3690.1362",1],["3.7055","100",1],["3.7063","0.1",1],["3.71","4421.3468",4],["3.719","17.3491",1],["3.72","1304.5995",3],["3.7211","10",1],["3.7248","0.1",1],["3.725","1900",1],["3.73","31.1785",2],["3.7375","38",1]],"bids":[["3.5182","151.5343",6],["3.5181","0.3691",1],["3.518","271.3967",2],["3.5179","257.8352",1],["3.5178","12.3811",1],["3.5173","34.1921",2],["3.5171","1013.8256",2],["3.517","272.1119",2],["3.5168","395.3376",1],["3.5166","317.1756",2],["3.5165","348.302",3],["3.5164","142.0414",1],["3.5163","96.8933",2],["3.516","600.1034",3],["3.5159","27.481",1],["3.5158","27.33",1],["3.5157","583.1898",2],["3.5156","24.6819",2],["3.5154","25",1],["3.5153","0.429",1],["3.5152","453.9204",3],["3.5151","2131.592",4],["3.515","335",3],["3.5149","37.1586",1],["3.5147","41.6759",1],["3.5146","54.569",1],["3.5145","70.3515",1],["3.5143","68.206",3],["3.5142","359.4538",2],["3.5139","45.4123",2],["3.5137","71.673",2],["3.5136","25",1],["3.5135","300",1],["3.5134","442.57",2],["3.5132","83.3518",1],["3.513","1245.2529",3],["3.5127","20",1],["3.512","284.1353",1],["3.5119","1136.8319",1],["3.5113","56.9351",1],["3.5111","588.1898",2],["3.5109","255.0946",1],["3.5105","48.65",1],["3.5103","50.2",1],["3.5098","720",1],["3.5096","148.95",1],["3.5094","570.5758",2],["3.509","2.386",1],["3.5089","0.4065",1],["3.5087","282.3859",2],["3.5086","145.036",2],["3.5084","2.386",1],["3.5082","90.98",1],["3.5081","2.386",1],["3.5079","2.386",1],["3.5078","857.6229",2],["3.5075","2.386",1],["3.5074","284.1877",1],["3.5073","100",1],["3.5071","100",1],["3.507","768.4159",3],["3.5069","313.0863",2],["3.5068","426.2938",1],["3.5066","568.3594",1],["3.5063","1136.6865",1],["3.5059","0.3",1],["3.5054","9.9999",1],["3.5053","0.2",1],["3.5051","392.428",1],["3.505","13.79",1],["3.5048","99.5497",2],["3.5047","78.5331",2],["3.5046","2153",1],["3.5041","5983.999",1],["3.5037","668.5682",1],["3.5036","160.5948",1],["3.5024","534.8075",1],["3.5014","28.5604",1],["3.5011","91",1],["3.5","1058.8771",2],["3.4997","50.2",1],["3.4985","3430.0414",1],["3.4949","232.0591",1],["3.4942","21521",1],["3.493","2",1],["3.4928","2",1],["3.4925","0.44",1],["3.4917","142.0656",1],["3.49","2051.8826",4],["3.488","280.7459",1],["3.4852","643.4038",1],["3.4851","86.0807",1],["3.485","213.2436",1],["3.484","0.1",1],["3.4811","144.3399",1],["3.4808","89",1],["3.4803","12.1999",1],["3.4801","2390",1],["3.48","930.8453",9],["3.4791","310",1],["3.4768","206",1],["3.4767","0.9415",1],["3.4754","1.4387",1],["3.4728","20",1],["3.4701","1219.2873",1],["3.47","1904.3139",7],["3.468","0.4035",1],["3.4667","0.1",1],["3.4666","3020.0101",1],["3.465","10",1],["3.464","0.4485",1],["3.462","2119.6556",1],["3.46","1305.6113",8],["3.4589","8.0228",1],["3.457","100",1],["3.456","70.3859",2],["3.4538","20",1],["3.4536","4323.9486",2],["3.4531","827.0427",1],["3.4528","0.439",1],["3.4522","8.0381",1],["3.4513","441.1873",1],["3.4512","50.707",1],["3.451","87.0902",1],["3.4509","200",1],["3.4506","100",1],["3.4505","86.4045",2],["3.45","12409.4595",28],["3.4494","0.5365",2],["3.449","10761",1],["3.4482","8.0476",1],["3.4469","0.449",1],["3.445","2000",1],["3.4427","14",1],["3.4421","100",1],["3.4416","8.0631",1],["3.4404","1",1],["3.44","4580.733",11],["3.4388","1868.2085",1],["3.438","937.7246",2],["3.4367","1500",1],["3.4366","62",1],["3.436","29.8743",1],["3.4356","25.4801",1],["3.4349","4.3086",1],["3.4343","43.2402",1],["3.433","2.0688",1],["3.4322","2.7335",2],["3.432","93.3233",1],["3.4302","328.8301",2],["3.43","4440.8158",11],["3.4288","754.574",2],["3.4283","125.7043",2],["3.428","744.3154",2],["3.4273","5460",1],["3.4258","50",1],["3.4255","109.005",1],["3.4248","100",1],["3.4241","129.2048",2],["3.4233","5.3598",1],["3.4228","4498.866",1],["3.4222","3.5435",1],["3.4217","404.3252",2],["3.4211","1000",1],["3.4208","31",1],["3.42","1834.024",9],["3.4175","300",1],["3.4162","400",1],["3.4152","0.1",1],["3.4151","4.3336",1],["3.415","1.5974",1],["3.414","1146",1],["3.4134","306.4246",1],["3.4129","7.5556",1],["3.4111","198.5188",1],["3.4109","500",1],["3.4106","4305",1],["3.41","2150.7635",13],["3.4085","4.342",1],["3.4054","5.6985",1],["3.4019","5.438",1],["3.4015","1010.846",1],["3.4009","8610",1],["3.4005","1.9122",1],["3.4004","1",1],["3.4","27081.1806",67],["3.3955","3.2682",1],["3.3953","5.4486",1],["3.3937","1591.3805",1],["3.39","3221.4155",8],["3.3899","3.2736",1],["3.3888","1500",2],["3.3887","5.4592",1],["3.385","117.0969",2],["3.3821","5.4699",1],["3.382","100.0529",1],["3.3818","172.0164",1],["3.3815","165.6288",1],["3.381","887.3115",1],["3.3808","100",1]],"timestamp":"2019-03-04T00:15:04.155Z","checksum":-2036653089}]}` var dataResponse okgroup.WebsocketDataResponse - err := common.JSONDecode([]byte(orderbookPartialJSON), &dataResponse) + err := json.Unmarshal([]byte(orderbookPartialJSON), &dataResponse) if err != nil { t.Error(err) } diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go index 1b24871e..afd95cf5 100644 --- a/exchanges/okgroup/okgroup.go +++ b/exchanges/okgroup/okgroup.go @@ -13,7 +13,6 @@ import ( "time" "github.com/google/go-querystring/query" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -566,7 +565,7 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d payload := []byte("") if data != nil { - payload, err = common.JSONEncode(data) + payload, err = json.Marshal(data) if err != nil { return errors.New("sendHTTPRequest: Unable to JSON request") } @@ -617,7 +616,7 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d return err } - err = common.JSONDecode(intermediary, &errCap) + err = json.Unmarshal(intermediary, &errCap) if err == nil { if errCap.ErrorMessage != "" { return fmt.Errorf("error: %v", errCap.ErrorMessage) @@ -631,7 +630,7 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d } } - return common.JSONDecode(intermediary, result) + return json.Unmarshal(intermediary, result) } // SetCheckVarDefaults sets main variables that will be used in requests because diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index 19ee8b79..13f95a20 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -1,6 +1,7 @@ package okgroup import ( + "encoding/json" "errors" "fmt" "hash/crc32" @@ -11,7 +12,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -226,7 +226,7 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) { } o.Websocket.TrafficAlert <- struct{}{} var dataResponse WebsocketDataResponse - err = common.JSONDecode(resp.Raw, &dataResponse) + err = json.Unmarshal(resp.Raw, &dataResponse) if err == nil && dataResponse.Table != "" { if len(dataResponse.Data) > 0 { o.WsHandleDataResponse(&dataResponse) @@ -234,7 +234,7 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) { continue } var errorResponse WebsocketErrorResponse - err = common.JSONDecode(resp.Raw, &errorResponse) + err = json.Unmarshal(resp.Raw, &errorResponse) if err == nil && errorResponse.ErrorCode > 0 { if o.Verbose { log.Debugf(log.ExchangeSys, @@ -247,7 +247,7 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) { continue } var eventResponse WebsocketEventResponse - err = common.JSONDecode(resp.Raw, &eventResponse) + err = json.Unmarshal(resp.Raw, &eventResponse) if err == nil && eventResponse.Event != "" { if eventResponse.Event == "login" { o.Websocket.SetCanUseAuthenticatedEndpoints(eventResponse.Success) diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index 3ce7d352..a89aca41 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -1,6 +1,7 @@ package poloniex import ( + "encoding/json" "net/http" "testing" "time" @@ -425,7 +426,7 @@ func TestWsHandleAccountData(t *testing.T) { } for i := range jsons { var result [][]interface{} - err := common.JSONDecode([]byte(jsons[i]), &result) + err := json.Unmarshal([]byte(jsons[i]), &result) if err != nil { t.Error(err) } diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index 648a64c8..b5078c45 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -1,6 +1,7 @@ package poloniex import ( + "encoding/json" "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -95,7 +95,7 @@ func (p *Poloniex) WsHandleData() { } p.Websocket.TrafficAlert <- struct{}{} var result interface{} - err = common.JSONDecode(resp.Raw, &result) + err = json.Unmarshal(resp.Raw, &result) if err != nil { p.Websocket.DataHandler <- err continue diff --git a/exchanges/request/request.go b/exchanges/request/request.go index 10141a36..ed2cfd9a 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -2,6 +2,7 @@ package request import ( "compress/gzip" + "encoding/json" "errors" "fmt" "io" @@ -329,7 +330,7 @@ func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, re } if result != nil { - return common.JSONDecode(contents, result) + return json.Unmarshal(contents, result) } return nil diff --git a/exchanges/websocket/wshandler/wshandler.go b/exchanges/websocket/wshandler/wshandler.go index 596a631b..27895cbd 100644 --- a/exchanges/websocket/wshandler/wshandler.go +++ b/exchanges/websocket/wshandler/wshandler.go @@ -4,6 +4,7 @@ import ( "bytes" "compress/flate" "compress/gzip" + "encoding/json" "errors" "fmt" "io/ioutil" @@ -15,7 +16,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" log "github.com/thrasher-corp/gocryptotrader/logger" ) @@ -653,7 +653,7 @@ func (w *WebsocketConnection) SendMessage(data interface{}) error { if !w.IsConnected() { return fmt.Errorf("%v cannot send message to a disconnected websocket", w.ExchangeName) } - json, err := common.JSONEncode(data) + json, err := json.Marshal(data) if err != nil { return err } diff --git a/exchanges/websocket/wshandler/wshandler_test.go b/exchanges/websocket/wshandler/wshandler_test.go index 0c7cb7ec..25ac23f6 100644 --- a/exchanges/websocket/wshandler/wshandler_test.go +++ b/exchanges/websocket/wshandler/wshandler_test.go @@ -4,6 +4,7 @@ import ( "bytes" "compress/flate" "compress/gzip" + "encoding/json" "errors" "net" "net/http" @@ -14,7 +15,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" ) @@ -666,7 +666,7 @@ func readMessages(wc *WebsocketConnection, t *testing.T) { return } var incoming testResponse - err = common.JSONDecode(resp.Raw, &incoming) + err = json.Unmarshal(resp.Raw, &incoming) if err != nil { t.Error(err) return diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index 63fe8428..998d4016 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" @@ -339,7 +338,7 @@ func (z *ZB) SendAuthenticatedHTTPRequest(httpMethod string, params url.Values, return err } - err = common.JSONDecode(intermediary, &errCap) + err = json.Unmarshal(intermediary, &errCap) if err == nil { if errCap.Code > 1000 { return fmt.Errorf("sendAuthenticatedHTTPRequest error code: %d message %s", @@ -348,7 +347,7 @@ func (z *ZB) SendAuthenticatedHTTPRequest(httpMethod string, params url.Values, } } - return common.JSONDecode(intermediary, result) + return json.Unmarshal(intermediary, result) } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index d097d406..c0bf48d4 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -1,6 +1,7 @@ package zb import ( + "encoding/json" "fmt" "net/http" "testing" @@ -506,10 +507,10 @@ func TestGetDepositAddress(t *testing.T) { // TestZBInvalidJSON ZB sends poorly formed JSON. this tests the JSON fixer // Then JSON decode it to test if successful func TestZBInvalidJSON(t *testing.T) { - json := `{"success":true,"code":1000,"channel":"getSubUserList","message":"[{"isOpenApi":false,"memo":"Memo","userName":"hello@imgoodthanksandyou.com@good","userId":1337,"isFreez":false}]","no":"0"}` - fixedJSON := z.wsFixInvalidJSON([]byte(json)) + data := `{"success":true,"code":1000,"channel":"getSubUserList","message":"[{"isOpenApi":false,"memo":"Memo","userName":"hello@imgoodthanksandyou.com@good","userId":1337,"isFreez":false}]","no":"0"}` + fixedJSON := z.wsFixInvalidJSON([]byte(data)) var response WsGetSubUserListResponse - err := common.JSONDecode(fixedJSON, &response) + err := json.Unmarshal(fixedJSON, &response) if err != nil { t.Fatal(err) } @@ -517,10 +518,10 @@ func TestZBInvalidJSON(t *testing.T) { t.Fatal("Expected extracted JSON USERID to equal 1337") } - json = `{"success":true,"code":1000,"channel":"createSubUserKey","message":"{"apiKey":"thisisnotareallykeyyousillybilly","apiSecret":"lol"}","no":"123"}` - fixedJSON = z.wsFixInvalidJSON([]byte(json)) + data = `{"success":true,"code":1000,"channel":"createSubUserKey","message":"{"apiKey":"thisisnotareallykeyyousillybilly","apiSecret":"lol"}","no":"123"}` + fixedJSON = z.wsFixInvalidJSON([]byte(data)) var response2 WsRequestResponse - err = common.JSONDecode(fixedJSON, &response2) + err = json.Unmarshal(fixedJSON, &response2) if err != nil { t.Error(err) } diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 0ac7d39d..4560f582 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -1,6 +1,7 @@ package zb import ( + "encoding/json" "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -64,7 +64,7 @@ func (z *ZB) WsHandleData() { z.Websocket.TrafficAlert <- struct{}{} fixedJSON := z.wsFixInvalidJSON(resp.Raw) var result Generic - err = common.JSONDecode(fixedJSON, &result) + err = json.Unmarshal(fixedJSON, &result) if err != nil { z.Websocket.DataHandler <- err continue @@ -80,7 +80,7 @@ func (z *ZB) WsHandleData() { switch { case strings.Contains(result.Channel, "markets"): var markets Markets - err := common.JSONDecode(result.Data, &markets) + err := json.Unmarshal(result.Data, &markets) if err != nil { z.Websocket.DataHandler <- err continue @@ -89,7 +89,7 @@ func (z *ZB) WsHandleData() { case strings.Contains(result.Channel, "ticker"): cPair := strings.Split(result.Channel, "_") var ticker WsTicker - err := common.JSONDecode(fixedJSON, &ticker) + err := json.Unmarshal(fixedJSON, &ticker) if err != nil { z.Websocket.DataHandler <- err continue @@ -111,7 +111,7 @@ func (z *ZB) WsHandleData() { case strings.Contains(result.Channel, "depth"): var depth WsDepth - err := common.JSONDecode(fixedJSON, &depth) + err := json.Unmarshal(fixedJSON, &depth) if err != nil { z.Websocket.DataHandler <- err continue @@ -156,7 +156,7 @@ func (z *ZB) WsHandleData() { case strings.Contains(result.Channel, "trades"): var trades WsTrades - err := common.JSONDecode(fixedJSON, &trades) + err := json.Unmarshal(fixedJSON, &trades) if err != nil { z.Websocket.DataHandler <- err continue @@ -252,7 +252,7 @@ func (z *ZB) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription } func (z *ZB) wsGenerateSignature(request interface{}) string { - jsonResponse, err := common.JSONEncode(request) + jsonResponse, err := json.Marshal(request) if err != nil { log.Error(log.ExchangeSys, err) return "" @@ -296,7 +296,7 @@ func (z *ZB) wsAddSubUser(username, password string) (*WsGetSubUserListResponse, return nil, err } var genericResponse Generic - err = common.JSONDecode(resp, &genericResponse) + err = json.Unmarshal(resp, &genericResponse) if err != nil { return nil, err } @@ -304,7 +304,7 @@ func (z *ZB) wsAddSubUser(username, password string) (*WsGetSubUserListResponse, return nil, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, genericResponse.Message, wsErrCodes[genericResponse.Code]) } var response WsGetSubUserListResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } @@ -327,7 +327,7 @@ func (z *ZB) wsGetSubUserList() (*WsGetSubUserListResponse, error) { return nil, err } var response WsGetSubUserListResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } @@ -358,7 +358,7 @@ func (z *ZB) wsDoTransferFunds(pair currency.Code, amount float64, fromUserName, return nil, err } var response WsRequestResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } @@ -391,7 +391,7 @@ func (z *ZB) wsCreateSubUserKey(assetPerm, entrustPerm, leverPerm, moneyPerm boo return nil, err } var response WsRequestResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } @@ -421,7 +421,7 @@ func (z *ZB) wsSubmitOrder(pair currency.Pair, amount, price float64, tradeType return nil, err } var response WsSubmitOrderResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } @@ -449,7 +449,7 @@ func (z *ZB) wsCancelOrder(pair currency.Pair, orderID int64) (*WsCancelOrderRes return nil, err } var response WsCancelOrderResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } @@ -477,7 +477,7 @@ func (z *ZB) wsGetOrder(pair currency.Pair, orderID int64) (*WsGetOrderResponse, return nil, err } var response WsGetOrderResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } @@ -505,7 +505,7 @@ func (z *ZB) wsGetOrders(pair currency.Pair, pageIndex, tradeType int64) (*WsGet return nil, err } var response WsGetOrdersResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } @@ -534,7 +534,7 @@ func (z *ZB) wsGetOrdersIgnoreTradeType(pair currency.Pair, pageIndex, pageSize return nil, err } var response WsGetOrdersIgnoreTradeTypeResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } @@ -561,7 +561,7 @@ func (z *ZB) wsGetAccountInfoRequest() (*WsGetAccountInfoResponse, error) { return nil, err } var response WsGetAccountInfoResponse - err = common.JSONDecode(resp, &response) + err = json.Unmarshal(resp, &response) if err != nil { return nil, err } diff --git a/logger/logger_types.go b/logger/logger_types.go index a9608744..19a8aabf 100644 --- a/logger/logger_types.go +++ b/logger/logger_types.go @@ -5,9 +5,31 @@ import ( "sync" ) -const timestampFormat = " 02/01/2006 15:04:05 " +const ( + timestampFormat = " 02/01/2006 15:04:05 " + spacer = "|" +) -const spacer = "|" +var ( + logger = &Logger{} + // FileLoggingConfiguredCorrectly flag set during config check if file logging meets requirements + FileLoggingConfiguredCorrectly bool + // GlobalLogConfig holds global configuration options for logger + GlobalLogConfig = &Config{} + // GlobalLogFile hold global configuration options for file logger + GlobalLogFile = &Rotate{} + + eventPool = &sync.Pool{ + New: func() interface{} { + return &LogEvent{ + data: make([]byte, 0, 80), + } + }, + } + + // LogPath system path to store log files in + LogPath string +) // Config holds configuration settings loaded from bot config type Config struct { @@ -72,24 +94,3 @@ type multiWriter struct { writers []io.Writer mu sync.RWMutex } - -var ( - logger = &Logger{} - // FileLoggingConfiguredCorrectly flag set during config check if file logging meets requirements - FileLoggingConfiguredCorrectly bool - // GlobalLogConfig holds global configuration options for logger - GlobalLogConfig = &Config{} - // GlobalLogFile hold global configuration options for file logger - GlobalLogFile = &Rotate{} - - eventPool = &sync.Pool{ - New: func() interface{} { - return &LogEvent{ - data: make([]byte, 0, 80), - } - }, - } - - // LogPath system path to store log files in - LogPath string -) From 49b9eced66b72dbcf0787c2d128943af585c893f Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 3 Dec 2019 13:33:28 +1100 Subject: [PATCH 67/71] Always set go.sum/go.mod line endings as LF (#391) Saves having Windows users experience pain and suffering as documented here: https://github.com/golang/go/issues/31870 --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..4fea60b9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +go.mod text eol=lf +go.sum text eol=lf \ No newline at end of file From a33ddcfa0a1b90b833a39058359e71d304a55374 Mon Sep 17 00:00:00 2001 From: Adam <31364354+MadCozBadd@users.noreply.github.com> Date: Wed, 4 Dec 2019 11:53:35 +1100 Subject: [PATCH 68/71] Engine: BTC Markets V3 Updates (#385) * Broken WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP * Errors Fixed * PR Changes * PR Changes * PR Changes * PR Changes * PR Changes * PR Changes * PR Changes * PR Changes * PR Changes * Offline Fees Fixed * MarketCandles fixed and constants added * t.log deleted * Broken WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP * Errors Fixed * PR Changes * PR Changes * PR Changes * MarketCandles fixed and constants added * t.log deleted * Added BSB and Order Status * WIP * Websocket and BatchPlaceCancelOrder and other minor nits fixed * Linter Issues Fixed * Function Name Change * Replacing b.GetMarkets with b.GetEnabledPairs * Pagination param changes and PlaceCancelBatch changes * Merge Conflicts WIP * Linter Issue Fixed * optional params fixed --- cmd/exchange_wrapper_issues/types.go | 2 +- exchanges/bitfinex/bitfinex.go | 2 +- exchanges/bitfinex/bitfinex_test.go | 4 +- exchanges/bithumb/bithumb_test.go | 2 +- exchanges/bithumb/bithumb_wrapper.go | 5 +- exchanges/bitstamp/bitstamp_test.go | 4 +- exchanges/btcmarkets/btcmarkets.go | 983 ++++++++++++------- exchanges/btcmarkets/btcmarkets_test.go | 779 +++++++-------- exchanges/btcmarkets/btcmarkets_types.go | 378 +++++-- exchanges/btcmarkets/btcmarkets_websocket.go | 109 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 531 +++++----- exchanges/exchange_types.go | 3 +- exchanges/order/order_types.go | 37 +- 13 files changed, 1710 insertions(+), 1129 deletions(-) diff --git a/cmd/exchange_wrapper_issues/types.go b/cmd/exchange_wrapper_issues/types.go index 60427c52..08b7e467 100644 --- a/cmd/exchange_wrapper_issues/types.go +++ b/cmd/exchange_wrapper_issues/types.go @@ -73,7 +73,7 @@ type EndpointResponse struct { // Bank contains all required data for a wrapper withdrawal request type Bank struct { BankAccountName string `json:"bankAccountName"` - BankAccountNumber float64 `json:"bankAccountNumber"` + BankAccountNumber string `json:"bankAccountNumber"` BankAddress string `json:"bankAddress"` BankCity string `json:"bankCity"` BankCountry string `json:"bankCountry"` diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 5e185410..f85e4c9d 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -548,7 +548,7 @@ func (b *Bitfinex) WithdrawFIAT(withdrawalType, walletType string, withdrawReque req["walletselected"] = walletType req["amount"] = strconv.FormatFloat(withdrawRequest.Amount, 'f', -1, 64) req["account_name"] = withdrawRequest.BankAccountName - req["account_number"] = strconv.FormatFloat(withdrawRequest.BankAccountNumber, 'f', -1, 64) + req["account_number"] = withdrawRequest.BankAccountNumber req["bank_name"] = withdrawRequest.BankName req["bank_address"] = withdrawRequest.BankAddress req["bank_city"] = withdrawRequest.BankCity diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 32208cd9..04f44be4 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -917,7 +917,7 @@ func TestWithdrawFiat(t *testing.T) { Description: "WITHDRAW IT ALL", }, BankAccountName: "Satoshi Nakamoto", - BankAccountNumber: 12345, + BankAccountNumber: "12345", BankAddress: "123 Fake St", BankCity: "Tarry Town", BankCountry: "Hyrule", @@ -952,7 +952,7 @@ func TestWithdrawInternationalBank(t *testing.T) { Description: "WITHDRAW IT ALL", }, BankAccountName: "Satoshi Nakamoto", - BankAccountNumber: 12345, + BankAccountNumber: "12345", BankAddress: "123 Fake St", BankCity: "Tarry Town", BankCountry: "Hyrule", diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index a4127426..8f82f248 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -497,7 +497,7 @@ func TestWithdrawFiat(t *testing.T) { Description: "WITHDRAW IT ALL", }, BankAccountName: "Satoshi Nakamoto", - BankAccountNumber: 12345, + BankAccountNumber: "12345", BankCode: 123, BankAddress: "123 Fake St", BankCity: "Tarry Town", diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index c4bf45a8..91f8f983 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "math" - "strconv" "sync" "time" @@ -419,9 +418,7 @@ func (b *Bithumb) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawReques return "", errors.New("only KRW is supported") } bankDetails := fmt.Sprintf("%v_%v", withdrawRequest.BankCode, withdrawRequest.BankName) - bankAccountNumber := strconv.FormatFloat(withdrawRequest.BankAccountNumber, 'f', -1, 64) - withdrawAmountInt := int64(withdrawRequest.Amount) - resp, err := b.RequestKRWWithdraw(bankDetails, bankAccountNumber, withdrawAmountInt) + resp, err := b.RequestKRWWithdraw(bankDetails, withdrawRequest.BankAccountNumber, int64(withdrawRequest.Amount)) if err != nil { return "", err } diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index c541dd8a..77d8b575 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -489,7 +489,7 @@ func TestWithdrawFiat(t *testing.T) { Description: "WITHDRAW IT ALL", }, BankAccountName: "Satoshi Nakamoto", - BankAccountNumber: 12345, + BankAccountNumber: "12345", BankAddress: "123 Fake St", BankCity: "Tarry Town", BankCountry: "AU", @@ -527,7 +527,7 @@ func TestWithdrawInternationalBank(t *testing.T) { Description: "WITHDRAW IT ALL", }, BankAccountName: "Satoshi Nakamoto", - BankAccountNumber: 12345, + BankAccountNumber: "12345", BankAddress: "123 Fake St", BankCity: "Tarry Town", BankCountry: "AU", diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 11882328..a477f483 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -5,44 +5,75 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" "net/url" + "strconv" "strings" + "time" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - log "github.com/thrasher-corp/gocryptotrader/logger" ) const ( - btcMarketsAPIURL = "https://api.btcmarkets.net" - btcMarketsAPIVersion = "0" - btcMarketsAccountBalance = "/account/balance" - btcMarketsTradingFee = "/account/%s/%s/tradingfee" - btcMarketsOrderCreate = "/order/create" - btcMarketsOrderCancel = "/order/cancel" - btcMarketsOrderHistory = "/order/history" - btcMarketsOrderOpen = "/order/open" - btcMarketsOrderTradeHistory = "/order/trade/history" - btcMarketsOrderDetail = "/order/detail" - btcMarketsWithdrawCrypto = "/fundtransfer/withdrawCrypto" - btcMarketsWithdrawAud = "/fundtransfer/withdrawEFT" + btcMarketsAPIURL = "https://api.btcmarkets.net" + btcMarketsAPIVersion = "/v3" - // Status Values - orderStatusNew = "New" - orderStatusPlaced = "Placed" - orderStatusFailed = "Failed" - orderStatusError = "Error" - orderStatusCancelled = "Cancelled" - orderStatusPartiallyCancelled = "Partially Cancelled" - orderStatusFullyMatched = "Fully Matched" - orderStatusPartiallyMatched = "Partially Matched" + // UnAuthenticated EPs + btcMarketsAllMarkets = "/markets/" + btcMarketsGetTicker = "/ticker/" + btcMarketsGetTrades = "/trades?" + btcMarketOrderBooks = "/orderbook?" + btcMarketsCandles = "/candles?" + btcMarketsTickers = "/tickers?" + btcMarketsMultipleOrderbooks = "/orderbooks?" + btcMarketsGetTime = "/time" + btcMarketsWithdrawalFees = "/withdrawal-fees" + btcMarketsUnauthPath = btcMarketsAPIURL + btcMarketsAPIVersion + btcMarketsAllMarkets - btcmarketsAuthLimit = 10 - btcmarketsUnauthLimit = 25 + // Authenticated EPs + btcMarketsAccountBalance = "/accounts/me/balances" + btcMarketsTradingFees = "/accounts/me/trading-fees" + btcMarketsTransactions = "/accounts/me/transactions" + btcMarketsOrders = "/orders" + btcMarketsTradeHistory = "/trades" + btcMarketsWithdrawals = "/withdrawals" + btcMarketsDeposits = "/deposits" + btcMarketsTransfers = "/transfers" + btcMarketsAddresses = "/addresses" + btcMarketsAssets = "/assets" + btcMarketsReports = "/reports" + btcMarketsBatchOrders = "/batchorders" + + btcmarketsAuthLimit = 3 + btcmarketsUnauthLimit = 50 + + orderFailed = "Failed" + orderPartiallyCancelled = "Partially Cancelled" + orderCancelled = "Cancelled" + orderFullyMatched = "FullyMatched" + orderPartiallyMatched = "Partially Matched" + orderPlaced = "Placed" + orderAccepted = "Accepted" + + ask = "ask" + limit = "Limit" + market = "Market" + stopLimit = "Stop Limit" + stop = "Stop" + takeProfit = "Take Profit" + + subscribe = "subscribe" + fundChange = "fundChange" + orderChange = "orderChange" + heartbeat = "heartbeat" + tick = "tick" + wsOB = "orderbook" + trade = "trade" ) // BTCMarkets is the overarching type across the BTCMarkets package @@ -53,316 +84,579 @@ type BTCMarkets struct { // GetMarkets returns the BTCMarkets instruments func (b *BTCMarkets) GetMarkets() ([]Market, error) { - type marketsResp struct { - Response - Markets []Market `json:"markets"` - } - - var resp marketsResp - path := fmt.Sprintf("%s/v2/market/active", b.API.Endpoints.URL) - - err := b.SendHTTPRequest(path, &resp) - if err != nil { - return nil, err - } - - if !resp.Success { - return nil, fmt.Errorf("%s unable to get markets: %s", b.Name, resp.ErrorMessage) - } - - return resp.Markets, nil + var resp []Market + return resp, b.SendHTTPRequest(btcMarketsUnauthPath, &resp) } // GetTicker returns a ticker // symbol - example "btc" or "ltc" -func (b *BTCMarkets) GetTicker(firstPair, secondPair string) (Ticker, error) { - tick := Ticker{} - path := fmt.Sprintf("%s/market/%s/%s/tick", - b.API.Endpoints.URL, - strings.ToUpper(firstPair), - strings.ToUpper(secondPair)) - - return tick, b.SendHTTPRequest(path, &tick) -} - -// GetOrderbook returns current orderbook -// symbol - example "btc" or "ltc" -func (b *BTCMarkets) GetOrderbook(firstPair, secondPair string) (Orderbook, error) { - orderbook := Orderbook{} - path := fmt.Sprintf("%s/market/%s/%s/orderbook", - b.API.Endpoints.URL, - strings.ToUpper(firstPair), - strings.ToUpper(secondPair)) - - return orderbook, b.SendHTTPRequest(path, &orderbook) +func (b *BTCMarkets) GetTicker(marketID string) (Ticker, error) { + var tick Ticker + return tick, b.SendHTTPRequest(btcMarketsUnauthPath+marketID+btcMarketsGetTicker, &tick) } // GetTrades returns executed trades on the exchange -// symbol - example "btc" or "ltc" -// values - optional paramater "since" example values.Set(since, "59868345231") -func (b *BTCMarkets) GetTrades(firstPair, secondPair string, values url.Values) ([]Trade, error) { +func (b *BTCMarkets) GetTrades(marketID string, before, after, limit int64) ([]Trade, error) { + if (before > 0) && (after >= 0) { + return nil, errors.New("BTCMarkets only supports either before or after, not both") + } var trades []Trade - path := common.EncodeURLValues(fmt.Sprintf("%s/market/%s/%s/trades", - b.API.Endpoints.URL, strings.ToUpper(firstPair), - strings.ToUpper(secondPair)), values) - - return trades, b.SendHTTPRequest(path, &trades) + params := url.Values{} + if before > 0 { + params.Set("before", strconv.FormatInt(before, 10)) + } + if after >= 0 { + params.Set("after", strconv.FormatInt(after, 10)) + } + if limit > 0 { + params.Set("limit", strconv.FormatInt(limit, 10)) + } + return trades, b.SendHTTPRequest(btcMarketsUnauthPath+marketID+btcMarketsGetTrades+params.Encode(), + &trades) } -// NewOrder requests a new order and returns an ID -// currency - example "AUD" -// instrument - example "BTC" -// price - example 13000000000 (i.e x 100000000) -// amount - example 100000000 (i.e x 100000000) -// orderside - example "Bid" or "Ask" -// orderType - example "limit" -// clientReq - example "abc-cdf-1000" -func (b *BTCMarkets) NewOrder(instrument, currency string, price, amount float64, orderSide, orderType, clientReq string) (int64, error) { - newPrice := int64(price * float64(common.SatoshisPerBTC)) - newVolume := int64(amount * float64(common.SatoshisPerBTC)) - - order := OrderToGo{ - Currency: strings.ToUpper(currency), - Instrument: strings.ToUpper(instrument), - Price: newPrice, - Volume: newVolume, - OrderSide: orderSide, - OrderType: orderType, - ClientRequestID: clientReq, +// GetOrderbook returns current orderbook +func (b *BTCMarkets) GetOrderbook(marketID string, level int64) (Orderbook, error) { + var orderbook Orderbook + var temp tempOrderbook + params := url.Values{} + if level != 0 { + params.Set("level", strconv.FormatInt(level, 10)) } - - resp := Response{} - - err := b.SendAuthenticatedRequest(http.MethodPost, btcMarketsOrderCreate, order, &resp) + err := b.SendHTTPRequest(btcMarketsUnauthPath+marketID+btcMarketOrderBooks+params.Encode(), + &temp) if err != nil { - return 0, err + return orderbook, err } - if !resp.Success { - return 0, fmt.Errorf("%s Unable to place order. Error message: %s", b.Name, resp.ErrorMessage) - } - return int64(resp.ID), nil -} - -// CancelExistingOrder cancels an order by its ID -// orderID - id for order example "1337" -func (b *BTCMarkets) CancelExistingOrder(orderID []int64) ([]ResponseDetails, error) { - resp := Response{} - type CancelOrder struct { - OrderIDs []int64 `json:"orderIds"` - } - orders := CancelOrder{} - orders.OrderIDs = append(orders.OrderIDs, orderID...) - - err := b.SendAuthenticatedRequest(http.MethodPost, btcMarketsOrderCancel, orders, &resp) - if err != nil { - return resp.Responses, err - } - - if !resp.Success { - return resp.Responses, fmt.Errorf("%s Unable to cancel order. Error message: %s", b.Name, resp.ErrorMessage) - } - - return resp.Responses, nil -} - -// GetOrders returns current order information on the exchange -// currency - example "AUD" -// instrument - example "BTC" -// limit - example "10" -// since - since a time example "33434568724" -// historic - if false just normal Orders open -func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, historic bool) ([]Order, error) { - req := make(map[string]interface{}) - - if currency != "" { - req["currency"] = strings.ToUpper(currency) - } - if instrument != "" { - req["instrument"] = strings.ToUpper(instrument) - } - if limit != 0 { - req["limit"] = limit - } - if since != 0 { - req["since"] = since - } - - path := btcMarketsOrderOpen - if historic { - path = btcMarketsOrderHistory - } - - resp := Response{} - - err := b.SendAuthenticatedRequest(http.MethodPost, path, req, &resp) - if err != nil { - return nil, err - } - - if !resp.Success { - return nil, errors.New(resp.ErrorMessage) - } - - for i := range resp.Orders { - resp.Orders[i].Price /= common.SatoshisPerBTC - resp.Orders[i].OpenVolume /= common.SatoshisPerBTC - resp.Orders[i].Volume /= common.SatoshisPerBTC - - for j := range resp.Orders[i].Trades { - resp.Orders[i].Trades[j].Fee /= common.SatoshisPerBTC - resp.Orders[i].Trades[j].Price /= common.SatoshisPerBTC - resp.Orders[i].Trades[j].Volume /= common.SatoshisPerBTC + orderbook.MarketID = temp.MarketID + orderbook.SnapshotID = temp.SnapshotID + for x := range temp.Asks { + price, err := strconv.ParseFloat(temp.Asks[x][0], 64) + if err != nil { + return orderbook, err } + amount, err := strconv.ParseFloat(temp.Asks[x][1], 64) + if err != nil { + return orderbook, err + } + orderbook.Asks = append(orderbook.Asks, OBData{ + Price: price, + Volume: amount, + }) } - - return resp.Orders, nil + for a := range temp.Bids { + price, err := strconv.ParseFloat(temp.Bids[a][0], 64) + if err != nil { + return orderbook, err + } + amount, err := strconv.ParseFloat(temp.Bids[a][1], 64) + if err != nil { + return orderbook, err + } + orderbook.Bids = append(orderbook.Bids, OBData{ + Price: price, + Volume: amount, + }) + } + return orderbook, nil } -// GetOpenOrders returns all open orders -func (b *BTCMarkets) GetOpenOrders() ([]Order, error) { - type marketsResp struct { - Response +// GetMarketCandles gets candles for specified currency pair +func (b *BTCMarkets) GetMarketCandles(marketID, timeWindow, from, to string, before, after, limit int64) ([]MarketCandle, error) { + if (before > 0) && (after >= 0) { + return nil, errors.New("BTCMarkets only supports either before or after, not both") } - req := make(map[string]interface{}) - var resp marketsResp - path := fmt.Sprintf("/v2/order/open") - - err := b.SendAuthenticatedRequest(http.MethodGet, path, req, &resp) + var marketCandles []MarketCandle + var temp [][]string + params := url.Values{} + if timeWindow != "" { + params.Set("timeWindow", timeWindow) + } + if from != "" { + params.Set("from", from) + } + if to != "" { + params.Set("to", to) + } + if before > 0 { + params.Set("before", strconv.FormatInt(before, 10)) + } + if after >= 0 { + params.Set("after", strconv.FormatInt(after, 10)) + } + if limit > 0 { + params.Set("limit", strconv.FormatInt(limit, 10)) + } + err := b.SendHTTPRequest(btcMarketsUnauthPath+marketID+btcMarketsCandles+params.Encode(), + &temp) if err != nil { - return nil, err + return marketCandles, err } - - if !resp.Success { - return nil, errors.New(resp.ErrorMessage) - } - - for i := range resp.Orders { - resp.Orders[i].Price /= common.SatoshisPerBTC - resp.Orders[i].OpenVolume /= common.SatoshisPerBTC - resp.Orders[i].Volume /= common.SatoshisPerBTC - - for j := range resp.Orders[i].Trades { - resp.Orders[i].Trades[j].Fee /= common.SatoshisPerBTC - resp.Orders[i].Trades[j].Price /= common.SatoshisPerBTC - resp.Orders[i].Trades[j].Volume /= common.SatoshisPerBTC + var tempData MarketCandle + var tempTime time.Time + for x := range temp { + tempTime, err = time.Parse(time.RFC3339, temp[x][0]) + if err != nil { + return marketCandles, err } + tempData.Time = tempTime + tempData.Open, err = strconv.ParseFloat(temp[x][1], 64) + if err != nil { + return marketCandles, err + } + tempData.High, err = strconv.ParseFloat(temp[x][2], 64) + if err != nil { + return marketCandles, err + } + tempData.Low, err = strconv.ParseFloat(temp[x][3], 64) + if err != nil { + return marketCandles, err + } + tempData.Close, err = strconv.ParseFloat(temp[x][4], 64) + if err != nil { + return marketCandles, err + } + tempData.Volume, err = strconv.ParseFloat(temp[x][5], 64) + if err != nil { + return marketCandles, err + } + marketCandles = append(marketCandles, tempData) } - - return resp.Orders, nil + return marketCandles, nil } -// GetOrderDetail returns order information an a specific order -// orderID - example "1337" -func (b *BTCMarkets) GetOrderDetail(orderID []int64) ([]Order, error) { - type OrderDetail struct { - OrderIDs []int64 `json:"orderIds"` +// GetTickers gets multiple tickers +func (b *BTCMarkets) GetTickers(marketIDs []string) ([]Ticker, error) { + var tickers []Ticker + params := url.Values{} + for x := range marketIDs { + params.Add("marketId", marketIDs[x]) } - orders := OrderDetail{} - orders.OrderIDs = append(orders.OrderIDs, orderID...) + return tickers, b.SendHTTPRequest(btcMarketsUnauthPath+btcMarketsTickers+params.Encode(), + &tickers) +} - resp := Response{} - - err := b.SendAuthenticatedRequest(http.MethodPost, btcMarketsOrderDetail, orders, &resp) +// GetMultipleOrderbooks gets orderbooks +func (b *BTCMarkets) GetMultipleOrderbooks(marketIDs []string) ([]Orderbook, error) { + var orderbooks []Orderbook + var temp []tempOrderbook + var tempOB Orderbook + params := url.Values{} + for x := range marketIDs { + params.Add("marketId", marketIDs[x]) + } + err := b.SendHTTPRequest(btcMarketsUnauthPath+btcMarketsMultipleOrderbooks+params.Encode(), + &temp) if err != nil { - return nil, err + return orderbooks, err } - - if !resp.Success { - return nil, errors.New(resp.ErrorMessage) - } - - for i := range resp.Orders { - resp.Orders[i].Price /= common.SatoshisPerBTC - resp.Orders[i].OpenVolume /= common.SatoshisPerBTC - resp.Orders[i].Volume /= common.SatoshisPerBTC - - for x := range resp.Orders[i].Trades { - resp.Orders[i].Trades[x].Fee /= common.SatoshisPerBTC - resp.Orders[i].Trades[x].Price /= common.SatoshisPerBTC - resp.Orders[i].Trades[x].Volume /= common.SatoshisPerBTC + for i := range temp { + var price, volume float64 + tempOB.MarketID = temp[i].MarketID + tempOB.SnapshotID = temp[i].SnapshotID + for a := range temp[i].Asks { + volume, err = strconv.ParseFloat(temp[i].Asks[a][1], 64) + if err != nil { + return orderbooks, err + } + price, err = strconv.ParseFloat(temp[i].Asks[a][0], 64) + if err != nil { + return orderbooks, err + } + tempOB.Asks = append(tempOB.Asks, OBData{Price: price, Volume: volume}) } + for y := range temp[i].Bids { + volume, err = strconv.ParseFloat(temp[i].Bids[y][1], 64) + if err != nil { + return orderbooks, err + } + price, err = strconv.ParseFloat(temp[i].Bids[y][0], 64) + if err != nil { + return orderbooks, err + } + tempOB.Bids = append(tempOB.Bids, OBData{Price: price, Volume: volume}) + } + orderbooks = append(orderbooks, tempOB) } - return resp.Orders, nil + return orderbooks, nil +} + +// GetServerTime gets time from btcmarkets +func (b *BTCMarkets) GetServerTime() (time.Time, error) { + var resp TimeResp + return resp.Time, b.SendHTTPRequest(btcMarketsAPIURL+btcMarketsAPIVersion+btcMarketsGetTime, + &resp) } // GetAccountBalance returns the full account balance -func (b *BTCMarkets) GetAccountBalance() ([]AccountBalance, error) { - var balance []AccountBalance - - err := b.SendAuthenticatedRequest(http.MethodGet, btcMarketsAccountBalance, nil, &balance) - if err != nil { - return nil, err - } - - // All values are returned in Satoshis, even for fiat currencies. - for i := range balance { - balance[i].Balance /= common.SatoshisPerBTC - balance[i].PendingFunds /= common.SatoshisPerBTC - } - return balance, nil +func (b *BTCMarkets) GetAccountBalance() ([]AccountData, error) { + var resp []AccountData + return resp, + b.SendAuthenticatedRequest(http.MethodGet, + btcMarketsAccountBalance, + nil, + &resp) } -// GetTradingFee returns the account's trading fee for a currency pair -func (b *BTCMarkets) GetTradingFee(base, quote currency.Code) (TradingFee, error) { - var tradingFee TradingFee - path := fmt.Sprintf(btcMarketsTradingFee, base, quote) - return tradingFee, b.SendAuthenticatedRequest(http.MethodGet, path, nil, &tradingFee) -} - -// WithdrawCrypto withdraws cryptocurrency into a designated address -func (b *BTCMarkets) WithdrawCrypto(amount float64, currency, address string) (string, error) { - newAmount := int64(amount * float64(common.SatoshisPerBTC)) - - req := WithdrawRequestCrypto{ - Amount: newAmount, - Currency: strings.ToUpper(currency), - Address: address, - } - - resp := Response{} - err := b.SendAuthenticatedRequest(http.MethodPost, - btcMarketsWithdrawCrypto, - req, +// GetTradingFees returns trading fees for all pairs based on trading activity +func (b *BTCMarkets) GetTradingFees() (TradingFeeResponse, error) { + var resp TradingFeeResponse + return resp, b.SendAuthenticatedRequest(http.MethodGet, + btcMarketsTradingFees, + nil, &resp) - if err != nil { - return "", err - } - - if !resp.Success { - return "", errors.New(resp.ErrorMessage) - } - - return resp.Status, nil } -// WithdrawAUD withdraws AUD into a designated bank address -// Does not return a TxID! -func (b *BTCMarkets) WithdrawAUD(accountName, accountNumber, bankName, bsbNumber string, amount float64) (string, error) { - newAmount := int64(amount * float64(common.SatoshisPerBTC)) - - req := WithdrawRequestAUD{ - AccountName: accountName, - AccountNumber: accountNumber, - BankName: bankName, - BSBNumber: bsbNumber, - Amount: newAmount, - Currency: "AUD", +// GetTradeHistory returns trade history +func (b *BTCMarkets) GetTradeHistory(marketID, orderID string, before, after, limit int64) ([]TradeHistoryData, error) { + if (before > 0) && (after >= 0) { + return nil, errors.New("BTCMarkets only supports either before or after, not both") } - - resp := Response{} - err := b.SendAuthenticatedRequest(http.MethodPost, btcMarketsWithdrawAud, - req, + var resp []TradeHistoryData + params := url.Values{} + if marketID != "" { + params.Set("marketId", marketID) + } + if orderID != "" { + params.Set("orderId", orderID) + } + if before > 0 { + params.Set("before", strconv.FormatInt(before, 10)) + } + if after >= 0 { + params.Set("after", strconv.FormatInt(after, 10)) + } + if limit > 0 { + params.Set("limit", strconv.FormatInt(limit, 10)) + } + return resp, b.SendAuthenticatedRequest(http.MethodGet, + common.EncodeURLValues(btcMarketsTradeHistory, params), + nil, &resp) - if err != nil { - return "", err - } +} - if !resp.Success { - return "", errors.New(resp.ErrorMessage) - } +// GetTradeByID returns the singular trade of the ID given +func (b *BTCMarkets) GetTradeByID(id string) (TradeHistoryData, error) { + var resp TradeHistoryData + return resp, b.SendAuthenticatedRequest(http.MethodGet, + btcMarketsTradeHistory+"/"+id, + nil, + &resp) +} - return resp.Status, nil +// NewOrder requests a new order and returns an ID +func (b *BTCMarkets) NewOrder(marketID string, price, amount float64, orderType, side string, triggerPrice, + targetAmount float64, timeInForce string, postOnly bool, selfTrade, clientOrderID string) (OrderData, error) { + var resp OrderData + req := make(map[string]interface{}) + req["marketId"] = marketID + req["price"] = strconv.FormatFloat(price, 'f', -1, 64) + req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) + req["type"] = orderType + req["side"] = side + if orderType == stopLimit || orderType == takeProfit || orderType == stop { + req["triggerPrice"] = strconv.FormatFloat(triggerPrice, 'f', -1, 64) + } + if targetAmount > 0 { + req["targetAmount"] = strconv.FormatFloat(targetAmount, 'f', -1, 64) + } + if timeInForce != "" { + req["timeInForce"] = timeInForce + } + req["postOnly"] = postOnly + if selfTrade != "" { + req["selfTrade"] = selfTrade + } + if clientOrderID != "" { + req["clientOrderID"] = clientOrderID + } + return resp, b.SendAuthenticatedRequest(http.MethodPost, btcMarketsOrders, req, &resp) +} + +// GetOrders returns current order information on the exchange +func (b *BTCMarkets) GetOrders(marketID string, before, after, limit int64, status string) ([]OrderData, error) { + if (before > 0) && (after >= 0) { + return nil, errors.New("BTCMarkets only supports either before or after, not both") + } + var resp []OrderData + params := url.Values{} + if marketID != "" { + params.Set("marketId", marketID) + } + if before > 0 { + params.Set("before", strconv.FormatInt(before, 10)) + } + if after >= 0 { + params.Set("after", strconv.FormatInt(after, 10)) + } + if limit > 0 { + params.Set("limit", strconv.FormatInt(limit, 10)) + } + if status != "" { + params.Set("status", status) + } + return resp, b.SendAuthenticatedRequest(http.MethodGet, + common.EncodeURLValues(btcMarketsOrders, params), nil, &resp) +} + +// CancelAllOpenOrdersByPairs cancels all open orders unless pairs are specified +func (b *BTCMarkets) CancelAllOpenOrdersByPairs(marketIDs []string) ([]CancelOrderResp, error) { + var resp []CancelOrderResp + req := make(map[string]interface{}) + if len(marketIDs) > 0 { + var strTemp strings.Builder + for x := range marketIDs { + strTemp.WriteString("marketId=" + marketIDs[x] + "&") + } + req["marketId"] = strTemp.String()[:strTemp.Len()-1] + } + return resp, b.SendAuthenticatedRequest(http.MethodDelete, btcMarketsOrders, req, &resp) +} + +// FetchOrder finds order based on the provided id +func (b *BTCMarkets) FetchOrder(id string) (OrderData, error) { + var resp OrderData + return resp, b.SendAuthenticatedRequest(http.MethodGet, btcMarketsOrders+"/"+id, + nil, &resp) +} + +// RemoveOrder removes a given order +func (b *BTCMarkets) RemoveOrder(id string) (CancelOrderResp, error) { + var resp CancelOrderResp + return resp, b.SendAuthenticatedRequest(http.MethodDelete, btcMarketsOrders+"/"+id, + nil, &resp) +} + +// ListWithdrawals lists the withdrawal history +func (b *BTCMarkets) ListWithdrawals(before, after, limit int64) ([]TransferData, error) { + if (before > 0) && (after >= 0) { + return nil, errors.New("BTCMarkets only supports either before or after, not both") + } + var resp []TransferData + params := url.Values{} + if before > 0 { + params.Set("before", strconv.FormatInt(before, 10)) + } + if after >= 0 { + params.Set("after", strconv.FormatInt(after, 10)) + } + if limit > 0 { + params.Set("limit", strconv.FormatInt(limit, 10)) + } + return resp, b.SendAuthenticatedRequest(http.MethodGet, + common.EncodeURLValues(btcMarketsWithdrawals, params), + nil, + &resp) +} + +// GetWithdrawal gets withdrawawl info for a given id +func (b *BTCMarkets) GetWithdrawal(id string) (TransferData, error) { + var resp TransferData + if id == "" { + return resp, errors.New("id cannot be an empty string") + } + return resp, b.SendAuthenticatedRequest(http.MethodGet, btcMarketsWithdrawals+"/"+id, + nil, &resp) +} + +// ListDeposits lists the deposit history +func (b *BTCMarkets) ListDeposits(before, after, limit int64) ([]TransferData, error) { + if (before > 0) && (after >= 0) { + return nil, errors.New("BTCMarkets only supports either before or after, not both") + } + var resp []TransferData + params := url.Values{} + if before > 0 { + params.Set("before", strconv.FormatInt(before, 10)) + } + if after >= 0 { + params.Set("after", strconv.FormatInt(after, 10)) + } + if limit > 0 { + params.Set("limit", strconv.FormatInt(limit, 10)) + } + return resp, b.SendAuthenticatedRequest(http.MethodGet, + common.EncodeURLValues(btcMarketsDeposits, params), + nil, + &resp) +} + +// GetDeposit gets deposit info for a given ID +func (b *BTCMarkets) GetDeposit(id string) (TransferData, error) { + var resp TransferData + return resp, b.SendAuthenticatedRequest(http.MethodGet, btcMarketsDeposits+"/"+id, + nil, &resp) +} + +// ListTransfers lists the past asset transfers +func (b *BTCMarkets) ListTransfers(before, after, limit int64) ([]TransferData, error) { + if (before > 0) && (after >= 0) { + return nil, errors.New("BTCMarkets only supports either before or after, not both") + } + var resp []TransferData + params := url.Values{} + if before > 0 { + params.Set("before", strconv.FormatInt(before, 10)) + } + if after >= 0 { + params.Set("after", strconv.FormatInt(after, 10)) + } + if limit > 0 { + params.Set("limit", strconv.FormatInt(limit, 10)) + } + return resp, b.SendAuthenticatedRequest(http.MethodGet, + common.EncodeURLValues(btcMarketsTransfers, params), + nil, + &resp) +} + +// GetTransfer gets asset transfer info for a given ID +func (b *BTCMarkets) GetTransfer(id string) (TransferData, error) { + var resp TransferData + return resp, b.SendAuthenticatedRequest(http.MethodGet, btcMarketsTransfers+"/"+id, + nil, &resp) +} + +// FetchDepositAddress gets deposit address for the given asset +func (b *BTCMarkets) FetchDepositAddress(assetName string, before, after, limit int64) (DepositAddress, error) { + var resp DepositAddress + if (before > 0) && (after >= 0) { + return resp, errors.New("BTCMarkets only supports either before or after, not both") + } + params := url.Values{} + params.Set("assetName", assetName) + if before > 0 { + params.Set("before", strconv.FormatInt(before, 10)) + } + if after >= 0 { + params.Set("after", strconv.FormatInt(after, 10)) + } + if limit > 0 { + params.Set("limit", strconv.FormatInt(limit, 10)) + } + return resp, b.SendAuthenticatedRequest(http.MethodGet, + common.EncodeURLValues(btcMarketsAddresses, params), + nil, + &resp) +} + +// GetWithdrawalFees gets withdrawal fees for all assets +func (b *BTCMarkets) GetWithdrawalFees() ([]WithdrawalFeeData, error) { + var resp []WithdrawalFeeData + return resp, b.SendHTTPRequest(btcMarketsAPIURL+btcMarketsAPIVersion+btcMarketsWithdrawalFees, + &resp) +} + +// ListAssets lists all available assets +func (b *BTCMarkets) ListAssets() ([]AssetData, error) { + var resp []AssetData + return resp, b.SendAuthenticatedRequest(http.MethodGet, btcMarketsAssets, nil, &resp) +} + +// GetTransactions gets trading fees +func (b *BTCMarkets) GetTransactions(assetName string, before, after, limit int64) ([]TransactionData, error) { + if (before > 0) && (after >= 0) { + return nil, errors.New("BTCMarkets only supports either before or after, not both") + } + var resp []TransactionData + params := url.Values{} + if assetName != "" { + params.Set("assetName", assetName) + } + if before > 0 { + params.Set("before", strconv.FormatInt(before, 10)) + } + if after >= 0 { + params.Set("after", strconv.FormatInt(after, 10)) + } + if limit > 0 { + params.Set("limit", strconv.FormatInt(limit, 10)) + } + return resp, b.SendAuthenticatedRequest(http.MethodGet, + common.EncodeURLValues(btcMarketsTransactions, params), + nil, + &resp) +} + +// CreateNewReport creates a new report +func (b *BTCMarkets) CreateNewReport(reportType, format string) (CreateReportResp, error) { + var resp CreateReportResp + req := make(map[string]interface{}) + req["type"] = reportType + req["format"] = format + return resp, b.SendAuthenticatedRequest(http.MethodPost, btcMarketsReports, req, &resp) +} + +// GetReport finds details bout a past report +func (b *BTCMarkets) GetReport(reportID string) (ReportData, error) { + var resp ReportData + return resp, b.SendAuthenticatedRequest(http.MethodGet, btcMarketsReports+"/"+reportID, + nil, &resp) +} + +// RequestWithdraw requests withdrawals +func (b *BTCMarkets) RequestWithdraw(assetName string, amount float64, + toAddress, accountName, accountNumber, bsbNumber, bankName string) (TransferData, error) { + var resp TransferData + req := make(map[string]interface{}) + req["assetName"] = assetName + req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) + if assetName != "AUD" { + req["toAddress"] = toAddress + } else { + if accountName != "" { + req["accountName"] = accountName + } + if accountNumber != "" { + req["accountNumber"] = accountNumber + } + if bsbNumber != "" { + req["bsbNumber"] = bsbNumber + } + if bankName != "" { + req["bankName"] = bankName + } + } + return resp, b.SendAuthenticatedRequest(http.MethodPost, btcMarketsWithdrawals, req, &resp) +} + +// BatchPlaceCancelOrders places and cancels batch orders +func (b *BTCMarkets) BatchPlaceCancelOrders(cancelOrders []CancelBatch, placeOrders []PlaceBatch) (BatchPlaceCancelResponse, error) { + var resp BatchPlaceCancelResponse + var orderRequests []interface{} + if len(cancelOrders)+len(placeOrders) > 4 { + return resp, errors.New("BTCMarkets can only handle 4 orders at a time") + } + for x := range cancelOrders { + orderRequests = append(orderRequests, CancelOrderMethod{CancelOrder: cancelOrders[x]}) + } + for y := range placeOrders { + if placeOrders[y].ClientOrderID == "" { + return resp, errors.New("placeorders must have clientorderids filled") + } + orderRequests = append(orderRequests, PlaceOrderMethod{PlaceOrder: placeOrders[y]}) + } + return resp, b.SendAuthenticatedRequest(http.MethodPost, btcMarketsBatchOrders, orderRequests, &resp) +} + +// GetBatchTrades gets batch trades +func (b *BTCMarkets) GetBatchTrades(ids []string) (BatchTradeResponse, error) { + var resp BatchTradeResponse + if len(ids) > 50 { + return resp, errors.New("batchtrades can only handle 50 ids at a time") + } + marketIDs := strings.Join(ids, ",") + return resp, b.SendAuthenticatedRequest(http.MethodGet, btcMarketsBatchOrders+"/"+marketIDs, + nil, &resp) +} + +// CancelBatchOrders cancels given ids +func (b *BTCMarkets) CancelBatchOrders(ids []string) (BatchCancelResponse, error) { + var resp BatchCancelResponse + marketIDs := strings.Join(ids, ",") + return resp, b.SendAuthenticatedRequest(http.MethodDelete, btcMarketsBatchOrders+"/"+marketIDs, + nil, &resp) } // SendHTTPRequest sends an unauthenticated HTTP request @@ -380,52 +674,47 @@ func (b *BTCMarkets) SendHTTPRequest(path string, result interface{}) error { } // SendAuthenticatedRequest sends an authenticated HTTP request -func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data, result interface{}) (err error) { +func (b *BTCMarkets) SendAuthenticatedRequest(method, path string, data, result interface{}) (err error) { if !b.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } - n := b.Requester.GetNonce(true).String()[0:13] + strTime := strconv.FormatInt(time.Now().UTC().UnixNano()/1000000, 10) - var req string - payload := []byte("") - - if data != nil { + var body io.Reader + var payload, hmac []byte + switch data.(type) { + case map[string]interface{}, []interface{}: payload, err = json.Marshal(data) if err != nil { return err } - req = path + "\n" + n + "\n" + string(payload) - } else { - req = path + "\n" + n + "\n" - } - - hmac := crypto.GetHMAC(crypto.HashSHA512, - []byte(req), []byte(b.API.Credentials.Secret)) - - if b.Verbose { - log.Debugf(log.ExchangeSys, "Sending %s request to URL %s with params %s\n", - reqType, - b.API.Endpoints.URL+path, - req) + body = bytes.NewBuffer(payload) + strMsg := method + btcMarketsAPIVersion + path + strTime + string(payload) + hmac = crypto.GetHMAC(crypto.HashSHA512, + []byte(strMsg), []byte(b.API.Credentials.Secret)) + default: + strArray := strings.Split(path, "?") + hmac = crypto.GetHMAC(crypto.HashSHA512, + []byte(method+btcMarketsAPIVersion+strArray[0]+strTime), + []byte(b.API.Credentials.Secret)) } headers := make(map[string]string) headers["Accept"] = "application/json" headers["Accept-Charset"] = "UTF-8" headers["Content-Type"] = "application/json" - headers["apikey"] = b.API.Credentials.Key - headers["timestamp"] = n - headers["signature"] = crypto.Base64Encode(hmac) - - return b.SendPayload(reqType, - b.API.Endpoints.URL+path, + headers["BM-AUTH-APIKEY"] = b.API.Credentials.Key + headers["BM-AUTH-TIMESTAMP"] = strTime + headers["BM-AUTH-SIGNATURE"] = crypto.Base64Encode(hmac) + return b.SendPayload(method, + btcMarketsAPIURL+btcMarketsAPIVersion+path, headers, - bytes.NewBuffer(payload), + body, result, true, - true, + false, b.Verbose, b.HTTPDebugging, b.HTTPRecording) @@ -437,22 +726,33 @@ func (b *BTCMarkets) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) { switch feeBuilder.FeeType { case exchange.CryptocurrencyTradeFee: - tradingFee, err := b.GetTradingFee(feeBuilder.Pair.Base, - feeBuilder.Pair.Quote) + temp, err := b.GetTradingFees() if err != nil { - return 0, err + return fee, err + } + for x := range temp.FeeByMarkets { + if currency.NewPairFromString(temp.FeeByMarkets[x].MarketID) == feeBuilder.Pair { + fee = temp.FeeByMarkets[x].MakerFeeRate + if !feeBuilder.IsMaker { + fee = temp.FeeByMarkets[x].TakerFeeRate + } + } } - - fee = calculateTradingFee(tradingFee, - feeBuilder.PurchasePrice, - feeBuilder.Amount) - case exchange.CryptocurrencyWithdrawalFee: - fee = getCryptocurrencyWithdrawalFee(feeBuilder.Pair.Base) + temp, err := b.GetWithdrawalFees() + if err != nil { + return fee, err + } + for x := range temp { + if currency.NewCode(temp[x].AssetName) == feeBuilder.Pair.Base { + fee = temp[x].Fee * feeBuilder.PurchasePrice * feeBuilder.Amount + } + } case exchange.InternationalBankWithdrawalFee: - fee = getInternationalBankWithdrawalFee(feeBuilder.FiatCurrency) + return 0, errors.New("international bank withdrawals are not supported") + case exchange.OfflineTradeFee: - fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount) + fee = getOfflineTradeFee(feeBuilder) } if fee < 0 { fee = 0 @@ -461,24 +761,11 @@ func (b *BTCMarkets) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) { } // getOfflineTradeFee calculates the worst case-scenario trading fee -func getOfflineTradeFee(price, amount float64) float64 { - return 0.0085 * price * amount -} - -func calculateTradingFee(tradingFee TradingFee, purchasePrice, amount float64) (fee float64) { - fee = tradingFee.TradingFeeRate / 100000000 - return fee * amount * purchasePrice -} - -func getCryptocurrencyWithdrawalFee(c currency.Code) float64 { - return WithdrawalFees[c] -} - -func getInternationalBankWithdrawalFee(c currency.Code) float64 { - var fee float64 - - if c == currency.AUD { - fee = 0 +func getOfflineTradeFee(feeBuilder *exchange.FeeBuilder) float64 { + switch { + case feeBuilder.Pair.IsCryptoPair(): + return 0.002 * feeBuilder.PurchasePrice * feeBuilder.Amount + default: + return 0.0085 * feeBuilder.PurchasePrice * feeBuilder.Amount } - return fee } diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index 4fca1c10..f7e669cb 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -1,13 +1,13 @@ package btcmarkets import ( - "net/url" + "log" + "os" "testing" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) @@ -18,21 +18,23 @@ const ( apiKey = "" apiSecret = "" canManipulateRealOrders = false + BTCAUD = "BTC-AUD" + LTCAUD = "LTC-AUD" + ETHAUD = "ETH-AUD" + fakePair = "Fake-USDT" + bid = "bid" ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("BTC Markets load config error", err) + log.Fatal(err) } bConfig, err := cfg.GetExchangeConfig("BTC Markets") if err != nil { - t.Error("BTC Markets Setup() init error") + log.Fatal(err) } bConfig.API.Credentials.Key = apiKey bConfig.API.Credentials.Secret = apiSecret @@ -40,29 +42,27 @@ func TestSetup(t *testing.T) { err = b.Setup(bConfig) if err != nil { - t.Fatal("BTC Markets setup error", err) + log.Fatal(err) } + + os.Exit(m.Run()) +} + +func areTestAPIKeysSet() bool { + return b.AllowAuthenticatedRequest() } func TestGetMarkets(t *testing.T) { t.Parallel() _, err := b.GetMarkets() if err != nil { - t.Error("GetMarkets() error", err) + t.Error("GetTicker() error", err) } } func TestGetTicker(t *testing.T) { t.Parallel() - _, err := b.GetTicker("BTC", "AUD") - if err != nil { - t.Error("GetTicker() error", err) - } -} - -func TestGetOrderbook(t *testing.T) { - t.Parallel() - _, err := b.GetOrderbook("BTC", "AUD") + _, err := b.GetTicker(BTCAUD) if err != nil { t.Error("GetOrderbook() error", err) } @@ -70,437 +70,412 @@ func TestGetOrderbook(t *testing.T) { func TestGetTrades(t *testing.T) { t.Parallel() - _, err := b.GetTrades("BTC", "AUD", nil) - if err != nil { - t.Error("GetTrades() error", err) - } - - val := url.Values{} - val.Set("since", "0") - _, err = b.GetTrades("BTC", "AUD", val) + _, err := b.GetTrades(BTCAUD, 0, 0, 5) if err != nil { t.Error("GetTrades() error", err) } } -func TestNewOrder(t *testing.T) { +func TestGetOrderbook(t *testing.T) { t.Parallel() - _, err := b.NewOrder("AUD", "BTC", 0, 0, "Bid", - order.Limit.Lower(), "testTest") - if err == nil { - t.Error("NewOrder() Expected error") + _, err := b.GetOrderbook(BTCAUD, 2) + if err != nil { + t.Error("GetTrades() error", err) } } -func TestCancelExistingOrder(t *testing.T) { +func TestGetMarketCandles(t *testing.T) { t.Parallel() - _, err := b.CancelExistingOrder([]int64{1337}) - if err == nil { - t.Error("CancelExistingOrder() Expected error") + _, err := b.GetMarketCandles(BTCAUD, "", "", "", 0, 0, 5) + if err != nil { + t.Error(err) } } -func TestGetOrders(t *testing.T) { +func TestGetTickers(t *testing.T) { t.Parallel() - _, err := b.GetOrders("AUD", "BTC", 10, 0, false) - if err == nil { - t.Error("GetOrders() Expected error") - } - _, err = b.GetOrders("AUD", "BTC", 10, 0, true) - if err == nil { - t.Error("GetOrders() Expected error") + temp := []string{BTCAUD, LTCAUD, ETHAUD} + _, err := b.GetTickers(temp) + if err != nil { + t.Error(err) } } -func TestGetOrderDetail(t *testing.T) { +func TestGetMultipleOrderbooks(t *testing.T) { t.Parallel() - _, err := b.GetOrderDetail([]int64{1337}) - if err == nil { - t.Error("GetOrderDetail() Expected error") + temp := []string{BTCAUD, LTCAUD, ETHAUD} + _, err := b.GetMultipleOrderbooks(temp) + if err != nil { + t.Error(err) + } +} + +func TestGetServerTime(t *testing.T) { + t.Parallel() + _, err := b.GetServerTime() + if err != nil { + t.Error(err) } } func TestGetAccountBalance(t *testing.T) { t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } _, err := b.GetAccountBalance() - if err == nil { - t.Error("GetAccountBalance() Expected error") + if err != nil { + t.Error(err) } } -func TestWithdrawCrypto(t *testing.T) { +func TestGetTradingFees(t *testing.T) { t.Parallel() - _, err := b.WithdrawCrypto(0, "BTC", "LOLOLOL") - if err == nil { - t.Error("WithdrawCrypto() Expected error") + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.GetTradingFees() + if err != nil { + t.Error(err) } } -func TestWithdrawAUD(t *testing.T) { +func TestGetTradeHistory(t *testing.T) { t.Parallel() - _, err := b.WithdrawAUD("BLA", "1337", "blawest", "1336", 10000000) + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.GetTradeHistory(ETHAUD, "", -1, -1, -1) + if err != nil { + t.Error(err) + } + _, err = b.GetTradeHistory(BTCAUD, "", -1, -1, 1) + if err != nil { + t.Error(err) + } + _, err = b.GetTradeHistory(fakePair, "", -1, -1, -1) if err == nil { - t.Error("WithdrawAUD() Expected error") + t.Error("expected an error due to invalid trading pair") + } +} + +func TestGetTradeByID(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.GetTradeByID("4712043732") + if err != nil { + t.Error(err) + } +} + +func TestNewOrder(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") + } + _, err := b.NewOrder(BTCAUD, 100, 1, limit, bid, 0, 0, "", true, "", "") + if err != nil { + t.Error(err) + } + _, err = b.NewOrder(BTCAUD, 100, 1, "invalid", bid, 0, 0, "", true, "", "") + if err == nil { + t.Error("expected an error due to invalid ordertype") + } + _, err = b.NewOrder(BTCAUD, 100, 1, limit, "invalid", 0, 0, "", true, "", "") + if err == nil { + t.Error("expected an error due to invalid orderside") + } +} + +func TestGetOrders(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.GetOrders("", -1, -1, 2, "") + if err != nil { + t.Error(err) + } + _, err = b.GetOrders(LTCAUD, -1, -1, -1, "open") + if err != nil { + t.Error(err) + } +} + +func TestCancelOpenOrders(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") + } + temp := []string{BTCAUD, LTCAUD} + _, err := b.CancelAllOpenOrdersByPairs(temp) + if err != nil { + t.Error(err) + } + temp = []string{BTCAUD, fakePair} + _, err = b.CancelAllOpenOrdersByPairs(temp) + if err == nil { + t.Error("expected an error due to invalid marketID") + } +} + +func TestFetchOrder(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.FetchOrder("4477045999") + if err != nil { + t.Error(err) + } + _, err = b.FetchOrder("696969") + if err == nil { + t.Error(err) + } +} + +func TestRemoveOrder(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") + } + _, err := b.RemoveOrder("") + if err != nil { + t.Error(err) + } +} + +func TestListWithdrawals(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.ListWithdrawals(-1, -1, -1) + if err != nil { + t.Error(err) + } +} + +func TestGetWithdrawal(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.GetWithdrawal("4477381751") + if err != nil { + t.Error(err) + } +} + +func TestListDeposits(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.ListDeposits(-1, -1, -1) + if err != nil { + t.Error(err) + } +} + +func TestGetDeposit(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.GetDeposit("4476769607") + if err != nil { + t.Error(err) + } +} + +func TestListTransfers(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.ListTransfers(-1, -1, -1) + if err != nil { + t.Error(err) + } +} + +func TestGetTransfer(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.GetTransfer("4476769607") + if err != nil { + t.Error(err) + } + _, err = b.GetTransfer("6969696") + if err == nil { + t.Error("expected an error due to invalid transferID") + } +} + +func TestFetchDepositAddress(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.FetchDepositAddress("LTC", -1, -1, -1) + if err != nil { + t.Error(err) + } + _, err = b.FetchDepositAddress(fakePair, -1, -1, -1) + if err != nil { + t.Error("expected an error due to invalid assetID") + } +} + +func TestGetWithdrawalFees(t *testing.T) { + t.Parallel() + _, err := b.GetWithdrawalFees() + if err != nil { + t.Error(err) + } +} + +func TestListAssets(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.ListAssets() + if err != nil { + t.Error(err) + } +} + +func TestGetTransactions(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.GetTransactions("", -1, -1, -1) + if err != nil { + t.Error(err) + } +} + +func TestCreateNewReport(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.CreateNewReport("TransactionReport", "json") + if err != nil { + t.Error(err) + } +} + +func TestGetReport(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + _, err := b.GetReport("1kv38epne5v7lek9f18m60idg6") + if err != nil { + t.Error(err) + } +} + +func TestRequestWithdaw(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") + } + _, err := b.RequestWithdraw("BTC", 1, "sdjflajdslfjld", "", "", "", "") + if err == nil { + t.Error("expected an error due to invalid toAddress") + } +} + +func TestBatchPlaceCancelOrders(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") + } + var temp []PlaceBatch + o := PlaceBatch{ + MarketID: BTCAUD, + Amount: 11000, + Price: 1, + OrderType: order.Limit.String(), + Side: bid, + } + _, err := b.BatchPlaceCancelOrders(nil, append(temp, o)) + if err != nil { + t.Error(err) + } +} + +func TestGetBatchTrades(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } + temp := []string{"4477045999", "4477381751", "4476769607"} + _, err := b.GetBatchTrades(temp) + if err != nil { + t.Error(err) + } +} + +func TestCancelBatchOrders(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") + } + temp := []string{"4477045999", "4477381751", "4477381751"} + _, err := b.CancelBatchOrders(temp) + if err != nil { + t.Error(err) } } func TestGetAccountInfo(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") + } _, err := b.GetAccountInfo() - if err == nil { - t.Error("GetAccountInfo() Expected error") - } -} - -func TestGetFundingHistory(t *testing.T) { - _, err := b.GetFundingHistory() - if err == nil { - t.Error("GetAccountInfo() Expected error") - } -} - -func TestCancelOrder(t *testing.T) { - _, err := b.CancelExistingOrder([]int64{1337}) - - if err == nil { - t.Error("CancelgOrder() Expected error") - } -} - -func TestGetOrderInfo(t *testing.T) { - _, err := b.GetOrderInfo("1337") - if err == nil { - t.Error("GetOrderInfo() Expected error") - } -} - -func setFeeBuilder() *exchange.FeeBuilder { - return &exchange.FeeBuilder{ - Amount: 1, - FeeType: exchange.CryptocurrencyTradeFee, - Pair: currency.NewPair(currency.BTC, currency.LTC), - PurchasePrice: 1, - } -} - -// TestGetFeeByTypeOfflineTradeFee logic test -func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { - var feeBuilder = setFeeBuilder() - b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { - if feeBuilder.FeeType != exchange.OfflineTradeFee { - t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) - } - } else { - if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee { - t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) - } - } -} - -func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) - - var feeBuilder = setFeeBuilder() - - if apiKey != "" || apiSecret != "" { - // CryptocurrencyTradeFee Fiat - feeBuilder = setFeeBuilder() - feeBuilder.Pair.Quote = currency.USD - if resp, err := b.GetFee(feeBuilder); resp != float64(0.00849999) || err != nil { - t.Error(err) - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.00849999), resp) - } - - // CryptocurrencyTradeFee Basic - feeBuilder = setFeeBuilder() - if resp, err := b.GetFee(feeBuilder); resp != float64(0.0022) || err != nil { - t.Error(err) - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0022), resp) - } - - // CryptocurrencyTradeFee High quantity - feeBuilder = setFeeBuilder() - feeBuilder.Amount = 1000 - feeBuilder.PurchasePrice = 1000 - if resp, err := b.GetFee(feeBuilder); resp != float64(2200) || err != nil { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(22000), resp) - t.Error(err) - } - - // CryptocurrencyTradeFee IsMaker - feeBuilder = setFeeBuilder() - feeBuilder.IsMaker = true - if resp, err := b.GetFee(feeBuilder); resp != float64(0.0022) || err != nil { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.0022), resp) - t.Error(err) - } - - // CryptocurrencyTradeFee Negative purchase price - feeBuilder = setFeeBuilder() - feeBuilder.PurchasePrice = -1000 - if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) - t.Error(err) - } - } - - // CryptocurrencyWithdrawalFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee - if resp, err := b.GetFee(feeBuilder); resp != float64(0.001) || err != nil { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) + if err != nil { t.Error(err) } - - // CyptocurrencyDepositFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.CyptocurrencyDepositFee - if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) - t.Error(err) - } - - // InternationalBankDepositFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.InternationalBankDepositFee - feeBuilder.FiatCurrency = currency.AUD - if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) - t.Error(err) - } - - // InternationalBankWithdrawalFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee - feeBuilder.FiatCurrency = currency.AUD - if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) - t.Error(err) - } -} - -func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() - expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.AutoWithdrawFiatText - - withdrawPermissions := b.FormatWithdrawPermissions() - - if withdrawPermissions != expectedResult { - t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) - } -} - -func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - - var getOrdersRequest = order.GetOrdersRequest{ - OrderType: order.AnyType, - } - - _, err := b.GetActiveOrders(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - - var getOrdersRequest = order.GetOrdersRequest{ - OrderType: order.AnyType, - Currencies: []currency.Pair{currency.NewPair(currency.LTC, - currency.BTC)}, + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required but not set, skipping test") } - - _, err := b.GetOrderHistory(&getOrdersRequest) - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") + var input order.GetOrdersRequest + input.OrderSide = order.Buy + _, err := b.GetOrderHistory(&input) + if err != nil { + t.Error(err) } } -// Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them -// ---------------------------------------------------------------------------------------------------------------------------- -func areTestAPIKeysSet() bool { - return b.ValidateAPICredentials() -} - -func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - - var orderSubmission = &order.Submit{ - Pair: currency.Pair{ - Delimiter: "-", - Base: currency.BTC, - Quote: currency.LTC, - }, - OrderSide: order.Buy, - OrderType: order.Limit, - Price: 1, - Amount: 1, - ClientID: "meowOrder", - } - response, err := b.SubmitOrder(orderSubmission) - if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { - t.Errorf("Order failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") +func TestUpdateOrderbook(t *testing.T) { + t.Parallel() + cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.AUD.String(), "-") + _, err := b.UpdateOrderbook(cp, asset.Spot) + if err != nil { + t.Error(err) } } -func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - - var orderCancellation = &order.Cancel{ - OrderID: "1", - WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - AccountID: "1", - CurrencyPair: currencyPair, - } - - err := b.CancelOrder(orderCancellation) - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } -} - -func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - - var orderCancellation = &order.Cancel{ - OrderID: "1", - WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - AccountID: "1", - CurrencyPair: currencyPair, - } - - resp, err := b.CancelAllOrders(orderCancellation) - - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } - - if len(resp.Status) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.Status)) - } -} - -func TestModifyOrder(t *testing.T) { - _, err := b.ModifyOrder(&order.Modify{}) - if err == nil { - t.Error("ModifyOrder() Expected error") - } -} - -func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) - withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ - GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ - Amount: -1, - Currency: currency.BTC, - Description: "WITHDRAW IT ALL", - }, - Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", - } - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - - _, err := b.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } -} - -func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - - var withdrawFiatRequest = exchange.FiatWithdrawRequest{ - GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ - Amount: -1, - Currency: currency.USD, - Description: "WITHDRAW IT ALL", - }, - BankAccountName: "Satoshi Nakamoto", - BankAccountNumber: 12345, - BankAddress: "123 Fake St", - BankCity: "Tarry Town", - BankCountry: "Hyrule", - BankName: "Commonwealth Bank of Australia", - WireCurrency: currency.AUD.String(), - SwiftCode: "Taylor", - RequiresIntermediaryBank: false, - IsExpressWire: false, - } - - _, err := b.WithdrawFiatFunds(&withdrawFiatRequest) - if !areTestAPIKeysSet() && err == nil { - t.Error("Expecting an error when no keys are set") - } - if areTestAPIKeysSet() && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } -} - -func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - - var withdrawFiatRequest = exchange.FiatWithdrawRequest{} - _, err := b.WithdrawFiatFundsToInternationalBank(&withdrawFiatRequest) - if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) - } -} - -func TestGetDepositAddress(t *testing.T) { - _, err := b.GetDepositAddress(currency.BTC, "") - if err == nil { - t.Error("GetDepositAddress() error cannot be nil") +func TestUpdateTicker(t *testing.T) { + t.Parallel() + cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.AUD.String(), "-") + _, err := b.UpdateTicker(cp, asset.Spot) + if err != nil { + t.Error(err) } } diff --git a/exchanges/btcmarkets/btcmarkets_types.go b/exchanges/btcmarkets/btcmarkets_types.go index 7cccb8b2..64730e9a 100644 --- a/exchanges/btcmarkets/btcmarkets_types.go +++ b/exchanges/btcmarkets/btcmarkets_types.go @@ -1,66 +1,74 @@ package btcmarkets -import ( - "time" - - "github.com/thrasher-corp/gocryptotrader/currency" -) - -// Response is the genralized response type -type Response struct { - Success bool `json:"success"` - ErrorCode int `json:"errorCode"` - ErrorMessage string `json:"errorMessage"` - ID int `json:"id"` - Responses []ResponseDetails `json:"responses"` - ClientRequestID string `json:"clientRequestId"` - Orders []Order `json:"orders"` - Status string `json:"status"` -} - -// ResponseDetails holds order status details -type ResponseDetails struct { - Success bool `json:"success"` - ErrorCode int `json:"errorCode"` - ErrorMessage string `json:"errorMessage"` - ID int64 `json:"id"` -} +import "time" // Market holds a tradable market instrument type Market struct { - Instrument string `json:"instrument"` - Currency string `json:"currency"` + MarketID string `json:"marketId"` + BaseAsset string `json:"baseAsset"` + QuoteAsset string `json:"quoteAsset"` + MinOrderAmount float64 `json:"minOrderAmount,string"` + MaxOrderAmount float64 `json:"maxOrderAmount,string"` + AmountDecimals int64 `json:"amountDecimals,string"` + PriceDecimals int64 `json:"priceDecimals,string"` } // Ticker holds ticker information type Ticker struct { - BestAsk float64 `json:"bestAsk"` - BestBid float64 `json:"bestBid"` - Currency currency.Code `json:"currency"` - High24h float64 `json:"high24h"` - Instrument currency.Pair `json:"instrument"` - LastPrice float64 `json:"lastPrice"` - Low24h float64 `json:"low24h"` - Price24h float64 `json:"price24h"` - Timestamp int64 `json:"timestamp"` - Volume24h float64 `json:"volume24h"` -} - -// Orderbook holds current orderbook information returned from the exchange -type Orderbook struct { - Currency string `json:"currency"` - Instrument string `json:"instrument"` - Timestamp int64 `json:"timestamp"` - Asks [][]float64 `json:"asks"` - Bids [][]float64 `json:"bids"` + MarketID string `json:"marketId"` + BestBID float64 `json:"bestBid,string"` + BestAsk float64 `json:"bestAsk,string"` + LastPrice float64 `json:"lastPrice,string"` + Volume float64 `json:"volume24h,string"` + Change24h float64 `json:"price24h,string"` + Low24h float64 `json:"low24h,string"` + High24h float64 `json:"high24h,string"` + Timestamp time.Time `json:"timestamp"` } // Trade holds trade information type Trade struct { - TradeID int64 `json:"tid"` - Amount float64 `json:"amount"` - Price float64 `json:"price"` - Date int64 `json:"date"` + TradeID string `json:"id"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + Timestamp time.Time `json:"timestamp"` +} + +// tempOrderbook stores orderbook data +type tempOrderbook struct { + MarketID string `json:"marketId"` + SnapshotID int64 `json:"snapshotId"` + Asks [][2]string `json:"asks"` + Bids [][2]string `json:"bids"` +} + +// OBData stores orderbook data +type OBData struct { + Price float64 + Volume float64 +} + +// Orderbook holds current orderbook information returned from the exchange +type Orderbook struct { + MarketID string + SnapshotID int64 + Asks []OBData + Bids []OBData +} + +// MarketCandle stores candle data for a given pair +type MarketCandle struct { + Time time.Time + Open float64 + Close float64 + Low float64 + High float64 + Volume float64 +} + +// TimeResp stores server time +type TimeResp struct { + Time time.Time `json:"timestamp"` } // TradingFee 30 day trade volume @@ -90,7 +98,7 @@ type Order struct { Instrument string `json:"instrument"` OrderSide string `json:"orderSide"` OrderType string `json:"ordertype"` - CreationTime float64 `json:"creationTime"` + CreationTime time.Time `json:"creationTime"` Status string `json:"status"` ErrorMessage string `json:"errorMessage"` Price float64 `json:"price"` @@ -102,19 +110,164 @@ type Order struct { // TradeResponse holds trade information type TradeResponse struct { - ID int64 `json:"id"` - CreationTime float64 `json:"creationTime"` - Description string `json:"description"` - Price float64 `json:"price"` - Volume float64 `json:"volume"` - Fee float64 `json:"fee"` + ID int64 `json:"id"` + CreationTime time.Time `json:"creationTime"` + Description string `json:"description"` + Price float64 `json:"price"` + Volume float64 `json:"volume"` + Fee float64 `json:"fee"` } -// AccountBalance holds account balance details -type AccountBalance struct { - Balance float64 `json:"balance"` - PendingFunds float64 `json:"pendingFunds"` - Currency string `json:"currency"` +// AccountData stores account data +type AccountData struct { + AssetName string `json:"assetName"` + Balance float64 `json:"balance,string"` + Available float64 `json:"available,string"` + Locked float64 `json:"locked,string"` +} + +// TradeHistoryData stores data of past trades +type TradeHistoryData struct { + ID string `json:"id"` + MarketID string `json:"marketId"` + Timestamp time.Time `json:"timestamp"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + Side string `json:"side"` + Fee float64 `json:"fee,string"` + OrderID string `json:"orderId"` + LiquidityType string `json:"liquidityType"` +} + +// OrderData stores data for new order created +type OrderData struct { + OrderID string `json:"orderId"` + MarketID string `json:"marketId"` + Side string `json:"side"` + Type string `json:"type"` + CreationTime time.Time `json:"creationTime"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + OpenAmount float64 `json:"openAmount,string"` + Status string `json:"status"` +} + +// CancelOrderResp stores data for cancelled orders +type CancelOrderResp struct { + OrderID string `json:"orderId"` + ClientOrderID string `json:"clientOrderId"` +} + +// PaymentDetails stores payment address +type PaymentDetails struct { + Address string `json:"address"` +} + +// TransferData stores data from asset transfers +type TransferData struct { + ID string `json:"id"` + AssetName string `json:"assetName"` + Amount float64 `json:"amount,string"` + RequestType string `json:"type"` + CreationTime time.Time `json:"creationTime"` + Status string `json:"status"` + Description string `json:"description"` + Fee float64 `json:"fee,string"` + LastUpdate string `json:"lastUpdate"` + PaymentDetails PaymentDetails `json:"paymentDetail"` +} + +// DepositAddress stores deposit address data +type DepositAddress struct { + Address string `json:"address"` + AssetName string `json:"assetName"` +} + +// WithdrawalFeeData stores data for fees +type WithdrawalFeeData struct { + AssetName string `json:"assetName"` + Fee float64 `json:"fee,string"` +} + +// AssetData stores data for given asset +type AssetData struct { + AssetName string `json:"assetName"` + MinDepositAmount float64 `json:"minDepositAmount,string"` + MaxDepositAmount float64 `json:"maxDepositAmount,string"` + DepositDecimals float64 `json:"depositDecimals,string"` + MinWithdrawalAmount float64 `json:"minWithdrawalAmount,string"` + MaxWithdrawalAmount float64 `json:"maxWithdrawalAmount,string"` + WithdrawalDecimals float64 `json:"withdrawalDecimals,string"` + WithdrawalFee float64 `json:"withdrawalFee,string"` + DepositFee float64 `json:"depositFee,string"` +} + +// TransactionData stores data from past transactions +type TransactionData struct { + ID string `json:"id"` + CreationTime time.Time `json:"creationTime"` + Description string `json:"description"` + AssetName string `json:"assetName"` + Amount float64 `json:"amount,string"` + Balance float64 `json:"balance,string"` + FeeType string `json:"type"` + RecordType string `json:"recordType"` + ReferrenceID string `json:"referrenceId"` +} + +// CreateReportResp stores data for created report +type CreateReportResp struct { + ReportID string `json:"reportId"` +} + +// ReportData gets data for a created report +type ReportData struct { + ID string `json:"id"` + ContentURL string `json:"contentUrl"` + CreationTime time.Time `json:"creationTime"` + ReportType string `json:"reportType"` + Status string `json:"status"` + Format string `json:"format"` +} + +// BatchPlaceData stores data for placed batch orders +type BatchPlaceData struct { + OrderID string `json:"orderId"` + MarketID string `json:"marketId"` + Side string `json:"side"` + Type string `json:"type"` + CreationTime time.Time `json:"creationTime"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + OpenAmount float64 `json:"openAmount,string"` + Status string `json:"status"` + ClientOrderID string `json:"clientOrderId"` +} + +// UnprocessedBatchResp stores data for unprocessed response +type UnprocessedBatchResp struct { + Code string `json:"code"` + Message string `json:"message"` + RequestID string `json:"requestId"` +} + +// BatchPlaceCancelResponse stores place and cancel batch data +type BatchPlaceCancelResponse struct { + PlacedOrders []BatchPlaceData `json:"placeOrders"` + CancelledOrders []CancelOrderResp `json:"cancelOrders"` + UnprocessedOrders []UnprocessedBatchResp `json:"unprocessedRequests"` +} + +// BatchTradeResponse stores the trades from batchtrades +type BatchTradeResponse struct { + Orders []BatchPlaceData `json:"orders"` + UnprocessedRequests []UnprocessedBatchResp `json:"unprocessedRequests"` +} + +// BatchCancelResponse stores the cancellation details from batch cancels +type BatchCancelResponse struct { + CancelOrders []CancelOrderResp `json:"cancelOrders"` + UnprocessedRequests []UnprocessedBatchResp `json:"unprocessedRequests"` } // WithdrawRequestCrypto is a generalized withdraw request type @@ -134,18 +287,48 @@ type WithdrawRequestAUD struct { BSBNumber string `json:"bsbNumber"` } -// WithdrawalFees the large list of predefined withdrawal fees -// Prone to change -var WithdrawalFees = map[currency.Code]float64{ - currency.AUD: 0, - currency.BTC: 0.001, - currency.ETH: 0.001, - currency.ETC: 0.001, - currency.LTC: 0.0001, - currency.XRP: 0.15, - currency.BCH: 0.0001, - currency.OMG: 0.15, - currency.POWR: 5, +// CancelBatch stores data for batch cancel request +type CancelBatch struct { + OrderID string `json:"orderId,omitempty"` + ClientOrderID string `json:"clientOrderId,omitempty"` +} + +// PlaceBatch stores data for place batch request +type PlaceBatch struct { + MarketID string `json:"marketId"` + Price float64 `json:"price"` + Amount float64 `json:"amount"` + OrderType string `json:"type"` + Side string `json:"side"` + TriggerPrice float64 `json:"triggerPrice,omitempty"` + TriggerAmount float64 `json:"triggerAmount,omitempty"` + TimeInForce string `json:"timeInForce,omitempty"` + PostOnly bool `json:"postOnly,omitempty"` + SelfTrade string `json:"selfTrade,omitempty"` + ClientOrderID string `json:"clientOrderId,omitempty"` +} + +// PlaceOrderMethod stores data for place request +type PlaceOrderMethod struct { + PlaceOrder PlaceBatch `json:"placeOrder,omitempty"` +} + +// CancelOrderMethod stores data for Cancel request +type CancelOrderMethod struct { + CancelOrder CancelBatch `json:"cancelOrder,omitempty"` +} + +// TradingFeeData stores trading fee data +type TradingFeeData struct { + MakerFeeRate float64 `json:"makerFeeRate,string"` + TakerFeeRate float64 `json:"takerFeeRate,string"` + MarketID string `json:"marketId"` +} + +// TradingFeeResponse stores trading fee data +type TradingFeeResponse struct { + MonthlyVolume float64 `json:"volume30Day,string"` + FeeByMarkets []TradingFeeData `json:"FeeByMarkets"` } // WsSubscribe message sent via ws to subscribe @@ -155,6 +338,16 @@ type WsSubscribe struct { MessageType string `json:"messageType"` } +// WsAuthSubscribe message sent via login to subscribe +type WsAuthSubscribe struct { + MarketIDs []string `json:"marketIds,omitempty"` + Channels []string `json:"channels"` + Key string `json:"key"` + Signature string `json:"signature"` + Timestamp string `json:"timestamp"` + MessageType string `json:"messageType"` +} + // WsMessageType message sent via ws to determine type type WsMessageType struct { MessageType string `json:"messageType"` @@ -193,7 +386,42 @@ type WsOrderbook struct { MessageType string `json:"messageType"` } -// WsError message received for orderbook errors +// WsFundTransfer stores fund transfer data for websocket +type WsFundTransfer struct { + FundTransferID int64 `json:"fundtransferId"` + TransferType string `json:"type"` + Status string `json:"status"` + Timestamp time.Time `json:"timestamp"` + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + Fee float64 `json:"fee,string"` + MessageType string `json:"messageType"` +} + +// WsTradeData stores trade data for websocket +type WsTradeData struct { + TradeID int64 `json:"tradeId"` + Price float64 `json:"price,string"` + Volume float64 `json:"volume,string"` + Fee float64 `json:"fee,string"` + LiquidityType string `json:"liquidityType"` +} + +// WsOrderChange stores order data +type WsOrderChange struct { + OrderID int64 `json:"orderId"` + MarketID string `json:"marketId"` + Side string `json:"side"` + OrderType string `json:"type"` + OpenVolume float64 `json:"openVolume,string"` + Status string `json:"status"` + TriggerStatus string `json:"triggerStatus"` + Trades []WsTradeData `json:"trades"` + Timestamp time.Time `json:"timestamp"` + MessageType string `json:"messageType"` +} + +// WsError stores websocket error data type WsError struct { MessageType string `json:"messageType"` Code int64 `json:"code"` diff --git a/exchanges/btcmarkets/btcmarkets_websocket.go b/exchanges/btcmarkets/btcmarkets_websocket.go index 7b5d3917..79cf815a 100644 --- a/exchanges/btcmarkets/btcmarkets_websocket.go +++ b/exchanges/btcmarkets/btcmarkets_websocket.go @@ -6,9 +6,13 @@ import ( "fmt" "net/http" "strconv" + "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -32,10 +36,15 @@ func (b *BTCMarkets) WsConnect() error { if b.Verbose { log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name) } - - b.generateDefaultSubscriptions() go b.WsHandleData() - + if b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + b.createChannels() + if err != nil { + b.Websocket.DataHandler <- err + b.Websocket.SetCanUseAuthenticatedEndpoints(false) + } + } + b.generateDefaultSubscriptions() return nil } @@ -64,11 +73,11 @@ func (b *BTCMarkets) WsHandleData() { continue } switch wsResponse.MessageType { - case "heartbeat": + case heartbeat: if b.Verbose { log.Debugf(log.ExchangeSys, "%v - Websocket heartbeat received %s", b.Name, resp.Raw) } - case "orderbook": + case wsOB: var ob WsOrderbook err := json.Unmarshal(resp.Raw, &ob) if err != nil { @@ -129,7 +138,7 @@ func (b *BTCMarkets) WsHandleData() { Asset: asset.Spot, Exchange: b.Name, } - case "trade": + case trade: var trade WsTrade err := json.Unmarshal(resp.Raw, &trade) if err != nil { @@ -145,7 +154,7 @@ func (b *BTCMarkets) WsHandleData() { Price: trade.Price, Amount: trade.Volume, } - case "tick": + case tick: var tick WsTick err := json.Unmarshal(resp.Raw, &tick) if err != nil { @@ -166,6 +175,22 @@ func (b *BTCMarkets) WsHandleData() { AssetType: asset.Spot, Pair: p, } + case fundChange: + var transferData WsFundTransfer + err := json.Unmarshal(resp.Raw, &transferData) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + b.Websocket.DataHandler <- transferData + case orderChange: + var orderData WsOrderChange + err := json.Unmarshal(resp.Raw, &orderData) + if err != nil { + b.Websocket.DataHandler <- err + continue + } + b.Websocket.DataHandler <- orderData case "error": var wsErr WsError err := json.Unmarshal(resp.Raw, &wsErr) @@ -182,7 +207,7 @@ func (b *BTCMarkets) WsHandleData() { } func (b *BTCMarkets) generateDefaultSubscriptions() { - var channels = []string{"tick", "trade", "orderbook"} + var channels = []string{tick, trade, wsOB} enabledCurrencies := b.GetEnabledPairs(asset.Spot) var subscriptions []wshandler.WebsocketChannelSubscription for i := range channels { @@ -198,10 +223,68 @@ func (b *BTCMarkets) generateDefaultSubscriptions() { // Subscribe sends a websocket message to receive data from the channel func (b *BTCMarkets) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - req := WsSubscribe{ - MarketIDs: []string{channelToSubscribe.Currency.String()}, - Channels: []string{channelToSubscribe.Channel}, - MessageType: "subscribe", + unauthChannels := []string{tick, trade, wsOB} + authChannels := []string{fundChange, heartbeat, orderChange} + switch { + case common.StringDataCompare(unauthChannels, channelToSubscribe.Channel): + req := WsSubscribe{ + MarketIDs: []string{b.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String()}, + Channels: []string{channelToSubscribe.Channel}, + MessageType: subscribe, + } + err := b.WebsocketConn.SendMessage(req) + if err != nil { + return err + } + case common.StringDataCompare(authChannels, channelToSubscribe.Channel): + message, ok := channelToSubscribe.Params["AuthSub"].(WsAuthSubscribe) + if !ok { + return errors.New("invalid params data") + } + tempAuthData := b.generateAuthSubscriptions() + message.Channels = append(message.Channels, channelToSubscribe.Channel, heartbeat) + message.Key = tempAuthData.Key + message.Signature = tempAuthData.Signature + message.Timestamp = tempAuthData.Timestamp + err := b.WebsocketConn.SendMessage(message) + if err != nil { + return err + } } - return b.WebsocketConn.SendMessage(req) + return nil +} + +// Login logs in allowing private ws events +func (b *BTCMarkets) generateAuthSubscriptions() WsAuthSubscribe { + var authSubInfo WsAuthSubscribe + signTime := strconv.FormatInt(time.Now().UTC().UnixNano()/1000000, 10) + strToSign := "/users/self/subscribe" + "\n" + signTime + tempSign := crypto.GetHMAC(crypto.HashSHA512, + []byte(strToSign), + []byte(b.API.Credentials.Secret)) + sign := crypto.Base64Encode(tempSign) + authSubInfo.Key = b.API.Credentials.Key + authSubInfo.Signature = sign + authSubInfo.Timestamp = signTime + return authSubInfo +} + +// createChannels creates channels that need to be +func (b *BTCMarkets) createChannels() { + tempChannels := []string{orderChange, fundChange} + var channels []wshandler.WebsocketChannelSubscription + pairArray := b.GetEnabledPairs(asset.Spot) + for y := range tempChannels { + for x := range pairArray { + var authSub WsAuthSubscribe + var channel wshandler.WebsocketChannelSubscription + channel.Params = make(map[string]interface{}) + channel.Channel = tempChannels[y] + authSub.MarketIDs = append(authSub.MarketIDs, b.FormatExchangeCurrency(pairArray[x], asset.Spot).String()) + authSub.MessageType = subscribe + channel.Params["AuthSub"] = authSub + channels = append(channels, channel) + } + } + b.Websocket.SubscribeToChannels(channels) } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index b89f199b..7c59d882 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -3,7 +3,6 @@ package btcmarkets import ( "errors" "fmt" - "strconv" "strings" "sync" "time" @@ -62,6 +61,7 @@ func (b *BTCMarkets) SetDefaults() { }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ + Delimiter: "-", Uppercase: true, }, ConfigFormat: ¤cy.PairFormat{ @@ -172,19 +172,31 @@ func (b *BTCMarkets) Start(wg *sync.WaitGroup) { // Run implements the BTC Markets wrapper func (b *BTCMarkets) Run() { if b.Verbose { + log.Debugf(log.ExchangeSys, + "%s Websocket: %s (url: %s).\n", + b.Name, + common.IsEnabled(b.Websocket.IsEnabled()), + btcMarketsWSURL) b.PrintEnabledPairs() } - forceUpdate := false if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), "-") || !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), "-") { - enabledPairs := []string{"BTC-AUD"} log.Warnln(log.ExchangeSys, "Available pairs for BTC Markets reset due to config upgrade, please enable the pairs you would like again.") forceUpdate = true - - err := b.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), asset.Spot, true, true) + } + if forceUpdate { + enabledPairs := currency.Pairs{currency.Pair{ + Base: currency.BTC.Lower(), + Quote: currency.AUD.Lower(), + Delimiter: "-", + }, + } + err := b.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, + "%s Failed to update enabled currencies.\n", + b.Name) } } @@ -194,12 +206,18 @@ func (b *BTCMarkets) Run() { err := b.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + b.Name, + err) } } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *BTCMarkets) FetchTradablePairs(asset asset.Item) ([]string, error) { +func (b *BTCMarkets) FetchTradablePairs(a asset.Item) ([]string, error) { + if a != asset.Spot { + return nil, fmt.Errorf("asset type of %s is not supported by %s", a, b.Name) + } markets, err := b.GetMarkets() if err != nil { return nil, err @@ -207,9 +225,8 @@ func (b *BTCMarkets) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range markets { - pairs = append(pairs, fmt.Sprintf("%v%v%v", markets[x].Instrument, b.GetPairFormat(asset, false).Delimiter, markets[x].Currency)) + pairs = append(pairs, markets[x].MarketID) } - return pairs, nil } @@ -226,28 +243,26 @@ func (b *BTCMarkets) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { - var tickerPrice ticker.Price - tick, err := b.GetTicker(p.Base.String(), p.Quote.String()) - if err != nil { - return tickerPrice, err + var resp ticker.Price + allPairs := b.GetEnabledPairs(assetType) + for x := range allPairs { + tick, err := b.GetTicker(b.FormatExchangeCurrency(allPairs[x], assetType).String()) + if err != nil { + return resp, err + } + resp.Pair = allPairs[x] + resp.Last = tick.LastPrice + resp.High = tick.High24h + resp.Low = tick.Low24h + resp.Bid = tick.BestBID + resp.Ask = tick.BestAsk + resp.Volume = tick.Volume + resp.LastUpdated = time.Now() + err = ticker.ProcessTicker(b.Name, &resp, assetType) + if err != nil { + return resp, err + } } - - tickerPrice = ticker.Price{ - Last: tick.LastPrice, - High: tick.High24h, - Low: tick.Low24h, - Bid: tick.BestBid, - Ask: tick.BestAsk, - Volume: tick.Volume24h, - Pair: p, - LastUpdated: time.Unix(tick.Timestamp, 0), - } - - err = ticker.ProcessTicker(b.Name, &tickerPrice, assetType) - if err != nil { - return tickerPrice, err - } - return ticker.GetTicker(b.Name, p, assetType) } @@ -272,64 +287,50 @@ func (b *BTCMarkets) FetchOrderbook(p currency.Pair, assetType asset.Item) (orde // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := b.GetOrderbook(p.Base.String(), - p.Quote.String()) + tempResp, err := b.GetOrderbook(b.FormatExchangeCurrency(p, assetType).String(), 2) if err != nil { return orderBook, err } - - for x := range orderbookNew.Bids { + for x := range tempResp.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.Item{ - Amount: orderbookNew.Bids[x][1], - Price: orderbookNew.Bids[x][0], - }) + Amount: tempResp.Bids[x].Volume, + Price: tempResp.Bids[x].Price}) } - - for x := range orderbookNew.Asks { + for y := range tempResp.Asks { orderBook.Asks = append(orderBook.Asks, orderbook.Item{ - Amount: orderbookNew.Asks[x][1], - Price: orderbookNew.Asks[x][0], - }) + Amount: tempResp.Asks[y].Volume, + Price: tempResp.Asks[y].Price}) } - orderBook.Pair = p orderBook.ExchangeName = b.Name orderBook.AssetType = assetType - err = orderBook.Process() if err != nil { return orderBook, err } - return orderbook.Get(b.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the -// BTCMarkets exchange +// GetAccountInfo retrieves balances for all enabled currencies func (b *BTCMarkets) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo - response.Exchange = b.Name - - accountBalance, err := b.GetAccountBalance() + var resp exchange.AccountInfo + data, err := b.GetAccountBalance() if err != nil { - return response, err + return resp, err } - - var currencies []exchange.AccountCurrencyInfo - for i := 0; i < len(accountBalance); i++ { - var exchangeCurrency exchange.AccountCurrencyInfo - exchangeCurrency.CurrencyName = currency.NewCode(accountBalance[i].Currency) - exchangeCurrency.TotalValue = accountBalance[i].Balance - exchangeCurrency.Hold = accountBalance[i].PendingFunds - - currencies = append(currencies, exchangeCurrency) + var account exchange.Account + for key := range data { + c := currency.NewCode(data[key].AssetName) + hold := data[key].Locked + total := data[key].Balance + account.Currencies = append(account.Currencies, + exchange.AccountCurrencyInfo{CurrencyName: c, + TotalValue: total, + Hold: hold}) } - - response.Accounts = append(response.Accounts, exchange.Account{ - Currencies: currencies, - }) - - return response, nil + resp.Accounts = append(resp.Accounts, account) + resp.Exchange = b.Name + return resp, nil } // GetFundingHistory returns funding history, deposits and @@ -345,35 +346,35 @@ func (b *BTCMarkets) GetExchangeHistory(p currency.Pair, assetType asset.Item) ( // SubmitOrder submits a new order func (b *BTCMarkets) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { - var submitOrderResponse order.SubmitResponse + var resp order.SubmitResponse if err := s.Validate(); err != nil { - return submitOrderResponse, err + return resp, err } - if strings.EqualFold(s.OrderSide.String(), order.Sell.String()) { + if s.OrderSide == order.Sell { s.OrderSide = order.Ask } - if strings.EqualFold(s.OrderSide.String(), order.Buy.String()) { + if s.OrderSide == order.Buy { s.OrderSide = order.Bid } - response, err := b.NewOrder(s.Pair.Base.Upper().String(), - s.Pair.Quote.Upper().String(), + tempResp, err := b.NewOrder(b.FormatExchangeCurrency(s.Pair, asset.Spot).String(), s.Price, s.Amount, s.OrderSide.String(), s.OrderType.String(), + s.TriggerPrice, + s.TargetAmount, + "", + false, + "", s.ClientID) - - if response > 0 { - submitOrderResponse.OrderID = strconv.FormatInt(response, 10) + if err != nil { + return resp, err } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - - return submitOrderResponse, err + resp.IsOrderPlaced = true + resp.OrderID = tempResp.OrderID + return resp, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -383,116 +384,130 @@ func (b *BTCMarkets) ModifyOrder(action *order.Modify) (string, error) { } // CancelOrder cancels an order by its corresponding ID number -func (b *BTCMarkets) CancelOrder(order *order.Cancel) error { - orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) - if err != nil { - return err - } - - _, err = b.CancelExistingOrder([]int64{orderIDInt}) +func (b *BTCMarkets) CancelOrder(o *order.Cancel) error { + _, err := b.RemoveOrder(o.OrderID) return err } // CancelAllOrders cancels all orders associated with a currency pair func (b *BTCMarkets) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { - cancelAllOrdersResponse := order.CancelAllResponse{ - Status: make(map[string]string), - } - openOrders, err := b.GetOpenOrders() + var resp order.CancelAllResponse + tempMap := make(map[string]string) + var orderIDs []string + orders, err := b.GetOrders("", -1, -1, -1, "open") if err != nil { - return cancelAllOrdersResponse, err + return resp, err } - - var orderList []int64 - for i := range openOrders { - orderList = append(orderList, openOrders[i].ID) + for x := range orders { + orderIDs = append(orderIDs, orders[x].OrderID) } - - if len(orderList) > 0 { - orders, err := b.CancelExistingOrder(orderList) - if err != nil { - return cancelAllOrdersResponse, err - } - - for i := range orders { - if !orders[i].Success { - cancelAllOrdersResponse.Status[strconv.FormatInt(orders[i].ID, 10)] = orders[i].ErrorMessage - } - } + tempResp, err := b.CancelBatchOrders(orderIDs) + if err != nil { + return resp, err } - return cancelAllOrdersResponse, nil + for y := range tempResp.CancelOrders { + tempMap[tempResp.CancelOrders[y].OrderID] = "Success" + } + for z := range tempResp.UnprocessedRequests { + tempMap[tempResp.UnprocessedRequests[z].RequestID] = "Cancellation Failed" + } + return resp, nil } // GetOrderInfo returns information on a current open order func (b *BTCMarkets) GetOrderInfo(orderID string) (order.Detail, error) { - var OrderDetail order.Detail - - o, err := strconv.ParseInt(orderID, 10, 64) + var resp order.Detail + o, err := b.FetchOrder(orderID) if err != nil { - return OrderDetail, err + return resp, err } - - orders, err := b.GetOrderDetail([]int64{o}) - if err != nil { - return OrderDetail, err + resp.Exchange = b.Name + resp.ID = orderID + resp.CurrencyPair = currency.NewPairFromString(o.MarketID) + resp.Price = o.Price + resp.OrderDate = o.CreationTime + resp.ExecutedAmount = o.Amount - o.OpenAmount + resp.OrderSide = order.Bid + if o.Side == ask { + resp.OrderSide = order.Ask } - - if len(orders) > 1 { - return OrderDetail, errors.New("too many orders returned") + switch o.Type { + case limit: + resp.OrderType = order.Limit + case market: + resp.OrderType = order.Market + case stopLimit: + resp.OrderType = order.Stop + case stop: + resp.OrderType = order.Stop + case takeProfit: + resp.OrderType = order.ImmediateOrCancel + default: + resp.OrderType = order.Unknown } - - if len(orders) == 0 { - return OrderDetail, errors.New("no orders found") + resp.RemainingAmount = o.OpenAmount + switch o.Status { + case orderAccepted: + resp.Status = order.Active + case orderPlaced: + resp.Status = order.Active + case orderPartiallyMatched: + resp.Status = order.PartiallyFilled + case orderFullyMatched: + resp.Status = order.Filled + case orderCancelled: + resp.Status = order.Cancelled + case orderPartiallyCancelled: + resp.Status = order.PartiallyCancelled + case orderFailed: + resp.Status = order.Rejected + default: + resp.Status = order.UnknownStatus } - - for i := range orders { - var side order.Side - if strings.EqualFold(orders[i].OrderSide, order.Ask.String()) { - side = order.Sell - } else if strings.EqualFold(orders[i].OrderSide, order.Bid.String()) { - side = order.Buy - } - orderDate := time.Unix(int64(orders[i].CreationTime), 0) - orderType := order.Type(strings.ToUpper(orders[i].OrderType)) - - OrderDetail.Amount = orders[i].Volume - OrderDetail.OrderDate = orderDate - OrderDetail.Exchange = b.Name - OrderDetail.ID = strconv.FormatInt(orders[i].ID, 10) - OrderDetail.RemainingAmount = orders[i].OpenVolume - OrderDetail.OrderSide = side - OrderDetail.OrderType = orderType - OrderDetail.Price = orders[i].Price - OrderDetail.Status = order.Status(orders[i].Status) - OrderDetail.CurrencyPair = currency.NewPairWithDelimiter(orders[i].Instrument, - orders[i].Currency, - b.GetPairFormat(asset.Spot, false).Delimiter) - } - - return OrderDetail, nil + return resp, nil } // GetDepositAddress returns a deposit address for a specified currency func (b *BTCMarkets) GetDepositAddress(cryptocurrency currency.Code, accountID string) (string, error) { - return "", common.ErrFunctionNotSupported + temp, err := b.FetchDepositAddress(strings.ToUpper(cryptocurrency.String()), -1, -1, -1) + if err != nil { + return "", err + } + return temp.Address, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is submitted func (b *BTCMarkets) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { - return b.WithdrawCrypto(withdrawRequest.Amount, withdrawRequest.Currency.String(), withdrawRequest.Address) + a, err := b.RequestWithdraw(withdrawRequest.Currency.String(), + withdrawRequest.Amount, + withdrawRequest.Address, + "", + "", + "", + "") + if err != nil { + return "", err + } + return a.Status, nil } // WithdrawFiatFunds returns a withdrawal ID when a // withdrawal is submitted func (b *BTCMarkets) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawRequest) (string, error) { if withdrawRequest.Currency != currency.AUD { - return "", errors.New("only AUD is supported for withdrawals") + return "", errors.New("only aud is supported for withdrawals") } - return b.WithdrawAUD(withdrawRequest.BankAccountName, - strconv.FormatFloat(withdrawRequest.BankAccountNumber, 'f', -1, 64), - withdrawRequest.BankName, - strconv.FormatFloat(withdrawRequest.BankCode, 'f', -1, 64), - withdrawRequest.Amount) + a, err := b.RequestWithdraw(withdrawRequest.GenericWithdrawRequestInfo.Currency.String(), + withdrawRequest.GenericWithdrawRequestInfo.Amount, + "", + withdrawRequest.BankAccountName, + withdrawRequest.BankAccountNumber, + withdrawRequest.BSB, + withdrawRequest.BankName) + if err != nil { + return "", err + } + return a.Status, nil } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a @@ -517,124 +532,116 @@ func (b *BTCMarkets) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, err // GetActiveOrders retrieves any orders that are active/open func (b *BTCMarkets) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { - resp, err := b.GetOpenOrders() - if err != nil { - return nil, err + var resp []order.Detail + var tempResp order.Detail + var tempData []OrderData + if len(req.Currencies) == 0 { + allPairs := b.GetEnabledPairs(asset.Spot) + for a := range allPairs { + req.Currencies = append(req.Currencies, + allPairs[a]) + } } - - var orders []order.Detail - for i := range resp { - var side order.Side - if strings.EqualFold(resp[i].OrderSide, order.Ask.String()) { - side = order.Sell - } else if strings.EqualFold(resp[i].OrderSide, order.Bid.String()) { - side = order.Buy + var err error + for x := range req.Currencies { + tempData, err = b.GetOrders(b.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String(), -1, -1, -1, "") + if err != nil { + return resp, err } - orderDate := time.Unix(int64(resp[i].CreationTime), 0) - orderType := order.Type(strings.ToUpper(resp[i].OrderType)) - - openOrder := order.Detail{ - ID: strconv.FormatInt(resp[i].ID, 10), - Amount: resp[i].Volume, - Exchange: b.Name, - RemainingAmount: resp[i].OpenVolume, - OrderDate: orderDate, - OrderSide: side, - OrderType: orderType, - Price: resp[i].Price, - Status: order.Status(resp[i].Status), - CurrencyPair: currency.NewPairWithDelimiter(resp[i].Instrument, - resp[i].Currency, - b.GetPairFormat(asset.Spot, false).Delimiter), + for y := range tempData { + tempResp.Exchange = b.Name + tempResp.CurrencyPair = req.Currencies[x] + tempResp.OrderSide = order.Bid + if tempData[y].Side == ask { + tempResp.OrderSide = order.Ask + } + tempResp.OrderDate = tempData[y].CreationTime + switch tempData[y].Status { + case orderAccepted: + tempResp.Status = order.Active + case orderPlaced: + tempResp.Status = order.Active + case orderPartiallyMatched: + tempResp.Status = order.PartiallyFilled + case orderFullyMatched: + tempResp.Status = order.Filled + case orderCancelled: + tempResp.Status = order.Cancelled + case orderPartiallyCancelled: + tempResp.Status = order.PartiallyCancelled + case orderFailed: + tempResp.Status = order.Rejected + } + tempResp.Price = tempData[y].Price + tempResp.Amount = tempData[y].Amount + tempResp.ExecutedAmount = tempData[y].Amount - tempData[y].OpenAmount + tempResp.RemainingAmount = tempData[y].OpenAmount + resp = append(resp, tempResp) } - - for j := range resp[i].Trades { - tradeDate := time.Unix(int64(resp[i].Trades[j].CreationTime), 0) - openOrder.Trades = append(openOrder.Trades, order.TradeHistory{ - Amount: resp[i].Trades[j].Volume, - Exchange: b.Name, - Price: resp[i].Trades[j].Price, - TID: resp[i].Trades[j].ID, - Timestamp: tradeDate, - Fee: resp[i].Trades[j].Fee, - Description: resp[i].Trades[j].Description, - }) - } - - orders = append(orders, openOrder) } - - order.FilterOrdersByType(&orders, req.OrderType) - order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) - order.FilterOrdersBySide(&orders, req.OrderSide) - return orders, nil + order.FilterOrdersByType(&resp, req.OrderType) + order.FilterOrdersByTickRange(&resp, req.StartTicks, req.EndTicks) + order.FilterOrdersBySide(&resp, req.OrderSide) + return resp, nil } // GetOrderHistory retrieves account order information // Can Limit response to specific order status func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { + var resp []order.Detail + var tempResp order.Detail + var tempArray []string if len(req.Currencies) == 0 { - return nil, errors.New("requires at least one currency pair to retrieve history") - } - - var respOrders []Order - for i := range req.Currencies { - resp, err := b.GetOrders(req.Currencies[i].Base.String(), - req.Currencies[i].Quote.String(), - 200, - 0, - true) + orders, err := b.GetOrders("", -1, -1, -1, "") if err != nil { - return nil, err + return resp, err + } + for x := range orders { + tempArray = append(tempArray, orders[x].OrderID) } - respOrders = append(respOrders, resp...) } - - var orders []order.Detail - for i := range respOrders { - var side order.Side - if strings.EqualFold(respOrders[i].OrderSide, order.Ask.String()) { - side = order.Sell - } else if strings.EqualFold(respOrders[i].OrderSide, order.Bid.String()) { - side = order.Buy + for y := range req.Currencies { + orders, err := b.GetOrders(b.FormatExchangeCurrency(req.Currencies[y], asset.Spot).String(), -1, -1, -1, "") + if err != nil { + return resp, err } - orderDate := time.Unix(int64(respOrders[i].CreationTime), 0) - orderType := order.Type(strings.ToUpper(respOrders[i].OrderType)) - - openOrder := order.Detail{ - ID: strconv.FormatInt(respOrders[i].ID, 10), - Amount: respOrders[i].Volume, - Exchange: b.Name, - RemainingAmount: respOrders[i].OpenVolume, - OrderDate: orderDate, - OrderSide: side, - OrderType: orderType, - Price: respOrders[i].Price, - Status: order.Status(respOrders[i].Status), - CurrencyPair: currency.NewPairWithDelimiter(respOrders[i].Instrument, - respOrders[i].Currency, - b.GetPairFormat(asset.Spot, false).Delimiter), + for z := range orders { + tempArray = append(tempArray, orders[z].OrderID) } - - for j := range respOrders[i].Trades { - tradeDate := time.Unix(int64(respOrders[i].Trades[j].CreationTime), 0) - openOrder.Trades = append(openOrder.Trades, order.TradeHistory{ - Amount: respOrders[i].Trades[j].Volume, - Exchange: b.Name, - Price: respOrders[i].Trades[j].Price, - TID: respOrders[i].Trades[j].ID, - Timestamp: tradeDate, - Fee: respOrders[i].Trades[j].Fee, - Description: respOrders[i].Trades[j].Description, - }) - } - orders = append(orders, openOrder) } - - order.FilterOrdersByType(&orders, req.OrderType) - order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) - order.FilterOrdersBySide(&orders, req.OrderSide) - return orders, nil + tempData, err := b.GetBatchTrades(tempArray) + if err != nil { + return resp, err + } + for c := range tempData.Orders { + switch tempData.Orders[c].Status { + case orderFailed: + tempResp.Status = order.Rejected + case orderPartiallyCancelled: + tempResp.Status = order.PartiallyCancelled + case orderCancelled: + tempResp.Status = order.Cancelled + case orderFullyMatched: + tempResp.Status = order.Filled + case orderPartiallyMatched: + continue + case orderPlaced: + continue + case orderAccepted: + continue + } + tempResp.Exchange = b.Name + tempResp.CurrencyPair = currency.NewPairFromString(tempData.Orders[c].MarketID) + tempResp.OrderSide = order.Bid + if tempData.Orders[c].Side == ask { + tempResp.OrderSide = order.Ask + } + tempResp.OrderDate = tempData.Orders[c].CreationTime + tempResp.Price = tempData.Orders[c].Price + tempResp.ExecutedAmount = tempData.Orders[c].Amount + resp = append(resp, tempResp) + } + return resp, nil } // SubscribeToWebsocketChannels appends to ChannelsToSubscribe diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index 344e8e6c..8b5e8839 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -193,12 +193,13 @@ type FiatWithdrawRequest struct { GenericWithdrawRequestInfo // FIAT related information BankAccountName string - BankAccountNumber float64 + BankAccountNumber string BankName string BankAddress string BankCity string BankCountry string BankPostalCode string + BSB string SwiftCode string IBAN string BankCode float64 diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 02eb6d8e..0a02e9ed 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -37,12 +37,14 @@ var ( // Submit contains the order submission data type Submit struct { - Pair currency.Pair - OrderType Type - OrderSide Side - Price float64 - Amount float64 - ClientID string + Pair currency.Pair + OrderType Type + OrderSide Side + TriggerPrice float64 + TargetAmount float64 + Price float64 + Amount float64 + ClientID string } // SubmitResponse is what is returned after submitting an order to an exchange @@ -161,17 +163,18 @@ type Status string // All order status types const ( - AnyStatus Status = "ANY" - New Status = "NEW" - Active Status = "ACTIVE" - PartiallyFilled Status = "PARTIALLY_FILLED" - Filled Status = "FILLED" - Cancelled Status = "CANCELED" - PendingCancel Status = "PENDING_CANCEL" - Rejected Status = "REJECTED" - Expired Status = "EXPIRED" - Hidden Status = "HIDDEN" - UnknownStatus Status = "UNKNOWN" + AnyStatus Status = "ANY" + New Status = "NEW" + Active Status = "ACTIVE" + PartiallyCancelled Status = "PARTIALLY_CANCELLED" + PartiallyFilled Status = "PARTIALLY_FILLED" + Filled Status = "FILLED" + Cancelled Status = "CANCELED" + PendingCancel Status = "PENDING_CANCEL" + Rejected Status = "REJECTED" + Expired Status = "EXPIRED" + Hidden Status = "HIDDEN" + UnknownStatus Status = "UNKNOWN" ) // ByPrice used for sorting orders by price From 11a68a9bb7c486d949fcca848fb536247af6fe0d Mon Sep 17 00:00:00 2001 From: Scott Date: Wed, 4 Dec 2019 14:16:23 +1100 Subject: [PATCH 69/71] Utilising authenticated websocket functions in exchange wrappers (#384) * Basic concept commit * Initial changes to support bitfinex v2. Reverts linter changes as they suck. Exports bitfinex ws types * Adds ticker, trade and orderbook support * Candles sub that returns no data COMPLETE * Adds authenticated ws support * Adds the barebones endpoints to support * Adds more endpoints * Even more endpoints * minicommit to switch and test * All the interactive types * Adds support for simultaneous connections. Updates tests. Nothing is working * Successfully adds place order. Moves all authenticated endpoints to new switch case * Cancel order and modify order * Cancel all orders, cancel multi orders * Finalises implementation. Uses testMain * Adds WS wrapper support for some funcs * Fixing rebasing issues * Replaces use of currency as a variable. Updates a lot of coinut websocket auth endpoint stuff * Fixes some fun for loops with GetEnabledPairs * Fixes tests impacted by currency var change * Adds coinut support for WS functions. Replaces `order` vars with `ord`. Fixes some for loops too. Removes verbose from bitfinex * So many panics * I'm fixing a hole, where the panics get in, and stops my mind from wandering, where it will go * Moves func `CanUseAuthenticatedWebsocketEndpoint` to Websocket package as it fits better. Adds test coverage of `CanUseAuthenticatedWebsocketEndpoint` * Finishes up all of coinuts ws implementations. * GateIO implementation * Adds some helper funcs for types, sides and status. Adds support for huobi. Removes unnecessary type * Adds forgotten huobi endpoint * Fixes cancel order endpoint * go hates my formatting and so do I * The process to get authenticated kraken websocket to work. Uses testmain. Adds new auth channel, auth subscriptions, auth data handling. Not working yet * Finishes open orders handling * Mini update for status only updates * Fixes some kraken points * Finishes WS kraken since it doesn't work * Unfinished commit, cleaning up types * Finishes the const replacing * Fixes extra GetNAmes after rebase * An end to the cleanup. testmain for gateio * Adds ZB support * Bitfinex cleanup. Renamed func * Testmain-47s for everyone!!! yayaaaaaaa * Adds kraken websocket wrapper support * Fixes rebase issues * Fixes tests from rebase * Adds test for conversion. Fixes for loop. Updates test order pricing. Fixes some poor made tests. Adds proper error handling for ws responses instead of logging them. Fixed issue where commented code ruined kraken ws. * Fixes secret linting issues. Prioritises bitfinex channelID responses over authorised * Fixes sloppy error/var declarations * Fixes crazy bad logic where submit order errors weren't really considered. Parralols alphapoint/alphapoint_test.go. Removes buffer for multi-websocket comms channel. * Removal of inline string and removal of redundant nil checkerinos * Fixes err checks. Checks whether float has decimal. Fixes append. Drops omitempties. Parallel to some tests. Moves var declarations * Replaces my lazy sprintfs with strconv.FormatInt(time.Now().Unix(), 10) * Adds shiny new FullyMatched bool. Fixes coinbene buy sell consts * Fixes oopsie with coinbene const replacement * Fixes currency issue * Cleans up new places that use JSONDecode * Fixes huge panic bug from string int conversion. Adds large testtable for strings to order types * Fixes some more strconversion issues. Fixes table test var usage. Changes mapperino name * Added some new scenarios for number splitting * Fixes lint issues * negative num fix * Typo fix * Accuracy warning comment --- common/convert/convert.go | 30 + common/convert/convert_test.go | 56 + currency/pair_test.go | 8 +- currency/storage.go | 16 +- engine/orders.go | 6 +- engine/syncer.go | 20 +- exchanges/alphapoint/alphapoint_test.go | 272 ++--- exchanges/alphapoint/alphapoint_wrapper.go | 12 +- exchanges/anx/anx_test.go | 6 +- exchanges/anx/anx_wrapper.go | 21 +- exchanges/binance/binance_test.go | 5 +- exchanges/binance/binance_websocket.go | 5 +- exchanges/binance/binance_wrapper.go | 12 +- exchanges/bitfinex/bitfinex.go | 15 +- exchanges/bitfinex/bitfinex_test.go | 239 +++- exchanges/bitfinex/bitfinex_types.go | 247 +++- exchanges/bitfinex/bitfinex_websocket.go | 1050 +++++++++++------ exchanges/bitfinex/bitfinex_wrapper.go | 111 +- exchanges/bitflyer/bitflyer_test.go | 72 +- exchanges/bithumb/bithumb_test.go | 71 +- exchanges/bithumb/bithumb_wrapper.go | 24 +- exchanges/bitmex/bitmex_test.go | 55 +- exchanges/bitmex/bitmex_wrapper.go | 11 +- exchanges/bitstamp/bitstamp_test.go | 3 +- exchanges/bitstamp/bitstamp_wrapper.go | 12 +- exchanges/bittrex/bittrex.go | 2 +- exchanges/bittrex/bittrex_test.go | 57 +- exchanges/bittrex/bittrex_wrapper.go | 11 +- exchanges/btse/btse_test.go | 4 +- exchanges/btse/btse_wrapper.go | 9 +- exchanges/coinbasepro/coinbasepro_test.go | 52 +- .../coinbasepro/coinbasepro_websocket.go | 3 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 25 +- exchanges/coinbene/coinbene.go | 7 +- exchanges/coinbene/coinbene_test.go | 3 +- exchanges/coinbene/coinbene_wrapper.go | 9 +- exchanges/coinut/coinut.go | 96 +- exchanges/coinut/coinut_test.go | 120 +- exchanges/coinut/coinut_types.go | 132 +-- exchanges/coinut/coinut_websocket.go | 167 ++- exchanges/coinut/coinut_wrapper.go | 549 +++++---- exchanges/exchange_types.go | 75 +- exchanges/exmo/exmo_test.go | 58 +- exchanges/exmo/exmo_wrapper.go | 35 +- exchanges/gateio/gateio_test.go | 80 +- exchanges/gateio/gateio_types.go | 28 +- exchanges/gateio/gateio_websocket.go | 5 +- exchanges/gateio/gateio_wrapper.go | 215 ++-- exchanges/gemini/gemini_test.go | 4 - exchanges/gemini/gemini_websocket.go | 8 +- exchanges/gemini/gemini_wrapper.go | 15 +- exchanges/hitbtc/hitbtc.go | 2 +- exchanges/hitbtc/hitbtc_test.go | 56 +- exchanges/hitbtc/hitbtc_websocket.go | 5 +- exchanges/hitbtc/hitbtc_wrapper.go | 46 +- exchanges/huobi/huobi.go | 14 +- exchanges/huobi/huobi_test.go | 78 +- exchanges/huobi/huobi_types.go | 31 +- exchanges/huobi/huobi_websocket.go | 6 +- exchanges/huobi/huobi_wrapper.go | 267 +++-- exchanges/itbit/itbit_test.go | 49 +- exchanges/itbit/itbit_wrapper.go | 12 +- exchanges/kraken/kraken.go | 17 +- exchanges/kraken/kraken_test.go | 135 ++- exchanges/kraken/kraken_types.go | 110 +- exchanges/kraken/kraken_websocket.go | 460 ++++++-- exchanges/kraken/kraken_wrapper.go | 133 ++- exchanges/lakebtc/lakebtc_test.go | 89 +- exchanges/lakebtc/lakebtc_wrapper.go | 15 +- exchanges/lbank/lbank_test.go | 6 +- exchanges/lbank/lbank_wrapper.go | 3 + exchanges/localbitcoins/localbitcoins.go | 2 +- exchanges/localbitcoins/localbitcoins_test.go | 5 +- exchanges/okcoin/okcoin_test.go | 147 +-- exchanges/okex/okex_test.go | 203 +--- exchanges/okgroup/okgroup.go | 26 +- exchanges/okgroup/okgroup_test.go | 78 -- exchanges/okgroup/okgroup_wrapper.go | 3 + exchanges/order/order_test.go | 136 +++ exchanges/order/order_types.go | 3 +- exchanges/order/orders.go | 86 +- exchanges/orderbook/orderbook_test.go | 12 +- exchanges/poloniex/poloniex_test.go | 6 +- exchanges/poloniex/poloniex_websocket.go | 12 +- exchanges/poloniex/poloniex_wrapper.go | 26 +- exchanges/websocket/wshandler/wshandler.go | 20 +- .../websocket/wshandler/wshandler_test.go | 19 + .../websocket/wshandler/wshandler_types.go | 3 +- exchanges/yobit/yobit_test.go | 51 +- exchanges/yobit/yobit_wrapper.go | 32 +- exchanges/zb/zb.go | 6 +- exchanges/zb/zb_test.go | 61 +- exchanges/zb/zb_types.go | 5 +- exchanges/zb/zb_websocket.go | 34 - exchanges/zb/zb_websocket_types.go | 103 +- exchanges/zb/zb_wrapper.go | 169 ++- 96 files changed, 4112 insertions(+), 2818 deletions(-) delete mode 100644 exchanges/okgroup/okgroup_test.go diff --git a/common/convert/convert.go b/common/convert/convert.go index 5bf7e740..1993de89 100644 --- a/common/convert/convert.go +++ b/common/convert/convert.go @@ -1,8 +1,11 @@ package convert import ( + "errors" "fmt" + "math" "strconv" + "strings" "time" ) @@ -77,3 +80,30 @@ func UnixMillis(t time.Time) int64 { func RecvWindow(d time.Duration) int64 { return int64(d) / int64(time.Millisecond) } + +// SplitFloatDecimals takes in a float64 and splits +// the decimals into their own integers +// Warning. Passing in numbers with many decimals +// can lead to a loss of accuracy +func SplitFloatDecimals(input float64) (baseNum, decimalNum int64, err error) { + if input > float64(math.MaxInt64) { + return 0, 0, errors.New("number too large to split into integers") + } + if input == float64(int64(input)) { + return int64(input), 0, nil + } + decStr := strconv.FormatFloat(input, 'f', -1, 64) + splitNum := strings.Split(decStr, ".") + baseNum, err = strconv.ParseInt(splitNum[0], 10, 64) + if err != nil { + return 0, 0, err + } + decimalNum, err = strconv.ParseInt(splitNum[1], 10, 64) + if err != nil { + return 0, 0, err + } + if baseNum < 0 { + decimalNum *= -1 + } + return baseNum, decimalNum, nil +} diff --git a/common/convert/convert_test.go b/common/convert/convert_test.go index 03aabb3d..60d84153 100644 --- a/common/convert/convert_test.go +++ b/common/convert/convert_test.go @@ -1,6 +1,7 @@ package convert import ( + "math" "testing" "time" ) @@ -149,3 +150,58 @@ func TestRecvWindow(t *testing.T) { expectedOutput, actualOutput) } } + +// TestSplitFloatDecimals ensures SplitFloatDecimals +// accurately splits decimals into integers +func TestSplitFloatDecimals(t *testing.T) { + x, y, err := SplitFloatDecimals(1.2) + if err != nil { + t.Error(err) + } + if x != 1 && y != 2 { + t.Error("Conversion error") + } + x, y, err = SplitFloatDecimals(123456.654321) + if err != nil { + t.Error(err) + } + if x != 123456 && y != 654321 { + t.Error("Conversion error") + } + x, y, err = SplitFloatDecimals(123.111000) + if err != nil { + t.Error(err) + } + if x != 123 && y != 111 { + t.Error("Conversion error") + } + x, y, err = SplitFloatDecimals(0123.111001) + if err != nil { + t.Error(err) + } + if x != 123 && y != 111001 { + t.Error("Conversion error") + } + x, y, err = SplitFloatDecimals(1) + if err != nil { + t.Error(err) + } + if x != 1 && y != 0 { + t.Error("Conversion error") + } + _, _, err = SplitFloatDecimals(float64(math.MaxInt64) + 1) + if err == nil { + t.Error("Expected conversion error") + } + _, _, err = SplitFloatDecimals(1797693134862315700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111) + if err == nil { + t.Error("Expected conversion error") + } + x, y, err = SplitFloatDecimals(-1.2) + if err != nil { + t.Error(err) + } + if x != -1 && y != -2 { + t.Error("Conversion error") + } +} diff --git a/currency/pair_test.go b/currency/pair_test.go index 837d52e2..b3856528 100644 --- a/currency/pair_test.go +++ b/currency/pair_test.go @@ -343,10 +343,10 @@ func TestNewPairDelimiter(t *testing.T) { // specific index func TestNewPairFromIndex(t *testing.T) { t.Parallel() - currency := defaultPair + curr := defaultPair index := "BTC" - pair, err := NewPairFromIndex(currency, index) + pair, err := NewPairFromIndex(curr, index) if err != nil { t.Error("NewPairFromIndex() error", err) } @@ -362,9 +362,9 @@ func TestNewPairFromIndex(t *testing.T) { ) } - currency = "DOGEBTC" + curr = "DOGEBTC" - pair, err = NewPairFromIndex(currency, index) + pair, err = NewPairFromIndex(curr, index) if err != nil { t.Error("NewPairFromIndex() error", err) } diff --git a/currency/storage.go b/currency/storage.go index b0452f0e..3ed5aec0 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -175,19 +175,15 @@ func (s *Storage) SetupConversionRates() { // SetDefaultFiatCurrencies assigns the default fiat currency list and adds it // to the running list func (s *Storage) SetDefaultFiatCurrencies(c ...Code) { - for _, currency := range c { - s.defaultFiatCurrencies = append(s.defaultFiatCurrencies, currency) - s.fiatCurrencies = append(s.fiatCurrencies, currency) - } + s.defaultFiatCurrencies = append(s.defaultFiatCurrencies, c...) + s.fiatCurrencies = append(s.fiatCurrencies, c...) } // SetDefaultCryptocurrencies assigns the default cryptocurrency list and adds // it to the running list func (s *Storage) SetDefaultCryptocurrencies(c ...Code) { - for _, currency := range c { - s.defaultCryptoCurrencies = append(s.defaultCryptoCurrencies, currency) - s.cryptocurrencies = append(s.cryptocurrencies, currency) - } + s.defaultCryptoCurrencies = append(s.defaultCryptoCurrencies, c...) + s.cryptocurrencies = append(s.cryptocurrencies, c...) } // SetupForexProviders sets up a new instance of the forex providers @@ -507,8 +503,8 @@ func (s *Storage) GetTotalMarketCryptocurrencies() (Currencies, error) { // IsDefaultCurrency returns if a currency is a default currency func (s *Storage) IsDefaultCurrency(c Code) bool { t, _ := GetTranslation(c) - for _, d := range s.defaultFiatCurrencies { - if d.Match(c) || d.Match(t) { + for i := range s.defaultFiatCurrencies { + if s.defaultFiatCurrencies[i].Match(c) || s.defaultFiatCurrencies[i].Match(t) { return true } } diff --git a/engine/orders.go b/engine/orders.go index 303df534..aa50406e 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -264,11 +264,11 @@ func (o *orderManager) processOrders() { } for x := range result { - order := &result[x] - result := o.orderStore.Add(order) + ord := &result[x] + result := o.orderStore.Add(ord) if result != ErrOrdersAlreadyExists { msg := fmt.Sprintf("Order manager: Exchange %s added order ID=%v pair=%v price=%v amount=%v side=%v type=%v.", - order.Exchange, order.ID, order.CurrencyPair, order.Price, order.Amount, order.OrderSide, order.OrderType) + ord.Exchange, ord.ID, ord.CurrencyPair, ord.Price, ord.Amount, ord.OrderSide, ord.OrderType) log.Debugf(log.OrderMgr, "%v\n", msg) Bot.CommsManager.PushEvent(base.Event{ Type: "order", diff --git a/engine/syncer.go b/engine/syncer.go index 6807dd9c..d900160e 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -310,16 +310,17 @@ func (e *ExchangeCurrencyPairSyncer) worker() { } for y := range assetTypes { - for _, p := range Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) { + enabledPairs := Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) + for i := range enabledPairs { if atomic.LoadInt32(&e.shutdown) == 1 { return } - if !e.exists(exchangeName, p, assetTypes[y]) { + if !e.exists(exchangeName, enabledPairs[i], assetTypes[y]) { c := CurrencyPairSyncAgent{ AssetType: assetTypes[y], Exchange: exchangeName, - Pair: p, + Pair: enabledPairs[i], } if e.Cfg.SyncTicker { @@ -346,7 +347,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { e.add(&c) } - c, err := e.get(exchangeName, p, assetTypes[y]) + c, err := e.get(exchangeName, enabledPairs[i], assetTypes[y]) if err != nil { log.Errorf(log.SyncMgr, "failed to get item. Err: %s\n", err) continue @@ -354,7 +355,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { if switchedToRest && usingWebsocket { log.Infof(log.SyncMgr, "%s %s: Websocket re-enabled, switching from rest to websocket\n", - c.Exchange, FormatCurrency(p).String()) + c.Exchange, FormatCurrency(enabledPairs[i]).String()) switchedToRest = false } if e.Cfg.SyncTicker { @@ -371,7 +372,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { c.Ticker.IsUsingREST = true log.Warnf(log.SyncMgr, "%s %s: No ticker update after 10 seconds, switching from websocket to rest\n", - c.Exchange, FormatCurrency(p).String()) + c.Exchange, FormatCurrency(enabledPairs[i]).String()) switchedToRest = true e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, false) } @@ -521,14 +522,15 @@ func (e *ExchangeCurrencyPairSyncer) Start() { } for y := range assetTypes { - for _, p := range Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) { - if e.exists(exchangeName, p, assetTypes[y]) { + enabledPairs := Bot.Exchanges[x].GetEnabledPairs(assetTypes[y]) + for i := range enabledPairs { + if e.exists(exchangeName, enabledPairs[i], assetTypes[y]) { continue } c := CurrencyPairSyncAgent{ AssetType: assetTypes[y], Exchange: exchangeName, - Pair: p, + Pair: enabledPairs[i], } if e.Cfg.SyncTicker { diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index 964cd0d8..910e9880 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -2,6 +2,8 @@ package alphapoint import ( "encoding/json" + "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -17,45 +19,37 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { - t.Parallel() - SetDefaults := Alphapoint{} +var a Alphapoint - SetDefaults.SetDefaults() - if SetDefaults.API.Endpoints.URL != "https://sim3.alphapoint.com:8400" { - t.Error("SetDefaults: String Incorrect -", SetDefaults.API.Endpoints.URL) - } - if SetDefaults.API.Endpoints.WebsocketURL != "wss://sim3.alphapoint.com:8401/v1/GetTicker/" { - t.Error("SetDefaults: String Incorrect -", SetDefaults.API.Endpoints.WebsocketURL) - } -} - -func testSetAPIKey(a *Alphapoint) { +func TestMain(m *testing.M) { + a.SetDefaults() a.API.Credentials.Key = apiKey a.API.Credentials.Secret = apiSecret a.API.AuthenticatedSupport = true -} - -func testIsAPIKeysSet(a *Alphapoint) bool { - if apiKey != "" && apiSecret != "" && a.API.AuthenticatedSupport { - return true + if a.API.Endpoints.URL != "https://sim3.alphapoint.com:8400" { + log.Fatal("SetDefaults: String Incorrect -", a.API.Endpoints.URL) } - return false + if a.API.Endpoints.WebsocketURL != "wss://sim3.alphapoint.com:8401/v1/GetTicker/" { + log.Fatal("SetDefaults: String Incorrect -", a.API.Endpoints.WebsocketURL) + } + os.Exit(m.Run()) } -func TestGetTicker(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() +func areTestAPIKeysSet() bool { + return a.ValidateAPICredentials() +} + +func TestGetTicker(t *testing.T) { + t.Parallel() var ticker Ticker var err error - if onlineTest { - ticker, err = alpha.GetTicker("BTCUSD") + ticker, err = a.GetTicker("BTCUSD") if err != nil { t.Fatal("Alphapoint GetTicker init error: ", err) } - _, err = alpha.GetTicker("wigwham") + _, err = a.GetTicker("wigwham") if err == nil { t.Error("Alphapoint GetTicker Expected error") } @@ -80,19 +74,16 @@ func TestGetTicker(t *testing.T) { } func TestGetTrades(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() - + t.Parallel() var trades Trades var err error - if onlineTest { - trades, err = alpha.GetTrades("BTCUSD", 0, 10) + trades, err = a.GetTrades("BTCUSD", 0, 10) if err != nil { t.Fatalf("Init error: %s", err) } - _, err = alpha.GetTrades("wigwham", 0, 10) + _, err = a.GetTrades("wigwham", 0, 10) if err == nil { t.Fatal("GetTrades Expected error") } @@ -121,18 +112,15 @@ func TestGetTrades(t *testing.T) { } func TestGetTradesByDate(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() - + t.Parallel() var trades Trades var err error - if onlineTest { - trades, err = alpha.GetTradesByDate("BTCUSD", 1414799400, 1414800000) + trades, err = a.GetTradesByDate("BTCUSD", 1414799400, 1414800000) if err != nil { t.Errorf("Init error: %s", err) } - _, err = alpha.GetTradesByDate("wigwham", 1414799400, 1414800000) + _, err = a.GetTradesByDate("wigwham", 1414799400, 1414800000) if err == nil { t.Error("GetTradesByDate Expected error") } @@ -168,19 +156,16 @@ func TestGetTradesByDate(t *testing.T) { } func TestGetOrderbook(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() - + t.Parallel() var orderBook Orderbook var err error - if onlineTest { - orderBook, err = alpha.GetOrderbook("BTCUSD") + orderBook, err = a.GetOrderbook("BTCUSD") if err != nil { t.Errorf("Init error: %s", err) } - _, err = alpha.GetOrderbook("wigwham") + _, err = a.GetOrderbook("wigwham") if err == nil { t.Error("GetOrderbook() Expected error") } @@ -213,14 +198,12 @@ func TestGetOrderbook(t *testing.T) { } func TestGetProductPairs(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() - + t.Parallel() var products ProductPairs var err error if onlineTest { - products, err = alpha.GetProductPairs() + products, err = a.GetProductPairs() if err != nil { t.Errorf("Init error: %s", err) } @@ -253,14 +236,12 @@ func TestGetProductPairs(t *testing.T) { } func TestGetProducts(t *testing.T) { - alpha := Alphapoint{} - alpha.SetDefaults() - + t.Parallel() var products Products var err error if onlineTest { - products, err = alpha.GetProducts() + products, err = a.GetProducts() if err != nil { t.Errorf("Init error: %s", err) } @@ -293,12 +274,9 @@ func TestGetProducts(t *testing.T) { } func TestCreateAccount(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } err := a.CreateAccount("test", "account", "something@something.com", "0292383745", "lolcat123") @@ -316,12 +294,9 @@ func TestCreateAccount(t *testing.T) { } func TestGetUserInfo(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetUserInfo() @@ -331,12 +306,9 @@ func TestGetUserInfo(t *testing.T) { } func TestSetUserInfo(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.SetUserInfo("bla", "bla", "1", "meh", true, true) @@ -346,12 +318,9 @@ func TestSetUserInfo(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetAccountInfo() @@ -361,12 +330,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestGetAccountTrades(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetAccountTrades("", 1, 2) @@ -376,12 +342,9 @@ func TestGetAccountTrades(t *testing.T) { } func TestGetDepositAddresses(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetDepositAddresses() @@ -391,12 +354,9 @@ func TestGetDepositAddresses(t *testing.T) { } func TestWithdrawCoins(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } err := a.WithdrawCoins("", "", "", 0.01) @@ -406,12 +366,9 @@ func TestWithdrawCoins(t *testing.T) { } func TestCreateOrder(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.CreateOrder("", "", order.Limit.String(), 0.01, 0) @@ -421,12 +378,9 @@ func TestCreateOrder(t *testing.T) { } func TestModifyExistingOrder(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.ModifyExistingOrder("", 1, 1) @@ -436,12 +390,9 @@ func TestModifyExistingOrder(t *testing.T) { } func TestCancelAllExistingOrders(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } err := a.CancelAllExistingOrders("") @@ -451,12 +402,9 @@ func TestCancelAllExistingOrders(t *testing.T) { } func TestGetOrders(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetOrders() @@ -466,12 +414,9 @@ func TestGetOrders(t *testing.T) { } func TestGetOrderFee(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - testSetAPIKey(a) - - if !testIsAPIKeysSet(a) { - return + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") } _, err := a.GetOrderFee("", "", 1, 1) @@ -481,45 +426,38 @@ func TestGetOrderFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() + t.Parallel() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := a.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } _, err := a.GetActiveOrders(&getOrdersRequest) - if areTestAPIKeysSet(a) && err != nil { + if areTestAPIKeysSet() && err != nil { t.Errorf("Could not get open orders: %s", err) - } else if !areTestAPIKeysSet(a) && err == nil { + } else if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } } func TestGetOrderHistory(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } _, err := a.GetOrderHistory(&getOrdersRequest) - if areTestAPIKeysSet(a) && err != nil { + if areTestAPIKeysSet() && err != nil { t.Errorf("Could not get order history: %s", err) - } else if !areTestAPIKeysSet(a) && err == nil { + } else if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } } @@ -527,15 +465,9 @@ func TestGetOrderHistory(t *testing.T) { // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- -func areTestAPIKeysSet(a *Alphapoint) bool { - return a.ValidateAPICredentials() -} - func TestSubmitOrder(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - - if areTestAPIKeysSet(a) && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -553,10 +485,10 @@ func TestSubmitOrder(t *testing.T) { } response, err := a.SubmitOrder(orderSubmission) - if !areTestAPIKeysSet(a) && err == nil { + if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } - if areTestAPIKeysSet(a) && err != nil { + if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) if !response.IsOrderPlaced { @@ -566,15 +498,12 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - - if areTestAPIKeysSet(a) && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -583,24 +512,21 @@ func TestCancelExchangeOrder(t *testing.T) { } err := a.CancelOrder(orderCancellation) - if !areTestAPIKeysSet(a) && err == nil { + if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } - if areTestAPIKeysSet(a) && err != nil { + if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) } } func TestCancelAllExchangeOrders(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - - if areTestAPIKeysSet(a) && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -609,11 +535,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { } resp, err := a.CancelAllOrders(orderCancellation) - - if !areTestAPIKeysSet(a) && err == nil { + if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } - if areTestAPIKeysSet(a) && err != nil { + if areTestAPIKeysSet() && err != nil { t.Errorf("Withdraw failed to be placed: %v", err) } @@ -623,9 +548,10 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := a.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -633,10 +559,8 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() + t.Parallel() var withdrawCryptoRequest = exchange.CryptoWithdrawRequest{} - _, err := a.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not implemented', received %v", err) @@ -644,10 +568,8 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - - if areTestAPIKeysSet(a) && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -659,10 +581,8 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - a := &Alphapoint{} - a.SetDefaults() - - if areTestAPIKeysSet(a) && !canManipulateRealOrders { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 2eb89200..4df2c5d5 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -212,16 +212,18 @@ func (a *Alphapoint) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) s.OrderSide.String(), s.Amount, s.Price) - + if err != nil { + return submitOrderResponse, err + } if response > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index e6b29f10..c817c725 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -86,7 +86,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { t.Parallel() var feeBuilder = setFeeBuilder() a.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -171,9 +171,7 @@ func TestFormatWithdrawPermissions(t *testing.T) { t.Parallel() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.WithdrawCryptoWithEmailText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := a.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } @@ -254,7 +252,6 @@ func TestCancelExchangeOrder(t *testing.T) { } currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -280,7 +277,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { } currencyPair := currency.NewPair(currency.BTC, currency.LTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 96b657ff..2a393832 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -2,6 +2,7 @@ package anx import ( "fmt" + "strconv" "strings" "sync" "time" @@ -346,16 +347,18 @@ func (a *ANX) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { false, "", false) - + if err != nil { + return submitOrderResponse, err + } if response != "" { submitOrderResponse.OrderID = response } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -391,9 +394,9 @@ func (a *ANX) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) return cancelAllOrdersResponse, err } - for _, order := range resp.OrderCancellationResponses { - if order.Error != CancelRequestSubmitted { - cancelAllOrdersResponse.Status[order.UUID] = order.Error + for i := range resp.OrderCancellationResponses { + if resp.OrderCancellationResponses[i].Error != CancelRequestSubmitted { + cancelAllOrdersResponse.Status[resp.OrderCancellationResponses[i].UUID] = resp.OrderCancellationResponses[i].Error } } @@ -414,7 +417,7 @@ func (a *ANX) GetDepositAddress(cryptocurrency currency.Code, _ string) (string, // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted func (a *ANX) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoWithdrawRequest) (string, error) { - return a.Send(withdrawRequest.Currency.String(), withdrawRequest.Address, "", fmt.Sprintf("%v", withdrawRequest.Amount)) + return a.Send(withdrawRequest.Currency.String(), withdrawRequest.Address, "", strconv.FormatFloat(withdrawRequest.Amount, 'f', -1, 64)) } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 0f142019..0934d278 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -194,7 +194,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -282,7 +282,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { t.Parallel() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := b.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) @@ -382,7 +381,6 @@ func TestCancelExchangeOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -407,7 +405,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index ab437935..21d40cd3 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -53,8 +53,9 @@ func (b *Binance) WsConnect() error { kline + "/" + depth - for _, ePair := range b.GetEnabledPairs(asset.Spot) { - err = b.SeedLocalCache(ePair) + enabledPairs := b.GetEnabledPairs(asset.Spot) + for i := range enabledPairs { + err = b.SeedLocalCache(enabledPairs[i]) if err != nil { return err } diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 7d66a87b..59203ff1 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -426,16 +426,18 @@ func (b *Binance) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } response, err := b.NewOrder(&orderRequest) - + if err != nil { + return submitOrderResponse, err + } if response.OrderID > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true + if response.ExecutedQty == response.OrigQty { + submitOrderResponse.FullyMatched = true } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index f85e4c9d..2877b2cb 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -85,8 +85,9 @@ const ( // depending on some factors (e.g. servers load, endpoint, etc.). type Bitfinex struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection - WebsocketSubdChannels map[int]WebsocketChanInfo + WebsocketConn *wshandler.WebsocketConnection + AuthenticatedWebsocketConn *wshandler.WebsocketConnection + WebsocketSubdChannels map[int]WebsocketChanInfo } // GetPlatformStatus returns the Bifinex platform status @@ -358,11 +359,11 @@ func (b *Bitfinex) GetTradesV2(currencyPair string, timestampStart, timestampEnd } var tempHistory TradeStructureV2 - for _, data := range resp { - tempHistory.TID = int64(data[0].(float64)) - tempHistory.Timestamp = int64(data[1].(float64)) - tempHistory.Amount = data[2].(float64) - tempHistory.Price = data[3].(float64) + for i := range resp { + tempHistory.TID = int64(resp[i][0].(float64)) + tempHistory.Timestamp = int64(resp[i][1].(float64)) + tempHistory.Amount = resp[i][2].(float64) + tempHistory.Price = resp[i][3].(float64) tempHistory.Exchange = b.Name tempHistory.Type = order.Buy.String() diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 04f44be4..ce29001d 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -1,8 +1,10 @@ package bitfinex import ( + "log" "net/http" "net/url" + "os" "reflect" "testing" "time" @@ -25,37 +27,43 @@ const ( ) var b Bitfinex +var wsAuthExecuted bool -func TestSetup(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Bitfinex load config error", err) + log.Fatal("Bitfinex load config error", err) } bfxConfig, err := cfg.GetExchangeConfig("Bitfinex") if err != nil { - t.Error("Bitfinex Setup() init error") + log.Fatal("Bitfinex Setup() init error") } err = b.Setup(bfxConfig) if err != nil { - t.Fatal("Bitfinex setup error", err) + log.Fatal("Bitfinex setup error", err) } b.API.Credentials.Key = apiKey b.API.Credentials.Secret = apiSecret if !b.Enabled || b.API.AuthenticatedSupport || b.Verbose || b.Websocket.IsEnabled() || len(b.BaseCurrencies) < 1 { - t.Error("Bitfinex Setup values not set correctly") + log.Fatal("Bitfinex Setup values not set correctly") + } + + if areTestAPIKeysSet() { + b.API.AuthenticatedSupport = true + b.API.AuthenticatedWebsocketSupport = true } - b.API.AuthenticatedSupport = true - b.API.AuthenticatedWebsocketSupport = true // custom rate limit for testing b.Requester.SetRateLimit(true, time.Millisecond*300, 1) b.Requester.SetRateLimit(false, time.Millisecond*300, 1) + os.Exit(m.Run()) } func TestAppendOptionalDelimiter(t *testing.T) { + t.Parallel() curr1 := currency.NewPairFromString("BTCUSD") b.appendOptionalDelimiter(&curr1) if curr1.Delimiter != "" { @@ -71,7 +79,6 @@ func TestAppendOptionalDelimiter(t *testing.T) { func TestGetPlatformStatus(t *testing.T) { t.Parallel() - result, err := b.GetPlatformStatus() if err != nil { t.Errorf("TestGetPlatformStatus error: %s", err) @@ -653,7 +660,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -665,11 +672,10 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) var feeBuilder = setFeeBuilder() + t.Parallel() - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) @@ -738,20 +744,16 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() + t.Parallel() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -765,9 +767,7 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -787,9 +787,7 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -806,23 +804,25 @@ func TestSubmitOrder(t *testing.T) { ClientID: "meowOrder", } response, err := b.SubmitOrder(orderSubmission) - if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { - t.Errorf("Order failed to be placed: %v", err) - } else if !areTestAPIKeysSet() && err == nil { + + if areTestAPIKeysSet() && err != nil { + t.Errorf("Could not cancel orders: %v", err) + } + if areTestAPIKeysSet() && !response.IsOrderPlaced { + t.Error("Order not placed") + } + if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -840,15 +840,12 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrdera(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -871,6 +868,10 @@ func TestCancelAllExchangeOrdera(t *testing.T) { } func TestModifyOrder(t *testing.T) { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -878,8 +879,7 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -903,9 +903,7 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -938,9 +936,7 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -979,6 +975,7 @@ func TestWithdrawInternationalBank(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { + t.Parallel() if areTestAPIKeysSet() { _, err := b.GetDepositAddress(currency.BTC, "deposit") if err != nil { @@ -992,40 +989,168 @@ func TestGetDepositAddress(t *testing.T) { } } -// TestWsAuth dials websocket, sends login request. -func TestWsAuth(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) - } - b.WebsocketConn = &wshandler.WebsocketConnection{ +func setupWs() { + b.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ ExchangeName: b.Name, - URL: b.Websocket.GetWebsocketURL(), + URL: authenticatedBitfinexWebsocketEndpoint, Verbose: b.Verbose, ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, } var dialer websocket.Dialer - err := b.WebsocketConn.Dial(&dialer, http.Header{}) + err := b.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) if err != nil { - t.Fatal(err) + log.Fatal(err) } b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() + go b.WsReadData(b.AuthenticatedWebsocketConn) go b.WsDataHandler() - err = b.WsSendAuth() +} + +// TestWsAuth dials websocket, sends login request. +func TestWsAuth(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + runAuth(t) +} + +func runAuth(t *testing.T) { + setupWs() + err := b.WsSendAuth() if err != nil { t.Error(err) } timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout) select { case resp := <-b.Websocket.DataHandler: - if resp.(map[string]interface{})["event"] != "auth" && resp.(map[string]interface{})["status"] != "OK" { - t.Error("expected successful login") + if logResponse, ok := resp.(map[string]interface{}); ok { + if logResponse["event"] != "auth" && logResponse["status"] != "OK" { + t.Error("expected successful login") + } + } else { + t.Error("Unexpected response") } case <-timer.C: t.Error("Have not received a response") } timer.Stop() + wsAuthExecuted = true +} + +// TestWsPlaceOrder dials websocket, sends order request. +func TestWsPlaceOrder(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + _, err := b.WsNewOrder(&WsNewOrderRequest{ + CustomID: 1337, + Type: order.Buy.String(), + Symbol: "tBTCUSD", + Amount: 10, + Price: -10, + }) + if err != nil { + t.Error(err) + } +} + +// TestWsCancelOrder dials websocket, sends cancel request. +func TestWsCancelOrder(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsCancelOrder(1234) + if err != nil { + t.Error(err) + } +} + +// TestWsCancelOrder dials websocket, sends modify request. +func TestWsUpdateOrder(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsModifyOrder(&WsUpdateOrderRequest{ + OrderID: 1234, + Price: -111, + Amount: 111, + }) + if err != nil { + t.Error(err) + } +} + +// TestWsCancelAllOrders dials websocket, sends cancel all request. +func TestWsCancelAllOrders(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsCancelAllOrders() + if err != nil { + t.Error(err) + } +} + +// TestWsCancelAllOrders dials websocket, sends cancel all request. +func TestWsCancelMultiOrders(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsCancelMultiOrders([]int64{1, 2, 3, 4}) + if err != nil { + t.Error(err) + } +} + +// TestWsNewOffer dials websocket, sends new offer request. +func TestWsNewOffer(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsNewOffer(&WsNewOfferRequest{ + Type: order.Limit.String(), + Symbol: "fBTC", + Amount: -10, + Rate: 10, + Period: 30, + }) + if err != nil { + t.Error(err) + } + time.Sleep(time.Second) +} + +// TestWsCancelOffer dials websocket, sends cancel offer request. +func TestWsCancelOffer(t *testing.T) { + if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip("API keys not set, skipping") + } + if !wsAuthExecuted { + runAuth(t) + } + err := b.WsCancelOffer(1234) + if err != nil { + t.Error(err) + } + time.Sleep(time.Second) } diff --git a/exchanges/bitfinex/bitfinex_types.go b/exchanges/bitfinex/bitfinex_types.go index a54e7584..23864bb4 100644 --- a/exchanges/bitfinex/bitfinex_types.go +++ b/exchanges/bitfinex/bitfinex_types.go @@ -385,7 +385,7 @@ type WebsocketChanInfo struct { // WebsocketBook holds booking information type WebsocketBook struct { Price float64 - Count int + ID int Amount float64 } @@ -397,6 +397,16 @@ type WebsocketTrade struct { Amount float64 } +// WebsocketCandle candle data +type WebsocketCandle struct { + Timestamp int64 + Open float64 + Close float64 + High float64 + Low float64 + Volume float64 +} + // WebsocketTicker holds ticker information type WebsocketTicker struct { Bid float64 @@ -416,7 +426,11 @@ type WebsocketPosition struct { Amount float64 Price float64 MarginFunding float64 - MarginFundingType int + MarginFundingType int64 + ProfitLoss float64 + ProfitLossPercent float64 + LiquidationPrice float64 + Leverage float64 } // WebsocketWallet holds wallet information @@ -459,6 +473,9 @@ type WebsocketTradeData struct { OrderID int64 AmountExecuted float64 PriceExecuted float64 + OrderType string + OrderPrice float64 + Maker bool Fee float64 FeeCurrency string } @@ -468,21 +485,215 @@ type ErrorCapture struct { Message string `json:"message"` } -// TimeInterval represents interval enum. -type TimeInterval string +// WebsocketHandshake defines the communication between the websocket API for +// initial connection +type WebsocketHandshake struct { + Event string `json:"event"` + Code int64 `json:"code"` + Version float64 `json:"version"` +} -// TimeInvterval vars -var ( - TimeIntervalMinute = TimeInterval("1m") - TimeIntervalFiveMinutes = TimeInterval("5m") - TimeIntervalFifteenMinutes = TimeInterval("15m") - TimeIntervalThirtyMinutes = TimeInterval("30m") - TimeIntervalHour = TimeInterval("1h") - TimeIntervalThreeHours = TimeInterval("3h") - TimeIntervalSixHours = TimeInterval("6h") - TimeIntervalTwelveHours = TimeInterval("12h") - TimeIntervalDay = TimeInterval("1d") - TimeIntervalSevenDays = TimeInterval("7d") - TimeIntervalFourteenDays = TimeInterval("14d") - TimeIntervalMonth = TimeInterval("1M") +const ( + authenticatedBitfinexWebsocketEndpoint = "wss://api.bitfinex.com/ws/2" + publicBitfinexWebsocketEndpoint = "wss://api-pub.bitfinex.com/ws/2" + pong = "pong" + wsHeartbeat = "hb" + wsPositionSnapshot = "ps" + wsPositionNew = "pn" + wsPositionUpdate = "pu" + wsPositionClose = "pc" + wsWalletSnapshot = "ws" + wsWalletUpdate = "wu" + wsTradeExecutionUpdate = "tu" + wsTradeExecuted = "te" + wsFundingCreditSnapshot = "fcs" + wsFundingCreditNew = "fcn" + wsFundingCreditUpdate = "fcu" + wsFundingCreditCancel = "fcc" + wsFundingLoanSnapshot = "fls" + wsFundingLoanNew = "fln" + wsFundingLoanUpdate = "flu" + wsFundingLoanCancel = "flc" + wsFundingTradeExecuted = "fte" + wsFundingTradeUpdate = "ftu" + wsFundingInfoUpdate = "fiu" + wsBalanceUpdate = "bu" + wsMarginInfoUpdate = "miu" + wsNotification = "n" + wsOrderNew = "on" + wsOrderUpdate = "ou" + wsOrderCancel = "oc" + wsFundingOrderSnapshot = "fos" + wsFundingOrderNew = "fon" + wsFundingOrderUpdate = "fou" + wsFundingOrderCancel = "foc" + wsCancelMultipleOrders = "oc_multi" + wsBook = "book" + wsCandles = "candles" + wsTicker = "ticker" + wsTrades = "trades" + wsError = "error" ) + +// WsAuthRequest container for WS auth request +type WsAuthRequest struct { + Event string `json:"event"` + APIKey string `json:"apiKey"` + AuthPayload string `json:"authPayload"` + AuthSig string `json:"authSig"` + AuthNonce string `json:"authNonce"` + DeadManSwitch int64 `json:"dms,omitempty"` +} + +// WsFundingOffer funding offer received via websocket +type WsFundingOffer struct { + ID int64 + Symbol string + Created int64 + Updated int64 + Amount float64 + AmountOrig float64 + Type string + Flags interface{} + Status string + Rate float64 + Period int64 + Notify bool + Hidden bool + Insure bool + Renew bool + RateReal float64 +} + +// WsCredit credit details received via websocket +type WsCredit struct { + ID int64 + Symbol string + Side string + Created int64 + Updated int64 + Amount float64 + Flags interface{} + Status string + Rate float64 + Period int64 + Opened int64 + LastPayout int64 + Notify bool + Hidden bool + Insure bool + Renew bool + RateReal float64 + NoClose bool + PositionPair string +} + +// WsWallet wallet update details received via websocket +type WsWallet struct { + Type string + Currency string + Balance float64 + UnsettledInterest float64 + BalanceAvailable float64 +} + +// WsBalanceInfo the total and net assets in your account received via websocket +type WsBalanceInfo struct { + TotalAssetsUnderManagement float64 + NetAssetsUnderManagement float64 +} + +// WsFundingInfo account funding info received via websocket +type WsFundingInfo struct { + Symbol string + YieldLoan float64 + YieldLend float64 + DurationLoan float64 + DurationLend float64 +} + +// WsMarginInfoBase account margin info received via websocket +type WsMarginInfoBase struct { + UserProfitLoss float64 + UserSwaps float64 + MarginBalance float64 + MarginNet float64 +} + +// WsMarginInfoBase recent funding trades received via websocket +type WsFundingTrade struct { + ID int64 + Symbol string + MTSCreated int64 + OfferID int64 + Amount float64 + Rate float64 + Period int64 + Maker bool +} + +// WsNewOrderRequest new order request... +type WsNewOrderRequest struct { + GroupID int64 `json:"gid,omitempty"` + CustomID int64 `json:"cid,omitempty"` + Type string `json:"type"` + Symbol string `json:"symbol"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + Leverage int64 `json:"lev,omitempty"` + TrailingPrice float64 `json:"price_trailing,string,omitempty"` + AuxiliaryLimitPrice float64 `json:"price_aux_limit,string,omitempty"` + StopPrice float64 `json:"price_oco_stop,string,omitempty"` + Flags int64 `json:"flags,omitempty"` + TimeInForce string `json:"tif,omitempty"` +} + +// WsUpdateOrderRequest update order request... +type WsUpdateOrderRequest struct { + OrderID int64 `json:"id,omitempty"` + CustomID int64 `json:"cid,omitempty"` + CustomIDDate string `json:"cid_date,omitempty"` + GroupID int64 `json:"gid,omitempty"` + Price float64 `json:"price,string,omitempty"` + Amount float64 `json:"amount,string,omitempty"` + Leverage int64 `json:"lev,omitempty"` + Delta float64 `json:"delta,string,omitempty"` + AuxiliaryLimitPrice float64 `json:"price_aux_limit,string,omitempty"` + TrailingPrice float64 `json:"price_trailing,string,omitempty"` + Flags int64 `json:"flags,omitempty"` + TimeInForce string `json:"tif,omitempty"` +} + +// WsCancelOrderRequest cancel order request... +type WsCancelOrderRequest struct { + OrderID int64 `json:"id,omitempty"` + CustomID int64 `json:"cid,omitempty"` + CustomIDDate string `json:"cid_date,omitempty"` +} + +// WsCancelGroupOrdersRequest cancel orders request... +type WsCancelGroupOrdersRequest struct { + OrderID []int64 `json:"id,omitempty"` + CustomID [][]int64 `json:"cid,omitempty"` + GroupOrderID []int64 `json:"gid,omitempty"` +} + +// WsNewOfferRequest new offer request +type WsNewOfferRequest struct { + Type string `json:"type,omitempty"` + Symbol string `json:"symbol,omitempty"` + Amount float64 `json:"amount,string,omitempty"` + Rate float64 `json:"rate,string,omitempty"` + Period float64 `json:"period,omitempty"` + Flags int64 `json:"flags,omitempty"` +} + +// WsCancelOfferRequest cancel offer request +type WsCancelOfferRequest struct { + OrderID int64 `json:"id"` +} + +// WsCancelAllOrdersRequest cancel all orders request +type WsCancelAllOrdersRequest struct { + All int64 `json:"all"` +} diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 3fb9a7f9..2b54be05 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -7,6 +7,7 @@ import ( "net/http" "reflect" "strconv" + "strings" "time" "github.com/gorilla/websocket" @@ -21,98 +22,7 @@ import ( log "github.com/thrasher-corp/gocryptotrader/logger" ) -const ( - bitfinexWebsocket = "wss://api.bitfinex.com/ws" - bitfinexWebsocketVersion = "1.1" - bitfinexWebsocketPositionSnapshot = "ps" - bitfinexWebsocketPositionNew = "pn" - bitfinexWebsocketPositionUpdate = "pu" - bitfinexWebsocketPositionClose = "pc" - bitfinexWebsocketWalletSnapshot = "ws" - bitfinexWebsocketWalletUpdate = "wu" - bitfinexWebsocketOrderSnapshot = "os" - bitfinexWebsocketOrderNew = "on" - bitfinexWebsocketOrderUpdate = "ou" - bitfinexWebsocketOrderCancel = "oc" - bitfinexWebsocketTradeExecuted = "te" - bitfinexWebsocketTradeExecutionUpdate = "tu" - bitfinexWebsocketTradeSnapshots = "ts" - bitfinexWebsocketHeartbeat = "hb" - bitfinexWebsocketAlertRestarting = "20051" - bitfinexWebsocketAlertRefreshing = "20060" - bitfinexWebsocketAlertResume = "20061" - bitfinexWebsocketUnknownEvent = "10000" - bitfinexWebsocketUnknownPair = "10001" - bitfinexWebsocketSubscriptionFailed = "10300" - bitfinexWebsocketAlreadySubscribed = "10301" - bitfinexWebsocketUnknownChannel = "10302" -) - -// WebsocketHandshake defines the communication between the websocket API for -// initial connection -type WebsocketHandshake struct { - Event string `json:"event"` - Code int64 `json:"code"` - Version float64 `json:"version"` -} - -var pongReceive chan struct{} - -// WsPingHandler sends a ping request to the websocket server -func (b *Bitfinex) WsPingHandler() error { - req := make(map[string]string) - req["event"] = "ping" - return b.WebsocketConn.SendMessage(req) -} - -// WsSendAuth sends a autheticated event payload -func (b *Bitfinex) WsSendAuth() error { - if !b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", b.Name) - } - req := make(map[string]interface{}) - payload := "AUTH" + strconv.FormatInt(time.Now().UnixNano(), 10)[:13] - req["event"] = "auth" - req["apiKey"] = b.API.Credentials.Key - - req["authSig"] = crypto.HexEncodeToString( - crypto.GetHMAC( - crypto.HashSHA512_384, - []byte(payload), - []byte(b.API.Credentials.Secret))) - - req["authPayload"] = payload - - err := b.WebsocketConn.SendMessage(req) - if err != nil { - b.Websocket.SetCanUseAuthenticatedEndpoints(false) - return err - } - return nil -} - -// WsSendUnauth sends an unauthenticated payload -func (b *Bitfinex) WsSendUnauth() error { - req := make(map[string]string) - req["event"] = "unauth" - - return b.WebsocketConn.SendMessage(req) -} - -// WsAddSubscriptionChannel adds a new subscription channel to the -// WebsocketSubdChannels map in bitfinex.go (Bitfinex struct) -func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) { - chanInfo := WebsocketChanInfo{Pair: pair, Channel: channel} - b.WebsocketSubdChannels[chanID] = chanInfo - - if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", - b.Name, - channel, - pair, - chanID) - } -} +var comms = make(chan wshandler.WebsocketResponse) // WsConnect starts a new websocket connection func (b *Bitfinex) WsConnect() error { @@ -121,70 +31,64 @@ func (b *Bitfinex) WsConnect() error { } var dialer websocket.Dialer - b.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, - URL: b.Websocket.GetWebsocketURL(), - ProxyURL: b.Websocket.GetProxyAddress(), - Verbose: b.Verbose, - } err := b.WebsocketConn.Dial(&dialer, http.Header{}) if err != nil { return fmt.Errorf("%v unable to connect to Websocket. Error: %s", b.Name, err) } + go b.WsReadData(b.WebsocketConn) - resp, err := b.WebsocketConn.ReadMessage() - if err != nil { - b.Websocket.ReadMessageErrors <- err - return fmt.Errorf("%v unable to read from Websocket. Error: %s", b.Name, err) - } - b.Websocket.TrafficAlert <- struct{}{} - var hs WebsocketHandshake - err = json.Unmarshal(resp.Raw, &hs) - if err != nil { - return err - } - - err = b.WsSendAuth() - if err != nil { - log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err) - } - - b.GenerateDefaultSubscriptions() - if hs.Event == "info" { - if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name) + if b.Websocket.CanUseAuthenticatedEndpoints() { + err = b.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) + if err != nil { + log.Errorf(log.ExchangeSys, "%v unable to connect to authenticated Websocket. Error: %s", b.Name, err) + b.Websocket.SetCanUseAuthenticatedEndpoints(false) + } + go b.WsReadData(b.AuthenticatedWebsocketConn) + err = b.WsSendAuth() + if err != nil { + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err) + b.Websocket.SetCanUseAuthenticatedEndpoints(false) } } - pongReceive = make(chan struct{}, 1) - + b.GenerateDefaultSubscriptions() go b.WsDataHandler() - return nil } +// WsReadData funnels both auth and public ws data into one manageable place +func (b *Bitfinex) WsReadData(ws *wshandler.WebsocketConnection) { + b.Websocket.Wg.Add(1) + defer b.Websocket.Wg.Done() + for { + select { + case <-b.Websocket.ShutdownC: + return + default: + resp, err := ws.ReadMessage() + if err != nil { + b.Websocket.DataHandler <- err + return + } + b.Websocket.TrafficAlert <- struct{}{} + comms <- resp + } + } +} + // WsDataHandler handles data from WsReadData func (b *Bitfinex) WsDataHandler() { b.Websocket.Wg.Add(1) - defer b.Websocket.Wg.Done() for { select { case <-b.Websocket.ShutdownC: return - - default: - stream, err := b.WebsocketConn.ReadMessage() - if err != nil { - b.Websocket.ReadMessageErrors <- err - return - } - b.Websocket.TrafficAlert <- struct{}{} - + case stream := <-comms: if stream.Type == websocket.TextMessage { var result interface{} - err = json.Unmarshal(stream.Raw, &result) + err := json.Unmarshal(stream.Raw, &result) if err != nil { b.Websocket.DataHandler <- err return @@ -195,10 +99,17 @@ func (b *Bitfinex) WsDataHandler() { event := eventData["event"] switch event { case "subscribed": - b.WsAddSubscriptionChannel(int(eventData["chanId"].(float64)), - eventData["channel"].(string), - eventData["pair"].(string)) - + if symbol, ok := eventData["pair"].(string); ok { + b.WsAddSubscriptionChannel(int(eventData["chanId"].(float64)), + eventData["channel"].(string), + symbol, + ) + } else if key, ok := eventData["key"].(string); ok { + b.WsAddSubscriptionChannel(int(eventData["chanId"].(float64)), + eventData["channel"].(string), + key, + ) + } case "auth": status := eventData["status"].(string) if status == "OK" { @@ -209,201 +120,104 @@ func (b *Bitfinex) WsDataHandler() { eventData["code"].(string)) } } - case "[]interface {}": chanData := result.([]interface{}) chanID := int(chanData[0].(float64)) - chanInfo, ok := b.WebsocketSubdChannels[chanID] - if !ok { + if !ok && chanID != 0 { b.Websocket.DataHandler <- fmt.Errorf("bitfinex.go error - Unable to locate chanID: %d", chanID) continue } - if len(chanData) == 2 { - if reflect.TypeOf(chanData[1]).String() == "string" { - if chanData[1].(string) == bitfinexWebsocketHeartbeat { - continue - } else if chanData[1].(string) == "pong" { - pongReceive <- struct{}{} - continue - } - } - } switch chanInfo.Channel { - case "book": + case wsBook: var newOrderbook []WebsocketBook curr := currency.NewPairFromString(chanInfo.Pair) - switch len(chanData) { - case 2: - data := chanData[1].([]interface{}) - for i := range data { - y := data[i].([]interface{}) + if obSnapBundle, ok := chanData[1].([]interface{}); ok { + switch snapshot := obSnapBundle[0].(type) { + case []interface{}: + for i := range snapshot { + obSnap := snapshot[i].([]interface{}) + newOrderbook = append(newOrderbook, WebsocketBook{ + ID: int(obSnap[0].(float64)), + Price: obSnap[1].(float64), + Amount: obSnap[2].(float64)}) + } + err := b.WsInsertSnapshot(curr, + asset.Spot, + newOrderbook) + if err != nil { + b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s", + err) + } + case float64: newOrderbook = append(newOrderbook, WebsocketBook{ - Price: y[0].(float64), - Count: int(y[1].(float64)), - Amount: y[2].(float64)}) - } - - err := b.WsInsertSnapshot(curr, - asset.Spot, - newOrderbook) - - if err != nil { - b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s", - err) - } - case 4: - newOrderbook = append(newOrderbook, WebsocketBook{ - Price: chanData[1].(float64), - Count: int(chanData[2].(float64)), - Amount: chanData[3].(float64)}) - err := b.WsUpdateOrderbook(curr, - asset.Spot, - newOrderbook) - - if err != nil { - b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go updating orderbook error: %s", - err) + ID: int(snapshot), + Price: obSnapBundle[1].(float64), + Amount: obSnapBundle[2].(float64)}) + err := b.WsUpdateOrderbook(curr, + asset.Spot, + newOrderbook) + if err != nil { + b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s", + err) + } } } - case "ticker": + continue + case wsCandles: + curr := currency.NewPairFromString(chanInfo.Pair) + if candleBundle, ok := chanData[1].([]interface{}); ok { + if len(candleBundle) == 0 { + continue + } + switch candleBundle[0].(type) { + case []interface{}: + for i := range candleBundle { + candle := candleBundle[i].([]interface{}) + b.Websocket.DataHandler <- wshandler.KlineData{ + Timestamp: time.Unix(0, candle[0].(int64)), + Exchange: b.Name, + AssetType: asset.Spot, + Pair: curr, + OpenPrice: candle[1].(float64), + ClosePrice: candle[2].(float64), + HighPrice: candle[3].(float64), + LowPrice: candle[4].(float64), + Volume: candle[5].(float64), + } + } + case float64: + b.Websocket.DataHandler <- wshandler.KlineData{ + Timestamp: time.Unix(0, candleBundle[0].(int64)), + Exchange: b.Name, + AssetType: asset.Spot, + Pair: curr, + OpenPrice: candleBundle[1].(float64), + ClosePrice: candleBundle[2].(float64), + HighPrice: candleBundle[3].(float64), + LowPrice: candleBundle[4].(float64), + Volume: candleBundle[5].(float64), + } + } + } + continue + case wsTicker: + tickerData := chanData[1].([]interface{}) b.Websocket.DataHandler <- wshandler.TickerData{ Exchange: b.Name, - Volume: chanData[8].(float64), - High: chanData[9].(float64), - Low: chanData[10].(float64), - Bid: chanData[1].(float64), - Ask: chanData[3].(float64), - Last: chanData[7].(float64), + Bid: tickerData[0].(float64), + Ask: tickerData[2].(float64), + Last: tickerData[6].(float64), + Volume: tickerData[7].(float64), + High: tickerData[8].(float64), + Low: tickerData[9].(float64), AssetType: asset.Spot, Pair: currency.NewPairFromString(chanInfo.Pair), } - - case "account": - switch chanData[1].(string) { - case bitfinexWebsocketPositionSnapshot: - var positionSnapshot []WebsocketPosition - data := chanData[2].([]interface{}) - for i := range data { - y := data[i].([]interface{}) - positionSnapshot = append(positionSnapshot, - WebsocketPosition{ - Pair: y[0].(string), - Status: y[1].(string), - Amount: y[2].(float64), - Price: y[3].(float64), - MarginFunding: y[4].(float64), - MarginFundingType: int(y[5].(float64))}) - } - - if len(positionSnapshot) == 0 { - continue - } - - b.Websocket.DataHandler <- positionSnapshot - - case bitfinexWebsocketPositionNew, bitfinexWebsocketPositionUpdate, bitfinexWebsocketPositionClose: - data := chanData[2].([]interface{}) - position := WebsocketPosition{ - Pair: data[0].(string), - Status: data[1].(string), - Amount: data[2].(float64), - Price: data[3].(float64), - MarginFunding: data[4].(float64), - MarginFundingType: int(data[5].(float64))} - - b.Websocket.DataHandler <- position - - case bitfinexWebsocketWalletSnapshot: - data := chanData[2].([]interface{}) - var walletSnapshot []WebsocketWallet - for i := range data { - y := data[i].([]interface{}) - walletSnapshot = append(walletSnapshot, - WebsocketWallet{ - Name: y[0].(string), - Currency: y[1].(string), - Balance: y[2].(float64), - UnsettledInterest: y[3].(float64)}) - } - - b.Websocket.DataHandler <- walletSnapshot - - case bitfinexWebsocketWalletUpdate: - data := chanData[2].([]interface{}) - wallet := WebsocketWallet{ - Name: data[0].(string), - Currency: data[1].(string), - Balance: data[2].(float64), - UnsettledInterest: data[3].(float64)} - - b.Websocket.DataHandler <- wallet - - case bitfinexWebsocketOrderSnapshot: - var orderSnapshot []WebsocketOrder - data := chanData[2].([]interface{}) - for i := range data { - y := data[i].([]interface{}) - orderSnapshot = append(orderSnapshot, - WebsocketOrder{ - OrderID: int64(y[0].(float64)), - Pair: y[1].(string), - Amount: y[2].(float64), - OrigAmount: y[3].(float64), - OrderType: y[4].(string), - Status: y[5].(string), - Price: y[6].(float64), - PriceAvg: y[7].(float64), - Timestamp: y[8].(string)}) - } - - b.Websocket.DataHandler <- orderSnapshot - - case bitfinexWebsocketOrderNew, bitfinexWebsocketOrderUpdate, bitfinexWebsocketOrderCancel: - data := chanData[2].([]interface{}) - order := WebsocketOrder{ - OrderID: int64(data[0].(float64)), - Pair: data[1].(string), - Amount: data[2].(float64), - OrigAmount: data[3].(float64), - OrderType: data[4].(string), - Status: data[5].(string), - Price: data[6].(float64), - PriceAvg: data[7].(float64), - Timestamp: data[8].(string), - Notify: int(data[9].(float64))} - - b.Websocket.DataHandler <- order - - case bitfinexWebsocketTradeExecuted: - data := chanData[2].([]interface{}) - trade := WebsocketTradeExecuted{ - TradeID: int64(data[0].(float64)), - Pair: data[1].(string), - Timestamp: int64(data[2].(float64)), - OrderID: int64(data[3].(float64)), - AmountExecuted: data[4].(float64), - PriceExecuted: data[5].(float64)} - - b.Websocket.DataHandler <- trade - case bitfinexWebsocketTradeSnapshots, bitfinexWebsocketTradeExecutionUpdate: - data := chanData[2].([]interface{}) - trade := WebsocketTradeData{ - TradeID: int64(data[0].(float64)), - Pair: data[1].(string), - Timestamp: int64(data[2].(float64)), - OrderID: int64(data[3].(float64)), - AmountExecuted: data[4].(float64), - PriceExecuted: data[5].(float64), - Fee: data[6].(float64), - FeeCurrency: data[7].(string)} - - b.Websocket.DataHandler <- trade - } - - case "trades": + continue + case wsTrades: var trades []WebsocketTrade switch len(chanData) { case 2: @@ -413,42 +227,370 @@ func (b *Bitfinex) WsDataHandler() { if _, ok := y[0].(string); ok { continue } - - id, _ := y[0].(float64) - trades = append(trades, WebsocketTrade{ - ID: int64(id), + ID: int64(y[0].(float64)), Timestamp: int64(y[1].(float64)), - Price: y[2].(float64), - Amount: y[3].(float64)}) + Price: y[3].(float64), + Amount: y[2].(float64)}) } - - case 7: - trade := WebsocketTrade{ - ID: int64(chanData[3].(float64)), - Timestamp: int64(chanData[4].(float64)), - Price: chanData[5].(float64), - Amount: chanData[6].(float64)} - trades = append(trades, trade) + case 3: + if chanData[1].(string) == wsTradeExecuted { + // the te update contains less data then the "tu" + continue + } + data := chanData[2].([]interface{}) + trades = append(trades, WebsocketTrade{ + ID: int64(data[0].(float64)), + Timestamp: int64(data[1].(float64)), + Price: data[3].(float64), + Amount: data[2].(float64)}) } - if len(trades) > 0 { - side := order.Buy - newAmount := trades[0].Amount - if newAmount < 0 { - side = order.Sell - newAmount *= -1 + for i := range trades { + side := order.Buy.String() + newAmount := trades[i].Amount + if newAmount < 0 { + side = order.Sell.String() + newAmount *= -1 + } + b.Websocket.DataHandler <- wshandler.TradeData{ + CurrencyPair: currency.NewPairFromString(chanInfo.Pair), + Timestamp: time.Unix(trades[i].Timestamp, 0), + Price: trades[i].Price, + Amount: newAmount, + Exchange: b.Name, + AssetType: asset.Spot, + Side: side, + } } - - b.Websocket.DataHandler <- wshandler.TradeData{ - CurrencyPair: currency.NewPairFromString(chanInfo.Pair), - Timestamp: time.Unix(trades[0].Timestamp, 0), - Price: trades[0].Price, - Amount: newAmount, - Exchange: b.Name, - AssetType: asset.Spot, - Side: side.String(), + continue + } + } + if authResp, ok := chanData[1].(string); ok { + switch authResp { + case wsHeartbeat, pong: + continue + case wsNotification: + notification := chanData[2].([]interface{}) + if data, ok := notification[4].([]interface{}); ok { + channelName := notification[1].(string) + switch { + case strings.Contains(channelName, wsOrderUpdate), + strings.Contains(channelName, wsOrderCancel), + strings.Contains(channelName, wsFundingOrderCancel): + if data[0] != nil && data[0].(float64) > 0 { + b.AuthenticatedWebsocketConn.AddResponseWithID(int64(data[0].(float64)), stream.Raw) + continue + } + case strings.Contains(channelName, wsOrderNew): + if data[2] != nil && data[2].(float64) > 0 { + b.AuthenticatedWebsocketConn.AddResponseWithID(int64(data[2].(float64)), stream.Raw) + continue + } + } + b.Websocket.DataHandler <- fmt.Errorf("%s - Unexpected data returned %s", b.Name, stream.Raw) + continue + } + if notification[5] != nil && strings.EqualFold(notification[5].(string), wsError) { + b.Websocket.DataHandler <- fmt.Errorf("%s - Error %s", b.Name, notification[6].(string)) + } + case wsPositionSnapshot: + var snapshot []WebsocketPosition + if snapBundle, ok := chanData[2].([]interface{}); ok && len(snapBundle) > 0 { + if _, ok := snapBundle[0].([]interface{}); ok { + for i := range snapBundle { + positionData := snapBundle[i].([]interface{}) + position := WebsocketPosition{ + Pair: positionData[0].(string), + Status: positionData[1].(string), + Amount: positionData[2].(float64), + Price: positionData[3].(float64), + MarginFunding: positionData[4].(float64), + MarginFundingType: int64(positionData[5].(float64)), + ProfitLoss: positionData[6].(float64), + ProfitLossPercent: positionData[7].(float64), + LiquidationPrice: positionData[8].(float64), + Leverage: positionData[9].(float64), + } + snapshot = append(snapshot, position) + } + b.Websocket.DataHandler <- snapshot + } + } + case wsPositionNew, wsPositionUpdate, wsPositionClose: + if positionData, ok := chanData[2].([]interface{}); ok && len(positionData) > 0 { + position := WebsocketPosition{ + Pair: positionData[0].(string), + Status: positionData[1].(string), + Amount: positionData[2].(float64), + Price: positionData[3].(float64), + MarginFunding: positionData[4].(float64), + MarginFundingType: int64(positionData[5].(float64)), + ProfitLoss: positionData[6].(float64), + ProfitLossPercent: positionData[7].(float64), + LiquidationPrice: positionData[8].(float64), + Leverage: positionData[9].(float64), + } + b.Websocket.DataHandler <- position + } + case wsTradeExecutionUpdate: + if tradeData, ok := chanData[2].([]interface{}); ok && len(tradeData) > 4 { + b.Websocket.DataHandler <- WebsocketTradeData{ + TradeID: int64(tradeData[0].(float64)), + Pair: tradeData[1].(string), + Timestamp: int64(tradeData[2].(float64)), + OrderID: int64(tradeData[3].(float64)), + AmountExecuted: tradeData[4].(float64), + PriceExecuted: tradeData[5].(float64), + OrderType: tradeData[6].(string), + OrderPrice: tradeData[7].(float64), + Maker: tradeData[8].(float64) == 1, + Fee: tradeData[9].(float64), + FeeCurrency: tradeData[10].(string), + } + } + case wsFundingOrderSnapshot: + var snapshot []WsFundingOffer + if snapBundle, ok := chanData[2].([]interface{}); ok && len(snapBundle) > 0 { + if _, ok := snapBundle[0].([]interface{}); ok { + for i := range snapBundle { + data := snapBundle[i].([]interface{}) + offer := WsFundingOffer{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Created: int64(data[2].(float64)), + Updated: int64(data[3].(float64)), + Amount: data[4].(float64), + AmountOrig: data[5].(float64), + Type: data[6].(string), + Flags: data[9].(float64), + Status: data[10].(string), + Rate: data[14].(float64), + Period: int64(data[15].(float64)), + Notify: data[16].(float64) == 1, + Hidden: data[17].(float64) == 1, + Insure: data[18].(float64) == 1, + Renew: data[19].(float64) == 1, + RateReal: data[20].(float64), + } + snapshot = append(snapshot, offer) + } + b.Websocket.DataHandler <- snapshot + } + } + case wsFundingOrderNew, wsFundingOrderUpdate, wsFundingOrderCancel: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + b.Websocket.DataHandler <- WsFundingOffer{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Created: int64(data[2].(float64)), + Updated: int64(data[3].(float64)), + Amount: data[4].(float64), + AmountOrig: data[5].(float64), + Type: data[6].(string), + Flags: data[9].(float64), + Status: data[10].(string), + Rate: data[14].(float64), + Period: int64(data[15].(float64)), + Notify: data[16].(float64) == 1, + Hidden: data[17].(float64) == 1, + Insure: data[18].(float64) == 1, + Renew: data[19].(float64) == 1, + RateReal: data[20].(float64), + } + } + case wsFundingCreditSnapshot: + var snapshot []WsCredit + if snapBundle, ok := chanData[2].([]interface{}); ok && len(snapBundle) > 0 { + if _, ok := snapBundle[0].([]interface{}); ok { + for i := range snapBundle { + data := snapBundle[i].([]interface{}) + credit := WsCredit{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Side: data[2].(string), + Created: int64(data[3].(float64)), + Updated: int64(data[4].(float64)), + Amount: data[5].(float64), + Flags: data[6].(string), + Status: data[7].(string), + Rate: data[11].(float64), + Period: int64(data[12].(float64)), + Opened: int64(data[13].(float64)), + LastPayout: int64(data[14].(float64)), + Notify: data[15].(float64) == 1, + Hidden: data[16].(float64) == 1, + Insure: data[17].(float64) == 1, + Renew: data[18].(float64) == 1, + RateReal: data[19].(float64), + NoClose: data[20].(float64) == 1, + PositionPair: data[21].(string), + } + snapshot = append(snapshot, credit) + } + b.Websocket.DataHandler <- snapshot + } + } + case wsFundingCreditNew, wsFundingCreditUpdate, wsFundingCreditCancel: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + b.Websocket.DataHandler <- WsCredit{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Side: data[2].(string), + Created: int64(data[3].(float64)), + Updated: int64(data[4].(float64)), + Amount: data[5].(float64), + Flags: data[6].(string), + Status: data[7].(string), + Rate: data[11].(float64), + Period: int64(data[12].(float64)), + Opened: int64(data[13].(float64)), + LastPayout: int64(data[14].(float64)), + Notify: data[15].(float64) == 1, + Hidden: data[16].(float64) == 1, + Insure: data[17].(float64) == 1, + Renew: data[18].(float64) == 1, + RateReal: data[19].(float64), + NoClose: data[20].(float64) == 1, + PositionPair: data[21].(string), + } + } + case wsFundingLoanSnapshot: + var snapshot []WsCredit + if snapBundle, ok := chanData[2].([]interface{}); ok && len(snapBundle) > 0 { + if _, ok := snapBundle[0].([]interface{}); ok { + for i := range snapBundle { + data := snapBundle[i].([]interface{}) + credit := WsCredit{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Side: data[2].(string), + Created: int64(data[3].(float64)), + Updated: int64(data[4].(float64)), + Amount: data[5].(float64), + Flags: data[6].(string), + Status: data[7].(string), + Rate: data[11].(float64), + Period: int64(data[12].(float64)), + Opened: int64(data[13].(float64)), + LastPayout: int64(data[14].(float64)), + Notify: data[15].(float64) == 1, + Hidden: data[16].(float64) == 1, + Insure: data[17].(float64) == 1, + Renew: data[18].(float64) == 1, + RateReal: data[19].(float64), + NoClose: data[20].(float64) == 1, + } + snapshot = append(snapshot, credit) + } + b.Websocket.DataHandler <- snapshot + } + } + case wsFundingLoanNew, wsFundingLoanUpdate, wsFundingLoanCancel: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + b.Websocket.DataHandler <- WsCredit{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + Side: data[2].(string), + Created: int64(data[3].(float64)), + Updated: int64(data[4].(float64)), + Amount: data[5].(float64), + Flags: data[6].(string), + Status: data[7].(string), + Rate: data[11].(float64), + Period: int64(data[12].(float64)), + Opened: int64(data[13].(float64)), + LastPayout: int64(data[14].(float64)), + Notify: data[15].(float64) == 1, + Hidden: data[16].(float64) == 1, + Insure: data[17].(float64) == 1, + Renew: data[18].(float64) == 1, + RateReal: data[19].(float64), + NoClose: data[20].(float64) == 1, + } + } + case wsWalletSnapshot: + var snapshot []WsWallet + if snapBundle, ok := chanData[2].([]interface{}); ok && len(snapBundle) > 0 { + if _, ok := snapBundle[0].([]interface{}); ok { + for i := range snapBundle { + data := snapBundle[i].([]interface{}) + var balanceAvailable float64 + if _, ok := data[4].(float64); ok { + balanceAvailable = data[4].(float64) + } + wallet := WsWallet{ + Type: data[0].(string), + Currency: data[1].(string), + Balance: data[2].(float64), + UnsettledInterest: data[3].(float64), + BalanceAvailable: balanceAvailable, + } + snapshot = append(snapshot, wallet) + } + b.Websocket.DataHandler <- snapshot + } + } + case wsWalletUpdate: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + var balanceAvailable float64 + if _, ok := data[4].(float64); ok { + balanceAvailable = data[4].(float64) + } + b.Websocket.DataHandler <- WsWallet{ + Type: data[0].(string), + Currency: data[1].(string), + Balance: data[2].(float64), + UnsettledInterest: data[3].(float64), + BalanceAvailable: balanceAvailable, + } + } + case wsBalanceUpdate: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + b.Websocket.DataHandler <- WsBalanceInfo{ + TotalAssetsUnderManagement: data[0].(float64), + NetAssetsUnderManagement: data[1].(float64), + } + } + case wsMarginInfoUpdate: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + if data[0].(string) == "base" { + if infoBase, ok := chanData[2].([]interface{}); ok && len(infoBase) > 0 { + baseData := data[1].([]interface{}) + b.Websocket.DataHandler <- WsMarginInfoBase{ + UserProfitLoss: baseData[0].(float64), + UserSwaps: baseData[1].(float64), + MarginBalance: baseData[2].(float64), + MarginNet: baseData[3].(float64), + } + } + } + } + case wsFundingInfoUpdate: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + if data[0].(string) == "sym" { + symbolData := data[1].([]interface{}) + b.Websocket.DataHandler <- WsFundingInfo{ + YieldLoan: symbolData[0].(float64), + YieldLend: symbolData[1].(float64), + DurationLoan: symbolData[2].(float64), + DurationLend: symbolData[3].(float64), + } + } + } + case wsFundingTradeExecuted, wsFundingTradeUpdate: + if data, ok := chanData[2].([]interface{}); ok && len(data) > 0 { + b.Websocket.DataHandler <- WsFundingTrade{ + ID: int64(data[0].(float64)), + Symbol: data[1].(string), + MTSCreated: int64(data[2].(float64)), + OfferID: int64(data[3].(float64)), + Amount: data[4].(float64), + Rate: data[5].(float64), + Period: int64(data[6].(float64)), + Maker: data[7].(float64) == 1, + } } } } @@ -504,7 +646,7 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book for i := 0; i < len(book); i++ { switch { - case book[i].Count > 0: + case book[i].Price > 0: if book[i].Amount > 0 { // update bid orderbookUpdate.Bids = append(orderbookUpdate.Bids, orderbook.Item{Amount: book[i].Amount, Price: book[i].Price}) @@ -512,7 +654,7 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book // update ask orderbookUpdate.Asks = append(orderbookUpdate.Asks, orderbook.Item{Amount: book[i].Amount * -1, Price: book[i].Price}) } - case book[i].Count == 0: + case book[i].Price == 0: if book[i].Amount == 1 { // delete bid orderbookUpdate.Bids = append(orderbookUpdate.Bids, orderbook.Item{Amount: 0, Price: book[i].Price}) @@ -522,7 +664,6 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book } } } - orderbookUpdate.UpdateTime = time.Now() err := b.Websocket.Orderbook.Update(&orderbookUpdate) if err != nil { return err @@ -537,17 +678,29 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (b *Bitfinex) GenerateDefaultSubscriptions() { - var channels = []string{"book", "trades", "ticker"} + var channels = []string{ + wsBook, + wsTrades, + wsTicker, + wsCandles, + } var subscriptions []wshandler.WebsocketChannelSubscription for i := range channels { enabledPairs := b.GetEnabledPairs(asset.Spot) for j := range enabledPairs { + if strings.HasPrefix(enabledPairs[j].Base.String(), "f") { + log.Warnf(log.WebsocketMgr, + "%v - Websocket does not support funding currency %v, skipping", + b.Name, enabledPairs[j]) + continue + } b.appendOptionalDelimiter(&enabledPairs[j]) params := make(map[string]interface{}) - if channels[i] == "book" { - params["prec"] = "P0" + if channels[i] == wsBook { + params["prec"] = "R0" params["len"] = "100" } + subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ Channel: channels[i], Currency: enabledPairs[j], @@ -564,8 +717,14 @@ func (b *Bitfinex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscr req["event"] = "subscribe" req["channel"] = channelToSubscribe.Channel if channelToSubscribe.Currency.String() != "" { - req["pair"] = b.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String() + if channelToSubscribe.Channel == wsCandles { + // TODO: Add ability to select timescale + req["key"] = fmt.Sprintf("trade:1D:%v", + b.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String()) + } else { + req["symbol"] = b.FormatExchangeCurrency(channelToSubscribe.Currency, + asset.Spot).String() + } } if len(channelToSubscribe.Params) > 0 { for k, v := range channelToSubscribe.Params { @@ -588,3 +747,196 @@ func (b *Bitfinex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubs } return b.WebsocketConn.SendMessage(req) } + +// WsSendAuth sends a autheticated event payload +func (b *Bitfinex) WsSendAuth() error { + if !b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", b.Name) + } + nonce := strconv.FormatInt(time.Now().Unix(), 10) + payload := "AUTH" + nonce + request := WsAuthRequest{ + Event: "auth", + APIKey: b.API.Credentials.Key, + AuthPayload: payload, + AuthSig: crypto.HexEncodeToString( + crypto.GetHMAC( + crypto.HashSHA512_384, + []byte(payload), + []byte(b.API.Credentials.Secret))), + AuthNonce: nonce, + DeadManSwitch: 0, + } + err := b.AuthenticatedWebsocketConn.SendMessage(request) + if err != nil { + b.Websocket.SetCanUseAuthenticatedEndpoints(false) + return err + } + return nil +} + +// WsSendUnauth sends an unauthenticated payload +func (b *Bitfinex) WsSendUnauth() error { + req := make(map[string]string) + req["event"] = "unauth" + + return b.WebsocketConn.SendMessage(req) +} + +// WsAddSubscriptionChannel adds a new subscription channel to the +// WebsocketSubdChannels map in bitfinex.go (Bitfinex struct) +func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) { + chanInfo := WebsocketChanInfo{Pair: pair, Channel: channel} + b.WebsocketSubdChannels[chanID] = chanInfo + + if b.Verbose { + log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", + b.Name, + channel, + pair, + chanID) + } +} + +// WsNewOrder authenticated new order request +func (b *Bitfinex) WsNewOrder(data *WsNewOrderRequest) (string, error) { + data.CustomID = b.AuthenticatedWebsocketConn.GenerateMessageID(false) + request := makeRequestInterface(wsOrderNew, data) + resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(data.CustomID, request) + if err != nil { + return "", err + } + if resp == nil { + return "", errors.New(b.Name + " - Order message not returned") + } + var respData []interface{} + err = json.Unmarshal(resp, &respData) + if err != nil { + return "", err + } + responseDataDetail := respData[2].([]interface{}) + responseOrderDetail := responseDataDetail[4].([]interface{}) + var orderID string + if responseOrderDetail[0] != nil && responseOrderDetail[0].(float64) > 0 { + orderID = strconv.FormatFloat(responseOrderDetail[0].(float64), 'f', -1, 64) + } + errCode := responseDataDetail[6].(string) + errorMessage := responseDataDetail[7].(string) + + if strings.EqualFold(errCode, wsError) { + return orderID, errors.New(b.Name + " - " + errCode + ": " + errorMessage) + } + + return orderID, nil +} + +// WsModifyOrder authenticated modify order request +func (b *Bitfinex) WsModifyOrder(data *WsUpdateOrderRequest) error { + request := makeRequestInterface(wsOrderUpdate, data) + resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(data.OrderID, request) + if err != nil { + return err + } + if resp == nil { + return errors.New(b.Name + " - Order message not returned") + } + + var responseData []interface{} + err = json.Unmarshal(resp, &responseData) + if err != nil { + return err + } + responseOrderData := responseData[2].([]interface{}) + errCode := responseOrderData[6].(string) + errorMessage := responseOrderData[7].(string) + if strings.EqualFold(errCode, wsError) { + return errors.New(b.Name + " - " + errCode + ": " + errorMessage) + } + + return nil +} + +// WsCancelMultiOrders authenticated cancel multi order request +func (b *Bitfinex) WsCancelMultiOrders(orderIDs []int64) error { + cancel := WsCancelGroupOrdersRequest{ + OrderID: orderIDs, + } + request := makeRequestInterface(wsCancelMultipleOrders, cancel) + return b.AuthenticatedWebsocketConn.SendMessage(request) +} + +// WsCancelOrder authenticated cancel order request +func (b *Bitfinex) WsCancelOrder(orderID int64) error { + cancel := WsCancelOrderRequest{ + OrderID: orderID, + } + request := makeRequestInterface(wsOrderCancel, cancel) + resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(orderID, request) + if err != nil { + return err + } + if resp == nil { + return fmt.Errorf("%v - Order %v failed to cancel", b.Name, orderID) + } + var responseData []interface{} + err = json.Unmarshal(resp, &responseData) + if err != nil { + return err + } + responseOrderData := responseData[2].([]interface{}) + errCode := responseOrderData[6].(string) + errorMessage := responseOrderData[7].(string) + if strings.EqualFold(errCode, wsError) { + return errors.New(b.Name + " - " + errCode + ": " + errorMessage) + } + + return nil +} + +// WsCancelAllOrders authenticated cancel all orders request +func (b *Bitfinex) WsCancelAllOrders() error { + cancelAll := WsCancelAllOrdersRequest{All: 1} + request := makeRequestInterface(wsCancelMultipleOrders, cancelAll) + return b.AuthenticatedWebsocketConn.SendMessage(request) +} + +// WsNewOffer authenticated new offer request +func (b *Bitfinex) WsNewOffer(data *WsNewOfferRequest) error { + request := makeRequestInterface(wsFundingOrderNew, data) + return b.AuthenticatedWebsocketConn.SendMessage(request) +} + +// WsCancelOffer authenticated cancel offer request +func (b *Bitfinex) WsCancelOffer(orderID int64) error { + cancel := WsCancelOrderRequest{ + OrderID: orderID, + } + request := makeRequestInterface(wsFundingOrderCancel, cancel) + resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(orderID, request) + if err != nil { + return err + } + if resp == nil { + return fmt.Errorf("%v - Order %v failed to cancel", b.Name, orderID) + } + var responseData []interface{} + err = json.Unmarshal(resp, &responseData) + if err != nil { + return err + } + responseOrderData := responseData[2].([]interface{}) + errCode := responseOrderData[6].(string) + var errorMessage string + if responseOrderData[7] != nil { + errorMessage = responseOrderData[7].(string) + } + if strings.EqualFold(errCode, wsError) { + return errors.New(b.Name + " - " + errCode + ": " + errorMessage) + } + + return nil +} + +func makeRequestInterface(channelName string, data interface{}) []interface{} { + return []interface{}{0, channelName, nil, data} +} diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index f5337b75..95838ffc 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -87,7 +87,6 @@ func (b *Bitfinex) SetDefaults() { CancelOrder: true, SubmitOrder: true, SubmitOrders: true, - ModifyOrder: true, DepositHistory: true, WithdrawalHistory: true, TradeFetching: true, @@ -99,12 +98,21 @@ func (b *Bitfinex) SetDefaults() { CryptoWithdrawalFee: true, }, WebsocketCapabilities: protocol.Features{ + AccountBalance: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + ModifyOrder: true, TickerFetching: true, + KlineFetching: true, TradeFetching: true, OrderbookFetching: true, + AccountInfo: true, Subscribe: true, Unsubscribe: true, AuthenticatedEndpoints: true, + MessageCorrelation: true, + DeadMansSwitch: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | exchange.AutoWithdrawFiatWithAPIPermission, @@ -121,7 +129,7 @@ func (b *Bitfinex) SetDefaults() { b.API.Endpoints.URLDefault = bitfinexAPIURLBase b.API.Endpoints.URL = b.API.Endpoints.URLDefault - b.API.Endpoints.WebsocketURL = bitfinexWebsocket + b.API.Endpoints.WebsocketURL = publicBitfinexWebsocketEndpoint b.Websocket = wshandler.New() b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout @@ -146,7 +154,7 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error { Verbose: exch.Verbose, AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: bitfinexWebsocket, + DefaultURL: publicBitfinexWebsocketEndpoint, ExchangeName: exch.Name, RunningURL: exch.API.Endpoints.WebsocketURL, Connector: b.WsConnect, @@ -166,6 +174,14 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error { ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, } + b.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ + ExchangeName: b.Name, + URL: authenticatedBitfinexWebsocketEndpoint, + ProxyURL: b.Websocket.GetProxyAddress(), + Verbose: b.Verbose, + ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, + ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + } b.Websocket.Orderbook.Setup( exch.WebsocketOrderbookBufferLimit, @@ -318,6 +334,7 @@ func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (order func (b *Bitfinex) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo response.Exchange = b.Name + accountBalance, err := b.GetAccountBalance() if err != nil { return response, err @@ -358,39 +375,67 @@ func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([] } // SubmitOrder submits a new order -func (b *Bitfinex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { +func (b *Bitfinex) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - if err := s.Validate(); err != nil { + err := o.Validate() + if err != nil { return submitOrderResponse, err } + if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + submitOrderResponse.OrderID, err = b.WsNewOrder(&WsNewOrderRequest{ + CustomID: b.AuthenticatedWebsocketConn.GenerateMessageID(false), + Type: o.OrderType.String(), + Symbol: b.FormatExchangeCurrency(o.Pair, asset.Spot).String(), + Amount: o.Amount, + Price: o.Price, + }) + if err != nil { + return submitOrderResponse, err + } + } else { + var response Order + isBuying := o.OrderSide == order.Buy + b.appendOptionalDelimiter(&o.Pair) + response, err = b.NewOrder(o.Pair.String(), + o.Amount, + o.Price, + isBuying, + o.OrderType.String(), + false) + if err != nil { + return submitOrderResponse, err + } + if response.OrderID > 0 { + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) + } + if response.RemainingAmount == 0 { + submitOrderResponse.FullyMatched = true + } - var isBuying bool - if s.OrderSide == order.Buy { - isBuying = true - } - b.appendOptionalDelimiter(&s.Pair) - response, err := b.NewOrder(s.Pair.String(), - s.Amount, - s.Price, - isBuying, - s.OrderType.String(), - false) - - if response.OrderID > 0 { - submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) - } - - if err == nil { submitOrderResponse.IsOrderPlaced = true } - return submitOrderResponse, err } // ModifyOrder will allow of changing orderbook placement and limit to // market conversion func (b *Bitfinex) ModifyOrder(action *order.Modify) (string, error) { - return "", common.ErrFunctionNotSupported + orderIDInt, err := strconv.ParseInt(action.OrderID, 10, 64) + if err != nil { + return action.OrderID, err + } + if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + if action.Side == order.Sell && action.Amount > 0 { + action.Amount = -1 * action.Amount + } + err = b.WsModifyOrder(&WsUpdateOrderRequest{ + OrderID: orderIDInt, + Price: action.Price, + Amount: action.Amount, + }) + return action.OrderID, err + } + return "", common.ErrNotYetImplemented } // CancelOrder cancels an order by its corresponding ID number @@ -399,13 +444,22 @@ func (b *Bitfinex) CancelOrder(order *order.Cancel) error { if err != nil { return err } - _, err = b.CancelExistingOrder(orderIDInt) + if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + err = b.WsCancelOrder(orderIDInt) + } else { + _, err = b.CancelExistingOrder(orderIDInt) + } return err } // CancelAllOrders cancels all orders associated with a currency pair func (b *Bitfinex) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { - _, err := b.CancelAllExistingOrders() + var err error + if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + err = b.WsCancelAllOrders() + } else { + _, err = b.CancelAllExistingOrders() + } return order.CancelAllResponse{}, err } @@ -422,7 +476,8 @@ func (b *Bitfinex) GetDepositAddress(cryptocurrency currency.Code, accountID str return "", err } - resp, err := b.NewDeposit(method, accountID, 0) + var resp DepositResponse + resp, err = b.NewDeposit(method, accountID, 0) if err != nil { return "", err } @@ -547,7 +602,7 @@ func (b *Bitfinex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, orderDetail.Status = order.UnknownStatus } - // API docs discrepency. Example contains prefixed "exchange " + // API docs discrepancy. Example contains prefixed "exchange " // Return type suggests “market” / “limit” / “stop” / “trailing-stop” orderType := strings.Replace(resp[i].Type, "exchange ", "", 1) if orderType == "trailing-stop" { diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index 3364b944..31a87a5c 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -1,6 +1,8 @@ package bitflyer import ( + "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -9,7 +11,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" - log "github.com/thrasher-corp/gocryptotrader/logger" ) // Please supply your own keys here for due diligence testing @@ -21,19 +22,16 @@ const ( var b Bitflyer -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Bitflyer load config error", err) + log.Fatal("Bitflyer load config error", err) } bitflyerConfig, err := cfg.GetExchangeConfig("Bitflyer") if err != nil { - t.Error("bitflyer Setup() init error") + log.Fatal("bitflyer Setup() init error") } bitflyerConfig.API.AuthenticatedSupport = true @@ -42,8 +40,10 @@ func TestSetup(t *testing.T) { err = b.Setup(bitflyerConfig) if err != nil { - t.Fatal("Bitflyer setup error", err) + log.Fatal("Bitflyer setup error", err) } + + os.Exit(m.Run()) } func TestGetLatestBlockCA(t *testing.T) { @@ -85,7 +85,7 @@ func TestGetAddressInfoCA(t *testing.T) { t.Error("Bitflyer - GetAddressInfoCA() error:", err) } if v.UnconfirmedBalance == 0 || v.ConfirmedBalance == 0 { - log.Warn(log.ExchangeSys, "Donation wallet is empty :( - please consider donating") + t.Log("Donation wallet is empty :( - please consider donating") } } @@ -171,7 +171,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -183,11 +183,10 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() var feeBuilder = setFeeBuilder() - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil { t.Error(err) @@ -256,20 +255,16 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() + t.Parallel() expectedResult := exchange.AutoWithdrawFiatText + " & " + exchange.WithdrawCryptoViaWebsiteOnlyText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -283,9 +278,7 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -303,9 +296,7 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -328,9 +319,7 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -351,9 +340,7 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -374,8 +361,11 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -385,10 +375,6 @@ func TestWithdraw(t *testing.T) { Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - _, err := b.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) if err != common.ErrNotYetImplemented { t.Errorf("Expected 'Not Yet Implemented', received %v", err) @@ -396,6 +382,10 @@ func TestWithdraw(t *testing.T) { } func TestModifyOrder(t *testing.T) { + t.Parallel() + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -403,9 +393,7 @@ func TestModifyOrder(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -419,9 +407,7 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index 8f82f248..b2331ba2 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -1,6 +1,8 @@ package bithumb import ( + "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -20,19 +22,16 @@ const ( var b Bithumb -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Bithumb load config error", err) + log.Fatal("Bithumb load config error", err) } bitConfig, err := cfg.GetExchangeConfig("Bithumb") if err != nil { - t.Error("Bithumb Setup() init error") + log.Fatal("Bithumb Setup() init error") } bitConfig.API.AuthenticatedSupport = true @@ -41,8 +40,10 @@ func TestSetup(t *testing.T) { err = b.Setup(bitConfig) if err != nil { - t.Fatal("Bithumb setup error", err) + log.Fatal("Bithumb setup error", err) } + + os.Exit(m.Run()) } func TestGetTradablePairs(t *testing.T) { @@ -87,7 +88,7 @@ func TestGetTransactionHistory(t *testing.T) { func TestGetAccountBalance(t *testing.T) { t.Parallel() - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { t.Skip() } @@ -98,11 +99,11 @@ func TestGetAccountBalance(t *testing.T) { } func TestGetWalletAddress(t *testing.T) { - if apiKey == "" || apiSecret == "" { + t.Parallel() + if !areTestAPIKeysSet() { t.Skip() } - t.Parallel() _, err := b.GetWalletAddress("") if err == nil { t.Error("Bithumb GetWalletAddress() Expected error") @@ -167,7 +168,7 @@ func TestWithdrawCrypto(t *testing.T) { func TestRequestKRWDepositDetails(t *testing.T) { t.Parallel() - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { t.Skip() } _, err := b.RequestKRWDepositDetails() @@ -213,7 +214,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -225,10 +226,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) var feeBuilder = setFeeBuilder() - // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.0025) || err != nil { t.Error(err) @@ -296,20 +294,16 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() + t.Parallel() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.AutoWithdrawFiatText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, OrderSide: order.Sell, @@ -324,9 +318,7 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -346,9 +338,7 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -373,15 +363,12 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -399,15 +386,12 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -431,7 +415,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestGetAccountInfo(t *testing.T) { t.Parallel() - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { _, err := b.GetAccountInfo() if err != nil { t.Error("Bithumb GetAccountInfo() error", err) @@ -445,6 +429,7 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + t.Parallel() curr := currency.NewPairFromString("BTCUSD") _, err := b.ModifyOrder(&order.Modify{ OrderID: "1337", @@ -458,8 +443,7 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -483,9 +467,7 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -519,9 +501,7 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - + t.Parallel() if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -534,7 +514,8 @@ func TestWithdrawInternationalBank(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { - if apiKey != "" && apiSecret != "" { + t.Parallel() + if areTestAPIKeysSet() { _, err := b.GetDepositAddress(currency.BTC, "") if err != nil { t.Error("GetDepositAddress() error", err) diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 91f8f983..8d08da2b 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -220,9 +220,9 @@ func (b *Bithumb) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbo // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *Bithumb) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - currency := p.Base.String() + curr := p.Base.String() - orderbookNew, err := b.GetOrderBook(currency) + orderbookNew, err := b.GetOrderBook(curr) if err != nil { return orderBook, err } @@ -311,20 +311,25 @@ func (b *Bithumb) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { if s.OrderSide == order.Buy { var result MarketBuy result, err = b.MarketBuyOrder(s.Pair.Base.String(), s.Amount) + if err != nil { + return submitOrderResponse, err + } orderID = result.OrderID } else if s.OrderSide == order.Sell { var result MarketSell result, err = b.MarketSellOrder(s.Pair.Base.String(), s.Amount) + if err != nil { + return submitOrderResponse, err + } orderID = result.OrderID } - if orderID != "" { submitOrderResponse.OrderID = orderID + submitOrderResponse.FullyMatched = true } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + submitOrderResponse.IsOrderPlaced = true + + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -358,12 +363,13 @@ func (b *Bithumb) CancelAllOrders(orderCancellation *order.Cancel) (order.Cancel } var allOrders []OrderData - for _, currency := range b.GetEnabledPairs(asset.Spot) { + currs := b.GetEnabledPairs(asset.Spot) + for i := range currs { orders, err := b.GetOrders("", orderCancellation.Side.String(), "100", "", - currency.Base.String()) + currs[i].Base.String()) if err != nil { return cancelAllOrdersResponse, err } diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index c1dd5b64..f92fdee0 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -1,7 +1,9 @@ package bitmex import ( + "log" "net/http" + "os" "sync" "testing" "time" @@ -25,19 +27,16 @@ const ( var b Bitmex -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Bitmex load config error", err) + log.Fatal("Bitmex load config error", err) } bitmexConfig, err := cfg.GetExchangeConfig("Bitmex") if err != nil { - t.Error("Bitmex Setup() init error") + log.Fatal("Bitmex Setup() init error") } bitmexConfig.API.AuthenticatedSupport = true @@ -47,8 +46,9 @@ func TestSetup(t *testing.T) { err = b.Setup(bitmexConfig) if err != nil { - t.Fatal("Bitmex setup error", err) + log.Fatal("Bitmex setup error", err) } + os.Exit(m.Run()) } func TestStart(t *testing.T) { @@ -386,7 +386,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -398,9 +398,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.00075) || err != nil { @@ -469,21 +466,15 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.WithdrawCryptoWithEmailText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -497,9 +488,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, @@ -521,9 +509,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -548,15 +533,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "123456789012345678901234567890123456", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -574,15 +555,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "123456789012345678901234567890123456", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -605,7 +582,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { _, err := b.GetAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) @@ -619,6 +596,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := b.ModifyOrder(&order.Modify{OrderID: "1337"}) if err == nil { t.Error("ModifyOrder() error") @@ -626,9 +606,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) - withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -653,9 +630,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -668,9 +642,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -698,8 +669,6 @@ func TestGetDepositAddress(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { - b.SetDefaults() - TestSetup(t) if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 306a8a9a..4f65011d 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -432,15 +432,18 @@ func (b *Bitmex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } response, err := b.CreateOrder(&orderNewParams) + if err != nil { + return submitOrderResponse, err + } if response.OrderID != "" { submitOrderResponse.OrderID = response.OrderID } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 77d8b575..39d7dfd4 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -38,7 +38,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, @@ -319,7 +319,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.AutoWithdrawFiatText - withdrawPermissions := b.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index facc2950..5b74ddf6 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -364,16 +364,18 @@ func (b *Bitstamp) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.Amount, buy, market) - + if err != nil { + return submitOrderResponse, err + } if response.ID > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.ID, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true + submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index 4d9489d5..a564dd16 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -329,7 +329,7 @@ func (b *Bittrex) Withdraw(currency, paymentID, address string, quantity float64 var id UUID values := url.Values{} values.Set("currency", currency) - values.Set("quantity", fmt.Sprintf("%v", quantity)) + values.Set("quantity", strconv.FormatFloat(quantity, 'f', -1, 64)) values.Set("address", address) if len(paymentID) > 0 { values.Set("paymentid", paymentID) diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 976fe165..5cc15712 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -1,6 +1,8 @@ package bittrex import ( + "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -19,22 +21,16 @@ const ( var b Bittrex -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { b.SetDefaults() - if b.Name != "Bittrex" { - t.Error("Bittrex - SetDefaults() error") - } -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Bittrex load config error", err) + log.Fatal("Bittrex load config error", err) } bConfig, err := cfg.GetExchangeConfig("Bittrex") if err != nil { - t.Error("Bittrex Setup() init error") + log.Fatal("Bittrex Setup() init error") } bConfig.API.Credentials.Key = apiKey bConfig.API.Credentials.Secret = apiSecret @@ -42,13 +38,15 @@ func TestSetup(t *testing.T) { err = b.Setup(bConfig) if err != nil { - t.Fatal("Bittrex setup error", err) + log.Fatal("Bittrex setup error", err) } if !b.IsEnabled() || !b.API.AuthenticatedSupport || b.Verbose || len(b.BaseCurrencies) < 1 { - t.Error("Bittrex Setup values not set correctly") + log.Fatal("Bittrex Setup values not set correctly") } + + os.Exit(m.Run()) } func TestGetMarkets(t *testing.T) { @@ -236,7 +234,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -248,11 +246,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var feeBuilder = setFeeBuilder() - // CryptocurrencyTradeFee Basic if resp, err := b.GetFee(feeBuilder); resp != float64(0.0025) || err != nil { t.Error(err) @@ -320,20 +314,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - b.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, @@ -351,9 +339,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - b.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -373,9 +358,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -401,15 +383,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -427,15 +405,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -458,6 +432,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := b.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -465,8 +442,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - b.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -490,9 +465,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -506,9 +478,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - b.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index da8a543b..0af7bb87 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -329,7 +329,6 @@ func (b *Bittrex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } buy := s.OrderSide == order.Buy - if s.OrderType != order.Limit { return submitOrderResponse, errors.New("limit orders only supported on exchange") @@ -346,16 +345,16 @@ func (b *Bittrex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.Amount, s.Price) } - + if err != nil { + return submitOrderResponse, err + } if response.Result.ID != "" { submitOrderResponse.OrderID = response.Result.ID } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/btse/btse_test.go b/exchanges/btse/btse_test.go index d97130b0..d0cd92fa 100644 --- a/exchanges/btse/btse_test.go +++ b/exchanges/btse/btse_test.go @@ -205,7 +205,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } b.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -320,7 +320,6 @@ func TestCancelExchangeOrder(t *testing.T) { currencyPair := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-") - var orderCancellation = &order.Cancel{ OrderID: "b334ecef-2b42-4998-b8a4-b6b14f6d2671", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -341,7 +340,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { currencyPair := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-") - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 5a5ca079..02444533 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -3,6 +3,7 @@ package btse import ( "errors" "fmt" + "strconv" "strings" "sync" "time" @@ -357,7 +358,9 @@ func (b *BTSE) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { resp.IsOrderPlaced = true resp.OrderID = *r } - + if s.OrderType == order.Market { + resp.FullyMatched = true + } return resp, nil } @@ -475,7 +478,7 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) { } od.Trades = append(od.Trades, order.TradeHistory{ Timestamp: createdAt, - TID: fills[i].ID, + TID: strconv.FormatInt(fills[i].ID, 10), Price: fills[i].Price, Amount: fills[i].Amount, Exchange: b.Name, @@ -569,7 +572,7 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err } openOrder.Trades = append(openOrder.Trades, order.TradeHistory{ Timestamp: createdAt, - TID: fills[i].ID, + TID: strconv.FormatInt(fills[i].ID, 10), Price: fills[i].Price, Amount: fills[i].Amount, Exchange: b.Name, diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 95d84f4a..b594603c 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1,7 +1,9 @@ package coinbasepro import ( + "log" "net/http" + "os" "testing" "time" @@ -25,20 +27,18 @@ const ( testPair = "BTC-USD" ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { c.SetDefaults() c.Requester.SetRateLimit(false, time.Second, 1) -} -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("coinbasepro load config error", err) + log.Fatal("coinbasepro load config error", err) } gdxConfig, err := cfg.GetExchangeConfig("CoinbasePro") if err != nil { - t.Error("coinbasepro Setup() init error") + log.Fatal("coinbasepro Setup() init error") } gdxConfig.API.Credentials.Key = apiKey gdxConfig.API.Credentials.Secret = apiSecret @@ -47,8 +47,10 @@ func TestSetup(t *testing.T) { gdxConfig.API.AuthenticatedWebsocketSupport = true err = c.Setup(gdxConfig) if err != nil { - t.Fatal("CoinbasePro setup error", err) + log.Fatal("CoinbasePro setup error", err) } + + os.Exit(m.Run()) } func TestGetProducts(t *testing.T) { @@ -198,7 +200,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() c.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -210,12 +212,9 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - c.SetDefaults() - TestSetup(t) - var feeBuilder = setFeeBuilder() - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic if resp, err := c.GetFee(feeBuilder); resp != float64(0.003) || err != nil { t.Error(err) @@ -371,7 +370,6 @@ func TestCalculateTradingFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - c.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText withdrawPermissions := c.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { @@ -380,9 +378,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { } func TestGetActiveOrders(t *testing.T) { - c.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, @@ -398,9 +393,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - c.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, @@ -422,9 +414,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -450,15 +439,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -476,15 +461,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -507,6 +488,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := c.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -514,8 +498,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - c.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -539,9 +521,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -564,9 +543,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -597,8 +573,6 @@ func TestGetDepositAddress(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { - c.SetDefaults() - TestSetup(t) if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 96c80b44..6994211b 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -3,7 +3,6 @@ package coinbasepro import ( "encoding/json" "errors" - "fmt" "net/http" "strconv" "time" @@ -311,7 +310,7 @@ func (c *CoinbasePro) Subscribe(channelToSubscribe wshandler.WebsocketChannelSub }, } if channelToSubscribe.Channel == "user" || channelToSubscribe.Channel == "full" { - n := fmt.Sprintf("%v", time.Now().Unix()) + n := strconv.FormatInt(time.Now().Unix(), 10) message := n + "GET" + "/users/self/verify" hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(message), []byte(c.API.Credentials.Secret)) diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index a66ebf41..959a3de8 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -400,16 +400,19 @@ func (c *CoinbasePro) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) default: err = errors.New("order type not supported") } - + if err != nil { + return submitOrderResponse, err + } + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true + } if response != "" { submitOrderResponse.OrderID = response } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -509,7 +512,7 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta var orders []order.Detail for i := range respOrders { - currency := currency.NewPairDelimiter(respOrders[i].ProductID, + curr := currency.NewPairDelimiter(respOrders[i].ProductID, c.GetPairFormat(asset.Spot, false).Delimiter) orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) orderType := order.Type(strings.ToUpper(respOrders[i].Type)) @@ -530,7 +533,7 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta OrderType: orderType, OrderDate: orderDate, OrderSide: orderSide, - CurrencyPair: currency, + CurrencyPair: curr, Exchange: c.Name, }) } @@ -545,9 +548,9 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta // Can Limit response to specific order status func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var respOrders []GeneralizedOrderResponse - for _, currency := range req.Currencies { + for i := range req.Currencies { resp, err := c.GetOrders([]string{"done", "settled"}, - c.FormatExchangeCurrency(currency, asset.Spot).String()) + c.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String()) if err != nil { return nil, err } @@ -556,7 +559,7 @@ func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Deta var orders []order.Detail for i := range respOrders { - currency := currency.NewPairDelimiter(respOrders[i].ProductID, + curr := currency.NewPairDelimiter(respOrders[i].ProductID, c.GetPairFormat(asset.Spot, false).Delimiter) orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) orderType := order.Type(strings.ToUpper(respOrders[i].Type)) @@ -577,7 +580,7 @@ func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Deta OrderType: orderType, OrderDate: orderDate, OrderSide: orderSide, - CurrencyPair: currency, + CurrencyPair: curr, Exchange: c.Name, }) } diff --git a/exchanges/coinbene/coinbene.go b/exchanges/coinbene/coinbene.go index 4bb0653e..482edab7 100644 --- a/exchanges/coinbene/coinbene.go +++ b/exchanges/coinbene/coinbene.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) @@ -27,8 +28,6 @@ const ( coinbeneAPIURL = "https://openapi-exchange.coinbene.com/api/exchange/" coinbeneAuthPath = "/api/exchange/v2" coinbeneAPIVersion = "v2" - buy = "buy" - sell = "sell" // Public endpoints coinbeneFetchTicker = "/market/ticker/one" @@ -114,9 +113,9 @@ func (c *Coinbene) PlaceOrder(price, quantity float64, symbol, direction, client params := url.Values{} params.Set("symbol", symbol) switch direction { - case sell: + case order.Buy.Lower(): params.Set("direction", "2") - case buy: + case order.Sell.Lower(): params.Set("direction", "1") default: return resp, diff --git a/exchanges/coinbene/coinbene_test.go b/exchanges/coinbene/coinbene_test.go index b870ce71..5c77b5f1 100644 --- a/exchanges/coinbene/coinbene_test.go +++ b/exchanges/coinbene/coinbene_test.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Please supply your own keys here for due diligence testing @@ -103,7 +104,7 @@ func TestPlaceOrder(t *testing.T) { if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly") } - _, err := c.PlaceOrder(140, 1, btcusdt, "buy", "") + _, err := c.PlaceOrder(1, 1, btcusdt, order.Buy.Lower(), "") if err != nil { t.Error(err) } diff --git a/exchanges/coinbene/coinbene_wrapper.go b/exchanges/coinbene/coinbene_wrapper.go index d88c751f..373222f1 100644 --- a/exchanges/coinbene/coinbene_wrapper.go +++ b/exchanges/coinbene/coinbene_wrapper.go @@ -3,6 +3,7 @@ package coinbene import ( "fmt" "strconv" + "strings" "sync" "time" @@ -504,9 +505,9 @@ func (c *Coinbene) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([] for y := range tempData.OpenOrders { tempResp.Exchange = c.Name tempResp.CurrencyPair = getOrdersRequest.Currencies[x] - tempResp.OrderSide = buy - if tempData.OpenOrders[y].OrderType == sell { - tempResp.OrderSide = sell + tempResp.OrderSide = order.Buy + if strings.EqualFold(tempData.OpenOrders[y].OrderType, order.Sell.String()) { + tempResp.OrderSide = order.Sell } t, err = time.Parse(time.RFC3339, tempData.OpenOrders[y].OrderTime) if err != nil { @@ -556,7 +557,7 @@ func (c *Coinbene) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([] tempResp.Exchange = c.Name tempResp.CurrencyPair = getOrdersRequest.Currencies[x] tempResp.OrderSide = order.Buy - if tempData.Data[y].OrderType == sell { + if strings.EqualFold(tempData.Data[y].OrderType, order.Sell.String()) { tempResp.OrderSide = order.Sell } t, err = time.Parse(time.RFC3339, tempData.Data[y].OrderTime) diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 4ba2ec97..7cb7157e 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "github.com/thrasher-corp/gocryptotrader/common/crypto" @@ -105,9 +106,8 @@ func (c *COINUT) GetTrades(instrumentID int) (Trades, error) { } // GetUserBalance returns the full user balance -func (c *COINUT) GetUserBalance() (UserBalance, error) { - result := UserBalance{} - +func (c *COINUT) GetUserBalance() (*UserBalance, error) { + var result *UserBalance return result, c.SendHTTPRequest(coinutBalance, nil, true, &result) } @@ -117,26 +117,16 @@ func (c *COINUT) NewOrder(instrumentID int64, quantity, price float64, buy bool, params := make(map[string]interface{}) params["inst_id"] = instrumentID if price > 0 { - params["price"] = fmt.Sprintf("%v", price) + params["price"] = strconv.FormatFloat(price, 'f', -1, 64) } - params["qty"] = fmt.Sprintf("%v", quantity) + params["qty"] = strconv.FormatFloat(quantity, 'f', -1, 64) params["side"] = order.Buy.String() if !buy { params["side"] = order.Sell.String() } params["client_ord_id"] = orderID - err := c.SendHTTPRequest(coinutOrder, params, true, &result) - if _, ok := result.(OrderRejectResponse); ok { - return result.(OrderRejectResponse), err - } - if _, ok := result.(OrderFilledResponse); ok { - return result.(OrderFilledResponse), err - } - if _, ok := result.(OrdersBase); ok { - return result.(OrdersBase), err - } - return result, err + return result, c.SendHTTPRequest(coinutOrder, params, true, &result) } // NewOrders places multiple orders on the exchange @@ -425,3 +415,77 @@ func getInternationalBankDepositFee(c currency.Code, amount float64) float64 { return fee } + +// IsLoaded returns whether or not the instrument map has been seeded +func (i *instrumentMap) IsLoaded() bool { + i.m.Lock() + defer i.m.Unlock() + return i.Loaded +} + +// Seed seeds the instrument map +func (i *instrumentMap) Seed(curr string, id int64) { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + i.Instruments = make(map[string]int64) + } + + // check to see if the instrument already exists + _, ok := i.Instruments[curr] + if ok { + return + } + + i.Instruments[curr] = id + i.Loaded = true +} + +// LookupInstrument looks up an instrument based on an id +func (i *instrumentMap) LookupInstrument(id int64) string { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + return "" + } + + for k, v := range i.Instruments { + if v == id { + return k + } + } + return "" +} + +// LookupID looks up an ID based on a string +func (i *instrumentMap) LookupID(curr string) int64 { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + return 0 + } + + if ic, ok := i.Instruments[curr]; ok { + return ic + } + return 0 +} + +// GetInstrumentIDs returns a list of IDs +func (i *instrumentMap) GetInstrumentIDs() []int64 { + i.m.Lock() + defer i.m.Unlock() + + if !i.Loaded { + return nil + } + + var instruments []int64 + for _, x := range i.Instruments { + instruments = append(instruments, x) + } + return instruments +} diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 622d75c1..c3616708 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -1,7 +1,9 @@ package coinut import ( + "log" "net/http" + "os" "testing" "github.com/gorilla/websocket" @@ -24,45 +26,42 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { c.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Coinut load config error", err) + log.Fatal("Coinut load config error", err) } bConfig, err := cfg.GetExchangeConfig("COINUT") if err != nil { - t.Error("Coinut Setup() init error") + log.Fatal("Coinut Setup() init error") } bConfig.API.AuthenticatedSupport = true bConfig.API.AuthenticatedWebsocketSupport = true bConfig.API.Credentials.Key = apiKey bConfig.API.Credentials.ClientID = clientID - bConfig.Verbose = true err = c.Setup(bConfig) if err != nil { - t.Fatal("Coinut setup error", err) + log.Fatal("Coinut setup error", err) } - if !c.IsEnabled() || !c.Verbose || - c.Websocket.IsEnabled() || len(c.BaseCurrencies) < 1 { - t.Error("Coinut Setup values not set correctly") - } + c.SeedInstruments() + + os.Exit(m.Run()) } func setupWSTestAuth(t *testing.T) { if wsSetupRan { return } - c.SetDefaults() - TestSetup(t) + if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } + if areTestAPIKeysSet() { + c.Websocket.SetCanUseAuthenticatedEndpoints(true) + } c.WebsocketConn = &wshandler.WebsocketConnection{ ExchangeName: c.Name, URL: coinutWebsocketURL, @@ -71,6 +70,7 @@ func setupWSTestAuth(t *testing.T) { ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, } + var dialer websocket.Dialer err := c.WebsocketConn.Dial(&dialer, http.Header{}) if err != nil { @@ -84,6 +84,10 @@ func setupWSTestAuth(t *testing.T) { t.Error(err) } wsSetupRan = true + _, err = c.WsGetInstruments() + if err != nil { + t.Error(err) + } } func TestGetInstruments(t *testing.T) { @@ -130,12 +134,8 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - c.SetDefaults() - TestSetup(t) t.Parallel() - var feeBuilder = setFeeBuilder() - // CryptocurrencyTradeFee Basic if resp, err := c.GetFee(feeBuilder); resp != float64(0.001) || err != nil { t.Error(err) @@ -248,38 +248,29 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - c.SetDefaults() expectedResult := exchange.WithdrawCryptoViaWebsiteOnlyText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := c.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - c.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } - _, err := c.GetActiveOrders(&getOrdersRequest) if areTestAPIKeysSet() && err != nil { t.Errorf("Could not get open orders: %s", err) } } -func TestGetOrderHistory(t *testing.T) { - c.SetDefaults() - TestSetup(t) - +func TestGetOrderHistoryWrapper(t *testing.T) { + setupWSTestAuth(t) var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, - currency.LTC)}, + currency.USD)}, } _, err := c.GetOrderHistory(&getOrdersRequest) @@ -295,9 +286,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -311,7 +299,7 @@ func TestSubmitOrder(t *testing.T) { OrderType: order.Limit, Price: 1, Amount: 1, - ClientID: "meowOrder", + ClientID: "123", } response, err := c.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) { @@ -322,15 +310,10 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - + currencyPair := currency.NewPair(currency.BTC, currency.USD) var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -348,15 +331,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -393,6 +372,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := c.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -400,8 +382,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - c.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -422,9 +402,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -437,9 +414,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - c.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -473,14 +447,14 @@ func TestWsAuthSubmitOrder(t *testing.T) { if !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - order := WsSubmitOrderParameters{ + ord := WsSubmitOrderParameters{ Amount: 1, Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 1, Price: 1, Side: order.Buy, } - _, err := c.wsSubmitOrder(&order) + _, err := c.wsSubmitOrder(&ord) if err != nil { t.Error(err) } @@ -513,12 +487,13 @@ func TestWsAuthSubmitOrders(t *testing.T) { } // TestWsAuthCancelOrders dials websocket, cancels orders +// doesn't care about if the order cancellations fail func TestWsAuthCancelOrders(t *testing.T) { setupWSTestAuth(t) if !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - order := WsCancelOrderParameters{ + ord := WsCancelOrderParameters{ Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 1, } @@ -526,9 +501,28 @@ func TestWsAuthCancelOrders(t *testing.T) { Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 2, } - _, errs := c.wsCancelOrders([]WsCancelOrderParameters{order, order2}) - if len(errs) > 0 { - t.Error(errs) + resp, err := c.wsCancelOrders([]WsCancelOrderParameters{ord, order2}) + if err != nil { + t.Error(err) + } + if resp.Status[0] != "OK" { + t.Error("Order failed to cancel") + } +} + +// TestWsAuthCancelOrders dials websocket, cancels orders +// Checks that the wrapper oversight works +func TestWsAuthCancelOrdersWrapper(t *testing.T) { + setupWSTestAuth(t) + if !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } + orderDetails := order.Cancel{ + CurrencyPair: currency.NewPair(currency.LTC, currency.BTC), + } + _, err := c.CancelAllOrders(&orderDetails) + if err != nil { + t.Error(err) } } @@ -538,20 +532,23 @@ func TestWsAuthCancelOrder(t *testing.T) { if !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - order := WsCancelOrderParameters{ + ord := &WsCancelOrderParameters{ Currency: currency.NewPair(currency.LTC, currency.BTC), OrderID: 1, } - err := c.wsCancelOrder(order) + resp, err := c.wsCancelOrder(ord) if err != nil { t.Error(err) } + if len(resp.Status) >= 1 && resp.Status[0] != "OK" { + t.Errorf("Failed to cancel order") + } } // TestWsAuthGetOpenOrders dials websocket, retrieves open orders func TestWsAuthGetOpenOrders(t *testing.T) { setupWSTestAuth(t) - err := c.wsGetOpenOrders(currency.NewPair(currency.LTC, currency.BTC)) + _, err := c.wsGetOpenOrders(currency.NewPair(currency.LTC, currency.BTC).String()) if err != nil { t.Error(err) } @@ -573,7 +570,6 @@ func TestCurrencyMapIsLoaded(t *testing.T) { func TestCurrencyMapSeed(t *testing.T) { t.Parallel() var i instrumentMap - // Test non-seeded lookups if id := i.LookupInstrument(1234); id != "" { t.Error("unexpected result") diff --git a/exchanges/coinut/coinut_types.go b/exchanges/coinut/coinut_types.go index b42d3425..a647afdf 100644 --- a/exchanges/coinut/coinut_types.go +++ b/exchanges/coinut/coinut_types.go @@ -1,7 +1,6 @@ package coinut import ( - "strings" "sync" "github.com/thrasher-corp/gocryptotrader/currency" @@ -177,7 +176,7 @@ type CancelOrdersResponse struct { Results []struct { OrderID int64 `json:"order_id"` Status string `json:"status"` - InstrumentID int `json:"inst_id"` + InstrumentID int64 `json:"inst_id"` } `json:"results"` } @@ -372,10 +371,10 @@ type WsTradeUpdate struct { // WsInstrumentList defines instrument list type WsInstrumentList struct { - Spot map[string][]WsSupportedCurrency `json:"SPOT"` - Nonce int64 `json:"nonce"` - Reply string `json:"inst_list"` - Status []interface{} `json:"status"` + Spot map[string][]InstrumentBase `json:"SPOT"` + Nonce int64 `json:"nonce,omitempty"` + Reply string `json:"inst_list,omitempty"` + Status []interface{} `json:"status,omitempty"` } // WsSupportedCurrency defines supported currency on the exchange @@ -536,14 +535,15 @@ type WsOrderFilledResponse struct { // WsOrderData ws response data type WsOrderData struct { - ClientOrdID int64 `json:"client_ord_id"` - InstID int64 `json:"inst_id"` - OpenQty float64 `json:"open_qty,string"` - OrderID int64 `json:"order_id"` - Price float64 `json:"price,string"` - Qty float64 `json:"qty,string"` - Side string `json:"side"` - Timestamp int64 `json:"timestamp"` + ClientOrdID int64 `json:"client_ord_id"` + InstID int64 `json:"inst_id"` + OpenQty float64 `json:"open_qty,string"` + OrderID int64 `json:"order_id"` + Price float64 `json:"price,string"` + Qty float64 `json:"qty,string"` + Side string `json:"side"` + Timestamp int64 `json:"timestamp"` + Status []string `json:"status"` } // WsOrderFilledCommissionData ws response data @@ -656,20 +656,20 @@ type WsNewOrderResponse struct { // WsGetAccountBalanceResponse contains values of each currency type WsGetAccountBalanceResponse struct { - BCH string `json:"BCH"` - BTC string `json:"BTC"` - BTG string `json:"BTG"` - CAD string `json:"CAD"` - ETC string `json:"ETC"` - ETH string `json:"ETH"` - LCH string `json:"LCH"` - LTC string `json:"LTC"` - MYR string `json:"MYR"` - SGD string `json:"SGD"` - USD string `json:"USD"` - USDT string `json:"USDT"` - XMR string `json:"XMR"` - ZEC string `json:"ZEC"` + BCH float64 `json:"BCH,string"` + BTC float64 `json:"BTC,string"` + BTG float64 `json:"BTG,string"` + CAD float64 `json:"CAD,string"` + ETC float64 `json:"ETC,string"` + ETH float64 `json:"ETH,string"` + LCH float64 `json:"LCH,string"` + LTC float64 `json:"LTC,string"` + MYR float64 `json:"MYR,string"` + SGD float64 `json:"SGD,string"` + USD float64 `json:"USD,string"` + USDT float64 `json:"USDT,string"` + XMR float64 `json:"XMR,string"` + ZEC float64 `json:"ZEC,string"` Nonce int64 `json:"nonce"` Reply string `json:"reply"` Status []string `json:"status"` @@ -681,79 +681,3 @@ type instrumentMap struct { Loaded bool m sync.Mutex } - -// IsLoaded returns whether or not the instrument map has been seeded -func (i *instrumentMap) IsLoaded() bool { - i.m.Lock() - defer i.m.Unlock() - return i.Loaded -} - -// Seed seeds the instrument map -func (i *instrumentMap) Seed(currency string, id int64) { - i.m.Lock() - defer i.m.Unlock() - - if !i.Loaded { - i.Instruments = make(map[string]int64) - } - - // check to see if the instrument already exists - _, ok := i.Instruments[currency] - if ok { - return - } - - i.Instruments[currency] = id - i.Loaded = true -} - -// LookupInstrument looks up an instrument based on an id -func (i *instrumentMap) LookupInstrument(id int64) string { - i.m.Lock() - defer i.m.Unlock() - - if !i.Loaded { - return "" - } - - for k, v := range i.Instruments { - if v == id { - return k - } - } - return "" -} - -// LookupID looks up an ID based on a string -func (i *instrumentMap) LookupID(currency string) int64 { - i.m.Lock() - defer i.m.Unlock() - - if !i.Loaded { - return 0 - } - - for k, v := range i.Instruments { - if strings.EqualFold(currency, k) { - return v - } - } - return 0 -} - -// GetInstrumentIDs returns a list of IDs -func (i *instrumentMap) GetInstrumentIDs() []int64 { - i.m.Lock() - defer i.m.Unlock() - - if !i.Loaded { - return nil - } - - var instruments []int64 - for _, x := range i.Instruments { - instruments = append(instruments, x) - } - return instruments -} diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index 6d242239..c5dfbb19 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -17,6 +17,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" + log "github.com/thrasher-corp/gocryptotrader/logger" ) const ( @@ -25,8 +26,7 @@ const ( ) var ( - channels map[string]chan []byte - wsInstrumentMap instrumentMap + channels map[string]chan []byte ) // NOTE for speed considerations @@ -46,13 +46,17 @@ func (c *COINUT) WsConnect() error { } go c.WsHandleData() - if !wsInstrumentMap.IsLoaded() { - err = c.WsSetInstrumentList() + if !c.instrumentMap.IsLoaded() { + _, err = c.WsGetInstruments() if err != nil { return err } } - c.wsAuthenticate() + err = c.wsAuthenticate() + if err != nil { + c.Websocket.SetCanUseAuthenticatedEndpoints(false) + log.Error(log.WebsocketMgr, err) + } c.GenerateDefaultSubscriptions() // define bi-directional communication @@ -135,7 +139,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { return } - currencyPair := wsInstrumentMap.LookupInstrument(ticker.InstID) + currencyPair := c.instrumentMap.LookupInstrument(ticker.InstID) c.Websocket.DataHandler <- wshandler.TickerData{ Exchange: c.Name, Volume: ticker.Volume24, @@ -164,7 +168,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } - currencyPair := wsInstrumentMap.LookupInstrument(orderbooksnapshot.InstID) + currencyPair := c.instrumentMap.LookupInstrument(orderbooksnapshot.InstID) c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Exchange: c.Name, Asset: asset.Spot, @@ -184,7 +188,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } - currencyPair := wsInstrumentMap.LookupInstrument(orderbookUpdate.InstID) + currencyPair := c.instrumentMap.LookupInstrument(orderbookUpdate.InstID) c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ Exchange: c.Name, Asset: asset.Spot, @@ -207,7 +211,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) { c.Websocket.DataHandler <- err return } - currencyPair := wsInstrumentMap.LookupInstrument(tradeUpdate.InstID) + currencyPair := c.instrumentMap.LookupInstrument(tradeUpdate.InstID) c.Websocket.DataHandler <- wshandler.TradeData{ Timestamp: time.Unix(tradeUpdate.Timestamp, 0), CurrencyPair: currency.NewPairFromFormattedPairs(currencyPair, @@ -238,8 +242,9 @@ func (c *COINUT) GetNonce() int64 { return int64(c.Nonce.Get()) } -// WsSetInstrumentList fetches instrument list and propagates a local cache -func (c *COINUT) WsSetInstrumentList() error { +// WsGetInstruments fetches instrument list and propagates a local cache +func (c *COINUT) WsGetInstruments() (Instruments, error) { + var list Instruments request := wsRequest{ Request: "inst_list", SecType: strings.ToUpper(asset.Spot.String()), @@ -247,20 +252,19 @@ func (c *COINUT) WsSetInstrumentList() error { } resp, err := c.WebsocketConn.SendMessageReturnResponse(request.Nonce, request) if err != nil { - return err + return list, err } - var list WsInstrumentList err = json.Unmarshal(resp, &list) if err != nil { - return err + return list, err } - for curr, data := range list.Spot { - wsInstrumentMap.Seed(curr, data[0].InstID) + for curr, data := range list.Instruments { + c.instrumentMap.Seed(curr, data[0].InstID) } - if len(wsInstrumentMap.GetInstrumentIDs()) == 0 { - return errors.New("instrument list failed to populate") + if len(c.instrumentMap.GetInstrumentIDs()) == 0 { + return list, errors.New("instrument list failed to populate") } - return nil + return list, nil } // WsProcessOrderbookSnapshot processes the orderbook snapshot @@ -285,7 +289,7 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { newOrderBook.Asks = asks newOrderBook.Bids = bids newOrderBook.Pair = currency.NewPairFromFormattedPairs( - wsInstrumentMap.LookupInstrument(ob.InstID), + c.instrumentMap.LookupInstrument(ob.InstID), c.GetEnabledPairs(asset.Spot), c.GetPairFormat(asset.Spot, true), ) @@ -298,7 +302,7 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { // WsProcessOrderbookUpdate process an orderbook update func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error { p := currency.NewPairFromFormattedPairs( - wsInstrumentMap.LookupInstrument(update.InstID), + c.instrumentMap.LookupInstrument(update.InstID), c.GetEnabledPairs(asset.Spot), c.GetPairFormat(asset.Spot, true), ) @@ -335,7 +339,7 @@ func (c *COINUT) GenerateDefaultSubscriptions() { func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { subscribe := wsRequest{ Request: channelToSubscribe.Channel, - InstID: wsInstrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, + InstID: c.instrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String()), Subscribe: true, Nonce: c.WebsocketConn.GenerateMessageID(false), @@ -347,7 +351,7 @@ func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscrip func (c *COINUT) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { subscribe := wsRequest{ Request: channelToSubscribe.Channel, - InstID: wsInstrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, + InstID: c.instrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String()), Subscribe: false, Nonce: c.WebsocketConn.GenerateMessageID(false), @@ -406,7 +410,7 @@ func (c *COINUT) wsAuthenticate() error { return nil } -func (c *COINUT) wsGetAccountBalance() (*WsGetAccountBalanceResponse, error) { +func (c *COINUT) wsGetAccountBalance() (*UserBalance, error) { if !c.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authorised to submit order", c.Name) } @@ -418,7 +422,7 @@ func (c *COINUT) wsGetAccountBalance() (*WsGetAccountBalanceResponse, error) { if err != nil { return nil, err } - var response WsGetAccountBalanceResponse + var response UserBalance err = json.Unmarshal(resp, &response) if err != nil { return nil, err @@ -429,21 +433,21 @@ func (c *COINUT) wsGetAccountBalance() (*WsGetAccountBalanceResponse, error) { return &response, nil } -func (c *COINUT) wsSubmitOrder(order *WsSubmitOrderParameters) (*WsStandardOrderResponse, error) { +func (c *COINUT) wsSubmitOrder(o *WsSubmitOrderParameters) (*WsStandardOrderResponse, error) { if !c.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authorised to submit order", c.Name) } - curr := c.FormatExchangeCurrency(order.Currency, asset.Spot).String() + curr := c.FormatExchangeCurrency(o.Currency, asset.Spot).String() var orderSubmissionRequest WsSubmitOrderRequest orderSubmissionRequest.Request = "new_order" orderSubmissionRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) - orderSubmissionRequest.InstID = wsInstrumentMap.LookupID(curr) - orderSubmissionRequest.Qty = order.Amount - orderSubmissionRequest.Price = order.Price - orderSubmissionRequest.Side = string(order.Side) + orderSubmissionRequest.InstID = c.instrumentMap.LookupID(curr) + orderSubmissionRequest.Qty = o.Amount + orderSubmissionRequest.Price = o.Price + orderSubmissionRequest.Side = string(o.Side) - if order.OrderID > 0 { - orderSubmissionRequest.OrderID = order.OrderID + if o.OrderID > 0 { + orderSubmissionRequest.OrderID = o.OrderID } resp, err := c.WebsocketConn.SendMessageReturnResponse(orderSubmissionRequest.Nonce, orderSubmissionRequest) if err != nil { @@ -548,7 +552,7 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO Qty: orders[i].Amount, Price: orders[i].Price, Side: string(orders[i].Side), - InstID: wsInstrumentMap.LookupID(curr), + InstID: c.instrumentMap.LookupID(curr), ClientOrdID: i + 1, }) } @@ -585,7 +589,7 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO if len(standardOrder.Reasons) > 0 && standardOrder.Reasons[0] != "" { errors = append(errors, fmt.Errorf("%v order submission failed for currency %v and orderID %v, message %v ", c.Name, - wsInstrumentMap.LookupInstrument(standardOrder.InstID), + c.instrumentMap.LookupInstrument(standardOrder.InstID), standardOrder.OrderID, standardOrder.Reasons[0])) @@ -597,73 +601,73 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO return ordersResponse, errors } -func (c *COINUT) wsGetOpenOrders(p currency.Pair) error { +func (c *COINUT) wsGetOpenOrders(curr string) (*WsUserOpenOrdersResponse, error) { + var response *WsUserOpenOrdersResponse if !c.Websocket.CanUseAuthenticatedEndpoints() { - return fmt.Errorf("%v not authorised to get open orders", c.Name) + return response, fmt.Errorf("%v not authorised to get open orders", c.Name) } - curr := c.FormatExchangeCurrency(p, asset.Spot).String() var openOrdersRequest WsGetOpenOrdersRequest openOrdersRequest.Request = "user_open_orders" openOrdersRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) - openOrdersRequest.InstID = wsInstrumentMap.LookupID(curr) + openOrdersRequest.InstID = c.instrumentMap.LookupID(curr) resp, err := c.WebsocketConn.SendMessageReturnResponse(openOrdersRequest.Nonce, openOrdersRequest) if err != nil { - return err + return response, err } - var response map[string]interface{} err = json.Unmarshal(resp, &response) if err != nil { - return err + return response, err } - if response["status"].([]interface{})[0] != "OK" { - return fmt.Errorf("%v get open orders failed for currency %v", + if response.Status[0] != "OK" { + return response, fmt.Errorf("%v get open orders failed for currency %v", c.Name, - p) + curr) } - return nil + return response, nil } -func (c *COINUT) wsCancelOrder(cancellation WsCancelOrderParameters) error { +func (c *COINUT) wsCancelOrder(cancellation *WsCancelOrderParameters) (*CancelOrdersResponse, error) { + var response *CancelOrdersResponse if !c.Websocket.CanUseAuthenticatedEndpoints() { - return fmt.Errorf("%v not authorised to cancel order", c.Name) + return response, fmt.Errorf("%v not authorised to cancel order", c.Name) } - currency := c.FormatExchangeCurrency(cancellation.Currency, asset.Spot).String() + curr := c.FormatExchangeCurrency(cancellation.Currency, asset.Spot).String() var cancellationRequest WsCancelOrderRequest cancellationRequest.Request = "cancel_order" - cancellationRequest.InstID = wsInstrumentMap.LookupID(currency) + cancellationRequest.InstID = c.instrumentMap.LookupID(curr) cancellationRequest.OrderID = cancellation.OrderID cancellationRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) resp, err := c.WebsocketConn.SendMessageReturnResponse(cancellationRequest.Nonce, cancellationRequest) if err != nil { - return err + return response, err } - var response map[string]interface{} err = json.Unmarshal(resp, &response) if err != nil { - return err + return response, err } - if response["status"].([]interface{})[0] != "OK" { - return fmt.Errorf("%v order cancellation failed for currency %v and orderID %v, message %v", + if response.Status[0] != "OK" { + return response, fmt.Errorf("%v order cancellation failed for currency %v and orderID %v, message %v", c.Name, cancellation.Currency, cancellation.OrderID, - response["status"]) + response.Status[0]) } - return nil + return response, nil } -func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*WsCancelOrdersResponse, []error) { - var errors []error +func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*CancelOrdersResponse, error) { + var err error + var response *CancelOrdersResponse if !c.Websocket.CanUseAuthenticatedEndpoints() { - return nil, errors + return nil, err } - cancelOrderRequest := WsCancelOrdersRequest{} + var cancelOrderRequest WsCancelOrdersRequest for i := range cancellations { - currency := c.FormatExchangeCurrency(cancellations[i].Currency, asset.Spot).String() + curr := c.FormatExchangeCurrency(cancellations[i].Currency, asset.Spot).String() cancelOrderRequest.Entries = append(cancelOrderRequest.Entries, WsCancelOrdersRequestEntry{ - InstID: wsInstrumentMap.LookupID(currency), + InstID: c.instrumentMap.LookupID(curr), OrderID: cancellations[i].OrderID, }) } @@ -672,53 +676,40 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*WsCan cancelOrderRequest.Nonce = c.WebsocketConn.GenerateMessageID(false) resp, err := c.WebsocketConn.SendMessageReturnResponse(cancelOrderRequest.Nonce, cancelOrderRequest) if err != nil { - return nil, []error{err} + return response, err } - var response WsCancelOrdersResponse err = json.Unmarshal(resp, &response) if err != nil { - return nil, []error{err} + return response, err } - if response.Status[0] != "OK" { - return &response, []error{err} - } - for i := range response.Results { - if response.Results[i].Status != "OK" { - errors = append(errors, fmt.Errorf("%v order cancellation failed for currency %v and orderID %v, message %v", - c.Name, - wsInstrumentMap.LookupInstrument(response.Results[i].InstID), - response.Results[i].OrderID, - response.Results[i].Status)) - } - } - return &response, errors + return response, err } -func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) error { +func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) (*WsTradeHistoryResponse, error) { + var response *WsTradeHistoryResponse if !c.Websocket.CanUseAuthenticatedEndpoints() { - return fmt.Errorf("%v not authorised to get trade history", c.Name) + return response, fmt.Errorf("%v not authorised to get trade history", c.Name) } curr := c.FormatExchangeCurrency(p, asset.Spot).String() var request WsTradeHistoryRequest request.Request = "trade_history" - request.InstID = wsInstrumentMap.LookupID(curr) + request.InstID = c.instrumentMap.LookupID(curr) request.Nonce = c.WebsocketConn.GenerateMessageID(false) request.Start = start request.Limit = limit resp, err := c.WebsocketConn.SendMessageReturnResponse(request.Nonce, request) if err != nil { - return err + return response, err } - var response map[string]interface{} err = json.Unmarshal(resp, &response) if err != nil { - return err + return response, err } - if response["status"].([]interface{})[0] != "OK" { - return fmt.Errorf("%v get trade history failed for %v", + if response.Status[0] != "OK" { + return response, fmt.Errorf("%v get trade history failed for %v", c.Name, request) } - return nil + return response, nil } diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index c5c3e03e..aaa0b0e3 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -88,15 +88,20 @@ func (c *COINUT) SetDefaults() { FiatWithdrawalFee: true, }, WebsocketCapabilities: protocol.Features{ + AccountBalance: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + SubmitOrders: true, + UserTradeHistory: true, TickerFetching: true, - OrderbookFetching: true, TradeFetching: true, + OrderbookFetching: true, + AccountInfo: true, Subscribe: true, Unsubscribe: true, AuthenticatedEndpoints: true, - SubmitOrder: true, - SubmitOrders: true, - CancelOrder: true, MessageCorrelation: true, }, WithdrawPermissions: exchange.WithdrawCryptoViaWebsiteOnly | @@ -217,15 +222,25 @@ func (c *COINUT) Run() { // FetchTradablePairs returns a list of the exchanges tradable pairs func (c *COINUT) FetchTradablePairs(asset asset.Item) ([]string, error) { - i, err := c.GetInstruments() - if err != nil { - return nil, err + var instruments map[string][]InstrumentBase + var resp Instruments + var err error + if c.Websocket.IsConnected() { + resp, err = c.WsGetInstruments() + if err != nil { + return nil, err + } + } else { + resp, err = c.GetInstruments() + if err != nil { + return nil, err + } } - + instruments = resp.Instruments var pairs []string - for _, y := range i.Instruments { - c.instrumentMap.Seed(y[0].Base+y[0].Quote, y[0].InstID) - p := y[0].Base + c.GetPairFormat(asset, false).Delimiter + y[0].Quote + for i := range instruments { + c.instrumentMap.Seed(instruments[i][0].Base+instruments[i][0].Quote, instruments[i][0].InstID) + p := instruments[i][0].Base + c.GetPairFormat(asset, false).Delimiter + instruments[i][0].Quote pairs = append(pairs, p) } @@ -248,9 +263,20 @@ func (c *COINUT) UpdateTradablePairs(forceUpdate bool) error { // COINUT exchange func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - bal, err := c.GetUserBalance() - if err != nil { - return info, err + var bal *UserBalance + var err error + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var resp *UserBalance + resp, err = c.wsGetAccountBalance() + if err != nil { + return info, err + } + bal = resp + } else { + bal, err = c.GetUserBalance() + if err != nil { + return info, err + } } var balances = []exchange.AccountCurrencyInfo{ @@ -322,12 +348,9 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { // UpdateTicker updates and returns the ticker for a currency pair func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) { var tickerPrice ticker.Price - - if !c.instrumentMap.IsLoaded() { - err := c.SeedInstruments() - if err != nil { - return tickerPrice, err - } + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return tickerPrice, err } instID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(p, @@ -335,8 +358,8 @@ func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri if instID == 0 { return tickerPrice, errors.New("unable to lookup instrument ID") } - - tick, err := c.GetInstrumentTicker(instID) + var tick Ticker + tick, err = c.GetInstrumentTicker(instID) if err != nil { return tickerPrice, err } @@ -379,12 +402,9 @@ func (c *COINUT) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderboo // UpdateOrderbook updates and returns the orderbook for a currency pair func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - - if !c.instrumentMap.IsLoaded() { - err := c.SeedInstruments() - if err != nil { - return orderBook, err - } + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return orderBook, err } instID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(p, @@ -430,69 +450,74 @@ func (c *COINUT) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex } // SubmitOrder submits a new order -func (c *COINUT) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { +func (c *COINUT) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - if err := s.Validate(); err != nil { - return submitOrderResponse, err + var err error + if _, err = strconv.Atoi(o.ClientID); err != nil { + return submitOrderResponse, fmt.Errorf("%s - ClientID must be a number, received: %s", c.Name, o.ClientID) } + err = o.Validate() - var APIresponse interface{} - isBuyOrder := s.OrderSide == order.Buy - clientIDInt, err := strconv.ParseUint(s.ClientID, 0, 32) if err != nil { return submitOrderResponse, err } - clientIDUint := uint32(clientIDInt) - - if !c.instrumentMap.IsLoaded() { - err = c.SeedInstruments() + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var response *WsStandardOrderResponse + response, err = c.wsSubmitOrder(&WsSubmitOrderParameters{ + Currency: o.Pair, + Side: o.OrderSide, + Amount: o.Amount, + Price: o.Price, + }) if err != nil { return submitOrderResponse, err } - } - - currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(s.Pair, - asset.Spot).String()) - if currencyID == 0 { - return submitOrderResponse, errLookupInstrumentID - } - - switch s.OrderType { - case order.Limit: - APIresponse, err = c.NewOrder(currencyID, - s.Amount, - s.Price, - isBuyOrder, - clientIDUint) - case order.Market: - APIresponse, err = c.NewOrder(currencyID, - s.Amount, - 0, - isBuyOrder, - clientIDUint) - } - - switch apiResp := APIresponse.(type) { - case OrdersBase: - orderResult := apiResp - submitOrderResponse.OrderID = strconv.FormatInt(orderResult.OrderID, 10) - case OrderFilledResponse: - orderResult := apiResp - submitOrderResponse.OrderID = strconv.FormatInt(orderResult.Order.OrderID, 10) - case OrderRejectResponse: - orderResult := apiResp - submitOrderResponse.OrderID = strconv.FormatInt(orderResult.OrderID, 10) - err = fmt.Errorf("orderID: %d was rejected: %v", - orderResult.OrderID, - orderResult.Reasons) - } - - if err == nil { + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) submitOrderResponse.IsOrderPlaced = true - } + } else { + err = c.loadInstrumentsIfNotLoaded() + if err != nil { + return submitOrderResponse, err + } - return submitOrderResponse, err + currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(o.Pair, + asset.Spot).String()) + if currencyID == 0 { + return submitOrderResponse, errLookupInstrumentID + } + + var APIResponse interface{} + var clientIDInt uint64 + isBuyOrder := o.OrderSide == order.Buy + clientIDInt, err = strconv.ParseUint(o.ClientID, 0, 32) + if err != nil { + return submitOrderResponse, err + } + clientIDUint := uint32(clientIDInt) + APIResponse, err = c.NewOrder(currencyID, o.Amount, o.Price, + isBuyOrder, clientIDUint) + if err != nil { + return submitOrderResponse, err + } + responseMap := APIResponse.(map[string]interface{}) + switch responseMap["reply"].(string) { + case "order_rejected": + return submitOrderResponse, fmt.Errorf("clientOrderID: %v was rejected: %v", o.ClientID, responseMap["reasons"]) + case "order_filled": + orderID := responseMap["order_id"].(float64) + submitOrderResponse.OrderID = strconv.FormatFloat(orderID, 'f', -1, 64) + submitOrderResponse.IsOrderPlaced = true + submitOrderResponse.FullyMatched = true + return submitOrderResponse, nil + case "order_accepted": + orderID := responseMap["order_id"].(float64) + submitOrderResponse.OrderID = strconv.FormatFloat(orderID, 'f', -1, 64) + submitOrderResponse.IsOrderPlaced = true + return submitOrderResponse, nil + } + } + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -502,76 +527,108 @@ func (c *COINUT) ModifyOrder(action *order.Modify) (string, error) { } // CancelOrder cancels an order by its corresponding ID number -func (c *COINUT) CancelOrder(order *order.Cancel) error { - orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64) +func (c *COINUT) CancelOrder(o *order.Cancel) error { + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return err + } + orderIDInt, err := strconv.ParseInt(o.OrderID, 10, 64) if err != nil { return err } - if !c.instrumentMap.IsLoaded() { - err = c.SeedInstruments() + currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency( + o.CurrencyPair, + asset.Spot).String(), + ) + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var resp *CancelOrdersResponse + resp, err = c.wsCancelOrder(&WsCancelOrderParameters{ + Currency: o.CurrencyPair, + OrderID: orderIDInt, + }) + if err != nil { + return err + } + if len(resp.Status) >= 1 && resp.Status[0] != "OK" { + return errors.New(c.Name + " - Failed to cancel order " + o.OrderID) + } + } else { + if currencyID == 0 { + return errLookupInstrumentID + } + _, err = c.CancelExistingOrder(currencyID, orderIDInt) if err != nil { return err } } - currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency( - order.CurrencyPair, - asset.Spot).String(), - ) - if currencyID == 0 { - return errLookupInstrumentID - } - _, err = c.CancelExistingOrder(currencyID, orderIDInt) - return err + return nil } // CancelAllOrders cancels all orders associated with a currency pair -func (c *COINUT) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { - // TODO, this is a terrible implementation. Requires DB to improve - // Coinut provides no way of retrieving orders without a currency - // So we need to retrieve all currencies, then retrieve orders for each - // currency then cancel. Advisable to never use this until DB due to - // performance. - cancelAllOrdersResponse := order.CancelAllResponse{ - Status: make(map[string]string), +func (c *COINUT) CancelAllOrders(details *order.Cancel) (order.CancelAllResponse, error) { + var cancelAllOrdersResponse order.CancelAllResponse + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return cancelAllOrdersResponse, err } - - if !c.instrumentMap.IsLoaded() { - err := c.SeedInstruments() + cancelAllOrdersResponse.Status = make(map[string]string) + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + openOrders, err := c.wsGetOpenOrders(details.CurrencyPair.String()) if err != nil { return cancelAllOrdersResponse, err } - } - - var allTheOrders []OrderResponse - ids := c.instrumentMap.GetInstrumentIDs() - for x := range ids { - openOrders, err := c.GetOpenOrders(ids[x]) + var ordersToCancel []WsCancelOrderParameters + for i := range openOrders.Orders { + if openOrders.Orders[i].InstID == c.instrumentMap.LookupID(c.FormatExchangeCurrency(details.CurrencyPair, asset.Spot).String()) { + ordersToCancel = append(ordersToCancel, WsCancelOrderParameters{ + Currency: details.CurrencyPair, + OrderID: openOrders.Orders[i].OrderID, + }) + } + } + resp, err := c.wsCancelOrders(ordersToCancel) if err != nil { return cancelAllOrdersResponse, err } - allTheOrders = append(allTheOrders, openOrders.Orders...) - } - - var allTheOrdersToCancel []CancelOrders - for _, orderToCancel := range allTheOrders { - cancelOrder := CancelOrders{ - InstrumentID: orderToCancel.InstrumentID, - OrderID: orderToCancel.OrderID, + for i := range resp.Results { + if openOrders.Orders[i].Status[0] != "OK" { + cancelAllOrdersResponse.Status[strconv.FormatInt(openOrders.Orders[i].OrderID, 10)] = strings.Join(openOrders.Orders[i].Status, ",") + } } - allTheOrdersToCancel = append(allTheOrdersToCancel, cancelOrder) - } - - if len(allTheOrdersToCancel) > 0 { - resp, err := c.CancelOrders(allTheOrdersToCancel) - if err != nil { - return cancelAllOrdersResponse, err + } else { + var allTheOrders []OrderResponse + ids := c.instrumentMap.GetInstrumentIDs() + for x := range ids { + if ids[x] == c.instrumentMap.LookupID(c.FormatExchangeCurrency(details.CurrencyPair, asset.Spot).String()) { + openOrders, err := c.GetOpenOrders(ids[x]) + if err != nil { + return cancelAllOrdersResponse, err + } + allTheOrders = append(allTheOrders, openOrders.Orders...) + } } - for _, order := range resp.Results { - if order.Status != "OK" { - cancelAllOrdersResponse.Status[strconv.FormatInt(order.OrderID, 10)] = order.Status + var allTheOrdersToCancel []CancelOrders + for i := range allTheOrders { + cancelOrder := CancelOrders{ + InstrumentID: allTheOrders[i].InstrumentID, + OrderID: allTheOrders[i].OrderID, + } + allTheOrdersToCancel = append(allTheOrdersToCancel, cancelOrder) + } + + if len(allTheOrdersToCancel) > 0 { + resp, err := c.CancelOrders(allTheOrdersToCancel) + if err != nil { + return cancelAllOrdersResponse, err + } + + for i := range resp.Results { + if resp.Results[i].Status != "OK" { + cancelAllOrdersResponse.Status[strconv.FormatInt(resp.Results[i].OrderID, 10)] = resp.Results[i].Status + } } } } @@ -623,51 +680,81 @@ func (c *COINUT) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) // GetActiveOrders retrieves any orders that are active/open func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { - if !c.instrumentMap.IsLoaded() { - err := c.SeedInstruments() - if err != nil { - return nil, err - } + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return nil, err } - - var instrumentsToUse []int64 - if len(req.Currencies) > 0 { - for x := range req.Currencies { - currency := c.FormatExchangeCurrency(req.Currencies[x], - asset.Spot).String() - instrumentsToUse = append(instrumentsToUse, - c.instrumentMap.LookupID(currency)) + var orders []order.Detail + var currenciesToCheck []string + if len(req.Currencies) == 0 { + for i := range req.Currencies { + currenciesToCheck = append(currenciesToCheck, c.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String()) } } else { - instrumentsToUse = c.instrumentMap.GetInstrumentIDs() - } - - if len(instrumentsToUse) == 0 { - return nil, errors.New("no instrument IDs to use") - } - - var orders []order.Detail - for x := range instrumentsToUse { - openOrders, err := c.GetOpenOrders(instrumentsToUse[x]) - if err != nil { - return nil, err + for k := range c.instrumentMap.Instruments { + currenciesToCheck = append(currenciesToCheck, k) } - for y := range openOrders.Orders { - curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) - p := currency.NewPairFromFormattedPairs(curr, - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true)) - orderSide := order.Side(strings.ToUpper(openOrders.Orders[y].Side)) - orderDate := time.Unix(openOrders.Orders[y].Timestamp, 0) - orders = append(orders, order.Detail{ - ID: strconv.FormatInt(openOrders.Orders[y].OrderID, 10), - Amount: openOrders.Orders[y].Quantity, - Price: openOrders.Orders[y].Price, - Exchange: c.Name, - OrderSide: orderSide, - OrderDate: orderDate, - CurrencyPair: p, - }) + } + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + for x := range currenciesToCheck { + openOrders, err := c.wsGetOpenOrders(currenciesToCheck[x]) + if err != nil { + return nil, err + } + for i := range openOrders.Orders { + orders = append(orders, order.Detail{ + Exchange: c.Name, + ID: strconv.FormatInt(openOrders.Orders[i].OrderID, 10), + CurrencyPair: c.FormatExchangeCurrency(currency.NewPairFromString(currenciesToCheck[x]), asset.Spot), + OrderSide: order.Side(openOrders.Orders[i].Side), + OrderDate: time.Unix(0, openOrders.Orders[i].Timestamp), + Status: order.Active, + Price: openOrders.Orders[i].Price, + Amount: openOrders.Orders[i].Qty, + ExecutedAmount: openOrders.Orders[i].Qty - openOrders.Orders[i].OpenQty, + RemainingAmount: openOrders.Orders[i].OpenQty, + }) + } + } + } else { + var instrumentsToUse []int64 + if len(req.Currencies) > 0 { + for x := range req.Currencies { + curr := c.FormatExchangeCurrency(req.Currencies[x], + asset.Spot).String() + instrumentsToUse = append(instrumentsToUse, + c.instrumentMap.LookupID(curr)) + } + } else { + instrumentsToUse = c.instrumentMap.GetInstrumentIDs() + } + + if len(instrumentsToUse) == 0 { + return nil, errors.New("no instrument IDs to use") + } + + for x := range instrumentsToUse { + openOrders, err := c.GetOpenOrders(instrumentsToUse[x]) + if err != nil { + return nil, err + } + for y := range openOrders.Orders { + curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) + p := currency.NewPairFromFormattedPairs(curr, + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true)) + orderSide := order.Side(strings.ToUpper(openOrders.Orders[y].Side)) + orderDate := time.Unix(openOrders.Orders[y].Timestamp, 0) + orders = append(orders, order.Detail{ + ID: strconv.FormatInt(openOrders.Orders[y].OrderID, 10), + Amount: openOrders.Orders[y].Quantity, + Price: openOrders.Orders[y].Price, + Exchange: c.Name, + OrderSide: orderSide, + OrderDate: orderDate, + CurrencyPair: p, + }) + } } } @@ -679,51 +766,78 @@ func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e // GetOrderHistory retrieves account order information // Can Limit response to specific order status func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { - if !c.instrumentMap.IsLoaded() { - err := c.SeedInstruments() - if err != nil { - return nil, err - } + err := c.loadInstrumentsIfNotLoaded() + if err != nil { + return nil, err } - - var instrumentsToUse []int64 - if len(req.Currencies) > 0 { - for x := range req.Currencies { - currency := c.FormatExchangeCurrency(req.Currencies[x], - asset.Spot).String() - instrumentsToUse = append(instrumentsToUse, - c.instrumentMap.LookupID(currency)) + var allOrders []order.Detail + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + for i := range req.Currencies { + for j := int64(0); ; j += 100 { + trades, err := c.wsGetTradeHistory(req.Currencies[i], j, 100) + if err != nil { + return allOrders, err + } + for x := range trades.Trades { + curr := c.instrumentMap.LookupInstrument(trades.Trades[x].InstID) + allOrders = append(allOrders, order.Detail{ + Exchange: c.Name, + ID: strconv.FormatInt(trades.Trades[x].OrderID, 10), + CurrencyPair: currency.NewPairFromString(curr), + OrderSide: order.Side(trades.Trades[x].Side), + OrderDate: time.Unix(0, trades.Trades[x].Timestamp), + Status: order.Filled, + Price: trades.Trades[x].Price, + Amount: trades.Trades[x].Qty, + ExecutedAmount: trades.Trades[x].Qty, + RemainingAmount: trades.Trades[x].OpenQty, + }) + } + if len(trades.Trades) < 100 { + break + } + } } } else { - instrumentsToUse = c.instrumentMap.GetInstrumentIDs() - } - - if len(instrumentsToUse) == 0 { - return nil, errors.New("no instrument IDs to use") - } - - var allOrders []order.Detail - for x := range instrumentsToUse { - orders, err := c.GetTradeHistory(instrumentsToUse[x], -1, -1) - if err != nil { - return nil, err + var instrumentsToUse []int64 + if len(req.Currencies) > 0 { + for x := range req.Currencies { + curr := c.FormatExchangeCurrency(req.Currencies[x], + asset.Spot).String() + instrumentID := c.instrumentMap.LookupID(curr) + if instrumentID > 0 { + instrumentsToUse = append(instrumentsToUse, instrumentID) + } + } + } else { + instrumentsToUse = c.instrumentMap.GetInstrumentIDs() } - for y := range orders.Trades { - curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) - p := currency.NewPairFromFormattedPairs(curr, - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true)) - orderSide := order.Side(strings.ToUpper(orders.Trades[y].Order.Side)) - orderDate := time.Unix(orders.Trades[y].Order.Timestamp, 0) - allOrders = append(allOrders, order.Detail{ - ID: strconv.FormatInt(orders.Trades[y].Order.OrderID, 10), - Amount: orders.Trades[y].Order.Quantity, - Price: orders.Trades[y].Order.Price, - Exchange: c.Name, - OrderSide: orderSide, - OrderDate: orderDate, - CurrencyPair: p, - }) + + if len(instrumentsToUse) == 0 { + return nil, errors.New("no instrument IDs to use") + } + for x := range instrumentsToUse { + orders, err := c.GetTradeHistory(instrumentsToUse[x], -1, -1) + if err != nil { + return nil, err + } + for y := range orders.Trades { + curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) + p := currency.NewPairFromFormattedPairs(curr, + c.GetEnabledPairs(asset.Spot), + c.GetPairFormat(asset.Spot, true)) + orderSide := order.Side(strings.ToUpper(orders.Trades[y].Order.Side)) + orderDate := time.Unix(orders.Trades[y].Order.Timestamp, 0) + allOrders = append(allOrders, order.Detail{ + ID: strconv.FormatInt(orders.Trades[y].Order.OrderID, 10), + Amount: orders.Trades[y].Order.Quantity, + Price: orders.Trades[y].Order.Price, + Exchange: c.Name, + OrderSide: orderSide, + OrderDate: orderDate, + CurrencyPair: p, + }) + } } } @@ -755,3 +869,20 @@ func (c *COINUT) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, e func (c *COINUT) AuthenticateWebsocket() error { return c.wsAuthenticate() } + +func (c *COINUT) loadInstrumentsIfNotLoaded() error { + if !c.instrumentMap.IsLoaded() { + if c.Websocket.IsConnected() { + _, err := c.WsGetInstruments() + if err != nil { + return err + } + } else { + err := c.SeedInstruments() + if err != nil { + return err + } + } + } + return nil +} diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index 8b5e8839..804f7921 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -14,27 +14,8 @@ import ( const ( RestAuthentication uint8 = 0 WebsocketAuthentication uint8 = 1 -) - -// FeeType custom type for calculating fees based on method -type FeeType uint8 - -// Const declarations for fee types -const ( - BankFee FeeType = iota - InternationalBankDepositFee - InternationalBankWithdrawalFee - CryptocurrencyTradeFee - CyptocurrencyDepositFee - CryptocurrencyWithdrawalFee - OfflineTradeFee -) - -// InternationalBankTransactionType custom type for calculating fees based on fiat transaction types -type InternationalBankTransactionType uint8 - -// Const declarations for international transaction types -const ( + // Repeated exchange strings + // FeeType custom type for calculating fees based on method WireTransfer InternationalBankTransactionType = iota PerfectMoney Neteller @@ -53,26 +34,15 @@ const ( WesternUnion MoneyGram Contact -) - -// FeeBuilder is the type which holds all parameters required to calculate a fee -// for an exchange -type FeeBuilder struct { - FeeType FeeType - // Used for calculating crypto trading fees, deposits & withdrawals - Pair currency.Pair - IsMaker bool - // Fiat currency used for bank deposits & withdrawals - FiatCurrency currency.Code - BankTransactionType InternationalBankTransactionType - // Used to multiply for fee calculations - PurchasePrice float64 - Amount float64 -} - -// Definitions for each type of withdrawal method for a given exchange -const ( - // No withdraw + // Const declarations for fee types + BankFee FeeType = iota + InternationalBankDepositFee + InternationalBankWithdrawalFee + CryptocurrencyTradeFee + CyptocurrencyDepositFee + CryptocurrencyWithdrawalFee + OfflineTradeFee + // Definitions for each type of withdrawal method for a given exchange NoAPIWithdrawalMethods uint32 = 0 NoAPIWithdrawalMethodsText string = "NONE, WEBSITE ONLY" AutoWithdrawCrypto uint32 = (1 << 0) @@ -113,10 +83,29 @@ const ( WithdrawFiatViaWebsiteOnlyText string = "WITHDRAW FIAT VIA WEBSITE ONLY" NoFiatWithdrawals uint32 = (1 << 18) NoFiatWithdrawalsText string = "NO FIAT WITHDRAWAL" - - UnknownWithdrawalTypeText string = "UNKNOWN" + UnknownWithdrawalTypeText string = "UNKNOWN" ) +type FeeType uint8 + +// InternationalBankTransactionType custom type for calculating fees based on fiat transaction types +type InternationalBankTransactionType uint8 + +// FeeBuilder is the type which holds all parameters required to calculate a fee +// for an exchange +type FeeBuilder struct { + FeeType FeeType + // Used for calculating crypto trading fees, deposits & withdrawals + Pair currency.Pair + IsMaker bool + // Fiat currency used for bank deposits & withdrawals + FiatCurrency currency.Code + BankTransactionType InternationalBankTransactionType + // Used to multiply for fee calculations + PurchasePrice float64 + Amount float64 +} + // AccountInfo is a Generic type to hold each exchange's holdings in // all enabled currencies type AccountInfo struct { diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index 0d009164..2a5fa1d9 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -2,6 +2,7 @@ package exmo import ( "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -21,11 +22,8 @@ var ( e EXMO ) -func TestDefault(t *testing.T) { +func TestMain(m *testing.M) { e.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { @@ -33,17 +31,19 @@ func TestSetup(t *testing.T) { } exmoConf, err := cfg.GetExchangeConfig("EXMO") if err != nil { - t.Error("Exmo Setup() init error") + log.Fatal("Exmo Setup() init error") } err = e.Setup(exmoConf) if err != nil { - t.Fatal("Exmo setup error", err) + log.Fatal("Exmo setup error", err) } e.API.AuthenticatedSupport = true e.API.Credentials.Key = APIKey e.API.Credentials.Secret = APISecret + + os.Exit(m.Run()) } func TestGetTrades(t *testing.T) { @@ -88,10 +88,9 @@ func TestGetCurrency(t *testing.T) { func TestGetUserInfo(t *testing.T) { t.Parallel() - if APIKey == "" || APISecret == "" { + if !areTestAPIKeysSet() { t.Skip() } - TestSetup(t) _, err := e.GetUserInfo() if err != nil { t.Errorf("Err: %s", err) @@ -100,10 +99,9 @@ func TestGetUserInfo(t *testing.T) { func TestGetRequiredAmount(t *testing.T) { t.Parallel() - if APIKey == "" || APISecret == "" { + if !areTestAPIKeysSet() { t.Skip() } - TestSetup(t) _, err := e.GetRequiredAmount("BTC_USD", 100) if err != nil { t.Errorf("Err: %s", err) @@ -125,7 +123,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() e.GetFeeByType(feeBuilder) - if APIKey == "" || APISecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -137,8 +135,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - e.SetDefaults() - TestSetup(t) t.Parallel() var feeBuilder = setFeeBuilder() @@ -255,20 +251,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - e.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := e.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - e.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -282,9 +272,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - e.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -307,8 +294,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - e.SetDefaults() - TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -334,14 +319,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - e.SetDefaults() - TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -359,9 +341,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - e.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -389,6 +368,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := e.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -396,8 +378,10 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - e.SetDefaults() - TestSetup(t) + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } + withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -407,10 +391,6 @@ func TestWithdraw(t *testing.T) { Address: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", } - if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip("API keys set, canManipulateRealOrders false, skipping test") - } - _, err := e.WithdrawCryptocurrencyFunds(&withdrawCryptoRequest) if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") @@ -421,9 +401,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - e.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -436,9 +413,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - e.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index e228fc7a..374cbf81 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -238,10 +238,10 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook if err != nil { return orderBook, err } - - for _, x := range e.GetEnabledPairs(assetType) { - currency := e.FormatExchangeCurrency(x, assetType) - data, ok := result[currency.String()] + enabledPairs := e.GetEnabledPairs(assetType) + for i := range enabledPairs { + curr := e.FormatExchangeCurrency(enabledPairs[i], assetType) + data, ok := result[curr.String()] if !ok { continue } @@ -266,7 +266,7 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook } orderBook.Bids = obItems - orderBook.Pair = x + orderBook.Pair = enabledPairs[i] orderBook.ExchangeName = e.Name orderBook.AssetType = assetType @@ -344,15 +344,18 @@ func (e *EXMO) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { oT, s.Price, s.Amount) + if err != nil { + return submitOrderResponse, err + } if response > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true + submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -382,10 +385,10 @@ func (e *EXMO) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) return cancelAllOrdersResponse, err } - for _, order := range openOrders { - err = e.CancelExistingOrder(order.OrderID) + for i := range openOrders { + err = e.CancelExistingOrder(openOrders[i].OrderID) if err != nil { - cancelAllOrdersResponse.Status[strconv.FormatInt(order.OrderID, 10)] = err.Error() + cancelAllOrdersResponse.Status[strconv.FormatInt(openOrders[i].OrderID, 10)] = err.Error() } } @@ -486,13 +489,13 @@ func (e *EXMO) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, err } var allTrades []UserTrades - for _, currency := range req.Currencies { - resp, err := e.GetUserTrades(e.FormatExchangeCurrency(currency, asset.Spot).String(), "", "10000") + for i := range req.Currencies { + resp, err := e.GetUserTrades(e.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String(), "", "10000") if err != nil { return nil, err } - for _, order := range resp { - allTrades = append(allTrades, order...) + for j := range resp { + allTrades = append(allTrades, resp[j]...) } } diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 7437e6da..cfa6cf2f 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -1,7 +1,9 @@ package gateio import ( + "log" "net/http" + "os" "testing" "github.com/gorilla/websocket" @@ -25,29 +27,28 @@ const ( var g Gateio var wsSetupRan bool -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { g.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("GateIO load config error", err) + log.Fatal("GateIO load config error", err) } - gateioConfig, err := cfg.GetExchangeConfig("GateIO") + gConf, err := cfg.GetExchangeConfig("GateIO") if err != nil { - t.Error("GateIO Setup() init error") + log.Fatal("GateIO Setup() init error") } - gateioConfig.API.AuthenticatedSupport = true - gateioConfig.API.AuthenticatedWebsocketSupport = true - gateioConfig.API.Credentials.Key = apiKey - gateioConfig.API.Credentials.Secret = apiSecret + gConf.API.AuthenticatedSupport = true + gConf.API.AuthenticatedWebsocketSupport = true + gConf.API.Credentials.Key = apiKey + gConf.API.Credentials.Secret = apiSecret - err = g.Setup(gateioConfig) + err = g.Setup(gConf) if err != nil { - t.Fatal("GateIO setup error", err) + log.Fatal("GateIO setup error", err) } + + os.Exit(m.Run()) } func TestGetSymbols(t *testing.T) { @@ -100,7 +101,7 @@ func TestCancelExistingOrder(t *testing.T) { func TestGetBalances(t *testing.T) { t.Parallel() - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { t.Skip() } @@ -173,7 +174,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() g.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -185,9 +186,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - g.SetDefaults() - TestSetup(t) - var feeBuilder = setFeeBuilder() if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic @@ -265,20 +263,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - g.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := g.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - g.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -292,9 +284,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - g.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -318,9 +307,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip() } @@ -346,15 +332,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip() } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -372,15 +354,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip() } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -417,6 +395,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := g.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -424,8 +405,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - g.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -449,9 +428,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -464,9 +440,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -492,9 +465,6 @@ func TestGetDepositAddress(t *testing.T) { } } func TestGetOrderInfo(t *testing.T) { - g.SetDefaults() - TestSetup(t) - if !areTestAPIKeysSet() { t.Skip("no API keys set skipping test") } @@ -509,8 +479,6 @@ func TestGetOrderInfo(t *testing.T) { // TestWsGetBalance dials websocket, sends balance request. func TestWsGetBalance(t *testing.T) { - g.SetDefaults() - TestSetup(t) if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -541,12 +509,14 @@ func TestWsGetBalance(t *testing.T) { if err != nil { t.Error(err) } + _, err = g.wsGetBalance([]string{}) + if err != nil { + t.Error(err) + } } // TestWsGetOrderInfo dials websocket, sends order info request. func TestWsGetOrderInfo(t *testing.T) { - g.SetDefaults() - TestSetup(t) if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -573,7 +543,7 @@ func TestWsGetOrderInfo(t *testing.T) { if resp.Result.Status != "success" { t.Fatal("Unsuccessful login") } - _, err = g.wsGetOrderInfo("EOS_USDT", 0, 10) + _, err = g.wsGetOrderInfo("EOS_USDT", 0, 1000) if err != nil { t.Error(err) } @@ -583,8 +553,6 @@ func setupWSTestAuth(t *testing.T) { if wsSetupRan { return } - g.SetDefaults() - TestSetup(t) if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport { t.Skip(wshandler.WebsocketNotEnabled) } diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index 8336d8e5..283cfbf4 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -445,19 +445,19 @@ type WebSocketOrderQueryResult struct { // WebSocketOrderQueryRecords contains order information from a order.query websocket request type WebSocketOrderQueryRecords struct { - ID int `json:"id"` + ID int64 `json:"id"` Market string `json:"market"` - User int `json:"user"` + User int64 `json:"user"` Ctime float64 `json:"ctime"` Mtime float64 `json:"mtime"` - Price string `json:"price"` - Amount string `json:"amount"` - Left string `json:"left"` - DealFee string `json:"dealFee"` - OrderType int `json:"orderType"` - Type int `json:"type"` - FilledAmount string `json:"filledAmount"` - FilledTotal string `json:"filledTotal"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + Left float64 `json:"left,string"` + DealFee float64 `json:"dealFee,string"` + OrderType int64 `json:"orderType"` + Type int64 `json:"type"` + FilledAmount float64 `json:"filledAmount,string"` + FilledTotal float64 `json:"filledTotal,string"` } // WebsocketAuthenticationResponse contains the result of a login request @@ -473,14 +473,14 @@ type WebsocketAuthenticationResponse struct { type wsGetBalanceRequest struct { ID int64 `json:"id"` Method string `json:"method"` - Params []string `json:"params,omitempty"` + Params []string `json:"params"` } // WsGetBalanceResponse stores WS GetBalance response type WsGetBalanceResponse struct { - Error interface{} `json:"error"` - Result map[currency.Code]WsGetBalanceResponseData `json:"result,omitempty"` - ID int64 `json:"id"` + Error interface{} `json:"error"` + Result map[string]WsGetBalanceResponseData `json:"result"` + ID int64 `json:"id"` } // WsGetBalanceResponseData contains currency data diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 04545179..8959f1f1 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -40,6 +40,7 @@ func (g *Gateio) WsConnect() error { _, err = g.wsServerSignIn() if err != nil { log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", g.Name, err) + g.Websocket.SetCanUseAuthenticatedEndpoints(false) } g.GenerateAuthenticatedSubscriptions() g.GenerateDefaultSubscriptions() @@ -416,7 +417,7 @@ func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) (*WebSocketOrd if !g.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authorised to get order info", g.Name) } - order := WebsocketRequest{ + ord := WebsocketRequest{ ID: g.WebsocketConn.GenerateMessageID(true), Method: "order.query", Params: []interface{}{ @@ -425,7 +426,7 @@ func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) (*WebSocketOrd limit, }, } - resp, err := g.WebsocketConn.SendMessageReturnResponse(order.ID, order) + resp, err := g.WebsocketConn.SendMessageReturnResponse(ord.ID, ord) if err != nil { return nil, err } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 91970c94..5d0d8384 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -9,6 +9,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -99,6 +100,8 @@ func (g *Gateio) SetDefaults() { Unsubscribe: true, AuthenticatedEndpoints: true, MessageCorrelation: true, + GetOrder: true, + AccountBalance: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.NoFiatWithdrawals, @@ -269,9 +272,9 @@ func (g *Gateio) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderboo // UpdateOrderbook updates and returns the orderbook for a currency pair func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - currency := g.FormatExchangeCurrency(p, assetType).String() + curr := g.FormatExchangeCurrency(p, assetType).String() - orderbookNew, err := g.GetOrderbook(currency) + orderbookNew, err := g.GetOrderbook(curr) if err != nil { return orderBook, err } @@ -306,61 +309,78 @@ func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbo // ZB exchange func (g *Gateio) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - - balance, err := g.GetBalances() - if err != nil { - return info, err - } - var balances []exchange.AccountCurrencyInfo - switch l := balance.Locked.(type) { - case map[string]interface{}: - for x := range l { - lockedF, err := strconv.ParseFloat(l[x].(string), 64) - if err != nil { - return info, err - } - - balances = append(balances, exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(x), - Hold: lockedF, + if g.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + resp, err := g.wsGetBalance([]string{}) + if err != nil { + return info, err + } + var currData []exchange.AccountCurrencyInfo + for k := range resp.Result { + currData = append(currData, exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(k), + TotalValue: resp.Result[k].Available + resp.Result[k].Freeze, + Hold: resp.Result[k].Freeze, }) } - default: - break - } + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: currData, + }) + } else { + balance, err := g.GetBalances() + if err != nil { + return info, err + } - switch v := balance.Available.(type) { - case map[string]interface{}: - for x := range v { - availAmount, err := strconv.ParseFloat(v[x].(string), 64) - if err != nil { - return info, err - } - - var updated bool - for i := range balances { - if balances[i].CurrencyName == currency.NewCode(x) { - balances[i].TotalValue = balances[i].Hold + availAmount - updated = true - break + switch l := balance.Locked.(type) { + case map[string]interface{}: + for x := range l { + lockedF, err := strconv.ParseFloat(l[x].(string), 64) + if err != nil { + return info, err } - } - if !updated { + balances = append(balances, exchange.AccountCurrencyInfo{ CurrencyName: currency.NewCode(x), - TotalValue: availAmount, + Hold: lockedF, }) } + default: + break } - default: - break - } - info.Accounts = append(info.Accounts, exchange.Account{ - Currencies: balances, - }) + switch v := balance.Available.(type) { + case map[string]interface{}: + for x := range v { + availAmount, err := strconv.ParseFloat(v[x].(string), 64) + if err != nil { + return info, err + } + + var updated bool + for i := range balances { + if balances[i].CurrencyName == currency.NewCode(x) { + balances[i].TotalValue = balances[i].Hold + availAmount + updated = true + break + } + } + if !updated { + balances = append(balances, exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(x), + TotalValue: availAmount, + }) + } + } + default: + break + } + + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: balances, + }) + } info.Exchange = g.Name @@ -401,15 +421,18 @@ func (g *Gateio) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } response, err := g.SpotNewOrder(spotNewOrderRequestParams) + if err != nil { + return submitOrderResponse, err + } if response.OrderNumber > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) } - - if err == nil { - submitOrderResponse.IsOrderPlaced = true + if response.LeftAmount == 0 { + submitOrderResponse.FullyMatched = true } + submitOrderResponse.IsOrderPlaced = true - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -457,7 +480,6 @@ func (g *Gateio) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, erro // GetOrderInfo returns information on a current open order func (g *Gateio) GetOrderInfo(orderID string) (order.Detail, error) { var orderDetail order.Detail - orders, err := g.GetOpenOrders("") if err != nil { return orderDetail, errors.New("failed to get open orders") @@ -534,40 +556,79 @@ func (g *Gateio) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) // GetActiveOrders retrieves any orders that are active/open func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { + var orders []order.Detail var currPair string if len(req.Currencies) == 1 { currPair = req.Currencies[0].String() } + if g.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + for i := 0; ; i += 100 { + resp, err := g.wsGetOrderInfo(req.OrderType.String(), i, 100) + if err != nil { + return orders, err + } - resp, err := g.GetOpenOrders(currPair) - if err != nil { - return nil, err - } - - var orders []order.Detail - for i := range resp.Orders { - if resp.Orders[i].Status != "open" { - continue + for j := range resp.WebSocketOrderQueryRecords { + orderSide := order.Buy + if resp.WebSocketOrderQueryRecords[j].Type == 1 { + orderSide = order.Sell + } + orderType := order.Market + if resp.WebSocketOrderQueryRecords[j].OrderType == 1 { + orderType = order.Limit + } + firstNum, decNum, err := convert.SplitFloatDecimals(resp.WebSocketOrderQueryRecords[j].Ctime) + if err != nil { + return orders, err + } + orderDate := time.Unix(firstNum, decNum) + orders = append(orders, order.Detail{ + Exchange: g.Name, + AccountID: strconv.FormatInt(resp.WebSocketOrderQueryRecords[j].User, 10), + ID: strconv.FormatInt(resp.WebSocketOrderQueryRecords[j].ID, 10), + CurrencyPair: currency.NewPairFromString(resp.WebSocketOrderQueryRecords[j].Market), + OrderSide: orderSide, + OrderType: orderType, + OrderDate: orderDate, + Price: resp.WebSocketOrderQueryRecords[j].Price, + Amount: resp.WebSocketOrderQueryRecords[j].Amount, + ExecutedAmount: resp.WebSocketOrderQueryRecords[j].FilledAmount, + RemainingAmount: resp.WebSocketOrderQueryRecords[j].Left, + Fee: resp.WebSocketOrderQueryRecords[j].DealFee, + }) + } + if len(resp.WebSocketOrderQueryRecords) < 100 { + break + } + } + } else { + resp, err := g.GetOpenOrders(currPair) + if err != nil { + return nil, err } - symbol := currency.NewPairDelimiter(resp.Orders[i].CurrencyPair, - g.GetPairFormat(asset.Spot, false).Delimiter) - side := order.Side(strings.ToUpper(resp.Orders[i].Type)) - orderDate := time.Unix(resp.Orders[i].Timestamp, 0) + for i := range resp.Orders { + if resp.Orders[i].Status != "open" { + continue + } - orders = append(orders, order.Detail{ - ID: resp.Orders[i].OrderNumber, - Amount: resp.Orders[i].Amount, - Price: resp.Orders[i].Rate, - RemainingAmount: resp.Orders[i].FilledAmount, - OrderDate: orderDate, - OrderSide: side, - Exchange: g.Name, - CurrencyPair: symbol, - Status: order.Status(resp.Orders[i].Status), - }) + symbol := currency.NewPairDelimiter(resp.Orders[i].CurrencyPair, + g.GetPairFormat(asset.Spot, false).Delimiter) + side := order.Side(strings.ToUpper(resp.Orders[i].Type)) + orderDate := time.Unix(resp.Orders[i].Timestamp, 0) + orders = append(orders, order.Detail{ + ID: resp.Orders[i].OrderNumber, + Amount: resp.Orders[i].Amount, + Price: resp.Orders[i].Rate, + RemainingAmount: resp.Orders[i].FilledAmount, + OrderDate: orderDate, + OrderSide: side, + Exchange: g.Name, + CurrencyPair: symbol, + Status: order.Status(resp.Orders[i].Status), + }) + } } - order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks) order.FilterOrdersBySide(&orders, req.OrderSide) return orders, nil @@ -577,8 +638,8 @@ func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e // Can Limit response to specific order status func (g *Gateio) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var trades []TradesResponse - for _, currency := range req.Currencies { - resp, err := g.GetTradeHistory(currency.String()) + for i := range req.Currencies { + resp, err := g.GetTradeHistory(req.Currencies[i].String()) if err != nil { return nil, err } diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index dec36bab..02704567 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -336,9 +336,7 @@ func TestFormatWithdrawPermissions(t *testing.T) { exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := g.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, @@ -425,7 +423,6 @@ func TestCancelExchangeOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "266029865", } @@ -448,7 +445,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 9815ab36..36bf2cb2 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -32,7 +32,7 @@ const ( ) // Instantiates a communications channel between websocket connections -var comms = make(chan ReadData, 1) +var comms = make(chan ReadData) var responseMaxLimit time.Duration var responseCheckTimeout time.Duration @@ -62,13 +62,13 @@ func (g *Gemini) WsConnect() error { // WsSubscribe subscribes to the full websocket suite on gemini exchange func (g *Gemini) WsSubscribe(dialer *websocket.Dialer) error { enabledCurrencies := g.GetEnabledPairs(asset.Spot) - for i, c := range enabledCurrencies { + for i := range enabledCurrencies { val := url.Values{} val.Set("heartbeat", "true") endpoint := fmt.Sprintf("%s%s/%s?%s", g.API.Endpoints.WebsocketURL, geminiWsMarketData, - c.String(), + enabledCurrencies[i].String(), val.Encode()) connection := &wshandler.WebsocketConnection{ ExchangeName: g.Name, @@ -82,7 +82,7 @@ func (g *Gemini) WsSubscribe(dialer *websocket.Dialer) error { return fmt.Errorf("%v Websocket connection %v error. Error %v", g.Name, endpoint, err) } - go g.WsReadData(connection, c) + go g.WsReadData(connection, enabledCurrencies[i]) if len(enabledCurrencies)-1 == i { return nil } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 16e66d45..d730b463 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -333,13 +333,16 @@ func (g *Gemini) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.Price, s.OrderSide.String(), "exchange limit") + if err != nil { + return submitOrderResponse, err + } if response > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + + submitOrderResponse.IsOrderPlaced = true + + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -369,8 +372,8 @@ func (g *Gemini) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, erro return cancelAllOrdersResponse, err } - for _, order := range resp.Details.CancelRejects { - cancelAllOrdersResponse.Status[order] = "Could not cancel order" + for i := range resp.Details.CancelRejects { + cancelAllOrdersResponse.Status[resp.Details.CancelRejects[i]] = "Could not cancel order" } return cancelAllOrdersResponse, nil diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index f6145843..63649393 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -315,7 +315,7 @@ func (h *HitBTC) GetOpenOrders(currency string) ([]OrderHistoryResponse, error) // PlaceOrder places an order on the exchange func (h *HitBTC) PlaceOrder(currency string, rate, amount float64, orderType, side string) (OrderResponse, error) { - result := OrderResponse{} + var result OrderResponse values := url.Values{} values.Set("symbol", currency) diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index 976de532..208d5fdc 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -1,7 +1,9 @@ package hitbtc import ( + "log" "net/http" + "os" "testing" "time" @@ -26,19 +28,16 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { h.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("HitBTC load config error", err) + log.Fatal("HitBTC load config error", err) } hitbtcConfig, err := cfg.GetExchangeConfig("HitBTC") if err != nil { - t.Error("HitBTC Setup() init error") + log.Fatal("HitBTC Setup() init error") } hitbtcConfig.API.AuthenticatedSupport = true hitbtcConfig.API.AuthenticatedWebsocketSupport = true @@ -47,8 +46,10 @@ func TestSetup(t *testing.T) { err = h.Setup(hitbtcConfig) if err != nil { - t.Fatal("HitBTC setup error", err) + log.Fatal("HitBTC setup error", err) } + + os.Exit(m.Run()) } func TestGetOrderbook(t *testing.T) { @@ -94,7 +95,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() h.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -106,9 +107,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestUpdateTicker(t *testing.T) { - h.SetDefaults() - TestSetup(t) - h.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTC-USD", "XRP-USD"}), true) _, err := h.UpdateTicker(currency.NewPair(currency.BTC, currency.USD), asset.Spot) if err != nil { @@ -136,9 +134,6 @@ func TestGetSingularTicker(t *testing.T) { } func TestGetFee(t *testing.T) { - h.SetDefaults() - TestSetup(t) - var feeBuilder = setFeeBuilder() if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic @@ -219,20 +214,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - h.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := h.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - h.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.ETH, currency.BTC)}, @@ -247,9 +236,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - h.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.ETH, currency.BTC)}, @@ -270,9 +256,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -297,15 +280,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -323,15 +302,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -354,6 +329,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := h.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -361,8 +339,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - h.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -386,9 +362,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -401,9 +374,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -432,8 +402,6 @@ func setupWsAuth(t *testing.T) { if wsSetupRan { return } - TestSetDefaults(t) - TestSetup(t) if !h.Websocket.IsEnabled() && !h.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index e77522ff..aa24a6dc 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "time" @@ -395,7 +396,7 @@ func (h *HitBTC) wsLogin() error { return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", h.Name) } h.Websocket.SetCanUseAuthenticatedEndpoints(true) - nonce := fmt.Sprintf("%v", time.Now().Unix()) + nonce := strconv.FormatInt(time.Now().Unix(), 10) hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(nonce), []byte(h.API.Credentials.Secret)) request := WsLoginRequest{ Method: "login", @@ -483,7 +484,7 @@ func (h *HitBTC) wsReplaceOrder(clientOrderID string, quantity, price float64) ( Method: "cancelReplaceOrder", Params: WsReplaceOrderRequestData{ ClientOrderID: clientOrderID, - RequestClientID: fmt.Sprintf("%v", time.Now().Unix()), + RequestClientID: strconv.FormatInt(time.Now().Unix(), 10), Quantity: quantity, Price: price, }, diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index bd823364..c1cc92b6 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -373,24 +373,42 @@ func (h *HitBTC) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex } // SubmitOrder submits a new order -func (h *HitBTC) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { +func (h *HitBTC) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - if err := s.Validate(); err != nil { + err := o.Validate() + if err != nil { return submitOrderResponse, err } + if h.Websocket.IsConnected() && h.Websocket.CanUseAuthenticatedEndpoints() { + var response *WsSubmitOrderSuccessResponse + response, err = h.wsPlaceOrder(o.Pair, o.OrderSide.String(), o.Amount, o.Price) + if err != nil { + return submitOrderResponse, err + } + submitOrderResponse.OrderID = strconv.FormatInt(response.ID, 10) + if response.Result.CumQuantity == o.Amount { + submitOrderResponse.FullyMatched = true + } + } else { + var response OrderResponse + response, err = h.PlaceOrder(o.Pair.String(), + o.Price, + o.Amount, + strings.ToLower(o.OrderType.String()), + strings.ToLower(o.OrderSide.String())) + if err != nil { + return submitOrderResponse, err + } + if response.OrderNumber > 0 { + submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) + } + if o.OrderType == order.Market { + submitOrderResponse.FullyMatched = true + } + } + submitOrderResponse.IsOrderPlaced = true - response, err := h.PlaceOrder(s.Pair.String(), - s.Price, - s.Amount, - s.OrderType.Lower(), - s.OrderSide.Lower()) - if response.OrderNumber > 0 { - submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) - } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index f01482ea..22121506 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -44,7 +44,7 @@ const ( huobiOrderCancel = "order/orders/%s/submitcancel" huobiOrderCancelBatch = "order/orders/batchcancel" huobiBatchCancelOpenOrders = "order/orders/batchCancelOpenOrders" - huobiGetOrder = "order/orders/%s" + huobiGetOrder = "order/orders/getClientOrder" huobiGetOrderMatch = "order/orders/%s/matchresults" huobiGetOrders = "order/orders" huobiGetOpenOrders = "order/openOrders" @@ -443,8 +443,9 @@ func (h *HUOBI) GetOrder(orderID int64) (OrderInfo, error) { } var result response - endpoint := fmt.Sprintf(huobiGetOrder, strconv.FormatInt(orderID, 10)) - err := h.SendAuthenticatedHTTPRequest(http.MethodGet, endpoint, url.Values{}, nil, &result) + urlVal := url.Values{} + urlVal.Set("clientOrderId", strconv.FormatInt(orderID, 10)) + err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOrder, urlVal, nil, &result) if result.ErrorMessage != "" { return result.Order, errors.New(result.ErrorMessage) @@ -514,7 +515,7 @@ func (h *HUOBI) GetOrders(symbol, types, start, end, states, from, direct, size } // GetOpenOrders returns a list of orders -func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int) ([]OrderInfo, error) { +func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int64) ([]OrderInfo, error) { type response struct { Response Orders []OrderInfo `json:"data"` @@ -526,7 +527,7 @@ func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int) ([]Order if len(side) > 0 { vals.Set("side", side) } - vals.Set("size", fmt.Sprintf("%v", size)) + vals.Set("size", strconv.FormatInt(size, 10)) var result response err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOpenOrders, vals, nil, &result) @@ -855,8 +856,7 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url values.Set("PrivateSignature", crypto.Base64Encode(privSig)) } - urlPath := fmt.Sprintf("%s%s", common.EncodeURLValues(h.API.Endpoints.URL, values), - endpoint) + urlPath := h.API.Endpoints.URL + common.EncodeURLValues(endpoint, values) var body []byte diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 91079f85..714af623 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -7,6 +7,7 @@ import ( "encoding/pem" "io/ioutil" "log" + "os" "strconv" "strings" "testing" @@ -33,11 +34,8 @@ const ( var h HUOBI var wsSetupRan bool -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { h.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { @@ -45,7 +43,7 @@ func TestSetup(t *testing.T) { } hConfig, err := cfg.GetExchangeConfig("Huobi") if err != nil { - t.Error("Huobi Setup() init error") + log.Fatal("Huobi Setup() init error") } hConfig.API.AuthenticatedSupport = true hConfig.API.AuthenticatedWebsocketSupport = true @@ -54,16 +52,16 @@ func TestSetup(t *testing.T) { err = h.Setup(hConfig) if err != nil { - t.Fatal("Huobi setup error", err) + log.Fatal("Huobi setup error", err) } + + os.Exit(m.Run()) } func setupWsTests(t *testing.T) { if wsSetupRan { return } - TestSetDefaults(t) - TestSetup(t) if !h.Websocket.IsEnabled() && !h.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -195,8 +193,7 @@ func TestGetTimestamp(t *testing.T) { func TestGetAccounts(t *testing.T) { t.Parallel() - - if !h.ValidateAPICredentials() { + if !h.ValidateAPICredentials() || !canManipulateRealOrders { t.Skip() } @@ -208,8 +205,7 @@ func TestGetAccounts(t *testing.T) { func TestGetAccountBalance(t *testing.T) { t.Parallel() - - if !h.ValidateAPICredentials() { + if !h.ValidateAPICredentials() || !canManipulateRealOrders { t.Skip() } @@ -227,7 +223,6 @@ func TestGetAccountBalance(t *testing.T) { func TestGetAggregatedBalance(t *testing.T) { t.Parallel() - if !h.ValidateAPICredentials() { t.Skip() } @@ -240,8 +235,7 @@ func TestGetAggregatedBalance(t *testing.T) { func TestSpotNewOrder(t *testing.T) { t.Parallel() - - if !h.ValidateAPICredentials() { + if !h.ValidateAPICredentials() || !canManipulateRealOrders { t.Skip() } @@ -261,7 +255,9 @@ func TestSpotNewOrder(t *testing.T) { func TestCancelExistingOrder(t *testing.T) { t.Parallel() - + if !h.ValidateAPICredentials() || !canManipulateRealOrders { + t.Skip() + } _, err := h.CancelExistingOrder(1337) if err == nil { t.Error("Huobi TestCancelExistingOrder Expected error") @@ -270,16 +266,17 @@ func TestCancelExistingOrder(t *testing.T) { func TestGetOrder(t *testing.T) { t.Parallel() - + if !h.ValidateAPICredentials() || !canManipulateRealOrders { + t.Skip() + } _, err := h.GetOrder(1337) - if err == nil { - t.Error("Huobi TestCancelOrder Expected error") + if err != nil { + t.Error(err) } } func TestGetMarginLoanOrders(t *testing.T) { t.Parallel() - if !h.ValidateAPICredentials() { t.Skip() } @@ -292,7 +289,6 @@ func TestGetMarginLoanOrders(t *testing.T) { func TestGetMarginAccountBalance(t *testing.T) { t.Parallel() - if !h.ValidateAPICredentials() { t.Skip() } @@ -305,7 +301,9 @@ func TestGetMarginAccountBalance(t *testing.T) { func TestCancelWithdraw(t *testing.T) { t.Parallel() - + if !h.ValidateAPICredentials() || !canManipulateRealOrders { + t.Skip() + } _, err := h.CancelWithdraw(1337) if err == nil { t.Error("Huobi TestCancelWithdraw Expected error") @@ -314,7 +312,6 @@ func TestCancelWithdraw(t *testing.T) { func TestPEMLoadAndSign(t *testing.T) { t.Parallel() - pemKey := strings.NewReader(h.API.Credentials.PEMKey) pemBytes, err := ioutil.ReadAll(pemKey) if err != nil { @@ -355,7 +352,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() h.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -443,20 +440,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - h.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := h.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - h.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)}, @@ -471,9 +462,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - h.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)}, @@ -494,9 +482,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if !h.ValidateAPICredentials() { t.Skip() } @@ -528,15 +513,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -555,8 +536,6 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - h.SetDefaults() - TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -584,7 +563,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { _, err := h.GetAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") @@ -598,6 +577,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := h.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -605,8 +587,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - h.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -630,9 +610,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -645,9 +622,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - h.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -669,7 +643,7 @@ func TestGetDepositAddress(t *testing.T) { // TestWsGetAccountsList connects to WS, logs in, gets account list func TestWsGetAccountsList(t *testing.T) { setupWsTests(t) - resp, err := h.wsGetAccountsList(currency.NewPairFromString("ethbtc")) + resp, err := h.wsGetAccountsList() if err != nil { t.Fatal(err) } diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index c4d516bc..9ad0f9f2 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -170,24 +170,23 @@ type CancelOrderBatch struct { // OrderInfo stores the order info type OrderInfo struct { - ID int `json:"id"` + ID int64 `json:"id"` Symbol string `json:"symbol"` - AccountID float64 `json:"account-id"` + AccountID int64 `json:"account-id"` Amount float64 `json:"amount,string"` Price float64 `json:"price,string"` CreatedAt int64 `json:"created-at"` Type string `json:"type"` FieldAmount float64 `json:"field-amount,string"` FieldCashAmount float64 `json:"field-cash-amount,string"` - Fieldees float64 `json:"field-fees,string"` FilledAmount float64 `json:"filled-amount,string"` FilledCashAmount float64 `json:"filled-cash-amount,string"` FilledFees float64 `json:"filled-fees,string"` FinishedAt int64 `json:"finished-at"` - UserID int `json:"user-id"` + UserID int64 `json:"user-id"` Source string `json:"source"` State string `json:"state"` - CanceledAt int `json:"canceled-at"` + CanceledAt int64 `json:"canceled-at"` Exchange string `json:"exchange"` Batch string `json:"batch"` } @@ -547,31 +546,13 @@ type WsAuthenticatedAccountsListResponseDataList struct { // WsAuthenticatedOrdersListResponse response from OrdersList authenticated endpoint type WsAuthenticatedOrdersListResponse struct { WsAuthenticatedDataResponse - Data []WsAuthenticatedOrdersListResponseData `json:"data"` -} - -// WsAuthenticatedOrdersListResponseData contains order details -type WsAuthenticatedOrdersListResponseData struct { - ID int64 `json:"id"` - Symbol string `json:"symbol"` - AccountID int64 `json:"account-id"` - Amount float64 `json:"amount,string"` - Price float64 `json:"price,string"` - CreatedAt int64 `json:"created-at"` - Type string `json:"type"` - FilledAmount float64 `json:"filled-amount,string"` - FilledCashAmount float64 `json:"filled-cash-amount,string"` - FilledFees float64 `json:"filled-fees,string"` - FinishedAt int64 `json:"finished-at"` - Source string `json:"source"` - State string `json:"state"` - CanceledAt int64 `json:"canceled-at"` + Data []OrderInfo `json:"data"` } // WsAuthenticatedOrderDetailResponse response from OrderDetail authenticated endpoint type WsAuthenticatedOrderDetailResponse struct { WsAuthenticatedDataResponse - Data WsAuthenticatedOrdersListResponseData `json:"data"` + Data OrderInfo `json:"data"` } // WsPong sent for pong messages diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index fa7022f4..93dda0a8 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -49,7 +49,7 @@ const ( ) // Instantiates a communications channel between websocket connections -var comms = make(chan WsMessage, 1) +var comms = make(chan WsMessage) // WsConnect initiates a new websocket connection func (h *HUOBI) WsConnect() error { @@ -68,6 +68,7 @@ func (h *HUOBI) WsConnect() error { err = h.wsLogin() if err != nil { log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err) + h.Websocket.SetCanUseAuthenticatedEndpoints(false) } go h.WsHandleData() @@ -434,7 +435,7 @@ func (h *HUOBI) wsAuthenticatedSubscribe(operation, endpoint, topic string) erro return h.AuthenticatedWebsocketConn.SendMessage(request) } -func (h *HUOBI) wsGetAccountsList(pair currency.Pair) (*WsAuthenticatedAccountsListResponse, error) { +func (h *HUOBI) wsGetAccountsList() (*WsAuthenticatedAccountsListResponse, error) { if !h.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authenticated cannot get accounts list", h.Name) } @@ -446,7 +447,6 @@ func (h *HUOBI) wsGetAccountsList(pair currency.Pair) (*WsAuthenticatedAccountsL SignatureVersion: signatureVersion, Timestamp: timestamp, Topic: wsAccountsList, - Symbol: h.FormatExchangeCurrency(pair, asset.Spot).String(), } hmac := h.wsGenerateSignature(timestamp, wsAccountListEndpoint) request.Signature = crypto.Base64Encode(hmac) diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index e83c5e8b..c2429488 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" "time" @@ -96,6 +97,8 @@ func (h *HUOBI) SetDefaults() { AuthenticatedEndpoints: true, AccountInfo: true, MessageCorrelation: true, + GetOrder: true, + GetOrders: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | exchange.NoFiatWithdrawals, @@ -393,62 +396,83 @@ func (h *HUOBI) GetAccountID() ([]Account, error) { func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo info.Exchange = h.Name - - accounts, err := h.GetAccountID() - if err != nil { - return info, err - } - - for i := range accounts { - var acc exchange.Account - acc.ID = strconv.FormatInt(accounts[i].ID, 10) - balances, err := h.GetAccountBalance(acc.ID) + if h.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + resp, err := h.wsGetAccountsList() if err != nil { return info, err } - var currencyDetails []exchange.AccountCurrencyInfo - for j := range balances { - var frozen bool - if balances[j].Type == "frozen" { - frozen = true + for i := range resp.Data { + if len(resp.Data[i].List) == 0 { + continue + } + currData := exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(resp.Data[i].List[0].Currency), + TotalValue: resp.Data[i].List[0].Balance, + } + if len(resp.Data[i].List) > 1 && resp.Data[i].List[1].Type == "frozen" { + currData.Hold = resp.Data[i].List[1].Balance + } + currencyDetails = append(currencyDetails, currData) + } + var acc exchange.Account + acc.Currencies = currencyDetails + info.Accounts = append(info.Accounts, acc) + } else { + accounts, err := h.GetAccountID() + if err != nil { + return info, err + } + for i := range accounts { + var acc exchange.Account + acc.ID = strconv.FormatInt(accounts[i].ID, 10) + balances, err := h.GetAccountBalance(acc.ID) + if err != nil { + return info, err } - var updated bool - for i := range currencyDetails { - if currencyDetails[i].CurrencyName == currency.NewCode(balances[j].Currency) { - if frozen { - currencyDetails[i].Hold = balances[j].Balance - } else { - currencyDetails[i].TotalValue = balances[j].Balance + var currencyDetails []exchange.AccountCurrencyInfo + for j := range balances { + var frozen bool + if balances[j].Type == "frozen" { + frozen = true + } + + var updated bool + for i := range currencyDetails { + if currencyDetails[i].CurrencyName == currency.NewCode(balances[j].Currency) { + if frozen { + currencyDetails[i].Hold = balances[j].Balance + } else { + currencyDetails[i].TotalValue = balances[j].Balance + } + updated = true } - updated = true + } + + if updated { + continue + } + + if frozen { + currencyDetails = append(currencyDetails, + exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(balances[j].Currency), + Hold: balances[j].Balance, + }) + } else { + currencyDetails = append(currencyDetails, + exchange.AccountCurrencyInfo{ + CurrencyName: currency.NewCode(balances[j].Currency), + TotalValue: balances[j].Balance, + }) } } - if updated { - continue - } - - if frozen { - currencyDetails = append(currencyDetails, - exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(balances[j].Currency), - Hold: balances[j].Balance, - }) - } else { - currencyDetails = append(currencyDetails, - exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(balances[j].Currency), - TotalValue: balances[j].Balance, - }) - } + acc.Currencies = currencyDetails + info.Accounts = append(info.Accounts, acc) } - - acc.Currencies = currencyDetails - info.Accounts = append(info.Accounts, acc) } - return info, nil } @@ -498,13 +522,18 @@ func (h *HUOBI) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { params.Type = formattedType response, err := h.SpotNewOrder(params) + if err != nil { + return submitOrderResponse, err + } if response > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true + + submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -527,9 +556,10 @@ func (h *HUOBI) CancelOrder(order *order.Cancel) error { // CancelAllOrders cancels all orders associated with a currency pair func (h *HUOBI) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { var cancelAllOrdersResponse order.CancelAllResponse - for _, currency := range h.GetEnabledPairs(asset.Spot) { + enabledPairs := h.GetEnabledPairs(asset.Spot) + for i := range enabledPairs { resp, err := h.CancelOpenOrdersBatch(orderCancellation.AccountID, - h.FormatExchangeCurrency(currency, asset.Spot).String()) + h.FormatExchangeCurrency(enabledPairs[i], asset.Spot).String()) if err != nil { return cancelAllOrdersResponse, err } @@ -551,7 +581,56 @@ func (h *HUOBI) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAl // GetOrderInfo returns information on a current open order func (h *HUOBI) GetOrderInfo(orderID string) (order.Detail, error) { var orderDetail order.Detail - return orderDetail, common.ErrNotYetImplemented + var respData *OrderInfo + if h.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + resp, err := h.wsGetOrderDetails(orderID) + if err != nil { + return orderDetail, err + } + respData = &resp.Data + } else { + oID, err := strconv.ParseInt(orderID, 10, 64) + if err != nil { + return orderDetail, err + } + resp, err := h.GetOrder(oID) + if err != nil { + return orderDetail, err + } + respData = &resp + } + if respData.ID == 0 { + return orderDetail, fmt.Errorf("%s - order not found for orderid %s", h.Name, orderID) + } + + typeDetails := strings.Split(respData.Type, "-") + orderSide, err := order.StringToOrderSide(typeDetails[0]) + if err != nil { + return orderDetail, err + } + orderType, err := order.StringToOrderType(typeDetails[1]) + if err != nil { + return orderDetail, err + } + orderStatus, err := order.StringToOrderStatus(respData.State) + if err != nil { + return orderDetail, err + } + orderDetail = order.Detail{ + Exchange: h.Name, + ID: strconv.FormatInt(respData.ID, 10), + AccountID: strconv.FormatInt(respData.AccountID, 10), + CurrencyPair: currency.NewPairFromString(respData.Symbol), + OrderType: orderType, + OrderSide: orderSide, + OrderDate: time.Unix(respData.CreatedAt, 0), + Status: orderStatus, + Price: respData.Price, + Amount: respData.Amount, + ExecutedAmount: respData.FilledAmount, + Fee: respData.FilledFees, + } + return orderDetail, nil } // GetDepositAddress returns a deposit address for a specified currency @@ -607,32 +686,72 @@ func (h *HUOBI) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er var orders []order.Detail - for i := range req.Currencies { - resp, err := h.GetOpenOrders(h.API.Credentials.ClientID, - req.Currencies[i].Lower().String(), - side, - 500) - if err != nil { - return nil, err + if h.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + for i := range req.Currencies { + resp, err := h.wsGetOrdersList(-1, req.Currencies[i]) + if err != nil { + return orders, err + } + for j := range resp.Data { + sideData := strings.Split(resp.Data[j].OrderState, "-") + side = sideData[0] + orderSide, err := order.StringToOrderSide(side) + if err != nil { + return orders, err + } + orderType, err := order.StringToOrderType(sideData[1]) + if err != nil { + return orders, err + } + orderStatus, err := order.StringToOrderStatus(resp.Data[j].OrderState) + if err != nil { + return orders, err + } + orders = append(orders, order.Detail{ + Exchange: h.Name, + AccountID: strconv.FormatInt(resp.Data[j].AccountID, 10), + ID: strconv.FormatInt(resp.Data[j].OrderID, 10), + CurrencyPair: req.Currencies[i], + OrderType: orderType, + OrderSide: orderSide, + OrderDate: time.Unix(resp.Data[j].CreatedAt, 0), + Status: orderStatus, + Price: resp.Data[j].Price, + Amount: resp.Data[j].OrderAmount, + ExecutedAmount: resp.Data[j].FilledAmount, + RemainingAmount: resp.Data[j].UnfilledAmount, + Fee: resp.Data[j].FilledFees, + }) + } } - - for i := range resp { - orderDetail := order.Detail{ - ID: strconv.FormatInt(int64(resp[i].ID), 10), - Price: resp[i].Price, - Amount: resp[i].Amount, - CurrencyPair: req.Currencies[i], - Exchange: h.Name, - ExecutedAmount: resp[i].FilledAmount, - OrderDate: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)), - Status: order.Status(resp[i].State), - AccountID: strconv.FormatFloat(resp[i].AccountID, 'f', -1, 64), - Fee: resp[i].FilledFees, + } else { + for i := range req.Currencies { + resp, err := h.GetOpenOrders(h.API.Credentials.ClientID, + req.Currencies[i].Lower().String(), + side, + 500) + if err != nil { + return nil, err } - setOrderSideAndType(resp[i].Type, &orderDetail) + for i := range resp { + orderDetail := order.Detail{ + ID: strconv.FormatInt(resp[i].ID, 10), + Price: resp[i].Price, + Amount: resp[i].Amount, + CurrencyPair: req.Currencies[i], + Exchange: h.Name, + ExecutedAmount: resp[i].FilledAmount, + OrderDate: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)), + Status: order.Status(resp[i].State), + AccountID: strconv.FormatInt(resp[i].AccountID, 10), + Fee: resp[i].FilledFees, + } - orders = append(orders, orderDetail) + setOrderSideAndType(resp[i].Type, &orderDetail) + + orders = append(orders, orderDetail) + } } } @@ -664,7 +783,7 @@ func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er for i := range resp { orderDetail := order.Detail{ - ID: strconv.FormatInt(int64(resp[i].ID), 10), + ID: strconv.FormatInt(resp[i].ID, 10), Price: resp[i].Price, Amount: resp[i].Amount, CurrencyPair: req.Currencies[i], @@ -672,7 +791,7 @@ func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er ExecutedAmount: resp[i].FilledAmount, OrderDate: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)), Status: order.Status(resp[i].State), - AccountID: strconv.FormatFloat(resp[i].AccountID, 'f', -1, 64), + AccountID: strconv.FormatInt(resp[i].AccountID, 10), Fee: resp[i].FilledFees, } diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index 42f29425..85cfd4d6 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -1,7 +1,9 @@ package itbit import ( + "log" "net/url" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -21,19 +23,16 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { i.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Itbit load config error", err) + log.Fatal("Itbit load config error", err) } itbitConfig, err := cfg.GetExchangeConfig("ITBIT") if err != nil { - t.Error("Itbit Setup() init error") + log.Fatal("Itbit Setup() init error") } itbitConfig.API.AuthenticatedSupport = true itbitConfig.API.Credentials.Key = apiKey @@ -42,8 +41,10 @@ func TestSetup(t *testing.T) { err = i.Setup(itbitConfig) if err != nil { - t.Fatal("Itbit setup error", err) + log.Fatal("Itbit setup error", err) } + + os.Exit(m.Run()) } func TestGetTicker(t *testing.T) { @@ -167,7 +168,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() i.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -255,20 +256,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - i.SetDefaults() expectedResult := exchange.WithdrawCryptoViaWebsiteOnlyText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := i.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - i.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -282,9 +277,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - i.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -304,8 +296,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - i.SetDefaults() - TestSetup(t) if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -330,15 +320,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - i.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -357,15 +343,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - i.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -388,7 +370,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - if apiKey != "" || apiSecret != "" || clientID != "" { + if areTestAPIKeysSet() { _, err := i.GetAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") @@ -397,6 +379,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := i.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -404,8 +389,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - i.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -426,9 +409,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - i.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -441,9 +421,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - i.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 1168ebc4..918b877d 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -331,14 +331,18 @@ func (i *ItBit) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.Price, s.Pair.String(), "") + if err != nil { + return submitOrderResponse, err + } if response.ID != "" { submitOrderResponse.OrderID = response.ID } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + if response.AmountFilled == s.Amount { + submitOrderResponse.FullyMatched = true + } + submitOrderResponse.IsOrderPlaced = true + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 9bc63241..46e532db 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -47,6 +47,7 @@ const ( krakenDepositAddresses = "DepositAddresses" krakenWithdrawStatus = "WithdrawStatus" krakenWithdrawCancel = "WithdrawCancel" + krakenWebsocketToken = "GetWebSocketsToken" krakenAuthRate = 0 krakenUnauthRate = 0 @@ -57,8 +58,9 @@ var assetPairMap map[string]string // Kraken is the overarching type across the alphapoint package type Kraken struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection - wsRequestMtx sync.Mutex + WebsocketConn *wshandler.WebsocketConnection + AuthenticatedWebsocketConn *wshandler.WebsocketConnection + wsRequestMtx sync.Mutex } // GetServerTime returns current server time @@ -1036,3 +1038,14 @@ func (k *Kraken) WithdrawCancel(c currency.Code, refID string) (bool, error) { return response.Result, GetError(response.Error) } + +func (k *Kraken) GetWebsocketToken() (string, error) { + var response WsTokenResponse + if err := k.SendAuthenticatedHTTPRequest(krakenWebsocketToken, url.Values{}, &response); err != nil { + return "", err + } + if len(response.Error) > 0 { + return "", fmt.Errorf("%s - %v", k.Name, response.Error) + } + return response.Result.Token, nil +} diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 41b22ed7..227bde0b 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -3,6 +3,8 @@ package kraken import ( "log" "net/http" + "os" + "strings" "testing" "github.com/gorilla/websocket" @@ -25,13 +27,9 @@ const ( canManipulateRealOrders = false ) -// TestSetDefaults setup func -func TestSetDefaults(t *testing.T) { - k.SetDefaults() -} - // TestSetup setup func -func TestSetup(t *testing.T) { +func TestMain(m *testing.M) { + k.SetDefaults() cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { @@ -39,19 +37,19 @@ func TestSetup(t *testing.T) { } krakenConfig, err := cfg.GetExchangeConfig("Kraken") if err != nil { - t.Error("kraken Setup() init error", err) + log.Fatal("kraken Setup() init error", err) } krakenConfig.API.AuthenticatedSupport = true krakenConfig.API.Credentials.Key = apiKey krakenConfig.API.Credentials.Secret = apiSecret krakenConfig.API.Credentials.ClientID = clientID krakenConfig.API.Endpoints.WebsocketURL = k.API.Endpoints.WebsocketURL - subscribeToDefaultChannels = false - err = k.Setup(krakenConfig) if err != nil { - t.Fatal("Kraken setup error", err) + log.Fatal("Kraken setup error", err) } + + os.Exit(m.Run()) } // TestGetServerTime API endpoint test @@ -278,7 +276,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() k.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -290,8 +288,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - k.SetDefaults() - TestSetup(t) var feeBuilder = setFeeBuilder() if areTestAPIKeysSet() { @@ -373,11 +369,8 @@ func TestGetFee(t *testing.T) { // TestFormatWithdrawPermissions logic test func TestFormatWithdrawPermissions(t *testing.T) { - k.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithSetupText + " & " + exchange.WithdrawCryptoWith2FAText + " & " + exchange.AutoWithdrawFiatWithSetupText + " & " + exchange.WithdrawFiatWith2FAText - withdrawPermissions := k.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } @@ -385,9 +378,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { // TestGetActiveOrders wrapper test func TestGetActiveOrders(t *testing.T) { - k.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -402,9 +392,6 @@ func TestGetActiveOrders(t *testing.T) { // TestGetOrderHistory wrapper test func TestGetOrderHistory(t *testing.T) { - k.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -417,6 +404,21 @@ func TestGetOrderHistory(t *testing.T) { } } +// TestGetOrderHistory wrapper test +func TestGetOrderInfo(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } + + _, err := k.GetOrderInfo("ImACoolOrderID") + if !areTestAPIKeysSet() && err == nil { + t.Error("Expecting error") + } + if areTestAPIKeysSet() && !strings.Contains(err.Error(), "- Order ID not found:") { + t.Error("Expected Order ID not found error") + } +} + // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them // ---------------------------------------------------------------------------------------------------------------------------- func areTestAPIKeysSet() bool { @@ -425,9 +427,6 @@ func areTestAPIKeysSet() bool { // TestSubmitOrder wrapper test func TestSubmitOrder(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -453,15 +452,11 @@ func TestSubmitOrder(t *testing.T) { // TestCancelExchangeOrder wrapper test func TestCancelExchangeOrder(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -480,15 +475,11 @@ func TestCancelExchangeOrder(t *testing.T) { // TestCancelAllExchangeOrders wrapper test func TestCancelAllExchangeOrders(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -512,7 +503,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { // TestGetAccountInfo wrapper test func TestGetAccountInfo(t *testing.T) { - if apiKey != "" || apiSecret != "" || clientID != "" { + if areTestAPIKeysSet() || clientID != "" { _, err := k.GetAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) @@ -527,6 +518,9 @@ func TestGetAccountInfo(t *testing.T) { // TestModifyOrder wrapper test func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := k.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -535,9 +529,6 @@ func TestModifyOrder(t *testing.T) { // TestWithdraw wrapper test func TestWithdraw(t *testing.T) { - k.SetDefaults() - TestSetup(t) - withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -563,9 +554,6 @@ func TestWithdraw(t *testing.T) { // TestWithdrawFiat wrapper test func TestWithdrawFiat(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -590,9 +578,6 @@ func TestWithdrawFiat(t *testing.T) { // TestWithdrawInternationalBank wrapper test func TestWithdrawInternationalBank(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -632,9 +617,6 @@ func TestGetDepositAddress(t *testing.T) { // TestWithdrawStatus wrapper test func TestWithdrawStatus(t *testing.T) { - k.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() { _, err := k.WithdrawStatus(currency.BTC, "") if err != nil { @@ -650,8 +632,6 @@ func TestWithdrawStatus(t *testing.T) { // TestWithdrawCancel wrapper test func TestWithdrawCancel(t *testing.T) { - k.SetDefaults() - TestSetup(t) _, err := k.WithdrawCancel(currency.BTC, "") if areTestAPIKeysSet() && err == nil { t.Error("WithdrawCancel() error cannot be nil") @@ -666,12 +646,11 @@ func setupWsTests(t *testing.T) { if wsSetupRan { return } - TestSetDefaults(t) - TestSetup(t) if !k.Websocket.IsEnabled() && !k.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() + comms = make(chan wshandler.WebsocketResponse, sharedtestvalues.WebsocketChannelOverrideCapacity) k.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() k.WebsocketConn = &wshandler.WebsocketConnection{ ExchangeName: k.Name, @@ -680,12 +659,33 @@ func setupWsTests(t *testing.T) { ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, } + k.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ + ExchangeName: k.Name, + URL: krakenAuthWSURL, + Verbose: k.Verbose, + ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, + ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, + } var dialer websocket.Dialer err := k.WebsocketConn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } + err = k.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) + if err != nil { + t.Fatal(err) + } + + token, err := k.GetWebsocketToken() + if err != nil { + t.Error(err) + } + authToken = token + + go k.WsReadData(k.WebsocketConn) + go k.WsReadData(k.AuthenticatedWebsocketConn) go k.WsHandleData() + go k.wsPingHandler() wsSetupRan = true } @@ -700,3 +700,38 @@ func TestWebsocketSubscribe(t *testing.T) { t.Error(err) } } + +func TestGetWSToken(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("API keys required, skipping") + } + resp, err := k.GetWebsocketToken() + if err != nil { + t.Error(err) + } + if resp == "" { + t.Error("Token not returned") + } +} + +func TestWsAddOrder(t *testing.T) { + setupWsTests(t) + _, err := k.wsAddOrder(&WsAddOrderRequest{ + OrderType: order.Limit.Lower(), + OrderSide: order.Buy.Lower(), + Pair: "XBT/USD", + Price: -100, + }) + if err != nil { + t.Error(err) + } +} + +func TestWsCancelOrder(t *testing.T) { + setupWsTests(t) + err := k.wsCancelOrders([]string{"1337"}) + if err != nil { + t.Error(err) + } +} diff --git a/exchanges/kraken/kraken_types.go b/exchanges/kraken/kraken_types.go index e816afd4..d950a6a9 100644 --- a/exchanges/kraken/kraken_types.go +++ b/exchanges/kraken/kraken_types.go @@ -1,6 +1,10 @@ package kraken -import "github.com/thrasher-corp/gocryptotrader/currency" +import ( + "time" + + "github.com/thrasher-corp/gocryptotrader/currency" +) // TimeResponse type type TimeResponse struct { @@ -391,7 +395,7 @@ type WithdrawStatusResponse struct { type WebsocketSubscriptionEventRequest struct { Event string `json:"event"` // subscribe RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message. - Pairs []string `json:"pair"` // Array of currency pairs (pair1,pair2,pair3). + Pairs []string `json:"pair,omitempty"` // Array of currency pairs (pair1,pair2,pair3). Subscription WebsocketSubscriptionData `json:"subscription,omitempty"` } @@ -413,6 +417,8 @@ type WebsocketSubscriptionData struct { Name string `json:"name,omitempty"` // ticker|ohlc|trade|book|spread|*, * for all (ohlc interval value is 1 if all channels subscribed) Interval int64 `json:"interval,omitempty"` // Optional - Time interval associated with ohlc subscription in minutes. Default 1. Valid Interval values: 1|5|15|30|60|240|1440|10080|21600 Depth int64 `json:"depth,omitempty"` // Optional - depth associated with book subscription in number of levels each side, default 10. Valid Options are: 10, 25, 100, 500, 1000 + Token string `json:"token,omitempty"` // Optional used for authenticated requests + } // WebsocketEventResponse holds all data response types @@ -459,3 +465,103 @@ type WebsocketChannelData struct { Pair currency.Pair ChannelID int64 } + +// WsTokenResponse holds the WS auth token +type WsTokenResponse struct { + Error []string `json:"error"` + Result struct { + Expires int64 `json:"expires"` + Token string `json:"token"` + } `json:"result"` +} + +// WsOwnTrade ws auth owntrade data +type WsOwnTrade struct { + Cost float64 `json:"cost,string"` + Fee float64 `json:"fee,string"` + Margin float64 `json:"margin,string"` + OrderTransactionID string `json:"ordertxid"` + OrderType string `json:"ordertype"` + Pair string `json:"pair"` + PostTransactionID string `json:"postxid"` + Price float64 `json:"price,string"` + Time time.Time `json:"time"` + Type string `json:"type"` + Vol float64 `json:"vol,string"` +} + +// WsOpenOrders ws auth open order data +type WsOpenOrders struct { + Cost float64 `json:"cost,string"` + Description WsOpenOrderDescription `json:"descr"` + ExpireTime time.Time `json:"expiretm"` + Fee float64 `json:"fee,string"` + LimitPrice float64 `json:"limitprice,string"` + Misc string `json:"misc"` + OFlags string `json:"oflags"` + OpenTime time.Time `json:"opentm"` + Price float64 `json:"price,string"` + RefID string `json:"refid"` + StartTime time.Time `json:"starttm"` + Status string `json:"status"` + StopPrice float64 `json:"stopprice,string"` + UserReference float64 `json:"userref"` + Volume float64 `json:"vol,string"` + ExecutedVolume float64 `json:"vol_exec,string"` +} + +// WsOpenOrderDescription additional data for WsOpenOrders +type WsOpenOrderDescription struct { + Close string `json:"close"` + Leverage string `json:"leverage"` + Order string `json:"order"` + OrderType string `json:"ordertype"` + Pair string `json:"pair"` + Price float64 `json:"price,string"` + Price2 float64 `json:"price2,string"` + Type string `json:"type"` +} + +// WsAddOrderRequest request type for ws adding order +type WsAddOrderRequest struct { + Event string `json:"event"` + Token string `json:"token"` + OrderType string `json:"ordertype"` + OrderSide string `json:"type"` + Pair string `json:"pair"` + Price float64 `json:"price,omitempty"` // optional + Price2 float64 `json:"price2,omitempty"` // optional + Volume float64 `json:"volume,omitempty"` + Leverage float64 `json:"leverage,omitempty"` // optional + OFlags string `json:"oflags,omitempty"` // optional + StartTime string `json:"starttm,omitempty"` // optional + ExpireTime string `json:"expiretm,omitempty"` // optional + UserReferenceID string `json:"userref,omitempty"` // optional + Validate string `json:"validate,omitempty"` // optional + CloseOrderType string `json:"close[ordertype],omitempty"` // optional + ClosePrice float64 `json:"close[price],omitempty"` // optional + ClosePrice2 float64 `json:"close[price2],omitempty"` // optional +} + +// WsAddOrderResponse response data for ws order +type WsAddOrderResponse struct { + Description string `json:"descr"` + Event string `json:"event"` + Status string `json:"status"` + TransactionID string `json:"txid"` + ErrorMessage string `json:"errorMessage"` +} + +// WsCancelOrderRequest request for ws cancel order +type WsCancelOrderRequest struct { + Event string `json:"event"` + Token string `json:"token"` + TransactionIDs []string `json:"txid"` +} + +// WsCancelOrderResponse response data for ws cancel order +type WsCancelOrderResponse struct { + Event string `json:"event"` + Status string `json:"status"` + ErrorMessage string `json:"errorMessage"` +} diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index 1be268b1..8faa8761 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -10,7 +10,9 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" @@ -21,11 +23,9 @@ import ( // List of all websocket channels to subscribe to const ( krakenWSURL = "wss://ws.kraken.com" + krakenAuthWSURL = "wss://ws-auth.kraken.com" krakenWSSandboxURL = "wss://sandbox.kraken.com" - krakenWSSupportedVersion = "0.2.0" - // If a checksum fails, then resubscribing to the channel fails, fatal after these attempts - krakenWsResubscribeFailureLimit = 3 - krakenWsResubscribeDelayInSeconds = 3 + krakenWSSupportedVersion = "0.3.0" // WS endpoints krakenWsHeartbeat = "heartbeat" krakenWsPing = "ping" @@ -39,18 +39,22 @@ const ( krakenWsTrade = "trade" krakenWsSpread = "spread" krakenWsOrderbook = "book" - - orderbookBufferLimit = 3 - krakenWsRateLimit = 50 + krakenWsOwnTrades = "ownTrades" + krakenWsOpenOrders = "openOrders" + krakenWsAddOrder = "addOrder" + krakenWsCancelOrder = "cancelOrder" + krakenWsRateLimit = 50 ) // orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time var subscriptionChannelPair []WebsocketChannelData -var subscribeToDefaultChannels = true +var comms = make(chan wshandler.WebsocketResponse) +var authToken string // Channels require a topic and a currency // Format [[ticker,but-t4u],[orderbook,nce-btt]] var defaultSubscribedChannels = []string{krakenWsTicker, krakenWsTrade, krakenWsOrderbook, krakenWsOHLC, krakenWsSpread} +var authenticatedChannels = []string{krakenWsOwnTrades, krakenWsOpenOrders} // WsConnect initiates a websocket connection func (k *Kraken) WsConnect() error { @@ -62,15 +66,86 @@ func (k *Kraken) WsConnect() error { if err != nil { return err } - go k.WsHandleData() - go k.wsPingHandler() - if subscribeToDefaultChannels { - k.GenerateDefaultSubscriptions() + if k.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + authToken, err = k.GetWebsocketToken() + if err != nil { + k.Websocket.SetCanUseAuthenticatedEndpoints(false) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", k.Name, err) + } + err = k.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) + if err != nil { + k.Websocket.SetCanUseAuthenticatedEndpoints(false) + log.Errorf(log.ExchangeSys, "%v - failed to connect to authenticated endpoint: %v\n", k.Name, err) + } + go k.WsReadData(k.AuthenticatedWebsocketConn) + k.GenerateAuthenticatedSubscriptions() } + go k.WsReadData(k.WebsocketConn) + go k.WsHandleData() + go k.wsPingHandler() + k.GenerateDefaultSubscriptions() + return nil } +// WsReadData funnels both auth and public ws data into one manageable place +func (k *Kraken) WsReadData(ws *wshandler.WebsocketConnection) { + k.Websocket.Wg.Add(1) + defer k.Websocket.Wg.Done() + for { + select { + case <-k.Websocket.ShutdownC: + return + default: + resp, err := ws.ReadMessage() + if err != nil { + k.Websocket.DataHandler <- err + return + } + k.Websocket.TrafficAlert <- struct{}{} + comms <- resp + } + } +} + +// WsHandleData handles the read data from the websocket connection +func (k *Kraken) WsHandleData() { + k.Websocket.Wg.Add(1) + defer func() { + k.Websocket.Wg.Done() + }() + + for { + select { + case <-k.Websocket.ShutdownC: + return + default: + resp := <-comms + // event response handling + var eventResponse WebsocketEventResponse + err := json.Unmarshal(resp.Raw, &eventResponse) + if err == nil && eventResponse.Event != "" { + k.WsHandleEventResponse(&eventResponse, resp.Raw) + continue + } + // Data response handling + var dataResponse WebsocketDataResponse + err = json.Unmarshal(resp.Raw, &dataResponse) + if err != nil { + log.Error(log.WebsocketMgr, fmt.Errorf("%s - unhandled websocket data: %v", k.Name, err)) + continue + } + if _, ok := dataResponse[0].(float64); ok { + k.WsHandleDataResponse(dataResponse) + } + if _, ok := dataResponse[1].(string); ok { + k.wsHandleAuthDataResponse(dataResponse) + } + } + } +} + // wsPingHandler sends a message "ping" every 27 to maintain the connection to the websocket func (k *Kraken) wsPingHandler() { k.Websocket.Wg.Add(1) @@ -96,82 +171,47 @@ func (k *Kraken) wsPingHandler() { } } -// WsHandleData handles the read data from the websocket connection -func (k *Kraken) WsHandleData() { - k.Websocket.Wg.Add(1) - defer func() { - k.Websocket.Wg.Done() - }() - - for { - select { - case <-k.Websocket.ShutdownC: - return - default: - resp, err := k.WebsocketConn.ReadMessage() - if err != nil { - k.Websocket.ReadMessageErrors <- err - return - } - k.Websocket.TrafficAlert <- struct{}{} - // event response handling - var eventResponse WebsocketEventResponse - err = json.Unmarshal(resp.Raw, &eventResponse) - if err == nil && eventResponse.Event != "" { - k.WsHandleEventResponse(&eventResponse, resp.Raw) - continue - } - // Data response handling - var dataResponse WebsocketDataResponse - err = json.Unmarshal(resp.Raw, &dataResponse) - if err == nil && dataResponse[0].(float64) >= 0 { - k.WsHandleDataResponse(dataResponse) - continue - } - continue - } - } -} - // WsHandleDataResponse classifies the WS response and sends to appropriate handler func (k *Kraken) WsHandleDataResponse(response WebsocketDataResponse) { - channelID := int64(response[0].(float64)) - channelData := getSubscriptionChannelData(channelID) - switch channelData.Subscription { - case krakenWsTicker: - if k.Verbose { - log.Debugf(log.ExchangeSys, "%v Websocket ticker data received", - k.Name) + if cID, ok := response[0].(float64); ok { + channelID := int64(cID) + channelData := getSubscriptionChannelData(channelID) + switch channelData.Subscription { + case krakenWsTicker: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket ticker data received", + k.Name) + } + k.wsProcessTickers(&channelData, response[1].(map[string]interface{})) + case krakenWsOHLC: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket OHLC data received", + k.Name) + } + k.wsProcessCandles(&channelData, response[1].([]interface{})) + case krakenWsOrderbook: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket Orderbook data received", + k.Name) + } + k.wsProcessOrderBook(&channelData, response[1].(map[string]interface{})) + case krakenWsSpread: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket Spread data received", + k.Name) + } + k.wsProcessSpread(&channelData, response[1].([]interface{})) + case krakenWsTrade: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket Trade data received", + k.Name) + } + k.wsProcessTrades(&channelData, response[1].([]interface{})) + default: + log.Errorf(log.ExchangeSys, "%v Unidentified websocket data received: %v", + k.Name, + response) } - k.wsProcessTickers(&channelData, response[1].(map[string]interface{})) - case krakenWsOHLC: - if k.Verbose { - log.Debugf(log.ExchangeSys, "%v Websocket OHLC data received", - k.Name) - } - k.wsProcessCandles(&channelData, response[1].([]interface{})) - case krakenWsOrderbook: - if k.Verbose { - log.Debugf(log.ExchangeSys, "%v Websocket Orderbook data received", - k.Name) - } - k.wsProcessOrderBook(&channelData, response[1].(map[string]interface{})) - case krakenWsSpread: - if k.Verbose { - log.Debugf(log.ExchangeSys, "%v Websocket Spread data received", - k.Name) - } - k.wsProcessSpread(&channelData, response[1].([]interface{})) - case krakenWsTrade: - if k.Verbose { - log.Debugf(log.ExchangeSys, "%v Websocket Trade data received", - k.Name) - } - k.wsProcessTrades(&channelData, response[1].([]interface{})) - default: - log.Errorf(log.ExchangeSys, "%v Unidentified websocket data received: %v", - k.Name, - response) } } @@ -197,7 +237,7 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse, rawResp k.Websocket.DataHandler <- fmt.Errorf("%v Websocket status '%v'", k.Name, response.Status) } - if response.WebsocketStatusResponse.Version != krakenWSSupportedVersion { + if response.WebsocketStatusResponse.Version > krakenWSSupportedVersion { log.Warnf(log.ExchangeSys, "%v New version of Websocket API released. Was %v Now %v", k.Name, krakenWSSupportedVersion, response.WebsocketStatusResponse.Version) } @@ -214,6 +254,192 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse, rawResp } } +func (k *Kraken) wsHandleAuthDataResponse(response WebsocketDataResponse) { + if chName, ok := response[1].(string); ok { + switch chName { + case krakenWsOwnTrades: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket auth own trade data received", + k.Name) + } + k.wsProcessOwnTrades(&response[0]) + case krakenWsOpenOrders: + if k.Verbose { + log.Debugf(log.ExchangeSys, "%v Websocket auth open order data received", + k.Name) + } + k.wsProcessOpenOrders(&response[0]) + } + } +} + +func (k *Kraken) wsProcessOwnTrades(ownOrders interface{}) { + if data, ok := ownOrders.([]interface{}); ok { + for i := range data { + ownTrade := data[i].(map[string]interface{}) + for _, val := range ownTrade { + tradeData := val.(map[string]interface{}) + cost, err := strconv.ParseFloat(tradeData["cost"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + fee, err := strconv.ParseFloat(tradeData["fee"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + margin, err := strconv.ParseFloat(tradeData["margin"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + vol, err := strconv.ParseFloat(tradeData["vol"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + price, err := strconv.ParseFloat(tradeData["price"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + timeTogether, err := strconv.ParseFloat(tradeData["time"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + first, second, err := convert.SplitFloatDecimals(timeTogether) + if err != nil { + k.Websocket.DataHandler <- err + } + k.Websocket.DataHandler <- WsOwnTrade{ + Cost: cost, + Fee: fee, + Margin: margin, + OrderTransactionID: tradeData["ordertxid"].(string), + OrderType: tradeData["ordertype"].(string), + Pair: tradeData["pair"].(string), + PostTransactionID: tradeData["postxid"].(string), + Price: price, + Time: time.Unix(first, second), + Type: tradeData["type"].(string), + Vol: vol, + } + } + } + } else { + k.Websocket.DataHandler <- errors.New(k.Name + " - Invalid own trades data") + } +} + +func (k *Kraken) wsProcessOpenOrders(ownOrders interface{}) { + if data, ok := ownOrders.([]interface{}); ok { + for i := range data { + ownTrade := data[i].(map[string]interface{}) + for key, val := range ownTrade { + tradeData := val.(map[string]interface{}) + if len(tradeData) == 1 { + // just a status update + if status, ok := tradeData["status"].(string); ok { + k.Websocket.DataHandler <- k.Name + " - Order " + key + " " + status + } + } + startTimeConv, err := strconv.ParseFloat(tradeData["starttm"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + startTime, startTimeNano, err := convert.SplitFloatDecimals(startTimeConv) + if err != nil { + k.Websocket.DataHandler <- err + } + openTimeConv, err := strconv.ParseFloat(tradeData["opentm"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + openTime, openTimeNano, err := convert.SplitFloatDecimals(openTimeConv) + if err != nil { + k.Websocket.DataHandler <- err + } + expireTimeConv, err := strconv.ParseFloat(tradeData["expiretm"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + expireTime, expireTimeNano, err := convert.SplitFloatDecimals(expireTimeConv) + if err != nil { + k.Websocket.DataHandler <- err + } + cost, err := strconv.ParseFloat(tradeData["cost"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + executedVolume, err := strconv.ParseFloat(tradeData["vol_exec"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + volume, err := strconv.ParseFloat(tradeData["vol"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + userReference, err := strconv.ParseFloat(tradeData["userref"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + stopPrice, err := strconv.ParseFloat(tradeData["stopprice"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + price, err := strconv.ParseFloat(tradeData["price"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + limitPrice, err := strconv.ParseFloat(tradeData["limitprice"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + fee, err := strconv.ParseFloat(tradeData["fee"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + descriptionSubData := tradeData["description"].(map[string]interface{}) + descriptionPrice, err := strconv.ParseFloat(descriptionSubData["price"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + descriptionPrice2, err := strconv.ParseFloat(descriptionSubData["price2"].(string), 64) + if err != nil { + k.Websocket.DataHandler <- err + } + description := WsOpenOrderDescription{ + Close: descriptionSubData["close"].(string), + Leverage: descriptionSubData["leverage"].(string), + Order: descriptionSubData["order"].(string), + OrderType: descriptionSubData["ordertype"].(string), + Pair: descriptionSubData["pair"].(string), + Price: descriptionPrice, + Price2: descriptionPrice2, + Type: descriptionSubData["type"].(string), + } + + k.Websocket.DataHandler <- WsOpenOrders{ + Cost: cost, + ExpireTime: time.Unix(expireTime, expireTimeNano), + Description: description, + Fee: fee, + LimitPrice: limitPrice, + Misc: tradeData["misc"].(string), + OFlags: tradeData["oflags"].(string), + OpenTime: time.Unix(openTime, openTimeNano), + Price: price, + RefID: tradeData["refid"].(string), + StartTime: time.Unix(startTime, startTimeNano), + Status: tradeData["status"].(string), + StopPrice: stopPrice, + UserReference: userReference, + Volume: volume, + ExecutedVolume: executedVolume, + } + } + } + } else { + k.Websocket.DataHandler <- errors.New(k.Name + " - Invalid own trades data") + } +} + // addNewSubscriptionChannelData stores channel ids, pairs and subscription types to an array // allowing correlation between subscriptions and returned data func addNewSubscriptionChannelData(response *WebsocketEventResponse) { @@ -623,22 +849,39 @@ func (k *Kraken) GenerateDefaultSubscriptions() { k.Websocket.SubscribeToChannels(subscriptions) } +// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() +func (k *Kraken) GenerateAuthenticatedSubscriptions() { + var subscriptions []wshandler.WebsocketChannelSubscription + for i := range authenticatedChannels { + params := make(map[string]interface{}) + subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + Channel: authenticatedChannels[i], + Params: params, + }) + } + k.Websocket.SubscribeToChannels(subscriptions) +} + // Subscribe sends a websocket message to receive data from the channel func (k *Kraken) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - var depth int64 - if channelToSubscribe.Channel == "book" { - depth = 1000 - } - resp := WebsocketSubscriptionEventRequest{ Event: krakenWsSubscribe, - Pairs: []string{channelToSubscribe.Currency.String()}, Subscription: WebsocketSubscriptionData{ - Name: channelToSubscribe.Channel, - Depth: depth, + Name: channelToSubscribe.Channel, }, RequestID: k.WebsocketConn.GenerateMessageID(false), } + if channelToSubscribe.Channel == "book" { + // TODO: Add ability to make depth customisable + resp.Subscription.Depth = 1000 + } + if !channelToSubscribe.Currency.IsEmpty() { + resp.Pairs = []string{channelToSubscribe.Currency.String()} + } + if channelToSubscribe.Params != nil { + resp.Subscription.Token = authToken + } + _, err := k.WebsocketConn.SendMessageReturnResponse(resp.RequestID, resp) return err } @@ -656,3 +899,32 @@ func (k *Kraken) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscr _, err := k.WebsocketConn.SendMessageReturnResponse(resp.RequestID, resp) return err } + +func (k *Kraken) wsAddOrder(request *WsAddOrderRequest) (string, error) { + id := k.AuthenticatedWebsocketConn.GenerateMessageID(false) + request.UserReferenceID = strconv.FormatInt(id, 10) + request.Event = krakenWsAddOrder + request.Token = authToken + jsonResp, err := k.AuthenticatedWebsocketConn.SendMessageReturnResponse(id, request) + if err != nil { + return "", err + } + var resp WsAddOrderResponse + err = json.Unmarshal(jsonResp, &resp) + if err != nil { + return "", err + } + if resp.ErrorMessage != "" { + return "", fmt.Errorf(k.Name + " - " + resp.ErrorMessage) + } + return resp.TransactionID, nil +} + +func (k *Kraken) wsCancelOrders(orderIDs []string) error { + request := WsCancelOrderRequest{ + Event: krakenWsCancelOrder, + Token: authToken, + TransactionIDs: orderIDs, + } + return k.AuthenticatedWebsocketConn.SendMessage(request) +} diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 2831e760..fa820996 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -9,6 +9,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -106,6 +107,9 @@ func (k *Kraken) SetDefaults() { Subscribe: true, Unsubscribe: true, MessageCorrelation: true, + SubmitOrder: true, + CancelOrder: true, + CancelOrders: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup | exchange.WithdrawCryptoWith2FA | @@ -171,6 +175,16 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) error { ResponseMaxLimit: exch.WebsocketResponseMaxLimit, } + k.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ + ExchangeName: k.Name, + URL: krakenAuthWSURL, + ProxyURL: k.Websocket.GetProxyAddress(), + Verbose: k.Verbose, + RateLimit: krakenWsRateLimit, + ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, + ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + } + k.Websocket.Orderbook.Setup( exch.WebsocketOrderbookBufferLimit, true, @@ -397,25 +411,47 @@ func (k *Kraken) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]ex // SubmitOrder submits a new order func (k *Kraken) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - if err := s.Validate(); err != nil { + err := s.Validate() + if err != nil { return submitOrderResponse, err } - response, err := k.AddOrder(s.Pair.String(), - s.OrderSide.String(), - s.OrderType.String(), - s.Amount, - s.Price, - 0, - 0, - &AddOrderOptions{}) - if len(response.TransactionIds) > 0 { - submitOrderResponse.OrderID = strings.Join(response.TransactionIds, ", ") - } - if err == nil { + if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var resp string + resp, err = k.wsAddOrder(&WsAddOrderRequest{ + OrderType: s.OrderType.String(), + OrderSide: s.OrderSide.String(), + Pair: s.Pair.String(), + Price: s.Price, + Volume: s.Amount, + }) + if err != nil { + return submitOrderResponse, err + } + submitOrderResponse.OrderID = resp submitOrderResponse.IsOrderPlaced = true + } else { + var response AddOrderResponse + response, err = k.AddOrder(s.Pair.String(), + s.OrderSide.String(), + s.OrderType.String(), + s.Amount, + s.Price, + 0, + 0, + &AddOrderOptions{}) + if err != nil { + return submitOrderResponse, err + } + if len(response.TransactionIds) > 0 { + submitOrderResponse.OrderID = strings.Join(response.TransactionIds, ", ") + } } - return submitOrderResponse, err + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true + } + submitOrderResponse.IsOrderPlaced = true + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -426,6 +462,9 @@ func (k *Kraken) ModifyOrder(action *order.Modify) (string, error) { // CancelOrder cancels an order by its corresponding ID number func (k *Kraken) CancelOrder(order *order.Cancel) error { + if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + return k.wsCancelOrders([]string{order.OrderID}) + } _, err := k.CancelExistingOrder(order.OrderID) return err @@ -436,26 +475,78 @@ func (k *Kraken) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, erro cancelAllOrdersResponse := order.CancelAllResponse{ Status: make(map[string]string), } + var emptyOrderOptions OrderInfoOptions openOrders, err := k.GetOpenOrders(emptyOrderOptions) if err != nil { return cancelAllOrdersResponse, err } - for orderID := range openOrders.Open { - _, err = k.CancelExistingOrder(orderID) + var err error + if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + err = k.wsCancelOrders([]string{orderID}) + } else { + _, err = k.CancelExistingOrder(orderID) + } if err != nil { cancelAllOrdersResponse.Status[orderID] = err.Error() } } - return cancelAllOrdersResponse, nil } // GetOrderInfo returns information on a current open order func (k *Kraken) GetOrderInfo(orderID string) (order.Detail, error) { var orderDetail order.Detail - return orderDetail, common.ErrNotYetImplemented + var emptyOrderOptions OrderInfoOptions + openOrders, err := k.GetOpenOrders(emptyOrderOptions) + if err != nil { + return orderDetail, err + } + if orderInfo, ok := openOrders.Open[orderID]; ok { + var trades []order.TradeHistory + for i := range orderInfo.Trades { + trades = append(trades, order.TradeHistory{ + TID: orderInfo.Trades[i], + }) + } + firstNum, decNum, err := convert.SplitFloatDecimals(orderInfo.StartTime) + if err != nil { + return orderDetail, err + } + side, err := order.StringToOrderSide(orderInfo.Description.Type) + if err != nil { + return orderDetail, err + } + status, err := order.StringToOrderStatus(orderInfo.Status) + if err != nil { + return orderDetail, err + } + oType, err := order.StringToOrderType(orderInfo.Description.OrderType) + if err != nil { + return orderDetail, err + } + + orderDetail = order.Detail{ + Exchange: k.Name, + ID: orderID, + CurrencyPair: currency.NewPairFromString(orderInfo.Description.Pair), + OrderSide: side, + OrderType: oType, + OrderDate: time.Unix(firstNum, decNum), + Status: status, + Price: orderInfo.Price, + Amount: orderInfo.Volume, + ExecutedAmount: orderInfo.VolumeExecuted, + RemainingAmount: orderInfo.Volume - orderInfo.VolumeExecuted, + Fee: orderInfo.Fee, + Trades: trades, + } + } else { + return orderDetail, errors.New(k.Name + " - Order ID not found: " + orderID) + } + + return orderDetail, nil } // GetDepositAddress returns a deposit address for a specified currency @@ -606,5 +697,9 @@ func (k *Kraken) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, e // AuthenticateWebsocket sends an authentication message to the websocket func (k *Kraken) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported + resp, err := k.GetWebsocketToken() + if resp != "" { + authToken = resp + } + return err } diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index 48b8e33c..fd2c87f1 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -3,6 +3,7 @@ package lakebtc import ( "fmt" "log" + "os" "testing" "github.com/thrasher-corp/gocryptotrader/common" @@ -16,7 +17,6 @@ import ( ) var l LakeBTC -var setupRan bool // Please add your own APIkeys to do correct due diligence testing. const ( @@ -25,34 +25,28 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { - if !setupRan { - l.SetDefaults() +func TestMain(m *testing.M) { + l.SetDefaults() + cfg := config.GetConfig() + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + log.Fatal("LakeBTC load config error", err) } -} + lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC") + if err != nil { + log.Fatal("LakeBTC Setup() init error") + } + lakebtcConfig.API.AuthenticatedSupport = true + lakebtcConfig.API.Credentials.Key = apiKey + lakebtcConfig.API.Credentials.Secret = apiSecret + lakebtcConfig.Features.Enabled.Websocket = true + err = l.Setup(lakebtcConfig) + if err != nil { + log.Fatal("LakeBTC setup error", err) + } + l.API.Endpoints.WebsocketURL = lakeBTCWSURL -func TestSetup(t *testing.T) { - if !setupRan { - cfg := config.GetConfig() - err := cfg.LoadConfig("../../testdata/configtest.json", true) - if err != nil { - log.Fatal("LakeBTC load config error", err) - } - lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC") - if err != nil { - t.Error("LakeBTC Setup() init error") - } - lakebtcConfig.API.AuthenticatedSupport = true - lakebtcConfig.API.Credentials.Key = apiKey - lakebtcConfig.API.Credentials.Secret = apiSecret - lakebtcConfig.Features.Enabled.Websocket = true - err = l.Setup(lakebtcConfig) - if err != nil { - t.Fatal("LakeBTC setup error", err) - } - l.API.Endpoints.WebsocketURL = lakeBTCWSURL - setupRan = true - } + os.Exit(m.Run()) } func TestFetchTradablePairs(t *testing.T) { @@ -171,7 +165,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() l.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -259,20 +253,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - l.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := l.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - l.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -286,9 +274,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - l.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, } @@ -308,9 +293,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - l.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -335,15 +317,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - l.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -361,15 +339,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - l.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -392,6 +366,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := l.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -399,8 +376,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - l.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -424,9 +399,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - l.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -439,9 +411,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - l.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -469,8 +438,6 @@ func TestGetDepositAddress(t *testing.T) { // TestWsConn websocket connection test func TestWsConn(t *testing.T) { - TestSetDefaults(t) - TestSetup(t) if !l.Websocket.IsEnabled() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -484,8 +451,6 @@ func TestWsConn(t *testing.T) { // TestWsTradeProcessing logic test func TestWsTradeProcessing(t *testing.T) { - TestSetDefaults(t) - TestSetup(t) l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() json := `{"trades":[{"type":"sell","date":1564985787,"price":"11913.02","amount":"0.49"}]}` @@ -497,8 +462,6 @@ func TestWsTradeProcessing(t *testing.T) { // TestWsTickerProcessing logic test func TestWsTickerProcessing(t *testing.T) { - TestSetDefaults(t) - TestSetup(t) const testChanSize = 26 l.Websocket.DataHandler = make(chan interface{}, testChanSize) l.Websocket.TrafficAlert = make(chan struct{}, testChanSize) @@ -519,8 +482,6 @@ func TestGetCurrencyFromChannel(t *testing.T) { // TestWsOrderbookProcessing logic test func TestWsOrderbookProcessing(t *testing.T) { - TestSetDefaults(t) - TestSetup(t) l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() json := `{"asks":[["11905.66","0.0019"],["11905.73","0.0015"],["11906.43","0.0013"],["11906.62","0.0019"],["11907.25","11.087"],["11907.66","0.0006"],["11907.73","0.3113"],["11907.84","0.0006"],["11908.37","0.0016"],["11908.86","10.3786"],["11909.54","4.2955"],["11910.15","0.0012"],["11910.56","13.5505"],["11911.06","0.0011"],["11911.37","0.0023"]],"bids":[["11905.55","0.0171"],["11904.43","0.0225"],["11903.31","0.0223"],["11902.2","0.0027"],["11901.92","1.002"],["11901.6","0.0015"],["11901.49","0.0012"],["11901.08","0.0227"],["11900.93","0.0009"],["11900.53","1.662"],["11900.08","0.001"],["11900.01","3.6745"],["11899.96","0.003"],["11899.91","0.0006"],["11899.44","0.0013"]]}` diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 3be7eb82..0e15e043 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -334,13 +334,18 @@ func (l *LakeBTC) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { isBuyOrder := s.OrderSide == order.Buy response, err := l.Trade(isBuyOrder, s.Amount, s.Price, s.Pair.Lower().String()) + if err != nil { + return submitOrderResponse, err + } if response.ID > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.ID, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true + + submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -369,8 +374,8 @@ func (l *LakeBTC) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, err } var ordersToCancel []string - for _, order := range openOrders { - ordersToCancel = append(ordersToCancel, strconv.FormatInt(order.ID, 10)) + for i := range openOrders { + ordersToCancel = append(ordersToCancel, strconv.FormatInt(openOrders[i].ID, 10)) } return cancelAllOrdersResponse, l.CancelExistingOrders(ordersToCancel) diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go index 05309537..4aa2c4a7 100644 --- a/exchanges/lbank/lbank_test.go +++ b/exchanges/lbank/lbank_test.go @@ -1,9 +1,9 @@ package lbank import ( - "fmt" "log" "os" + "strconv" "testing" "time" @@ -91,7 +91,7 @@ func TestGetMarketDepths(t *testing.T) { func TestGetTrades(t *testing.T) { t.Parallel() _, err := l.GetTrades(testCurrencyPair, "600", - fmt.Sprintf("%v", time.Now().Unix())) + strconv.FormatInt(time.Now().Unix(), 10)) if err != nil { t.Error(err) } @@ -104,7 +104,7 @@ func TestGetTrades(t *testing.T) { func TestGetKlines(t *testing.T) { t.Parallel() _, err := l.GetKlines(testCurrencyPair, "600", "minute1", - fmt.Sprintf("%v", time.Now().Unix())) + strconv.FormatInt(time.Now().Unix(), 10)) if err != nil { t.Error(err) } diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index e9fbd8b3..d5eab666 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -315,6 +315,9 @@ func (l *Lbank) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { } resp.IsOrderPlaced = true resp.OrderID = tempResp.OrderID + if s.OrderType == order.Market { + resp.FullyMatched = true + } return resp, nil } diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index dd37c207..4e97dc89 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -579,7 +579,7 @@ func (l *LocalBitcoins) WalletSend(address string, amount float64, pin int64) er path := localbitcoinsAPIWalletSend if pin > 0 { - values.Set("pincode", fmt.Sprintf("%v", pin)) + values.Set("pincode", strconv.FormatInt(pin, 10)) path = localbitcoinsAPIWalletSendPin } diff --git a/exchanges/localbitcoins/localbitcoins_test.go b/exchanges/localbitcoins/localbitcoins_test.go index 8962afe1..d1c6bc0e 100644 --- a/exchanges/localbitcoins/localbitcoins_test.go +++ b/exchanges/localbitcoins/localbitcoins_test.go @@ -96,7 +96,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { t.Parallel() var feeBuilder = setFeeBuilder() l.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -189,7 +189,6 @@ func TestFormatWithdrawPermissions(t *testing.T) { expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := l.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", @@ -275,7 +274,6 @@ func TestCancelExchangeOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -300,7 +298,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 8b7fa490..2d212f0e 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -2,7 +2,9 @@ package okcoin import ( "encoding/json" + "log" "net/http" + "os" "strings" "sync" "testing" @@ -34,43 +36,25 @@ var testSetupRan bool var spotCurrency = currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-").Lower().String() var websocketEnabled bool -// TestSetDefaults Sets standard default settings for running a test -func TestSetDefaults(t *testing.T) { - if o.Name != OKGroupExchange { - o.SetDefaults() - } - if o.Name != OKGroupExchange { - t.Errorf("%v - SetDefaults() error", OKGroupExchange) - } - TestSetup(t) -} - // TestSetRealOrderDefaults Sets test defaults when test can impact real money/orders func TestSetRealOrderDefaults(t *testing.T) { - TestSetDefaults(t) if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("Ensure canManipulateRealOrders is true and your API keys are set") } } // TestSetup Sets defaults for test environment -func TestSetup(t *testing.T) { - if testSetupRan { - return - } - if o.API.Credentials.Key == apiKey && o.API.Credentials.Secret == apiSecret && - o.API.Credentials.ClientID == passphrase && apiKey != "" && apiSecret != "" && passphrase != "" { - return - } +func TestMain(m *testing.M) { + o.SetDefaults() o.ExchangeName = OKGroupExchange cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Okcoin load config error", err) + log.Fatal("Okcoin load config error", err) } okcoinConfig, err := cfg.GetExchangeConfig(OKGroupExchange) if err != nil { - t.Fatalf("%v Setup() init error", OKGroupExchange) + log.Fatalf("%v Setup() init error", OKGroupExchange) } if okcoinConfig.Features.Enabled.Websocket { websocketEnabled = true @@ -84,11 +68,13 @@ func TestSetup(t *testing.T) { okcoinConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL err = o.Setup(okcoinConfig) if err != nil { - t.Fatal("OKCoin setup error", err) + log.Fatal("OKCoin setup error", err) } testSetupRan = true o.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() o.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() + + os.Exit(m.Run()) } func areTestAPIKeysSet() bool { @@ -106,14 +92,12 @@ func testStandardErrorHandling(t *testing.T, err error) { // TestGetAccountCurrencies API endpoint test func TestGetAccountCurrencies(t *testing.T) { - TestSetDefaults(t) _, err := o.GetAccountCurrencies() testStandardErrorHandling(t, err) } // TestGetAccountWalletInformation API endpoint test func TestGetAccountWalletInformation(t *testing.T) { - TestSetDefaults(t) resp, err := o.GetAccountWalletInformation("") if areTestAPIKeysSet() { if err != nil { @@ -129,7 +113,6 @@ func TestGetAccountWalletInformation(t *testing.T) { // TestGetAccountWalletInformationForCurrency API endpoint test func TestGetAccountWalletInformationForCurrency(t *testing.T) { - TestSetDefaults(t) resp, err := o.GetAccountWalletInformation(currency.BTC.String()) if areTestAPIKeysSet() { if err != nil { @@ -173,7 +156,6 @@ func TestAccountWithdrawRequest(t *testing.T) { // TestGetAccountWithdrawalFee API endpoint test func TestGetAccountWithdrawalFee(t *testing.T) { - TestSetDefaults(t) resp, err := o.GetAccountWithdrawalFee("") if areTestAPIKeysSet() { if err != nil { @@ -189,7 +171,6 @@ func TestGetAccountWithdrawalFee(t *testing.T) { // TestGetWithdrawalFeeForCurrency API endpoint test func TestGetAccountWithdrawalFeeForCurrency(t *testing.T) { - TestSetDefaults(t) resp, err := o.GetAccountWithdrawalFee(currency.BTC.String()) if areTestAPIKeysSet() { if err != nil { @@ -205,63 +186,54 @@ func TestGetAccountWithdrawalFeeForCurrency(t *testing.T) { // TestGetAccountWithdrawalHistory API endpoint test func TestGetAccountWithdrawalHistory(t *testing.T) { - TestSetDefaults(t) _, err := o.GetAccountWithdrawalHistory("") testStandardErrorHandling(t, err) } // TestGetAccountWithdrawalHistoryForCurrency API endpoint test func TestGetAccountWithdrawalHistoryForCurrency(t *testing.T) { - TestSetDefaults(t) _, err := o.GetAccountWithdrawalHistory(currency.BTC.String()) testStandardErrorHandling(t, err) } // TestGetAccountBillDetails API endpoint test func TestGetAccountBillDetails(t *testing.T) { - TestSetDefaults(t) _, err := o.GetAccountBillDetails(okgroup.GetAccountBillDetailsRequest{}) testStandardErrorHandling(t, err) } // TestGetAccountDepositAddressForCurrency API endpoint test func TestGetAccountDepositAddressForCurrency(t *testing.T) { - TestSetDefaults(t) _, err := o.GetAccountDepositAddressForCurrency(currency.BTC.String()) testStandardErrorHandling(t, err) } // TestGetAccountDepositHistory API endpoint test func TestGetAccountDepositHistory(t *testing.T) { - TestSetDefaults(t) _, err := o.GetAccountDepositHistory("") testStandardErrorHandling(t, err) } // TestGetAccountDepositHistoryForCurrency API endpoint test func TestGetAccountDepositHistoryForCurrency(t *testing.T) { - TestSetDefaults(t) _, err := o.GetAccountDepositHistory(currency.BTC.String()) testStandardErrorHandling(t, err) } // TestGetSpotTradingAccounts API endpoint test func TestGetSpotTradingAccounts(t *testing.T) { - TestSetDefaults(t) _, err := o.GetSpotTradingAccounts() testStandardErrorHandling(t, err) } // TestGetSpotTradingAccountsForCurrency API endpoint test func TestGetSpotTradingAccountsForCurrency(t *testing.T) { - TestSetDefaults(t) _, err := o.GetSpotTradingAccountForCurrency(currency.BTC.String()) testStandardErrorHandling(t, err) } // TestGetSpotBillDetailsForCurrency API endpoint test func TestGetSpotBillDetailsForCurrency(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: currency.BTC.String(), Limit: 100, @@ -272,7 +244,6 @@ func TestGetSpotBillDetailsForCurrency(t *testing.T) { // TestGetSpotBillDetailsForCurrencyBadLimit API logic test func TestGetSpotBillDetailsForCurrencyBadLimit(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: currency.BTC.String(), Limit: -1, @@ -316,7 +287,7 @@ func TestPlaceSpotOrderMarket(t *testing.T) { // TestPlaceMultipleSpotOrders API endpoint test func TestPlaceMultipleSpotOrders(t *testing.T) { TestSetRealOrderDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -325,7 +296,7 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } _, errs := o.PlaceMultipleSpotOrders(request) @@ -336,8 +307,7 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { // TestPlaceMultipleSpotOrdersOverCurrencyLimits API logic test func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { - TestSetDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -346,11 +316,11 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, - order, - order, - order, - order, + ord, + ord, + ord, + ord, + ord, } _, errs := o.PlaceMultipleSpotOrders(request) @@ -361,8 +331,7 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { // TestPlaceMultipleSpotOrdersOverPairLimits API logic test func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { - TestSetDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -371,7 +340,7 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } pairs := currency.Pairs{ @@ -382,8 +351,8 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { } for x := range pairs { - order.InstrumentID = pairs[x].Format("-", false).String() - request = append(request, order) + ord.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, ord) } _, errs := o.PlaceMultipleSpotOrders(request) @@ -439,7 +408,6 @@ func TestCancelMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { // TestGetSpotOrders API endpoint test func TestGetSpotOrders(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOrdersRequest{ InstrumentID: spotCurrency, Status: "all", @@ -450,7 +418,6 @@ func TestGetSpotOrders(t *testing.T) { // TestGetSpotOpenOrders API endpoint test func TestGetSpotOpenOrders(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOpenOrdersRequest{} _, err := o.GetSpotOpenOrders(request) testStandardErrorHandling(t, err) @@ -458,7 +425,6 @@ func TestGetSpotOpenOrders(t *testing.T) { // TestGetSpotOrder API endpoint test func TestGetSpotOrder(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOrderRequest{ OrderID: "-1234", InstrumentID: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-").Upper().String(), @@ -469,7 +435,6 @@ func TestGetSpotOrder(t *testing.T) { // TestGetSpotTransactionDetails API endpoint test func TestGetSpotTransactionDetails(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotTransactionDetailsRequest{ OrderID: 1234, InstrumentID: spotCurrency, @@ -480,7 +445,6 @@ func TestGetSpotTransactionDetails(t *testing.T) { // TestGetSpotTokenPairDetails API endpoint test func TestGetSpotTokenPairDetails(t *testing.T) { - TestSetDefaults(t) _, err := o.GetSpotTokenPairDetails() if err != nil { t.Error(err) @@ -489,7 +453,6 @@ func TestGetSpotTokenPairDetails(t *testing.T) { // TestGetSpotAllTokenPairsInformation API endpoint test func TestGetSpotAllTokenPairsInformation(t *testing.T) { - TestSetDefaults(t) _, err := o.GetSpotAllTokenPairsInformation() if err != nil { t.Error(err) @@ -498,7 +461,6 @@ func TestGetSpotAllTokenPairsInformation(t *testing.T) { // TestGetSpotAllTokenPairsInformationForCurrency API endpoint test func TestGetSpotAllTokenPairsInformationForCurrency(t *testing.T) { - TestSetDefaults(t) _, err := o.GetSpotAllTokenPairsInformationForCurrency(spotCurrency) if err != nil { t.Error(err) @@ -507,7 +469,6 @@ func TestGetSpotAllTokenPairsInformationForCurrency(t *testing.T) { // TestGetSpotFilledOrdersInformation API endpoint test func TestGetSpotFilledOrdersInformation(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotFilledOrdersInformationRequest{ InstrumentID: spotCurrency, } @@ -519,7 +480,6 @@ func TestGetSpotFilledOrdersInformation(t *testing.T) { // TestGetSpotMarketData API endpoint test func TestGetSpotMarketData(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotMarketDataRequest{ InstrumentID: spotCurrency, Granularity: 604800, @@ -532,21 +492,18 @@ func TestGetSpotMarketData(t *testing.T) { // TestGetMarginTradingAccounts API endpoint test func TestGetMarginTradingAccounts(t *testing.T) { - TestSetDefaults(t) _, err := o.GetMarginTradingAccounts() testStandardErrorHandling(t, err) } // TestGetMarginTradingAccountsForCurrency API endpoint test func TestGetMarginTradingAccountsForCurrency(t *testing.T) { - TestSetDefaults(t) _, err := o.GetMarginTradingAccountsForCurrency(spotCurrency) testStandardErrorHandling(t, err) } // TestGetMarginBillDetails API endpoint test func TestGetMarginBillDetails(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetMarginBillDetailsRequest{ InstrumentID: spotCurrency, Limit: 100, @@ -557,14 +514,12 @@ func TestGetMarginBillDetails(t *testing.T) { // TestGetMarginAccountSettings API endpoint test func TestGetMarginAccountSettings(t *testing.T) { - TestSetDefaults(t) _, err := o.GetMarginAccountSettings("") testStandardErrorHandling(t, err) } // TestGetMarginAccountSettingsForCurrency API endpoint test func TestGetMarginAccountSettingsForCurrency(t *testing.T) { - TestSetDefaults(t) _, err := o.GetMarginAccountSettings(spotCurrency) testStandardErrorHandling(t, err) } @@ -631,7 +586,7 @@ func TestPlaceMarginOrderMarket(t *testing.T) { // TestPlaceMultipleMarginOrders API endpoint test func TestPlaceMultipleMarginOrders(t *testing.T) { TestSetRealOrderDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -641,7 +596,7 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } _, errs := o.PlaceMultipleMarginOrders(request) @@ -652,8 +607,7 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { // TestPlaceMultipleMarginOrdersOverCurrencyLimits API logic test func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { - TestSetDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -663,11 +617,11 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, - order, - order, - order, - order, + ord, + ord, + ord, + ord, + ord, } _, errs := o.PlaceMultipleMarginOrders(request) @@ -678,8 +632,7 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { // TestPlaceMultipleMarginOrdersOverPairLimits API logic test func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { - TestSetDefaults(t) - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -689,7 +642,7 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } pairs := currency.Pairs{ @@ -700,8 +653,8 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { } for x := range pairs { - order.InstrumentID = pairs[x].Format("-", false).String() - request = append(request, order) + ord.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, ord) } _, errs := o.PlaceMultipleMarginOrders(request) @@ -752,7 +705,6 @@ func TestCancelMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { // TestGetMarginOrders API endpoint test func TestGetMarginOrders(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOrdersRequest{ InstrumentID: spotCurrency, Status: "all", @@ -763,7 +715,6 @@ func TestGetMarginOrders(t *testing.T) { // TestGetMarginOpenOrders API endpoint test func TestGetMarginOpenOrders(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOpenOrdersRequest{} _, err := o.GetMarginOpenOrders(request) testStandardErrorHandling(t, err) @@ -771,7 +722,6 @@ func TestGetMarginOpenOrders(t *testing.T) { // TestGetMarginOrder API endpoint test func TestGetMarginOrder(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotOrderRequest{ OrderID: "1234", InstrumentID: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-").Upper().String(), @@ -782,7 +732,6 @@ func TestGetMarginOrder(t *testing.T) { // TestGetMarginTransactionDetails API endpoint test func TestGetMarginTransactionDetails(t *testing.T) { - TestSetDefaults(t) request := okgroup.GetSpotTransactionDetailsRequest{ OrderID: 1234, InstrumentID: spotCurrency, @@ -797,7 +746,6 @@ func TestGetMarginTransactionDetails(t *testing.T) { // Attempts to subscribe to a channel that doesn't exist // Will log in if credentials are present func TestSendWsMessages(t *testing.T) { - TestSetDefaults(t) if !o.Websocket.IsEnabled() && !o.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -850,7 +798,6 @@ func TestGetAssetTypeFromTableName(t *testing.T) { // TestGetWsChannelWithoutOrderType logic test func TestGetWsChannelWithoutOrderType(t *testing.T) { - TestSetDefaults(t) str := "spot/depth5:BTC-USDT" expected := "depth5" resp := o.GetWsChannelWithoutOrderType(str) @@ -872,7 +819,6 @@ func TestGetWsChannelWithoutOrderType(t *testing.T) { // TestOrderBookUpdateChecksumCalculator logic test func TestOrderBookUpdateChecksumCalculator(t *testing.T) { - TestSetDefaults(t) if !websocketEnabled { t.Skip("Websocket not enabled, skipping") } @@ -902,7 +848,6 @@ func TestOrderBookUpdateChecksumCalculator(t *testing.T) { // TestOrderBookUpdateChecksumCalculatorWithDash logic test func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { - TestSetDefaults(t) if !websocketEnabled { t.Skip("Websocket not enabled, skipping") } @@ -964,7 +909,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() o.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -976,7 +921,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - TestSetDefaults(t) var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic if resp, err := o.GetFee(feeBuilder); resp != float64(0.0015) || err != nil { @@ -1031,7 +975,6 @@ func TestGetFee(t *testing.T) { // TestFormatWithdrawPermissions helper test func TestFormatWithdrawPermissions(t *testing.T) { - TestSetDefaults(t) expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText withdrawPermissions := o.FormatWithdrawPermissions() if withdrawPermissions != expectedResult { @@ -1149,3 +1092,25 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) } } + +// TestGetOrderbook logic test +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: "BTC-USDT"}, + asset.Spot) + if err != nil { + t.Error(err) + } + + _, err = o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: "Payload"}, + asset.Futures) + if err == nil { + t.Error("error cannot be nil") + } + + _, err = o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, + asset.PerpetualSwap) + if err == nil { + t.Error("error cannot be nil") + } +} diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index fa9d6e2a..70ae6246 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -3,7 +3,9 @@ package okex import ( "encoding/json" "fmt" + "log" "net/http" + "os" "strconv" "strings" "sync" @@ -31,49 +33,30 @@ const ( canManipulateRealOrders = false ) -var testSetupRan bool -var o = OKEX{} +var o OKEX var spotCurrency = currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "-").Lower().String() var websocketEnabled bool -// TestSetDefaults Sets standard default settings for running a test -func TestSetDefaults(t *testing.T) { - if o.Name != OKGroupExchange { - o.SetDefaults() - } - if o.Name != OKGroupExchange { - t.Errorf("%v - SetDefaults() error", OKGroupExchange) - } - TestSetup(t) -} - // TestSetRealOrderDefaults Sets test defaults when test can impact real money/orders func TestSetRealOrderDefaults(t *testing.T) { - TestSetDefaults(t) if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("Ensure canManipulateRealOrders is true and your API keys are set") } } // TestSetup Sets defaults for test environment -func TestSetup(t *testing.T) { - if testSetupRan { - return - } - if o.API.Credentials.Key == apiKey && o.API.Credentials.Secret == apiSecret && - o.API.Credentials.ClientID == passphrase && apiKey != "" && apiSecret != "" && passphrase != "" { - return - } +func TestMain(m *testing.M) { + o.SetDefaults() o.ExchangeName = OKGroupExchange cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Okex load config error", err) + log.Fatal("Okex load config error", err) } okexConfig, err := cfg.GetExchangeConfig(OKGroupExchange) if err != nil { - t.Fatalf("%v Setup() init error", OKGroupExchange) + log.Fatalf("%v Setup() init error", OKGroupExchange) } if okexConfig.Features.Enabled.Websocket { websocketEnabled = true @@ -86,11 +69,12 @@ func TestSetup(t *testing.T) { okexConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL err = o.Setup(okexConfig) if err != nil { - t.Fatal("Okex setup error", err) + log.Fatal("Okex setup error", err) } - testSetupRan = true o.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() o.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() + + os.Exit(m.Run()) } func areTestAPIKeysSet() bool { @@ -108,7 +92,6 @@ func testStandardErrorHandling(t *testing.T, err error) { // TestGetAccountCurrencies API endpoint test func TestGetAccountCurrencies(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountCurrencies() testStandardErrorHandling(t, err) @@ -116,7 +99,6 @@ func TestGetAccountCurrencies(t *testing.T) { // TestGetAccountWalletInformation API endpoint test func TestGetAccountWalletInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() resp, err := o.GetAccountWalletInformation("") if areTestAPIKeysSet() { @@ -133,7 +115,6 @@ func TestGetAccountWalletInformation(t *testing.T) { // TestGetAccountWalletInformationForCurrency API endpoint test func TestGetAccountWalletInformationForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() resp, err := o.GetAccountWalletInformation(currency.BTC.String()) if areTestAPIKeysSet() { @@ -182,7 +163,6 @@ func TestAccountWithdrawRequest(t *testing.T) { // TestGetAccountWithdrawalFee API endpoint test func TestGetAccountWithdrawalFee(t *testing.T) { - TestSetDefaults(t) t.Parallel() resp, err := o.GetAccountWithdrawalFee("") if areTestAPIKeysSet() { @@ -199,7 +179,6 @@ func TestGetAccountWithdrawalFee(t *testing.T) { // TestGetWithdrawalFeeForCurrency API endpoint test func TestGetAccountWithdrawalFeeForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() resp, err := o.GetAccountWithdrawalFee(currency.BTC.String()) if areTestAPIKeysSet() { @@ -216,7 +195,6 @@ func TestGetAccountWithdrawalFeeForCurrency(t *testing.T) { // TestGetAccountWithdrawalHistory API endpoint test func TestGetAccountWithdrawalHistory(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountWithdrawalHistory("") testStandardErrorHandling(t, err) @@ -224,7 +202,6 @@ func TestGetAccountWithdrawalHistory(t *testing.T) { // TestGetAccountWithdrawalHistoryForCurrency API endpoint test func TestGetAccountWithdrawalHistoryForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountWithdrawalHistory(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -232,7 +209,6 @@ func TestGetAccountWithdrawalHistoryForCurrency(t *testing.T) { // TestGetAccountBillDetails API endpoint test func TestGetAccountBillDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountBillDetails(okgroup.GetAccountBillDetailsRequest{}) testStandardErrorHandling(t, err) @@ -240,7 +216,6 @@ func TestGetAccountBillDetails(t *testing.T) { // TestGetAccountDepositAddressForCurrency API endpoint test func TestGetAccountDepositAddressForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountDepositAddressForCurrency(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -248,7 +223,6 @@ func TestGetAccountDepositAddressForCurrency(t *testing.T) { // TestGetAccountDepositHistory API endpoint test func TestGetAccountDepositHistory(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountDepositHistory("") testStandardErrorHandling(t, err) @@ -256,7 +230,6 @@ func TestGetAccountDepositHistory(t *testing.T) { // TestGetAccountDepositHistoryForCurrency API endpoint test func TestGetAccountDepositHistoryForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAccountDepositHistory(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -264,7 +237,6 @@ func TestGetAccountDepositHistoryForCurrency(t *testing.T) { // TestGetSpotTradingAccounts API endpoint test func TestGetSpotTradingAccounts(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSpotTradingAccounts() testStandardErrorHandling(t, err) @@ -272,7 +244,6 @@ func TestGetSpotTradingAccounts(t *testing.T) { // TestGetSpotTradingAccountsForCurrency API endpoint test func TestGetSpotTradingAccountsForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSpotTradingAccountForCurrency(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -280,7 +251,6 @@ func TestGetSpotTradingAccountsForCurrency(t *testing.T) { // TestGetSpotBillDetailsForCurrency API endpoint test func TestGetSpotBillDetailsForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: currency.BTC.String(), @@ -292,7 +262,6 @@ func TestGetSpotBillDetailsForCurrency(t *testing.T) { // TestGetSpotBillDetailsForCurrencyBadLimit API logic test func TestGetSpotBillDetailsForCurrencyBadLimit(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: currency.BTC.String(), @@ -340,7 +309,7 @@ func TestPlaceSpotOrderMarket(t *testing.T) { func TestPlaceMultipleSpotOrders(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -349,7 +318,7 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } _, errs := o.PlaceMultipleSpotOrders(request) @@ -360,9 +329,8 @@ func TestPlaceMultipleSpotOrders(t *testing.T) { // TestPlaceMultipleSpotOrdersOverCurrencyLimits API logic test func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { - TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -371,11 +339,11 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, - order, - order, - order, - order, + ord, + ord, + ord, + ord, + ord, } _, errs := o.PlaceMultipleSpotOrders(request) @@ -386,9 +354,8 @@ func TestPlaceMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { // TestPlaceMultipleSpotOrdersOverPairLimits API logic test func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { - TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -397,7 +364,7 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } pairs := currency.Pairs{ @@ -408,8 +375,8 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) { } for x := range pairs { - order.InstrumentID = pairs[x].Format("-", false).String() - request = append(request, order) + ord.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, ord) } _, errs := o.PlaceMultipleSpotOrders(request) @@ -468,7 +435,6 @@ func TestCancelMultipleSpotOrdersOverCurrencyLimits(t *testing.T) { // TestGetSpotOrders API endpoint test func TestGetSpotOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOrdersRequest{ InstrumentID: spotCurrency, @@ -481,7 +447,6 @@ func TestGetSpotOrders(t *testing.T) { // TestGetSpotOpenOrders API endpoint test func TestGetSpotOpenOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOpenOrdersRequest{} _, err := o.GetSpotOpenOrders(request) @@ -490,7 +455,6 @@ func TestGetSpotOpenOrders(t *testing.T) { // TestGetSpotOrder API endpoint test func TestGetSpotOrder(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOrderRequest{ OrderID: "-1234", @@ -502,7 +466,6 @@ func TestGetSpotOrder(t *testing.T) { // TestGetSpotTransactionDetails API endpoint test func TestGetSpotTransactionDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotTransactionDetailsRequest{ OrderID: 1234, @@ -514,7 +477,6 @@ func TestGetSpotTransactionDetails(t *testing.T) { // TestGetSpotTokenPairDetails API endpoint test func TestGetSpotTokenPairDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSpotTokenPairDetails() if err != nil { @@ -524,7 +486,6 @@ func TestGetSpotTokenPairDetails(t *testing.T) { // TestGetSpotAllTokenPairsInformation API endpoint test func TestGetSpotAllTokenPairsInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSpotAllTokenPairsInformation() if err != nil { @@ -534,7 +495,6 @@ func TestGetSpotAllTokenPairsInformation(t *testing.T) { // TestGetSpotAllTokenPairsInformationForCurrency API endpoint test func TestGetSpotAllTokenPairsInformationForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSpotAllTokenPairsInformationForCurrency(spotCurrency) if err != nil { @@ -544,7 +504,6 @@ func TestGetSpotAllTokenPairsInformationForCurrency(t *testing.T) { // TestGetSpotFilledOrdersInformation API endpoint test func TestGetSpotFilledOrdersInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotFilledOrdersInformationRequest{ InstrumentID: spotCurrency, @@ -557,7 +516,6 @@ func TestGetSpotFilledOrdersInformation(t *testing.T) { // TestGetSpotMarketData API endpoint test func TestGetSpotMarketData(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotMarketDataRequest{ InstrumentID: spotCurrency, @@ -571,7 +529,6 @@ func TestGetSpotMarketData(t *testing.T) { // TestGetMarginTradingAccounts API endpoint test func TestGetMarginTradingAccounts(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetMarginTradingAccounts() testStandardErrorHandling(t, err) @@ -579,7 +536,6 @@ func TestGetMarginTradingAccounts(t *testing.T) { // TestGetMarginTradingAccountsForCurrency API endpoint test func TestGetMarginTradingAccountsForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetMarginTradingAccountsForCurrency(spotCurrency) testStandardErrorHandling(t, err) @@ -587,7 +543,6 @@ func TestGetMarginTradingAccountsForCurrency(t *testing.T) { // TestGetMarginBillDetails API endpoint test func TestGetMarginBillDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetMarginBillDetailsRequest{ InstrumentID: spotCurrency, @@ -600,7 +555,6 @@ func TestGetMarginBillDetails(t *testing.T) { // TestGetMarginAccountSettings API endpoint test func TestGetMarginAccountSettings(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetMarginAccountSettings("") testStandardErrorHandling(t, err) @@ -608,7 +562,6 @@ func TestGetMarginAccountSettings(t *testing.T) { // TestGetMarginAccountSettingsForCurrency API endpoint test func TestGetMarginAccountSettingsForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetMarginAccountSettings(spotCurrency) testStandardErrorHandling(t, err) @@ -681,7 +634,7 @@ func TestPlaceMarginOrderMarket(t *testing.T) { func TestPlaceMultipleMarginOrders(t *testing.T) { TestSetRealOrderDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -691,7 +644,7 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } _, errs := o.PlaceMultipleMarginOrders(request) @@ -702,9 +655,8 @@ func TestPlaceMultipleMarginOrders(t *testing.T) { // TestPlaceMultipleMarginOrdersOverCurrencyLimits API logic test func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { - TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -714,11 +666,11 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, - order, - order, - order, - order, + ord, + ord, + ord, + ord, + ord, } _, errs := o.PlaceMultipleMarginOrders(request) @@ -729,9 +681,8 @@ func TestPlaceMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { // TestPlaceMultipleMarginOrdersOverPairLimits API logic test func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { - TestSetDefaults(t) t.Parallel() - order := okgroup.PlaceOrderRequest{ + ord := okgroup.PlaceOrderRequest{ InstrumentID: spotCurrency, Type: order.Limit.Lower(), Side: order.Buy.Lower(), @@ -741,7 +692,7 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { } request := []okgroup.PlaceOrderRequest{ - order, + ord, } pairs := currency.Pairs{ @@ -752,8 +703,8 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) { } for x := range pairs { - order.InstrumentID = pairs[x].Format("-", false).String() - request = append(request, order) + ord.InstrumentID = pairs[x].Format("-", false).String() + request = append(request, ord) } _, errs := o.PlaceMultipleMarginOrders(request) @@ -807,7 +758,6 @@ func TestCancelMultipleMarginOrdersOverCurrencyLimits(t *testing.T) { // TestGetMarginOrders API endpoint test func TestGetMarginOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOrdersRequest{ InstrumentID: spotCurrency, @@ -819,7 +769,6 @@ func TestGetMarginOrders(t *testing.T) { // TestGetMarginOpenOrders API endpoint test func TestGetMarginOpenOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOpenOrdersRequest{} _, err := o.GetMarginOpenOrders(request) @@ -828,7 +777,6 @@ func TestGetMarginOpenOrders(t *testing.T) { // TestGetMarginOrder API endpoint test func TestGetMarginOrder(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotOrderRequest{ OrderID: "1234", @@ -840,7 +788,6 @@ func TestGetMarginOrder(t *testing.T) { // TestGetMarginTransactionDetails API endpoint test func TestGetMarginTransactionDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSpotTransactionDetailsRequest{ OrderID: 1234, @@ -869,7 +816,6 @@ func getFutureInstrumentID() string { // TestGetFuturesPostions API endpoint test func TestGetFuturesPostions(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesPostions() testStandardErrorHandling(t, err) @@ -877,7 +823,6 @@ func TestGetFuturesPostions(t *testing.T) { // TestGetFuturesPostionsForCurrency API endpoint test func TestGetFuturesPostionsForCurrency(t *testing.T) { - TestSetDefaults(t) currencyContract := getFutureInstrumentID() _, err := o.GetFuturesPostionsForCurrency(currencyContract) testStandardErrorHandling(t, err) @@ -885,7 +830,6 @@ func TestGetFuturesPostionsForCurrency(t *testing.T) { // TestGetFuturesAccountOfAllCurrencies API endpoint test func TestGetFuturesAccountOfAllCurrencies(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesAccountOfAllCurrencies() testStandardErrorHandling(t, err) @@ -893,7 +837,6 @@ func TestGetFuturesAccountOfAllCurrencies(t *testing.T) { // TestGetFuturesAccountOfACurrency API endpoint test func TestGetFuturesAccountOfACurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesAccountOfACurrency(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -901,7 +844,6 @@ func TestGetFuturesAccountOfACurrency(t *testing.T) { // TestGetFuturesLeverage API endpoint test func TestGetFuturesLeverage(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesLeverage(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -922,7 +864,6 @@ func TestSetFuturesLeverage(t *testing.T) { // TestGetFuturesBillDetails API endpoint test func TestGetFuturesBillDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesBillDetails(okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: currency.BTC.String(), @@ -987,7 +928,6 @@ func TestCancelMultipleFuturesOrders(t *testing.T) { // TestGetFuturesOrderList API endpoint test func TestGetFuturesOrderList(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesOrderList(okgroup.GetFuturesOrdersListRequest{ InstrumentID: getFutureInstrumentID(), Status: 6, @@ -997,7 +937,6 @@ func TestGetFuturesOrderList(t *testing.T) { // TestGetFuturesOrderDetails API endpoint test func TestGetFuturesOrderDetails(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesOrderDetails(okgroup.GetFuturesOrderDetailsRequest{ InstrumentID: getFutureInstrumentID(), OrderID: 1, @@ -1007,7 +946,6 @@ func TestGetFuturesOrderDetails(t *testing.T) { // TestGetFuturesTransactionDetails API endpoint test func TestGetFuturesTransactionDetails(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesTransactionDetails(okgroup.GetFuturesTransactionDetailsRequest{ InstrumentID: getFutureInstrumentID(), OrderID: 1, @@ -1017,7 +955,6 @@ func TestGetFuturesTransactionDetails(t *testing.T) { // TestGetFuturesContractInformation API endpoint test func TestGetFuturesContractInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesContractInformation() if err != nil { @@ -1027,7 +964,6 @@ func TestGetFuturesContractInformation(t *testing.T) { // TestGetAllFuturesTokenInfo API endpoint test func TestGetAllFuturesTokenInfo(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAllFuturesTokenInfo() if err != nil { @@ -1037,7 +973,6 @@ func TestGetAllFuturesTokenInfo(t *testing.T) { // TestGetAllFuturesTokenInfo API endpoint test func TestGetFuturesTokenInfoForCurrency(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesTokenInfoForCurrency(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1046,7 +981,6 @@ func TestGetFuturesTokenInfoForCurrency(t *testing.T) { // TestGetFuturesFilledOrder API endpoint test func TestGetFuturesFilledOrder(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesFilledOrder(okgroup.GetFuturesFilledOrderRequest{ InstrumentID: getFutureInstrumentID(), }) @@ -1055,14 +989,12 @@ func TestGetFuturesFilledOrder(t *testing.T) { // TestGetFuturesHoldAmount API endpoint test func TestGetFuturesHoldAmount(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesHoldAmount(getFutureInstrumentID()) testStandardErrorHandling(t, err) } // TestGetFuturesHoldAmount API endpoint test func TestGetFuturesIndices(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesIndices(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1071,7 +1003,6 @@ func TestGetFuturesIndices(t *testing.T) { // TestGetFuturesHoldAmount API endpoint test func TestGetFuturesExchangeRates(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetFuturesExchangeRates() if err != nil { @@ -1081,7 +1012,6 @@ func TestGetFuturesExchangeRates(t *testing.T) { // TestGetFuturesHoldAmount API endpoint test func TestGetFuturesEstimatedDeliveryPrice(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesEstimatedDeliveryPrice(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1090,7 +1020,6 @@ func TestGetFuturesEstimatedDeliveryPrice(t *testing.T) { // TestGetFuturesOpenInterests API endpoint test func TestGetFuturesOpenInterests(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesOpenInterests(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1099,7 +1028,6 @@ func TestGetFuturesOpenInterests(t *testing.T) { // TestGetFuturesOpenInterests API endpoint test func TestGetFuturesCurrentPriceLimit(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesCurrentPriceLimit(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1108,7 +1036,6 @@ func TestGetFuturesCurrentPriceLimit(t *testing.T) { // TestGetFuturesCurrentMarkPrice API endpoint test func TestGetFuturesCurrentMarkPrice(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesCurrentMarkPrice(getFutureInstrumentID()) if err != nil { t.Error(err) @@ -1117,7 +1044,6 @@ func TestGetFuturesCurrentMarkPrice(t *testing.T) { // TestGetFuturesForceLiquidatedOrders API endpoint test func TestGetFuturesForceLiquidatedOrders(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesForceLiquidatedOrders(okgroup.GetFuturesForceLiquidatedOrdersRequest{ InstrumentID: getFutureInstrumentID(), Status: "1", @@ -1129,14 +1055,12 @@ func TestGetFuturesForceLiquidatedOrders(t *testing.T) { // TestGetFuturesTagPrice API endpoint test func TestGetFuturesTagPrice(t *testing.T) { - TestSetDefaults(t) _, err := o.GetFuturesTagPrice(getFutureInstrumentID()) testStandardErrorHandling(t, err) } // TestGetSwapPostions API endpoint test func TestGetSwapPostions(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapPostions() testStandardErrorHandling(t, err) @@ -1144,7 +1068,6 @@ func TestGetSwapPostions(t *testing.T) { // TestGetSwapPostionsForContract API endpoint test func TestGetSwapPostionsForContract(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapPostionsForContract(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) testStandardErrorHandling(t, err) @@ -1152,7 +1075,6 @@ func TestGetSwapPostionsForContract(t *testing.T) { // TestGetSwapAccountOfAllCurrency API endpoint test func TestGetSwapAccountOfAllCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapAccountOfAllCurrency() testStandardErrorHandling(t, err) @@ -1160,7 +1082,6 @@ func TestGetSwapAccountOfAllCurrency(t *testing.T) { // TestGetSwapAccountSettingsOfAContract API endpoint test func TestGetSwapAccountSettingsOfAContract(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapAccountSettingsOfAContract(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) testStandardErrorHandling(t, err) @@ -1168,7 +1089,6 @@ func TestGetSwapAccountSettingsOfAContract(t *testing.T) { // TestSetSwapLeverageLevelOfAContract API endpoint test func TestSetSwapLeverageLevelOfAContract(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.SetSwapLeverageLevelOfAContract(okgroup.SetSwapLeverageLevelOfAContractRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1181,7 +1101,6 @@ func TestSetSwapLeverageLevelOfAContract(t *testing.T) { // TestGetSwapAccountSettingsOfAContract API endpoint test func TestGetSwapBillDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapBillDetails(okgroup.GetSpotBillDetailsForCurrencyRequest{ Currency: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1252,7 +1171,6 @@ func TestCancelMultipleSwapOrders(t *testing.T) { // TestGetSwapOrderList API endpoint test func TestGetSwapOrderList(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapOrderList(okgroup.GetSwapOrderListRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1263,7 +1181,6 @@ func TestGetSwapOrderList(t *testing.T) { // TestGetSwapOrderDetails API endpoint test func TestGetSwapOrderDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapOrderDetails(okgroup.GetSwapOrderDetailsRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1274,7 +1191,6 @@ func TestGetSwapOrderDetails(t *testing.T) { // TestGetSwapTransactionDetails API endpoint test func TestGetSwapTransactionDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapTransactionDetails(okgroup.GetSwapTransactionDetailsRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1285,7 +1201,6 @@ func TestGetSwapTransactionDetails(t *testing.T) { // TestGetSwapContractInformation API endpoint test func TestGetSwapContractInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapContractInformation() if err != nil { @@ -1295,7 +1210,6 @@ func TestGetSwapContractInformation(t *testing.T) { // TestGetAllSwapTokensInformation API endpoint test func TestGetAllSwapTokensInformation(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetAllSwapTokensInformation() if err != nil { @@ -1305,7 +1219,6 @@ func TestGetAllSwapTokensInformation(t *testing.T) { // TestGetSwapTokensInformationForCurrency API endpoint test func TestGetSwapTokensInformationForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapTokensInformationForCurrency(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1315,7 +1228,6 @@ func TestGetSwapTokensInformationForCurrency(t *testing.T) { // TestGetSwapFilledOrdersData API endpoint test func TestGetSwapFilledOrdersData(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapFilledOrdersData(&okgroup.GetSwapFilledOrdersDataRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1328,7 +1240,6 @@ func TestGetSwapFilledOrdersData(t *testing.T) { // TestGetSwapMarketData API endpoint test func TestGetSwapMarketData(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetSwapMarketDataRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1342,7 +1253,6 @@ func TestGetSwapMarketData(t *testing.T) { // TestGetSwapIndeces API endpoint test func TestGetSwapIndeces(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapIndices(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1352,7 +1262,6 @@ func TestGetSwapIndeces(t *testing.T) { // TestGetSwapExchangeRates API endpoint test func TestGetSwapExchangeRates(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapExchangeRates() if err != nil { @@ -1362,7 +1271,6 @@ func TestGetSwapExchangeRates(t *testing.T) { // TestGetSwapOpenInterest API endpoint test func TestGetSwapOpenInterest(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapOpenInterest(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1372,7 +1280,6 @@ func TestGetSwapOpenInterest(t *testing.T) { // TestGetSwapCurrentPriceLimits API endpoint test func TestGetSwapCurrentPriceLimits(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapCurrentPriceLimits(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1382,7 +1289,6 @@ func TestGetSwapCurrentPriceLimits(t *testing.T) { // TestGetSwapForceLiquidatedOrders API endpoint test func TestGetSwapForceLiquidatedOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapForceLiquidatedOrders(okgroup.GetSwapForceLiquidatedOrdersRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1395,7 +1301,6 @@ func TestGetSwapForceLiquidatedOrders(t *testing.T) { // TestGetSwapOnHoldAmountForOpenOrders API endpoint test func TestGetSwapOnHoldAmountForOpenOrders(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapOnHoldAmountForOpenOrders(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) testStandardErrorHandling(t, err) @@ -1403,7 +1308,6 @@ func TestGetSwapOnHoldAmountForOpenOrders(t *testing.T) { // TestGetSwapNextSettlementTime API endpoint test func TestGetSwapNextSettlementTime(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapNextSettlementTime(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1413,7 +1317,6 @@ func TestGetSwapNextSettlementTime(t *testing.T) { // TestGetSwapMarkPrice API endpoint test func TestGetSwapMarkPrice(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapMarkPrice(fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD)) if err != nil { @@ -1423,7 +1326,6 @@ func TestGetSwapMarkPrice(t *testing.T) { // TestGetSwapFundingRateHistory API endpoint test func TestGetSwapFundingRateHistory(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetSwapFundingRateHistory(okgroup.GetSwapFundingRateHistoryRequest{ InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD), @@ -1436,7 +1338,6 @@ func TestGetSwapFundingRateHistory(t *testing.T) { // TestGetETT API endpoint test func TestGetETT(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetETT() testStandardErrorHandling(t, err) @@ -1444,7 +1345,6 @@ func TestGetETT(t *testing.T) { // TestGetETTAccountInformationForCurrency API endpoint test func TestGetETTAccountInformationForCurrency(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetETTBillsDetails(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -1452,7 +1352,6 @@ func TestGetETTAccountInformationForCurrency(t *testing.T) { // TestGetETTBillsDetails API endpoint test func TestGetETTBillsDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetETTBillsDetails(currency.BTC.String()) testStandardErrorHandling(t, err) @@ -1487,7 +1386,6 @@ func TestCancelETTOrder(t *testing.T) { // Or when it is submitted as URL params // Unsure how to fix func TestGetETTOrderList(t *testing.T) { - TestSetDefaults(t) t.Parallel() request := okgroup.GetETTOrderListRequest{ Type: 1, @@ -1501,7 +1399,6 @@ func TestGetETTOrderList(t *testing.T) { // TestGetETTOrderDetails API endpoint test func TestGetETTOrderDetails(t *testing.T) { - TestSetDefaults(t) t.Parallel() _, err := o.GetETTOrderDetails("888845020785408") testStandardErrorHandling(t, err) @@ -1510,7 +1407,6 @@ func TestGetETTOrderDetails(t *testing.T) { // TestGetETTConstituents API endpoint test func TestGetETTConstituents(t *testing.T) { t.Skip("ETT currently unavailable") - TestSetDefaults(t) t.Parallel() _, err := o.GetETTConstituents("OK06ETT") if err != nil { @@ -1521,7 +1417,6 @@ func TestGetETTConstituents(t *testing.T) { // TestGetETTSettlementPriceHistory API endpoint test func TestGetETTSettlementPriceHistory(t *testing.T) { t.Skip("ETT currently unavailable") - TestSetDefaults(t) t.Parallel() _, err := o.GetETTSettlementPriceHistory("OK06ETT") if err != nil { @@ -1535,7 +1430,6 @@ func TestGetETTSettlementPriceHistory(t *testing.T) { // Attempts to subscribe to a channel that doesn't exist // Will log in if credentials are present func TestSendWsMessages(t *testing.T) { - TestSetDefaults(t) if !o.Websocket.IsEnabled() && !o.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { t.Skip(wshandler.WebsocketNotEnabled) } @@ -1588,7 +1482,6 @@ func TestGetAssetTypeFromTableName(t *testing.T) { // TestGetWsChannelWithoutOrderType logic test func TestGetWsChannelWithoutOrderType(t *testing.T) { - TestSetDefaults(t) t.Parallel() str := "spot/depth5:BTC-USDT" expected := "depth5" @@ -1611,7 +1504,6 @@ func TestGetWsChannelWithoutOrderType(t *testing.T) { // TestOrderBookUpdateChecksumCalculator logic test func TestOrderBookUpdateChecksumCalculator(t *testing.T) { - TestSetDefaults(t) original := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"BTC-USDT","asks":[["3864.6786","0.145",1],["3864.7682","0.005",1],["3864.9851","0.57",1],["3864.9852","0.30137754",1],["3864.9986","2.81818419",1],["3864.9995","0.002",1],["3865","0.0597",1],["3865.0309","0.4",1],["3865.1995","0.004",1],["3865.3995","0.004",1],["3865.5995","0.004",1],["3865.7995","0.004",1],["3865.9995","0.004",1],["3866.0961","0.25865886",1],["3866.1995","0.004",1],["3866.3995","0.004",1],["3866.4004","0.3243",2],["3866.5995","0.004",1],["3866.7633","0.44247086",1],["3866.7995","0.004",1],["3866.9197","0.511",1],["3867.256","0.51716256",1],["3867.3951","0.02588112",1],["3867.4014","0.025",1],["3867.4566","0.02499999",1],["3867.4675","4.01155057",5],["3867.5515","1.1",1],["3867.6113","0.009",1],["3867.7349","0.026",1],["3867.7781","0.03738652",1],["3867.9163","0.0521",1],["3868.0381","0.34354941",1],["3868.0436","0.051",1],["3868.0657","0.90552172",3],["3868.1819","0.03863346",1],["3868.2013","0.194",1],["3868.346","0.051",1],["3868.3863","0.01155",1],["3868.7716","0.009",1],["3868.947","0.025",1],["3868.98","0.001",1],["3869.0764","1.03487931",1],["3869.2773","0.07724578",1],["3869.4039","0.025",1],["3869.4068","1.03",1],["3869.7068","2.06976398",1],["3870","0.5",1],["3870.0465","0.01",1],["3870.7042","0.02099651",1],["3870.9451","2.07047375",1],["3871.5254","1.2",1],["3871.5596","0.001",1],["3871.6605","0.01035032",1],["3871.7179","2.07047375",1],["3871.8816","0.51751625",1],["3872.1","0.75",1],["3872.2464","0.0646",1],["3872.3747","0.283",1],["3872.4039","0.2",1],["3872.7655","0.23179307",1],["3872.8005","2.06976398",1],["3873.1509","2",1],["3873.3215","0.26",1],["3874.1392","0.001",1],["3874.1487","3.88224364",4],["3874.1685","1.8",1],["3874.5571","0.08974762",1],["3874.734","2.06976398",1],["3874.99","0.3",1],["3875","1.001",2],["3875.0041","1.03505051",1],["3875.45","0.3",1],["3875.4766","0.15",1],["3875.7057","0.51751625",1],["3876","0.001",1],["3876.68","0.3",1],["3876.7188","0.001",1],["3877","0.75",1],["3877.31","0.035",1],["3877.38","0.3",1],["3877.7","0.3",1],["3877.88","0.3",1],["3878.0364","0.34770122",1],["3878.4525","0.48579748",1],["3878.4955","0.02812511",1],["3878.8855","0.00258579",1],["3878.9605","0.895",1],["3879","0.001",1],["3879.2984","0.002",2],["3879.432","0.001",1],["3879.6313","6",1],["3879.9999","0.002",2],["3880","1.25132834",5],["3880.2526","0.04075162",1],["3880.7145","0.0647",1],["3881.2469","1.883",1],["3881.878","0.002",2],["3884.4576","0.002",2],["3885","0.002",2],["3885.2233","0.28304103",1],["3885.7416","18",1],["3886","0.001",1],["3886.1554","5.4",1],["3887","0.001",1],["3887.0372","0.002",2],["3887.2559","0.05214011",1],["3887.9238","0.0019",1],["3888","0.15810538",4],["3889","0.001",1],["3889.5175","0.50510653",1],["3889.6168","0.002",2],["3889.9999","0.001",1],["3890","2.34968109",4],["3890.5222","0.00257806",1],["3891.2659","5",1],["3891.9999","0.00893897",1],["3892.1964","0.002",2],["3892.4358","0.0176",1],["3893.1388","1.4279",1],["3894","0.0026321",1],["3894.776","0.001",1],["3895","1.501",2],["3895.379","0.25881288",1],["3897","0.05",1],["3897.3556","0.001",1],["3897.8432","0.73708079",1],["3898","3.31353018",7],["3898.4462","4.757",1],["3898.6","0.47159638",1],["3898.8769","0.0129",1],["3899","6",2],["3899.6516","0.025",1],["3899.9352","0.001",1],["3899.9999","0.013",2],["3900","22.37447743",24],["3900.9999","0.07763916",1],["3901","0.10192487",1],["3902.1937","0.00257034",1],["3902.3991","1.5532141",1],["3902.5148","0.001",1],["3904","1.49331984",1],["3904.9999","0.95905447",1],["3905","0.501",2],["3905.0944","0.001",1],["3905.61","0.099",1],["3905.6801","0.54343686",1],["3906.2901","0.0258",1],["3907.674","0.001",1],["3907.85","1.35778084",1],["3908","0.03846153",1],["3908.23","1.95189531",1],["3908.906","0.03148978",1],["3909","0.001",1],["3909.9999","0.01398721",2],["3910","0.016",2],["3910.2536","0.001",1],["3912.5406","0.88270517",1],["3912.8332","0.001",1],["3913","1.2640608",1],["3913.87","1.69114184",1],["3913.9003","0.00256266",1],["3914","1.21766411",1],["3915","0.001",1],["3915.4128","0.001",1],["3915.7425","6.848",1],["3916","0.0050949",1],["3917.36","1.28658296",1],["3917.9924","0.001",1],["3919","0.001",1],["3919.9999","0.001",1],["3920","1.21171832",3],["3920.0002","0.20217038",1],["3920.572","0.001",1],["3921","0.128",1],["3923.0756","0.00148064",1],["3923.1516","0.001",1],["3923.86","1.38831714",1],["3925","0.01867801",2],["3925.642","0.00255499",1],["3925.7312","0.001",1],["3926","0.04290757",1],["3927","0.023",1],["3927.3175","0.01212865",1],["3927.65","1.51375612",1],["3928","0.5",1],["3928.3108","0.001",1],["3929","0.001",1],["3929.9999","0.01519338",2],["3930","0.0174985",3],["3930.21","1.49335799",1],["3930.8904","0.001",1],["3932.2999","0.01953",1],["3932.8962","7.96",1],["3933.0387","11.808",1],["3933.47","0.001",1],["3934","1.40839932",1],["3935","0.001",1],["3936.8","0.62879518",1],["3937.23","1.56977841",1],["3937.4189","0.00254735",1]],"bids":[["3864.5217","0.00540709",1],["3864.5216","0.14068758",2],["3864.2275","0.01033576",1],["3864.0989","0.00825047",1],["3864.0273","0.38",1],["3864.0272","0.4",1],["3863.9957","0.01083539",1],["3863.9184","0.01653723",1],["3863.8282","0.25588165",1],["3863.8153","0.154",1],["3863.7791","1.14122492",1],["3863.6866","0.01733662",1],["3863.6093","0.02645958",1],["3863.3775","0.02773862",1],["3863.0297","0.513",1],["3863.0286","1.1028564",2],["3862.8489","0.01",1],["3862.5972","0.01890179",1],["3862.3431","0.01152944",1],["3862.313","0.009",1],["3862.2445","0.90551002",3],["3862.0734","0.014",1],["3862.0539","0.64976067",1],["3861.8586","0.025",1],["3861.7888","0.025",1],["3861.7673","0.008",1],["3861.5785","0.01",1],["3861.3895","0.005",1],["3861.3338","0.25875855",1],["3861.161","0.01",1],["3861.1111","0.03863352",1],["3861.0732","0.51703882",1],["3860.9116","0.17754895",1],["3860.75","0.19",1],["3860.6554","0.015",1],["3860.6172","0.005",1],["3860.6088","0.008",1],["3860.4724","0.12940042",1],["3860.4424","0.25880084",1],["3860.42","0.01",1],["3860.3725","0.51760102",1],["3859.8449","0.005",1],["3859.8285","0.03738652",1],["3859.7638","0.07726703",1],["3859.4502","0.008",1],["3859.3772","0.05173471",1],["3859.3409","0.194",1],["3859","5",1],["3858.827","0.0521",1],["3858.8208","0.001",1],["3858.679","0.26",1],["3858.4814","0.07477305",1],["3858.1669","1.03503422",1],["3857.6005","0.006",1],["3857.4005","0.004",1],["3857.2005","0.004",1],["3857.1871","1.218",1],["3857.0005","0.004",1],["3856.8135","0.0646",1],["3856.8005","0.004",1],["3856.2412","0.001",1],["3856.2349","1.03503422",1],["3856.0197","0.01037339",1],["3855.8781","0.23178117",1],["3855.8005","0.004",1],["3855.7165","0.00259355",1],["3855.4858","0.25875855",1],["3854.4584","0.01",1],["3853.6616","0.001",1],["3853.1373","0.92",1],["3852.5072","0.48599702",1],["3851.3926","0.13008333",1],["3851.082","0.001",1],["3850.9317","2",1],["3850.6359","0.34770165",1],["3850.2058","0.51751624",1],["3850.0823","0.15",1],["3850.0042","0.5175171",1],["3850","0.001",1],["3849.6325","1.8",1],["3849.41","0.3",1],["3848.9686","1.85",1],["3848.7426","0.18511466",1],["3848.52","0.3",1],["3848.5024","0.001",1],["3848.42","0.3",1],["3848.1618","2.204",1],["3847.77","0.3",1],["3847.48","0.3",1],["3847.3581","2.05",1],["3846.8259","0.0646",1],["3846.59","0.3",1],["3846.49","0.3",1],["3845.9228","0.001",1],["3844.184","0.00260133",1],["3844.0092","6.3",1],["3843.3432","0.001",1],["3841","0.06300963",1],["3840.7636","0.001",1],["3840","0.201",3],["3839.7681","18",1],["3839.5328","0.05214011",1],["3838.184","0.001",1],["3837.2344","0.27589557",1],["3836.6479","5.2",1],["3836","2.37196773",3],["3835.6044","0.001",1],["3833.6053","0.25873556",1],["3833.0248","0.001",1],["3833","0.8726502",1],["3832.6859","0.00260913",1],["3832","0.007",1],["3831.637","6",1],["3831.0602","0.001",1],["3830.4452","0.001",1],["3830","0.20375718",4],["3829.7125","0.07833486",1],["3829.6283","0.3519681",1],["3829","0.0039261",1],["3827.8656","0.001",1],["3826.0001","0.53251232",1],["3826","0.0509",1],["3825.7834","0.00698562",1],["3825.286","0.001",1],["3823.0001","0.03010127",1],["3822.8014","0.00261588",1],["3822.7064","0.001",1],["3822.2","1",1],["3822.1121","0.35994101",1],["3821.2222","0.00261696",1],["3821","0.001",1],["3820.1268","0.001",1],["3820","1.12992803",4],["3819","0.01331195",2],["3817.5472","0.001",1],["3816","1.13807184",2],["3815.8343","0.32463428",1],["3815.7834","0.00525295",1],["3815","28.99386799",4],["3814.9676","0.001",1],["3813","0.91303023",4],["3812.388","0.002",2],["3811.2257","0.07",1],["3810","0.32573997",2],["3809.8084","0.001",1],["3809.7928","0.00262481",1],["3807.2288","0.001",1],["3806.8421","0.07003461",1],["3806","0.19",1],["3805.8041","0.05678805",1],["3805","1.01",2],["3804.6492","0.001",1],["3804.3551","0.1",1],["3803","0.005",1],["3802.22","2.05042631",1],["3802.0696","0.001",1],["3802","1.63290092",1],["3801.2257","0.07",1],["3801","57.4",3],["3800.9853","0.02492278",1],["3800.8421","0.06503533",1],["3800.7844","0.02812628",1],["3800.0001","0.00409473",1],["3800","17.91401074",15],["3799.49","0.001",1],["3799","0.1",1],["3796.9104","0.001",1],["3796","9.00128053",2],["3795.5441","0.0028",1],["3794.3308","0.001",1],["3791","55",1],["3790.7777","0.07",1],["3790","12.03238184",7],["3789","1",1],["3788","0.21110454",2],["3787.2959","9",1],["3786.592","0.001",1],["3786","9.01916822",2],["3785","12.87914268",5],["3784.0124","0.001",1],["3781.4328","0.002",2],["3781","56.3",2],["3780.7777","0.07",1],["3780","23.41537654",10],["3778.8532","0.002",2],["3776","9",1],["3774","0.003",1],["3772.2481","0.06901672",1],["3771","55.1",2],["3770.7777","0.07",1],["3770","7.30268416",5],["3769","0.25",1],["3768","1.3725",3],["3766.66","0.02",1],["3766","7.64837924",2],["3765.58","1.22775492",1],["3762.58","1.22873383",1],["3761","51.68262164",1],["3760.8031","0.0399",1],["3760.7777","0.07",1]],"timestamp":"2019-03-06T23:19:17.705Z","checksum":-1785549915}]}` update := `{"table":"spot/depth","action":"update","data":[{"instrument_id":"BTC-USDT","asks":[["3864.6786","0",0],["3864.9852","0",0],["3865.9994","0.48402971",1],["3866.4004","0.001",1],["3866.7995","0.3273",2],["3867.4566","0",0],["3867.7031","0.025",1],["3868.0436","0",0],["3868.346","0",0],["3868.3695","0.051",1],["3870.9243","0.642",1],["3874.9942","0.51751796",1],["3875.7057","0",0],["3939","0.001",1]],"bids":[["3864.55","0.0565449",1],["3863.8282","0",0],["3863.8153","0",0],["3863.7898","0.01320077",1],["3863.4807","0.02112123",1],["3863.3002","0.04233533",1],["3863.1717","0.03379397",1],["3863.0685","0.04438179",1],["3863.0286","0.7362564",1],["3862.9912","0.06773651",1],["3862.8626","0.05407035",1],["3862.7595","0.07101087",1],["3862.313","0.3756",2],["3862.1848","0.012",1],["3862.0734","0",0],["3861.8391","0.025",1],["3861.7888","0",0],["3856.6716","0.38893641",1],["3768","0",0],["3766.66","0",0],["3766","0",0],["3765.58","0",0],["3762.58","0",0],["3761","0",0],["3760.8031","0",0],["3760.7777","0",0]],"timestamp":"2019-03-06T23:19:18.239Z","checksum":-1587788848}]}` var dataResponse okgroup.WebsocketDataResponse @@ -1638,7 +1530,6 @@ func TestOrderBookUpdateChecksumCalculator(t *testing.T) { // TestOrderBookUpdateChecksumCalculatorWithDash logic test func TestOrderBookUpdateChecksumCalculatorWith8DecimalPlaces(t *testing.T) { - TestSetDefaults(t) original := `{"table":"spot/depth","action":"partial","data":[{"instrument_id":"WAVES-BTC","asks":[["0.000714","1.15414979",1],["0.000715","3.3",2],["0.000717","426.71348",2],["0.000719","140.84507042",1],["0.00072","590.77",1],["0.000721","991.77",1],["0.000724","0.3532032",1],["0.000725","58.82698567",1],["0.000726","1033.15469748",2],["0.000729","0.35320321",1],["0.00073","352.77",1],["0.000735","0.38469748",1],["0.000736","625.77",1],["0.00075191","152.44796961",1],["0.00075192","114.3359772",1],["0.00075193","85.7519829",1],["0.00075194","64.31398718",1],["0.00075195","48.23549038",1],["0.00075196","36.17661779",1],["0.00075199","61.04804253",1],["0.0007591","70.71318474",1],["0.0007621","53.03488855",1],["0.00076211","39.77616642",1],["0.00076212","29.83212481",1],["0.0007635","22.37409361",1],["0.00076351","29.36599786",2],["0.00076352","9.43907074",1],["0.00076353","7.07930306",1],["0.00076354","14.15860612",1],["0.00076355","3.53965153",1],["0.00076369","3.53965153",1],["0.0008","34.36841101",1],["0.00082858","1.69936503",1],["0.00083232","2.8",1],["0.00084","15.69220129",1],["0.00085","4.42785042",1],["0.00088","0.1",1],["0.000891","0.1",1],["0.0009","12.41486491",2],["0.00093","5",1],["0.0012","12.31486492",1],["0.00531314","6.91803114",1],["0.00799999","0.02",1],["0.0084","0.05989",1],["0.00931314","5.18852336",1],["0.0799999","0.02",1],["0.499","6.00423396",1],["0.5","0.4995",1],["0.799999","0.02",1],["4.99","2",1],["5","3.98583144",1],["7.99999999","0.02",1],["79.99999999","0.02",1],["799.99999999","0.02986704",1]],"bids":[["0.000709","222.91679881",3],["0.000703","0.47161952",1],["0.000701","140.73015789",2],["0.0007","0.3",1],["0.000699","401",1],["0.000698","232.61801667",2],["0.000689","0.71396896",1],["0.000688","0.69910125",1],["0.000613","227.54771052",1],["0.0005","0.01",1],["0.00026789","3.69905341",1],["0.000238","2.4",1],["0.00022","0.53",1],["0.0000055","374.09871696",1],["0.00000056","222",1],["0.00000055","736.84761363",1],["0.0000002","999",1],["0.00000009","1222.22222417",1],["0.00000008","20868.64520447",1],["0.00000002","110000",1],["0.00000001","10000",1]],"timestamp":"2019-03-12T22:22:42.274Z","checksum":1319037905}]}` update := `{"table":"spot/depth","action":"update","data":[{"instrument_id":"WAVES-BTC","asks":[["0.000715","100.48199596",3],["0.000716","62.21679881",1]],"bids":[["0.000713","38.95772168",1]],"timestamp":"2019-03-12T22:22:42.938Z","checksum":-131160897}]}` var dataResponse okgroup.WebsocketDataResponse @@ -1696,7 +1587,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() o.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -1708,7 +1599,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - TestSetDefaults(t) t.Parallel() var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic @@ -1770,7 +1660,6 @@ func TestGetFee(t *testing.T) { // TestFormatWithdrawPermissions helper test func TestFormatWithdrawPermissions(t *testing.T) { - TestSetDefaults(t) t.Parallel() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText withdrawPermissions := o.FormatWithdrawPermissions() @@ -1901,3 +1790,25 @@ func TestWithdrawInternationalBank(t *testing.T) { err) } } + +// TestGetOrderbook logic test +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: "BTC-USDT"}, + asset.Spot) + if err != nil { + t.Error(err) + } + contract := getFutureInstrumentID() + _, err = o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: contract}, + asset.Futures) + if err != nil { + t.Error(err) + } + + _, err = o.GetOrderBook(okgroup.GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, + asset.PerpetualSwap) + if err != nil { + t.Error(err) + } +} diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go index afd95cf5..0b6b7c93 100644 --- a/exchanges/okgroup/okgroup.go +++ b/exchanges/okgroup/okgroup.go @@ -233,8 +233,8 @@ func (o *OKGroup) PlaceMultipleSpotOrders(request []PlaceOrderRequest) (map[stri var orderErrors []error for currency, orderResponse := range resp { - for _, order := range orderResponse { - if !order.Result { + for i := range orderResponse { + if !orderResponse[i].Result { orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency)) } } @@ -262,15 +262,15 @@ func (o *OKGroup) CancelMultipleSpotOrders(request CancelMultipleSpotOrdersReque } for currency, orderResponse := range resp { - for _, order := range orderResponse { + for i := range orderResponse { cancellationResponse := CancelMultipleSpotOrdersResponse{ - OrderID: order.OrderID, - Result: order.Result, - ClientOID: order.ClientOID, + OrderID: orderResponse[i].OrderID, + Result: orderResponse[i].Result, + ClientOID: orderResponse[i].ClientOID, } - if !order.Result { - cancellationResponse.Error = fmt.Errorf("order %v for currency %v failed to be cancelled", order.OrderID, currency) + if !orderResponse[i].Result { + cancellationResponse.Error = fmt.Errorf("order %v for currency %v failed to be cancelled", orderResponse[i].OrderID, currency) } resp[currency] = append(resp[currency], cancellationResponse) @@ -454,8 +454,8 @@ func (o *OKGroup) PlaceMultipleMarginOrders(request []PlaceOrderRequest) (map[st var orderErrors []error for currency, orderResponse := range resp { - for _, order := range orderResponse { - if !order.Result { + for i := range orderResponse { + if !orderResponse[i].Result { orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency)) } } @@ -484,9 +484,9 @@ func (o *OKGroup) CancelMultipleMarginOrders(request CancelMultipleSpotOrdersReq var orderErrors []error for currency, orderResponse := range resp { - for _, order := range orderResponse { - if !order.Result { - orderErrors = append(orderErrors, fmt.Errorf("order %v for currency %v failed to be cancelled", order.OrderID, currency)) + for i := range orderResponse { + if !orderResponse[i].Result { + orderErrors = append(orderErrors, fmt.Errorf("order %v for currency %v failed to be cancelled", orderResponse[i].OrderID, currency)) } } } diff --git a/exchanges/okgroup/okgroup_test.go b/exchanges/okgroup/okgroup_test.go deleted file mode 100644 index edcbf618..00000000 --- a/exchanges/okgroup/okgroup_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package okgroup - -import ( - "log" - "os" - "testing" - "time" - - "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" -) - -const ( - apiKey = "" - apiSecret = "" - - testAPIURL = "https://www.okex.com/api/" - testAPIVersion = "/v3/" -) - -var o OKGroup - -func TestMain(m *testing.M) { - cfg := config.GetConfig() - err := cfg.LoadConfig("../../testdata/configtest.json", true) - if err != nil { - log.Fatal("okgroup load config error", err) - } - okgroup, err := cfg.GetExchangeConfig("Okex") - if err != nil { - log.Fatal("okgroup Setup() init error", err) - } - - okgroup.API.AuthenticatedSupport = true - okgroup.API.Credentials.Key = apiKey - okgroup.API.Credentials.Secret = apiSecret - o.API.Endpoints.URL = testAPIURL - o.APIVersion = testAPIVersion - - o.Requester = request.New("okgroup_test_things", - request.NewRateLimit(time.Second, 10), - request.NewRateLimit(time.Second, 10), - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - ) - o.Websocket = wshandler.New() - - err = o.Setup(okgroup) - if err != nil { - log.Fatal("okgroup setup error", err) - } - os.Exit(m.Run()) -} - -func TestGetOrderbook(t *testing.T) { - t.Parallel() - _, err := o.GetOrderBook(GetOrderBookRequest{InstrumentID: "BTC-USDT"}, - asset.Spot) - if err != nil { - t.Error(err) - } - - // futures expire and break test, will need to mock this in the future - _, err = o.GetOrderBook(GetOrderBookRequest{InstrumentID: "Payload"}, - asset.Futures) - if err == nil { - t.Error("error cannot be nil") - } - - _, err = o.GetOrderBook(GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, - asset.PerpetualSwap) - if err != nil { - t.Error(err) - } -} diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index 91658ba0..c3dcca42 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -273,6 +273,9 @@ func (o *OKGroup) SubmitOrder(s *order.Submit) (resp order.SubmitResponse, err e resp.IsOrderPlaced = orderResponse.Result resp.OrderID = orderResponse.OrderID + if s.OrderType == order.Market { + resp.FullyMatched = true + } return } diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 2d7b7cd3..ed019e9d 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -1,6 +1,7 @@ package order import ( + "errors" "strings" "testing" "time" @@ -398,3 +399,138 @@ func TestSortOrdersByOrderType(t *testing.T) { orders[0].OrderType) } } + +var stringsToOrderSide = []struct { + in string + out Side + err error +}{ + {"buy", Buy, nil}, + {"BUY", Buy, nil}, + {"bUy", Buy, nil}, + {"sell", Sell, nil}, + {"SELL", Sell, nil}, + {"sElL", Sell, nil}, + {"bid", Bid, nil}, + {"BID", Bid, nil}, + {"bId", Bid, nil}, + {"ask", Ask, nil}, + {"ASK", Ask, nil}, + {"aSk", Ask, nil}, + {"any", AnySide, nil}, + {"ANY", AnySide, nil}, + {"aNy", AnySide, nil}, + {"woahMan", Buy, errors.New("woahMan not recognised as side type")}, +} + +func TestStringToOrderSide(t *testing.T) { + for i := 0; i < len(stringsToOrderSide); i++ { + testData := &stringsToOrderSide[i] + t.Run(testData.in, func(t *testing.T) { + out, err := StringToOrderSide(testData.in) + if err != nil { + if err.Error() != testData.err.Error() { + t.Error("Unexpected error", err) + } + } else if out != testData.out { + t.Errorf("Unexpected output %v. Expected %v", out, testData.out) + } + }) + } +} + +var stringsToOrderType = []struct { + in string + out Type + err error +}{ + {"limit", Limit, nil}, + {"LIMIT", Limit, nil}, + {"lImIt", Limit, nil}, + {"market", Market, nil}, + {"MARKET", Market, nil}, + {"mArKeT", Market, nil}, + {"immediate_or_cancel", ImmediateOrCancel, nil}, + {"IMMEDIATE_OR_CANCEL", ImmediateOrCancel, nil}, + {"iMmEdIaTe_Or_CaNcEl", ImmediateOrCancel, nil}, + {"stop", Stop, nil}, + {"STOP", Stop, nil}, + {"sToP", Stop, nil}, + {"trailingstop", TrailingStop, nil}, + {"TRAILINGSTOP", TrailingStop, nil}, + {"tRaIlInGsToP", TrailingStop, nil}, + {"any", AnyType, nil}, + {"ANY", AnyType, nil}, + {"aNy", AnyType, nil}, + {"woahMan", Unknown, errors.New("woahMan not recognised as order type")}, +} + +func TestStringToOrderType(t *testing.T) { + for i := 0; i < len(stringsToOrderType); i++ { + testData := &stringsToOrderType[i] + t.Run(testData.in, func(t *testing.T) { + out, err := StringToOrderType(testData.in) + if err != nil { + if err.Error() != testData.err.Error() { + t.Error("Unexpected error", err) + } + } else if out != testData.out { + t.Errorf("Unexpected output %v. Expected %v", out, testData.out) + } + }) + } +} + +var stringsToOrderStatus = []struct { + in string + out Status + err error +}{ + {"any", AnyStatus, nil}, + {"ANY", AnyStatus, nil}, + {"aNy", AnyStatus, nil}, + {"new", New, nil}, + {"NEW", New, nil}, + {"nEw", New, nil}, + {"active", Active, nil}, + {"ACTIVE", Active, nil}, + {"aCtIvE", Active, nil}, + {"partially_filled", PartiallyFilled, nil}, + {"PARTIALLY_FILLED", PartiallyFilled, nil}, + {"pArTiAlLy_FiLlEd", PartiallyFilled, nil}, + {"filled", Filled, nil}, + {"FILLED", Filled, nil}, + {"fIlLeD", Filled, nil}, + {"canceled", Cancelled, nil}, + {"CANCELED", Cancelled, nil}, + {"cAnCelEd", Cancelled, nil}, + {"pending_cancel", PendingCancel, nil}, + {"PENDING_CANCEL", PendingCancel, nil}, + {"pENdInG_cAnCeL", PendingCancel, nil}, + {"rejected", Rejected, nil}, + {"REJECTED", Rejected, nil}, + {"rEjEcTeD", Rejected, nil}, + {"expired", Expired, nil}, + {"EXPIRED", Expired, nil}, + {"eXpIrEd", Expired, nil}, + {"hidden", Hidden, nil}, + {"HIDDEN", Hidden, nil}, + {"hIdDeN", Hidden, nil}, + {"woahMan", UnknownStatus, errors.New("woahMan not recognised as order STATUS")}, +} + +func TestStringToOrderStatus(t *testing.T) { + for i := 0; i < len(stringsToOrderStatus); i++ { + testData := &stringsToOrderStatus[i] + t.Run(testData.in, func(t *testing.T) { + out, err := StringToOrderStatus(testData.in) + if err != nil { + if err.Error() != testData.err.Error() { + t.Error("Unexpected error", err) + } + } else if out != testData.out { + t.Errorf("Unexpected output %v. Expected %v", out, testData.out) + } + }) + } +} diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 0a02e9ed..ef869e02 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -50,6 +50,7 @@ type Submit struct { // SubmitResponse is what is returned after submitting an order to an exchange type SubmitResponse struct { IsOrderPlaced bool + FullyMatched bool OrderID string } @@ -127,7 +128,7 @@ type Detail struct { // TradeHistory holds exchange history data type TradeHistory struct { Timestamp time.Time - TID int64 + TID string Price float64 Amount float64 Exchange string diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 85452186..1fb1d2c7 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -1,6 +1,7 @@ package order import ( + "fmt" "sort" "strings" "time" @@ -10,18 +11,18 @@ import ( // NewOrder creates a new order and returns a an orderID func NewOrder(exchangeName string, amount, price float64) int { - order := &Order{} + ord := &Order{} if len(Orders) == 0 { - order.OrderID = 0 + ord.OrderID = 0 } else { - order.OrderID = len(Orders) + ord.OrderID = len(Orders) } - order.Exchange = exchangeName - order.Amount = amount - order.Price = price - Orders = append(Orders, order) - return order.OrderID + ord.Exchange = exchangeName + ord.Amount = amount + ord.Price = price + Orders = append(Orders, ord) + return ord.OrderID } // DeleteOrder deletes orders by ID and returns state @@ -300,3 +301,72 @@ func SortOrdersBySide(orders *[]Detail, reverse bool) { sort.Sort(ByOrderSide(*orders)) } } + +// StringToOrderSide for converting case insensitive order side +// and returning a real Side +func StringToOrderSide(side string) (Side, error) { + switch { + case strings.EqualFold(side, Buy.String()): + return Buy, nil + case strings.EqualFold(side, Sell.String()): + return Sell, nil + case strings.EqualFold(side, Bid.String()): + return Bid, nil + case strings.EqualFold(side, Ask.String()): + return Ask, nil + case strings.EqualFold(side, AnySide.String()): + return AnySide, nil + default: + return Side(""), fmt.Errorf("%s not recognised as side type", side) + } +} + +// StringToOrderType for converting case insensitive order type +// and returning a real Type +func StringToOrderType(oType string) (Type, error) { + switch { + case strings.EqualFold(oType, Limit.String()): + return Limit, nil + case strings.EqualFold(oType, Market.String()): + return Market, nil + case strings.EqualFold(oType, ImmediateOrCancel.String()): + return ImmediateOrCancel, nil + case strings.EqualFold(oType, Stop.String()): + return Stop, nil + case strings.EqualFold(oType, TrailingStop.String()): + return TrailingStop, nil + case strings.EqualFold(oType, AnyType.String()): + return AnyType, nil + default: + return Unknown, fmt.Errorf("%s not recognised as order type", oType) + } +} + +// StringToOrderStatus for converting case insensitive order status +// and returning a real Status +func StringToOrderStatus(status string) (Status, error) { + switch { + case strings.EqualFold(status, AnyStatus.String()): + return AnyStatus, nil + case strings.EqualFold(status, New.String()): + return New, nil + case strings.EqualFold(status, Active.String()): + return Active, nil + case strings.EqualFold(status, PartiallyFilled.String()): + return PartiallyFilled, nil + case strings.EqualFold(status, Filled.String()): + return Filled, nil + case strings.EqualFold(status, Cancelled.String()): + return Cancelled, nil + case strings.EqualFold(status, PendingCancel.String()): + return PendingCancel, nil + case strings.EqualFold(status, Rejected.String()): + return Rejected, nil + case strings.EqualFold(status, Expired.String()): + return Expired, nil + case strings.EqualFold(status, Hidden.String()): + return Hidden, nil + default: + return UnknownStatus, fmt.Errorf("%s not recognised as order STATUS", status) + } +} diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 9c750478..8c767abf 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -158,9 +158,9 @@ func TestVerify(t *testing.T) { func TestCalculateTotalBids(t *testing.T) { t.Parallel() - currency := currency.NewPairFromStrings("BTC", "USD") + curr := currency.NewPairFromStrings("BTC", "USD") base := Base{ - Pair: currency, + Pair: curr, Bids: []Item{{Price: 100, Amount: 10}}, LastUpdated: time.Now(), } @@ -173,9 +173,9 @@ func TestCalculateTotalBids(t *testing.T) { func TestCalculateTotaAsks(t *testing.T) { t.Parallel() - currency := currency.NewPairFromStrings("BTC", "USD") + curr := currency.NewPairFromStrings("BTC", "USD") base := Base{ - Pair: currency, + Pair: curr, Asks: []Item{{Price: 100, Amount: 10}}, } @@ -187,10 +187,10 @@ func TestCalculateTotaAsks(t *testing.T) { func TestUpdate(t *testing.T) { t.Parallel() - currency := currency.NewPairFromStrings("BTC", "USD") + curr := currency.NewPairFromStrings("BTC", "USD") timeNow := time.Now() base := Base{ - Pair: currency, + Pair: curr, Asks: []Item{{Price: 100, Amount: 10}}, Bids: []Item{{Price: 200, Amount: 10}}, LastUpdated: timeNow, diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index a89aca41..cc939aca 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -103,7 +103,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() p.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, @@ -202,9 +202,7 @@ func TestFormatWithdrawPermissions(t *testing.T) { expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := p.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, @@ -284,7 +282,6 @@ func TestCancelExchangeOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -310,7 +307,6 @@ func TestCancelAllExchangeOrders(t *testing.T) { } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index b5078c45..18b91525 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -469,12 +469,12 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []inter func (p *Poloniex) GenerateDefaultSubscriptions() { var subscriptions []wshandler.WebsocketChannelSubscription subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: fmt.Sprintf("%v", wsTickerDataID), + Channel: strconv.FormatInt(wsTickerDataID, 10), }) if p.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: fmt.Sprintf("%v", wsAccountNotificationID), + Channel: strconv.FormatInt(wsAccountNotificationID, 10), }) } @@ -495,9 +495,9 @@ func (p *Poloniex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscr Command: "subscribe", } switch { - case strings.EqualFold(fmt.Sprintf("%v", wsAccountNotificationID), channelToSubscribe.Channel): + case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), channelToSubscribe.Channel): return p.wsSendAuthorisedCommand("subscribe") - case strings.EqualFold(fmt.Sprintf("%v", wsTickerDataID), channelToSubscribe.Channel): + case strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), channelToSubscribe.Channel): subscriptionRequest.Channel = wsTickerDataID default: subscriptionRequest.Channel = channelToSubscribe.Currency.String() @@ -511,9 +511,9 @@ func (p *Poloniex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubs Command: "unsubscribe", } switch { - case strings.EqualFold(fmt.Sprintf("%v", wsAccountNotificationID), channelToSubscribe.Channel): + case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), channelToSubscribe.Channel): return p.wsSendAuthorisedCommand("unsubscribe") - case strings.EqualFold(fmt.Sprintf("%v", wsTickerDataID), channelToSubscribe.Channel): + case strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), channelToSubscribe.Channel): unsubscriptionRequest.Channel = wsTickerDataID default: unsubscriptionRequest.Channel = channelToSubscribe.Currency.String() diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 02ed3818..496e1e63 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -238,13 +238,14 @@ func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType asset.Item return tickerPrice, err } - for _, x := range p.GetEnabledPairs(assetType) { + enabledPairs := p.GetEnabledPairs(assetType) + for i := range enabledPairs { var tp ticker.Price - curr := p.FormatExchangeCurrency(x, assetType).String() + curr := p.FormatExchangeCurrency(enabledPairs[i], assetType).String() if _, ok := tick[curr]; !ok { continue } - tp.Pair = x + tp.Pair = enabledPairs[i] tp.Ask = tick[curr].LowestAsk tp.Bid = tick[curr].HighestBid tp.High = tick[curr].High24Hr @@ -287,9 +288,9 @@ func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.I return orderBook, err } - for _, x := range p.GetEnabledPairs(assetType) { - currency := p.FormatExchangeCurrency(x, assetType).String() - data, ok := orderbookNew.Data[currency] + enabledPairs := p.GetEnabledPairs(assetType) + for i := range enabledPairs { + data, ok := orderbookNew.Data[p.FormatExchangeCurrency(enabledPairs[i], assetType).String()] if !ok { continue } @@ -307,7 +308,7 @@ func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.I Amount: data.Asks[y].Amount, Price: data.Asks[y].Price}) } orderBook.Asks = obItems - orderBook.Pair = x + orderBook.Pair = enabledPairs[i] orderBook.ExchangeName = p.Name orderBook.AssetType = assetType @@ -370,13 +371,18 @@ func (p *Poloniex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { false, fillOrKill, isBuyOrder) + if err != nil { + return submitOrderResponse, err + } if response.OrderNumber > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true + + submitOrderResponse.IsOrderPlaced = true + if s.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/websocket/wshandler/wshandler.go b/exchanges/websocket/wshandler/wshandler.go index 27895cbd..be0ee3c2 100644 --- a/exchanges/websocket/wshandler/wshandler.go +++ b/exchanges/websocket/wshandler/wshandler.go @@ -327,6 +327,17 @@ func (w *Websocket) IsConnectionMonitorRunning() bool { return w.connectionMonitorRunning } +// CanUseAuthenticatedWebsocketForWrapper Handles a common check to +// verify whether a wrapper can use an authenticated websocket endpoint +func (w *Websocket) CanUseAuthenticatedWebsocketForWrapper() bool { + if w.IsConnected() && w.CanUseAuthenticatedEndpoints() { + return true + } else if w.IsConnected() && !w.CanUseAuthenticatedEndpoints() { + log.Infof(log.WebsocketMgr, WebsocketNotAuthenticatedUsingRest, w.exchangeName) + } + return false +} + // SetWebsocketURL sets websocket URL func (w *Websocket) SetWebsocketURL(websocketURL string) { if websocketURL == "" || websocketURL == config.WebsocketURLNonDefaultMessage { @@ -629,7 +640,6 @@ func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header } dialer.Proxy = http.ProxyURL(proxy) } - var err error var conStatus *http.Response w.Connection, conStatus, err = dialer.Dial(w.URL, headers) @@ -640,7 +650,7 @@ func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header return fmt.Errorf("%v Error: %v", w.URL, err) } if w.Verbose { - log.Infof(log.WebsocketMgr, "%v Websocket connected", w.ExchangeName) + log.Infof(log.WebsocketMgr, "%v Websocket connected to %s", w.ExchangeName, w.URL) } w.setConnectedStatus(true) return nil @@ -703,6 +713,12 @@ func (w *WebsocketConnection) WaitForResult(id int64, wg *sync.WaitGroup) { for k := range w.IDResponses { if k == id { w.Unlock() + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } return } } diff --git a/exchanges/websocket/wshandler/wshandler_test.go b/exchanges/websocket/wshandler/wshandler_test.go index 25ac23f6..bfb20816 100644 --- a/exchanges/websocket/wshandler/wshandler_test.go +++ b/exchanges/websocket/wshandler/wshandler_test.go @@ -678,3 +678,22 @@ func readMessages(wc *WebsocketConnection, t *testing.T) { } } } + +// TestCanUseAuthenticatedWebsocketForWrapper logic test +func TestCanUseAuthenticatedWebsocketForWrapper(t *testing.T) { + ws := &Websocket{} + resp := ws.CanUseAuthenticatedWebsocketForWrapper() + if resp { + t.Error("Expected false, `connected` is false") + } + ws.setConnectedStatus(true) + resp = ws.CanUseAuthenticatedWebsocketForWrapper() + if resp { + t.Error("Expected false, `connected` is true and `CanUseAuthenticatedEndpoints` is false") + } + ws.canUseAuthenticatedEndpoints = true + resp = ws.CanUseAuthenticatedWebsocketForWrapper() + if !resp { + t.Error("Expected true, `connected` and `CanUseAuthenticatedEndpoints` is true") + } +} diff --git a/exchanges/websocket/wshandler/wshandler_types.go b/exchanges/websocket/wshandler/wshandler_types.go index c4e9b341..22e47266 100644 --- a/exchanges/websocket/wshandler/wshandler_types.go +++ b/exchanges/websocket/wshandler/wshandler_types.go @@ -17,7 +17,8 @@ const ( WebsocketNotEnabled = "exchange_websocket_not_enabled" manageSubscriptionsDelay = 5 * time.Second // connection monitor time delays and limits - connectionMonitorDelay = 2 * time.Second + connectionMonitorDelay = 2 * time.Second + WebsocketNotAuthenticatedUsingRest = "%v - Websocket not authenticated, using REST" ) // Websocket defines a return type for websocket connections via the interface diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index 3307cb71..4a2dafab 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -1,7 +1,9 @@ package yobit import ( + "log" "math" + "os" "testing" "time" @@ -22,19 +24,16 @@ const ( canManipulateRealOrders = false ) -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { y.SetDefaults() -} - -func TestSetup(t *testing.T) { yobitConfig := config.GetConfig() err := yobitConfig.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("Yobit load config error", err) + log.Fatal("Yobit load config error", err) } conf, err := yobitConfig.GetExchangeConfig("Yobit") if err != nil { - t.Fatal("Yobit init error", err) + log.Fatal("Yobit init error", err) } conf.API.Credentials.Key = apiKey conf.API.Credentials.Secret = apiSecret @@ -42,8 +41,9 @@ func TestSetup(t *testing.T) { err = y.Setup(conf) if err != nil { - t.Fatal("Yobit setup error", err) + log.Fatal("Yobit setup error", err) } + os.Exit(m.Run()) } func TestFetchTradablePairs(t *testing.T) { @@ -167,7 +167,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() y.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -179,8 +179,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - y.SetDefaults() - TestSetup(t) var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic @@ -317,20 +315,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - y.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.WithdrawFiatViaWebsiteOnlyText - withdrawPermissions := y.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - y.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, @@ -346,9 +338,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - y.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.LTC, @@ -372,9 +361,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - y.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -400,15 +386,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - y.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -426,15 +408,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - y.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -457,6 +435,9 @@ func TestCancelAllExchangeOrders(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := y.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -464,8 +445,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - y.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -489,9 +468,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - y.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -506,9 +482,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - y.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -523,7 +496,7 @@ func TestWithdrawInternationalBank(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { _, err := y.GetDepositAddress(currency.BTC, "") if err != nil { t.Error("GetDepositAddress() Expected error") diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 400383a4..363f8208 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -187,20 +187,22 @@ func (y *Yobit) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pric return tickerPrice, err } - for _, x := range y.GetEnabledPairs(assetType) { - curr := y.FormatExchangeCurrency(x, assetType).Lower().String() + enabledPairs := y.GetEnabledPairs(assetType) + for i := range enabledPairs { + curr := y.FormatExchangeCurrency(enabledPairs[i], assetType).Lower().String() if _, ok := result[curr]; !ok { continue } + resultCurr := result[curr] var tickerPrice ticker.Price - tickerPrice.Pair = x - tickerPrice.Last = result[curr].Last - tickerPrice.Ask = result[curr].Sell - tickerPrice.Bid = result[curr].Buy - tickerPrice.Last = result[curr].Last - tickerPrice.Low = result[curr].Low - tickerPrice.QuoteVolume = result[curr].VolumeCurrent - tickerPrice.Volume = result[curr].Vol + tickerPrice.Pair = enabledPairs[i] + tickerPrice.Last = resultCurr.Last + tickerPrice.Ask = resultCurr.Sell + tickerPrice.Bid = resultCurr.Buy + tickerPrice.Last = resultCurr.Last + tickerPrice.Low = resultCurr.Low + tickerPrice.QuoteVolume = resultCurr.VolumeCurrent + tickerPrice.Volume = resultCurr.Vol err = ticker.ProcessTicker(y.Name, &tickerPrice, assetType) if err != nil { @@ -323,13 +325,15 @@ func (y *Yobit) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.OrderSide.String(), s.Amount, s.Price) + if err != nil { + return submitOrderResponse, err + } if response > 0 { submitOrderResponse.OrderID = strconv.FormatInt(response, 10) } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + + submitOrderResponse.IsOrderPlaced = true + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index 998d4016..dfb5d457 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -423,10 +423,10 @@ func (z *ZB) Withdraw(currency, address, safepassword string, amount, fees float vals := url.Values{} vals.Set("accesskey", z.API.Credentials.Key) - vals.Set("amount", fmt.Sprintf("%v", amount)) + vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) vals.Set("currency", currency) - vals.Set("fees", fmt.Sprintf("%v", fees)) - vals.Set("itransfer", fmt.Sprintf("%v", itransfer)) + vals.Set("fees", strconv.FormatFloat(fees, 'f', -1, 64)) + vals.Set("itransfer", strconv.FormatBool(itransfer)) vals.Set("method", "withdraw") vals.Set("recieveAddr", address) vals.Set("safePwd", safepassword) diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index c0bf48d4..3182e16e 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -3,7 +3,10 @@ package zb import ( "encoding/json" "fmt" + "log" "net/http" + "os" + "strconv" "testing" "github.com/gorilla/websocket" @@ -25,19 +28,16 @@ const ( var z ZB var wsSetupRan bool -func TestSetDefaults(t *testing.T) { +func TestMain(m *testing.M) { z.SetDefaults() -} - -func TestSetup(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - t.Fatal("ZB load config error", err) + log.Fatal("ZB load config error", err) } zbConfig, err := cfg.GetExchangeConfig("ZB") if err != nil { - t.Fatal("ZB Setup() init error", err) + log.Fatal("ZB Setup() init error", err) } zbConfig.API.AuthenticatedSupport = true zbConfig.API.AuthenticatedWebsocketSupport = true @@ -46,16 +46,16 @@ func TestSetup(t *testing.T) { err = z.Setup(zbConfig) if err != nil { - t.Fatal("ZB setup error", err) + log.Fatal("ZB setup error", err) } + + os.Exit(m.Run()) } func setupWsAuth(t *testing.T) { if wsSetupRan { return } - z.SetDefaults() - TestSetup(t) if !z.Websocket.IsEnabled() && !z.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip(wshandler.WebsocketNotEnabled) } @@ -90,11 +90,9 @@ func TestSpotNewOrder(t *testing.T) { Amount: 0.01, Price: 10246.1, } - orderid, err := z.SpotNewOrder(arg) + _, err := z.SpotNewOrder(arg) if err != nil { t.Errorf("ZB SpotNewOrder: %s", err) - } else { - t.Log(orderid) } } @@ -182,7 +180,7 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() z.GetFeeByType(feeBuilder) - if apiKey == "" || apiSecret == "" { + if !areTestAPIKeysSet() { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -194,8 +192,6 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestGetFee(t *testing.T) { - z.SetDefaults() - TestSetup(t) var feeBuilder = setFeeBuilder() // CryptocurrencyTradeFee Basic @@ -272,20 +268,14 @@ func TestGetFee(t *testing.T) { } func TestFormatWithdrawPermissions(t *testing.T) { - z.SetDefaults() expectedResult := exchange.AutoWithdrawCryptoText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := z.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) } } func TestGetActiveOrders(t *testing.T) { - z.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, Currencies: []currency.Pair{currency.NewPair(currency.XRP, @@ -301,9 +291,6 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { - z.SetDefaults() - TestSetup(t) - var getOrdersRequest = order.GetOrdersRequest{ OrderType: order.AnyType, OrderSide: order.Buy, @@ -326,9 +313,6 @@ func areTestAPIKeysSet() bool { } func TestSubmitOrder(t *testing.T) { - z.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip(fmt.Sprintf("ApiKey: %s. Can place orders: %v", z.API.Credentials.Key, @@ -356,15 +340,11 @@ func TestSubmitOrder(t *testing.T) { } func TestCancelExchangeOrder(t *testing.T) { - z.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.XRP, currency.USDT) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -382,15 +362,11 @@ func TestCancelExchangeOrder(t *testing.T) { } func TestCancelAllExchangeOrders(t *testing.T) { - z.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } currencyPair := currency.NewPair(currency.XRP, currency.USDT) - var orderCancellation = &order.Cancel{ OrderID: "1", WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB", @@ -427,6 +403,9 @@ func TestGetAccountInfo(t *testing.T) { } func TestModifyOrder(t *testing.T) { + if areTestAPIKeysSet() && !canManipulateRealOrders { + t.Skip("API keys set, canManipulateRealOrders false, skipping test") + } _, err := z.ModifyOrder(&order.Modify{}) if err == nil { t.Error("ModifyOrder() Expected error") @@ -434,8 +413,6 @@ func TestModifyOrder(t *testing.T) { } func TestWithdraw(t *testing.T) { - z.SetDefaults() - TestSetup(t) withdrawCryptoRequest := exchange.CryptoWithdrawRequest{ GenericWithdrawRequestInfo: exchange.GenericWithdrawRequestInfo{ Amount: -1, @@ -460,9 +437,6 @@ func TestWithdraw(t *testing.T) { } func TestWithdrawFiat(t *testing.T) { - z.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -475,9 +449,6 @@ func TestWithdrawFiat(t *testing.T) { } func TestWithdrawInternationalBank(t *testing.T) { - z.SetDefaults() - TestSetup(t) - if areTestAPIKeysSet() && !canManipulateRealOrders { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } @@ -490,7 +461,7 @@ func TestWithdrawInternationalBank(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { - if apiKey != "" || apiSecret != "" { + if areTestAPIKeysSet() { _, err := z.GetDepositAddress(currency.BTC, "") if err != nil { t.Error("GetDepositAddress() error PLEASE MAKE SURE YOU CREATE DEPOSIT ADDRESSES VIA ZB.COM", @@ -548,7 +519,7 @@ func TestWsCreateSuUserKey(t *testing.T) { t.Fatal(err) } userID := subUsers.Message[0].UserID - _, err = z.wsCreateSubUserKey(true, true, true, true, "subu", fmt.Sprintf("%v", userID)) + _, err = z.wsCreateSubUserKey(true, true, true, true, "subu", strconv.FormatInt(userID, 10)) if err != nil { t.Fatal(err) } diff --git a/exchanges/zb/zb_types.go b/exchanges/zb/zb_types.go index 6953e841..2dc9308a 100644 --- a/exchanges/zb/zb_types.go +++ b/exchanges/zb/zb_types.go @@ -16,7 +16,7 @@ type OrderbookResponse struct { // AccountsResponseCoin holds the accounts coin details type AccountsResponseCoin struct { - Freez string `json:"freez"` // 冻结资产 + Freeze string `json:"freez"` // 冻结资产 EnName string `json:"enName"` // 币种英文名 UnitDecimal int `json:"unitDecimal"` // 保留小数位 UnName string `json:"cnName"` // 币种中文名 @@ -44,6 +44,9 @@ type Order struct { TradeDate int `json:"trade_date"` TradeMoney float64 `json:"trade_money"` Type int64 `json:"type"` + Fees float64 `json:"fees,omitempty"` + TradePrice float64 `json:"trade_price,omitempty"` + No int64 `json:"no,string,omitempty"` } // AccountsResponse 用户基本信息 diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 4560f582..b4d31804 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -187,40 +187,6 @@ func (z *ZB) WsHandleData() { } } -var wsErrCodes = map[int64]string{ - 1000: "Successful call", - 1001: "General error message", - 1002: "internal error", - 1003: "Verification failed", - 1004: "Financial security password lock", - 1005: "The fund security password is incorrect. Please confirm and re-enter.", - 1006: "Real-name certification is awaiting review or review", - 1007: "Channel is empty", - 1008: "Event is empty", - 1009: "This interface is being maintained", - 1011: "Not open yet", - 1012: "Insufficient permissions", - 1013: "Can not trade, if you have any questions, please contact online customer service", - 1014: "Cannot be sold during the pre-sale period", - 2002: "Insufficient balance in Bitcoin account", - 2003: "Insufficient balance of Litecoin account", - 2005: "Insufficient balance in Ethereum account", - 2006: "Insufficient balance in ETC currency account", - 2007: "Insufficient balance of BTS currency account", - 2008: "Insufficient balance in EOS currency account", - 2009: "Insufficient account balance", - 3001: "Pending order not found", - 3002: "Invalid amount", - 3003: "Invalid quantity", - 3004: "User does not exist", - 3005: "Invalid parameter", - 3006: "Invalid IP or inconsistent with the bound IP", - 3007: "Request time has expired", - 3008: "Transaction history not found", - 4001: "API interface is locked", - 4002: "Request too frequently", -} - // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (z *ZB) GenerateDefaultSubscriptions() { var subscriptions []wshandler.WebsocketChannelSubscription diff --git a/exchanges/zb/zb_websocket_types.go b/exchanges/zb/zb_websocket_types.go index 983c92aa..29275f48 100644 --- a/exchanges/zb/zb_websocket_types.go +++ b/exchanges/zb/zb_websocket_types.go @@ -191,28 +191,12 @@ type WsGetOrderRequest struct { // WsGetOrderResponse contains order data type WsGetOrderResponse struct { - Message string `json:"message"` - No int64 `json:"no,string"` - Code int64 `json:"code"` - Channel string `json:"channel"` - Success bool `json:"success"` - Data WsGetOrderResponseData `json:"data"` -} - -// WsGetOrderResponseData Detailed order data -type WsGetOrderResponseData struct { - Currency string `json:"currency"` - Fees float64 `json:"fees"` - ID string `json:"id"` - Price float64 `json:"price"` - Status int64 `json:"status"` - TotalAmount float64 `json:"total_amount"` - TradeAmount float64 `json:"trade_amount"` - TradePrice float64 `json:"trade_price"` - TradeDate int64 `json:"trade_date"` - TradeMoney float64 `json:"trade_money"` - Type int64 `json:"type"` - No int64 `json:"no,string"` + Message string `json:"message"` + No int64 `json:"no,string"` + Code int64 `json:"code"` + Channel string `json:"channel"` + Success bool `json:"success"` + Data []Order `json:"data"` } // WsGetOrdersRequest get more orders, with no orderID filtering @@ -228,12 +212,12 @@ type WsGetOrdersRequest struct { // WsGetOrdersResponse contains orders data type WsGetOrdersResponse struct { - Message string `json:"message"` - No int64 `json:"no,string"` - Code int64 `json:"code"` - Channel string `json:"channel"` - Success bool `json:"success"` - Data []WsGetOrderResponseData `json:"data"` + Message string `json:"message"` + No int64 `json:"no,string"` + Code int64 `json:"code"` + Channel string `json:"channel"` + Success bool `json:"success"` + Data []Order `json:"data"` } // WsGetOrdersIgnoreTradeTypeRequest ws request @@ -249,12 +233,12 @@ type WsGetOrdersIgnoreTradeTypeRequest struct { // WsGetOrdersIgnoreTradeTypeResponse contains orders data type WsGetOrdersIgnoreTradeTypeResponse struct { - Message string `json:"message"` - No int64 `json:"no,string"` - Code int64 `json:"code"` - Channel string `json:"channel"` - Success bool `json:"success"` - Data []WsGetOrderResponseData `json:"data"` + Message string `json:"message"` + No int64 `json:"no,string"` + Code int64 `json:"code"` + Channel string `json:"channel"` + Success bool `json:"success"` + Data []Order `json:"data"` } // WsGetAccountInfoResponse contains account data @@ -262,23 +246,44 @@ type WsGetAccountInfoResponse struct { Message string `json:"message"` No int64 `json:"no,string"` Data struct { - Coins []struct { - Freez float64 `json:"freez,string"` - EnName string `json:"enName"` - UnitDecimal int64 `json:"unitDecimal"` - CnName string `json:"cnName"` - UnitTag string `json:"unitTag"` - Available float64 `json:"available,string"` - Key string `json:"key"` - } `json:"coins"` - Base struct { - Username string `json:"username"` - TradePasswordEnabled bool `json:"trade_password_enabled"` - AuthGoogleEnabled bool `json:"auth_google_enabled"` - AuthMobileEnabled bool `json:"auth_mobile_enabled"` - } `json:"base"` + Coins []AccountsResponseCoin `json:"coins"` + Base AccountsBaseResponse `json:"base"` } `json:"data"` Code int64 `json:"code"` Channel string `json:"channel"` Success bool `json:"success"` } + +var wsErrCodes = map[int64]string{ + 1000: "Successful call", + 1001: "General error message", + 1002: "internal error", + 1003: "Verification failed", + 1004: "Financial security password lock", + 1005: "The fund security password is incorrect. Please confirm and re-enter.", + 1006: "Real-name certification is awaiting review or review", + 1007: "Channel is empty", + 1008: "Event is empty", + 1009: "This interface is being maintained", + 1011: "Not open yet", + 1012: "Insufficient permissions", + 1013: "Can not trade, if you have any questions, please contact online customer service", + 1014: "Cannot be sold during the pre-sale period", + 2002: "Insufficient balance in Bitcoin account", + 2003: "Insufficient balance of Litecoin account", + 2005: "Insufficient balance in Ethereum account", + 2006: "Insufficient balance in ETC currency account", + 2007: "Insufficient balance of BTS currency account", + 2008: "Insufficient balance in EOS currency account", + 2009: "Insufficient account balance", + 3001: "Pending order not found", + 3002: "Invalid amount", + 3003: "Invalid quantity", + 3004: "User does not exist", + 3005: "Invalid parameter", + 3006: "Invalid IP or inconsistent with the bound IP", + 3007: "Request time has expired", + 3008: "Transaction history not found", + 4001: "API interface is locked", + 4002: "Request too frequently", +} diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 2f9608e9..eac00ae5 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -2,6 +2,7 @@ package zb import ( "errors" + "fmt" "strconv" "strings" "sync" @@ -268,8 +269,9 @@ func (z *ZB) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Ba // UpdateOrderbook updates and returns the orderbook for a currency pair func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := z.GetOrderbook(z.FormatExchangeCurrency(p, - assetType).String()) + curr := z.FormatExchangeCurrency(p, assetType).String() + + orderbookNew, err := z.GetOrderbook(curr) if err != nil { return orderBook, err } @@ -304,25 +306,35 @@ func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.B // ZB exchange func (z *ZB) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - bal, err := z.GetAccountInformation() - if err != nil { - return info, err + var balances []exchange.AccountCurrencyInfo + var coins []AccountsResponseCoin + if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + resp, err := z.wsGetAccountInfoRequest() + if err != nil { + return info, err + } + coins = resp.Data.Coins + } else { + bal, err := z.GetAccountInformation() + if err != nil { + return info, err + } + coins = bal.Result.Coins } - var balances []exchange.AccountCurrencyInfo - for _, data := range bal.Result.Coins { - hold, err := strconv.ParseFloat(data.Freez, 64) + for i := range coins { + hold, err := strconv.ParseFloat(coins[i].Freeze, 64) if err != nil { return info, err } - avail, err := strconv.ParseFloat(data.Available, 64) + avail, err := strconv.ParseFloat(coins[i].Available, 64) if err != nil { return info, err } balances = append(balances, exchange.AccountCurrencyInfo{ - CurrencyName: currency.NewCode(data.EnName), + CurrencyName: currency.NewCode(coins[i].EnName), TotalValue: hold + avail, Hold: hold, }) @@ -348,33 +360,53 @@ func (z *ZB) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchan } // SubmitOrder submits a new order -func (z *ZB) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { +func (z *ZB) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { var submitOrderResponse order.SubmitResponse - if err := s.Validate(); err != nil { + err := o.Validate() + if err != nil { return submitOrderResponse, err } - - var oT SpotNewOrderRequestParamsType - if s.OrderSide == order.Buy { - oT = SpotNewOrderRequestParamsTypeBuy + if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var isBuyOrder int64 + if o.OrderSide == order.Buy { + isBuyOrder = 1 + } else { + isBuyOrder = 0 + } + var response *WsSubmitOrderResponse + response, err = z.wsSubmitOrder(o.Pair, o.Amount, o.Price, isBuyOrder) + if err != nil { + return submitOrderResponse, err + } + submitOrderResponse.OrderID = strconv.FormatInt(response.Data.EntrustID, 10) } else { - oT = SpotNewOrderRequestParamsTypeSell - } + var oT SpotNewOrderRequestParamsType + if o.OrderSide == order.Buy { + oT = SpotNewOrderRequestParamsTypeBuy + } else { + oT = SpotNewOrderRequestParamsTypeSell + } - var params = SpotNewOrderRequestParams{ - Amount: s.Amount, - Price: s.Price, - Symbol: s.Pair.Lower().String(), - Type: oT, + var params = SpotNewOrderRequestParams{ + Amount: o.Amount, + Price: o.Price, + Symbol: o.Pair.Lower().String(), + Type: oT, + } + var response int64 + response, err = z.SpotNewOrder(params) + if err != nil { + return submitOrderResponse, err + } + if response > 0 { + submitOrderResponse.OrderID = strconv.FormatInt(response, 10) + } } - response, err := z.SpotNewOrder(params) - if response > 0 { - submitOrderResponse.OrderID = strconv.FormatInt(response, 10) + submitOrderResponse.IsOrderPlaced = true + if o.OrderType == order.Market { + submitOrderResponse.FullyMatched = true } - if err == nil { - submitOrderResponse.IsOrderPlaced = true - } - return submitOrderResponse, err + return submitOrderResponse, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -389,8 +421,20 @@ func (z *ZB) CancelOrder(o *order.Cancel) error { if err != nil { return err } - curr := z.FormatExchangeCurrency(o.CurrencyPair, o.AssetType).String() - return z.CancelExistingOrder(orderIDInt, curr) + + if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + var response *WsCancelOrderResponse + response, err = z.wsCancelOrder(o.CurrencyPair, orderIDInt) + if err != nil { + return err + } + if !response.Success { + return fmt.Errorf("%v - Could not cancel order %v", z.Name, o.OrderID) + } + return nil + } + return z.CancelExistingOrder(orderIDInt, z.FormatExchangeCurrency(o.CurrencyPair, + o.AssetType).String()) } // CancelAllOrders cancels all orders associated with a currency pair @@ -424,11 +468,12 @@ func (z *ZB) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { } for i := range allOpenOrders { - err := z.CancelExistingOrder(allOpenOrders[i].ID, - allOpenOrders[i].Currency) + err := z.CancelOrder(&order.Cancel{ + OrderID: strconv.FormatInt(allOpenOrders[i].ID, 10), + CurrencyPair: currency.NewPairFromString(allOpenOrders[i].Currency), + }) if err != nil { - ID := strconv.FormatInt(allOpenOrders[i].ID, 10) - cancelAllOrdersResponse.Status[ID] = err.Error() + cancelAllOrdersResponse.Status[strconv.FormatInt(allOpenOrders[i].ID, 10)] = err.Error() } } @@ -539,35 +584,45 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error if req.OrderSide == order.AnySide || req.OrderSide == "" { return nil, errors.New("specific order side is required") } - var allOrders []Order - + var orders []order.Detail var side int64 - if req.OrderSide == order.Buy { - side = 1 - } - for x := range req.Currencies { - for y := int64(1); ; y++ { - fPair := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() - resp, err := z.GetOrders(fPair, y, side) - if err != nil { - return nil, err + if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + for x := range req.Currencies { + for y := int64(1); ; y++ { + resp, err := z.wsGetOrdersIgnoreTradeType(req.Currencies[x], y, 10) + if err != nil { + return nil, err + } + allOrders = append(allOrders, resp.Data...) + if len(resp.Data) != 10 { + break + } } - - if len(resp) == 0 { - break - } - - allOrders = append(allOrders, resp...) - - if len(resp) != 10 { - break + } + } else { + if req.OrderSide == order.Buy { + side = 1 + } + for x := range req.Currencies { + for y := int64(1); ; y++ { + fPair := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String() + resp, err := z.GetOrders(fPair, y, side) + if err != nil { + return nil, err + } + if len(resp) == 0 { + break + } + allOrders = append(allOrders, resp...) + if len(resp) != 10 { + break + } } } } - var orders []order.Detail for i := range allOrders { symbol := currency.NewPairDelimiter(allOrders[i].Currency, z.GetPairFormat(asset.Spot, false).Delimiter) From 17a786536d216fdf8d7fe224efc3d1252c3c5a81 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 4 Dec 2019 16:25:55 +1100 Subject: [PATCH 70/71] Engine pre-merge changes (#392) * Engine pre merge changes * Remove redundant "seconds" --- .gitignore | 1 + config/config.go | 20 - config/config_test.go | 60 +- config_example.json | 2360 ++++++++++++++++++++++---------- engine/syncer.go | 8 +- main.go | 2 +- testdata/configtest.json | 2344 ++++++++++++++++++++++--------- testdata/preengine_config.json | 1469 ++++++++++++++++++++ 8 files changed, 4830 insertions(+), 1434 deletions(-) create mode 100644 testdata/preengine_config.json diff --git a/.gitignore b/.gitignore index ba7a3bae..09c57015 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ lib .vscode testdata/dump +testdata/preengine_config.json testdata/writefiletest # InteliJ diff --git a/config/config.go b/config/config.go index 88db3703..94892c1f 100644 --- a/config/config.go +++ b/config/config.go @@ -665,26 +665,6 @@ func (c *Config) CountEnabledExchanges() int { return counter } -// GetConfigCurrencyPairFormat returns the config currency pair format -// for a specific exchange -func (c *Config) GetConfigCurrencyPairFormat(exchName string) (*currency.PairFormat, error) { - exchCfg, err := c.GetExchangeConfig(exchName) - if err != nil { - return nil, err - } - return exchCfg.ConfigCurrencyPairFormat, nil -} - -// GetRequestCurrencyPairFormat returns the request currency pair format -// for a specific exchange -func (c *Config) GetRequestCurrencyPairFormat(exchName string) (*currency.PairFormat, error) { - exchCfg, err := c.GetExchangeConfig(exchName) - if err != nil { - return nil, err - } - return exchCfg.RequestCurrencyPairFormat, nil -} - // GetCurrencyPairDisplayConfig retrieves the currency pair display preference func (c *Config) GetCurrencyPairDisplayConfig() *CurrencyPairFormatConfig { return c.Currency.CurrencyPairFormat diff --git a/config/config_test.go b/config/config_test.go index c71f40c5..45a0409a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1052,59 +1052,6 @@ func TestCountEnabledExchanges(t *testing.T) { } } -func TestGetConfigCurrencyPairFormat(t *testing.T) { - cfg := GetConfig() - err := cfg.LoadConfig(TestFile, true) - if err != nil { - t.Errorf( - "TestGetConfigCurrencyPairFormat. LoadConfig Error: %s", err.Error(), - ) - } - _, err = cfg.GetConfigCurrencyPairFormat("asdasdasd") - if err == nil { - t.Errorf( - "TestGetRequestCurrencyPairFormat. Non-existent exchange returned nil error", - ) - } - - exchFmt, err := cfg.GetConfigCurrencyPairFormat("Yobit") - if err != nil { - t.Errorf("TestGetConfigCurrencyPairFormat err: %s", err) - } - if !exchFmt.Uppercase || exchFmt.Delimiter != "_" { - t.Errorf( - "TestGetConfigCurrencyPairFormat. Invalid values", - ) - } -} - -func TestGetRequestCurrencyPairFormat(t *testing.T) { - cfg := GetConfig() - err := cfg.LoadConfig(TestFile, true) - if err != nil { - t.Errorf( - "TestGetRequestCurrencyPairFormat. LoadConfig Error: %s", err.Error(), - ) - } - - _, err = cfg.GetRequestCurrencyPairFormat("asdasdasd") - if err == nil { - t.Errorf( - "TestGetRequestCurrencyPairFormat. Non-existent exchange returned nil error", - ) - } - - exchFmt, err := cfg.GetRequestCurrencyPairFormat("Yobit") - if err != nil { - t.Errorf("TestGetRequestCurrencyPairFormat. Err: %s", err) - } - if exchFmt.Uppercase || exchFmt.Delimiter != "_" || exchFmt.Separator != "-" { - t.Errorf( - "TestGetRequestCurrencyPairFormat. Invalid values", - ) - } -} - func TestGetCurrencyPairDisplayConfig(t *testing.T) { cfg := GetConfig() err := cfg.LoadConfig(TestFile, true) @@ -1929,3 +1876,10 @@ func TestCheckCurrencyConfigValues(t *testing.T) { t.Error("Failed to set CryptocurrencyProvider.APIkey and AccountPlan") } } + +func TestPreengineConfigUpgrade(t *testing.T) { + var c Config + if err := c.LoadConfig("../testdata/preengine_config.json", false); err != nil { + t.Fatal(err) + } +} diff --git a/config_example.json b/config_example.json index 2506e16f..ee3b31ca 100644 --- a/config_example.json +++ b/config_example.json @@ -4,14 +4,15 @@ "globalHTTPTimeout": 15000000000, "database": { "enabled": false, + "verbose": false, "driver": "sqlite", "connectionDetails": { - "Host": "", - "Port": 0, - "Username": "", - "Password": "", - "Database": "", - "SSLMode": "" + "host": "", + "port": 0, + "username": "", + "password": "", + "database": "", + "sslmode": "" } }, "logging": { @@ -19,10 +20,10 @@ "level": "INFO|WARN|DEBUG|ERROR", "output": "console", "fileSettings": { - "filename": "log.txt", - "rotate": true, - "maxsize": 250 - }, + "filename": "log.txt", + "rotate": true, + "maxsize": 250 + }, "advancedSettings": { "spacer": " | ", "timeStampFormat": "02/01/2006 15:04:05", @@ -32,8 +33,21 @@ "debug": "[DEBUG]", "error": "[ERROR]" } - }, - "subloggers": [] + } + }, + "connectionMonitor": { + "preferredDNSList": [ + "8.8.8.8", + "8.8.4.4", + "1.1.1.1", + "1.0.0.1" + ], + "preferredDomainList": [ + "www.google.com", + "www.cloudflare.com", + "www.facebook.com" + ], + "checkInterval": 1000000000 }, "profiler": { "enabled": false @@ -120,6 +134,7 @@ }, "smsGlobal": { "name": "SMSGlobal", + "from": "Skynet", "enabled": false, "verbose": false, "username": "Username", @@ -140,6 +155,7 @@ "port": "537", "accountName": "some", "accountPassword": "password", + "from": "", "recipientList": "lol123@gmail.com" }, "telegram": { @@ -149,8 +165,29 @@ "verificationToken": "testest" } }, + "remoteControl": { + "username": "admin", + "password": "Password", + "gRPC": { + "enabled": true, + "listenAddress": "localhost:9052", + "grpcProxyEnabled": false, + "grpcProxyListenAddress": "localhost:9053" + }, + "deprecatedRPC": { + "enabled": true, + "listenAddress": "localhost:9050" + }, + "websocketRPC": { + "enabled": true, + "listenAddress": "localhost:9051", + "connectionLimit": 1, + "maxAuthFailures": 3, + "allowInsecureOrigin": true + } + }, "portfolioAddresses": { - "Addresses": [ + "addresses": [ { "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", "CoinType": "BTC", @@ -177,53 +214,76 @@ } ] }, - "webserver": { - "enabled": true, - "adminUsername": "admin", - "adminPassword": "Password", - "listenAddress": ":9050", - "websocketConnectionLimit": 1, - "websocketMaxAuthFailures": 3, - "websocketAllowInsecureOrigin": true - }, "exchanges": [ { "name": "ANX", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "ATENC_GBP,ATENC_NZD,BTC_AUD,BTC_SGD,LTC_BTC,START_GBP,STR_BTC,XRP_BTC,ATENC_SGD,BTC_GBP,DOGE_BTC,OAX_ETH,START_AUD,START_JPY,ATENC_USD,BTC_EUR,GNT_ETH,START_EUR,ATENC_EUR,BTC_CAD,START_BTC,START_CAD,ATENC_HKD,ATENC_JPY,ETH_BTC,ETH_HKD,START_HKD,START_USD,ATENC_AUD,ETH_USD,START_SGD,ATENC_CAD,BTC_HKD,BTC_JPY,BTC_NZD,BTC_USD,START_NZD", - "enabledPairs": "BTC_USD,BTC_HKD,BTC_EUR,BTC_CAD,BTC_AUD,BTC_SGD,BTC_JPY,BTC_GBP,BTC_NZD,LTC_BTC,STR_BTC,XRP_BTC", "baseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USD,BTC_HKD,BTC_EUR,BTC_CAD,BTC_AUD,BTC_SGD,BTC_JPY,BTC_GBP,BTC_NZD,LTC_BTC,STR_BTC,XRP_BTC", + "available": "ATENC_GBP,ATENC_NZD,BTC_AUD,BTC_SGD,LTC_BTC,START_GBP,STR_BTC,XRP_BTC,ATENC_SGD,BTC_GBP,DOGE_BTC,OAX_ETH,START_AUD,START_JPY,ATENC_USD,BTC_EUR,GNT_ETH,START_EUR,ATENC_EUR,BTC_CAD,START_BTC,START_CAD,ATENC_HKD,ATENC_JPY,ETH_BTC,ETH_HKD,START_HKD,START_USD,ATENC_AUD,ETH_USD,START_SGD,ATENC_CAD,BTC_HKD,BTC_JPY,BTC_NZD,BTC_USD,START_NZD" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -236,39 +296,71 @@ "name": "Binance", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,SNGLS-ETH,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,BTS-BNB,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,FUEL-ETH,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADX-BNB,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,NAV-ETH,NAV-BNB,LUN-BTC,LUN-ETH,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,NCASH-BNB,POA-BTC,POA-ETH,POA-BNB,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,GNT-BNB,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,REP-BNB,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,CVC-BNB,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-BTC,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-BTC,DENT-ETH,ARDR-BTC,ARDR-ETH,ARDR-BNB,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BCHABC-BTC,BCHABC-USDT,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-PAX,WAVES-USDC,BCHABC-TUSD,BCHABC-PAX,BCHABC-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BTC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-PAX,ATOM-TUSD,ETC-USDC,ETC-PAX,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-USDC,PHB-TUSD,PHB-PAX,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,TFUEL-USDC,TFUEL-TUSD,TFUEL-PAX,ONE-BNB,ONE-BTC,ONE-USDT,ONE-TUSD,ONE-PAX,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-TUSD,FTM-PAX,FTM-USDC,BTCB-BTC,BCPT-TUSD,BCPT-PAX,BCPT-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,USDSB-USDT,USDSB-USDS,GTO-USDT,GTO-PAX,GTO-TUSD,GTO-USDC,ERD-BNB,ERD-BTC,ERD-USDT,ERD-PAX,ERD-USDC,DOGE-BNB,DOGE-BTC,DOGE-USDT,DOGE-PAX,DOGE-USDC,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ANKR-TUSD,ANKR-PAX,ANKR-USDC,ONT-PAX,ONT-USDC,WIN-BNB,WIN-BTC,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,TUSDB-TUSD,NPXS-USDT,NPXS-USDC,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC", - "enabledPairs": "BTC-USDT,ETH-USDT,LTC-USDT,ADA-USDT,XRP-USDT", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USDT,ETH-USDT,LTC-USDT,ADA-USDT,XRP-USDT", + "available": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADX-BNB,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,LUN-BTC,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,POA-BTC,POA-ETH,POA-BNB,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,GNT-BNB,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-ETH,ARDR-BTC,ARDR-ETH,ARDR-BNB,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-TUSD,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-TUSD,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,ONE-BNB,ONE-BTC,ONE-USDT,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,GTO-USDT,ERD-BNB,ERD-BTC,ERD-USDT,DOGE-BNB,DOGE-BTC,DOGE-USDT,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ONT-PAX,ONT-USDC,WIN-BNB,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,NPXS-USDT,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC,PERL-BNB,PERL-BTC,PERL-USDC,PERL-USDT,DENT-USDT,MFT-USDT,KEY-USDT,STORM-USDT,DOCK-USDT,WAN-USDT,FUN-USDT,CVC-USDT,BTT-TRX,WIN-TRX,CHZ-BNB,CHZ-BTC,CHZ-USDT,BAND-BNB,BAND-BTC,BAND-USDT,BNB-BUSD,BTC-BUSD,BUSD-USDT,BEAM-BNB,BEAM-BTC,BEAM-USDT,XTZ-BNB,XTZ-BTC,XTZ-USDT,REN-USDT,RVN-USDT,HC-USDT,HBAR-BNB,HBAR-BTC,HBAR-USDT,NKN-BNB,NKN-BTC,NKN-USDT,XRP-BUSD,ETH-BUSD,LTC-BUSD,LINK-BUSD,ETC-BUSD,STX-BNB,STX-BTC,STX-USDT,KAVA-BNB,KAVA-BTC,KAVA-USDT,BUSD-NGN,BNB-NGN,BTC-NGN,ARPA-BNB,ARPA-BTC,ARPA-USDT,TRX-BUSD,EOS-BUSD,IOTX-USDT,RLC-USDT,MCO-USDT,XLM-BUSD,ADA-BUSD,CTXC-BNB,CTXC-BTC,CTXC-USDT,BCH-BNB,BCH-BTC,BCH-USDT,BCH-USDC,BCH-TUSD,BCH-PAX,BCH-BUSD,BTC-RUB,ETH-RUB,XRP-RUB,BNB-RUB" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -281,38 +373,70 @@ "name": "Bitfinex", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,BTCF0:USTF0,ETHF0:USTF0", - "enabledPairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", + "available": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,PAXUST,UDCUST,TSDUST,BTC:CNHT,UST:CNHT,CNH:CNHT,CHZUSD,CHZUST,BTCF0:USTF0,ETHF0:USTF0" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -325,41 +449,73 @@ "name": "Bitflyer", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC", - "enabledPairs": "BTC_JPY,ETH_BTC,BCH_BTC", "baseCurrencies": "JPY", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": false, - "pairsLastUpdated": 1566798411, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot", + "futures" + ], + "pairs": { + "spot": { + "enabled": "BTC_JPY,ETH_BTC,BCH_BTC", + "available": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -372,40 +528,72 @@ "name": "Bithumb", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "VETKRW,REPKRW,ARNKRW,OCNKRW,ETHOSKRW,STEEMKRW,LRCKRW,ETCKRW,CMTKRW,HDACKRW,WTCKRW,PLYKRW,QTUMKRW,MCOKRW,NPXSKRW,ABTKRW,BSVKRW,SNTKRW,STRATKRW,BATKRW,ETHKRW,CTXCKRW,AUTOKRW,HYCKRW,POLYKRW,QKCKRW,TMTGKRW,BCHKRW,MXCKRW,XEMKRW,GTOKRW,BTTKRW,APISKRW,DACKRW,ELFKRW,XLMKRW,DACCKRW,GNTKRW,EOSKRW,TRXKRW,BZNTKRW,ETZKRW,XRPKRW,WAVESKRW,WETKRW,HCKRW,XMRKRW,PPTKRW,LOOMKRW,KNCKRW,MIXKRW,RDNKRW,ADAKRW,ENJKRW,ZRXKRW,DASHKRW,PIVXKRW,THETAKRW,VALORKRW,BHPKRW,OMGKRW,RNTKRW,GXCKRW,AMOKRW,CROKRW,LAMBKRW,LINKKRW,ROMKRW,ZILKRW,ORBSKRW,POWRKRW,INSKRW,CONKRW,XVGKRW,BCDKRW,ICXKRW,BTCKRW,BTGKRW,LBAKRW,MTLKRW,MITHKRW,PAYKRW,WAXKRW,ANKRKRW,IOSTKRW,AEKRW,LTCKRW,ITCKRW,SALTKRW,ZECKRW,TRUEKRW,PSTKRW", - "enabledPairs": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,XMRKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", "baseCurrencies": "KRW", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "index": "KRW" + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "index": "KRW" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,XMRKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", + "available": "POWRKRW,CMTKRW,ABTKRW,WICCKRW,ADAKRW,ITCKRW,ETHKRW,XMRKRW,BATKRW,MIXKRW,BTGKRW,DVPKRW,XRPKRW,LOOMKRW,BCDKRW,ETZKRW,KNCKRW,CHRKRW,OGOKRW,DASHKRW,CROKRW,TRUEKRW,LAMBKRW,ANKRKRW,STRATKRW,HDACKRW,VALORKRW,PCMKRW,ZECKRW,PAYKRW,INSKRW,FNBKRW,XLMKRW,BSVKRW,BZNTKRW,REPKRW,TRXKRW,AEKRW,ZILKRW,THETAKRW,QKCKRW,GNTKRW,WTCKRW,BTCKRW,ORBSKRW,WOMKRW,FABKRW,WETKRW,ELFKRW,AMOKRW,OMGKRW,POLYKRW,IOSTKRW,HCKRW,PIVXKRW,BCHKRW,AUTOKRW,BHPKRW,ICXKRW,AOAKRW,ETHOSKRW,FCTKRW,NPXSKRW,WAXPKRW,ENJKRW,WAVESKRW,FXKRW,OCNKRW,ARNKRW,MTLKRW,ZRXKRW,QTUMKRW,LRCKRW,APISKRW,MXCKRW,PLYKRW,STEEMKRW,SNTKRW,RNTKRW,EOSKRW,DADKRW,XVGKRW,SALTKRW,TMTGKRW,XSRKRW,CTXCKRW,LBAKRW,PPTKRW,LINKKRW,MCOKRW,FZZKRW,GXCKRW,VETKRW,DACKRW,CONKRW,MITHKRW,BTTKRW,XEMKRW,ETCKRW,HYCKRW,DACCKRW,TRVKRW,LTCKRW,RDNKRW,ROMKRW,PSTKRW,GTOKRW" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -418,38 +606,106 @@ "name": "Bitmex", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "XRPU19,BCHU19,ADAU19,EOSU19,TRXU19,XBTUSD,XBT7D_U105,XBT7D_D95,XBTU19,XBTZ19,ETHUSD,ETHU19,LTCU19", - "enabledPairs": "XBTUSD", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "assetTypes": [ + "perpetualcontract", + "futures", + "downsideprofitcontract", + "upsideprofitcontract" + ], + "pairs": { + "downsideprofitcontract": { + "available": "XBT7D_D95", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "futures": { + "available": "XRPZ19,BCHZ19,ADAZ19,EOSZ19,TRXZ19,XBTZ19,ETHZ19,LTCZ19", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + } + }, + "perpetualcontract": { + "available": "XBTUSD,ETHUSD", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + } + }, + "spot": { + "enabled": "XBTUSD", + "available": "XRPU19,BCHU19,ADAU19,EOSU19,TRXU19,XBTUSD,XBT7D_U105,XBT7D_D95,XBTU19,XBTZ19,ETHUSD,ETHU19,LTCU19" + }, + "upsideprofitcontract": { + "available": "XBT7D_U105", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -462,39 +718,71 @@ "name": "Bitstamp", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR", - "enabledPairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", "baseCurrencies": "USD,EUR", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", + "available": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -507,40 +795,72 @@ "name": "Bittrex", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "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-GLC,BTC-GRS,BTC-NLG,BTC-MONA,BTC-VRC,BTC-CURE,BTC-XMR,BTC-XDN,BTC-NAV,BTC-XST,BTC-VIA,BTC-PINK,BTC-IOC,BTC-SYS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-BLOCK,BTC-BTS,BTC-XRP,BTC-GAME,BTC-NXS,BTC-GEO,BTC-FLDC,BTC-FLO,BTC-MUE,BTC-XEM,BTC-SPHR,BTC-OK,BTC-AEON,BTC-ETH,BTC-EXP,BTC-AMP,BTC-XLM,USDT-BTC,BTC-RVR,BTC-FCT,BTC-MAID,BTC-SLS,BTC-RADS,BTC-DCR,BTC-XVG,BTC-PIVX,BTC-MEME,BTC-STEEM,BTC-LSK,BTC-DGD,BTC-WAVES,BTC-LBC,BTC-SBD,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-IOP,BTC-UBQ,BTC-KMD,BTC-SIB,BTC-ION,BTC-CRW,BTC-SWT,BTC-MLN,BTC-ARK,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-EDG,BTC-MORE,ETH-GNT,ETH-REP,USDT-ETH,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,ETH-GNO,BTC-HMQ,BTC-ANT,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,BTC-PTOY,BTC-BNT,ETH-BNT,BTC-NMR,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,BTC-FUN,BTC-PAY,ETH-PAY,BTC-MTL,BTC-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-BCH,USDT-BCH,BTC-BCH,BTC-DNT,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,USDT-OMG,BTC-ADA,BTC-MANA,ETH-MANA,BTC-RCN,BTC-VIB,ETH-VIB,BTC-MER,BTC-POWR,ETH-POWR,ETH-ADA,BTC-ENG,ETH-ENG,USDT-ADA,USDT-XVG,USDT-NXT,BTC-UKG,ETH-UKG,BTC-IGNIS,BTC-SRN,ETH-SRN,BTC-WAX,ETH-WAX,BTC-ZRX,ETH-ZRX,BTC-VEE,BTC-BCPT,BTC-TRX,ETH-TRX,BTC-TUSD,BTC-LRC,ETH-TUSD,BTC-UP,BTC-DMT,ETH-DMT,USDT-TUSD,BTC-POLY,ETH-POLY,BTC-PRO,USDT-SC,USDT-TRX,BTC-BLT,BTC-STORM,ETH-STORM,BTC-AID,BTC-NGC,BTC-GTO,USDT-DCR,BTC-OCN,ETH-OCN,USD-BTC,USD-USDT,USD-TUSD,BTC-TUBE,BTC-CMCT,USD-ETH,BTC-NLC2,BTC-BKX,BTC-MFT,BTC-LOOM,BTC-RFR,USDT-DGB,BTC-RVN,USD-XRP,USD-ETC,BTC-BFT,BTC-GO,BTC-HYDRO,BTC-UPP,USD-ADA,USD-ZEC,USDT-DOGE,BTC-ENJ,BTC-MET,USD-LTC,USD-TRX,BTC-DTA,BTC-EDR,BTC-BOXX,BTC-IHT,USD-BCH,BTC-XHV,USDT-ZRX,BTC-NPXS,BTC-PMA,USDT-BAT,USDT-RVN,BTC-PAL,USD-SC,BTC-PAX,USDT-PAX,BTC-ZIL,BTC-MOC,BTC-OST,BTC-SPC,BTC-MEDX,BTC-BSV,BTC-IOST,BTC-XNK,USDT-BSV,ETH-BSV,BTC-NCASH,BTC-SOLVE,BTC-USDS,USDT-PMA,ETH-NPXS,USDT-NPXS,USD-ZRX,BTC-JNT,BTC-LBA,BTC-MOBI,USD-BAT,USD-BSV,BTC-DENT,USD-USDS,BTC-DRGN,USD-PAX,BTC-VITE,BTC-IOTX,USD-DGB,BTC-BTM,BTC-ELF,USD-EDR,BTC-QNT,BTC-BTU,USD-ZEN,BTC-SPND,BTC-BTT,BTC-NKN,USD-KMD,USDT-BTT,BTC-GRIN,BTC-CTXC,BTC-HXRO,BTC-META,USDT-GRIN,BTC-FSN,BTC-HST,BTC-ANKR,USDT-XLM,BTC-TRAC,BTC-CRO,BTC-ONT,ETH-SOLVE,BTC-ONG,BTC-AERGO,BTC-TTC,USD-SPND,BTC-SLT,BTC-PTON,BTC-PI,ETH-ANKR,BTC-PLA,BTC-ART,BTC-ORBS,USDT-ENJ,BTC-VBK,BTC-BORA,BTC-CND,USDT-ONT,BTC-TRIO,BTC-FX,ETH-FX,BTC-ATOM,USDT-ATOM,ETH-ATOM,BTC-XYO,BTC-OCEAN,USDT-OCEAN,BTC-WIB,BTC-BWX,BTC-SNX,BTC-SUSD,BTC-VDX,USDT-VDX,ETH-VDX,BTC-COSM,BTC-OGO,USDT-OGO,BTC-ITM,BTC-LAMB,BTC-STPT,BTC-FET,BTC-MKR,ETH-MKR,BTC-DAI,ETH-DAI,USDT-DAI,BTC-CPT,BTC-ABT,BTC-PROM,BTC-FTM,BTC-ABYSS,BTC-EOS,ETH-EOS,USDT-EOS,BTC-FXC,BTC-DUSK,BTC-URAC,BTC-BLOC,BTC-BRZ,BTC-TEMCO,BTC-SPIN,BTC-HINT,BTC-LUNA,BTC-CHR,BTC-TUDA,BTC-UTK,BTC-PXL,BTC-AKRO,BTC-TSHP", - "enabledPairs": "USDT-BTC", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "USDT-BTC", + "available": "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-GRS,BTC-NLG,BTC-MONA,BTC-VRC,BTC-CURE,BTC-XMR,BTC-XDN,BTC-NAV,BTC-XST,BTC-VIA,BTC-PINK,BTC-IOC,BTC-SYS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-BLOCK,BTC-BTS,BTC-XRP,BTC-GAME,BTC-NXS,BTC-GEO,BTC-FLO,BTC-MUE,BTC-XEM,BTC-SPHR,BTC-OK,BTC-AEON,BTC-ETH,BTC-EXP,BTC-XLM,USDT-BTC,BTC-FCT,BTC-MAID,BTC-SLS,BTC-RADS,BTC-DCR,BTC-XVG,BTC-PIVX,BTC-MEME,BTC-STEEM,BTC-LSK,BTC-WAVES,BTC-LBC,BTC-SBD,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-REP,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-UBQ,BTC-KMD,BTC-SIB,BTC-ION,BTC-CRW,BTC-ARK,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-EDG,BTC-MORE,ETH-GNT,ETH-REP,USDT-ETH,BTC-RLC,BTC-GNO,ETH-GNO,BTC-HMQ,BTC-ANT,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,BTC-PTOY,BTC-BNT,ETH-BNT,BTC-NMR,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,BTC-PAY,ETH-PAY,BTC-MTL,BTC-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-BCH,USDT-BCH,BTC-BCH,BTC-DNT,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,USDT-OMG,BTC-ADA,BTC-MANA,ETH-MANA,BTC-RCN,BTC-VIB,ETH-VIB,BTC-MER,ETH-ADA,BTC-ENG,ETH-ENG,USDT-ADA,USDT-XVG,BTC-UKG,ETH-UKG,BTC-IGNIS,BTC-SRN,ETH-SRN,BTC-WAXP,ETH-WAXP,BTC-ZRX,ETH-ZRX,BTC-VEE,BTC-TRX,ETH-TRX,BTC-TUSD,BTC-LRC,ETH-TUSD,BTC-DMT,ETH-DMT,USDT-TUSD,USDT-SC,USDT-TRX,BTC-STORM,ETH-STORM,BTC-AID,BTC-GTO,USDT-DCR,USD-BTC,USD-USDT,USD-TUSD,BTC-TUBE,BTC-CMCT,USD-ETH,BTC-NLC2,BTC-BKX,BTC-MFT,BTC-LOOM,BTC-RFR,USDT-DGB,BTC-RVN,USD-XRP,USD-ETC,BTC-BFT,BTC-GO,BTC-HYDRO,BTC-UPP,USD-ADA,USD-ZEC,USDT-DOGE,BTC-ENJ,BTC-MET,USD-LTC,USD-TRX,BTC-DTA,BTC-EDR,BTC-IHT,USD-BCH,BTC-XHV,USDT-ZRX,BTC-NPXS,BTC-PMA,USDT-BAT,USDT-RVN,BTC-PAL,USD-SC,BTC-PAX,BTC-ZIL,BTC-MOC,BTC-OST,BTC-SPC,BTC-MED,BTC-BSV,BTC-IOST,USDT-BSV,ETH-BSV,BTC-SOLVE,BTC-USDS,USDT-PMA,ETH-NPXS,USDT-NPXS,USD-ZRX,BTC-JNT,BTC-LBA,USD-BAT,USD-BSV,BTC-DENT,USD-USDS,BTC-DRGN,USD-PAX,BTC-VITE,BTC-IOTX,USD-DGB,BTC-BTM,BTC-ELF,BTC-QNT,BTC-BTU,USD-ZEN,BTC-SPND,BTC-BTT,BTC-NKN,USD-KMD,USDT-BTT,BTC-GRIN,BTC-CTXC,BTC-HXRO,BTC-META,USDT-GRIN,BTC-FSN,BTC-ANKR,USDT-XLM,BTC-TRAC,BTC-CRO,BTC-ONT,ETH-SOLVE,BTC-ONG,BTC-TTC,BTC-PTON,BTC-PI,ETH-ANKR,BTC-PLA,BTC-ART,BTC-ORBS,USDT-ENJ,BTC-VBK,BTC-BORA,BTC-CND,USDT-ONT,BTC-FX,ETH-FX,BTC-ATOM,USDT-ATOM,ETH-ATOM,BTC-OCEAN,USDT-OCEAN,BTC-BWX,BTC-VDX,USDT-VDX,ETH-VDX,BTC-COSM,BTC-LAMB,BTC-STPT,BTC-DAI,ETH-DAI,USDT-DAI,BTC-CPT,BTC-FNB,BTC-PROM,BTC-ABYSS,BTC-EOS,ETH-EOS,USDT-EOS,BTC-FXC,BTC-DUSK,BTC-URAC,BTC-BLOC,BTC-TEMCO,BTC-SPIN,BTC-LUNA,BTC-CHR,BTC-TUDA,BTC-UTK,BTC-PXL,BTC-AKRO,BTC-TSHP,BTC-HEDG,BTC-MRPH,BTC-HBAR,ETH-HBAR,USD-HBAR,USDT-HBAR,BTC-PLG,BTC-VET,USDT-VET,BTC-SIX,BTC-WGP,BTC-APM,BTC-FLETA,USD-DCR,BTC-BLTV" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -553,40 +873,71 @@ "name": "BTSE", "enabled": true, "verbose": false, - "websocket": true, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD", - "enabledPairs": "BTC-USD", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD,XMR-CNY,XMR-EUR,XMR-GBP,XMR-HKD,XMR-JPY,XMR-SGD,XMR-USD" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -599,39 +950,71 @@ "name": "BTC Markets", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC", - "enabledPairs": "BTC-AUD", "baseCurrencies": "AUD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-AUD", + "available": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -644,39 +1027,70 @@ "name": "COINUT", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "BTCUSDT,ETCBTC,ETHUSDT,BTCCAD,ETHLTC,ETHUSD,LTCUSD,BTCSGD,ETCSGD,ETHSGD,ZECBTC,ZECUSD,ZECUSDT,ETHBTC,LTCBTC,USDTSGD,USDTUSD,XMRUSDT,BTCUSD,LTCCAD,LTCSGD,LTCUSDT,XMRBTC,XMRLTC,ZECLTC,ETCUSDT,ZECSGD,ETCLTC,ETHCAD,ZECCAD", - "enabledPairs": "LTCBTC,ETCBTC,ETHBTC", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "LTC-USDT", + "available": "ETH-BTC,LTC-SGD,BTC-CAD,DAI-SGD,ETH-CAD,ETH-SGD,ETH-USD,ZEC-USDT,BTC-USD,ETC-SGD,ETH-LTC,ZEC-SGD,BTC-SGD,ETC-USDT,XMR-BTC,XMR-USDT,ZEC-BTC,ZEC-CAD,ZEC-LTC,LTC-CAD,LTC-USDT,LTC-USD,XMR-LTC,ETC-LTC,LTC-BTC,ETH-USDT,ZEC-USD,USDT-SGD,USDT-USD,BTC-USDT,ETC-BTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -689,41 +1103,73 @@ "name": "EXMO", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "WAVES_BTC,BTC_RUB,DCR_UAH,XMR_UAH,USDC_BTC,XEM_USD,XLM_RUB,ATMCASH_BTC,QTUM_USD,ADA_USD,TRX_BTC,XRP_BTC,MKR_DAI,STQ_USD,ETH_USD,KICK_USDT,ZRX_USD,USDC_ETH,GUSD_BTC,ZRX_ETH,DASH_BTC,ETC_BTC,LTC_RUB,BTC_USD,STQ_EUR,BCH_RUB,XRP_USDT,WAVES_ETH,XTZ_ETH,QTUM_BTC,XEM_BTC,LSK_BTC,TRX_RUB,ETH_PLN,PTI_USDT,MNC_ETH,DAI_BTC,NEO_USD,KICK_BTC,ETH_BTC,ZEC_BTC,ETZ_USDT,DAI_ETH,DAI_USD,GNT_ETH,HBZ_USD,DXT_BTC,XRP_TRY,DAI_RUB,MNX_BTC,BCH_ETH,WAVES_USD,TRX_USD,INK_ETH,XLM_BTC,XMR_USD,KICK_ETH,DASH_RUB,LTC_BTC,USDT_RUB,USDT_EUR,DOGE_USD,DASH_UAH,XTZ_USD,ETZ_ETH,HB_BTC,GUSD_RUB,BTC_TRY,ADA_BTC,ADA_ETH,BTG_BTC,BCH_USDT,USDT_UAH,PTI_RUB,XTZ_RUB,DASH_USD,LTC_USD,ETH_USDT,MNC_BTC,XEM_EUR,GUSD_USD,XMR_BTC,XRP_EUR,SMART_USD,HBZ_BTC,BCH_USD,ETH_RUB,XRP_ETH,ZEC_RUB,XRP_RUB,DCR_BTC,DCR_RUB,PTI_EOS,EOS_USD,DXT_USD,ETH_LTC,BTC_USDT,USDT_USD,DASH_USDT,BTG_ETH,BCH_UAH,ROOBEE_ETH,TRX_UAH,MNC_USD,QTUM_ETH,BTCZ_BTC,XRP_UAH,USDC_USDT,NEO_BTC,OMG_ETH,STQ_BTC,ETC_USD,XMR_EUR,EOS_EUR,BTC_PLN,NEO_RUB,ZRX_BTC,INK_BTC,MNX_ETH,ETH_UAH,LSK_RUB,BCH_BTC,ETH_EUR,XLM_USD,ETC_RUB,DOGE_BTC,EXM_BTC,ROOBEE_BTC,LSK_USD,HBZ_ETH,LTC_EUR,USD_RUB,KICK_RUB,USDC_USD,PTI_BTC,OMG_USD,XRP_USD,XEM_UAH,GNT_BTC,LTC_UAH,SMART_BTC,SMART_EUR,SMART_RUB,BTG_USD,GAS_USD,BTC_UAH,XTZ_BTC,ZEC_USD,MKR_BTC,INK_USD,EOS_BTC,STQ_RUB,ZEC_EUR,XMR_ETH,BTC_EUR,XMR_RUB,XLM_TRY,GAS_BTC,MNX_USD,WAVES_RUB,ETZ_BTC,ETH_TRY,OMG_BTC,BCH_EUR", - "enabledPairs": "BTC_USD,LTC_USD", "baseCurrencies": "USD,EUR,RUB,PLN,UAH", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_", + "separator": "," + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USD,LTC_USD", + "available": "DCR_RUB,DAI_USD,ZRX_ETH,VLX_BTC,XRP_BTC,DASH_UAH,ETH_RUB,ZEC_RUB,USD_RUB,XTZ_BTC,PTI_EOS,ETH_LTC,SMART_USD,ADA_BTC,GNT_BTC,XRP_RUB,USDC_USDT,ETZ_BTC,HB_BTC,DAI_BTC,SMART_EUR,BCH_ETH,LTC_BTC,USDT_RUB,BTC_PLN,XMR_UAH,TRX_UAH,XRP_TRY,TRX_RUB,LTC_UAH,USDC_USD,ZRX_BTC,DASH_USD,DCR_BTC,PTI_USDT,MKR_BTC,DASH_RUB,DASH_USDT,XTZ_RUB,QTUM_ETH,OMG_USD,MNX_ETH,XRP_UAH,NEO_BTC,GNT_ETH,INK_ETH,BTG_USD,ZAG_BTC,XTZ_USD,LSK_RUB,BTT_BTC,BCH_EUR,ETH_USDT,WAVES_BTC,TRX_USD,MNX_USD,BCH_RUB,GAS_BTC,ETH_UAH,KICK_ETH,USDC_BTC,ETZ_USDT,LSK_USD,BCH_UAH,XMR_RUB,ETH_USD,LTC_EUR,USDT_USD,XLM_USD,BTG_BTC,BCH_BTC,SMART_BTC,XEM_USD,XMR_BTC,QTUM_BTC,OMG_ETH,ZEC_EUR,XTZ_ETH,MNC_ETH,DAI_RUB,XEM_EUR,DXT_USD,ETH_EUR,ZEC_USD,DOGE_BTC,DCR_UAH,MKR_DAI,QTUM_USD,XRP_USDT,WAVES_ETH,BTG_ETH,KICK_RUB,ETC_RUB,KICK_USDT,ZEC_BTC,XEM_UAH,DOGE_USD,GUSD_BTC,GUSD_RUB,GAS_USD,LSK_BTC,MNX_BTC,DXT_BTC,LTC_USD,BTC_USD,EXM_BTC,MNC_BTC,BTC_USDT,PTI_RUB,SMART_RUB,XMR_EUR,GUSD_USD,OMG_BTC,ETH_PLN,EOS_EUR,XRP_ETH,BTC_TRY,ROOBEE_BTC,ETH_TRY,XMR_ETH,ADA_USD,XLM_RUB,ETC_BTC,ETC_USD,XRP_USD,ATMCASH_BTC,XEM_BTC,NEO_RUB,XMR_USD,USDT_EUR,INK_USD,USDC_ETH,ETZ_ETH,TRX_BTC,DASH_BTC,ZRX_USD,EOS_USD,XRP_EUR,BTC_EUR,BTT_UAH,INK_BTC,BCH_USD,ETH_BTC,XLM_TRY,NEO_USD,BTCZ_BTC,LTC_RUB,KICK_BTC,BCH_USDT,ROOBEE_ETH,MNC_USD,DAI_ETH,EOS_BTC,WAVES_USD,BTC_RUB,BTC_UAH,ADA_ETH,WAVES_RUB,USDT_UAH,BTT_RUB,PTI_BTC,XLM_BTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_", - "separator": "," + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -736,40 +1182,73 @@ "name": "CoinbasePro", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "EOSUSD,ETHBTC,ETHUSDC,ETHEUR,ZECUSDC,REPUSD,LIN-ETH,EOSBTC,LTCGBP,CVCUSDC,XLMEUR,ETCGBP,XTZBTC,XRPUSD,XRPBTC,ALG-USD,BTCUSDC,GNTUSDC,ZRXBTC,DNTUSDC,BTCUSD,LTCBTC,LTCUSD,ETHGBP,ZRXUSD,BATETH,ZRXEUR,REPBTC,ETCEUR,XRPEUR,EOSEUR,BCHEUR,MAN-USDC,XLMUSD,BATUSDC,LOO-USDC,BTCEUR,BCHGBP,LTCEUR,BCHBTC,LIN-USD,DAIUSDC,XTZUSD,ETCBTC,BCHUSD,BTCGBP,ETHUSD,XLMBTC,ETCUSD,ZECBTC,ETHDAI", - "enabledPairs": "BTCUSD,BTCGBP,BTCEUR", "baseCurrencies": "USD,GBP,EUR", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-GBP,BTC-USD,REP-BTC,LTC-EUR,BCH-BTC,XRP-USD,BTC-USDC,DNT-USDC,ETH-DAI,GNT-USDC,EOS-BTC,XTZ-BTC,EOS-USD,ZEC-USDC,BTC-EUR,BAT-USDC,DASH-USD,ETC-USD,XLM-BTC,XRP-EUR,ETH-BTC,BCH-GBP,XRP-BTC,LTC-BTC,MANA-USDC,LOOM-USDC,BAT-ETH,ZRX-BTC,REP-USD,LTC-USD,EOS-EUR,BCH-USD,XLM-EUR,XTZ-USD,ETC-BTC,ZEC-BTC,ETC-EUR,ZRX-EUR,ETH-EUR,LTC-GBP,DAI-USDC,ZRX-USD,ETH-USDC,BCH-EUR,LINK-ETH,ETC-GBP,DASH-BTC,XLM-USD,CVC-USDC,ETH-USD,ETH-GBP,ALGO-USD,LINK-USD" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -779,90 +1258,153 @@ ] }, { - "name": "Coinbene", - "enabled": true, - "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "ABBC/BTC,ABT/ETH,ABT/USDT,ABYSS/ETH,ACDC/BTC,ACDC/USDT,ADI/ETH,ADK/BTC,ADN/BTC,AE/BTC,AE/USDT,AID/BTC,AIDOC/BTC,AION/BTC,AIPE/USDT,AIT/USDT,ALGO/USDT,ALI/ETH,ALX/ETH,APL/ETH,ATX/BTC,B2G/BTC,B91/USDT,BAAS/BTC,BAT/BTC,BCHABC/USDT,BCHSV/USDT,BEAUTY/ETH,BETHER/ETH,BEZ/BTC,BGC/USDT,BKG/BTC,BNT/BTC,BOA/USDT,BSTN/ETH,BTC/USDT,BTFM/USDT,BTNT/BTC,BTSC/BTC,BTT/USDT,BU/ETH,BVT/ETH,C3W/ETH,CAN/ETH,CCC/ETH,CCE/USDT,CC/USDT,CEDEX/ETH,CENT/BTC,CFT/USDT,CLO/BTC,CMT/ETH,CMT/USDT,CNN/BTC,CNN/ETH,CNN/USDT,CONI/USDT,COSM/BTC,COSM/ETH,COZP/BTC,CPC/BTC,CPMS/USDT,CREDO/ETH,CRN/BTC,CS/ETH,CS/USDT,CTXC/ETH,CUST/USDT,CVC/BTC,CXC/USDT,CXP/BTC,DCA/ETH,DCT/BTC,DENT/BTC,DGD/BTC,DOCK/ETH,DSCB/USDT,DTA/ETH,DUC/BTC,DVC/ETH,EBC/BTC,EBC/ETH,EBC/USDT,ECA/BTC,EDC/BTC,EDR/ETH,ELF/BTC,EMT/USDT,EOS/BTC,EOS/USDT,EQUAD/BTC,ETC/BTC,ETC/USDT,ETH/BTC,ETH/USDT,ETK/BTC,ETN/BTC,FAB/ETH,FACC/ETH,FCC/BTC,FDS/USDT,FND/ETH,FNKOS/ETH,FTN/BTC,FTN/USDT,FTT/BTC,FXT/ETH,GETX/ETH,GLDR/ETH,GMTK/ETH,GOM/USDT,GRAM/USDT,GRIN/BTC,GRN/BTC,GSTT/USDT,GUSD/USDT,GVT/BTC,HAPPY/BTC,HDAC/BTC,HMB/USDT,HNB/USDT,HPT/ETH,HUP/USDT,INCX/ETH,IOST/BTC,IOTE/USDT,ISR/BTC,ISR/ETH,IVY/ETH,JOB/BTC,KBC/BTC,KBC/USDT,KMD/BTC,KNT/ETH,KST/BTC,KUE/BTC,KUE/ETH,KUKY/BTC,LAMB/USDT,LATX/BTC,LBK/BTC,LINK/BTC,LOOM/BTC,LTC/BTC,LTC/USDT,LUC/ETH,LUX/BTC,LVTC/ETH,MDC/USDT,MGC/USDT,MIB/BTC,MINX/BTC,MINX/ETH,MOAC/USDT,MPL/BTC,MTC/BTC,MT/ETH,MTN/ETH,MT/USDT,MVL/ETH,MVPT/ETH,MWT/USDT,NANO/BTC,NBAI/ETH,NCASH/BTC,NEO/BTC,NEO/USDT,NOBS/BTC,NPXS/ETH,NPXS/USDT,NTY/ETH,ODC/USDT,OMG/BTC,OMX/ETH,OVC/ETH,OZX/ETH,PAL/ETH,PAT/ETH,PAX/USDT,PKX/BTC,PLAY/BTC,PMA/ETH,POLL/BTC,POLY/BTC,PPT/BTC,PSM/BTC,QKC/BTC,QTUM/BTC,QTUM/USDT,RBG/BTC,RBG/ETH,RBG/USDT,RBTC/BTC,RBZ/USDT,RCOIN/BTC,RCOIN/USDT,REP/BTC,REV/BTC,RIF/BTC,SALT/BTC,SCC/BTC,SCO/BTC,SEN/BTC,SENC/ETH,SHE/BTC,SHVR/BTC,SIM/BTC,SKB/BTC,SKM/ETH,SKYM/USDT,SLT/ETH,SMARTUP/ETH,SMARTUP/USDT,SMART/USDT,SORO/USDT,SRCOIN/BTC,SRCOIN/ETH,STORJ/BTC,STQ/BTC,SWET/BTC,SWTC/USDT,TCT/BTC,TEMCO/USDT,TEN/BTC,TEN/ETH,THM/ETH,TIB/BTC,TIMO/USDT,TMTG/BTC,TOC/ETH,TOSC/BTC,TRUE/ETH,TRX/BTC,TRX/USDT,TSL/BTC,TVB/USDT,UTNP/BTC,VBT/USDT,VEEN/BTC,VME/BTC,VME/ETH,VOLLAR/USDT,VSC/ETH,W12/BTC,W12/ETH,WBL/BTC,WFX/BTC,XEM/BTC,XLM/BTC,XMCT/ETH,XMCT/USDT,XMR/BTC,XNK/ETH,XRP/BTC,XRP/USDT,XSR/USDT,YTA/USDT,ZAT/ETH,ZDC/BTC,ZEC/BTC,ZGC/BTC,ZRX/BTC", - "enabledPairs": "BTC/USDT", - "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "/" - }, - "requestCurrencyPairFormat": { + "name": "Coinbene", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { "uppercase": true, "delimiter": "/" }, - "bankAccounts": [ - { - "bankName": "", - "bankAddress": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" + "configFormat": { + "uppercase": true, + "delimiter": "/" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC/USDT", + "available": "ABBC/BTC,ABBC/USDT,ABT/ETH,ABT/USDT,ABYSS/ETH,ACDC/BTC,ACDC/USDT,ADI/ETH,ADK/BTC,ADN/BTC,AE/BTC,AE/USDT,AIDOC/BTC,AION/BTC,AIPE/USDT,AIT/USDT,ALGO/USDT,ALI/ETH,ALX/ETH,APL/ETH,ATX/BTC,B2G/BTC,BAAS/BTC,BAT/BTC,BCH/USDT,BEAUTY/ETH,BETHER/ETH,BEZ/BTC,BGC/USDT,BKG/BTC,BNB/USDT,BNT/BTC,BOA/USDT,BSTN/ETH,BSV/USDT,BTC/USDT,BTFM/USDT,BTNT/BTC,BTSC/BTC,BTT/USDT,BU/ETH,BVT/ETH,C3W/ETH,CAN/ETH,CCC/ETH,CCE/USDT,CC/USDT,CEDEX/ETH,CENT/BTC,CFT/USDT,CLO/BTC,CMT/ETH,CMT/USDT,CNN/BTC,CNN/ETH,CNN/USDT,CONI/USDT,COSM/BTC,COSM/ETH,COZP/BTC,CPC/BTC,CREDO/ETH,CRN/BTC,CSCC/USDT,CS/ETH,CS/USDT,CTXC/ETH,CUST/USDT,CVC/BTC,CXP/BTC,DCA/ETH,DCT/BTC,DDAM/ETH,DDAM/USDT,DENT/BTC,DGD/BTC,DOCK/ETH,DSCB/USDT,DTA/ETH,DUC/BTC,DVC/ETH,EBC/BTC,EBC/ETH,EBC/USDT,ECA/BTC,ECP/BTC,EDC/BTC,EDR/ETH,ELF/BTC,EMT/USDT,EOS/BTC,EOS/USDT,EQUAD/BTC,ETC/BTC,ETC/USDT,ETH/BTC,ETH/USDT,ETK/BTC,ETN/BTC,FAB/ETH,FCC/BTC,FND/ETH,FNKOS/ETH,FTN/BTC,FTN/USDT,FTT/BTC,FXT/ETH,GDC/BTC,GDC/ETH,GDC/USDT,GETX/ETH,GLDR/ETH,GMTK/ETH,GOM2/USDT,GRAM/USDT,GRIN/BTC,GRN/BTC,GUSD/USDT,GVT/BTC,HAPPY/BTC,HDAC/BTC,HMB/USDT,HNB/USDT,HPT/ETH,HT/USDT,HUP/USDT,INCX/ETH,IOST/BTC,IOTE/USDT,ISR/BTC,ISR/ETH,IVY/ETH,JOB/BTC,KBC/BTC,KBC/USDT,KMD/BTC,KNT/ETH,KST/BTC,KUE/BTC,KUE/ETH,LAMB/USDT,LATX/BTC,LBK/BTC,LINK/BTC,LOOM/BTC,LTC/BTC,LTC/USDT,LUC/ETH,LUX/BTC,LVTC/ETH,MC/USDT,MDC/USDT,MIB/BTC,MINX/BTC,MINX/ETH,MOAC/USDT,MPL/BTC,MTC/BTC,MT/ETH,MTN/ETH,MT/USDT,MVL/ETH,MVPT/ETH,MXM/ETH,MXM/USDT,MZG/USDT,NANO/BTC,NBAI/ETH,NCASH/BTC,NEO/BTC,NEO/USDT,NFT/USDT,NOBS/BTC,NPXS/ETH,NPXS/USDT,NTY/ETH,ODC/USDT,OMG/BTC,OMX/ETH,OVC/ETH,OZX/ETH,PAL/ETH,PAT/ETH,PAX/USDT,PKX/BTC,PLAY/BTC,PMA/ETH,POLL/BTC,POLY/BTC,PPT/BTC,PSM/BTC,QKC/BTC,QTUM/BTC,QTUM/USDT,RBTC/BTC,RCOIN/BTC,RCOIN/USDT,REP/BTC,REV/BTC,RIF/BTC,SBT/USDT,SCC/BTC,SCO/BTC,SEN/BTC,SENC/ETH,SHE/BTC,SHVR/BTC,SIM/BTC,SKB/BTC,SKM/ETH,SKYM/USDT,SLT/ETH,SMARTUP/ETH,SMARTUP/USDT,SMART/USDT,SORO/USDT,SRCOIN/BTC,SRCOIN/ETH,STORJ/BTC,STQ/BTC,SWET/BTC,SWTC/USDT,TCT/BTC,TEMCO/USDT,TEN/BTC,TEN/ETH,TIB/BTC,TMTG/BTC,TOC/ETH,TOOS/USDT,TOSC/BTC,TRUE/ETH,TRX/BTC,TRX/USDT,TSL/BTC,TVB/USDT,UNI/USDT,UTNP/BTC,VBT/USDT,VEEN/BTC,VME/BTC,VME/ETH,VSC/ETH,VSF/BTC,W12/BTC,W12/ETH,WBL/BTC,WFX/BTC,XEM/BTC,XLM/BTC,XMCT/ETH,XMCT/USDT,XMR/BTC,XNK/ETH,XRP/BTC,XRP/USDT,XSR/USDT,YAP/BTC,YAP/USDT,YTA/USDT,ZAT/ETH,ZDC/BTC,ZEC/BTC,ZGC/BTC,ZRX/BTC" } - ] + } }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, { "name": "GateIO", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "USDT_CNYX,BTC_CNYX,ETH_CNYX,EOS_CNYX,BCH_CNYX,XRP_CNYX,DOGE_CNYX,TIPS_CNYX,BTC_USDC,BTC_PAX,BTC_USDT,BCH_USDT,ETH_USDT,ETC_USDT,QTUM_USDT,LTC_USDT,DASH_USDT,ZEC_USDT,BTM_USDT,EOS_USDT,REQ_USDT,SNT_USDT,OMG_USDT,PAY_USDT,CVC_USDT,ZRX_USDT,TNT_USDT,XMR_USDT,XRP_USDT,DOGE_USDT,BAT_USDT,PST_USDT,BTG_USDT,DPY_USDT,LRC_USDT,STORJ_USDT,RDN_USDT,STX_USDT,KNC_USDT,LINK_USDT,CDT_USDT,AE_USDT,AE_ETH,AE_BTC,CDT_ETH,RDN_ETH,STX_ETH,KNC_ETH,LINK_ETH,REQ_ETH,RCN_ETH,TRX_ETH,ARN_ETH,KICK_ETH,BNT_ETH,VET_ETH,MCO_ETH,FUN_ETH,DATA_ETH,RLC_ETH,RLC_USDT,ZSC_ETH,WINGS_ETH,MDA_ETH,RCN_USDT,TRX_USDT,KICK_USDT,VET_USDT,MCO_USDT,FUN_USDT,DATA_USDT,ZSC_USDT,MDA_USDT,XTZ_USDT,XTZ_BTC,XTZ_ETH,GNT_USDT,GNT_ETH,GEM_USDT,GEM_ETH,RFR_USDT,RFR_ETH,DADI_USDT,DADI_ETH,ABT_USDT,ABT_ETH,LEDU_BTC,LEDU_ETH,OST_USDT,OST_ETH,XLM_USDT,XLM_ETH,XLM_BTC,MOBI_USDT,MOBI_ETH,MOBI_BTC,OCN_USDT,OCN_ETH,OCN_BTC,ZPT_USDT,ZPT_ETH,ZPT_BTC,COFI_USDT,COFI_ETH,JNT_USDT,JNT_ETH,JNT_BTC,BLZ_USDT,BLZ_ETH,GXS_USDT,GXS_BTC,MTN_USDT,MTN_ETH,RUFF_USDT,RUFF_ETH,RUFF_BTC,TNC_USDT,TNC_ETH,TNC_BTC,ZIL_USDT,ZIL_ETH,BTO_USDT,BTO_ETH,THETA_USDT,THETA_ETH,DDD_USDT,DDD_ETH,DDD_BTC,MKR_USDT,MKR_ETH,DAI_USDT,SMT_USDT,SMT_ETH,MDT_USDT,MDT_ETH,MDT_BTC,MANA_USDT,MANA_ETH,LUN_USDT,LUN_ETH,SALT_USDT,SALT_ETH,FUEL_USDT,FUEL_ETH,ELF_USDT,ELF_ETH,DRGN_USDT,DRGN_ETH,GTC_USDT,GTC_ETH,GTC_BTC,QLC_USDT,QLC_BTC,QLC_ETH,DBC_USDT,DBC_BTC,DBC_ETH,BNTY_USDT,BNTY_ETH,LEND_USDT,LEND_ETH,ICX_USDT,ICX_ETH,BTF_USDT,BTF_BTC,ADA_USDT,ADA_BTC,LSK_USDT,LSK_BTC,WAVES_USDT,WAVES_BTC,BIFI_USDT,BIFI_BTC,MDS_ETH,MDS_USDT,DGD_USDT,DGD_ETH,QASH_USDT,QASH_ETH,QASH_BTC,POWR_USDT,POWR_ETH,POWR_BTC,FIL_USDT,BCD_USDT,BCD_BTC,SBTC_USDT,SBTC_BTC,GOD_USDT,GOD_BTC,BCX_USDT,BCX_BTC,QSP_USDT,QSP_ETH,INK_BTC,INK_USDT,INK_ETH,INK_QTUM,MED_QTUM,MED_ETH,MED_USDT,QBT_QTUM,QBT_ETH,QBT_USDT,TSL_QTUM,TSL_USDT,GNX_USDT,GNX_ETH,NEO_USDT,GAS_USDT,NEO_BTC,GAS_BTC,IOTA_USDT,IOTA_BTC,NAS_USDT,NAS_ETH,NAS_BTC,ETH_BTC,ETC_BTC,ETC_ETH,ZEC_BTC,DASH_BTC,LTC_BTC,BCH_BTC,BTG_BTC,QTUM_BTC,QTUM_ETH,XRP_BTC,DOGE_BTC,XMR_BTC,ZRX_BTC,ZRX_ETH,DNT_ETH,DPY_ETH,OAX_BTC,OAX_USDT,OAX_ETH,REP_ETH,LRC_ETH,LRC_BTC,PST_ETH,BCDN_ETH,BCDN_USDT,TNT_ETH,SNT_ETH,SNT_BTC,BTM_ETH,BTM_BTC,SNET_ETH,SNET_USDT,LLT_SNET,OMG_ETH,OMG_BTC,PAY_ETH,PAY_BTC,BAT_ETH,BAT_BTC,CVC_ETH,STORJ_ETH,STORJ_BTC,EOS_ETH,EOS_BTC,BTS_USDT,BTS_BTC,TIPS_ETH,GT_BTC,GT_USDT,ATOM_BTC,ATOM_USDT,XEM_ETH,XEM_USDT,XEM_BTC,BU_USDT,BU_ETH,BU_BTC,BCHSV_USDT,BCHSV_CNYX,BCHSV_BTC,DCR_USDT,DCR_BTC,BCN_USDT,BCN_BTC,XMC_USDT,XMC_BTC,ATP_USDT,ATP_ETH,NBOT_ETH,NBOT_USDT,MEDX_USDT,MEDX_ETH,GRIN_USDT,GRIN_ETH,GRIN_BTC,BEAM_USDT,BEAM_ETH,BEAM_BTC,VTHO_ETH,BTT_USDT,BTT_ETH,BTT_TRX,TFUEL_ETH,TFUEL_USDT,CELR_ETH,CELR_USDT,CS_ETH,CS_USDT,MAN_ETH,MAN_USDT,REM_ETH,REM_USDT,LYM_ETH,LYM_BTC,LYM_USDT,ONG_ETH,ONG_USDT,ONT_ETH,ONT_USDT,BFT_ETH,BFT_USDT,IHT_ETH,IHT_USDT,SENC_ETH,SENC_USDT,TOMO_ETH,TOMO_USDT,ELEC_ETH,ELEC_USDT,HAV_ETH,HAV_USDT,SWTH_ETH,SWTH_USDT,NKN_ETH,NKN_USDT,SOUL_ETH,SOUL_USDT,LRN_ETH,LRN_USDT,EOSDAC_ETH,EOSDAC_USDT,DOCK_USDT,DOCK_ETH,GSE_USDT,GSE_ETH,RATING_USDT,RATING_ETH,HSC_USDT,HSC_ETH,HIT_USDT,HIT_ETH,DX_USDT,DX_ETH,CNNS_ETH,CNNS_USDT,DREP_ETH,DREP_USDT,MBL_USDT,MBL_ETH,GMAT_USDT,GMAT_ETH,MIX_USDT,MIX_ETH,LAMB_USDT,LAMB_ETH,LEO_USDT,LEO_BTC,WICC_USDT,WICC_ETH,SERO_USDT,SERO_ETH,VIDY_USDT,VIDY_ETH,KGC_USDT,FTM_USDT,FTM_ETH,ONE_USDT,ARPA_USDT,ARPA_ETH,ALGO_USDT,BKC_USDT,BXC_USDT,BXC_ETH,PAX_USDT,PAX_CNYX,USDC_CNYX,USDC_USDT,TUSD_CNYX,TUSD_USDT,HC_USDT,HC_BTC,HC_ETH,GARD_USDT,GARD_ETH,FTI_USDT,FTI_ETH,SOP_ETH,SOP_USDT,LEMO_USDT,LEMO_ETH,QKC_USDT,QKC_ETH,IOTX_USDT,IOTX_ETH,RED_USDT,RED_ETH,LBA_USDT,LBA_ETH,OPEN_USDT,OPEN_ETH,MITH_USDT,MITH_ETH,SKM_USDT,SKM_ETH,XVG_USDT,XVG_BTC,NANO_USDT,NANO_BTC,HT_USDT,BNB_USDT,MET_ETH,MET_USDT,TCT_ETH,TCT_USDT,MXC_USDT,MXC_BTC,MXC_ETH", - "enabledPairs": "BTC_USDT", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USDT", + "available": "USDT_CNYX,BTC_CNYX,ETH_CNYX,EOS_CNYX,BCH_CNYX,XRP_CNYX,DOGE_CNYX,TIPS_CNYX,BTC_USDC,BTC_PAX,BTC_USDT,BCH_USDT,ETH_USDT,ETC_USDT,QTUM_USDT,LTC_USDT,DASH_USDT,ZEC_USDT,BTM_USDT,EOS_USDT,REQ_USDT,SNT_USDT,OMG_USDT,PAY_USDT,CVC_USDT,ZRX_USDT,TNT_USDT,XMR_USDT,XRP_USDT,DOGE_USDT,BAT_USDT,PST_USDT,BTG_USDT,DPY_USDT,LRC_USDT,STORJ_USDT,RDN_USDT,STX_USDT,KNC_USDT,LINK_USDT,CDT_USDT,AE_USDT,AE_ETH,AE_BTC,CDT_ETH,RDN_ETH,STX_ETH,KNC_ETH,LINK_ETH,REQ_ETH,RCN_ETH,TRX_ETH,ARN_ETH,KICK_ETH,BNT_ETH,VET_ETH,MCO_ETH,FUN_ETH,DATA_ETH,RLC_ETH,RLC_USDT,ZSC_ETH,WINGS_ETH,MDA_ETH,RCN_USDT,TRX_USDT,KICK_USDT,VET_USDT,MCO_USDT,FUN_USDT,DATA_USDT,ZSC_USDT,MDA_USDT,XTZ_USDT,XTZ_BTC,XTZ_ETH,GNT_USDT,GNT_ETH,GEM_USDT,GEM_ETH,RFR_USDT,RFR_ETH,DADI_USDT,DADI_ETH,ABT_USDT,ABT_ETH,LEDU_BTC,LEDU_ETH,OST_USDT,OST_ETH,XLM_USDT,XLM_ETH,XLM_BTC,MOBI_USDT,MOBI_ETH,MOBI_BTC,OCN_USDT,OCN_ETH,OCN_BTC,ZPT_USDT,ZPT_ETH,ZPT_BTC,COFI_USDT,COFI_ETH,JNT_USDT,JNT_ETH,JNT_BTC,BLZ_USDT,BLZ_ETH,GXS_USDT,GXS_BTC,MTN_USDT,MTN_ETH,RUFF_USDT,RUFF_ETH,RUFF_BTC,TNC_USDT,TNC_ETH,TNC_BTC,ZIL_USDT,ZIL_ETH,BTO_USDT,BTO_ETH,THETA_USDT,THETA_ETH,DDD_USDT,DDD_ETH,DDD_BTC,MKR_USDT,MKR_ETH,DAI_USDT,SMT_USDT,SMT_ETH,MDT_USDT,MDT_ETH,MDT_BTC,MANA_USDT,MANA_ETH,LUN_USDT,LUN_ETH,SALT_USDT,SALT_ETH,FUEL_USDT,FUEL_ETH,ELF_USDT,ELF_ETH,DRGN_USDT,DRGN_ETH,GTC_USDT,GTC_ETH,GTC_BTC,QLC_USDT,QLC_BTC,QLC_ETH,DBC_USDT,DBC_BTC,DBC_ETH,BNTY_USDT,BNTY_ETH,LEND_USDT,LEND_ETH,ICX_USDT,ICX_ETH,BTF_USDT,BTF_BTC,ADA_USDT,ADA_BTC,LSK_USDT,LSK_BTC,WAVES_USDT,WAVES_BTC,BIFI_USDT,BIFI_BTC,MDS_ETH,MDS_USDT,DGD_USDT,DGD_ETH,QASH_USDT,QASH_ETH,QASH_BTC,POWR_USDT,POWR_ETH,POWR_BTC,FIL_USDT,BCD_USDT,BCD_BTC,SBTC_USDT,SBTC_BTC,GOD_USDT,GOD_BTC,BCX_USDT,BCX_BTC,QSP_USDT,QSP_ETH,INK_BTC,INK_USDT,INK_ETH,INK_QTUM,QBT_QTUM,QBT_ETH,QBT_USDT,TSL_QTUM,TSL_USDT,GNX_USDT,GNX_ETH,NEO_USDT,GAS_USDT,NEO_BTC,GAS_BTC,IOTA_USDT,IOTA_BTC,NAS_USDT,NAS_ETH,NAS_BTC,ETH_BTC,ETC_BTC,ETC_ETH,ZEC_BTC,DASH_BTC,LTC_BTC,BCH_BTC,BTG_BTC,QTUM_BTC,QTUM_ETH,XRP_BTC,DOGE_BTC,XMR_BTC,ZRX_BTC,ZRX_ETH,DNT_ETH,DPY_ETH,OAX_BTC,OAX_USDT,OAX_ETH,REP_ETH,LRC_ETH,LRC_BTC,PST_ETH,BCDN_ETH,BCDN_USDT,TNT_ETH,SNT_ETH,SNT_BTC,BTM_ETH,BTM_BTC,SNET_ETH,SNET_USDT,LLT_SNET,OMG_ETH,OMG_BTC,PAY_ETH,PAY_BTC,BAT_ETH,BAT_BTC,CVC_ETH,STORJ_ETH,STORJ_BTC,EOS_ETH,EOS_BTC,BTS_USDT,BTS_BTC,TIPS_ETH,GT_BTC,GT_USDT,ATOM_BTC,ATOM_USDT,XEM_ETH,XEM_USDT,XEM_BTC,BU_USDT,BU_ETH,BU_BTC,BCHSV_USDT,BCHSV_CNYX,BCHSV_BTC,DCR_USDT,DCR_BTC,BCN_USDT,BCN_BTC,XMC_USDT,XMC_BTC,ATP_USDT,ATP_ETH,NAX_ETH,NBOT_ETH,NBOT_USDT,MED_USDT,MED_ETH,GRIN_USDT,GRIN_ETH,GRIN_BTC,BEAM_USDT,BEAM_ETH,BEAM_BTC,VTHO_ETH,BTT_USDT,BTT_ETH,BTT_TRX,TFUEL_ETH,TFUEL_USDT,CELR_ETH,CELR_USDT,CS_ETH,CS_USDT,MAN_ETH,MAN_USDT,REM_ETH,REM_USDT,LYM_ETH,LYM_BTC,LYM_USDT,ONG_ETH,ONG_USDT,ONT_ETH,ONT_USDT,BFT_ETH,BFT_USDT,IHT_ETH,IHT_USDT,SENC_ETH,SENC_USDT,TOMO_ETH,TOMO_USDT,ELEC_ETH,ELEC_USDT,HAV_ETH,HAV_USDT,SWTH_ETH,SWTH_USDT,NKN_ETH,NKN_USDT,SOUL_ETH,SOUL_USDT,LRN_ETH,LRN_USDT,EOSDAC_ETH,EOSDAC_USDT,DOCK_USDT,DOCK_ETH,GSE_USDT,GSE_ETH,RATING_USDT,RATING_ETH,HSC_USDT,HSC_ETH,HIT_USDT,HIT_ETH,DX_USDT,DX_ETH,CNNS_ETH,CNNS_USDT,DREP_ETH,DREP_USDT,MBL_USDT,MBL_ETH,GMAT_USDT,GMAT_ETH,MIX_USDT,MIX_ETH,LAMB_USDT,LAMB_ETH,LEO_USDT,LEO_BTC,WICC_USDT,WICC_ETH,SERO_USDT,SERO_ETH,VIDY_USDT,VIDY_ETH,KGC_USDT,FTM_USDT,FTM_ETH,COS_USDT,CRO_USDT,ALY_USDT,WIN_USDT,MTV_USDT,ONE_USDT,ARPA_USDT,ARPA_ETH,DILI_USDT,ALGO_USDT,PI_USDT,CKB_USDT,CKB_BTC,CKB_ETH,BKC_USDT,BXC_USDT,BXC_ETH,PAX_USDT,PAX_CNYX,USDC_CNYX,USDC_USDT,TUSD_CNYX,TUSD_USDT,HC_USDT,HC_BTC,HC_ETH,GARD_USDT,GARD_ETH,FTI_USDT,FTI_ETH,SOP_ETH,SOP_USDT,LEMO_USDT,LEMO_ETH,QKC_USDT,QKC_ETH,QKC_BTC,IOTX_USDT,IOTX_ETH,RED_USDT,RED_ETH,LBA_USDT,LBA_ETH,OPEN_USDT,OPEN_ETH,MITH_USDT,MITH_ETH,SKM_USDT,SKM_ETH,XVG_USDT,XVG_BTC,NANO_USDT,NANO_BTC,HT_USDT,BNB_USDT,MET_ETH,MET_USDT,TCT_ETH,TCT_USDT,MXC_USDT,MXC_BTC,MXC_ETH" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -875,38 +1417,69 @@ "name": "Gemini", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC", - "enabledPairs": "BTCUSD", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD", + "available": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -919,39 +1492,71 @@ "name": "HitBTC", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,AMP-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,PIX-BTC,PIX-ETH,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-BTC,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,DRPU-BTC,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,DRPU-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAX-BTC,WAX-ETH,WAX-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,NOAH-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,NOAH-ETH,NOAH-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,SUNC-ETH,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,FREC-BTC,NAVI-BTC,FREC-ETH,FREC-USD,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,HERO-BTC,HERO-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,ZPT-BTC,ZPT-ETH,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,ZPT-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,JOT-BTC,JBC-BTC,JBC-ETH,BTS-BTC,BNK-BTC,KBC-BTC,KBC-ETH,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,CLN-BTC,CLN-ETH,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,COSM-BTC,COSM-ETH,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,XCLR-BTC,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,CCL-USD,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,MESSE-BTC,MESSE-ETH,MESSE-USD,CCL-ETH,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,MESSE-EOS,MESSE-EURS,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,MRS-BTC,MRS-ETH,MRS-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD", - "enabledPairs": "BTC-USD", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAXP-BTC,WAXP-ETH,WAXP-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,NAVI-BTC,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,HERO-BTC,HERO-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,BTS-BTC,BNK-BTC,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD,NJBC-BTC,NJBC-ETH,XRC-BTC,EOS-BCH,LTC-BCH,XRP-BCH,TRX-BCH,XLM-BCH,ETC-BCH,DASH-BCH,ZEC-BCH,BKX-USD,LAMB-BTC,NPXS-BTC,HBAR-BTC,HBAR-USD,ONE-BTC,RFR-BTC,RFR-USD,BUSD-USD,PAXG-BTC,PAXG-USD,REN-BTC,IGNIS-BTC,CEL-BTC,CEL-ETH,WIN-USD,ADK-BTC,PART-BTC,SOZ-BTC,SOZ-ETH,SOZ-USD,WAVES-USD,ADA-BCH,ONT-BCH,XMR-BCH,ATOM-BCH,LINK-BCH,OMG-BCH,WAVES-BCH,IOTX-BTC,HOT-BTC,SLV-BTC,HEDG-BTC,CHZ-BTC,CHZ-USD,COCOS-BTC,COCOS-USD,SEELE-BTC,SEELE-USD,MDA-BTC,LEO-USD,REM-BTC,REM-ETH,REM-USD,SCD-DAI,BTC-BUSD,RVN-BTC,BST-BTC,ERD-BTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -964,40 +1569,72 @@ "name": "Huobi", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiAuthPemKey": "-----BEGIN EC PRIVATE KEY-----\nJUSTADUMMY\n-----END EC PRIVATE KEY-----\n", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "HT-USDT,BAT-ETH,AST-ETH,TRX-BTC,NEW-BTC,AE-BTC,IIC-BTC,NEW-USDT,CDC-BTC,AE-USDT,DGB-BTC,NAS-ETH,QSP-BTC,LYM-ETH,YCC-BTC,BCH-HT,BIX-ETH,WXT-BTC,XRP-BTC,IOST-BTC,CHAT-BTC,BTC-USDT,XTZ-BTC,PVT-BTC,PVT-USDT,WAVES-ETH,ACT-BTC,RSR-BTC,ACT-USDT,WXT-USDT,XLM-ETH,HT-BTC,UUU-USDT,XRP-USDT,UGAS-BTC,BTS-ETH,IRIS-ETH,LUN-BTC,IOST-HT,DOCK-BTC,ABT-ETH,CRO-BTC,MAN-ETH,ENG-ETH,QUN-BTC,APPC-BTC,KAN-ETH,VET-USDT,SOC-ETH,RSR-HT,RUFF-ETH,RCCC-ETH,AAC-ETH,MCO-BTC,RSR-USDT,TNB-ETH,UTK-ETH,ADX-BTC,WAX-ETH,IOST-USDT,HOT-ETH,WTC-USDT,CVCOIN-BTC,NCASH-ETH,ATP-BTC,SWFTC-ETH,GTC-BTC,PNT-BTC,GT-HT,NEO-BTC,OMG-BTC,EOS-HUSD,WPR-ETH,ARPA-BTC,BTM-BTC,BTM-USDT,KCASH-ETH,SSP-ETH,ARPA-USDT,CNN-BTC,NKN-BTC,NPXS-BTC,OMG-USDT,TOPC-ETH,XEM-BTC,BCH-USDT,SNC-BTC,POLY-ETH,CMT-ETH,PAI-USDT,ZEC-USDT,LSK-ETH,SMT-ETH,DASH-USDT,GAS-ETH,DASH-BTC,GXC-ETH,FTT-HT,IOTA-ETH,FTI-BTC,TRIO-ETH,LET-BTC,ZRX-ETH,ETN-ETH,EVX-ETH,BFT-ETH,GRS-BTC,XRP-HT,DASH-HT,QTUM-ETH,HIT-ETH,NEXO-BTC,QASH-BTC,EOS-ETH,ARDR-ETH,ADA-BTC,NEO-USDT,BTT-TRX,COVA-ETH,REN-BTC,LOOM-BTC,CVC-ETH,NANO-ETH,ARPA-HT,NEW-HT,BLZ-ETH,LINK-ETH,XTZ-USDT,PAY-BTC,GNT-USDT,YEE-ETH,XZC-ETH,EGCC-ETH,PROPY-ETH,ZEC-BTC,EDU-ETH,RTE-BTC,DCR-USDT,FTT-BTC,DCR-BTC,EKO-BTC,SBTC-BTC,ZLA-ETH,TOP-HT,ALGO-BTC,DTA-ETH,EKT-ETH,ATOM-USDT,LXT-USDT,ZEN-ETH,LOL-USDT,LTC-USDT,DAT-BTC,REQ-ETH,ELA-ETH,NKN-HT,PC-BTC,HIT-BTC,EKO-ETH,STK-ETH,LAMB-USDT,LAMB-HT,DOGE-ETH,ATOM-BTC,THETA-USDT,LOL-BTC,THETA-BTC,LSK-BTC,ADA-USDT,RDN-BTC,OGO-HT,UIP-USDT,WICC-BTC,OCN-BTC,ELF-BTC,AKRO-USDT,USDC-HUSD,LAMB-BTC,DBC-ETH,BTT-ETH,FAIR-BTC,POWR-ETH,MUSK-ETH,MT-BTC,STEEM-USDT,RBTC-BTC,CTXC-BTC,MANA-USDT,ICX-ETH,GET-BTC,LTC-BTC,ITC-ETH,BCV-BTC,ZJLT-BTC,AKRO-HT,TNT-ETH,TOP-BTC,MEX-BTC,DATX-BTC,ALGO-USDT,LXT-BTC,GT-USDT,FSN-HT,FSN-USDT,MTX-ETH,LET-ETH,OGO-USDT,PHX-BTC,KCASH-HT,HC-USDT,LOL-HT,NKN-USDT,HOT-BTC,LBA-BTC,XMX-BTC,OST-ETH,VEN-USDT,LTC-HT,LBA-USDT,VEN-BTC,CRE-HT,BIFI-BTC,BT1-BTC,HPT-BTC,NULS-BTC,WAN-BTC,ZIL-BTC,ETC-HT,TOS-BTC,MANA-BTC,SHE-BTC,GT-BTC,FSN-BTC,MCO-ETH,MTN-BTC,MDS-BTC,SRN-ETH,GVE-BTC,XMR-ETH,MEET-ETH,NULS-USDT,BCH-BTC,PAI-BTC,NCC-ETH,BSV-BTC,AKRO-BTC,ELF-USDT,DGD-ETH,PVT-HT,UIP-BTC,ATP-USDT,SEELE-ETH,GSC-BTC,ETC-USDT,SOC-BTC,GNX-BTC,WICC-USDT,QSP-ETH,RUFF-BTC,KNC-ETH,ATP-HT,CTXC-USDT,KMD-ETH,OGO-BTC,BKBT-BTC,DGB-ETH,WAVES-USDT,BCD-BTC,HPT-HT,ZIL-USDT,BUT-ETH,CVNT-BTC,OCN-USDT,SALT-ETH,XLM-BTC,TRX-USDT,RCN-BTC,DAC-ETH,MT-HT,ETH-HUSD,HPT-USDT,XTZ-ETH,USDT-HUSD,CHAT-ETH,ONT-USDT,SKM-USDT,MAN-BTC,ARDR-BTC,BCX-BTC,SKM-BTC,EOS-USDT,GNX-ETH,CRE-USDT,PORTAL-ETH,COVA-BTC,BIX-BTC,UUU-ETH,AAC-BTC,TRX-ETH,NEXO-ETH,NAS-BTC,ENG-BTC,AST-BTC,TT-HT,QUN-ETH,EOS-BTC,18C-ETH,WTC-ETH,CVCOIN-ETH,CRE-BTC,CNNS-USDT,WAX-BTC,AIDOC-BTC,VET-ETH,CMT-USDT,BSV-USDT,IDT-ETH,IOST-ETH,BTC-HUSD,IOTA-BTC,TNB-BTC,LINK-BTC,TOPC-BTC,RCCC-BTC,ZRX-USDT,CNNS-BTC,BOX-BTC,MDS-USDT,XLM-USDT,BAT-BTC,LYM-BTC,UC-ETH,RUFF-USDT,LUN-ETH,BIX-USDT,CDC-ETH,BTS-USDT,YCC-ETH,KAN-USDT,MTL-BTC,WAVES-BTC,ONT-BTC,HT-HUSD,IRIS-USDT,SOC-USDT,WPR-BTC,ETC-BTC,TUSD-HUSD,CVC-USDT,PROPY-BTC,TRIO-BTC,CVC-BTC,BTT-USDT,NANO-BTC,GXC-BTC,NCASH-BTC,XRP-HUSD,TT-USDT,SHE-ETH,NANO-USDT,LOOM-ETH,POWR-BTC,QTUM-BTC,SSP-BTC,BTM-ETH,QTUM-USDT,XZC-BTC,GNT-ETH,OMG-ETH,NPXS-ETH,SNT-USDT,ETH-USDT,ABT-BTC,BTS-BTC,STEEM-BTC,VSYS-USDT,BLZ-BTC,CNNS-HT,ADX-ETH,SMT-USDT,IOTA-USDT,PAY-ETH,CMT-BTC,UTK-BTC,SWFTC-BTC,GTC-ETH,LINK-USDT,SNC-ETH,SNT-BTC,EOS-HT,REN-ETH,PAX-HUSD,KCASH-BTC,HC-BTC,IIC-ETH,QASH-ETH,GRS-ETH,EDU-BTC,HIT-USDT,TOP-USDT,XZC-USDT,KAN-BTC,SC-BTC,SKM-HT,AE-ETH,STORJ-USDT,XVG-ETH,ZRX-BTC,EVX-BTC,ETN-BTC,BFT-BTC,FTI-ETH,DAT-ETH,UGAS-ETH,BAT-USDT,GXC-USDT,GAS-BTC,TNT-BTC,HB10-USDT,MUSK-BTC,FTT-USDT,STK-BTC,ELF-ETH,KNC-BTC,CTXC-ETH,DBC-BTC,HC-ETH,EKT-BTC,DTA-USDT,ZLA-BTC,EKT-USDT,DTA-BTC,OCN-ETH,DGD-BTC,BHT-USDT,MTX-BTC,BCV-ETH,YEE-BTC,VSYS-HT,MEX-ETH,DATX-ETH,EGCC-BTC,LXT-ETH,ITC-USDT,TOS-ETH,ITC-BTC,RCN-ETH,XVG-BTC,SC-ETH,BT2-BTC,REQ-BTC,ELA-USDT,LET-USDT,STORJ-BTC,ALGO-ETH,POLY-BTC,LAMB-ETH,DCR-ETH,EGT-BTC,RTE-ETH,FAIR-ETH,CNN-ETH,BHT-BTC,GSC-ETH,GNT-BTC,PAI-ETH,PC-ETH,ADA-ETH,DOGE-BTC,ZEN-BTC,STEEM-ETH,XMR-BTC,XMR-USDT,MDS-ETH,TT-BTC,BTT-BTC,BHT-HT,ZJLT-ETH,UC-BTC,GVE-ETH,MXC-BTC,MANA-ETH,VSYS-BTC,THETA-ETH,NCC-BTC,APPC-ETH,SMT-BTC,IDT-BTC,UIP-ETH,ETH-BTC,BOX-ETH,LBA-ETH,NULS-ETH,PNT-ETH,BTG-BTC,CVNT-ETH,SALT-BTC,XEM-USDT,WXT-HT,BUT-BTC,DAC-BTC,DOCK-ETH,GET-ETH,AIDOC-ETH,EGT-USDT,WAN-ETH,KMD-BTC,MTN-ETH,CRO-USDT,ONT-ETH,BKBT-ETH,MEET-BTC,VEN-ETH,MT-ETH,SRN-BTC,UUU-BTC,SEELE-BTC,ICX-BTC,RDN-ETH,EGT-HT,ZIL-ETH,IRIS-BTC,CRO-HT,ACT-ETH,DOGE-USDT,NAS-USDT,PORTAL-BTC,ELA-BTC,OST-BTC,WICC-ETH,VET-BTC,XMX-ETH,WTC-BTC,HT-ETH,ATOM-ETH,18C-BTC", - "enabledPairs": "BTC-USDT", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": false + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USDT", + "available": "NKN-HT,HC-BTC,ZEC-USDT,KCASH-HT,CRE-USDT,SRN-BTC,LOOM-BTC,WAN-ETH,DAC-ETH,EDU-BTC,KCASH-BTC,BHD-BTC,UTK-ETH,LINK-USDT,EDU-ETH,WAN-BTC,DAC-BTC,OST-ETH,UTK-BTC,BCX-BTC,NODE-BTC,OST-BTC,BTM-USDT,NEXO-ETH,NPXS-BTC,GNT-USDT,FOR-USDT,LOOM-ETH,KCASH-ETH,DCR-USDT,NPXS-ETH,NKN-BTC,DASH-HT,EGT-USDT,NODE-HT,OGO-USDT,HC-ETH,ATOM-BTC,ZIL-USDT,ATOM-ETH,XMR-USDT,BKBT-BTC,STK-ETH,AST-ETH,NEXO-BTC,STK-BTC,STEEM-ETH,MTX-ETH,EVX-ETH,AKRO-BTC,IDT-ETH,DATX-ETH,AKRO-HT,GNX-BTC,WICC-ETH,DASH-BTC,YEE-BTC,WAVES-ETH,IOST-HT,SRN-ETH,DOCK-USDT,ZRX-ETH,CNNS-HT,TT-HT,AE-USDT,TT-BTC,RDN-BTC,IOST-BTC,ITC-ETH,COVA-BTC,AAC-ETH,PAI-ETH,CNNS-BTC,BHD-HT,DTA-BTC,REN-ETH,FTI-ETH,ALGO-ETH,IIC-BTC,PC-BTC,STORJ-BTC,ELA-BTC,AE-BTC,YCC-BTC,ETH-USDT,KMD-BTC,HT-USDT,BTT-BTC,QASH-BTC,MTN-BTC,MX-HT,AE-ETH,ETH-HUSD,FTI-BTC,LUN-BTC,BAT-ETH,TNB-BTC,PC-ETH,LAMB-BTC,PAY-BTC,18C-BTC,MUSK-BTC,QSP-BTC,DOGE-USDT,MANA-ETH,TOPC-ETH,QSP-ETH,HB10-USDT,HT-BTC,BCH-HT,MUSK-ETH,LYM-ETH,PORTAL-BTC,YCC-ETH,TOPC-BTC,ELA-ETH,ARDR-BTC,BSV-HUSD,CVNT-ETH,NCC-ETH,LOL-BTC,AST-BTC,HIT-ETH,KAN-USDT,BKBT-ETH,BHT-HT,BTS-BTC,ZRX-BTC,FOR-HT,AIDOC-BTC,NEO-BTC,THETA-ETH,GNX-ETH,BSV-BTC,WICC-BTC,EVX-BTC,ZIL-BTC,WTC-ETH,KNC-BTC,CNN-BTC,XRP-HT,TOP-BTC,DTA-ETH,REN-BTC,HT-HUSD,AAC-BTC,BTT-USDT,PAI-BTC,LAMB-USDT,YEE-ETH,RSR-HT,BTM-ETH,NKN-USDT,CRE-HT,WXT-BTC,NAS-ETH,SKM-HT,CRE-BTC,DAT-BTC,BIFI-BTC,DGD-ETH,TNT-ETH,HC-USDT,OCN-ETH,XTZ-ETH,PROPY-ETH,BCD-BTC,EKO-BTC,MEET-BTC,SMT-BTC,SALT-BTC,GXC-BTC,HOT-BTC,SBTC-BTC,SALT-ETH,CKB-BTC,UC-BTC,NANO-BTC,PAX-HUSD,FAIR-ETH,SWFTC-ETH,GSC-ETH,ONT-ETH,SKM-BTC,USDT-HUSD,EM-USDT,CKB-HT,XVG-BTC,CVCOIN-ETH,RTE-ETH,BHD-USDT,NANO-ETH,BFT-BTC,ICX-BTC,FTT-HT,XZC-BTC,CVNT-BTC,WAVES-USDT,GNT-ETH,HIT-BTC,LSK-ETH,NCC-BTC,CMT-BTC,STEEM-USDT,ATOM-USDT,LOL-USDT,DOGE-ETH,SMT-ETH,PROPY-BTC,KAN-BTC,DCR-BTC,GXC-ETH,CNN-ETH,REN-USDT,AIDOC-ETH,ITC-USDT,MCO-BTC,ETC-USDT,VSYS-USDT,BSV-USDT,FTT-BTC,TRIO-ETH,GT-USDT,ARPA-USDT,LET-USDT,LTC-BTC,XLM-BTC,NAS-BTC,REQ-BTC,NCASH-BTC,ARPA-BTC,ELA-USDT,ARDR-ETH,RSR-BTC,LAMB-HT,IIC-ETH,MANA-USDT,IOTA-ETH,ONE-BTC,DTA-USDT,MTN-ETH,MX-USDT,MEX-ETH,BTG-BTC,MAN-ETH,VIDY-BTC,RSR-USDT,MX-BTC,UUU-BTC,APPC-BTC,MANA-BTC,PAY-ETH,EGCC-ETH,GTC-BTC,SEELE-BTC,DBC-ETH,MDS-BTC,RCCC-ETH,NEW-USDT,SHE-BTC,STEEM-BTC,BLZ-ETH,ATP-HT,ITC-BTC,FOR-BTC,XMR-ETH,LTC-USDT,CTXC-USDT,XMX-BTC,BUT-BTC,VIDY-USDT,UIP-ETH,18C-ETH,NEW-BTC,NAS-USDT,BCV-BTC,ELF-ETH,BIX-BTC,GT-BTC,ETC-BTC,ONE-USDT,ADX-BTC,LINK-ETH,WAVES-BTC,RDN-ETH,PHX-BTC,ENG-BTC,MDS-USDT,HIT-USDT,IRIS-ETH,ACT-ETH,RCN-BTC,IOST-ETH,EOS-ETH,BOX-ETH,ADA-USDT,SKM-USDT,SMT-USDT,SNT-USDT,RUFF-BTC,POWR-BTC,XTZ-USDT,CKB-USDT,RUFF-ETH,EM-HT,LET-ETH,OCN-USDT,WXT-USDT,LET-BTC,POWR-ETH,PVT-HT,BOX-BTC,CRO-BTC,VSYS-BTC,EM-BTC,QTUM-BTC,LBA-ETH,XRP-USDT,CTXC-ETH,LBA-BTC,QTUM-ETH,GT-HT,CTXC-BTC,GAS-ETH,CRO-HT,PVT-BTC,VSYS-HT,BTS-USDT,NEO-USDT,QUN-BTC,NANO-USDT,BTC-USDT,BTC-HUSD,NULS-USDT,GRS-ETH,ETN-BTC,TOS-BTC,SC-BTC,LXT-USDT,ZEN-BTC,CVC-BTC,SSP-BTC,SNC-BTC,ELF-BTC,ZJLT-ETH,ACT-BTC,OMG-BTC,VET-BTC,EOS-BTC,ATP-USDT,BHT-USDT,GXC-USDT,IRIS-BTC,BIX-USDT,RCN-ETH,REQ-ETH,POLY-BTC,UUU-USDT,EOS-HUSD,CHAT-ETH,MEX-BTC,TOP-USDT,CVC-USDT,EOS-USDT,XLM-USDT,HPT-BTC,SC-ETH,ZEN-ETH,PNT-BTC,UGAS-BTC,DGB-BTC,ATP-BTC,SEELE-USDT,IOTA-BTC,UIP-USDT,ETC-HT,SHE-ETH,MT-BTC,WPR-ETH,PNT-ETH,HPT-USDT,XZC-USDT,VIDY-HT,ELF-USDT,BLZ-BTC,UGAS-ETH,MT-ETH,NULS-ETH,POLY-ETH,ARPA-HT,USDC-HUSD,ONE-HT,RCCC-BTC,ETN-ETH,TRX-ETH,MDS-ETH,DOGE-BTC,EKT-ETH,TUSD-HUSD,FTT-USDT,GAS-BTC,GRS-BTC,SOC-ETH,APPC-ETH,IOST-USDT,LSK-BTC,ONT-USDT,CMT-USDT,ABT-ETH,GET-ETH,WAXP-ETH,DOCK-BTC,OMG-USDT,TRIO-BTC,IOTA-USDT,SNT-BTC,NEW-HT,MCO-ETH,VET-ETH,OGO-HT,FSN-HT,NODE-USDT,ADA-BTC,TOS-ETH,EKO-ETH,EGT-BTC,OCN-BTC,DGD-BTC,ZEC-BTC,BTM-BTC,XTZ-BTC,ACT-USDT,IRIS-USDT,SWFTC-BTC,QTUM-USDT,ZLA-BTC,DCR-ETH,ABT-BTC,MEET-ETH,ZLA-ETH,WXT-HT,BFT-ETH,XVG-ETH,TT-USDT,CVCOIN-BTC,PVT-USDT,CNNS-USDT,HOT-ETH,GVE-ETH,RTE-BTC,GSC-BTC,DAT-ETH,GVE-BTC,RUFF-USDT,ONT-BTC,FAIR-BTC,UC-ETH,LXT-BTC,MTL-BTC,WICC-USDT,LXT-ETH,LBA-USDT,NULS-BTC,TRX-BTC,XEM-BTC,ZIL-ETH,LTC-HT,ZRX-USDT,EKT-BTC,CRO-USDT,XMX-ETH,ENG-ETH,BUT-ETH,BCV-ETH,BIX-ETH,THETA-BTC,XMR-BTC,ADX-ETH,UIP-BTC,WAXP-BTC,ADA-ETH,BTS-ETH,PAI-USDT,SOC-BTC,GET-BTC,DOCK-ETH,KNC-ETH,EGT-HT,LINK-BTC,ETH-BTC,XLM-ETH,BTT-ETH,STORJ-USDT,FSN-BTC,QASH-ETH,SOC-USDT,BTT-TRX,LAMB-ETH,LUN-ETH,TNB-ETH,MXC-BTC,RBTC-BTC,TOP-HT,EOS-HT,ALGO-USDT,MAN-BTC,MT-HT,BCH-BTC,EGCC-BTC,LYM-BTC,BAT-BTC,CHAT-BTC,EKT-USDT,WTC-BTC,DBC-BTC,UUU-ETH,HT-ETH,ALGO-BTC,NCASH-ETH,FSN-USDT,GTC-ETH,SEELE-ETH,THETA-USDT,DGB-ETH,WPR-BTC,DATX-BTC,PORTAL-ETH,XEM-USDT,MTX-BTC,XZC-ETH,CMT-ETH,QUN-ETH,BCH-USDT,LOL-HT,ICX-ETH,GNT-BTC,AKRO-USDT,BCH-HUSD,BHT-BTC,XRP-HUSD,XRP-BTC,OGO-BTC,VET-USDT,TRX-USDT,SSP-ETH,KAN-ETH,SNC-ETH,KMD-ETH,HPT-HT,COVA-ETH,OMG-ETH,WTC-USDT,TNT-BTC,DASH-USDT,BAT-USDT,CVC-ETH,IDT-BTC,ZJLT-BTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "pemKey": "-----BEGIN EC PRIVATE KEY-----\nJUSTADUMMY\n-----END EC PRIVATE KEY-----\n" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1010,40 +1647,69 @@ "name": "ITBIT", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "XBTUSD,XBTSGD", - "enabledPairs": "XBTUSD,XBTSGD", "baseCurrencies": "USD,SGD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": false, - "pairsLastUpdated": 1566798411, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "XBTUSD,XBTSGD", + "available": "XBTUSD,XBTSGD" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": {}, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1056,40 +1722,73 @@ "name": "Kraken", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "ATOM-ETH,QTUM-EUR,QTUM-USD,LTC-XBT,XTZ-ETH,XMR-XBT,ADA-EUR,BAT-ETH,BAT-EUR,BAT-XBT,QTUM-CAD,WAVES-USD,ETC-EUR,MLN-ETH,XLM-USD,XRP-CAD,ADA-USD,DASH-XBT,REP-XBT,XBT-CAD,XBT-EUR,XBT-GBP,USDT-USD,ETH-JPY,XBT-USD,ZEC-USD,ETH-EUR,ETH-USD,XTZ-XBT,ZEC-EUR,ZEC-JPY,ADA-ETH,EOS-ETH,QTUM-ETH,ETH-CAD,XTZ-EUR,EOS-EUR,REP-USD,XMR-USD,BCH-XBT,EOS-XBT,ETC-ETH,XLM-XBT,ADA-CAD,ADA-XBT,ATOM-EUR,ATOM-XBT,DASH-EUR,GNO-USD,GNO-XBT,WAVES-XBT,ETH-GBP,XBT-JPY,ZEC-XBT,QTUM-XBT,WAVES-ETH,XDG-XBT,XRP-XBT,EOS-USD,XMR-EUR,XRP-EUR,ATOM-CAD,DASH-USD,ETC-USD,ETH-XBT,GNO-EUR,ETC-XBT,LTC-EUR,REP-ETH,XTZ-USD,XLM-EUR,GNO-ETH,LTC-USD,REP-EUR,XRP-JPY,XRP-USD,ATOM-USD,BCH-USD,WAVES-EUR,BAT-USD,BCH-EUR,MLN-XBT,XTZ-CAD", - "enabledPairs": "XBT-USD", "baseCurrencies": "EUR,USD,CAD,GBP,JPY", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "separator": "," + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "XBT-USD", + "available": "ATOM-CAD,ATOM-XBT,XBT-CAD,XMR-USD,XRP-USD,BAT-XBT,OMG-USD,XBT-EUR,XTZ-CAD,ETH-GBP,ETH-JPY,XBT-JPY,ZEC-XBT,DASH-XBT,LSK-EUR,PAXG-EUR,XRP-EUR,BCH-XBT,GNO-USD,REP-EUR,XTZ-USD,ADA-CAD,ETH-XBT,LTC-USD,XLM-EUR,DASH-EUR,ICX-XBT,LSK-ETH,ZEC-EUR,REP-XBT,ATOM-EUR,EOS-EUR,EOS-USD,ETC-ETH,NANO-XBT,XTZ-ETH,EOS-XBT,ICX-ETH,LINK-ETH,ETH-EUR,ICX-EUR,ICX-USD,REP-ETH,ADA-EUR,WAVES-EUR,XRP-XBT,MLN-ETH,BAT-EUR,LSK-USD,NANO-USD,OMG-ETH,XLM-USD,XMR-XBT,ZEC-USD,BAT-USD,DAI-USD,PAXG-ETH,WAVES-USD,ADA-ETH,GNO-ETH,XTZ-XBT,ETH-CAD,ETH-USD,MLN-XBT,XBT-GBP,PAXG-USD,NANO-ETH,LTC-XBT,XRP-JPY,ATOM-ETH,LSK-XBT,XLM-XBT,ADA-XBT,GNO-EUR,LINK-XBT,ETC-USD,XTZ-EUR,XBT-USD,QTUM-ETH,QTUM-XBT,ETC-XBT,EOS-ETH,OMG-EUR,XDG-XBT,USDT-USD,ATOM-USD,GNO-XBT,LINK-EUR,SC-EUR,LTC-EUR,BCH-EUR,BCH-USD,QTUM-EUR,SC-XBT,DAI-USDT,NANO-EUR,SC-ETH,WAVES-XBT,ETC-EUR,REP-USD,ADA-USD,ETH-DAI,PAXG-XBT,QTUM-CAD,LINK-USD,OMG-XBT,SC-USD,WAVES-ETH,XMR-EUR,XRP-CAD,ZEC-JPY,BAT-ETH,DAI-EUR,DASH-USD,QTUM-USD" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "separator": "," + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1102,38 +1801,70 @@ "name": "LakeBTC", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BACETH,BTCUSD,USDSGD,USDJPY,LTCBTC,BTCCHF,BTCNZD,BTCJPY,USDNGN,BCHBTC,BTCAUD,NZDUSD,EURUSD,USDHKD,BTCEUR,USDCHF,GBPUSD,XRPBTC,AUDUSD,BTCHKD,BTCGBP,BTCCAD,BTCNGN,BTCSGD,USDCAD,ETHBTC", - "enabledPairs": "BTCUSD,BTCAUD", "baseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,BTCAUD", + "available": "BACETH,BTCUSD,USDSGD,USDJPY,LTCBTC,BTCCHF,BTCNZD,BTCJPY,USDNGN,BCHBTC,BTCAUD,NZDUSD,EURUSD,USDHKD,BTCEUR,USDCHF,GBPUSD,XRPBTC,AUDUSD,BTCHKD,BTCGBP,BTCCAD,BTCNGN,BTCSGD,USDCAD,ETHBTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1146,40 +1877,72 @@ "name": "LBank", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "FBC_USDT,HDS_USDT,GALT_USDT,IOG_USDT,IOEX_USDT,VOLLAR_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,BZKY_ETH,ONOT_ETH,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,BSV_USDT,OPX_USDT,TENA_ETH,VTHO_BTC,VNX_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,ALI_ETH,SDC_ETH,SAIT_ETH,ARTCN_USDT,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,BFDT_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,IIC_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,TKY_ETH,OCN_ETH,DCT_ETH,ZPT_ETH,EKO_ETH,MDA_ETH,PST_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,UIP_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,INC_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,B91_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,CXC_BTC,CXC_USDT,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,VCC_ETH,DLX_USDT,KDS_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT", - "enabledPairs": "btc_usdt", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_" + }, + "configFormat": { + "uppercase": false, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "btc_usdt", + "available": "FBC_USDT,GALT_USDT,IOEX_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,OPX_USDT,VTHO_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,SAIT_ETH,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,OCN_ETH,EKO_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,DLX_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT,DOT_USDT,DILI_USDT,DUO_USDT,TEP_USDT,BIKI_USDT,MX_USDT,DNS_USDT,OKB_USDT,FLDT_USDT,CCTC_USDT,WIN_USDT,BTT_USDT,TRX_USDT,GRS_BTC,GST_USDT,GST_ETH,ABBC_USDT,UTK_USDT,GKI_USDT,BPX_USDT,SUTER_USDT,LT_USDT,LM_USDT" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1192,38 +1955,70 @@ "name": "LocalBitcoins", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTCXAF,BTCHKD,BTCBRL,BTCPLN,BTCGHS,BTCPEN,BTCSAR,BTCCAD,BTCJOD,BTCVES,BTCXOF,BTCRWF,BTCEUR,BTCNOK,BTCLTC,BTCZMW,BTCXRP,BTCPAB,BTCUSD,BTCCRC,BTCTTD,BTCLBP,BTCOMR,BTCRON,BTCGEL,BTCKRW,BTCCLP,BTCSZL,BTCNGN,BTCILS,BTCDKK,BTCMYR,BTCRUB,BTCKES,BTCINR,BTCJPY,BTCKHR,BTCCOP,BTCIRR,BTCARS,BTCKZT,BTCTZS,BTCVND,BTCEGP,BTCGBP,BTCTHB,BTCAED,BTCGTQ,BTCCHF,BTCIDR,BTCAUD,BTCNZD,BTCKWD,BTCBOB,BTCUGX,BTCETH,BTCUAH,BTCSGD,BTCCNY,BTCPHP,BTCTWD,BTCLKR,BTCNAD,BTCMXN,BTCBYN,BTCBDT,BTCDOP,BTCTRY,BTCPYG,BTCPKR,BTCQAR,BTCSEK,BTCMAD,BTCZAR", - "enabledPairs": "BTCAUD,BTCUSD", "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", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCAUD,BTCUSD", + "available": "BTCXAF,BTCRUB,BTCXRP,BTCIDR,BTCTZS,BTCKWD,BTCTHB,BTCPKR,BTCBWP,BTCTRY,BTCCLP,BTCMXN,BTCCZK,BTCLTC,BTCARS,BTCMAD,BTCMUR,BTCGEL,BTCPEN,BTCSAR,BTCKES,BTCBGN,BTCGBP,BTCUAH,BTCCNY,BTCHKD,BTCRON,BTCSGD,BTCPLN,BTCINR,BTCDKK,BTCCRC,BTCUGX,BTCSEK,BTCPYG,BTCZMW,BTCPHP,BTCNZD,BTCNOK,BTCZAR,BTCBDT,BTCUSD,BTCEGP,BTCBOB,BTCRSD,BTCCHF,BTCKZT,BTCPAB,BTCTWD,BTCAED,BTCVND,BTCETH,BTCDOP,BTCCAD,BTCJPY,BTCAUD,BTCBRL,BTCJOD,BTCGHS,BTCQAR,BTCGTQ,BTCKRW,BTCBYN,BTCEUR,BTCBAM,BTCLKR,BTCHUF,BTCTTD,BTCVES,BTCILS,BTCCOP,BTCSZL,BTCMWK,BTCMYR,BTCUYU,BTCNGN,BTCJMD,BTCXOF,BTCRWF" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1236,40 +2031,74 @@ "name": "OKCOIN International", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC-USD,LTC-USD,ETH-USD,ETC-USD,TUSD-USD,USDT-USD,ZEC-USD,ADA-USD,XLM-USD,ZRX-USD,XRP-USD,BAT-USD,PAX-USD,GUSD-USD,USDC-USD,BCH-USD,BSV-USD,TRX-USD,GRIN-USD,DCR-USD,EOS-USD,BTC-TUSD,BTC-USDT,BTC-PAX,BTC-GUSD,BTC-USDC", - "enabledPairs": "BTC-USD", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "margin" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-USD,LTC-USD,ETH-USD,ETC-USD,TUSD-USD,BCH-USD,EOS-USD,XRP-USD,TRX-USD,BSV-USD,USDT-USD,USDK-USD,XLM-USD,ADA-USD,BAT-USD,DCR-USD,EURS-USD,HBAR-USD,PAX-USD,USDC-USD,ZEC-USD,BTC-USDT,BTC-EUR,BTC-EURS,ETH-EUR,BCH-EUR,EURS-EUR" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1282,40 +2111,108 @@ "name": "OKEX", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "DASH-BTC,CTXC-BTC,ZIL-BTC,YOU-BTC,LBA-BTC,LSK-BTC,CAI-BTC,AE-BTC,SC-BTC,KAN-BTC,WIN-BTC,DCR-BTC,WAVES-BTC,ORS-BTC,NXT-BTC,ARDR-BTC,XAS-BTC,CVT-BTC,EGT-BTC,ZCO-BTC,LET-BTC,HPB-BTC,ADA-BTC,HYC-BTC,VITE-BTC,ABL-BTC,PAX-BTC,TUSD-BTC,USDC-BTC,GUSD-BTC,BCH-BTC,BSV-BTC,BTT-BTC,ATOM-BTC,BLOC-BTC,XRP-BTC,LRC-BTC,NULS-BTC,MCO-BTC,ELF-BTC,ZEC-BTC,CMT-BTC,ITC-BTC,SBTC-BTC,EDO-BTC,BCX-BTC,NEO-BTC,GAS-BTC,HC-BTC,QTUM-BTC,IOTA-BTC,XUC-BTC,EOS-BTC,SNT-BTC,OMG-BTC,LTC-BTC,ETH-BTC,ETC-BTC,BCD-BTC,BTG-BTC,ACT-BTC,PAY-BTC,BTM-BTC,DGD-BTC,GNT-BTC,LINK-BTC,WTC-BTC,ZRX-BTC,BNT-BTC,CVC-BTC,MANA-BTC,KNC-BTC,GNX-BTC,ICX-BTC,XEM-BTC,ARK-BTC,YOYO-BTC,FUN-BTC,ACE-BTC,TRX-BTC,DGB-BTC,SWFTC-BTC,XMR-BTC,XLM-BTC,KCASH-BTC,MDT-BTC,NAS-BTC,UGC-BTC,DPY-BTC,SSC-BTC,AAC-BTC,VIB-BTC,QUN-BTC,INT-BTC,IOST-BTC,INS-BTC,MOF-BTC,TCT-BTC,STC-BTC,THETA-BTC,PST-BTC,SNC-BTC,MKR-BTC,LIGHT-BTC,TRUE-BTC,OF-BTC,SOC-BTC,ZEN-BTC,HMC-BTC,ZIP-BTC,NANO-BTC,CIC-BTC,GTO-BTC,CHAT-BTC,INSUR-BTC,R-BTC,BEC-BTC,MITH-BTC,ABT-BTC,BKX-BTC,RFR-BTC,TRIO-BTC,DADI-BTC,ONT-BTC,OKB-BTC,CTXC-ETH,ZIL-ETH,YOU-ETH,LBA-ETH,LSK-ETH,CAI-ETH,SC-ETH,AE-ETH,KAN-ETH,WIN-ETH,DCR-ETH,WAVES-ETH,ORS-ETH,MVP-ETH,EGT-ETH,ZCO-ETH,LET-ETH,HPB-ETH,SDA-ETH,ADA-ETH,HYC-ETH,VITE-ETH,ABL-ETH,BTT-ETH,ATOM-ETH,ELF-ETH,LTC-ETH,CMT-ETH,ITC-ETH,PRA-ETH,EDO-ETH,LRC-ETH,NULS-ETH,MCO-ETH,STORJ-ETH,SNT-ETH,PAY-ETH,DGD-ETH,GNT-ETH,ACT-ETH,BTM-ETH,EOS-ETH,OMG-ETH,DASH-ETH,XRP-ETH,ZEC-ETH,NEO-ETH,GAS-ETH,HC-ETH,QTUM-ETH,IOTA-ETH,XUC-ETH,ETC-ETH,LINK-ETH,WTC-ETH,ZRX-ETH,BNT-ETH,CVC-ETH,MANA-ETH,GNX-ETH,ICX-ETH,XEM-ETH,ARK-ETH,YOYO-ETH,TRX-ETH,DGB-ETH,PPT-ETH,SWFTC-ETH,XMR-ETH,XLM-ETH,KCASH-ETH,MDT-ETH,NAS-ETH,RNT-ETH,UGC-ETH,DPY-ETH,SSC-ETH,AAC-ETH,FAIR-ETH,RCT-ETH,VIB-ETH,TOPC-ETH,QUN-ETH,INT-ETH,IOST-ETH,INS-ETH,MOF-ETH,REF-ETH,THETA-ETH,PST-ETH,SNC-ETH,MKR-ETH,LIGHT-ETH,TRUE-ETH,OF-ETH,SOC-ETH,ZEN-ETH,HMC-ETH,ZIP-ETH,NANO-ETH,CIC-ETH,GTO-ETH,INSUR-ETH,R-ETH,UCT-ETH,BEC-ETH,MITH-ETH,ABT-ETH,BKX-ETH,AUTO-ETH,RFR-ETH,TRIO-ETH,TRA-ETH,DADI-ETH,ONT-ETH,OKB-ETH,CTXC-USDT,ZIL-USDT,YOU-OKB,YOU-USDT,LBA-OKB,LBA-USDT,CAI-OKB,LSK-USDT,CAI-USDT,AE-OKB,SC-OKB,KAN-OKB,WIN-OKB,SC-USDT,AE-USDT,KAN-USDT,WIN-USDT,ORS-OKB,DCR-OKB,DCR-USDT,WAVES-OKB,WAVES-USDT,ORS-USDT,MVP-USDT,NAS-OKB,XAS-OKB,ZCO-OKB,EGT-OKB,XAS-USDT,CVT-USDT,EGT-USDT,LET-OKB,LET-USDT,HPB-OKB,HPB-USDT,SDA-OKB,ADA-OKB,ADA-USDT,HYC-USDT,VITE-OKB,TRX-OKB,PAX-USDT,TUSD-USDT,USDC-USDT,GUSD-USDT,BCH-USDT,BSV-USDT,BTT-USDT,BLOC-OKB,BLOC-USDT,ATOM-USDT,ELF-USDT,DASH-USDT,LRC-USDT,NULS-USDT,MCO-USDT,BTG-USDT,DASH-OKB,XRP-USDT,ZEC-USDT,NEO-USDT,GAS-USDT,HC-USDT,QTUM-USDT,IOTA-USDT,BTC-USDT,BCD-USDT,XUC-USDT,CMT-USDT,ITC-USDT,PRA-USDT,EDO-USDT,ETH-USDT,LTC-USDT,ETC-USDT,EOS-USDT,OMG-USDT,ACT-USDT,BTM-USDT,STORJ-USDT,PAY-USDT,DGD-USDT,GNT-USDT,SNT-USDT,LINK-USDT,WTC-USDT,ZRX-USDT,BNT-USDT,CVC-USDT,MANA-USDT,KNC-USDT,ICX-USDT,XEM-USDT,ARK-USDT,YOYO-USDT,AST-USDT,TRX-USDT,MDA-USDT,DGB-USDT,PPT-USDT,SWFTC-USDT,XMR-USDT,XLM-USDT,KCASH-USDT,MDT-USDT,NAS-USDT,RNT-USDT,UGC-USDT,DPY-USDT,SSC-USDT,AAC-USDT,FAIR-USDT,UBTC-USDT,SHOW-USDT,VIB-USDT,MOT-USDT,UTK-USDT,TOPC-USDT,QUN-USDT,INT-USDT,IPC-USDT,IOST-USDT,INS-USDT,YEE-USDT,MOF-USDT,TCT-USDT,STC-USDT,THETA-USDT,PST-USDT,MKR-USDT,LIGHT-USDT,TRUE-USDT,OF-USDT,SOC-USDT,ZEN-USDT,HMC-USDT,ZIP-USDT,NANO-USDT,CIC-USDT,GTO-USDT,CHAT-USDT,INSUR-USDT,R-USDT,BEC-USDT,MITH-USDT,ABT-USDT,BKX-USDT,RFR-USDT,TRIO-USDT,DADI-USDT,ONT-USDT,OKB-USDT,NEO-OKB,LTC-OKB,ETC-OKB,XRP-OKB,ZEC-OKB,QTUM-OKB,IOTA-OKB,EOS-OKB", - "enabledPairs": "EOS-USDT", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "assetTypes": [ + "spot", + "futures", + "perpetualswap", + "index" + ], + "pairs": { + "futures": { + "available": "XRP-USD_191206,XRP-USD_191213,XRP-USD_191227,BTC-USD_191206,BTC-USD_191213,BTC-USD_191227,BTC-USDT_191206,BTC-USDT_191213,BTC-USDT_191227,LTC-USD_191206,LTC-USD_191213,LTC-USD_191227,LTC-USDT_191206,LTC-USDT_191213,LTC-USDT_191227,ETH-USD_191206,ETH-USD_191213,ETH-USD_191227,ETH-USDT_191206,ETH-USDT_191213,ETH-USDT_191227,ETC-USD_191206,ETC-USD_191213,ETC-USD_191227,BCH-USD_191206,BCH-USD_191213,BCH-USD_191227,BCH-USDT_191206,BCH-USDT_191213,BCH-USDT_191227,BSV-USD_191206,BSV-USD_191213,BSV-USD_191227,EOS-USDT_191206,EOS-USDT_191213,EOS-USDT_191227,EOS-USD_191206,EOS-USD_191213,EOS-USD_191227,TRX-USD_191206,TRX-USD_191213,TRX-USD_191227", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "index": { + "available": "XRP-USD,XRP-USD,XRP-USD,BTC-USD,BTC-USD,BTC-USD,BTC-USDT,BTC-USDT,BTC-USDT,LTC-USD,LTC-USD,LTC-USD,LTC-USDT,LTC-USDT,LTC-USDT,ETH-USD,ETH-USD,ETH-USD,ETH-USDT,ETH-USDT,ETH-USDT,ETC-USD,ETC-USD,ETC-USD,BCH-USD,BCH-USD,BCH-USD,BCH-USDT,BCH-USDT,BCH-USDT,BSV-USD,BSV-USD,BSV-USD,EOS-USDT,EOS-USDT,EOS-USDT,EOS-USD,EOS-USD,EOS-USD,TRX-USD,TRX-USD,TRX-USD", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "perpetualswap": { + "available": "BTC-USD_SWAP,LTC-USD_SWAP,ETH-USD_SWAP,TRX-USD_SWAP,BCH-USD_SWAP,BSV-USD_SWAP,EOS-USD_SWAP,XRP-USD_SWAP,ETC-USD_SWAP", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "spot": { + "enabled": "EOS-USDT", + "available": "XPO-USDT,SPND-USDK,SPND-BTC,ROAD-USDK,BCH-BTC,BSV-BTC,DASH-BTC,ADA-BTC,ABL-BTC,AE-BTC,ALGO-BTC,ARDR-BTC,ATOM-BTC,BLOC-BTC,BTT-BTC,CAI-BTC,CRO-BTC,CTXC-BTC,CVT-BTC,DCR-BTC,EGT-BTC,GUSD-BTC,HBAR-BTC,HPB-BTC,HYC-BTC,KAN-BTC,LBA-BTC,LEO-BTC,LET-BTC,LSK-BTC,NXT-BTC,ORS-BTC,PAX-BTC,PMA-BTC,SC-BTC,TUSD-BTC,USDC-BTC,VITE-BTC,VSYS-BTC,WAVES-BTC,WIN-BTC,WXT-BTC,XAS-BTC,XTZ-BTC,YOU-BTC,ZIL-BTC,XRP-BTC,ELF-BTC,LRC-BTC,MCO-BTC,NULS-BTC,BCX-BTC,CMT-BTC,EDO-BTC,ITC-BTC,SBTC-BTC,ZEC-BTC,NEO-BTC,GAS-BTC,HC-BTC,QTUM-BTC,IOTA-BTC,XUC-BTC,EOS-BTC,SNT-BTC,OMG-BTC,LTC-BTC,ETH-BTC,ETC-BTC,BCD-BTC,BTG-BTC,ACT-BTC,PAY-BTC,BTM-BTC,DGD-BTC,GNT-BTC,LINK-BTC,WTC-BTC,ZRX-BTC,BNT-BTC,CVC-BTC,MANA-BTC,KNC-BTC,GNX-BTC,ICX-BTC,XEM-BTC,ARK-BTC,YOYO-BTC,FUN-BTC,ACE-BTC,TRX-BTC,DGB-BTC,SWFTC-BTC,XMR-BTC,XLM-BTC,KCASH-BTC,MDT-BTC,NAS-BTC,UGC-BTC,DPY-BTC,SSC-BTC,AAC-BTC,VIB-BTC,QUN-BTC,INT-BTC,IOST-BTC,INS-BTC,MOF-BTC,TCT-BTC,STC-BTC,THETA-BTC,PST-BTC,SNC-BTC,MKR-BTC,LIGHT-BTC,OF-BTC,TRUE-BTC,SOC-BTC,ZEN-BTC,HMC-BTC,ZIP-BTC,NANO-BTC,CIC-BTC,GTO-BTC,CHAT-BTC,INSUR-BTC,R-BTC,BEC-BTC,MITH-BTC,ABT-BTC,BKX-BTC,RFR-BTC,TRIO-BTC,EDGE-BTC,ONT-BTC,OKB-BTC,ADA-ETH,ABL-ETH,AE-ETH,ALGO-ETH,ATOM-ETH,BTT-ETH,CAI-ETH,CTXC-ETH,DCR-ETH,EGT-ETH,HPB-ETH,HYC-ETH,KAN-ETH,LEO-ETH,MVP-ETH,ORS-ETH,SC-ETH,SDA-ETH,WAVES-ETH,WIN-ETH,YOU-ETH,ZIL-ETH,ELF-ETH,LTC-ETH,CMT-ETH,PRA-ETH,LRC-ETH,MCO-ETH,NULS-ETH,DGD-ETH,STORJ-ETH,BTM-ETH,EOS-ETH,OMG-ETH,DASH-ETH,XRP-ETH,ZEC-ETH,NEO-ETH,GAS-ETH,HC-ETH,QTUM-ETH,IOTA-ETH,ETC-ETH,LINK-ETH,WTC-ETH,ZRX-ETH,CVC-ETH,MANA-ETH,GNX-ETH,XEM-ETH,TRX-ETH,SWFTC-ETH,XMR-ETH,XLM-ETH,KCASH-ETH,MDT-ETH,NAS-ETH,SSC-ETH,AAC-ETH,FAIR-ETH,RCT-ETH,TOPC-ETH,INT-ETH,IOST-ETH,INS-ETH,MOF-ETH,REF-ETH,MKR-ETH,LIGHT-ETH,OF-ETH,TRUE-ETH,ZEN-ETH,NANO-ETH,CIC-ETH,GTO-ETH,UCT-ETH,MITH-ETH,ABT-ETH,AUTO-ETH,TRIO-ETH,ONT-ETH,OKB-ETH,BTC-USDK,LTC-USDK,ETH-USDK,OKB-USDK,ETC-USDK,BCH-USDT,BCH-USDK,EOS-USDK,XRP-USDK,TRX-USDK,BSV-USDT,BSV-USDK,USDT-USDK,ADA-USDT,AE-USDT,ALGO-USDT,ALGO-USDK,ALV-USDT,ATOM-USDT,BLOC-USDT,BTT-USDT,CAI-USDT,CRO-USDT,CRO-USDK,CTXC-USDT,CVT-USDT,DCR-USDT,DOGE-USDT,DOGE-USDK,EC-USDT,EC-USDK,EGT-USDT,EM-USDT,EM-USDK,ETM-USDT,ETM-USDK,FSN-USDT,FSN-USDK,FTM-USDT,FTM-USDK,GUSD-USDT,HBAR-USDT,HBAR-USDK,HPB-USDT,HYC-USDT,KAN-USDT,LAMB-USDT,LAMB-USDK,LBA-USDT,LEO-USDT,LEO-USDK,LET-USDT,LSK-USDT,MVP-USDT,ORBS-USDT,ORBS-USDK,ORS-USDT,PAX-USDT,PLG-USDT,PLG-USDK,PMA-USDK,ROAD-USDT,SC-USDT,TUSD-USDT,USDC-USDT,VNT-USDT,VNT-USDK,VSYS-USDT,VSYS-USDK,WAVES-USDT,WIN-USDT,WXT-USDT,WXT-USDK,XAS-USDT,XPO-USDK,XTZ-USDT,YOU-USDT,ZIL-USDT,TRX-OKB,AE-OKB,BLOC-OKB,EGT-OKB,SC-OKB,WXT-OKB,ELF-USDT,DASH-USDT,BTG-USDT,LRC-USDT,MCO-USDT,NULS-USDT,DASH-OKB,XRP-USDT,ZEC-USDT,NEO-USDT,GAS-USDT,HC-USDT,QTUM-USDT,IOTA-USDT,BTC-USDT,BCD-USDT,XUC-USDT,CMT-USDT,EDO-USDT,ITC-USDT,PRA-USDT,ETH-USDT,LTC-USDT,ETC-USDT,EOS-USDT,OMG-USDT,ACT-USDT,BTM-USDT,DGD-USDT,GNT-USDT,PAY-USDT,STORJ-USDT,SNT-USDT,LINK-USDT,WTC-USDT,ZRX-USDT,BNT-USDT,CVC-USDT,MANA-USDT,KNC-USDT,ICX-USDT,XEM-USDT,ARK-USDT,YOYO-USDT,AST-USDT,TRX-USDT,MDA-USDT,DGB-USDT,PPT-USDT,SWFTC-USDT,XMR-USDT,XLM-USDT,KCASH-USDT,MDT-USDT,NAS-USDT,RNT-USDT,UGC-USDT,DPY-USDT,SSC-USDT,AAC-USDT,FAIR-USDT,UBTC-USDT,SHOW-USDT,VIB-USDT,MOT-USDT,UTK-USDT,TOPC-USDT,QUN-USDT,INT-USDT,IPC-USDT,IOST-USDT,INS-USDT,YEE-USDT,MOF-USDT,TCT-USDT,STC-USDT,THETA-USDT,PST-USDT,MKR-USDT,LIGHT-USDT,OF-USDT,TRUE-USDT,SOC-USDT,ZEN-USDT,HMC-USDT,ZIP-USDT,NANO-USDT,CIC-USDT,GTO-USDT,CHAT-USDT,INSUR-USDT,R-USDT,BEC-USDT,MITH-USDT,ABT-USDT,BKX-USDT,RFR-USDT,TRIO-USDT,EDGE-USDT,ONT-USDT,OKB-USDT,NEO-OKB,LTC-OKB,ETC-OKB,XRP-OKB,ZEC-OKB,IOTA-OKB,EOS-OKB", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1328,40 +2225,72 @@ "name": "Poloniex", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC_DASH,BTC_VIA,USDC_ZEC,USDT_BCHSV,BTC_XEM,USDT_STR,ETH_REP,BTC_MANA,USDC_STR,USDC_ETH,USDT_BTC,BTC_REP,BTC_PASC,BTC_GNT,ETH_ZRX,BTC_SNT,ETH_BAT,USDC_DOGE,BTC_POLY,BTC_ATOM,BTC_BAT,BTC_DGB,BTC_NXT,BTC_STR,BTC_STRAT,BTC_EOS,ETH_EOS,BTC_KNC,USDT_SC,BTC_BCHSV,BTC_XMR,BTC_STEEM,BTC_ZEC,BTC_GAS,USDT_BCHABC,USDC_ATOM,BTC_XRP,USDT_DASH,USDT_ETH,USDT_DOGE,BTC_QTUM,USDC_BTC,BTC_LPT,USDT_DGB,BTC_DOGE,USDT_BAT,USDC_BCHSV,BTC_BTS,BTC_GAME,BTC_SC,BTC_OMG,USDT_MANA,BTC_GRIN,BTC_LTC,BTC_ETH,USDT_EOS,USDT_LSK,USDC_BCHABC,USDC_XMR,BTC_NMR,USDT_REP,BTC_ZRX,USDT_GNT,USDT_QTUM,BTC_BNT,USDC_EOS,BTC_BCN,USDT_ATOM,USDC_DASH,BTC_OMNI,BTC_FCT,BTC_LSK,USDC_XRP,BTC_FOAM,BTC_CVC,BTC_NAV,USDT_LTC,USDT_NXT,USDT_XMR,USDT_XRP,BTC_LBC,USDT_ETC,BTC_LOOM,USDT_GRIN,BTC_DCR,BTC_ETC,ETH_ETC,BTC_ARDR,USDT_ZEC,BTC_BCHABC,USDC_GRIN,BTC_STORJ,USDT_ZRX,USDC_USDT,USDC_ETC,BTC_CLAM,BTC_MAID,BTC_VTC,BTC_XPM,ETH_ZEC,USDC_LTC", - "enabledPairs": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", + "available": "BTC_ATOM,USDT_BCHABC,BTC_STR,USDT_DASH,ETH_BAT,BTC_MANA,USDT_MANA,USDT_ETC,USDT_REP,BTC_STORJ,USDT_BCHSV,USDC_GRIN,BTC_DOGE,BTC_XRP,USDT_ZEC,USDT_DOGE,USDC_ETH,BTC_LSK,ETH_ETC,BTC_ZRX,USDC_ZEC,USDT_XMR,USDT_EOS,BTC_QTUM,USDC_STR,USDC_USDT,BTC_BNT,BTC_BCHSV,BTC_LTC,BTC_ETC,BTC_REP,BTC_ARDR,BTC_KNC,BTC_LPT,BTC_TRX,USDT_ETH,USDT_ATOM,USDC_ETC,USDC_BCHABC,USDC_ATOM,USDT_GRIN,BTC_XPM,USDT_BAT,BTC_LOOM,USDT_QTUM,BTC_BCHABC,BTC_GAS,USDT_GNT,USDC_LTC,BTC_BTS,BTC_DASH,USDT_STR,BTC_ETH,ETH_ZEC,USDC_DASH,BTC_XMR,USDT_BTC,BTC_SC,BTC_ZEC,BTC_MAID,BTC_NXT,ETH_REP,BTC_STRAT,BTC_NMR,USDC_XMR,USDC_EOS,BTC_OMNI,BTC_OMG,ETH_EOS,USDT_ZRX,USDC_BCHSV,BTC_BCN,BTC_EOS,BTC_BAT,USDT_DGB,USDC_BTC,USDC_DOGE,USDT_TRX,BTC_DGB,BTC_FCT,ETH_ZRX,USDT_LSK,USDT_SC,BTC_VIA,USDT_LTC,BTC_GNT,USDC_XRP,BTC_CVC,BTC_SNT,BTC_FOAM,BTC_VTC,BTC_XEM,USDT_NXT,USDT_XRP,BTC_DCR,BTC_POLY,BTC_GRIN,USDC_TRX" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1374,42 +2303,74 @@ "name": "Yobit", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR", - "enabledPairs": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", "baseCurrencies": "USD,RUR", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": false, - "pairsLastUpdated": 1566798411, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_", + "separator": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", + "available": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_", - "separator": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1422,40 +2383,72 @@ "name": "ZB", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "DASH_USDT,XLM_QC,DOGE_QC,SBTC_USDT,SNT_USDT,BRC_BTC,BCHSV_QC,TUSD_USDT,ZB_BTC,GRIN_USDT,BAT_USDT,HPY_USDT,ADA_BTC,XTZ_USDT,XWC_USDT,YTNB_USDT,QTUM_USDT,EDO_USDT,BTC_QC,ETC_PAX,TV_BTC,HSR_BTC,XWC_QC,TRX_USDT,VSYS_ZB,LTC_PAX,OMG_QC,ETH_BTC,NEO_BTC,HPY_QC,TOPC_USDT,ICX_USDT,BCX_USDT,GNT_QC,B91_QC,EOS_QC,PAX_QC,BTC_PAX,XRP_QC,LTC_USDT,MANA_BTC,BITE_BTC,EOS_BTC,XUC_QC,HOTC_QC,BAR_USDT,ETZ_QC,XRP_USDT,HOTC_USDT,DOGE_BTC,ZRX_BTC,TRUE_USDT,GRAM_USDT,BTH_QC,HLC_QC,SLT_QC,BCD_USDT,ETC_USDT,GNT_BTC,BTP_QC,ZRX_USDT,BCW_QC,PDX_QC,QTUM_BTC,LTC_QC,BRC_USDT,EPC_QC,GRAM_QC,CHAT_USDT,KNC_QC,DASH_BTC,XMR_QC,XEM_QC,BTP_USDT,HSR_QC,BCD_QC,EOSDAC_USDT,MTL_USDT,ENTC_USDT,KNC_USDT,MITH_QC,SAFE_USDT,1ST_USDT,TRX_QC,OMG_BTC,BRC_QC,MCO_QC,LBTC_BTC,KAN_BTC,1ST_QC,BTM_QC,INK_USDT,GRIN_QC,UBTC_QC,EPC_BTC,XEM_BTC,TV_USDT,ETC_BTC,XEM_USDT,UBTC_USDT,TRUE_BTC,HSR_USDT,BCHSV_USDT,AE_BTC,BCX_QC,ETH_PAX,ACC_USDT,OMG_USDT,ETZ_USDT,DDM_QC,KAN_QC,INK_QC,DOGE_USDT,BCHABC_QC,BITCNY_QC,TRUE_QC,DASH_QC,QUN_USDT,ZRX_QC,BTM_BTC,BTM_USDT,HLC_USDT,SLT_USDT,BTC_USDT,CDC_QC,AE_QC,LBTC_USDT,MCO_USDT,XLM_BTC,LEO_USDT,BTN_QC,SAFE_QC,XRP_BTC,BTS_BTC,BCX_BTC,DDM_USDT,TRX_BTC,QUN_QC,BTS_USDT,PDX_BTC,ETC_QC,BCHABC_USDT,QTUM_QC,ADA_USDT,EOSDAC_QC,BDS_QC,BTN_USDT,SLT_BTC,PDX_USDT,SUB_QC,USDT_QC,TOPC_QC,XMR_USDT,BAT_QC,SNT_QC,B91_USDT,GNT_USDT,PAX_USDT,AE_USDT,ZB_USDT,NWT_USDT,CDC_USDT,RCN_USDT,NEO_QC,MANA_USDT,TV_QC,VSYS_BTC,ZB_QC,GRAM_BTC,BTH_USDT,AAA_QC,ICX_QC,LTC_BTC,ETH_QC,CHAT_QC,BCW_USDT,SNT_BTC,ADA_QC,VSYS_QC,XLM_USDT,BAT_BTC,ETH_USDT,EOS_USDT,ICX_BTC,LBTC_QC,NEO_USDT,MANA_QC,BTS_QC", - "enabledPairs": "BTC_USDT,ETH_USDT", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USDT,ETH_USDT", + "available": "TOPC_QC,CDC_QC,1ST_USDT,TSR_USDT,XRP_QC,ZRX_USDT,INK_QC,PAX_USDT,BTC_USDT,B91_QC,ETZ_USDT,ETH_USDT,SLT_QC,BRC_QC,XWC_QC,ZB_BTC,EOS_BTC,BTH_QC,BCD_USDT,MTL_USDT,SLT_BTC,EOS_USDT,HLC_QC,EPC_QC,QUN_USDT,BTP_USDT,NEO_BTC,LTC_QC,BRC_USDT,SNT_BTC,BCW_QC,BAT_QC,ADA_QC,HSR_BTC,ZRX_QC,SLT_USDT,SNT_QC,MANA_QC,MITH_QC,XEM_USDT,ZB_USDT,RCN_USDT,LVN_QC,ADA_BTC,PDX_USDT,CDC_USDT,PAX_QC,XLM_QC,LEO_USDT,BCHSV_QC,BTM_BTC,ETH_QC,DOGE_USDT,MANA_BTC,GRAM_QC,TRX_USDT,AAA_QC,TOPC_USDT,TRX_QC,YTNB_USDT,EDO_USDT,HSR_USDT,XRP_BTC,LTC_USDT,TUSD_USDT,EOSDAC_QC,BTM_USDT,HPY_USDT,DOGE_BTC,XEM_QC,QUN_QC,DASH_BTC,BCW_USDT,SUB_QC,SAFE_QC,ZB_QC,OMG_QC,ICX_QC,DDM_USDT,BCX_USDT,BAT_BTC,EOS_QC,BTS_USDT,GNT_QC,LBTC_USDT,MANA_USDT,TRUE_QC,UBTC_QC,BRC_BTC,CHAT_USDT,ICX_BTC,BITCNY_QC,MCO_USDT,BCHSV_USDT,EOSDAC_USDT,TRUE_BTC,VSYS_BTC,CRO_QC,BCHABC_QC,TV_QC,ETC_BTC,EPC_BTC,HPY_QC,XMR_QC,GRAM_BTC,VSYS_QC,QTUM_USDT,NWT_USDT,ACC_USDT,NEO_QC,OMG_BTC,GRIN_QC,HSR_QC,INK_USDT,AE_QC,BTH_USDT,BCX_QC,ADA_USDT,KNC_USDT,B91_USDT,ENTC_USDT,TV_USDT,XRP_USDT,ETZ_QC,BCX_BTC,TRUE_USDT,USDT_QC,XLM_USDT,DASH_USDT,XWC_USDT,HOTC_USDT,1ST_QC,MCO_QC,GRAM_USDT,QTUM_QC,UBTC_USDT,SBTC_USDT,KNC_QC,XTZ_USDT,NEO_USDT,KAN_BTC,BTP_QC,DASH_QC,TV_BTC,QTUM_BTC,BAT_USDT,BCHABC_USDT,FN_QC,BAR_USDT,ZRX_BTC,PDX_BTC,GNT_USDT,BTN_QC,BTS_QC,GRIN_USDT,TRX_BTC,ETH_BTC,LBTC_QC,BTN_USDT,VSYS_ZB,AE_USDT,BITE_BTC,ICX_USDT,DDM_QC,PDX_QC,LTC_BTC,KAN_QC,BTS_BTC,GNT_BTC,BCD_QC,HOTC_QC,BDS_QC,LBTC_BTC,CHAT_QC,ETC_USDT,SNT_USDT,BTC_QC,XEM_BTC,XMR_USDT,ETC_QC,BTM_QC,OMG_USDT,DOGE_QC,XLM_BTC,HLC_USDT,AE_BTC,SAFE_USDT,XUC_QC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1467,8 +2460,12 @@ ], "bankAccounts": [ { + "enabled": false, "bankName": "test", "bankAddress": "test", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "TestAccount", "accountNumber": "0234", "swiftCode": "91272837", @@ -1476,20 +2473,5 @@ "supportedCurrencies": "USD", "supportedExchanges": "ANX,Kraken" } - ], - "connectionMonitor": { - "preferredDNSList": [ - "8.8.8.8", - "8.8.4.4", - "1.1.1.1", - "1.0.0.1" - ], - "preferredDomainList": [ - "www.google.com", - "www.cloudflare.com", - "www.facebook.com" - ], - "checkInterval": 1000000000 - }, - "fiatDispayCurrency": "" -} + ] +} \ No newline at end of file diff --git a/engine/syncer.go b/engine/syncer.go index d900160e..cdeb418d 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -371,8 +371,8 @@ func (e *ExchangeCurrencyPairSyncer) worker() { c.Ticker.IsUsingWebsocket = false c.Ticker.IsUsingREST = true log.Warnf(log.SyncMgr, - "%s %s: No ticker update after 10 seconds, switching from websocket to rest\n", - c.Exchange, FormatCurrency(enabledPairs[i]).String()) + "%s %s: No ticker update after %s, switching from websocket to rest\n", + c.Exchange, FormatCurrency(enabledPairs[i]).String(), e.Cfg.SyncTimeout) switchedToRest = true e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, false) } @@ -435,8 +435,8 @@ func (e *ExchangeCurrencyPairSyncer) worker() { c.Orderbook.IsUsingWebsocket = false c.Orderbook.IsUsingREST = true log.Warnf(log.SyncMgr, - "%s %s: No orderbook update after 15 seconds, switching from websocket to rest\n", - c.Exchange, FormatCurrency(c.Pair).String()) + "%s %s: No orderbook update after %s, switching from websocket to rest\n", + c.Exchange, FormatCurrency(c.Pair).String(), e.Cfg.SyncTimeout) switchedToRest = true e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, false) } diff --git a/main.go b/main.go index 38dab747..fdb02f25 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,7 @@ func main() { flag.BoolVar(&settings.EnableAllPairs, "enableallpairs", false, "enables all pairs for enabled exchanges") flag.BoolVar(&settings.EnablePortfolioManager, "portfoliomanager", true, "enables the portfolio manager") flag.BoolVar(&settings.EnableGRPC, "grpc", true, "enables the grpc server") - flag.BoolVar(&settings.EnableGRPCProxy, "grpcproxy", true, "enables the grpc proxy server") + flag.BoolVar(&settings.EnableGRPCProxy, "grpcproxy", false, "enables the grpc proxy server") flag.BoolVar(&settings.EnableWebsocketRPC, "websocketrpc", true, "enables the websocket RPC server") flag.BoolVar(&settings.EnableDeprecatedRPC, "deprecatedrpc", true, "enables the deprecated RPC server") flag.BoolVar(&settings.EnableCommsRelayer, "enablecommsrelayer", true, "enables available communications relayer") diff --git a/testdata/configtest.json b/testdata/configtest.json index 041a3654..b831e9b8 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -2,12 +2,51 @@ "name": "Skynet", "encryptConfig": -1, "globalHTTPTimeout": 15000000000, + "database": { + "enabled": false, + "verbose": false, + "driver": "sqlite3", + "connectionDetails": { + "host": "", + "port": 0, + "username": "", + "password": "", + "database": "gocryptotrader.db", + "sslmode": "" + } + }, "logging": { "enabled": true, - "file": "debug.txt", - "colour": false, - "level": "DEBUG|WARN|INFO|ERROR|FATAL", - "rotate": true + "level": "INFO|DEBUG|WARN|ERROR", + "output": "console", + "fileSettings": { + "filename": "log.txt", + "rotate": false + }, + "advancedSettings": { + "spacer": " | ", + "timeStampFormat": " 02/01/2006 15:04:05 ", + "headers": { + "info": "[INFO]", + "warn": "[WARN]", + "debug": "[DEBUG]", + "error": "[ERROR]" + } + } + }, + "connectionMonitor": { + "preferredDNSList": [ + "8.8.8.8", + "8.8.4.4", + "1.1.1.1", + "1.0.0.1" + ], + "preferredDomainList": [ + "www.google.com", + "www.cloudflare.com", + "www.facebook.com" + ], + "checkInterval": 1000000000 }, "profiler": { "enabled": false @@ -95,6 +134,7 @@ }, "smsGlobal": { "name": "SMSGlobal", + "from": "Skynet", "enabled": true, "verbose": false, "username": "1234", @@ -115,6 +155,7 @@ "port": "537", "accountName": "some", "accountPassword": "password", + "from": "", "recipientList": "lol123@gmail.com" }, "telegram": { @@ -124,6 +165,27 @@ "verificationToken": "testest" } }, + "remoteControl": { + "username": "admin", + "password": "Password", + "gRPC": { + "enabled": true, + "listenAddress": "localhost:9052", + "grpcProxyEnabled": true, + "grpcProxyListenAddress": "localhost:9053" + }, + "deprecatedRPC": { + "enabled": true, + "listenAddress": "localhost:9050" + }, + "websocketRPC": { + "enabled": true, + "listenAddress": "localhost:9051", + "connectionLimit": 1, + "maxAuthFailures": 3, + "allowInsecureOrigin": true + } + }, "portfolioAddresses": { "addresses": [ { @@ -152,53 +214,76 @@ } ] }, - "webserver": { - "enabled": true, - "adminUsername": "admin", - "adminPassword": "Password", - "listenAddress": ":9050", - "websocketConnectionLimit": 1, - "websocketMaxAuthFailures": 3, - "websocketAllowInsecureOrigin": true - }, "exchanges": [ { "name": "ANX", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "ETH_HKD,START_GBP,BTC_CAD,OAX_ETH,START_SGD,LTC_BTC,STR_BTC,ATENC_NZD,BTC_AUD,BTC_SGD,ETH_BTC,XRP_BTC,START_JPY,ATENC_CAD,BTC_GBP,ETH_USD,GNT_ETH,START_AUD,START_HKD,ATENC_GBP,BTC_USD,START_BTC,START_CAD,START_EUR,BTC_JPY,BTC_NZD,DOGE_BTC,ATENC_EUR,ATENC_JPY,ATENC_USD,BTC_EUR,BTC_HKD,START_NZD,START_USD,ATENC_AUD,ATENC_HKD,ATENC_SGD", - "enabledPairs": "BTC_USD,BTC_HKD,BTC_EUR,BTC_CAD,BTC_AUD,BTC_SGD,BTC_JPY,BTC_GBP,BTC_NZD,LTC_BTC,STR_BTC,XRP_BTC", "baseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USD,BTC_HKD,BTC_EUR,BTC_CAD,BTC_AUD,BTC_SGD,BTC_JPY,BTC_GBP,BTC_NZD,LTC_BTC,STR_BTC,XRP_BTC", + "available": "ETH_HKD,START_GBP,BTC_CAD,OAX_ETH,START_SGD,LTC_BTC,STR_BTC,ATENC_NZD,BTC_AUD,BTC_SGD,ETH_BTC,XRP_BTC,START_JPY,ATENC_CAD,BTC_GBP,ETH_USD,GNT_ETH,START_AUD,START_HKD,ATENC_GBP,BTC_USD,START_BTC,START_CAD,START_EUR,BTC_JPY,BTC_NZD,DOGE_BTC,ATENC_EUR,ATENC_JPY,ATENC_USD,BTC_EUR,BTC_HKD,START_NZD,START_USD,ATENC_AUD,ATENC_HKD,ATENC_SGD" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -211,39 +296,71 @@ "name": "Binance", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,SNGLS-ETH,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,BTS-BNB,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,FUEL-ETH,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADX-BNB,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,NAV-ETH,NAV-BNB,LUN-BTC,LUN-ETH,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,NCASH-BNB,POA-BTC,POA-ETH,POA-BNB,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,GNT-BNB,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,REP-BNB,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,CVC-BNB,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-BTC,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-BTC,DENT-ETH,ARDR-BTC,ARDR-ETH,ARDR-BNB,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BCHABC-BTC,BCHABC-USDT,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-PAX,WAVES-USDC,BCHABC-TUSD,BCHABC-PAX,BCHABC-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BTC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-PAX,ATOM-TUSD,ETC-USDC,ETC-PAX,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-USDC,PHB-TUSD,PHB-PAX,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,TFUEL-USDC,TFUEL-TUSD,TFUEL-PAX,ONE-BNB,ONE-BTC,ONE-USDT,ONE-TUSD,ONE-PAX,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-TUSD,FTM-PAX,FTM-USDC,BTCB-BTC,BCPT-TUSD,BCPT-PAX,BCPT-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,USDSB-USDT,USDSB-USDS,GTO-USDT,GTO-PAX,GTO-TUSD,GTO-USDC,ERD-BNB,ERD-BTC,ERD-USDT,ERD-PAX,ERD-USDC,DOGE-BNB,DOGE-BTC,DOGE-USDT,DOGE-PAX,DOGE-USDC,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ANKR-TUSD,ANKR-PAX,ANKR-USDC,ONT-PAX,ONT-USDC,WIN-BNB,WIN-BTC,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,TUSDB-TUSD,NPXS-USDT,NPXS-USDC,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC", - "enabledPairs": "BTC-USDT", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USDT", + "available": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADX-BNB,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,LUN-BTC,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,POA-BTC,POA-ETH,POA-BNB,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,GNT-BNB,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-ETH,ARDR-BTC,ARDR-ETH,ARDR-BNB,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-TUSD,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-TUSD,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,ONE-BNB,ONE-BTC,ONE-USDT,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,GTO-USDT,ERD-BNB,ERD-BTC,ERD-USDT,DOGE-BNB,DOGE-BTC,DOGE-USDT,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ONT-PAX,ONT-USDC,WIN-BNB,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,NPXS-USDT,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC,PERL-BNB,PERL-BTC,PERL-USDC,PERL-USDT,DENT-USDT,MFT-USDT,KEY-USDT,STORM-USDT,DOCK-USDT,WAN-USDT,FUN-USDT,CVC-USDT,BTT-TRX,WIN-TRX,CHZ-BNB,CHZ-BTC,CHZ-USDT,BAND-BNB,BAND-BTC,BAND-USDT,BNB-BUSD,BTC-BUSD,BUSD-USDT,BEAM-BNB,BEAM-BTC,BEAM-USDT,XTZ-BNB,XTZ-BTC,XTZ-USDT,REN-USDT,RVN-USDT,HC-USDT,HBAR-BNB,HBAR-BTC,HBAR-USDT,NKN-BNB,NKN-BTC,NKN-USDT,XRP-BUSD,ETH-BUSD,LTC-BUSD,LINK-BUSD,ETC-BUSD,STX-BNB,STX-BTC,STX-USDT,KAVA-BNB,KAVA-BTC,KAVA-USDT,BUSD-NGN,BNB-NGN,BTC-NGN,ARPA-BNB,ARPA-BTC,ARPA-USDT,TRX-BUSD,EOS-BUSD,IOTX-USDT,RLC-USDT,MCO-USDT,XLM-BUSD,ADA-BUSD,CTXC-BNB,CTXC-BTC,CTXC-USDT,BCH-BNB,BCH-BTC,BCH-USDT,BCH-USDC,BCH-TUSD,BCH-PAX,BCH-BUSD,BTC-RUB,ETH-RUB,XRP-RUB,BNB-RUB" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -256,38 +373,70 @@ "name": "Bitfinex", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,BTCF0:USTF0,ETHF0:USTF0", - "enabledPairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", + "available": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,PAXUST,UDCUST,TSDUST,BTC:CNHT,UST:CNHT,CNH:CNHT,CHZUSD,CHZUST,BTCF0:USTF0,ETHF0:USTF0" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", "bankAddress": "Karlsruhe, 76125, GERMANY", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "GLOBAL TRADE SOLUTIONS GmbH", "accountNumber": "DE51660700240057016802", "swiftCode": "DEUTDEDB660", @@ -295,8 +444,12 @@ "supportedCurrencies": "EUR,USD" }, { + "enabled": false, "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", "bankAddress": "Karlsruhe, 76125, GERMANY", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "GLOBAL TRADE SOLUTIONS GmbH", "accountNumber": "DE78660700240057016801", "swiftCode": "DEUTDEDB660", @@ -309,41 +462,73 @@ "name": "Bitflyer", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC", - "enabledPairs": "BTC_JPY,ETH_BTC,BCH_BTC", "baseCurrencies": "JPY", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": false, - "pairsLastUpdated": 1566798411, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot", + "futures" + ], + "pairs": { + "spot": { + "enabled": "BTC_JPY,ETH_BTC,BCH_BTC", + "available": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -356,40 +541,72 @@ "name": "Bithumb", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "STRATKRW,PLYKRW,ETCKRW,LOOMKRW,BZNTKRW,WTCKRW,ENJKRW,INSKRW,ELFKRW,ZRXKRW,CMTKRW,CROKRW,APISKRW,LINKKRW,MCOKRW,MIXKRW,BHPKRW,ZECKRW,BSVKRW,BATKRW,WAXKRW,SNTKRW,ORBSKRW,XLMKRW,LAMBKRW,EOSKRW,HYCKRW,OCNKRW,MITHKRW,OMGKRW,XRPKRW,BCHKRW,VALORKRW,AEKRW,BTTKRW,THETAKRW,IOSTKRW,RNTKRW,AMOKRW,XVGKRW,ABTKRW,SALTKRW,MXCKRW,KNCKRW,REPKRW,POLYKRW,LRCKRW,ADAKRW,DACCKRW,MTLKRW,HDACKRW,ITCKRW,LBAKRW,RDNKRW,TMTGKRW,TRUEKRW,ARNKRW,VETKRW,DASHKRW,PSTKRW,WETKRW,ICXKRW,STEEMKRW,DACKRW,ROMKRW,AUTOKRW,CONKRW,XEMKRW,QKCKRW,WAVESKRW,TRXKRW,XMRKRW,BTGKRW,NPXSKRW,ANKRKRW,QTUMKRW,POWRKRW,HCKRW,ETZKRW,ETHKRW,CTXCKRW,GTOKRW,BCDKRW,ETHOSKRW,PIVXKRW,LTCKRW,GNTKRW,PAYKRW,BTCKRW,ZILKRW,PPTKRW,GXCKRW", - "enabledPairs": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,XMRKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", "baseCurrencies": "KRW", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "index": "KRW" + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "index": "KRW" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,XMRKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", + "available": "ICXKRW,CMTKRW,BCHKRW,MCOKRW,XVGKRW,KNCKRW,XMRKRW,EOSKRW,IOSTKRW,HDACKRW,TMTGKRW,QKCKRW,TRXKRW,ARNKRW,WAVESKRW,DASHKRW,BTCKRW,RNTKRW,DADKRW,HYCKRW,DACKRW,XSRKRW,LINKKRW,BTTKRW,FABKRW,DACCKRW,STEEMKRW,PSTKRW,GTOKRW,ZILKRW,APISKRW,VALORKRW,PAYKRW,BHPKRW,THETAKRW,XLMKRW,ETCKRW,FNBKRW,MTLKRW,LTCKRW,ETZKRW,ROMKRW,PPTKRW,AUTOKRW,FCTKRW,VETKRW,NPXSKRW,BSVKRW,ZRXKRW,LOOMKRW,CROKRW,AOAKRW,WETKRW,GXCKRW,CONKRW,SALTKRW,CHRKRW,POWRKRW,ZECKRW,SNTKRW,INSKRW,ETHKRW,XEMKRW,DVPKRW,ETHOSKRW,PLYKRW,BZNTKRW,OGOKRW,LBAKRW,ADAKRW,WOMKRW,GNTKRW,LRCKRW,HCKRW,ABTKRW,FZZKRW,PIVXKRW,ENJKRW,RDNKRW,TRUEKRW,LAMBKRW,MITHKRW,ORBSKRW,OCNKRW,WICCKRW,PCMKRW,WTCKRW,OMGKRW,MXCKRW,MIXKRW,BTGKRW,AEKRW,WAXPKRW,TRVKRW,FXKRW,AMOKRW,XRPKRW,ELFKRW,ITCKRW,REPKRW,ANKRKRW,QTUMKRW,POLYKRW,BATKRW,STRATKRW,BCDKRW,CTXCKRW" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -402,39 +619,71 @@ "name": "Bitstamp", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR", - "enabledPairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", "baseCurrencies": "USD,EUR", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", + "available": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -447,40 +696,72 @@ "name": "LBank", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "FBC_USDT,HDS_USDT,GALT_USDT,IOG_USDT,IOEX_USDT,VOLLAR_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,BZKY_ETH,ONOT_ETH,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,BSV_USDT,OPX_USDT,TENA_ETH,VTHO_BTC,VNX_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,ALI_ETH,SDC_ETH,SAIT_ETH,ARTCN_USDT,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,BFDT_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,IIC_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,TKY_ETH,OCN_ETH,DCT_ETH,ZPT_ETH,EKO_ETH,MDA_ETH,PST_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,UIP_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,INC_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,B91_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,CXC_BTC,CXC_USDT,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,VCC_ETH,DLX_USDT,KDS_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT", - "enabledPairs": "eth_btc", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_" + }, + "configFormat": { + "uppercase": false, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "eth_btc", + "available": "FBC_USDT,GALT_USDT,IOEX_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,OPX_USDT,VTHO_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,SAIT_ETH,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,OCN_ETH,EKO_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,DLX_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT,DOT_USDT,DILI_USDT,DUO_USDT,TEP_USDT,BIKI_USDT,MX_USDT,DNS_USDT,OKB_USDT,FLDT_USDT,CCTC_USDT,WIN_USDT,BTT_USDT,TRX_USDT,GRS_BTC,GST_USDT,GST_ETH,ABBC_USDT,UTK_USDT,GKI_USDT,BPX_USDT,SUTER_USDT,LT_USDT,LM_USDT" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -493,40 +774,72 @@ "name": "Bittrex", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "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-GLC,BTC-GRS,BTC-NLG,BTC-MONA,BTC-VRC,BTC-CURE,BTC-XMR,BTC-XDN,BTC-NAV,BTC-XST,BTC-VIA,BTC-PINK,BTC-IOC,BTC-SYS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-BLOCK,BTC-BTS,BTC-XRP,BTC-GAME,BTC-NXS,BTC-GEO,BTC-FLDC,BTC-FLO,BTC-MUE,BTC-XEM,BTC-SPHR,BTC-OK,BTC-AEON,BTC-ETH,BTC-EXP,BTC-AMP,BTC-XLM,USDT-BTC,BTC-RVR,BTC-FCT,BTC-MAID,BTC-SLS,BTC-RADS,BTC-DCR,BTC-XVG,BTC-PIVX,BTC-MEME,BTC-STEEM,BTC-LSK,BTC-DGD,BTC-WAVES,BTC-LBC,BTC-SBD,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-IOP,BTC-UBQ,BTC-KMD,BTC-SIB,BTC-ION,BTC-CRW,BTC-SWT,BTC-MLN,BTC-ARK,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-EDG,BTC-MORE,ETH-GNT,ETH-REP,USDT-ETH,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,ETH-GNO,BTC-HMQ,BTC-ANT,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,BTC-PTOY,BTC-BNT,ETH-BNT,BTC-NMR,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,BTC-FUN,BTC-PAY,ETH-PAY,BTC-MTL,BTC-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-BCH,USDT-BCH,BTC-BCH,BTC-DNT,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,USDT-OMG,BTC-ADA,BTC-MANA,ETH-MANA,BTC-RCN,BTC-VIB,ETH-VIB,BTC-MER,BTC-POWR,ETH-POWR,ETH-ADA,BTC-ENG,ETH-ENG,USDT-ADA,USDT-XVG,USDT-NXT,BTC-UKG,ETH-UKG,BTC-IGNIS,BTC-SRN,ETH-SRN,BTC-WAX,ETH-WAX,BTC-ZRX,ETH-ZRX,BTC-VEE,BTC-BCPT,BTC-TRX,ETH-TRX,BTC-TUSD,BTC-LRC,ETH-TUSD,BTC-UP,BTC-DMT,ETH-DMT,USDT-TUSD,BTC-POLY,ETH-POLY,BTC-PRO,USDT-SC,USDT-TRX,BTC-BLT,BTC-STORM,ETH-STORM,BTC-AID,BTC-NGC,BTC-GTO,USDT-DCR,BTC-OCN,ETH-OCN,USD-BTC,USD-USDT,USD-TUSD,BTC-TUBE,BTC-CMCT,USD-ETH,BTC-NLC2,BTC-BKX,BTC-MFT,BTC-LOOM,BTC-RFR,USDT-DGB,BTC-RVN,USD-XRP,USD-ETC,BTC-BFT,BTC-GO,BTC-HYDRO,BTC-UPP,USD-ADA,USD-ZEC,USDT-DOGE,BTC-ENJ,BTC-MET,USD-LTC,USD-TRX,BTC-DTA,BTC-EDR,BTC-BOXX,BTC-IHT,USD-BCH,BTC-XHV,USDT-ZRX,BTC-NPXS,BTC-PMA,USDT-BAT,USDT-RVN,BTC-PAL,USD-SC,BTC-PAX,USDT-PAX,BTC-ZIL,BTC-MOC,BTC-OST,BTC-SPC,BTC-MEDX,BTC-BSV,BTC-IOST,BTC-XNK,USDT-BSV,ETH-BSV,BTC-NCASH,BTC-SOLVE,BTC-USDS,USDT-PMA,ETH-NPXS,USDT-NPXS,USD-ZRX,BTC-JNT,BTC-LBA,BTC-MOBI,USD-BAT,USD-BSV,BTC-DENT,USD-USDS,BTC-DRGN,USD-PAX,BTC-VITE,BTC-IOTX,USD-DGB,BTC-BTM,BTC-ELF,USD-EDR,BTC-QNT,BTC-BTU,USD-ZEN,BTC-SPND,BTC-BTT,BTC-NKN,USD-KMD,USDT-BTT,BTC-GRIN,BTC-CTXC,BTC-HXRO,BTC-META,USDT-GRIN,BTC-FSN,BTC-HST,BTC-ANKR,USDT-XLM,BTC-TRAC,BTC-CRO,BTC-ONT,ETH-SOLVE,BTC-ONG,BTC-AERGO,BTC-TTC,USD-SPND,BTC-SLT,BTC-PTON,BTC-PI,ETH-ANKR,BTC-PLA,BTC-ART,BTC-ORBS,USDT-ENJ,BTC-VBK,BTC-BORA,BTC-CND,USDT-ONT,BTC-TRIO,BTC-FX,ETH-FX,BTC-ATOM,USDT-ATOM,ETH-ATOM,BTC-XYO,BTC-OCEAN,USDT-OCEAN,BTC-WIB,BTC-BWX,BTC-SNX,BTC-SUSD,BTC-VDX,USDT-VDX,ETH-VDX,BTC-COSM,BTC-OGO,USDT-OGO,BTC-ITM,BTC-LAMB,BTC-STPT,BTC-FET,BTC-MKR,ETH-MKR,BTC-DAI,ETH-DAI,USDT-DAI,BTC-CPT,BTC-ABT,BTC-PROM,BTC-FTM,BTC-ABYSS,BTC-EOS,ETH-EOS,USDT-EOS,BTC-FXC,BTC-DUSK,BTC-URAC,BTC-BLOC,BTC-BRZ,BTC-TEMCO,BTC-SPIN,BTC-HINT,BTC-LUNA,BTC-CHR,BTC-TUDA,BTC-UTK,BTC-PXL,BTC-AKRO,BTC-TSHP", - "enabledPairs": "USDT-BTC", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "USDT-BTC", + "available": "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-GRS,BTC-NLG,BTC-MONA,BTC-VRC,BTC-CURE,BTC-XMR,BTC-XDN,BTC-NAV,BTC-XST,BTC-VIA,BTC-PINK,BTC-IOC,BTC-SYS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-BLOCK,BTC-BTS,BTC-XRP,BTC-GAME,BTC-NXS,BTC-GEO,BTC-FLO,BTC-MUE,BTC-XEM,BTC-SPHR,BTC-OK,BTC-AEON,BTC-ETH,BTC-EXP,BTC-XLM,USDT-BTC,BTC-FCT,BTC-MAID,BTC-SLS,BTC-RADS,BTC-DCR,BTC-XVG,BTC-PIVX,BTC-MEME,BTC-STEEM,BTC-LSK,BTC-WAVES,BTC-LBC,BTC-SBD,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-REP,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-UBQ,BTC-KMD,BTC-SIB,BTC-ION,BTC-CRW,BTC-ARK,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-EDG,BTC-MORE,ETH-GNT,ETH-REP,USDT-ETH,BTC-RLC,BTC-GNO,ETH-GNO,BTC-HMQ,BTC-ANT,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,BTC-PTOY,BTC-BNT,ETH-BNT,BTC-NMR,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,BTC-PAY,ETH-PAY,BTC-MTL,BTC-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-BCH,USDT-BCH,BTC-BCH,BTC-DNT,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,USDT-OMG,BTC-ADA,BTC-MANA,ETH-MANA,BTC-RCN,BTC-VIB,ETH-VIB,BTC-MER,ETH-ADA,BTC-ENG,ETH-ENG,USDT-ADA,USDT-XVG,BTC-UKG,ETH-UKG,BTC-IGNIS,BTC-SRN,ETH-SRN,BTC-WAXP,ETH-WAXP,BTC-ZRX,ETH-ZRX,BTC-VEE,BTC-TRX,ETH-TRX,BTC-TUSD,BTC-LRC,ETH-TUSD,BTC-DMT,ETH-DMT,USDT-TUSD,USDT-SC,USDT-TRX,BTC-STORM,ETH-STORM,BTC-AID,BTC-GTO,USDT-DCR,USD-BTC,USD-USDT,USD-TUSD,BTC-TUBE,BTC-CMCT,USD-ETH,BTC-NLC2,BTC-BKX,BTC-MFT,BTC-LOOM,BTC-RFR,USDT-DGB,BTC-RVN,USD-XRP,USD-ETC,BTC-BFT,BTC-GO,BTC-HYDRO,BTC-UPP,USD-ADA,USD-ZEC,USDT-DOGE,BTC-ENJ,BTC-MET,USD-LTC,USD-TRX,BTC-DTA,BTC-EDR,BTC-IHT,USD-BCH,BTC-XHV,USDT-ZRX,BTC-NPXS,BTC-PMA,USDT-BAT,USDT-RVN,BTC-PAL,USD-SC,BTC-PAX,BTC-ZIL,BTC-MOC,BTC-OST,BTC-SPC,BTC-MED,BTC-BSV,BTC-IOST,USDT-BSV,ETH-BSV,BTC-SOLVE,BTC-USDS,USDT-PMA,ETH-NPXS,USDT-NPXS,USD-ZRX,BTC-JNT,BTC-LBA,USD-BAT,USD-BSV,BTC-DENT,USD-USDS,BTC-DRGN,USD-PAX,BTC-VITE,BTC-IOTX,USD-DGB,BTC-BTM,BTC-ELF,BTC-QNT,BTC-BTU,USD-ZEN,BTC-SPND,BTC-BTT,BTC-NKN,USD-KMD,USDT-BTT,BTC-GRIN,BTC-CTXC,BTC-HXRO,BTC-META,USDT-GRIN,BTC-FSN,BTC-ANKR,USDT-XLM,BTC-TRAC,BTC-CRO,BTC-ONT,ETH-SOLVE,BTC-ONG,BTC-TTC,BTC-PTON,BTC-PI,ETH-ANKR,BTC-PLA,BTC-ART,BTC-ORBS,USDT-ENJ,BTC-VBK,BTC-BORA,BTC-CND,USDT-ONT,BTC-FX,ETH-FX,BTC-ATOM,USDT-ATOM,ETH-ATOM,BTC-OCEAN,USDT-OCEAN,BTC-BWX,BTC-VDX,USDT-VDX,ETH-VDX,BTC-COSM,BTC-LAMB,BTC-STPT,BTC-DAI,ETH-DAI,USDT-DAI,BTC-CPT,BTC-FNB,BTC-PROM,BTC-ABYSS,BTC-EOS,ETH-EOS,USDT-EOS,BTC-FXC,BTC-DUSK,BTC-URAC,BTC-BLOC,BTC-TEMCO,BTC-SPIN,BTC-LUNA,BTC-CHR,BTC-TUDA,BTC-UTK,BTC-PXL,BTC-AKRO,BTC-TSHP,BTC-HEDG,BTC-MRPH,BTC-HBAR,ETH-HBAR,USD-HBAR,USDT-HBAR,BTC-PLG,BTC-VET,USDT-VET,BTC-SIX,BTC-WGP,BTC-APM,BTC-FLETA,USD-DCR,BTC-BLTV" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -539,40 +852,71 @@ "name": "BTSE", "enabled": true, "verbose": false, - "websocket": true, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD", - "enabledPairs": "BTC-USD", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD,XMR-CNY,XMR-EUR,XMR-GBP,XMR-HKD,XMR-JPY,XMR-SGD,XMR-USD" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -585,39 +929,71 @@ "name": "BTC Markets", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC", - "enabledPairs": "BTC-AUD", "baseCurrencies": "AUD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-AUD", + "available": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -630,39 +1006,70 @@ "name": "COINUT", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "ETCBTC,ETCSGD,LTCSGD,ZECCAD,BTCUSDT,XMRBTC,USDTUSD,LTCBTC,LTCCAD,ZECLTC,BTCUSD,ZECBTC,ETHLTC,ETCLTC,ETCUSDT,ETHBTC,ETHUSDT,LTCUSDT,XMRLTC,ZECSGD,BTCCAD,ZECUSD,XMRUSDT,ZECUSDT,ETHSGD,ETHCAD,ETHUSD,LTCUSD,USDTSGD,BTCSGD", - "enabledPairs": "LTCBTC,ETCBTC,ETHBTC", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "LTC-USDT", + "available": "LTC-CAD,LTC-SGD,USDT-USD,ETC-LTC,LTC-BTC,USDT-SGD,XMR-USDT,ZEC-SGD,ETH-USD,BTC-USDT,ETC-BTC,ETH-LTC,LTC-USD,BTC-USD,ETH-USDT,XMR-LTC,ZEC-USD,ETC-SGD,DAI-SGD,ZEC-CAD,BTC-SGD,ETH-BTC,ETH-SGD,LTC-USDT,ZEC-BTC,ZEC-USDT,BTC-CAD,XMR-BTC,ZEC-LTC,ETC-USDT,ETH-CAD" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -675,41 +1082,73 @@ "name": "EXMO", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "MNX_ETH,OMG_USD,XMR_USD,BTC_TRY,GUSD_RUB,TRX_USD,DAI_ETH,MNX_USD,ETH_USD,DCR_RUB,PTI_USDT,PTI_EOS,XLM_USD,ETH_BTC,EXM_BTC,BTC_PLN,ROOBEE_ETH,XRP_EUR,USDC_BTC,XLM_RUB,ETH_LTC,BCH_BTC,DASH_UAH,ETZ_BTC,NEO_USD,TRX_RUB,XMR_UAH,XLM_TRY,BCH_EUR,OMG_ETH,ETH_PLN,ZEC_BTC,XRP_BTC,ATMCASH_BTC,XRP_TRY,MNC_USD,WAVES_BTC,USDT_EUR,ETZ_USDT,MNC_BTC,ETH_UAH,STQ_BTC,HBZ_BTC,LTC_BTC,MKR_BTC,HB_BTC,GNT_BTC,STQ_RUB,BCH_ETH,ZEC_EUR,XMR_EUR,USD_RUB,USDC_USDT,ETH_TRY,LSK_BTC,XRP_USD,ZRX_ETH,DASH_BTC,ETH_EUR,STQ_USD,STQ_EUR,BTCZ_BTC,LTC_UAH,XTZ_BTC,DAI_RUB,ADA_USD,XLM_BTC,LTC_RUB,GNT_ETH,BCH_RUB,USDT_RUB,XTZ_RUB,ADA_BTC,ADA_ETH,LSK_USD,BCH_UAH,WAVES_ETH,ZRX_USD,ZRX_BTC,BTG_USD,DXT_USD,XEM_USD,GUSD_USD,ETC_RUB,BCH_USDT,DOGE_USD,ROOBEE_BTC,USDC_ETH,SMART_USD,USDT_UAH,DAI_BTC,EOS_USD,KICK_ETH,DAI_USD,QTUM_BTC,BTG_BTC,LTC_EUR,WAVES_RUB,BTC_EUR,BTC_RUB,USDC_USD,DOGE_BTC,MKR_DAI,QTUM_ETH,ZEC_USD,BCH_USD,DCR_UAH,SMART_BTC,NEO_BTC,DASH_USDT,XMR_RUB,LTC_USD,XMR_BTC,ETH_RUB,EOS_EUR,XRP_USDT,WAVES_USD,XRP_ETH,ETZ_ETH,LSK_RUB,OMG_BTC,USDT_USD,INK_ETH,INK_USD,ETC_BTC,XRP_UAH,QTUM_USD,XEM_BTC,MNX_BTC,HBZ_USD,KICK_USDT,XEM_UAH,BTC_USD,DCR_BTC,MNC_ETH,DASH_RUB,KICK_BTC,PTI_BTC,INK_BTC,EOS_BTC,GAS_BTC,XRP_RUB,ETH_USDT,BTG_ETH,PTI_RUB,KICK_RUB,NEO_RUB,SMART_RUB,ETC_USD,ZEC_RUB,BTC_UAH,XTZ_ETH,TRX_UAH,TRX_BTC,GAS_USD,DASH_USD,BTC_USDT,XMR_ETH,SMART_EUR,XEM_EUR,GUSD_BTC,XTZ_USD,HBZ_ETH,DXT_BTC", - "enabledPairs": "BTC_USD,LTC_USD", "baseCurrencies": "USD,EUR,RUB,PLN,UAH", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_", + "separator": "," + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USD,LTC_USD", + "available": "XEM_UAH,DASH_USDT,XMR_ETH,ROOBEE_BTC,PTI_EOS,GUSD_USD,XLM_RUB,KICK_USDT,SMART_BTC,ADA_BTC,TRX_BTC,DASH_RUB,USDC_BTC,ETC_BTC,XRP_USD,DAI_ETH,ETH_USD,ADA_USD,XRP_UAH,KICK_RUB,QTUM_BTC,XMR_EUR,WAVES_BTC,ZEC_EUR,BTT_RUB,XMR_RUB,GNT_ETH,BCH_BTC,OMG_ETH,ETH_EUR,BTG_ETH,QTUM_ETH,HB_BTC,LSK_USD,MNX_ETH,WAVES_USD,WAVES_ETH,BTC_TRY,VLX_BTC,ZRX_ETH,BTCZ_BTC,QTUM_USD,XEM_USD,ETH_RUB,DCR_UAH,DAI_USD,XRP_RUB,KICK_BTC,DXT_USD,BCH_USDT,XRP_ETH,ETZ_USDT,PTI_USDT,DAI_RUB,BTG_BTC,OMG_USD,XMR_BTC,LTC_UAH,XTZ_USD,ETZ_ETH,MNC_USD,ROOBEE_ETH,XTZ_ETH,BCH_ETH,XMR_USD,TRX_RUB,LTC_RUB,BCH_EUR,DCR_BTC,ETZ_BTC,LSK_BTC,NEO_RUB,INK_USD,BTG_USD,ETH_PLN,DASH_UAH,XRP_EUR,USDC_USDT,MNC_BTC,DXT_BTC,ETH_BTC,GAS_USD,ZEC_RUB,ETH_USDT,USDT_USD,DOGE_USD,USDC_ETH,MKR_BTC,ETC_RUB,LTC_USD,LTC_EUR,BTC_USDT,BCH_UAH,BTC_USD,ATMCASH_BTC,GNT_BTC,ETH_LTC,ZEC_BTC,EOS_EUR,MNX_USD,XLM_USD,EOS_USD,LTC_BTC,BTC_UAH,TRX_UAH,XEM_BTC,NEO_USD,XRP_BTC,WAVES_RUB,MKR_DAI,TRX_USD,BCH_RUB,BTC_RUB,ZEC_USD,MNC_ETH,ZRX_BTC,MNX_BTC,USD_RUB,BTC_PLN,XTZ_RUB,USDC_USD,XLM_TRY,XLM_BTC,DASH_USD,ETH_UAH,USDT_RUB,BTC_EUR,XTZ_BTC,XEM_EUR,NEO_BTC,ZRX_USD,SMART_EUR,GAS_BTC,BCH_USD,DCR_RUB,PTI_RUB,PTI_BTC,ETH_TRY,ZAG_BTC,BTT_BTC,GUSD_RUB,ETC_USD,SMART_USD,OMG_BTC,EOS_BTC,DOGE_BTC,USDT_EUR,BTT_UAH,SMART_RUB,ADA_ETH,KICK_ETH,LSK_RUB,INK_BTC,DASH_BTC,EXM_BTC,XRP_TRY,DAI_BTC,GUSD_BTC,XMR_UAH,INK_ETH,XRP_USDT,USDT_UAH" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_", - "separator": "," + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -722,40 +1161,73 @@ "name": "CoinbasePro", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "REPUSD,LTCGBP,ZECBTC,ETCBTC,BCHUSD,DAIUSDC,MAN-USDC,BCHBTC,ETHUSD,XLMUSD,EOSUSD,XRPEUR,ZRXEUR,LTCEUR,ALG-USD,ETHUSDC,BTCGBP,LTCUSD,EOSEUR,ZRXUSD,DNTUSDC,LOO-USDC,GNTUSDC,XRPUSD,BCHEUR,ETHGBP,ZRXBTC,BATUSDC,REPBTC,ETCUSD,ETHBTC,ZECUSDC,XLMBTC,BTCUSDC,EOSBTC,XLMEUR,XTZBTC,LIN-USD,BTCUSD,BCHGBP,XRPBTC,BTCEUR,ETHDAI,LIN-ETH,BATETH,ETCGBP,LTCBTC,ETCEUR,ETHEUR,CVCUSDC,XTZUSD", - "enabledPairs": "BTCUSD,BTCGBP,BTCEUR", "baseCurrencies": "USD,GBP,EUR", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "LTC-GBP,XLM-BTC,DASH-BTC,DAI-USDC,ZEC-USDC,XLM-EUR,ZRX-BTC,LTC-BTC,ETC-BTC,ETH-USD,XRP-EUR,BTC-USDC,REP-USD,EOS-BTC,ZEC-BTC,ETC-GBP,LINK-ETH,XRP-BTC,ZRX-USD,ETH-USDC,MANA-USDC,BTC-EUR,BCH-GBP,DNT-USDC,EOS-EUR,BCH-EUR,LTC-EUR,CVC-USDC,ETH-GBP,DASH-USD,ETH-EUR,XTZ-BTC,ZRX-EUR,BAT-ETH,BTC-GBP,ETC-USD,BAT-USDC,BCH-USD,GNT-USDC,ALGO-USD,LINK-USD,XLM-USD,ETH-BTC,EOS-USD,REP-BTC,ETH-DAI,XRP-USD,LTC-USD,ETC-EUR,BTC-USD,XTZ-USD,BCH-BTC,LOOM-USDC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -768,40 +1240,72 @@ "name": "GateIO", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "USDT_CNYX,BTC_CNYX,ETH_CNYX,EOS_CNYX,BCH_CNYX,XRP_CNYX,DOGE_CNYX,TIPS_CNYX,BTC_USDC,BTC_PAX,BTC_USDT,BCH_USDT,ETH_USDT,ETC_USDT,QTUM_USDT,LTC_USDT,DASH_USDT,ZEC_USDT,BTM_USDT,EOS_USDT,REQ_USDT,SNT_USDT,OMG_USDT,PAY_USDT,CVC_USDT,ZRX_USDT,TNT_USDT,XMR_USDT,XRP_USDT,DOGE_USDT,BAT_USDT,PST_USDT,BTG_USDT,DPY_USDT,LRC_USDT,STORJ_USDT,RDN_USDT,STX_USDT,KNC_USDT,LINK_USDT,CDT_USDT,AE_USDT,AE_ETH,AE_BTC,CDT_ETH,RDN_ETH,STX_ETH,KNC_ETH,LINK_ETH,REQ_ETH,RCN_ETH,TRX_ETH,ARN_ETH,KICK_ETH,BNT_ETH,VET_ETH,MCO_ETH,FUN_ETH,DATA_ETH,RLC_ETH,RLC_USDT,ZSC_ETH,WINGS_ETH,MDA_ETH,RCN_USDT,TRX_USDT,KICK_USDT,VET_USDT,MCO_USDT,FUN_USDT,DATA_USDT,ZSC_USDT,MDA_USDT,XTZ_USDT,XTZ_BTC,XTZ_ETH,GNT_USDT,GNT_ETH,GEM_USDT,GEM_ETH,RFR_USDT,RFR_ETH,DADI_USDT,DADI_ETH,ABT_USDT,ABT_ETH,LEDU_BTC,LEDU_ETH,OST_USDT,OST_ETH,XLM_USDT,XLM_ETH,XLM_BTC,MOBI_USDT,MOBI_ETH,MOBI_BTC,OCN_USDT,OCN_ETH,OCN_BTC,ZPT_USDT,ZPT_ETH,ZPT_BTC,COFI_USDT,COFI_ETH,JNT_USDT,JNT_ETH,JNT_BTC,BLZ_USDT,BLZ_ETH,GXS_USDT,GXS_BTC,MTN_USDT,MTN_ETH,RUFF_USDT,RUFF_ETH,RUFF_BTC,TNC_USDT,TNC_ETH,TNC_BTC,ZIL_USDT,ZIL_ETH,BTO_USDT,BTO_ETH,THETA_USDT,THETA_ETH,DDD_USDT,DDD_ETH,DDD_BTC,MKR_USDT,MKR_ETH,DAI_USDT,SMT_USDT,SMT_ETH,MDT_USDT,MDT_ETH,MDT_BTC,MANA_USDT,MANA_ETH,LUN_USDT,LUN_ETH,SALT_USDT,SALT_ETH,FUEL_USDT,FUEL_ETH,ELF_USDT,ELF_ETH,DRGN_USDT,DRGN_ETH,GTC_USDT,GTC_ETH,GTC_BTC,QLC_USDT,QLC_BTC,QLC_ETH,DBC_USDT,DBC_BTC,DBC_ETH,BNTY_USDT,BNTY_ETH,LEND_USDT,LEND_ETH,ICX_USDT,ICX_ETH,BTF_USDT,BTF_BTC,ADA_USDT,ADA_BTC,LSK_USDT,LSK_BTC,WAVES_USDT,WAVES_BTC,BIFI_USDT,BIFI_BTC,MDS_ETH,MDS_USDT,DGD_USDT,DGD_ETH,QASH_USDT,QASH_ETH,QASH_BTC,POWR_USDT,POWR_ETH,POWR_BTC,FIL_USDT,BCD_USDT,BCD_BTC,SBTC_USDT,SBTC_BTC,GOD_USDT,GOD_BTC,BCX_USDT,BCX_BTC,QSP_USDT,QSP_ETH,INK_BTC,INK_USDT,INK_ETH,INK_QTUM,MED_QTUM,MED_ETH,MED_USDT,QBT_QTUM,QBT_ETH,QBT_USDT,TSL_QTUM,TSL_USDT,GNX_USDT,GNX_ETH,NEO_USDT,GAS_USDT,NEO_BTC,GAS_BTC,IOTA_USDT,IOTA_BTC,NAS_USDT,NAS_ETH,NAS_BTC,ETH_BTC,ETC_BTC,ETC_ETH,ZEC_BTC,DASH_BTC,LTC_BTC,BCH_BTC,BTG_BTC,QTUM_BTC,QTUM_ETH,XRP_BTC,DOGE_BTC,XMR_BTC,ZRX_BTC,ZRX_ETH,DNT_ETH,DPY_ETH,OAX_BTC,OAX_USDT,OAX_ETH,REP_ETH,LRC_ETH,LRC_BTC,PST_ETH,BCDN_ETH,BCDN_USDT,TNT_ETH,SNT_ETH,SNT_BTC,BTM_ETH,BTM_BTC,SNET_ETH,SNET_USDT,LLT_SNET,OMG_ETH,OMG_BTC,PAY_ETH,PAY_BTC,BAT_ETH,BAT_BTC,CVC_ETH,STORJ_ETH,STORJ_BTC,EOS_ETH,EOS_BTC,BTS_USDT,BTS_BTC,TIPS_ETH,GT_BTC,GT_USDT,ATOM_BTC,ATOM_USDT,XEM_ETH,XEM_USDT,XEM_BTC,BU_USDT,BU_ETH,BU_BTC,BCHSV_USDT,BCHSV_CNYX,BCHSV_BTC,DCR_USDT,DCR_BTC,BCN_USDT,BCN_BTC,XMC_USDT,XMC_BTC,ATP_USDT,ATP_ETH,NBOT_ETH,NBOT_USDT,MEDX_USDT,MEDX_ETH,GRIN_USDT,GRIN_ETH,GRIN_BTC,BEAM_USDT,BEAM_ETH,BEAM_BTC,VTHO_ETH,BTT_USDT,BTT_ETH,BTT_TRX,TFUEL_ETH,TFUEL_USDT,CELR_ETH,CELR_USDT,CS_ETH,CS_USDT,MAN_ETH,MAN_USDT,REM_ETH,REM_USDT,LYM_ETH,LYM_BTC,LYM_USDT,ONG_ETH,ONG_USDT,ONT_ETH,ONT_USDT,BFT_ETH,BFT_USDT,IHT_ETH,IHT_USDT,SENC_ETH,SENC_USDT,TOMO_ETH,TOMO_USDT,ELEC_ETH,ELEC_USDT,HAV_ETH,HAV_USDT,SWTH_ETH,SWTH_USDT,NKN_ETH,NKN_USDT,SOUL_ETH,SOUL_USDT,LRN_ETH,LRN_USDT,EOSDAC_ETH,EOSDAC_USDT,DOCK_USDT,DOCK_ETH,GSE_USDT,GSE_ETH,RATING_USDT,RATING_ETH,HSC_USDT,HSC_ETH,HIT_USDT,HIT_ETH,DX_USDT,DX_ETH,CNNS_ETH,CNNS_USDT,DREP_ETH,DREP_USDT,MBL_USDT,MBL_ETH,GMAT_USDT,GMAT_ETH,MIX_USDT,MIX_ETH,LAMB_USDT,LAMB_ETH,LEO_USDT,LEO_BTC,WICC_USDT,WICC_ETH,SERO_USDT,SERO_ETH,VIDY_USDT,VIDY_ETH,KGC_USDT,FTM_USDT,FTM_ETH,ONE_USDT,ARPA_USDT,ARPA_ETH,ALGO_USDT,BKC_USDT,BXC_USDT,BXC_ETH,PAX_USDT,PAX_CNYX,USDC_CNYX,USDC_USDT,TUSD_CNYX,TUSD_USDT,HC_USDT,HC_BTC,HC_ETH,GARD_USDT,GARD_ETH,FTI_USDT,FTI_ETH,SOP_ETH,SOP_USDT,LEMO_USDT,LEMO_ETH,QKC_USDT,QKC_ETH,IOTX_USDT,IOTX_ETH,RED_USDT,RED_ETH,LBA_USDT,LBA_ETH,OPEN_USDT,OPEN_ETH,MITH_USDT,MITH_ETH,SKM_USDT,SKM_ETH,XVG_USDT,XVG_BTC,NANO_USDT,NANO_BTC,HT_USDT,BNB_USDT,MET_ETH,MET_USDT,TCT_ETH,TCT_USDT,MXC_USDT,MXC_BTC,MXC_ETH", - "enabledPairs": "BTC_USDT", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USDT", + "available": "USDT_CNYX,BTC_CNYX,ETH_CNYX,EOS_CNYX,BCH_CNYX,XRP_CNYX,DOGE_CNYX,TIPS_CNYX,BTC_USDC,BTC_PAX,BTC_USDT,BCH_USDT,ETH_USDT,ETC_USDT,QTUM_USDT,LTC_USDT,DASH_USDT,ZEC_USDT,BTM_USDT,EOS_USDT,REQ_USDT,SNT_USDT,OMG_USDT,PAY_USDT,CVC_USDT,ZRX_USDT,TNT_USDT,XMR_USDT,XRP_USDT,DOGE_USDT,BAT_USDT,PST_USDT,BTG_USDT,DPY_USDT,LRC_USDT,STORJ_USDT,RDN_USDT,STX_USDT,KNC_USDT,LINK_USDT,CDT_USDT,AE_USDT,AE_ETH,AE_BTC,CDT_ETH,RDN_ETH,STX_ETH,KNC_ETH,LINK_ETH,REQ_ETH,RCN_ETH,TRX_ETH,ARN_ETH,KICK_ETH,BNT_ETH,VET_ETH,MCO_ETH,FUN_ETH,DATA_ETH,RLC_ETH,RLC_USDT,ZSC_ETH,WINGS_ETH,MDA_ETH,RCN_USDT,TRX_USDT,KICK_USDT,VET_USDT,MCO_USDT,FUN_USDT,DATA_USDT,ZSC_USDT,MDA_USDT,XTZ_USDT,XTZ_BTC,XTZ_ETH,GNT_USDT,GNT_ETH,GEM_USDT,GEM_ETH,RFR_USDT,RFR_ETH,DADI_USDT,DADI_ETH,ABT_USDT,ABT_ETH,LEDU_BTC,LEDU_ETH,OST_USDT,OST_ETH,XLM_USDT,XLM_ETH,XLM_BTC,MOBI_USDT,MOBI_ETH,MOBI_BTC,OCN_USDT,OCN_ETH,OCN_BTC,ZPT_USDT,ZPT_ETH,ZPT_BTC,COFI_USDT,COFI_ETH,JNT_USDT,JNT_ETH,JNT_BTC,BLZ_USDT,BLZ_ETH,GXS_USDT,GXS_BTC,MTN_USDT,MTN_ETH,RUFF_USDT,RUFF_ETH,RUFF_BTC,TNC_USDT,TNC_ETH,TNC_BTC,ZIL_USDT,ZIL_ETH,BTO_USDT,BTO_ETH,THETA_USDT,THETA_ETH,DDD_USDT,DDD_ETH,DDD_BTC,MKR_USDT,MKR_ETH,DAI_USDT,SMT_USDT,SMT_ETH,MDT_USDT,MDT_ETH,MDT_BTC,MANA_USDT,MANA_ETH,LUN_USDT,LUN_ETH,SALT_USDT,SALT_ETH,FUEL_USDT,FUEL_ETH,ELF_USDT,ELF_ETH,DRGN_USDT,DRGN_ETH,GTC_USDT,GTC_ETH,GTC_BTC,QLC_USDT,QLC_BTC,QLC_ETH,DBC_USDT,DBC_BTC,DBC_ETH,BNTY_USDT,BNTY_ETH,LEND_USDT,LEND_ETH,ICX_USDT,ICX_ETH,BTF_USDT,BTF_BTC,ADA_USDT,ADA_BTC,LSK_USDT,LSK_BTC,WAVES_USDT,WAVES_BTC,BIFI_USDT,BIFI_BTC,MDS_ETH,MDS_USDT,DGD_USDT,DGD_ETH,QASH_USDT,QASH_ETH,QASH_BTC,POWR_USDT,POWR_ETH,POWR_BTC,FIL_USDT,BCD_USDT,BCD_BTC,SBTC_USDT,SBTC_BTC,GOD_USDT,GOD_BTC,BCX_USDT,BCX_BTC,QSP_USDT,QSP_ETH,INK_BTC,INK_USDT,INK_ETH,INK_QTUM,QBT_QTUM,QBT_ETH,QBT_USDT,TSL_QTUM,TSL_USDT,GNX_USDT,GNX_ETH,NEO_USDT,GAS_USDT,NEO_BTC,GAS_BTC,IOTA_USDT,IOTA_BTC,NAS_USDT,NAS_ETH,NAS_BTC,ETH_BTC,ETC_BTC,ETC_ETH,ZEC_BTC,DASH_BTC,LTC_BTC,BCH_BTC,BTG_BTC,QTUM_BTC,QTUM_ETH,XRP_BTC,DOGE_BTC,XMR_BTC,ZRX_BTC,ZRX_ETH,DNT_ETH,DPY_ETH,OAX_BTC,OAX_USDT,OAX_ETH,REP_ETH,LRC_ETH,LRC_BTC,PST_ETH,BCDN_ETH,BCDN_USDT,TNT_ETH,SNT_ETH,SNT_BTC,BTM_ETH,BTM_BTC,SNET_ETH,SNET_USDT,LLT_SNET,OMG_ETH,OMG_BTC,PAY_ETH,PAY_BTC,BAT_ETH,BAT_BTC,CVC_ETH,STORJ_ETH,STORJ_BTC,EOS_ETH,EOS_BTC,BTS_USDT,BTS_BTC,TIPS_ETH,GT_BTC,GT_USDT,ATOM_BTC,ATOM_USDT,XEM_ETH,XEM_USDT,XEM_BTC,BU_USDT,BU_ETH,BU_BTC,BCHSV_USDT,BCHSV_CNYX,BCHSV_BTC,DCR_USDT,DCR_BTC,BCN_USDT,BCN_BTC,XMC_USDT,XMC_BTC,ATP_USDT,ATP_ETH,NAX_ETH,NBOT_ETH,NBOT_USDT,MED_USDT,MED_ETH,GRIN_USDT,GRIN_ETH,GRIN_BTC,BEAM_USDT,BEAM_ETH,BEAM_BTC,VTHO_ETH,BTT_USDT,BTT_ETH,BTT_TRX,TFUEL_ETH,TFUEL_USDT,CELR_ETH,CELR_USDT,CS_ETH,CS_USDT,MAN_ETH,MAN_USDT,REM_ETH,REM_USDT,LYM_ETH,LYM_BTC,LYM_USDT,ONG_ETH,ONG_USDT,ONT_ETH,ONT_USDT,BFT_ETH,BFT_USDT,IHT_ETH,IHT_USDT,SENC_ETH,SENC_USDT,TOMO_ETH,TOMO_USDT,ELEC_ETH,ELEC_USDT,HAV_ETH,HAV_USDT,SWTH_ETH,SWTH_USDT,NKN_ETH,NKN_USDT,SOUL_ETH,SOUL_USDT,LRN_ETH,LRN_USDT,EOSDAC_ETH,EOSDAC_USDT,DOCK_USDT,DOCK_ETH,GSE_USDT,GSE_ETH,RATING_USDT,RATING_ETH,HSC_USDT,HSC_ETH,HIT_USDT,HIT_ETH,DX_USDT,DX_ETH,CNNS_ETH,CNNS_USDT,DREP_ETH,DREP_USDT,MBL_USDT,MBL_ETH,GMAT_USDT,GMAT_ETH,MIX_USDT,MIX_ETH,LAMB_USDT,LAMB_ETH,LEO_USDT,LEO_BTC,WICC_USDT,WICC_ETH,SERO_USDT,SERO_ETH,VIDY_USDT,VIDY_ETH,KGC_USDT,FTM_USDT,FTM_ETH,COS_USDT,CRO_USDT,ALY_USDT,WIN_USDT,MTV_USDT,ONE_USDT,ARPA_USDT,ARPA_ETH,DILI_USDT,ALGO_USDT,PI_USDT,CKB_USDT,CKB_BTC,CKB_ETH,BKC_USDT,BXC_USDT,BXC_ETH,PAX_USDT,PAX_CNYX,USDC_CNYX,USDC_USDT,TUSD_CNYX,TUSD_USDT,HC_USDT,HC_BTC,HC_ETH,GARD_USDT,GARD_ETH,FTI_USDT,FTI_ETH,SOP_ETH,SOP_USDT,LEMO_USDT,LEMO_ETH,QKC_USDT,QKC_ETH,QKC_BTC,IOTX_USDT,IOTX_ETH,RED_USDT,RED_ETH,LBA_USDT,LBA_ETH,OPEN_USDT,OPEN_ETH,MITH_USDT,MITH_ETH,SKM_USDT,SKM_ETH,XVG_USDT,XVG_BTC,NANO_USDT,NANO_BTC,HT_USDT,BNB_USDT,MET_ETH,MET_USDT,TCT_ETH,TCT_USDT,MXC_USDT,MXC_BTC,MXC_ETH" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -814,38 +1318,69 @@ "name": "Gemini", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC", - "enabledPairs": "BTCUSD", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD", + "available": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -858,39 +1393,71 @@ "name": "HitBTC", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,AMP-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,PIX-BTC,PIX-ETH,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-BTC,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,DRPU-BTC,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,DRPU-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAX-BTC,WAX-ETH,WAX-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,NOAH-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,NOAH-ETH,NOAH-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,SUNC-ETH,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,FREC-BTC,NAVI-BTC,FREC-ETH,FREC-USD,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,HERO-BTC,HERO-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,ZPT-BTC,ZPT-ETH,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,ZPT-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,JOT-BTC,JBC-BTC,JBC-ETH,BTS-BTC,BNK-BTC,KBC-BTC,KBC-ETH,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,CLN-BTC,CLN-ETH,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,COSM-BTC,COSM-ETH,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,XCLR-BTC,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,CCL-USD,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,MESSE-BTC,MESSE-ETH,MESSE-USD,CCL-ETH,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,MESSE-EOS,MESSE-EURS,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,MRS-BTC,MRS-ETH,MRS-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD", - "enabledPairs": "BTC-USD", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAXP-BTC,WAXP-ETH,WAXP-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,NAVI-BTC,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,HERO-BTC,HERO-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,BTS-BTC,BNK-BTC,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD,NJBC-BTC,NJBC-ETH,XRC-BTC,EOS-BCH,LTC-BCH,XRP-BCH,TRX-BCH,XLM-BCH,ETC-BCH,DASH-BCH,ZEC-BCH,BKX-USD,LAMB-BTC,NPXS-BTC,HBAR-BTC,HBAR-USD,ONE-BTC,RFR-BTC,RFR-USD,BUSD-USD,PAXG-BTC,PAXG-USD,REN-BTC,IGNIS-BTC,CEL-BTC,CEL-ETH,WIN-USD,ADK-BTC,PART-BTC,SOZ-BTC,SOZ-ETH,SOZ-USD,WAVES-USD,ADA-BCH,ONT-BCH,XMR-BCH,ATOM-BCH,LINK-BCH,OMG-BCH,WAVES-BCH,IOTX-BTC,HOT-BTC,SLV-BTC,HEDG-BTC,CHZ-BTC,CHZ-USD,COCOS-BTC,COCOS-USD,SEELE-BTC,SEELE-USD,MDA-BTC,LEO-USD,REM-BTC,REM-ETH,REM-USD,SCD-DAI,BTC-BUSD,RVN-BTC,BST-BTC,ERD-BTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -903,40 +1470,72 @@ "name": "Huobi", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiAuthPemKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIPVSj8YkpXibCAL9HwpGkDNSEXR9jcpiCthdikJqipNooAoGCCqGSM49\nAwEHoUQDQgAEHiB7q/HCqUrCNqPeTtRmKjyi2T+2O2JgoU8Mjx2R4z1h81uOZHCk\nxbsDg1fb7ACRMpKWPs59QWpQxhqMQrNw8w==\n-----END EC PRIVATE KEY-----\n", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "HT-USDT,BAT-ETH,AST-ETH,TRX-BTC,NEW-BTC,AE-BTC,IIC-BTC,NEW-USDT,CDC-BTC,AE-USDT,DGB-BTC,NAS-ETH,QSP-BTC,LYM-ETH,YCC-BTC,BCH-HT,BIX-ETH,WXT-BTC,XRP-BTC,IOST-BTC,CHAT-BTC,BTC-USDT,XTZ-BTC,PVT-BTC,PVT-USDT,WAVES-ETH,ACT-BTC,RSR-BTC,ACT-USDT,WXT-USDT,XLM-ETH,HT-BTC,UUU-USDT,XRP-USDT,UGAS-BTC,BTS-ETH,IRIS-ETH,LUN-BTC,IOST-HT,DOCK-BTC,ABT-ETH,CRO-BTC,MAN-ETH,ENG-ETH,QUN-BTC,APPC-BTC,KAN-ETH,VET-USDT,SOC-ETH,RSR-HT,RUFF-ETH,RCCC-ETH,AAC-ETH,MCO-BTC,RSR-USDT,TNB-ETH,UTK-ETH,ADX-BTC,WAX-ETH,IOST-USDT,HOT-ETH,WTC-USDT,CVCOIN-BTC,NCASH-ETH,ATP-BTC,SWFTC-ETH,GTC-BTC,PNT-BTC,GT-HT,NEO-BTC,OMG-BTC,EOS-HUSD,WPR-ETH,ARPA-BTC,BTM-BTC,BTM-USDT,KCASH-ETH,SSP-ETH,ARPA-USDT,CNN-BTC,NKN-BTC,NPXS-BTC,OMG-USDT,TOPC-ETH,XEM-BTC,BCH-USDT,SNC-BTC,POLY-ETH,CMT-ETH,PAI-USDT,ZEC-USDT,LSK-ETH,SMT-ETH,DASH-USDT,GAS-ETH,DASH-BTC,GXC-ETH,FTT-HT,IOTA-ETH,FTI-BTC,TRIO-ETH,LET-BTC,ZRX-ETH,ETN-ETH,EVX-ETH,BFT-ETH,GRS-BTC,XRP-HT,DASH-HT,QTUM-ETH,HIT-ETH,NEXO-BTC,QASH-BTC,EOS-ETH,ARDR-ETH,ADA-BTC,NEO-USDT,BTT-TRX,COVA-ETH,REN-BTC,LOOM-BTC,CVC-ETH,NANO-ETH,ARPA-HT,NEW-HT,BLZ-ETH,LINK-ETH,XTZ-USDT,PAY-BTC,GNT-USDT,YEE-ETH,XZC-ETH,EGCC-ETH,PROPY-ETH,ZEC-BTC,EDU-ETH,RTE-BTC,DCR-USDT,FTT-BTC,DCR-BTC,EKO-BTC,SBTC-BTC,ZLA-ETH,TOP-HT,ALGO-BTC,DTA-ETH,EKT-ETH,ATOM-USDT,LXT-USDT,ZEN-ETH,LOL-USDT,LTC-USDT,DAT-BTC,REQ-ETH,ELA-ETH,NKN-HT,PC-BTC,HIT-BTC,EKO-ETH,STK-ETH,LAMB-USDT,LAMB-HT,DOGE-ETH,ATOM-BTC,THETA-USDT,LOL-BTC,THETA-BTC,LSK-BTC,ADA-USDT,RDN-BTC,OGO-HT,UIP-USDT,WICC-BTC,OCN-BTC,ELF-BTC,AKRO-USDT,USDC-HUSD,LAMB-BTC,DBC-ETH,BTT-ETH,FAIR-BTC,POWR-ETH,MUSK-ETH,MT-BTC,STEEM-USDT,RBTC-BTC,CTXC-BTC,MANA-USDT,ICX-ETH,GET-BTC,LTC-BTC,ITC-ETH,BCV-BTC,ZJLT-BTC,AKRO-HT,TNT-ETH,TOP-BTC,MEX-BTC,DATX-BTC,ALGO-USDT,LXT-BTC,GT-USDT,FSN-HT,FSN-USDT,MTX-ETH,LET-ETH,OGO-USDT,PHX-BTC,KCASH-HT,HC-USDT,LOL-HT,NKN-USDT,HOT-BTC,LBA-BTC,XMX-BTC,OST-ETH,VEN-USDT,LTC-HT,LBA-USDT,VEN-BTC,CRE-HT,BIFI-BTC,BT1-BTC,HPT-BTC,NULS-BTC,WAN-BTC,ZIL-BTC,ETC-HT,TOS-BTC,MANA-BTC,SHE-BTC,GT-BTC,FSN-BTC,MCO-ETH,MTN-BTC,MDS-BTC,SRN-ETH,GVE-BTC,XMR-ETH,MEET-ETH,NULS-USDT,BCH-BTC,PAI-BTC,NCC-ETH,BSV-BTC,AKRO-BTC,ELF-USDT,DGD-ETH,PVT-HT,UIP-BTC,ATP-USDT,SEELE-ETH,GSC-BTC,ETC-USDT,SOC-BTC,GNX-BTC,WICC-USDT,QSP-ETH,RUFF-BTC,KNC-ETH,ATP-HT,CTXC-USDT,KMD-ETH,OGO-BTC,BKBT-BTC,DGB-ETH,WAVES-USDT,BCD-BTC,HPT-HT,ZIL-USDT,BUT-ETH,CVNT-BTC,OCN-USDT,SALT-ETH,XLM-BTC,TRX-USDT,RCN-BTC,DAC-ETH,MT-HT,ETH-HUSD,HPT-USDT,XTZ-ETH,USDT-HUSD,CHAT-ETH,ONT-USDT,SKM-USDT,MAN-BTC,ARDR-BTC,BCX-BTC,SKM-BTC,EOS-USDT,GNX-ETH,CRE-USDT,PORTAL-ETH,COVA-BTC,BIX-BTC,UUU-ETH,AAC-BTC,TRX-ETH,NEXO-ETH,NAS-BTC,ENG-BTC,AST-BTC,TT-HT,QUN-ETH,EOS-BTC,18C-ETH,WTC-ETH,CVCOIN-ETH,CRE-BTC,CNNS-USDT,WAX-BTC,AIDOC-BTC,VET-ETH,CMT-USDT,BSV-USDT,IDT-ETH,IOST-ETH,BTC-HUSD,IOTA-BTC,TNB-BTC,LINK-BTC,TOPC-BTC,RCCC-BTC,ZRX-USDT,CNNS-BTC,BOX-BTC,MDS-USDT,XLM-USDT,BAT-BTC,LYM-BTC,UC-ETH,RUFF-USDT,LUN-ETH,BIX-USDT,CDC-ETH,BTS-USDT,YCC-ETH,KAN-USDT,MTL-BTC,WAVES-BTC,ONT-BTC,HT-HUSD,IRIS-USDT,SOC-USDT,WPR-BTC,ETC-BTC,TUSD-HUSD,CVC-USDT,PROPY-BTC,TRIO-BTC,CVC-BTC,BTT-USDT,NANO-BTC,GXC-BTC,NCASH-BTC,XRP-HUSD,TT-USDT,SHE-ETH,NANO-USDT,LOOM-ETH,POWR-BTC,QTUM-BTC,SSP-BTC,BTM-ETH,QTUM-USDT,XZC-BTC,GNT-ETH,OMG-ETH,NPXS-ETH,SNT-USDT,ETH-USDT,ABT-BTC,BTS-BTC,STEEM-BTC,VSYS-USDT,BLZ-BTC,CNNS-HT,ADX-ETH,SMT-USDT,IOTA-USDT,PAY-ETH,CMT-BTC,UTK-BTC,SWFTC-BTC,GTC-ETH,LINK-USDT,SNC-ETH,SNT-BTC,EOS-HT,REN-ETH,PAX-HUSD,KCASH-BTC,HC-BTC,IIC-ETH,QASH-ETH,GRS-ETH,EDU-BTC,HIT-USDT,TOP-USDT,XZC-USDT,KAN-BTC,SC-BTC,SKM-HT,AE-ETH,STORJ-USDT,XVG-ETH,ZRX-BTC,EVX-BTC,ETN-BTC,BFT-BTC,FTI-ETH,DAT-ETH,UGAS-ETH,BAT-USDT,GXC-USDT,GAS-BTC,TNT-BTC,HB10-USDT,MUSK-BTC,FTT-USDT,STK-BTC,ELF-ETH,KNC-BTC,CTXC-ETH,DBC-BTC,HC-ETH,EKT-BTC,DTA-USDT,ZLA-BTC,EKT-USDT,DTA-BTC,OCN-ETH,DGD-BTC,BHT-USDT,MTX-BTC,BCV-ETH,YEE-BTC,VSYS-HT,MEX-ETH,DATX-ETH,EGCC-BTC,LXT-ETH,ITC-USDT,TOS-ETH,ITC-BTC,RCN-ETH,XVG-BTC,SC-ETH,BT2-BTC,REQ-BTC,ELA-USDT,LET-USDT,STORJ-BTC,ALGO-ETH,POLY-BTC,LAMB-ETH,DCR-ETH,EGT-BTC,RTE-ETH,FAIR-ETH,CNN-ETH,BHT-BTC,GSC-ETH,GNT-BTC,PAI-ETH,PC-ETH,ADA-ETH,DOGE-BTC,ZEN-BTC,STEEM-ETH,XMR-BTC,XMR-USDT,MDS-ETH,TT-BTC,BTT-BTC,BHT-HT,ZJLT-ETH,UC-BTC,GVE-ETH,MXC-BTC,MANA-ETH,VSYS-BTC,THETA-ETH,NCC-BTC,APPC-ETH,SMT-BTC,IDT-BTC,UIP-ETH,ETH-BTC,BOX-ETH,LBA-ETH,NULS-ETH,PNT-ETH,BTG-BTC,CVNT-ETH,SALT-BTC,XEM-USDT,WXT-HT,BUT-BTC,DAC-BTC,DOCK-ETH,GET-ETH,AIDOC-ETH,EGT-USDT,WAN-ETH,KMD-BTC,MTN-ETH,CRO-USDT,ONT-ETH,BKBT-ETH,MEET-BTC,VEN-ETH,MT-ETH,SRN-BTC,UUU-BTC,SEELE-BTC,ICX-BTC,RDN-ETH,EGT-HT,ZIL-ETH,IRIS-BTC,CRO-HT,ACT-ETH,DOGE-USDT,NAS-USDT,PORTAL-BTC,ELA-BTC,OST-BTC,WICC-ETH,VET-BTC,XMX-ETH,WTC-BTC,HT-ETH,ATOM-ETH,18C-BTC", - "enabledPairs": "BTC-USDT", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": false + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USDT", + "available": "SWFTC-BTC,PAY-ETH,WICC-BTC,OST-ETH,FTI-BTC,EOS-HUSD,CRO-HT,LET-USDT,BTM-BTC,SSP-ETH,PNT-ETH,MAN-ETH,LYM-ETH,BUT-BTC,DGD-BTC,BTM-USDT,UC-BTC,BCV-BTC,ZIL-ETH,REQ-ETH,UTK-BTC,HC-USDT,EDU-BTC,AE-ETH,BTS-USDT,GTC-BTC,ENG-BTC,WPR-BTC,HOT-ETH,SNC-BTC,UUU-BTC,CVC-ETH,CRE-HT,TNT-ETH,MX-BTC,VET-USDT,WTC-BTC,GRS-BTC,NULS-BTC,WAXP-BTC,TRIO-BTC,LINK-ETH,GT-BTC,FTT-BTC,OMG-BTC,UUU-USDT,WXT-BTC,TOP-HT,BTS-BTC,TNB-BTC,VET-ETH,VIDY-USDT,CKB-BTC,MTL-BTC,ITC-ETH,BSV-BTC,POWR-BTC,ARDR-BTC,BTT-ETH,TRX-BTC,DAT-ETH,EGT-HT,LET-BTC,GAS-ETH,LOL-BTC,GAS-BTC,OMG-ETH,BFT-BTC,PC-ETH,GET-BTC,HOT-BTC,YEE-BTC,KAN-USDT,XTZ-BTC,SMT-BTC,NANO-ETH,ZJLT-ETH,OGO-HT,BCH-HT,BCV-ETH,BTS-ETH,RUFF-ETH,PAX-HUSD,SMT-USDT,FTT-USDT,WXT-USDT,SHE-ETH,CNNS-HT,MEX-ETH,ZIL-USDT,EM-BTC,TUSD-HUSD,SSP-BTC,OST-BTC,MTN-ETH,PAI-ETH,STK-BTC,TNT-BTC,EKO-BTC,KNC-BTC,EOS-USDT,ONT-USDT,DAC-ETH,SBTC-BTC,WPR-ETH,DGB-BTC,WTC-USDT,BFT-ETH,WAVES-ETH,NPXS-ETH,UTK-ETH,RCCC-BTC,EVX-BTC,UIP-ETH,XMR-ETH,RTE-ETH,LET-ETH,BTT-TRX,CHAT-BTC,XTZ-ETH,LINK-BTC,NULS-ETH,PC-BTC,BLZ-BTC,WICC-ETH,CKB-USDT,ADA-ETH,NEW-BTC,TRX-ETH,BSV-USDT,IIC-BTC,BTT-BTC,LAMB-USDT,TNB-ETH,BOX-BTC,ATP-HT,YCC-BTC,AAC-BTC,EM-HT,CNNS-BTC,HPT-USDT,SKM-USDT,RTE-BTC,XLM-ETH,XRP-HUSD,EGCC-ETH,TRIO-ETH,18C-ETH,BCH-HUSD,WAVES-BTC,TOPC-ETH,ETC-HT,NAS-USDT,MTN-BTC,MTX-ETH,LSK-BTC,RUFF-BTC,PROPY-BTC,ACT-USDT,AKRO-BTC,IRIS-ETH,LTC-USDT,DBC-BTC,REN-ETH,AE-BTC,BAT-BTC,TOS-ETH,ACT-BTC,ARPA-BTC,FOR-USDT,KNC-ETH,EKO-ETH,GXC-BTC,ELA-USDT,SNT-BTC,IOTA-ETH,ARPA-USDT,PAI-USDT,BIX-ETH,KCASH-BTC,TT-USDT,UIP-BTC,ZEN-ETH,NANO-BTC,RSR-USDT,CTXC-BTC,BKBT-ETH,BSV-HUSD,QTUM-USDT,RDN-ETH,DASH-HT,HT-HUSD,STEEM-BTC,ADX-ETH,PORTAL-BTC,EOS-BTC,LAMB-BTC,QUN-BTC,IIC-ETH,BHT-HT,GNX-BTC,MEET-ETH,BTM-ETH,ATP-BTC,XMR-USDT,BTC-USDT,NAS-ETH,VSYS-USDT,XTZ-USDT,KMD-BTC,EVX-ETH,CVC-BTC,CRE-BTC,HC-ETH,KAN-BTC,NEW-USDT,DAC-BTC,DOGE-USDT,IOST-USDT,RCN-BTC,AIDOC-BTC,EDU-ETH,MT-HT,SRN-ETH,MAN-BTC,GT-HT,ETC-BTC,QSP-BTC,ETH-HUSD,XZC-ETH,XRP-HT,DATX-ETH,XMX-ETH,FAIR-ETH,LOL-USDT,GVE-ETH,REN-USDT,IRIS-USDT,PVT-USDT,MANA-ETH,SEELE-BTC,CMT-ETH,THETA-USDT,ABT-BTC,ETN-BTC,GSC-ETH,MCO-BTC,TRX-USDT,ITC-BTC,CNN-ETH,AKRO-HT,COVA-BTC,PAY-BTC,ICX-ETH,FSN-USDT,NEXO-BTC,QASH-ETH,ZEC-USDT,DAT-BTC,NKN-USDT,MX-HT,LXT-BTC,ALGO-BTC,OMG-USDT,EGT-BTC,ARDR-ETH,WAXP-ETH,GET-ETH,WICC-USDT,POWR-ETH,VIDY-BTC,EKT-ETH,HIT-BTC,LOOM-BTC,BHT-BTC,ZRX-ETH,WTC-ETH,ATOM-ETH,ELF-BTC,ONT-ETH,SEELE-ETH,OCN-USDT,LBA-BTC,POLY-ETH,ZEN-BTC,OCN-BTC,LBA-USDT,CVCOIN-ETH,IOST-ETH,DOGE-ETH,OGO-BTC,QTUM-ETH,XVG-ETH,BKBT-BTC,IDT-ETH,AIDOC-ETH,QSP-ETH,GSC-BTC,OGO-USDT,LSK-ETH,DBC-ETH,BIFI-BTC,ZLA-ETH,APPC-ETH,GNT-ETH,MTX-BTC,UGAS-ETH,BAT-ETH,NCASH-ETH,MT-BTC,BCH-USDT,MUSK-ETH,RBTC-BTC,LUN-BTC,HIT-USDT,DTA-BTC,ALGO-ETH,ELA-ETH,SOC-USDT,SC-ETH,CRO-USDT,XZC-USDT,HT-BTC,AST-ETH,THETA-BTC,ZRX-BTC,DCR-BTC,MCO-ETH,CHAT-ETH,VIDY-HT,NKN-BTC,SALT-BTC,ABT-ETH,SOC-ETH,BIX-USDT,LXT-ETH,MDS-ETH,ATOM-BTC,TT-HT,ADA-BTC,XLM-USDT,ATOM-USDT,DOCK-BTC,DTA-ETH,NODE-USDT,IDT-BTC,NCC-ETH,MDS-USDT,IOTA-BTC,DOCK-USDT,SC-BTC,LXT-USDT,DOGE-BTC,CKB-HT,EGT-USDT,OCN-ETH,AST-BTC,ETH-BTC,CVNT-BTC,CRO-BTC,GNT-USDT,YCC-ETH,XZC-BTC,ETC-USDT,APPC-BTC,ARPA-HT,BHD-USDT,ZLA-BTC,MANA-BTC,GNT-BTC,BOX-ETH,NEO-BTC,BAT-USDT,XRP-BTC,EKT-USDT,SOC-BTC,CTXC-USDT,MEX-BTC,BCX-BTC,WAN-ETH,TOP-BTC,DCR-ETH,CVNT-ETH,HB10-USDT,PROPY-ETH,SKM-HT,WXT-HT,QTUM-BTC,CMT-USDT,THETA-ETH,SALT-ETH,STEEM-ETH,AAC-ETH,PORTAL-ETH,NCASH-BTC,CVCOIN-BTC,ZRX-USDT,RDN-BTC,LAMB-HT,EOS-HT,FTT-HT,DASH-BTC,BIX-BTC,LUN-ETH,ONE-HT,GVE-BTC,STORJ-BTC,XEM-BTC,ONE-USDT,STK-ETH,STORJ-USDT,ONE-BTC,ELA-BTC,ICX-BTC,WAVES-USDT,CNNS-USDT,SRN-BTC,GRS-ETH,MT-ETH,PHX-BTC,HT-ETH,AKRO-USDT,KCASH-HT,BHD-HT,STEEM-USDT,BTT-USDT,SKM-BTC,ZJLT-BTC,NCC-BTC,FOR-BTC,RCN-ETH,RSR-BTC,DGB-ETH,GXC-USDT,NAS-BTC,RUFF-USDT,HPT-BTC,MUSK-BTC,ENG-ETH,LTC-BTC,BCD-BTC,XMX-BTC,AE-USDT,BTC-HUSD,LOOM-ETH,POLY-BTC,EKT-BTC,MXC-BTC,PNT-BTC,NEW-HT,ZEC-BTC,MANA-USDT,FOR-HT,ELF-USDT,KMD-ETH,SWFTC-ETH,USDT-HUSD,XVG-BTC,VSYS-HT,ZIL-BTC,WAN-BTC,ELF-ETH,LYM-BTC,IOTA-USDT,NODE-HT,EM-USDT,LOL-HT,QASH-BTC,BLZ-ETH,NPXS-BTC,FSN-BTC,CNN-BTC,COVA-ETH,BHT-USDT,ONT-BTC,XEM-USDT,CMT-BTC,ALGO-USDT,ETN-ETH,ITC-USDT,HIT-ETH,USDC-HUSD,LINK-USDT,CVC-USDT,REN-BTC,NODE-BTC,GTC-ETH,IRIS-BTC,DASH-USDT,MDS-BTC,LTC-HT,ADA-USDT,XMR-BTC,NEXO-ETH,VSYS-BTC,IOST-BTC,TOPC-BTC,CRE-USDT,TOP-USDT,SNC-ETH,KCASH-ETH,DGD-ETH,RCCC-ETH,SHE-BTC,DATX-BTC,HT-USDT,SEELE-USDT,FTI-ETH,BHD-BTC,PVT-BTC,XRP-USDT,UGAS-BTC,MX-USDT,HPT-HT,RSR-HT,ETH-USDT,UUU-ETH,GXC-ETH,ACT-ETH,BTG-BTC,FAIR-BTC,GT-USDT,SMT-ETH,TOS-BTC,EGCC-BTC,TT-BTC,BUT-ETH,ADX-BTC,REQ-BTC,MEET-BTC,IOST-HT,YEE-ETH,PAI-BTC,UIP-USDT,NEO-USDT,VET-BTC,SNT-USDT,KAN-ETH,NULS-USDT,BCH-BTC,HC-BTC,PVT-HT,UC-ETH,18C-BTC,EOS-ETH,LAMB-ETH,DTA-USDT,CTXC-ETH,NANO-USDT,QUN-ETH,XLM-BTC,DOCK-ETH,ATP-USDT,NKN-HT,DCR-USDT,GNX-ETH,LBA-ETH,FSN-HT" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "pemKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIPVSj8YkpXibCAL9HwpGkDNSEXR9jcpiCthdikJqipNooAoGCCqGSM49\nAwEHoUQDQgAEHiB7q/HCqUrCNqPeTtRmKjyi2T+2O2JgoU8Mjx2R4z1h81uOZHCk\nxbsDg1fb7ACRMpKWPs59QWpQxhqMQrNw8w==\n-----END EC PRIVATE KEY-----\n" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -949,40 +1548,69 @@ "name": "ITBIT", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "clientId": "ClientID", - "availablePairs": "XBTUSD,XBTSGD", - "enabledPairs": "XBTUSD,XBTSGD", "baseCurrencies": "USD,SGD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": false, - "pairsLastUpdated": 1566798411, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "XBTUSD,XBTSGD", + "available": "XBTUSD,XBTSGD" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": {}, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -995,40 +1623,73 @@ "name": "Kraken", "enabled": true, "verbose": false, - "websocket": true, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "ETH-GBP,XTZ-ETH,ATOM-USD,ETC-EUR,ATOM-XBT,QTUM-USD,USDT-USD,ETH-CAD,ETH-USD,XTZ-USD,ADA-CAD,ATOM-CAD,XLM-XBT,ZEC-USD,QTUM-ETH,REP-XBT,REP-ETH,GNO-XBT,WAVES-XBT,EOS-ETH,QTUM-XBT,ZEC-XBT,BAT-EUR,BCH-XBT,WAVES-USD,ETC-XBT,MLN-ETH,REP-USD,ADA-XBT,GNO-ETH,DASH-EUR,EOS-XBT,XLM-EUR,XLM-USD,XRP-USD,ADA-USD,WAVES-ETH,XMR-EUR,XRP-JPY,ZEC-EUR,EOS-EUR,GNO-USD,ETH-JPY,LTC-EUR,MLN-XBT,XTZ-EUR,XBT-CAD,BAT-ETH,BCH-EUR,BAT-XBT,EOS-USD,ADA-EUR,BAT-USD,ETC-ETH,LTC-XBT,REP-EUR,XMR-USD,XRP-XBT,XRP-CAD,DASH-XBT,GNO-EUR,ZEC-JPY,ETH-EUR,XBT-USD,ADA-ETH,ETH-XBT,XTZ-XBT,XBT-JPY,XDG-XBT,XMR-XBT,ATOM-EUR,WAVES-EUR,ETC-USD,XTZ-CAD,XBT-EUR,QTUM-CAD,QTUM-EUR,DASH-USD,LTC-USD,XBT-GBP,XRP-EUR,ATOM-ETH,BCH-USD", - "enabledPairs": "XBT-USD", "baseCurrencies": "EUR,USD,CAD,GBP,JPY", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "-" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "separator": "," + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "XBT-USD", + "available": "DAI-USDT,SC-USD,XBT-GBP,GNO-USD,ICX-ETH,LSK-USD,OMG-USD,MLN-XBT,REP-USD,XBT-EUR,XBT-USD,XDG-XBT,XRP-CAD,ICX-USD,REP-XBT,XLM-USD,LSK-XBT,SC-EUR,ETC-EUR,XMR-USD,ZEC-XBT,EOS-ETH,GNO-ETH,ETH-GBP,ATOM-CAD,EOS-USD,GNO-XBT,ADA-EUR,ATOM-EUR,DASH-EUR,PAXG-EUR,PAXG-USD,ETC-ETH,ETC-XBT,ETH-USD,LTC-EUR,MLN-ETH,XTZ-EUR,BAT-ETH,ETH-CAD,XTZ-XBT,ETC-USD,ETH-EUR,BAT-XBT,LSK-ETH,XTZ-ETH,XTZ-USD,ADA-CAD,ATOM-USD,XRP-USD,ZEC-EUR,ICX-XBT,LINK-ETH,LINK-XBT,LSK-EUR,NANO-XBT,WAVES-EUR,REP-ETH,XMR-XBT,ZEC-USD,USDT-USD,WAVES-ETH,XMR-EUR,BCH-USD,DAI-EUR,ETH-XBT,BCH-XBT,XBT-JPY,XLM-XBT,ADA-ETH,ADA-USD,NANO-USD,PAXG-ETH,QTUM-ETH,BAT-USD,BCH-EUR,SC-XBT,LTC-XBT,LINK-EUR,SC-ETH,DASH-XBT,QTUM-CAD,PAXG-XBT,ADA-XBT,ATOM-XBT,ETH-DAI,LINK-USD,QTUM-USD,WAVES-XBT,ATOM-ETH,GNO-EUR,NANO-ETH,OMG-ETH,WAVES-USD,REP-EUR,DASH-USD,OMG-EUR,DAI-USD,EOS-EUR,QTUM-XBT,ETH-JPY,LTC-USD,XTZ-CAD,EOS-XBT,XLM-EUR,BAT-EUR,ICX-EUR,XRP-XBT,XRP-EUR,NANO-EUR,QTUM-EUR,XBT-CAD,XRP-JPY,ZEC-JPY,OMG-XBT" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "separator": "," + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1041,38 +1702,70 @@ "name": "LakeBTC", "enabled": true, "verbose": false, - "websocket": true, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "USDJPY,USDCHF,BTCCAD,GBPUSD,USDHKD,ETHBTC,LTCBTC,BTCGBP,BTCHKD,BACETH,BTCNGN,USDCAD,BCHBTC,USDNGN,BTCEUR,AUDUSD,BTCUSD,BTCJPY,USDSGD,XRPBTC,BTCSGD,NZDUSD,BTCAUD,BTCNZD,BTCCHF,EURUSD", - "enabledPairs": "BTCUSD,BTCEUR,LTCBTC", "baseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,BTCEUR,LTCBTC", + "available": "USDJPY,USDCHF,BTCCAD,GBPUSD,USDHKD,ETHBTC,LTCBTC,BTCGBP,BTCHKD,BACETH,BTCNGN,USDCAD,BCHBTC,USDNGN,BTCEUR,AUDUSD,BTCUSD,BTCJPY,USDSGD,XRPBTC,BTCSGD,NZDUSD,BTCAUD,BTCNZD,BTCCHF,EURUSD" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1085,38 +1778,70 @@ "name": "LocalBitcoins", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTCCOP,BTCTTD,BTCJOD,BTCXAF,BTCPLN,BTCGTQ,BTCBYN,BTCBRL,BTCTWD,BTCUAH,BTCSZL,BTCGHS,BTCLTC,BTCSEK,BTCKZT,BTCEGP,BTCLBP,BTCPEN,BTCRON,BTCVES,BTCCHF,BTCNZD,BTCTRY,BTCMAD,BTCQAR,BTCSGD,BTCPKR,BTCNAD,BTCHKD,BTCGEL,BTCMXN,BTCCNY,BTCGBP,BTCSAR,BTCDOP,BTCBOB,BTCNGN,BTCPYG,BTCCRC,BTCTZS,BTCCLP,BTCKES,BTCINR,BTCJPY,BTCPHP,BTCUGX,BTCKRW,BTCKWD,BTCILS,BTCETH,BTCDKK,BTCZMW,BTCRWF,BTCEUR,BTCXRP,BTCOMR,BTCAED,BTCIDR,BTCAUD,BTCTHB,BTCKHR,BTCVND,BTCNOK,BTCPAB,BTCZAR,BTCMYR,BTCCAD,BTCBDT,BTCRUB,BTCUSD,BTCLKR,BTCXOF,BTCIRR,BTCARS", - "enabledPairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,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", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", + "available": "BTCXAF,BTCARS,BTCKES,BTCXRP,BTCNZD,BTCLTC,BTCZAR,BTCHKD,BTCCZK,BTCBDT,BTCAED,BTCPEN,BTCBAM,BTCLKR,BTCRON,BTCSGD,BTCBWP,BTCBYN,BTCBGN,BTCUYU,BTCTTD,BTCKRW,BTCAUD,BTCSAR,BTCTHB,BTCPYG,BTCILS,BTCJMD,BTCXOF,BTCTZS,BTCQAR,BTCCHF,BTCRUB,BTCBRL,BTCMWK,BTCKWD,BTCUGX,BTCGHS,BTCPHP,BTCJOD,BTCSZL,BTCGBP,BTCCAD,BTCPKR,BTCGEL,BTCDOP,BTCJPY,BTCHUF,BTCGTQ,BTCCLP,BTCKZT,BTCEGP,BTCMUR,BTCIDR,BTCCRC,BTCRWF,BTCVES,BTCMAD,BTCTWD,BTCBOB,BTCRSD,BTCMYR,BTCZMW,BTCUAH,BTCCNY,BTCEUR,BTCNGN,BTCNOK,BTCTRY,BTCINR,BTCMXN,BTCPLN,BTCVND,BTCSEK,BTCPAB,BTCETH,BTCUSD,BTCDKK,BTCCOP" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1129,40 +1854,74 @@ "name": "OKCOIN International", "enabled": true, "verbose": false, - "websocket": true, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC_USD,LTC_USD,ETH_USD,ETC_USD,TUSD_USD,BCH_USD,EOS_USD,XRP_USD,TRX_USD,BSV_USD,USDT_USD,USDK_USD,XLM_USD,ADA_USD,BAT_USD,DCR_USD,EURS_USD,GRIN_USD,GUSD_USD,PAX_USD,USDC_USD,ZEC_USD,ZRX_USD,BTC_USDT,BTC_GUSD,BTC_PAX,BTC_TUSD,BTC_EUR,BTC_EURS,BTC_USDC,ETH_EUR,BCH_EUR,EURS_EUR", - "enabledPairs": "BTC_USD", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "margin" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-USD,LTC-USD,ETH-USD,ETC-USD,TUSD-USD,BCH-USD,EOS-USD,XRP-USD,TRX-USD,BSV-USD,USDT-USD,USDK-USD,XLM-USD,ADA-USD,BAT-USD,DCR-USD,EURS-USD,HBAR-USD,PAX-USD,USDC-USD,ZEC-USD,BTC-USDT,BTC-EUR,BTC-EURS,ETH-EUR,BCH-EUR,EURS-EUR" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1175,40 +1934,108 @@ "name": "OKEX", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BCH_BTC,BSV_BTC,DASH_BTC,ADA_BTC,ABL_BTC,AE_BTC,ALGO_BTC,ARDR_BTC,ATOM_BTC,BLOC_BTC,BTT_BTC,CAI_BTC,CTXC_BTC,CVT_BTC,DCR_BTC,EGT_BTC,GUSD_BTC,HPB_BTC,HYC_BTC,KAN_BTC,LBA_BTC,LEO_BTC,LET_BTC,LSK_BTC,NXT_BTC,ORS_BTC,PAX_BTC,SC_BTC,TUSD_BTC,USDC_BTC,VITE_BTC,WAVES_BTC,WIN_BTC,WXT_BTC,XAS_BTC,YOU_BTC,ZCO_BTC,ZIL_BTC,XRP_BTC,ELF_BTC,LRC_BTC,MCO_BTC,NULS_BTC,BCX_BTC,CMT_BTC,EDO_BTC,ITC_BTC,SBTC_BTC,ZEC_BTC,NEO_BTC,GAS_BTC,HC_BTC,QTUM_BTC,IOTA_BTC,XUC_BTC,EOS_BTC,SNT_BTC,OMG_BTC,LTC_BTC,ETH_BTC,ETC_BTC,BCD_BTC,BTG_BTC,ACT_BTC,PAY_BTC,BTM_BTC,DGD_BTC,GNT_BTC,LINK_BTC,WTC_BTC,ZRX_BTC,BNT_BTC,CVC_BTC,MANA_BTC,KNC_BTC,GNX_BTC,ICX_BTC,XEM_BTC,ARK_BTC,YOYO_BTC,FUN_BTC,ACE_BTC,TRX_BTC,DGB_BTC,SWFTC_BTC,XMR_BTC,XLM_BTC,KCASH_BTC,MDT_BTC,NAS_BTC,UGC_BTC,DPY_BTC,SSC_BTC,AAC_BTC,VIB_BTC,QUN_BTC,INT_BTC,IOST_BTC,INS_BTC,MOF_BTC,TCT_BTC,STC_BTC,THETA_BTC,PST_BTC,SNC_BTC,MKR_BTC,LIGHT_BTC,OF_BTC,TRUE_BTC,SOC_BTC,ZEN_BTC,HMC_BTC,ZIP_BTC,NANO_BTC,CIC_BTC,GTO_BTC,CHAT_BTC,INSUR_BTC,R_BTC,BEC_BTC,MITH_BTC,ABT_BTC,BKX_BTC,RFR_BTC,TRIO_BTC,DADI_BTC,ONT_BTC,OKB_BTC,ADA_ETH,ABL_ETH,AE_ETH,ALGO_ETH,ATOM_ETH,BTT_ETH,CAI_ETH,CTXC_ETH,DCR_ETH,EGT_ETH,HPB_ETH,HYC_ETH,KAN_ETH,LEO_ETH,LSK_ETH,MVP_ETH,ORS_ETH,SC_ETH,SDA_ETH,WAVES_ETH,WIN_ETH,YOU_ETH,ZIL_ETH,ELF_ETH,LTC_ETH,CMT_ETH,PRA_ETH,LRC_ETH,MCO_ETH,NULS_ETH,DGD_ETH,SNT_ETH,STORJ_ETH,ACT_ETH,BTM_ETH,EOS_ETH,OMG_ETH,DASH_ETH,XRP_ETH,ZEC_ETH,NEO_ETH,GAS_ETH,HC_ETH,QTUM_ETH,IOTA_ETH,ETC_ETH,LINK_ETH,WTC_ETH,ZRX_ETH,BNT_ETH,CVC_ETH,MANA_ETH,GNX_ETH,ICX_ETH,XEM_ETH,YOYO_ETH,TRX_ETH,DGB_ETH,SWFTC_ETH,XMR_ETH,XLM_ETH,KCASH_ETH,MDT_ETH,NAS_ETH,SSC_ETH,AAC_ETH,FAIR_ETH,RCT_ETH,TOPC_ETH,QUN_ETH,INT_ETH,IOST_ETH,INS_ETH,MOF_ETH,REF_ETH,SNC_ETH,MKR_ETH,LIGHT_ETH,OF_ETH,TRUE_ETH,ZEN_ETH,HMC_ETH,ZIP_ETH,NANO_ETH,CIC_ETH,GTO_ETH,INSUR_ETH,UCT_ETH,MITH_ETH,ABT_ETH,AUTO_ETH,TRIO_ETH,TRA_ETH,ONT_ETH,OKB_ETH,BTC_USDK,LTC_USDK,ETH_USDK,OKB_USDK,ETC_USDK,BCH_USDT,BCH_USDK,EOS_USDK,XRP_USDK,TRX_USDK,BSV_USDT,BSV_USDK,USDT_USDK,ADA_USDT,AE_USDT,ALGO_USDT,ALGO_USDK,ALV_USDT,ATOM_USDT,BLOC_USDT,BTT_USDT,CAI_USDT,CRO_USDT,CRO_USDK,CTXC_USDT,CVT_USDT,DCR_USDT,DOGE_USDT,DOGE_USDK,EC_USDT,EC_USDK,EGT_USDT,EM_USDT,EM_USDK,ETM_USDT,ETM_USDK,FSN_USDT,FSN_USDK,FTM_USDT,FTM_USDK,GUSD_USDT,HPB_USDT,HYC_USDT,KAN_USDT,LAMB_USDT,LAMB_USDK,LBA_USDT,LEO_USDT,LEO_USDK,LET_USDT,LSK_USDT,MVP_USDT,ORBS_USDT,ORBS_USDK,ORS_USDT,PAX_USDT,PLG_USDT,PLG_USDK,SC_USDT,TUSD_USDT,USDC_USDT,VNT_USDT,VNT_USDK,WAVES_USDT,WIN_USDT,WXT_USDT,WXT_USDK,XAS_USDT,YOU_USDT,ZIL_USDT,TRX_OKB,ADA_OKB,AE_OKB,BLOC_OKB,DCR_OKB,EGT_OKB,SC_OKB,WAVES_OKB,WXT_OKB,ELF_USDT,DASH_USDT,BTG_USDT,LRC_USDT,MCO_USDT,NULS_USDT,DASH_OKB,XRP_USDT,ZEC_USDT,NEO_USDT,GAS_USDT,HC_USDT,QTUM_USDT,IOTA_USDT,BTC_USDT,BCD_USDT,XUC_USDT,CMT_USDT,EDO_USDT,ITC_USDT,PRA_USDT,ETH_USDT,LTC_USDT,ETC_USDT,EOS_USDT,OMG_USDT,ACT_USDT,BTM_USDT,DGD_USDT,GNT_USDT,PAY_USDT,STORJ_USDT,SNT_USDT,LINK_USDT,WTC_USDT,ZRX_USDT,BNT_USDT,CVC_USDT,MANA_USDT,KNC_USDT,ICX_USDT,XEM_USDT,ARK_USDT,YOYO_USDT,AST_USDT,TRX_USDT,MDA_USDT,DGB_USDT,PPT_USDT,SWFTC_USDT,XMR_USDT,XLM_USDT,KCASH_USDT,MDT_USDT,NAS_USDT,RNT_USDT,UGC_USDT,DPY_USDT,SSC_USDT,AAC_USDT,FAIR_USDT,UBTC_USDT,SHOW_USDT,VIB_USDT,MOT_USDT,UTK_USDT,TOPC_USDT,QUN_USDT,INT_USDT,IPC_USDT,IOST_USDT,INS_USDT,YEE_USDT,MOF_USDT,TCT_USDT,STC_USDT,THETA_USDT,PST_USDT,MKR_USDT,LIGHT_USDT,OF_USDT,TRUE_USDT,SOC_USDT,ZEN_USDT,HMC_USDT,ZIP_USDT,NANO_USDT,CIC_USDT,GTO_USDT,CHAT_USDT,INSUR_USDT,R_USDT,BEC_USDT,MITH_USDT,ABT_USDT,BKX_USDT,RFR_USDT,TRIO_USDT,DADI_USDT,ONT_USDT,OKB_USDT,NEO_OKB,LTC_OKB,ETC_OKB,XRP_OKB,ZEC_OKB,QTUM_OKB,IOTA_OKB,EOS_OKB", - "enabledPairs": "ltc_btc", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "assetTypes": [ + "spot", + "futures", + "perpetualswap", + "index" + ], + "pairs": { + "futures": { + "available": "XRP-USD_191206,XRP-USD_191213,XRP-USD_191227,BTC-USD_191206,BTC-USD_191213,BTC-USD_191227,BTC-USDT_191206,BTC-USDT_191213,BTC-USDT_191227,LTC-USD_191206,LTC-USD_191213,LTC-USD_191227,LTC-USDT_191206,LTC-USDT_191213,LTC-USDT_191227,ETH-USD_191206,ETH-USD_191213,ETH-USD_191227,ETH-USDT_191206,ETH-USDT_191213,ETH-USDT_191227,ETC-USD_191206,ETC-USD_191213,ETC-USD_191227,BCH-USD_191206,BCH-USD_191213,BCH-USD_191227,BCH-USDT_191206,BCH-USDT_191213,BCH-USDT_191227,BSV-USD_191206,BSV-USD_191213,BSV-USD_191227,EOS-USDT_191206,EOS-USDT_191213,EOS-USDT_191227,EOS-USD_191206,EOS-USD_191213,EOS-USD_191227,TRX-USD_191206,TRX-USD_191213,TRX-USD_191227", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "index": { + "available": "XRP-USD,XRP-USD,XRP-USD,BTC-USD,BTC-USD,BTC-USD,BTC-USDT,BTC-USDT,BTC-USDT,LTC-USD,LTC-USD,LTC-USD,LTC-USDT,LTC-USDT,LTC-USDT,ETH-USD,ETH-USD,ETH-USD,ETH-USDT,ETH-USDT,ETH-USDT,ETC-USD,ETC-USD,ETC-USD,BCH-USD,BCH-USD,BCH-USD,BCH-USDT,BCH-USDT,BCH-USDT,BSV-USD,BSV-USD,BSV-USD,EOS-USDT,EOS-USDT,EOS-USDT,EOS-USD,EOS-USD,EOS-USD,TRX-USD,TRX-USD,TRX-USD", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "perpetualswap": { + "available": "BTC-USD_SWAP,LTC-USD_SWAP,ETH-USD_SWAP,TRX-USD_SWAP,BCH-USD_SWAP,BSV-USD_SWAP,EOS-USD_SWAP,XRP-USD_SWAP,ETC-USD_SWAP", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "spot": { + "enabled": "EOS-USDT", + "available": "XPO-USDT,SPND-USDK,SPND-BTC,ROAD-USDK,BCH-BTC,BSV-BTC,DASH-BTC,ADA-BTC,ABL-BTC,AE-BTC,ALGO-BTC,ARDR-BTC,ATOM-BTC,BLOC-BTC,BTT-BTC,CAI-BTC,CRO-BTC,CTXC-BTC,CVT-BTC,DCR-BTC,EGT-BTC,GUSD-BTC,HBAR-BTC,HPB-BTC,HYC-BTC,KAN-BTC,LBA-BTC,LEO-BTC,LET-BTC,LSK-BTC,NXT-BTC,ORS-BTC,PAX-BTC,PMA-BTC,SC-BTC,TUSD-BTC,USDC-BTC,VITE-BTC,VSYS-BTC,WAVES-BTC,WIN-BTC,WXT-BTC,XAS-BTC,XTZ-BTC,YOU-BTC,ZIL-BTC,XRP-BTC,ELF-BTC,LRC-BTC,MCO-BTC,NULS-BTC,BCX-BTC,CMT-BTC,EDO-BTC,ITC-BTC,SBTC-BTC,ZEC-BTC,NEO-BTC,GAS-BTC,HC-BTC,QTUM-BTC,IOTA-BTC,XUC-BTC,EOS-BTC,SNT-BTC,OMG-BTC,LTC-BTC,ETH-BTC,ETC-BTC,BCD-BTC,BTG-BTC,ACT-BTC,PAY-BTC,BTM-BTC,DGD-BTC,GNT-BTC,LINK-BTC,WTC-BTC,ZRX-BTC,BNT-BTC,CVC-BTC,MANA-BTC,KNC-BTC,GNX-BTC,ICX-BTC,XEM-BTC,ARK-BTC,YOYO-BTC,FUN-BTC,ACE-BTC,TRX-BTC,DGB-BTC,SWFTC-BTC,XMR-BTC,XLM-BTC,KCASH-BTC,MDT-BTC,NAS-BTC,UGC-BTC,DPY-BTC,SSC-BTC,AAC-BTC,VIB-BTC,QUN-BTC,INT-BTC,IOST-BTC,INS-BTC,MOF-BTC,TCT-BTC,STC-BTC,THETA-BTC,PST-BTC,SNC-BTC,MKR-BTC,LIGHT-BTC,OF-BTC,TRUE-BTC,SOC-BTC,ZEN-BTC,HMC-BTC,ZIP-BTC,NANO-BTC,CIC-BTC,GTO-BTC,CHAT-BTC,INSUR-BTC,R-BTC,BEC-BTC,MITH-BTC,ABT-BTC,BKX-BTC,RFR-BTC,TRIO-BTC,EDGE-BTC,ONT-BTC,OKB-BTC,ADA-ETH,ABL-ETH,AE-ETH,ALGO-ETH,ATOM-ETH,BTT-ETH,CAI-ETH,CTXC-ETH,DCR-ETH,EGT-ETH,HPB-ETH,HYC-ETH,KAN-ETH,LEO-ETH,MVP-ETH,ORS-ETH,SC-ETH,SDA-ETH,WAVES-ETH,WIN-ETH,YOU-ETH,ZIL-ETH,ELF-ETH,LTC-ETH,CMT-ETH,PRA-ETH,LRC-ETH,MCO-ETH,NULS-ETH,DGD-ETH,STORJ-ETH,BTM-ETH,EOS-ETH,OMG-ETH,DASH-ETH,XRP-ETH,ZEC-ETH,NEO-ETH,GAS-ETH,HC-ETH,QTUM-ETH,IOTA-ETH,ETC-ETH,LINK-ETH,WTC-ETH,ZRX-ETH,CVC-ETH,MANA-ETH,GNX-ETH,XEM-ETH,TRX-ETH,SWFTC-ETH,XMR-ETH,XLM-ETH,KCASH-ETH,MDT-ETH,NAS-ETH,SSC-ETH,AAC-ETH,FAIR-ETH,RCT-ETH,TOPC-ETH,INT-ETH,IOST-ETH,INS-ETH,MOF-ETH,REF-ETH,MKR-ETH,LIGHT-ETH,OF-ETH,TRUE-ETH,ZEN-ETH,NANO-ETH,CIC-ETH,GTO-ETH,UCT-ETH,MITH-ETH,ABT-ETH,AUTO-ETH,TRIO-ETH,ONT-ETH,OKB-ETH,BTC-USDK,LTC-USDK,ETH-USDK,OKB-USDK,ETC-USDK,BCH-USDT,BCH-USDK,EOS-USDK,XRP-USDK,TRX-USDK,BSV-USDT,BSV-USDK,USDT-USDK,ADA-USDT,AE-USDT,ALGO-USDT,ALGO-USDK,ALV-USDT,ATOM-USDT,BLOC-USDT,BTT-USDT,CAI-USDT,CRO-USDT,CRO-USDK,CTXC-USDT,CVT-USDT,DCR-USDT,DOGE-USDT,DOGE-USDK,EC-USDT,EC-USDK,EGT-USDT,EM-USDT,EM-USDK,ETM-USDT,ETM-USDK,FSN-USDT,FSN-USDK,FTM-USDT,FTM-USDK,GUSD-USDT,HBAR-USDT,HBAR-USDK,HPB-USDT,HYC-USDT,KAN-USDT,LAMB-USDT,LAMB-USDK,LBA-USDT,LEO-USDT,LEO-USDK,LET-USDT,LSK-USDT,MVP-USDT,ORBS-USDT,ORBS-USDK,ORS-USDT,PAX-USDT,PLG-USDT,PLG-USDK,PMA-USDK,ROAD-USDT,SC-USDT,TUSD-USDT,USDC-USDT,VNT-USDT,VNT-USDK,VSYS-USDT,VSYS-USDK,WAVES-USDT,WIN-USDT,WXT-USDT,WXT-USDK,XAS-USDT,XPO-USDK,XTZ-USDT,YOU-USDT,ZIL-USDT,TRX-OKB,AE-OKB,BLOC-OKB,EGT-OKB,SC-OKB,WXT-OKB,ELF-USDT,DASH-USDT,BTG-USDT,LRC-USDT,MCO-USDT,NULS-USDT,DASH-OKB,XRP-USDT,ZEC-USDT,NEO-USDT,GAS-USDT,HC-USDT,QTUM-USDT,IOTA-USDT,BTC-USDT,BCD-USDT,XUC-USDT,CMT-USDT,EDO-USDT,ITC-USDT,PRA-USDT,ETH-USDT,LTC-USDT,ETC-USDT,EOS-USDT,OMG-USDT,ACT-USDT,BTM-USDT,DGD-USDT,GNT-USDT,PAY-USDT,STORJ-USDT,SNT-USDT,LINK-USDT,WTC-USDT,ZRX-USDT,BNT-USDT,CVC-USDT,MANA-USDT,KNC-USDT,ICX-USDT,XEM-USDT,ARK-USDT,YOYO-USDT,AST-USDT,TRX-USDT,MDA-USDT,DGB-USDT,PPT-USDT,SWFTC-USDT,XMR-USDT,XLM-USDT,KCASH-USDT,MDT-USDT,NAS-USDT,RNT-USDT,UGC-USDT,DPY-USDT,SSC-USDT,AAC-USDT,FAIR-USDT,UBTC-USDT,SHOW-USDT,VIB-USDT,MOT-USDT,UTK-USDT,TOPC-USDT,QUN-USDT,INT-USDT,IPC-USDT,IOST-USDT,INS-USDT,YEE-USDT,MOF-USDT,TCT-USDT,STC-USDT,THETA-USDT,PST-USDT,MKR-USDT,LIGHT-USDT,OF-USDT,TRUE-USDT,SOC-USDT,ZEN-USDT,HMC-USDT,ZIP-USDT,NANO-USDT,CIC-USDT,GTO-USDT,CHAT-USDT,INSUR-USDT,R-USDT,BEC-USDT,MITH-USDT,ABT-USDT,BKX-USDT,RFR-USDT,TRIO-USDT,EDGE-USDT,ONT-USDT,OKB-USDT,NEO-OKB,LTC-OKB,ETC-OKB,XRP-OKB,ZEC-OKB,IOTA-OKB,EOS-OKB", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1221,40 +2048,72 @@ "name": "Poloniex", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BTC_DASH,BTC_ETC,USDT_REP,BTC_OMG,USDT_BCHSV,BTC_DOGE,USDT_XMR,BTC_LBC,ETH_ZEC,BTC_PASC,BTC_CVC,BTC_EOS,USDT_DOGE,USDT_QTUM,BTC_BCN,BTC_VTC,BTC_XRP,BTC_FCT,BTC_SNT,USDC_ZEC,BTC_POLY,USDC_ATOM,BTC_MAID,ETH_REP,BTC_GAS,ETH_BAT,BTC_MANA,BTC_NAV,USDT_STR,USDT_XRP,BTC_STEEM,USDT_EOS,BTC_BNT,BTC_LTC,BTC_XEM,BTC_DCR,BTC_STORJ,BTC_BCHSV,USDC_DOGE,BTC_CLAM,BTC_DGB,BTC_SC,ETH_ETC,BTC_STRAT,USDT_BCHABC,USDT_ZEC,ETH_EOS,BTC_LOOM,USDT_LSK,USDC_XMR,BTC_LPT,USDT_DGB,USDT_LTC,USDT_ZRX,BTC_QTUM,BTC_BCHABC,USDC_STR,BTC_GRIN,USDC_ETC,USDT_DASH,USDT_NXT,USDT_ETH,USDT_GNT,USDT_MANA,USDC_BCHABC,USDC_XRP,BTC_FOAM,USDT_ATOM,BTC_OMNI,BTC_NXT,BTC_VIA,BTC_BAT,USDC_BTC,USDC_GRIN,USDT_BTC,BTC_LSK,BTC_REP,BTC_ARDR,BTC_ZEC,BTC_ZRX,USDT_BAT,BTC_GAME,BTC_XPM,BTC_ETH,USDT_ETC,ETH_ZRX,USDC_EOS,BTC_GNT,USDT_SC,USDC_ETH,USDC_USDT,BTC_NMR,BTC_ATOM,USDT_GRIN,BTC_BTS,BTC_XMR,USDC_LTC,USDC_DASH,BTC_STR,BTC_KNC,USDC_BCHSV", - "enabledPairs": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", + "available": "USDC_GRIN,BTC_BCN,BTC_DGB,BTC_XMR,USDT_STR,BTC_SC,BTC_ZRX,USDC_XMR,BTC_TRX,BTC_STR,BTC_SNT,USDT_QTUM,USDC_BTC,BTC_NMR,BTC_DASH,BTC_NXT,USDT_LTC,BTC_DCR,USDT_ZRX,USDC_ZEC,USDT_REP,USDT_BAT,BTC_MANA,USDC_BCHABC,USDC_STR,BTC_XRP,USDT_ETH,BTC_REP,USDT_EOS,USDC_ATOM,USDT_XRP,BTC_ETH,USDT_LSK,USDT_SC,USDT_MANA,USDC_ETC,USDC_ETH,BTC_BTS,BTC_LTC,BTC_ETC,BTC_OMG,BTC_STORJ,USDC_XRP,USDT_GRIN,BTC_QTUM,BTC_MAID,BTC_XEM,USDT_BTC,USDT_DASH,ETH_REP,BTC_ZEC,BTC_STRAT,USDC_LTC,BTC_FOAM,USDC_TRX,BTC_DOGE,BTC_VIA,BTC_VTC,ETH_ETC,USDT_ETC,ETH_EOS,USDC_BCHSV,USDT_NXT,USDT_XMR,BTC_ARDR,BTC_CVC,ETH_BAT,USDC_DOGE,BTC_XPM,BTC_LOOM,BTC_LPT,USDC_EOS,USDT_DGB,USDT_BCHSV,BTC_OMNI,ETH_ZEC,BTC_EOS,BTC_KNC,BTC_BCHSV,BTC_POLY,USDC_DASH,USDT_GNT,BTC_BCHABC,BTC_GRIN,BTC_ATOM,USDT_ATOM,USDT_BCHABC,BTC_LSK,ETH_ZRX,BTC_GAS,BTC_BAT,BTC_BNT,USDT_TRX,BTC_FCT,USDT_ZEC,BTC_GNT,USDT_DOGE,USDC_USDT" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1267,42 +2126,74 @@ "name": "Yobit", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR", - "enabledPairs": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": false, - "pairsLastUpdated": 1566798411, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_", + "separator": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", + "available": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_", - "separator": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1315,40 +2206,72 @@ "name": "ZB", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "BRC_BTC,ETH_USDT,BCHSV_QC,LTC_PAX,BITE_BTC,QUN_QC,NWT_USDT,NEO_QC,PDX_BTC,SLT_USDT,XEM_USDT,XMR_QC,KAN_BTC,QTUM_BTC,BTN_USDT,SLT_BTC,GRIN_USDT,USDT_QC,BAR_USDT,ETC_PAX,EOS_USDT,B91_QC,BTS_QC,HSR_BTC,BTS_BTC,XRP_QC,ETC_QC,BTC_PAX,SNT_USDT,ETH_PAX,BTM_QC,TRUE_USDT,INK_QC,BCW_USDT,LBTC_USDT,PAX_QC,LTC_BTC,VSYS_ZB,CHAT_USDT,CDC_USDT,AE_QC,BDS_QC,MCO_USDT,KAN_QC,EPC_QC,XLM_QC,UBTC_USDT,BRC_USDT,TRUE_BTC,MANA_QC,ACC_USDT,BAT_BTC,LBTC_BTC,AAA_QC,BTN_QC,MITH_QC,BCD_QC,BTC_QC,ZRX_USDT,B91_USDT,BCHSV_USDT,MCO_QC,ETC_BTC,RCN_USDT,LBTC_QC,PDX_QC,BCX_BTC,SAFE_USDT,MANA_BTC,TOPC_USDT,DOGE_BTC,NEO_USDT,YTNB_USDT,LTC_QC,HPY_USDT,TRX_BTC,ZRX_QC,DASH_BTC,HSR_USDT,CDC_QC,KNC_USDT,GNT_USDT,GRAM_BTC,AE_USDT,GRAM_QC,XWC_USDT,ETZ_QC,XEM_BTC,VSYS_QC,ADA_BTC,1ST_USDT,UBTC_QC,BITCNY_QC,TOPC_QC,HLC_USDT,XMR_USDT,SLT_QC,XLM_USDT,ETC_USDT,TRUE_QC,ICX_BTC,ADA_USDT,BCX_USDT,PDX_USDT,BAT_USDT,DOGE_QC,NEO_BTC,TUSD_USDT,TV_BTC,QUN_USDT,XUC_QC,OMG_USDT,BTC_USDT,TRX_QC,ZRX_BTC,ETH_QC,SBTC_USDT,VSYS_BTC,ZB_USDT,DASH_USDT,DDM_QC,ETZ_USDT,MTL_USDT,EDO_USDT,KNC_QC,BCHABC_QC,EOSDAC_USDT,ZB_BTC,SNT_QC,DDM_USDT,XRP_USDT,ICX_QC,INK_USDT,BTS_USDT,OMG_QC,ETH_BTC,QTUM_QC,TRX_USDT,SAFE_QC,XLM_BTC,BCD_USDT,SUB_QC,GRIN_QC,EOS_BTC,EPC_BTC,ENTC_USDT,HOTC_USDT,BTH_USDT,TV_USDT,BCHABC_USDT,BTP_USDT,TV_QC,XTZ_USDT,MANA_USDT,1ST_QC,PAX_USDT,BTM_USDT,HSR_QC,LTC_USDT,BCW_QC,LEO_USDT,ZB_QC,BTP_QC,ADA_QC,XRP_BTC,BTH_QC,HOTC_QC,GRAM_USDT,EOSDAC_QC,GNT_BTC,QTUM_USDT,BTM_BTC,EOS_QC,DOGE_USDT,XEM_QC,SNT_BTC,BRC_QC,CHAT_QC,GNT_QC,HPY_QC,DASH_QC,ICX_USDT,BAT_QC,HLC_QC,BCX_QC,XWC_QC,OMG_BTC,AE_BTC", - "enabledPairs": "BTC_USDT,ETH_USDT", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "_" + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USDT,ETH_USDT", + "available": "BTP_USDT,SNT_QC,HSR_QC,BTM_USDT,INK_USDT,EOSDAC_QC,BCHSV_USDT,BCHABC_USDT,KAN_QC,LTC_BTC,DDM_USDT,B91_QC,GRIN_USDT,ZB_USDT,ETH_BTC,ZB_QC,TV_BTC,AE_QC,MANA_BTC,BITE_BTC,DOGE_BTC,BCX_BTC,NEO_QC,BAT_USDT,ICX_USDT,TRUE_BTC,ETH_USDT,BRC_BTC,BCHSV_QC,GRIN_QC,HPY_QC,LBTC_BTC,BCW_QC,XRP_USDT,DASH_QC,HX_USDT,GRAM_QC,ADA_BTC,SBTC_USDT,LVN_QC,BDS_QC,XLM_QC,UBTC_QC,MANA_QC,BCX_QC,UBTC_USDT,SLT_BTC,LTC_USDT,OMG_QC,XEM_BTC,TV_USDT,ZRX_QC,ETZ_USDT,XTZ_USDT,XEM_USDT,TRUE_USDT,TRX_QC,QTUM_BTC,XLM_USDT,HC_BTC,SNT_BTC,SNT_USDT,VSYS_QC,EDO_USDT,HC_QC,PDX_QC,CRO_QC,AE_BTC,BAR_USDT,DDM_QC,AAA_QC,XWC_USDT,XRP_QC,BTM_BTC,HSR_USDT,HOTC_USDT,ENTC_USDT,SUB_QC,ETZ_QC,HC_USDT,BCX_USDT,DOGE_USDT,1ST_QC,SLT_QC,EOS_QC,BTP_QC,OMG_BTC,ZB_BTC,CDC_QC,PDX_BTC,XMR_QC,VSYS_BTC,BRC_USDT,XEM_QC,XLM_BTC,BTS_QC,HX_QC,SAFE_USDT,GNT_QC,INK_QC,KNC_USDT,BTM_QC,FN_USDT,LBTC_USDT,BCW_USDT,HOTC_QC,BCD_USDT,HSR_BTC,LVN_USDT,PAX_QC,BTN_QC,DASH_BTC,ETH_QC,HLC_QC,YTNB_USDT,HPY_USDT,USDT_QC,DASH_USDT,SAFE_QC,MCO_QC,BTN_USDT,EPC_BTC,TV_QC,MTL_USDT,OMG_USDT,ETC_USDT,ZRX_USDT,TSR_USDT,DOGE_QC,MANA_USDT,CHAT_USDT,TRX_USDT,ETC_QC,LTC_QC,MITH_QC,ETC_BTC,PDX_USDT,B91_USDT,XWC_QC,GNT_BTC,KAN_BTC,GNT_USDT,SLT_USDT,ZRX_BTC,PAX_USDT,QTUM_USDT,BTS_USDT,BTC_QC,GRAM_USDT,BCD_QC,CRO_USDT,NEO_USDT,BAT_BTC,LEO_USDT,EOSDAC_USDT,CDC_USDT,LBTC_QC,XRP_BTC,1ST_USDT,TUSD_USDT,BCHABC_QC,ICX_QC,EOS_USDT,EPC_QC,GRAM_BTC,QTUM_QC,NEO_BTC,BTH_QC,KNC_QC,BRC_QC,BTC_USDT,HLC_USDT,VSYS_ZB,TOPC_USDT,MCO_USDT,TRUE_QC,TOPC_QC,QUN_QC,BTH_USDT,ICX_BTC,XUC_QC,NWT_USDT,EOS_BTC,ADA_USDT,TRX_BTC,ADA_QC,BITCNY_QC,XMR_USDT,ACC_USDT,BAT_QC,RCN_USDT,BTS_BTC,QUN_USDT,FN_QC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": false, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1361,38 +2284,106 @@ "name": "Bitmex", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, "httpTimeout": 15000000000, "websocketResponseCheckTimeout": 30000000, "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, "websocketOrderbookBufferLimit": 5, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "proxyAddress": "", - "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", - "availablePairs": "XRPU19,BCHU19,ADAU19,EOSU19,TRXU19,XBTUSD,XBT7D_U105,XBT7D_D95,XBTU19,XBTZ19,ETHUSD,ETHU19,LTCU19", - "enabledPairs": "XBTUSD", "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true + "currencyPairs": { + "assetTypes": [ + "perpetualcontract", + "futures", + "downsideprofitcontract", + "upsideprofitcontract" + ], + "pairs": { + "downsideprofitcontract": { + "available": "XBT7D_D95", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "futures": { + "available": "XRPZ19,BCHZ19,ADAZ19,EOSZ19,TRXZ19,XBTZ19,ETHZ19,LTCZ19", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + } + }, + "perpetualcontract": { + "available": "XBTUSD,ETHUSD", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + } + }, + "spot": { + "enabled": "XBTUSD", + "available": "XRPU19,BCHU19,ADAU19,EOSU19,TRXU19,XBTUSD,XBT7D_U105,XBT7D_D95,XBTU19,XBTZ19,ETHUSD,ETHU19,LTCU19" + }, + "upsideprofitcontract": { + "available": "XBT7D_U105", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1405,40 +2396,71 @@ "name": "Coinbene", "enabled": true, "verbose": false, - "websocket": false, - "useSandbox": false, - "restPollingDelay": 10, - "httpTimeout": 0, - "websocketResponseCheckTimeout": 0, - "websocketResponseMaxLimit": 0, - "websocketOrderbookBufferLimit": 0, - "httpUserAgent": "", - "httpDebugging": false, - "authenticatedApiSupport": false, - "authenticatedWebsocketApiSupport": false, - "apiKey": "Key", - "apiSecret": "Secret", - "apiUrl": "", - "apiUrlSecondary": "", - "proxyAddress": "", - "websocketUrl": "", - "availablePairs": "BTC/USDT", - "enabledPairs": "BTC/USDT", + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, "baseCurrencies": "USD", - "assetTypes": "SPOT", - "supportsAutoPairUpdates": true, - "configCurrencyPairFormat": { - "uppercase": true, - "delimiter": "/" + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "/" + }, + "configFormat": { + "uppercase": true, + "delimiter": "/" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC/USDT", + "available": "ABBC/BTC,ABBC/USDT,ABT/ETH,ABT/USDT,ABYSS/ETH,ACDC/BTC,ACDC/USDT,ADI/ETH,ADK/BTC,ADN/BTC,AE/BTC,AE/USDT,AIDOC/BTC,AION/BTC,AIPE/USDT,AIT/USDT,ALGO/USDT,ALI/ETH,ALX/ETH,APL/ETH,ATX/BTC,B2G/BTC,BAAS/BTC,BAT/BTC,BCH/USDT,BEAUTY/ETH,BETHER/ETH,BEZ/BTC,BGC/USDT,BKG/BTC,BNB/USDT,BNT/BTC,BOA/USDT,BSTN/ETH,BSV/USDT,BTC/USDT,BTFM/USDT,BTNT/BTC,BTSC/BTC,BTT/USDT,BU/ETH,BVT/ETH,C3W/ETH,CAN/ETH,CCC/ETH,CCE/USDT,CC/USDT,CEDEX/ETH,CENT/BTC,CFT/USDT,CLO/BTC,CMT/ETH,CMT/USDT,CNN/BTC,CNN/ETH,CNN/USDT,CONI/USDT,COSM/BTC,COSM/ETH,COZP/BTC,CPC/BTC,CREDO/ETH,CRN/BTC,CSCC/USDT,CS/ETH,CS/USDT,CTXC/ETH,CUST/USDT,CVC/BTC,CXP/BTC,DCA/ETH,DCT/BTC,DDAM/ETH,DDAM/USDT,DENT/BTC,DGD/BTC,DOCK/ETH,DSCB/USDT,DTA/ETH,DUC/BTC,DVC/ETH,EBC/BTC,EBC/ETH,EBC/USDT,ECA/BTC,ECP/BTC,EDC/BTC,EDR/ETH,ELF/BTC,EMT/USDT,EOS/BTC,EOS/USDT,EQUAD/BTC,ETC/BTC,ETC/USDT,ETH/BTC,ETH/USDT,ETK/BTC,ETN/BTC,FAB/ETH,FCC/BTC,FND/ETH,FNKOS/ETH,FTN/BTC,FTN/USDT,FTT/BTC,FXT/ETH,GDC/BTC,GDC/ETH,GDC/USDT,GETX/ETH,GLDR/ETH,GMTK/ETH,GOM2/USDT,GRAM/USDT,GRIN/BTC,GRN/BTC,GUSD/USDT,GVT/BTC,HAPPY/BTC,HDAC/BTC,HMB/USDT,HNB/USDT,HPT/ETH,HT/USDT,HUP/USDT,INCX/ETH,IOST/BTC,IOTE/USDT,ISR/BTC,ISR/ETH,IVY/ETH,JOB/BTC,KBC/BTC,KBC/USDT,KMD/BTC,KNT/ETH,KST/BTC,KUE/BTC,KUE/ETH,LAMB/USDT,LATX/BTC,LBK/BTC,LINK/BTC,LOOM/BTC,LTC/BTC,LTC/USDT,LUC/ETH,LUX/BTC,LVTC/ETH,MC/USDT,MDC/USDT,MIB/BTC,MINX/BTC,MINX/ETH,MOAC/USDT,MPL/BTC,MTC/BTC,MT/ETH,MTN/ETH,MT/USDT,MVL/ETH,MVPT/ETH,MXM/ETH,MXM/USDT,MZG/USDT,NANO/BTC,NBAI/ETH,NCASH/BTC,NEO/BTC,NEO/USDT,NFT/USDT,NOBS/BTC,NPXS/ETH,NPXS/USDT,NTY/ETH,ODC/USDT,OMG/BTC,OMX/ETH,OVC/ETH,OZX/ETH,PAL/ETH,PAT/ETH,PAX/USDT,PKX/BTC,PLAY/BTC,PMA/ETH,POLL/BTC,POLY/BTC,PPT/BTC,PSM/BTC,QKC/BTC,QTUM/BTC,QTUM/USDT,RBTC/BTC,RCOIN/BTC,RCOIN/USDT,REP/BTC,REV/BTC,RIF/BTC,SBT/USDT,SCC/BTC,SCO/BTC,SEN/BTC,SENC/ETH,SHE/BTC,SHVR/BTC,SIM/BTC,SKB/BTC,SKM/ETH,SKYM/USDT,SLT/ETH,SMARTUP/ETH,SMARTUP/USDT,SMART/USDT,SORO/USDT,SRCOIN/BTC,SRCOIN/ETH,STORJ/BTC,STQ/BTC,SWET/BTC,SWTC/USDT,TCT/BTC,TEMCO/USDT,TEN/BTC,TEN/ETH,TIB/BTC,TMTG/BTC,TOC/ETH,TOOS/USDT,TOSC/BTC,TRUE/ETH,TRX/BTC,TRX/USDT,TSL/BTC,TVB/USDT,UNI/USDT,UTNP/BTC,VBT/USDT,VEEN/BTC,VME/BTC,VME/ETH,VSC/ETH,VSF/BTC,W12/BTC,W12/ETH,WBL/BTC,WFX/BTC,XEM/BTC,XLM/BTC,XMCT/ETH,XMCT/USDT,XMR/BTC,XNK/ETH,XRP/BTC,XRP/USDT,XSR/USDT,YAP/BTC,YAP/USDT,YTA/USDT,ZAT/ETH,ZDC/BTC,ZEC/BTC,ZGC/BTC,ZRX/BTC" + } + } }, - "requestCurrencyPairFormat": { - "uppercase": true, - "delimiter": "/" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, "bankAccounts": [ { + "enabled": false, "bankName": "", "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "", "accountNumber": "", "swiftCode": "", @@ -1450,9 +2472,12 @@ ], "bankAccounts": [ { - "enabled": true, + "enabled": false, "bankName": "test", "bankAddress": "test", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", "accountName": "TestAccount", "accountNumber": "0234", "swiftCode": "91272837", @@ -1460,20 +2485,5 @@ "supportedCurrencies": "USD", "supportedExchanges": "ANX,Kraken" } - ], - "connectionMonitor": { - "preferredDNSList": [ - "8.8.8.8", - "8.8.4.4", - "1.1.1.1", - "1.0.0.1" - ], - "preferredDomainList": [ - "www.google.com", - "www.cloudflare.com", - "www.facebook.com" - ], - "checkInterval": 1000000000 - }, - "fiatDispayCurrency": "" -} + ] +} \ No newline at end of file diff --git a/testdata/preengine_config.json b/testdata/preengine_config.json new file mode 100644 index 00000000..7f058795 --- /dev/null +++ b/testdata/preengine_config.json @@ -0,0 +1,1469 @@ +{ + "name": "Skynet", + "encryptConfig": 0, + "globalHTTPTimeout": 15000000000, + "logging": { + "enabled": true, + "file": "debug.txt", + "colour": false, + "level": "DEBUG|WARN|INFO|ERROR|FATAL", + "rotate": false + }, + "profiler": { + "enabled": false + }, + "ntpclient": { + "enabled": 0, + "pool": [ + "pool.ntp.org:123" + ], + "allowedDifference": 50000000, + "allowedNegativeDifference": 50000000 + }, + "currencyConfig": { + "forexProviders": [ + { + "name": "CurrencyConverter", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "CurrencyLayer", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "Fixer", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "OpenExchangeRates", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "ExchangeRates", + "enabled": true, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": true + } + ], + "cryptocurrencyProvider": { + "name": "CoinMarketCap", + "enabled": false, + "verbose": false, + "apiKey": "Key", + "accountPlan": "accountPlan" + }, + "cryptocurrencies": "BTC,LTC,ETH,XRP,NMC,NVC,PPC,XBT,DOGE,DASH", + "currencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "fiatDisplayCurrency": "USD", + "currencyFileUpdateDuration": 0, + "foreignExchangeUpdateDuration": 0 + }, + "communications": { + "slack": { + "name": "Slack", + "enabled": false, + "verbose": false, + "targetChannel": "general", + "verificationToken": "testtest" + }, + "smsGlobal": { + "name": "SMSGlobal", + "enabled": false, + "verbose": false, + "username": "Username", + "password": "Password", + "contacts": [ + { + "name": "Bob", + "number": "12345", + "enabled": false + } + ] + }, + "smtp": { + "name": "SMTP", + "enabled": false, + "verbose": false, + "host": "smtp.google.com", + "port": "537", + "accountName": "some", + "accountPassword": "password", + "recipientList": "lol123@gmail.com" + }, + "telegram": { + "name": "Telegram", + "enabled": false, + "verbose": false, + "verificationToken": "testest" + } + }, + "portfolioAddresses": { + "Addresses": [ + { + "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", + "CoinType": "BTC", + "Balance": 53000.01741264, + "Description": "" + }, + { + "Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", + "CoinType": "BTC", + "Balance": 107848.28963408, + "Description": "" + }, + { + "Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh", + "CoinType": "LTC", + "Balance": 0.03665026, + "Description": "" + }, + { + "Address": "0xb794f5ea0ba39494ce839613fffba74279579268", + "CoinType": "ETH", + "Balance": 0.25555604051325775, + "Description": "" + } + ] + }, + "webserver": { + "enabled": true, + "adminUsername": "admin", + "adminPassword": "Password", + "listenAddress": ":9050", + "websocketConnectionLimit": 1, + "websocketMaxAuthFailures": 3, + "websocketAllowInsecureOrigin": true + }, + "exchanges": [ + { + "name": "ANX", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "ATENC_GBP,ATENC_NZD,BTC_AUD,BTC_SGD,LTC_BTC,START_GBP,STR_BTC,XRP_BTC,ATENC_SGD,BTC_GBP,DOGE_BTC,OAX_ETH,START_AUD,START_JPY,ATENC_USD,BTC_EUR,GNT_ETH,START_EUR,ATENC_EUR,BTC_CAD,START_BTC,START_CAD,ATENC_HKD,ATENC_JPY,ETH_BTC,ETH_HKD,START_HKD,START_USD,ATENC_AUD,ETH_USD,START_SGD,ATENC_CAD,BTC_HKD,BTC_JPY,BTC_NZD,BTC_USD,START_NZD", + "enabledPairs": "BTC_USD,BTC_HKD,BTC_EUR,BTC_CAD,BTC_AUD,BTC_SGD,BTC_JPY,BTC_GBP,BTC_NZD,LTC_BTC,STR_BTC,XRP_BTC", + "baseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Binance", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,SNGLS-ETH,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,BTS-BNB,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,FUEL-ETH,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADX-BNB,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,NAV-ETH,NAV-BNB,LUN-BTC,LUN-ETH,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,NCASH-BNB,POA-BTC,POA-ETH,POA-BNB,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,GNT-BNB,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,REP-BNB,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,CVC-BNB,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-BTC,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-BTC,DENT-ETH,ARDR-BTC,ARDR-ETH,ARDR-BNB,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BCHABC-BTC,BCHABC-USDT,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-PAX,WAVES-USDC,BCHABC-TUSD,BCHABC-PAX,BCHABC-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BTC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-PAX,ATOM-TUSD,ETC-USDC,ETC-PAX,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-USDC,PHB-TUSD,PHB-PAX,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,TFUEL-USDC,TFUEL-TUSD,TFUEL-PAX,ONE-BNB,ONE-BTC,ONE-USDT,ONE-TUSD,ONE-PAX,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-TUSD,FTM-PAX,FTM-USDC,BTCB-BTC,BCPT-TUSD,BCPT-PAX,BCPT-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,USDSB-USDT,USDSB-USDS,GTO-USDT,GTO-PAX,GTO-TUSD,GTO-USDC,ERD-BNB,ERD-BTC,ERD-USDT,ERD-PAX,ERD-USDC,DOGE-BNB,DOGE-BTC,DOGE-USDT,DOGE-PAX,DOGE-USDC,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ANKR-TUSD,ANKR-PAX,ANKR-USDC,ONT-PAX,ONT-USDC,WIN-BNB,WIN-BTC,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,TUSDB-TUSD,NPXS-USDT,NPXS-USDC,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC", + "enabledPairs": "BTC-USDT,ETH-USDT,LTC-USDT,ADA-USDT,XRP-USDT", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bitfinex", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,BTCF0:USTF0,ETHF0:USTF0", + "enabledPairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bitflyer", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC", + "enabledPairs": "BTC_JPY,ETH_BTC,BCH_BTC", + "baseCurrencies": "JPY", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": false, + "pairsLastUpdated": 1566798411, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bithumb", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "clientId": "ClientID", + "availablePairs": "VETKRW,REPKRW,ARNKRW,OCNKRW,ETHOSKRW,STEEMKRW,LRCKRW,ETCKRW,CMTKRW,HDACKRW,WTCKRW,PLYKRW,QTUMKRW,MCOKRW,NPXSKRW,ABTKRW,BSVKRW,SNTKRW,STRATKRW,BATKRW,ETHKRW,CTXCKRW,AUTOKRW,HYCKRW,POLYKRW,QKCKRW,TMTGKRW,BCHKRW,MXCKRW,XEMKRW,GTOKRW,BTTKRW,APISKRW,DACKRW,ELFKRW,XLMKRW,DACCKRW,GNTKRW,EOSKRW,TRXKRW,BZNTKRW,ETZKRW,XRPKRW,WAVESKRW,WETKRW,HCKRW,XMRKRW,PPTKRW,LOOMKRW,KNCKRW,MIXKRW,RDNKRW,ADAKRW,ENJKRW,ZRXKRW,DASHKRW,PIVXKRW,THETAKRW,VALORKRW,BHPKRW,OMGKRW,RNTKRW,GXCKRW,AMOKRW,CROKRW,LAMBKRW,LINKKRW,ROMKRW,ZILKRW,ORBSKRW,POWRKRW,INSKRW,CONKRW,XVGKRW,BCDKRW,ICXKRW,BTCKRW,BTGKRW,LBAKRW,MTLKRW,MITHKRW,PAYKRW,WAXKRW,ANKRKRW,IOSTKRW,AEKRW,LTCKRW,ITCKRW,SALTKRW,ZECKRW,TRUEKRW,PSTKRW", + "enabledPairs": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,XMRKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", + "baseCurrencies": "KRW", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "index": "KRW" + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bitmex", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "XRPU19,BCHU19,ADAU19,EOSU19,TRXU19,XBTUSD,XBT7D_U105,XBT7D_D95,XBTU19,XBTZ19,ETHUSD,ETHU19,LTCU19", + "enabledPairs": "XBTUSD", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bitstamp", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "clientId": "ClientID", + "availablePairs": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR", + "enabledPairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", + "baseCurrencies": "USD,EUR", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bittrex", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "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-GLC,BTC-GRS,BTC-NLG,BTC-MONA,BTC-VRC,BTC-CURE,BTC-XMR,BTC-XDN,BTC-NAV,BTC-XST,BTC-VIA,BTC-PINK,BTC-IOC,BTC-SYS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-BLOCK,BTC-BTS,BTC-XRP,BTC-GAME,BTC-NXS,BTC-GEO,BTC-FLDC,BTC-FLO,BTC-MUE,BTC-XEM,BTC-SPHR,BTC-OK,BTC-AEON,BTC-ETH,BTC-EXP,BTC-AMP,BTC-XLM,USDT-BTC,BTC-RVR,BTC-FCT,BTC-MAID,BTC-SLS,BTC-RADS,BTC-DCR,BTC-XVG,BTC-PIVX,BTC-MEME,BTC-STEEM,BTC-LSK,BTC-DGD,BTC-WAVES,BTC-LBC,BTC-SBD,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-IOP,BTC-UBQ,BTC-KMD,BTC-SIB,BTC-ION,BTC-CRW,BTC-SWT,BTC-MLN,BTC-ARK,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-EDG,BTC-MORE,ETH-GNT,ETH-REP,USDT-ETH,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,ETH-GNO,BTC-HMQ,BTC-ANT,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,BTC-PTOY,BTC-BNT,ETH-BNT,BTC-NMR,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,BTC-FUN,BTC-PAY,ETH-PAY,BTC-MTL,BTC-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-BCH,USDT-BCH,BTC-BCH,BTC-DNT,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,USDT-OMG,BTC-ADA,BTC-MANA,ETH-MANA,BTC-RCN,BTC-VIB,ETH-VIB,BTC-MER,BTC-POWR,ETH-POWR,ETH-ADA,BTC-ENG,ETH-ENG,USDT-ADA,USDT-XVG,USDT-NXT,BTC-UKG,ETH-UKG,BTC-IGNIS,BTC-SRN,ETH-SRN,BTC-WAX,ETH-WAX,BTC-ZRX,ETH-ZRX,BTC-VEE,BTC-BCPT,BTC-TRX,ETH-TRX,BTC-TUSD,BTC-LRC,ETH-TUSD,BTC-UP,BTC-DMT,ETH-DMT,USDT-TUSD,BTC-POLY,ETH-POLY,BTC-PRO,USDT-SC,USDT-TRX,BTC-BLT,BTC-STORM,ETH-STORM,BTC-AID,BTC-NGC,BTC-GTO,USDT-DCR,BTC-OCN,ETH-OCN,USD-BTC,USD-USDT,USD-TUSD,BTC-TUBE,BTC-CMCT,USD-ETH,BTC-NLC2,BTC-BKX,BTC-MFT,BTC-LOOM,BTC-RFR,USDT-DGB,BTC-RVN,USD-XRP,USD-ETC,BTC-BFT,BTC-GO,BTC-HYDRO,BTC-UPP,USD-ADA,USD-ZEC,USDT-DOGE,BTC-ENJ,BTC-MET,USD-LTC,USD-TRX,BTC-DTA,BTC-EDR,BTC-BOXX,BTC-IHT,USD-BCH,BTC-XHV,USDT-ZRX,BTC-NPXS,BTC-PMA,USDT-BAT,USDT-RVN,BTC-PAL,USD-SC,BTC-PAX,USDT-PAX,BTC-ZIL,BTC-MOC,BTC-OST,BTC-SPC,BTC-MEDX,BTC-BSV,BTC-IOST,BTC-XNK,USDT-BSV,ETH-BSV,BTC-NCASH,BTC-SOLVE,BTC-USDS,USDT-PMA,ETH-NPXS,USDT-NPXS,USD-ZRX,BTC-JNT,BTC-LBA,BTC-MOBI,USD-BAT,USD-BSV,BTC-DENT,USD-USDS,BTC-DRGN,USD-PAX,BTC-VITE,BTC-IOTX,USD-DGB,BTC-BTM,BTC-ELF,USD-EDR,BTC-QNT,BTC-BTU,USD-ZEN,BTC-SPND,BTC-BTT,BTC-NKN,USD-KMD,USDT-BTT,BTC-GRIN,BTC-CTXC,BTC-HXRO,BTC-META,USDT-GRIN,BTC-FSN,BTC-HST,BTC-ANKR,USDT-XLM,BTC-TRAC,BTC-CRO,BTC-ONT,ETH-SOLVE,BTC-ONG,BTC-AERGO,BTC-TTC,USD-SPND,BTC-SLT,BTC-PTON,BTC-PI,ETH-ANKR,BTC-PLA,BTC-ART,BTC-ORBS,USDT-ENJ,BTC-VBK,BTC-BORA,BTC-CND,USDT-ONT,BTC-TRIO,BTC-FX,ETH-FX,BTC-ATOM,USDT-ATOM,ETH-ATOM,BTC-XYO,BTC-OCEAN,USDT-OCEAN,BTC-WIB,BTC-BWX,BTC-SNX,BTC-SUSD,BTC-VDX,USDT-VDX,ETH-VDX,BTC-COSM,BTC-OGO,USDT-OGO,BTC-ITM,BTC-LAMB,BTC-STPT,BTC-FET,BTC-MKR,ETH-MKR,BTC-DAI,ETH-DAI,USDT-DAI,BTC-CPT,BTC-ABT,BTC-PROM,BTC-FTM,BTC-ABYSS,BTC-EOS,ETH-EOS,USDT-EOS,BTC-FXC,BTC-DUSK,BTC-URAC,BTC-BLOC,BTC-BRZ,BTC-TEMCO,BTC-SPIN,BTC-HINT,BTC-LUNA,BTC-CHR,BTC-TUDA,BTC-UTK,BTC-PXL,BTC-AKRO,BTC-TSHP", + "enabledPairs": "USDT-BTC", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "requestCurrencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "BTSE", + "enabled": true, + "verbose": false, + "websocket": true, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD", + "enabledPairs": "BTC-USD", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "requestCurrencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "BTC Markets", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC", + "enabledPairs": "BTC-AUD", + "baseCurrencies": "AUD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "COINUT", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "clientId": "ClientID", + "availablePairs": "BTCUSDT,ETCBTC,ETHUSDT,BTCCAD,ETHLTC,ETHUSD,LTCUSD,BTCSGD,ETCSGD,ETHSGD,ZECBTC,ZECUSD,ZECUSDT,ETHBTC,LTCBTC,USDTSGD,USDTUSD,XMRUSDT,BTCUSD,LTCCAD,LTCSGD,LTCUSDT,XMRBTC,XMRLTC,ZECLTC,ETCUSDT,ZECSGD,ETCLTC,ETHCAD,ZECCAD", + "enabledPairs": "LTCBTC,ETCBTC,ETHBTC", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "EXMO", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "WAVES_BTC,BTC_RUB,DCR_UAH,XMR_UAH,USDC_BTC,XEM_USD,XLM_RUB,ATMCASH_BTC,QTUM_USD,ADA_USD,TRX_BTC,XRP_BTC,MKR_DAI,STQ_USD,ETH_USD,KICK_USDT,ZRX_USD,USDC_ETH,GUSD_BTC,ZRX_ETH,DASH_BTC,ETC_BTC,LTC_RUB,BTC_USD,STQ_EUR,BCH_RUB,XRP_USDT,WAVES_ETH,XTZ_ETH,QTUM_BTC,XEM_BTC,LSK_BTC,TRX_RUB,ETH_PLN,PTI_USDT,MNC_ETH,DAI_BTC,NEO_USD,KICK_BTC,ETH_BTC,ZEC_BTC,ETZ_USDT,DAI_ETH,DAI_USD,GNT_ETH,HBZ_USD,DXT_BTC,XRP_TRY,DAI_RUB,MNX_BTC,BCH_ETH,WAVES_USD,TRX_USD,INK_ETH,XLM_BTC,XMR_USD,KICK_ETH,DASH_RUB,LTC_BTC,USDT_RUB,USDT_EUR,DOGE_USD,DASH_UAH,XTZ_USD,ETZ_ETH,HB_BTC,GUSD_RUB,BTC_TRY,ADA_BTC,ADA_ETH,BTG_BTC,BCH_USDT,USDT_UAH,PTI_RUB,XTZ_RUB,DASH_USD,LTC_USD,ETH_USDT,MNC_BTC,XEM_EUR,GUSD_USD,XMR_BTC,XRP_EUR,SMART_USD,HBZ_BTC,BCH_USD,ETH_RUB,XRP_ETH,ZEC_RUB,XRP_RUB,DCR_BTC,DCR_RUB,PTI_EOS,EOS_USD,DXT_USD,ETH_LTC,BTC_USDT,USDT_USD,DASH_USDT,BTG_ETH,BCH_UAH,ROOBEE_ETH,TRX_UAH,MNC_USD,QTUM_ETH,BTCZ_BTC,XRP_UAH,USDC_USDT,NEO_BTC,OMG_ETH,STQ_BTC,ETC_USD,XMR_EUR,EOS_EUR,BTC_PLN,NEO_RUB,ZRX_BTC,INK_BTC,MNX_ETH,ETH_UAH,LSK_RUB,BCH_BTC,ETH_EUR,XLM_USD,ETC_RUB,DOGE_BTC,EXM_BTC,ROOBEE_BTC,LSK_USD,HBZ_ETH,LTC_EUR,USD_RUB,KICK_RUB,USDC_USD,PTI_BTC,OMG_USD,XRP_USD,XEM_UAH,GNT_BTC,LTC_UAH,SMART_BTC,SMART_EUR,SMART_RUB,BTG_USD,GAS_USD,BTC_UAH,XTZ_BTC,ZEC_USD,MKR_BTC,INK_USD,EOS_BTC,STQ_RUB,ZEC_EUR,XMR_ETH,BTC_EUR,XMR_RUB,XLM_TRY,GAS_BTC,MNX_USD,WAVES_RUB,ETZ_BTC,ETH_TRY,OMG_BTC,BCH_EUR", + "enabledPairs": "BTC_USD,LTC_USD", + "baseCurrencies": "USD,EUR,RUB,PLN,UAH", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_", + "separator": "," + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "CoinbasePro", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "clientId": "ClientID", + "availablePairs": "EOSUSD,ETHBTC,ETHUSDC,ETHEUR,ZECUSDC,REPUSD,LIN-ETH,EOSBTC,LTCGBP,CVCUSDC,XLMEUR,ETCGBP,XTZBTC,XRPUSD,XRPBTC,ALG-USD,BTCUSDC,GNTUSDC,ZRXBTC,DNTUSDC,BTCUSD,LTCBTC,LTCUSD,ETHGBP,ZRXUSD,BATETH,ZRXEUR,REPBTC,ETCEUR,XRPEUR,EOSEUR,BCHEUR,MAN-USDC,XLMUSD,BATUSDC,LOO-USDC,BTCEUR,BCHGBP,LTCEUR,BCHBTC,LIN-USD,DAIUSDC,XTZUSD,ETCBTC,BCHUSD,BTCGBP,ETHUSD,XLMBTC,ETCUSD,ZECBTC,ETHDAI", + "enabledPairs": "BTCUSD,BTCGBP,BTCEUR", + "baseCurrencies": "USD,GBP,EUR", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true + }, + "requestCurrencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Coinbene", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "clientId": "ClientID", + "availablePairs": "ABBC/BTC,ABT/ETH,ABT/USDT,ABYSS/ETH,ACDC/BTC,ACDC/USDT,ADI/ETH,ADK/BTC,ADN/BTC,AE/BTC,AE/USDT,AID/BTC,AIDOC/BTC,AION/BTC,AIPE/USDT,AIT/USDT,ALGO/USDT,ALI/ETH,ALX/ETH,APL/ETH,ATX/BTC,B2G/BTC,B91/USDT,BAAS/BTC,BAT/BTC,BCHABC/USDT,BCHSV/USDT,BEAUTY/ETH,BETHER/ETH,BEZ/BTC,BGC/USDT,BKG/BTC,BNT/BTC,BOA/USDT,BSTN/ETH,BTC/USDT,BTFM/USDT,BTNT/BTC,BTSC/BTC,BTT/USDT,BU/ETH,BVT/ETH,C3W/ETH,CAN/ETH,CCC/ETH,CCE/USDT,CC/USDT,CEDEX/ETH,CENT/BTC,CFT/USDT,CLO/BTC,CMT/ETH,CMT/USDT,CNN/BTC,CNN/ETH,CNN/USDT,CONI/USDT,COSM/BTC,COSM/ETH,COZP/BTC,CPC/BTC,CPMS/USDT,CREDO/ETH,CRN/BTC,CS/ETH,CS/USDT,CTXC/ETH,CUST/USDT,CVC/BTC,CXC/USDT,CXP/BTC,DCA/ETH,DCT/BTC,DENT/BTC,DGD/BTC,DOCK/ETH,DSCB/USDT,DTA/ETH,DUC/BTC,DVC/ETH,EBC/BTC,EBC/ETH,EBC/USDT,ECA/BTC,EDC/BTC,EDR/ETH,ELF/BTC,EMT/USDT,EOS/BTC,EOS/USDT,EQUAD/BTC,ETC/BTC,ETC/USDT,ETH/BTC,ETH/USDT,ETK/BTC,ETN/BTC,FAB/ETH,FACC/ETH,FCC/BTC,FDS/USDT,FND/ETH,FNKOS/ETH,FTN/BTC,FTN/USDT,FTT/BTC,FXT/ETH,GETX/ETH,GLDR/ETH,GMTK/ETH,GOM/USDT,GRAM/USDT,GRIN/BTC,GRN/BTC,GSTT/USDT,GUSD/USDT,GVT/BTC,HAPPY/BTC,HDAC/BTC,HMB/USDT,HNB/USDT,HPT/ETH,HUP/USDT,INCX/ETH,IOST/BTC,IOTE/USDT,ISR/BTC,ISR/ETH,IVY/ETH,JOB/BTC,KBC/BTC,KBC/USDT,KMD/BTC,KNT/ETH,KST/BTC,KUE/BTC,KUE/ETH,KUKY/BTC,LAMB/USDT,LATX/BTC,LBK/BTC,LINK/BTC,LOOM/BTC,LTC/BTC,LTC/USDT,LUC/ETH,LUX/BTC,LVTC/ETH,MDC/USDT,MGC/USDT,MIB/BTC,MINX/BTC,MINX/ETH,MOAC/USDT,MPL/BTC,MTC/BTC,MT/ETH,MTN/ETH,MT/USDT,MVL/ETH,MVPT/ETH,MWT/USDT,NANO/BTC,NBAI/ETH,NCASH/BTC,NEO/BTC,NEO/USDT,NOBS/BTC,NPXS/ETH,NPXS/USDT,NTY/ETH,ODC/USDT,OMG/BTC,OMX/ETH,OVC/ETH,OZX/ETH,PAL/ETH,PAT/ETH,PAX/USDT,PKX/BTC,PLAY/BTC,PMA/ETH,POLL/BTC,POLY/BTC,PPT/BTC,PSM/BTC,QKC/BTC,QTUM/BTC,QTUM/USDT,RBG/BTC,RBG/ETH,RBG/USDT,RBTC/BTC,RBZ/USDT,RCOIN/BTC,RCOIN/USDT,REP/BTC,REV/BTC,RIF/BTC,SALT/BTC,SCC/BTC,SCO/BTC,SEN/BTC,SENC/ETH,SHE/BTC,SHVR/BTC,SIM/BTC,SKB/BTC,SKM/ETH,SKYM/USDT,SLT/ETH,SMARTUP/ETH,SMARTUP/USDT,SMART/USDT,SORO/USDT,SRCOIN/BTC,SRCOIN/ETH,STORJ/BTC,STQ/BTC,SWET/BTC,SWTC/USDT,TCT/BTC,TEMCO/USDT,TEN/BTC,TEN/ETH,THM/ETH,TIB/BTC,TIMO/USDT,TMTG/BTC,TOC/ETH,TOSC/BTC,TRUE/ETH,TRX/BTC,TRX/USDT,TSL/BTC,TVB/USDT,UTNP/BTC,VBT/USDT,VEEN/BTC,VME/BTC,VME/ETH,VOLLAR/USDT,VSC/ETH,W12/BTC,W12/ETH,WBL/BTC,WFX/BTC,XEM/BTC,XLM/BTC,XMCT/ETH,XMCT/USDT,XMR/BTC,XNK/ETH,XRP/BTC,XRP/USDT,XSR/USDT,YTA/USDT,ZAT/ETH,ZDC/BTC,ZEC/BTC,ZGC/BTC,ZRX/BTC", + "enabledPairs": "BTC/USDT", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "/" + }, + "requestCurrencyPairFormat": { + "uppercase": true, + "delimiter": "/" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "GateIO", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "USDT_CNYX,BTC_CNYX,ETH_CNYX,EOS_CNYX,BCH_CNYX,XRP_CNYX,DOGE_CNYX,TIPS_CNYX,BTC_USDC,BTC_PAX,BTC_USDT,BCH_USDT,ETH_USDT,ETC_USDT,QTUM_USDT,LTC_USDT,DASH_USDT,ZEC_USDT,BTM_USDT,EOS_USDT,REQ_USDT,SNT_USDT,OMG_USDT,PAY_USDT,CVC_USDT,ZRX_USDT,TNT_USDT,XMR_USDT,XRP_USDT,DOGE_USDT,BAT_USDT,PST_USDT,BTG_USDT,DPY_USDT,LRC_USDT,STORJ_USDT,RDN_USDT,STX_USDT,KNC_USDT,LINK_USDT,CDT_USDT,AE_USDT,AE_ETH,AE_BTC,CDT_ETH,RDN_ETH,STX_ETH,KNC_ETH,LINK_ETH,REQ_ETH,RCN_ETH,TRX_ETH,ARN_ETH,KICK_ETH,BNT_ETH,VET_ETH,MCO_ETH,FUN_ETH,DATA_ETH,RLC_ETH,RLC_USDT,ZSC_ETH,WINGS_ETH,MDA_ETH,RCN_USDT,TRX_USDT,KICK_USDT,VET_USDT,MCO_USDT,FUN_USDT,DATA_USDT,ZSC_USDT,MDA_USDT,XTZ_USDT,XTZ_BTC,XTZ_ETH,GNT_USDT,GNT_ETH,GEM_USDT,GEM_ETH,RFR_USDT,RFR_ETH,DADI_USDT,DADI_ETH,ABT_USDT,ABT_ETH,LEDU_BTC,LEDU_ETH,OST_USDT,OST_ETH,XLM_USDT,XLM_ETH,XLM_BTC,MOBI_USDT,MOBI_ETH,MOBI_BTC,OCN_USDT,OCN_ETH,OCN_BTC,ZPT_USDT,ZPT_ETH,ZPT_BTC,COFI_USDT,COFI_ETH,JNT_USDT,JNT_ETH,JNT_BTC,BLZ_USDT,BLZ_ETH,GXS_USDT,GXS_BTC,MTN_USDT,MTN_ETH,RUFF_USDT,RUFF_ETH,RUFF_BTC,TNC_USDT,TNC_ETH,TNC_BTC,ZIL_USDT,ZIL_ETH,BTO_USDT,BTO_ETH,THETA_USDT,THETA_ETH,DDD_USDT,DDD_ETH,DDD_BTC,MKR_USDT,MKR_ETH,DAI_USDT,SMT_USDT,SMT_ETH,MDT_USDT,MDT_ETH,MDT_BTC,MANA_USDT,MANA_ETH,LUN_USDT,LUN_ETH,SALT_USDT,SALT_ETH,FUEL_USDT,FUEL_ETH,ELF_USDT,ELF_ETH,DRGN_USDT,DRGN_ETH,GTC_USDT,GTC_ETH,GTC_BTC,QLC_USDT,QLC_BTC,QLC_ETH,DBC_USDT,DBC_BTC,DBC_ETH,BNTY_USDT,BNTY_ETH,LEND_USDT,LEND_ETH,ICX_USDT,ICX_ETH,BTF_USDT,BTF_BTC,ADA_USDT,ADA_BTC,LSK_USDT,LSK_BTC,WAVES_USDT,WAVES_BTC,BIFI_USDT,BIFI_BTC,MDS_ETH,MDS_USDT,DGD_USDT,DGD_ETH,QASH_USDT,QASH_ETH,QASH_BTC,POWR_USDT,POWR_ETH,POWR_BTC,FIL_USDT,BCD_USDT,BCD_BTC,SBTC_USDT,SBTC_BTC,GOD_USDT,GOD_BTC,BCX_USDT,BCX_BTC,QSP_USDT,QSP_ETH,INK_BTC,INK_USDT,INK_ETH,INK_QTUM,MED_QTUM,MED_ETH,MED_USDT,QBT_QTUM,QBT_ETH,QBT_USDT,TSL_QTUM,TSL_USDT,GNX_USDT,GNX_ETH,NEO_USDT,GAS_USDT,NEO_BTC,GAS_BTC,IOTA_USDT,IOTA_BTC,NAS_USDT,NAS_ETH,NAS_BTC,ETH_BTC,ETC_BTC,ETC_ETH,ZEC_BTC,DASH_BTC,LTC_BTC,BCH_BTC,BTG_BTC,QTUM_BTC,QTUM_ETH,XRP_BTC,DOGE_BTC,XMR_BTC,ZRX_BTC,ZRX_ETH,DNT_ETH,DPY_ETH,OAX_BTC,OAX_USDT,OAX_ETH,REP_ETH,LRC_ETH,LRC_BTC,PST_ETH,BCDN_ETH,BCDN_USDT,TNT_ETH,SNT_ETH,SNT_BTC,BTM_ETH,BTM_BTC,SNET_ETH,SNET_USDT,LLT_SNET,OMG_ETH,OMG_BTC,PAY_ETH,PAY_BTC,BAT_ETH,BAT_BTC,CVC_ETH,STORJ_ETH,STORJ_BTC,EOS_ETH,EOS_BTC,BTS_USDT,BTS_BTC,TIPS_ETH,GT_BTC,GT_USDT,ATOM_BTC,ATOM_USDT,XEM_ETH,XEM_USDT,XEM_BTC,BU_USDT,BU_ETH,BU_BTC,BCHSV_USDT,BCHSV_CNYX,BCHSV_BTC,DCR_USDT,DCR_BTC,BCN_USDT,BCN_BTC,XMC_USDT,XMC_BTC,ATP_USDT,ATP_ETH,NBOT_ETH,NBOT_USDT,MEDX_USDT,MEDX_ETH,GRIN_USDT,GRIN_ETH,GRIN_BTC,BEAM_USDT,BEAM_ETH,BEAM_BTC,VTHO_ETH,BTT_USDT,BTT_ETH,BTT_TRX,TFUEL_ETH,TFUEL_USDT,CELR_ETH,CELR_USDT,CS_ETH,CS_USDT,MAN_ETH,MAN_USDT,REM_ETH,REM_USDT,LYM_ETH,LYM_BTC,LYM_USDT,ONG_ETH,ONG_USDT,ONT_ETH,ONT_USDT,BFT_ETH,BFT_USDT,IHT_ETH,IHT_USDT,SENC_ETH,SENC_USDT,TOMO_ETH,TOMO_USDT,ELEC_ETH,ELEC_USDT,HAV_ETH,HAV_USDT,SWTH_ETH,SWTH_USDT,NKN_ETH,NKN_USDT,SOUL_ETH,SOUL_USDT,LRN_ETH,LRN_USDT,EOSDAC_ETH,EOSDAC_USDT,DOCK_USDT,DOCK_ETH,GSE_USDT,GSE_ETH,RATING_USDT,RATING_ETH,HSC_USDT,HSC_ETH,HIT_USDT,HIT_ETH,DX_USDT,DX_ETH,CNNS_ETH,CNNS_USDT,DREP_ETH,DREP_USDT,MBL_USDT,MBL_ETH,GMAT_USDT,GMAT_ETH,MIX_USDT,MIX_ETH,LAMB_USDT,LAMB_ETH,LEO_USDT,LEO_BTC,WICC_USDT,WICC_ETH,SERO_USDT,SERO_ETH,VIDY_USDT,VIDY_ETH,KGC_USDT,FTM_USDT,FTM_ETH,ONE_USDT,ARPA_USDT,ARPA_ETH,ALGO_USDT,BKC_USDT,BXC_USDT,BXC_ETH,PAX_USDT,PAX_CNYX,USDC_CNYX,USDC_USDT,TUSD_CNYX,TUSD_USDT,HC_USDT,HC_BTC,HC_ETH,GARD_USDT,GARD_ETH,FTI_USDT,FTI_ETH,SOP_ETH,SOP_USDT,LEMO_USDT,LEMO_ETH,QKC_USDT,QKC_ETH,IOTX_USDT,IOTX_ETH,RED_USDT,RED_ETH,LBA_USDT,LBA_ETH,OPEN_USDT,OPEN_ETH,MITH_USDT,MITH_ETH,SKM_USDT,SKM_ETH,XVG_USDT,XVG_BTC,NANO_USDT,NANO_BTC,HT_USDT,BNB_USDT,MET_ETH,MET_USDT,TCT_ETH,TCT_USDT,MXC_USDT,MXC_BTC,MXC_ETH", + "enabledPairs": "BTC_USDT", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Gemini", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC", + "enabledPairs": "BTCUSD", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "HitBTC", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,AMP-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,PIX-BTC,PIX-ETH,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-BTC,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,DRPU-BTC,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,DRPU-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAX-BTC,WAX-ETH,WAX-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,NOAH-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,NOAH-ETH,NOAH-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,SUNC-ETH,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,FREC-BTC,NAVI-BTC,FREC-ETH,FREC-USD,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,HERO-BTC,HERO-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,ZPT-BTC,ZPT-ETH,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,ZPT-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,JOT-BTC,JBC-BTC,JBC-ETH,BTS-BTC,BNK-BTC,KBC-BTC,KBC-ETH,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,CLN-BTC,CLN-ETH,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,COSM-BTC,COSM-ETH,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,XCLR-BTC,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,CCL-USD,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,MESSE-BTC,MESSE-ETH,MESSE-USD,CCL-ETH,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,MESSE-EOS,MESSE-EURS,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,MRS-BTC,MRS-ETH,MRS-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD", + "enabledPairs": "BTC-USD", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Huobi", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiAuthPemKey": "-----BEGIN EC PRIVATE KEY-----\nJUSTADUMMY\n-----END EC PRIVATE KEY-----\n", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "HT-USDT,BAT-ETH,AST-ETH,TRX-BTC,NEW-BTC,AE-BTC,IIC-BTC,NEW-USDT,CDC-BTC,AE-USDT,DGB-BTC,NAS-ETH,QSP-BTC,LYM-ETH,YCC-BTC,BCH-HT,BIX-ETH,WXT-BTC,XRP-BTC,IOST-BTC,CHAT-BTC,BTC-USDT,XTZ-BTC,PVT-BTC,PVT-USDT,WAVES-ETH,ACT-BTC,RSR-BTC,ACT-USDT,WXT-USDT,XLM-ETH,HT-BTC,UUU-USDT,XRP-USDT,UGAS-BTC,BTS-ETH,IRIS-ETH,LUN-BTC,IOST-HT,DOCK-BTC,ABT-ETH,CRO-BTC,MAN-ETH,ENG-ETH,QUN-BTC,APPC-BTC,KAN-ETH,VET-USDT,SOC-ETH,RSR-HT,RUFF-ETH,RCCC-ETH,AAC-ETH,MCO-BTC,RSR-USDT,TNB-ETH,UTK-ETH,ADX-BTC,WAX-ETH,IOST-USDT,HOT-ETH,WTC-USDT,CVCOIN-BTC,NCASH-ETH,ATP-BTC,SWFTC-ETH,GTC-BTC,PNT-BTC,GT-HT,NEO-BTC,OMG-BTC,EOS-HUSD,WPR-ETH,ARPA-BTC,BTM-BTC,BTM-USDT,KCASH-ETH,SSP-ETH,ARPA-USDT,CNN-BTC,NKN-BTC,NPXS-BTC,OMG-USDT,TOPC-ETH,XEM-BTC,BCH-USDT,SNC-BTC,POLY-ETH,CMT-ETH,PAI-USDT,ZEC-USDT,LSK-ETH,SMT-ETH,DASH-USDT,GAS-ETH,DASH-BTC,GXC-ETH,FTT-HT,IOTA-ETH,FTI-BTC,TRIO-ETH,LET-BTC,ZRX-ETH,ETN-ETH,EVX-ETH,BFT-ETH,GRS-BTC,XRP-HT,DASH-HT,QTUM-ETH,HIT-ETH,NEXO-BTC,QASH-BTC,EOS-ETH,ARDR-ETH,ADA-BTC,NEO-USDT,BTT-TRX,COVA-ETH,REN-BTC,LOOM-BTC,CVC-ETH,NANO-ETH,ARPA-HT,NEW-HT,BLZ-ETH,LINK-ETH,XTZ-USDT,PAY-BTC,GNT-USDT,YEE-ETH,XZC-ETH,EGCC-ETH,PROPY-ETH,ZEC-BTC,EDU-ETH,RTE-BTC,DCR-USDT,FTT-BTC,DCR-BTC,EKO-BTC,SBTC-BTC,ZLA-ETH,TOP-HT,ALGO-BTC,DTA-ETH,EKT-ETH,ATOM-USDT,LXT-USDT,ZEN-ETH,LOL-USDT,LTC-USDT,DAT-BTC,REQ-ETH,ELA-ETH,NKN-HT,PC-BTC,HIT-BTC,EKO-ETH,STK-ETH,LAMB-USDT,LAMB-HT,DOGE-ETH,ATOM-BTC,THETA-USDT,LOL-BTC,THETA-BTC,LSK-BTC,ADA-USDT,RDN-BTC,OGO-HT,UIP-USDT,WICC-BTC,OCN-BTC,ELF-BTC,AKRO-USDT,USDC-HUSD,LAMB-BTC,DBC-ETH,BTT-ETH,FAIR-BTC,POWR-ETH,MUSK-ETH,MT-BTC,STEEM-USDT,RBTC-BTC,CTXC-BTC,MANA-USDT,ICX-ETH,GET-BTC,LTC-BTC,ITC-ETH,BCV-BTC,ZJLT-BTC,AKRO-HT,TNT-ETH,TOP-BTC,MEX-BTC,DATX-BTC,ALGO-USDT,LXT-BTC,GT-USDT,FSN-HT,FSN-USDT,MTX-ETH,LET-ETH,OGO-USDT,PHX-BTC,KCASH-HT,HC-USDT,LOL-HT,NKN-USDT,HOT-BTC,LBA-BTC,XMX-BTC,OST-ETH,VEN-USDT,LTC-HT,LBA-USDT,VEN-BTC,CRE-HT,BIFI-BTC,BT1-BTC,HPT-BTC,NULS-BTC,WAN-BTC,ZIL-BTC,ETC-HT,TOS-BTC,MANA-BTC,SHE-BTC,GT-BTC,FSN-BTC,MCO-ETH,MTN-BTC,MDS-BTC,SRN-ETH,GVE-BTC,XMR-ETH,MEET-ETH,NULS-USDT,BCH-BTC,PAI-BTC,NCC-ETH,BSV-BTC,AKRO-BTC,ELF-USDT,DGD-ETH,PVT-HT,UIP-BTC,ATP-USDT,SEELE-ETH,GSC-BTC,ETC-USDT,SOC-BTC,GNX-BTC,WICC-USDT,QSP-ETH,RUFF-BTC,KNC-ETH,ATP-HT,CTXC-USDT,KMD-ETH,OGO-BTC,BKBT-BTC,DGB-ETH,WAVES-USDT,BCD-BTC,HPT-HT,ZIL-USDT,BUT-ETH,CVNT-BTC,OCN-USDT,SALT-ETH,XLM-BTC,TRX-USDT,RCN-BTC,DAC-ETH,MT-HT,ETH-HUSD,HPT-USDT,XTZ-ETH,USDT-HUSD,CHAT-ETH,ONT-USDT,SKM-USDT,MAN-BTC,ARDR-BTC,BCX-BTC,SKM-BTC,EOS-USDT,GNX-ETH,CRE-USDT,PORTAL-ETH,COVA-BTC,BIX-BTC,UUU-ETH,AAC-BTC,TRX-ETH,NEXO-ETH,NAS-BTC,ENG-BTC,AST-BTC,TT-HT,QUN-ETH,EOS-BTC,18C-ETH,WTC-ETH,CVCOIN-ETH,CRE-BTC,CNNS-USDT,WAX-BTC,AIDOC-BTC,VET-ETH,CMT-USDT,BSV-USDT,IDT-ETH,IOST-ETH,BTC-HUSD,IOTA-BTC,TNB-BTC,LINK-BTC,TOPC-BTC,RCCC-BTC,ZRX-USDT,CNNS-BTC,BOX-BTC,MDS-USDT,XLM-USDT,BAT-BTC,LYM-BTC,UC-ETH,RUFF-USDT,LUN-ETH,BIX-USDT,CDC-ETH,BTS-USDT,YCC-ETH,KAN-USDT,MTL-BTC,WAVES-BTC,ONT-BTC,HT-HUSD,IRIS-USDT,SOC-USDT,WPR-BTC,ETC-BTC,TUSD-HUSD,CVC-USDT,PROPY-BTC,TRIO-BTC,CVC-BTC,BTT-USDT,NANO-BTC,GXC-BTC,NCASH-BTC,XRP-HUSD,TT-USDT,SHE-ETH,NANO-USDT,LOOM-ETH,POWR-BTC,QTUM-BTC,SSP-BTC,BTM-ETH,QTUM-USDT,XZC-BTC,GNT-ETH,OMG-ETH,NPXS-ETH,SNT-USDT,ETH-USDT,ABT-BTC,BTS-BTC,STEEM-BTC,VSYS-USDT,BLZ-BTC,CNNS-HT,ADX-ETH,SMT-USDT,IOTA-USDT,PAY-ETH,CMT-BTC,UTK-BTC,SWFTC-BTC,GTC-ETH,LINK-USDT,SNC-ETH,SNT-BTC,EOS-HT,REN-ETH,PAX-HUSD,KCASH-BTC,HC-BTC,IIC-ETH,QASH-ETH,GRS-ETH,EDU-BTC,HIT-USDT,TOP-USDT,XZC-USDT,KAN-BTC,SC-BTC,SKM-HT,AE-ETH,STORJ-USDT,XVG-ETH,ZRX-BTC,EVX-BTC,ETN-BTC,BFT-BTC,FTI-ETH,DAT-ETH,UGAS-ETH,BAT-USDT,GXC-USDT,GAS-BTC,TNT-BTC,HB10-USDT,MUSK-BTC,FTT-USDT,STK-BTC,ELF-ETH,KNC-BTC,CTXC-ETH,DBC-BTC,HC-ETH,EKT-BTC,DTA-USDT,ZLA-BTC,EKT-USDT,DTA-BTC,OCN-ETH,DGD-BTC,BHT-USDT,MTX-BTC,BCV-ETH,YEE-BTC,VSYS-HT,MEX-ETH,DATX-ETH,EGCC-BTC,LXT-ETH,ITC-USDT,TOS-ETH,ITC-BTC,RCN-ETH,XVG-BTC,SC-ETH,BT2-BTC,REQ-BTC,ELA-USDT,LET-USDT,STORJ-BTC,ALGO-ETH,POLY-BTC,LAMB-ETH,DCR-ETH,EGT-BTC,RTE-ETH,FAIR-ETH,CNN-ETH,BHT-BTC,GSC-ETH,GNT-BTC,PAI-ETH,PC-ETH,ADA-ETH,DOGE-BTC,ZEN-BTC,STEEM-ETH,XMR-BTC,XMR-USDT,MDS-ETH,TT-BTC,BTT-BTC,BHT-HT,ZJLT-ETH,UC-BTC,GVE-ETH,MXC-BTC,MANA-ETH,VSYS-BTC,THETA-ETH,NCC-BTC,APPC-ETH,SMT-BTC,IDT-BTC,UIP-ETH,ETH-BTC,BOX-ETH,LBA-ETH,NULS-ETH,PNT-ETH,BTG-BTC,CVNT-ETH,SALT-BTC,XEM-USDT,WXT-HT,BUT-BTC,DAC-BTC,DOCK-ETH,GET-ETH,AIDOC-ETH,EGT-USDT,WAN-ETH,KMD-BTC,MTN-ETH,CRO-USDT,ONT-ETH,BKBT-ETH,MEET-BTC,VEN-ETH,MT-ETH,SRN-BTC,UUU-BTC,SEELE-BTC,ICX-BTC,RDN-ETH,EGT-HT,ZIL-ETH,IRIS-BTC,CRO-HT,ACT-ETH,DOGE-USDT,NAS-USDT,PORTAL-BTC,ELA-BTC,OST-BTC,WICC-ETH,VET-BTC,XMX-ETH,WTC-BTC,HT-ETH,ATOM-ETH,18C-BTC", + "enabledPairs": "BTC-USDT", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "requestCurrencyPairFormat": { + "uppercase": false + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "ITBIT", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "clientId": "ClientID", + "availablePairs": "XBTUSD,XBTSGD", + "enabledPairs": "XBTUSD,XBTSGD", + "baseCurrencies": "USD,SGD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": false, + "pairsLastUpdated": 1566798411, + "configCurrencyPairFormat": { + "uppercase": true + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Kraken", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "ATOM-ETH,QTUM-EUR,QTUM-USD,LTC-XBT,XTZ-ETH,XMR-XBT,ADA-EUR,BAT-ETH,BAT-EUR,BAT-XBT,QTUM-CAD,WAVES-USD,ETC-EUR,MLN-ETH,XLM-USD,XRP-CAD,ADA-USD,DASH-XBT,REP-XBT,XBT-CAD,XBT-EUR,XBT-GBP,USDT-USD,ETH-JPY,XBT-USD,ZEC-USD,ETH-EUR,ETH-USD,XTZ-XBT,ZEC-EUR,ZEC-JPY,ADA-ETH,EOS-ETH,QTUM-ETH,ETH-CAD,XTZ-EUR,EOS-EUR,REP-USD,XMR-USD,BCH-XBT,EOS-XBT,ETC-ETH,XLM-XBT,ADA-CAD,ADA-XBT,ATOM-EUR,ATOM-XBT,DASH-EUR,GNO-USD,GNO-XBT,WAVES-XBT,ETH-GBP,XBT-JPY,ZEC-XBT,QTUM-XBT,WAVES-ETH,XDG-XBT,XRP-XBT,EOS-USD,XMR-EUR,XRP-EUR,ATOM-CAD,DASH-USD,ETC-USD,ETH-XBT,GNO-EUR,ETC-XBT,LTC-EUR,REP-ETH,XTZ-USD,XLM-EUR,GNO-ETH,LTC-USD,REP-EUR,XRP-JPY,XRP-USD,ATOM-USD,BCH-USD,WAVES-EUR,BAT-USD,BCH-EUR,MLN-XBT,XTZ-CAD", + "enabledPairs": "XBT-USD", + "baseCurrencies": "EUR,USD,CAD,GBP,JPY", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "requestCurrencyPairFormat": { + "uppercase": true, + "separator": "," + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "LakeBTC", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BACETH,BTCUSD,USDSGD,USDJPY,LTCBTC,BTCCHF,BTCNZD,BTCJPY,USDNGN,BCHBTC,BTCAUD,NZDUSD,EURUSD,USDHKD,BTCEUR,USDCHF,GBPUSD,XRPBTC,AUDUSD,BTCHKD,BTCGBP,BTCCAD,BTCNGN,BTCSGD,USDCAD,ETHBTC", + "enabledPairs": "BTCUSD,BTCAUD", + "baseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "LBank", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "FBC_USDT,HDS_USDT,GALT_USDT,IOG_USDT,IOEX_USDT,VOLLAR_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,BZKY_ETH,ONOT_ETH,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,BSV_USDT,OPX_USDT,TENA_ETH,VTHO_BTC,VNX_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,ALI_ETH,SDC_ETH,SAIT_ETH,ARTCN_USDT,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,BFDT_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,IIC_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,TKY_ETH,OCN_ETH,DCT_ETH,ZPT_ETH,EKO_ETH,MDA_ETH,PST_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,UIP_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,INC_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,B91_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,CXC_BTC,CXC_USDT,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,VCC_ETH,DLX_USDT,KDS_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT", + "enabledPairs": "btc_usdt", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "LocalBitcoins", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BTCXAF,BTCHKD,BTCBRL,BTCPLN,BTCGHS,BTCPEN,BTCSAR,BTCCAD,BTCJOD,BTCVES,BTCXOF,BTCRWF,BTCEUR,BTCNOK,BTCLTC,BTCZMW,BTCXRP,BTCPAB,BTCUSD,BTCCRC,BTCTTD,BTCLBP,BTCOMR,BTCRON,BTCGEL,BTCKRW,BTCCLP,BTCSZL,BTCNGN,BTCILS,BTCDKK,BTCMYR,BTCRUB,BTCKES,BTCINR,BTCJPY,BTCKHR,BTCCOP,BTCIRR,BTCARS,BTCKZT,BTCTZS,BTCVND,BTCEGP,BTCGBP,BTCTHB,BTCAED,BTCGTQ,BTCCHF,BTCIDR,BTCAUD,BTCNZD,BTCKWD,BTCBOB,BTCUGX,BTCETH,BTCUAH,BTCSGD,BTCCNY,BTCPHP,BTCTWD,BTCLKR,BTCNAD,BTCMXN,BTCBYN,BTCBDT,BTCDOP,BTCTRY,BTCPYG,BTCPKR,BTCQAR,BTCSEK,BTCMAD,BTCZAR", + "enabledPairs": "BTCAUD,BTCUSD", + "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", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true + }, + "requestCurrencyPairFormat": { + "uppercase": true + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "OKCOIN International", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BTC_USD,LTC_USD,ETH_USD,ETC_USD,TUSD_USD,BCH_USD,EOS_USD,XRP_USD,TRX_USD,BSV_USD,USDT_USD,USDK_USD,XLM_USD,ADA_USD,BAT_USD,DCR_USD,EURS_USD,GRIN_USD,GUSD_USD,PAX_USD,USDC_USD,ZEC_USD,ZRX_USD,BTC_USDT,BTC_GUSD,BTC_PAX,BTC_TUSD,BTC_EUR,BTC_EURS,BTC_USDC,ETH_EUR,BCH_EUR,EURS_EUR", + "enabledPairs": "BTC_USD", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "OKEX", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BCH_BTC,BSV_BTC,DASH_BTC,ADA_BTC,ABL_BTC,AE_BTC,ALGO_BTC,ARDR_BTC,ATOM_BTC,BLOC_BTC,BTT_BTC,CAI_BTC,CTXC_BTC,CVT_BTC,DCR_BTC,EGT_BTC,GUSD_BTC,HPB_BTC,HYC_BTC,KAN_BTC,LBA_BTC,LEO_BTC,LET_BTC,LSK_BTC,NXT_BTC,ORS_BTC,PAX_BTC,SC_BTC,TUSD_BTC,USDC_BTC,VITE_BTC,WAVES_BTC,WIN_BTC,WXT_BTC,XAS_BTC,YOU_BTC,ZCO_BTC,ZIL_BTC,XRP_BTC,ELF_BTC,LRC_BTC,MCO_BTC,NULS_BTC,BCX_BTC,CMT_BTC,EDO_BTC,ITC_BTC,SBTC_BTC,ZEC_BTC,NEO_BTC,GAS_BTC,HC_BTC,QTUM_BTC,IOTA_BTC,XUC_BTC,EOS_BTC,SNT_BTC,OMG_BTC,LTC_BTC,ETH_BTC,ETC_BTC,BCD_BTC,BTG_BTC,ACT_BTC,PAY_BTC,BTM_BTC,DGD_BTC,GNT_BTC,LINK_BTC,WTC_BTC,ZRX_BTC,BNT_BTC,CVC_BTC,MANA_BTC,KNC_BTC,GNX_BTC,ICX_BTC,XEM_BTC,ARK_BTC,YOYO_BTC,FUN_BTC,ACE_BTC,TRX_BTC,DGB_BTC,SWFTC_BTC,XMR_BTC,XLM_BTC,KCASH_BTC,MDT_BTC,NAS_BTC,UGC_BTC,DPY_BTC,SSC_BTC,AAC_BTC,VIB_BTC,QUN_BTC,INT_BTC,IOST_BTC,INS_BTC,MOF_BTC,TCT_BTC,STC_BTC,THETA_BTC,PST_BTC,SNC_BTC,MKR_BTC,LIGHT_BTC,OF_BTC,TRUE_BTC,SOC_BTC,ZEN_BTC,HMC_BTC,ZIP_BTC,NANO_BTC,CIC_BTC,GTO_BTC,CHAT_BTC,INSUR_BTC,R_BTC,BEC_BTC,MITH_BTC,ABT_BTC,BKX_BTC,RFR_BTC,TRIO_BTC,DADI_BTC,ONT_BTC,OKB_BTC,ADA_ETH,ABL_ETH,AE_ETH,ALGO_ETH,ATOM_ETH,BTT_ETH,CAI_ETH,CTXC_ETH,DCR_ETH,EGT_ETH,HPB_ETH,HYC_ETH,KAN_ETH,LEO_ETH,LSK_ETH,MVP_ETH,ORS_ETH,SC_ETH,SDA_ETH,WAVES_ETH,WIN_ETH,YOU_ETH,ZIL_ETH,ELF_ETH,LTC_ETH,CMT_ETH,PRA_ETH,LRC_ETH,MCO_ETH,NULS_ETH,DGD_ETH,SNT_ETH,STORJ_ETH,ACT_ETH,BTM_ETH,EOS_ETH,OMG_ETH,DASH_ETH,XRP_ETH,ZEC_ETH,NEO_ETH,GAS_ETH,HC_ETH,QTUM_ETH,IOTA_ETH,ETC_ETH,LINK_ETH,WTC_ETH,ZRX_ETH,BNT_ETH,CVC_ETH,MANA_ETH,GNX_ETH,ICX_ETH,XEM_ETH,YOYO_ETH,TRX_ETH,DGB_ETH,SWFTC_ETH,XMR_ETH,XLM_ETH,KCASH_ETH,MDT_ETH,NAS_ETH,SSC_ETH,AAC_ETH,FAIR_ETH,RCT_ETH,TOPC_ETH,QUN_ETH,INT_ETH,IOST_ETH,INS_ETH,MOF_ETH,REF_ETH,SNC_ETH,MKR_ETH,LIGHT_ETH,OF_ETH,TRUE_ETH,ZEN_ETH,HMC_ETH,ZIP_ETH,NANO_ETH,CIC_ETH,GTO_ETH,INSUR_ETH,UCT_ETH,MITH_ETH,ABT_ETH,AUTO_ETH,TRIO_ETH,TRA_ETH,ONT_ETH,OKB_ETH,BTC_USDK,LTC_USDK,ETH_USDK,OKB_USDK,ETC_USDK,BCH_USDT,BCH_USDK,EOS_USDK,XRP_USDK,TRX_USDK,BSV_USDT,BSV_USDK,USDT_USDK,ADA_USDT,AE_USDT,ALGO_USDT,ALGO_USDK,ALV_USDT,ATOM_USDT,BLOC_USDT,BTT_USDT,CAI_USDT,CRO_USDT,CRO_USDK,CTXC_USDT,CVT_USDT,DCR_USDT,DOGE_USDT,DOGE_USDK,EC_USDT,EC_USDK,EGT_USDT,EM_USDT,EM_USDK,ETM_USDT,ETM_USDK,FSN_USDT,FSN_USDK,FTM_USDT,FTM_USDK,GUSD_USDT,HPB_USDT,HYC_USDT,KAN_USDT,LAMB_USDT,LAMB_USDK,LBA_USDT,LEO_USDT,LEO_USDK,LET_USDT,LSK_USDT,MVP_USDT,ORBS_USDT,ORBS_USDK,ORS_USDT,PAX_USDT,PLG_USDT,PLG_USDK,SC_USDT,TUSD_USDT,USDC_USDT,VNT_USDT,VNT_USDK,WAVES_USDT,WIN_USDT,WXT_USDT,WXT_USDK,XAS_USDT,YOU_USDT,ZIL_USDT,TRX_OKB,ADA_OKB,AE_OKB,BLOC_OKB,DCR_OKB,EGT_OKB,SC_OKB,WAVES_OKB,WXT_OKB,ELF_USDT,DASH_USDT,BTG_USDT,LRC_USDT,MCO_USDT,NULS_USDT,DASH_OKB,XRP_USDT,ZEC_USDT,NEO_USDT,GAS_USDT,HC_USDT,QTUM_USDT,IOTA_USDT,BTC_USDT,BCD_USDT,XUC_USDT,CMT_USDT,EDO_USDT,ITC_USDT,PRA_USDT,ETH_USDT,LTC_USDT,ETC_USDT,EOS_USDT,OMG_USDT,ACT_USDT,BTM_USDT,DGD_USDT,GNT_USDT,PAY_USDT,STORJ_USDT,SNT_USDT,LINK_USDT,WTC_USDT,ZRX_USDT,BNT_USDT,CVC_USDT,MANA_USDT,KNC_USDT,ICX_USDT,XEM_USDT,ARK_USDT,YOYO_USDT,AST_USDT,TRX_USDT,MDA_USDT,DGB_USDT,PPT_USDT,SWFTC_USDT,XMR_USDT,XLM_USDT,KCASH_USDT,MDT_USDT,NAS_USDT,RNT_USDT,UGC_USDT,DPY_USDT,SSC_USDT,AAC_USDT,FAIR_USDT,UBTC_USDT,SHOW_USDT,VIB_USDT,MOT_USDT,UTK_USDT,TOPC_USDT,QUN_USDT,INT_USDT,IPC_USDT,IOST_USDT,INS_USDT,YEE_USDT,MOF_USDT,TCT_USDT,STC_USDT,THETA_USDT,PST_USDT,MKR_USDT,LIGHT_USDT,OF_USDT,TRUE_USDT,SOC_USDT,ZEN_USDT,HMC_USDT,ZIP_USDT,NANO_USDT,CIC_USDT,GTO_USDT,CHAT_USDT,INSUR_USDT,R_USDT,BEC_USDT,MITH_USDT,ABT_USDT,BKX_USDT,RFR_USDT,TRIO_USDT,DADI_USDT,ONT_USDT,OKB_USDT,NEO_OKB,LTC_OKB,ETC_OKB,XRP_OKB,ZEC_OKB,QTUM_OKB,IOTA_OKB,EOS_OKB", + "enabledPairs": "eos_usdt", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Poloniex", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "BTC_DASH,BTC_VIA,USDC_ZEC,USDT_BCHSV,BTC_XEM,USDT_STR,ETH_REP,BTC_MANA,USDC_STR,USDC_ETH,USDT_BTC,BTC_REP,BTC_PASC,BTC_GNT,ETH_ZRX,BTC_SNT,ETH_BAT,USDC_DOGE,BTC_POLY,BTC_ATOM,BTC_BAT,BTC_DGB,BTC_NXT,BTC_STR,BTC_STRAT,BTC_EOS,ETH_EOS,BTC_KNC,USDT_SC,BTC_BCHSV,BTC_XMR,BTC_STEEM,BTC_ZEC,BTC_GAS,USDT_BCHABC,USDC_ATOM,BTC_XRP,USDT_DASH,USDT_ETH,USDT_DOGE,BTC_QTUM,USDC_BTC,BTC_LPT,USDT_DGB,BTC_DOGE,USDT_BAT,USDC_BCHSV,BTC_BTS,BTC_GAME,BTC_SC,BTC_OMG,USDT_MANA,BTC_GRIN,BTC_LTC,BTC_ETH,USDT_EOS,USDT_LSK,USDC_BCHABC,USDC_XMR,BTC_NMR,USDT_REP,BTC_ZRX,USDT_GNT,USDT_QTUM,BTC_BNT,USDC_EOS,BTC_BCN,USDT_ATOM,USDC_DASH,BTC_OMNI,BTC_FCT,BTC_LSK,USDC_XRP,BTC_FOAM,BTC_CVC,BTC_NAV,USDT_LTC,USDT_NXT,USDT_XMR,USDT_XRP,BTC_LBC,USDT_ETC,BTC_LOOM,USDT_GRIN,BTC_DCR,BTC_ETC,ETH_ETC,BTC_ARDR,USDT_ZEC,BTC_BCHABC,USDC_GRIN,BTC_STORJ,USDT_ZRX,USDC_USDT,USDC_ETC,BTC_CLAM,BTC_MAID,BTC_VTC,BTC_XPM,ETH_ZEC,USDC_LTC", + "enabledPairs": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Yobit", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR", + "enabledPairs": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", + "baseCurrencies": "USD,RUR", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": false, + "pairsLastUpdated": 1566798411, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_", + "separator": "-" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "ZB", + "enabled": true, + "verbose": false, + "websocket": false, + "useSandbox": false, + "restPollingDelay": 10, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketOrderbookBufferLimit": 5, + "httpUserAgent": "", + "httpDebugging": false, + "authenticatedApiSupport": false, + "authenticatedWebsocketApiSupport": false, + "apiKey": "Key", + "apiSecret": "Secret", + "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "proxyAddress": "", + "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API", + "availablePairs": "DASH_USDT,XLM_QC,DOGE_QC,SBTC_USDT,SNT_USDT,BRC_BTC,BCHSV_QC,TUSD_USDT,ZB_BTC,GRIN_USDT,BAT_USDT,HPY_USDT,ADA_BTC,XTZ_USDT,XWC_USDT,YTNB_USDT,QTUM_USDT,EDO_USDT,BTC_QC,ETC_PAX,TV_BTC,HSR_BTC,XWC_QC,TRX_USDT,VSYS_ZB,LTC_PAX,OMG_QC,ETH_BTC,NEO_BTC,HPY_QC,TOPC_USDT,ICX_USDT,BCX_USDT,GNT_QC,B91_QC,EOS_QC,PAX_QC,BTC_PAX,XRP_QC,LTC_USDT,MANA_BTC,BITE_BTC,EOS_BTC,XUC_QC,HOTC_QC,BAR_USDT,ETZ_QC,XRP_USDT,HOTC_USDT,DOGE_BTC,ZRX_BTC,TRUE_USDT,GRAM_USDT,BTH_QC,HLC_QC,SLT_QC,BCD_USDT,ETC_USDT,GNT_BTC,BTP_QC,ZRX_USDT,BCW_QC,PDX_QC,QTUM_BTC,LTC_QC,BRC_USDT,EPC_QC,GRAM_QC,CHAT_USDT,KNC_QC,DASH_BTC,XMR_QC,XEM_QC,BTP_USDT,HSR_QC,BCD_QC,EOSDAC_USDT,MTL_USDT,ENTC_USDT,KNC_USDT,MITH_QC,SAFE_USDT,1ST_USDT,TRX_QC,OMG_BTC,BRC_QC,MCO_QC,LBTC_BTC,KAN_BTC,1ST_QC,BTM_QC,INK_USDT,GRIN_QC,UBTC_QC,EPC_BTC,XEM_BTC,TV_USDT,ETC_BTC,XEM_USDT,UBTC_USDT,TRUE_BTC,HSR_USDT,BCHSV_USDT,AE_BTC,BCX_QC,ETH_PAX,ACC_USDT,OMG_USDT,ETZ_USDT,DDM_QC,KAN_QC,INK_QC,DOGE_USDT,BCHABC_QC,BITCNY_QC,TRUE_QC,DASH_QC,QUN_USDT,ZRX_QC,BTM_BTC,BTM_USDT,HLC_USDT,SLT_USDT,BTC_USDT,CDC_QC,AE_QC,LBTC_USDT,MCO_USDT,XLM_BTC,LEO_USDT,BTN_QC,SAFE_QC,XRP_BTC,BTS_BTC,BCX_BTC,DDM_USDT,TRX_BTC,QUN_QC,BTS_USDT,PDX_BTC,ETC_QC,BCHABC_USDT,QTUM_QC,ADA_USDT,EOSDAC_QC,BDS_QC,BTN_USDT,SLT_BTC,PDX_USDT,SUB_QC,USDT_QC,TOPC_QC,XMR_USDT,BAT_QC,SNT_QC,B91_USDT,GNT_USDT,PAX_USDT,AE_USDT,ZB_USDT,NWT_USDT,CDC_USDT,RCN_USDT,NEO_QC,MANA_USDT,TV_QC,VSYS_BTC,ZB_QC,GRAM_BTC,BTH_USDT,AAA_QC,ICX_QC,LTC_BTC,ETH_QC,CHAT_QC,BCW_USDT,SNT_BTC,ADA_QC,VSYS_QC,XLM_USDT,BAT_BTC,ETH_USDT,EOS_USDT,ICX_BTC,LBTC_QC,NEO_USDT,MANA_QC,BTS_QC", + "enabledPairs": "BTC_USDT,ETH_USDT", + "baseCurrencies": "USD", + "assetTypes": "SPOT", + "supportsAutoPairUpdates": true, + "configCurrencyPairFormat": { + "uppercase": true, + "delimiter": "_" + }, + "requestCurrencyPairFormat": { + "uppercase": false, + "delimiter": "_" + }, + "bankAccounts": [ + { + "bankName": "", + "bankAddress": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + } + ], + "bankAccounts": [ + { + "bankName": "test", + "bankAddress": "test", + "accountName": "TestAccount", + "accountNumber": "0234", + "swiftCode": "91272837", + "iban": "98218738671897", + "supportedCurrencies": "USD", + "supportedExchanges": "ANX,Kraken" + } + ], + "connectionMonitor": { + "preferredDNSList": [ + "8.8.8.8", + "8.8.4.4", + "1.1.1.1", + "1.0.0.1" + ], + "preferredDomainList": [ + "www.google.com", + "www.cloudflare.com", + "www.facebook.com" + ], + "checkInterval": 1000000000 + }, + "fiatDispayCurrency": "" + } \ No newline at end of file From 8c30505d462b4c94dde3e5a115cf2dbdf000a1a7 Mon Sep 17 00:00:00 2001 From: lozdog245 <37864968+lozdog245@users.noreply.github.com> Date: Mon, 9 Dec 2019 11:44:01 +1100 Subject: [PATCH 71/71] String concatenation/conversion/formatting improvements (#393) * Magic strings * Comment, format and builder fixes --- common/common.go | 2 +- .../currencylayer/currencylayer.go | 5 ++-- exchanges/anx/anx_wrapper.go | 5 ++-- exchanges/binance/binance.go | 20 ++++++++-------- exchanges/binance/binance_wrapper.go | 7 +++--- exchanges/bitfinex/bitfinex.go | 2 +- exchanges/bitfinex/bitfinex_types.go | 2 +- exchanges/bitfinex/bitfinex_wrapper.go | 20 +++++++--------- exchanges/bitflyer/bitflyer.go | 21 +++++++--------- exchanges/bithumb/bithumb.go | 15 ++++++------ exchanges/bithumb/bithumb_wrapper.go | 4 +++- exchanges/bitmex/bitmex.go | 3 +-- exchanges/bitmex/bitmex_websocket.go | 4 ++-- exchanges/bitstamp/bitstamp.go | 8 +++---- exchanges/bitstamp/bitstamp_websocket.go | 3 +-- exchanges/bitstamp/bitstamp_wrapper.go | 24 +++++++++---------- exchanges/coinbasepro/coinbasepro_wrapper.go | 5 ++-- exchanges/coinut/coinut_websocket.go | 5 +++- exchanges/exchange.go | 10 ++++---- exchanges/exmo/exmo.go | 5 +--- exchanges/gemini/gemini_websocket.go | 2 +- exchanges/hitbtc/hitbtc_wrapper.go | 4 ++-- exchanges/huobi/huobi_wrapper.go | 7 +++--- exchanges/kraken/kraken.go | 1 + exchanges/kraken/kraken_websocket.go | 2 +- exchanges/kraken/kraken_wrapper.go | 7 +++--- exchanges/lakebtc/lakebtc_test.go | 3 +-- exchanges/lakebtc/lakebtc_websocket.go | 7 +++--- exchanges/localbitcoins/localbitcoins.go | 8 +++---- exchanges/okcoin/okcoin_wrapper.go | 5 ++-- exchanges/okgroup/okgroup_websocket.go | 24 +++++++++---------- exchanges/zb/zb_websocket.go | 10 ++++---- 32 files changed, 122 insertions(+), 128 deletions(-) diff --git a/common/common.go b/common/common.go index 83229675..ecccac19 100644 --- a/common/common.go +++ b/common/common.go @@ -282,7 +282,7 @@ func GetURIPath(uri string) string { return "" } if urip.RawQuery != "" { - return fmt.Sprintf("%s?%s", urip.Path, urip.RawQuery) + return urip.Path + "?" + urip.RawQuery } return urip.Path } diff --git a/currency/forexprovider/currencylayer/currencylayer.go b/currency/forexprovider/currencylayer/currencylayer.go index 59c85a31..b0a36fc9 100644 --- a/currency/forexprovider/currencylayer/currencylayer.go +++ b/currency/forexprovider/currencylayer/currencylayer.go @@ -13,7 +13,6 @@ package currencylayer import ( "errors" - "fmt" "net/http" "net/url" "strconv" @@ -200,10 +199,10 @@ func (c *CurrencyLayer) SendHTTPRequest(endPoint string, values url.Values, resu var auth bool if c.APIKeyLvl == AccountFree { - path = fmt.Sprintf("%s%s%s", APIEndpointURL, endPoint, "?") + path = APIEndpointURL + endPoint + "?" } else { auth = true - path = fmt.Sprintf("%s%s%s", APIEndpointURLSSL, endPoint, "?") + path = APIEndpointURLSSL + endPoint + "?" } path += values.Encode() diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 2a393832..5c1184bf 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -1,7 +1,6 @@ package anx import ( - "fmt" "strconv" "strings" "sync" @@ -191,7 +190,9 @@ func (a *ANX) FetchTradablePairs(asset asset.Item) ([]string, error) { var currencies []string for x := range result.CurrencyPairs { - currencies = append(currencies, fmt.Sprintf("%v%v%v", result.CurrencyPairs[x].TradedCcy, a.GetPairFormat(asset, false).Delimiter, result.CurrencyPairs[x].SettlementCcy)) + currencies = append(currencies, result.CurrencyPairs[x].TradedCcy+ + a.GetPairFormat(asset, false).Delimiter+ + result.CurrencyPairs[x].SettlementCcy) } return currencies, nil diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 17e609f1..fb0f8a16 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -279,7 +279,7 @@ func (b *Binance) GetPriceChangeStats(symbol string) (PriceChangeStats, error) { // GetTickers returns the ticker data for the last 24 hrs func (b *Binance) GetTickers() ([]PriceChangeStats, error) { var resp []PriceChangeStats - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, priceChange) + path := b.API.Endpoints.URL + priceChange return resp, b.SendHTTPRequest(path, &resp) } @@ -313,7 +313,7 @@ func (b *Binance) GetBestPrice(symbol string) (BestPrice, error) { func (b *Binance) NewOrder(o *NewOrderRequest) (NewOrderResponse, error) { var resp NewOrderResponse - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, newOrder) + path := b.API.Endpoints.URL + newOrder params := url.Values{} params.Set("symbol", o.Symbol) @@ -357,7 +357,7 @@ func (b *Binance) NewOrder(o *NewOrderRequest) (NewOrderResponse, error) { func (b *Binance) CancelExistingOrder(symbol string, orderID int64, origClientOrderID string) (CancelOrderResponse, error) { var resp CancelOrderResponse - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, cancelOrder) + path := b.API.Endpoints.URL + cancelOrder params := url.Values{} params.Set("symbol", symbol) @@ -379,7 +379,7 @@ func (b *Binance) CancelExistingOrder(symbol string, orderID int64, origClientOr func (b *Binance) OpenOrders(symbol string) ([]QueryOrderData, error) { var resp []QueryOrderData - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, openOrders) + path := b.API.Endpoints.URL + openOrders params := url.Values{} @@ -400,7 +400,7 @@ func (b *Binance) OpenOrders(symbol string) ([]QueryOrderData, error) { func (b *Binance) AllOrders(symbol, orderID, limit string) ([]QueryOrderData, error) { var resp []QueryOrderData - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, allOrders) + path := b.API.Endpoints.URL + allOrders params := url.Values{} params.Set("symbol", strings.ToUpper(symbol)) @@ -421,7 +421,7 @@ func (b *Binance) AllOrders(symbol, orderID, limit string) ([]QueryOrderData, er func (b *Binance) QueryOrder(symbol, origClientOrderID string, orderID int64) (QueryOrderData, error) { var resp QueryOrderData - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, queryOrder) + path := b.API.Endpoints.URL + queryOrder params := url.Values{} params.Set("symbol", strings.ToUpper(symbol)) @@ -451,7 +451,7 @@ func (b *Binance) GetAccount() (*Account, error) { var resp response - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, accountInfo) + path := b.API.Endpoints.URL + accountInfo params := url.Values{} if err := b.SendAuthHTTPRequest(http.MethodGet, path, params, &resp); err != nil { @@ -503,7 +503,7 @@ func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, re } path = common.EncodeURLValues(path, params) - path += fmt.Sprintf("&signature=%s", hmacSignedStr) + path += "&signature=" + hmacSignedStr interim := json.RawMessage{} @@ -643,7 +643,7 @@ func getCryptocurrencyWithdrawalFee(c currency.Code) float64 { // WithdrawCrypto sends cryptocurrency to the address of your choosing func (b *Binance) WithdrawCrypto(asset, address, addressTag, name, amount string) (string, error) { var resp WithdrawResponse - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, withdraw) + path := b.API.Endpoints.URL + withdraw params := url.Values{} params.Set("asset", asset) @@ -669,7 +669,7 @@ func (b *Binance) WithdrawCrypto(asset, address, addressTag, name, amount string // GetDepositAddressForCurrency retrieves the wallet address for a given currency func (b *Binance) GetDepositAddressForCurrency(currency string) (string, error) { - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, depositAddress) + path := b.API.Endpoints.URL + depositAddress resp := struct { Address string `json:"address"` diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 59203ff1..4f79f15c 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -231,10 +231,9 @@ func (b *Binance) FetchTradablePairs(asset asset.Item) ([]string, error) { for x := range info.Symbols { if info.Symbols[x].Status == "TRADING" { - validCurrencyPairs = append(validCurrencyPairs, fmt.Sprintf("%v%v%v", - info.Symbols[x].BaseAsset, - b.GetPairFormat(asset, false).Delimiter, - info.Symbols[x].QuoteAsset)) + validCurrencyPairs = append(validCurrencyPairs, info.Symbols[x].BaseAsset+ + b.GetPairFormat(asset, false).Delimiter+ + info.Symbols[x].QuoteAsset) } } return validCurrencyPairs, nil diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 2877b2cb..a79811f3 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -948,7 +948,7 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequest(method, path string, params map[ n := b.Requester.GetNonce(true) req := make(map[string]interface{}) - req["request"] = fmt.Sprintf("%s%s", bitfinexAPIVersion, path) + req["request"] = bitfinexAPIVersion + path req["nonce"] = n.String() for key, value := range params { diff --git a/exchanges/bitfinex/bitfinex_types.go b/exchanges/bitfinex/bitfinex_types.go index 23864bb4..7ef5ceb6 100644 --- a/exchanges/bitfinex/bitfinex_types.go +++ b/exchanges/bitfinex/bitfinex_types.go @@ -620,7 +620,7 @@ type WsMarginInfoBase struct { MarginNet float64 } -// WsMarginInfoBase recent funding trades received via websocket +// WsFundingTrade recent funding trades received via websocket type WsFundingTrade struct { ID int64 Symbol string diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 95838ffc..555dedf4 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -2,7 +2,6 @@ package bitfinex import ( "errors" - "fmt" "net/url" "strconv" "strings" @@ -524,21 +523,20 @@ func (b *Bitfinex) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawReque return "", errors.New("no withdrawID returned. Check order status") } - var withdrawalSuccesses string - var withdrawalErrors string - for _, withdrawal := range resp { - if withdrawal.Status == "error" { - withdrawalErrors += fmt.Sprintf("%v ", withdrawal.Message) + var withdrawalSuccesses, withdrawalErrors strings.Builder + for x := range resp { + if resp[x].Status == "error" { + withdrawalErrors.WriteString(resp[x].Message + " ") } - if withdrawal.Status == "success" { - withdrawalSuccesses += fmt.Sprintf("%v,", withdrawal.WithdrawalID) + if resp[x].Status == "success" { + withdrawalSuccesses.WriteString(strconv.FormatInt(resp[x].WithdrawalID, 10) + ",") } } - if len(withdrawalErrors) > 0 { - return withdrawalSuccesses, errors.New(withdrawalErrors) + if withdrawalErrors.Len() > 0 { + return withdrawalSuccesses.String(), errors.New(withdrawalErrors.String()) } - return withdrawalSuccesses, nil + return withdrawalSuccesses.String(), nil } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is submitted diff --git a/exchanges/bitflyer/bitflyer.go b/exchanges/bitflyer/bitflyer.go index 77aad8c5..9c9a520a 100644 --- a/exchanges/bitflyer/bitflyer.go +++ b/exchanges/bitflyer/bitflyer.go @@ -74,8 +74,7 @@ type Bitflyer struct { // analysis system func (b *Bitflyer) GetLatestBlockCA() (ChainAnalysisBlock, error) { var resp ChainAnalysisBlock - path := fmt.Sprintf("%s%s", b.API.Endpoints.URLSecondary, latestBlock) - + path := b.API.Endpoints.URLSecondary + latestBlock return resp, b.SendHTTPRequest(path, &resp) } @@ -83,8 +82,7 @@ func (b *Bitflyer) GetLatestBlockCA() (ChainAnalysisBlock, error) { // analysis system func (b *Bitflyer) GetBlockCA(blockhash string) (ChainAnalysisBlock, error) { var resp ChainAnalysisBlock - path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URLSecondary, blockByBlockHash, blockhash) - + path := b.API.Endpoints.URLSecondary + blockByBlockHash + blockhash return resp, b.SendHTTPRequest(path, &resp) } @@ -92,8 +90,9 @@ func (b *Bitflyer) GetBlockCA(blockhash string) (ChainAnalysisBlock, error) { // analysis system func (b *Bitflyer) GetBlockbyHeightCA(height int64) (ChainAnalysisBlock, error) { var resp ChainAnalysisBlock - path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URLSecondary, blockByBlockHeight, strconv.FormatInt(height, 10)) - + path := b.API.Endpoints.URLSecondary + + blockByBlockHeight + + strconv.FormatInt(height, 10) return resp, b.SendHTTPRequest(path, &resp) } @@ -101,8 +100,7 @@ func (b *Bitflyer) GetBlockbyHeightCA(height int64) (ChainAnalysisBlock, error) // bitflyer chain analysis system func (b *Bitflyer) GetTransactionByHashCA(txHash string) (ChainAnalysisTransaction, error) { var resp ChainAnalysisTransaction - path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URLSecondary, transaction, txHash) - + path := b.API.Endpoints.URLSecondary + transaction + txHash return resp, b.SendHTTPRequest(path, &resp) } @@ -110,7 +108,7 @@ func (b *Bitflyer) GetTransactionByHashCA(txHash string) (ChainAnalysisTransacti // from bitflyer chain analysis system func (b *Bitflyer) GetAddressInfoCA(addressln string) (ChainAnalysisAddress, error) { var resp ChainAnalysisAddress - path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URLSecondary, address, addressln) + path := b.API.Endpoints.URLSecondary + address + addressln return resp, b.SendHTTPRequest(path, &resp) } @@ -118,7 +116,7 @@ func (b *Bitflyer) GetAddressInfoCA(addressln string) (ChainAnalysisAddress, err // GetMarkets returns market information func (b *Bitflyer) GetMarkets() ([]MarketInfo, error) { var resp []MarketInfo - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, pubGetMarkets) + path := b.API.Endpoints.URL + pubGetMarkets return resp, b.SendHTTPRequest(path, &resp) } @@ -139,7 +137,6 @@ func (b *Bitflyer) GetTicker(symbol string) (Ticker, error) { v := url.Values{} v.Set("product_code", symbol) path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, pubGetTicker, v.Encode()) - return resp, b.SendHTTPRequest(path, &resp) } @@ -157,7 +154,7 @@ func (b *Bitflyer) GetExecutionHistory(symbol string) ([]ExecutedTrade, error) { func (b *Bitflyer) GetExchangeStatus() (string, error) { resp := make(map[string]string) - path := fmt.Sprintf("%s%s", b.API.Endpoints.URL, pubGetHealth) + path := b.API.Endpoints.URL + pubGetHealth err := b.SendHTTPRequest(path, &resp) if err != nil { diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go index 3cdd892a..d4fc20fc 100644 --- a/exchanges/bithumb/bithumb.go +++ b/exchanges/bithumb/bithumb.go @@ -74,10 +74,9 @@ func (b *Bithumb) GetTradablePairs() ([]string, error) { // symbol e.g. "btc" func (b *Bithumb) GetTicker(symbol string) (Ticker, error) { var response TickerResponse - path := fmt.Sprintf("%s%s%s", - b.API.Endpoints.URL, - publicTicker, - strings.ToUpper(symbol)) + path := b.API.Endpoints.URL + + publicTicker + + strings.ToUpper(symbol) err := b.SendHTTPRequest(path, &response) if err != nil { return response.Data, err @@ -93,7 +92,7 @@ func (b *Bithumb) GetTicker(symbol string) (Ticker, error) { // GetAllTickers returns all ticker information func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) { var response TickersResponse - path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTicker, "all") + path := b.API.Endpoints.URL + publicTicker + "all" err := b.SendHTTPRequest(path, &response) if err != nil { return nil, err @@ -123,7 +122,7 @@ func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) { // symbol e.g. "btc" func (b *Bithumb) GetOrderBook(symbol string) (Orderbook, error) { response := Orderbook{} - path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicOrderBook, strings.ToUpper(symbol)) + path := b.API.Endpoints.URL + publicOrderBook + strings.ToUpper(symbol) err := b.SendHTTPRequest(path, &response) if err != nil { @@ -142,7 +141,9 @@ func (b *Bithumb) GetOrderBook(symbol string) (Orderbook, error) { // symbol e.g. "btc" func (b *Bithumb) GetTransactionHistory(symbol string) (TransactionHistory, error) { response := TransactionHistory{} - path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTransactionHistory, strings.ToUpper(symbol)) + path := b.API.Endpoints.URL + + publicTransactionHistory + + strings.ToUpper(symbol) err := b.SendHTTPRequest(path, &response) if err != nil { diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 8d08da2b..73dc3c3f 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math" + "strconv" "sync" "time" @@ -423,7 +424,8 @@ func (b *Bithumb) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawReques if withdrawRequest.Currency != currency.KRW { return "", errors.New("only KRW is supported") } - bankDetails := fmt.Sprintf("%v_%v", withdrawRequest.BankCode, withdrawRequest.BankName) + bankDetails := strconv.FormatFloat(withdrawRequest.BankCode, 'f', -1, 64) + + "_" + withdrawRequest.BankName resp, err := b.RequestKRWWithdraw(bankDetails, withdrawRequest.BankAccountNumber, int64(withdrawRequest.Amount)) if err != nil { return "", err diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index a073a923..5a886b4d 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -327,9 +327,8 @@ func (b *Bitmex) GetCurrentNotifications() ([]Notification, error) { // GetOrders returns all the orders, open and closed func (b *Bitmex) GetOrders(params *OrdersRequest) ([]Order, error) { var orders []Order - return orders, b.SendAuthenticatedHTTPRequest(http.MethodGet, - fmt.Sprintf("%v%v", bitmexEndpointOrder, ""), + bitmexEndpointOrder, params, &orders) } diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index b0338ad9..61db8928 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -434,7 +434,7 @@ func (b *Bitmex) GenerateDefaultSubscriptions() { for i := range channels { for j := range allPairs { subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: fmt.Sprintf("%v:%v", channels[i], allPairs[j].String()), + Channel: channels[i] + ":" + allPairs[j].String(), Currency: allPairs[j], }) } @@ -474,7 +474,7 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() { for i := range channels { for j := range contracts { subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: fmt.Sprintf("%v:%v", channels[i], contracts[j].String()), + Channel: channels[i] + ":" + contracts[j].String(), Currency: contracts[j], }) } diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 61b7b5fc..ad254a60 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -681,11 +681,11 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url return errors.New(errCap.Error) } if data, ok := errCap.Reason.(map[string][]string); ok { - var details string - for _, v := range data { - details += strings.Join(v, "") + var details strings.Builder + for x := range data { + details.WriteString(strings.Join(data[x], "")) } - return errors.New(details) + return errors.New(details.String()) } if data, ok := errCap.Reason.(string); ok { diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 8bc00249..65be546a 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -3,7 +3,6 @@ package bitstamp import ( "encoding/json" "errors" - "fmt" "net/http" "strconv" "strings" @@ -128,7 +127,7 @@ func (b *Bitstamp) generateDefaultSubscriptions() { for i := range channels { for j := range enabledCurrencies { subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: fmt.Sprintf("%v%v", channels[i], enabledCurrencies[j].Lower().String()), + Channel: channels[i] + enabledCurrencies[j].Lower().String(), }) } } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 5b74ddf6..fc6fa247 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -430,11 +430,11 @@ func (b *Bitstamp) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.CryptoW return "", err } if len(resp.Error) != 0 { - var details string - for _, v := range resp.Error { - details += strings.Join(v, "") + var details strings.Builder + for x := range resp.Error { + details.WriteString(strings.Join(resp.Error[x], "")) } - return "", errors.New(details) + return "", errors.New(details.String()) } return resp.ID, nil @@ -458,11 +458,11 @@ func (b *Bitstamp) WithdrawFiatFunds(withdrawRequest *exchange.FiatWithdrawReque return "", err } if resp.Status == errStr { - var details string - for _, v := range resp.Reason { - details += strings.Join(v, "") + var details strings.Builder + for x := range resp.Reason { + details.WriteString(strings.Join(resp.Reason[x], "")) } - return "", errors.New(details) + return "", errors.New(details.String()) } return resp.ID, nil @@ -492,11 +492,11 @@ func (b *Bitstamp) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchang return "", err } if resp.Status == errStr { - var details string - for _, v := range resp.Reason { - details += strings.Join(v, "") + var details strings.Builder + for x := range resp.Reason { + details.WriteString(strings.Join(resp.Reason[x], "")) } - return "", errors.New(details) + return "", errors.New(details.String()) } return resp.ID, nil diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 959a3de8..db4848e8 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -233,8 +233,9 @@ func (c *CoinbasePro) FetchTradablePairs(asset asset.Item) ([]string, error) { var products []string for x := range pairs { - products = append(products, fmt.Sprintf("%s%s%s", pairs[x].BaseCurrency, - c.GetPairFormat(asset, false).Delimiter, pairs[x].QuoteCurrency)) + products = append(products, pairs[x].BaseCurrency+ + c.GetPairFormat(asset, false).Delimiter+ + pairs[x].QuoteCurrency) } return products, nil diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index c5dfbb19..a60a2c96 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "time" @@ -377,7 +378,9 @@ func (c *COINUT) wsAuthenticate() error { } timestamp := time.Now().Unix() nonce := c.WebsocketConn.GenerateMessageID(false) - payload := fmt.Sprintf("%v|%v|%v", c.API.Credentials.ClientID, timestamp, nonce) + payload := c.API.Credentials.ClientID + "|" + + strconv.FormatInt(timestamp, 10) + "|" + + strconv.FormatInt(nonce, 10) hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(c.API.Credentials.Key)) loginRequest := struct { Request string `json:"request"` diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 0f76eb90..2c71fafb 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -362,21 +362,21 @@ func (e *Base) SupportsPair(p currency.Pair, enabledPairs bool, assetType asset. // FormatExchangeCurrencies returns a string containing // the exchanges formatted currency pairs func (e *Base) FormatExchangeCurrencies(pairs []currency.Pair, assetType asset.Item) (string, error) { - var currencyItems string + var currencyItems strings.Builder pairFmt := e.GetPairFormat(assetType, true) for x := range pairs { - currencyItems += e.FormatExchangeCurrency(pairs[x], assetType).String() + currencyItems.WriteString(e.FormatExchangeCurrency(pairs[x], assetType).String()) if x == len(pairs)-1 { continue } - currencyItems += pairFmt.Separator + currencyItems.WriteString(pairFmt.Separator) } - if currencyItems == "" { + if currencyItems.Len() == 0 { return "", errors.New("returned empty string") } - return currencyItems, nil + return currencyItems.String(), nil } // FormatExchangeCurrency is a method that formats and returns a currency pair diff --git a/exchanges/exmo/exmo.go b/exchanges/exmo/exmo.go index e4f41b26..0e2dd899 100644 --- a/exchanges/exmo/exmo.go +++ b/exchanges/exmo/exmo.go @@ -210,12 +210,9 @@ func (e *EXMO) GetCryptoDepositAddress() (map[string]string, error) { switch r := result.(type) { case map[string]interface{}: mapString := make(map[string]string) - for key, value := range r { - strValue := fmt.Sprintf("%v", value) - mapString[key] = strValue + mapString[key] = value.(string) } - return mapString, nil default: diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 36bf2cb2..e4c23b06 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -104,7 +104,7 @@ func (g *Gemini) WsSecureSubscribe(dialer *websocket.Dialer, url string) error { return fmt.Errorf("%v sendAuthenticatedHTTPRequest: Unable to JSON request", g.Name) } - endpoint := fmt.Sprintf("%v%v", g.API.Endpoints.WebsocketURL, url) + endpoint := g.API.Endpoints.WebsocketURL + url PayloadBase64 := crypto.Base64Encode(PayloadJSON) hmac := crypto.GetHMAC(crypto.HashSHA512_384, []byte(PayloadBase64), []byte(g.API.Credentials.Secret)) headers := http.Header{} diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index c1cc92b6..3acdf45d 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -222,8 +222,8 @@ func (h *HitBTC) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range symbols { - pairs = append(pairs, fmt.Sprintf("%v%v%v", symbols[x].BaseCurrency, - h.GetPairFormat(asset, false).Delimiter, symbols[x].QuoteCurrency)) + pairs = append(pairs, symbols[x].BaseCurrency+ + h.GetPairFormat(asset, false).Delimiter+symbols[x].QuoteCurrency) } return pairs, nil } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index c2429488..b224c629 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -267,10 +267,9 @@ func (h *HUOBI) FetchTradablePairs(asset asset.Item) ([]string, error) { if symbols[x].State != "online" { continue } - pairs = append(pairs, fmt.Sprintf("%v%v%v", - symbols[x].BaseCurrency, - h.GetPairFormat(asset, false).Delimiter, - symbols[x].QuoteCurrency)) + pairs = append(pairs, symbols[x].BaseCurrency+ + h.GetPairFormat(asset, false).Delimiter+ + symbols[x].QuoteCurrency) } return pairs, nil diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 46e532db..ca487ee8 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -1039,6 +1039,7 @@ func (k *Kraken) WithdrawCancel(c currency.Code, refID string) (bool, error) { return response.Result, GetError(response.Error) } +// GetWebsocketToken returns a websocket token func (k *Kraken) GetWebsocketToken() (string, error) { var response WsTokenResponse if err := k.SendAuthenticatedHTTPRequest(krakenWebsocketToken, url.Values{}, &response); err != nil { diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index 8faa8761..e6659a99 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -849,7 +849,7 @@ func (k *Kraken) GenerateDefaultSubscriptions() { k.Websocket.SubscribeToChannels(subscriptions) } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() +// GenerateAuthenticatedSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (k *Kraken) GenerateAuthenticatedSubscriptions() { var subscriptions []wshandler.WebsocketChannelSubscription for i := range authenticatedChannels { diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index fa820996..71c23b31 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -260,10 +260,9 @@ func (k *Kraken) FetchTradablePairs(asset asset.Item) ([]string, error) { if v.Quote[0] == 'Z' || v.Quote[0] == 'X' { v.Quote = v.Quote[1:] } - products = append(products, fmt.Sprintf("%v%v%v", - v.Base, - k.GetPairFormat(asset, false).Delimiter, - v.Quote)) + products = append(products, v.Base+ + k.GetPairFormat(asset, false).Delimiter+ + v.Quote) } return products, nil } diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index fd2c87f1..afb75558 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -1,7 +1,6 @@ package lakebtc import ( - "fmt" "log" "os" "testing" @@ -474,7 +473,7 @@ func TestWsTickerProcessing(t *testing.T) { func TestGetCurrencyFromChannel(t *testing.T) { curr := currency.NewPair(currency.LTC, currency.BTC) - result := l.getCurrencyFromChannel(fmt.Sprintf("%v%v%v", marketSubstring, curr, globalSubstring)) + result := l.getCurrencyFromChannel(marketSubstring + curr.String() + globalSubstring) if curr != result { t.Errorf("currency result is not equal. Expected %v", curr) } diff --git a/exchanges/lakebtc/lakebtc_websocket.go b/exchanges/lakebtc/lakebtc_websocket.go index 0639ddc3..0bdf0955 100644 --- a/exchanges/lakebtc/lakebtc_websocket.go +++ b/exchanges/lakebtc/lakebtc_websocket.go @@ -76,10 +76,9 @@ func (l *LakeBTC) GenerateDefaultSubscriptions() { for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "" - channel := fmt.Sprintf("%v%v%v", - marketSubstring, - enabledCurrencies[j].Lower(), - globalSubstring) + channel := marketSubstring + + enabledCurrencies[j].Lower().String() + + globalSubstring subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ Channel: channel, diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index 4e97dc89..f84f0728 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -601,11 +601,11 @@ func (l *LocalBitcoins) WalletSend(address string, amount float64, pin int64) er if resp.Data.Message != "Money is being sent" { if len(resp.Error.Errors) != 0 { - var details string - for _, val := range resp.Error.Errors { - details += val + var details strings.Builder + for x := range resp.Error.Errors { + details.WriteString(resp.Error.Errors[x]) } - return errors.New(details) + return errors.New(details.String()) } return errors.New(resp.Data.Message) } diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index f5a80933..eb75893d 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -194,8 +194,9 @@ func (o *OKCoin) FetchTradablePairs(asset asset.Item) ([]string, error) { var pairs []string for x := range prods { - pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].BaseCurrency, - o.GetPairFormat(asset, false).Delimiter, prods[x].QuoteCurrency)) + pairs = append(pairs, prods[x].BaseCurrency+ + o.GetPairFormat(asset, false).Delimiter+ + prods[x].QuoteCurrency) } return pairs, nil diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index 13f95a20..c1ae5fb5 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -670,23 +670,23 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, in // there are less than 25 entries (for whatever reason) // eg Bid:Ask:Bid:Ask:Ask:Ask func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketDataWrapper) int32 { - var checksum string + var checksum strings.Builder for i := 0; i < allowableIterations; i++ { if len(orderbookData.Bids)-1 >= i { - checksum += orderbookData.Bids[i][0].(string) + + checksum.WriteString(orderbookData.Bids[i][0].(string) + delimiterColon + orderbookData.Bids[i][1].(string) + - delimiterColon + delimiterColon) } if len(orderbookData.Asks)-1 >= i { - checksum += orderbookData.Asks[i][0].(string) + + checksum.WriteString(orderbookData.Asks[i][0].(string) + delimiterColon + orderbookData.Asks[i][1].(string) + - delimiterColon + delimiterColon) } } - checksum = strings.TrimSuffix(checksum, delimiterColon) - return int32(crc32.ChecksumIEEE([]byte(checksum))) + checksumStr := strings.TrimSuffix(checksum.String(), delimiterColon) + return int32(crc32.ChecksumIEEE([]byte(checksumStr))) } // CalculateUpdateOrderbookChecksum alternates over the first 25 bid and ask @@ -695,21 +695,21 @@ func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketData // there are less than 25 entries (for whatever reason) // eg Bid:Ask:Bid:Ask:Ask:Ask func (o *OKGroup) CalculateUpdateOrderbookChecksum(orderbookData *orderbook.Base) int32 { - var checksum string + var checksum strings.Builder for i := 0; i < allowableIterations; i++ { if len(orderbookData.Bids)-1 >= i { price := strconv.FormatFloat(orderbookData.Bids[i].Price, 'f', -1, 64) amount := strconv.FormatFloat(orderbookData.Bids[i].Amount, 'f', -1, 64) - checksum += price + delimiterColon + amount + delimiterColon + checksum.WriteString(price + delimiterColon + amount + delimiterColon) } if len(orderbookData.Asks)-1 >= i { price := strconv.FormatFloat(orderbookData.Asks[i].Price, 'f', -1, 64) amount := strconv.FormatFloat(orderbookData.Asks[i].Amount, 'f', -1, 64) - checksum += price + delimiterColon + amount + delimiterColon + checksum.WriteString(price + delimiterColon + amount + delimiterColon) } } - checksum = strings.TrimSuffix(checksum, delimiterColon) - return int32(crc32.ChecksumIEEE([]byte(checksum))) + checksumStr := strings.TrimSuffix(checksum.String(), delimiterColon) + return int32(crc32.ChecksumIEEE([]byte(checksumStr))) } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index b4d31804..bc4466a9 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -377,7 +377,7 @@ func (z *ZB) wsSubmitOrder(pair currency.Pair, amount, price float64, tradeType TradeType: tradeType, No: z.WebsocketConn.GenerateMessageID(true), } - request.Channel = fmt.Sprintf("%v_order", pair.String()) + request.Channel = pair.String() + "_order" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) @@ -405,7 +405,7 @@ func (z *ZB) wsCancelOrder(pair currency.Pair, orderID int64) (*WsCancelOrderRes ID: orderID, No: z.WebsocketConn.GenerateMessageID(true), } - request.Channel = fmt.Sprintf("%v_cancelorder", pair.String()) + request.Channel = pair.String() + "_cancelorder" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) @@ -433,7 +433,7 @@ func (z *ZB) wsGetOrder(pair currency.Pair, orderID int64) (*WsGetOrderResponse, ID: orderID, No: z.WebsocketConn.GenerateMessageID(true), } - request.Channel = fmt.Sprintf("%v_getorder", pair.String()) + request.Channel = pair.String() + "_getorder" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) @@ -462,7 +462,7 @@ func (z *ZB) wsGetOrders(pair currency.Pair, pageIndex, tradeType int64) (*WsGet TradeType: tradeType, No: z.WebsocketConn.GenerateMessageID(true), } - request.Channel = fmt.Sprintf("%v_getorders", pair.String()) + request.Channel = pair.String() + "_getorders" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) @@ -490,7 +490,7 @@ func (z *ZB) wsGetOrdersIgnoreTradeType(pair currency.Pair, pageIndex, pageSize PageSize: pageSize, No: z.WebsocketConn.GenerateMessageID(true), } - request.Channel = fmt.Sprintf("%v_getordersignoretradetype", pair.String()) + request.Channel = pair.String() + "_getordersignoretradetype" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request)